Games

Arcade Express

Arcade Express — the express train, wheels rendered as complex-log domain colourings

Arcade Express is a modern and lean treatment of classical games — 23 of them, in the browser, with no ads and no subscription. Chess, Go, Sudoku, Tetris, Hashiwokakero, Othello, Peg Solitaire and the rest, each written from scratch rather than wrapped.

Signing in. The game icons drift, flee the cursor and collide; click one and it pops.

Signing in. The game icons drift, flee the cursor and collide; click one and it pops.

Read more >

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");
        }
}