Theft

Connect Four

Theft again; alas.

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

/*-- Constants --*/

/// How many pieces we're trying to connect
#define CONNECT 4

/// The minimum and maximum board dimensions
#define MIN_BOARD_DIMENSION 4
#define MAX_BOARD_WIDTH     9
#define MAX_BOARD_HEIGHT    16

/// The three cell types
#define CELL_EMPTY  '.'
#define CELL_RED    'R'
#define CELL_YELLOW 'Y'

/// The winner conditions
#define WINNER_NONE   0
#define WINNER_RED    1
#define WINNER_YELLOW 2

/// Whose turn is it?
#define TURN_RED    0
#define TURN_YELLOW 1


/*-- Function prototypes --*/

void assert_board_dimension(int dimension, int min, int max);
void initialise_board(void);
void play_game(void);
int  play_turn(int whose_turn);
int  check_winner(void);
int  check_line(int start_row, int start_col, int offset_row, int offset_col);

// provided for you:
bool is_board_full(void);
void print_board(void);


/*-- Global variables --*/

char board[MAX_BOARD_HEIGHT][MAX_BOARD_WIDTH];
int board_width;
int board_height;


/*-- Code --*/

int main(void) {
        printf("Enter board width: ");
        scanf("%d", &board_width);
        assert_board_dimension(board_width, MIN_BOARD_DIMENSION, MAX_BOARD_WIDTH);

        printf("Enter board height: ");
        scanf("%d", &board_height);
        assert_board_dimension(board_height, MIN_BOARD_DIMENSION, MAX_BOARD_HEIGHT);

        initialise_board();
        print_board();
        play_game();

        return 0;
}

/// Make sure the board dimensions we're
/// given fit within the correct bounds
void assert_board_dimension(int dimension, int min, int max) {
        if (dimension < min) {
                printf("Board dimension too small (min %d)\n", min);
                exit(1);
        }

        if (dimension > max) {
                printf("Board dimension too large (max %d)\n", max);
                exit(1);
        }
}

/// Initialise the board to all empty cells
void initialise_board(void) {
        for (int row = 0; row < board_height; row++) {
                for (int col = 0; col < board_width; col++) {
                        board[row][col] = CELL_EMPTY;
                }
        }
}

/// The main game loop
void play_game(void) {
        int whose_turn = TURN_RED;
        int winner;

        // Play a single turn
        do {
                whose_turn = play_turn(whose_turn);
                print_board();
                winner = check_winner();
        } while (winner == WINNER_NONE && !is_board_full());

        // Either we have a winner, or the board is full!
        if (winner == WINNER_NONE) {
                printf("The game is a draw!\n");
        } else if (winner == WINNER_RED) {
                printf("Game over, Red wins!\n");
        } else {
                printf("Game over, Yellow wins!\n");
        }
}

/// Play a single turn!
/// This in sequence:
/// - Reads in the column to insert,
/// - Makes sure it is valid,
/// - Inserts the color into that column,
/// - Switches the turn to the next player.
int play_turn(int whose_turn) {
        if (whose_turn == TURN_RED)	printf("[RED] ");
        else                        printf("[YELLOW] ");
        printf("Choose a column: ");

        int target_col = 0;
        scanf("%d", &target_col);
        target_col--; // user input is 1-indexed
        if (target_col < 0 || target_col >= board_width) {
                printf("Invalid column\n");
                return whose_turn;
        }

        int target_row = board_height - 1;
        while (target_row >= 0 && board[target_row][target_col] != CELL_EMPTY) {
                target_row--;

                if (target_row < 0) {
                        printf("No space in that column!\n");
                        return whose_turn;
                }
        }

        if (whose_turn == TURN_RED) {
                board[target_row][target_col] = CELL_RED;
                return TURN_YELLOW;
        } else {
                board[target_row][target_col] = CELL_YELLOW;
                return TURN_RED;
        }
}

/// Checks if the game has a winner yet!
/// For each position on the board, the
/// loop will check vertical, horizontal,
/// North-East vertical, and
/// North-West vertical for a connection.
int check_winner(void) {
        for (int row = 0; row < board_height; row++) {
                for (int col = 0; col < board_width; col++) {
                        int check;

                        check = check_line(row, col, 1, 0);
                        if (check != WINNER_NONE) return check;

                        check = check_line(row, col, 0, 1);
                        if (check != WINNER_NONE) return check;

                        check = check_line(row, col, 1, 1);
                        if (check != WINNER_NONE) return check;

                        check = check_line(row, col, 1, -1);
                        if (check != WINNER_NONE) return check;
                }
        }

        return WINNER_NONE;
}

/// Checks if a particular line represents
/// a connect 4!
/// It uses a start_row and start_col,
/// and then checks if there are 3 further
/// tokens of the same color, offsetting by
/// offset_row and offset_col each time.
int check_line(int start_row, int start_col, int offset_row, int offset_col) {

        //printf("Debug1: \n%d\n%d\n%d\n%d\n", start_row, start_col, offset_row, offset_col);
        char first_cell = board[start_row][start_col];
        if (first_cell == CELL_EMPTY) return WINNER_NONE;

        int row = start_row + offset_row;
        int col = start_col + offset_col;
        for (int i = 0; i < CONNECT - 1; i++) {
                if (row < 0 || col < 0) return WINNER_NONE;
                if (row >= board_height || col >= board_width) return WINNER_NONE;

                char cell = board[row][col];
                if (cell != first_cell) return WINNER_NONE;

                row += offset_row;
                col += offset_col;
        }

        if (first_cell == CELL_RED) return WINNER_RED;
        else						return WINNER_YELLOW;
}

/// Checks if the board is completely full
/// i.e. there is no free space for a turn.
/// This is the condition that causes a draw
bool is_board_full(void) {
        for (int row = 0; row < board_height; row++) {
                for (int col = 0; col < board_width; col++) {
                        if (board[row][col] == CELL_EMPTY) return false;
                }
        }

        return true;
}

/// Print the board out to the terminal,
/// with numbers at the top to indicate
/// the column indicies.
void print_board(void) {
        printf("\n");

        for (int col = 0; col < board_width; col++) {
                printf("%d ", col + 1);
        }

        printf("\n");

        for (int row = 0; row < board_height; row++) {
                for (int col = 0; col < board_width; col++) {
                        printf("%c ", board[row][col]);
                }

                printf("\n");
        }
}