├── .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