Skip to content

C Chess

A chess board and match server written in C for a university programming course in 2017. Two players connect over TCP, the server holds the board, and every move is validated before it is broadcast back to both terminals.

The interesting part was never the chess. It was that a single-threaded C program had to become a matchmaker: game_room runs one thread per game, and the first player to arrive blocks on a pthread_cond_t until a second one signals it. The board itself is a wchar_t ** so the pieces are literal Unicode chess glyphs in memory, and it is flattened to 64 ASCII characters before going over the wire.

Move validation is where the real work is. is_move_valid checks piece ownership, then dispatches on piece type: rooks and queens through is_rect_clear, bishops and queens through is_diagonal_clear, knights by magnitude, pawns with their first-move exception and their diagonal-only capture. Each rejection has its own code — e-30 for an obstructed path, e-07 for moving a piece that isn't yours — written straight to the offending player's socket.

Playable, eight years later

The board below is that same C code, compiled to WebAssembly with Emscripten. board.c and cchess-server.c are vendored unmodified; the only new code is a shim supplying a send() that captures the engine's error codes instead of writing them to a socket that doesn't exist. When you make an illegal move, the message you see is the 2017 engine's own verdict.

It compiles to 16KB of WebAssembly.

Two caveats, both honest inheritances rather than bugs introduced in the port. The engine validates how pieces move but has no notion of check, checkmate, castling or en passant — turn order and the win condition lived in the socket loop, not in the rules. And create_od_board allocates sizeof(char *) per square instead of sizeof(char), quietly reserving 512 bytes for 64. Harmless, and left in place: the point is to run the code that was written, not the code I would write now.

Loading the engine…