├── .clang-format ├── .github └── workflows │ └── cmake.yml ├── .gitignore ├── CMakeLists.txt ├── README.md ├── include ├── chess.h ├── config.h ├── draw.h ├── engine.h ├── fen.h ├── frontend_state.h ├── openings.h ├── threadpool.h ├── tptable.h ├── ui.h ├── util.h └── zobrist.h ├── res ├── opening_book.bin ├── piece_0_b.png ├── piece_0_w.png ├── piece_1_b.png ├── piece_1_w.png ├── piece_2_b.png ├── piece_2_w.png ├── piece_3_b.png ├── piece_3_w.png ├── piece_4_b.png ├── piece_4_w.png ├── piece_5_b.png └── piece_5_w.png ├── screenshot.png └── src ├── chess.c ├── draw.c ├── engine.c ├── fen.c ├── frontend_state.c ├── main.c ├── openings.c ├── raygui.c ├── tests ├── test_engine.c ├── test_fen.c └── test_main.c ├── threadpool.c ├── tptable.c ├── ui.c └── zobrist.c /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Google 3 | TabWidth: '4' 4 | UseTab: Never 5 | IndentWidth: 4 6 | ColumnLimit: 120 7 | ... 8 | -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | BUILD_TYPE: Release 11 | 12 | jobs: 13 | build-game-windows: 14 | runs-on: windows-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | 19 | - name: Cache deps 20 | id: cache-deps 21 | uses: actions/cache@v2 22 | with: 23 | path: ${{github.workspace}}/build/_deps 24 | key: deps-windows-${{ hashFiles('CMakeLists.txt') }} 25 | 26 | - name: Configure CMake 27 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCHESS_ENABLE_DEBUG_KEYS=1 28 | 29 | - name: Build 30 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 31 | 32 | - uses: actions/upload-artifact@v2 33 | with: 34 | name: game-build-windows 35 | path: | 36 | ${{github.workspace}}/build/${{env.BUILD_TYPE}}/chess.exe 37 | ${{github.workspace}}/build/${{env.BUILD_TYPE}}/res 38 | 39 | build-game-linux: 40 | runs-on: ubuntu-latest 41 | 42 | steps: 43 | - uses: actions/checkout@v2 44 | 45 | - name: Cache deps 46 | id: cache-deps 47 | uses: actions/cache@v2 48 | with: 49 | path: ${{github.workspace}}/build/_deps 50 | key: deps-linux-${{ hashFiles('CMakeLists.txt') }} 51 | 52 | - name: Setup Environment 53 | run: | 54 | sudo apt-get update -qq 55 | sudo apt-get install gcc-multilib 56 | sudo apt-get install -y --no-install-recommends libglfw3 libglfw3-dev libx11-dev libxcursor-dev libxrandr-dev libxinerama-dev libxi-dev libxext-dev libxfixes-dev 57 | 58 | - name: Configure CMake 59 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCHESS_ENABLE_DEBUG_KEYS=1 60 | 61 | - name: Build 62 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 63 | 64 | - uses: actions/upload-artifact@v2 65 | with: 66 | name: game-build-linux 67 | path: | 68 | ${{github.workspace}}/build/chess 69 | ${{github.workspace}}/build/res 70 | 71 | run-tests: 72 | runs-on: windows-latest 73 | 74 | steps: 75 | - uses: actions/checkout@v2 76 | 77 | - name: Cache deps 78 | id: cache-deps 79 | uses: actions/cache@v2 80 | with: 81 | path: ${{github.workspace}}/build/_deps 82 | key: deps-windows-${{ hashFiles('CMakeLists.txt') }} 83 | 84 | - name: Configure CMake 85 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCHESS_TEST=1 86 | 87 | - name: Build 88 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 89 | 90 | - name: Run tests 91 | run: ${{github.workspace}}/build/${{env.BUILD_TYPE}}/chess.exe 92 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .cache 3 | .vscode -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11) 2 | project(chess C) 3 | 4 | set(CMAKE_C_STANDARD 11) 5 | set(OpenGL_GL_PREFERENCE GLVND) 6 | 7 | # Find raylib, download if not found 8 | # find_package(raylib 3.7 QUIET) broken? 9 | if (NOT raylib_FOUND) 10 | include(FetchContent) 11 | 12 | FetchContent_Declare( 13 | raylib 14 | URL https://github.com/raysan5/raylib/archive/bf2ad9df5fdcaa385b2a7f66fd85632eeebbadaa.zip 15 | ) 16 | 17 | FetchContent_GetProperties(raylib) 18 | if (NOT raylib_POPULATED) 19 | set(FETCHCONTENT_QUIET NO) 20 | FetchContent_Populate(raylib) 21 | set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) 22 | add_subdirectory(${raylib_SOURCE_DIR} ${raylib_BINARY_DIR}) 23 | endif() 24 | endif() 25 | 26 | # Find raygui, download if not found 27 | find_package(raygui 2.6 QUIET) 28 | if (NOT raygui_FOUND) 29 | include(FetchContent) 30 | 31 | FetchContent_Declare( 32 | raygui 33 | URL https://github.com/raysan5/raygui/archive/4e2a878e715c4aafa6ad7bd58d851221503c6e60.zip 34 | ) 35 | 36 | FetchContent_GetProperties(raygui) 37 | if (NOT raygui_POPULATED) 38 | set(FETCHCONTENT_QUIET NO) 39 | FetchContent_Populate(raygui) 40 | set(BUILD_RAYGUI_EXAMPLES OFF CACHE BOOL "" FORCE) 41 | add_subdirectory(${raygui_SOURCE_DIR}/projects/CMake ${raygui_BINARY_DIR}) 42 | endif() 43 | endif() 44 | 45 | include_directories(include ${raygui_SOURCE_DIR}/src) 46 | add_executable(chess src/main.c src/chess.c src/draw.c src/engine.c src/zobrist.c src/openings.c src/tptable.c src/raygui.c src/fen.c src/frontend_state.c src/ui.c src/threadpool.c) 47 | target_link_libraries(chess PRIVATE raylib) 48 | 49 | if(MSVC) 50 | target_compile_options(chess PRIVATE /W4 $<$:/arch:AVX2 /GL>) 51 | target_compile_options(raylib PRIVATE $<$:/arch:AVX2 /GL>) 52 | else() 53 | target_compile_options(chess PRIVATE -g -Wall -Wextra -pedantic $<$:-mavx2 -flto>) 54 | target_compile_options(raylib PRIVATE -g $<$:-mavx2 -flto>) 55 | endif() 56 | 57 | if(CHESS_ENABLE_DEBUG_KEYS) 58 | target_compile_definitions(chess PRIVATE CHESS_ENABLE_DEBUG_KEYS) 59 | endif() 60 | 61 | if(CHESS_TEST) 62 | target_compile_definitions(chess PRIVATE CHESS_TEST) 63 | endif() 64 | 65 | add_custom_command(TARGET chess POST_BUILD COMMAND 66 | ${CMAKE_COMMAND} -E copy_directory 67 | ${CMAKE_SOURCE_DIR}/res $/res) 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A chess game & engine 2 | 3 | ## Features 4 | - Computer move generation 5 | - minimax (negamax), alpha-beta pruning & iterative deepening 6 | - Multithreaded search (Lazy SMP) on platforms supporting C11 threads 7 | - Move ordering based on captures and a stored principal variation 8 | - Polyglot BIN format opening book support 9 | - Transposition table optimisations 10 | - Chess GUI implemented using [Raylib](https://github.com/raysan5/raylib) and [Raygui](https://github.com/raysan5/raygui) 11 | - Local player-vs-player and player-vs-computer modes 12 | - Game loading and saving in [FEN (Forsyth–Edwards Notation)](https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation) 13 | - All chess rules are supported except the 50 move rule, repetition and promotion to non-queen pieces 14 | - Supports Linux, Windows, macOS, FreeBSD, probably anything Raylib supports 15 | 16 | ## Screenshot 17 | ![](screenshot.png) 18 | 19 | ## Installation 20 | Binaries are available for [Linux and Windows](https://github.com/camc/chess/releases/latest). 21 | 22 | ## Building 23 | 24 | Required dependencies to build are CMake, a C11 compiler and the [build dependencies of Raylib](https://github.com/raysan5/raylib/wiki/Working-on-GNU-Linux/4eec3af730128bdb5f85553b37a325a853034ae7). 25 | 26 | Build steps on Linux: 27 | ```sh 28 | git clone https://github.com/camc/chess 29 | cd chess 30 | cmake -B build -DCMAKE_BUILD_TYPE=Release 31 | cmake --build build --config Release 32 | # Run: ./build/chess 33 | ``` -------------------------------------------------------------------------------- /include/chess.h: -------------------------------------------------------------------------------- 1 | #ifndef CHESS_H 2 | #define CHESS_H 3 | 4 | #include 5 | #include 6 | 7 | typedef uint64_t ZobristHash; 8 | 9 | // Each chess piece type is assigned a unique value which is used in the board to identify pieces 10 | enum PieceType { 11 | Empty = 0, 12 | King = 1, 13 | Queen = 2, 14 | Rook = 3, 15 | Bishop = 4, 16 | Knight = 5, 17 | Pawn = 6, 18 | }; 19 | 20 | enum Player { WhitePlayer = 0, BlackPlayer = 1 }; 21 | 22 | // Each piece on the board has a type and a player 23 | struct Piece { 24 | enum PieceType type; 25 | enum Player player; 26 | }; 27 | 28 | #ifdef __GNUC__ 29 | #define _PACK( __Declaration__ ) __Declaration__ __attribute__((__packed__)) 30 | #endif 31 | 32 | #ifdef _MSC_VER 33 | #define _PACK( __Declaration__ ) __pragma( pack(push, 1) ) __Declaration__ __pragma( pack(pop)) 34 | #endif 35 | 36 | // Stores representation of a position on the board. 37 | // {file 0, rank 0} is the top left of the board (from whites POV) 38 | // and {file 7, rank 7} is the bottom right (from whites POV) 39 | // The file and rank are signed so that the BoardPos can also be used to 40 | // store directions of movement, e.g. {file - 1, rank 0} would be moving in 41 | // the 'east' direction (from whites POV) 42 | _PACK(struct BoardPos { 43 | int8_t file; 44 | int8_t rank; 45 | }); 46 | 47 | // Structure used to store a move (actually a ply), used by the engine 48 | // when generating moves etc. 49 | struct Move { 50 | struct BoardPos from; 51 | struct BoardPos to; 52 | }; 53 | 54 | // The GameState struct stores all information about an ongoing game that is used 55 | // by the engine. 56 | // It can be initialised to the starting state using init_gamestate() 57 | struct GameState { 58 | struct Piece board[8][8]; // column/file major 2d array 59 | bool white_to_move; // set to true if it is whites move 60 | int8_t enpassant_target_white; // En passant target file for white (-1 if no target) 61 | int8_t enpassant_target_black; // En passant target file for black (-1 if no target) 62 | bool white_castlert_left; // Castling rights, where left is the rook at file=0 63 | bool white_castlert_right; // 64 | bool black_castlert_left; // 65 | bool black_castlert_right; // 66 | struct BoardPos white_king; // White king position 67 | struct BoardPos black_king; // Black king position 68 | bool white_king_in_check; // True if the white king is currently in check 69 | bool black_king_in_check; // True if the black king is currently in check 70 | int move_count; // Number of moves played (actually number of ply) 71 | struct BoardPos piece_list_white[16]; // A list of the positions of all white pieces 72 | struct BoardPos piece_list_black[16]; // A list of the positions of all black pieces 73 | ZobristHash hash; // The zobrist hash of the state 74 | }; 75 | 76 | extern const struct BoardPos NULL_BOARDPOS; 77 | 78 | struct BoardPos BoardPos(int8_t file, int8_t rank); 79 | bool boardpos_eq(struct BoardPos a, struct BoardPos b); 80 | struct BoardPos boardpos_add(struct BoardPos a, struct BoardPos b); 81 | struct Piece Piece(enum PieceType, enum Player); 82 | void put_piece(struct GameState *state, struct Piece piece, struct BoardPos pos); 83 | struct Piece get_piece(struct GameState *state, struct BoardPos pos); 84 | struct GameState *init_gamestate(); 85 | void deinit_gamestate(struct GameState *state); 86 | int get_enpassant_target_file(struct GameState *state, enum Player player); 87 | void unset_enpassant_target_file(struct GameState *state, enum Player attacking_player); 88 | void unset_castlert_left(struct GameState *state, enum Player player); 89 | void unset_castlert_right(struct GameState *state, enum Player player); 90 | void boardpos_to_algn(struct BoardPos pos, char *buf); 91 | int move_to_str(struct GameState *state, struct BoardPos from, struct BoardPos to, char *buf); 92 | struct GameState *copy_gamestate(struct GameState *state); 93 | void set_king_pos(struct GameState *state, enum Player player, struct BoardPos pos); 94 | struct BoardPos get_king_pos(struct GameState *state, enum Player player); 95 | bool is_player_in_check(struct GameState *state, enum Player player); 96 | enum Player other_player(enum Player player); 97 | void change_piece_list_pos(struct GameState *state, enum Player player, struct BoardPos from, struct BoardPos to); 98 | void print_gamestate(struct GameState *state); 99 | void clear_board(struct GameState *state); 100 | void set_player_in_check(struct GameState *state, enum Player player, bool in_check); 101 | void move_piece(struct GameState *state, struct BoardPos from, struct BoardPos to); 102 | 103 | #endif /* CHESS_H */ 104 | -------------------------------------------------------------------------------- /include/config.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H 2 | #define CONFIG_H 3 | 4 | #include 5 | 6 | // Chess board width/height (pixels) 7 | #define BOARD_SIZE 504 8 | #define BOARD_SQUARE_SIZE (int)(BOARD_SIZE / 8) 9 | 10 | // The size of the window displaying the chess board and GUI 11 | #define WINDOW_WIDTH 896 12 | #define WINDOW_HEIGHT 504 13 | 14 | // The size of the transposition table 15 | #define TRANSPOSITION_TABLE_SIZE 1048576 16 | 17 | // The board square background colours, hex RGBA 18 | #define LIGHT_SQUARE_COLOUR 0xfffedbff 19 | #define DARK_SQUARE_COLOUR 0x38a3beff 20 | 21 | // The maximum number of seconds allowed for move generation threads per move. 22 | #define MAX_MOVEGEN_SEARCH_TIME 3 23 | 24 | // The maximum movegen search depth attempted 25 | // Higher values will increase memory usage. 26 | #define MAX_SEARCH_DEPTH 32 27 | 28 | // Defined if debug settings such as keybindings should be enabled 29 | #if !defined(NDEBUG) || defined(CHESS_ENABLE_DEBUG_KEYS) 30 | #define DEBUG_SETTINGS_ENABLED 31 | #endif 32 | 33 | #endif /* CONFIG_H */ 34 | -------------------------------------------------------------------------------- /include/draw.h: -------------------------------------------------------------------------------- 1 | #ifndef DRAW_H 2 | #define DRAW_H 3 | 4 | #include 5 | 6 | void load_textures(); 7 | void unload_textures(); 8 | void draw_board(struct GameState *state); 9 | 10 | #endif /* DRAW_H */ 11 | -------------------------------------------------------------------------------- /include/engine.h: -------------------------------------------------------------------------------- 1 | #ifndef ENGINE_H 2 | #define ENGINE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // The maximum number of legal moves a single piece can have 10 | #define PIECE_LEGAL_MOVES_MAX 27 11 | 12 | // The rough maximum value a position can have as calulated by position_value 13 | // Used for estimating who will win 14 | #define ROUGH_MAX_POSITION_VALUE 4000 15 | 16 | bool is_piece_attacked(struct GameState *state, struct BoardPos attackee_pos, enum Player attacker); 17 | bool is_move_legal(struct GameState *state, struct Move move); 18 | void make_move(struct GameState *state, struct Move move, bool calculate_hash); 19 | bool is_player_checkmated(struct GameState *state, enum Player player); 20 | void generate_move(struct GameState *state, struct ThreadPool *pool, time_t start_time); 21 | bool is_stalemate(struct GameState *state); 22 | int position_value(struct GameState *state); 23 | 24 | #endif /* ENGINE_H */ 25 | -------------------------------------------------------------------------------- /include/fen.h: -------------------------------------------------------------------------------- 1 | #ifndef FEN_H 2 | #define FEN_H 3 | 4 | #include 5 | #include 6 | 7 | struct GameState *fen_to_gamestate(const char *fen); 8 | void gamestate_to_fen(struct GameState *state, char *fen); 9 | 10 | #endif /* FEN_H */ 11 | -------------------------------------------------------------------------------- /include/frontend_state.h: -------------------------------------------------------------------------------- 1 | #ifndef FRONTEND_STATE_H 2 | #define FRONTEND_STATE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // Frontend state stores information needed by the frontend 11 | // to the chess implementation (the UI, input handlers, etc) 12 | struct FrontendState { 13 | struct ThreadPool *threadpool; // Threadpool used for movegen 14 | struct GameState *game_state; // The current gamestate, NULL if there is none 15 | struct BoardPos selected_position; // The current position on the board selected by the user 16 | bool two_player_mode; // Whether the game is two player or vs computer. True if two player. 17 | char *move_log; // The move log content 18 | int move_log_size; // The size of the move_log buffer 19 | int move_log_idx; // The current index in the move_log buffer 20 | int move_log_line_chars; // The number of chars on the current line of the move log 21 | int winner; // The winner of the game or -1 if it is ongoing. 0, 1, 2 for white win, black win and draw 22 | const char *message_box; // The content of a message box to be displayed on screen, NULL if no message. 23 | time_t movegen_started; // The time at which move generation was last started, or 0. 24 | 25 | // Debug settings 26 | bool debug_allow_illegal_moves; // Disables legality checking of human moves 27 | bool debug_copy_on_move; // Instead of moving pieces copy them 28 | }; 29 | 30 | // There is a single static instance of FrontendState for the entire program, 31 | // defined in frontend_state.c 32 | extern struct FrontendState frontend_state; 33 | 34 | #define WINNER_NONE -1 35 | #define WINNER_WHITE 0 36 | #define WINNER_BLACK 1 37 | #define WINNER_DRAW 2 38 | 39 | void frontend_new_game(); 40 | bool frontend_new_game_from_fen(const char *fen); 41 | void log_move(struct BoardPos from, struct BoardPos to); 42 | 43 | #endif /* FRONTEND_STATE_H */ 44 | -------------------------------------------------------------------------------- /include/openings.h: -------------------------------------------------------------------------------- 1 | #ifndef OPENINGS_H 2 | #define OPENINGS_H 3 | 4 | #include 5 | #include 6 | 7 | struct OpeningItem { 8 | ZobristHash hash; 9 | struct Move* moves; 10 | uint8_t moves_count; 11 | }; 12 | 13 | void init_opening_book(); 14 | void deinit_opening_book(); 15 | struct OpeningItem* find_opening_by_hash(ZobristHash hash); 16 | 17 | #endif /* OPENINGS_H */ 18 | -------------------------------------------------------------------------------- /include/threadpool.h: -------------------------------------------------------------------------------- 1 | #ifndef THREADPOOL_H 2 | #define THREADPOOL_H 3 | 4 | #include 5 | #include 6 | 7 | #if !defined(__STDC_NO_THREADS__) && !defined(__STDC_NO_ATOMICS__) 8 | #define HAS_C11_CONCURRENCY 9 | #include 10 | #include 11 | #endif 12 | 13 | #define THREADPOOL_QUEUE_SIZE 256 14 | 15 | #ifndef HAS_C11_CONCURRENCY 16 | typedef struct { 17 | char _; 18 | } mtx_t; 19 | typedef struct { 20 | char _; 21 | } cnd_t; 22 | typedef size_t atomic_size_t; 23 | typedef unsigned short atomic_ushort; 24 | #endif 25 | 26 | typedef bool (*TaskFunc)(void *); 27 | 28 | struct Task { 29 | TaskFunc func; 30 | void *arg; 31 | }; 32 | 33 | // Do not use these members directly. Use the threadpool_ functions. 34 | struct ThreadPool { 35 | mtx_t mutex; // Guards queue, queue_front, queue_back, task_available 36 | cnd_t task_available; 37 | struct Task queue[THREADPOOL_QUEUE_SIZE]; 38 | size_t queue_front; // Index of the next item to be dequeued, or THREADPOOL_QUEUE_SIZE 39 | size_t queue_back; // Index where the next enqueued item should be placed 40 | atomic_size_t thread_count; 41 | }; 42 | 43 | // Atomic ushort counter. 44 | // Used for atomic reference counting. 45 | struct AtomicCounter { 46 | atomic_ushort count; 47 | }; 48 | 49 | struct ThreadPool *threadpool_init(); 50 | void threadpool_enqueue(struct ThreadPool *pool, TaskFunc func, void *arg); 51 | void threadpool_deinit(struct ThreadPool *pool); 52 | struct AtomicCounter *acnt_init(unsigned short val); 53 | bool acnt_dec(struct AtomicCounter *counter); 54 | 55 | #endif /* THREADPOOL_H */ -------------------------------------------------------------------------------- /include/tptable.h: -------------------------------------------------------------------------------- 1 | #ifndef TPTABLE_H 2 | #define TPTABLE_H 3 | 4 | #include 5 | #include 6 | 7 | // For entries with an evaluation value, states whether the value is an upper bound, lower bound, or an exact value. 8 | enum EntryType { EntryTypeExact, EntryTypeUpper, EntryTypeLower }; 9 | 10 | // An entry in the transposition table 11 | struct TranspositionEntry { 12 | ZobristHash hash; 13 | struct Move best_move; // May be absent (NULL_BOARDPOS as the `from` position) 14 | char depth; 15 | int value; // May be absent (set to 0, when depth = 0) 16 | enum EntryType type; 17 | }; 18 | 19 | struct TranspositionEntry tptable_get(ZobristHash hash); 20 | void tptable_put(struct TranspositionEntry entry); 21 | void tptable_clear(); 22 | void tptable_init(); 23 | void tptable_deinit(); 24 | void tptable_set_protected_hash(ZobristHash hash); 25 | 26 | #endif /* TPTABLE_H */ 27 | -------------------------------------------------------------------------------- /include/ui.h: -------------------------------------------------------------------------------- 1 | #ifndef UI_H 2 | #define UI_H 3 | 4 | void draw_ui(); 5 | 6 | #endif /* UI_H */ 7 | -------------------------------------------------------------------------------- /include/util.h: -------------------------------------------------------------------------------- 1 | #ifndef UTIL_H 2 | #define UTIL_H 3 | 4 | #define MIN(a, b) ((a) > (b) ? (b) : (a)) 5 | #define MAX(a, b) ((a) < (b) ? (b) : (a)) 6 | 7 | #endif /* UTIL_H */ -------------------------------------------------------------------------------- /include/zobrist.h: -------------------------------------------------------------------------------- 1 | #ifndef ZOBRIST_H 2 | #define ZOBRIST_H 3 | 4 | #include 5 | 6 | ZobristHash hash_state(struct GameState *state); 7 | 8 | #endif /* ZOBRIST_H */ 9 | -------------------------------------------------------------------------------- /res/opening_book.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camc/chess/0a18e1d4a4c85806f9a7a972ff15a16fe14198d7/res/opening_book.bin -------------------------------------------------------------------------------- /res/piece_0_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camc/chess/0a18e1d4a4c85806f9a7a972ff15a16fe14198d7/res/piece_0_b.png -------------------------------------------------------------------------------- /res/piece_0_w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camc/chess/0a18e1d4a4c85806f9a7a972ff15a16fe14198d7/res/piece_0_w.png -------------------------------------------------------------------------------- /res/piece_1_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camc/chess/0a18e1d4a4c85806f9a7a972ff15a16fe14198d7/res/piece_1_b.png -------------------------------------------------------------------------------- /res/piece_1_w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camc/chess/0a18e1d4a4c85806f9a7a972ff15a16fe14198d7/res/piece_1_w.png -------------------------------------------------------------------------------- /res/piece_2_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camc/chess/0a18e1d4a4c85806f9a7a972ff15a16fe14198d7/res/piece_2_b.png -------------------------------------------------------------------------------- /res/piece_2_w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camc/chess/0a18e1d4a4c85806f9a7a972ff15a16fe14198d7/res/piece_2_w.png -------------------------------------------------------------------------------- /res/piece_3_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camc/chess/0a18e1d4a4c85806f9a7a972ff15a16fe14198d7/res/piece_3_b.png -------------------------------------------------------------------------------- /res/piece_3_w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camc/chess/0a18e1d4a4c85806f9a7a972ff15a16fe14198d7/res/piece_3_w.png -------------------------------------------------------------------------------- /res/piece_4_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camc/chess/0a18e1d4a4c85806f9a7a972ff15a16fe14198d7/res/piece_4_b.png -------------------------------------------------------------------------------- /res/piece_4_w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camc/chess/0a18e1d4a4c85806f9a7a972ff15a16fe14198d7/res/piece_4_w.png -------------------------------------------------------------------------------- /res/piece_5_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camc/chess/0a18e1d4a4c85806f9a7a972ff15a16fe14198d7/res/piece_5_b.png -------------------------------------------------------------------------------- /res/piece_5_w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camc/chess/0a18e1d4a4c85806f9a7a972ff15a16fe14198d7/res/piece_5_w.png -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/camc/chess/0a18e1d4a4c85806f9a7a972ff15a16fe14198d7/screenshot.png -------------------------------------------------------------------------------- /src/chess.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | // A boardpos signifiying 'no position' 9 | // For example, it is returned by the engines move generator 10 | // as the `from` of a Move struct when there are no legal moves 11 | const struct BoardPos NULL_BOARDPOS = {0xf, 0xf}; 12 | 13 | // BoardPos constructor 14 | struct BoardPos BoardPos(int8_t file, int8_t rank) { return (struct BoardPos){file, rank}; } 15 | 16 | // Check if two BoardPos are equal 17 | bool boardpos_eq(struct BoardPos a, struct BoardPos b) { 18 | assert(sizeof(struct BoardPos) == 2); 19 | return memcmp(&a, &b, 2) == 0; 20 | } 21 | 22 | // Add two BoardPos, returning NULL_BOARDPOS if the result is outside the board 23 | struct BoardPos boardpos_add(struct BoardPos a, struct BoardPos b) { 24 | struct BoardPos r = {a.file + b.file, a.rank + b.rank}; 25 | return (r.file > 7 || r.rank > 7 || r.file < 0 || r.rank < 0) ? NULL_BOARDPOS : r; 26 | } 27 | 28 | // Piece constructor 29 | struct Piece Piece(enum PieceType type, enum Player player) { 30 | struct Piece p = {type, player}; 31 | return p; 32 | } 33 | 34 | // Put a piece onto the board 35 | // `pos` must be a valid position 36 | void put_piece(struct GameState *state, struct Piece piece, struct BoardPos pos) { 37 | assert(pos.file >= 0 && pos.file < 8 && pos.rank >= 0 && pos.rank < 8); 38 | state->board[pos.file][pos.rank] = piece; 39 | } 40 | 41 | // Get a piece from the board 42 | // `pos` must be a valid position 43 | struct Piece get_piece(struct GameState *state, struct BoardPos pos) { 44 | assert(pos.file >= 0 && pos.file < 8 && pos.rank >= 0 && pos.rank < 8); 45 | return state->board[pos.file][pos.rank]; 46 | } 47 | 48 | // Returns the enpassant target file for a player, or -1 if there is no target 49 | int get_enpassant_target_file(struct GameState *state, enum Player player) { 50 | if (player == WhitePlayer) { 51 | return state->enpassant_target_white; 52 | } else { 53 | return state->enpassant_target_black; 54 | } 55 | } 56 | 57 | // Unset an en passant target file for the player 58 | void unset_enpassant_target_file(struct GameState *state, enum Player attacking_player) { 59 | if (attacking_player == WhitePlayer) { 60 | state->enpassant_target_white = -1; 61 | } else { 62 | state->enpassant_target_black = -1; 63 | } 64 | } 65 | 66 | // Unset the left side castling right for a player 67 | void unset_castlert_left(struct GameState *state, enum Player player) { 68 | if (player == WhitePlayer) 69 | state->white_castlert_left = false; 70 | else 71 | state->black_castlert_left = false; 72 | } 73 | 74 | // Unset the right side castling right for a player 75 | void unset_castlert_right(struct GameState *state, enum Player player) { 76 | if (player == WhitePlayer) 77 | state->white_castlert_right = false; 78 | else 79 | state->black_castlert_right = false; 80 | } 81 | 82 | // Removes all pieces from the board, also removing the stored positions 83 | // of the kings 84 | void clear_board(struct GameState *state) { 85 | memset(state->board, 0, sizeof(state->board)); 86 | memset(state->piece_list_white, NULL_BOARDPOS.file, sizeof(state->piece_list_white)); 87 | memset(state->piece_list_black, NULL_BOARDPOS.file, sizeof(state->piece_list_black)); 88 | state->white_king = NULL_BOARDPOS; 89 | state->black_king = NULL_BOARDPOS; 90 | } 91 | 92 | // Constructs a new gamestate representing the start of a default chess game 93 | // Return value must be freed via deinit_gamestate() 94 | struct GameState *init_gamestate() { 95 | struct GameState *state = malloc(sizeof(*state)); 96 | clear_board(state); 97 | state->white_to_move = true; 98 | state->enpassant_target_white = -1; 99 | state->enpassant_target_black = -1; 100 | state->white_castlert_left = true; 101 | state->white_castlert_right = true; 102 | state->black_castlert_left = true; 103 | state->black_castlert_right = true; 104 | state->white_king = BoardPos(4, 7); 105 | state->black_king = BoardPos(4, 0); 106 | state->white_king_in_check = false; 107 | state->black_king_in_check = false; 108 | state->move_count = 0; 109 | 110 | // Order of pieces on the initial chess board, excluding pawns 111 | static const enum PieceType PIECES_ORDER[8] = {Rook, Knight, Bishop, Queen, King, Bishop, Knight, Rook}; 112 | 113 | for (int i = 0; i < 8; i++) { 114 | // Place pawns 115 | put_piece(state, Piece(Pawn, BlackPlayer), BoardPos(i, 1)); 116 | put_piece(state, Piece(Pawn, WhitePlayer), BoardPos(i, 6)); 117 | 118 | // Place other pieces 119 | put_piece(state, Piece(PIECES_ORDER[i], BlackPlayer), BoardPos(i, 0)); 120 | put_piece(state, Piece(PIECES_ORDER[i], WhitePlayer), BoardPos(i, 7)); 121 | 122 | // Add pieces to piece list 123 | // Pawns are stored at the end of the list as they will likely be lost first 124 | state->piece_list_white[i + 8] = BoardPos(i, 6); 125 | state->piece_list_black[i + 8] = BoardPos(i, 1); 126 | // Other pieces 127 | state->piece_list_white[i] = BoardPos(i, 7); 128 | state->piece_list_black[i] = BoardPos(i, 0); 129 | } 130 | 131 | state->hash = hash_state(state); 132 | return state; 133 | } 134 | 135 | // Deallocates the GameState 136 | void deinit_gamestate(struct GameState *state) { free(state); } 137 | 138 | // Returns a copy of the gamestate, must be deallocated 139 | struct GameState *copy_gamestate(struct GameState *state) { 140 | struct GameState *new = malloc(sizeof(*state)); 141 | memcpy(new, state, sizeof(*state)); 142 | return new; 143 | } 144 | 145 | // Sets the stored king position for a player 146 | void set_king_pos(struct GameState *state, enum Player player, struct BoardPos pos) { 147 | player == WhitePlayer ? (state->white_king = pos) : (state->black_king = pos); 148 | } 149 | 150 | // Gets the stored king position for a player 151 | struct BoardPos get_king_pos(struct GameState *state, enum Player player) { 152 | return player == WhitePlayer ? state->white_king : state->black_king; 153 | } 154 | 155 | // Checks if a player's king is in check 156 | bool is_player_in_check(struct GameState *state, enum Player player) { 157 | return player == WhitePlayer ? state->white_king_in_check : state->black_king_in_check; 158 | } 159 | 160 | // Set a player's king in check status 161 | void set_player_in_check(struct GameState *state, enum Player player, bool in_check) { 162 | player == WhitePlayer ? (state->white_king_in_check = in_check) : (state->black_king_in_check = in_check); 163 | } 164 | 165 | // Returns the other player, i.e. white if `player` is black and black if `player` is white 166 | enum Player other_player(enum Player player) { return player == WhitePlayer ? BlackPlayer : WhitePlayer; } 167 | 168 | // Swaps a position in the piece list with another position for a player 169 | void change_piece_list_pos(struct GameState *state, enum Player player, struct BoardPos from, struct BoardPos to) { 170 | struct BoardPos *piece_list = player == WhitePlayer ? state->piece_list_white : state->piece_list_black; 171 | for (int i = 0; i < 16; i++) { 172 | if (boardpos_eq(from, piece_list[i])) { 173 | piece_list[i] = to; 174 | } 175 | } 176 | } 177 | 178 | // Prints out the state of the board to stdout, used for debugging 179 | static const char *PIECE_NAMES[7] = {"Empty", "King", "Queen", "Rook", "Bishop", "Knight", "Pawn"}; 180 | void print_gamestate(struct GameState *state) { 181 | printf("GameState {\n"); 182 | for (int file = 0; file < 8; file++) { 183 | for (int rank = 0; rank < 8; rank++) { 184 | struct Piece p = get_piece(state, BoardPos(file, rank)); 185 | printf("\t%d, %d : %s %s\n", file, rank, (p.player == WhitePlayer ? "White" : "Black"), 186 | PIECE_NAMES[p.type]); 187 | } 188 | } 189 | printf("}\n"); 190 | } 191 | 192 | // Convert a boardpos to algrebraic notation 193 | // https://en.wikipedia.org/wiki/Algebraic_notation_(chess) 194 | // `buf` must be at least 2 bytes 195 | void boardpos_to_algn(struct BoardPos pos, char *buf) { 196 | buf[0] = pos.file + 'a'; 197 | buf[1] = 8 - pos.rank + '0'; 198 | } 199 | 200 | // Convert a piece to its algebraic notation character (returns \0 if the piece 201 | // is a pawn or empty) 202 | char piece_to_algn(struct Piece piece) { 203 | static const char PIECES[7] = {'\0', 'K', 'Q', 'R', 'B', 'N', '\0'}; 204 | return PIECES[piece.type]; 205 | } 206 | 207 | // Converts a move to a string (close but not strict algebraic notation, doesnt consider if in 208 | // check etc). 209 | // `buf` must be at least 5 bytes. 210 | // Returns the number of bytes written. 211 | int move_to_str(struct GameState *state, struct BoardPos from, struct BoardPos to, char *buf) { 212 | struct Piece from_piece = get_piece(state, from); 213 | 214 | // Castling moves have a special format. 215 | if (from_piece.type == King && abs(from.file - to.file) == 2) { 216 | if (to.file == 2) { 217 | // Queenside castle. 218 | memcpy(buf, "0-0-0", 5); 219 | return 5; 220 | } else { 221 | // Kingside castle. 222 | memcpy(buf, "0-0", 3); 223 | return 3; 224 | } 225 | 226 | } else { 227 | // Add the from piece's character first. 228 | buf[0] = piece_to_algn(from_piece); 229 | 230 | // The pawn does not have a character in algebraic notation, the pawn's file is used instead. 231 | // So in the case of a pawn \0 is added at buf[0]. 232 | char *to_buf = buf[0] == '\0' ? buf : buf + 1; 233 | struct Piece to_piece = get_piece(state, to); 234 | 235 | // Capture moves have an 'x' added. 236 | if (to_piece.type != Empty) { 237 | // to_buf == buf when the from piece is a pawn. 238 | if (to_buf == buf) { 239 | boardpos_to_algn(from, to_buf); 240 | to_buf++; 241 | } 242 | 243 | *to_buf++ = 'x'; 244 | } 245 | 246 | // Add the destination square. 247 | boardpos_to_algn(to, to_buf); 248 | 249 | // The number of bytes written is the difference between to_buf and buf (which depends on if the move was a 250 | // capture and if it was a pawn move) + the two bytes written for the destination square. 251 | return to_buf - buf + 2; 252 | } 253 | } 254 | 255 | // Moves the piece at `from` to `to`. Simply replaces the piece at `to` with the 256 | // piece at `from`, then puts Empty at `from`. Does not update other state (e.g. 257 | // en passant), handle castling or check legality. 258 | void move_piece(struct GameState *state, struct BoardPos from, struct BoardPos to) { 259 | put_piece(state, get_piece(state, from), to); 260 | put_piece(state, Piece(Empty, WhitePlayer), from); 261 | } -------------------------------------------------------------------------------- /src/draw.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | // Stores the textures used to draw chess pieces 7 | // Must be initialised before use by calling load_textures() 8 | static Texture2D white_piece_textures[6] = {0}; 9 | static Texture2D black_piece_textures[6] = {0}; 10 | 11 | // Load the piece textures from disk (stored in $PWD/res/) into VRAM 12 | // Texture info stored in a static array, resizing the images if needed 13 | // Textures must be unloaded from VRAM with unload_textures() 14 | void load_textures() { 15 | for (int i = 0; i < 6; i += 1) { 16 | char filename[18]; 17 | sprintf(filename, "res/piece_%d_w.png", i); 18 | Image piece_image = LoadImage(filename); 19 | ImageResize(&piece_image, BOARD_SQUARE_SIZE, BOARD_SQUARE_SIZE); 20 | white_piece_textures[i] = LoadTextureFromImage(piece_image); 21 | UnloadImage(piece_image); 22 | 23 | sprintf(filename, "res/piece_%d_b.png", i); 24 | piece_image = LoadImage(filename); 25 | ImageResize(&piece_image, BOARD_SQUARE_SIZE, BOARD_SQUARE_SIZE); 26 | black_piece_textures[i] = LoadTextureFromImage(piece_image); 27 | UnloadImage(piece_image); 28 | } 29 | } 30 | 31 | // Unload textures which were loaded with `load_textures` from VRAM 32 | void unload_textures() { 33 | for (int i = 0; i < 6; i++) { 34 | UnloadTexture(white_piece_textures[i]); 35 | UnloadTexture(black_piece_textures[i]); 36 | } 37 | } 38 | 39 | // Draws a chess piece at a specified square position 40 | // Piece textures must be loaded prior to call via load_textures() 41 | // Should be called within BeginDrawing/EndDrawing 42 | static void draw_piece(struct Piece piece, struct BoardPos pos) { 43 | if (piece.type == Empty) return; 44 | Texture2D texture = (piece.player == WhitePlayer ? white_piece_textures : black_piece_textures)[piece.type - 1]; 45 | DrawTexture(texture, BOARD_SQUARE_SIZE * pos.file, BOARD_SQUARE_SIZE * pos.rank, WHITE); 46 | } 47 | 48 | // Draws a chess board from a GameState, including the pieces and the board background 49 | // Should be called within BeginDrawing/EndDrawing 50 | void draw_board(struct GameState *state) { 51 | for (int file = 0; file < 8; file++) { 52 | for (int rank = 0; rank < 8; rank++) { 53 | // Draw the square background 54 | Color square_colour = GetColor((file + rank) % 2 == 0 ? LIGHT_SQUARE_COLOUR : DARK_SQUARE_COLOUR); 55 | DrawRectangle(file * BOARD_SQUARE_SIZE, rank * BOARD_SQUARE_SIZE, BOARD_SQUARE_SIZE, BOARD_SQUARE_SIZE, 56 | square_colour); 57 | 58 | // Draw the piece 59 | draw_piece(get_piece(state, BoardPos(file, rank)), BoardPos(file, rank)); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/engine.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | // clang-format off 17 | // A list of all move directions white piece can make in (x,y), indexed by the (piece id - 1) 18 | // where {0xf, 0xf} is the null boardpos (no direction) 19 | // The magnitude is also stored e.g. for knight 2 is used to represent the long part of the 'L-shape' 20 | static const struct BoardPos PIECE_MOVE_DIRECTIONS[6][8] = { 21 | {{0, 1}, {1, 1}, {1, 0}, {0, -1}, {-1, -1}, {-1, 0}, {-1, 1}, {1, -1}}, // Queen 22 | {{0, 1}, {1, 1}, {1, 0}, {0, -1}, {-1, -1}, {-1, 0}, {-1, 1}, {1, -1}}, // King 23 | {{0, 1}, {0, -1}, {-1, 0}, {1, 0}, {0xf, 0xf}, {0xf, 0xf}, {0xf, 0xf}, {0xf, 0xf}}, // Rook 24 | {{1, 1}, {-1, -1}, {1, -1}, {-1, 1}, {0xf, 0xf}, {0xf, 0xf}, {0xf, 0xf}, {0xf, 0xf}}, // Bishop 25 | {{2, 1}, {2, -1}, {-2, 1}, {-2, -1}, {1, 2}, {-1, 2}, {1, -2}, {-1, -2}}, // Knight 26 | {{1, -1}, {-1, -1}, {0, -1}, {0, -2}, {0xf, 0xf}, {0xf, 0xf}, {0xf, 0xf}, {0xf, 0xf}} // Pawn 27 | }; 28 | // clang-format on 29 | 30 | // The starting positions for the white and black rooks 31 | const struct BoardPos ROOK_STARTING_POSITIONS_LEFT[2] = {{0, 7}, {0, 0}}; 32 | const struct BoardPos ROOK_STARTING_POSITIONS_RIGHT[2] = {{7, 7}, {7, 0}}; 33 | 34 | // The starting positions for the white and black kings 35 | const struct BoardPos KING_STARTING_POSITIONS[2] = {{4, 7}, {4, 0}}; 36 | 37 | // Writes an array of the legal move destinations the piece at `initial` can make to `moves_dest`. 38 | // `moves_dest` must be an array of length at least `PIECE_LEGAL_MOVES_MAX` (defined in engine.h). 39 | // Returns the number of legal moves. 40 | static unsigned int legal_moves_from_pos(struct GameState *state, struct BoardPos initial, 41 | struct BoardPos *moves_dest) { 42 | struct Piece piece = get_piece(state, initial); 43 | unsigned int moves_idx = 0; 44 | 45 | // Each piece has different patterns which they are allowed to move in. 46 | // Every move possibility will be checked for the piece type to get all the legal moves. 47 | switch (piece.type) { 48 | case King: 49 | for (int i = 0; i < 8; i++) { 50 | // Check each possible move direction to see if it is legal. 51 | struct BoardPos direction = PIECE_MOVE_DIRECTIONS[piece.type - 1][i]; 52 | struct BoardPos check = boardpos_add(initial, direction); 53 | if (!boardpos_eq(check, NULL_BOARDPOS)) { 54 | if (is_move_legal(state, (struct Move){initial, check})) { 55 | moves_dest[moves_idx++] = check; 56 | } 57 | } 58 | } 59 | 60 | // Check each possible castling move if the king is at its starting position. 61 | if (boardpos_eq(initial, KING_STARTING_POSITIONS[piece.player == WhitePlayer ? 0 : 1])) { 62 | static const struct BoardPos castling_directions[2] = {{2, 0}, {-2, 0}}; 63 | for (int i = 0; i < 2; i++) { 64 | struct BoardPos check = boardpos_add(initial, castling_directions[i]); 65 | if (is_move_legal(state, (struct Move){initial, check})) { 66 | moves_dest[moves_idx++] = check; 67 | } 68 | } 69 | } 70 | 71 | break; 72 | 73 | case Queen: 74 | case Rook: 75 | case Bishop: 76 | for (int i = 0; i < 8 && !boardpos_eq(PIECE_MOVE_DIRECTIONS[piece.type - 1][i], NULL_BOARDPOS); i++) { 77 | // Check each possible move direction for legality. 78 | struct BoardPos check = boardpos_add(initial, PIECE_MOVE_DIRECTIONS[piece.type - 1][i]); 79 | 80 | // The queen, rook and bishop can move multiple times in the same direction. 81 | while (!boardpos_eq(check, NULL_BOARDPOS)) { 82 | if (is_move_legal(state, (struct Move){initial, check})) { 83 | moves_dest[moves_idx++] = check; 84 | } 85 | 86 | check = boardpos_add(check, PIECE_MOVE_DIRECTIONS[piece.type - 1][i]); 87 | } 88 | } 89 | break; 90 | 91 | case Knight: 92 | for (int i = 0; i < 8 && !boardpos_eq(PIECE_MOVE_DIRECTIONS[piece.type - 1][i], NULL_BOARDPOS); i++) { 93 | // Check each possible move direction for legality. 94 | struct BoardPos direction = PIECE_MOVE_DIRECTIONS[piece.type - 1][i]; 95 | struct BoardPos check = boardpos_add(initial, direction); 96 | if (!boardpos_eq(check, NULL_BOARDPOS)) { 97 | if (is_move_legal(state, (struct Move){initial, check})) { 98 | moves_dest[moves_idx++] = check; 99 | } 100 | } 101 | } 102 | break; 103 | 104 | case Pawn: 105 | for (int i = 0; i < 8 && !boardpos_eq(PIECE_MOVE_DIRECTIONS[piece.type - 1][i], NULL_BOARDPOS); i++) { 106 | // Check each possible move direction for legality. 107 | struct BoardPos direction = PIECE_MOVE_DIRECTIONS[piece.type - 1][i]; 108 | 109 | // If it is black moving the pawns move in the opposite direction. 110 | if (piece.player == BlackPlayer) { 111 | direction.file *= -1; 112 | direction.rank *= -1; 113 | } 114 | 115 | struct BoardPos check = boardpos_add(initial, direction); 116 | if (!boardpos_eq(check, NULL_BOARDPOS)) { 117 | if (is_move_legal(state, (struct Move){initial, check})) { 118 | moves_dest[moves_idx++] = check; 119 | } 120 | } 121 | } 122 | break; 123 | 124 | default: 125 | return 0; 126 | } 127 | 128 | return moves_idx; 129 | } 130 | 131 | // Writes a list of all the legal moves for a player, ordered using a heuristic to place the better moves first. 132 | // The list must be deallocated. 133 | // Returns the number of moves in the list. 134 | static unsigned int all_legal_moves_ordered(struct GameState *state, enum Player player, struct Move **moves_out) { 135 | // Captures and other moves will be collected separately, as captures are likely to be better moves. 136 | int moves_size = 50; 137 | int moves_idx = 0; 138 | int captures_size = 9; 139 | int captures_idx = 0; 140 | struct Move *moves = malloc(sizeof(struct Move) * 50); 141 | struct Move *captures = malloc(sizeof(struct Move) * 9); 142 | 143 | // If there is a principal variation stored in the transposition table for this position, place that move first as 144 | // it is known to be the best. 145 | struct TranspositionEntry tp_entry = tptable_get(state->hash); 146 | bool has_pvn = tp_entry.depth != 0 && !boardpos_eq(tp_entry.best_move.from, NULL_BOARDPOS); 147 | bool waiting_for_pvn = has_pvn; 148 | 149 | // Get all legal moves for each piece. 150 | struct BoardPos *piece_list = player == WhitePlayer ? state->piece_list_white : state->piece_list_black; 151 | for (int i = 0; i < 16; i++) { 152 | struct BoardPos from = piece_list[i]; 153 | if (boardpos_eq(from, NULL_BOARDPOS)) continue; 154 | struct Piece from_piece = get_piece(state, from); 155 | 156 | // If there is insufficent space in the arrays extend them. 157 | if (moves_size - moves_idx < 27) { 158 | moves = realloc(moves, sizeof(struct Move) * moves_size * 2); 159 | moves_size *= 2; 160 | } 161 | 162 | if (captures_size - captures_idx < 9) { 163 | captures = realloc(captures, sizeof(struct Move) * captures_size * 2); 164 | captures_size *= 2; 165 | } 166 | 167 | // Get all the legal moves for this piece. 168 | struct BoardPos legal_moves[PIECE_LEGAL_MOVES_MAX]; 169 | int move_count = legal_moves_from_pos(state, from, legal_moves); 170 | 171 | // Add each move to either the captures array or the other moves list. 172 | for (int m = 0; m < move_count; m++) { 173 | // If this move is the principal variation then skip, as the principal variation will be added to the start. 174 | if (waiting_for_pvn && boardpos_eq(tp_entry.best_move.from, from) && 175 | boardpos_eq(tp_entry.best_move.to, legal_moves[m])) { 176 | waiting_for_pvn = false; 177 | continue; 178 | } 179 | 180 | // Check if the move is a capture. 181 | struct Piece to_piece = get_piece(state, legal_moves[m]); 182 | if ((to_piece.type != Empty && to_piece.player != player) || 183 | (from_piece.type == Pawn && from.file != legal_moves[m].file)) { 184 | captures[captures_idx++] = (struct Move){from, legal_moves[m]}; 185 | } else { 186 | moves[moves_idx++] = (struct Move){from, legal_moves[m]}; 187 | } 188 | } 189 | } 190 | 191 | size_t move_count = moves_idx + captures_idx + has_pvn; 192 | 193 | // Combine the captures and moves into one array, placing the captures first. 194 | struct Move *combined_moves = malloc(sizeof(struct Move) * move_count); 195 | int combined_idx = 0; 196 | 197 | // If there is a principal variation place that first. 198 | if (has_pvn) { 199 | combined_moves[combined_idx++] = tp_entry.best_move; 200 | } 201 | 202 | // Copy the captures first. 203 | memcpy(&combined_moves[combined_idx], captures, captures_idx * sizeof(struct Move)); 204 | free(captures); 205 | combined_idx += captures_idx; 206 | 207 | // Followed by the other moves. 208 | memcpy(&combined_moves[combined_idx], moves, moves_idx * sizeof(struct Move)); 209 | free(moves); 210 | combined_idx += moves_idx; 211 | 212 | *moves_out = combined_moves; 213 | return move_count; 214 | } 215 | 216 | // Returns a value represeting how good a chess position is for white. 217 | // Checkmate & stalemate are not considered - the function assumes the game is ongoing. 218 | // Positive values indicate that the position is better for white, negative values indicate that the position is better 219 | // for black. 220 | int position_value(struct GameState *state) { 221 | int value = 0; 222 | 223 | // Being in check is bad, the enemy being in check is good. 224 | if (is_player_in_check(state, WhitePlayer)) { 225 | value -= 30; 226 | } else if (is_player_in_check(state, BlackPlayer)) { 227 | value += 30; 228 | } 229 | 230 | // Add the values for each piece on the board. 231 | // Each piece type has a value associated with it, e.g. the value of a queen is greater than that of a pawn as the 232 | // queen is much more powerful. 233 | // The values for each piece type are stored in an array indexed by the (piece_type - 1). 234 | static const int PIECE_VALUES[6] = {20000, 900, 500, 330, 320, 100}; 235 | for (int i = 0; i < 16; i++) { 236 | // Add the sum of all the piece values for each white piece on the board. 237 | if (!boardpos_eq(state->piece_list_white[i], NULL_BOARDPOS)) { 238 | struct Piece piece = get_piece(state, state->piece_list_white[i]); 239 | value += PIECE_VALUES[piece.type - 1]; 240 | } 241 | 242 | // Subtract the sum of all the piece values for each black piece on the board. 243 | if (!boardpos_eq(state->piece_list_black[i], NULL_BOARDPOS)) { 244 | struct Piece piece = get_piece(state, state->piece_list_black[i]); 245 | value -= PIECE_VALUES[piece.type - 1]; 246 | } 247 | } 248 | 249 | // Add value if white has castling rights, remove value if black has castling rights. 250 | value += state->white_castlert_left + state->white_castlert_right; 251 | value -= state->black_castlert_left + state->black_castlert_right; 252 | 253 | // The more 'safe' the king of a player is the less likely they are to be checkmated. 254 | // Safety is measured by the number of friendly pieces each player has in the adjacent squares of their kings. 255 | for (int i = 0; i < 8; i++) { 256 | struct BoardPos direction = PIECE_MOVE_DIRECTIONS[King - 1][i]; 257 | struct BoardPos check_friendly = boardpos_add(state->white_king, direction); 258 | struct BoardPos check_enemy = boardpos_add(state->black_king, direction); 259 | if (!boardpos_eq(check_friendly, NULL_BOARDPOS)) { 260 | struct Piece piece = get_piece(state, check_friendly); 261 | // If there is a white piece adjacent to the white king then add value. 262 | if (piece.type != Empty && piece.player == WhitePlayer) { 263 | value += 10; 264 | } 265 | } 266 | 267 | if (!boardpos_eq(check_enemy, NULL_BOARDPOS)) { 268 | struct Piece piece = get_piece(state, check_enemy); 269 | // If there is a black piece adjacent to the black king then subtract value. 270 | if (piece.type != Empty && piece.player == BlackPlayer) { 271 | value -= 10; 272 | } 273 | } 274 | } 275 | 276 | // Controlling the centre of the board is advantageous in chess (due to better mobilty etc.). 277 | // Each of the central squares are checked for the presence of each player's pieces. 278 | for (int file = 2; file <= 5; file++) { 279 | for (int rank = 2; rank <= 5; rank++) { 280 | struct Piece piece = get_piece(state, BoardPos(file, rank)); 281 | if (piece.type == Empty) continue; 282 | 283 | // Add value if white is in the centre, subtract value if black is in the centre. 284 | // More value is added/subtracted for squares which are more central. 285 | if (file == 2 || file == 5 || rank == 2 || rank == 5) { 286 | value += piece.player == WhitePlayer ? 2 : -2; 287 | } else { 288 | value += piece.player == WhitePlayer ? 5 : -5; 289 | } 290 | } 291 | } 292 | 293 | return value; 294 | } 295 | 296 | // Evaluates the current position, returning a value representing how good the position is for the player to move. 297 | // Recursively calls itself, decreasing `depth` each time. When `depth` = 0 the function returns the heuristic value of 298 | // the postition by calling position_value. 299 | // Alpha-beta pruning is used to improve performance by 'pruning' branches in the game tree to avoid unneeded 300 | // computation. 301 | static int negamax(struct GameState *state, int alpha, int beta, int depth, time_t start_time) { 302 | enum Player player = state->white_to_move ? WhitePlayer : BlackPlayer; 303 | 304 | // The alpha value at the start of this node evaluation is stored so it can be compared the the evaluation value 305 | // later to detect if it failed low. 306 | int start_alpha = alpha; 307 | 308 | // Check the transposition table if we have already evaluated the position at a greater or equal depth then we would 309 | // be evaluating it now. So that the value can be reused, improving performance. 310 | struct TranspositionEntry tp_entry = tptable_get(state->hash); 311 | if (tp_entry.depth != 0 && tp_entry.depth >= depth) { 312 | if (tp_entry.type == EntryTypeExact) { 313 | // We have the exact value of this position, return it. 314 | return tp_entry.value; 315 | } else if (tp_entry.type == EntryTypeLower) { 316 | // We have a lower bound on the value of this postition, assign it to alpha so that more cutoffs can occur. 317 | // Since we have a lower bound, we can be sure that alpha will be the lower bound or more, as alpha is the 318 | // best value the maximising player has. 319 | alpha = MAX(alpha, tp_entry.value); 320 | } else if (tp_entry.type == EntryTypeUpper) { 321 | // We have an upper bound on the value of this position, assign in to beta so that more cutoffs can occur. 322 | // Since we have an upper bound, we can be sure that beta will be the upper bound or less, as beta is the 323 | // best value the minimising player has. 324 | beta = MIN(beta, tp_entry.value); 325 | } 326 | 327 | // If the best value the maximising player has is better than the best value the minimising player has then a 328 | // beta-cutoff occurs, there is no point evaluating this branch as it will never be better for the minimising 329 | // player than a previously evaluated branch, so the minimising player will choose the previously evaluated 330 | // branch regardless and the value of this node does not matter. 331 | if (alpha >= beta) { 332 | return tp_entry.value; 333 | } 334 | } 335 | 336 | // If the game is over return now, there are no legal moves. 337 | if (is_player_checkmated(state, player)) { 338 | // Player loses 339 | return -1000000; 340 | } else if (is_player_checkmated(state, other_player(player))) { 341 | // Player wins 342 | return 1000000; 343 | } else if (is_stalemate(state)) { 344 | // If it is stalemate then return 0, a draw so not good for either player. 345 | return 0; 346 | } 347 | 348 | // Return the position value if we have no more depth to search. 349 | if (depth == 0) return position_value(state) * (player == WhitePlayer ? 1 : -1); 350 | 351 | // If the maximum amount of time that can be spent on move generation has elapsed then return now. 352 | if (time(NULL) - start_time >= MAX_MOVEGEN_SEARCH_TIME) return INT_MIN; 353 | 354 | // Setup the transposition table entry, to be added at the end of the evaluation. 355 | if (tp_entry.depth == 0) { 356 | tp_entry.best_move = (struct Move){NULL_BOARDPOS, NULL_BOARDPOS}; 357 | } 358 | 359 | tp_entry.hash = state->hash; 360 | tp_entry.depth = depth; 361 | 362 | // Store the best value found so it can be returned. 363 | int best_value = INT_MIN; 364 | 365 | // Get a list of every legal move from this position for the player, and order them using a heuristic so that better 366 | // moves are ideally first. Alpga-beta pruning performs better if the better moves are first as more beta cutoffs 367 | // can occur. 368 | struct Move *legal_moves; 369 | unsigned int move_count = all_legal_moves_ordered(state, player, &legal_moves); 370 | 371 | // Evaluate each of the moves to find the move with the best value. 372 | for (unsigned int i = 0; i < move_count; i++) { 373 | struct Move move = legal_moves[i]; 374 | 375 | // A copy of the GameState is created so that the move can be made on it temporarily while it is being 376 | // evaluated. 377 | struct GameState *state_copy = copy_gamestate(state); 378 | make_move(state_copy, move, true); 379 | 380 | // Negamax is recursively called to evaluate the position after the move has been made. 381 | // The properties `min(a, b) === -max(-a, -b)` and `max(a, b) === -min(-a, -b)` are used to allow the same 382 | // function to be used for both players (the 'maximising' and 'minimising' players). In the lower nodes alpha, 383 | // the best value the maximising player is negated to become beta, the best value the minimising player has and 384 | // vice versa. 385 | // In each call of negamax the player to move changes, so alpha, beta and the return values are negated. 386 | int value = negamax(state_copy, -beta, -alpha, depth - 1, start_time); 387 | 388 | // The position has been evaluated and the temporary copy is no longer needed. 389 | free(state_copy); 390 | 391 | // INT_MIN is returned when the move generation time limit is reached. The value we be bubbled up so it is 392 | // detected by negamax_from_root. 393 | if (value == INT_MIN) { 394 | free(legal_moves); 395 | return INT_MIN; 396 | } 397 | 398 | // The value returned by negamax is relative to the player to move (greater values are better for the player to 399 | // move), so the value is negated so that it is relative to the opposite player, since the value which will be 400 | // returned in this call represents the value of the parent of the node which is being negated, which is the 401 | // node before the above move was made, so the opposite player is to move. The value is not immediately negated 402 | // as that would cause undefined behavior due to signed integer overflow if the value is INT_MIN (on machines 403 | // which use two's complement |INT_MIN| > |INT_MAX|). 404 | value = -value; 405 | 406 | // Keep track of the best value we have found. 407 | if (value > best_value) { 408 | best_value = value; 409 | tp_entry.best_move = move; 410 | // In negamax we act as the maximising player, so update alpha if we have a new max. 411 | if (value > alpha) { 412 | alpha = value; 413 | } 414 | } 415 | 416 | // If alpha >= beta then a beta cutoff can occur. The best value the 'maximising' player has is better than the 417 | // best value the best value the 'minimising' player has, so there is no point continuing to evaluate this 418 | // position as the 'minimising' player will always choose the other branch which we know to be lower than the 419 | // minimum value this node can be. 420 | if (alpha >= beta) { 421 | break; 422 | } 423 | } 424 | 425 | free(legal_moves); 426 | 427 | // If best_value is still INT_MIN that means no legal moves were found. But if there are no legal moves that means 428 | // it is either checkmate or stalemate, which should have been detected earlier in the function. 429 | // So if it is still INT_MIN then there is a logic error, so assert that it is not INT_MIN to ensure that if it 430 | // happens it can be detected and fixed. This will not have a performance impact as assertions are only enabled in 431 | // debug builds. 432 | assert(best_value != INT_MIN); 433 | 434 | tp_entry.value = best_value; 435 | 436 | // Set the entry type 437 | if (best_value <= start_alpha) { 438 | // The value is an upper bound as it failed low, none of the moves 439 | // searched were greater than the starting alpha. 440 | tp_entry.type = EntryTypeUpper; 441 | } else if (best_value >= beta) { 442 | // The value is a lower bound as it failed high, a move searched was 443 | // greater than beta (a beta cutoff occured). 444 | // When a beta cutoff occurs we have stopped in the middle of maximising, so it is a lower bound since 445 | // maximising can only increase the value. 446 | tp_entry.type = EntryTypeLower; 447 | } else { 448 | // The value improved for both the minimising and maximising player, 449 | // so there is a principal variation node and an exact value can be returned. 450 | tp_entry.type = EntryTypeExact; 451 | } 452 | 453 | // Add an entry to the transposition table so that the results of this evaluation can be reused later. 454 | tptable_put(tp_entry); 455 | 456 | return best_value; 457 | } 458 | 459 | // Used for testing 460 | // https://www.chessprogramming.org/Perft 461 | // NOTE only queen promotions are counted, so results will be lower if there are promotion moves 462 | // static uint64_t perft(struct GameState *state, int depth, enum Player player) { 463 | // if (depth == 0) return 1ULL; 464 | 465 | // uint64_t nodes = 0; 466 | // struct Move *legal_moves = all_legal_moves_ordered(state, player); 467 | // for (int i = 0; !boardpos_eq(legal_moves[i].from, NULL_BOARDPOS); i++) { 468 | // struct Move move = legal_moves[i]; 469 | 470 | // struct GameState *state_copy = copy_gamestate(state); 471 | // make_move(state_copy, move.from, move.to); 472 | // nodes += perft(state_copy, depth - 1, other_player(player)); 473 | // free(state_copy); 474 | // } 475 | 476 | // free(legal_moves); 477 | // return nodes; 478 | // } 479 | 480 | // Returns the best move for the player to move from the current position. Calls negamax to evaluate each possible move, 481 | // and returns the best. Returns a Move with NULL_BOARDPOS as the `from` if the time limit was reached or there are no 482 | // legal moves. 483 | static void negamax_from_root(struct GameState *state, int depth, time_t start_time) { 484 | // Inititalise alpha and beta to the starting values. 485 | // In the alpha-beta pruning algorithm alpha is used to store the best value the maximising player has so far and 486 | // beta is used to store the best value the minimising player has so far. 487 | // So, alpha is initialised to the worst possible value for the maximising player (actually INT_MIN + 1 to avoid 488 | // signed integer overflow on negation) and beta is initialised to the worst possible value for the minimising 489 | // player. 490 | int alpha = INT_MIN + 1; 491 | const int beta = INT_MAX; 492 | 493 | enum Player player = state->white_to_move ? WhitePlayer : BlackPlayer; 494 | 495 | // The best move found will be stored so it can be returned. 496 | struct Move best_move = (struct Move){NULL_BOARDPOS, NULL_BOARDPOS}; 497 | 498 | // The best value found will be stored so that moves can be compared. 499 | int best_value = INT_MIN; 500 | 501 | // Get a list of every legal move from this position for the player, and order them using a heuristic so that better 502 | // moves are ideally first. Alpha-beta pruning performs better if the better moves are first as more beta cutoffs 503 | // can occur. 504 | struct Move *legal_moves; 505 | unsigned int move_count = all_legal_moves_ordered(state, player, &legal_moves); 506 | 507 | // Every legal move is evaluated and compared to find the move with the highest value, the best move for the player. 508 | for (unsigned int i = 0; i < move_count; i++) { 509 | struct Move move = legal_moves[i]; 510 | 511 | // A copy of the state is created so the current state is not affected by the move. 512 | struct GameState *state_copy = copy_gamestate(state); 513 | 514 | // Make the move 515 | make_move(state_copy, move, true); 516 | 517 | // Negamax is called to evaluate the position after the move has been made. 518 | // The properties `min(a, b) === -max(-a, -b)` and `max(a, b) === -min(-a, -b)` are used to allow the same 519 | // function to be used for both players (the 'maximising' and 'minimising' players). In the lower nodes alpha, 520 | // the best value the maximising player is negated to become beta, the best value the minimising player has and 521 | // vice versa. 522 | // In each call of negamax the player to move changes, so alpha, beta and the return values are negated. 523 | int value = negamax(state_copy, -beta, -alpha, depth - 1, start_time); 524 | free(state_copy); 525 | 526 | // INT_MIN is returned when the time limit is reached. 527 | if (value == INT_MIN) { 528 | free(legal_moves); 529 | return; 530 | } 531 | 532 | // The value returned by negamax is relative to the player to move (greater values are better for the player to 533 | // move), so the value is negated so that it is relative to the opposite player, since the value which will be 534 | // returned in this call represents the value of the parent of the node which is being negated, which is the 535 | // node before the above move was made, so the opposite player is to move. The value is not immediately negated 536 | // as that would cause undefined behavior due to signed integer overflow if the value is INT_MIN (on machines 537 | // which use two's complement |INT_MIN| > |INT_MAX|). 538 | value = -value; 539 | 540 | // Update the best value and alpha if needed. 541 | // Alpha is the best value the maximising player has so far. 542 | if (value > best_value) { 543 | best_value = value; 544 | best_move = move; 545 | if (value > alpha) { 546 | alpha = value; 547 | } 548 | } 549 | } 550 | 551 | free(legal_moves); 552 | 553 | if (!boardpos_eq(best_move.from, NULL_BOARDPOS)) { 554 | // Add the principal variation (best move) to the transposition table, so that it can be used in move 555 | // ordering and by generate_move. 556 | struct TranspositionEntry entry = tptable_get(state->hash); 557 | entry.hash = state->hash; 558 | entry.depth = depth; 559 | entry.best_move = best_move; 560 | entry.value = best_value; 561 | entry.type = EntryTypeExact; 562 | tptable_put(entry); 563 | printf("[movegen] **** %d %d\n", depth, best_value); 564 | } 565 | } 566 | 567 | struct MovegenTaskArg { 568 | struct AtomicCounter *refcount; // Refcount of state and legal_moves. 569 | struct GameState *state; 570 | struct Move *legal_moves; 571 | unsigned int move_count; 572 | int depth; 573 | time_t start_time; 574 | }; 575 | 576 | // Task executed the thread pool to perform move generation. 577 | // See generate_move. 578 | static bool movegen_task(struct MovegenTaskArg *arg) { 579 | negamax_from_root(arg->state, arg->depth, arg->start_time); 580 | 581 | if (acnt_dec(arg->refcount)) { 582 | free(arg->refcount); 583 | free(arg->state); 584 | free(arg->legal_moves); 585 | } 586 | 587 | free(arg); 588 | return true; 589 | } 590 | 591 | // Generate the best move for the player to move, using negamax with iterative deepening and Lazy SMP on supported 592 | // systems. 593 | // The best move will be stored in the transposition table. 594 | // On systems with multithreading support the function will not block. 595 | void generate_move(struct GameState *state, struct ThreadPool *pool, time_t start_time) { 596 | // Prevent entries for this hash being replaced by other hashes. 597 | tptable_set_protected_hash(state->hash); 598 | 599 | // Check if there is a move available in the opening book if we are on move <= 5. 600 | if (state->move_count <= 5) { 601 | struct OpeningItem *opening = find_opening_by_hash(state->hash); 602 | if (opening) { 603 | // If there are multiple moves available then one is chosen at random. 604 | struct Move move = opening->moves[rand() % opening->moves_count]; 605 | 606 | // Ensure the move is legal to reduce the impact of Zobrist hash collisions. 607 | if (is_move_legal(state, move)) { 608 | struct TranspositionEntry entry = tptable_get(state->hash); 609 | entry.hash = state->hash; 610 | entry.best_move = move; 611 | entry.depth = CHAR_MAX; 612 | entry.value = 0; 613 | entry.type = EntryTypeExact; 614 | tptable_put(entry); 615 | return; 616 | } 617 | } 618 | } 619 | 620 | // The threads will need a copy of the gamestate incase it is deallocated. 621 | struct GameState *state_for_threads = copy_gamestate(state); 622 | 623 | // Find the legal moves now to avoid doing it on each thread. 624 | struct Move *legal_moves; 625 | unsigned int move_count = 626 | all_legal_moves_ordered(state, state->white_to_move ? WhitePlayer : BlackPlayer, &legal_moves); 627 | 628 | // Counts references to the legal moves and the copied gamestate. 629 | struct AtomicCounter *refcount = acnt_init(MAX_SEARCH_DEPTH); 630 | 631 | // Use iterative deepening to find the best move. 632 | // Initially the searching algorithm is ran with a low maximum depth. This depth is iteratively increased and the 633 | // algorithm is reran, until a time limit is reached. The result from the last completed search is used as the best 634 | // move. This allows for searching at the maximum depth (searching at greater depths will yield a more accurate 635 | // result) while keeping inside a time limit, so that the move is returned within a certain time on all hardware. 636 | // The performance effect of rerunning the algorithm multiple times instead of running it once is minimised as a 637 | // transposition table is used to store the results of previous evaluations, and the principal variation is stored 638 | // which is used in move ordering to improve alpha-beta pruning performance. 639 | for (int depth = 1; depth <= MAX_SEARCH_DEPTH; depth++) { 640 | // Freed by thread 641 | struct MovegenTaskArg *arg = malloc(sizeof(*arg)); 642 | arg->state = state_for_threads; 643 | arg->depth = depth; 644 | arg->start_time = start_time; 645 | arg->legal_moves = legal_moves; 646 | arg->move_count = move_count; 647 | arg->refcount = refcount; 648 | 649 | threadpool_enqueue(pool, (TaskFunc)movegen_task, arg); 650 | } 651 | } 652 | 653 | // Checks if the game is stalemate. 654 | // The game is stalemate when the player to move has no possible legal moves, but is not in check. 655 | bool is_stalemate(struct GameState *state) { 656 | enum Player to_move = state->white_to_move ? WhitePlayer : BlackPlayer; 657 | 658 | // If the king is in check it cannot be stalemate. 659 | if (is_player_in_check(state, to_move)) return false; 660 | 661 | // If the king is not in check and there are no legal moves it is stalemate. 662 | struct BoardPos legal_moves[PIECE_LEGAL_MOVES_MAX]; 663 | struct BoardPos *piece_list = to_move == WhitePlayer ? state->piece_list_white : state->piece_list_black; 664 | for (int i = 0; i < 16; i++) { 665 | if (!boardpos_eq(piece_list[i], NULL_BOARDPOS)) { 666 | if (legal_moves_from_pos(state, piece_list[i], legal_moves) != 0) { 667 | return false; 668 | } 669 | } 670 | } 671 | 672 | // No legal moves were found, it is stalemate. 673 | return true; 674 | } 675 | 676 | // Checks if a player has been checkmated. 677 | bool is_player_checkmated(struct GameState *state, enum Player player) { 678 | // A player must be in check to be checkmated. 679 | if (!is_player_in_check(state, player)) return false; 680 | 681 | // A player is checkmated if they are in check and have no legal moves to get themselves out of check. 682 | // All the player's pieces are checked to see if they have any legal moves. 683 | struct BoardPos legal_moves[PIECE_LEGAL_MOVES_MAX]; 684 | struct BoardPos *piece_list = player == WhitePlayer ? state->piece_list_white : state->piece_list_black; 685 | for (int i = 0; i < 16; i++) { 686 | if (!boardpos_eq(piece_list[i], NULL_BOARDPOS)) { 687 | if (legal_moves_from_pos(state, piece_list[i], legal_moves) != 0) { 688 | return false; 689 | } 690 | } 691 | } 692 | 693 | // No legal moves were found, the player is checkmated. 694 | return true; 695 | } 696 | 697 | // Checks if a state is legal, e.g. if white is to move, black's king must not be in check for the state to be legal. 698 | static bool is_state_legal(struct GameState *state) { 699 | // NOTE This must not use state->hash (may be unset). 700 | enum Player last_move = state->white_to_move ? BlackPlayer : WhitePlayer; 701 | return !is_player_in_check(state, last_move); 702 | } 703 | 704 | // Makes a move, updating the board and other state such as castling rights and en passant targets. 705 | // If calculate_hash is true the Zobrist hash of the new state will be written to state->hash. 706 | void make_move(struct GameState *state, struct Move move, bool calculate_hash) { 707 | struct Piece from_piece = get_piece(state, move.from); 708 | struct Piece to_piece = get_piece(state, move.to); 709 | 710 | if (from_piece.type == Pawn) { 711 | if ((abs(move.from.rank - move.to.rank) == 2)) { 712 | // Add en passant target files on double pawn push. 713 | if (from_piece.player == WhitePlayer) { 714 | state->enpassant_target_black = move.from.file; 715 | } else { 716 | state->enpassant_target_white = move.from.file; 717 | } 718 | } else { 719 | if (move.from.file != move.to.file && to_piece.type == Empty) { 720 | // Perform en passant capture. 721 | put_piece(state, Piece(Empty, WhitePlayer), BoardPos(move.to.file, move.from.rank)); 722 | // Remove en passant captured piece from piece list 723 | change_piece_list_pos(state, other_player(from_piece.player), BoardPos(move.to.file, move.from.rank), 724 | NULL_BOARDPOS); 725 | } 726 | 727 | // Remove en passant target if a pawn on the file does not double push. 728 | unset_enpassant_target_file(state, other_player(from_piece.player)); 729 | } 730 | } else if (from_piece.type == Rook && (boardpos_eq(move.from, ROOK_STARTING_POSITIONS_LEFT[from_piece.player]) || 731 | boardpos_eq(move.from, ROOK_STARTING_POSITIONS_RIGHT[from_piece.player]))) { 732 | // Remove castling rights if a rook moves from its starting position. 733 | if (move.from.file == 0) 734 | unset_castlert_left(state, from_piece.player); 735 | else if (move.from.file == 7) 736 | unset_castlert_right(state, from_piece.player); 737 | } else if (to_piece.type == Rook && (boardpos_eq(move.to, ROOK_STARTING_POSITIONS_LEFT[to_piece.player]) || 738 | boardpos_eq(move.to, ROOK_STARTING_POSITIONS_RIGHT[to_piece.player]))) { 739 | // Remove castling rights if a rook is captured at its starting position. 740 | if (move.to.file == 0) 741 | unset_castlert_left(state, to_piece.player); 742 | else if (move.to.file == 7) 743 | unset_castlert_right(state, to_piece.player); 744 | } else if (from_piece.type == King) { 745 | // Remove all castling rights if the king moves. 746 | unset_castlert_left(state, from_piece.player); 747 | unset_castlert_right(state, from_piece.player); 748 | 749 | // Move the rook if the move is castling. 750 | if (boardpos_eq(move.from, KING_STARTING_POSITIONS[from_piece.player])) { 751 | if (move.to.file == 2) { 752 | move_piece(state, BoardPos(0, move.from.rank), BoardPos(3, move.from.rank)); 753 | change_piece_list_pos(state, from_piece.player, BoardPos(0, move.from.rank), 754 | BoardPos(3, move.from.rank)); 755 | } else if (move.to.file == 6) { 756 | put_piece(state, Piece(Empty, WhitePlayer), BoardPos(7, move.from.rank)); 757 | put_piece(state, Piece(Rook, from_piece.player), BoardPos(5, move.from.rank)); 758 | change_piece_list_pos(state, from_piece.player, BoardPos(7, move.from.rank), 759 | BoardPos(5, move.from.rank)); 760 | } 761 | } 762 | 763 | // Update the king position. 764 | set_king_pos(state, from_piece.player, move.to); 765 | } 766 | 767 | // Update the piece list. 768 | change_piece_list_pos(state, from_piece.player, move.from, move.to); 769 | if (to_piece.type != Empty) { 770 | change_piece_list_pos(state, to_piece.player, move.to, NULL_BOARDPOS); 771 | } 772 | 773 | // Remove en passant target file, the player has moved. 774 | unset_enpassant_target_file(state, from_piece.player); 775 | 776 | // If the move is a promotion the new piece will be a queen. 777 | struct Piece new_piece = from_piece; 778 | if (from_piece.type == Pawn && (move.to.rank == 0 || move.to.rank == 7)) { 779 | new_piece = Piece(Queen, from_piece.player); 780 | } 781 | 782 | // Move the piece. 783 | put_piece(state, new_piece, move.to); 784 | put_piece(state, Piece(Empty, WhitePlayer), move.from); 785 | 786 | // Update check status. 787 | state->black_king_in_check = is_piece_attacked(state, state->black_king, WhitePlayer); 788 | state->white_king_in_check = is_piece_attacked(state, state->white_king, BlackPlayer); 789 | 790 | // Update move count. 791 | state->move_count += 1; 792 | 793 | // A move has been made so the player to move swaps. 794 | state->white_to_move = !state->white_to_move; 795 | 796 | // Calculate the Zobrist hash of the new state if needed. 797 | if (calculate_hash) { 798 | state->hash = hash_state(state); 799 | } else { 800 | state->hash = 0; 801 | } 802 | } 803 | 804 | // Returns if it is possible for a piece to move from one position to another. 805 | // It does not check legality (e.g. if in check, castling rights), it only checks if a the move follows the patterns of 806 | // the piece. 807 | // Also checks if any squares under a castling moves are being attacked. 808 | static bool is_move_possible(struct GameState *state, struct Move move) { 809 | struct Piece from_piece = get_piece(state, move.from); 810 | struct Piece to_piece = get_piece(state, move.to); 811 | 812 | // A piece cannot move to a square occupied by the same player. 813 | if (to_piece.type != Empty && from_piece.player == to_piece.player) return false; 814 | 815 | // Check if the move follows the pieces pattern (e.g diagonal only for bishop) and if there are any pieces in the 816 | // way. 817 | switch (from_piece.type) { 818 | case King: 819 | // The king can normally move a maximum of one square in any direction. 820 | if (abs(move.from.file - move.to.file) <= 1 && abs(move.from.rank - move.to.rank) <= 1) { 821 | return true; 822 | } 823 | 824 | // Check if the move is a castling move. 825 | bool is_castle = move.from.rank == move.to.rank && (move.to.file == 6 || move.to.file == 2) && 826 | boardpos_eq(move.from, KING_STARTING_POSITIONS[from_piece.player]); 827 | if (is_castle) { 828 | int direction = move.to.file == 6 ? 1 : -1; 829 | 830 | // The last file which will be checked in the loop below. 831 | int last_checked_file = move.to.file == 6 ? 6 : 1; 832 | 833 | // Make sure all squares between the king and the destination are empty and not under attack. 834 | for (int i = 4; i != last_checked_file + direction; i += direction) { 835 | struct Piece check = get_piece(state, BoardPos(i, move.from.rank)); 836 | 837 | // All squares between the castling move must be empty, except the king itself. 838 | if (check.type != Empty && i != 4) return false; 839 | 840 | // All squares between the castling move must not be attacked by the enemy, except for queenside 841 | // castling where file 1 can be attacked. 842 | if (i != 1 && 843 | is_piece_attacked(state, BoardPos(i, move.from.rank), other_player(from_piece.player))) 844 | return false; 845 | } 846 | return true; 847 | } 848 | 849 | return false; 850 | case Queen: 851 | case Rook: 852 | case Bishop: 853 | if (move.from.file == move.to.file) { 854 | // Vertical movement - only the queen and rook can move vertically. 855 | if (from_piece.type == Bishop) return false; 856 | 857 | // Make sure there are no pieces in the way of the move. 858 | for (int i = MIN(move.from.rank, move.to.rank) + 1; i < MAX(move.from.rank, move.to.rank); i++) { 859 | struct Piece check = get_piece(state, BoardPos(move.from.file, i)); 860 | if (check.type != Empty) return false; 861 | } 862 | 863 | return true; 864 | } else if (move.from.rank == move.to.rank) { 865 | // Horizontal movement - only the queen and rook can move horizontally. 866 | if (from_piece.type == Bishop) return false; 867 | 868 | // Make sure there are no pieces in the way of the move. 869 | for (int i = MIN(move.from.file, move.to.file) + 1; i < MAX(move.from.file, move.to.file); i++) { 870 | struct Piece check = get_piece(state, BoardPos(i, move.from.rank)); 871 | if (check.type != Empty) return false; 872 | } 873 | 874 | return true; 875 | } else if (abs(move.from.file - move.to.file) == abs(move.from.rank - move.to.rank)) { 876 | // Diagonal movement - only the queen and bishop can move diagonally. 877 | if (from_piece.type == Rook) return false; 878 | 879 | // Make sure there are no pieces in the way of the move. 880 | int file_add = move.from.file > move.to.file ? -1 : 1; 881 | int rank_add = move.from.rank > move.to.rank ? -1 : 1; 882 | int file = move.from.file + file_add; 883 | int rank = move.from.rank + rank_add; 884 | while (file != move.to.file) { 885 | struct Piece check = get_piece(state, BoardPos(file, rank)); 886 | if (check.type != Empty) return false; 887 | 888 | file += file_add; 889 | rank += rank_add; 890 | } 891 | 892 | return true; 893 | } 894 | 895 | return false; 896 | case Knight: 897 | // The knight must move in an 'L' shape. 898 | // A knight can move over the top of other pieces, so no need to check if there are pieces in the way. 899 | return (abs(move.from.file - move.to.file) == 2 && abs(move.from.rank - move.to.rank) == 1) || 900 | (abs(move.from.file - move.to.file) == 1 && abs(move.from.rank - move.to.rank) == 2); 901 | case Pawn:; 902 | // A pawn can move one or two squares vertically in the direction towards the other player, 903 | // or one square diagonally in the direction towards the other player. 904 | // When moving two squares vertically it must not 'jump' over a piece. 905 | int direction = from_piece.player == BlackPlayer ? 1 : -1; 906 | 907 | return (move.to.rank - move.from.rank == direction && abs(move.from.file - move.to.file) <= 1) || 908 | (move.to.rank - move.from.rank == 2 * direction && move.from.file == move.to.file); 909 | case Empty: 910 | return false; 911 | } 912 | 913 | return false; 914 | } 915 | 916 | // Checks if a move is legal. 917 | bool is_move_legal(struct GameState *state, struct Move move) { 918 | // First check if the move follows the move patterns of the piece being moved. 919 | if (!is_move_possible(state, move)) return false; 920 | 921 | struct Piece from_piece = get_piece(state, move.from); 922 | struct Piece to_piece = get_piece(state, move.to); 923 | 924 | // The king may not be captured. 925 | if (to_piece.type == King) return false; 926 | 927 | // The move must be made by the player to move. 928 | if (from_piece.player != (state->white_to_move ? WhitePlayer : BlackPlayer)) return false; 929 | 930 | if (from_piece.type == Pawn) { 931 | if (move.from.file != move.to.file) { 932 | // Check en passant legality. 933 | // The moved to file must be an en passant target file, and the rank must be a valid en passant rank. 934 | if (to_piece.type == Empty) { 935 | if ((from_piece.player == WhitePlayer && move.from.rank != 3) || 936 | (from_piece.player == BlackPlayer && move.from.rank != 4) || 937 | (get_enpassant_target_file(state, from_piece.player) != move.to.file)) 938 | return false; 939 | } 940 | } else if (abs(move.from.rank - move.to.rank) == 2) { 941 | // Check double pawn push legality. 942 | 943 | // A double pawn push can only be made from the pawn's starting position. 944 | if (from_piece.player == BlackPlayer && move.from.rank != 1) return false; 945 | if (from_piece.player == WhitePlayer && move.from.rank != 6) return false; 946 | 947 | // A double pawn push cannot "jump over" a piece. 948 | int max_rank = MAX(move.to.rank, move.from.rank); 949 | struct Piece piece1 = get_piece(state, BoardPos(move.from.file, max_rank - 1)); 950 | struct Piece piece2 = get_piece(state, BoardPos(move.from.file, move.to.rank)); 951 | if (piece1.type != Empty || piece2.type != Empty) return false; 952 | } else { 953 | // A "normal" pawn push must not land on an occupied square. 954 | struct Piece piece = get_piece(state, move.to); 955 | if (piece.type != Empty) return false; 956 | } 957 | } else if (from_piece.type == King && abs(move.from.file - move.to.file) == 2) { 958 | // Check castling rights. 959 | if (from_piece.player == WhitePlayer) { 960 | if ((move.to.file == 2 && !state->white_castlert_left) || 961 | (move.to.file == 6 && !state->white_castlert_right)) 962 | return false; 963 | } else { 964 | if ((move.to.file == 2 && !state->black_castlert_left) || 965 | (move.to.file == 6 && !state->black_castlert_right)) 966 | return false; 967 | } 968 | } 969 | 970 | // Check if the resulting state after the move is legal, for example the player which has moved must not be in 971 | // check. 972 | struct GameState *state_copy = copy_gamestate(state); 973 | make_move(state_copy, move, false); 974 | bool legal = is_state_legal(state_copy); 975 | free(state_copy); 976 | 977 | return legal; 978 | } 979 | 980 | // Checks if a certain player's piece is being attacked, or if an empty square is controlled. 981 | bool is_piece_attacked(struct GameState *state, struct BoardPos attackee_pos, enum Player attacker) { 982 | struct Piece attackee_piece = get_piece(state, attackee_pos); 983 | 984 | // Each possible attacker piece type is checked, each piece moves differently. 985 | // For each piece type every valid (regardless of legality) move pattern ending at the attackee position is checked 986 | // to see if there is an attacking piece of that type in an attacking position. 987 | 988 | // Check King, Rook, Bishop, Queen 989 | for (int i = 0; i < 8; i++) { 990 | const struct BoardPos translation = PIECE_MOVE_DIRECTIONS[Queen - 1][i]; 991 | 992 | const bool is_diagonal = abs(translation.file) == abs(translation.rank); 993 | bool is_king = true; 994 | 995 | struct BoardPos check = boardpos_add(attackee_pos, translation); 996 | while (!boardpos_eq(check, NULL_BOARDPOS)) { 997 | struct Piece check_piece = get_piece(state, check); 998 | 999 | if (check_piece.type != Empty) { 1000 | // Check if there is an attacking piece of the correct type in the possible attacking 1001 | // position. 1002 | 1003 | bool correct_piece = check_piece.type == Queen || (is_king && check_piece.type == King) || 1004 | (is_diagonal && check_piece.type == Bishop) || 1005 | (!is_diagonal && check_piece.type == Rook); 1006 | 1007 | if (correct_piece && check_piece.player == attacker) { 1008 | return true; 1009 | } else { 1010 | break; 1011 | } 1012 | } 1013 | 1014 | // Kings can only move one square. 1015 | is_king = false; 1016 | 1017 | // Queens, rooks and bishops can attack over multiple squares in the same direction, so the 1018 | // check continues for every square in that direction. Overflow is handled by boardpos_add, 1019 | // NULL_BOARDPOS will be returned if the add overflows. 1020 | check = boardpos_add(check, translation); 1021 | } 1022 | } 1023 | 1024 | // Check Pawn 1025 | for (int i = 0; i < 8; i++) { 1026 | // The pawn has less than eight possible move directions, so a NULL_BOARDPOS singifies 1027 | // we have reached the end of all the possible directions. 1028 | if (boardpos_eq(PIECE_MOVE_DIRECTIONS[Pawn - 1][i], NULL_BOARDPOS)) { 1029 | break; 1030 | } 1031 | 1032 | struct BoardPos direction = PIECE_MOVE_DIRECTIONS[Pawn - 1][i]; 1033 | 1034 | // The pawn only attacks on the two diagonal squares in front of it, so ignore moves which remain in 1035 | // the same file. 1036 | if (direction.file == 0) continue; 1037 | 1038 | // If it is black attacking the pawns move in the opposite direction. 1039 | if (attackee_piece.player == BlackPlayer) { 1040 | direction.file *= -1; 1041 | direction.rank *= -1; 1042 | } 1043 | 1044 | struct BoardPos check = boardpos_add(attackee_pos, direction); 1045 | if (!boardpos_eq(check, NULL_BOARDPOS)) { 1046 | // Check if there is an attacking piece of the correct type in the possible attacking 1047 | // position. 1048 | struct Piece check_piece = get_piece(state, check); 1049 | if (check_piece.type == Pawn && check_piece.player == attacker) { 1050 | return true; 1051 | } 1052 | } 1053 | } 1054 | 1055 | // Check Knight 1056 | for (int i = 0; i < 8; i++) { 1057 | // The knight has less than eight possible move directions, so a NULL_BOARDPOS singifies 1058 | // we have reached the end of all the possible directions. 1059 | if (boardpos_eq(PIECE_MOVE_DIRECTIONS[Knight - 1][i], NULL_BOARDPOS)) { 1060 | break; 1061 | } 1062 | 1063 | struct BoardPos direction = PIECE_MOVE_DIRECTIONS[Knight - 1][i]; 1064 | struct BoardPos check = boardpos_add(attackee_pos, direction); 1065 | 1066 | if (!boardpos_eq(check, NULL_BOARDPOS)) { 1067 | // Check if there is an attacking piece of the correct type in the possible attacking 1068 | // position. 1069 | struct Piece check_piece = get_piece(state, check); 1070 | if (check_piece.type == Knight && check_piece.player == attacker) { 1071 | return true; 1072 | } 1073 | } 1074 | } 1075 | 1076 | return false; 1077 | } 1078 | -------------------------------------------------------------------------------- /src/fen.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | // Convert a piece to its FEN representation 9 | // Returns '\0' if the piece is Empty 10 | static char piece_to_fen(struct Piece piece) { 11 | static const char PIECES[7] = {'\0', 'K', 'Q', 'R', 'B', 'N', 'P'}; 12 | char out = PIECES[piece.type]; 13 | 14 | // Black pieces are lowercase 15 | if (out != '\0' && piece.player == BlackPlayer) { 16 | out = tolower(out); 17 | } 18 | 19 | return out; 20 | } 21 | 22 | // Returns the piece if it was successfully parsed, 23 | // an Empty piece otherwise 24 | static struct Piece parse_fen_piece(char piece_char) { 25 | struct Piece out_piece = Piece(Empty, WhitePlayer); 26 | 27 | // Lowercase chars are black pieces, 28 | // convert them to uppercase so they can be 29 | // matched below 30 | if (islower(piece_char)) { 31 | out_piece.player = BlackPlayer; 32 | piece_char = toupper(piece_char); 33 | } 34 | 35 | switch (piece_char) { 36 | case 'P': 37 | out_piece.type = Pawn; 38 | break; 39 | case 'N': 40 | out_piece.type = Knight; 41 | break; 42 | case 'B': 43 | out_piece.type = Bishop; 44 | break; 45 | case 'R': 46 | out_piece.type = Rook; 47 | break; 48 | case 'Q': 49 | out_piece.type = Queen; 50 | break; 51 | case 'K': 52 | out_piece.type = King; 53 | break; 54 | } 55 | 56 | return out_piece; 57 | } 58 | 59 | // Converts an algebraic notation file character to an integer 60 | // Returns -1 if the char is invalid 61 | static inline int algebraic_file_to_int(char file) { 62 | if (file < 'a' || file > 'h') { 63 | return -1; 64 | } else { 65 | return file - 'a'; 66 | } 67 | } 68 | 69 | // Convert an integer file to its algebraic notation equivalent 70 | // Assumes the file is valid 71 | static inline char int_file_to_algebraic(int file) { 72 | assert(file >= 0 && file < 8); 73 | return file + 'a'; 74 | } 75 | 76 | // Parse a FEN string into a GameState. 77 | // The returned GameState must be deallocated with deinit_gamestate. 78 | // Returns NULL if there was an error, e.g. invalid FEN. 79 | // https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation 80 | struct GameState *fen_to_gamestate(const char *fen) { 81 | struct GameState *out = init_gamestate(); 82 | clear_board(out); 83 | 84 | // Parse piece placement 85 | int file = 0; 86 | int rank = 0; 87 | int i = 0; 88 | int piece_list_idx_white = 0; 89 | int piece_list_idx_black = 0; 90 | for (;; i++) { 91 | // Unexpected end of string 92 | if (fen[i] == '\0') { 93 | goto exit_error; 94 | } 95 | 96 | // Try parse a FEN piece character, e.g. K = white king 97 | struct Piece piece = parse_fen_piece(fen[i]); 98 | 99 | if (piece.type != Empty) { 100 | // A piece was successfully parsed, add it to the board 101 | 102 | // The maximum file is 7 103 | if (file == 8) { 104 | goto exit_error; 105 | } 106 | 107 | out->board[file++][rank] = piece; 108 | 109 | if (piece.type == King) { 110 | // Cannot have multiple kings 111 | if (!boardpos_eq(get_king_pos(out, piece.player), NULL_BOARDPOS)) { 112 | goto exit_error; 113 | } 114 | 115 | // Set the king position 116 | set_king_pos(out, piece.player, BoardPos(file - 1, rank)); 117 | } 118 | 119 | // Add the piece to the piece list, checking the limits 120 | if (piece.player == WhitePlayer) { 121 | // White's piece limit has been reached 122 | if (piece_list_idx_white == sizeof(out->piece_list_white) / sizeof(out->piece_list_white[0])) { 123 | goto exit_error; 124 | } 125 | 126 | out->piece_list_white[piece_list_idx_white++] = BoardPos(file - 1, rank); 127 | } else { 128 | // Black's piece limit has been reached 129 | if (piece_list_idx_black == sizeof(out->piece_list_black) / sizeof(out->piece_list_black[0])) { 130 | goto exit_error; 131 | } 132 | 133 | out->piece_list_black[piece_list_idx_black++] = BoardPos(file - 1, rank); 134 | } 135 | } else if (fen[i] == '/') { 136 | // The '/' character separates the ranks 137 | rank += 1; 138 | file = 0; 139 | 140 | // The rank should never reach 8 as it is incremented 141 | // only when there is a '/' separator, which is only 142 | // between the ranks, not at the end of each rank. 143 | if (rank == 8) { 144 | goto exit_error; 145 | } 146 | } else if (fen[i] == ' ') { 147 | // The space character separates the piece placement 148 | // from the rest of the string 149 | break; 150 | } else if (isdigit(fen[i])) { 151 | // Digits denote a number of empty spaces on the file 152 | 153 | // Convert the ASCII digit character to an integer, 154 | // add add it to the file 155 | file += fen[i] - '0'; 156 | 157 | // The file may be 8 at the end of the piece placement section 158 | // as it is incremented after each square. 159 | // It cannot exceed 8, as there are 8 squares per file. 160 | if (file > 8) { 161 | goto exit_error; 162 | } 163 | } else { 164 | // There was an invalid character 165 | goto exit_error; 166 | } 167 | } 168 | 169 | // If the file and rank arent at the ending values then part of the piece placement 170 | // was missing, and the FEN is invalid 171 | if (file != 8 || rank != 7) { 172 | goto exit_error; 173 | } 174 | 175 | // A space separates sections of the FEN 176 | if (fen[i++] != ' ') { 177 | goto exit_error; 178 | } 179 | 180 | // Parse player to move 181 | if (fen[i] == 'w') { 182 | out->white_to_move = true; 183 | } else if (fen[i] == 'b') { 184 | out->white_to_move = false; 185 | } else { 186 | goto exit_error; 187 | } 188 | 189 | i += 1; 190 | 191 | // A space separates sections of the FEN 192 | if (fen[i++] != ' ') { 193 | goto exit_error; 194 | } 195 | 196 | // Parse castling rights 197 | out->white_castlert_left = false; 198 | out->white_castlert_right = false; 199 | out->black_castlert_left = false; 200 | out->black_castlert_right = false; 201 | 202 | if (fen[i] == '-') { 203 | // Neither side have castling rights 204 | // This is already set above as the starting case 205 | i += 1; 206 | } else { 207 | // Parse the rights, capital letters are white's rights, 208 | // lowercase are black's. K = kingside castling, Q = queenside castling 209 | 210 | if (fen[i] == 'K') { 211 | out->white_castlert_right = true; 212 | i += 1; 213 | } 214 | 215 | if (fen[i] == 'Q') { 216 | out->white_castlert_left = true; 217 | i += 1; 218 | } 219 | 220 | if (fen[i] == 'k') { 221 | out->black_castlert_right = true; 222 | i += 1; 223 | } 224 | 225 | if (fen[i] == 'q') { 226 | out->black_castlert_left = true; 227 | i += 1; 228 | } 229 | } 230 | 231 | // A space separates sections of the FEN 232 | if (fen[i++] != ' ') { 233 | goto exit_error; 234 | } 235 | 236 | // Parse en passant target square 237 | if (fen[i] == '-') { 238 | i += 1; 239 | } else { 240 | int file = algebraic_file_to_int(fen[i++]); 241 | if (file == -1) { 242 | goto exit_error; 243 | } 244 | 245 | if (fen[i] == '3') { 246 | out->enpassant_target_black = file; 247 | } else if (fen[i] == '6') { 248 | out->enpassant_target_white = file; 249 | } else { 250 | goto exit_error; 251 | } 252 | 253 | i += 1; 254 | } 255 | 256 | // A space separates sections of the FEN 257 | if (fen[i++] != ' ') { 258 | goto exit_error; 259 | } 260 | 261 | // The halfmove clock and fullmove number would usually follow, 262 | // but they are not used in this game so are ignored. 263 | 264 | // Check that there are two kings on the board 265 | if (boardpos_eq(out->white_king, NULL_BOARDPOS) || boardpos_eq(out->black_king, NULL_BOARDPOS)) { 266 | goto exit_error; 267 | } 268 | 269 | // Check if the kings are in check 270 | out->white_king_in_check = is_piece_attacked(out, out->white_king, BlackPlayer); 271 | out->black_king_in_check = is_piece_attacked(out, out->black_king, WhitePlayer); 272 | 273 | // Add the Zobrist hash 274 | out->hash = hash_state(out); 275 | 276 | return out; 277 | 278 | exit_error: 279 | // If there was an error the gamestate must be deallocated 280 | deinit_gamestate(out); 281 | return NULL; 282 | } 283 | 284 | // Serialise a GameState to a FEN string 285 | // `fen` must be at least 90 bytes 286 | void gamestate_to_fen(struct GameState *state, char *fen) { 287 | int i = 0; 288 | 289 | // Add pieces 290 | for (int rank = 0; rank < 8; rank++) { 291 | int empty_count = 0; 292 | for (int file = 0; file < 8; file++) { 293 | struct Piece piece = get_piece(state, BoardPos(file, rank)); 294 | if (piece.type != Empty) { 295 | // If the number of empty squares counted on the file 296 | // is non-zero, and we are reaching the first non-empty square 297 | // so add the number of empty squares to the FEN first 298 | if (empty_count != 0) { 299 | fen[i++] = empty_count + '0'; 300 | empty_count = 0; 301 | } 302 | 303 | // Add the piece to the FEN 304 | fen[i++] = piece_to_fen(piece); 305 | } else { 306 | empty_count++; 307 | } 308 | 309 | // A series of empty squares on a file are represented by 310 | // a digit representing the number of empty squares 311 | if (file == 7 && empty_count != 0) { 312 | fen[i++] = empty_count + '0'; 313 | empty_count = 0; 314 | } 315 | } 316 | 317 | // If we have added the final rank then terminate the section 318 | // with a space, otherwise add a '/' to separate the ranks 319 | if (rank == 7) { 320 | fen[i++] = ' '; 321 | } else { 322 | fen[i++] = '/'; 323 | } 324 | } 325 | 326 | // Add player to move 327 | fen[i++] = state->white_to_move ? 'w' : 'b'; 328 | fen[i++] = ' '; 329 | 330 | // Add castling rights 331 | if (!state->white_castlert_left && !state->white_castlert_right && !state->black_castlert_left && 332 | !state->black_castlert_right) { 333 | // Neither player having castling rights is represented with a '-' 334 | fen[i++] = '-'; 335 | } else { 336 | if (state->white_castlert_right) { 337 | fen[i++] = 'K'; 338 | } 339 | 340 | if (state->white_castlert_left) { 341 | fen[i++] = 'Q'; 342 | } 343 | 344 | if (state->black_castlert_right) { 345 | fen[i++] = 'k'; 346 | } 347 | 348 | if (state->black_castlert_left) { 349 | fen[i++] = 'q'; 350 | } 351 | } 352 | 353 | // A space separates the sections 354 | fen[i++] = ' '; 355 | 356 | // Add en passant target square 357 | int target_file = get_enpassant_target_file(state, state->white_to_move ? WhitePlayer : BlackPlayer); 358 | if (target_file != -1) { 359 | // If there is an en passant target file the position behind the pawn 360 | // which just double pushed is added 361 | fen[i++] = int_file_to_algebraic(target_file); 362 | fen[i++] = state->white_to_move ? '6' : '3'; 363 | } else { 364 | fen[i++] = '-'; 365 | } 366 | 367 | // A space separates the sections 368 | fen[i++] = ' '; 369 | 370 | // The halfmove clock and fullmove number are not used 371 | // in this game, so are both set to zero 372 | fen[i++] = '0'; 373 | fen[i++] = ' '; 374 | fen[i++] = '0'; 375 | 376 | // Add NUL terminator 377 | fen[i++] = '\0'; 378 | } -------------------------------------------------------------------------------- /src/frontend_state.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | // The single instance of FrontendState used throughout the program 8 | // initialised to default values 9 | struct FrontendState frontend_state = {.game_state = NULL, 10 | .selected_position = {0xf, 0xf}, // NULL_BOARDPOS 11 | .two_player_mode = true, 12 | .move_log = NULL, 13 | .move_log_size = 0, 14 | .move_log_idx = 0, 15 | .move_log_line_chars = 0, 16 | .winner = WINNER_NONE, 17 | .message_box = NULL, 18 | .debug_allow_illegal_moves = false, 19 | .debug_copy_on_move = false, 20 | .threadpool = NULL, 21 | .movegen_started = 0}; 22 | 23 | // Resets the parts of the frontend state used to store 24 | // data about the current game to the default values. 25 | // Deallocating structures if necessary. 26 | // Other data not tied to a specific game, such as the desired game mode 27 | // and debug settings are not changed. 28 | static void reset_ingame_frontend_state() { 29 | if (frontend_state.game_state != NULL) { 30 | deinit_gamestate(frontend_state.game_state); 31 | } 32 | 33 | if (frontend_state.move_log != NULL) { 34 | free(frontend_state.move_log); 35 | } 36 | 37 | frontend_state.selected_position = NULL_BOARDPOS; 38 | frontend_state.move_log = NULL; 39 | frontend_state.move_log_size = 0; 40 | frontend_state.move_log_idx = 0; 41 | frontend_state.move_log_line_chars = 0; 42 | frontend_state.winner = WINNER_NONE; 43 | frontend_state.movegen_started = 0; 44 | tptable_clear(); 45 | } 46 | 47 | // Sets the frontend state to the default values at the start of a chess game, 48 | // deallocating existing structures if neccesary 49 | void frontend_new_game() { 50 | if (frontend_state.threadpool == NULL) { 51 | frontend_state.threadpool = threadpool_init(); 52 | } 53 | 54 | reset_ingame_frontend_state(); 55 | frontend_state.game_state = init_gamestate(); 56 | } 57 | 58 | // Sets the frontend state to defaults with a game state parsed from FEN 59 | // deallocating existing structures if neccesary. 60 | // Returns false if there was an error in the FEN, true otherwise 61 | bool frontend_new_game_from_fen(const char *fen) { 62 | reset_ingame_frontend_state(); 63 | 64 | frontend_state.game_state = fen_to_gamestate(fen); 65 | return frontend_state.game_state != NULL; 66 | } 67 | 68 | // Adds a move to the move log 69 | // Must be called while in a game (frontend_new_game has been called) 70 | void log_move(struct BoardPos from, struct BoardPos to) { 71 | // Extend the move log if needed 72 | if (frontend_state.move_log_size <= frontend_state.move_log_idx + 9) { 73 | frontend_state.move_log_size += 18; 74 | frontend_state.move_log = realloc(frontend_state.move_log, frontend_state.move_log_size); 75 | } 76 | 77 | // Convert the move to a string 78 | char move[5]; 79 | int move_char_count = move_to_str(frontend_state.game_state, from, to, move); 80 | 81 | // If there is not enough space for the move on the current line 82 | // insert a newline 83 | if (frontend_state.move_log_line_chars + move_char_count >= 44) { 84 | frontend_state.move_log[frontend_state.move_log_idx++] = '\n'; 85 | frontend_state.move_log_line_chars = 0; 86 | } 87 | 88 | // Add the move to the log 89 | memcpy(frontend_state.move_log + frontend_state.move_log_idx, move, move_char_count); 90 | frontend_state.move_log_idx += move_char_count; 91 | 92 | // Add a space 93 | frontend_state.move_log[frontend_state.move_log_idx++] = ' '; 94 | frontend_state.move_log_line_chars += move_char_count + 1; 95 | 96 | frontend_state.move_log[frontend_state.move_log_idx] = '\0'; 97 | } -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #ifdef CHESS_TEST 2 | #include "tests/test_main.c" 3 | #endif 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | // Handles input and draws the chess board 25 | // To be called every frame 26 | // Must be called within BeginDrawing/EndDrawing 27 | static void game_loop() { 28 | // Enables the debug keybinds if the 'not debug' automatic cmake definition is undefined or CHESS_ENABLE_DEBUG_KEYS is 29 | // defined 30 | #ifdef DEBUG_SETTINGS_ENABLED 31 | // Toggle the next player to move 32 | if (IsKeyPressed(KEY_T)) { 33 | frontend_state.game_state->white_to_move = !frontend_state.game_state->white_to_move; 34 | } 35 | 36 | // Toggle allowing illegal moves to be made by human players 37 | if (IsKeyPressed(KEY_I)) { 38 | frontend_state.debug_allow_illegal_moves = !frontend_state.debug_allow_illegal_moves; 39 | } 40 | 41 | // Toggle copying the source piece when humans move, instead of removing it from the source 42 | if (IsKeyPressed(KEY_C)) { 43 | frontend_state.debug_copy_on_move = !frontend_state.debug_copy_on_move; 44 | } 45 | 46 | // Toggle the frontend_state.winner state between no frontend_state.winner, white wins, black wins and draw 47 | if (IsKeyPressed(KEY_W)) { 48 | frontend_state.winner = frontend_state.winner == WINNER_DRAW ? WINNER_NONE : frontend_state.winner + 1; 49 | } 50 | 51 | // Toggle between player-vs-player and player-vs-computer game modes 52 | if (IsKeyPressed(KEY_M)) { 53 | frontend_state.two_player_mode = !frontend_state.two_player_mode; 54 | } 55 | 56 | // Print the position value relative to white 57 | if (IsKeyPressed(KEY_V)) { 58 | printf("v: %d\n", position_value(frontend_state.game_state)); 59 | } 60 | 61 | // Print the zobrist hash of the state 62 | if (IsKeyPressed(KEY_Z)) { 63 | printf("z: %" PRIx64 "\n", frontend_state.game_state->hash); 64 | } 65 | 66 | // Print the FEN string of the state 67 | if (IsKeyPressed(KEY_F)) { 68 | char fen[90]; 69 | gamestate_to_fen(frontend_state.game_state, fen); 70 | printf("f: %s\n", fen); 71 | } 72 | 73 | #endif 74 | 75 | // Computer moves 76 | if (frontend_state.winner == WINNER_NONE && !frontend_state.two_player_mode && 77 | !frontend_state.game_state->white_to_move) { 78 | struct TranspositionEntry entry = tptable_get(frontend_state.game_state->hash); 79 | 80 | if (frontend_state.movegen_started == 0) { 81 | // Start generating moves if it wasn't started alrady. 82 | frontend_state.movegen_started = time(NULL); 83 | generate_move(frontend_state.game_state, frontend_state.threadpool, frontend_state.movegen_started); 84 | } else if (entry.depth == CHAR_MAX || time(NULL) - frontend_state.movegen_started >= MAX_MOVEGEN_SEARCH_TIME) { 85 | // Movegen has completed, make the move. 86 | 87 | frontend_state.movegen_started = 0; 88 | printf("[movegen] DONE %d %d\n", entry.depth, entry.value); 89 | 90 | if (!boardpos_eq(entry.best_move.from, NULL_BOARDPOS)) { 91 | // Log and make the move. 92 | log_move(entry.best_move.from, entry.best_move.to); 93 | make_move(frontend_state.game_state, entry.best_move, true); 94 | 95 | // Remove the selected piece if the piece no longer exists. 96 | if (!boardpos_eq(frontend_state.selected_position, NULL_BOARDPOS) && 97 | (get_piece(frontend_state.game_state, frontend_state.selected_position).type == Empty || 98 | boardpos_eq(frontend_state.selected_position, entry.best_move.to))) { 99 | frontend_state.selected_position = NULL_BOARDPOS; 100 | } 101 | 102 | // Set the winner if there was checkmate or stalemate. 103 | if (is_player_checkmated(frontend_state.game_state, WhitePlayer)) { 104 | frontend_state.winner = WINNER_BLACK; 105 | } else if (is_stalemate(frontend_state.game_state)) { 106 | frontend_state.winner = WINNER_DRAW; 107 | } 108 | } 109 | } 110 | } 111 | 112 | // Human moves 113 | if (frontend_state.winner == WINNER_NONE && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { 114 | int x = GetMouseX(); 115 | int y = GetMouseY(); 116 | 117 | if (x < BOARD_SIZE && y < BOARD_SIZE) { 118 | enum Player player_to_move = frontend_state.game_state->white_to_move ? WhitePlayer : BlackPlayer; 119 | struct BoardPos pos = BoardPos(x / BOARD_SQUARE_SIZE, y / BOARD_SQUARE_SIZE); 120 | struct Piece piece = get_piece(frontend_state.game_state, pos); 121 | 122 | // Piece selection and moving 123 | if (piece.type != Empty && ((frontend_state.two_player_mode && piece.player == player_to_move) || 124 | (!frontend_state.two_player_mode && piece.player == WhitePlayer))) { 125 | frontend_state.selected_position = pos; 126 | } else if (!boardpos_eq(frontend_state.selected_position, NULL_BOARDPOS)) { 127 | if (frontend_state.debug_allow_illegal_moves || 128 | is_move_legal(frontend_state.game_state, (struct Move){frontend_state.selected_position, pos})) { 129 | // Add the move to the move log 130 | log_move(frontend_state.selected_position, pos); 131 | 132 | // Make the move 133 | make_move(frontend_state.game_state, (struct Move){frontend_state.selected_position, pos}, true); 134 | 135 | // BUG: doesnt add to piece list 136 | if (frontend_state.debug_copy_on_move) { 137 | put_piece(frontend_state.game_state, get_piece(frontend_state.game_state, pos), 138 | frontend_state.selected_position); 139 | } 140 | 141 | frontend_state.selected_position = NULL_BOARDPOS; 142 | 143 | if (is_player_checkmated(frontend_state.game_state, other_player(player_to_move))) { 144 | frontend_state.winner = player_to_move == WhitePlayer ? WINNER_WHITE : WINNER_BLACK; 145 | } else if (is_stalemate(frontend_state.game_state)) { 146 | frontend_state.winner = WINNER_DRAW; 147 | } 148 | } 149 | } 150 | } 151 | } 152 | 153 | draw_board(frontend_state.game_state); 154 | 155 | // Draw a line on where the computer is thinking of moving 156 | struct TranspositionEntry entry = tptable_get(frontend_state.game_state->hash); 157 | if (!frontend_state.two_player_mode && !frontend_state.game_state->white_to_move && 158 | !boardpos_eq(entry.best_move.from, NULL_BOARDPOS)) { 159 | int x = entry.best_move.from.file * BOARD_SQUARE_SIZE + BOARD_SQUARE_SIZE / 2; 160 | int y = entry.best_move.from.rank * BOARD_SQUARE_SIZE + BOARD_SQUARE_SIZE / 2; 161 | 162 | int x2 = entry.best_move.to.file * BOARD_SQUARE_SIZE + BOARD_SQUARE_SIZE / 2; 163 | int y2 = entry.best_move.to.rank * BOARD_SQUARE_SIZE + BOARD_SQUARE_SIZE / 2; 164 | 165 | DrawLineEx((Vector2){x, y}, (Vector2){x2, y2}, 1.5f, Fade(RED, 0.8f)); 166 | } 167 | 168 | // Draw a circle on the currently selected piece 169 | if (!boardpos_eq(frontend_state.selected_position, NULL_BOARDPOS)) { 170 | struct BoardPos pos = frontend_state.selected_position; 171 | int x = pos.file * BOARD_SQUARE_SIZE; 172 | int y = pos.rank * BOARD_SQUARE_SIZE; 173 | DrawCircle(x + BOARD_SQUARE_SIZE / 2, y + BOARD_SQUARE_SIZE / 2, BOARD_SQUARE_SIZE / 7.0f, 174 | Fade(DARKBLUE, 0.8f)); 175 | } 176 | } 177 | 178 | int main() { 179 | #if defined(CHESS_TEST) 180 | test_main(); 181 | return EXIT_SUCCESS; 182 | #elif !defined(NDEBUG) 183 | puts("DEBUG BUILD!"); 184 | #endif 185 | 186 | srand(time(NULL)); // seed the prng, used for choosing opening book moves 187 | 188 | // Initialise the window 189 | InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "chess"); 190 | SetTargetFPS(15); 191 | SetWindowMinSize(WINDOW_WIDTH, WINDOW_HEIGHT); 192 | GuiSetStyle(DEFAULT, TEXT_SIZE, 16); 193 | 194 | // Load required assets and create a new game 195 | load_textures(); 196 | init_opening_book(); 197 | frontend_new_game(); 198 | tptable_init(); 199 | 200 | while (!WindowShouldClose()) { 201 | BeginDrawing(); 202 | ClearBackground(WHITE); 203 | if (frontend_state.game_state != NULL) game_loop(); 204 | draw_ui(); 205 | EndDrawing(); 206 | } 207 | 208 | // Cleanup 209 | if (frontend_state.game_state != NULL) { 210 | deinit_gamestate(frontend_state.game_state); 211 | } 212 | 213 | if (frontend_state.threadpool != NULL) { 214 | threadpool_deinit(frontend_state.threadpool); 215 | } 216 | 217 | free(frontend_state.move_log); 218 | 219 | deinit_opening_book(); 220 | unload_textures(); 221 | tptable_deinit(); 222 | CloseWindow(); 223 | return EXIT_SUCCESS; 224 | } 225 | -------------------------------------------------------------------------------- /src/openings.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | // Stores the moves in the opening list. 7 | // Implemented as a dynamically allocated array as the number of opening moves in the book is unknown, 8 | // so using a hash table would be difficult and there would be a chance of collisions. 9 | // The items list is binary searched, since the list is sorted by hash. Binary search has 10 | // an O(log n) worst case time complexity which is more than efficient enough for the expected number 11 | // of items (< 1 million). 12 | static struct OpeningItem *items = NULL; 13 | static const unsigned int ITEMS_MAX = INT32_MAX; 14 | static unsigned int items_count = 0; 15 | 16 | // Binary search the opening items, returns NULL if the item is not found. 17 | // High must be < items_count. 18 | static struct OpeningItem *binary_search_items(uint64_t hash, unsigned int low, unsigned int high) { 19 | // Ensure overflow is not possible in debug builds. 20 | assert(high < items_count); 21 | assert(2 * (unsigned int)ITEMS_MAX - 2 <= UINT32_MAX); 22 | 23 | while (low <= high) { 24 | // SAFETY: high and low must be less than (2^31 - 1) as ITEMS_MAX is INT32_MAX (2^31 - 1). 25 | // So (high + low) will be at most (2^32 - 4) which is within the range of an unsigned integer. 26 | unsigned int mid = (high + low) / 2; 27 | if (items[mid].hash == hash) { 28 | return &items[mid]; 29 | } else if (items[mid].hash < hash) { 30 | low = mid + 1; 31 | } else { 32 | high = mid - 1; 33 | } 34 | } 35 | 36 | return NULL; 37 | } 38 | 39 | // Finds a OpeningItem by hash, returns NULL if the hash is not found 40 | // The OpeningItem returned will be deallocated if deinit_opening_book is called 41 | struct OpeningItem *find_opening_by_hash(uint64_t hash) { 42 | if (items_count == 0) 43 | return NULL; 44 | else 45 | return binary_search_items(hash, 0, items_count - 1); 46 | } 47 | 48 | // Converts a big endian u64 to the host byte order 49 | // These functions are reimplemented as they are not standard functions 50 | // available on all platforms. 51 | static uint64_t ntohll(uint64_t n) { 52 | unsigned char *np = (unsigned char *)&n; 53 | return ((uint64_t)np[0] << 56) | ((uint64_t)np[1] << 48) | ((uint64_t)np[2] << 40) | ((uint64_t)np[3] << 32) | 54 | ((uint64_t)np[4] << 24) | ((uint64_t)np[5] << 16) | ((uint64_t)np[6] << 8) | (uint64_t)np[7]; 55 | } 56 | 57 | // Converts a big endian u16 to the host byte order 58 | static uint16_t ntohs(uint16_t n) { 59 | unsigned char *np = (unsigned char *)&n; 60 | return ((uint16_t)np[0] << 8) | (uint16_t)np[1]; 61 | } 62 | 63 | // Initialise the opening book from the opening book file. 64 | // Allocated memory must be deallocated using deinit_opening_book(). 65 | // The book is in Polyglot BIN format: http://hgm.nubati.net/book_format.html 66 | void init_opening_book() { 67 | // The size of the structure used to represent an opening in the book, in bytes. 68 | const size_t ENTRY_SIZE = 16; 69 | 70 | // Each value in the move bit field is 3 bits wide. 71 | const uint16_t MOVE_VALUE_MASK = 0x7; 72 | 73 | // The offsets by which the bit field must be right logically shifted to get the value as the least significant 3 74 | // bits. 75 | const uint16_t TO_FILE_SHIFT = 0; 76 | const uint16_t TO_ROW_SHIFT = 3; 77 | const uint16_t FROM_FILE_SHIFT = 6; 78 | const uint16_t FROM_ROW_SHIFT = 9; 79 | const uint16_t PROMO_PIECE_SHIFT = 12; 80 | 81 | // The values used to represent pawn promotion in the promo_piece value. 82 | // Only queen promotion is handled. 83 | const int PROMO_PIECE_NONE = 0; 84 | const int PROMO_PIECE_QUEEN = 4; 85 | 86 | size_t items_size = 100; 87 | items = malloc(sizeof(struct OpeningItem) * items_size); 88 | 89 | FILE *book_file = fopen("res/opening_book.bin", "rb"); 90 | if (!book_file) { 91 | perror("error initialising opening book"); 92 | exit(EXIT_FAILURE); 93 | } 94 | 95 | char entry[16]; 96 | while (fread(entry, ENTRY_SIZE, 1, book_file) == 1) { 97 | // Stop reading new items if the maximum number was reached. 98 | if (items_count == ITEMS_MAX) { 99 | break; 100 | } 101 | 102 | // Extend the dynamic array if it is full. 103 | if (items_count == items_size) { 104 | items = realloc(items, sizeof(struct OpeningItem) * items_size * 2); 105 | items_size *= 2; 106 | } 107 | 108 | // Load the values from the move entry. 109 | // The first 8 bytes is the Zobrist hash of the position, the next 2 bytes is the response move. 110 | uint64_t hash = ntohll(*(uint64_t *)entry); 111 | uint16_t move = ntohs(*(uint16_t *)(entry + 8)); 112 | int to_file = (move >> TO_FILE_SHIFT) & MOVE_VALUE_MASK; 113 | int to_row = (move >> TO_ROW_SHIFT) & MOVE_VALUE_MASK; 114 | int from_file = (move >> FROM_FILE_SHIFT) & MOVE_VALUE_MASK; 115 | int from_row = (move >> FROM_ROW_SHIFT) & MOVE_VALUE_MASK; 116 | int promo_piece = (move >> PROMO_PIECE_SHIFT) & MOVE_VALUE_MASK; 117 | 118 | // Promoting to pieces other than queen is unsupported. 119 | if (promo_piece != PROMO_PIECE_NONE && promo_piece != PROMO_PIECE_QUEEN) { 120 | continue; 121 | } 122 | 123 | // Row 0 is the bottom in the Polyglot BIN format (backwards compared to the scheme used for BoardPos). 124 | struct BoardPos from = BoardPos(from_file, 7 - from_row); 125 | struct BoardPos to = BoardPos(to_file, 7 - to_row); 126 | struct Move actual_move = {from, to}; 127 | 128 | if (items_count != 0 && items[items_count - 1].hash == hash) { 129 | struct OpeningItem *item = &items[items_count - 1]; 130 | 131 | // The move count is stored as a uint8, so a max 132 | // of 255 moves per hash can be stored 133 | if (item->moves_count == UINT8_MAX) { 134 | continue; 135 | } 136 | 137 | item->moves_count += 1; 138 | item->moves = realloc(item->moves, sizeof(struct Move) * item->moves_count); 139 | item->moves[item->moves_count - 1] = actual_move; 140 | } else { 141 | struct OpeningItem item = {.hash = hash, .moves = NULL, .moves_count = 1}; 142 | item.moves = malloc(sizeof(struct Move)); 143 | item.moves[0] = actual_move; 144 | items[items_count++] = item; 145 | } 146 | } 147 | 148 | fclose(book_file); 149 | 150 | // If no items were found in the book it is invalid. 151 | if (items_count == 0) { 152 | fprintf(stderr, "error: invalid opening book file"); 153 | exit(EXIT_FAILURE); 154 | } 155 | 156 | // Shrink the items array down to the minimum size. 157 | items = realloc(items, sizeof(struct OpeningItem) * items_count); 158 | } 159 | 160 | // Deinitialise and deallocate the opening book currently loaded 161 | void deinit_opening_book() { 162 | for (unsigned int i = 0; i < items_count; i++) { 163 | free(items[i].moves); 164 | } 165 | 166 | free(items); 167 | items = NULL; 168 | items_count = 0; 169 | } 170 | -------------------------------------------------------------------------------- /src/raygui.c: -------------------------------------------------------------------------------- 1 | #define RAYGUI_IMPLEMENTATION 2 | #include 3 | -------------------------------------------------------------------------------- /src/tests/test_engine.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | static int test_is_piece_attacked_King() { 6 | struct BoardPos white_piece = BoardPos(4, 7); 7 | struct BoardPos black_king = BoardPos(4, 6); 8 | 9 | struct GameState *state = init_gamestate(); 10 | bool r1 = is_piece_attacked(state, white_piece, BlackPlayer); 11 | put_piece(state, Piece(King, BlackPlayer), black_king); 12 | bool r2 = is_piece_attacked(state, white_piece, BlackPlayer); 13 | 14 | deinit_gamestate(state); 15 | 16 | TEST_ASSERT(!r1, "expected is_piece_attacked() == false, got true"); 17 | TEST_ASSERT(r2, "expected is_piece_attacked() == true, got false"); 18 | return 0; 19 | } 20 | 21 | static int test_is_piece_attacked_Queen() { 22 | struct BoardPos white_piece = BoardPos(2, 4); 23 | struct BoardPos black_queen = BoardPos(4, 6); 24 | 25 | struct GameState *state = init_gamestate(); 26 | bool r1 = is_piece_attacked(state, white_piece, BlackPlayer); 27 | put_piece(state, Piece(Queen, BlackPlayer), black_queen); 28 | bool r2 = is_piece_attacked(state, white_piece, BlackPlayer); 29 | 30 | deinit_gamestate(state); 31 | 32 | TEST_ASSERT(!r1, "expected is_piece_attacked() == false, got true"); 33 | TEST_ASSERT(r2, "expected is_piece_attacked() == true, got false"); 34 | return 0; 35 | } 36 | 37 | static int test_is_piece_attacked_Rook() { 38 | struct BoardPos white_piece = BoardPos(2, 4); 39 | struct BoardPos black_rook = BoardPos(2, 6); 40 | 41 | struct GameState *state = init_gamestate(); 42 | bool r1 = is_piece_attacked(state, white_piece, BlackPlayer); 43 | put_piece(state, Piece(Rook, BlackPlayer), black_rook); 44 | bool r2 = is_piece_attacked(state, white_piece, BlackPlayer); 45 | 46 | deinit_gamestate(state); 47 | 48 | TEST_ASSERT(!r1, "expected is_piece_attacked() == false, got true"); 49 | TEST_ASSERT(r2, "expected is_piece_attacked() == true, got false"); 50 | return 0; 51 | } 52 | 53 | static int test_is_piece_attacked_Bishop() { 54 | struct BoardPos white_piece = BoardPos(2, 4); 55 | struct BoardPos black_bishop = BoardPos(4, 6); 56 | 57 | struct GameState *state = init_gamestate(); 58 | bool r1 = is_piece_attacked(state, white_piece, BlackPlayer); 59 | put_piece(state, Piece(Bishop, BlackPlayer), black_bishop); 60 | bool r2 = is_piece_attacked(state, white_piece, BlackPlayer); 61 | 62 | deinit_gamestate(state); 63 | 64 | TEST_ASSERT(!r1, "expected is_piece_attacked() == false, got true"); 65 | TEST_ASSERT(r2, "expected is_piece_attacked() == true, got false"); 66 | return 0; 67 | } 68 | 69 | static int test_is_piece_attacked_Knight() { 70 | struct BoardPos white_piece = BoardPos(2, 4); 71 | struct BoardPos black_knight = BoardPos(3, 6); 72 | 73 | struct GameState *state = init_gamestate(); 74 | bool r1 = is_piece_attacked(state, white_piece, BlackPlayer); 75 | put_piece(state, Piece(Knight, BlackPlayer), black_knight); 76 | bool r2 = is_piece_attacked(state, white_piece, BlackPlayer); 77 | 78 | deinit_gamestate(state); 79 | 80 | TEST_ASSERT(!r1, "expected is_piece_attacked() == false, got true"); 81 | TEST_ASSERT(r2, "expected is_piece_attacked() == true, got false"); 82 | return 0; 83 | } 84 | 85 | DEF_TEST_MODULE(engine, test_is_piece_attacked_King, test_is_piece_attacked_Queen, test_is_piece_attacked_Rook, 86 | test_is_piece_attacked_Bishop, test_is_piece_attacked_Knight); 87 | -------------------------------------------------------------------------------- /src/tests/test_fen.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | static int test_parse_normal() { 6 | static const char *test_string = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 0"; 7 | 8 | struct GameState *state = fen_to_gamestate(test_string); 9 | bool is_null = state == NULL; 10 | if (!is_null) { 11 | TEST_ASSERT(hash_state(state) == 0x463b96181691fc9cULL, 12 | "zobrist hash of the state parsed from FEN does not match expected value"); 13 | deinit_gamestate(state); 14 | } 15 | 16 | TEST_ASSERT(!is_null, "unexpected error in fen_to_gamestate"); 17 | 18 | return 0; 19 | } 20 | 21 | static int test_serialize_normal() { 22 | struct GameState *starting_state = init_gamestate(); 23 | char fen[90]; 24 | gamestate_to_fen(starting_state, fen); 25 | deinit_gamestate(starting_state); 26 | TEST_ASSERT(strncmp(fen, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 0", 57) == 0, 27 | "the serialised FEN of the starting GameState does not match the expected value"); 28 | return 0; 29 | } 30 | 31 | DEF_TEST_MODULE(fen, test_parse_normal, test_serialize_normal); 32 | -------------------------------------------------------------------------------- /src/tests/test_main.c: -------------------------------------------------------------------------------- 1 | // Used for unit testing in /src/tests/* when CHESS_TEST is defined 2 | 3 | #include 4 | #include 5 | 6 | // A test function runs a test and returns an int, 7 | // 0 indicating the test passed and -1 indicating it failed 8 | typedef int (*TestFunction)(void); 9 | 10 | // Defines a new testing module (`mod`) containing the test functions in the 11 | // following arguments (must be at least 1) 12 | #define DEF_TEST_MODULE(mod, ...) static TestFunction _test_##mod##__TESTS[] = {__VA_ARGS__, NULL} 13 | 14 | // Runs all tests in a module (`mod`), gets the list of tests from the variables 15 | // set by DEF_TEST_MODULE 16 | #define RUN_TEST_MODULE(mod) \ 17 | do { \ 18 | puts("Running tests in `" #mod "`"); \ 19 | _test_run_test_module(_test_##mod##__TESTS); \ 20 | } while (0) 21 | 22 | // Used by tests to assert that `cond` must be true. 23 | // If it is not true, the test fails 24 | #define TEST_ASSERT(cond, msg) \ 25 | if (!(cond)) { \ 26 | printf("\t[x] %s failed: %s\n", __func__, (msg)); \ 27 | return -1; \ 28 | } 29 | 30 | // Helper function used in RUN_TEST_MODULE 31 | static void _test_run_test_module(TestFunction *tests) { 32 | int passed = 0; 33 | int i; 34 | for (i = 0; tests[i] != NULL; i++) { 35 | passed += tests[i]() == 0; 36 | } 37 | 38 | printf("\t%d/%d tests passed\n", passed, i); 39 | } 40 | 41 | #include "test_engine.c" 42 | #include "test_fen.c" 43 | 44 | void test_main() { 45 | RUN_TEST_MODULE(engine); 46 | RUN_TEST_MODULE(fen); 47 | } 48 | -------------------------------------------------------------------------------- /src/threadpool.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) 9 | #include 10 | #endif 11 | 12 | // Returns the number of logical cores on systems supporting sysconf(_SC_NPROCESSORS_ONLN), or 1 otherwise. 13 | static unsigned long nproc() { 14 | char *count = getenv("CHESS_NPROC"); 15 | if (count != NULL) { 16 | return MAX(atol(count), 1); 17 | } else { 18 | #ifdef _SC_NPROCESSORS_ONLN 19 | long onl = sysconf(_SC_NPROCESSORS_ONLN); 20 | return MAX(1, onl); 21 | #endif 22 | return 1; 23 | } 24 | } 25 | 26 | #ifdef HAS_C11_CONCURRENCY 27 | static void check_err(int ret) { 28 | if (ret != thrd_success) { 29 | puts("threadpool c11threads error"); 30 | exit(EXIT_FAILURE); 31 | } 32 | } 33 | #endif 34 | 35 | // Enqueues a task. Returns false if the queue is full. 36 | // Thread safe 37 | #ifdef HAS_C11_CONCURRENCY 38 | static bool enqueue(struct ThreadPool *pool, struct Task task) { 39 | check_err(mtx_lock(&pool->mutex)); 40 | 41 | if (pool->queue_back == pool->queue_front) { 42 | check_err(mtx_unlock(&pool->mutex)); 43 | return false; 44 | } 45 | 46 | size_t inserted_at = pool->queue_back; 47 | pool->queue[pool->queue_back] = task; 48 | pool->queue_back = (pool->queue_back + 1) % THREADPOOL_QUEUE_SIZE; 49 | 50 | if (pool->queue_front == THREADPOOL_QUEUE_SIZE) { 51 | pool->queue_front = inserted_at; 52 | } 53 | 54 | check_err(mtx_unlock(&pool->mutex)); 55 | return true; 56 | } 57 | #endif 58 | 59 | // Dequeues a task. Returns false if the queue is empty. 60 | // Caller must lock the mutex. 61 | #ifdef HAS_C11_CONCURRENCY 62 | static bool dequeue_already_locked(struct ThreadPool *pool, struct Task *task) { 63 | if (pool->queue_back == pool->queue_front || pool->queue_front == THREADPOOL_QUEUE_SIZE) { 64 | return false; 65 | } 66 | 67 | *task = pool->queue[pool->queue_front]; 68 | pool->queue_front = (pool->queue_front + 1) % THREADPOOL_QUEUE_SIZE; 69 | 70 | if (pool->queue_back == pool->queue_front) { 71 | pool->queue_front = THREADPOOL_QUEUE_SIZE; 72 | } 73 | 74 | return true; 75 | } 76 | #endif 77 | 78 | #ifdef HAS_C11_CONCURRENCY 79 | static int thread_start_func(struct ThreadPool *pool) { 80 | for (;;) { 81 | check_err(mtx_lock(&pool->mutex)); 82 | 83 | struct Task task; 84 | if (dequeue_already_locked(pool, &task)) { 85 | goto got_task; 86 | } else { 87 | check_err(cnd_wait(&pool->task_available, &pool->mutex)); 88 | } 89 | 90 | if (!dequeue_already_locked(pool, &task)) { 91 | // Should never happen 92 | check_err(mtx_unlock(&pool->mutex)); 93 | break; 94 | } 95 | 96 | got_task: 97 | check_err(mtx_unlock(&pool->mutex)); 98 | 99 | // Return value of false is a stop request. 100 | if (!task.func(task.arg)) { 101 | break; 102 | } 103 | } 104 | 105 | atomic_fetch_sub(&pool->thread_count, 1); 106 | 107 | return 0; 108 | } 109 | #endif 110 | 111 | static bool task_stop(void *_arg) { 112 | (void)_arg; 113 | return false; 114 | } 115 | 116 | // Terminates all threads in the pool 117 | // Must be called on the thread which created the pool 118 | #ifdef HAS_C11_CONCURRENCY 119 | static void threadpool_stop(struct ThreadPool *pool) { 120 | struct Task stop = {.func = task_stop, .arg = NULL}; 121 | 122 | while (atomic_load(&pool->thread_count) > 0) { 123 | while (!enqueue(pool, stop)) { 124 | } 125 | check_err(cnd_signal(&pool->task_available)); 126 | } 127 | } 128 | #endif 129 | 130 | // Creates a new ThreadPool. 131 | // Must be deallocated with threadpool_deinit 132 | struct ThreadPool *threadpool_init() { 133 | struct ThreadPool *pool = malloc(sizeof(*pool)); 134 | 135 | pool->queue_front = THREADPOOL_QUEUE_SIZE; 136 | pool->queue_back = 0; 137 | 138 | #ifdef HAS_C11_CONCURRENCY 139 | check_err(mtx_init(&pool->mutex, mtx_plain)); 140 | check_err(cnd_init(&pool->task_available)); 141 | 142 | size_t thread_count = nproc(); 143 | atomic_init(&pool->thread_count, thread_count); 144 | 145 | printf("[threadpool] Starting %zu threads. To change set CHESS_NPROC environment variable.\n", thread_count); 146 | 147 | for (size_t i = 0; i < thread_count; i++) { 148 | thrd_t thread; 149 | check_err(thrd_create(&thread, (thrd_start_t)thread_start_func, pool)); 150 | check_err(thrd_detach(thread)); 151 | } 152 | #else 153 | printf("[threadpool] Compiled without C11 threads support. Thread pool will not be used.\n"); 154 | pool->thread_count = 0; 155 | #endif 156 | 157 | return pool; 158 | } 159 | 160 | // Enqueues a task on the thread pool. 161 | // If threading is unsupported or the queue is full 162 | // the task is immediately executed. 163 | // Thread safe. 164 | void threadpool_enqueue(struct ThreadPool *pool, TaskFunc func, void *arg) { 165 | #ifdef HAS_C11_CONCURRENCY 166 | struct Task task = {.func = func, .arg = arg}; 167 | 168 | if (atomic_load(&pool->thread_count) == 0 || !enqueue(pool, task)) { 169 | func(arg); 170 | } else { 171 | check_err(cnd_signal(&pool->task_available)); 172 | } 173 | #else 174 | func(arg); 175 | #endif 176 | } 177 | 178 | // Deallocates the pool and terminates all threads. 179 | // Must be called on the thread which created the pool 180 | void threadpool_deinit(struct ThreadPool *pool) { 181 | #ifdef HAS_C11_CONCURRENCY 182 | threadpool_stop(pool); 183 | mtx_destroy(&pool->mutex); 184 | cnd_destroy(&pool->task_available); 185 | #endif 186 | 187 | free(pool); 188 | } 189 | 190 | // Returns a new atomic counter with value `val`. 191 | // The counter must be freed. 192 | struct AtomicCounter *acnt_init(unsigned short val) { 193 | struct AtomicCounter *c = malloc(sizeof(*c)); 194 | 195 | #ifdef HAS_C11_CONCURRENCY 196 | atomic_init(&c->count, val); 197 | #else 198 | c->count = val; 199 | #endif 200 | 201 | return c; 202 | } 203 | 204 | // Decrements an atomic counter. 205 | // Returns true if the value is now zero. 206 | bool acnt_dec(struct AtomicCounter *counter) { 207 | #ifdef HAS_C11_CONCURRENCY 208 | return atomic_fetch_sub(&counter->count, 1) == 1; 209 | #else 210 | return counter->count-- == 1; 211 | #endif 212 | } -------------------------------------------------------------------------------- /src/tptable.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // The hash table used to store the entries 10 | static struct TranspositionEntry tp_table[TRANSPOSITION_TABLE_SIZE] = {0}; 11 | static ZobristHash protected_hash = 0; 12 | 13 | #ifdef HAS_C11_CONCURRENCY 14 | static mtx_t tp_table_mutex; 15 | #endif 16 | 17 | #ifdef HAS_C11_CONCURRENCY 18 | static void check_err(int ret) { 19 | if (ret != thrd_success) { 20 | puts("tptable c11threads error"); 21 | exit(EXIT_FAILURE); 22 | } 23 | } 24 | #endif 25 | 26 | void tptable_init() { 27 | #ifdef HAS_C11_CONCURRENCY 28 | check_err(mtx_init(&tp_table_mutex, mtx_plain)); 29 | #endif 30 | } 31 | 32 | void tptable_deinit() { 33 | #ifdef HAS_C11_CONCURRENCY 34 | mtx_destroy(&tp_table_mutex); 35 | #endif 36 | } 37 | 38 | // Get a move from the transpoition table by a zobrist hash of the state 39 | // Returns a Move with NULL_BOARDPOS as the `from` and 0 as the depth if there is no move for the 40 | // specified hash 41 | struct TranspositionEntry tptable_get(ZobristHash hash) { 42 | #ifdef HAS_C11_CONCURRENCY 43 | check_err(mtx_lock(&tp_table_mutex)); 44 | #endif 45 | 46 | struct TranspositionEntry t = tp_table[hash % TRANSPOSITION_TABLE_SIZE]; 47 | if (t.hash != hash) { 48 | t = (struct TranspositionEntry){.best_move = (struct Move){NULL_BOARDPOS, NULL_BOARDPOS}, 49 | .depth = 0, 50 | .hash = 0, 51 | .type = EntryTypeExact, 52 | .value = 0}; 53 | } 54 | 55 | #ifdef HAS_C11_CONCURRENCY 56 | check_err(mtx_unlock(&tp_table_mutex)); 57 | #endif 58 | 59 | return t; 60 | } 61 | 62 | // Put a move into the transposition table, 63 | // Replacing an existing move if this move is from a greater or equal `depth`, or if it adds a best move. 64 | void tptable_put(struct TranspositionEntry entry) { 65 | #ifdef HAS_C11_CONCURRENCY 66 | check_err(mtx_lock(&tp_table_mutex)); 67 | #endif 68 | 69 | struct TranspositionEntry prev = tp_table[entry.hash % TRANSPOSITION_TABLE_SIZE]; 70 | if ((prev.hash != protected_hash && entry.hash != prev.hash) || 71 | (prev.hash == entry.hash && prev.depth <= entry.depth)) { 72 | tp_table[entry.hash % TRANSPOSITION_TABLE_SIZE] = entry; 73 | } 74 | 75 | #ifdef HAS_C11_CONCURRENCY 76 | check_err(mtx_unlock(&tp_table_mutex)); 77 | #endif 78 | } 79 | 80 | // Clears all entries from the transposition table 81 | void tptable_clear() { 82 | #ifdef HAS_C11_CONCURRENCY 83 | check_err(mtx_lock(&tp_table_mutex)); 84 | #endif 85 | 86 | memset((void*)tp_table, 0, TRANSPOSITION_TABLE_SIZE * sizeof(struct TranspositionEntry)); 87 | 88 | #ifdef HAS_C11_CONCURRENCY 89 | check_err(mtx_unlock(&tp_table_mutex)); 90 | #endif 91 | } 92 | 93 | // The entry for a protected hash can only be replaced by an entry with the same hash. 94 | // Usually if (hash % TRANSPOSITION_TABLE_SIZE) is the same for two hashes one will replace the other. 95 | // If the entry for hash has another hash in it the entry will be reset with the inputted hash. 96 | void tptable_set_protected_hash(ZobristHash hash) { 97 | #ifdef HAS_C11_CONCURRENCY 98 | check_err(mtx_lock(&tp_table_mutex)); 99 | #endif 100 | 101 | protected_hash = hash; 102 | struct TranspositionEntry entry = tp_table[hash % TRANSPOSITION_TABLE_SIZE]; 103 | if (entry.hash != hash) { 104 | entry.hash = hash; 105 | entry.depth = 0; 106 | entry.value = 0; 107 | entry.best_move = (struct Move){NULL_BOARDPOS, NULL_BOARDPOS}; 108 | tp_table[hash % TRANSPOSITION_TABLE_SIZE] = entry; 109 | } 110 | 111 | #ifdef HAS_C11_CONCURRENCY 112 | check_err(mtx_unlock(&tp_table_mutex)); 113 | #endif 114 | } -------------------------------------------------------------------------------- /src/ui.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // Draws the UI onto the window 10 | // Must be called within BeginDrawing/EndDrawing 11 | void draw_ui() { 12 | static const int LABEL_WIDTH = (int)((3.0f / 7.0f) * (WINDOW_WIDTH - BOARD_SIZE)); 13 | static const int BUTTON_WIDTH = (int)((2.0f / 7.0f) * (WINDOW_WIDTH - BOARD_SIZE)); 14 | 15 | // Draw borders around the grouped buttons & labels 16 | DrawRectangleLinesEx((Rectangle){BOARD_SIZE, 0, WINDOW_WIDTH - BOARD_SIZE, 30}, 2, GRAY); 17 | DrawRectangleLinesEx((Rectangle){BOARD_SIZE, 30, WINDOW_WIDTH - BOARD_SIZE, 30}, 2, GRAY); 18 | 19 | // Draw the game type labels 20 | GuiLabel((Rectangle){BOARD_SIZE, 0, LABEL_WIDTH, 30}, " Two player mode"); 21 | GuiLabel((Rectangle){BOARD_SIZE, 30, LABEL_WIDTH, 30}, " Computer mode"); 22 | 23 | // Draw the new two player game button 24 | if (GuiButton((Rectangle){BOARD_SIZE + LABEL_WIDTH, 0, BUTTON_WIDTH, 30}, "New Game")) { 25 | frontend_state.two_player_mode = true; 26 | frontend_new_game(); 27 | } 28 | 29 | // Draw the load two player game button 30 | if (GuiButton((Rectangle){BOARD_SIZE + LABEL_WIDTH + BUTTON_WIDTH, 0, BUTTON_WIDTH, 30}, "Load Game")) { 31 | const char *fen = GetClipboardText(); 32 | if (fen == NULL || !frontend_new_game_from_fen(fen)) { 33 | // If the provided FEN is invalid display a message notifying the user 34 | // and start a default game 35 | frontend_state.message_box = 36 | "An invalid game was provided.\nMake sure a valid FEN string is copied to the clipboard."; 37 | frontend_new_game(); 38 | } 39 | 40 | frontend_state.two_player_mode = true; 41 | } 42 | 43 | // Draw the save game button, only if there is a game ongoing 44 | if (frontend_state.game_state != NULL && 45 | GuiButton((Rectangle){BOARD_SIZE, WINDOW_HEIGHT - 30, WINDOW_WIDTH - BOARD_SIZE, 30}, "Save game")) { 46 | char fen[90]; 47 | gamestate_to_fen(frontend_state.game_state, fen); 48 | SetClipboardText(fen); 49 | frontend_state.message_box = 50 | "The game was saved to the clipboard.\nYou can paste it where you like so that you can reload it later."; 51 | } 52 | 53 | // Draw the new computer game button 54 | if (GuiButton((Rectangle){BOARD_SIZE + LABEL_WIDTH, 30, BUTTON_WIDTH, 30}, "New Game")) { 55 | frontend_state.two_player_mode = false; 56 | frontend_new_game(); 57 | } 58 | 59 | // Draw the load computer game button 60 | if (GuiButton((Rectangle){BOARD_SIZE + LABEL_WIDTH + BUTTON_WIDTH, 30, BUTTON_WIDTH, 30}, "Load Game")) { 61 | const char *fen = GetClipboardText(); 62 | if (fen == NULL || !frontend_new_game_from_fen(fen)) { 63 | // If the provided FEN is invalid display a message notifying the user 64 | // and start a default game 65 | frontend_state.message_box = 66 | "An invalid game was provided.\nMake sure a valid game string is copied to the clipboard."; 67 | frontend_new_game(); 68 | } 69 | 70 | frontend_state.two_player_mode = false; 71 | } 72 | 73 | // Draw the move log text 74 | if (frontend_state.move_log_idx != 0) { 75 | GuiTextBoxMulti((Rectangle){BOARD_SIZE, BOARD_SQUARE_SIZE * 2, WINDOW_WIDTH - BOARD_SIZE, 0}, 76 | frontend_state.move_log, 2, false); 77 | } 78 | 79 | // Draw the game over winner/draw text or the winner prediction 80 | if (frontend_state.winner != -1) { 81 | char label_s[23] = "Game over! White wins"; 82 | if (frontend_state.winner == 1) { 83 | memcpy(label_s + 11, "Black", 5); 84 | } else if (frontend_state.winner == 2) { 85 | memcpy(label_s + 11, "Draw", 5); 86 | } 87 | 88 | GuiLabel((Rectangle){BOARD_SIZE + 5, 60, WINDOW_WIDTH - BOARD_SIZE, 30}, label_s); 89 | } else { 90 | int value = position_value(frontend_state.game_state); 91 | 92 | // Limit certainty to 95% since this is a really bad way of predicting... 93 | int certainty = 100.0f * (float)abs(value) / (float)ROUGH_MAX_POSITION_VALUE; 94 | certainty = certainty > 95 ? 95 : certainty; 95 | 96 | const char *winner = "Unknown"; 97 | if (value > 0 && certainty != 0) { 98 | winner = "White"; 99 | } else if (value < 0 && certainty != 0) { 100 | winner = "Black"; 101 | } 102 | 103 | // "Predicted winner: xxxxxxx (XX% certainty)" 104 | char label[51] = {0}; 105 | snprintf(label, 51, "Predicted winner: %s (%d%% certainty)", winner, certainty); 106 | 107 | // Don't bother saying 0% certainty of unknown winner 108 | if (value == 0 || certainty == 0) { 109 | label[25] = '\0'; 110 | } 111 | 112 | GuiLabel((Rectangle){BOARD_SIZE + 5, 60, WINDOW_WIDTH - BOARD_SIZE, 30}, label); 113 | } 114 | 115 | // Draw a message box if there is a message 116 | if (frontend_state.message_box != NULL) { 117 | if (GuiMessageBox((Rectangle){WINDOW_WIDTH / 5, WINDOW_HEIGHT / 4, (WINDOW_WIDTH * 3) / 5, WINDOW_HEIGHT / 2}, 118 | "Info", frontend_state.message_box, "Close") != -1) { 119 | // Remove the message if the box was closed 120 | frontend_state.message_box = NULL; 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/zobrist.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Defined later in this file. 4 | // NOTE redefinition like this is illegal in C++ 5 | static const uint64_t ZOBRIST_RANDOMS[781]; 6 | 7 | // Convert a Piece struct to its representation in polyglot books 8 | static inline int piece_to_polyglot(struct Piece piece) { 9 | return 2 * (6 - piece.type) + (piece.player == WhitePlayer ? 1 : 0); 10 | } 11 | 12 | // Compute the Zobrist hash of a GameState. 13 | ZobristHash hash_state(struct GameState *state) { 14 | // Constants used to index the Zobrist randoms. 15 | static const int PIECES = 0; 16 | static const int WHITE_TO_MOVE = 780; 17 | static const int WHITE_CASTLERT_RIGHT = 768; 18 | static const int WHITE_CASTLERT_LEFT = 769; 19 | static const int BLACK_CASTLERT_RIGHT = 770; 20 | static const int BLACK_CASTLERT_LEFT = 771; 21 | static const int ENPASSANT = 772; 22 | 23 | ZobristHash hash = 0; 24 | 25 | // Add pieces to hash 26 | for (int i = 0; i < 16; i++) { 27 | if (!boardpos_eq(state->piece_list_white[i], NULL_BOARDPOS)) { 28 | struct BoardPos pos = state->piece_list_white[i]; 29 | struct Piece p = get_piece(state, pos); 30 | hash ^= ZOBRIST_RANDOMS[PIECES + 64 * piece_to_polyglot(p) + 8 * (7 - pos.rank) + pos.file]; 31 | } 32 | 33 | if (!boardpos_eq(state->piece_list_black[i], NULL_BOARDPOS)) { 34 | struct BoardPos pos = state->piece_list_black[i]; 35 | struct Piece p = get_piece(state, pos); 36 | hash ^= ZOBRIST_RANDOMS[PIECES + 64 * piece_to_polyglot(p) + 8 * (7 - pos.rank) + pos.file]; 37 | } 38 | } 39 | 40 | // Add side to move 41 | if (state->white_to_move) hash ^= ZOBRIST_RANDOMS[WHITE_TO_MOVE]; 42 | 43 | // Add castling rights 44 | if (state->white_castlert_right) hash ^= ZOBRIST_RANDOMS[WHITE_CASTLERT_RIGHT]; 45 | if (state->white_castlert_left) hash ^= ZOBRIST_RANDOMS[WHITE_CASTLERT_LEFT]; 46 | if (state->black_castlert_right) hash ^= ZOBRIST_RANDOMS[BLACK_CASTLERT_RIGHT]; 47 | if (state->black_castlert_left) hash ^= ZOBRIST_RANDOMS[BLACK_CASTLERT_LEFT]; 48 | 49 | // Add en passant file 50 | enum Player to_move = state->white_to_move ? WhitePlayer : BlackPlayer; 51 | int enp_file = get_enpassant_target_file(state, to_move); 52 | int enp_rank = state->white_to_move ? 3 : 4; 53 | if (enp_file != -1) { 54 | // Make sure we stop looking after we have already 55 | // found a pawn and XORed the enpassant value. 56 | bool got_pawn = false; 57 | // Check the file after the enpassant file 58 | if (enp_file != 7) { 59 | struct Piece p = get_piece(state, BoardPos(enp_file + 1, enp_rank)); 60 | if (p.type != Empty && p.player == to_move) { 61 | hash ^= ZOBRIST_RANDOMS[ENPASSANT + enp_file]; 62 | got_pawn = true; 63 | } 64 | } 65 | 66 | // Check the file before the enpassant file 67 | if (!got_pawn && enp_file != 0) { 68 | struct Piece p = get_piece(state, BoardPos(enp_file - 1, enp_rank)); 69 | if (p.type != Empty && p.player == to_move) { 70 | hash ^= ZOBRIST_RANDOMS[ENPASSANT + enp_file]; 71 | } 72 | } 73 | } 74 | 75 | return hash; 76 | } 77 | 78 | // clang-format off 79 | // polyglot format zobrist hashes 80 | // https://www.chessprogramming.org/Zobrist_Hashing 81 | // http://hgm.nubati.net/book_format.html 82 | // first 768 keys for each piece at each square, 83 | // next four keys indicate castling rights (white right, white left, black right, black left) 84 | // next eight keys indicate enpassant files (not target files, only if the player to move has a pawn next to an enemy pawn that just double pushed) 85 | // next & last key indicates if the side to move is white 86 | static const uint64_t ZOBRIST_RANDOMS[781] = 87 | { 88 | 0x9D39247E33776D41ULL, 0x2AF7398005AAA5C7ULL, 0x44DB015024623547ULL, 0x9C15F73E62A76AE2ULL, 89 | 0x75834465489C0C89ULL, 0x3290AC3A203001BFULL, 0x0FBBAD1F61042279ULL, 0xE83A908FF2FB60CAULL, 90 | 0x0D7E765D58755C10ULL, 0x1A083822CEAFE02DULL, 0x9605D5F0E25EC3B0ULL, 0xD021FF5CD13A2ED5ULL, 91 | 0x40BDF15D4A672E32ULL, 0x011355146FD56395ULL, 0x5DB4832046F3D9E5ULL, 0x239F8B2D7FF719CCULL, 92 | 0x05D1A1AE85B49AA1ULL, 0x679F848F6E8FC971ULL, 0x7449BBFF801FED0BULL, 0x7D11CDB1C3B7ADF0ULL, 93 | 0x82C7709E781EB7CCULL, 0xF3218F1C9510786CULL, 0x331478F3AF51BBE6ULL, 0x4BB38DE5E7219443ULL, 94 | 0xAA649C6EBCFD50FCULL, 0x8DBD98A352AFD40BULL, 0x87D2074B81D79217ULL, 0x19F3C751D3E92AE1ULL, 95 | 0xB4AB30F062B19ABFULL, 0x7B0500AC42047AC4ULL, 0xC9452CA81A09D85DULL, 0x24AA6C514DA27500ULL, 96 | 0x4C9F34427501B447ULL, 0x14A68FD73C910841ULL, 0xA71B9B83461CBD93ULL, 0x03488B95B0F1850FULL, 97 | 0x637B2B34FF93C040ULL, 0x09D1BC9A3DD90A94ULL, 0x3575668334A1DD3BULL, 0x735E2B97A4C45A23ULL, 98 | 0x18727070F1BD400BULL, 0x1FCBACD259BF02E7ULL, 0xD310A7C2CE9B6555ULL, 0xBF983FE0FE5D8244ULL, 99 | 0x9F74D14F7454A824ULL, 0x51EBDC4AB9BA3035ULL, 0x5C82C505DB9AB0FAULL, 0xFCF7FE8A3430B241ULL, 100 | 0x3253A729B9BA3DDEULL, 0x8C74C368081B3075ULL, 0xB9BC6C87167C33E7ULL, 0x7EF48F2B83024E20ULL, 101 | 0x11D505D4C351BD7FULL, 0x6568FCA92C76A243ULL, 0x4DE0B0F40F32A7B8ULL, 0x96D693460CC37E5DULL, 102 | 0x42E240CB63689F2FULL, 0x6D2BDCDAE2919661ULL, 0x42880B0236E4D951ULL, 0x5F0F4A5898171BB6ULL, 103 | 0x39F890F579F92F88ULL, 0x93C5B5F47356388BULL, 0x63DC359D8D231B78ULL, 0xEC16CA8AEA98AD76ULL, 104 | 0x5355F900C2A82DC7ULL, 0x07FB9F855A997142ULL, 0x5093417AA8A7ED5EULL, 0x7BCBC38DA25A7F3CULL, 105 | 0x19FC8A768CF4B6D4ULL, 0x637A7780DECFC0D9ULL, 0x8249A47AEE0E41F7ULL, 0x79AD695501E7D1E8ULL, 106 | 0x14ACBAF4777D5776ULL, 0xF145B6BECCDEA195ULL, 0xDABF2AC8201752FCULL, 0x24C3C94DF9C8D3F6ULL, 107 | 0xBB6E2924F03912EAULL, 0x0CE26C0B95C980D9ULL, 0xA49CD132BFBF7CC4ULL, 0xE99D662AF4243939ULL, 108 | 0x27E6AD7891165C3FULL, 0x8535F040B9744FF1ULL, 0x54B3F4FA5F40D873ULL, 0x72B12C32127FED2BULL, 109 | 0xEE954D3C7B411F47ULL, 0x9A85AC909A24EAA1ULL, 0x70AC4CD9F04F21F5ULL, 0xF9B89D3E99A075C2ULL, 110 | 0x87B3E2B2B5C907B1ULL, 0xA366E5B8C54F48B8ULL, 0xAE4A9346CC3F7CF2ULL, 0x1920C04D47267BBDULL, 111 | 0x87BF02C6B49E2AE9ULL, 0x092237AC237F3859ULL, 0xFF07F64EF8ED14D0ULL, 0x8DE8DCA9F03CC54EULL, 112 | 0x9C1633264DB49C89ULL, 0xB3F22C3D0B0B38EDULL, 0x390E5FB44D01144BULL, 0x5BFEA5B4712768E9ULL, 113 | 0x1E1032911FA78984ULL, 0x9A74ACB964E78CB3ULL, 0x4F80F7A035DAFB04ULL, 0x6304D09A0B3738C4ULL, 114 | 0x2171E64683023A08ULL, 0x5B9B63EB9CEFF80CULL, 0x506AACF489889342ULL, 0x1881AFC9A3A701D6ULL, 115 | 0x6503080440750644ULL, 0xDFD395339CDBF4A7ULL, 0xEF927DBCF00C20F2ULL, 0x7B32F7D1E03680ECULL, 116 | 0xB9FD7620E7316243ULL, 0x05A7E8A57DB91B77ULL, 0xB5889C6E15630A75ULL, 0x4A750A09CE9573F7ULL, 117 | 0xCF464CEC899A2F8AULL, 0xF538639CE705B824ULL, 0x3C79A0FF5580EF7FULL, 0xEDE6C87F8477609DULL, 118 | 0x799E81F05BC93F31ULL, 0x86536B8CF3428A8CULL, 0x97D7374C60087B73ULL, 0xA246637CFF328532ULL, 119 | 0x043FCAE60CC0EBA0ULL, 0x920E449535DD359EULL, 0x70EB093B15B290CCULL, 0x73A1921916591CBDULL, 120 | 0x56436C9FE1A1AA8DULL, 0xEFAC4B70633B8F81ULL, 0xBB215798D45DF7AFULL, 0x45F20042F24F1768ULL, 121 | 0x930F80F4E8EB7462ULL, 0xFF6712FFCFD75EA1ULL, 0xAE623FD67468AA70ULL, 0xDD2C5BC84BC8D8FCULL, 122 | 0x7EED120D54CF2DD9ULL, 0x22FE545401165F1CULL, 0xC91800E98FB99929ULL, 0x808BD68E6AC10365ULL, 123 | 0xDEC468145B7605F6ULL, 0x1BEDE3A3AEF53302ULL, 0x43539603D6C55602ULL, 0xAA969B5C691CCB7AULL, 124 | 0xA87832D392EFEE56ULL, 0x65942C7B3C7E11AEULL, 0xDED2D633CAD004F6ULL, 0x21F08570F420E565ULL, 125 | 0xB415938D7DA94E3CULL, 0x91B859E59ECB6350ULL, 0x10CFF333E0ED804AULL, 0x28AED140BE0BB7DDULL, 126 | 0xC5CC1D89724FA456ULL, 0x5648F680F11A2741ULL, 0x2D255069F0B7DAB3ULL, 0x9BC5A38EF729ABD4ULL, 127 | 0xEF2F054308F6A2BCULL, 0xAF2042F5CC5C2858ULL, 0x480412BAB7F5BE2AULL, 0xAEF3AF4A563DFE43ULL, 128 | 0x19AFE59AE451497FULL, 0x52593803DFF1E840ULL, 0xF4F076E65F2CE6F0ULL, 0x11379625747D5AF3ULL, 129 | 0xBCE5D2248682C115ULL, 0x9DA4243DE836994FULL, 0x066F70B33FE09017ULL, 0x4DC4DE189B671A1CULL, 130 | 0x51039AB7712457C3ULL, 0xC07A3F80C31FB4B4ULL, 0xB46EE9C5E64A6E7CULL, 0xB3819A42ABE61C87ULL, 131 | 0x21A007933A522A20ULL, 0x2DF16F761598AA4FULL, 0x763C4A1371B368FDULL, 0xF793C46702E086A0ULL, 132 | 0xD7288E012AEB8D31ULL, 0xDE336A2A4BC1C44BULL, 0x0BF692B38D079F23ULL, 0x2C604A7A177326B3ULL, 133 | 0x4850E73E03EB6064ULL, 0xCFC447F1E53C8E1BULL, 0xB05CA3F564268D99ULL, 0x9AE182C8BC9474E8ULL, 134 | 0xA4FC4BD4FC5558CAULL, 0xE755178D58FC4E76ULL, 0x69B97DB1A4C03DFEULL, 0xF9B5B7C4ACC67C96ULL, 135 | 0xFC6A82D64B8655FBULL, 0x9C684CB6C4D24417ULL, 0x8EC97D2917456ED0ULL, 0x6703DF9D2924E97EULL, 136 | 0xC547F57E42A7444EULL, 0x78E37644E7CAD29EULL, 0xFE9A44E9362F05FAULL, 0x08BD35CC38336615ULL, 137 | 0x9315E5EB3A129ACEULL, 0x94061B871E04DF75ULL, 0xDF1D9F9D784BA010ULL, 0x3BBA57B68871B59DULL, 138 | 0xD2B7ADEEDED1F73FULL, 0xF7A255D83BC373F8ULL, 0xD7F4F2448C0CEB81ULL, 0xD95BE88CD210FFA7ULL, 139 | 0x336F52F8FF4728E7ULL, 0xA74049DAC312AC71ULL, 0xA2F61BB6E437FDB5ULL, 0x4F2A5CB07F6A35B3ULL, 140 | 0x87D380BDA5BF7859ULL, 0x16B9F7E06C453A21ULL, 0x7BA2484C8A0FD54EULL, 0xF3A678CAD9A2E38CULL, 141 | 0x39B0BF7DDE437BA2ULL, 0xFCAF55C1BF8A4424ULL, 0x18FCF680573FA594ULL, 0x4C0563B89F495AC3ULL, 142 | 0x40E087931A00930DULL, 0x8CFFA9412EB642C1ULL, 0x68CA39053261169FULL, 0x7A1EE967D27579E2ULL, 143 | 0x9D1D60E5076F5B6FULL, 0x3810E399B6F65BA2ULL, 0x32095B6D4AB5F9B1ULL, 0x35CAB62109DD038AULL, 144 | 0xA90B24499FCFAFB1ULL, 0x77A225A07CC2C6BDULL, 0x513E5E634C70E331ULL, 0x4361C0CA3F692F12ULL, 145 | 0xD941ACA44B20A45BULL, 0x528F7C8602C5807BULL, 0x52AB92BEB9613989ULL, 0x9D1DFA2EFC557F73ULL, 146 | 0x722FF175F572C348ULL, 0x1D1260A51107FE97ULL, 0x7A249A57EC0C9BA2ULL, 0x04208FE9E8F7F2D6ULL, 147 | 0x5A110C6058B920A0ULL, 0x0CD9A497658A5698ULL, 0x56FD23C8F9715A4CULL, 0x284C847B9D887AAEULL, 148 | 0x04FEABFBBDB619CBULL, 0x742E1E651C60BA83ULL, 0x9A9632E65904AD3CULL, 0x881B82A13B51B9E2ULL, 149 | 0x506E6744CD974924ULL, 0xB0183DB56FFC6A79ULL, 0x0ED9B915C66ED37EULL, 0x5E11E86D5873D484ULL, 150 | 0xF678647E3519AC6EULL, 0x1B85D488D0F20CC5ULL, 0xDAB9FE6525D89021ULL, 0x0D151D86ADB73615ULL, 151 | 0xA865A54EDCC0F019ULL, 0x93C42566AEF98FFBULL, 0x99E7AFEABE000731ULL, 0x48CBFF086DDF285AULL, 152 | 0x7F9B6AF1EBF78BAFULL, 0x58627E1A149BBA21ULL, 0x2CD16E2ABD791E33ULL, 0xD363EFF5F0977996ULL, 153 | 0x0CE2A38C344A6EEDULL, 0x1A804AADB9CFA741ULL, 0x907F30421D78C5DEULL, 0x501F65EDB3034D07ULL, 154 | 0x37624AE5A48FA6E9ULL, 0x957BAF61700CFF4EULL, 0x3A6C27934E31188AULL, 0xD49503536ABCA345ULL, 155 | 0x088E049589C432E0ULL, 0xF943AEE7FEBF21B8ULL, 0x6C3B8E3E336139D3ULL, 0x364F6FFA464EE52EULL, 156 | 0xD60F6DCEDC314222ULL, 0x56963B0DCA418FC0ULL, 0x16F50EDF91E513AFULL, 0xEF1955914B609F93ULL, 157 | 0x565601C0364E3228ULL, 0xECB53939887E8175ULL, 0xBAC7A9A18531294BULL, 0xB344C470397BBA52ULL, 158 | 0x65D34954DAF3CEBDULL, 0xB4B81B3FA97511E2ULL, 0xB422061193D6F6A7ULL, 0x071582401C38434DULL, 159 | 0x7A13F18BBEDC4FF5ULL, 0xBC4097B116C524D2ULL, 0x59B97885E2F2EA28ULL, 0x99170A5DC3115544ULL, 160 | 0x6F423357E7C6A9F9ULL, 0x325928EE6E6F8794ULL, 0xD0E4366228B03343ULL, 0x565C31F7DE89EA27ULL, 161 | 0x30F5611484119414ULL, 0xD873DB391292ED4FULL, 0x7BD94E1D8E17DEBCULL, 0xC7D9F16864A76E94ULL, 162 | 0x947AE053EE56E63CULL, 0xC8C93882F9475F5FULL, 0x3A9BF55BA91F81CAULL, 0xD9A11FBB3D9808E4ULL, 163 | 0x0FD22063EDC29FCAULL, 0xB3F256D8ACA0B0B9ULL, 0xB03031A8B4516E84ULL, 0x35DD37D5871448AFULL, 164 | 0xE9F6082B05542E4EULL, 0xEBFAFA33D7254B59ULL, 0x9255ABB50D532280ULL, 0xB9AB4CE57F2D34F3ULL, 165 | 0x693501D628297551ULL, 0xC62C58F97DD949BFULL, 0xCD454F8F19C5126AULL, 0xBBE83F4ECC2BDECBULL, 166 | 0xDC842B7E2819E230ULL, 0xBA89142E007503B8ULL, 0xA3BC941D0A5061CBULL, 0xE9F6760E32CD8021ULL, 167 | 0x09C7E552BC76492FULL, 0x852F54934DA55CC9ULL, 0x8107FCCF064FCF56ULL, 0x098954D51FFF6580ULL, 168 | 0x23B70EDB1955C4BFULL, 0xC330DE426430F69DULL, 0x4715ED43E8A45C0AULL, 0xA8D7E4DAB780A08DULL, 169 | 0x0572B974F03CE0BBULL, 0xB57D2E985E1419C7ULL, 0xE8D9ECBE2CF3D73FULL, 0x2FE4B17170E59750ULL, 170 | 0x11317BA87905E790ULL, 0x7FBF21EC8A1F45ECULL, 0x1725CABFCB045B00ULL, 0x964E915CD5E2B207ULL, 171 | 0x3E2B8BCBF016D66DULL, 0xBE7444E39328A0ACULL, 0xF85B2B4FBCDE44B7ULL, 0x49353FEA39BA63B1ULL, 172 | 0x1DD01AAFCD53486AULL, 0x1FCA8A92FD719F85ULL, 0xFC7C95D827357AFAULL, 0x18A6A990C8B35EBDULL, 173 | 0xCCCB7005C6B9C28DULL, 0x3BDBB92C43B17F26ULL, 0xAA70B5B4F89695A2ULL, 0xE94C39A54A98307FULL, 174 | 0xB7A0B174CFF6F36EULL, 0xD4DBA84729AF48ADULL, 0x2E18BC1AD9704A68ULL, 0x2DE0966DAF2F8B1CULL, 175 | 0xB9C11D5B1E43A07EULL, 0x64972D68DEE33360ULL, 0x94628D38D0C20584ULL, 0xDBC0D2B6AB90A559ULL, 176 | 0xD2733C4335C6A72FULL, 0x7E75D99D94A70F4DULL, 0x6CED1983376FA72BULL, 0x97FCAACBF030BC24ULL, 177 | 0x7B77497B32503B12ULL, 0x8547EDDFB81CCB94ULL, 0x79999CDFF70902CBULL, 0xCFFE1939438E9B24ULL, 178 | 0x829626E3892D95D7ULL, 0x92FAE24291F2B3F1ULL, 0x63E22C147B9C3403ULL, 0xC678B6D860284A1CULL, 179 | 0x5873888850659AE7ULL, 0x0981DCD296A8736DULL, 0x9F65789A6509A440ULL, 0x9FF38FED72E9052FULL, 180 | 0xE479EE5B9930578CULL, 0xE7F28ECD2D49EECDULL, 0x56C074A581EA17FEULL, 0x5544F7D774B14AEFULL, 181 | 0x7B3F0195FC6F290FULL, 0x12153635B2C0CF57ULL, 0x7F5126DBBA5E0CA7ULL, 0x7A76956C3EAFB413ULL, 182 | 0x3D5774A11D31AB39ULL, 0x8A1B083821F40CB4ULL, 0x7B4A38E32537DF62ULL, 0x950113646D1D6E03ULL, 183 | 0x4DA8979A0041E8A9ULL, 0x3BC36E078F7515D7ULL, 0x5D0A12F27AD310D1ULL, 0x7F9D1A2E1EBE1327ULL, 184 | 0xDA3A361B1C5157B1ULL, 0xDCDD7D20903D0C25ULL, 0x36833336D068F707ULL, 0xCE68341F79893389ULL, 185 | 0xAB9090168DD05F34ULL, 0x43954B3252DC25E5ULL, 0xB438C2B67F98E5E9ULL, 0x10DCD78E3851A492ULL, 186 | 0xDBC27AB5447822BFULL, 0x9B3CDB65F82CA382ULL, 0xB67B7896167B4C84ULL, 0xBFCED1B0048EAC50ULL, 187 | 0xA9119B60369FFEBDULL, 0x1FFF7AC80904BF45ULL, 0xAC12FB171817EEE7ULL, 0xAF08DA9177DDA93DULL, 188 | 0x1B0CAB936E65C744ULL, 0xB559EB1D04E5E932ULL, 0xC37B45B3F8D6F2BAULL, 0xC3A9DC228CAAC9E9ULL, 189 | 0xF3B8B6675A6507FFULL, 0x9FC477DE4ED681DAULL, 0x67378D8ECCEF96CBULL, 0x6DD856D94D259236ULL, 190 | 0xA319CE15B0B4DB31ULL, 0x073973751F12DD5EULL, 0x8A8E849EB32781A5ULL, 0xE1925C71285279F5ULL, 191 | 0x74C04BF1790C0EFEULL, 0x4DDA48153C94938AULL, 0x9D266D6A1CC0542CULL, 0x7440FB816508C4FEULL, 192 | 0x13328503DF48229FULL, 0xD6BF7BAEE43CAC40ULL, 0x4838D65F6EF6748FULL, 0x1E152328F3318DEAULL, 193 | 0x8F8419A348F296BFULL, 0x72C8834A5957B511ULL, 0xD7A023A73260B45CULL, 0x94EBC8ABCFB56DAEULL, 194 | 0x9FC10D0F989993E0ULL, 0xDE68A2355B93CAE6ULL, 0xA44CFE79AE538BBEULL, 0x9D1D84FCCE371425ULL, 195 | 0x51D2B1AB2DDFB636ULL, 0x2FD7E4B9E72CD38CULL, 0x65CA5B96B7552210ULL, 0xDD69A0D8AB3B546DULL, 196 | 0x604D51B25FBF70E2ULL, 0x73AA8A564FB7AC9EULL, 0x1A8C1E992B941148ULL, 0xAAC40A2703D9BEA0ULL, 197 | 0x764DBEAE7FA4F3A6ULL, 0x1E99B96E70A9BE8BULL, 0x2C5E9DEB57EF4743ULL, 0x3A938FEE32D29981ULL, 198 | 0x26E6DB8FFDF5ADFEULL, 0x469356C504EC9F9DULL, 0xC8763C5B08D1908CULL, 0x3F6C6AF859D80055ULL, 199 | 0x7F7CC39420A3A545ULL, 0x9BFB227EBDF4C5CEULL, 0x89039D79D6FC5C5CULL, 0x8FE88B57305E2AB6ULL, 200 | 0xA09E8C8C35AB96DEULL, 0xFA7E393983325753ULL, 0xD6B6D0ECC617C699ULL, 0xDFEA21EA9E7557E3ULL, 201 | 0xB67C1FA481680AF8ULL, 0xCA1E3785A9E724E5ULL, 0x1CFC8BED0D681639ULL, 0xD18D8549D140CAEAULL, 202 | 0x4ED0FE7E9DC91335ULL, 0xE4DBF0634473F5D2ULL, 0x1761F93A44D5AEFEULL, 0x53898E4C3910DA55ULL, 203 | 0x734DE8181F6EC39AULL, 0x2680B122BAA28D97ULL, 0x298AF231C85BAFABULL, 0x7983EED3740847D5ULL, 204 | 0x66C1A2A1A60CD889ULL, 0x9E17E49642A3E4C1ULL, 0xEDB454E7BADC0805ULL, 0x50B704CAB602C329ULL, 205 | 0x4CC317FB9CDDD023ULL, 0x66B4835D9EAFEA22ULL, 0x219B97E26FFC81BDULL, 0x261E4E4C0A333A9DULL, 206 | 0x1FE2CCA76517DB90ULL, 0xD7504DFA8816EDBBULL, 0xB9571FA04DC089C8ULL, 0x1DDC0325259B27DEULL, 207 | 0xCF3F4688801EB9AAULL, 0xF4F5D05C10CAB243ULL, 0x38B6525C21A42B0EULL, 0x36F60E2BA4FA6800ULL, 208 | 0xEB3593803173E0CEULL, 0x9C4CD6257C5A3603ULL, 0xAF0C317D32ADAA8AULL, 0x258E5A80C7204C4BULL, 209 | 0x8B889D624D44885DULL, 0xF4D14597E660F855ULL, 0xD4347F66EC8941C3ULL, 0xE699ED85B0DFB40DULL, 210 | 0x2472F6207C2D0484ULL, 0xC2A1E7B5B459AEB5ULL, 0xAB4F6451CC1D45ECULL, 0x63767572AE3D6174ULL, 211 | 0xA59E0BD101731A28ULL, 0x116D0016CB948F09ULL, 0x2CF9C8CA052F6E9FULL, 0x0B090A7560A968E3ULL, 212 | 0xABEEDDB2DDE06FF1ULL, 0x58EFC10B06A2068DULL, 0xC6E57A78FBD986E0ULL, 0x2EAB8CA63CE802D7ULL, 213 | 0x14A195640116F336ULL, 0x7C0828DD624EC390ULL, 0xD74BBE77E6116AC7ULL, 0x804456AF10F5FB53ULL, 214 | 0xEBE9EA2ADF4321C7ULL, 0x03219A39EE587A30ULL, 0x49787FEF17AF9924ULL, 0xA1E9300CD8520548ULL, 215 | 0x5B45E522E4B1B4EFULL, 0xB49C3B3995091A36ULL, 0xD4490AD526F14431ULL, 0x12A8F216AF9418C2ULL, 216 | 0x001F837CC7350524ULL, 0x1877B51E57A764D5ULL, 0xA2853B80F17F58EEULL, 0x993E1DE72D36D310ULL, 217 | 0xB3598080CE64A656ULL, 0x252F59CF0D9F04BBULL, 0xD23C8E176D113600ULL, 0x1BDA0492E7E4586EULL, 218 | 0x21E0BD5026C619BFULL, 0x3B097ADAF088F94EULL, 0x8D14DEDB30BE846EULL, 0xF95CFFA23AF5F6F4ULL, 219 | 0x3871700761B3F743ULL, 0xCA672B91E9E4FA16ULL, 0x64C8E531BFF53B55ULL, 0x241260ED4AD1E87DULL, 220 | 0x106C09B972D2E822ULL, 0x7FBA195410E5CA30ULL, 0x7884D9BC6CB569D8ULL, 0x0647DFEDCD894A29ULL, 221 | 0x63573FF03E224774ULL, 0x4FC8E9560F91B123ULL, 0x1DB956E450275779ULL, 0xB8D91274B9E9D4FBULL, 222 | 0xA2EBEE47E2FBFCE1ULL, 0xD9F1F30CCD97FB09ULL, 0xEFED53D75FD64E6BULL, 0x2E6D02C36017F67FULL, 223 | 0xA9AA4D20DB084E9BULL, 0xB64BE8D8B25396C1ULL, 0x70CB6AF7C2D5BCF0ULL, 0x98F076A4F7A2322EULL, 224 | 0xBF84470805E69B5FULL, 0x94C3251F06F90CF3ULL, 0x3E003E616A6591E9ULL, 0xB925A6CD0421AFF3ULL, 225 | 0x61BDD1307C66E300ULL, 0xBF8D5108E27E0D48ULL, 0x240AB57A8B888B20ULL, 0xFC87614BAF287E07ULL, 226 | 0xEF02CDD06FFDB432ULL, 0xA1082C0466DF6C0AULL, 0x8215E577001332C8ULL, 0xD39BB9C3A48DB6CFULL, 227 | 0x2738259634305C14ULL, 0x61CF4F94C97DF93DULL, 0x1B6BACA2AE4E125BULL, 0x758F450C88572E0BULL, 228 | 0x959F587D507A8359ULL, 0xB063E962E045F54DULL, 0x60E8ED72C0DFF5D1ULL, 0x7B64978555326F9FULL, 229 | 0xFD080D236DA814BAULL, 0x8C90FD9B083F4558ULL, 0x106F72FE81E2C590ULL, 0x7976033A39F7D952ULL, 230 | 0xA4EC0132764CA04BULL, 0x733EA705FAE4FA77ULL, 0xB4D8F77BC3E56167ULL, 0x9E21F4F903B33FD9ULL, 231 | 0x9D765E419FB69F6DULL, 0xD30C088BA61EA5EFULL, 0x5D94337FBFAF7F5BULL, 0x1A4E4822EB4D7A59ULL, 232 | 0x6FFE73E81B637FB3ULL, 0xDDF957BC36D8B9CAULL, 0x64D0E29EEA8838B3ULL, 0x08DD9BDFD96B9F63ULL, 233 | 0x087E79E5A57D1D13ULL, 0xE328E230E3E2B3FBULL, 0x1C2559E30F0946BEULL, 0x720BF5F26F4D2EAAULL, 234 | 0xB0774D261CC609DBULL, 0x443F64EC5A371195ULL, 0x4112CF68649A260EULL, 0xD813F2FAB7F5C5CAULL, 235 | 0x660D3257380841EEULL, 0x59AC2C7873F910A3ULL, 0xE846963877671A17ULL, 0x93B633ABFA3469F8ULL, 236 | 0xC0C0F5A60EF4CDCFULL, 0xCAF21ECD4377B28CULL, 0x57277707199B8175ULL, 0x506C11B9D90E8B1DULL, 237 | 0xD83CC2687A19255FULL, 0x4A29C6465A314CD1ULL, 0xED2DF21216235097ULL, 0xB5635C95FF7296E2ULL, 238 | 0x22AF003AB672E811ULL, 0x52E762596BF68235ULL, 0x9AEBA33AC6ECC6B0ULL, 0x944F6DE09134DFB6ULL, 239 | 0x6C47BEC883A7DE39ULL, 0x6AD047C430A12104ULL, 0xA5B1CFDBA0AB4067ULL, 0x7C45D833AFF07862ULL, 240 | 0x5092EF950A16DA0BULL, 0x9338E69C052B8E7BULL, 0x455A4B4CFE30E3F5ULL, 0x6B02E63195AD0CF8ULL, 241 | 0x6B17B224BAD6BF27ULL, 0xD1E0CCD25BB9C169ULL, 0xDE0C89A556B9AE70ULL, 0x50065E535A213CF6ULL, 242 | 0x9C1169FA2777B874ULL, 0x78EDEFD694AF1EEDULL, 0x6DC93D9526A50E68ULL, 0xEE97F453F06791EDULL, 243 | 0x32AB0EDB696703D3ULL, 0x3A6853C7E70757A7ULL, 0x31865CED6120F37DULL, 0x67FEF95D92607890ULL, 244 | 0x1F2B1D1F15F6DC9CULL, 0xB69E38A8965C6B65ULL, 0xAA9119FF184CCCF4ULL, 0xF43C732873F24C13ULL, 245 | 0xFB4A3D794A9A80D2ULL, 0x3550C2321FD6109CULL, 0x371F77E76BB8417EULL, 0x6BFA9AAE5EC05779ULL, 246 | 0xCD04F3FF001A4778ULL, 0xE3273522064480CAULL, 0x9F91508BFFCFC14AULL, 0x049A7F41061A9E60ULL, 247 | 0xFCB6BE43A9F2FE9BULL, 0x08DE8A1C7797DA9BULL, 0x8F9887E6078735A1ULL, 0xB5B4071DBFC73A66ULL, 248 | 0x230E343DFBA08D33ULL, 0x43ED7F5A0FAE657DULL, 0x3A88A0FBBCB05C63ULL, 0x21874B8B4D2DBC4FULL, 249 | 0x1BDEA12E35F6A8C9ULL, 0x53C065C6C8E63528ULL, 0xE34A1D250E7A8D6BULL, 0xD6B04D3B7651DD7EULL, 250 | 0x5E90277E7CB39E2DULL, 0x2C046F22062DC67DULL, 0xB10BB459132D0A26ULL, 0x3FA9DDFB67E2F199ULL, 251 | 0x0E09B88E1914F7AFULL, 0x10E8B35AF3EEAB37ULL, 0x9EEDECA8E272B933ULL, 0xD4C718BC4AE8AE5FULL, 252 | 0x81536D601170FC20ULL, 0x91B534F885818A06ULL, 0xEC8177F83F900978ULL, 0x190E714FADA5156EULL, 253 | 0xB592BF39B0364963ULL, 0x89C350C893AE7DC1ULL, 0xAC042E70F8B383F2ULL, 0xB49B52E587A1EE60ULL, 254 | 0xFB152FE3FF26DA89ULL, 0x3E666E6F69AE2C15ULL, 0x3B544EBE544C19F9ULL, 0xE805A1E290CF2456ULL, 255 | 0x24B33C9D7ED25117ULL, 0xE74733427B72F0C1ULL, 0x0A804D18B7097475ULL, 0x57E3306D881EDB4FULL, 256 | 0x4AE7D6A36EB5DBCBULL, 0x2D8D5432157064C8ULL, 0xD1E649DE1E7F268BULL, 0x8A328A1CEDFE552CULL, 257 | 0x07A3AEC79624C7DAULL, 0x84547DDC3E203C94ULL, 0x990A98FD5071D263ULL, 0x1A4FF12616EEFC89ULL, 258 | 0xF6F7FD1431714200ULL, 0x30C05B1BA332F41CULL, 0x8D2636B81555A786ULL, 0x46C9FEB55D120902ULL, 259 | 0xCCEC0A73B49C9921ULL, 0x4E9D2827355FC492ULL, 0x19EBB029435DCB0FULL, 0x4659D2B743848A2CULL, 260 | 0x963EF2C96B33BE31ULL, 0x74F85198B05A2E7DULL, 0x5A0F544DD2B1FB18ULL, 0x03727073C2E134B1ULL, 261 | 0xC7F6AA2DE59AEA61ULL, 0x352787BAA0D7C22FULL, 0x9853EAB63B5E0B35ULL, 0xABBDCDD7ED5C0860ULL, 262 | 0xCF05DAF5AC8D77B0ULL, 0x49CAD48CEBF4A71EULL, 0x7A4C10EC2158C4A6ULL, 0xD9E92AA246BF719EULL, 263 | 0x13AE978D09FE5557ULL, 0x730499AF921549FFULL, 0x4E4B705B92903BA4ULL, 0xFF577222C14F0A3AULL, 264 | 0x55B6344CF97AAFAEULL, 0xB862225B055B6960ULL, 0xCAC09AFBDDD2CDB4ULL, 0xDAF8E9829FE96B5FULL, 265 | 0xB5FDFC5D3132C498ULL, 0x310CB380DB6F7503ULL, 0xE87FBB46217A360EULL, 0x2102AE466EBB1148ULL, 266 | 0xF8549E1A3AA5E00DULL, 0x07A69AFDCC42261AULL, 0xC4C118BFE78FEAAEULL, 0xF9F4892ED96BD438ULL, 267 | 0x1AF3DBE25D8F45DAULL, 0xF5B4B0B0D2DEEEB4ULL, 0x962ACEEFA82E1C84ULL, 0x046E3ECAAF453CE9ULL, 268 | 0xF05D129681949A4CULL, 0x964781CE734B3C84ULL, 0x9C2ED44081CE5FBDULL, 0x522E23F3925E319EULL, 269 | 0x177E00F9FC32F791ULL, 0x2BC60A63A6F3B3F2ULL, 0x222BBFAE61725606ULL, 0x486289DDCC3D6780ULL, 270 | 0x7DC7785B8EFDFC80ULL, 0x8AF38731C02BA980ULL, 0x1FAB64EA29A2DDF7ULL, 0xE4D9429322CD065AULL, 271 | 0x9DA058C67844F20CULL, 0x24C0E332B70019B0ULL, 0x233003B5A6CFE6ADULL, 0xD586BD01C5C217F6ULL, 272 | 0x5E5637885F29BC2BULL, 0x7EBA726D8C94094BULL, 0x0A56A5F0BFE39272ULL, 0xD79476A84EE20D06ULL, 273 | 0x9E4C1269BAA4BF37ULL, 0x17EFEE45B0DEE640ULL, 0x1D95B0A5FCF90BC6ULL, 0x93CBE0B699C2585DULL, 274 | 0x65FA4F227A2B6D79ULL, 0xD5F9E858292504D5ULL, 0xC2B5A03F71471A6FULL, 0x59300222B4561E00ULL, 275 | 0xCE2F8642CA0712DCULL, 0x7CA9723FBB2E8988ULL, 0x2785338347F2BA08ULL, 0xC61BB3A141E50E8CULL, 276 | 0x150F361DAB9DEC26ULL, 0x9F6A419D382595F4ULL, 0x64A53DC924FE7AC9ULL, 0x142DE49FFF7A7C3DULL, 277 | 0x0C335248857FA9E7ULL, 0x0A9C32D5EAE45305ULL, 0xE6C42178C4BBB92EULL, 0x71F1CE2490D20B07ULL, 278 | 0xF1BCC3D275AFE51AULL, 0xE728E8C83C334074ULL, 0x96FBF83A12884624ULL, 0x81A1549FD6573DA5ULL, 279 | 0x5FA7867CAF35E149ULL, 0x56986E2EF3ED091BULL, 0x917F1DD5F8886C61ULL, 0xD20D8C88C8FFE65FULL, 280 | 0x31D71DCE64B2C310ULL, 0xF165B587DF898190ULL, 0xA57E6339DD2CF3A0ULL, 0x1EF6E6DBB1961EC9ULL, 281 | 0x70CC73D90BC26E24ULL, 0xE21A6B35DF0C3AD7ULL, 0x003A93D8B2806962ULL, 0x1C99DED33CB890A1ULL, 282 | 0xCF3145DE0ADD4289ULL, 0xD0E4427A5514FB72ULL, 0x77C621CC9FB3A483ULL, 0x67A34DAC4356550BULL, 283 | 0xF8D626AAAF278509ULL, 284 | }; 285 | // clang-format on --------------------------------------------------------------------------------