If you don't use vcpkg, then you can skip this part, but make sure that CMake can find the required dependencies.
vcpkg install sdl2:x64-mingw-dynamic vcpkg install sdl2-image:x64-mingw-dynamic vcpkg install sdl2-ttf:x64-mingw-dynamic vcpkg install sdl2-gfx:x64-mingw-dynamiccmake -Bbuild-directory-to-be-created -DCMAKE_BUILD_TYPE=<Debug/Release> -G "Ninja" -DCMAKE_TOOLCHAIN_FILE=<your-vcpkg-directory>\scripts\buildsystems\vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-mingw-dynamicYou should see CMake creating the specified build directory.
cmake --build build-directory-to-be-createdCmake should build an executable in chess-project under your build directory.
This process should be fairly similar on
LinuxandmacOS, but I haven't tested that yet.
For optimizing the chess bot, a method is needed to efficiently make and undo moves on the board. This is essential because finding the best move requires navigating through millions of positions to locate the optimal one.
The Piece type stores a piece in a single byte, with a unique number assigned to each type of piece.
The PieceType type does the same but ignores the color of the piece, focusing only on its type.
typedef Uint8 Piece;
typedef Uint8 PieceType;For fast moves, it's necessary to store which pieces occupy each square and also which squares
each type of piece occupies. The square array is a 64-element 1D representation of the chessboard,
storing the piece on each square.
The PieceLists maintain where each piece type is located, separated by color.
The currentGameState and gameStateHistory structures store irreversible states for move
retraction.
zobristHash and zobristHistory store the board’s hash, detailed further in the section
on position hashing.
typedef struct Board {
Piece square[64];
Uint8 kingSquare[2];
PieceList Rooks[2];
PieceList Bishops[2];
PieceList Queens[2];
PieceList Knights[2];
PieceList Pawns[2];
Uint64 zobristHash;
Uint64 zobristHistory[10000];
GameState currentGameState;
GameState gameStateHistory[10000];
Uint16 gameStateHistoryCount;
bool isWhitesMove;
Uint64 underAttackMap;
Uint16 fullmoveClock;
} Board;The PieceList type stores pieces of the same type in an unordered list, with each piece’s
position in the list.
The map assists with locating a piece’s position based on its position on the board.
typedef struct PieceList {
Uint8 list[10];
Uint8 count;
Uint8 map[64];
} PieceList;-
The
enpassantFilefield notes which column a pawn moved double in the previous turn, -
with 255 meaning no such move.
-
castleRightsmanages castling rights, and -
capturedPiecestores the last captured piece. -
The
halfmoveClockkeeps track of half moves since the last capture or pawn move.
typedef struct GameState {
Uint8 enpassantFile;
Uint8 castleRights;
PieceType capturedPiece;
Uint8 halfmoveClock;
} GameState;Moves are stored in 16 bits for optimization:
- The first 4 bits represent various flags like pawn promotion or castling.
- The next 6 bits represent the starting position.
- The last 6 bits represent the ending position.
typedef Uint16 Move;void makeMove(Board* board, Move move);– Executes a move and saves the current- state in
gameStateHistory. void unmakeMove(Board* board, Move move);– Reverts a move.
int generateMoves(Board* boardIn, Move* resultIn, bool onlyAttackIn);
generates all possible moves for a given position and stores them in resultIn,
returning the total count. The onlyAttackIn parameter specifies if only attacks should be considered.
The graphical renderer displays the board with pieces and buttons for move retraction, saving states, and activating the chess bot.
The board display includes a static background and dynamic piece layout:
void renderBoard(SDL_Renderer* renderer, int boardSizeNew, int boardOffsetX, int boardOffsetY);– Renders the board to a texture buffer.void renderStatic(SDL_Renderer* renderer);– Loads the pre-rendered board into the screen buffer.void renderPieces(SDL_Renderer* renderer, Piece board[64]);– Displays pieces.
The rest of the UI (e.g., buttons) is managed here:
void recalcUIData();– Updates the size and position of UI elements if the window is resized.
The chess bot uses a minimax search algorithm that explores move sequences and evaluates positions to find the best move.
Efficient move generation and move-making functions are essential for deep searches. Another critical optimization is reducing the number of paths checked to find the best move.
Alpha-beta pruning skips paths that are unlikely to yield good moves.
Zobrist hashing uniquely represents game states using random numbers for each piece’s position and other game conditions. The hash is used for detecting move repetition and storing evaluated positions in a transposition table.
typedef struct Transposition {
Uint64 zobrist;
int eval;
Move bestMove;
Uint8 depth;
Uint8 type;
} Transposition;Move ordering optimizes alpha-beta pruning by examining the most promising moves first.
When the search reaches the desired depth, a basic evaluation assigns values to pieces based on their positions.
int search(int depth, int distFromRoot, int alpha, int beta); is the main function of the minimax algorithm, returning
the current position’s evaluation.
Game management functions are in the gameHandler module, which also includes the main game loop.
Games are saved in PGN format with moves recorded in algebraic notation. Initial positions can be specified using FEN notation.
The application has a graphical interface that the user can interact with via mouse input.
Clicking on a square highlights it, showing possible moves for pieces in blue. The previous move is shown in green. A set of buttons below the board allows move retraction and navigation to the beginning or end of the game.
Click the "Start Bot" button to enable the bot, which will think for 2 seconds per move and continue unless disabled.
Buttons on the right side allow game saving and loading using the FEN and PGN standards.
Trying to load in invalid data might result in crashes or unexpected behaviour.