├── .github └── workflows │ └── cmake.yml ├── .gitignore ├── AUTHORS ├── CMakeLists.txt ├── Doxyfile ├── LICENSE ├── README.md ├── src ├── engine │ ├── bitboard.cpp │ ├── bitboard.hpp │ ├── board.cpp │ ├── board.hpp │ ├── constants.hpp │ ├── move.cpp │ ├── move.hpp │ ├── movegen │ │ ├── attacks.cpp │ │ ├── attacks.hpp │ │ ├── magics.cpp │ │ ├── magics.hpp │ │ ├── movegen.cpp │ │ ├── movegen.hpp │ │ ├── perft.cpp │ │ ├── perft.hpp │ │ ├── tables.cpp │ │ └── tables.hpp │ ├── movepicker │ │ ├── eval.cpp │ │ ├── eval.hpp │ │ ├── histtable.cpp │ │ ├── histtable.hpp │ │ ├── movepicker.cpp │ │ ├── movepicker.hpp │ │ ├── pvtable.cpp │ │ ├── pvtable.hpp │ │ ├── ttable.cpp │ │ └── ttable.hpp │ ├── utils.hpp │ ├── zobrist.cpp │ └── zobrist.hpp ├── interfaces │ ├── cli │ │ ├── cli.cpp │ │ ├── cli.hpp │ │ └── commands │ │ │ ├── ascii.cpp │ │ │ ├── captures.cpp │ │ │ ├── commands.hpp │ │ │ ├── display.cpp │ │ │ ├── dividedperft.cpp │ │ │ ├── eval.cpp │ │ │ ├── exit.cpp │ │ │ ├── getfen.cpp │ │ │ ├── help.cpp │ │ │ ├── info.cpp │ │ │ ├── magics.cpp │ │ │ ├── move.cpp │ │ │ ├── moves.cpp │ │ │ ├── new.cpp │ │ │ ├── perft.cpp │ │ │ ├── plmoves.cpp │ │ │ ├── rotate.cpp │ │ │ ├── setfen.cpp │ │ │ └── switch.cpp │ ├── uci │ │ ├── commands │ │ │ ├── commands.hpp │ │ │ ├── display.cpp │ │ │ ├── exit.cpp │ │ │ ├── go.cpp │ │ │ ├── isready.cpp │ │ │ ├── position.cpp │ │ │ ├── uci.cpp │ │ │ └── ucinewgame.cpp │ │ ├── timectl.cpp │ │ ├── timectl.hpp │ │ ├── uci.cpp │ │ └── uci.hpp │ ├── utils.cpp │ └── utils.hpp ├── main.cpp ├── opts.cpp └── opts.hpp └── tests ├── catch.hpp ├── movegen ├── test_board.cpp ├── test_move.cpp ├── test_perft.cpp ├── test_tables.cpp └── test_unmake.cpp └── movepicker ├── test_movepicker.cpp └── test_zobrist.cpp /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: 4 | push: 5 | branches: [ "main", "develop" ] 6 | pull_request: 7 | branches: [ "main", "develop" ] 8 | 9 | env: 10 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 11 | BUILD_TYPE: Release 12 | 13 | jobs: 14 | build: 15 | # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. 16 | # You can convert this to a matrix build if you need cross-platform coverage. 17 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | 23 | - name: Configure CMake 24 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 25 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 26 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} 27 | 28 | - name: Build 29 | # Build your program with the given configuration 30 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 31 | 32 | - name: Test 33 | working-directory: ${{github.workspace}}/build 34 | # Execute tests defined by the CMake configuration. 35 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 36 | run: ctest -C ${{env.BUILD_TYPE}} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | .idea/ 3 | build/ 4 | cmake-build-debug/ 5 | cmake-build-debug-coverage/ -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # List of authors for Codfish 2 | João Silveira (jsilll) 3 | Henrique Caraça (HCaraca) 4 | Guilherme Costa (GuiBandeiraCosta) -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.2...3.15) 2 | 3 | #-------------------Setup------------------- 4 | set(CMAKE_C_STANDARD 99) 5 | set(CMAKE_CXX_STANDARD 20) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | project(codfish VERSION 1.0 DESCRIPTION "UCI Compliant Chess Engine") 8 | 9 | set(CMAKE_CXX_FLAGS "-Wall -Wpedantic -Wextra -Wconversion -Werror") 10 | set(CMAKE_CXX_FLAGS_RELEASE "-Ofast -m64") 11 | set(CMAKE_CXX_FLAGS_DEBUG "-g") 12 | 13 | if(NOT CMAKE_BUILD_TYPE) 14 | set(CMAKE_BUILD_TYPE Debug) 15 | endif() 16 | 17 | message(STATUS "CXX: ${CMAKE_CXX_COMPILER}") 18 | 19 | set(THREADS_PREFER_PTHREAD_FLAG ON) 20 | find_package(Threads REQUIRED) 21 | 22 | #-------------------Library------------------- 23 | file(GLOB CHESS_SRC ${PROJECT_SOURCE_DIR}/src/engine/*.cpp) 24 | file(GLOB MOVEPICKER_SRC ${PROJECT_SOURCE_DIR}/src/engine/movepicker/*.cpp) 25 | file(GLOB MOVEGEN_SRC ${PROJECT_SOURCE_DIR}/src/engine/movegen/*.cpp) 26 | add_library(engine ${CHESS_SRC} ${MOVEGEN_SRC} ${MOVEPICKER_SRC}) 27 | target_include_directories(engine PRIVATE ${PROJECT_SOURCE_DIR}/src) 28 | 29 | #-------------------Executable------------------- 30 | file(GLOB EXEC_SRC ${PROJECT_SOURCE_DIR}/src/*.cpp 31 | ${PROJECT_SOURCE_DIR}/src/interfaces/*.cpp 32 | ${PROJECT_SOURCE_DIR}/src/interfaces/cli/*.cpp 33 | ${PROJECT_SOURCE_DIR}/src/interfaces/uci/*.cpp 34 | ${PROJECT_SOURCE_DIR}/src/interfaces/cli/commands/*.cpp 35 | ${PROJECT_SOURCE_DIR}/src/interfaces/uci/commands/*.cpp 36 | ) 37 | add_executable(codfish ${EXEC_SRC}) 38 | target_include_directories(engine PUBLIC ${PROJECT_SOURCE_DIR}/src) 39 | target_link_libraries(codfish engine Threads::Threads ) 40 | 41 | #-------------------Tests------------------- 42 | enable_testing() 43 | 44 | file(GLOB TESTS_SRC ${PROJECT_SOURCE_DIR}/tests/movegen/test_*.cpp 45 | ${PROJECT_SOURCE_DIR}/tests/movepicker/test_*.cpp 46 | ) 47 | 48 | foreach(TEST_SRC ${TESTS_SRC}) 49 | get_filename_component(TEST_NAME ${TEST_SRC} NAME_WLE) 50 | add_executable(${TEST_NAME} ${TEST_SRC}) 51 | target_include_directories(${TEST_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/src) 52 | target_link_libraries(${TEST_NAME} engine) 53 | add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME}) 54 | endforeach() 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 João Silveira 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

Codfish

6 | 7 |

8 | 9 |

10 | 11 | Codfish is a strong chess engine written in C++. 12 | 13 | Currently, Codfish doesn't support opening books, endgame tables, multi-threading or any kind of neural network evaluation. Some of these features will certainly be implemented in future releases. 14 | 15 | The main goal of this project is to provide a simple introduction to newcomers to the chess programming world. The code is meant to be efficient and fast but also clean, maintainable and, above all, understandable. 16 | 17 | Features 18 | === 19 | ### Interfaces 20 | - [UCI](http://wbec-ridderkerk.nl/html/UCIProtocol.html) Mode for GUI interaction 21 | - CLI Mode for Terminal Usage 22 | 23 | ### Move Generation 24 | - [Bitboard Representation](https://www.chessprogramming.org/Bitboards) 25 | - [Pre-calculated Attack Tables](https://www.chessprogramming.org/Attack_and_Defend_Maps) 26 | - [Magic Bitboards](https://www.chessprogramming.org/Looking_for_Magics) 27 | - [Pseudo-Legal Move Generation](https://www.chessprogramming.org/Pseudo-Legal_Move) 28 | - [Make Unmake Approach](https://www.chessprogramming.org/Make_Move) 29 | 30 | ### Move Picker 31 | - [PeSTO inspired evaluation function](https://www.chessprogramming.org/PeSTO%27s_Evaluation_Function) 32 | - [NegaMax Search](https://www.chessprogramming.org/Negamax) 33 | - [Quiescense Search](https://www.chessprogramming.org/Quiescence_Search) 34 | - [Move Ordering with PV, Killer, History and MVV LVA moves](https://www.chessprogramming.org/index.php?title=Move_Ordering&mobileaction=toggle_view_mobile) 35 | - [Triangular PV-Table](https://www.chessprogramming.org/index.php?title=Triangular_PV-Table&mobileaction=toggle_view_mobile) 36 | - [Iterative Deepening](https://www.chessprogramming.org/Iterative_Deepening) 37 | - [Principal Variation Search](https://www.chessprogramming.org/Principal_Variation_Search) 38 | - [Late Move Reduction](https://www.chessprogramming.org/Late_Move_Reductions) 39 | - [Null Move Pruning](https://www.chessprogramming.org/Null_Move_Pruning) 40 | - [Null Window Search](https://www.chessprogramming.org/Null_Window) 41 | - [Transposition Table](https://en.wikipedia.org/wiki/Transposition_table) 42 | - [Repetition Detection](https://www.chessprogramming.org/Repetitions) 43 | 44 | Building and Installation 45 | === 46 | 47 | Codfish is meant to be built only for Unix systems (although it may be built for other target platforms with little to no effort). 48 | 49 | To compile the source code and the unit tests into their respective binaries: 50 | ``` 51 | mkdir build 52 | cd build 53 | cmake ../ 54 | cd ../ 55 | cmake --build build --config Release --target all 56 | ``` 57 | 58 | ### Build Dependencies 59 | - C++ Standard Library 60 | - [Catch2](https://github.com/catchorg/Catch2) (header included) for Unit Tests 61 | 62 | Usage 63 | === 64 | ### Starting in UCI mode 65 | ``` 66 | codfish --uci 67 | ``` 68 | 69 | ### Starting in CLI mode 70 | ``` 71 | codfish --cli 72 | ``` 73 | 74 | If this argument is not specified at startup the engine will default to UCI mode. 75 | Once in UCI mode mode you can use any UCI compliant chess GUI to interact with the engine, 76 | some examples are [CuteChess](https://cutechess.com/) and [Arena](https://cutechess.com/). 77 | 78 | Files 79 | === 80 | Codfish consists of the following files: 81 | - [`CMakeLists.txt`](https://github.com/jsilll/codfish/blob/master/CMakeLists.txt), a file that contains the rules for building the engine using [cmake](https://cmake.org/). 82 | - [`src`](https://github.com/jsilll/codfish/blob/master/src), a subdirectory that contains all the source code for the engine. 83 | - [`tests`](https://github.com/jsilll/codfish/blob/master/tests), a subdirectory that contains all the source for the unit tests. 84 | - [`Doxyfile`](https://github.com/jsilll/codfish/blob/master/Doxyfile), a file for generating documentation for the source code using [Doxygen](https://doxygen.nl/). 85 | 86 | Contributing 87 | === 88 | Everyone is more than welcome to contribute to the development of the project. In case you want to help improve this project here's a list of resources to get you started with chess programming: 89 | 90 | - [Bitboard Chess Engine in C Video Tutorials Playlist](https://youtube.com/playlist?list=PLmN0neTso3Jxh8ZIylk74JpwfiWNI76Cs) 91 | - [Chess Programming Wiki](https://www.chessprogramming.org/Main_Page) 92 | - [Chess Programming Youtube Channel](https://www.youtube.com/channel/UCB9-prLkPwgvlKKqDgXhsMQ) 93 | - [FEN Editor](http://en.lichess.org/editor) 94 | - [Load PGN Files](http://en.lichess.org/paste) 95 | 96 | Terms of Use 97 | === 98 | Codfish is free, and distributed under the **MIT License**. 99 | 100 | For full details, read the copy of the MIT License found in the file named 101 | [LICENSE](https://github.com/jsilll/codfish/blob/master/LICENSE). 102 | -------------------------------------------------------------------------------- /src/engine/bitboard.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | namespace bitboard 10 | { 11 | int bit_count(u64 bb) 12 | { 13 | unsigned int count = 0; 14 | while (bb) 15 | { 16 | count++; 17 | bb &= bb - 1; 18 | } 19 | return (int)count; 20 | } 21 | 22 | Square bit_scan(u64 bb) 23 | { 24 | if (bb) 25 | { 26 | return (Square)bit_count((bb & -bb) - 1); 27 | } 28 | 29 | return EMPTY_SQUARE; 30 | } 31 | 32 | Square bit_scan_forward(u64 bb) 33 | { 34 | static const int index64[64] = { 35 | 0, 47, 1, 56, 48, 27, 2, 60, 36 | 57, 49, 41, 37, 28, 16, 3, 61, 37 | 54, 58, 35, 52, 50, 42, 21, 44, 38 | 38, 32, 29, 23, 17, 11, 4, 62, 39 | 46, 55, 26, 59, 40, 36, 15, 53, 40 | 34, 51, 20, 43, 31, 22, 10, 45, 41 | 25, 39, 14, 33, 19, 30, 9, 24, 42 | 13, 18, 8, 12, 7, 6, 5, 63}; 43 | 44 | static const u64 debruijn64 = 0x03f79d71b4cb0a89; 45 | return (Square)index64[((bb ^ (bb - 1)) * debruijn64) >> 58]; 46 | } 47 | 48 | u64 set_occupancy(int index, int bits_in_mask, u64 attack_mask) 49 | { 50 | u64 occupancy = ZERO; 51 | for (int bit = 0; bit < bits_in_mask; bit++) 52 | { 53 | Square lsb_sq = bit_scan_forward(attack_mask); 54 | pop_bit(attack_mask, lsb_sq); 55 | if (index & (1 << bit)) 56 | { 57 | occupancy |= tables::square_to_bitboard((Square)lsb_sq); 58 | } 59 | } 60 | 61 | return occupancy; 62 | } 63 | 64 | void print(u64 bb) 65 | { 66 | for (int i = RANK_8; i >= RANK_1; i--) 67 | { 68 | std::cout << i + 1 << " "; 69 | for (int n = FILE_A; n < N_FILES; n++) 70 | { 71 | std::cout << ((bb >> utils::get_square((Rank)i, (File)n)) & ONE) << " "; 72 | } 73 | std::cout << "\n"; 74 | } 75 | std::cout << "\n a b c d e f g h\n\n"; 76 | printf("bitboard %lud\n", bb); 77 | } 78 | 79 | } // namespace bitboard -------------------------------------------------------------------------------- /src/engine/bitboard.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | typedef std::uint64_t u64; 8 | 9 | constexpr u64 ONE = 1ULL; 10 | constexpr u64 ZERO = 0ULL; 11 | 12 | namespace bitboard 13 | { 14 | 15 | // Common Bitboard Operations 16 | inline bool get_bit(u64 bb, Square sq) { return ((bb >> sq) & 1ULL); } 17 | inline void pop_bit(u64 &bn, Square sq) { bn &= ~(1ULL << sq); } 18 | inline void pop_last_bit(u64 &bb) { bb &= bb - 1; } 19 | inline void set_bit(u64 &bb, Square sq) { bb |= (1ULL << sq); } 20 | 21 | // Common Bitboard Shifts 22 | inline u64 sout_one(u64 bb) { return (bb >> 8); } 23 | inline u64 nort_one(u64 bb) { return (bb << 8); } 24 | inline u64 east_one(u64 bb) { return (bb << 1) & 0xFEFEFEFEFEFEFEFE; } 25 | inline u64 no_ea_one(u64 bb) { return (bb << 9) & 0xFEFEFEFEFEFEFEFE; } 26 | inline u64 so_ea_one(u64 bb) { return (bb >> 7) & 0xFEFEFEFEFEFEFEFE; } 27 | inline u64 west_one(u64 bb) { return (bb >> 1) & 0x7F7F7F7F7F7F7F7F; } 28 | inline u64 so_we_one(u64 bb) { return (bb >> 9) & 0x7F7F7F7F7F7F7F7F; } 29 | inline u64 no_we_one(u64 bb) { return (bb << 7) & 0x7F7F7F7F7F7F7F7F; } 30 | 31 | /** 32 | * @brief Returns number of bits in Bitboard 33 | * 34 | * @param bb 35 | * @return unsigned int 36 | */ 37 | int bit_count(u64 bb); 38 | 39 | /** 40 | * @brief Returns index of LSB bit 41 | * 42 | * @param bb 43 | * @return unsigned int 44 | */ 45 | Square bit_scan(u64 bb); 46 | 47 | /** 48 | * @brief Returns index of LSB bit 49 | * 50 | * @param bb 51 | * @return unsigned int 52 | */ 53 | Square bit_scan_forward(u64 bb); 54 | 55 | /** 56 | * @brief Sets the occupancy bits 57 | * 58 | * @param index 59 | * @param bits_in_mask 60 | * @param attack_mask 61 | * @return U64 62 | */ 63 | u64 set_occupancy(int index, int bits_in_mask, u64 attack_mask); 64 | 65 | /** 66 | * @brief Prints a bitboard 67 | * 68 | * @param bb 69 | */ 70 | void print(u64 bb); 71 | 72 | } // namespace bitboard 73 | -------------------------------------------------------------------------------- /src/engine/board.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | Board::Board() { this->set_starting_position(); } 15 | 16 | Board::Board(const Board &board) { 17 | memcpy(_pieces, board._pieces, sizeof(_pieces)); 18 | memcpy(_occupancies, board._occupancies, sizeof(_occupancies)); 19 | 20 | _to_move = board._to_move; 21 | _castling_rights = board._castling_rights; 22 | _en_passant_square = board._en_passant_square; 23 | _half_move_clock = board._half_move_clock; 24 | _full_move_number = board._full_move_number; 25 | _hash_key = board._hash_key; 26 | 27 | _ascii = board._ascii; 28 | _white_on_bottom = board._white_on_bottom; 29 | memcpy(_square, board._square, sizeof(_square)); 30 | } 31 | 32 | void Board::update_occupancies() { 33 | _occupancies[WHITE] = ZERO; 34 | _occupancies[BLACK] = ZERO; 35 | _occupancies[BOTH] = ZERO; 36 | 37 | _occupancies[WHITE] |= _pieces[WHITE][PAWN]; 38 | _occupancies[WHITE] |= _pieces[WHITE][KNIGHT]; 39 | _occupancies[WHITE] |= _pieces[WHITE][BISHOP]; 40 | _occupancies[WHITE] |= _pieces[WHITE][ROOK]; 41 | _occupancies[WHITE] |= _pieces[WHITE][QUEEN]; 42 | _occupancies[WHITE] |= _pieces[WHITE][KING]; 43 | 44 | _occupancies[BLACK] |= _pieces[BLACK][PAWN]; 45 | _occupancies[BLACK] |= _pieces[BLACK][KNIGHT]; 46 | _occupancies[BLACK] |= _pieces[BLACK][BISHOP]; 47 | _occupancies[BLACK] |= _pieces[BLACK][ROOK]; 48 | _occupancies[BLACK] |= _pieces[BLACK][QUEEN]; 49 | _occupancies[BLACK] |= _pieces[BLACK][KING]; 50 | 51 | _occupancies[BOTH] |= _occupancies[WHITE]; 52 | _occupancies[BOTH] |= _occupancies[BLACK]; 53 | } 54 | 55 | void Board::update_bitboards_from_squares() { 56 | for (int piece_type = PAWN; piece_type < N_PIECES; piece_type++) { 57 | _pieces[WHITE][piece_type] = ZERO; 58 | _pieces[BLACK][piece_type] = ZERO; 59 | } 60 | 61 | for (int sq = A1; sq < N_SQUARES; sq++) { 62 | if (_square[sq].type != EMPTY_PIECE) { 63 | bitboard::set_bit(_pieces[_square[sq].color][_square[sq].type], 64 | (Square)sq); 65 | } 66 | } 67 | 68 | this->update_occupancies(); 69 | } 70 | 71 | u64 Board::get_pieces(Color color, PieceType type) const { 72 | return _pieces[color][type]; 73 | } 74 | 75 | u64 Board::get_occupancies(Color color) const { return _occupancies[color]; } 76 | 77 | Color Board::get_side_to_move() const { return _to_move; } 78 | 79 | Color Board::get_opponent() const { return utils::get_opponent(_to_move); } 80 | 81 | int Board::get_castling_rights() const { return _castling_rights; } 82 | 83 | Square Board::get_en_passant_square() const { return _en_passant_square; } 84 | 85 | int Board::get_half_move_clock() const { return _half_move_clock; } 86 | 87 | int Board::get_full_move_number() const { return _full_move_number; } 88 | 89 | u64 Board::get_hash_key() const { return _hash_key; } 90 | 91 | Board::Piece Board::get_piece_from_square(Square sq) const { 92 | return _square[sq]; 93 | } 94 | 95 | bool Board::is_square_attacked(Square sq, Color attacker) const { 96 | u64 pawns = _pieces[attacker][PAWN]; 97 | if (tables::get_pawn_attacks(utils::get_opponent(attacker), sq) & pawns) { 98 | return true; 99 | } 100 | u64 knights = _pieces[attacker][KNIGHT]; 101 | if (tables::get_knight_attacks(sq) & knights) { 102 | return true; 103 | } 104 | u64 king = _pieces[attacker][KING]; 105 | if (tables::get_king_attacks(sq) & king) { 106 | return true; 107 | } 108 | u64 bishopsQueens = _pieces[attacker][QUEEN] | _pieces[attacker][BISHOP]; 109 | if (tables::get_bishop_attacks(sq, _occupancies[BOTH]) & bishopsQueens) { 110 | return true; 111 | } 112 | u64 rooksQueens = _pieces[attacker][QUEEN] | _pieces[attacker][ROOK]; 113 | if (tables::get_rook_attacks(sq, _occupancies[BOTH]) & rooksQueens) { 114 | return true; 115 | } 116 | 117 | return false; 118 | } 119 | 120 | bool Board::is_ascii() const { return _ascii; } 121 | bool Board::is_white_on_bottom() const { return _white_on_bottom; } 122 | 123 | std::string Board::get_fen() const { 124 | std::string piece_placements; 125 | std::string active_color; 126 | std::string castling_rights; 127 | std::string en_passant; 128 | std::string half_move_clock; 129 | std::string full_move_number; 130 | 131 | int empty_squares = 0; 132 | for (int rank = RANK_8; rank >= RANK_1; rank--) { 133 | for (int file = FILE_A; file < N_FILES; file++) { 134 | Square sq = utils::get_square((Rank)rank, (File)file); 135 | if (file == 0) { 136 | if (empty_squares) { 137 | piece_placements += std::to_string(empty_squares); 138 | empty_squares = 0; 139 | } 140 | piece_placements += '/'; 141 | } 142 | switch (_square[sq].type) { 143 | case EMPTY_PIECE: 144 | empty_squares++; 145 | break; 146 | default: 147 | if (empty_squares) { 148 | piece_placements += std::to_string(empty_squares); 149 | empty_squares = 0; 150 | } 151 | piece_placements += 152 | PIECE_REPR[_square[sq].type + (6 * _square[sq].color)]; 153 | break; 154 | } 155 | } 156 | if (empty_squares) { 157 | piece_placements += std::to_string(empty_squares); 158 | empty_squares = 0; 159 | } 160 | } 161 | 162 | active_color = _to_move == WHITE ? "w" : "b"; 163 | 164 | char castling_rights_buf[5]; 165 | snprintf(castling_rights_buf, 5, "%s%s%s%s", 166 | (_castling_rights & CASTLE_KING_WHITE) ? "K" : "", 167 | (_castling_rights & CASTLE_QUEEN_WHITE) ? "Q" : "", 168 | (_castling_rights & CASTLE_KING_BLACK) ? "k" : "", 169 | (_castling_rights & CASTLE_QUEEN_BLACK) ? "q" : ""); 170 | castling_rights = std::string(castling_rights_buf); 171 | if (castling_rights.empty()) { 172 | castling_rights = "-"; 173 | } 174 | 175 | std::string fen = piece_placements + " " + active_color + " " + 176 | castling_rights + " " + 177 | SQUARE_NAMES[this->get_en_passant_square() == -1 178 | ? 64 179 | : this->get_en_passant_square()] + 180 | " " + std::to_string(_half_move_clock) + " " + 181 | std::to_string(_full_move_number) + "\n"; 182 | 183 | return fen.substr(1, std::string::npos); 184 | } 185 | 186 | struct Board::GameState Board::get_state() const { 187 | return GameState{_en_passant_square, _castling_rights, _half_move_clock, 188 | _hash_key}; 189 | } 190 | 191 | void Board::set_en_passant_square(Square sq) { 192 | // Remove from hash key en passant square 193 | if (_en_passant_square != EMPTY_SQUARE) { 194 | _hash_key ^= zobrist::en_passant_keys[_en_passant_square]; 195 | } 196 | 197 | _en_passant_square = sq; 198 | 199 | // Update hash key with new en passant square 200 | if (_en_passant_square != EMPTY_SQUARE) { 201 | _hash_key ^= zobrist::en_passant_keys[_en_passant_square]; 202 | } 203 | } 204 | 205 | void Board::set_castling_rights(int castling_rights) { 206 | _castling_rights = castling_rights; 207 | } 208 | 209 | void Board::set_state(GameState state) { 210 | _en_passant_square = state.en_passant_square; 211 | _castling_rights = state.castling_rights; 212 | _half_move_clock = state.half_move_clock; 213 | _hash_key = state.hash_key; 214 | } 215 | 216 | void Board::display() const { 217 | int offset = _ascii ? 0 : 13; 218 | std::cout << '\n'; 219 | if (!_white_on_bottom) { 220 | std::cout << " h g f e d c b a\n"; 221 | for (int rank = RANK_1; rank < RANK_8; rank++) { 222 | std::cout << " +---+---+---+---+---+---+---+---+\n" 223 | << " |"; 224 | for (int file = FILE_H; file >= FILE_A; file--) { 225 | struct Piece piece = _square[utils::get_square((Rank)rank, (File)file)]; 226 | std::cout << " " << PIECE_REPR[piece.type + offset + (6 * piece.color)] 227 | << " |"; 228 | } 229 | std::cout << std::setw(3) << rank + 1 << "\n"; 230 | } 231 | std::cout << " +---+---+---+---+---+---+---+---+\n"; 232 | } else { 233 | for (int rank = RANK_8; rank >= RANK_1; rank--) { 234 | std::cout << " +---+---+---+---+---+---+---+---+\n" 235 | << std::setw(3) << rank + 1 << " |"; 236 | 237 | for (int file = 0; file < 8; file++) { 238 | struct Piece piece = _square[utils::get_square((Rank)rank, (File)file)]; 239 | std::cout << " " << PIECE_REPR[piece.type + offset + (6 * piece.color)] 240 | << " |"; 241 | } 242 | std::cout << '\n'; 243 | } 244 | std::cout << " +---+---+---+---+---+---+---+---+\n" 245 | << " a b c d e f g h\n"; 246 | } 247 | std::cout << std::endl; 248 | } 249 | 250 | bool Board::toggle_ascii() { return _ascii = !_ascii; } 251 | 252 | bool Board::rotate_display() { return _white_on_bottom = !_white_on_bottom; } 253 | 254 | void Board::clear() { 255 | for (int color = WHITE; color < BOTH; color++) { 256 | for (int piece_type = PAWN; piece_type < N_PIECES; piece_type++) { 257 | _pieces[color][piece_type] = ZERO; 258 | } 259 | _occupancies[color] = ZERO; 260 | } 261 | 262 | _occupancies[BOTH] = ZERO; 263 | 264 | _to_move = WHITE; 265 | _castling_rights = 0; 266 | _en_passant_square = EMPTY_SQUARE; 267 | _half_move_clock = 0; 268 | _full_move_number = 0; 269 | _hash_key = ZERO; 270 | 271 | _white_on_bottom = true; 272 | 273 | for (int sq = A1; sq < N_SQUARES; sq++) { 274 | _square[sq].type = EMPTY_PIECE; 275 | _square[sq].color = BLACK; 276 | } 277 | } 278 | 279 | void Board::set_starting_position() { 280 | this->set_from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR", "w", "KQkq", 281 | "-", "0", "1"); 282 | } 283 | 284 | void Board::set_from_fen(const std::string &piece_placements, 285 | const std::string &active_color, 286 | const std::string &castling_rights, 287 | const std::string &en_passant, 288 | const std::string &half_move_clock, 289 | const std::string &full_move_number) { 290 | this->clear(); 291 | 292 | int file = FILE_A, rank = RANK_8; 293 | for (const char &c : piece_placements) { 294 | switch (c) { 295 | case 'p': 296 | _square[utils::get_square((Rank)rank, (File)file)].type = PAWN; 297 | _square[utils::get_square((Rank)rank, (File)file)].color = BLACK; 298 | file = (file + 1) % 8; 299 | break; 300 | case 'n': 301 | _square[utils::get_square((Rank)rank, (File)file)].type = KNIGHT; 302 | _square[utils::get_square((Rank)rank, (File)file)].color = BLACK; 303 | file = (file + 1) % 8; 304 | break; 305 | case 'b': 306 | _square[utils::get_square((Rank)rank, (File)file)].type = BISHOP; 307 | _square[utils::get_square((Rank)rank, (File)file)].color = BLACK; 308 | file = (file + 1) % 8; 309 | break; 310 | case 'r': 311 | _square[utils::get_square((Rank)rank, (File)file)].type = ROOK; 312 | _square[utils::get_square((Rank)rank, (File)file)].color = BLACK; 313 | file = (file + 1) % 8; 314 | break; 315 | case 'q': 316 | _square[utils::get_square((Rank)rank, (File)file)].type = QUEEN; 317 | _square[utils::get_square((Rank)rank, (File)file)].color = BLACK; 318 | file = (file + 1) % 8; 319 | break; 320 | case 'k': 321 | _square[utils::get_square((Rank)rank, (File)file)].type = KING; 322 | _square[utils::get_square((Rank)rank, (File)file)].color = BLACK; 323 | file = (file + 1) % 8; 324 | break; 325 | case 'P': 326 | _square[utils::get_square((Rank)rank, (File)file)].type = PAWN; 327 | _square[utils::get_square((Rank)rank, (File)file)].color = WHITE; 328 | file = (file + 1) % 8; 329 | break; 330 | case 'N': 331 | _square[utils::get_square((Rank)rank, (File)file)].type = KNIGHT; 332 | _square[utils::get_square((Rank)rank, (File)file)].color = WHITE; 333 | file = (file + 1) % 8; 334 | break; 335 | case 'B': 336 | _square[utils::get_square((Rank)rank, (File)file)].type = BISHOP; 337 | _square[utils::get_square((Rank)rank, (File)file)].color = WHITE; 338 | file = (file + 1) % 8; 339 | break; 340 | case 'R': 341 | _square[utils::get_square((Rank)rank, (File)file)].type = ROOK; 342 | _square[utils::get_square((Rank)rank, (File)file)].color = WHITE; 343 | file = (file + 1) % 8; 344 | break; 345 | case 'Q': 346 | _square[utils::get_square((Rank)rank, (File)file)].type = QUEEN; 347 | _square[utils::get_square((Rank)rank, (File)file)].color = WHITE; 348 | file = (file + 1) % 8; 349 | break; 350 | case 'K': 351 | _square[utils::get_square((Rank)rank, (File)file)].type = KING; 352 | _square[utils::get_square((Rank)rank, (File)file)].color = WHITE; 353 | file = (file + 1) % 8; 354 | break; 355 | case '/': 356 | rank--; 357 | file = 0; 358 | break; 359 | default: 360 | file += (c - '0'); 361 | break; 362 | } 363 | } 364 | 365 | if (active_color == "w") { 366 | _to_move = WHITE; 367 | } else { 368 | _to_move = BLACK; 369 | } 370 | 371 | for (const char &c : castling_rights) { 372 | switch (c) { 373 | case 'Q': 374 | _castling_rights += CASTLE_QUEEN_WHITE; 375 | break; 376 | case 'K': 377 | _castling_rights += CASTLE_KING_WHITE; 378 | break; 379 | case 'q': 380 | _castling_rights += CASTLE_QUEEN_BLACK; 381 | break; 382 | case 'k': 383 | _castling_rights += CASTLE_KING_BLACK; 384 | break; 385 | default: 386 | break; 387 | } 388 | } 389 | 390 | if (en_passant != "-") { 391 | int en_passant_file = en_passant[0] - 'a'; 392 | int en_passant_rank = en_passant[1] - '1'; 393 | _en_passant_square = 394 | utils::get_square((Rank)en_passant_rank, (File)en_passant_file); 395 | } else { 396 | _en_passant_square = EMPTY_SQUARE; 397 | } 398 | 399 | _half_move_clock = std::stoi(half_move_clock); 400 | 401 | _full_move_number = std::stoi(full_move_number); 402 | 403 | this->update_bitboards_from_squares(); 404 | 405 | _hash_key = zobrist::generate_hash_key(*this); 406 | } 407 | 408 | int Board::switch_side_to_move() { return _to_move = this->get_opponent(); } 409 | 410 | void Board::make(const Move move) { 411 | // clang-format off 412 | static const int castling_rights[64] = { 413 | 13, 15, 15, 15, 12, 15, 15, 14, 414 | 15, 15, 15, 15, 15, 15, 15, 15, 415 | 15, 15, 15, 15, 15, 15, 15, 15, 416 | 15, 15, 15, 15, 15, 15, 15, 15, 417 | 15, 15, 15, 15, 15, 15, 15, 15, 418 | 15, 15, 15, 15, 15, 15, 15, 15, 419 | 15, 15, 15, 15, 15, 15, 15, 15, 420 | 7, 15, 15, 15, 3, 15, 15, 11, 421 | }; 422 | // clang-format on 423 | 424 | Square from_square = move.get_from_square(); 425 | Square to_square = move.get_to_square(); 426 | PieceType piece_type = move.get_piece_type(); 427 | PieceType captured_piece = move.get_captured_piece_type(); 428 | PieceType promoted_piece = move.get_promoted_piece_type(); 429 | bool is_capture = move.is_capture(); 430 | bool is_promotion = move.is_promotion(); 431 | bool is_double_push = move.is_double_push(); 432 | bool is_en_passant = move.is_en_passant(); 433 | bool is_castle = move.is_castle(); 434 | 435 | int pawn_push_en_passant_offset = _to_move == WHITE ? -8 : 8; 436 | 437 | bitboard::pop_bit(_pieces[_to_move][piece_type], (Square)from_square); 438 | _square[from_square].type = EMPTY_PIECE; 439 | _square[from_square].color = BLACK; 440 | 441 | // Remove from hash key moved piece 442 | _hash_key ^= zobrist::piece_keys[_to_move][piece_type][from_square]; 443 | 444 | if (is_en_passant) { 445 | Square captured_piece_square = 446 | (Square)(to_square + pawn_push_en_passant_offset); 447 | _square[captured_piece_square].type = EMPTY_PIECE; 448 | _square[captured_piece_square].color = BLACK; 449 | bitboard::pop_bit(_pieces[this->get_opponent()][PAWN], 450 | captured_piece_square); 451 | 452 | // Remove from hash key captured pawn 453 | _hash_key ^= 454 | zobrist::piece_keys[this->get_opponent()][PAWN][captured_piece_square]; 455 | } else if (is_capture) { 456 | bitboard::pop_bit(_pieces[this->get_opponent()][captured_piece], to_square); 457 | 458 | // Remove from hash key captured piece 459 | _hash_key ^= 460 | zobrist::piece_keys[this->get_opponent()][captured_piece][to_square]; 461 | } 462 | 463 | if (is_promotion) { 464 | _square[to_square].type = promoted_piece; 465 | bitboard::set_bit(_pieces[_to_move][promoted_piece], to_square); 466 | 467 | // Update hash key with promoted piece 468 | _hash_key ^= zobrist::piece_keys[_to_move][promoted_piece][to_square]; 469 | } else { 470 | _square[to_square].type = piece_type; 471 | bitboard::set_bit(_pieces[_to_move][piece_type], to_square); 472 | 473 | // Update hash key with moved piece 474 | _hash_key ^= zobrist::piece_keys[_to_move][piece_type][to_square]; 475 | } 476 | 477 | _square[to_square].color = _to_move; 478 | 479 | if (is_castle) { 480 | Square rook_from_square, rook_to_square; 481 | if (to_square - from_square > 0) { 482 | rook_from_square = _to_move == WHITE ? H1 : H8; 483 | rook_to_square = _to_move == WHITE ? F1 : F8; 484 | } else { 485 | rook_from_square = _to_move == WHITE ? A1 : A8; 486 | rook_to_square = _to_move == WHITE ? D1 : D8; 487 | } 488 | 489 | _square[rook_from_square].type = EMPTY_PIECE; 490 | _square[rook_from_square].color = BLACK; 491 | _square[rook_to_square].type = ROOK; 492 | _square[rook_to_square].color = _to_move; 493 | 494 | bitboard::pop_bit(_pieces[_to_move][ROOK], rook_from_square); 495 | 496 | // Remove from hash key rook 497 | _hash_key ^= zobrist::piece_keys[_to_move][ROOK][rook_from_square]; 498 | 499 | bitboard::set_bit(_pieces[_to_move][ROOK], rook_to_square); 500 | 501 | // Update hash key with rook 502 | _hash_key ^= zobrist::piece_keys[_to_move][ROOK][rook_to_square]; 503 | } 504 | 505 | // Remove from hash key en passant square 506 | if (_en_passant_square != EMPTY_SQUARE) { 507 | _hash_key ^= zobrist::en_passant_keys[_en_passant_square]; 508 | } 509 | 510 | // Remove from hash key castling rights 511 | _hash_key ^= zobrist::castle_keys[_castling_rights]; 512 | 513 | _en_passant_square = is_double_push 514 | ? (Square)(to_square + pawn_push_en_passant_offset) 515 | : EMPTY_SQUARE; 516 | _castling_rights &= castling_rights[from_square]; 517 | _castling_rights &= castling_rights[to_square]; 518 | 519 | // Update hash key with en passant square 520 | if (_en_passant_square != EMPTY_SQUARE) { 521 | _hash_key ^= zobrist::en_passant_keys[_en_passant_square]; 522 | } 523 | 524 | // Update hash key with castling rights 525 | _hash_key ^= zobrist::castle_keys[_castling_rights]; 526 | 527 | if (piece_type == PAWN || (is_capture)) { 528 | _half_move_clock = 0; 529 | } else { 530 | _half_move_clock++; 531 | } 532 | 533 | if (_to_move == BLACK) { 534 | _full_move_number++; 535 | } 536 | 537 | // Remove (and Update) from hash key side to move 538 | // This works because zobrist::side_key[WHITE] = 0 539 | // So XOR side[BLACK] + XOR side[WHITE] = XOR side[WHITE] + side[BLACK] = XOR 540 | // side[BLACK] 541 | _hash_key ^= zobrist::side_key[BLACK]; 542 | 543 | // Update from hash key new side to move 544 | this->switch_side_to_move(); 545 | 546 | this->update_occupancies(); 547 | } 548 | 549 | void Board::unmake(const Move move, const GameState state) { 550 | this->switch_side_to_move(); 551 | 552 | Square from_square = move.get_from_square(); 553 | Square to_square = move.get_to_square(); 554 | PieceType piece_type = move.get_piece_type(); 555 | PieceType captured_piece = move.get_captured_piece_type(); 556 | PieceType promoted_piece = move.get_promoted_piece_type(); 557 | bool is_capture = move.is_capture(); 558 | bool is_promotion = move.is_promotion(); 559 | bool is_en_passant = move.is_en_passant(); 560 | bool is_castle = move.is_castle(); 561 | 562 | _square[from_square].type = piece_type; 563 | _square[from_square].color = _to_move; 564 | bitboard::set_bit(_pieces[_to_move][piece_type], from_square); 565 | 566 | bitboard::pop_bit(_pieces[_to_move][piece_type], to_square); 567 | 568 | if (is_en_passant) { 569 | Square captured_piece_square = 570 | _to_move == WHITE ? (Square)(to_square - 8) : (Square)(to_square + 8); 571 | 572 | _square[captured_piece_square].type = PAWN; 573 | _square[captured_piece_square].color = this->get_opponent(); 574 | bitboard::set_bit(_pieces[this->get_opponent()][PAWN], 575 | captured_piece_square); 576 | 577 | _square[to_square].type = EMPTY_PIECE; 578 | _square[to_square].color = BLACK; 579 | } else if (is_capture) { 580 | _square[to_square].type = captured_piece; 581 | _square[to_square].color = this->get_opponent(); 582 | bitboard::set_bit(_pieces[this->get_opponent()][captured_piece], to_square); 583 | } else { 584 | _square[to_square].type = EMPTY_PIECE; 585 | _square[to_square].color = BLACK; 586 | } 587 | 588 | if (is_promotion) { 589 | bitboard::pop_bit(_pieces[_to_move][promoted_piece], to_square); 590 | } 591 | 592 | if (is_castle) { 593 | Square rook_from_square, rook_to_square; 594 | if (to_square - from_square > 0) { 595 | rook_from_square = _to_move == WHITE ? H1 : H8; 596 | rook_to_square = _to_move == WHITE ? F1 : F8; 597 | } else { 598 | rook_from_square = _to_move == WHITE ? A1 : A8; 599 | rook_to_square = _to_move == WHITE ? D1 : D8; 600 | } 601 | 602 | _square[rook_to_square].type = EMPTY_PIECE; 603 | _square[rook_to_square].color = BLACK; 604 | bitboard::pop_bit(_pieces[_to_move][ROOK], rook_to_square); 605 | 606 | _square[rook_from_square].type = ROOK; 607 | _square[rook_from_square].color = _to_move; 608 | bitboard::set_bit(_pieces[_to_move][ROOK], rook_from_square); 609 | } 610 | 611 | if (_to_move == BLACK) { 612 | _full_move_number--; 613 | } 614 | 615 | _en_passant_square = state.en_passant_square; 616 | _castling_rights = state.castling_rights; 617 | _half_move_clock = state.half_move_clock; 618 | _hash_key = state.hash_key; 619 | 620 | this->update_occupancies(); 621 | } 622 | -------------------------------------------------------------------------------- /src/engine/board.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class Move; 7 | 8 | class Board 9 | { 10 | public: 11 | struct Piece 12 | { 13 | PieceType type; 14 | Color color; 15 | }; 16 | 17 | struct GameState 18 | { 19 | Square en_passant_square; 20 | int castling_rights; 21 | int half_move_clock; 22 | u64 hash_key; 23 | }; 24 | 25 | private: 26 | Color _to_move; 27 | int _castling_rights; 28 | Square _en_passant_square; 29 | int _half_move_clock; 30 | int _full_move_number; 31 | u64 _hash_key; 32 | 33 | u64 _pieces[N_SIDES][N_PIECES]; 34 | u64 _occupancies[N_SIDES + 1]; 35 | 36 | bool _ascii{}; 37 | bool _white_on_bottom; 38 | Piece _square[N_SQUARES]; 39 | 40 | void update_occupancies(); 41 | void update_bitboards_from_squares(); 42 | 43 | public: 44 | Board(); 45 | Board(const Board &board); 46 | 47 | u64 get_pieces(Color color, PieceType type) const; 48 | u64 get_occupancies(Color color) const; 49 | Color get_side_to_move() const; 50 | Color get_opponent() const; 51 | int get_castling_rights() const; 52 | Square get_en_passant_square() const; 53 | int get_half_move_clock() const; 54 | int get_full_move_number() const; 55 | Piece get_piece_from_square(Square sq) const; 56 | bool is_square_attacked(Square sq, Color attacker_side) const; 57 | bool is_ascii() const; 58 | bool is_white_on_bottom() const; 59 | GameState get_state() const; 60 | std::string get_fen() const; 61 | 62 | void set_en_passant_square(Square sq); 63 | void set_castling_rights(int castling_rights); 64 | void set_fifty_move(int fifty_move); 65 | void set_state(GameState state); 66 | 67 | u64 get_hash_key() const; 68 | 69 | void clear(); 70 | void set_starting_position(); 71 | void set_from_fen(const std::string &piece_placements, 72 | const std::string &active_color, 73 | const std::string &castling_rights, 74 | const std::string &en_passant, 75 | const std::string &halfmove_clock, 76 | const std::string &fullmove_number); 77 | int switch_side_to_move(); 78 | void make(const Move move); 79 | void unmake(const Move move, const GameState info_board); 80 | 81 | void display() const; 82 | bool toggle_ascii(); 83 | bool rotate_display(); 84 | }; 85 | 86 | #ifdef DEBUG 87 | inline bool operator==(const Board::Piece &p1, const Board::Piece &p2) 88 | { 89 | return p1.color == p2.color && p1.type == p2.type; 90 | } 91 | 92 | inline bool operator!=(const Board::Piece &p1, const Board::Piece &p2) 93 | { 94 | return p1.color != p2.color || p1.type != p2.type; 95 | } 96 | 97 | inline bool operator==(const Board::GameState &s1, const Board::GameState &s2) 98 | { 99 | return s1.castling_rights == s2.castling_rights && s1.en_passant_square == s2.en_passant_square && s1.half_move_clock == s2.half_move_clock; 100 | } 101 | 102 | inline bool operator!=(const Board::GameState &s1, const Board::GameState &s2) 103 | { 104 | return s1.castling_rights != s2.castling_rights || s1.en_passant_square != s2.en_passant_square || s1.half_move_clock != s2.half_move_clock; 105 | } 106 | 107 | inline bool operator==(const Board &b1, const Board &b2) 108 | { 109 | if (b1.get_side_to_move() != b2.get_side_to_move()) 110 | { 111 | return false; 112 | } 113 | if (b1.get_castling_rights() != b2.get_castling_rights()) 114 | { 115 | return false; 116 | } 117 | if (b1.get_en_passant_square() != b2.get_en_passant_square()) 118 | { 119 | return false; 120 | } 121 | if (b1.get_half_move_clock() != b2.get_half_move_clock()) 122 | { 123 | return false; 124 | } 125 | if (b1.get_full_move_number() != b2.get_full_move_number()) 126 | { 127 | return false; 128 | } 129 | if (b1.is_ascii() != b2.is_ascii()) 130 | { 131 | return false; 132 | } 133 | if (b1.is_white_on_bottom() != b2.is_white_on_bottom()) 134 | { 135 | return false; 136 | } 137 | 138 | for (int side = 0; side < N_SIDES; side++) 139 | { 140 | for (int piece = 0; piece < N_PIECES; piece++) 141 | { 142 | if (b1.get_pieces(side, piece) != b2.get_pieces(side, piece)) 143 | { 144 | return false; 145 | } 146 | } 147 | } 148 | 149 | for (int side = 0; side < N_SIDES + 1; side++) 150 | { 151 | if (b1.get_occupancies(side) != b2.get_occupancies(side)) 152 | { 153 | return false; 154 | } 155 | } 156 | 157 | for (int square = 0; square < N_SQUARES; square++) 158 | { 159 | if (b1.get_piece_from_square(square) != b2.get_piece_from_square(square)) 160 | { 161 | return false; 162 | } 163 | } 164 | 165 | return true; 166 | } 167 | 168 | #endif 169 | -------------------------------------------------------------------------------- /src/engine/constants.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | enum Rank : int 6 | { 7 | RANK_1, 8 | RANK_2, 9 | RANK_3, 10 | RANK_4, 11 | RANK_5, 12 | RANK_6, 13 | RANK_7, 14 | RANK_8, 15 | N_RANKS, 16 | }; 17 | 18 | enum File : int 19 | { 20 | FILE_A, 21 | FILE_B, 22 | FILE_C, 23 | FILE_D, 24 | FILE_E, 25 | FILE_F, 26 | FILE_G, 27 | FILE_H, 28 | N_FILES, 29 | }; 30 | 31 | // clang-format off 32 | enum Square : int { 33 | A1, B1, C1, D1, E1, F1, G1, H1, 34 | A2, B2, C2, D2, E2, F2, G2, H2, 35 | A3, B3, C3, D3, E3, F3, G3, H3, 36 | A4, B4, C4, D4, E4, F4, G4, H4, 37 | A5, B5, C5, D5, E5, F5, G5, H5, 38 | A6, B6, C6, D6, E6, F6, G6, H6, 39 | A7, B7, C7, D7, E7, F7, G7, H7, 40 | A8, B8, C8, D8, E8, F8, G8, H8, 41 | N_SQUARES, 42 | EMPTY_SQUARE = -1 43 | }; 44 | // clang-format on 45 | 46 | // clang-format off 47 | const std::string SQUARE_NAMES[] = { 48 | "a1", "b1", "c1", "d1", "e1", "f1", "g1", "h1", 49 | "a2", "b2", "c2", "d2", "e2", "f2", "g2", "h2", 50 | "a3", "b3", "c3", "d3", "e3", "f3", "g3", "h3", 51 | "a4", "b4", "c4", "d4", "e4", "f4", "g4", "h4", 52 | "a5", "b5", "c5", "d5", "e5", "f5", "g5", "h5", 53 | "a6", "b6", "c6", "d6", "e6", "f6", "g6", "h6", 54 | "a7", "b7", "c7", "d7", "e7", "f7", "g7", "h7", 55 | "a8", "b8", "c8", "d8", "e8", "f8", "g8", "h8", 56 | "-"}; 57 | // clang-format on 58 | 59 | enum Direction : int 60 | { 61 | NORTH = 8, 62 | SOUTH = -8, 63 | WEST = -1, 64 | EAST = 1, 65 | NORTH_WEST = 7, 66 | NORTH_EAST = 9, 67 | SOUTH_WEST = -9, 68 | SOUTH_EAST = -7, 69 | N_DIRECTIONS = 8, 70 | }; 71 | 72 | enum Color : int 73 | { 74 | WHITE = 0, 75 | BLACK = 1, 76 | N_SIDES = 2, 77 | BOTH = 2, 78 | }; 79 | 80 | enum PieceType : int 81 | { 82 | PAWN = 0, 83 | KNIGHT = 1, 84 | BISHOP = 2, 85 | ROOK = 3, 86 | QUEEN = 4, 87 | KING = 5, 88 | N_PIECES = 6, 89 | EMPTY_PIECE = 6, 90 | }; 91 | 92 | // clang-format off 93 | const std::string PIECE_REPR[26] = { 94 | "P", "N", "B", "R", "Q", "K", 95 | "p", "n", "b", "r", "q", "k", 96 | " ", 97 | "♙", "♘", "♗", "♖", "♕", "♔", 98 | "♟︎", "♞", "♝", "♜", "♛", "♚", 99 | " ", 100 | }; 101 | // clang-format on 102 | 103 | enum CastlingRight : int 104 | { 105 | CASTLE_KING_WHITE = 1, 106 | CASTLE_QUEEN_WHITE = 2, 107 | CASTLE_KING_BLACK = 4, 108 | CASTLE_QUEEN_BLACK = 8 109 | }; -------------------------------------------------------------------------------- /src/engine/move.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | std::string Move::get_uci() const 4 | { 5 | if (this->is_promotion()) 6 | { 7 | return SQUARE_NAMES[this->get_from_square()] + SQUARE_NAMES[this->get_to_square()] + PIECE_REPR[this->get_promoted_piece_type() + 6]; 8 | } 9 | return SQUARE_NAMES[this->get_from_square()] + SQUARE_NAMES[this->get_to_square()]; 10 | } 11 | 12 | Move::Move(int source_square, int target_square, int piece, int captured_piece, int promoted_piece, bool is_double_push, bool is_en_passant, bool is_castle) 13 | { 14 | _move_encoded = (uint32_t)source_square | ((uint32_t)target_square << 6) | ((uint32_t)piece << 12) | ((uint32_t)captured_piece << 15) | ((uint32_t)promoted_piece << 18) | ((uint32_t)is_double_push << 21) | ((uint32_t)is_en_passant << 22) | ((uint32_t)is_castle << 23); 15 | } 16 | 17 | Square Move::get_from_square() const 18 | { 19 | return (Square)(_move_encoded & 0x3f); 20 | } 21 | 22 | Square Move::get_to_square() const 23 | { 24 | return (Square)((_move_encoded & 0xfc0) >> 6); 25 | } 26 | 27 | PieceType Move::get_piece_type() const 28 | { 29 | return (PieceType)((_move_encoded & 0x7000) >> 12); 30 | } 31 | 32 | PieceType Move::get_captured_piece_type() const 33 | { 34 | return (PieceType)((_move_encoded & 0x38000) >> 15); 35 | } 36 | 37 | PieceType Move::get_promoted_piece_type() const 38 | { 39 | return (PieceType)((_move_encoded & 0x1C0000) >> 18); 40 | } 41 | 42 | bool Move::is_double_push() const 43 | { 44 | return (_move_encoded & 0x200000); 45 | } 46 | 47 | bool Move::is_en_passant() const 48 | { 49 | return (_move_encoded & 0x400000); 50 | } 51 | 52 | bool Move::is_castle() const 53 | { 54 | return (_move_encoded & 0x800000); 55 | } 56 | 57 | bool Move::is_capture() const 58 | { 59 | return this->get_captured_piece_type() != EMPTY_PIECE; 60 | } 61 | 62 | bool Move::is_promotion() const 63 | { 64 | return this->get_promoted_piece_type() != EMPTY_PIECE; 65 | } 66 | 67 | uint32_t Move::get_encoded() const 68 | { 69 | return _move_encoded; 70 | } 71 | -------------------------------------------------------------------------------- /src/engine/move.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | class Move { 9 | private: 10 | uint32_t _move_encoded; 11 | 12 | public: 13 | Move() : _move_encoded(0){}; 14 | Move(int source_square, int target_square, int piece, int captured_piece, 15 | int promoted_piece, bool is_double_push, bool is_en_passant, 16 | bool is_castle); 17 | 18 | [[nodiscard]] Square get_from_square() const; 19 | [[nodiscard]] Square get_to_square() const; 20 | [[nodiscard]] PieceType get_piece_type() const; 21 | [[nodiscard]] PieceType get_captured_piece_type() const; 22 | [[nodiscard]] PieceType get_promoted_piece_type() const; 23 | [[nodiscard]] bool is_double_push() const; 24 | [[nodiscard]] bool is_en_passant() const; 25 | [[nodiscard]] bool is_castle() const; 26 | [[nodiscard]] bool is_capture() const; 27 | [[nodiscard]] bool is_promotion() const; 28 | [[nodiscard]] uint32_t get_encoded() const; 29 | 30 | [[nodiscard]] std::string get_uci() const; 31 | }; 32 | 33 | inline bool operator==(const Move &m1, const Move &m2) { 34 | return m1.get_encoded() == m2.get_encoded(); 35 | } 36 | 37 | inline bool operator!=(const Move &m1, const Move &m2) { 38 | return m1.get_encoded() != m2.get_encoded(); 39 | } 40 | -------------------------------------------------------------------------------- /src/engine/movegen/attacks.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace attacks 8 | { 9 | static inline u64 mask_white_pawn_east_attacks(u64 wpawns) { return bitboard::no_ea_one(wpawns); } 10 | static inline u64 mask_white_pawn_west_attacks(u64 wpawns) { return bitboard::no_we_one(wpawns); } 11 | static inline u64 mask_black_pawn_east_attacks(u64 bpawns) { return bitboard::so_ea_one(bpawns); } 12 | static inline u64 mask_black_pawn_west_attacks(u64 bpawns) { return bitboard::so_we_one(bpawns); } 13 | 14 | u64 mask_white_pawn_single_pushes(u64 wpawns, u64 empty) { return bitboard::nort_one(wpawns) & empty; } 15 | u64 mask_black_pawn_single_pushes(u64 bpawns, u64 empty) { return bitboard::sout_one(bpawns) & empty; } 16 | 17 | u64 mask_white_pawn_double_pushes(u64 wpawns, u64 empty) 18 | { 19 | u64 single_pushes = mask_white_pawn_single_pushes(wpawns, empty); 20 | return bitboard::nort_one(single_pushes) & empty & tables::MASK_RANK[3]; 21 | } 22 | 23 | u64 mask_black_pawn_double_pushes(u64 bpawns, u64 empty) 24 | { 25 | u64 single_pushes = mask_black_pawn_single_pushes(bpawns, empty); 26 | return bitboard::sout_one(single_pushes) & empty & tables::MASK_RANK[4]; 27 | } 28 | 29 | u64 mask_white_pawn_any_attacks(u64 wpawns) { return mask_white_pawn_east_attacks(wpawns) | mask_white_pawn_west_attacks(wpawns); } 30 | u64 mask_black_pawn_any_attacks(u64 bpawns) { return mask_black_pawn_east_attacks(bpawns) | mask_black_pawn_west_attacks(bpawns); } 31 | 32 | u64 mask_knight_attacks(u64 knights) 33 | { 34 | static const u64 CLEAR_FILE_HG = 0x3f3f3f3f3f3f3f3f; 35 | static const u64 CLEAR_FILE_AB = 0xfcfcfcfcfcfcfcfc; 36 | 37 | u64 l1 = (knights >> 1) & tables::MASK_CLEAR_FILE[7]; 38 | u64 r1 = (knights << 1) & tables::MASK_CLEAR_FILE[0]; 39 | u64 h1 = l1 | r1; 40 | 41 | u64 l2 = (knights >> 2) & CLEAR_FILE_HG; 42 | u64 r2 = (knights << 2) & CLEAR_FILE_AB; 43 | u64 h2 = l2 | r2; 44 | 45 | return (h1 << 16) | (h1 >> 16) | (h2 << 8) | (h2 >> 8); 46 | } 47 | 48 | u64 mask_king_attacks(u64 kings) 49 | { 50 | u64 attacks = bitboard::east_one(kings) | bitboard::west_one(kings); 51 | kings |= attacks; 52 | attacks |= bitboard::nort_one(kings) | bitboard::sout_one(kings); 53 | return attacks; 54 | } 55 | 56 | u64 mask_bishop_attack_rays(Square sq) 57 | { 58 | u64 attacks = ZERO; 59 | Rank rank = utils::get_rank(sq); 60 | File file = utils::get_file(sq); 61 | for (int r = rank + 1, f = file + 1; r < RANK_8 && f < FILE_H; r++, f++) 62 | { 63 | attacks |= (ONE << utils::get_square((Rank)r, (File)f)); 64 | } 65 | 66 | for (int r = rank + 1, f = file - 1; r < RANK_8 && f > FILE_A; r++, f--) 67 | { 68 | attacks |= (ONE << utils::get_square((Rank)r, (File)f)); 69 | } 70 | 71 | for (int r = rank - 1, f = file + 1; r > RANK_1 && f < FILE_H; r--, f++) 72 | { 73 | attacks |= (ONE << utils::get_square((Rank)r, (File)f)); 74 | } 75 | 76 | for (int r = rank - 1, f = file - 1; r > RANK_1 && f > FILE_A; r--, f--) 77 | { 78 | attacks |= (ONE << utils::get_square((Rank)r, (File)f)); 79 | } 80 | 81 | return attacks; 82 | } 83 | 84 | u64 mask_rook_attack_rays(Square sq) 85 | { 86 | u64 attacks = ZERO; 87 | Rank rank = utils::get_rank(sq); 88 | File file = utils::get_file(sq); 89 | for (int r = rank + 1; r < RANK_8; r++) 90 | { 91 | attacks |= (ONE << utils::get_square((Rank)r, file)); 92 | } 93 | 94 | for (int r = rank - 1; r > RANK_1; r--) 95 | { 96 | attacks |= (ONE << utils::get_square((Rank)r, file)); 97 | } 98 | 99 | for (int f = file + 1; f < FILE_H; f++) 100 | { 101 | attacks |= (ONE << utils::get_square(rank, (File)f)); 102 | } 103 | 104 | for (int f = file - 1; f > FILE_A; f--) 105 | { 106 | attacks |= (ONE << utils::get_square(rank, (File)f)); 107 | } 108 | 109 | return attacks; 110 | } 111 | 112 | u64 mask_bishop_xray_attacks(Square sq, u64 block) 113 | { 114 | u64 attacks = ZERO; 115 | Rank rank = utils::get_rank(sq); 116 | File file = utils::get_file(sq); 117 | for (int r = rank + 1, f = file + 1; r < N_RANKS && f < N_FILES; r++, f++) 118 | { 119 | attacks |= (ONE << utils::get_square((Rank)r, (File)f)); 120 | if ((ONE << utils::get_square((Rank)r, (File)f)) & block) 121 | { 122 | break; 123 | } 124 | } 125 | 126 | for (int r = rank + 1, f = file - 1; r < N_RANKS && f >= FILE_A; r++, f--) 127 | { 128 | attacks |= (ONE << utils::get_square((Rank)r, (File)f)); 129 | if ((ONE << utils::get_square((Rank)r, (File)f)) & block) 130 | { 131 | break; 132 | } 133 | } 134 | 135 | for (int r = rank - 1, f = file + 1; r >= RANK_1 && f < N_FILES; r--, f++) 136 | { 137 | attacks |= (ONE << utils::get_square((Rank)r, (File)f)); 138 | if ((ONE << utils::get_square((Rank)r, (File)f)) & block) 139 | { 140 | break; 141 | } 142 | } 143 | 144 | for (int r = rank - 1, f = file - 1; r >= RANK_1 && f >= FILE_A; r--, f--) 145 | { 146 | attacks |= (ONE << utils::get_square((Rank)r, (File)f)); 147 | if ((ONE << utils::get_square((Rank)r, (File)f)) & block) 148 | { 149 | break; 150 | } 151 | } 152 | 153 | return attacks; 154 | } 155 | 156 | u64 mask_rook_xray_attacks(Square sq, u64 block) 157 | { 158 | u64 attacks = ZERO; 159 | Rank rank = utils::get_rank(sq); 160 | File file = utils::get_file(sq); 161 | for (int r = rank + 1; r < N_RANKS; r++) 162 | { 163 | attacks |= (ONE << utils::get_square((Rank)r, file)); 164 | if ((ONE << utils::get_square((Rank)r, file)) & block) 165 | { 166 | break; 167 | } 168 | } 169 | 170 | for (int r = rank - 1; r >= RANK_1; r--) 171 | { 172 | attacks |= (ONE << utils::get_square((Rank)r, file)); 173 | if ((ONE << utils::get_square((Rank)r, file)) & block) 174 | { 175 | break; 176 | } 177 | } 178 | 179 | for (int f = file + 1; f < N_FILES; f++) 180 | { 181 | attacks |= (ONE << utils::get_square(rank, (File)f)); 182 | if ((ONE << utils::get_square(rank, (File)f)) & block) 183 | { 184 | break; 185 | } 186 | } 187 | 188 | for (int f = file - 1; f >= FILE_A; f--) 189 | { 190 | attacks |= (ONE << utils::get_square(rank, (File)f)); 191 | if ((ONE << utils::get_square(rank, (File)f)) & block) 192 | { 193 | break; 194 | } 195 | } 196 | 197 | return attacks; 198 | } 199 | 200 | } // namespace attacks -------------------------------------------------------------------------------- /src/engine/movegen/attacks.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace attacks 6 | { 7 | // Pawn Pushes 8 | u64 mask_white_pawn_single_pushes(u64 wpawns, u64 empty); 9 | u64 mask_black_pawn_single_pushes(u64 bpawns, u64 empty); 10 | u64 mask_white_pawn_double_pushes(u64 wpawns, u64 empty); 11 | u64 mask_black_pawn_double_pushes(u64 bpawns, u64 empty); 12 | 13 | // Bishop and Rook Attack Rays 14 | u64 mask_rook_attack_rays(Square sq); 15 | u64 mask_bishop_attack_rays(Square sq); 16 | 17 | // Attacks 18 | u64 mask_white_pawn_any_attacks(u64 wpawns); 19 | u64 mask_black_pawn_any_attacks(u64 bpawns); 20 | u64 mask_knight_attacks(u64 knights); 21 | u64 mask_king_attacks(u64 kings); 22 | u64 mask_bishop_xray_attacks(Square sq, u64 block); 23 | u64 mask_rook_xray_attacks(Square sq, u64 block); 24 | 25 | } // namespace attacks -------------------------------------------------------------------------------- /src/engine/movegen/magics.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace magics 13 | { 14 | static const u64 MAGICS_BISHOP[N_SQUARES] = { 15 | 0x40040844404084ULL, 16 | 0x2004208a004208ULL, 17 | 0x10190041080202ULL, 18 | 0x108060845042010ULL, 19 | 0x581104180800210ULL, 20 | 0x2112080446200010ULL, 21 | 0x1080820820060210ULL, 22 | 0x3c0808410220200ULL, 23 | 0x4050404440404ULL, 24 | 0x21001420088ULL, 25 | 0x24d0080801082102ULL, 26 | 0x1020a0a020400ULL, 27 | 0x40308200402ULL, 28 | 0x4011002100800ULL, 29 | 0x401484104104005ULL, 30 | 0x801010402020200ULL, 31 | 0x400210c3880100ULL, 32 | 0x404022024108200ULL, 33 | 0x810018200204102ULL, 34 | 0x4002801a02003ULL, 35 | 0x85040820080400ULL, 36 | 0x810102c808880400ULL, 37 | 0xe900410884800ULL, 38 | 0x8002020480840102ULL, 39 | 0x220200865090201ULL, 40 | 0x2010100a02021202ULL, 41 | 0x152048408022401ULL, 42 | 0x20080002081110ULL, 43 | 0x4001001021004000ULL, 44 | 0x800040400a011002ULL, 45 | 0xe4004081011002ULL, 46 | 0x1c004001012080ULL, 47 | 0x8004200962a00220ULL, 48 | 0x8422100208500202ULL, 49 | 0x2000402200300c08ULL, 50 | 0x8646020080080080ULL, 51 | 0x80020a0200100808ULL, 52 | 0x2010004880111000ULL, 53 | 0x623000a080011400ULL, 54 | 0x42008c0340209202ULL, 55 | 0x209188240001000ULL, 56 | 0x400408a884001800ULL, 57 | 0x110400a6080400ULL, 58 | 0x1840060a44020800ULL, 59 | 0x90080104000041ULL, 60 | 0x201011000808101ULL, 61 | 0x1a2208080504f080ULL, 62 | 0x8012020600211212ULL, 63 | 0x500861011240000ULL, 64 | 0x180806108200800ULL, 65 | 0x4000020e01040044ULL, 66 | 0x300000261044000aULL, 67 | 0x802241102020002ULL, 68 | 0x20906061210001ULL, 69 | 0x5a84841004010310ULL, 70 | 0x4010801011c04ULL, 71 | 0xa010109502200ULL, 72 | 0x4a02012000ULL, 73 | 0x500201010098b028ULL, 74 | 0x8040002811040900ULL, 75 | 0x28000010020204ULL, 76 | 0x6000020202d0240ULL, 77 | 0x8918844842082200ULL, 78 | 0x4010011029020020ULL}; 79 | 80 | static const u64 MAGICS_ROOK[N_SQUARES] = { 81 | 0x8a80104000800020ULL, 82 | 0x140002000100040ULL, 83 | 0x2801880a0017001ULL, 84 | 0x100081001000420ULL, 85 | 0x200020010080420ULL, 86 | 0x3001c0002010008ULL, 87 | 0x8480008002000100ULL, 88 | 0x2080088004402900ULL, 89 | 0x800098204000ULL, 90 | 0x2024401000200040ULL, 91 | 0x100802000801000ULL, 92 | 0x120800800801000ULL, 93 | 0x208808088000400ULL, 94 | 0x2802200800400ULL, 95 | 0x2200800100020080ULL, 96 | 0x801000060821100ULL, 97 | 0x80044006422000ULL, 98 | 0x100808020004000ULL, 99 | 0x12108a0010204200ULL, 100 | 0x140848010000802ULL, 101 | 0x481828014002800ULL, 102 | 0x8094004002004100ULL, 103 | 0x4010040010010802ULL, 104 | 0x20008806104ULL, 105 | 0x100400080208000ULL, 106 | 0x2040002120081000ULL, 107 | 0x21200680100081ULL, 108 | 0x20100080080080ULL, 109 | 0x2000a00200410ULL, 110 | 0x20080800400ULL, 111 | 0x80088400100102ULL, 112 | 0x80004600042881ULL, 113 | 0x4040008040800020ULL, 114 | 0x440003000200801ULL, 115 | 0x4200011004500ULL, 116 | 0x188020010100100ULL, 117 | 0x14800401802800ULL, 118 | 0x2080040080800200ULL, 119 | 0x124080204001001ULL, 120 | 0x200046502000484ULL, 121 | 0x480400080088020ULL, 122 | 0x1000422010034000ULL, 123 | 0x30200100110040ULL, 124 | 0x100021010009ULL, 125 | 0x2002080100110004ULL, 126 | 0x202008004008002ULL, 127 | 0x20020004010100ULL, 128 | 0x2048440040820001ULL, 129 | 0x101002200408200ULL, 130 | 0x40802000401080ULL, 131 | 0x4008142004410100ULL, 132 | 0x2060820c0120200ULL, 133 | 0x1001004080100ULL, 134 | 0x20c020080040080ULL, 135 | 0x2935610830022400ULL, 136 | 0x44440041009200ULL, 137 | 0x280001040802101ULL, 138 | 0x2100190040002085ULL, 139 | 0x80c0084100102001ULL, 140 | 0x4024081001000421ULL, 141 | 0x20030a0244872ULL, 142 | 0x12001008414402ULL, 143 | 0x2006104900a0804ULL, 144 | 0x1004081002402ULL}; 145 | 146 | static Magic MAGIC_TABLE_BISHOP[N_SQUARES]; 147 | static Magic MAGIC_TABLE_ROOK[N_SQUARES]; 148 | 149 | static inline u64 generate_dense_random_number_u64() 150 | { 151 | u64 n1 = ((u64)std::rand()) & 0xFFFF; 152 | u64 n2 = ((u64)std::rand()) & 0xFFFF; 153 | u64 n3 = ((u64)std::rand()) & 0xFFFF; 154 | u64 n4 = ((u64)std::rand()) & 0xFFFF; 155 | return n1 | (n2 << 16) | (n3 << 32) | (n4 << 48); 156 | } 157 | 158 | static inline u64 get_magic_number_candidate() 159 | { 160 | return generate_dense_random_number_u64() & generate_dense_random_number_u64() & generate_dense_random_number_u64(); 161 | } 162 | 163 | static u64 generate_magic_number(Square sq, int relevant_bits, u64 (*mask_attacks_fun)(Square), u64 (*mask_attacks_occ_fun)(Square, u64)) 164 | { 165 | int occupancy_indices = 1 << relevant_bits; 166 | u64 attack_mask = mask_attacks_fun(sq); 167 | u64 occupancies[4096], attacks[4096], used_attacks[4096]; 168 | 169 | for (int i = 0; i < occupancy_indices; i++) 170 | { 171 | occupancies[i] = bitboard::set_occupancy(i, relevant_bits, attack_mask); 172 | attacks[i] = mask_attacks_occ_fun(sq, occupancies[i]); 173 | } 174 | 175 | for (int max_tries = 0; max_tries < 99999999; max_tries++) 176 | { 177 | u64 candidate = get_magic_number_candidate(); 178 | 179 | if (bitboard::bit_count((attack_mask * candidate) & 0xFF00000000000000) < 6) 180 | { 181 | continue; 182 | } 183 | 184 | memset(used_attacks, ZERO, sizeof(used_attacks)); 185 | 186 | int fail = 0; 187 | for (int index = 0; !fail && index < occupancy_indices; index++) 188 | { 189 | int magic_index = (int)((occupancies[index] * candidate) >> (64 - relevant_bits)); 190 | if (used_attacks[magic_index] == ZERO) 191 | { 192 | used_attacks[magic_index] = attacks[index]; 193 | } 194 | else 195 | { 196 | fail = 1; 197 | break; 198 | } 199 | } 200 | 201 | if (!fail) 202 | { 203 | return candidate; 204 | } 205 | } 206 | 207 | return ZERO; 208 | } 209 | 210 | void generate() 211 | { 212 | std::cout << "Rook Magic Numbers" << std::endl; 213 | for (int sq = A1; sq < N_SQUARES; sq++) 214 | { 215 | int bit_count = tables::get_relevant_bits_count_rook((Square)sq); 216 | u64 magic = generate_magic_number((Square)sq, bit_count, &attacks::mask_rook_attack_rays, &attacks::mask_rook_xray_attacks); 217 | printf("%d : 0x%lxULL\n", sq, magic); 218 | } 219 | std::cout << std::endl; 220 | 221 | std::cout << "Bishop Magic Numbers" << std::endl; 222 | for (int sq = A1; sq < N_SQUARES; sq++) 223 | { 224 | int bit_count = tables::get_relevant_bits_count_bishop((Square)sq); 225 | u64 magic = generate_magic_number((Square)sq, bit_count, &attacks::mask_bishop_attack_rays, &attacks::mask_bishop_xray_attacks); 226 | printf("%d : 0x%lxULL\n", sq, magic); 227 | } 228 | std::cout << std::endl; 229 | } 230 | 231 | void init() 232 | { 233 | for (int sq = A1; sq < N_SQUARES; sq++) 234 | { 235 | MAGIC_TABLE_BISHOP[sq].mask = attacks::mask_bishop_attack_rays((Square)sq); 236 | MAGIC_TABLE_BISHOP[sq].magic = MAGICS_BISHOP[sq]; 237 | MAGIC_TABLE_BISHOP[sq].shift = 64 - tables::get_relevant_bits_count_bishop((Square)sq); 238 | } 239 | 240 | for (int sq = A1; sq < N_SQUARES; sq++) 241 | { 242 | MAGIC_TABLE_ROOK[sq].mask = attacks::mask_rook_attack_rays((Square)sq); 243 | MAGIC_TABLE_ROOK[sq].magic = MAGICS_ROOK[sq]; 244 | MAGIC_TABLE_ROOK[sq].shift = 64 - tables::get_relevant_bits_count_rook((Square)sq); 245 | } 246 | } 247 | 248 | Magic get_bishop_magic(Square sq) 249 | { 250 | return MAGIC_TABLE_BISHOP[sq]; 251 | } 252 | 253 | Magic get_rook_magic(Square sq) 254 | { 255 | return MAGIC_TABLE_ROOK[sq]; 256 | } 257 | 258 | } // namespace magics -------------------------------------------------------------------------------- /src/engine/movegen/magics.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace magics 7 | { 8 | struct Magic 9 | { 10 | u64 mask; 11 | u64 magic; 12 | int shift; 13 | }; 14 | 15 | void generate(); 16 | 17 | void init(); 18 | 19 | Magic get_bishop_magic(Square sq); 20 | Magic get_rook_magic(Square sq); 21 | 22 | } // namespace magics -------------------------------------------------------------------------------- /src/engine/movegen/movegen.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define MAX_SIZE_MOVES_ARRAY 256 10 | 11 | namespace movegen 12 | { 13 | enum GenType 14 | { 15 | QUIETS, 16 | CAPTURES 17 | }; 18 | 19 | // Castling 20 | template 21 | static void generate_castling_moves(std::vector &move_list, const Board &board); 22 | 23 | // Pawns 24 | template 25 | static void generate_en_passant_capture(std::vector &move_list, const Board &board); 26 | template 27 | static void generate_pawn_single_push_with_promotion(std::vector &move_list, const Board &board); 28 | template 29 | static void generate_pawn_single_push_no_promotion(std::vector &move_list, const Board &board); 30 | template 31 | static void generate_pawn_captures_with_promotion(std::vector &move_list, const Board &board); 32 | template 33 | static void generate_pawn_captures_no_promotion(std::vector &move_list, const Board &board); 34 | template 35 | static void generate_pawn_double_pushes(std::vector &move_list, const Board &board); 36 | 37 | // Leaper Pieces 38 | template 39 | static void generate_leaper_moves(std::vector &move_list, const Board &board); 40 | 41 | // Slider Pieces 42 | template 43 | static void generate_slider_moves(std::vector &move_list, const Board &board); 44 | 45 | bool has_legal_moves(Board &board) 46 | { 47 | Board::GameState state = board.get_state(); 48 | for (const Move &move : movegen::generate_pseudo_legal_moves(board)) 49 | { 50 | board.make(move); 51 | Square king_sq = bitboard::bit_scan_forward(board.get_pieces(board.get_opponent(), KING)); 52 | if (!board.is_square_attacked(king_sq, board.get_side_to_move())) 53 | { 54 | board.unmake(move, state); 55 | return true; 56 | } 57 | board.unmake(move, state); 58 | } 59 | return false; 60 | } 61 | 62 | // All Pseudo Legal Moves 63 | std::vector generate_pseudo_legal_moves(const Board &board) 64 | { 65 | std::vector moves; 66 | moves.reserve(MAX_SIZE_MOVES_ARRAY); 67 | 68 | if (board.get_side_to_move() == WHITE) 69 | { 70 | generate_pawn_captures_with_promotion(moves, board); 71 | generate_pawn_captures_no_promotion(moves, board); 72 | generate_en_passant_capture(moves, board); 73 | generate_pawn_single_push_with_promotion(moves, board); 74 | generate_pawn_double_pushes(moves, board); 75 | generate_pawn_single_push_no_promotion(moves, board); 76 | 77 | generate_leaper_moves(moves, board); 78 | generate_leaper_moves(moves, board); 79 | generate_slider_moves(moves, board); 80 | generate_slider_moves(moves, board); 81 | generate_slider_moves(moves, board); 82 | 83 | generate_leaper_moves(moves, board); 84 | generate_leaper_moves(moves, board); 85 | generate_slider_moves(moves, board); 86 | generate_slider_moves(moves, board); 87 | generate_slider_moves(moves, board); 88 | 89 | generate_castling_moves(moves, board); 90 | } 91 | else 92 | { 93 | generate_pawn_captures_with_promotion(moves, board); 94 | generate_pawn_captures_no_promotion(moves, board); 95 | generate_en_passant_capture(moves, board); 96 | generate_pawn_single_push_with_promotion(moves, board); 97 | generate_pawn_double_pushes(moves, board); 98 | generate_pawn_single_push_no_promotion(moves, board); 99 | 100 | generate_leaper_moves(moves, board); 101 | generate_leaper_moves(moves, board); 102 | generate_slider_moves(moves, board); 103 | generate_slider_moves(moves, board); 104 | generate_slider_moves(moves, board); 105 | 106 | generate_leaper_moves(moves, board); 107 | generate_leaper_moves(moves, board); 108 | generate_slider_moves(moves, board); 109 | generate_slider_moves(moves, board); 110 | generate_slider_moves(moves, board); 111 | 112 | generate_castling_moves(moves, board); 113 | } 114 | 115 | return moves; 116 | } 117 | 118 | std::vector generate_pseudo_legal_captures(const Board &board) 119 | { 120 | std::vector moves; 121 | moves.reserve(MAX_SIZE_MOVES_ARRAY); 122 | 123 | if (board.get_side_to_move() == WHITE) 124 | { 125 | generate_pawn_captures_with_promotion(moves, board); 126 | generate_pawn_captures_no_promotion(moves, board); 127 | generate_en_passant_capture(moves, board); 128 | 129 | generate_leaper_moves(moves, board); 130 | generate_leaper_moves(moves, board); 131 | 132 | generate_slider_moves(moves, board); 133 | generate_slider_moves(moves, board); 134 | generate_slider_moves(moves, board); 135 | } 136 | else 137 | { 138 | generate_pawn_captures_with_promotion(moves, board); 139 | generate_pawn_captures_no_promotion(moves, board); 140 | generate_en_passant_capture(moves, board); 141 | 142 | generate_leaper_moves(moves, board); 143 | generate_leaper_moves(moves, board); 144 | 145 | generate_slider_moves(moves, board); 146 | generate_slider_moves(moves, board); 147 | generate_slider_moves(moves, board); 148 | } 149 | 150 | return moves; 151 | } 152 | 153 | std::vector generateLegalMoves(Board &board) 154 | { 155 | std::vector moves; 156 | moves.reserve(MAX_SIZE_MOVES_ARRAY); 157 | 158 | Board::GameState state = board.get_state(); 159 | for (const Move &move : movegen::generate_pseudo_legal_moves(board)) 160 | { 161 | board.make(move); 162 | Color attacker_side = board.get_side_to_move(); 163 | Square king_sq = bitboard::bit_scan_forward(board.get_pieces(board.get_opponent(), KING)); 164 | if (!board.is_square_attacked(king_sq, attacker_side)) 165 | { 166 | moves.push_back(move); 167 | } 168 | board.unmake(move, state); 169 | } 170 | return moves; 171 | } 172 | 173 | std::vector generate_legal_captures(Board &board) 174 | { 175 | std::vector captures; 176 | captures.reserve(MAX_SIZE_MOVES_ARRAY); 177 | 178 | Board::GameState state = board.get_state(); 179 | for (const Move &move : movegen::generate_pseudo_legal_captures(board)) 180 | { 181 | board.make(move); 182 | Color attacker_side = board.get_side_to_move(); 183 | Square king_sq = bitboard::bit_scan_forward(board.get_pieces(board.get_opponent(), KING)); 184 | if (!board.is_square_attacked(king_sq, attacker_side)) 185 | { 186 | captures.push_back(move); 187 | } 188 | board.unmake(move, state); 189 | } 190 | return captures; 191 | } 192 | 193 | template 194 | static void generate_castling_moves(std::vector &move_list, const Board &board) 195 | { 196 | constexpr Color Opponent = (ToMove == WHITE ? BLACK : WHITE); 197 | constexpr Square CASTLE_B_SQ = (ToMove == WHITE ? B1 : B8); 198 | constexpr Square CASTLE_C_SQ = (ToMove == WHITE ? C1 : C8); 199 | constexpr Square CASTLE_D_SQ = (ToMove == WHITE ? D1 : D8); 200 | constexpr Square CASTLE_E_SQ = (ToMove == WHITE ? E1 : E8); 201 | constexpr Square CASTLE_F_SQ = (ToMove == WHITE ? F1 : F8); 202 | constexpr Square CASTLE_G_SQ = (ToMove == WHITE ? G1 : G8); 203 | constexpr CastlingRight CASTLE_KING_MASK = (ToMove == WHITE ? CASTLE_KING_WHITE : CASTLE_KING_BLACK); 204 | constexpr CastlingRight CASTLE_QUEEN_MASK = (ToMove == WHITE ? CASTLE_QUEEN_WHITE : CASTLE_QUEEN_BLACK); 205 | if (!board.is_square_attacked(CASTLE_E_SQ, Opponent)) 206 | { 207 | if ((board.get_castling_rights() & CASTLE_KING_MASK) && !bitboard::get_bit(board.get_occupancies(BOTH), CASTLE_F_SQ) && !bitboard::get_bit(board.get_occupancies(BOTH), CASTLE_G_SQ)) 208 | { 209 | if (!board.is_square_attacked(CASTLE_F_SQ, Opponent) && !board.is_square_attacked(CASTLE_G_SQ, Opponent)) 210 | { 211 | move_list.emplace_back(CASTLE_E_SQ, CASTLE_G_SQ, KING, EMPTY_PIECE, EMPTY_PIECE, false, false, true); 212 | } 213 | } 214 | if ((board.get_castling_rights() & CASTLE_QUEEN_MASK) && !bitboard::get_bit(board.get_occupancies(BOTH), CASTLE_D_SQ) && !bitboard::get_bit(board.get_occupancies(BOTH), CASTLE_C_SQ) && !bitboard::get_bit(board.get_occupancies(BOTH), CASTLE_B_SQ)) 215 | { 216 | if (!board.is_square_attacked(CASTLE_D_SQ, Opponent) && !board.is_square_attacked(CASTLE_C_SQ, Opponent)) 217 | { 218 | move_list.emplace_back(CASTLE_E_SQ, CASTLE_C_SQ, KING, EMPTY_PIECE, EMPTY_PIECE, false, false, true); 219 | } 220 | } 221 | } 222 | } 223 | 224 | template 225 | static void generate_pawn_double_pushes(std::vector &move_list, const Board &board) 226 | { 227 | constexpr int PAWN_DOUBLE_PUSH_OFFSET = ToMove == WHITE ? -16 : 16; 228 | constexpr u64 (*MASK_FUNC)(u64 wpawns, u64 empty) = ToMove == WHITE ? attacks::mask_white_pawn_double_pushes : attacks::mask_black_pawn_double_pushes; 229 | u64 pawn_double_pushes = MASK_FUNC(board.get_pieces(ToMove, PAWN), ~board.get_occupancies(BOTH)); 230 | while (pawn_double_pushes) 231 | { 232 | int to_square = bitboard::bit_scan_forward(pawn_double_pushes); 233 | int from_square = to_square + PAWN_DOUBLE_PUSH_OFFSET; 234 | move_list.emplace_back(from_square, to_square, PAWN, EMPTY_PIECE, EMPTY_PIECE, true, false, false); 235 | bitboard::pop_last_bit(pawn_double_pushes); 236 | } 237 | } 238 | 239 | template 240 | static void generate_pawn_single_push_with_promotion(std::vector &move_list, const Board &board) 241 | { 242 | constexpr int PAWN_SINGLE_PUSH_OFFSET = ToMove == WHITE ? -8 : 8; 243 | constexpr u64 (*MASK_FUNC)(u64 wpawns, u64 empty) = ToMove == WHITE ? attacks::mask_white_pawn_single_pushes : attacks::mask_black_pawn_single_pushes; 244 | u64 pawn_single_pushes = MASK_FUNC(board.get_pieces(ToMove, PAWN), ~board.get_occupancies(BOTH)); 245 | u64 pawn_single_pushes_promo = pawn_single_pushes & (tables::MASK_RANK[0] | tables::MASK_RANK[7]); 246 | while (pawn_single_pushes_promo) 247 | { 248 | int to_square = bitboard::bit_scan_forward(pawn_single_pushes_promo); 249 | int from_square = to_square + PAWN_SINGLE_PUSH_OFFSET; 250 | move_list.emplace_back(from_square, to_square, PAWN, EMPTY_PIECE, QUEEN, false, false, false); 251 | move_list.emplace_back(from_square, to_square, PAWN, EMPTY_PIECE, KNIGHT, false, false, false); 252 | move_list.emplace_back(from_square, to_square, PAWN, EMPTY_PIECE, ROOK, false, false, false); 253 | move_list.emplace_back(from_square, to_square, PAWN, EMPTY_PIECE, BISHOP, false, false, false); 254 | bitboard::pop_last_bit(pawn_single_pushes_promo); 255 | } 256 | } 257 | 258 | template 259 | static void generate_pawn_single_push_no_promotion(std::vector &move_list, const Board &board) 260 | { 261 | constexpr int PAWN_SINGLE_PUSH_OFFSET = ToMove == WHITE ? -8 : 8; 262 | constexpr u64 (*MASK_FUNC)(u64 wpawns, u64 empty) = ToMove == WHITE ? attacks::mask_white_pawn_single_pushes : attacks::mask_black_pawn_single_pushes; 263 | u64 pawn_single_pushes = MASK_FUNC(board.get_pieces(ToMove, PAWN), ~board.get_occupancies(BOTH)); 264 | u64 pawn_single_pushes_no_promo = pawn_single_pushes & tables::MASK_CLEAR_RANK[0] & tables::MASK_CLEAR_RANK[7]; 265 | while (pawn_single_pushes_no_promo) 266 | { 267 | int to_square = bitboard::bit_scan_forward(pawn_single_pushes_no_promo); 268 | int from_square = to_square + PAWN_SINGLE_PUSH_OFFSET; 269 | move_list.emplace_back(from_square, to_square, PAWN, EMPTY_PIECE, EMPTY_PIECE, false, false, false); 270 | bitboard::pop_last_bit(pawn_single_pushes_no_promo); 271 | } 272 | } 273 | 274 | template 275 | static void generate_pawn_captures_with_promotion(std::vector &move_list, const Board &board) 276 | { 277 | constexpr Color Opponent = ToMove == WHITE ? BLACK : WHITE; 278 | constexpr int MASK_INDEX = ToMove == WHITE ? 6 : 1; 279 | u64 pawns_can_capture_with_promo = board.get_pieces(ToMove, PAWN) & tables::MASK_RANK[MASK_INDEX]; 280 | while (pawns_can_capture_with_promo) 281 | { 282 | Square from_square = bitboard::bit_scan_forward(pawns_can_capture_with_promo); 283 | u64 pawn_captures_promo = tables::get_pawn_attacks(ToMove, from_square) & board.get_occupancies(Opponent); 284 | while (pawn_captures_promo) 285 | { 286 | Square to_square = bitboard::bit_scan_forward(pawn_captures_promo); 287 | move_list.emplace_back(from_square, to_square, PAWN, board.get_piece_from_square(to_square).type, QUEEN, false, false, false); 288 | move_list.emplace_back(from_square, to_square, PAWN, board.get_piece_from_square(to_square).type, KNIGHT, false, false, false); 289 | move_list.emplace_back(from_square, to_square, PAWN, board.get_piece_from_square(to_square).type, ROOK, false, false, false); 290 | move_list.emplace_back(from_square, to_square, PAWN, board.get_piece_from_square(to_square).type, BISHOP, false, false, false); 291 | bitboard::pop_last_bit(pawn_captures_promo); 292 | } 293 | bitboard::pop_last_bit(pawns_can_capture_with_promo); 294 | } 295 | } 296 | 297 | template 298 | static void generate_pawn_captures_no_promotion(std::vector &move_list, const Board &board) 299 | { 300 | constexpr Color Opponent = ToMove == WHITE ? BLACK : WHITE; 301 | constexpr int MASK_INDEX = ToMove == WHITE ? 6 : 1; 302 | u64 pawns_can_capture_no_promo = board.get_pieces(ToMove, PAWN) & tables::MASK_CLEAR_RANK[MASK_INDEX]; 303 | while (pawns_can_capture_no_promo) 304 | { 305 | Square from_square = bitboard::bit_scan_forward(pawns_can_capture_no_promo); 306 | u64 pawn_captures_no_promo = tables::get_pawn_attacks(ToMove, from_square) & board.get_occupancies(Opponent); 307 | while (pawn_captures_no_promo) 308 | { 309 | Square to_square = bitboard::bit_scan_forward(pawn_captures_no_promo); 310 | move_list.emplace_back(from_square, to_square, PAWN, board.get_piece_from_square(to_square).type, EMPTY_PIECE, false, false, false); 311 | bitboard::pop_last_bit(pawn_captures_no_promo); 312 | } 313 | bitboard::pop_last_bit(pawns_can_capture_no_promo); 314 | } 315 | } 316 | 317 | template 318 | static void generate_en_passant_capture(std::vector &move_list, const Board &board) 319 | { 320 | constexpr Color Opponent = ToMove == WHITE ? BLACK : WHITE; 321 | if (board.get_en_passant_square() != -1) 322 | { 323 | u64 pawns_can_en_passant = tables::get_pawn_attacks(Opponent, board.get_en_passant_square()) & board.get_pieces(ToMove, PAWN); 324 | while (pawns_can_en_passant) 325 | { 326 | int from_square = bitboard::bit_scan_forward(pawns_can_en_passant); 327 | move_list.emplace_back(from_square, board.get_en_passant_square(), PAWN, board.get_piece_from_square(board.get_en_passant_square()).type, EMPTY_PIECE, false, true, false); 328 | bitboard::pop_last_bit(pawns_can_en_passant); 329 | } 330 | } 331 | } 332 | 333 | template 334 | static void generate_leaper_moves(std::vector &move_list, const Board &board) 335 | { 336 | static_assert(PType == KNIGHT || PType == KING, "Unsupported piece type in generateLeaperMoves()"); 337 | 338 | constexpr Color Opponent = ToMove == WHITE ? BLACK : WHITE; 339 | constexpr u64 (*ATTACKS_FUNC)(Square sq) = PType == KNIGHT ? tables::get_knight_attacks : tables::get_king_attacks; 340 | u64 to_move_occupancies = board.get_occupancies(ToMove); 341 | u64 opponent_occupancies = board.get_occupancies(Opponent); 342 | u64 to_move_pieces = board.get_pieces(ToMove, PType); 343 | while (to_move_pieces) 344 | { 345 | Square from_square = bitboard::bit_scan_forward(to_move_pieces); 346 | u64 moves; 347 | if constexpr (GType == QUIETS) 348 | { 349 | moves = ATTACKS_FUNC(from_square) & ~to_move_occupancies & ~opponent_occupancies; 350 | } 351 | else 352 | { 353 | moves = ATTACKS_FUNC(from_square) & ~to_move_occupancies & opponent_occupancies; 354 | } 355 | while (moves) 356 | { 357 | Square to_square = bitboard::bit_scan_forward(moves); 358 | if constexpr (GType == QUIETS) 359 | { 360 | move_list.emplace_back(from_square, to_square, PType, EMPTY_PIECE, EMPTY_PIECE, false, false, false); 361 | } 362 | else 363 | { 364 | move_list.emplace_back(from_square, to_square, PType, board.get_piece_from_square(to_square).type, EMPTY_PIECE, false, false, false); 365 | } 366 | bitboard::pop_last_bit(moves); 367 | } 368 | bitboard::pop_last_bit(to_move_pieces); 369 | } 370 | } 371 | 372 | template 373 | static void generate_slider_moves(std::vector &move_list, const Board &board) 374 | { 375 | static_assert(PType == BISHOP || PType == ROOK || PType == QUEEN, "Unsupported piece type in generate_slider_moves()"); 376 | 377 | constexpr Color Opponent = ToMove == WHITE ? BLACK : WHITE; 378 | constexpr u64 (*ATTACKS_FUNC)(const Square sq, u64 occ) = (PType == BISHOP ? tables::get_bishop_attacks 379 | : PType == ROOK ? tables::get_rook_attacks 380 | : tables::get_queen_attacks); 381 | u64 to_move_occupancies = board.get_occupancies(ToMove); 382 | u64 opponent_occupancies = board.get_occupancies(Opponent); 383 | u64 to_move_pieces = board.get_pieces(ToMove, PType); 384 | while (to_move_pieces) 385 | { 386 | Square from_square = bitboard::bit_scan_forward(to_move_pieces); 387 | u64 moves; 388 | if constexpr (GType == QUIETS) 389 | { 390 | moves = ATTACKS_FUNC(from_square, board.get_occupancies(BOTH)) & ~to_move_occupancies & ~opponent_occupancies; 391 | } 392 | else 393 | { 394 | moves = ATTACKS_FUNC(from_square, board.get_occupancies(BOTH)) & ~to_move_occupancies & opponent_occupancies; 395 | } 396 | while (moves) 397 | { 398 | Square to_square = bitboard::bit_scan_forward(moves); 399 | if constexpr (GType == QUIETS) 400 | { 401 | move_list.emplace_back(from_square, to_square, PType, EMPTY_PIECE, EMPTY_PIECE, false, false, false); 402 | } 403 | else 404 | { 405 | move_list.emplace_back(from_square, to_square, PType, board.get_piece_from_square(to_square).type, EMPTY_PIECE, false, false, false); 406 | } 407 | bitboard::pop_last_bit(moves); 408 | } 409 | bitboard::pop_last_bit(to_move_pieces); 410 | } 411 | } 412 | 413 | } // namespace movegen -------------------------------------------------------------------------------- /src/engine/movegen/movegen.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace movegen 10 | { 11 | /** 12 | * @brief Generates all the pseudo-legal moves for a given board 13 | * 14 | * @param board 15 | * @return std::vector 16 | */ 17 | std::vector generate_pseudo_legal_moves(const Board &board); 18 | 19 | /** 20 | * @brief Generates all the pseudo-legal captures for a given board 21 | * 22 | * @param board 23 | * @return std::vector 24 | */ 25 | std::vector generate_pseudo_legal_captures(const Board &board); 26 | 27 | /** 28 | * @brief Generates all the legal moves for a given board 29 | * 30 | * @param board 31 | * @return std::vector 32 | */ 33 | std::vector generateLegalMoves(Board &board); 34 | 35 | /** 36 | * @brief Generates all the legal moves for a given board 37 | * 38 | * @param board 39 | * @return std::vector 40 | */ 41 | std::vector generate_legal_captures(Board &board); 42 | 43 | /** 44 | * @brief Checks whether a given board as has legal moves 45 | * 46 | * @param board 47 | * @return bool 48 | */ 49 | bool has_legal_moves(Board &board); 50 | 51 | } // namespace movegen 52 | -------------------------------------------------------------------------------- /src/engine/movegen/perft.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace perft 10 | { 11 | int perft(Board &board, int depth) 12 | { 13 | if (depth == 0) 14 | { 15 | return 1; 16 | } 17 | 18 | int nodes = 0; 19 | for (const Move &move : movegen::generate_pseudo_legal_moves(board)) 20 | { 21 | Board::GameState board_info = board.get_state(); 22 | board.make(move); 23 | Square king_sq = bitboard::bit_scan_forward(board.get_pieces(board.get_opponent(), KING)); 24 | Color attacker_side = board.get_side_to_move(); 25 | if (!board.is_square_attacked(king_sq, attacker_side)) 26 | { 27 | nodes += perft(board, depth - 1); 28 | } 29 | board.unmake(move, board_info); 30 | } 31 | 32 | return nodes; 33 | } 34 | 35 | } // namespace perft -------------------------------------------------------------------------------- /src/engine/movegen/perft.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class Board; 6 | 7 | namespace perft 8 | { 9 | /** 10 | * @brief perft Function 11 | * 12 | * @param depth 13 | * @return int 14 | */ 15 | int perft(Board &board, int depth); 16 | 17 | } // namespace perft -------------------------------------------------------------------------------- /src/engine/movegen/tables.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace tables 8 | { 9 | u64 SQUARE_BB[N_SQUARES]; 10 | 11 | // clang-format off 12 | static const int RELEVANT_BITS_COUNT_BISHOP[N_SQUARES] = { 13 | 6, 5, 5, 5, 5, 5, 5, 6, 14 | 5, 5, 5, 5, 5, 5, 5, 5, 15 | 5, 5, 7, 7, 7, 7, 5, 5, 16 | 5, 5, 7, 9, 9, 7, 5, 5, 17 | 5, 5, 7, 9, 9, 7, 5, 5, 18 | 5, 5, 7, 7, 7, 7, 5, 5, 19 | 5, 5, 5, 5, 5, 5, 5, 5, 20 | 6, 5, 5, 5, 5, 5, 5, 6, 21 | }; 22 | // clang-format on 23 | 24 | // clang-format off 25 | static const int RELEVANT_BITS_COUNT_ROOK[N_SQUARES] = { 26 | 12, 11, 11, 11, 11, 11, 11, 12, 27 | 11, 10, 10, 10, 10, 10, 10, 11, 28 | 11, 10, 10, 10, 10, 10, 10, 11, 29 | 11, 10, 10, 10, 10, 10, 10, 11, 30 | 11, 10, 10, 10, 10, 10, 10, 11, 31 | 11, 10, 10, 10, 10, 10, 10, 11, 32 | 11, 10, 10, 10, 10, 10, 10, 11, 33 | 12, 11, 11, 11, 11, 11, 11, 12, 34 | }; 35 | // clang-format on 36 | 37 | static auto ATTACKS_PAWN = new u64[N_SIDES][N_SQUARES]; 38 | static u64 *ATTACKS_KNIGHT = new u64[N_SQUARES]; 39 | static u64 *ATTACKS_KING = new u64[N_SQUARES]; 40 | static auto ATTACKS_BISHOP = new u64[N_SQUARES][512]; 41 | static auto ATTACKS_ROOK = new u64[N_SQUARES][4096]; 42 | 43 | u64 square_to_bitboard(Square sq) 44 | { 45 | return SQUARE_BB[sq]; 46 | } 47 | 48 | int get_relevant_bits_count_bishop(Square sq) 49 | { 50 | return RELEVANT_BITS_COUNT_BISHOP[sq]; 51 | } 52 | 53 | int get_relevant_bits_count_rook(Square sq) 54 | { 55 | return RELEVANT_BITS_COUNT_ROOK[sq]; 56 | } 57 | 58 | void init() 59 | { 60 | // Misc. Tables 61 | for (int sq = A1; sq < N_SQUARES; sq++) 62 | { 63 | SQUARE_BB[sq] = ONE << sq; 64 | } 65 | 66 | // Initializing Leaper Piece Attack Tables 67 | for (int sq = A1; sq < N_SQUARES; sq++) 68 | { 69 | ATTACKS_PAWN[WHITE][sq] = attacks::mask_white_pawn_any_attacks(SQUARE_BB[sq]); 70 | ATTACKS_PAWN[BLACK][sq] = attacks::mask_black_pawn_any_attacks(SQUARE_BB[sq]); 71 | } 72 | 73 | for (int sq = A1; sq < N_SQUARES; sq++) 74 | { 75 | ATTACKS_KNIGHT[sq] = attacks::mask_knight_attacks(SQUARE_BB[sq]); 76 | } 77 | 78 | for (int sq = A1; sq < N_SQUARES; sq++) 79 | { 80 | ATTACKS_KING[sq] = attacks::mask_king_attacks(SQUARE_BB[sq]); 81 | } 82 | 83 | // Initialize Slider Piece Attack Tables 84 | for (int sq = A1; sq < N_SQUARES; sq++) 85 | { 86 | int occupancy_indices = 1 << RELEVANT_BITS_COUNT_BISHOP[sq]; 87 | for (int i = 0; i < occupancy_indices; i++) 88 | { 89 | magics::Magic magic = magics::get_bishop_magic((Square)sq); 90 | u64 occupancy = bitboard::set_occupancy(i, RELEVANT_BITS_COUNT_BISHOP[sq], magic.mask); 91 | int index = (int)((occupancy * magic.magic) >> magic.shift); 92 | ATTACKS_BISHOP[sq][index] = attacks::mask_bishop_xray_attacks((Square)sq, occupancy); 93 | } 94 | } 95 | 96 | for (int sq = A1; sq < N_SQUARES; sq++) 97 | { 98 | int occupancy_indices = 1 << RELEVANT_BITS_COUNT_ROOK[sq]; 99 | for (int i = 0; i < occupancy_indices; i++) 100 | { 101 | magics::Magic magic = magics::get_rook_magic((Square)sq); 102 | u64 occupancy = bitboard::set_occupancy(i, RELEVANT_BITS_COUNT_ROOK[sq], magic.mask); 103 | int index = (int)((occupancy * magic.magic) >> magic.shift); 104 | ATTACKS_ROOK[sq][index] = attacks::mask_rook_xray_attacks((Square)sq, occupancy); 105 | } 106 | } 107 | } 108 | 109 | void teardown() 110 | { 111 | delete[] ATTACKS_PAWN; 112 | delete[] ATTACKS_BISHOP; 113 | delete[] ATTACKS_ROOK; 114 | } 115 | 116 | u64 get_pawn_attacks(Color color, Square sq) 117 | { 118 | return ATTACKS_PAWN[color][sq]; 119 | } 120 | 121 | u64 get_knight_attacks(Square sq) 122 | { 123 | return ATTACKS_KNIGHT[sq]; 124 | } 125 | 126 | u64 get_king_attacks(Square sq) 127 | { 128 | return ATTACKS_KING[sq]; 129 | } 130 | 131 | u64 get_bishop_attacks(const Square sq, u64 occ) 132 | { 133 | magics::Magic magic = magics::get_bishop_magic(sq); 134 | occ &= magic.mask; 135 | occ *= magic.magic; 136 | occ >>= magic.shift; 137 | return ATTACKS_BISHOP[sq][occ]; 138 | } 139 | 140 | u64 get_rook_attacks(const Square sq, u64 occ) 141 | { 142 | magics::Magic magic = magics::get_rook_magic(sq); 143 | occ &= magic.mask; 144 | occ *= magic.magic; 145 | occ >>= magic.shift; 146 | return ATTACKS_ROOK[sq][occ]; 147 | } 148 | 149 | u64 get_queen_attacks(const Square sq, u64 occ) 150 | { 151 | return get_bishop_attacks(sq, occ) | get_rook_attacks(sq, occ); 152 | } 153 | 154 | } // namespace tables -------------------------------------------------------------------------------- /src/engine/movegen/tables.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace tables 8 | { 9 | /** 10 | * This arrays should only be indexed with compile time values 11 | */ 12 | 13 | constexpr u64 MASK_RANK[] = { 14 | 0xFF, 15 | 0xFF00, 16 | 0xFF0000, 17 | 0xFF000000, 18 | 0xFF00000000, 19 | 0xFF0000000000, 20 | 0xFF000000000000, 21 | 0xFF00000000000000}; 22 | 23 | constexpr u64 MASK_FILE[] = { 24 | 0x0101010101010101, 25 | 0x202020202020202, 26 | 0x404040404040404, 27 | 0x808080808080808, 28 | 0x1010101010101010, 29 | 0x2020202020202020, 30 | 0x4040404040404040, 31 | 0x8080808080808080}; 32 | 33 | constexpr u64 MASK_CLEAR_RANK[] = { 34 | 0xFFFFFFFFFFFFFF00, 35 | 0xFFFFFFFFFFFF00FF, 36 | 0xFFFFFFFFFF00FFFF, 37 | 0xFFFFFFFF00FFFFFF, 38 | 0xFFFFFF00FFFFFFFF, 39 | 0xFFFF00FFFFFFFFFF, 40 | 0xFF00FFFFFFFFFFFF, 41 | 0x00FFFFFFFFFFFFFF}; 42 | 43 | constexpr u64 MASK_CLEAR_FILE[] = { 44 | 0xFEFEFEFEFEFEFEFE, 45 | 0xFDFDFDFDFDFDFDFD, 46 | 0xFBFBFBFBFBFBFBFB, 47 | 0xF7F7F7F7F7F7F7F7, 48 | 0xEFEFEFEFEFEFEFEF, 49 | 0xDFDFDFDFDFDFDFDF, 50 | 0xBFBFBFBFBFBFBFBF, 51 | 0x7F7F7F7F7F7F7F7F}; 52 | 53 | /** 54 | * @brief Converts a square to its bitboard representation 55 | * 56 | * @param sq 57 | * @return u64 58 | */ 59 | u64 square_to_bitboard(Square sq); 60 | 61 | /** 62 | * @brief Gets the relevant bits count for the bishop piece 63 | * 64 | * @param sq 65 | * @return int 66 | */ 67 | int get_relevant_bits_count_bishop(Square sq); 68 | 69 | /** 70 | * @brief Gets the relevant bits count for the rook piece 71 | * 72 | * @param sq 73 | * @return int 74 | */ 75 | int get_relevant_bits_count_rook(Square sq); 76 | 77 | /** 78 | * @brief Inits all the tables 79 | * 80 | */ 81 | void init(); 82 | 83 | /** 84 | * @brief Deallocates all the heap allocated tables 85 | * 86 | */ 87 | void teardown(); 88 | 89 | /** 90 | * @brief Gets the pawn attacks 91 | * 92 | * @param color 93 | * @param sq 94 | * @return u64 95 | */ 96 | u64 get_pawn_attacks(Color color, Square sq); 97 | 98 | /** 99 | * @brief Gets the knight attacks 100 | * 101 | * @param color 102 | * @param sq 103 | * @return u64 104 | */ 105 | u64 get_knight_attacks(Square sq); 106 | 107 | /** 108 | * @brief Gets the king attacks 109 | * 110 | * @param color 111 | * @param sq 112 | * @return u64 113 | */ 114 | u64 get_king_attacks(Square sq); 115 | 116 | /** 117 | * @brief Gets the bishop attacks 118 | * 119 | * @param sq 120 | * @param occ 121 | * @return u64 122 | */ 123 | u64 get_bishop_attacks(const Square sq, u64 occ); 124 | 125 | /** 126 | * @brief Gets the rook attacks 127 | * 128 | * @param sq 129 | * @param occ 130 | * @return u64 131 | */ 132 | u64 get_rook_attacks(const Square sq, u64 occ); 133 | 134 | /** 135 | * @brief Gets the queen attacks 136 | * 137 | * @param sq 138 | * @param occ 139 | * @return u64 140 | */ 141 | u64 get_queen_attacks(const Square sq, u64 occ); 142 | 143 | } // namespace tables -------------------------------------------------------------------------------- /src/engine/movepicker/eval.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace eval 8 | { 9 | // clang-format off 10 | int MG_PAWN_TABLE[64] = { 11 | 0, 0, 0, 0, 0, 0, 0, 0, 12 | 98, 134, 61, 95, 68, 126, 34, -11, 13 | -6, 7, 26, 31, 65, 56, 25, -20, 14 | -14, 13, 6, 21, 23, 12, 17, -23, 15 | -27, -2, -5, 12, 17, 6, 10, -25, 16 | -26, -4, -4, -10, 3, 3, 33, -12, 17 | -35, -1, -20, -23, -15, 24, 38, -22, 18 | 0, 0, 0, 0, 0, 0, 0, 0, 19 | }; 20 | 21 | int MG_KNIGHT_TABLE[64] = { 22 | -167, -89, -34, -49, 61, -97, -15, -107, 23 | -73, -41, 72, 36, 23, 62, 7, -17, 24 | -47, 60, 37, 65, 84, 129, 73, 44, 25 | -9, 17, 19, 53, 37, 69, 18, 22, 26 | -13, 4, 16, 13, 28, 19, 21, -8, 27 | -23, -9, 12, 10, 19, 17, 25, -16, 28 | -29, -53, -12, -3, -1, 18, -14, -19, 29 | -105, -21, -58, -33, -17, -28, -19, -23, 30 | }; 31 | 32 | int MG_BISHOP_TABLE[64] = { 33 | -29, 4, -82, -37, -25, -42, 7, -8, 34 | -26, 16, -18, -13, 30, 59, 18, -47, 35 | -16, 37, 43, 40, 35, 50, 37, -2, 36 | -4, 5, 19, 50, 37, 37, 7, -2, 37 | -6, 13, 13, 26, 34, 12, 10, 4, 38 | 0, 15, 15, 15, 14, 27, 18, 10, 39 | 4, 15, 16, 0, 7, 21, 33, 1, 40 | -33, -3, -14, -21, -13, -12, -39, -21, 41 | }; 42 | 43 | int MG_ROOK_TABLE[64] = { 44 | 32, 42, 32, 51, 63, 9, 31, 43, 45 | 27, 32, 58, 62, 80, 67, 26, 44, 46 | -5, 19, 26, 36, 17, 45, 61, 16, 47 | -24, -11, 7, 26, 24, 35, -8, -20, 48 | -36, -26, -12, -1, 9, -7, 6, -23, 49 | -45, -25, -16, -17, 3, 0, -5, -33, 50 | -44, -16, -20, -9, -1, 11, -6, -71, 51 | -19, -13, 1, 17, 16, 7, -37, -26, 52 | }; 53 | 54 | int MG_QUEEN_TABLE[64] = { 55 | -28, 0, 29, 12, 59, 44, 43, 45, 56 | -24, -39, -5, 1, -16, 57, 28, 54, 57 | -13, -17, 7, 8, 29, 56, 47, 57, 58 | -27, -27, -16, -16, -1, 17, -2, 1, 59 | -9, -26, -9, -10, -2, -4, 3, -3, 60 | -14, 2, -11, -2, -5, 2, 14, 5, 61 | -35, -8, 11, 2, 8, 15, -3, 1, 62 | -1, -18, -9, 10, -15, -25, -31, -50, 63 | }; 64 | 65 | int MG_KING_TABLE[64] = { 66 | -65, 23, 16, -15, -56, -34, 2, 13, 67 | 29, -1, -20, -7, -8, -4, -38, -29, 68 | -9, 24, 2, -16, -20, 6, 22, -22, 69 | -17, -20, -12, -27, -30, -25, -14, -36, 70 | -49, -1, -27, -39, -46, -44, -33, -51, 71 | -14, -14, -22, -46, -44, -30, -15, -27, 72 | 1, 7, -8, -64, -43, -16, 9, 8, 73 | -15, 36, 12, -54, 8, -28, 24, 14, 74 | }; 75 | 76 | int EG_PAWN_TABLE[64] = { 77 | 0, 0, 0, 0, 0, 0, 0, 0, 78 | 178, 173, 158, 134, 147, 132, 165, 187, 79 | 94, 100, 85, 67, 56, 53, 82, 84, 80 | 32, 24, 13, 5, -2, 4, 17, 17, 81 | 13, 9, -3, -7, -7, -8, 3, -1, 82 | 4, 7, -6, 1, 0, -5, -1, -8, 83 | 13, 8, 8, 10, 13, 0, 2, -7, 84 | 0, 0, 0, 0, 0, 0, 0, 0, 85 | }; 86 | 87 | int EG_KNIGHT_TABLE[64] = { 88 | -58, -38, -13, -28, -31, -27, -63, -99, 89 | -25, -8, -25, -2, -9, -25, -24, -52, 90 | -24, -20, 10, 9, -1, -9, -19, -41, 91 | -17, 3, 22, 22, 22, 11, 8, -18, 92 | -18, -6, 16, 25, 16, 17, 4, -18, 93 | -23, -3, -1, 15, 10, -3, -20, -22, 94 | -42, -20, -10, -5, -2, -20, -23, -44, 95 | -29, -51, -23, -15, -22, -18, -50, -64, 96 | }; 97 | 98 | int EG_BISHOP_TABLE[64] = { 99 | -14, -21, -11, -8, -7, -9, -17, -24, 100 | -8, -4, 7, -12, -3, -13, -4, -14, 101 | 2, -8, 0, -1, -2, 6, 0, 4, 102 | -3, 9, 12, 9, 14, 10, 3, 2, 103 | -6, 3, 13, 19, 7, 10, -3, -9, 104 | -12, -3, 8, 10, 13, 3, -7, -15, 105 | -14, -18, -7, -1, 4, -9, -15, -27, 106 | -23, -9, -23, -5, -9, -16, -5, -17, 107 | }; 108 | 109 | int EG_ROOK_TABLE[64] = { 110 | 13, 10, 18, 15, 12, 12, 8, 5, 111 | 11, 13, 13, 11, -3, 3, 8, 3, 112 | 7, 7, 7, 5, 4, -3, -5, -3, 113 | 4, 3, 13, 1, 2, 1, -1, 2, 114 | 3, 5, 8, 4, -5, -6, -8, -11, 115 | -4, 0, -5, -1, -7, -12, -8, -16, 116 | -6, -6, 0, 2, -9, -9, -11, -3, 117 | -9, 2, 3, -1, -5, -13, 4, -20, 118 | }; 119 | 120 | int EG_QUEEN_TABLE[64] = { 121 | -9, 22, 22, 27, 27, 19, 10, 20, 122 | -17, 20, 32, 41, 58, 25, 30, 0, 123 | -20, 6, 9, 49, 47, 35, 19, 9, 124 | 3, 22, 24, 45, 57, 40, 57, 36, 125 | -18, 28, 19, 47, 31, 34, 39, 23, 126 | -16, -27, 15, 6, 9, 17, 10, 5, 127 | -22, -23, -30, -16, -16, -23, -36, -32, 128 | -33, -28, -22, -43, -5, -32, -20, -41, 129 | }; 130 | 131 | int EG_KING_TABLE[64] = { 132 | -74, -35, -18, -18, -11, 15, 4, -17, 133 | -12, 17, 14, 17, 17, 38, 23, 11, 134 | 10, 17, 23, 15, 20, 45, 44, 13, 135 | -8, 22, 24, 27, 26, 33, 26, 3, 136 | -18, -4, 21, 24, 27, 23, 9, -11, 137 | -19, -3, 11, 21, 23, 16, 7, -9, 138 | -27, -11, 4, 13, 14, 4, -5, -17, 139 | -53, -34, -21, -11, -28, -14, -24, -43 140 | }; 141 | // clang-format on 142 | 143 | int MG_TABLE[2][6][64]; 144 | int EG_TABLE[2][6][64]; 145 | int GAME_PHASE_INC[6] = {0, 1, 1, 2, 4, 0}; 146 | 147 | // clang-format off 148 | int PIECE_SCORES[] = {100, 280, 320, 479, 929, 60000}; 149 | // clang-format on 150 | 151 | void init() 152 | { 153 | for (int sq = A1; sq < N_SQUARES; sq++) 154 | { 155 | Square sq_flipped = utils::flip_square((Square)sq); 156 | 157 | MG_TABLE[WHITE][PAWN][sq] = MG_PAWN_TABLE[sq_flipped]; 158 | MG_TABLE[WHITE][KNIGHT][sq] = MG_KNIGHT_TABLE[sq_flipped]; 159 | MG_TABLE[WHITE][BISHOP][sq] = MG_BISHOP_TABLE[sq_flipped]; 160 | MG_TABLE[WHITE][ROOK][sq] = MG_ROOK_TABLE[sq_flipped]; 161 | MG_TABLE[WHITE][QUEEN][sq] = MG_QUEEN_TABLE[sq_flipped]; 162 | MG_TABLE[WHITE][KING][sq] = MG_KING_TABLE[sq_flipped]; 163 | 164 | MG_TABLE[BLACK][PAWN][sq] = MG_PAWN_TABLE[sq]; 165 | MG_TABLE[BLACK][KNIGHT][sq] = MG_KNIGHT_TABLE[sq]; 166 | MG_TABLE[BLACK][BISHOP][sq] = MG_BISHOP_TABLE[sq]; 167 | MG_TABLE[BLACK][ROOK][sq] = MG_ROOK_TABLE[sq]; 168 | MG_TABLE[BLACK][QUEEN][sq] = MG_QUEEN_TABLE[sq]; 169 | MG_TABLE[BLACK][KING][sq] = MG_KING_TABLE[sq]; 170 | 171 | EG_TABLE[WHITE][PAWN][sq] = EG_PAWN_TABLE[sq_flipped]; 172 | EG_TABLE[WHITE][KNIGHT][sq] = EG_KNIGHT_TABLE[sq_flipped]; 173 | EG_TABLE[WHITE][BISHOP][sq] = EG_BISHOP_TABLE[sq_flipped]; 174 | EG_TABLE[WHITE][ROOK][sq] = EG_ROOK_TABLE[sq_flipped]; 175 | EG_TABLE[WHITE][QUEEN][sq] = EG_QUEEN_TABLE[sq_flipped]; 176 | EG_TABLE[WHITE][KING][sq] = EG_KING_TABLE[sq_flipped]; 177 | 178 | EG_TABLE[BLACK][PAWN][sq] = EG_PAWN_TABLE[sq]; 179 | EG_TABLE[BLACK][KNIGHT][sq] = EG_KNIGHT_TABLE[sq]; 180 | EG_TABLE[BLACK][BISHOP][sq] = EG_BISHOP_TABLE[sq]; 181 | EG_TABLE[BLACK][ROOK][sq] = EG_ROOK_TABLE[sq]; 182 | EG_TABLE[BLACK][QUEEN][sq] = EG_QUEEN_TABLE[sq]; 183 | EG_TABLE[BLACK][KING][sq] = EG_KING_TABLE[sq]; 184 | } 185 | } 186 | 187 | int eval(const Board &board) 188 | { 189 | int game_phase = 0; 190 | int material[2]{}; 191 | int mg[2]{}; 192 | int eg[2]{}; 193 | 194 | for (int piece_type = PAWN; piece_type < N_PIECES; piece_type++) 195 | { 196 | u64 pieces_white = board.get_pieces(WHITE, (PieceType)piece_type); 197 | while (pieces_white) 198 | { 199 | Square sq = bitboard::bit_scan_forward(pieces_white); 200 | mg[WHITE] += MG_TABLE[WHITE][piece_type][sq]; 201 | eg[WHITE] += EG_TABLE[WHITE][piece_type][sq]; 202 | material[WHITE] += PIECE_SCORES[piece_type]; 203 | game_phase += GAME_PHASE_INC[piece_type]; 204 | bitboard::pop_bit(pieces_white, sq); 205 | } 206 | 207 | u64 pieces_black = board.get_pieces(BLACK, (PieceType)piece_type); 208 | while (pieces_black) 209 | { 210 | Square sq = bitboard::bit_scan_forward(pieces_black); 211 | mg[BLACK] += MG_TABLE[BLACK][piece_type][sq]; 212 | eg[BLACK] += EG_TABLE[BLACK][piece_type][sq]; 213 | material[BLACK] += PIECE_SCORES[piece_type]; 214 | game_phase += GAME_PHASE_INC[piece_type]; 215 | bitboard::pop_bit(pieces_black, sq); 216 | } 217 | } 218 | 219 | Color to_move = board.get_side_to_move(); 220 | Color opponent = board.get_opponent(); 221 | 222 | int mg_score = mg[to_move] - mg[opponent]; 223 | int eg_score = eg[to_move] - eg[opponent]; 224 | int material_score = material[to_move] - material[opponent]; 225 | 226 | int mg_phase = game_phase > 24 ? 24 : game_phase; 227 | int eg_phase = 24 - mg_phase; 228 | return ((mg_score * mg_phase + eg_score * eg_phase) / 24) + material_score; 229 | } 230 | 231 | } // namespace eval -------------------------------------------------------------------------------- /src/engine/movepicker/eval.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class Board; 6 | 7 | namespace eval 8 | { 9 | void init(); 10 | 11 | /** 12 | * @brief Evaluates a given board 13 | * Positive values represent favorable position for white 14 | * Negative values represent favourable position for black 15 | * 16 | * @param board 17 | * @return absolute evaluation 18 | */ 19 | int eval(const Board &board); 20 | 21 | } // namespace eval -------------------------------------------------------------------------------- /src/engine/movepicker/histtable.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | HistoryTable::~HistoryTable() 4 | { 5 | delete[] _hist_table; 6 | } 7 | 8 | bool HistoryTable::is_repetition(u64 key) const 9 | { 10 | for (int index = 0; index < _index; index++) 11 | { 12 | if (_hist_table[index] == key) 13 | { 14 | return true; 15 | } 16 | } 17 | 18 | return false; 19 | } 20 | 21 | void HistoryTable::push(u64 key) 22 | { 23 | _hist_table[_index++] = key; 24 | } 25 | 26 | void HistoryTable::pop() 27 | { 28 | --_index; 29 | } 30 | 31 | void HistoryTable::clear() 32 | { 33 | _index = 0; 34 | } 35 | -------------------------------------------------------------------------------- /src/engine/movepicker/histtable.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class HistoryTable 6 | { 7 | private: 8 | static const int MAX_HISTORY_SIZE = 128; 9 | 10 | int _index = 0; 11 | u64 *_hist_table = new u64[MAX_HISTORY_SIZE]; 12 | 13 | public: 14 | ~HistoryTable(); 15 | 16 | bool is_repetition(u64 key) const; 17 | 18 | void push(u64 key); 19 | void pop(); 20 | 21 | void clear(); 22 | }; -------------------------------------------------------------------------------- /src/engine/movepicker/movepicker.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #define R 2 16 | #define REDUCTION_LIMIT 3 17 | #define FULL_DEPTH_MOVES 4 18 | #define WINDOW_EXPANSION 50 19 | #define MIN_EVAL (INT_MIN + 1) 20 | 21 | static bool can_lmr(const Move move) { 22 | // Move can't be a capture nor a promotion for LMR to happen 23 | /* TODO: missing check moves and in check moves */ 24 | return !move.is_capture() && !move.is_promotion(); 25 | } 26 | 27 | int MovePicker::score(const Move move) { 28 | // clang-format off 29 | static const int MVV_LVA[6][6] = { 30 | {10105, 10205, 10305, 10405, 10505, 10605}, 31 | {10104, 10204, 10304, 10404, 10504, 10604}, 32 | {10103, 10203, 10303, 10403, 10503, 10603}, 33 | {10102, 10202, 10302, 10402, 10502, 10602}, 34 | {10101, 10201, 10301, 10401, 10501, 10601}, 35 | {10100, 10200, 10300, 10400, 10500, 10600} 36 | }; 37 | // clang-format on 38 | 39 | if (_pv_table.get_pv_move(_current_depth) == move) { 40 | return 20000; 41 | } 42 | 43 | if (move.is_capture()) { 44 | return MVV_LVA[move.get_piece_type()][move.get_captured_piece_type()]; 45 | } 46 | 47 | if (_killer_moves[0][_current_depth] == move) { 48 | return 9000; 49 | } 50 | 51 | if (_killer_moves[1][_current_depth] == move) { 52 | return 8000; 53 | } 54 | 55 | return _history_moves[_board.get_side_to_move()][move.get_piece_type()] 56 | [move.get_to_square()]; 57 | } 58 | 59 | int MovePicker::search(int depth, int alpha, int beta) { 60 | auto moves = movegen::generate_pseudo_legal_moves(_board); 61 | std::sort(moves.begin(), moves.end(), _move_more_than_key); 62 | 63 | Move best_move = Move(); 64 | Board::GameState state = _board.get_state(); 65 | for (const Move &move : moves) { 66 | _board.make(move); 67 | 68 | Color attacker_side = _board.get_side_to_move(); 69 | Square king_sq = bitboard::bit_scan_forward( 70 | _board.get_pieces(_board.get_opponent(), KING)); 71 | if (!_board.is_square_attacked(king_sq, attacker_side)) { 72 | _current_depth++; 73 | _hist_table.push(state.hash_key); 74 | int score = -negamax(-beta, -alpha, depth - 1); 75 | _hist_table.pop(); 76 | _current_depth--; 77 | if (score > alpha) { 78 | // History Move Heuristic 79 | if (!move.is_capture()) { 80 | this->add_to_history_moves(move); 81 | } 82 | 83 | alpha = score; 84 | best_move = move; 85 | _pv_table.add(best_move, _current_depth); 86 | } 87 | } 88 | 89 | _board.unmake(move, state); 90 | } 91 | 92 | return alpha; 93 | } 94 | 95 | int MovePicker::negamax(int alpha, int beta, int depth) { 96 | _current_nodes++; 97 | 98 | // Terminal Node 99 | if (_board.get_half_move_clock() == 100) { 100 | // Draw 101 | return 0; 102 | } 103 | 104 | // Terminal Node 105 | if (_hist_table.is_repetition(_board.get_hash_key())) { 106 | // Three-Fold Draw 107 | return 0; 108 | } 109 | 110 | TTable::TTOutput hash_read = 111 | _tt.read_hash(_board.get_hash_key(), alpha, beta, depth); 112 | if (hash_read.found) { 113 | _pv_table.add_pv_from_depth(hash_read.moves, _current_depth); 114 | return hash_read.score; 115 | } 116 | 117 | // Forced Terminal Node 118 | if (depth <= 0) { 119 | _pv_table.set_length(_current_depth); 120 | return quiescence(alpha, beta); 121 | } 122 | 123 | Board::GameState state = _board.get_state(); 124 | 125 | // Null Move Pruning 126 | // TODO: Zugzwang checking 127 | if (depth >= 3) { 128 | _board.switch_side_to_move(); 129 | _board.set_en_passant_square(EMPTY_SQUARE); 130 | _current_depth += 2; 131 | int score = -negamax(-beta, -beta + 1, depth - 1 - R); 132 | _current_depth -= 2; 133 | _board.set_state(state); 134 | _board.switch_side_to_move(); 135 | if (score >= beta) { 136 | _tt.set_entry(state.hash_key, depth, TTable::HASH_FLAG_BETA, beta, 137 | _pv_table.get_pv_from_depth(_current_depth)); 138 | return beta; 139 | } 140 | } 141 | 142 | auto moves = movegen::generate_pseudo_legal_moves(_board); 143 | std::sort(moves.begin(), moves.end(), _move_more_than_key); 144 | 145 | Move best_move = Move(); 146 | int n_moves_searched = 0; 147 | bool has_legal_moves = false; 148 | int alpha_cutoff = TTable::HASH_FLAG_ALPHA; 149 | for (const Move &move : moves) { 150 | _board.make(move); 151 | 152 | Square king_sq = bitboard::bit_scan_forward( 153 | _board.get_pieces(_board.get_opponent(), KING)); 154 | if (!_board.is_square_attacked(king_sq, _board.get_side_to_move())) { 155 | has_legal_moves = true; 156 | 157 | int score; 158 | // First move, then use Full Window Search 159 | if (n_moves_searched == 0) { 160 | _current_depth++; 161 | _hist_table.push(state.hash_key); 162 | score = -negamax(-beta, -alpha, depth - 1); 163 | _hist_table.pop(); 164 | _current_depth--; 165 | } else { 166 | // For all the others moves, we assume they are worse moves than 167 | // the first one, so let's try to use Null Window Search first 168 | if (n_moves_searched >= FULL_DEPTH_MOVES && depth >= REDUCTION_LIMIT && 169 | can_lmr(move)) { 170 | // Perform a Null Window Search 171 | _current_depth++; 172 | _hist_table.push(state.hash_key); 173 | score = -negamax(-(alpha + 1), -alpha, depth - 2); 174 | _hist_table.pop(); 175 | _current_depth--; 176 | } else { 177 | // Hack to ensure that Full Depth Search is done 178 | score = alpha + 1; 179 | } 180 | 181 | // If this move failed to prove to be bad, re-search with normal bounds 182 | if (score > alpha) { 183 | _current_depth++; 184 | _hist_table.push(state.hash_key); 185 | score = -negamax(-(alpha + 1), -alpha, depth - 1); 186 | _hist_table.pop(); 187 | _current_depth--; 188 | 189 | if ((score > alpha) && (score < beta)) { 190 | _current_depth++; 191 | _hist_table.push(state.hash_key); 192 | score = -negamax(-beta, -alpha, depth - 1); 193 | _hist_table.pop(); 194 | _current_depth--; 195 | } 196 | } 197 | } 198 | 199 | if (score >= beta) { 200 | // Killer Move Heuristic 201 | if (!move.is_capture()) { 202 | this->add_to_killer_moves(move); 203 | } 204 | 205 | _board.unmake(move, state); 206 | 207 | _tt.set_entry(state.hash_key, depth, TTable::HASH_FLAG_BETA, beta, 208 | _pv_table.get_pv_from_depth(_current_depth)); 209 | return beta; 210 | } else if (score > alpha) { 211 | 212 | // History Move Heuristic 213 | if (!move.is_capture()) { 214 | this->add_to_history_moves(move); 215 | } 216 | 217 | alpha = score; 218 | alpha_cutoff = TTable::HASH_FLAG_SCORE; 219 | best_move = move; 220 | _pv_table.add(best_move, _current_depth); 221 | } 222 | 223 | n_moves_searched++; 224 | } 225 | 226 | _board.unmake(move, state); 227 | } 228 | 229 | // Terminal Node 230 | if (!has_legal_moves) { 231 | // Check Mate 232 | Square king_sq = bitboard::bit_scan_forward( 233 | _board.get_pieces(_board.get_side_to_move(), KING)); 234 | if (_board.is_square_attacked(king_sq, _board.get_opponent())) { 235 | _tt.set_entry(state.hash_key, depth, TTable::HASH_FLAG_SCORE, 236 | MIN_EVAL + _current_depth, 237 | _pv_table.get_pv_from_depth(_current_depth)); 238 | return MIN_EVAL + _current_depth; 239 | } 240 | 241 | // Stale Mate 242 | _tt.set_entry(state.hash_key, depth, TTable::HASH_FLAG_SCORE, 0, 243 | _pv_table.get_pv_from_depth(_current_depth)); 244 | return 0; 245 | } 246 | 247 | _tt.set_entry(state.hash_key, depth, alpha_cutoff, alpha, 248 | _pv_table.get_pv_from_depth(_current_depth)); 249 | return alpha; 250 | } 251 | 252 | int MovePicker::quiescence(int alpha, int beta) { 253 | _current_nodes++; 254 | 255 | if (_board.get_half_move_clock() == 100) { 256 | _tt.set_entry(_board.get_hash_key(), 0, TTable::HASH_FLAG_SCORE, 0, 257 | _pv_table.get_pv_from_depth(_current_depth)); 258 | return 0; 259 | } 260 | 261 | if (!movegen::has_legal_moves(_board)) { 262 | Square king_sq = bitboard::bit_scan_forward( 263 | _board.get_pieces(_board.get_side_to_move(), KING)); 264 | if (_board.is_square_attacked(king_sq, _board.get_opponent())) { 265 | _tt.set_entry(_board.get_hash_key(), 0, TTable::HASH_FLAG_SCORE, 266 | MIN_EVAL + _current_depth, 267 | _pv_table.get_pv_from_depth(_current_depth)); 268 | return MIN_EVAL + _current_depth; 269 | } 270 | 271 | _tt.set_entry(_board.get_hash_key(), 0, TTable::HASH_FLAG_SCORE, 0, 272 | _pv_table.get_pv_from_depth(_current_depth)); 273 | return 0; 274 | } 275 | 276 | int alpha_cutoff = TTable::HASH_FLAG_ALPHA; 277 | 278 | int stand_pat = eval::eval(_board); 279 | if (stand_pat >= beta) { 280 | _tt.set_entry(_board.get_hash_key(), 0, TTable::HASH_FLAG_BETA, beta, 281 | _pv_table.get_pv_from_depth(_current_depth)); 282 | return beta; 283 | } else if (stand_pat > alpha) { 284 | 285 | alpha_cutoff = TTable::HASH_FLAG_SCORE; 286 | alpha = stand_pat; 287 | } 288 | 289 | auto captures = movegen::generate_pseudo_legal_captures(_board); 290 | std::sort(captures.begin(), captures.end(), _move_more_than_key); 291 | 292 | u64 hash_key = _board.get_hash_key(); 293 | Board::GameState state = _board.get_state(); 294 | for (const Move &capture : captures) { 295 | _board.make(capture); 296 | 297 | Square king_sq = bitboard::bit_scan_forward( 298 | _board.get_pieces(_board.get_opponent(), KING)); 299 | if (!_board.is_square_attacked(king_sq, _board.get_side_to_move())) { 300 | _current_depth++; 301 | int score = -quiescence(-beta, -alpha); 302 | _current_depth--; 303 | 304 | if (score >= beta) { 305 | _board.unmake(capture, state); 306 | _tt.set_entry(hash_key, 0, TTable::HASH_FLAG_BETA, beta, 307 | _pv_table.get_pv_from_depth(_current_depth)); 308 | return beta; 309 | } else if (score > alpha) { 310 | 311 | alpha_cutoff = TTable::HASH_FLAG_SCORE; 312 | alpha = score; 313 | } 314 | } 315 | 316 | _board.unmake(capture, state); 317 | } 318 | 319 | _tt.set_entry(_board.get_hash_key(), 0, alpha_cutoff, alpha, 320 | _pv_table.get_pv_from_depth(_current_depth)); 321 | return alpha; 322 | } 323 | 324 | void MovePicker::add_to_killer_moves(const Move move) { 325 | // Killer Move Heuristic 326 | if (move != _killer_moves[0][_current_depth]) { 327 | _killer_moves[1][_current_depth] = _killer_moves[0][_current_depth]; 328 | } 329 | _killer_moves[0][_current_depth] = move; 330 | } 331 | 332 | void MovePicker::add_to_history_moves(const Move move) { 333 | // History Move Heuristic 334 | _history_moves[_board.get_side_to_move()][move.get_piece_type()] 335 | [move.get_to_square()] += _current_depth; 336 | } 337 | 338 | void MovePicker::clear_search_counters() { 339 | _current_nodes = 0; 340 | _current_depth = 0; 341 | } 342 | 343 | // Public Methods 344 | 345 | int MovePicker::get_max_depth() const { return _max_depth; } 346 | 347 | void MovePicker::set_max_depth(int depth) { 348 | if (depth <= 0) { 349 | throw std::invalid_argument("Depth argument must be positive integer."); 350 | } else if (depth > DEFAULT_MAX_DEPTH) { 351 | throw std::invalid_argument("Depth argument must be less than 64."); 352 | } 353 | 354 | _max_depth = depth; 355 | } 356 | 357 | MovePicker::SearchResult MovePicker::find_best_move() { 358 | int alpha = MIN_EVAL; 359 | int beta = -MIN_EVAL; 360 | 361 | this->clear_move_tables(); 362 | 363 | // Iterative Deepening 364 | for (int depth = 1; depth <= _max_depth; depth++) { 365 | this->clear_search_counters(); 366 | int score = search(depth, alpha, beta); 367 | 368 | // Aspiration Window 369 | if ((score <= alpha) || (score >= beta)) { 370 | // We fall outside the window, so the next search 371 | // iteration is going to have a full width window and same depth 372 | alpha = MIN_EVAL; 373 | beta = -MIN_EVAL; 374 | 375 | depth--; 376 | continue; 377 | } 378 | 379 | alpha = score - WINDOW_EXPANSION; 380 | beta = score + WINDOW_EXPANSION; 381 | } 382 | 383 | auto result = SearchResult{alpha, _current_nodes, _pv_table.get_pv()}; 384 | return result; 385 | } 386 | 387 | /** 388 | * @brief This function corresponds to one of 389 | * the loops of find_best_move() 390 | * 391 | * @param depth 392 | * @return MovePicker::SearchResult 393 | */ 394 | MovePicker::SearchResult MovePicker::find_best_move(int depth, int alpha, 395 | int beta) { 396 | this->clear_search_counters(); 397 | int score = search(depth, alpha, beta); 398 | auto result = SearchResult{score, _current_nodes, _pv_table.get_pv()}; 399 | return result; 400 | } 401 | 402 | void MovePicker::clear_move_tables() { 403 | memset(_history_moves, 0, sizeof(_history_moves)); 404 | memset(_killer_moves, 0, sizeof(_killer_moves)); 405 | memset(_killer_moves, 0, sizeof(_killer_moves)); 406 | _pv_table.clear(); 407 | } 408 | 409 | void MovePicker::clear_transposition_table() { _tt.clear(); } 410 | 411 | void MovePicker::add_to_history(u64 key) { _hist_table.push(key); } 412 | 413 | void MovePicker::clear_history() { _hist_table.clear(); } 414 | -------------------------------------------------------------------------------- /src/engine/movepicker/movepicker.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | class MovePicker 14 | { 15 | private: 16 | static const int DEFAULT_MAX_DEPTH = 64; 17 | 18 | Board &_board; 19 | 20 | int _max_depth; 21 | int _current_nodes; 22 | int _current_depth; 23 | 24 | int _history_moves[N_SIDES][N_PIECES][N_SQUARES]{}; 25 | Move _killer_moves[2][DEFAULT_MAX_DEPTH]{}; 26 | 27 | PVTable _pv_table; 28 | HistoryTable _hist_table; 29 | TTable _tt; 30 | 31 | struct MoveMoreThanKey 32 | { 33 | MovePicker &move_picker; 34 | inline bool operator()(const Move &move1, const Move &move2) const 35 | { 36 | return (move_picker.score(move1) > move_picker.score(move2)); 37 | } 38 | } _move_more_than_key; 39 | 40 | /** 41 | * @brief Evaluates a given move using a few heuristics: 42 | * - MVV LVA 43 | * - Killer Heuristic 44 | * - History Heuristic 45 | * 46 | * It is used for sorting the list of moves upon 47 | * generation, ini order to cause the most alpha-beta cuts. 48 | * 49 | * @param move 50 | * @return move score 51 | */ 52 | int score(const Move move); 53 | 54 | int search(int depth, int alpha, int beta); 55 | int negamax(int alpha, int beta, int depth); 56 | int quiescence(int alpha, int beta); 57 | 58 | void add_to_killer_moves(const Move move); 59 | void add_to_history_moves(const Move move); 60 | 61 | void clear_search_counters(); 62 | 63 | public: 64 | struct SearchResult 65 | { 66 | int score{}; 67 | int nodes{}; 68 | std::vector pv; 69 | }; 70 | 71 | explicit MovePicker(Board &board) : _board(board), _max_depth(6), _current_nodes(0), _move_more_than_key{*this} {} 72 | 73 | [[nodiscard]] int get_max_depth() const; 74 | 75 | /** 76 | * @brief Set the max depth for the search 77 | * 78 | * @param depth 79 | */ 80 | void set_max_depth(int depth); 81 | 82 | /** 83 | * @brief Adds a board hash to the current known history 84 | * 85 | * @param key 86 | */ 87 | void add_to_history(u64 key); 88 | 89 | /** 90 | * @brief Clears all known game history 91 | * 92 | */ 93 | void clear_history(); 94 | 95 | /** 96 | * @brief Clears all the move tables 97 | * 98 | */ 99 | void clear_move_tables(); 100 | 101 | /** 102 | * @brief Clears the transposition table 103 | * 104 | */ 105 | void clear_transposition_table(); 106 | 107 | /** 108 | * @brief Searches the current position with max_depth 109 | * 110 | * @return SearchResult 111 | */ 112 | SearchResult find_best_move(); 113 | 114 | /** 115 | * @brief Searches the current position with the given depth 116 | * 117 | * It can also be used to implement iterative deepening outside 118 | * of the ai class (for uci prints, for example) 119 | * 120 | * @param depth 121 | * @return SearchResult 122 | */ 123 | SearchResult find_best_move(int depth, int alpha, int beta); 124 | }; -------------------------------------------------------------------------------- /src/engine/movepicker/pvtable.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | Move PVTable::get_pv_move(int depth) 9 | { 10 | return _table[depth][depth]; 11 | } 12 | 13 | std::vector PVTable::get_pv() 14 | { 15 | std::vector res; 16 | for (int i = 0; i < _length[0]; i++) 17 | { 18 | res.push_back(_table[0][i]); 19 | } 20 | 21 | return res; 22 | } 23 | 24 | std::vector PVTable::get_pv_from_depth(int start_depth) 25 | { 26 | std::vector res; 27 | for (int i = start_depth; i < _length[start_depth]; i++) 28 | { 29 | res.push_back(_table[start_depth][i]); 30 | } 31 | 32 | return res; 33 | } 34 | 35 | void PVTable::add(Move const move, int depth) 36 | { 37 | // Write Principal Variation Move 38 | _table[depth][depth] = move; 39 | 40 | // Copy moves from deeper depth into current depths line 41 | memcpy(&_table[depth][depth + 1], &_table[depth + 1][depth + 1], (unsigned long)_length[depth + 1] * sizeof(int)); 42 | 43 | // Adjust Principal Variation Length 44 | _length[depth] = _length[depth + 1]; 45 | } 46 | 47 | void PVTable::add_pv_from_depth(std::vector moves, int starting_depth) 48 | { 49 | int last_depth = starting_depth + (int)moves.size() - 1; 50 | 51 | // Update PV with moves from starting_depth to last depth (reverse order) 52 | for (int i = (int)moves.size() - 1; i >= 0; i--) 53 | { 54 | this->add(moves[(std::vector::size_type)i], last_depth--); 55 | } 56 | 57 | // Update the Higher Depths 58 | for (int current_depth = starting_depth - 1; current_depth >= 0; current_depth--) 59 | { 60 | memcpy(&_table[current_depth][current_depth + 1], &_table[current_depth + 1][current_depth + 1], (unsigned long)_length[current_depth + 1] * sizeof(int)); 61 | } 62 | } 63 | 64 | void PVTable::clear() 65 | { 66 | memset(_table, 0, sizeof(_table)); 67 | } 68 | 69 | void PVTable::set_length(int depth) 70 | { 71 | _length[depth] = depth; 72 | } -------------------------------------------------------------------------------- /src/engine/movepicker/pvtable.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | class PVTable 8 | { 9 | private: 10 | static const int MAX_DEPTH = 64; 11 | 12 | int _length[MAX_DEPTH]{}; 13 | Move _table[MAX_DEPTH][MAX_DEPTH]{}; 14 | 15 | public: 16 | Move get_pv_move(int depth); 17 | std::vector get_pv(); 18 | std::vector get_pv_from_depth(int start_depth); 19 | 20 | void clear(); 21 | void set_length(int depth); 22 | void add(Move const move, int depth); 23 | void add_pv_from_depth(std::vector moves, int starting_depth); 24 | }; -------------------------------------------------------------------------------- /src/engine/movepicker/ttable.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | TTable::~TTable() 7 | { 8 | delete[] _table; 9 | } 10 | 11 | void TTable::clear() 12 | { 13 | for (int p = 0; p < TABLE_SIZE; p++) 14 | { 15 | _table[p] = {0ULL, 0, 0, 0, {}}; 16 | } 17 | } 18 | 19 | void TTable::set_entry(u64 hash_key, int depth, int flag, int score, std::vector moves) 20 | { 21 | int entry = hash_key % TABLE_SIZE; 22 | _table[entry].depth = depth; 23 | _table[entry].flag = flag; 24 | _table[entry].score = score; 25 | _table[entry].moves = moves; 26 | } 27 | 28 | TTable::TTOutput TTable::read_hash(u64 hash_key, int alpha, int beta, int depth) 29 | { 30 | TTOutput search_result = {false, 0, {}}; 31 | TTEntry hash_entry = _table[hash_key % TABLE_SIZE]; 32 | if ((hash_entry.hash_key == hash_key) && (hash_entry.depth >= depth)) 33 | { 34 | search_result.found = true; 35 | search_result.moves = hash_entry.moves; 36 | if (hash_entry.flag == HASH_FLAG_SCORE) 37 | { 38 | search_result.score = hash_entry.score; 39 | search_result.moves = hash_entry.moves; 40 | } 41 | 42 | if ((hash_entry.flag == HASH_FLAG_ALPHA) && (hash_entry.score <= alpha)) 43 | { 44 | search_result.score = alpha; 45 | search_result.moves = hash_entry.moves; 46 | } 47 | 48 | if ((hash_entry.flag == HASH_FLAG_BETA) && (hash_entry.score > beta)) 49 | { 50 | search_result.score = beta; 51 | search_result.moves = hash_entry.moves; 52 | } 53 | } 54 | 55 | return search_result; 56 | } -------------------------------------------------------------------------------- /src/engine/movepicker/ttable.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | class TTable 9 | { 10 | private: 11 | struct TTEntry 12 | { 13 | u64 hash_key; 14 | int depth; 15 | int flag; 16 | int score; 17 | std::vector moves; 18 | }; 19 | 20 | static const int TABLE_SIZE = 0x400000; 21 | 22 | TTEntry *_table = new TTEntry[TABLE_SIZE]; 23 | 24 | public: 25 | static const int HASH_FLAG_SCORE = 0; 26 | static const int HASH_FLAG_ALPHA = 1; 27 | static const int HASH_FLAG_BETA = 2; 28 | 29 | struct TTOutput 30 | { 31 | bool found; 32 | int score; 33 | std::vector moves; 34 | }; 35 | 36 | ~TTable(); 37 | 38 | void clear(); 39 | void set_entry(u64 hash_key, int depth, int flag, int score, std::vector moves); 40 | TTOutput read_hash(u64 hash_key, int alpha, int beta, int depth); 41 | }; -------------------------------------------------------------------------------- /src/engine/utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace utils 9 | { 10 | // Square Operations 11 | inline Square get_square(const Rank rk, const File fl) { return (Square)(8 * rk + fl); } 12 | inline Square flip_square(const Square sq) { return (Square)(sq ^ 56); } 13 | 14 | inline File get_file(const Square sq) { return (File)(sq & 7); } 15 | inline Rank get_rank(const Square sq) { return (Rank)(sq >> 3); } 16 | 17 | // Flip Side to Move 18 | inline Color get_opponent(Color to_move) { return (Color)((int)to_move ^ 1); } 19 | 20 | } // namespace utils -------------------------------------------------------------------------------- /src/engine/zobrist.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | static u64 generate_random_number_u64() 6 | { 7 | u64 n1 = ((u64)std::rand()); 8 | u64 n2 = ((u64)std::rand()); 9 | return n1 | (n2 << 32); 10 | } 11 | 12 | namespace zobrist 13 | { 14 | u64 piece_keys[N_SIDES][N_PIECES][N_SQUARES]; 15 | 16 | u64 en_passant_keys[N_SQUARES]; 17 | 18 | u64 castle_keys[16]; 19 | 20 | u64 side_key[BOTH] = {}; 21 | 22 | void init() 23 | { 24 | for (int piece = PAWN; piece < N_PIECES; piece++) 25 | { 26 | for (int square = A1; square < N_SQUARES; square++) 27 | { 28 | piece_keys[WHITE][piece][square] = generate_random_number_u64(); 29 | piece_keys[BLACK][piece][square] = generate_random_number_u64(); 30 | } 31 | } 32 | 33 | for (int square = A1; square < N_SQUARES; square++) 34 | { 35 | en_passant_keys[square] = generate_random_number_u64(); 36 | } 37 | 38 | for (int castle_state = 0; castle_state < 16; castle_state++) 39 | { 40 | castle_keys[castle_state] = generate_random_number_u64(); 41 | } 42 | 43 | side_key[1] = generate_random_number_u64(); 44 | } 45 | 46 | u64 generate_hash_key(const Board &board) 47 | { 48 | u64 final_key = 0ULL; 49 | for (int piece = PAWN; piece < N_PIECES; piece++) 50 | { 51 | for (int side = WHITE; side < BOTH; side++) 52 | { 53 | u64 bitboard = board.get_pieces((Color)side, (PieceType)piece); 54 | while (bitboard) 55 | { 56 | Square sq = bitboard::bit_scan_forward(bitboard); 57 | final_key ^= piece_keys[side][piece][sq]; 58 | bitboard::pop_bit(bitboard, sq); 59 | } 60 | } 61 | } 62 | 63 | if (board.get_en_passant_square() != EMPTY_SQUARE) 64 | { 65 | final_key ^= en_passant_keys[board.get_en_passant_square()]; 66 | } 67 | 68 | final_key ^= castle_keys[board.get_castling_rights()]; 69 | final_key ^= side_key[board.get_side_to_move()]; 70 | return final_key; 71 | } 72 | 73 | } // namespace zobrist 74 | -------------------------------------------------------------------------------- /src/engine/zobrist.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace zobrist 8 | { 9 | void init(); 10 | 11 | extern u64 piece_keys[N_SIDES][N_PIECES][N_SQUARES]; 12 | extern u64 en_passant_keys[N_SQUARES]; 13 | extern u64 castle_keys[16]; 14 | extern u64 side_key[BOTH]; 15 | 16 | u64 generate_hash_key(const Board &board); 17 | 18 | } // namespace zobrist 19 | -------------------------------------------------------------------------------- /src/interfaces/cli/cli.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | namespace cli 21 | { 22 | void init() 23 | { 24 | magics::init(); 25 | tables::init(); 26 | eval::init(); 27 | zobrist::init(); 28 | 29 | Board board = Board(); 30 | 31 | AsciiCommand ascii_command = AsciiCommand(board); 32 | CapturesCommand captures_command = CapturesCommand(board); 33 | DisplayCommand display_command = DisplayCommand(board); 34 | DividedPerftCommand dperft_command = DividedPerftCommand(board); 35 | EvalCommand eval_command = EvalCommand(board); 36 | ExitCommand exit_command = ExitCommand(board); 37 | GetFenCommand get_fen_command = GetFenCommand(board); 38 | HelpCommand help_command = HelpCommand(board); 39 | InfoCommand info_command = InfoCommand(board); 40 | MagicsCommand magics_command = MagicsCommand(board); 41 | MoveCommand move_command = MoveCommand(board); 42 | MovesCommand moves_command = MovesCommand(board); 43 | NewCommand new_command = NewCommand(board); 44 | PerftCommand perft_command = PerftCommand(board); 45 | PLMovesCommand pl_moves_command = PLMovesCommand(board); 46 | RotateCommand rotate_command = RotateCommand(board); 47 | SetFenCommand set_fen_command = SetFenCommand(board); 48 | SwitchCommand switch_command = SwitchCommand(board); 49 | 50 | for (;;) 51 | { 52 | std::cout << "> " << std::flush; 53 | 54 | std::string line; 55 | std::getline(std::cin, line); 56 | std::vector args = utils::tokenize(std::string(line)); 57 | 58 | if (args.empty()) 59 | { 60 | continue; 61 | } 62 | 63 | std::string cmd = args[0]; 64 | args.erase(args.begin()); 65 | 66 | if (cmd == "help") 67 | { 68 | help_command.execute(args); 69 | } 70 | else if (cmd == "ascii") 71 | { 72 | ascii_command.execute(args); 73 | } 74 | else if (cmd == "display") 75 | { 76 | display_command.execute(args); 77 | } 78 | else if (cmd == "dperft") 79 | { 80 | dperft_command.execute(args); 81 | } 82 | else if (cmd == "eval") 83 | { 84 | eval_command.execute(args); 85 | } 86 | else if (cmd == "exit") 87 | { 88 | exit_command.execute(args); 89 | } 90 | else if (cmd == "info") 91 | { 92 | info_command.execute(args); 93 | } 94 | else if (cmd == "magics") 95 | { 96 | magics_command.execute(args); 97 | } 98 | else if (cmd == "move") 99 | { 100 | move_command.execute(args); 101 | } 102 | else if (cmd == "moves") 103 | { 104 | moves_command.execute(args); 105 | } 106 | else if (cmd == "plmoves") 107 | { 108 | pl_moves_command.execute(args); 109 | } 110 | else if (cmd == "captures") 111 | { 112 | captures_command.execute(args); 113 | } 114 | else if (cmd == "new") 115 | { 116 | new_command.execute(args); 117 | } 118 | else if (cmd == "perft") 119 | { 120 | perft_command.execute(args); 121 | } 122 | else if (cmd == "getfen") 123 | { 124 | get_fen_command.execute(args); 125 | } 126 | else if (cmd == "rotate") 127 | { 128 | rotate_command.execute(args); 129 | } 130 | else if (cmd == "setfen") 131 | { 132 | set_fen_command.execute(args); 133 | } 134 | else if (cmd == "switch") 135 | { 136 | switch_command.execute(args); 137 | } 138 | else 139 | { 140 | std::cout << "command not found" << std::endl; 141 | } 142 | } 143 | } 144 | 145 | } // namespace cli -------------------------------------------------------------------------------- /src/interfaces/cli/cli.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cli 4 | { 5 | void init(); 6 | 7 | } // namespace cli 8 | -------------------------------------------------------------------------------- /src/interfaces/cli/commands/ascii.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | void cli::AsciiCommand::execute([[maybe_unused]] std::vector &args) 6 | { 7 | std::cout << "ascii mode toggled " << (_board.toggle_ascii() ? "on" : "off") << std::endl; 8 | } -------------------------------------------------------------------------------- /src/interfaces/cli/commands/captures.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | void cli::CapturesCommand::execute([[maybe_unused]] std::vector &args) 9 | { 10 | std::vector captures = movegen::generate_legal_captures(_board); 11 | std::for_each(captures.begin(), captures.end(), [](const Move &capture) 12 | { std::cout << capture.get_uci() << "\n"; }); 13 | std::cout << "Total number of captures: " << captures.size() << std::endl; 14 | } -------------------------------------------------------------------------------- /src/interfaces/cli/commands/commands.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace cli 6 | { 7 | class AsciiCommand : public utils::Command 8 | { 9 | public: 10 | AsciiCommand(Board &board) : Command(board){}; 11 | void execute([[maybe_unused]] std::vector &args); 12 | }; 13 | 14 | class CapturesCommand : public utils::Command 15 | { 16 | public: 17 | CapturesCommand(Board &board) : Command(board){}; 18 | void execute([[maybe_unused]] std::vector &args); 19 | }; 20 | 21 | class DisplayCommand : public utils::Command 22 | { 23 | public: 24 | DisplayCommand(Board &board) : Command(board){}; 25 | void execute([[maybe_unused]] std::vector &args); 26 | }; 27 | 28 | class DividedPerftCommand : public utils::Command 29 | { 30 | public: 31 | DividedPerftCommand(Board &board) : Command(board){}; 32 | void execute([[maybe_unused]] std::vector &args); 33 | }; 34 | 35 | class EvalCommand : public utils::Command 36 | { 37 | public: 38 | EvalCommand(Board &board) : Command(board){}; 39 | void execute([[maybe_unused]] std::vector &args); 40 | }; 41 | 42 | class ExitCommand : public utils::Command 43 | { 44 | public: 45 | ExitCommand(Board &board) : Command(board){}; 46 | void execute([[maybe_unused]] std::vector &args); 47 | }; 48 | 49 | class GetFenCommand : public utils::Command 50 | { 51 | public: 52 | GetFenCommand(Board &board) : Command(board){}; 53 | void execute([[maybe_unused]] std::vector &args); 54 | }; 55 | 56 | class HelpCommand : public utils::Command 57 | { 58 | public: 59 | HelpCommand(Board &board) : Command(board){}; 60 | void execute([[maybe_unused]] std::vector &args); 61 | }; 62 | 63 | class InfoCommand : public utils::Command 64 | { 65 | public: 66 | InfoCommand(Board &board) : Command(board){}; 67 | void execute([[maybe_unused]] std::vector &args); 68 | }; 69 | 70 | class MagicsCommand : public utils::Command 71 | { 72 | public: 73 | MagicsCommand(Board &board) : Command(board){}; 74 | void execute([[maybe_unused]] std::vector &args); 75 | }; 76 | 77 | class MoveCommand : public utils::Command 78 | { 79 | public: 80 | MoveCommand(Board &board) : Command(board){}; 81 | void execute([[maybe_unused]] std::vector &args); 82 | }; 83 | 84 | class MovesCommand : public utils::Command 85 | { 86 | public: 87 | MovesCommand(Board &board) : Command(board){}; 88 | void execute([[maybe_unused]] std::vector &args); 89 | }; 90 | 91 | class NewCommand : public utils::Command 92 | { 93 | public: 94 | NewCommand(Board &board) : Command(board){}; 95 | void execute([[maybe_unused]] std::vector &args); 96 | }; 97 | 98 | class PerftCommand : public utils::Command 99 | { 100 | public: 101 | PerftCommand(Board &board) : Command(board){}; 102 | void execute([[maybe_unused]] std::vector &args); 103 | }; 104 | 105 | class PLMovesCommand : public utils::Command 106 | { 107 | public: 108 | PLMovesCommand(Board &board) : Command(board){}; 109 | void execute([[maybe_unused]] std::vector &args); 110 | }; 111 | 112 | class RotateCommand : public utils::Command 113 | { 114 | public: 115 | RotateCommand(Board &board) : Command(board){}; 116 | void execute([[maybe_unused]] std::vector &args); 117 | }; 118 | 119 | class SetFenCommand : public utils::Command 120 | { 121 | public: 122 | SetFenCommand(Board &board) : Command(board){}; 123 | void execute([[maybe_unused]] std::vector &args); 124 | }; 125 | 126 | class SwitchCommand : public utils::Command 127 | { 128 | public: 129 | SwitchCommand(Board &board) : Command(board){}; 130 | void execute([[maybe_unused]] std::vector &args); 131 | }; 132 | 133 | } // namespace cli 134 | -------------------------------------------------------------------------------- /src/interfaces/cli/commands/display.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void cli::DisplayCommand::execute([[maybe_unused]] std::vector &args) 4 | { 5 | _board.display(); 6 | } -------------------------------------------------------------------------------- /src/interfaces/cli/commands/dividedperft.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | static void dperft(int depth, Board &board) 12 | { 13 | int total_nodes = 0; 14 | std::chrono::time_point start = std::chrono::system_clock::now(); 15 | for (const Move &move : movegen::generate_pseudo_legal_moves(board)) 16 | { 17 | Board::GameState state = board.get_state(); 18 | board.make(move); 19 | Square king_sq = bitboard::bit_scan_forward(board.get_pieces(board.get_opponent(), KING)); 20 | Color attacker_side = board.get_side_to_move(); 21 | if (!board.is_square_attacked(king_sq, attacker_side)) 22 | { 23 | int nodes = perft::perft(board, depth - 1); 24 | std::cout << move.get_uci() << ": " << nodes << std::endl; 25 | total_nodes += nodes; 26 | } 27 | board.unmake(move, state); 28 | } 29 | std::chrono::time_point end = std::chrono::system_clock::now(); 30 | std::chrono::duration elapsed_seconds = end - start; 31 | std::time_t end_time = std::chrono::system_clock::to_time_t(end); 32 | std::cout << "Found " << total_nodes << " nodes." << std::endl; 33 | std::cout << "Finished computation at " << std::ctime(&end_time); 34 | std::cout << "Elapsed time: " << elapsed_seconds.count() << "s" << std::endl; 35 | std::cout << "Nodes Per Second: " << (double)total_nodes / elapsed_seconds.count() << std::endl; 36 | } 37 | 38 | void cli::DividedPerftCommand::execute(std::vector &args) 39 | { 40 | if (args.size() == 0) 41 | { 42 | std::cout << "dperft command takes exactly one argument" << std::endl; 43 | return; 44 | } 45 | 46 | int depth = 0; 47 | try 48 | { 49 | depth = std::stoi(args[0]); 50 | } 51 | catch (const std::exception &e) 52 | { 53 | std::cout << "invalid depth value1." << std::endl; 54 | return; 55 | } 56 | 57 | if (depth >= 0) 58 | { 59 | dperft(depth, _board); 60 | } 61 | else 62 | { 63 | std::cout << "invalid depth value." << std::endl; 64 | } 65 | } -------------------------------------------------------------------------------- /src/interfaces/cli/commands/eval.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | void cli::EvalCommand::execute([[maybe_unused]] std::vector &args) 8 | { 9 | std::cout << "Static Evaluation: " << eval::eval(_board) << std::endl; 10 | } 11 | -------------------------------------------------------------------------------- /src/interfaces/cli/commands/exit.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | void cli::ExitCommand::execute([[maybe_unused]] std::vector &args) 6 | { 7 | tables::teardown(); 8 | exit(EXIT_SUCCESS); 9 | } -------------------------------------------------------------------------------- /src/interfaces/cli/commands/getfen.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | void cli::GetFenCommand::execute([[maybe_unused]] std::vector &args) 6 | { 7 | std::cout << _board.get_fen(); 8 | } -------------------------------------------------------------------------------- /src/interfaces/cli/commands/help.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | void cli::HelpCommand::execute([[maybe_unused]] std::vector &args) 6 | { 7 | std::cout 8 | << "ascii Toggles between ascii and utf-8 board representation\n" 9 | << "captures Shows all pseudo legal captures\n" 10 | << "cc Plays computer-to-computer [TODO]\n" 11 | << "display Displays board \n" 12 | << "dperft (n) Divided perft\n" 13 | << "eval Shows static evaluation of this position\n" 14 | << "exit Exits program\n" 15 | << "getfen Prints current position to in fen string format \n" 16 | << "go Computer plays his best move [TODO]\n" 17 | << "help Shows this help \n" 18 | << "info Displays variables (for testing purposes)\n" 19 | << "magics Generates magic numbers for the bishop and rook pieces\n" 20 | << "move (move) Plays a move (in uci format)\n" 21 | << "moves Shows all legal moves\n" 22 | << "new Starts new game\n" 23 | << "perf Benchmarks a number of key functions [TODO]\n" 24 | << "perft n Calculates raw number of nodes from here, depth n\n" 25 | << "plmoves Shows all pseudo legal moves\n" 26 | << "rotate Rotates board \n" 27 | << "sd (n) Sets the search depth to n [TODO]\n" 28 | << "setfen (fen) Reads fen string position and modifies board accordingly\n" 29 | << "switch Switches the next side to move\n" 30 | << "undo Takes back last move [TODO]\n" 31 | << std::endl; 32 | } -------------------------------------------------------------------------------- /src/interfaces/cli/commands/info.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | void cli::InfoCommand::execute([[maybe_unused]] std::vector &args) 8 | { 9 | std::string fen = _board.get_fen(); 10 | std::vector split_fen = utils::tokenize(fen); 11 | std::cout << "Side to Play = " << split_fen[1] 12 | << "\nCastling Rights = " << split_fen[2] 13 | << "\nEn-passant Square = " << split_fen[3] 14 | << "\nFifty Move Count = " << split_fen[4] 15 | << "\nFull Move Number = " << split_fen[5]; 16 | std::cout << "\nOccupied Squares:\n"; 17 | 18 | std::cout << "WHITE, PAWN" << std::endl; 19 | bitboard::print(_board.get_pieces(WHITE, PAWN)); 20 | std::cout << "WHITE, KNIGHT" << std::endl; 21 | bitboard::print(_board.get_pieces(WHITE, KNIGHT)); 22 | std::cout << "WHITE, BISHOP" << std::endl; 23 | bitboard::print(_board.get_pieces(WHITE, BISHOP)); 24 | std::cout << "WHITE, ROOK" << std::endl; 25 | bitboard::print(_board.get_pieces(WHITE, ROOK)); 26 | std::cout << "WHITE, QUEEN" << std::endl; 27 | bitboard::print(_board.get_pieces(WHITE, QUEEN)); 28 | std::cout << "WHITE, KING" << std::endl; 29 | bitboard::print(_board.get_pieces(WHITE, KING)); 30 | std::cout << "BLACK, PAWN" << std::endl; 31 | bitboard::print(_board.get_pieces(BLACK, PAWN)); 32 | std::cout << "BLACK, KNIGHT" << std::endl; 33 | bitboard::print(_board.get_pieces(BLACK, KNIGHT)); 34 | std::cout << "BLACK, BISHOP" << std::endl; 35 | bitboard::print(_board.get_pieces(BLACK, BISHOP)); 36 | std::cout << "BLACK, ROOK" << std::endl; 37 | bitboard::print(_board.get_pieces(BLACK, ROOK)); 38 | std::cout << "BLACK, QUEEN" << std::endl; 39 | bitboard::print(_board.get_pieces(BLACK, QUEEN)); 40 | std::cout << "BLACK, KING" << std::endl; 41 | bitboard::print(_board.get_pieces(BLACK, KING)); 42 | 43 | std::cout << "BOTH" << std::endl; 44 | bitboard::print(_board.get_occupancies(BOTH)); 45 | std::cout << "WHITE" << std::endl; 46 | bitboard::print(_board.get_occupancies(WHITE)); 47 | std::cout << "BLACK" << std::endl; 48 | bitboard::print(_board.get_occupancies(BLACK)); 49 | } -------------------------------------------------------------------------------- /src/interfaces/cli/commands/magics.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | void cli::MagicsCommand::execute([[maybe_unused]] std::vector &args) 6 | { 7 | magics::generate(); 8 | } -------------------------------------------------------------------------------- /src/interfaces/cli/commands/move.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | void cli::MoveCommand::execute(std::vector &args) 8 | { 9 | if (args.size() == 0) 10 | { 11 | return; 12 | } 13 | 14 | for (const Move &move : movegen::generateLegalMoves(_board)) 15 | { 16 | if (move.get_uci() == args[0]) 17 | { 18 | _board.make(move); 19 | return; 20 | } 21 | } 22 | 23 | std::cout << "invalid move" << std::endl; 24 | } -------------------------------------------------------------------------------- /src/interfaces/cli/commands/moves.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | void cli::MovesCommand::execute([[maybe_unused]] std::vector &args) 9 | { 10 | auto moves = movegen::generateLegalMoves(_board); 11 | std::for_each(moves.begin(), moves.end(), [](const Move &move) 12 | { std::cout << move.get_uci() << "\n"; }); 13 | std::cout << "Total number of moves: " << moves.size() << std::endl; 14 | } -------------------------------------------------------------------------------- /src/interfaces/cli/commands/new.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void cli::NewCommand::execute([[maybe_unused]] std::vector &args) 4 | { 5 | _board.set_starting_position(); 6 | } -------------------------------------------------------------------------------- /src/interfaces/cli/commands/perft.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | static void timed_perft(int depth, Board &board) 9 | { 10 | std::chrono::time_point start = std::chrono::system_clock::now(); 11 | int nodes = perft::perft(board, depth); 12 | std::chrono::time_point end = std::chrono::system_clock::now(); 13 | std::chrono::duration elapsed_seconds = end - start; 14 | std::time_t end_time = std::chrono::system_clock::to_time_t(end); 15 | std::cout << "Found " << nodes << " nodes." << std::endl; 16 | std::cout << "Finished computation at " << std::ctime(&end_time); 17 | std::cout << "Elapsed time: " << elapsed_seconds.count() << "s" << std::endl; 18 | std::cout << "Nodes Per Second: " << (double)nodes / elapsed_seconds.count() << std::endl; 19 | } 20 | 21 | void cli::PerftCommand::execute(std::vector &args) 22 | { 23 | 24 | if (args.size() == 0) 25 | { 26 | std::cout << "perft command takes exactly one argument" << std::endl; 27 | return; 28 | } 29 | 30 | int depth = 0; 31 | try 32 | { 33 | depth = std::stoi(args[0]); 34 | } 35 | catch (const std::exception &e) 36 | { 37 | std::cout << "invalid depth value." << std::endl; 38 | return; 39 | } 40 | if (depth >= 0) 41 | { 42 | timed_perft(depth, _board); 43 | } 44 | else 45 | { 46 | std::cout << "invalid depth value." << std::endl; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/interfaces/cli/commands/plmoves.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | void cli::PLMovesCommand::execute([[maybe_unused]] std::vector &args) 9 | { 10 | auto moves = movegen::generate_pseudo_legal_moves(_board); 11 | std::for_each(moves.begin(), moves.end(), [](const Move &move) 12 | { std::cout << move.get_uci() << "\n"; }); 13 | std::cout << "Total number of pseudo legal moves: " << moves.size() << std::endl; 14 | } -------------------------------------------------------------------------------- /src/interfaces/cli/commands/rotate.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | void cli::RotateCommand::execute([[maybe_unused]] std::vector &args) 6 | { 7 | std::cout << (_board.rotate_display() ? "white" : "black") << " is now on bottom" << std::endl; 8 | } -------------------------------------------------------------------------------- /src/interfaces/cli/commands/setfen.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | void cli::SetFenCommand::execute(std::vector &args) 7 | { 8 | if (!utils::is_fen_valid(args)) 9 | { 10 | std::cout << "Invalid FEN String" << std::endl; 11 | return; 12 | } 13 | 14 | _board.set_from_fen(args[0], args[1], args[2], args[3], args[4], args[5]); 15 | } 16 | -------------------------------------------------------------------------------- /src/interfaces/cli/commands/switch.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | void cli::SwitchCommand::execute([[maybe_unused]] std::vector &args) 6 | { 7 | std::cout << "side to play is now " << (_board.switch_side_to_move() == WHITE ? "white" : "black") << std::endl; 8 | } -------------------------------------------------------------------------------- /src/interfaces/uci/commands/commands.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace uci 8 | { 9 | class UCICommand : public utils::Command 10 | { 11 | public: 12 | UCICommand(Board &board) : Command(board){}; 13 | void execute([[maybe_unused]] std::vector &args); 14 | }; 15 | class IsReadyCommand : public utils::Command 16 | { 17 | public: 18 | IsReadyCommand(Board &board) : Command(board){}; 19 | void execute([[maybe_unused]] std::vector &args); 20 | }; 21 | class UCINewGameCommand : public utils::Command 22 | { 23 | private: 24 | MovePicker _move_picker; 25 | 26 | public: 27 | UCINewGameCommand(Board &board, MovePicker &move_picker) : Command(board), _move_picker(move_picker){}; 28 | void execute([[maybe_unused]] std::vector &args); 29 | }; 30 | class PositionCommand : public utils::Command 31 | { 32 | private: 33 | MovePicker &_move_picker; 34 | 35 | public: 36 | PositionCommand(Board &board, MovePicker &move_picker) : Command(board), _move_picker(move_picker){}; 37 | void execute([[maybe_unused]] std::vector &args); 38 | }; 39 | class DisplayCommand : public utils::Command 40 | { 41 | public: 42 | DisplayCommand(Board &board) : Command(board){}; 43 | void execute([[maybe_unused]] std::vector &args); 44 | }; 45 | class GoCommand : public utils::Command 46 | { 47 | private: 48 | MovePicker &_move_picker; 49 | 50 | public: 51 | GoCommand(Board &board, MovePicker &move_picker) : Command(board), _move_picker(move_picker){}; 52 | void execute([[maybe_unused]] std::vector &args); 53 | }; 54 | class QuitCommand : public utils::Command 55 | { 56 | public: 57 | QuitCommand(Board &board) : Command(board){}; 58 | void execute([[maybe_unused]] std::vector &args); 59 | }; 60 | 61 | } // namespace cli 62 | -------------------------------------------------------------------------------- /src/interfaces/uci/commands/display.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void uci::DisplayCommand::execute([[maybe_unused]] std::vector &args) 4 | { 5 | _board.display(); 6 | } -------------------------------------------------------------------------------- /src/interfaces/uci/commands/exit.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | void uci::QuitCommand::execute([[maybe_unused]] std::vector &args) 6 | { 7 | tables::teardown(); 8 | exit(EXIT_SUCCESS); 9 | } -------------------------------------------------------------------------------- /src/interfaces/uci/commands/go.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #define DEFAULT_MIN_DEPTH 8 14 | #define DEFAULT_MAX_DEPTH 32 15 | #define MIN_EVAL (INT_MIN + 1) 16 | #define WINDOW_EXPANSION 50 17 | 18 | struct UCIScore 19 | { 20 | std::string repr; 21 | bool is_mate; 22 | }; 23 | 24 | static UCIScore score_to_uci(int score) 25 | { 26 | if (score >= 0) 27 | { 28 | int check_mate_delta = (-MIN_EVAL) - score; 29 | int moves_for_mate = (check_mate_delta / 2) + (check_mate_delta % 2); 30 | if (0 < moves_for_mate && moves_for_mate <= 16) 31 | { 32 | return UCIScore{std::string("mate ") + std::to_string(moves_for_mate), true}; 33 | } 34 | } 35 | else 36 | { 37 | int check_mate_delta = -(MIN_EVAL - score); 38 | int moves_for_mate = (check_mate_delta / 2) + (check_mate_delta % 2); 39 | if (0 < moves_for_mate && moves_for_mate <= 16) 40 | { 41 | return UCIScore{std::string("mate ") + std::to_string(-moves_for_mate), true}; 42 | } 43 | } 44 | 45 | return UCIScore{std::string("cp ") + std::to_string(score), false}; 46 | } 47 | 48 | /** 49 | * @brief Displays all the necessary info about current iteration 50 | * If score corresponds to a mate in x moves, returns true 51 | * 52 | * @param result 53 | * @param depth 54 | * @param elapsed 55 | */ 56 | static bool display_search_iteration(MovePicker::SearchResult result, int depth, std::chrono::duration elapsed) 57 | { 58 | UCIScore score = score_to_uci(result.score); 59 | std::cout << "info score " << score.repr 60 | << " depth " << depth 61 | << " nodes " << result.nodes 62 | << " time " << (int)(elapsed / std::chrono::milliseconds(1)) 63 | << " pv "; 64 | 65 | std::for_each(result.pv.cbegin(), result.pv.cend(), [](const Move &move) 66 | { std::cout << move.get_uci() << " "; }); 67 | std::cout << std::endl; 68 | 69 | return score.is_mate; 70 | } 71 | 72 | static void search(std::future future, MovePicker &move_picker, MovePicker::SearchResult &result) 73 | { 74 | int alpha = MIN_EVAL; 75 | int beta = -MIN_EVAL; 76 | 77 | move_picker.clear_move_tables(); 78 | 79 | for (int depth = 1; depth <= move_picker.get_max_depth(); depth++) 80 | { 81 | auto start = std::chrono::system_clock::now(); 82 | result = move_picker.find_best_move(depth, alpha, beta); 83 | auto end = std::chrono::system_clock::now(); 84 | 85 | bool found_mate = display_search_iteration(result, depth, end - start); 86 | 87 | if (found_mate) 88 | { 89 | break; 90 | } 91 | 92 | if (result.nodes == 2) 93 | { 94 | break; 95 | } 96 | 97 | if (future.wait_for(std::chrono::milliseconds(1)) == std::future_status::ready) 98 | { 99 | break; 100 | } 101 | 102 | if ((result.score <= alpha) || (result.score >= beta)) 103 | { 104 | alpha = MIN_EVAL; 105 | beta = -MIN_EVAL; 106 | depth--; 107 | continue; 108 | } 109 | 110 | alpha = result.score - WINDOW_EXPANSION; 111 | beta = result.score + WINDOW_EXPANSION; 112 | } 113 | } 114 | 115 | void uci::GoCommand::execute(std::vector &args) 116 | { 117 | enum ArgCommand : int 118 | { 119 | DEPTH, 120 | WTIME, 121 | BTIME, 122 | MOVES_TO_GO, 123 | }; 124 | 125 | ArgCommand arg_cmd{}; 126 | 127 | int depth{}; 128 | int wtime{}, btime{}; 129 | bool infinite = false; 130 | [[maybe_unused]] int moves_to_go{}; 131 | for (const std::string &token : args) 132 | { 133 | if (token == "depth") 134 | { 135 | arg_cmd = DEPTH; 136 | } 137 | else if (token == "wtime") 138 | { 139 | arg_cmd = WTIME; 140 | } 141 | else if (token == "btime") 142 | { 143 | arg_cmd = BTIME; 144 | } 145 | else if (token == "movestogo") 146 | { 147 | arg_cmd = MOVES_TO_GO; 148 | } 149 | else if (token == "infinite") 150 | { 151 | infinite = true; 152 | } 153 | else 154 | { 155 | int val{}; 156 | try 157 | { 158 | val = std::stoi(token); 159 | } 160 | catch (const std::exception &e) 161 | { 162 | return; 163 | } 164 | 165 | if (val < 0) 166 | { 167 | return; 168 | } 169 | 170 | switch (arg_cmd) 171 | { 172 | case DEPTH: 173 | if (val < 0) 174 | { 175 | return; 176 | } 177 | else 178 | { 179 | depth = val; 180 | } 181 | break; 182 | case WTIME: 183 | if (val <= 0) 184 | { 185 | return; 186 | } 187 | else 188 | { 189 | wtime = val; 190 | } 191 | break; 192 | case BTIME: 193 | if (val <= 0) 194 | { 195 | return; 196 | } 197 | else 198 | { 199 | btime = val; 200 | } 201 | break; 202 | case MOVES_TO_GO: 203 | if (val <= 0) 204 | { 205 | return; 206 | } 207 | else 208 | { 209 | moves_to_go = val; 210 | } 211 | break; 212 | } 213 | } 214 | } 215 | 216 | MovePicker::SearchResult result; 217 | if (!depth && !infinite && wtime && btime) 218 | { 219 | _move_picker.set_max_depth(DEFAULT_MAX_DEPTH); 220 | } 221 | else 222 | { 223 | _move_picker.set_max_depth(DEFAULT_MIN_DEPTH); 224 | } 225 | 226 | std::promise signal_exit; 227 | std::future signal_exit_future = signal_exit.get_future(); 228 | std::thread search_thread(search, std::move(signal_exit_future), std::ref(_move_picker), std::ref(result)); 229 | if ((_board.get_side_to_move() == WHITE && wtime) || (_board.get_side_to_move() == BLACK && btime)) 230 | { 231 | std::this_thread::sleep_for(std::chrono::milliseconds(timectl::get_time_budget_ms(wtime, btime, _board))); 232 | signal_exit.set_value(); 233 | } 234 | search_thread.join(); 235 | 236 | std::cout << "bestmove " << result.pv[0].get_uci() << std::endl; 237 | } 238 | -------------------------------------------------------------------------------- /src/interfaces/uci/commands/isready.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | void uci::IsReadyCommand::execute([[maybe_unused]] std::vector &args) 6 | { 7 | std::cout << "readyok" << std::endl; 8 | } -------------------------------------------------------------------------------- /src/interfaces/uci/commands/position.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | 9 | static std::optional parse_move(std::string move_uci, Board &board) 10 | { 11 | for (Move const &move : movegen::generateLegalMoves(board)) 12 | { 13 | if (move.get_uci() == move_uci) 14 | { 15 | return move; 16 | } 17 | } 18 | 19 | return std::nullopt; 20 | } 21 | 22 | static void handle_moves(std::vector &moves, Board &board, MovePicker &move_picker) 23 | { 24 | move_picker.add_to_history(board.get_hash_key()); // For Three-Fold Draws 25 | for (std::string move_uci : moves) 26 | { 27 | std::optional parsed_move = parse_move(move_uci, board); 28 | if (parsed_move.has_value()) 29 | { 30 | Move move = parsed_move.value(); 31 | board.make(move); 32 | move_picker.add_to_history(board.get_hash_key()); // For Three-Fold Draws 33 | } 34 | } 35 | } 36 | 37 | void uci::PositionCommand::execute(std::vector &args) 38 | { 39 | if (args.size() == 0) 40 | { 41 | return; 42 | } 43 | 44 | if (args[0] == "startpos") 45 | { 46 | args.erase(args.begin()); 47 | 48 | _board.set_starting_position(); 49 | } 50 | else if (args[0] == "fen") 51 | { 52 | args.erase(args.begin()); 53 | 54 | if (args.size() < 6) 55 | { 56 | return; 57 | } 58 | 59 | std::vector fen_string(args.begin(), args.begin() + 6); 60 | if (!utils::is_fen_valid(fen_string)) 61 | { 62 | return; 63 | } 64 | else 65 | { 66 | _board.set_from_fen(args[0], args[1], args[2], args[3], args[4], args[5]); 67 | args.erase(args.begin(), args.begin() + 6); 68 | } 69 | } 70 | else 71 | { 72 | return; 73 | } 74 | 75 | if (args.size() == 0) 76 | { 77 | return; 78 | } 79 | 80 | _move_picker.clear_history(); 81 | 82 | if (args[0] == "moves") 83 | { 84 | args.erase(args.begin()); 85 | 86 | handle_moves(args, _board, _move_picker); 87 | } 88 | 89 | _move_picker.clear_move_tables(); 90 | _move_picker.clear_transposition_table(); 91 | } 92 | -------------------------------------------------------------------------------- /src/interfaces/uci/commands/uci.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | void uci::UCICommand::execute([[maybe_unused]] std::vector &args) 6 | { 7 | std::cout << "id name Codfish\n"; 8 | std::cout << "id name Codfish\n"; 9 | std::cout << "uciok" << std::endl; 10 | } -------------------------------------------------------------------------------- /src/interfaces/uci/commands/ucinewgame.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void uci::UCINewGameCommand::execute([[maybe_unused]] std::vector &args) 4 | { 5 | _board.set_starting_position(); 6 | 7 | _move_picker.clear_history(); 8 | _move_picker.clear_move_tables(); 9 | _move_picker.clear_transposition_table(); 10 | } 11 | -------------------------------------------------------------------------------- /src/interfaces/uci/timectl.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | namespace uci::timectl 6 | { 7 | int get_time_budget_ms(int wtime, int btime, const Board &board) 8 | { 9 | int remaining_time = board.get_side_to_move() == WHITE ? wtime : btime; 10 | int remaining_moves_pred = board.get_full_move_number() < 40 ? 40 - board.get_full_move_number() : std::max(80 - board.get_full_move_number(), 10); 11 | return remaining_time / remaining_moves_pred; 12 | } 13 | 14 | } // namespace uci::timectl 15 | -------------------------------------------------------------------------------- /src/interfaces/uci/timectl.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class Board; 4 | 5 | namespace uci::timectl 6 | { 7 | /** 8 | * @brief Get the time budget in milliseconds 9 | * 10 | * @param wtime 11 | * @param btime 12 | * @param board 13 | * @return int 14 | */ 15 | int get_time_budget_ms(int wtime, int btime, const Board &board); 16 | 17 | } // namespace uci::timectl 18 | -------------------------------------------------------------------------------- /src/interfaces/uci/uci.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | namespace uci 19 | { 20 | void init() 21 | { 22 | magics::init(); 23 | tables::init(); 24 | eval::init(); 25 | zobrist::init(); 26 | 27 | Board board = Board(); 28 | MovePicker move_picker = MovePicker(board); 29 | 30 | // TODO: use a map instead of if statements 31 | uci::UCICommand uciCommand = UCICommand(board); 32 | uci::IsReadyCommand isReadyCommand = IsReadyCommand(board); 33 | uci::UCINewGameCommand uciNewGameCommand = UCINewGameCommand(board, move_picker); 34 | uci::PositionCommand positionCommand = PositionCommand(board, move_picker); 35 | uci::DisplayCommand displayCommand = DisplayCommand(board); 36 | uci::GoCommand goCommand = GoCommand(board, move_picker); 37 | uci::QuitCommand quitCommand = QuitCommand(board); 38 | 39 | std::string line; 40 | while (std::getline(std::cin, line)) 41 | { 42 | std::string cmd; 43 | std::vector args = utils::tokenize(line); 44 | if (!args.empty()) 45 | { 46 | cmd = args[0]; 47 | args.erase(args.begin()); 48 | } 49 | else 50 | { 51 | continue; 52 | } 53 | 54 | if (cmd == "uci") 55 | { 56 | uciCommand.execute(args); 57 | } 58 | else if (cmd == "isready") 59 | { 60 | isReadyCommand.execute(args); 61 | } 62 | else if (cmd == "ucinewgame") 63 | { 64 | uciNewGameCommand.execute(args); 65 | } 66 | else if (cmd == "position") 67 | { 68 | positionCommand.execute(args); 69 | } 70 | else if (cmd == "d") 71 | { 72 | displayCommand.execute(args); 73 | } 74 | else if (cmd == "go") 75 | { 76 | goCommand.execute(args); 77 | } 78 | else if (cmd == "quit") 79 | { 80 | quitCommand.execute(args); 81 | } 82 | } 83 | } 84 | 85 | } // namespace uci 86 | -------------------------------------------------------------------------------- /src/interfaces/uci/uci.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace uci 4 | { 5 | /** 6 | * @brief Initializes the uci command line mode 7 | * 8 | */ 9 | void init(); 10 | 11 | } // namespace uci 12 | -------------------------------------------------------------------------------- /src/interfaces/utils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace utils 9 | { 10 | std::vector tokenize(const std::string &text) 11 | { 12 | std::vector tokens{}; 13 | std::istringstream iss(text); 14 | copy(std::istream_iterator(iss), std::istream_iterator(), std::back_inserter(tokens)); 15 | return tokens; 16 | } 17 | 18 | bool is_fen_valid(std::vector &args) 19 | { 20 | static const std::regex piece_placements_regex(R"((([pnbrqkPNBRQK1-8]{1,8})\/?){8})"); 21 | static const std::regex active_color_regex(R"(b|w)"); 22 | static const std::regex castling_rights_regex(R"(-|K?Q?k?q?)"); 23 | static const std::regex en_passant_regex(R"(-|[a-h][3-6])"); 24 | static const std::regex halfmove_clock_regex(R"(\d+)"); 25 | static const std::regex fullmove_number_regex(R"(\d+)"); 26 | 27 | return args.size() == 6 && 28 | std::regex_match(args[0], piece_placements_regex) && 29 | std::regex_match(args[1], active_color_regex) && 30 | std::regex_match(args[2], castling_rights_regex) && 31 | std::regex_match(args[3], en_passant_regex) && 32 | std::regex_match(args[4], halfmove_clock_regex) && 33 | std::regex_match(args[5], fullmove_number_regex); 34 | } 35 | 36 | } // namespace utils 37 | -------------------------------------------------------------------------------- /src/interfaces/utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace utils 9 | { 10 | /** 11 | * @brief Splits a string by spaces 12 | * 13 | * @param text 14 | * @return std::vector 15 | */ 16 | std::vector tokenize(const std::string &text); 17 | 18 | /** 19 | * @brief Checks Whether a given FEN string is valid or not 20 | * 21 | * @param args 22 | * @return true 23 | * @return false 24 | */ 25 | bool is_fen_valid(std::vector &args); 26 | 27 | /** 28 | * @brief Command Abstract class 29 | * All commands should implement this class 30 | * 31 | */ 32 | class Command 33 | { 34 | protected: 35 | Board &_board; 36 | 37 | public: 38 | Command(Board &board) : _board(board){}; 39 | virtual void execute(std::vector &args) = 0; 40 | }; 41 | 42 | } // namespace utils -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | int main(int argc, char const *argv[]) 9 | { 10 | opts::ExecOptions options = opts::parseOptions(argc, argv); 11 | 12 | switch (options.interface) 13 | { 14 | case opts::InterfaceMode::CLI: 15 | std::cout << "Codfish Initialized in CLI Mode" << std::endl; 16 | cli::init(); 17 | break; 18 | case opts::InterfaceMode::UCI: 19 | std::cout << "Codfish Initialized in UCI Mode" << std::endl; 20 | uci::init(); 21 | break; 22 | default: 23 | std::cout << "Codfish Initialized in UCI Mode" << std::endl; 24 | uci::init(); 25 | break; 26 | } 27 | 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /src/opts.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | namespace opts 6 | { 7 | ExecOptions parseOptions(int argc, char const *argv[]) 8 | { 9 | std::vector arguments(argv + 1, argv + argc); 10 | 11 | ExecOptions options{}; 12 | 13 | for (std::string arg : arguments) 14 | { 15 | if (arg == "--cli") 16 | { 17 | options.interface = CLI; 18 | } 19 | else if (arg == "--uci") 20 | { 21 | options.interface = UCI; 22 | } 23 | else 24 | { 25 | std::cerr << "Unknown Option: " << arg << "\n"; 26 | } 27 | } 28 | 29 | return options; 30 | } 31 | 32 | } // namespace opts 33 | -------------------------------------------------------------------------------- /src/opts.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace opts 7 | { 8 | 9 | typedef enum 10 | { 11 | UCI, 12 | CLI, 13 | } InterfaceMode; 14 | 15 | typedef struct 16 | { 17 | InterfaceMode interface; 18 | } ExecOptions; 19 | 20 | ExecOptions parseOptions(int argc, char const *argv[]); 21 | 22 | } // namespace opts 23 | -------------------------------------------------------------------------------- /tests/movegen/test_board.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | 3 | #include "../catch.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | static void setup() 11 | { 12 | magics::init(); 13 | tables::init(); 14 | } 15 | 16 | TEST_CASE("Test get piece from square") 17 | { 18 | setup(); 19 | 20 | SECTION("Position 1") 21 | { 22 | Board board = Board(); 23 | board.set_from_fen("r1b1kb1r/ppp1p2p/5pp1/7K/4pPn1/8/PP1P2nP/RNB2qNR", "w", "kq", "-", "0", "14"); 24 | REQUIRE(board.get_piece_from_square(A8).type == ROOK); 25 | REQUIRE(board.get_piece_from_square(G6).type == PAWN); 26 | REQUIRE(board.get_piece_from_square(C1).type == BISHOP); 27 | REQUIRE(board.get_piece_from_square(H5).type == KING); 28 | REQUIRE(board.get_piece_from_square(G2).type == KNIGHT); 29 | REQUIRE(board.get_piece_from_square(F1).type == QUEEN); 30 | REQUIRE(board.get_piece_from_square(D4).type == EMPTY_PIECE); 31 | } 32 | } -------------------------------------------------------------------------------- /tests/movegen/test_move.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | 3 | #include "../catch.hpp" 4 | 5 | #include 6 | 7 | // TODO: Missing tests for get_uci() 8 | 9 | TEST_CASE("Test Moves") 10 | { 11 | SECTION("Impossible Move") 12 | { 13 | Move move = Move(0, 63, PAWN, QUEEN, QUEEN, true, false, true); 14 | REQUIRE(move.get_from_square() == 0); 15 | REQUIRE(move.get_to_square() == 63); 16 | REQUIRE(move.get_piece_type() == PAWN); 17 | REQUIRE(move.get_captured_piece_type() == QUEEN); 18 | REQUIRE(move.get_promoted_piece_type() == QUEEN); 19 | REQUIRE(move.is_double_push() == true); 20 | REQUIRE(move.is_en_passant() == false); 21 | REQUIRE(move.is_castle() == true); 22 | REQUIRE(move.is_capture() == true); 23 | REQUIRE(move.get_uci() == "a1h8q"); 24 | } 25 | 26 | SECTION("Pawn Takes King") 27 | { 28 | Move move = Move(0, 1, PAWN, KING, EMPTY_PIECE, false, false, false); 29 | REQUIRE(move.get_from_square() == 0); 30 | REQUIRE(move.get_to_square() == 1); 31 | REQUIRE(move.get_piece_type() == PAWN); 32 | REQUIRE(move.get_captured_piece_type() == KING); 33 | REQUIRE(move.get_promoted_piece_type() == EMPTY_PIECE); 34 | REQUIRE(move.is_promotion() == false); 35 | REQUIRE(move.is_double_push() == false); 36 | REQUIRE(move.is_en_passant() == false); 37 | REQUIRE(move.is_castle() == false); 38 | REQUIRE(move.is_capture() == true); 39 | REQUIRE(move.is_promotion() == false); 40 | REQUIRE(move.is_promotion() == false); 41 | } 42 | 43 | SECTION("Pawn Takes Queen") 44 | { 45 | Move move = Move(0, 1, PAWN, QUEEN, EMPTY_PIECE, false, false, false); 46 | REQUIRE(move.get_from_square() == 0); 47 | REQUIRE(move.get_to_square() == 1); 48 | REQUIRE(move.get_piece_type() == PAWN); 49 | REQUIRE(move.get_captured_piece_type() == QUEEN); 50 | REQUIRE(move.get_promoted_piece_type() == EMPTY_PIECE); 51 | REQUIRE(move.is_promotion() == false); 52 | REQUIRE(move.is_double_push() == false); 53 | REQUIRE(move.is_en_passant() == false); 54 | REQUIRE(move.is_castle() == false); 55 | REQUIRE(move.is_capture() == true); 56 | REQUIRE(move.get_uci() == "a1b1"); 57 | } 58 | 59 | SECTION("Pawn Takes Rook") 60 | { 61 | Move move = Move(0, 1, PAWN, ROOK, EMPTY_PIECE, false, false, false); 62 | REQUIRE(move.get_from_square() == 0); 63 | REQUIRE(move.get_to_square() == 1); 64 | REQUIRE(move.get_piece_type() == PAWN); 65 | REQUIRE(move.get_captured_piece_type() == ROOK); 66 | REQUIRE(move.get_promoted_piece_type() == EMPTY_PIECE); 67 | REQUIRE(move.is_promotion() == false); 68 | REQUIRE(move.is_double_push() == false); 69 | REQUIRE(move.is_en_passant() == false); 70 | REQUIRE(move.is_castle() == false); 71 | REQUIRE(move.is_capture() == true); 72 | REQUIRE(move.is_promotion() == false); 73 | } 74 | 75 | SECTION("Pawn Takes Bishop") 76 | { 77 | Move move = Move(0, 1, PAWN, BISHOP, EMPTY_PIECE, false, false, false); 78 | REQUIRE(move.get_from_square() == 0); 79 | REQUIRE(move.get_to_square() == 1); 80 | REQUIRE(move.get_piece_type() == PAWN); 81 | REQUIRE(move.get_captured_piece_type() == BISHOP); 82 | REQUIRE(move.get_promoted_piece_type() == EMPTY_PIECE); 83 | REQUIRE(move.is_promotion() == false); 84 | REQUIRE(move.is_double_push() == false); 85 | REQUIRE(move.is_en_passant() == false); 86 | REQUIRE(move.is_castle() == false); 87 | REQUIRE(move.is_capture() == true); 88 | REQUIRE(move.get_uci() == "a1b1"); 89 | } 90 | 91 | SECTION("Pawn Takes Knight") 92 | { 93 | Move move = Move(0, 1, PAWN, KNIGHT, EMPTY_PIECE, false, false, false); 94 | REQUIRE(move.get_from_square() == 0); 95 | REQUIRE(move.get_to_square() == 1); 96 | REQUIRE(move.get_piece_type() == PAWN); 97 | REQUIRE(move.get_captured_piece_type() == KNIGHT); 98 | REQUIRE(move.get_promoted_piece_type() == EMPTY_PIECE); 99 | REQUIRE(move.is_promotion() == false); 100 | REQUIRE(move.is_double_push() == false); 101 | REQUIRE(move.is_en_passant() == false); 102 | REQUIRE(move.is_castle() == false); 103 | REQUIRE(move.is_capture() == true); 104 | REQUIRE(move.get_uci() == "a1b1"); 105 | } 106 | 107 | SECTION("Pawn Takes Pawn") 108 | { 109 | Move move = Move(0, 1, PAWN, PAWN, EMPTY_PIECE, false, false, false); 110 | REQUIRE(move.get_from_square() == 0); 111 | REQUIRE(move.get_to_square() == 1); 112 | REQUIRE(move.get_piece_type() == PAWN); 113 | REQUIRE(move.get_captured_piece_type() == PAWN); 114 | REQUIRE(move.is_promotion() == false); 115 | REQUIRE(move.is_double_push() == false); 116 | REQUIRE(move.is_en_passant() == false); 117 | REQUIRE(move.is_castle() == false); 118 | REQUIRE(move.is_capture() == true); 119 | REQUIRE(move.get_uci() == "a1b1"); 120 | } 121 | } -------------------------------------------------------------------------------- /tests/movegen/test_perft.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | 3 | #include "../catch.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | void setup() 11 | { 12 | magics::init(); 13 | tables::init(); 14 | } 15 | 16 | TEST_CASE("Starting Board") 17 | { 18 | setup(); 19 | 20 | Board board = Board(); 21 | 22 | SECTION("Perft 0") 23 | { 24 | REQUIRE(perft::perft(board, 0) == 1); 25 | } 26 | 27 | SECTION("Perft 1") 28 | { 29 | REQUIRE(perft::perft(board, 1) == 20); 30 | } 31 | 32 | SECTION("Perft 2") 33 | { 34 | REQUIRE(perft::perft(board, 2) == 400); 35 | } 36 | 37 | SECTION("Perft 3") 38 | { 39 | REQUIRE(perft::perft(board, 3) == 8902); 40 | } 41 | 42 | SECTION("Perft 4") 43 | { 44 | REQUIRE(perft::perft(board, 4) == 197281); 45 | } 46 | 47 | SECTION("Perft 5") 48 | { 49 | REQUIRE(perft::perft(board, 5) == 4865609); 50 | } 51 | 52 | SECTION("Perft 6") 53 | { 54 | REQUIRE(perft::perft(board, 6) == 119060324); 55 | } 56 | } 57 | 58 | TEST_CASE("KiwiPete Board") 59 | { 60 | setup(); 61 | 62 | Board board = Board(); 63 | board.set_from_fen("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R", "w", "KQkq", "-", "0", "1"); 64 | 65 | SECTION("Perft 0") 66 | { 67 | REQUIRE(perft::perft(board, 0) == 1); 68 | } 69 | 70 | SECTION("Perft 1") 71 | { 72 | REQUIRE(perft::perft(board, 1) == 48); 73 | } 74 | 75 | SECTION("Perft 2") 76 | { 77 | REQUIRE(perft::perft(board, 2) == 2039); 78 | } 79 | 80 | SECTION("Perft 3") 81 | { 82 | REQUIRE(perft::perft(board, 3) == 97862); 83 | } 84 | 85 | SECTION("Perft 4") 86 | { 87 | REQUIRE(perft::perft(board, 4) == 4085603); 88 | } 89 | 90 | SECTION("Perft 5") 91 | { 92 | REQUIRE(perft::perft(board, 5) == 193690690); 93 | } 94 | } 95 | 96 | TEST_CASE("Chess Programming Wiki Board 3") 97 | { 98 | setup(); 99 | 100 | Board board = Board(); 101 | board.set_from_fen("8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8", "w", "-", "-", "0", "1"); 102 | 103 | SECTION("Perft 0") 104 | { 105 | REQUIRE(perft::perft(board, 0) == 1); 106 | } 107 | 108 | SECTION("Perft 1") 109 | { 110 | REQUIRE(perft::perft(board, 1) == 14); 111 | } 112 | 113 | SECTION("Perft 2") 114 | { 115 | REQUIRE(perft::perft(board, 2) == 191); 116 | } 117 | 118 | SECTION("Perft 3") 119 | { 120 | REQUIRE(perft::perft(board, 3) == 2812); 121 | } 122 | 123 | SECTION("Perft 4") 124 | { 125 | REQUIRE(perft::perft(board, 4) == 43238); 126 | } 127 | 128 | SECTION("Perft 5") 129 | { 130 | REQUIRE(perft::perft(board, 5) == 674624); 131 | } 132 | 133 | SECTION("Perft 6") 134 | { 135 | REQUIRE(perft::perft(board, 6) == 11030083); 136 | } 137 | } 138 | 139 | TEST_CASE("Chess Programming Wiki Board 4") 140 | { 141 | setup(); 142 | 143 | Board board = Board(); 144 | board.set_from_fen("r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1", "w", "kq", "-", "0", "1"); 145 | 146 | SECTION("Perft 0") 147 | { 148 | REQUIRE(perft::perft(board, 0) == 1); 149 | } 150 | 151 | SECTION("Perft 1") 152 | { 153 | REQUIRE(perft::perft(board, 1) == 6); 154 | } 155 | 156 | SECTION("Perft 2") 157 | { 158 | REQUIRE(perft::perft(board, 2) == 264); 159 | } 160 | 161 | SECTION("Perft 3") 162 | { 163 | REQUIRE(perft::perft(board, 3) == 9467); 164 | } 165 | 166 | SECTION("Perft 4") 167 | { 168 | REQUIRE(perft::perft(board, 4) == 422333); 169 | } 170 | 171 | SECTION("Perft 5") 172 | { 173 | REQUIRE(perft::perft(board, 5) == 15833292); 174 | } 175 | } 176 | 177 | TEST_CASE("Chess Programming Wiki Board 5") 178 | { 179 | setup(); 180 | 181 | Board board = Board(); 182 | board.set_from_fen("rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R", "w", "KQ", "-", "1", "8"); 183 | 184 | SECTION("Perft 0") 185 | { 186 | REQUIRE(perft::perft(board, 0) == 1); 187 | } 188 | 189 | SECTION("Perft 1") 190 | { 191 | REQUIRE(perft::perft(board, 1) == 44); 192 | } 193 | 194 | SECTION("Perft 2") 195 | { 196 | REQUIRE(perft::perft(board, 2) == 1486); 197 | } 198 | 199 | SECTION("Perft 3") 200 | { 201 | REQUIRE(perft::perft(board, 3) == 62379); 202 | } 203 | 204 | SECTION("Perft 4") 205 | { 206 | REQUIRE(perft::perft(board, 4) == 2103487); 207 | } 208 | 209 | SECTION("Perft 5") 210 | { 211 | REQUIRE(perft::perft(board, 5) == 89941194); 212 | } 213 | } 214 | 215 | TEST_CASE("Chess Programming Wiki Board 6") 216 | { 217 | setup(); 218 | 219 | Board board = Board(); 220 | board.set_from_fen("r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1", "w", "-", "-", "0", "10"); 221 | 222 | SECTION("Perft 0") 223 | { 224 | REQUIRE(perft::perft(board, 0) == 1); 225 | } 226 | 227 | SECTION("Perft 1") 228 | { 229 | REQUIRE(perft::perft(board, 1) == 46); 230 | } 231 | 232 | SECTION("Perft 2") 233 | { 234 | REQUIRE(perft::perft(board, 2) == 2079); 235 | } 236 | 237 | SECTION("Perft 3") 238 | { 239 | REQUIRE(perft::perft(board, 3) == 89890); 240 | } 241 | 242 | SECTION("Perft 4") 243 | { 244 | REQUIRE(perft::perft(board, 4) == 3894594); 245 | } 246 | 247 | SECTION("Perft 5") 248 | { 249 | REQUIRE(perft::perft(board, 5) == 164075551); 250 | } 251 | } -------------------------------------------------------------------------------- /tests/movegen/test_tables.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | 3 | #include "../catch.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | void setup() 10 | { 11 | magics::init(); 12 | tables::init(); 13 | } 14 | 15 | // Test Pawn 16 | 17 | TEST_CASE("Test Pawn Moves") 18 | { 19 | setup(); 20 | 21 | SECTION("Corners Start") 22 | { 23 | REQUIRE(tables::get_pawn_attacks(WHITE, A2) == 0x20000); 24 | REQUIRE(tables::get_pawn_attacks(WHITE, H2) == 0x400000); 25 | REQUIRE(tables::get_pawn_attacks(BLACK, A7) == 0x20000000000); 26 | REQUIRE(tables::get_pawn_attacks(BLACK, H7) == 0x400000000000); 27 | } 28 | 29 | SECTION("Limits White") 30 | { 31 | REQUIRE(tables::get_pawn_attacks(WHITE, A8) == 0x0); 32 | REQUIRE(tables::get_pawn_attacks(WHITE, H8) == 0x0); 33 | } 34 | 35 | SECTION("Limits Black") 36 | { 37 | REQUIRE(tables::get_pawn_attacks(BLACK, A1) == 0x0); 38 | REQUIRE(tables::get_pawn_attacks(BLACK, H1) == 0x0); 39 | } 40 | 41 | SECTION("Middle of the Board") 42 | { 43 | REQUIRE(tables::get_pawn_attacks(WHITE, D4) == 0x1400000000); 44 | REQUIRE(tables::get_pawn_attacks(BLACK, E5) == 0x28000000); 45 | } 46 | } 47 | 48 | // Test King 49 | 50 | TEST_CASE("Test King Moves") 51 | { 52 | setup(); 53 | 54 | SECTION("Corners") 55 | { 56 | REQUIRE(tables::get_king_attacks(A1) == 0x302); 57 | REQUIRE(tables::get_king_attacks(H1) == 0xc040); 58 | REQUIRE(tables::get_king_attacks(A8) == 0x203000000000000); 59 | REQUIRE(tables::get_king_attacks(H8) == 0x40c0000000000000); 60 | } 61 | 62 | SECTION("Middle of the Board") 63 | { 64 | REQUIRE(tables::get_king_attacks(D4) == 0x1c141c0000); 65 | } 66 | } 67 | 68 | // Test Knights 69 | 70 | TEST_CASE("Test Knights Moves") 71 | { 72 | setup(); 73 | 74 | SECTION("Corners") 75 | { 76 | REQUIRE(tables::get_knight_attacks(A1) == 0x20400); 77 | REQUIRE(tables::get_knight_attacks(H1) == 0x402000); 78 | REQUIRE(tables::get_knight_attacks(A8) == 0x4020000000000); 79 | REQUIRE(tables::get_knight_attacks(H8) == 0x20400000000000); 80 | } 81 | 82 | SECTION("Middle of the Board") 83 | { 84 | REQUIRE(tables::get_knight_attacks(D4) == 0x142200221400); 85 | } 86 | } 87 | 88 | // Test Bishop 89 | 90 | TEST_CASE("Test Bishop Moves") 91 | { 92 | setup(); 93 | 94 | SECTION("Corners") 95 | { 96 | REQUIRE(tables::get_bishop_attacks(A1, 0xa000008) == 0x8040200); 97 | REQUIRE(tables::get_bishop_attacks(H1, 0xfefdfbf7efdfbfff) == 0x102040810204000); 98 | REQUIRE(tables::get_bishop_attacks(H1, 0xfefdfbf7efdfbf7f) == 0x102040810204000); 99 | REQUIRE(tables::get_bishop_attacks(H8, 0x8120000808000202) == 0x40201008000000); 100 | REQUIRE(tables::get_bishop_attacks(A8, 0x8124000858204282) == 0x2040800000000); 101 | } 102 | 103 | SECTION("Middle of the Board") 104 | { 105 | REQUIRE(tables::get_bishop_attacks(D4, 0x8124000858204282) == 0x8041221400142240); 106 | REQUIRE(tables::get_bishop_attacks(G6, 0x8124400858204282) == 0xa000a010000000); 107 | } 108 | } 109 | 110 | // Test Rook get_rook_attacks 111 | 112 | TEST_CASE("Test Rook Moves") 113 | { 114 | setup(); 115 | 116 | SECTION("Corners") 117 | { 118 | REQUIRE(tables::get_rook_attacks(A1, 0xa000009) == 0x10101010101010e); 119 | REQUIRE(tables::get_rook_attacks(H1, 0xfefdfbf7efdfbfff) == 0x8040); 120 | REQUIRE(tables::get_rook_attacks(H1, 0xfefdfbf7efdfbf7f) == 0x8040); 121 | REQUIRE(tables::get_rook_attacks(H8, 0x8120000808000202) == 0x7f80808080808080); 122 | REQUIRE(tables::get_rook_attacks(A8, 0x8324000958a142ba) == 0x201010100000000); 123 | } 124 | 125 | SECTION("Middle of the Board") 126 | { 127 | REQUIRE(tables::get_rook_attacks(D4, 0x8124000858204282) == 0x817080808); 128 | REQUIRE(tables::get_rook_attacks(F5, 0x8124002858204282) == 0x2020d820200000); 129 | } 130 | } 131 | 132 | // Test Queen 133 | 134 | TEST_CASE("Test Queen Moves") 135 | { 136 | setup(); 137 | 138 | SECTION("Corners") 139 | { 140 | REQUIRE(tables::get_queen_attacks(A1, 0xa000009) == 0x10101010905030e); 141 | REQUIRE(tables::get_queen_attacks(H1, 0xfefdfbf7efdfbfff) == 0x10204081020c040); 142 | REQUIRE(tables::get_queen_attacks(H1, 0xfefdfbf7efdfbf7f) == 0x10204081020c040); 143 | REQUIRE(tables::get_queen_attacks(H8, 0x8120000808000202) == 0x7fc0a09088808080); 144 | REQUIRE(tables::get_queen_attacks(A8, 0x8324000958a142ba) == 0x203050900000000); 145 | } 146 | 147 | SECTION("Middle of the Board") 148 | { 149 | REQUIRE(tables::get_queen_attacks(D4, 0x812402085a24428a) == 0x8040221c161c2848); 150 | REQUIRE(tables::get_queen_attacks(F6, 0x812432a8da245282) == 0x8870d07088000000); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /tests/movegen/test_unmake.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | 3 | #include "../catch.hpp" 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | void setup() 14 | { 15 | magics::init(); 16 | tables::init(); 17 | } 18 | 19 | TEST_CASE("unmake move") 20 | { 21 | setup(); 22 | 23 | Board board = Board(); 24 | 25 | SECTION("PAWNS rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") 26 | { 27 | // board.set_from_fen("1k6/p6p/K6P/8/8/8/8/1q4q1", "b", "-", "-", "0", "1"); 28 | Move move = Move(E2, E4, PAWN, EMPTY_PIECE, EMPTY_PIECE, true, false, false); 29 | Board::GameState state = board.get_state(); 30 | board.make(move); 31 | board.unmake(move, state); 32 | REQUIRE(board.get_fen() == "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1\n"); 33 | } 34 | 35 | SECTION("knight capture rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") 36 | { 37 | // board.set_from_fen("1k6/p6p/K6P/8/8/8/8/1q4q1", "b", "-", "-", "0", "1"); 38 | Move move = Move(B1, H7, KNIGHT, PAWN, EMPTY_PIECE, false, false, false); 39 | Board::GameState state = board.get_state(); 40 | board.make(move); 41 | board.unmake(move, state); 42 | REQUIRE(board.get_fen() == "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1\n"); 43 | } 44 | 45 | SECTION("castle rnbqkbnr/3ppppp/8/ppp5/6P1/5N1B/PPPPPP1P/RNBQK2R w KQkq - 0 4") 46 | { 47 | board.set_from_fen("rnbqkbnr/3ppppp/8/ppp5/6P1/5N1B/PPPPPP1P/RNBQK2R", "w", "KQkq", "-", "0", "4"); 48 | Move move = Move(E1, G1, KING, EMPTY_PIECE, EMPTY_PIECE, false, false, true); 49 | Board::GameState state = board.get_state(); 50 | board.make(move); 51 | board.unmake(move, state); 52 | REQUIRE(board.get_fen() == "rnbqkbnr/3ppppp/8/ppp5/6P1/5N1B/PPPPPP1P/RNBQK2R w KQkq - 0 4\n"); 53 | } 54 | 55 | SECTION("pawn capture rnbqkbnr/3ppppp/8/ppp5/2P3P1/5N1B/PP1PPP1P/RNBQK2R b KQkq - 0 4") 56 | { 57 | board.set_from_fen("rnbqkbnr/3ppppp/8/ppp5/2P3P1/5N1B/PP1PPP1P/RNBQK2R", "b", "KQkq", "-", "0", "4"); 58 | Move move = Move(B5, C4, PAWN, PAWN, EMPTY_PIECE, false, false, false); 59 | REQUIRE(move.is_capture() == true); 60 | Board::GameState state = board.get_state(); 61 | board.make(move); 62 | board.unmake(move, state); 63 | REQUIRE(board.get_fen() == "rnbqkbnr/3ppppp/8/ppp5/2P3P1/5N1B/PP1PPP1P/RNBQK2R b KQkq - 0 4\n"); 64 | } 65 | 66 | SECTION("promotion 4kbnr/P2ppppp/3q4/8/6P1/5N1B/PP1PPP1P/RNBQK1KR w k - 0 4") 67 | { 68 | board.set_from_fen("4kbnr/P2ppppp/3q4/8/6P1/5N1B/PP1PPP1P/RNBQK1KR", "w", "k", "-", "0", "4"); 69 | Move move = Move(A7, A8, PAWN, EMPTY_PIECE, QUEEN, false, false, false); 70 | Board::GameState state = board.get_state(); 71 | board.make(move); 72 | board.unmake(move, state); 73 | REQUIRE(board.get_fen() == "4kbnr/P2ppppp/3q4/8/6P1/5N1B/PP1PPP1P/RNBQK1KR w k - 0 4\n"); 74 | } 75 | 76 | SECTION("en passant rnbqkbnr/3ppppp/8/p1p5/2pP2P1/5N1B/PP2PP1P/RNBQK2R b KQkq d3 0 5") 77 | { 78 | board.set_from_fen("rnbqkbnr/3ppppp/8/p1p5/2pP2P1/5N1B/PP2PP1P/RNBQK2R", "b", "KQkq", "d3", "0", "5"); 79 | Move move = Move(C4, D3, PAWN, PAWN, EMPTY_PIECE, false, true, false); 80 | Board::GameState state = board.get_state(); 81 | board.make(move); 82 | board.unmake(move, state); 83 | REQUIRE(board.get_fen() == "rnbqkbnr/3ppppp/8/p1p5/2pP2P1/5N1B/PP2PP1P/RNBQK2R b KQkq d3 0 5\n"); 84 | } 85 | 86 | SECTION("d2d4 rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR w KQkq - 0 1") 87 | { 88 | board.set_from_fen("rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR", "w", "KQkq", "-", "0", "1"); 89 | Move move = Move(D2, D4, PAWN, EMPTY_PIECE, EMPTY_PIECE, true, false, false); 90 | Board::GameState state = board.get_state(); 91 | board.make(move); 92 | board.unmake(move, state); 93 | REQUIRE(board.get_fen() == "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1\n"); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /tests/movepicker/test_movepicker.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | 3 | #include "../catch.hpp" 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | void setup() 18 | { 19 | magics::init(); 20 | tables::init(); 21 | eval::init(); 22 | zobrist::init(); 23 | } 24 | 25 | TEST_CASE("Checkmate in one move") 26 | { 27 | setup(); 28 | 29 | Board board = Board(); 30 | MovePicker ai = MovePicker(board); 31 | ai.set_max_depth(3); 32 | 33 | SECTION("1k6/p6p/K6P/8/8/8/8/1q4q1 b - - 0 1") 34 | { 35 | board.set_from_fen("1k6/p6p/K6P/8/8/8/8/1q4q1", "b", "-", "-", "0", "1"); 36 | MovePicker::SearchResult result = ai.find_best_move(); 37 | Move best_move = result.pv[0]; 38 | REQUIRE(best_move.get_uci() == "g1b6"); 39 | } 40 | 41 | SECTION("k6r/8/8/8/8/8/8/2bPKPb1 b - - 0 1") 42 | { 43 | board.set_from_fen("k6r/8/8/8/8/8/8/2bPKPb1", "b", "-", "-", "0", "1"); 44 | MovePicker::SearchResult result = ai.find_best_move(); 45 | Move best_move = result.pv[0]; 46 | REQUIRE(best_move.get_uci() == "h8e8"); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/movepicker/test_zobrist.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | 3 | #include "../catch.hpp" 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | void setup() 16 | { 17 | magics::init(); 18 | tables::init(); 19 | zobrist::init(); 20 | } 21 | 22 | TEST_CASE("hash_key") 23 | { 24 | setup(); 25 | 26 | Board board = Board(); 27 | 28 | SECTION("fen hash key") 29 | { 30 | board.set_from_fen("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R", "w", "KQkq", "-", "0", "1"); 31 | REQUIRE(board.get_hash_key() == zobrist::generate_hash_key(board)); 32 | } 33 | 34 | SECTION("moves hash key") 35 | { 36 | board.set_from_fen("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R", "w", "KQkq", "-", "0", "1"); 37 | Move move = Move(D5, D6, PAWN, EMPTY_PIECE, EMPTY_PIECE, false, false, false); 38 | Board::GameState state = board.get_state(); 39 | board.make(move); 40 | REQUIRE(board.get_hash_key() == zobrist::generate_hash_key(board)); 41 | board.unmake(move, state); 42 | REQUIRE(board.get_hash_key() == state.hash_key); 43 | } 44 | 45 | SECTION("capture hash key") 46 | { 47 | board.set_from_fen("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R", "w", "KQkq", "-", "0", "1"); 48 | Move move = Move(F3, F6, QUEEN, KNIGHT, EMPTY_PIECE, false, false, false); 49 | Board::GameState state = board.get_state(); 50 | board.make(move); 51 | REQUIRE(board.get_hash_key() == zobrist::generate_hash_key(board)); 52 | board.unmake(move, state); 53 | REQUIRE(board.get_hash_key() == state.hash_key); 54 | } 55 | 56 | SECTION("double push hash key") 57 | { 58 | board.set_from_fen("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R", "w", "KQkq", "-", "0", "1"); 59 | Move move = Move(A2, A4, PAWN, EMPTY_PIECE, EMPTY_PIECE, true, false, false); 60 | Board::GameState state = board.get_state(); 61 | board.make(move); 62 | REQUIRE(board.get_hash_key() == zobrist::generate_hash_key(board)); 63 | board.unmake(move, state); 64 | REQUIRE(board.get_hash_key() == state.hash_key); 65 | } 66 | 67 | SECTION("promotion hash key") 68 | { 69 | board.set_from_fen("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q2/PPPBBP1p/R3K1P1", "b", "KQkq", "-", "0", "1"); 70 | Move move = Move(H2, H1, PAWN, EMPTY_PIECE, KNIGHT, false, false, false); 71 | Board::GameState state = board.get_state(); 72 | board.make(move); 73 | REQUIRE(board.get_hash_key() == zobrist::generate_hash_key(board)); 74 | board.unmake(move, state); 75 | REQUIRE(board.get_hash_key() == state.hash_key); 76 | } 77 | 78 | SECTION("promotion + capture hash key") 79 | { 80 | board.set_from_fen("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q2/PPPBBP1p/R3K1PR", "b", "KQkq", "-", "0", "1"); 81 | Move move = Move(H2, G1, PAWN, PAWN, QUEEN, false, false, false); 82 | Board::GameState state = board.get_state(); 83 | board.make(move); 84 | REQUIRE(board.get_hash_key() == zobrist::generate_hash_key(board)); 85 | board.unmake(move, state); 86 | REQUIRE(board.get_hash_key() == state.hash_key); 87 | } 88 | 89 | SECTION("en passant hash key") 90 | { 91 | board.set_from_fen("rnbqkbnr/3ppppp/8/p1p5/2pP2P1/5N1B/PP2PP1P/RNBQK2R", "b", "KQkq", "d3", "0", "5"); 92 | Move move = Move(C4, D3, PAWN, PAWN, EMPTY_PIECE, false, true, false); 93 | Board::GameState state = board.get_state(); 94 | board.make(move); 95 | REQUIRE(board.get_hash_key() == zobrist::generate_hash_key(board)); 96 | board.unmake(move, state); 97 | REQUIRE(board.get_hash_key() == state.hash_key); 98 | } 99 | 100 | SECTION("perft hash key") 101 | { 102 | Board::GameState state = board.get_state(); 103 | perft::perft(board, 5); 104 | REQUIRE(board.get_hash_key() == state.hash_key); 105 | } 106 | } --------------------------------------------------------------------------------