├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── Makefile ├── ReadMe.md ├── changelog.md ├── pretty-eval-printer.py ├── run_configs ├── .midnight_run │ └── Midnight.run.xml ├── .midnight_tests_run │ └── MidnightTests.run.xml └── .midnight_tune_run │ └── MidnightTune.run.xml ├── src ├── 3rd_party │ └── incbin.h ├── board │ ├── constants │ │ ├── board_masks.h │ │ ├── misc_constants.h │ │ └── zobrist_constants.h │ ├── position.cpp │ ├── position.h │ └── types │ │ ├── bitboard.cpp │ │ ├── bitboard.h │ │ ├── board_types.h │ │ ├── piece.h │ │ └── square.h ├── engine.cpp ├── engine.h ├── evaluation │ ├── evaluate.cpp │ ├── evaluate.h │ ├── netM006.nnue │ └── simd.h ├── main.cpp ├── move_gen │ ├── move_gen_masks.h │ ├── move_generator.h │ ├── tables │ │ ├── attack_tables.h │ │ └── square_tables.h │ └── types │ │ ├── move.h │ │ └── types.h ├── move_search │ ├── extensions.h │ ├── move_ordering │ │ ├── move_ordering.cpp │ │ ├── move_ordering.h │ │ └── ordering_constants.h │ ├── pruning.h │ ├── pvs.cpp │ ├── pvs.h │ ├── reductions.cpp │ ├── reductions.h │ ├── search_constants.h │ ├── search_params.h │ ├── tables │ │ ├── history_table.cpp │ │ ├── history_table.h │ │ ├── lmr_table.cpp │ │ ├── lmr_table.h │ │ ├── pv_table.h │ │ ├── transposition_table.cpp │ │ └── transposition_table.h │ ├── types.cpp │ └── types.h ├── types.h ├── uci_interpreter │ ├── bench_fens.h │ ├── datagen.cpp │ ├── datagen.h │ ├── time_manager.cpp │ ├── time_manager.h │ ├── uci_interpreter.cpp │ ├── uci_interpreter.h │ ├── uci_move_parse.cpp │ └── uci_move_parse.h └── utils │ ├── clock.cpp │ ├── clock.h │ ├── fen_constants.h │ ├── helpers.cpp │ ├── helpers.h │ └── stack.h ├── tests ├── attacks.cpp ├── attacks.txt ├── board-rep.cpp ├── hash.cpp ├── lib │ └── doctests.h ├── perft.cpp ├── perft_results.txt └── stack.cpp └── texel-tuner ├── base.h ├── config.h ├── midnight.cpp ├── midnight.h ├── sources.csv ├── threadpool.cpp ├── threadpool.h ├── tune_main.cpp ├── tuner.cpp └── tuner.h /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | 3 | cmake-build-*_diagnostics_file/ 4 | .DS_Store 5 | /releases/ 6 | /ChessEngine/cmake-build-*/ 7 | build 8 | /testing/ 9 | /cmake-build-*/ 10 | /tmp/ 11 | midnight 12 | midnight-tests 13 | midnight-tune 14 | *.txt 15 | /*.exe 16 | sprt.sh -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(Midnight) 3 | 4 | set(CMAKE_CXX_STANDARD 20) 5 | 6 | if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") 7 | set(CMAKE_CXX_FLAGS "-fconstexpr-steps=900000000") 8 | else() 9 | set(CMAKE_CXX_FLAGS "-fconstexpr-ops-limit=900000000") 10 | endif() 11 | 12 | include(CheckIPOSupported) 13 | check_ipo_supported(RESULT LTO_SUPPORTED) 14 | if(LTO_SUPPORTED) 15 | set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON) 16 | set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO OFF) 17 | set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_DEBUG OFF) 18 | endif() 19 | 20 | 21 | set(MIDNIGHT_LIB src/engine.cpp src/utils/helpers.cpp src/utils/clock.cpp src/move_search/types.cpp src/move_search/reductions.cpp src/move_search/pvs.cpp src/move_search/tables/transposition_table.cpp src/move_search/tables/history_table.cpp src/board/position.cpp src/board/types/bitboard.cpp src/move_search/tables/lmr_table.cpp src/move_search/move_ordering/move_ordering.cpp src/uci_interpreter/time_manager.cpp src/uci_interpreter/uci_move_parse.cpp src/uci_interpreter/uci_interpreter.cpp src/evaluation/evaluate.cpp src/uci_interpreter/datagen.cpp) 22 | 23 | add_executable(Midnight src/main.cpp ${MIDNIGHT_LIB}) 24 | add_executable(MidnightTune ${MIDNIGHT_LIB} texel-tuner/tune_main.cpp texel-tuner/tuner.cpp texel-tuner/threadpool.cpp texel-tuner/midnight.cpp) 25 | add_executable(MidnightTests ${MIDNIGHT_LIB} tests/attacks.cpp tests/board-rep.cpp tests/hash.cpp tests/perft.cpp tests/stack.cpp) 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 archishou 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. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | EXE = midnight 2 | 3 | SOURCES := $(wildcard src/*.cpp) $(wildcard src/*/*.cpp) $(wildcard src/*/*/*.cpp) 4 | SOURCES := $(filter-out src/tuner.cpp, $(SOURCES)) 5 | 6 | ifeq ($(MAKECMDGOALS),tune) 7 | SOURCES += $(wildcard texel-tuner/*.cpp) 8 | SOURCES := $(filter-out src/main.cpp, $(SOURCES)) 9 | EXE = midnight-tune 10 | endif 11 | 12 | ifeq ($(MAKECMDGOALS),tests) 13 | SOURCES += $(wildcard tests/*.cpp) $(wildcard tests/*/*.cpp) 14 | SOURCES := $(filter-out src/main.cpp, $(SOURCES)) 15 | EXE = midnight-tests 16 | endif 17 | 18 | CXXFLAGS := -O3 -Isrc -flto -std=c++20 -march=native -Wall -Wextra -pedantic -DNDEBUG 19 | LDFLAGS := 20 | 21 | CXX := clang++ 22 | SUFFIX := 23 | 24 | # Detect Windows 25 | ifeq ($(OS), Windows_NT) 26 | DETECTED_OS := Windows 27 | SUFFIX := .exe 28 | CXXFLAGS += -static 29 | else 30 | DETECTED_OS := $(shell uname -s) 31 | CXXFLAGS += -pthread 32 | endif 33 | 34 | ifneq (,$(findstring clang,$(shell $(CXX) --version))) 35 | ifneq ($(DETECTED_OS),Darwin) 36 | LDFLAGS += -fuse-ld=lld 37 | endif 38 | endif 39 | 40 | OUT := $(EXE)$(SUFFIX) 41 | 42 | all: $(EXE) 43 | tests: $(EXE) 44 | tune: $(EXE) 45 | $(EXE) : $(SOURCES) 46 | $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $(OUT) $(SOURCES) 47 | 48 | clean: -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # Midnight 4 | [![License][license-badge]][license-link] 5 | [![Release][release-badge]][release-link] 6 | [![Commits][commits-badge]][commits-link] 7 | 8 |
9 | A free and open source UCI chess engine written in C++. 10 | 11 | # Strength 12 | 13 | | Version | CCRL 40/15 | CCRL Blitz | 14 | |--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 15 | | Midnight 8.0 | [3257±21 [#42]](http://computerchess.org.uk/ccrl/4040/cgi/engine_details.cgi?match_length=30&each_game=0&print=Details&each_game=0&eng=Midnight%208%2064-bit#Midnight_8_64-bit) | [3373±17 [#44]](http://computerchess.org.uk/ccrl/404/cgi/engine_details.cgi?match_length=30&each_game=1&print=Details&each_game=1&eng=Midnight%208%2064-bit#Midnight_8_64-bit) | 16 | | Midnight 7.0 | [3020±21 [#93]](http://computerchess.org.uk/ccrl/4040/cgi/engine_details.cgi?match_length=30&each_game=0&print=Details&each_game=0&eng=Midnight%207%2064-bit#Midnight_7_64-bit) | [3113±17 [#89]](http://computerchess.org.uk/ccrl/404/cgi/engine_details.cgi?match_length=30&each_game=1&print=Details&each_game=1&eng=Midnight%207%2064-bit#Midnight_7_64-bit) | 17 | | Midnight 6.0 | [2919±30 [#110]](https://ccrl.chessdom.com/ccrl/4040/cgi/engine_details.cgi?print=Details&each_game=0&eng=Midnight%206%2064-bit#Midnight_6_64-bit) | [3055±19 [#92]](https://ccrl.chessdom.com/ccrl/404/cgi/engine_details.cgi?print=Details&each_game=1&eng=Midnight%206%2064-bit#Midnight_6_64-bit) | 18 | | Midnight 5.0 | | [2828±15 [#146]](http://ccrl.chessdom.com/ccrl/404/cgi/engine_details.cgi?print=Details&each_game=1&eng=Midnight%205%2064-bit#Midnight_5_64-bit) | 19 | 20 | # Compilation 21 | ``` 22 | git clone https://github.com/archishou/MidnightChessEngine 23 | cd MidnightChessEngine 24 | make 25 | ``` 26 | 27 | # Credits 28 | Thanks to [@Alex2262](https://github.com/Alex2262) for helping with lots of small improvements in my engine. 29 | 30 | Thanks to [@Ciekce](https://github.com/Ciekce) for helping me become a better C++ dev. He helped me find code that produced undefined behavior and is responsible for helping me fix my Makefile numerous times. 31 | 32 | [commits-badge]:https://img.shields.io/github/commits-since/archishou/MidnightChessEngine/latest?style=for-the-badge 33 | [commits-link]:https://github.com/archishou/MidnightChessEngine/commits/master 34 | [release-badge]:https://img.shields.io/github/v/release/archishou/MidnightChessEngine?style=for-the-badge&label=official%20release 35 | [release-link]:https://github.com/archishou/MidnightChessEngine/releases/latest 36 | [license-badge]:https://img.shields.io/github/license/archishou/MidnightChessEngine?style=for-the-badge&label=license&color=success 37 | [license-link]:https://github.com/archishou/MidnightChessEngine/blob/master/LICENSE -------------------------------------------------------------------------------- /pretty-eval-printer.py: -------------------------------------------------------------------------------- 1 | # Credit to Analog Hors 2 | import re, sys 3 | 4 | tables = "" 5 | reading_ignore = True 6 | for line in sys.stdin: 7 | if line.startswith('// pretty ignore'): reading_ignore = True 8 | if line.startswith("#") or reading_ignore: 9 | print(line, end="") 10 | else: 11 | tables += line 12 | if line.startswith('// pretty stop-ignore'): reading_ignore = False 13 | 14 | names = iter(re.findall("\w+(?=\[\])", tables)) 15 | ints = iter(re.findall("-?[0-9]+", tables)) 16 | for name in names: 17 | print(f"constexpr Score {name}[] = {{") 18 | for _ in range(8): 19 | print(" ", end="") 20 | for _ in range(8): 21 | mg = next(ints) 22 | eg = next(ints) 23 | print(f" S({mg:>4}, {eg:>4}),", end="") 24 | print() 25 | read_table = False 26 | print("};") -------------------------------------------------------------------------------- /run_configs/.midnight_run/Midnight.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /run_configs/.midnight_tests_run/MidnightTests.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /run_configs/.midnight_tune_run/MidnightTune.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /src/board/constants/board_masks.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../types/board_types.h" 4 | 5 | constexpr array MASK_FILE = { 6 | 0x101010101010101, 0x202020202020202, 0x404040404040404, 0x808080808080808, 7 | 0x1010101010101010, 0x2020202020202020, 0x4040404040404040, 0x8080808080808080, 8 | }; 9 | 10 | constexpr array MASK_RANK = { 11 | 0xff, 0xff00, 0xff0000, 0xff000000, 0xff00000000, 12 | 0xff0000000000, 0xff000000000000, 0xff00000000000000 13 | }; 14 | 15 | constexpr array MASK_DIAGONAL = { 16 | 0x80, 0x8040, 0x804020, 17 | 0x80402010, 0x8040201008, 0x804020100804, 18 | 0x80402010080402, 0x8040201008040201, 0x4020100804020100, 19 | 0x2010080402010000, 0x1008040201000000, 0x804020100000000, 20 | 0x402010000000000, 0x201000000000000, 0x100000000000000, 21 | }; 22 | 23 | constexpr array MASK_ANTI_DIAGONAL= { 24 | 0x1, 0x102, 0x10204, 25 | 0x1020408, 0x102040810, 0x10204081020, 26 | 0x1020408102040, 0x102040810204080, 0x204081020408000, 27 | 0x408102040800000, 0x810204080000000, 0x1020408000000000, 28 | 0x2040800000000000, 0x4080000000000000, 0x8000000000000000, 29 | }; 30 | -------------------------------------------------------------------------------- /src/board/constants/misc_constants.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Alexander Tian on 4/26/23. 3 | // 4 | 5 | #pragma once 6 | #include "../types/bitboard.h" 7 | #include "../../types.h" 8 | #include "../types/piece.h" 9 | 10 | const std::string START_FEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; 11 | 12 | constexpr char PIECE_MATCHER[NPIECES] = {'P', 'N', 'B', 'R', 'Q', 'K', '-', '-', 'p', 'n', 'b', 'r', 'q', 'k', '-'}; -------------------------------------------------------------------------------- /src/board/position.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Alex Tian on 12/2/2022. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include "position.h" 9 | #include "types/bitboard.h" 10 | #include "constants/misc_constants.h" 11 | #include "constants/zobrist_constants.h" 12 | #include "../utils/helpers.h" 13 | 14 | Position::Position(const std::string& fen) { 15 | set_fen(fen); 16 | } 17 | 18 | void Position::reset() { 19 | nnue.reset(); 20 | state_history.clear(); 21 | 22 | pieces.fill(0); 23 | board.fill(NO_PIECE); 24 | 25 | side = WHITE; 26 | } 27 | 28 | template 29 | void Position::place_piece(Piece piece, Square square) { 30 | pieces[piece] |= square_to_bitboard(square); 31 | board[square] = piece; 32 | if constexpr (feature_update) { 33 | nnue.update_feature(piece, square); 34 | state_history.top().hash ^= ZOBRIST_PIECE_SQUARE[piece][square]; 35 | } 36 | } 37 | 38 | template 39 | void Position::remove_piece(Square square) { 40 | if constexpr (feature_update) { 41 | nnue.update_feature(piece_at(square), square); 42 | state_history.top().hash ^= ZOBRIST_PIECE_SQUARE[piece_at(square)][square]; 43 | } 44 | pieces[piece_at(square)] &= ~square_to_bitboard(square); 45 | board[square] = NO_PIECE; 46 | } 47 | 48 | template 49 | void Position::move_piece(Square from, Square to) { 50 | Piece piece = piece_at(from); 51 | remove_piece(from); 52 | place_piece(piece, to); 53 | } 54 | 55 | u8 Position::castling_state(Bitboard from_to) const { 56 | i32 white_oo = !(from_to & PositionState::WHITE_OO_BANNED_MASK) << 3; 57 | i32 white_ooo = !(from_to & PositionState::WHITE_OOO_BANNED_MASK) << 2; 58 | i32 black_oo = !(from_to & PositionState::BLACK_OO_BANNED_MASK) << 1; 59 | i32 black_ooo = !(from_to & PositionState::BLACK_OOO_BANNED_MASK); 60 | return white_oo | white_ooo | black_oo | black_ooo; 61 | } 62 | 63 | std::string Position::fen() const { 64 | std::ostringstream fen; 65 | i32 empty; 66 | 67 | for (i32 i = 56; i >= 0; i -= 8) { 68 | empty = 0; 69 | for (i32 j = 0; j < 8; j++) { 70 | Piece p = board[i + j]; 71 | if (p == NO_PIECE) empty++; 72 | else { 73 | fen << (empty == 0 ? "" : std::to_string(empty)) << PIECE_MATCHER[p]; 74 | empty = 0; 75 | } 76 | } 77 | 78 | if (empty != 0) fen << empty; 79 | if (i > 0) fen << '/'; 80 | } 81 | 82 | std::string castling_rights; 83 | const Bitboard set_castling_state = castling_state(state_history.peek().from_to); 84 | if ((set_castling_state >> 3) & 0b1) castling_rights += "K"; 85 | if ((set_castling_state >> 2) & 0b1) castling_rights += "Q"; 86 | if ((set_castling_state >> 1) & 0b1) castling_rights += "k"; 87 | if (set_castling_state & 0b1) castling_rights += "q"; 88 | if (set_castling_state == 0) castling_rights = "-"; 89 | 90 | fen << (side == WHITE ? " w " : " b ") 91 | << castling_rights 92 | << (ep_square() == NO_SQUARE ? " -" : " " + std::string(SQ_TO_STRING[ep_square()])) 93 | << " " 94 | << std::to_string(fifty_move_rule()) << " " 95 | << "1"; 96 | return fen.str(); 97 | } 98 | 99 | void Position::set_fen(const std::string& fen_string) { 100 | reset(); 101 | 102 | // Push empty state to state history. 103 | state_history.push({}); 104 | 105 | std::vector fen_tokens = split(fen_string); 106 | 107 | if (fen_tokens.size() < 4) { 108 | throw std::invalid_argument("Fen is missing fields. "); 109 | } 110 | 111 | const std::string position = fen_tokens[0]; 112 | const std::string player = fen_tokens[1]; 113 | const std::string castling = fen_tokens[2]; 114 | const std::string en_passant = fen_tokens[3]; 115 | 116 | const std::string half_move_clock = fen_tokens.size() > 4 ? fen_tokens[4] : "0"; 117 | const std::string full_move_counter = fen_tokens.size() > 4 ? fen_tokens[5] : "1"; 118 | 119 | side = player == "w" ? WHITE : BLACK; 120 | state_history.top().hash ^= ZOBRIST_COLOR[side]; 121 | 122 | Square square = a8; 123 | 124 | for (char ch : position) { 125 | if (isdigit(ch)) square += std::stoi(std::string(1, ch)) * EAST; 126 | else if (ch == '/') square += SOUTH_SOUTH; 127 | else place_piece(piece_from_char(ch), square++); 128 | } 129 | 130 | state_history.top().from_to = PositionState::NO_CASTLING_MASK; 131 | for (char c : castling) { 132 | if (c == 'K') state_history.top().from_to &= ~PositionState::WHITE_OO_BANNED_MASK; 133 | else if (c == 'Q') state_history.top().from_to &= ~PositionState::WHITE_OOO_BANNED_MASK; 134 | else if (c == 'k') state_history.top().from_to &= ~PositionState::BLACK_OO_BANNED_MASK; 135 | else if (c == 'q') state_history.top().from_to &= ~PositionState::BLACK_OOO_BANNED_MASK; 136 | } 137 | state_history.top().hash ^= ZOBRIST_CASTLING_RIGHTS[castling_state(state_history.top().from_to)]; 138 | 139 | if (en_passant.size() > 1) { 140 | auto s = create_square(File(en_passant[0] - 'a'), Rank(en_passant[1] - '1')); 141 | state_history.top().ep_square = s; 142 | } else { 143 | state_history.top().ep_square = NO_SQUARE; 144 | } 145 | state_history.top().hash ^= ZOBRIST_EP_SQUARE[state_history.top().ep_square]; 146 | } 147 | 148 | std::ostream& operator << (std::ostream& os, const Position& p) { 149 | const std::string s = " +---+---+---+---+---+---+---+---+\n"; 150 | const std::string t = " A B C D E F G H\n"; 151 | os << t; 152 | for (i32 i = 56; i >= 0; i -= 8) { 153 | os << s << " " << i / 8 + 1 << " "; 154 | for (i32 j = 0; j < 8; j++) 155 | os << "| " << PIECE_MATCHER[p.board[i + j]] << " "; 156 | os << "| " << i / 8 + 1 << "\n"; 157 | } 158 | os << s; 159 | os << t << "\n"; 160 | 161 | os << "FEN: " << p.fen() << "\n"; 162 | os << "Hash: 0x" << std::hex << p.hash() << std::dec << "\n"; 163 | 164 | return os; 165 | } 166 | 167 | bool Position::has_repetition(Repetition fold) { 168 | int count = fold == THREE_FOLD ? 0 : 1; 169 | const auto hash_hist_size = static_cast(state_history.size()); 170 | const u64 current_hash = hash(); 171 | for (i32 idx = hash_hist_size - 3; 172 | idx >= 0 && idx >= hash_hist_size - fifty_move_rule(); 173 | idx -= 2) { 174 | ZobristHash stack_hash = state_history[idx].hash; 175 | if (stack_hash == current_hash) count += 1; 176 | if (count >= 2) return true; 177 | } 178 | return false; 179 | } 180 | 181 | template 182 | void Position::play(Move move) { 183 | PositionState next_state = {}; 184 | next_state.from_to = state_history.peek().from_to | square_to_bitboard(move.from()) | square_to_bitboard(move.to()); 185 | next_state.captured = piece_at(move.to()); 186 | next_state.hash = state_history.peek().hash; 187 | next_state.fifty_move_rule = state_history.peek().fifty_move_rule + 1; 188 | next_state.ep_square = NO_SQUARE; 189 | 190 | if (move.is_capture() || type_of(piece_at(move.from())) == PAWN) 191 | next_state.fifty_move_rule = 0; 192 | 193 | next_state.hash ^= ZOBRIST_COLOR[~color]; 194 | next_state.hash ^= ZOBRIST_COLOR[color]; 195 | next_state.hash ^= ZOBRIST_CASTLING_RIGHTS[castling_state(state_history.peek().from_to)]; 196 | next_state.hash ^= ZOBRIST_CASTLING_RIGHTS[castling_state(next_state.from_to)]; 197 | next_state.hash ^= ZOBRIST_EP_SQUARE[state_history.peek().ep_square]; 198 | nnue.push_copy(); 199 | state_history.push(next_state); 200 | 201 | if (move.type() & CAPTURE_TYPE && move.type() != ENPASSANT) 202 | remove_piece(move.to()); 203 | 204 | move_piece(move.from(), move.to()); 205 | 206 | MoveType type = move.type(); 207 | switch (type) { 208 | case DOUBLE_PUSH: 209 | state_history.top().ep_square = move.from() + relative_dir(); 210 | break; 211 | case OO: 212 | if constexpr (color == WHITE) move_piece(h1, f1); 213 | else move_piece(h8, f8); 214 | break; 215 | case OOO: 216 | if constexpr (color == WHITE) move_piece(a1, d1); 217 | else move_piece(a8, d8); 218 | break; 219 | case ENPASSANT: 220 | remove_piece(move.to() + relative_dir()); 221 | state_history.top().captured = make_piece<~color, PAWN>(); 222 | break; 223 | case PR_KNIGHT | CAPTURE_TYPE: [[fallthrough]]; 224 | case PR_KNIGHT: 225 | remove_piece(move.to()); 226 | place_piece(make_piece(), move.to()); 227 | break; 228 | case PR_BISHOP | CAPTURE_TYPE: [[fallthrough]]; 229 | case PR_BISHOP: 230 | remove_piece(move.to()); 231 | place_piece(make_piece(), move.to()); 232 | break; 233 | case PR_ROOK | CAPTURE_TYPE: [[fallthrough]]; 234 | case PR_ROOK: 235 | remove_piece(move.to()); 236 | place_piece(make_piece(), move.to()); 237 | break; 238 | case PR_QUEEN | CAPTURE_TYPE: [[fallthrough]]; 239 | case PR_QUEEN: 240 | remove_piece(move.to()); 241 | place_piece(make_piece(), move.to()); 242 | break; 243 | default: break; 244 | } 245 | state_history.top().hash ^= ZOBRIST_EP_SQUARE[state_history.peek().ep_square]; 246 | side = ~side; 247 | } 248 | 249 | template 250 | void Position::undo(Move move) { 251 | nnue.pop(); 252 | PositionState old_state = state_history.pop(); 253 | 254 | move_piece(move.to(), move.from()); 255 | place_piece(old_state.captured, move.to()); 256 | 257 | MoveType type = move.type(); 258 | switch (type) { 259 | case OO: 260 | if constexpr (color == WHITE) move_piece(f1, h1); 261 | else move_piece(f8, h8); 262 | break; 263 | case OOO: 264 | if constexpr (color == WHITE) move_piece(d1, a1); 265 | else move_piece(d8, a8); 266 | break; 267 | case ENPASSANT: 268 | remove_piece(move.to()); 269 | place_piece(make_piece<~color, PAWN>(), move.to() + relative_dir()); 270 | break; 271 | case PR_KNIGHT | CAPTURE_TYPE: 272 | case PR_KNIGHT: 273 | case PR_BISHOP | CAPTURE_TYPE: 274 | case PR_BISHOP: 275 | case PR_ROOK | CAPTURE_TYPE: 276 | case PR_ROOK: 277 | case PR_QUEEN | CAPTURE_TYPE: 278 | case PR_QUEEN: 279 | remove_piece(move.from()); 280 | place_piece(make_piece(), move.from()); 281 | break; 282 | default: break; 283 | } 284 | side = ~side; 285 | } 286 | 287 | template 288 | void Position::play_null() { 289 | PositionState next_state = {}; 290 | next_state.from_to = state_history.peek().from_to; 291 | next_state.captured = NO_PIECE; 292 | next_state.hash = state_history.peek().hash; 293 | next_state.fifty_move_rule = state_history.peek().fifty_move_rule + 1; 294 | next_state.ep_square = NO_SQUARE; 295 | 296 | next_state.hash ^= ZOBRIST_COLOR[~color]; 297 | next_state.hash ^= ZOBRIST_COLOR[color]; 298 | 299 | state_history.push(next_state); 300 | } 301 | 302 | template 303 | void Position::undo_null() { 304 | state_history.pop(); 305 | } 306 | 307 | template void Position::place_piece(Piece piece, Square square); 308 | template void Position::place_piece(Piece piece, Square square); 309 | 310 | template void Position::remove_piece(Square square); 311 | template void Position::remove_piece(Square square); 312 | 313 | template void Position::move_piece(Square from, Square to); 314 | template void Position::move_piece(Square from, Square to); 315 | 316 | template void Position::play(Move move); 317 | template void Position::play(Move move); 318 | 319 | template void Position::undo(Move move); 320 | template void Position::undo(Move move); 321 | 322 | template void Position::play_null(); 323 | template void Position::play_null(); 324 | 325 | template void Position::undo_null(); 326 | template void Position::undo_null(); 327 | -------------------------------------------------------------------------------- /src/board/position.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Alex Tian on 12/2/2022. 3 | // 4 | #pragma once 5 | #include 6 | #include "constants/misc_constants.h" 7 | #include "../types.h" 8 | #include "types/bitboard.h" 9 | #include "constants/misc_constants.h" 10 | #include "../move_gen/types/move.h" 11 | #include "types/board_types.h" 12 | #include "../utils/stack.h" 13 | #include "../move_gen/tables/attack_tables.h" 14 | #include "../evaluation/evaluate.h" 15 | 16 | class Position; 17 | 18 | class PositionState { 19 | friend Position; 20 | private: 21 | static constexpr Bitboard WHITE_OO_BANNED_MASK = 0x90; 22 | static constexpr Bitboard WHITE_OOO_BANNED_MASK = 0x11; 23 | 24 | static constexpr Bitboard BLACK_OO_BANNED_MASK = 0x9000000000000000; 25 | static constexpr Bitboard BLACK_OOO_BANNED_MASK = 0x1100000000000000; 26 | 27 | static constexpr Bitboard NO_CASTLING_MASK = 0x9100000000000091; 28 | 29 | Bitboard from_to{}; 30 | Piece captured{}; 31 | Square ep_square = NO_SQUARE; 32 | u16 fifty_move_rule{}; 33 | public: 34 | ZobristHash hash{}; 35 | 36 | public: 37 | PositionState() = default; 38 | ~PositionState() = default; 39 | }; 40 | 41 | class Position { 42 | private: 43 | Color side = WHITE; 44 | 45 | std::array pieces{}; 46 | std::array board{}; 47 | 48 | static constexpr i16 POSITION_STATE_SIZE = 4096; 49 | Stack state_history{}; 50 | 51 | NNUE nnue{}; 52 | 53 | static constexpr bool ENABLE_HASH_NNUE_UPDATE = true; 54 | static constexpr bool DISABLE_HASH_NNUE_UPDATE = false; 55 | 56 | template 57 | void place_piece(Piece piece, Square square); 58 | 59 | template 60 | void remove_piece(Square square); 61 | 62 | template 63 | void move_piece(Square from, Square to); 64 | 65 | void reset(); 66 | 67 | [[nodiscard]] u8 castling_state(Bitboard from_to) const; 68 | 69 | public: 70 | Position() = default; 71 | explicit Position(const std::string& fen); 72 | 73 | inline void reserve_nnue_capacity() { nnue.m_accumulator_stack.reserve(512); } 74 | 75 | [[nodiscard]] inline u16 fifty_move_rule() const { return state_history.peek().fifty_move_rule; } 76 | [[nodiscard]] inline Square ep_square() const { return state_history.peek().ep_square; } 77 | [[nodiscard]] inline ZobristHash hash() const { return state_history.peek().hash; } 78 | [[nodiscard]] inline Bitboard from_to() const { return state_history.peek().from_to; } 79 | [[nodiscard]] inline Color turn() const { return side; } 80 | 81 | template 82 | [[nodiscard]] i16 evaluate() { return nnue.evaluate(); } 83 | 84 | enum Repetition : i32 { 85 | TWO_FOLD, 86 | THREE_FOLD 87 | }; 88 | [[nodiscard]] bool has_repetition(Repetition fold = TWO_FOLD); 89 | 90 | template 91 | [[nodiscard]] inline bool king_and_oo_rook_not_moved() const { 92 | if constexpr (color == WHITE) return !(from_to() & PositionState::WHITE_OO_BANNED_MASK); 93 | return !(from_to() & PositionState::BLACK_OO_BANNED_MASK); 94 | } 95 | 96 | template 97 | [[nodiscard]] inline bool king_and_ooo_rook_not_moved() const { 98 | if constexpr (color == WHITE) return !(from_to() & PositionState::WHITE_OOO_BANNED_MASK); 99 | return !(from_to() & PositionState::BLACK_OOO_BANNED_MASK); 100 | } 101 | 102 | [[nodiscard]] inline Piece piece_at(Square square) const { return board[square]; } 103 | 104 | template 105 | [[nodiscard]] constexpr Bitboard occupancy() const { return pieces[make_piece()]; } 106 | 107 | template 108 | [[nodiscard]] constexpr Bitboard occupancy() const { return occupancy() | occupancy(); } 109 | 110 | template 111 | [[nodiscard]] constexpr Bitboard occupancy() const { return pieces[piece]; } 112 | 113 | template 114 | [[nodiscard]] constexpr Bitboard occupancy() const { 115 | return pieces[make_piece()] | 116 | pieces[make_piece()] | 117 | pieces[make_piece()] | 118 | pieces[make_piece()] | 119 | pieces[make_piece()] | 120 | pieces[make_piece()]; 121 | } 122 | 123 | inline Bitboard occupancy(Color color) const { 124 | if (color == WHITE) return occupancy(); 125 | return occupancy(); 126 | } 127 | 128 | inline Bitboard occupancy(Color color, PieceType pt) const { 129 | return pieces[make_piece(color, pt)]; 130 | } 131 | 132 | inline Bitboard occupancy(PieceType pt) const { 133 | return pieces[make_piece(WHITE, pt)] | pieces[make_piece(BLACK, pt)]; 134 | } 135 | 136 | [[nodiscard]] inline Bitboard occupancy() const { 137 | return occupancy() | occupancy(); 138 | } 139 | 140 | template 141 | [[nodiscard]] constexpr Bitboard diagonal_sliders() const { 142 | return occupancy() | occupancy(); 143 | } 144 | 145 | template 146 | [[nodiscard]] constexpr Bitboard orthogonal_sliders() const { 147 | return occupancy() | occupancy(); 148 | } 149 | 150 | template 151 | [[nodiscard]] constexpr Bitboard attackers_of(Square s, Bitboard occ) const { 152 | return (tables::attacks(s) & pieces[make_piece()]) | 153 | (tables::attacks(s, occ) & pieces[make_piece()]) | 154 | (tables::attacks(s, occ) & (pieces[make_piece()] | pieces[make_piece()])) | 155 | (tables::attacks(s, occ) & (pieces[make_piece()] | pieces[make_piece()])); 156 | } 157 | 158 | [[nodiscard]] constexpr Bitboard attackers_of(Square s, Bitboard occ) const { 159 | return attackers_of(s, occ) | attackers_of(s, occ); 160 | } 161 | 162 | template [[nodiscard]] inline bool in_check() const { 163 | return attackers_of<~C>(lsb(occupancy()), occupancy() | occupancy()); 164 | } 165 | 166 | void set_fen(const std::string& fen); 167 | [[nodiscard]] std::string fen() const; 168 | friend std::ostream& operator<<(std::ostream& os, const Position& p); 169 | 170 | template 171 | void play(Move move); 172 | 173 | template 174 | void undo(Move move); 175 | 176 | template 177 | void play_null(); 178 | 179 | template 180 | void undo_null(); 181 | }; 182 | -------------------------------------------------------------------------------- /src/board/types/bitboard.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 4/25/23. 3 | // 4 | #include 5 | #include 6 | #include 7 | #include "../../types.h" 8 | #include "bitboard.h" 9 | #include 10 | 11 | void print_bitboard(Bitboard bitboard) { 12 | std::bitset<64> b(bitboard); 13 | std::string str_bitset = b.to_string(); 14 | for (i32 i = 0; i < 64; i += 8) { 15 | std::string x = str_bitset.substr(i, 8); 16 | reverse(x.begin(), x.end()); 17 | for (auto c : x) std::cout << c << " "; 18 | std::cout << std::endl; 19 | } 20 | std::cout << std::endl; 21 | } 22 | 23 | // Compiler specific functions, taken from Stockfish https://github.com/official-stockfish/Stockfish 24 | #if defined(__GNUC__) // GCC, Clang, ICC 25 | 26 | [[nodiscard]] Square lsb(Bitboard bitboard) { 27 | assert(bitboard); 28 | return static_cast(__builtin_ctzll(bitboard)); 29 | } 30 | 31 | [[nodiscard]] Square msb(Bitboard bitboard) { 32 | assert(bitboard); 33 | return static_cast(63 ^ __builtin_clzll(bitboard)); 34 | } 35 | 36 | #elif defined(_MSC_VER) // MSVC 37 | 38 | #ifdef _WIN64 // MSVC, WIN64 39 | #include 40 | Square lsb(u64 b) { 41 | unsigned long idx; 42 | _BitScanForward64(&idx, b); 43 | return (Square)idx; 44 | } 45 | 46 | Square msb(u64 b) { 47 | unsigned long idx; 48 | _BitScanReverse64(&idx, b); 49 | return (Square)idx; 50 | } 51 | 52 | #else // MSVC, WIN32 53 | #include 54 | Square lsb(u64 b) { 55 | unsigned long idx; 56 | 57 | if (b & 0xffffffff) { 58 | _BitScanForward(&idx, int32_t(b)); 59 | return Square(idx); 60 | } 61 | else { 62 | _BitScanForward(&idx, int32_t(b >> 32)); 63 | return Square(idx + 32); 64 | } 65 | } 66 | 67 | Square msb(u64 b) { 68 | unsigned long idx; 69 | 70 | if (b >> 32) { 71 | _BitScanReverse(&idx, int32_t(b >> 32)); 72 | return Square(idx + 32); 73 | } 74 | else { 75 | _BitScanReverse(&idx, int32_t(b)); 76 | return Square(idx); 77 | } 78 | } 79 | 80 | #endif 81 | 82 | #else // Compiler is neither GCC nor MSVC compatible 83 | 84 | #error "Compiler not supported." 85 | 86 | #endif 87 | 88 | [[nodiscard]] uint32_t pop_count(Bitboard bitboard) { 89 | #if defined(_MSC_VER) || defined(__INTEL_COMPILER) 90 | 91 | return (uint8_t)_mm_popcnt_u64(bitboard); 92 | 93 | #else // Assumed gcc or compatible compiler 94 | 95 | return __builtin_popcountll(bitboard); 96 | 97 | #endif 98 | } 99 | 100 | [[nodiscard]] Square pop_lsb(Bitboard& bitboard) { 101 | Square s = lsb(bitboard); 102 | bitboard &= bitboard - 1; // compiler optimizes this to _blsr_u64 103 | return static_cast(s); 104 | } 105 | -------------------------------------------------------------------------------- /src/board/types/bitboard.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "../../types.h" 4 | #include "../constants/board_masks.h" 5 | #include "board_types.h" 6 | #include "square.h" 7 | #include 8 | 9 | void print_bitboard(Bitboard bitboard); 10 | 11 | [[nodiscard]] constexpr Bitboard square_to_bitboard(Square square) { 12 | return 1ULL << square; 13 | } 14 | 15 | [[nodiscard]] constexpr Bitboard shift(Direction D, Bitboard b) { 16 | if (D == NORTH) return b << 8; 17 | else if (D == SOUTH) return b >> 8; 18 | else if (D == NORTH + NORTH) return b << 16; 19 | else if (D == SOUTH + SOUTH) return b >> 16; 20 | else if (D == EAST) return (b & ~MASK_FILE[HFILE]) << 1; 21 | else if (D == WEST) return (b & ~MASK_FILE[AFILE]) >> 1; 22 | else if (D == NORTH_EAST) return (b & ~MASK_FILE[HFILE]) << 9; 23 | else if (D == NORTH_WEST) return (b & ~MASK_FILE[AFILE]) << 7; 24 | else if (D == SOUTH_EAST) return (b & ~MASK_FILE[HFILE]) >> 7; 25 | else if (D == SOUTH_WEST) return (b & ~MASK_FILE[AFILE]) >> 9; 26 | return 0; 27 | } 28 | 29 | template 30 | [[nodiscard]] constexpr Bitboard shift(Bitboard b) { 31 | return shift(D, b); 32 | } 33 | 34 | template 35 | [[nodiscard]] constexpr Bitboard shift_relative(Bitboard b) { 36 | return shift()>(b); 37 | } 38 | 39 | [[nodiscard]] Square lsb(Bitboard bitboard); 40 | [[nodiscard]] Square msb(Bitboard bitboard); 41 | 42 | [[nodiscard]] uint32_t pop_count(Bitboard bitboard); 43 | [[nodiscard]] Square pop_lsb(Bitboard& bitboard); -------------------------------------------------------------------------------- /src/board/types/board_types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../types.h" 3 | 4 | using ZobristHash = u64; 5 | using Bitboard = u64; 6 | 7 | constexpr i32 NCOLORS = 2; 8 | class Color { 9 | public: 10 | constexpr explicit Color(u32 v) : m_value{v} {} 11 | 12 | [[nodiscard]] constexpr operator u32() const { return m_value; } 13 | [[nodiscard]] constexpr bool operator==(const Color&) const = default; 14 | [[nodiscard]] constexpr Color operator~() const { return Color{m_value ^ 1}; } 15 | u32 m_value; 16 | }; 17 | 18 | constexpr Color WHITE{0}; 19 | constexpr Color BLACK{1}; 20 | 21 | constexpr i32 NDIRS = 8; 22 | enum Direction : i32 { 23 | NORTH = 8, NORTH_EAST = 9, EAST = 1, SOUTH_EAST = -7, 24 | SOUTH = -8, SOUTH_WEST = -9, WEST = -1, NORTH_WEST = 7, 25 | NORTH_NORTH = 16, SOUTH_SOUTH = -16 26 | }; 27 | 28 | template 29 | consteval Direction relative_dir() { 30 | if constexpr (C == WHITE) return D; 31 | return Direction(-D); 32 | } 33 | 34 | constexpr i32 NFILES = 8; 35 | using File = i32; 36 | 37 | constexpr File AFILE = 0; 38 | constexpr File BFILE = 1; 39 | constexpr File CFILE = 2; 40 | constexpr File DFILE = 3; 41 | constexpr File EFILE = 4; 42 | constexpr File FFILE = 5; 43 | constexpr File GFILE = 6; 44 | constexpr File HFILE = 7; 45 | 46 | constexpr i32 NRANKS = 8; 47 | using Rank = i32; 48 | 49 | constexpr Rank RANK1 = 0; 50 | constexpr Rank RANK2 = 1; 51 | constexpr Rank RANK3 = 2; 52 | constexpr Rank RANK4 = 3; 53 | constexpr Rank RANK5 = 4; 54 | constexpr Rank RANK6 = 5; 55 | constexpr Rank RANK7 = 6; 56 | constexpr Rank RANK8 = 7; 57 | 58 | template 59 | constexpr Rank relative_rank(Rank r) { 60 | if constexpr (color == WHITE) return r; 61 | return RANK8 - r; 62 | } 63 | 64 | constexpr i32 NCASTLING_RIGHTS = 4; 65 | using CastleRight = i32; 66 | 67 | constexpr CastleRight BLACK_OOO = 0b0001; 68 | constexpr CastleRight BLACK_OO = 0b0010; 69 | constexpr CastleRight WHITE_OOO = 0b0100; 70 | constexpr CastleRight WHITE_OO = 0b1000; 71 | -------------------------------------------------------------------------------- /src/board/types/piece.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../types.h" 4 | #include "board_types.h" 5 | 6 | constexpr u32 NPIECE_TYPES = 7; 7 | class PieceType { 8 | public: 9 | constexpr PieceType() : m_value{0} {} 10 | constexpr explicit PieceType(u32 v) : m_value{v} {} 11 | 12 | [[nodiscard]] constexpr operator u32() const { return m_value; } 13 | [[nodiscard]] constexpr bool operator==(const PieceType&) const = default; 14 | u32 m_value; 15 | }; 16 | constexpr PieceType PAWN{0}; 17 | constexpr PieceType KNIGHT{1}; 18 | constexpr PieceType BISHOP{2}; 19 | constexpr PieceType ROOK{3}; 20 | constexpr PieceType QUEEN{4}; 21 | constexpr PieceType KING{5}; 22 | constexpr PieceType NO_PIECE_TYPE{6}; 23 | 24 | constexpr i8 NPIECES = 15; 25 | class Piece { 26 | public: 27 | constexpr Piece() : m_value{0} {} 28 | constexpr explicit Piece(u32 v) : m_value{v} {} 29 | 30 | [[nodiscard]] constexpr operator u32() const { return m_value; } 31 | [[nodiscard]] constexpr bool operator==(const Piece&) const = default; 32 | u32 m_value; 33 | }; 34 | constexpr Piece WHITE_PAWN{0}; 35 | constexpr Piece WHITE_KNIGHT{1}; 36 | constexpr Piece WHITE_BISHOP{2}; 37 | constexpr Piece WHITE_ROOK{3}; 38 | constexpr Piece WHITE_QUEEN{4}; 39 | constexpr Piece WHITE_KING{5}; 40 | constexpr Piece BLACK_PAWN{8}; 41 | constexpr Piece BLACK_KNIGHT{9}; 42 | constexpr Piece BLACK_BISHOP{10}; 43 | constexpr Piece BLACK_ROOK{11}; 44 | constexpr Piece BLACK_QUEEN{12}; 45 | constexpr Piece BLACK_KING{13}; 46 | constexpr Piece NO_PIECE{14}; 47 | 48 | template 49 | consteval Piece make_piece() { 50 | return static_cast((color << 3) | piece_type); 51 | } 52 | 53 | inline Piece make_piece(Color color, PieceType piece_type) { 54 | return static_cast((color << 3) | piece_type); 55 | } 56 | 57 | template 58 | consteval PieceType type_of() { 59 | return static_cast(piece & 0b000111); 60 | } 61 | 62 | inline PieceType type_of(Piece piece) { 63 | return static_cast(piece & 0b000111); 64 | } 65 | 66 | constexpr Color color_of(Piece pc) { 67 | return Color((pc & 0b1000) >> 3); 68 | } 69 | 70 | inline Piece piece_from_char(char c) { 71 | switch (c) { 72 | case 'P': return WHITE_PAWN; 73 | case 'N': return WHITE_KNIGHT; 74 | case 'B': return WHITE_BISHOP; 75 | case 'R': return WHITE_ROOK; 76 | case 'Q': return WHITE_QUEEN; 77 | case 'K': return WHITE_KING; 78 | case 'p': return BLACK_PAWN; 79 | case 'n': return BLACK_KNIGHT; 80 | case 'b': return BLACK_BISHOP; 81 | case 'r': return BLACK_ROOK; 82 | case 'q': return BLACK_QUEEN; 83 | case 'k': return BLACK_KING; 84 | default: return NO_PIECE; 85 | } 86 | } -------------------------------------------------------------------------------- /src/board/types/square.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../types.h" 4 | #include "board_types.h" 5 | 6 | constexpr i32 NSQUARES = 64; 7 | 8 | enum Square : i32 { 9 | a1, b1, c1, d1, e1, f1, g1, h1, 10 | a2, b2, c2, d2, e2, f2, g2, h2, 11 | a3, b3, c3, d3, e3, f3, g3, h3, 12 | a4, b4, c4, d4, e4, f4, g4, h4, 13 | a5, b5, c5, d5, e5, f5, g5, h5, 14 | a6, b6, c6, d6, e6, f6, g6, h6, 15 | a7, b7, c7, d7, e7, f7, g7, h7, 16 | a8, b8, c8, d8, e8, f8, g8, h8, 17 | NO_SQUARE 18 | }; 19 | 20 | inline constexpr Square operator ++(Square& orig, i32) { 21 | Square r_val = orig; 22 | orig = static_cast(orig + 1); 23 | return r_val; 24 | } 25 | 26 | constexpr Square operator +(Square s, Direction d) { 27 | return Square(static_cast(s) + static_cast(d)); 28 | } 29 | 30 | constexpr Square operator -(Square s, Direction d) { 31 | return Square(static_cast(s) - static_cast(d)); 32 | } 33 | 34 | inline Square& operator +=(Square& s, Direction d) { 35 | return s = s + d; 36 | } 37 | 38 | inline Square& operator +=(Square& s, i32 i) { 39 | return s = static_cast(s + i); 40 | } 41 | 42 | inline Square& operator -=(Square& s, Direction d) { 43 | return s = s - d; 44 | } 45 | 46 | inline Square flip(Square s) { 47 | return static_cast(s ^ 0b111000); 48 | } 49 | 50 | constexpr Rank rank_of(Square s) { 51 | return Rank(s >> 3); 52 | } 53 | 54 | constexpr File file_of(Square s) { 55 | return File(s & 0b111); 56 | } 57 | 58 | constexpr i32 diagonal_of(Square s) { 59 | return 7 + rank_of(s) - file_of(s); 60 | } 61 | 62 | constexpr i32 anti_diagonal_of(Square s) { 63 | return rank_of(s) + file_of(s); 64 | } 65 | 66 | constexpr Square create_square(File f, Rank r) { 67 | return Square(r << 3 | f); 68 | } 69 | 70 | inline const string SQ_TO_STRING[NSQUARES + 1] = { 71 | "a1", "b1", "c1", "d1", "e1", "f1", "g1", "h1", 72 | "a2", "b2", "c2", "d2", "e2", "f2", "g2", "h2", 73 | "a3", "b3", "c3", "d3", "e3", "f3", "g3", "h3", 74 | "a4", "b4", "c4", "d4", "e4", "f4", "g4", "h4", 75 | "a5", "b5", "c5", "d5", "e5", "f5", "g5", "h5", 76 | "a6", "b6", "c6", "d6", "e6", "f6", "g6", "h6", 77 | "a7", "b7", "c7", "d7", "e7", "f7", "g7", "h7", 78 | "a8", "b8", "c8", "d8", "e8", "f8", "g8", "h8", 79 | "None" 80 | }; 81 | -------------------------------------------------------------------------------- /src/engine.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 3/20/23. 3 | // 4 | #include "engine.h" 5 | #include "move_search/move_ordering/move_ordering.h" 6 | #include "move_search/tables/lmr_table.h" 7 | 8 | void initialize_engine() { 9 | init_lmr_table(); 10 | t_table.reset_table(); 11 | } -------------------------------------------------------------------------------- /src/engine.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void initialize_engine(); 4 | -------------------------------------------------------------------------------- /src/evaluation/evaluate.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 7/8/23. 3 | // 4 | 5 | #include "evaluate.h" 6 | #ifdef _MSC_VER 7 | #undef _MSC_VER 8 | #endif 9 | #define INCBIN_SILENCE_BITCODE_WARNING 10 | #include "../3rd_party/incbin.h" 11 | INCBIN(nnue, "src/evaluation/netM006.nnue"); 12 | const NNUEParams &nnue_params = *reinterpret_cast(gnnueData); 13 | 14 | std::pair NNUE::index_of(Piece piece, Square square) { 15 | constexpr usize color_stride = NSQUARES * 6; 16 | constexpr usize piece_stride = NSQUARES; 17 | 18 | const auto base = type_of(piece); 19 | const auto color = color_of(piece); 20 | 21 | const auto white_idx = color * color_stride + base * piece_stride + static_cast(square); 22 | const auto black_idx = !color * color_stride + base * piece_stride + static_cast(flip(square)); 23 | 24 | return {white_idx, black_idx}; 25 | } 26 | 27 | i32 NNUE::screlu_flatten_norm(const std::array &us, 28 | const std::array &them, 29 | const std::array &weights) { 30 | i32 sum = 0; 31 | 32 | for (usize i = 0; i < HIDDEN_LAYER1_SIZE; ++i) { 33 | sum += screlu(us[i]) * weights[i]; 34 | sum += screlu(them[i]) * weights[HIDDEN_LAYER1_SIZE + i]; 35 | } 36 | 37 | return sum / QA; 38 | } 39 | 40 | i32 NNUE::screlu_flatten_simd(const std::array &us, 41 | const std::array &them, 42 | const std::array &weights) { 43 | auto sum = veci32_zero(); 44 | 45 | for (usize i = 0; i < HIDDEN_LAYER1_SIZE; i += REGISTER_WIDTH) { 46 | auto simd_input = loadi16_register(&us[i]); 47 | simd_input = veci16_clamp(simd_input, 0, QA); 48 | simd_input = veci16_mul(simd_input, simd_input); 49 | auto simd_weigh = loadi16_register(&weights[i]); 50 | auto product = veci16_mul_pair_accumi32(simd_input, simd_weigh); 51 | sum = veci32_add(sum, product); 52 | 53 | simd_input = loadi16_register(&them[i]); 54 | simd_input = veci16_clamp(simd_input, 0, QA); 55 | simd_input = veci16_mul(simd_input, simd_input); 56 | simd_weigh = loadi16_register(&weights[i + HIDDEN_LAYER1_SIZE]); 57 | product = veci16_mul_pair_accumi32(simd_input, simd_weigh); 58 | sum = veci32_add(sum, product); 59 | } 60 | 61 | return veci32_horizontal_add(sum) / QA; 62 | } 63 | 64 | i32 NNUE::screlu_flatten(const std::array &us, 65 | const std::array &them, 66 | const std::array &weights) { 67 | if constexpr (arch_type == SimdArchType::NONE) { 68 | return screlu_flatten_norm(us, them, weights); 69 | } 70 | return screlu_flatten_simd(us, them, weights); 71 | } 72 | -------------------------------------------------------------------------------- /src/evaluation/evaluate.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "../board/types/square.h" 11 | #include "../board/types/piece.h" 12 | #include "simd.h" 13 | 14 | constexpr usize INPUT_LAYER_SIZE = NSQUARES * 12; 15 | constexpr usize HIDDEN_LAYER1_SIZE = 768; 16 | 17 | constexpr i32 SCALE = 400; 18 | 19 | constexpr i32 QA = 181; 20 | constexpr i32 QB = 64; 21 | 22 | constexpr i32 QAB = QA * QB; 23 | constexpr bool ADD_TO_SQUARE = true; 24 | constexpr bool RM_FROM_SQUARE = false; 25 | 26 | // 64 byte alignment for avx512 SIMD instruction 27 | struct alignas(64) NNUEParams { 28 | std::array feature_weights; 29 | std::array feature_bias; 30 | std::array output_weights; 31 | i16 output_bias; 32 | }; 33 | 34 | extern const NNUEParams &nnue_params; 35 | 36 | struct alignas(64) LazyHiddenLayer { 37 | std::array white; 38 | std::array black; 39 | 40 | void init_biases(std::span bias) { 41 | std::memcpy(white.data(), bias.data(), bias.size_bytes()); 42 | std::memcpy(black.data(), bias.data(), bias.size_bytes()); 43 | } 44 | }; 45 | 46 | constexpr i32 screlu(i16 x) { 47 | auto clamped = std::clamp(static_cast(x), 0, QA); 48 | return clamped * clamped; 49 | } 50 | 51 | struct NNUE { 52 | std::vector m_accumulator_stack{}; 53 | 54 | explicit NNUE() { 55 | m_accumulator_stack.reserve(512); 56 | } 57 | 58 | ~NNUE() = default; 59 | 60 | std::pair index_of(Piece piece, Square square); 61 | i32 screlu_flatten(const std::array &us, 62 | const std::array &them, 63 | const std::array &weights); 64 | 65 | i32 screlu_flatten_simd(const std::array &us, 66 | const std::array &them, 67 | const std::array &weights); 68 | 69 | i32 screlu_flatten_norm(const std::array &us, 70 | const std::array &them, 71 | const std::array &weights); 72 | 73 | void reset() { 74 | m_accumulator_stack.clear(); 75 | m_accumulator_stack.push_back({}); 76 | m_accumulator_stack.back().init_biases(nnue_params.feature_bias); 77 | } 78 | 79 | void push_copy() { 80 | auto& m_curr = m_accumulator_stack.back(); 81 | assert(m_accumulator_stack.size() < m_accumulator_stack.capacity()); 82 | m_accumulator_stack.push_back(m_curr); 83 | } 84 | 85 | void pop() { 86 | m_accumulator_stack.pop_back(); 87 | assert(!m_accumulator_stack.empty()); 88 | } 89 | 90 | template 91 | void update_feature(Piece piece, Square square) { 92 | const auto [white_idx, black_idx] = index_of(piece, square); 93 | 94 | auto& m_curr = m_accumulator_stack.back(); 95 | if constexpr (add_to_square) { 96 | add_feature(m_curr.white, nnue_params.feature_weights, white_idx * HIDDEN_LAYER1_SIZE); 97 | add_feature(m_curr.black, nnue_params.feature_weights, black_idx * HIDDEN_LAYER1_SIZE); 98 | } else { 99 | remove_feature(m_curr.white, nnue_params.feature_weights, white_idx * HIDDEN_LAYER1_SIZE); 100 | remove_feature(m_curr.black, nnue_params.feature_weights, black_idx * HIDDEN_LAYER1_SIZE); 101 | } 102 | } 103 | 104 | inline void add_feature(std::array &input, 105 | const std::array &weights, 106 | usize offset) { 107 | if constexpr (arch_type == SimdArchType::NONE) { 108 | for (usize i = 0; i < input.size(); ++i) { 109 | input[i] += weights[offset + i]; 110 | } 111 | } else { 112 | for (usize i = 0; i < input.size(); i += REGISTER_WIDTH) { 113 | auto simd_reg_input = loadi16_register(&input[i]); 114 | auto simd_reg_weigh = loadi16_register(&weights[offset + i]); 115 | store_veci16(&input[i], veci16_add(simd_reg_input, simd_reg_weigh)); 116 | } 117 | } 118 | } 119 | 120 | inline void remove_feature(std::array &input, 121 | const std::array &weights, 122 | usize offset) { 123 | if constexpr (arch_type == SimdArchType::NONE) { 124 | for (usize i = 0; i < input.size(); ++i) { 125 | input[i] -= weights[offset + i]; 126 | } 127 | } else { 128 | for (usize i = 0; i < input.size(); i += REGISTER_WIDTH) { 129 | auto simd_reg_input = loadi16_register(&input[i]); 130 | auto simd_reg_weigh = loadi16_register(&weights[offset + i]); 131 | store_veci16(&input[i], veci16_sub(simd_reg_input, simd_reg_weigh)); 132 | } 133 | } 134 | } 135 | 136 | template 137 | i32 evaluate() { 138 | auto output = 0; 139 | const auto& m_curr = m_accumulator_stack.back(); 140 | if constexpr (color == WHITE) { 141 | output = screlu_flatten(m_curr.white, m_curr.black, nnue_params.output_weights); 142 | } else { 143 | output = screlu_flatten(m_curr.black, m_curr.white, nnue_params.output_weights); 144 | } 145 | return (output + nnue_params.output_bias) * SCALE / QAB; 146 | } 147 | }; 148 | -------------------------------------------------------------------------------- /src/evaluation/netM006.nnue: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archishou/MidnightChessEngine/ba6dcaecdb8901346c35264b08946ec282819e35/src/evaluation/netM006.nnue -------------------------------------------------------------------------------- /src/evaluation/simd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | enum class SimdArchType { 4 | ARM_NEON, 5 | AVX2, 6 | AVX512, 7 | NONE 8 | }; 9 | 10 | #if defined(__AVX512F__) 11 | #include 12 | static constexpr auto arch_type = SimdArchType::AVX512; 13 | static constexpr usize REGISTER_WIDTH = 32; 14 | 15 | #elif defined(__AVX2__) 16 | #include 17 | static constexpr auto arch_type = SimdArchType::AVX2; 18 | static constexpr usize REGISTER_WIDTH = 16; 19 | 20 | #elif defined(__ARM_NEON) 21 | #include 22 | static constexpr auto arch_type = SimdArchType::NONE; 23 | static constexpr usize REGISTER_WIDTH = 8; 24 | 25 | #else 26 | static constexpr auto arch_type = SimdArchType::NONE; 27 | static constexpr usize REGISTER_WIDTH = 0; 28 | #endif 29 | 30 | auto inline loadi16_register([[maybe_unused]] auto value) { 31 | #if defined(__AVX512F__) 32 | return _mm512_loadu_si512(reinterpret_cast(value)); 33 | #elif defined(__AVX2__) 34 | return _mm256_loadu_si256(reinterpret_cast(value)); 35 | #elif defined(__ARM_NEON) 36 | return vld1q_s16(value); 37 | #else 38 | return 0; 39 | #endif 40 | } 41 | 42 | auto inline store_veci16([[maybe_unused]] auto value, [[maybe_unused]] auto simd_register) { 43 | #if defined(__AVX512F__) 44 | _mm512_store_si512((__m512i*)value, simd_register); 45 | #elif defined(__AVX2__) 46 | _mm256_store_si256((__m256i*)value, simd_register); 47 | #elif defined(__ARM_NEON) 48 | vst1q_s16(value, simd_register); 49 | #endif 50 | } 51 | 52 | auto inline veci32_zero() { 53 | #if defined(__AVX512F__) 54 | return _mm512_setzero_si512(); 55 | #elif defined(__AVX2__) 56 | return _mm256_setzero_si256(); 57 | #elif defined(__ARM_NEON) 58 | return vdupq_n_s32(0); 59 | #else 60 | return 0; 61 | #endif 62 | } 63 | 64 | auto inline veci16_add([[maybe_unused]] auto vec1, [[maybe_unused]] auto vec2) { 65 | #if defined(__AVX512F__) 66 | return _mm512_add_epi16(vec1, vec2); 67 | #elif defined(__AVX2__) 68 | return _mm256_add_epi16(vec1, vec2); 69 | #elif defined(__ARM_NEON) 70 | return vaddq_s16(vec1, vec2); 71 | #else 72 | return 0; 73 | #endif 74 | } 75 | 76 | auto inline veci32_add([[maybe_unused]] auto vec1, [[maybe_unused]] auto vec2) { 77 | #if defined(__AVX512F__) 78 | return _mm512_add_epi32(vec1, vec2); 79 | #elif defined(__AVX2__) 80 | return _mm256_add_epi32(vec1, vec2); 81 | #elif defined(__ARM_NEON) 82 | return vaddq_s32(vec1, vec2); 83 | #else 84 | return 0; 85 | #endif 86 | } 87 | 88 | auto inline veci16_sub([[maybe_unused]] auto vec1, [[maybe_unused]] auto vec2) { 89 | #if defined(__AVX512F__) 90 | return _mm512_sub_epi16(vec1, vec2); 91 | #elif defined(__AVX2__) 92 | return _mm256_sub_epi16(vec1, vec2); 93 | #elif defined(__ARM_NEON) 94 | return vsubq_s16(vec1, vec2); 95 | #else 96 | return 0; 97 | #endif 98 | } 99 | 100 | auto inline veci16_mul([[maybe_unused]] auto vec1, [[maybe_unused]] auto vec2) { 101 | #if defined(__AVX512F__) 102 | return _mm512_mullo_epi16(vec1, vec2); 103 | #elif defined(__AVX2__) 104 | return _mm256_mullo_epi16(vec1, vec2); 105 | #elif defined(__ARM_NEON) 106 | return vmulq_s16(vec1, vec2); 107 | #else 108 | return 0; 109 | #endif 110 | } 111 | 112 | auto inline veci16_clamp([[maybe_unused]] auto vec, [[maybe_unused]] auto min, [[maybe_unused]] auto max) { 113 | #if defined(__AVX512F__) 114 | vec = _mm512_min_epi16(_mm512_set1_epi16(max), vec); 115 | vec = _mm512_max_epi16(_mm512_set1_epi16(min), vec); 116 | return vec; 117 | #elif defined(__AVX2__) 118 | vec = _mm256_min_epi16(_mm256_set1_epi16(max), vec); 119 | vec = _mm256_max_epi16(_mm256_set1_epi16(min), vec); 120 | return vec; 121 | #elif defined(__ARM_NEON) 122 | vec = vminq_s16(vdupq_n_s16(max), vec); 123 | vec = vmaxq_s16(vdupq_n_s16(min), vec); 124 | return vec; 125 | #else 126 | return 0; 127 | #endif 128 | } 129 | 130 | // Adds 131 | auto inline veci32_horizontal_add([[maybe_unused]] auto vec) { 132 | #if defined(__AVX512F__) 133 | auto low = _mm512_castsi512_si256(vec); 134 | auto high = _mm512_extracti32x8_epi32(vec, 1); 135 | auto sum8 = _mm256_add_epi32(low, high); 136 | auto sum4 = _mm256_hadd_epi32(sum8, sum8); // 4 numbers 137 | auto sum2 = _mm256_hadd_epi32(sum4, sum4); // 2 numbers 138 | 139 | auto lower_number = _mm256_castsi256_si128(sum2); 140 | auto higher_number = _mm256_extractf128_si256(sum2, 1); 141 | auto result = _mm_add_epi32(lower_number, higher_number); 142 | return _mm_extract_epi32(result, 0); 143 | #elif defined(__AVX2__) 144 | auto sum4 = _mm256_hadd_epi32(vec, vec); // 4 numbers 145 | auto sum2 = _mm256_hadd_epi32(sum4, sum4); // 2 numbers 146 | 147 | auto lower_number = _mm256_castsi256_si128(sum2); 148 | auto higher_number = _mm256_extractf128_si256(sum2, 1); 149 | auto result = _mm_add_epi32(lower_number, higher_number); 150 | return _mm_extract_epi32(result, 0); 151 | #elif defined(__ARM_NEON) 152 | return vaddvq_s32(vec); 153 | #else 154 | return 0; 155 | #endif 156 | } 157 | 158 | auto inline veci16_mul_pair_accumi32([[maybe_unused]] auto vec1, 159 | [[maybe_unused]] auto vec2) { 160 | #if defined(__AVX512F__) 161 | return _mm512_madd_epi16(vec1, vec2); 162 | #elif defined(__AVX2__) 163 | return _mm256_madd_epi16(vec1, vec2); 164 | #elif defined(__ARM_NEON) 165 | auto mul_low = vmull_s16(vget_low_s16(vec1), vget_low_s16(vec2)); 166 | auto mul_high = vmull_s16(vget_high_s16(vec1), vget_high_s16(vec2)); 167 | return veci32_add(mul_low, mul_high); 168 | #else 169 | return 0; 170 | #endif 171 | } -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "uci_interpreter/datagen.h" 2 | #include "uci_interpreter/uci_interpreter.h" 3 | 4 | int main(i32 argc, char *argv[]) { 5 | initialize_engine(); 6 | if (argc > 1) { 7 | if (string{argv[1]} == "bench") { 8 | bench(); 9 | } else if (string{argv[1]} == "datagen") { 10 | datagen(std::stoi(string{argv[2]}), string{argv[3]}); 11 | } 12 | return 0; 13 | } 14 | read_uci(); 15 | } 16 | -------------------------------------------------------------------------------- /src/move_gen/move_gen_masks.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../board/types/bitboard.h" 4 | #include "../board/types/board_types.h" 5 | 6 | constexpr Bitboard WHITE_OO_BLOCKERS_MASK = 0x60; 7 | constexpr Bitboard WHITE_OOO_DANGER_MASK = 0xC; 8 | constexpr Bitboard WHITE_OOO_BLOCKERS_MASK = 0xE; 9 | 10 | constexpr Bitboard BLACK_OO_BLOCKERS_MASK = 0x6000000000000000; 11 | constexpr Bitboard BLACK_OOO_DANGER_MASK = 0xC00000000000000; 12 | constexpr Bitboard BLACK_OOO_BLOCKERS_MASK = 0xE00000000000000; 13 | 14 | template 15 | Bitboard oo_blockers_mask() { 16 | if constexpr (C == WHITE) return WHITE_OO_BLOCKERS_MASK; 17 | return BLACK_OO_BLOCKERS_MASK; 18 | } 19 | 20 | template 21 | Bitboard ooo_danger_mask() { 22 | if constexpr (C == WHITE) return WHITE_OOO_DANGER_MASK; 23 | return BLACK_OOO_DANGER_MASK; 24 | } 25 | 26 | template 27 | Bitboard ooo_blockers_mask() { 28 | if constexpr (C == WHITE) return WHITE_OOO_BLOCKERS_MASK; 29 | return BLACK_OOO_BLOCKERS_MASK; 30 | } -------------------------------------------------------------------------------- /src/move_gen/tables/attack_tables.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #pragma once 3 | 4 | #include "../types/types.h" 5 | #include "../../board/types/bitboard.h" 6 | #include "../../board/types/piece.h" 7 | #include "../../board/constants/zobrist_constants.h" 8 | 9 | namespace tables { 10 | constexpr usize ROOK_TABLE_SIZE = 4096; 11 | constexpr usize BISHOP_TABLE_SIZE = 512; 12 | 13 | constexpr array ROOK_SHIFTS = { 14 | 52, 53, 53, 53, 53, 53, 53, 52, 15 | 53, 54, 54, 54, 54, 54, 54, 53, 16 | 53, 54, 54, 54, 54, 54, 54, 53, 17 | 53, 54, 54, 54, 54, 54, 54, 53, 18 | 53, 54, 54, 54, 54, 54, 54, 53, 19 | 53, 54, 54, 54, 54, 54, 54, 53, 20 | 53, 54, 54, 54, 54, 54, 54, 53, 21 | 52, 53, 53, 53, 53, 53, 53, 52, 22 | }; 23 | 24 | constexpr array BISHOP_SHIFTS = { 25 | 58, 59, 59, 59, 59, 59, 59, 58, 26 | 59, 59, 59, 59, 59, 59, 59, 59, 27 | 59, 59, 57, 57, 57, 57, 59, 59, 28 | 59, 59, 57, 55, 55, 57, 59, 59, 29 | 59, 59, 57, 55, 55, 57, 59, 59, 30 | 59, 59, 57, 57, 57, 57, 59, 59, 31 | 59, 59, 59, 59, 59, 59, 59, 59, 32 | 58, 59, 59, 59, 59, 59, 59, 58, 33 | }; 34 | 35 | constexpr array KING_ATTACKS = { 36 | 0x302, 0x705, 0xe0a, 0x1c14, 37 | 0x3828, 0x7050, 0xe0a0, 0xc040, 38 | 0x30203, 0x70507, 0xe0a0e, 0x1c141c, 39 | 0x382838, 0x705070, 0xe0a0e0, 0xc040c0, 40 | 0x3020300, 0x7050700, 0xe0a0e00, 0x1c141c00, 41 | 0x38283800, 0x70507000, 0xe0a0e000, 0xc040c000, 42 | 0x302030000, 0x705070000, 0xe0a0e0000, 0x1c141c0000, 43 | 0x3828380000, 0x7050700000, 0xe0a0e00000, 0xc040c00000, 44 | 0x30203000000, 0x70507000000, 0xe0a0e000000, 0x1c141c000000, 45 | 0x382838000000, 0x705070000000, 0xe0a0e0000000, 0xc040c0000000, 46 | 0x3020300000000, 0x7050700000000, 0xe0a0e00000000, 0x1c141c00000000, 47 | 0x38283800000000, 0x70507000000000, 0xe0a0e000000000, 0xc040c000000000, 48 | 0x302030000000000, 0x705070000000000, 0xe0a0e0000000000, 0x1c141c0000000000, 49 | 0x3828380000000000, 0x7050700000000000, 0xe0a0e00000000000, 0xc040c00000000000, 50 | 0x203000000000000, 0x507000000000000, 0xa0e000000000000, 0x141c000000000000, 51 | 0x2838000000000000, 0x5070000000000000, 0xa0e0000000000000, 0x40c0000000000000, 52 | }; 53 | 54 | constexpr array KNIGHT_ATTACKS = { 55 | 0x20400, 0x50800, 0xa1100, 0x142200, 56 | 0x284400, 0x508800, 0xa01000, 0x402000, 57 | 0x2040004, 0x5080008, 0xa110011, 0x14220022, 58 | 0x28440044, 0x50880088, 0xa0100010, 0x40200020, 59 | 0x204000402, 0x508000805, 0xa1100110a, 0x1422002214, 60 | 0x2844004428, 0x5088008850, 0xa0100010a0, 0x4020002040, 61 | 0x20400040200, 0x50800080500, 0xa1100110a00, 0x142200221400, 62 | 0x284400442800, 0x508800885000, 0xa0100010a000, 0x402000204000, 63 | 0x2040004020000, 0x5080008050000, 0xa1100110a0000, 0x14220022140000, 64 | 0x28440044280000, 0x50880088500000, 0xa0100010a00000, 0x40200020400000, 65 | 0x204000402000000, 0x508000805000000, 0xa1100110a000000, 0x1422002214000000, 66 | 0x2844004428000000, 0x5088008850000000, 0xa0100010a0000000, 0x4020002040000000, 67 | 0x400040200000000, 0x800080500000000, 0x1100110a00000000, 0x2200221400000000, 68 | 0x4400442800000000, 0x8800885000000000, 0x100010a000000000, 0x2000204000000000, 69 | 0x4020000000000, 0x8050000000000, 0x110a0000000000, 0x22140000000000, 70 | 0x44280000000000, 0x0088500000000000, 0x0010a00000000000, 0x20400000000000 71 | }; 72 | 73 | constexpr array WHITE_PAWN_ATTACKS = { 74 | 0x200, 0x500, 0xa00, 0x1400, 75 | 0x2800, 0x5000, 0xa000, 0x4000, 76 | 0x20000, 0x50000, 0xa0000, 0x140000, 77 | 0x280000, 0x500000, 0xa00000, 0x400000, 78 | 0x2000000, 0x5000000, 0xa000000, 0x14000000, 79 | 0x28000000, 0x50000000, 0xa0000000, 0x40000000, 80 | 0x200000000, 0x500000000, 0xa00000000, 0x1400000000, 81 | 0x2800000000, 0x5000000000, 0xa000000000, 0x4000000000, 82 | 0x20000000000, 0x50000000000, 0xa0000000000, 0x140000000000, 83 | 0x280000000000, 0x500000000000, 0xa00000000000, 0x400000000000, 84 | 0x2000000000000, 0x5000000000000, 0xa000000000000, 0x14000000000000, 85 | 0x28000000000000, 0x50000000000000, 0xa0000000000000, 0x40000000000000, 86 | 0x200000000000000, 0x500000000000000, 0xa00000000000000, 0x1400000000000000, 87 | 0x2800000000000000, 0x5000000000000000, 0xa000000000000000, 0x4000000000000000, 88 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 89 | }; 90 | 91 | constexpr array BLACK_PAWN_ATTACKS = { 92 | 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 93 | 0x2, 0x5, 0xa, 0x14, 0x28, 0x50, 0xa0, 0x40, 94 | 0x200, 0x500, 0xa00, 0x1400, 95 | 0x2800, 0x5000, 0xa000, 0x4000, 96 | 0x20000, 0x50000, 0xa0000, 0x140000, 97 | 0x280000, 0x500000, 0xa00000, 0x400000, 98 | 0x2000000, 0x5000000, 0xa000000, 0x14000000, 99 | 0x28000000, 0x50000000, 0xa0000000, 0x40000000, 100 | 0x200000000, 0x500000000, 0xa00000000, 0x1400000000, 101 | 0x2800000000, 0x5000000000, 0xa000000000, 0x4000000000, 102 | 0x20000000000, 0x50000000000, 0xa0000000000, 0x140000000000, 103 | 0x280000000000, 0x500000000000, 0xa00000000000, 0x400000000000, 104 | 0x2000000000000, 0x5000000000000, 0xa000000000000, 0x14000000000000, 105 | 0x28000000000000, 0x50000000000000, 0xa0000000000000, 0x40000000000000, 106 | }; 107 | 108 | constexpr array BISHOP_MAGICS = { 109 | 0x0002020202020200, 0x0002020202020000, 0x0004010202000000, 0x0004040080000000, 110 | 0x0001104000000000, 0x0000821040000000, 0x0000410410400000, 0x0000104104104000, 111 | 0x0000040404040400, 0x0000020202020200, 0x0000040102020000, 0x0000040400800000, 112 | 0x0000011040000000, 0x0000008210400000, 0x0000004104104000, 0x0000002082082000, 113 | 0x0004000808080800, 0x0002000404040400, 0x0001000202020200, 0x0000800802004000, 114 | 0x0000800400A00000, 0x0000200100884000, 0x0000400082082000, 0x0000200041041000, 115 | 0x0002080010101000, 0x0001040008080800, 0x0000208004010400, 0x0000404004010200, 116 | 0x0000840000802000, 0x0000404002011000, 0x0000808001041000, 0x0000404000820800, 117 | 0x0001041000202000, 0x0000820800101000, 0x0000104400080800, 0x0000020080080080, 118 | 0x0000404040040100, 0x0000808100020100, 0x0001010100020800, 0x0000808080010400, 119 | 0x0000820820004000, 0x0000410410002000, 0x0000082088001000, 0x0000002011000800, 120 | 0x0000080100400400, 0x0001010101000200, 0x0002020202000400, 0x0001010101000200, 121 | 0x0000410410400000, 0x0000208208200000, 0x0000002084100000, 0x0000000020880000, 122 | 0x0000001002020000, 0x0000040408020000, 0x0004040404040000, 0x0002020202020000, 123 | 0x0000104104104000, 0x0000002082082000, 0x0000000020841000, 0x0000000000208800, 124 | 0x0000000010020200, 0x0000000404080200, 0x0000040404040400, 0x0002020202020200 125 | }; 126 | 127 | constexpr array ROOK_MAGICS = { 128 | 0x0080001020400080, 0x0040001000200040, 0x0080081000200080, 0x0080040800100080, 129 | 0x0080020400080080, 0x0080010200040080, 0x0080008001000200, 0x0080002040800100, 130 | 0x0000800020400080, 0x0000400020005000, 0x0000801000200080, 0x0000800800100080, 131 | 0x0000800400080080, 0x0000800200040080, 0x0000800100020080, 0x0000800040800100, 132 | 0x0000208000400080, 0x0000404000201000, 0x0000808010002000, 0x0000808008001000, 133 | 0x0000808004000800, 0x0000808002000400, 0x0000010100020004, 0x0000020000408104, 134 | 0x0000208080004000, 0x0000200040005000, 0x0000100080200080, 0x0000080080100080, 135 | 0x0000040080080080, 0x0000020080040080, 0x0000010080800200, 0x0000800080004100, 136 | 0x0000204000800080, 0x0000200040401000, 0x0000100080802000, 0x0000080080801000, 137 | 0x0000040080800800, 0x0000020080800400, 0x0000020001010004, 0x0000800040800100, 138 | 0x0000204000808000, 0x0000200040008080, 0x0000100020008080, 0x0000080010008080, 139 | 0x0000040008008080, 0x0000020004008080, 0x0000010002008080, 0x0000004081020004, 140 | 0x0000204000800080, 0x0000200040008080, 0x0000100020008080, 0x0000080010008080, 141 | 0x0000040008008080, 0x0000020004008080, 0x0000800100020080, 0x0000800041000080, 142 | 0x00FFFCDDFCED714A, 0x007FFCDDFCED714A, 0x003FFFCDFFD88096, 0x0000040810002101, 143 | 0x0001000204080011, 0x0001000204000801, 0x0001000082000401, 0x0001FFFAABFAD1A2 144 | }; 145 | 146 | [[nodiscard]] static Bitboard board_edge(Direction D) { 147 | if (D == NORTH) return MASK_RANK[RANK8]; 148 | else if (D == SOUTH) return MASK_RANK[RANK1]; 149 | else if (D == EAST) return MASK_FILE[HFILE]; 150 | else if (D == WEST) return MASK_FILE[AFILE]; 151 | 152 | else if (D == NORTH_EAST) return MASK_RANK[RANK8] | MASK_FILE[HFILE]; 153 | else if (D == NORTH_WEST) return MASK_RANK[RANK8] | MASK_FILE[AFILE]; 154 | else if (D == SOUTH_EAST) return MASK_RANK[RANK1] | MASK_FILE[HFILE]; 155 | else if (D == SOUTH_WEST) return MASK_RANK[RANK1] | MASK_FILE[AFILE]; 156 | 157 | return 0; 158 | } 159 | 160 | [[nodiscard]] static Bitboard empty_board_rook_attacks(Square square) { 161 | return MASK_RANK[rank_of(square)] ^ MASK_FILE[file_of(square)]; 162 | } 163 | 164 | [[nodiscard]] static Bitboard empty_board_bishop_attacks(Square square) { 165 | return MASK_DIAGONAL[diagonal_of(square)] ^ MASK_ANTI_DIAGONAL[anti_diagonal_of(square)]; 166 | } 167 | 168 | [[nodiscard]] static array generate_rook_attack_masks() { 169 | array rook_attack_masks{}; 170 | for (Square sq = a1; sq < NSQUARES; sq++) { 171 | Bitboard edges = ((board_edge(NORTH) | board_edge(SOUTH)) & ~MASK_RANK[rank_of(sq)]) | 172 | ((board_edge(EAST) | board_edge(WEST)) & ~MASK_FILE[file_of(sq)]); 173 | rook_attack_masks[sq] = empty_board_rook_attacks(sq) & ~edges; 174 | } 175 | return rook_attack_masks; 176 | } 177 | 178 | [[nodiscard]] static array generate_bishop_attack_masks() { 179 | array bishop_attack_masks{}; 180 | for (Square sq = a1; sq < NSQUARES; sq++) { 181 | Bitboard edges = board_edge(NORTH) | board_edge(SOUTH) | board_edge(EAST) | board_edge(WEST); 182 | bishop_attack_masks[sq] = empty_board_bishop_attacks(sq) & ~edges; 183 | } 184 | return bishop_attack_masks; 185 | } 186 | 187 | const static array rook_attack_masks = generate_rook_attack_masks(); 188 | const static array bishop_attack_masks = generate_bishop_attack_masks(); 189 | 190 | [[nodiscard]] static Bitboard generate_slow_sliding_attacks(Square sq, Direction direction, Bitboard occupancy) { 191 | Bitboard attacks{}; 192 | 193 | Bitboard blockers = board_edge(direction); 194 | Bitboard square_bb = square_to_bitboard(sq); 195 | 196 | if ((blockers & square_bb) != 0) return attacks; 197 | 198 | blockers |= occupancy; 199 | 200 | do { 201 | square_bb = shift(direction, square_bb); 202 | attacks |= square_bb; 203 | } while ((blockers & square_bb) == 0); 204 | 205 | return attacks; 206 | } 207 | 208 | [[nodiscard]] static Bitboard generate_slow_rook_attacks(Square sq, Bitboard occupancy) { 209 | return generate_slow_sliding_attacks(sq, NORTH, occupancy) | 210 | generate_slow_sliding_attacks(sq, SOUTH, occupancy) | 211 | generate_slow_sliding_attacks(sq, EAST, occupancy) | 212 | generate_slow_sliding_attacks(sq, WEST, occupancy); 213 | } 214 | 215 | [[nodiscard]] static Bitboard generate_slow_bishop_attacks(Square sq, Bitboard occupancy) { 216 | return generate_slow_sliding_attacks(sq, NORTH_EAST, occupancy) | 217 | generate_slow_sliding_attacks(sq, NORTH_WEST, occupancy) | 218 | generate_slow_sliding_attacks(sq, SOUTH_EAST, occupancy) | 219 | generate_slow_sliding_attacks(sq, SOUTH_WEST, occupancy); 220 | } 221 | 222 | [[nodiscard]] static array, NSQUARES> generate_rook_attack_table() { 223 | array, NSQUARES> rook_attack_table{}; 224 | Bitboard subset{}, index{}; 225 | 226 | for (Square sq = a1; sq < NSQUARES; sq++) { 227 | subset = 0; 228 | do { 229 | index = subset; 230 | index = index * ROOK_MAGICS[sq]; 231 | index = index >> ROOK_SHIFTS[sq]; 232 | rook_attack_table[sq][index] = generate_slow_rook_attacks(sq, subset); 233 | subset = (subset - rook_attack_masks[sq]) & rook_attack_masks[sq]; 234 | } while (subset); 235 | } 236 | return rook_attack_table; 237 | } 238 | 239 | [[nodiscard]] static array, NSQUARES> generate_bishop_attack_table() { 240 | array, NSQUARES> bishop_attack_table{}; 241 | Bitboard subset{}, index{}; 242 | 243 | for (Square sq = a1; sq < NSQUARES; sq++) { 244 | subset = 0; 245 | do { 246 | index = subset; 247 | index = index * BISHOP_MAGICS[sq]; 248 | index = index >> BISHOP_SHIFTS[sq]; 249 | bishop_attack_table[sq][index] = generate_slow_bishop_attacks(sq, subset); 250 | subset = (subset - bishop_attack_masks[sq]) & bishop_attack_masks[sq]; 251 | } while (subset); 252 | } 253 | return bishop_attack_table; 254 | } 255 | 256 | const static array, NSQUARES> rook_attack_table = generate_rook_attack_table(); 257 | 258 | static Bitboard get_rook_attacks(Square square, Bitboard occ) { 259 | usize index = ((occ & rook_attack_masks[square]) * ROOK_MAGICS[square]) >> ROOK_SHIFTS[square]; 260 | return rook_attack_table[square][index]; 261 | } 262 | 263 | const static array, NSQUARES> bishop_attack_table = generate_bishop_attack_table(); 264 | 265 | static Bitboard get_bishop_attacks(Square square, Bitboard occ) { 266 | usize index = ((occ & bishop_attack_masks[square]) * BISHOP_MAGICS[square]) >> BISHOP_SHIFTS[square]; 267 | return bishop_attack_table[square][index]; 268 | } 269 | 270 | template 271 | constexpr Bitboard attacks(Square sq, Bitboard occupancy = 0) { 272 | if constexpr (piece_type == PAWN) { 273 | if constexpr (color == WHITE) return WHITE_PAWN_ATTACKS[sq]; 274 | return BLACK_PAWN_ATTACKS[sq]; 275 | } 276 | else if constexpr (piece_type == KNIGHT) return KNIGHT_ATTACKS[sq]; 277 | else if constexpr (piece_type == BISHOP) return get_bishop_attacks(sq, occupancy); 278 | else if constexpr (piece_type == ROOK) return get_rook_attacks(sq, occupancy); 279 | else if constexpr (piece_type == QUEEN) return get_bishop_attacks(sq, occupancy) | get_rook_attacks(sq, occupancy); 280 | else if constexpr (piece_type == KING) return KING_ATTACKS[sq]; 281 | return 0; 282 | } 283 | 284 | inline Bitboard attacks(PieceType piece_type, Square sq, Bitboard occupancy) { 285 | if (piece_type == PAWN) return 0; 286 | else if (piece_type == KNIGHT) return KNIGHT_ATTACKS[sq]; 287 | else if (piece_type == BISHOP) return get_bishop_attacks(sq, occupancy); 288 | else if (piece_type == ROOK) return get_rook_attacks(sq, occupancy); 289 | else if (piece_type == QUEEN) return get_bishop_attacks(sq, occupancy) | get_rook_attacks(sq, occupancy); 290 | else if (piece_type == KING) return KING_ATTACKS[sq]; 291 | return 0; 292 | } 293 | } // table namespace -------------------------------------------------------------------------------- /src/move_gen/tables/square_tables.h: -------------------------------------------------------------------------------- 1 | #include "../types/types.h" 2 | #include "../../board/types/bitboard.h" 3 | #include "../../board/types/piece.h" 4 | #include "../../board/constants/zobrist_constants.h" 5 | #include "attack_tables.h" 6 | 7 | namespace tables { 8 | static array, NSQUARES> generate_squares_in_between() { 9 | array, NSQUARES> squares_in_between{}; 10 | for (Square sq1 = a1; sq1 < NSQUARES; sq1++) { 11 | for (Square sq2 = a1; sq2 < NSQUARES; sq2++) { 12 | Bitboard sqs = square_to_bitboard(sq1) | square_to_bitboard(sq2); 13 | 14 | if (file_of(sq1) == file_of(sq2) || rank_of(sq1) == rank_of(sq2)) 15 | squares_in_between[sq1][sq2] = generate_slow_rook_attacks(sq1, sqs) & generate_slow_rook_attacks(sq2, sqs); 16 | 17 | else if (diagonal_of(sq1) == diagonal_of(sq2) || anti_diagonal_of(sq1) == anti_diagonal_of(sq2)) 18 | squares_in_between[sq1][sq2] = generate_slow_bishop_attacks(sq1, sqs) & generate_slow_bishop_attacks(sq2, sqs); 19 | } 20 | } 21 | return squares_in_between; 22 | } 23 | const static array, NSQUARES> squares_in_between = generate_squares_in_between(); 24 | 25 | static array, NSQUARES> generate_square_line() { 26 | array, NSQUARES> square_line{}; 27 | for (Square sq1 = a1; sq1 < NSQUARES; sq1++) { 28 | for (Square sq2 = a1; sq2 < NSQUARES; sq2++) { 29 | 30 | if (file_of(sq1) == file_of(sq2) || rank_of(sq1) == rank_of(sq2)) 31 | square_line[sq1][sq2] = generate_slow_rook_attacks(sq1, 0) & generate_slow_rook_attacks(sq2, 0); 32 | 33 | else if (diagonal_of(sq1) == diagonal_of(sq2) || anti_diagonal_of(sq1) == anti_diagonal_of(sq2)) 34 | square_line[sq1][sq2] = generate_slow_bishop_attacks(sq1, 0) & generate_slow_bishop_attacks(sq2, 0); 35 | } 36 | } 37 | return square_line; 38 | } 39 | const static array, NSQUARES> square_line = generate_square_line(); 40 | 41 | inline static Bitboard square_in_between(Square sq1, Square sq2) { 42 | return squares_in_between[sq1][sq2]; 43 | } 44 | 45 | inline static Bitboard line_of(Square sq1, Square sq2) { 46 | return square_line[sq1][sq2]; 47 | } 48 | } -------------------------------------------------------------------------------- /src/move_gen/types/move.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "../../types.h" 5 | #include "../../board/types/board_types.h" 6 | #include "../../board/types/square.h" 7 | 8 | using MoveType = u8; 9 | 10 | constexpr MoveType QUIET = 0b0000; 11 | constexpr MoveType OO = 0b0001; 12 | constexpr MoveType OOO = 0b0010; 13 | constexpr MoveType DOUBLE_PUSH = 0b0011; 14 | 15 | // Captures have the 4th bit set. 16 | constexpr MoveType CAPTURE_TYPE = 0b1000; 17 | constexpr MoveType ENPASSANT = 0b1001; 18 | 19 | // Promotions have the 3rd bit set. 20 | constexpr MoveType PROMOTION_TYPE = 0b0100; 21 | constexpr MoveType PR_KNIGHT = 0b0100; 22 | constexpr MoveType PR_BISHOP = 0b0101; 23 | constexpr MoveType PR_ROOK = 0b0110; 24 | constexpr MoveType PR_QUEEN = 0b0111; 25 | 26 | class Move { 27 | private: 28 | // Bits are arranged as follows 29 | // | 4 bits for type | 6 bits for from | 6 bits for to 30 | uint16_t move; 31 | 32 | static constexpr u8 TO_SHIFT = 0; 33 | static constexpr u8 FROM_SHIFT = 6; 34 | static constexpr u8 TYPE_SHIFT = 12; 35 | 36 | static constexpr u8 TO_BITMASK = 0b111111; 37 | static constexpr u8 FROM_BITMASK = 0b111111; 38 | static constexpr u8 TYPE_BITMASK = 0b1111; 39 | 40 | static constexpr u8 CAPTURE_BITMASK = 0b1000; 41 | static constexpr u8 PROMOTION_BITMASK = 0b0100; 42 | 43 | public: 44 | constexpr Move() : move(0) {} 45 | 46 | constexpr explicit Move(uint16_t m) { move = m; } 47 | 48 | constexpr Move(Square from, Square to) : move(0) { 49 | move = (from << 6) | to; 50 | } 51 | 52 | constexpr Move(Square from, Square to, MoveType type) : move(0) { 53 | move = (type << TYPE_SHIFT) | (from << FROM_SHIFT) | to << TO_SHIFT; 54 | } 55 | 56 | explicit Move(const std::string& m) { 57 | move = (create_square(File(m[0] - 'a'), Rank(m[1] - '1')) << 6) | 58 | create_square(File(m[2] - 'a'), Rank(m[3] - '1')); 59 | } 60 | 61 | [[nodiscard]] inline Square to() const { return Square(move & TO_BITMASK); } 62 | [[nodiscard]] inline Square from() const { return Square((move >> FROM_SHIFT) & FROM_BITMASK); } 63 | [[nodiscard]] inline MoveType type() const { return (move >> TYPE_SHIFT) & TYPE_BITMASK; } 64 | 65 | [[nodiscard]] inline bool is_capture() const { return (move >> TYPE_SHIFT) & CAPTURE_BITMASK; } 66 | [[nodiscard]] inline bool is_promotion() const { return (move >> TYPE_SHIFT) & PROMOTION_BITMASK; } 67 | [[nodiscard]] inline bool is_quiet() const { return !is_capture() && !is_promotion(); } 68 | 69 | bool operator==(Move a) const { return move == a.move; } 70 | bool operator!=(Move a) const { return move != a.move; } 71 | }; 72 | 73 | constexpr Move EMPTY_MOVE = Move(); 74 | 75 | inline array MOVE_TYPE_UCI = { 76 | "", "", "", "", "N", "B", "R", "Q", 77 | "", "", "", "", "N", "B", "R", "Q" 78 | }; 79 | 80 | inline std::ostream& operator<<(std::ostream& os, const Move& m) { 81 | os << SQ_TO_STRING[m.from()] << SQ_TO_STRING[m.to()] << MOVE_TYPE_UCI[m.type()]; 82 | return os; 83 | } 84 | -------------------------------------------------------------------------------- /src/move_gen/types/types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../types.h" 4 | 5 | enum class MoveGenerationType { 6 | ALL, 7 | CAPTURES 8 | }; 9 | -------------------------------------------------------------------------------- /src/move_search/extensions.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "search_params.h" 3 | #include "search_constants.h" 4 | #include "types.h" 5 | #include "tables/transposition_table.h" 6 | 7 | template 8 | i32 singular_extension(SearchData &sdata, ThreadData &tdata, Position &board, bool excluding_move, i32 depth, i32 ply, 9 | i32 alpha, i32 beta, Move legal_move, TranspositionTableSearchResults tt_probe_results) { 10 | 11 | i32 extension = 0; 12 | bool possible_singularity = !excluding_move && ply > 0 && tt_probe_results.entry_found && 13 | depth >= 8 && legal_move == tt_probe_results.entry.best_move && 14 | tt_probe_results.entry.depth >= depth - 3 && 15 | tt_probe_results.entry.node_type != TTNodeType::UPPER_NODE; 16 | if (possible_singularity) { 17 | tdata.thread_stack[ply].excluded_move = legal_move; 18 | 19 | i32 tt_value = tt_probe_results.entry.value; 20 | i32 singularity_beta = std::max(tt_value - 2 * depth, -MATE_BOUND); 21 | i32 singularity_depth = (depth - 1) >> 1; 22 | i32 singularity_score = pvs(sdata, tdata, board, singularity_depth, ply, singularity_beta - 1, singularity_beta, false); 23 | 24 | tdata.thread_stack[ply].excluded_move = EMPTY_MOVE; 25 | 26 | if (singularity_score < singularity_beta) extension = 1; 27 | else if (tt_value >= beta || tt_value <= alpha) extension = -1; 28 | else extension = 0; 29 | } 30 | return extension; 31 | } -------------------------------------------------------------------------------- /src/move_search/move_ordering/move_ordering.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 3/20/23. 3 | // 4 | 5 | #include "move_ordering.h" 6 | 7 | int get_piece_value(PieceType piece_type) { 8 | switch (piece_type) { 9 | case PAWN: return ORDERING_PAWN_VALUE; 10 | case KNIGHT: return ORDERING_KNIGHT_VALUE; 11 | case BISHOP: return ORDERING_BISHOP_VALUE; 12 | case ROOK: return ORDERING_ROOK_VALUE; 13 | case QUEEN: return ORDERING_QUEEN_VALUE; 14 | case KING: return ORDERING_KING_VALUE; 15 | default: return 0; 16 | } 17 | } 18 | 19 | int hash_move_score(Move& move, Move& previous_best_move) { 20 | if (move != previous_best_move) return 0; 21 | return PREVIOUS_BEST_MOVE_BONUS; 22 | } 23 | 24 | int promotion_move_score(Move move) { 25 | if (!move.is_promotion()) return 0; 26 | MoveType flag = move.type(); 27 | if ((flag == (PR_QUEEN | CAPTURE_TYPE)) || flag == PR_QUEEN) return ORDERING_QUEEN_VALUE + PROMOTION_BONUS; 28 | else if ((flag == (PR_ROOK | CAPTURE_TYPE)) || flag == PR_ROOK) return ORDERING_ROOK_VALUE + PROMOTION_BONUS; 29 | else if ((flag == (PR_BISHOP | CAPTURE_TYPE)) || flag == PR_BISHOP) return ORDERING_BISHOP_VALUE + PROMOTION_BONUS; 30 | else if ((flag == (PR_KNIGHT | CAPTURE_TYPE)) || flag == PR_KNIGHT) return ORDERING_KNIGHT_VALUE + PROMOTION_BONUS; 31 | else return 0; 32 | } 33 | 34 | Move& select_move(ScoredMoves& scored_moves, int idx) { 35 | int best_idx = idx; 36 | int best_score = scored_moves[idx].score; 37 | for (int i = idx + 1; i < static_cast(scored_moves.size()); i++) { 38 | if (scored_moves[i].score < best_score) { 39 | best_idx = i; 40 | best_score = scored_moves[i].score; 41 | } 42 | } 43 | std::swap(scored_moves[idx], scored_moves[best_idx]); 44 | return scored_moves[idx].move; 45 | } 46 | -------------------------------------------------------------------------------- /src/move_search/move_ordering/move_ordering.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 1/8/23. 3 | // 4 | #pragma once 5 | #include 6 | #include "../../board/types/piece.h" 7 | #include "../../move_gen/types/move.h" 8 | #include "../../board/position.h" 9 | #include "ordering_constants.h" 10 | #include "../tables/history_table.h" 11 | #include "../tables/transposition_table.h" 12 | #include "../../move_gen/move_generator.h" 13 | 14 | int get_piece_value(PieceType piece_type); 15 | 16 | int hash_move_score(Move& move, Move& previous_best_move); 17 | 18 | int capture_move_score(Move move, Position &board, ThreadData &tdata); 19 | 20 | int promotion_move_score(Move move); 21 | 22 | template 23 | bool static_exchange_eval(Position& board, Move move, const int threshold) { 24 | 25 | if (move.is_promotion()) return true; 26 | 27 | Square to = move.to(); 28 | Square from = move.from(); 29 | PieceType piece_captured = type_of(board.piece_at(to)); 30 | PieceType piece_capturing = type_of(board.piece_at(from)); 31 | 32 | // If we make the capture and don't loose our piece, we should beat the threshold. 33 | // If we don't it's likely a bad exchange. 34 | int value = ORDERING_PIECE_VALUES[piece_captured] - threshold; 35 | if (value < 0) return false; 36 | 37 | // If we loose our piece and are still positive, this is a good exchange. 38 | value -= ORDERING_PIECE_VALUES[piece_capturing]; 39 | if (value >= 0) return true; 40 | 41 | // We already know the piece on 'from' is an attacker. Ignore it. The piece on to will be captured. Ignore it. 42 | Bitboard occupied = board.occupancy() ^ square_to_bitboard(from) ^ square_to_bitboard(to); 43 | // This will be updated to only contain pieces that only have pieces we are concerned with. 44 | Bitboard attackers = board.attackers_of(to, occupied); 45 | 46 | Bitboard bishops = board.occupancy() | board.occupancy(); 47 | Bitboard rooks = board.occupancy() | board.occupancy(); 48 | 49 | Color side_to_play = ~color; 50 | 51 | while (true) { 52 | attackers &= occupied; 53 | 54 | Bitboard our_attackers = attackers & board.occupancy(side_to_play); 55 | if (!our_attackers) break; // If no remaining attackers for us, break. 56 | 57 | u32 ipt; 58 | for (ipt = PAWN; ipt < KING; ipt++) { // pt == king when we break if all others didn't cause break. 59 | if (our_attackers & board.occupancy(side_to_play, PieceType(ipt))) break; 60 | } 61 | auto piece_type = PieceType(ipt); 62 | 63 | side_to_play = ~side_to_play; 64 | 65 | value = -value - 1 - ORDERING_PIECE_VALUES[piece_type]; 66 | if (value >= 0) { 67 | if (piece_type == KING && (attackers & board.occupancy(side_to_play))) side_to_play = ~side_to_play; 68 | break; 69 | } 70 | 71 | occupied ^= square_to_bitboard(lsb(our_attackers & board.occupancy(piece_type))); 72 | 73 | // Add discovered attacks. 74 | if (piece_type == PAWN || piece_type == BISHOP || piece_type == QUEEN) 75 | attackers |= tables::attacks(to, occupied) & bishops; 76 | if (piece_type == ROOK || piece_type == QUEEN) 77 | attackers |= tables::attacks(to, occupied) & rooks; 78 | } 79 | return side_to_play != color_of(board.piece_at(from)); 80 | } 81 | 82 | template 83 | int capture_move_score(Move move, Position &board, ThreadData &tdata) { 84 | if (!move.is_capture()) return 0; 85 | PieceType to_type = type_of(board.piece_at(move.to())); 86 | PieceType from_type = type_of(board.piece_at(move.from())); 87 | int cap_hist_score = tdata.capture_history[board.piece_at(move.from())][move.to()][board.piece_at(move.to())]; 88 | return (MVV_LVA_BONUS + cap_hist_score) * static_exchange_eval(board, move, -107) + 89 | get_piece_value(to_type) - get_piece_value(from_type); 90 | } 91 | 92 | template 93 | int history_score(Move &move, int ply, Position &board, ThreadData &tdata) { 94 | if (!move.is_quiet()) return 0; 95 | if (move == tdata.killers[ply][0]) return KILLER_MOVE_BONUS + 2000; 96 | else if (move == tdata.killers[ply][1]) return KILLER_MOVE_BONUS + 1000; 97 | 98 | Move one_mv_ago = ply > 0 ? tdata.thread_stack[ply - 1].move : Move(); 99 | Move two_mv_ago = ply > 1 ? tdata.thread_stack[ply - 2].move : Move(); 100 | 101 | int one_mv_ago_score = tdata.continuation_history[board.piece_at(one_mv_ago.from())][one_mv_ago.to()][board.piece_at(move.from())][move.to()]; 102 | one_mv_ago_score = one_mv_ago != Move() ? one_mv_ago_score : 0; 103 | 104 | int two_mv_ago_score = tdata.continuation_history[board.piece_at(two_mv_ago.from())][two_mv_ago.to()][board.piece_at(move.from())][move.to()]; 105 | two_mv_ago_score = two_mv_ago != Move() ? two_mv_ago_score : 0; 106 | 107 | return tdata.history[color][move.from()][move.to()] + one_mv_ago_score + two_mv_ago_score; 108 | } 109 | 110 | template 111 | int in_opponent_pawn_territory(Move move, Position& board) { 112 | Bitboard them_pawns = board.occupancy<~color, PAWN>(); 113 | Bitboard opp_pawn_attacks = shift_relative(them_pawns) | shift_relative(them_pawns); 114 | if ((1ULL << move.to()) & opp_pawn_attacks) return IN_OPP_PAWN_TERRITORY_PENALTY; 115 | return 0; 116 | } 117 | 118 | template 119 | ScoredMoves order_moves(MoveList& move_list, Position& board, int ply, ThreadData& tdata) { 120 | ScoredMoves scored_moves; 121 | scored_moves.reserve(move_list.size()); 122 | Move previous_best_move = Move(); 123 | TranspositionTableSearchResults search_results = t_table.probe_for_move_ordering(board.hash()); 124 | if (search_results.entry_found) previous_best_move = search_results.entry.best_move; 125 | for (Move move : move_list) { 126 | ScoredMove scored_move; 127 | scored_move.move = move; 128 | int score = 0; //Higher score is likely a better move. 129 | score += hash_move_score(move, previous_best_move); 130 | score += capture_move_score(move, board, tdata); 131 | score += promotion_move_score(move); 132 | score += history_score(move, ply, board, tdata); 133 | score += in_opponent_pawn_territory(move, board); 134 | // Score negated for sorting. We want to evaluate high scoring moves first. 135 | scored_move.score = -score; 136 | scored_moves.push_back(scored_move); 137 | } 138 | return scored_moves; 139 | } 140 | 141 | Move& select_move(ScoredMoves& scored_moves, int idx); 142 | -------------------------------------------------------------------------------- /src/move_search/move_ordering/ordering_constants.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 2/14/23. 3 | // 4 | #pragma once 5 | #include "../types.h" 6 | #include "../../board/types/piece.h" 7 | 8 | constexpr i32 PREVIOUS_BEST_MOVE_BONUS = 10'000'000; 9 | constexpr i32 PROMOTION_BONUS = PREVIOUS_BEST_MOVE_BONUS / 10; // 1'000'000 10 | constexpr i32 MVV_LVA_BONUS = PROMOTION_BONUS / 10; // 100'000 11 | constexpr i32 KILLER_MOVE_BONUS = MVV_LVA_BONUS / 10; // 10'000 12 | constexpr i32 IN_OPP_PAWN_TERRITORY_PENALTY = -350; 13 | 14 | constexpr i32 ORDERING_PAWN_VALUE = 100; 15 | constexpr i32 ORDERING_KNIGHT_VALUE = 320; 16 | constexpr i32 ORDERING_BISHOP_VALUE = 340; 17 | constexpr i32 ORDERING_ROOK_VALUE = 500; 18 | constexpr i32 ORDERING_QUEEN_VALUE = 900; 19 | constexpr i32 ORDERING_KING_VALUE = 1000; 20 | constexpr i32 ORDERING_PIECE_VALUES[NPIECE_TYPES] = { 21 | ORDERING_PAWN_VALUE, ORDERING_KNIGHT_VALUE, ORDERING_BISHOP_VALUE, ORDERING_ROOK_VALUE, ORDERING_QUEEN_VALUE, 0, 0 22 | }; -------------------------------------------------------------------------------- /src/move_search/pruning.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "search_params.h" 3 | #include "search_constants.h" 4 | #include "../move_gen/types/move.h" 5 | #include 6 | #include "tables/history_table.h" 7 | 8 | constexpr array, MAX_DEPTH> generate_lmp_table() { 9 | array, MAX_DEPTH> lmp_table{}; 10 | for (i32 depth = 0; depth < MAX_DEPTH; depth++) { 11 | lmp_table[depth][0] = (3 + depth * depth) / 2; 12 | lmp_table[depth][1] = 3 + depth * depth; 13 | } 14 | return lmp_table; 15 | } 16 | 17 | constexpr auto lmp_table = generate_lmp_table(); 18 | 19 | inline bool late_move_prune(bool pv_node, i32 move_idx, i32 depth, bool improving) { 20 | return !pv_node && depth <= LMP_MIN_DEPTH && move_idx > lmp_table[depth][improving]; 21 | } 22 | 23 | inline bool late_move_prune_quiet(bool pv_node, i32 move_idx, Move legal_move, i32 depth) { 24 | return !pv_node && depth <= 6 && legal_move.is_quiet() && move_idx > depth * 9; 25 | } 26 | 27 | inline bool futility_prune(i32 static_eval, i32 alpha, i32 value, i32 depth) { 28 | i32 fp_margin = depth * FP_COEFFICIENT + FP_MARGIN; 29 | return value > -MATE_BOUND && depth < FP_DEPTH && static_eval + fp_margin <= alpha; 30 | } 31 | 32 | template 33 | inline bool history_prune(ThreadData &tdata, bool pv_node, i32 value, i32 depth, Move legal_move) { 34 | return !pv_node && value > -MATE_BOUND && depth < 3 && tdata.history[color][legal_move.from()][legal_move.to()] < -1024 * depth; 35 | } 36 | 37 | template 38 | inline bool see_prune_pvs(Position& board, bool pv_node, i32 depth, i32 value, Move legal_move) { 39 | return !pv_node && depth < SEE_PVS_MIN_DEPTH && value > -MATE_BOUND && 40 | !static_exchange_eval(board, legal_move, legal_move.is_quiet() ? SEE_PVS_QUIET_MARGIN * depth : SEE_PVS_TACTICAL_MARGIN * depth); 41 | } -------------------------------------------------------------------------------- /src/move_search/pvs.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 4/16/23. 3 | // 4 | #include "pvs.h" 5 | #include "../uci_interpreter/time_manager.h" 6 | 7 | using enum TTNodeType; 8 | 9 | void reset_search_data(SearchData &sdata) { 10 | sdata.search_completed = true; 11 | sdata.seldepth = 0; 12 | sdata.pv.table.fill({}); 13 | sdata.pv.length.fill(0); 14 | sdata.time_limit = 0; 15 | } 16 | 17 | bool position_is_draw(Position &board, const int ply) { 18 | if (board.fifty_move_rule() >= 100) return true; 19 | return board.has_repetition(ply == 0 ? Position::THREE_FOLD : Position::TWO_FOLD); 20 | } 21 | 22 | void update_best_move_results(SearchData &sdata, int sub_depth, bool debug) { 23 | Line pv{}; 24 | sdata.final_best_move = sdata.best_move; 25 | sdata.final_value = sdata.value; 26 | for (auto i = 0; i < sdata.pv.length[0]; i++) { 27 | pv[i] = sdata.pv.table[0][i]; 28 | } 29 | if (!debug) return; 30 | std::cout << "info depth " << sub_depth << " seldepth " << sdata.seldepth 31 | << " score cp " << sdata.value << " time " << get_elapsed_time(TimeResolution::Milliseconds) 32 | << " nodes " << sdata.nodes_searched << " pv " << pv << std::endl; 33 | } 34 | 35 | int scale_soft_time_limit(SearchParameters ¶ms, SearchData& sdata, int depth) { 36 | if (params.soft_time_limit == params.hard_time_limit && params.soft_time_limit == 86'400'000) 37 | return params.soft_time_limit; 38 | if (depth <= 9) return params.soft_time_limit; 39 | 40 | Move best_move = sdata.best_move; 41 | auto best_move_nodes_spent = static_cast(nodes_spent[best_move.from()][best_move.to()]); 42 | auto total_nodes_spent = static_cast(sdata.nodes_searched); 43 | auto percent_nodes_spent = best_move_nodes_spent / total_nodes_spent; 44 | auto scaled_soft_time_limit = (1.5 - percent_nodes_spent) * 1.35; 45 | return static_cast(std::min(params.soft_time_limit * scaled_soft_time_limit, static_cast(params.hard_time_limit))); 46 | } 47 | 48 | template 49 | i32 q_search(SearchData &sdata, ThreadData &tdata, Position &board, i32 ply, i32 alpha, i32 beta) { 50 | 51 | t_table.prefetch(board.hash()); 52 | 53 | if (ply >= MAX_PLY - 2) return board.evaluate(); 54 | 55 | if ((sdata.nodes_searched % 1024 == 0 && (sdata.stopped || time_elapsed_exceeds(sdata.time_limit, TimeResolution::Milliseconds))) || 56 | (sdata.nodes_searched > sdata.hard_node_limit && sdata.hard_node_limit != -1)) { 57 | 58 | sdata.search_completed = false; 59 | return 0; 60 | } 61 | 62 | sdata.seldepth = std::max(sdata.seldepth, ply); 63 | 64 | const i32 stand_pat = board.evaluate(); 65 | if (stand_pat >= beta) return beta; 66 | if (alpha < stand_pat) alpha = stand_pat; 67 | 68 | TranspositionTableSearchResults probe_results = t_table.probe_for_search(board.hash(), QSEARCH_TT_DEPTH, ply); 69 | if (probe_results.entry_found) { 70 | if ((probe_results.entry.node_type == EXACT) || 71 | (probe_results.entry.node_type == LOWER_NODE && probe_results.entry.value >= beta) || 72 | (probe_results.entry.node_type == UPPER_NODE && probe_results.entry.value <= alpha)) { 73 | return probe_results.entry.value; 74 | } 75 | } 76 | 77 | auto best_move = EMPTY_MOVE; 78 | MoveList capture_moves(board); 79 | ScoredMoves scored_moves = order_moves(capture_moves, board, ply, tdata); 80 | i32 futility = stand_pat + Q_SEARCH_FUTILITY_MARGIN; 81 | for (i32 move_idx = 0; move_idx < static_cast(scored_moves.size()); move_idx++) { 82 | const Move legal_move = select_move(scored_moves, move_idx); 83 | 84 | if (futility <= alpha && !static_exchange_eval(board, legal_move, SEE_Q_MARGIN)) { 85 | alpha = std::max(alpha, futility); 86 | continue; 87 | } 88 | 89 | board.play(legal_move); 90 | sdata.nodes_searched += 1; 91 | const auto score = -q_search<~color>(sdata, tdata, board, ply + 1, -beta, -alpha); 92 | board.undo(legal_move); 93 | 94 | if (score >= beta) { 95 | alpha = beta; 96 | best_move = legal_move; 97 | break; 98 | } 99 | if (score > alpha) { 100 | best_move = legal_move; 101 | alpha = score; 102 | } 103 | } 104 | TTNodeType node_type{}; 105 | if (alpha >= beta) node_type = LOWER_NODE; 106 | else node_type = UPPER_NODE; 107 | t_table.put(board.hash(), QSEARCH_TT_DEPTH, alpha, ply, best_move, QSEARCH_TT_PV_NODE, node_type); 108 | return alpha; 109 | } 110 | 111 | template 112 | i32 pvs(SearchData &sdata, ThreadData &tdata, Position &board, i16 depth, i32 ply, i32 alpha, i32 beta, bool do_null) { 113 | 114 | t_table.prefetch(board.hash()); 115 | 116 | if (ply >= MAX_PLY - 2) return board.evaluate(); 117 | 118 | if ((sdata.nodes_searched % 1024 == 0 && (sdata.stopped || time_elapsed_exceeds(sdata.time_limit, TimeResolution::Milliseconds))) || 119 | (sdata.nodes_searched > sdata.hard_node_limit && sdata.hard_node_limit != -1)) { 120 | 121 | sdata.search_completed = false; 122 | return 0; 123 | } 124 | 125 | init_pv(sdata.pv, ply); 126 | sdata.seldepth = std::max(sdata.seldepth, ply); 127 | 128 | if (ply > 0) { 129 | if (position_is_draw(board, ply)) return 0; 130 | alpha = std::max(alpha, -MATE_SCORE + ply); 131 | beta = std::min(beta, MATE_SCORE - ply); 132 | if (alpha >= beta) return alpha; 133 | } 134 | 135 | i32 alpha_initial = alpha; 136 | bool in_check = board.in_check(); 137 | bool pv_node = alpha != beta - 1; 138 | bool excluding_move = tdata.thread_stack[ply].excluded_move != EMPTY_MOVE; 139 | i32 static_eval = 0; 140 | 141 | if (in_check) depth++; 142 | 143 | if (depth == 0) { 144 | return q_search(sdata, tdata, board, ply, alpha, beta); 145 | } 146 | 147 | TranspositionTableSearchResults tt_probe_results = t_table.probe_for_search(board.hash(), depth, ply); 148 | if (tt_probe_results.entry_found && !pv_node && !excluding_move) { 149 | TranspositionTableEntry tt_entry = tt_probe_results.entry; 150 | if (tt_entry.node_type == EXACT) { 151 | return tt_entry.value; 152 | } else if (tt_entry.node_type == LOWER_NODE) { 153 | alpha = std::max(alpha, tt_entry.value); 154 | } else if (tt_entry.node_type == UPPER_NODE) { 155 | beta = std::min(beta, tt_entry.value); 156 | } 157 | if (alpha >= beta) { 158 | return tt_entry.value; 159 | } 160 | } 161 | 162 | TranspositionTableSearchResults static_eval_tt = t_table.probe_eval(board.hash(), ply); 163 | if (static_eval_tt.entry_found) { 164 | static_eval = static_eval_tt.entry.value; 165 | } else { 166 | static_eval = board.evaluate(); 167 | } 168 | 169 | tdata.thread_stack[ply].static_eval = static_eval; 170 | bool improving = !in_check && ply >= 2 && static_eval >= tdata.thread_stack[ply - 2].static_eval; 171 | 172 | if (!static_eval_tt.entry_found && depth >= 4) 173 | depth -= 1; 174 | 175 | if (!in_check && !pv_node && !excluding_move) { 176 | if (depth < RFP_MAX_DEPTH && static_eval >= beta + RFP_MARGIN * depth) { 177 | return static_eval; 178 | } 179 | } 180 | 181 | if (!pv_node && !in_check && !excluding_move) { 182 | if (depth <= 3 && static_eval - 63 + 182 * depth <= alpha) { 183 | return q_search(sdata, tdata, board, ply, alpha, beta); 184 | } 185 | } 186 | 187 | if (depth >= NMP_MIN_DEPTH && !in_check && !pv_node && !excluding_move && do_null && static_eval >= beta) { 188 | board.play_null(); 189 | 190 | tdata.thread_stack[ply].move = Move(); 191 | i32 reduction = nmp_reduction(depth, beta, static_eval); 192 | i32 depth_prime = std::max(depth - reduction, 0); 193 | i32 null_eval = -pvs<~color>(sdata, tdata, board, depth_prime, ply + 1, -beta, -beta + 1, false); 194 | 195 | board.undo_null(); 196 | if (null_eval >= beta) return null_eval; 197 | } 198 | 199 | MoveList all_legal_moves(board); 200 | if (all_legal_moves.size() == 0) { 201 | if (in_check) return -(MATE_SCORE - ply); 202 | return 0; 203 | } 204 | 205 | ScoredMoves scored_moves = order_moves(all_legal_moves, board, ply, tdata); 206 | 207 | Move best_move = select_move(scored_moves, 0); 208 | i32 moves_played = 0; 209 | i32 value = NEG_INF_CHESS; 210 | for (i32 move_idx = 0; move_idx < static_cast(scored_moves.size()); move_idx++) { 211 | Move legal_move = select_move(scored_moves, move_idx); 212 | 213 | if (legal_move == tdata.thread_stack[ply].excluded_move) continue; 214 | 215 | if (late_move_prune(pv_node, move_idx, depth, improving)) break; 216 | if (futility_prune(static_eval, alpha, value, depth)) break; 217 | if (late_move_prune_quiet(pv_node, move_idx, legal_move, depth)) continue; 218 | if (history_prune(tdata, pv_node, value, depth, legal_move)) continue; 219 | if (see_prune_pvs(board, pv_node, depth, value, legal_move)) continue; 220 | 221 | i32 search_extension = singular_extension(sdata, tdata, board, excluding_move, depth, ply, 222 | alpha, beta, legal_move, tt_probe_results); 223 | 224 | board.play(legal_move); 225 | moves_played += 1; 226 | sdata.nodes_searched += 1; 227 | u64 nodes_before_search = sdata.nodes_searched; 228 | 229 | i32 new_value; 230 | tdata.thread_stack[ply].move = legal_move; 231 | i32 search_depth = std::max(depth + search_extension, 0); 232 | 233 | i32 reduction = lmr_reduction(pv_node, ply, in_check, improving, move_idx, depth, legal_move); 234 | 235 | if (pv_node && move_idx == 0) { 236 | new_value = -pvs<~color>(sdata, tdata, board, search_depth - 1, ply + 1, -beta, -alpha, false); 237 | } else { 238 | new_value = -pvs<~color>(sdata, tdata, board, search_depth - reduction - 1, 239 | ply + 1, -alpha - 1, -alpha, true); 240 | 241 | if (new_value > alpha && reduction > 0) 242 | new_value = -pvs<~color>(sdata, tdata, board, search_depth - 1, 243 | ply + 1, -alpha - 1, -alpha, true); 244 | 245 | if (new_value > alpha && new_value < beta) 246 | new_value = -pvs<~color>(sdata, tdata, board, search_depth - 1, 247 | ply + 1, -beta, -alpha, false); 248 | } 249 | 250 | board.undo(legal_move); 251 | if (!sdata.search_completed) return 0; 252 | if (ply == 0 && tdata.tidx == 0) { 253 | u64 node_diff = sdata.nodes_searched - nodes_before_search; 254 | nodes_spent[legal_move.from()][legal_move.to()] += node_diff; 255 | } 256 | 257 | if (new_value > value) { 258 | value = new_value; 259 | 260 | update_pv(sdata.pv, ply, legal_move); 261 | best_move = legal_move; 262 | 263 | if (value > alpha) { 264 | alpha = value; 265 | update_history(tdata, board, scored_moves, best_move, depth, move_idx, ply); 266 | if (alpha >= beta) { 267 | update_killers(tdata, best_move, ply); 268 | break; 269 | } 270 | } 271 | } 272 | } 273 | if (moves_played == 0) { 274 | if (excluding_move) return alpha; 275 | else { 276 | std::cout << "No Moves Played! Panic Exit" << std::endl; 277 | exit(1); 278 | } 279 | } 280 | TTNodeType node_type = t_table.get_node_type(alpha_initial, beta, value); 281 | t_table.put(board.hash(), depth, value, ply, best_move, pv_node, node_type); 282 | return value; 283 | } 284 | 285 | template 286 | void aspiration_windows(SearchData &sdata, ThreadData &tdata, Position &board, i32 prev_score, i16 depth, i32 time_limit) { 287 | reset_search_data(sdata); 288 | sdata.time_limit = time_limit; 289 | i32 alpha = NEG_INF_CHESS; 290 | i32 beta = POS_INF_CHESS; 291 | i32 delta = ASP_WINDOW_INIT_DELTA; 292 | 293 | if (depth > ASP_WINDOW_MIN_DEPTH) { 294 | alpha = std::max(prev_score - ASP_WINDOW_INIT_WINDOW, NEG_INF_CHESS); 295 | beta = std::min(prev_score + ASP_WINDOW_INIT_WINDOW, POS_INF_CHESS); 296 | } 297 | 298 | while (true) { 299 | if (alpha < -ASP_WINDOW_FULL_SEARCH_BOUNDS) alpha = NEG_INF_CHESS; 300 | if (beta > ASP_WINDOW_FULL_SEARCH_BOUNDS) beta = POS_INF_CHESS; 301 | 302 | sdata.value = pvs(sdata, tdata, board, depth, 0, alpha, beta, false); 303 | i32 score = sdata.value; 304 | if (score <= alpha) { 305 | alpha = std::max(alpha - delta, NEG_INF_CHESS); 306 | beta = (alpha + 3 * beta) / 4; 307 | } else if (score >= beta) { 308 | beta = std::min(beta + delta, POS_INF_CHESS); 309 | depth = std::max(depth - 1, 1); 310 | } else break; 311 | delta += delta * 2 / 3; 312 | } 313 | sdata.best_move = sdata.pv.table[0][0]; 314 | } 315 | 316 | template 317 | void iterative_deepening(SearchData &sdata, ThreadData &tdata, Position &board, SearchParameters ¶ms) { 318 | reset_clock(); 319 | std::memset(nodes_spent.data(), 0, sizeof(nodes_spent)); 320 | sdata.nodes_searched = 0; 321 | for (i32 sub_depth = 1; sub_depth <= params.depth; sub_depth++) { 322 | i32 soft_limit = scale_soft_time_limit(params, sdata, sub_depth); 323 | if (time_elapsed_exceeds(soft_limit, TimeResolution::Milliseconds) || 324 | (sdata.nodes_searched > sdata.soft_node_limit && sdata.soft_node_limit != -1)) { 325 | break; 326 | } 327 | aspiration_windows(sdata, tdata, board, sdata.value, sub_depth, params.hard_time_limit); 328 | if (sdata.search_completed) { 329 | update_best_move_results(sdata, sub_depth, params.debug_info); 330 | } 331 | } 332 | } 333 | 334 | void search(SearchData &sdata, ThreadData &tdata, Position &board, SearchParameters ¶meters) { 335 | if (board.turn() == BLACK) iterative_deepening(sdata, tdata, board, parameters); 336 | else iterative_deepening(sdata, tdata, board, parameters); 337 | } 338 | 339 | template i32 q_search(SearchData &sdata, ThreadData &tdata, Position &board, i32 ply, i32 alpha, i32 beta); 340 | template i32 q_search(SearchData &sdata, ThreadData &tdata, Position &board, i32 ply, i32 alpha, i32 beta); 341 | 342 | template i32 pvs(SearchData &sdata, ThreadData &tdata, Position &board, i16 depth, 343 | i32 ply, i32 alpha, i32 beta, bool do_null); 344 | template i32 pvs(SearchData &sdata, ThreadData &tdata, Position &board, i16 depth, 345 | i32 ply, i32 alpha, i32 beta, bool do_null); 346 | 347 | template void 348 | aspiration_windows(SearchData &sdata, ThreadData &tdata, Position &board, 349 | i32 prev_score, i16 depth, i32 time_limit); 350 | template void 351 | aspiration_windows(SearchData &sdata, ThreadData &tdata, Position &board, 352 | i32 prev_score, i16 depth, i32 time_limit); 353 | 354 | template void 355 | iterative_deepening(SearchData &sdata, ThreadData &tdata, Position &board, SearchParameters ¶ms); 356 | template void 357 | iterative_deepening(SearchData &sdata, ThreadData &tdata, Position &board, SearchParameters ¶ms); 358 | -------------------------------------------------------------------------------- /src/move_search/pvs.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 1/1/23. 3 | // 4 | #pragma once 5 | #include 6 | #include "cstring" 7 | #include "search_params.h" 8 | #include "reductions.h" 9 | #include "pruning.h" 10 | #include "extensions.h" 11 | #include "../evaluation/evaluate.h" 12 | #include "../utils/clock.h" 13 | #include "../board/position.h" 14 | #include "tables/transposition_table.h" 15 | #include "../move_gen/move_generator.h" 16 | #include "move_ordering/move_ordering.h" 17 | 18 | void reset_search_data(SearchData &sdata); 19 | bool position_is_draw(Position &board, i32 ply); 20 | void update_best_move_results(SearchData &sdata, int sub_depth, bool debug); 21 | i32 scale_soft_time_limit(SearchParameters ¶ms, SearchData& sdata, i32 depth); 22 | 23 | template 24 | i32 q_search(SearchData &sdata, ThreadData &tdata, Position &board, i32 ply, i32 alpha, i32 beta); 25 | 26 | template 27 | i32 pvs(SearchData &sdata, ThreadData &tdata, Position &board, i16 depth, i32 ply, i32 alpha, i32 beta, bool do_null); 28 | 29 | template 30 | void aspiration_windows(SearchData &sdata, ThreadData &tdata, Position &board, i32 prev_score, i16 depth, i32 time_limit); 31 | 32 | template 33 | void iterative_deepening(SearchData &sdata, ThreadData &tdata, Position &board, SearchParameters ¶ms); 34 | 35 | void search(SearchData &sdata, ThreadData &tdata, Position &board, SearchParameters ¶meters); -------------------------------------------------------------------------------- /src/move_search/reductions.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 3/22/23. 3 | // 4 | 5 | #include "reductions.h" 6 | #include "tables/lmr_table.h" 7 | #include "../move_gen/types/move.h" 8 | #include 9 | 10 | i32 lmr_reduction(bool pv_node, i32 ply, bool in_check, bool improving, i32 move_idx, 11 | i32 depth, Move legal_move) { 12 | i32 reduction = 0; 13 | i32 lmr_depth = (pv_node || ply == 0) ? 5 : 3; 14 | if (depth >= 3 && move_idx > lmr_depth && !in_check) { 15 | reduction = static_cast(lmr_table[legal_move.is_quiet()][depth][move_idx]); 16 | reduction -= pv_node; 17 | reduction += !improving; 18 | reduction = std::clamp(reduction, 0, depth - 1); 19 | } 20 | return reduction; 21 | } 22 | 23 | i32 nmp_reduction(i32 depth, i32 beta, i32 static_eval) { 24 | auto reduction = 3 + depth / 3 + std::min((static_eval - beta) / 200, 3); 25 | return std::max(reduction, 3); 26 | } 27 | -------------------------------------------------------------------------------- /src/move_search/reductions.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | #include "../move_gen/types/move.h" 5 | 6 | i32 lmr_reduction(bool pv_node, i32 ply, bool in_check, bool improving, i32 move_idx, 7 | i32 depth, Move legal_move); 8 | i32 nmp_reduction(i32 depth, i32 beta, i32 static_eval); 9 | -------------------------------------------------------------------------------- /src/move_search/search_constants.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../types.h" 3 | 4 | constexpr auto POS_INF_CHESS = 1000000; 5 | constexpr auto NEG_INF_CHESS = -POS_INF_CHESS; 6 | constexpr auto MATE_SCORE = POS_INF_CHESS / 10; 7 | constexpr i16 MAX_DEPTH = 100; 8 | constexpr i16 MAX_PLY = MAX_DEPTH; 9 | constexpr auto MATE_BOUND = MATE_SCORE - MAX_DEPTH; 10 | constexpr auto DEFAULT_SEARCH_TIME = 86'400'000; 11 | -------------------------------------------------------------------------------- /src/move_search/search_params.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 1/1/23. 3 | // 4 | #pragma once 5 | #include "../types.h" 6 | 7 | constexpr auto RFP_MARGIN = 75; 8 | constexpr auto RFP_MAX_DEPTH = 9; 9 | 10 | constexpr auto LMR_BASE_CAPTURE = 1.40; 11 | constexpr auto LMR_DIVISOR_CAPTURE = 1.80; 12 | constexpr auto LMR_BASE_QUIET = 1.50; 13 | constexpr auto LMR_DIVISOR_QUIET = 1.75; 14 | 15 | constexpr auto NMP_MIN_DEPTH = 3; 16 | 17 | constexpr auto LMP_MIN_DEPTH = 3; 18 | constexpr auto LMP_DEPTH_MULTIPLIER = 12; 19 | 20 | constexpr auto Q_SEARCH_FUTILITY_MARGIN = 60; 21 | 22 | constexpr auto ASP_WINDOW_MIN_DEPTH = 6; 23 | constexpr auto ASP_WINDOW_INIT_WINDOW = 12; 24 | constexpr auto ASP_WINDOW_INIT_DELTA = 16; 25 | constexpr auto ASP_WINDOW_FULL_SEARCH_BOUNDS = 3500; 26 | 27 | constexpr auto SEE_PVS_MIN_DEPTH = 7; 28 | constexpr auto SEE_PVS_QUIET_MARGIN = -50; 29 | constexpr auto SEE_PVS_TACTICAL_MARGIN = -90; 30 | constexpr auto SEE_Q_MARGIN = 1; 31 | 32 | constexpr auto FP_COEFFICIENT = 100; 33 | constexpr auto FP_MARGIN = 75; 34 | constexpr auto FP_DEPTH = 6; -------------------------------------------------------------------------------- /src/move_search/tables/history_table.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 3/8/23. 3 | // 4 | #include "history_table.h" 5 | 6 | void init_history(ThreadData &tdata) { 7 | for (i32 i = 0; i < NSQUARES; i++) { 8 | for (i32 j = 0; j < NSQUARES; j++) { 9 | tdata.history[WHITE][i][j] = 0; 10 | tdata.history[BLACK][i][j] = 0; 11 | } 12 | } 13 | 14 | for (i32 previous_move_ptype = 0; previous_move_ptype < NPIECES; previous_move_ptype++) { 15 | for (i32 previous_move_to = 0; previous_move_to < NSQUARES; previous_move_to++) { 16 | for (i32 move_ptype = 0; move_ptype < NPIECES; move_ptype++) { 17 | for (i32 move_to = 0; move_to < NSQUARES; move_to++) { 18 | tdata.continuation_history[previous_move_ptype][previous_move_to][move_ptype][move_to] = 0; 19 | } 20 | } 21 | } 22 | } 23 | 24 | for (i32 attacking = 0; attacking < NPIECES; attacking++) { 25 | for (i32 capture_to = 0; capture_to < NSQUARES; capture_to++) { 26 | for (i32 piece_attacked = 0; piece_attacked < NPIECES; piece_attacked++) { 27 | tdata.capture_history[attacking][capture_to][piece_attacked] = 0; 28 | } 29 | } 30 | } 31 | 32 | for (i32 i = 0; i < MAX_PLY; i++) { 33 | for (i32 j = 0; j < 2; j++) { 34 | tdata.killers[i][j] = Move(); 35 | } 36 | } 37 | } 38 | 39 | void update_history_entry(i32& history_entry, i32 bonus) { 40 | history_entry -= (history_entry * abs(bonus)) / 324; 41 | history_entry += bonus * 32; 42 | } 43 | 44 | void update_killers(ThreadData &tdata, Move &best_move, i32 ply) { 45 | tdata.killers[ply][1] = tdata.killers[ply][0]; 46 | tdata.killers[ply][0] = best_move; 47 | } 48 | 49 | void update_continuation_history(ThreadData &tdata, Position &board, Move previous_move, Move attempted_move, i32 bonus) { 50 | update_history_entry( 51 | tdata.continuation_history 52 | [board.piece_at(previous_move.from())][previous_move.to()] 53 | [board.piece_at(attempted_move.from())][attempted_move.to()], 54 | bonus); 55 | } 56 | 57 | void update_capture_history(ThreadData &tdata, Position &board, Move attempted_move, i32 bonus) { 58 | update_history_entry( 59 | tdata.capture_history 60 | [board.piece_at(attempted_move.from())] 61 | [attempted_move.to()] 62 | [board.piece_at(attempted_move.to())], 63 | bonus); 64 | } 65 | -------------------------------------------------------------------------------- /src/move_search/tables/history_table.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 2/14/23. 3 | // 4 | #pragma once 5 | 6 | #include "../../board/types/board_types.h" 7 | #include "../../board/types/square.h" 8 | #include "../../board/types/piece.h" 9 | #include "../search_constants.h" 10 | #include "../move_ordering/ordering_constants.h" 11 | #include "../../board/position.h" 12 | #include "../types.h" 13 | #include "../../types.h" 14 | 15 | extern void init_history(ThreadData &tdata); 16 | extern void update_history_entry(int& history_entry, int bonus); 17 | extern void 18 | update_continuation_history(ThreadData &tdata, Position &board, Move previous_move, Move attempted_move, int bonus); 19 | extern void update_capture_history(ThreadData &tdata, Position &board, Move attempted_move, int bonus); 20 | extern void update_killers(ThreadData &tdata, Move &best_move, int ply); 21 | 22 | template 23 | void 24 | update_history(ThreadData &tdata, Position &board, ScoredMoves &ordered_moves, Move &best_move, int depth, int move_idx, 25 | int ply) { 26 | 27 | const Move one_move_ago = ply > 0 ? tdata.thread_stack[ply - 1].move : Move(); 28 | const Move two_move_ago = ply > 1 ? tdata.thread_stack[ply - 2].move : Move(); 29 | 30 | int history_bonus = depth * depth + depth - 1; 31 | 32 | if (best_move.is_quiet()) { 33 | update_history_entry(tdata.history[color][best_move.from()][best_move.to()], history_bonus); 34 | if (one_move_ago != Move()) 35 | update_continuation_history(tdata, board, one_move_ago, best_move, 36 | history_bonus); 37 | if (two_move_ago != Move()) 38 | update_continuation_history(tdata, board, two_move_ago, best_move, 39 | history_bonus); 40 | } else if (best_move.is_capture() && best_move.type() != ENPASSANT) { 41 | update_capture_history(tdata, board, best_move, history_bonus); 42 | } 43 | 44 | 45 | // We stop at idx = move_idx - 1 which is where the best move is so we don't need a condition to avoid it in the loop. 46 | for (int idx = 0; idx < move_idx; idx++) { 47 | const Move& move = ordered_moves[idx].move; 48 | if (move.is_quiet()) { 49 | update_history_entry(tdata.history[color][move.from()][move.to()], -history_bonus); 50 | if (one_move_ago != Move()) 51 | update_continuation_history(tdata, board, one_move_ago, move, 52 | -history_bonus); 53 | if (two_move_ago != Move()) 54 | update_continuation_history(tdata, board, two_move_ago, move, 55 | -history_bonus); 56 | } else if (move.is_capture() && best_move.type() != ENPASSANT) { 57 | update_capture_history(tdata, board, move, -history_bonus); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/move_search/tables/lmr_table.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 3/18/23. 3 | // 4 | 5 | #include 6 | #include "lmr_table.h" 7 | 8 | array, MAX_DEPTH + 1>, 2> lmr_table; 9 | 10 | void init_lmr_table() { 11 | for (int depth = 0; depth < MAX_DEPTH; depth++) { 12 | for (int move_idx = 0; move_idx < MAX_DEPTH; move_idx++) { 13 | lmr_table[0][depth][move_idx] = LMR_BASE_CAPTURE + log(depth) * log(move_idx) / LMR_DIVISOR_CAPTURE; 14 | lmr_table[1][depth][move_idx] = LMR_BASE_QUIET + log(depth) * log(move_idx) / LMR_DIVISOR_QUIET; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/move_search/tables/lmr_table.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../search_constants.h" 3 | #include "../search_params.h" 4 | 5 | extern array, MAX_DEPTH + 1>, 2> lmr_table; 6 | extern void init_lmr_table(); 7 | -------------------------------------------------------------------------------- /src/move_search/tables/pv_table.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 1/19/23. 3 | // 4 | #pragma once 5 | #include "../search_constants.h" 6 | #include "../../move_gen/types/move.h" 7 | 8 | using PVTable = array, MAX_DEPTH>; 9 | 10 | struct PV { 11 | array length{}; 12 | PVTable table{}; 13 | }; 14 | 15 | inline void init_pv(PV& pv, i32 ply) { 16 | pv.length[ply] = ply; 17 | } 18 | 19 | inline void update_pv(PV& pv, i32 ply, Move best_move) { 20 | pv.table[ply][ply] = best_move; 21 | for (int next_ply = ply + 1; next_ply < pv.length[ply + 1]; next_ply++) { 22 | pv.table[ply][next_ply] = pv.table[ply + 1][next_ply]; 23 | } 24 | pv.length[ply] = pv.length[ply + 1]; 25 | } 26 | -------------------------------------------------------------------------------- /src/move_search/tables/transposition_table.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 1/15/23. 3 | // 4 | #include "transposition_table.h" 5 | 6 | using enum TTNodeType; 7 | TranspositionTable::TranspositionTable(i32 mb) { 8 | resize(mb); 9 | } 10 | 11 | void TranspositionTable::reset_table() { 12 | TranspositionTableEntry default_entry = TranspositionTableEntry(); 13 | default_entry.value = 0; 14 | default_entry.zobrist_hash = 0; 15 | default_entry.depth = 0; 16 | default_entry.best_move = Move(); 17 | default_entry.node_type = EXACT; 18 | for (auto & i : transposition_table) { 19 | i = default_entry; 20 | } 21 | } 22 | 23 | i32 TranspositionTable::correct_mate_for_retrieval(i32 score, i32 ply) { 24 | if (score < -MATE_BOUND) score += ply; 25 | else if (score > MATE_BOUND) score -= ply; 26 | return score; 27 | } 28 | 29 | i32 TranspositionTable::correct_mate_for_storage(i32 score, i32 ply) { 30 | if (score < -MATE_BOUND) score -= ply; 31 | else if (score > MATE_BOUND) score += ply; 32 | return score; 33 | } 34 | 35 | u64 TranspositionTable::get_index(u64 zobrist_hash) { 36 | return zobrist_hash % entry_count(); 37 | } 38 | 39 | TTNodeType TranspositionTable::get_node_type(i32 alpha_initial, i32 beta, i32 value) { 40 | TTNodeType node_type; 41 | if (value <= alpha_initial) node_type = UPPER_NODE; 42 | else if (value >= beta) node_type = LOWER_NODE; 43 | else node_type = EXACT; 44 | return node_type; 45 | } 46 | 47 | void TranspositionTable::put(ZobristHash hash, i16 depth, i32 score, i32 ply, Move best_move, bool pv_node, 48 | TTNodeType node_type) { 49 | 50 | score = correct_mate_for_storage(score, ply); 51 | TranspositionTableEntry previous_entry = transposition_table[get_index(hash)]; 52 | 53 | if (node_type == EXACT || 54 | previous_entry.zobrist_hash != hash || 55 | depth + 7 + 2 * pv_node > previous_entry.depth - 4) { 56 | TranspositionTableEntry entry; 57 | entry.zobrist_hash = hash; 58 | entry.depth = depth; 59 | entry.value = score; 60 | entry.node_type = node_type; 61 | entry.best_move = best_move; 62 | transposition_table[get_index(hash)] = entry; 63 | } 64 | } 65 | 66 | TranspositionTableSearchResults 67 | TranspositionTable::probe_for_move_ordering(ZobristHash hash) { 68 | TranspositionTableEntry entry = transposition_table[get_index(hash)]; 69 | TranspositionTableSearchResults results; 70 | results.entry_found = false; 71 | if (entry.zobrist_hash == hash) { 72 | results.entry_found = true; 73 | results.entry = entry; 74 | } 75 | return results; 76 | } 77 | 78 | TranspositionTableSearchResults 79 | TranspositionTable::probe_for_search(ZobristHash hash, i32 depth, i32 ply) { 80 | 81 | TranspositionTableSearchResults results; 82 | results.entry_found = false; 83 | 84 | TranspositionTableEntry entry = transposition_table[get_index(hash)]; 85 | if (entry.zobrist_hash == hash && entry.depth >= depth && ply != 0) { 86 | results.entry_found = true; 87 | results.entry = entry; 88 | results.entry.value = correct_mate_for_retrieval(results.entry.value, ply); 89 | } 90 | return results; 91 | } 92 | 93 | TranspositionTableSearchResults TranspositionTable::probe_eval(ZobristHash hash, i32 ply) { 94 | TranspositionTableEntry entry = transposition_table[get_index(hash)]; 95 | TranspositionTableSearchResults results; 96 | results.entry_found = false; 97 | if (entry.zobrist_hash == hash) { 98 | results.entry_found = true; 99 | results.entry = entry; 100 | results.entry.value = correct_mate_for_retrieval(results.entry.value, ply); 101 | } 102 | return results; 103 | } 104 | 105 | usize TranspositionTable::mb_to_entries(i32 mb) { 106 | usize bytes = static_cast(mb) * 1'048'576; 107 | return static_cast(bytes / sizeof(TranspositionTableEntry)); 108 | } 109 | 110 | void TranspositionTable::resize(i32 mb) { 111 | usize entries = mb_to_entries(mb); 112 | transposition_table.resize(entries); 113 | transposition_table.shrink_to_fit(); 114 | reset_table(); 115 | } 116 | 117 | size_t TranspositionTable::entry_count() { 118 | return transposition_table.size(); 119 | } 120 | 121 | void TranspositionTable::prefetch(ZobristHash hash) { 122 | __builtin_prefetch(&transposition_table[get_index(hash)]); 123 | } 124 | 125 | TranspositionTable t_table = TranspositionTable(); 126 | -------------------------------------------------------------------------------- /src/move_search/tables/transposition_table.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "../../board/types/board_types.h" 3 | #include "../search_constants.h" 4 | #include "../../move_gen/types/move.h" 5 | #include 6 | 7 | enum class TTNodeType { 8 | EXACT, 9 | UPPER_NODE, 10 | LOWER_NODE 11 | }; 12 | 13 | struct TranspositionTableEntry { 14 | ZobristHash zobrist_hash{}; 15 | i16 depth{}; 16 | i32 value{}; 17 | TTNodeType node_type{}; 18 | Move best_move{}; 19 | }; 20 | 21 | struct TranspositionTableSearchResults { 22 | TranspositionTableEntry entry; 23 | bool entry_found{}; 24 | }; 25 | 26 | constexpr short QSEARCH_TT_DEPTH = -1; 27 | constexpr bool QSEARCH_TT_PV_NODE = false; 28 | 29 | // More to learn from here: https://github.com/kobolabs/stockfish/blob/master/tt.cpp 30 | class TranspositionTable { 31 | public: 32 | TTNodeType get_node_type(i32 alpha_initial, i32 beta, i32 value); 33 | i32 correct_mate_for_storage(i32 score, i32 ply); 34 | i32 correct_mate_for_retrieval(i32 score, i32 ply); 35 | void put(ZobristHash hash, i16 depth, i32 score, i32 ply, Move best_move, bool pv_node, 36 | TTNodeType node_type); 37 | void reset_table(); 38 | void resize(i32 mb); 39 | void prefetch(ZobristHash hash); 40 | TranspositionTableSearchResults probe_for_move_ordering(ZobristHash hash); 41 | TranspositionTableSearchResults probe_for_search(ZobristHash hash, i32 depth, i32 ply); 42 | TranspositionTableSearchResults probe_eval(ZobristHash hash, i32 ply); 43 | usize mb_to_entries(i32 mb); 44 | explicit TranspositionTable(i32 mb = 64); 45 | size_t entry_count(); 46 | 47 | private: 48 | u64 get_index(u64 zobrist_hash); 49 | std::vector transposition_table; 50 | }; 51 | 52 | extern TranspositionTable t_table; 53 | -------------------------------------------------------------------------------- /src/move_search/types.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 4/15/23. 3 | // 4 | #include "types.h" 5 | #include "iostream" 6 | #include "../move_gen/types/move.h" 7 | 8 | std::ostream& operator<<(std::ostream& os, const Line& line) { 9 | for (const Move& i : line) { 10 | if (i == Move()) break; 11 | os << i << " "; 12 | } 13 | return os; 14 | } 15 | -------------------------------------------------------------------------------- /src/move_search/types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "move_ordering/ordering_constants.h" 5 | #include "tables/pv_table.h" 6 | 7 | struct ScoredMove { 8 | Move move; 9 | i32 score{}; 10 | }; 11 | 12 | using ScoredMoves = std::vector; 13 | 14 | struct SearchParameters { 15 | i16 depth = MAX_DEPTH; 16 | std::atomic hard_time_limit{DEFAULT_SEARCH_TIME}; 17 | std::atomic soft_time_limit{DEFAULT_SEARCH_TIME}; 18 | i32 thread_count = 1; 19 | bool debug_info = false; 20 | }; 21 | 22 | using Line = Move[MAX_DEPTH]; 23 | extern std::ostream& operator<<(std::ostream& os, const Line& line); 24 | 25 | struct SearchStack { 26 | Move move{}; 27 | Move excluded_move{}; 28 | i32 static_eval{}; 29 | }; 30 | 31 | struct ThreadData { 32 | array, NSQUARES>, NCOLORS> history{}; 33 | array, NPIECES>, NSQUARES>, NPIECES> continuation_history{}; 34 | array, NSQUARES>, NPIECES> capture_history{}; 35 | array, MAX_PLY> killers{}; 36 | 37 | array thread_stack{}; 38 | 39 | i32 tidx = 0; 40 | }; 41 | 42 | struct SearchData { 43 | Move best_move; 44 | Move final_best_move; 45 | bool search_completed{}; 46 | i32 value{}; 47 | i32 final_value{}; 48 | // triangular-table-table 49 | PV pv{}; 50 | i64 nodes_searched{}; 51 | i32 seldepth{}; 52 | 53 | std::atomic time_limit{0}; 54 | std::atomic stopped{false}; 55 | i64 hard_node_limit = -1; 56 | i64 soft_node_limit = -1; 57 | 58 | SearchData& operator=(const SearchData& other) { 59 | best_move = other.best_move; 60 | final_best_move = other.final_best_move; 61 | search_completed = other.search_completed; 62 | value = other.value; 63 | final_value = other.final_value; 64 | 65 | pv = other.pv; 66 | nodes_searched = other.nodes_searched; 67 | seldepth = other.seldepth; 68 | 69 | time_limit = other.time_limit.load(); 70 | stopped = other.stopped.load(); 71 | hard_node_limit = other.hard_node_limit; 72 | soft_node_limit = other.soft_node_limit; 73 | return *this; 74 | } 75 | }; 76 | -------------------------------------------------------------------------------- /src/types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | using u8 = std::uint8_t; 7 | using u16 = std::uint16_t; 8 | using u32 = std::uint32_t; 9 | using u64 = std::uint64_t; 10 | 11 | using i8 = std::int8_t; 12 | using i16 = std::int16_t; 13 | using i32 = std::int32_t; 14 | using i64 = std::int64_t; 15 | 16 | using f32 = float; 17 | using f64 = double; 18 | 19 | using usize = std::size_t; 20 | 21 | using std::string; 22 | using std::array; -------------------------------------------------------------------------------- /src/uci_interpreter/bench_fens.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "iostream" 3 | 4 | constexpr i32 BENCH_SIZE = 50; 5 | constexpr const char *BENCH_FENS[BENCH_SIZE] = { 6 | "r3k2r/2pb1ppp/2pp1q2/p7/1nP1B3/1P2P3/P2N1PPP/R2QK2R w KQkq a6 0 14", 7 | "4rrk1/2p1b1p1/p1p3q1/4p3/2P2n1p/1P1NR2P/PB3PP1/3R1QK1 b - - 2 24", 8 | "r3qbrk/6p1/2b2pPp/p3pP1Q/PpPpP2P/3P1B2/2PB3K/R5R1 w - - 16 42", 9 | "6k1/1R3p2/6p1/2Bp3p/3P2q1/P7/1P2rQ1K/5R2 b - - 4 44", 10 | "8/8/1p2k1p1/3p3p/1p1P1P1P/1P2PK2/8/8 w - - 3 54", 11 | "7r/2p3k1/1p1p1qp1/1P1Bp3/p1P2r1P/P7/4R3/Q4RK1 w - - 0 36", 12 | "r1bq1rk1/pp2b1pp/n1pp1n2/3P1p2/2P1p3/2N1P2N/PP2BPPP/R1BQ1RK1 b - - 2 10", 13 | "3r3k/2r4p/1p1b3q/p4P2/P2Pp3/1B2P3/3BQ1RP/6K1 w - - 3 87", 14 | "2r4r/1p4k1/1Pnp4/3Qb1pq/8/4BpPp/5P2/2RR1BK1 w - - 0 42", 15 | "4q1bk/6b1/7p/p1p4p/PNPpP2P/KN4P1/3Q4/4R3 b - - 0 37", 16 | "2q3r1/1r2pk2/pp3pp1/2pP3p/P1Pb1BbP/1P4Q1/R3NPP1/4R1K1 w - - 2 34", 17 | "1r2r2k/1b4q1/pp5p/2pPp1p1/P3Pn2/1P1B1Q1P/2R3P1/4BR1K b - - 1 37", 18 | "r3kbbr/pp1n1p1P/3ppnp1/q5N1/1P1pP3/P1N1B3/2P1QP2/R3KB1R b KQkq b3 0 17", 19 | "8/6pk/2b1Rp2/3r4/1R1B2PP/P5K1/8/2r5 b - - 16 42", 20 | "1r4k1/4ppb1/2n1b1qp/pB4p1/1n1BP1P1/7P/2PNQPK1/3RN3 w - - 8 29", 21 | "8/p2B4/PkP5/4p1pK/4Pb1p/5P2/8/8 w - - 29 68", 22 | "3r4/ppq1ppkp/4bnp1/2pN4/2P1P3/1P4P1/PQ3PBP/R4K2 b - - 2 20", 23 | "5rr1/4n2k/4q2P/P1P2n2/3B1p2/4pP2/2N1P3/1RR1K2Q w - - 1 49", 24 | "1r5k/2pq2p1/3p3p/p1pP4/4QP2/PP1R3P/6PK/8 w - - 1 51", 25 | "q5k1/5ppp/1r3bn1/1B6/P1N2P2/BQ2P1P1/5K1P/8 b - - 2 34", 26 | "r1b2k1r/5n2/p4q2/1ppn1Pp1/3pp1p1/NP2P3/P1PPBK2/1RQN2R1 w - - 0 22", 27 | "r1bqk2r/pppp1ppp/5n2/4b3/4P3/P1N5/1PP2PPP/R1BQKB1R w KQkq - 0 5", 28 | "r1bqr1k1/pp1p1ppp/2p5/8/3N1Q2/P2BB3/1PP2PPP/R3K2n b Q - 1 12", 29 | "r1bq2k1/p4r1p/1pp2pp1/3p4/1P1B3Q/P2B1N2/2P3PP/4R1K1 b - - 2 19", 30 | "r4qk1/6r1/1p4p1/2ppBbN1/1p5Q/P7/2P3PP/5RK1 w - - 2 25", 31 | "r7/6k1/1p6/2pp1p2/7Q/8/p1P2K1P/8 w - - 0 32", 32 | "r3k2r/ppp1pp1p/2nqb1pn/3p4/4P3/2PP4/PP1NBPPP/R2QK1NR w KQkq - 1 5", 33 | "3r1rk1/1pp1pn1p/p1n1q1p1/3p4/Q3P3/2P5/PP1NBPPP/4RRK1 w - - 0 12", 34 | "5rk1/1pp1pn1p/p3Brp1/8/1n6/5N2/PP3PPP/2R2RK1 w - - 2 20", 35 | "8/1p2pk1p/p1p1r1p1/3n4/8/5R2/PP3PPP/4R1K1 b - - 3 27", 36 | "8/4pk2/1p1r2p1/p1p4p/Pn5P/3R4/1P3PP1/4RK2 w - - 1 33", 37 | "8/5k2/1pnrp1p1/p1p4p/P6P/4R1PK/1P3P2/4R3 b - - 1 38", 38 | "8/8/1p1kp1p1/p1pr1n1p/P6P/1R4P1/1P3PK1/1R6 b - - 15 45", 39 | "8/8/1p1k2p1/p1prp2p/P2n3P/6P1/1P1R1PK1/4R3 b - - 5 49", 40 | "8/8/1p4p1/p1p2k1p/P2npP1P/4K1P1/1P6/3R4 w - - 6 54", 41 | "8/8/1p4p1/p1p2k1p/P2n1P1P/4K1P1/1P6/6R1 b - - 6 59", 42 | "8/5k2/1p4p1/p1pK3p/P2n1P1P/6P1/1P6/4R3 b - - 14 63", 43 | "8/1R6/1p1K1kp1/p6p/P1p2P1P/6P1/1Pn5/8 w - - 0 67", 44 | "1rb1rn1k/p3q1bp/2p3p1/2p1p3/2P1P2N/PP1RQNP1/1B3P2/4R1K1 b - - 4 23", 45 | "4rrk1/pp1n1pp1/q5p1/P1pP4/2n3P1/7P/1P3PB1/R1BQ1RK1 w - - 3 22", 46 | "r2qr1k1/pb1nbppp/1pn1p3/2ppP3/3P4/2PB1NN1/PP3PPP/R1BQR1K1 w - - 4 12", 47 | "2r2k2/8/4P1R1/1p6/8/P4K1N/7b/2B5 b - - 0 55", 48 | "6k1/5pp1/8/2bKP2P/2P5/p4PNb/B7/8 b - - 1 44", 49 | "2rqr1k1/1p3p1p/p2p2p1/P1nPb3/2B1P3/5P2/1PQ2NPP/R1R4K w - - 3 25", 50 | "r1b2rk1/p1q1ppbp/6p1/2Q5/8/4BP2/PPP3PP/2KR1B1R b - - 2 14", 51 | "6r1/5k2/p1b1r2p/1pB1p1p1/1Pp3PP/2P1R1K1/2P2P2/3R4 w - - 1 36", 52 | "rnbqkb1r/pppppppp/5n2/8/2PP4/8/PP2PPPP/RNBQKBNR b KQkq c3 0 2", 53 | "2rr2k1/1p4bp/p1q1p1p1/4Pp1n/2PB4/1PN3P1/P3Q2P/2RR2K1 w - f6 0 20", 54 | "3br1k1/p1pn3p/1p3n2/5pNq/2P1p3/1PN3PP/P2Q1PB1/4R1K1 w - - 0 23", 55 | "2r2b2/5p2/5k2/p1r1pP2/P2pB3/1P3P2/K1P3R1/7R w - - 23 93" 56 | }; 57 | -------------------------------------------------------------------------------- /src/uci_interpreter/datagen.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 8/18/23. 3 | // 4 | #include 5 | #include "datagen.h" 6 | #include 7 | #include "../engine.h" 8 | 9 | Position datagen_random_game(std::atomic& run) { 10 | std::random_device dev; 11 | auto seed = dev(); 12 | std::mt19937 rng(seed); 13 | urng heads_tails(0, 1); 14 | 15 | Position board{INITIAL_BOARD_FEN}; 16 | usize num_initial_moves = heads_tails(rng) % 2 + 8; 17 | 18 | bool board_generated = false; 19 | while (!board_generated && run.load()) { 20 | board.set_fen(INITIAL_BOARD_FEN); 21 | for (usize idx = 0; idx < num_initial_moves; idx++) { 22 | board_generated = true; 23 | if (board.turn() == WHITE) { 24 | MoveList move_list(board); 25 | 26 | if (move_list.size() == 0) { 27 | board_generated = false; 28 | break; 29 | } 30 | urng idxrng(0, move_list.size() - 1); 31 | usize ridx = idxrng(rng); 32 | board.play(move_list[ridx]); 33 | } else { 34 | MoveList move_list(board); 35 | 36 | if (move_list.size() == 0) { 37 | board_generated = false; 38 | break; 39 | } 40 | urng idxrng(0, move_list.size() - 1); 41 | usize ridx = idxrng(rng); 42 | board.play(move_list[ridx]); 43 | } 44 | } 45 | } 46 | return board; 47 | } 48 | 49 | void single_thread_datagen(auto output_file_path, 50 | std::atomic& total_fens_collected, std::atomic& run) { 51 | auto tdata = std::make_unique(); 52 | SearchParameters sparams{}; 53 | 54 | std::ofstream output_fens; 55 | std::cout << "Opening " << output_file_path << std::endl; 56 | output_fens.open(output_file_path, std::ios_base::out | std::ios_base::app); 57 | std::vector collected_fens{}; 58 | collected_fens.reserve(256); 59 | 60 | while (run.load()) { 61 | Position board = datagen_random_game(run); 62 | initialize_engine(); 63 | init_history(*tdata); 64 | 65 | collected_fens.clear(); 66 | usize legal_move_size; 67 | do { 68 | if (board.turn() == WHITE) { 69 | MoveList move_list(board); 70 | legal_move_size = move_list.size(); 71 | } else { 72 | MoveList move_list(board); 73 | legal_move_size = move_list.size(); 74 | } 75 | 76 | if (legal_move_size != 0) { 77 | SearchData sdata = {}; 78 | sparams.soft_time_limit = sparams.hard_time_limit = 86'400'000; 79 | sdata.hard_node_limit = 8'000'000; 80 | sdata.soft_node_limit = 5'000; 81 | search(sdata, *tdata, board, sparams); 82 | 83 | if (!sdata.final_best_move.is_capture() && !board.in_check() && !board.in_check()) { 84 | if (board.turn() == BLACK) sdata.final_value *= -1; 85 | collected_fens.push_back({board.fen(), sdata.final_value}); 86 | } 87 | 88 | if (board.turn() == WHITE) board.play(sdata.final_best_move); 89 | else board.play(sdata.final_best_move); 90 | } 91 | } while (run.load() && legal_move_size != 0 && !board.has_repetition(Position::THREE_FOLD) && board.fifty_move_rule() < 100); 92 | 93 | if (!run.load()) break; 94 | 95 | DatagenGameResults game_result; 96 | if (!board.has_repetition(Position::THREE_FOLD) && board.fifty_move_rule() < 100) { 97 | if (board.in_check()) game_result = DatagenGameResults::BLACK_WON; 98 | else if (board.in_check()) game_result = DatagenGameResults::WHITE_WON; 99 | else game_result = DatagenGameResults::DRAW; 100 | } else game_result = DatagenGameResults::DRAW; 101 | 102 | auto skip_one = false; 103 | for (const auto& game_info : collected_fens) { 104 | if (!skip_one) { 105 | skip_one = true; 106 | continue; 107 | } 108 | if (std::abs(game_info.value) >= MATE_BOUND) break; 109 | 110 | total_fens_collected += 1; 111 | if (game_result == DatagenGameResults::DRAW) { 112 | output_fens << game_info.fen << " | " << game_info.value << " | " << 0.5 << "\n"; 113 | continue; 114 | } 115 | 116 | string game_score = "1.0"; 117 | if (game_result == DatagenGameResults::BLACK_WON) game_score = "0.0"; 118 | output_fens << game_info.fen << " | " << game_info.value << " | " << game_score << "\n"; 119 | } 120 | } 121 | output_fens.close(); 122 | } 123 | 124 | void datagen(const i32 thread_count, const string& output_directory) { 125 | std::vector threads{}; 126 | std::atomic total_fens_collected{0}; 127 | std::atomic run{true}; 128 | for (i32 idx = 0; idx < thread_count; idx += 1) { 129 | std::filesystem::path root(output_directory); 130 | std::filesystem::path file_name("fens" + std::to_string(idx) + ".txt"); 131 | auto output_path = root / file_name; 132 | std::cout << output_path << "\n"; 133 | threads.emplace_back( 134 | [output_path, &total_fens_collected, &run] { 135 | single_thread_datagen(output_path, total_fens_collected, run); 136 | }); 137 | } 138 | 139 | auto check_run_state = [&run] { 140 | std::cout.setf(std::ios::unitbuf); 141 | string input_line{}; 142 | while (std::getline(std::cin, input_line)) { 143 | std::this_thread::sleep_for(std::chrono::milliseconds(1000)); 144 | if (input_line.empty()) continue; 145 | run.store(false); 146 | break; 147 | } 148 | }; 149 | 150 | threads.emplace_back(check_run_state); 151 | 152 | auto dgen_start_time = std::chrono::high_resolution_clock::now(); 153 | while (run) { 154 | std::this_thread::sleep_for(std::chrono::milliseconds(1000)); 155 | auto current_time = std::chrono::high_resolution_clock::now(); 156 | std::chrono::duration diff = current_time - dgen_start_time; 157 | double seconds_elapsed = diff.count(); 158 | usize fps = total_fens_collected / seconds_elapsed; 159 | std::cout << "Total Fens Collected: " << total_fens_collected << " FENS/S: " << fps << "\n"; 160 | } 161 | 162 | for (auto& t : threads) t.join(); 163 | } 164 | -------------------------------------------------------------------------------- /src/uci_interpreter/datagen.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "../move_search/pvs.h" 7 | #include "../types.h" 8 | #include "../utils/fen_constants.h" 9 | 10 | using urng = std::uniform_int_distribution; 11 | 12 | struct DatagenGameInfo { 13 | string fen; 14 | i32 value; 15 | }; 16 | 17 | enum class DatagenGameResults { 18 | WHITE_WON, 19 | BLACK_WON, 20 | DRAW 21 | }; 22 | 23 | Position datagen_random_game(std::atomic& run); 24 | void single_thread_datagen(const string& output_file_path, 25 | std::atomic& total_fens_collected, std::atomic& run); 26 | void datagen(const i32 thread_count, const string& output_directory); 27 | -------------------------------------------------------------------------------- /src/uci_interpreter/time_manager.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 6/10/23. 3 | // 4 | #include "time_manager.h" 5 | 6 | array, NSQUARES> nodes_spent{}; 7 | 8 | TimeBounds allocate_time(i32 time_remaining, i32 increment, i32 moves_to_go) { 9 | TimeBounds time_bounds{}; 10 | if (moves_to_go == -1) time_bounds = allocate_time_standard(time_remaining, increment); 11 | else time_bounds = allocate_time_moves_to_go(time_remaining, moves_to_go); 12 | return time_bounds; 13 | } 14 | 15 | TimeBounds allocate_time_standard(i32 time_remaining, i32 increment) { 16 | const auto soft_limit = (time_remaining / 40) + (3 * increment / 4); 17 | const auto hard_limit = (time_remaining / 5) + (3 * increment / 4); 18 | return {soft_limit, hard_limit}; 19 | } 20 | 21 | TimeBounds allocate_time_moves_to_go(i32 time_remaining, i32 moves_to_go) { 22 | moves_to_go = std::min(moves_to_go, 50); 23 | auto scale = 0.7 / moves_to_go; 24 | auto eight = 0.95 * time_remaining; 25 | auto optimum = static_cast(std::min(scale * time_remaining, eight)); 26 | 27 | TimeBounds bounds{}; 28 | bounds.second = static_cast(std::min(5.0 * optimum, eight)); 29 | bounds.first = optimum; 30 | return bounds; 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/uci_interpreter/time_manager.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 2/23/23. 3 | // 4 | #pragma once 5 | #include 6 | #include 7 | #include "../types.h" 8 | #include "../board/types/square.h" 9 | 10 | extern array, NSQUARES> nodes_spent; 11 | 12 | // {Soft Limit, Hard Limit} 13 | using TimeBounds = std::pair; 14 | 15 | TimeBounds allocate_time_moves_to_go(i32 time_remaining, i32 moves_to_go); 16 | TimeBounds allocate_time_standard(i32 time_remaining, i32 increment); 17 | TimeBounds allocate_time(i32 time_remaining, i32 increment, i32 moves_to_go); 18 | -------------------------------------------------------------------------------- /src/uci_interpreter/uci_interpreter.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 6/21/23. 3 | // 4 | #include "uci_interpreter.h" 5 | #include 6 | 7 | void initialize_uci(Position& p) { 8 | initialize_engine(); 9 | p.set_fen(INITIAL_BOARD_FEN); 10 | } 11 | 12 | void uci_position(Position& board, const string& input_line) { 13 | if (input_line.substr(0, 17) == "position startpos") { 14 | string uci_moves; 15 | if (input_line.size() > 17) uci_moves = input_line.substr(24, input_line.size() - 24); 16 | board.set_fen(INITIAL_BOARD_FEN); 17 | uci_update_position_from_moves(board, uci_moves); 18 | } else { 19 | auto fen_start = static_cast(input_line.find("position fen ")) + 13; 20 | auto fen_end = static_cast(input_line.find(" moves")); 21 | auto moves_start = fen_end + 6; 22 | auto fen_size = fen_end - fen_start; 23 | const string& fen = input_line.substr(fen_start, fen_size); 24 | string moves; 25 | if (input_line.find(" moves") != string::npos) { 26 | moves = input_line.substr(moves_start + 1, input_line.size() - moves_start); 27 | } 28 | 29 | board.set_fen(fen); 30 | uci_update_position_from_moves(board, moves); 31 | } 32 | } 33 | 34 | void parse_move_time(Color side_to_play, const string& move_time_s, SearchParameters& params) { 35 | std::vector tokens = split(move_time_s); 36 | // Possible inputs to parse 37 | // input --> go movetime xxx 38 | // input --> go xtime ### xinc ### ytime ### yinc ### 39 | // input --> go xtime ### ytime ### 40 | if (tokens[1] == "movetime") { 41 | params.hard_time_limit = stoi(tokens[2]) * 0.95; 42 | params.soft_time_limit = stoi(tokens[2]) * 0.95; 43 | return; 44 | } 45 | i32 wtime = 0, winc = 0, btime = 0, binc = 0, moves_to_go = -1; 46 | for (usize i = 1; i < tokens.size(); i += 2) { 47 | string token = tokens[i]; 48 | auto value = 0; 49 | if (tokens.size() > i + 1) value = stoi(tokens[i + 1]); 50 | 51 | if (token == "wtime") wtime = value; 52 | else if (token == "winc") winc = value; 53 | else if (token == "btime") btime = value; 54 | else if (token == "binc") binc = value; 55 | else if (token == "movestogo") moves_to_go = value; 56 | } 57 | if (side_to_play == WHITE) { 58 | std::tie(params.soft_time_limit, params.hard_time_limit) = allocate_time(wtime, winc, moves_to_go); 59 | } else { 60 | std::tie(params.soft_time_limit, params.hard_time_limit) = allocate_time(btime, binc, moves_to_go); 61 | } 62 | } 63 | void uci_go(ThreadData &tdata, Position &board, const string &input_line, SearchParameters &sparams, SearchData& sdata, bool parse_time) { 64 | if (parse_time) { 65 | parse_move_time(board.turn(), input_line, sparams); 66 | } 67 | std::vector threads{}; 68 | std::vector nsparams(sparams.thread_count); 69 | std::vector nsdata(sparams.thread_count); 70 | for (auto& param : nsparams) { 71 | param.depth = sparams.depth; 72 | param.debug_info = sparams.debug_info; 73 | param.thread_count = sparams.thread_count; 74 | param.hard_time_limit = sparams.hard_time_limit.load(); 75 | param.soft_time_limit = sparams.soft_time_limit.load(); 76 | } 77 | SearchData final_sdata = {}; 78 | auto final_tdata = std::make_unique(); 79 | for (i32 idx = 0; idx < sparams.thread_count; idx += 1) { 80 | threads.emplace_back( 81 | [&, idx]() { 82 | auto copied_tdata = std::make_unique(tdata); 83 | copied_tdata->tidx = idx; 84 | auto copied_board = board; 85 | copied_board.reserve_nnue_capacity(); 86 | nsparams[idx].debug_info = idx == 0; 87 | if (idx != 0) { 88 | nsparams[idx].soft_time_limit = 86'400'000; 89 | nsparams[idx].hard_time_limit = 86'400'000; 90 | } 91 | search(nsdata[idx], *copied_tdata, copied_board, nsparams[idx]); 92 | if (idx == 0) { 93 | final_sdata = nsdata[idx]; 94 | *final_tdata = *copied_tdata; 95 | for (i32 midx = 0; midx < sparams.thread_count; midx += 1) { 96 | nsdata[midx].stopped = true; 97 | } 98 | } 99 | } 100 | ); 101 | } 102 | for (auto& t : threads) t.join(); 103 | std::cout << "bestmove " << final_sdata.final_best_move << std::endl; 104 | sdata = final_sdata; 105 | tdata = *final_tdata; 106 | } 107 | 108 | void bench() { 109 | u64 total_nodes = 0; 110 | auto start = std::chrono::steady_clock::now(); 111 | auto tdata = std::make_unique(); 112 | for (auto idx = 0; idx < BENCH_SIZE; idx++) { 113 | Position p; 114 | initialize_uci(p); 115 | p.set_fen(BENCH_FENS[idx]); 116 | 117 | std::cout << "\nPosition: " << idx + 1 << " " << BENCH_FENS[idx] << std::endl; 118 | 119 | SearchParameters parameters = { 120 | .depth = 14, 121 | .hard_time_limit = 86'400'000, // 1 Day 122 | .soft_time_limit = 86'400'000, 123 | .debug_info = true, 124 | }; 125 | SearchData sdata{}; 126 | string _; 127 | init_history(*tdata); 128 | uci_go(*tdata, p, _, parameters, sdata, false); 129 | total_nodes += sdata.nodes_searched; 130 | } 131 | auto end = std::chrono::steady_clock::now(); 132 | auto total_time_ms = std::chrono::duration_cast(end - start).count(); 133 | auto total_time = static_cast(total_time_ms) / 1000.0; 134 | std::cout << "Total Time: " << total_time << std::endl; 135 | std::cout << "\n"; 136 | std::cout << total_nodes << " nodes " << signed(total_nodes / (total_time + 1)) << " nps" << std::endl; 137 | } 138 | 139 | void read_uci() { 140 | Position board; 141 | auto tdata = std::make_unique(); 142 | SearchParameters sparams{ 143 | .debug_info = true 144 | }; 145 | 146 | initialize_uci(board); 147 | 148 | std::cout.setf(std::ios::unitbuf); 149 | 150 | string input_line; 151 | while (std::getline(std::cin, input_line)) { 152 | if (input_line == "uci") { 153 | std::cout << "id name Midnight v9" << std::endl; 154 | std::cout << "id author Archishmaan Peyyety" << std::endl; 155 | std::cout << "option name Hash type spin default 64 min 1 max 65536" << std::endl; 156 | std::cout << "option name Threads type spin default 1 min 1 max 512" << std::endl; 157 | std::cout << "uciok" << std::endl; 158 | } else if (input_line == "quit") { 159 | std::cout << "Bye Bye" << std::endl; 160 | break; 161 | } else if (input_line == "isready") { 162 | std::cout << "readyok" << std::endl; 163 | } else if (input_line == "ucinewgame") { 164 | init_history(*tdata); 165 | initialize_uci(board); 166 | } 167 | if (input_line.substr(0, 8) == "position") { 168 | uci_position(board, input_line); 169 | } else if (input_line == "stop") { 170 | } else if (input_line == "debug on") { 171 | sparams.debug_info = true; 172 | } else if (input_line == "debug off") { 173 | sparams.debug_info = false; 174 | } else if (input_line.substr(0, 2) == "go") { 175 | uci_go(*tdata, board, input_line, sparams); 176 | } else if (input_line == "bench") { 177 | bench(); 178 | } else if (input_line.substr(0, 14) == "setoption name") { 179 | std::vector parsed_options = split(input_line); 180 | std::transform(parsed_options[2].begin(), parsed_options[2].end(), parsed_options[2].begin(), 181 | [](unsigned char c){ return std::tolower(c); }); 182 | if (parsed_options[2] == "hash") { 183 | auto mb = std::stoi(parsed_options[4]); 184 | t_table.resize(mb); 185 | } else if (parsed_options[2] == "threads") { 186 | auto threads = std::stoi(parsed_options[4]); 187 | sparams.thread_count = threads; 188 | } 189 | } else if (input_line == "hash size") { 190 | std::cout << t_table.entry_count() << " entries" << std::endl; 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/uci_interpreter/uci_interpreter.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 1/9/23. 3 | // 4 | #pragma once 5 | #include 6 | #include 7 | #include 8 | #include "uci_move_parse.h" 9 | #include "time_manager.h" 10 | #include "bench_fens.h" 11 | #include "../engine.h" 12 | #include "../utils/fen_constants.h" 13 | #include "../utils/helpers.h" 14 | #include "../move_search/types.h" 15 | #include "../move_search/pvs.h" 16 | 17 | static auto EMPTY_SDATA = SearchData{}; 18 | void initialize_uci(Position& p); 19 | void uci_position(Position& board, const string& input_line); 20 | void parse_move_time(Color side_to_play, const string& move_time_s, SearchParameters& params); 21 | void uci_go(ThreadData &tdata, Position &board, const string &input_line, SearchParameters &sparams, SearchData &sdata = EMPTY_SDATA, bool parse_time = true); 22 | void bench(); 23 | Position datagen_random_game(); 24 | void single_thread_datagen(const string& output_file_path); 25 | void read_uci(); 26 | -------------------------------------------------------------------------------- /src/uci_interpreter/uci_move_parse.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 6/21/23. 3 | // 4 | #include "uci_move_parse.h" 5 | 6 | char promotion_character(string uci_move) { 7 | auto promotion_piece = uci_move.at(4); 8 | auto lower_case = std::tolower(promotion_piece, std::locale()); 9 | return lower_case; 10 | } 11 | 12 | Move uci_to_move(const string& moveStr, Position& position) { 13 | Move move = Move(moveStr.substr(0, 4)); 14 | // Pawn Promotion 15 | if (moveStr.size() == 5) { 16 | // Quiet Promotion 17 | auto p_char = promotion_character(moveStr); 18 | if (position.piece_at(move.to()) == NO_PIECE) { 19 | if (p_char == 'q') return {move.from(), move.to(), PR_QUEEN}; 20 | if (p_char == 'b') return {move.from(), move.to(), PR_BISHOP}; 21 | if (p_char == 'n') return {move.from(), move.to(), PR_KNIGHT}; 22 | if (p_char == 'r') return {move.from(), move.to(), PR_ROOK}; 23 | } 24 | if (p_char == 'q') return {move.from(), move.to(), PR_QUEEN | CAPTURE_TYPE}; 25 | if (p_char == 'b') return {move.from(), move.to(), PR_BISHOP | CAPTURE_TYPE}; 26 | if (p_char == 'n') return {move.from(), move.to(), PR_KNIGHT | CAPTURE_TYPE}; 27 | if (p_char == 'r') return {move.from(), move.to(), PR_ROOK | CAPTURE_TYPE}; 28 | } 29 | 30 | // En Passant 31 | if (position.piece_at(move.to()) == NO_PIECE && type_of(position.piece_at(move.from())) == PAWN && 32 | file_of(move.to()) != file_of(move.from())) { 33 | return {move.from(), move.to(), ENPASSANT}; 34 | } 35 | 36 | if (type_of(position.piece_at(move.from())) == PAWN && abs(rank_of(move.to()) - rank_of(move.from())) == 2) { 37 | return {move.from(), move.to(), DOUBLE_PUSH}; 38 | } 39 | 40 | // Castle 41 | if (type_of(position.piece_at(move.from())) == KING) { 42 | if (moveStr == "e1g1" || moveStr == "e8g8") return {move.from(), move.to(), OO}; 43 | if (moveStr == "e1c1" || moveStr == "e8c8") return {move.from(), move.to(), OOO}; 44 | } 45 | 46 | // Capture 47 | if (position.piece_at(move.to()) != NO_PIECE) { 48 | return {move.from(), move.to(), CAPTURE_TYPE}; 49 | } 50 | 51 | return {move.from(), move.to(), QUIET}; 52 | } 53 | 54 | void uci_update_position_from_moves(Position& board, const string& uci_move_string) { 55 | std::vector uci_moves = split(uci_move_string); 56 | for (const auto& uci_move : uci_moves) { 57 | if (uci_move.empty()) return; 58 | Move nextMove = uci_to_move(uci_move, board); 59 | if (board.turn() == BLACK) board.play(nextMove); 60 | else board.play(nextMove); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/uci_interpreter/uci_move_parse.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 2/1/23. 3 | // 4 | #pragma once 5 | #include "../board/position.h" 6 | #include "../utils/helpers.h" 7 | #include 8 | 9 | char promotion_character(string uci_move); 10 | Move uci_to_move(const string& moveStr, Position& position); 11 | void uci_update_position_from_moves(Position& board, const string& uci_move_string); 12 | -------------------------------------------------------------------------------- /src/utils/clock.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 1/31/23. 3 | // 4 | 5 | #include "clock.h" 6 | 7 | void reset_clock() { 8 | start_time = std::chrono::high_resolution_clock::now(); 9 | } 10 | 11 | int get_elapsed_time(TimeResolution resolution) { 12 | auto current_time = std::chrono::high_resolution_clock::now(); 13 | std::chrono::duration diff = current_time - start_time; 14 | double seconds_elapsed = diff.count(); 15 | return static_cast(seconds_elapsed * static_cast(resolution)); 16 | } 17 | 18 | bool time_elapsed_exceeds(i32 magnitude, TimeResolution resolution) { 19 | return get_elapsed_time(resolution) >= magnitude; 20 | } 21 | -------------------------------------------------------------------------------- /src/utils/clock.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 1/31/23. 3 | // 4 | #pragma once 5 | #include 6 | #include 7 | #include "../types.h" 8 | 9 | static auto start_time = std::chrono::high_resolution_clock::now(); 10 | enum class TimeResolution { 11 | Seconds = 1, 12 | Milliseconds = 1000, 13 | }; 14 | 15 | void reset_clock(); 16 | i32 get_elapsed_time(TimeResolution resolution); 17 | bool time_elapsed_exceeds(i32 magnitude, TimeResolution resolution); 18 | -------------------------------------------------------------------------------- /src/utils/fen_constants.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 2/1/23. 3 | // 4 | #pragma once 5 | 6 | constexpr auto INITIAL_BOARD_FEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; 7 | constexpr auto KIWIPETE_FEN = "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1"; 8 | constexpr auto TALKCHESS_FEN = "rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8"; 9 | -------------------------------------------------------------------------------- /src/utils/helpers.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 1/31/23. 3 | // 4 | 5 | #include "helpers.h" 6 | 7 | std::vector split(const string& s, const string& delimiter) { 8 | usize pos_start = 0, pos_end, delim_len = delimiter.length(); 9 | string token; 10 | std::vector res; 11 | 12 | while ((pos_end = s.find(delimiter, pos_start)) != string::npos) { 13 | token = s.substr(pos_start, pos_end - pos_start); 14 | pos_start = pos_end + delim_len; 15 | res.push_back(token); 16 | } 17 | 18 | res.push_back(s.substr(pos_start)); 19 | return res; 20 | } 21 | -------------------------------------------------------------------------------- /src/utils/helpers.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 1/31/23. 3 | // 4 | #pragma once 5 | #include 6 | #include "../types.h" 7 | #include "vector" 8 | 9 | std::vector split(const string& s, const string& delimiter = " "); 10 | -------------------------------------------------------------------------------- /src/utils/stack.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 5/1/23. 3 | // 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include "../types.h" 9 | 10 | // Credit to Polaris by Ciekce for massively inspiring this. 11 | 12 | template 13 | class Stack { 14 | public: 15 | Stack() = default; 16 | ~Stack() = default; 17 | 18 | inline void push(const T& element) { 19 | data[length++] = element; 20 | } 21 | 22 | inline void push(const T&& constructed_element) { 23 | data[length++] = std::move(constructed_element); 24 | } 25 | 26 | inline T pop() { return data[length-- -1]; } 27 | 28 | [[nodiscard]] inline T peek() const { return data[length - 1]; } 29 | [[nodiscard]] inline T& top() { return data[length - 1]; } 30 | 31 | inline void clear() { 32 | for (usize i = 0; i < length; i++) data[i].~T(); 33 | length = 0; 34 | } 35 | 36 | [[nodiscard]] inline auto size() const { return length; } 37 | 38 | [[nodiscard]] inline auto empty() const { return length == 0; } 39 | 40 | [[nodiscard]] inline auto operator[](usize i) const { return data[i]; } 41 | 42 | [[nodiscard]] inline auto begin() { return data.begin(); } 43 | [[nodiscard]] inline auto end() { return data.begin() + static_cast(length); } 44 | 45 | [[nodiscard]] inline auto &operator[](usize i) { return data[i]; } 46 | 47 | [[nodiscard]] inline auto begin() const { return data.begin(); } 48 | [[nodiscard]] inline auto end() const { return data.begin() + static_cast(length); } 49 | 50 | private: 51 | std::array data{}; 52 | usize length = 0; 53 | }; -------------------------------------------------------------------------------- /tests/attacks.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 5/2/23. 3 | // 4 | 5 | #include "lib/doctests.h" 6 | #include "iostream" 7 | #include "fstream" 8 | #include "../src/utils/helpers.h" 9 | #include "../src/move_gen/tables/attack_tables.h" 10 | 11 | TEST_SUITE_BEGIN("attacks"); 12 | 13 | TEST_CASE("attacks-10000") { 14 | std::string attacks_file_path = "./tests/attacks.txt"; 15 | std::ifstream input_file(attacks_file_path); 16 | std::string input_line; 17 | while (std::getline(input_file, input_line)) { 18 | std::vector tokens = split(input_line, ","); 19 | Bitboard occupancy = std::stoull(tokens[0]); 20 | auto square = static_cast(std::stoi(tokens[1])); 21 | Bitboard expected_white_pawn_attacks = std::stoull(tokens[2]); 22 | Bitboard expected_black_pawn_attacks = std::stoull(tokens[3]); 23 | Bitboard expected_knight_pawn_attacks = std::stoull(tokens[4]); 24 | Bitboard expected_bishop_attacks = std::stoull(tokens[5]); 25 | Bitboard expected_rook_attacks = std::stoull(tokens[6]); 26 | Bitboard expected_queen_attacks = std::stoull(tokens[7]); 27 | Bitboard expected_king_attacks = std::stoull(tokens[8]); 28 | 29 | CHECK_EQ(tables::attacks(square), expected_white_pawn_attacks); 30 | CHECK_EQ(tables::attacks(square), expected_black_pawn_attacks); 31 | CHECK_EQ(tables::attacks(square, occupancy), expected_knight_pawn_attacks); 32 | CHECK_EQ(tables::attacks(square, occupancy), expected_bishop_attacks); 33 | CHECK_EQ(tables::attacks(square, occupancy), expected_rook_attacks); 34 | CHECK_EQ(tables::attacks(square, occupancy), expected_queen_attacks); 35 | CHECK_EQ(tables::attacks(square, occupancy), expected_king_attacks); 36 | } 37 | } 38 | 39 | TEST_SUITE_END(); -------------------------------------------------------------------------------- /tests/board-rep.cpp: -------------------------------------------------------------------------------- 1 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 2 | #include "lib/doctests.h" 3 | #include "../src/board/position.h" 4 | 5 | TEST_SUITE_BEGIN("board-rep"); 6 | 7 | TEST_CASE("play-undo-white-ep") { 8 | Position p(START_FEN); 9 | 10 | CHECK_EQ(p.fen(), START_FEN); 11 | 12 | Move move_1 = Move(e2, e4, DOUBLE_PUSH); 13 | std::string fen_1 = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1"; 14 | 15 | Move move_2 = Move(h7, h6, QUIET); 16 | std::string fen_2 = "rnbqkbnr/ppppppp1/7p/8/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 1"; 17 | 18 | Move move_3 = Move(e4, e5, QUIET); 19 | std::string fen_3 = "rnbqkbnr/ppppppp1/7p/4P3/8/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1"; 20 | 21 | Move move_4 = Move(d7, d5, DOUBLE_PUSH); 22 | std::string fen_4 = "rnbqkbnr/ppp1ppp1/7p/3pP3/8/8/PPPP1PPP/RNBQKBNR w KQkq d6 0 1"; 23 | 24 | Move move_5 = Move(e5, d6, ENPASSANT); 25 | std::string fen_5 = "rnbqkbnr/ppp1ppp1/3P3p/8/8/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1"; 26 | 27 | p.play(move_1); 28 | CHECK_EQ(p.fen(), fen_1); 29 | 30 | p.play(move_2); 31 | CHECK_EQ(p.fen(), fen_2); 32 | 33 | p.play(move_3); 34 | CHECK_EQ(p.fen(), fen_3); 35 | 36 | p.play(move_4); 37 | CHECK_EQ(p.fen(), fen_4); 38 | 39 | p.play(move_5); 40 | CHECK_EQ(p.fen(), fen_5); 41 | p.undo(move_5); 42 | 43 | CHECK_EQ(p.fen(), fen_4); 44 | p.undo(move_4); 45 | 46 | CHECK_EQ(p.fen(), fen_3); 47 | p.undo(move_3); 48 | 49 | CHECK_EQ(p.fen(), fen_2); 50 | p.undo(move_2); 51 | 52 | CHECK_EQ(p.fen(), fen_1); 53 | p.undo(move_1); 54 | 55 | CHECK_EQ(p.fen(), START_FEN); 56 | } 57 | 58 | TEST_CASE("play-undo-black-ep") { 59 | Position p(START_FEN); 60 | 61 | CHECK_EQ(p.fen(), START_FEN); 62 | 63 | Move m1 = Move(h2, h3, QUIET); 64 | string m1_fen = "rnbqkbnr/pppppppp/8/8/8/7P/PPPPPPP1/RNBQKBNR b KQkq - 0 1"; 65 | 66 | Move m2 = Move(d7, d5, DOUBLE_PUSH); 67 | string m2_fen = "rnbqkbnr/ppp1pppp/8/3p4/8/7P/PPPPPPP1/RNBQKBNR w KQkq d6 0 1"; 68 | 69 | Move m3 = Move(g2, g3, QUIET); 70 | string m3_fen = "rnbqkbnr/ppp1pppp/8/3p4/8/6PP/PPPPPP2/RNBQKBNR b KQkq - 0 1"; 71 | 72 | Move m4 = Move(d5, d4, QUIET); 73 | string m4_fen = "rnbqkbnr/ppp1pppp/8/8/3p4/6PP/PPPPPP2/RNBQKBNR w KQkq - 0 1"; 74 | 75 | Move m5 = Move(e2, e4, DOUBLE_PUSH); 76 | string m5_fen = "rnbqkbnr/ppp1pppp/8/8/3pP3/6PP/PPPP1P2/RNBQKBNR b KQkq e3 0 1"; 77 | 78 | Move m6 = Move(d4, e3, ENPASSANT); 79 | string m6_fen = "rnbqkbnr/ppp1pppp/8/8/8/4p1PP/PPPP1P2/RNBQKBNR w KQkq - 0 1"; 80 | 81 | p.play(m1); 82 | CHECK_EQ(p.fen(), m1_fen); 83 | 84 | p.play(m2); 85 | CHECK_EQ(p.fen(), m2_fen); 86 | 87 | p.play(m3); 88 | CHECK_EQ(p.fen(), m3_fen); 89 | 90 | p.play(m4); 91 | CHECK_EQ(p.fen(), m4_fen); 92 | 93 | p.play(m5); 94 | CHECK_EQ(p.fen(), m5_fen); 95 | 96 | p.play(m6); 97 | CHECK_EQ(p.fen(), m6_fen); 98 | 99 | p.undo(m6); 100 | 101 | CHECK_EQ(p.fen(), m5_fen); 102 | p.undo(m5); 103 | 104 | CHECK_EQ(p.fen(), m4_fen); 105 | p.undo(m4); 106 | 107 | CHECK_EQ(p.fen(), m3_fen); 108 | p.undo(m3); 109 | 110 | CHECK_EQ(p.fen(), m2_fen); 111 | p.undo(m2); 112 | 113 | CHECK_EQ(p.fen(), m1_fen); 114 | p.undo(m1); 115 | 116 | CHECK_EQ(p.fen(), START_FEN); 117 | } 118 | 119 | TEST_CASE("play-undo-short-castle") { 120 | Position p(START_FEN); 121 | 122 | Move m1 = Move(e2, e4, DOUBLE_PUSH); 123 | std::string m1_fen = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1"; 124 | 125 | Move m2 = Move(e7, e5, DOUBLE_PUSH); 126 | std::string m2_fen = "rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq e6 0 1"; 127 | 128 | Move m3 = Move(f1, c4, QUIET); 129 | std::string m3_fen = "rnbqkbnr/pppp1ppp/8/4p3/2B1P3/8/PPPP1PPP/RNBQK1NR b KQkq - 0 1"; 130 | 131 | Move m4 = Move(f8, c5, QUIET); 132 | std::string m4_fen = "rnbqk1nr/pppp1ppp/8/2b1p3/2B1P3/8/PPPP1PPP/RNBQK1NR w KQkq - 0 1"; 133 | 134 | Move m5 = Move(g1, f3, QUIET); 135 | std::string m5_fen = "rnbqk1nr/pppp1ppp/8/2b1p3/2B1P3/5N2/PPPP1PPP/RNBQK2R b KQkq - 0 1"; 136 | 137 | Move m6 = Move(g8, f6, QUIET); 138 | std::string m6_fen = "rnbqk2r/pppp1ppp/5n2/2b1p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 0 1"; 139 | 140 | Move m7 = Move(e1, g1, OO); 141 | std::string m7_fen = "rnbqk2r/pppp1ppp/5n2/2b1p3/2B1P3/5N2/PPPP1PPP/RNBQ1RK1 b kq - 0 1"; 142 | 143 | Move m8 = Move(e8, g8, OO); 144 | std::string m8_fen = "rnbq1rk1/pppp1ppp/5n2/2b1p3/2B1P3/5N2/PPPP1PPP/RNBQ1RK1 w - - 0 1"; 145 | 146 | p.play(m1); 147 | CHECK_EQ(p.fen(), m1_fen); 148 | 149 | p.play(m2); 150 | CHECK_EQ(p.fen(), m2_fen); 151 | 152 | p.play(m3); 153 | CHECK_EQ(p.fen(), m3_fen); 154 | 155 | p.play(m4); 156 | CHECK_EQ(p.fen(), m4_fen); 157 | 158 | p.play(m5); 159 | CHECK_EQ(p.fen(), m5_fen); 160 | 161 | p.play(m6); 162 | CHECK_EQ(p.fen(), m6_fen); 163 | 164 | p.play(m7); 165 | CHECK_EQ(p.fen(), m7_fen); 166 | 167 | p.play(m8); 168 | CHECK_EQ(p.fen(), m8_fen); 169 | p.undo(m8); 170 | 171 | CHECK_EQ(p.fen(), m7_fen); 172 | p.undo(m7); 173 | 174 | CHECK_EQ(p.fen(), m6_fen); 175 | p.undo(m6); 176 | 177 | CHECK_EQ(p.fen(), m5_fen); 178 | p.undo(m5); 179 | 180 | CHECK_EQ(p.fen(), m4_fen); 181 | p.undo(m4); 182 | 183 | CHECK_EQ(p.fen(), m3_fen); 184 | p.undo(m3); 185 | 186 | CHECK_EQ(p.fen(), m2_fen); 187 | p.undo(m2); 188 | 189 | CHECK_EQ(p.fen(), m1_fen); 190 | p.undo(m1); 191 | 192 | CHECK_EQ(p.fen(), START_FEN); 193 | } 194 | 195 | TEST_CASE("play-undo-long-castle") { 196 | string start_fen = "r3kbnr/ppp1pppp/1nbq4/8/3pP3/1NBQ2PP/PPPP1P2/R3KBNR w KQkq - 0 1"; 197 | Position p(start_fen); 198 | 199 | Move m1 = Move(e1, c1, OOO); 200 | string m1_fen = "r3kbnr/ppp1pppp/1nbq4/8/3pP3/1NBQ2PP/PPPP1P2/2KR1BNR b kq - 0 1"; 201 | 202 | Move m2 = Move(e8, c8, OOO); 203 | string m2_fen = "2kr1bnr/ppp1pppp/1nbq4/8/3pP3/1NBQ2PP/PPPP1P2/2KR1BNR w - - 0 1"; 204 | 205 | p.play(m1); 206 | CHECK_EQ(p.fen(), m1_fen); 207 | 208 | p.play(m2); 209 | CHECK_EQ(p.fen(), m2_fen); 210 | p.undo(m2); 211 | 212 | CHECK_EQ(p.fen(), m1_fen); 213 | p.undo(m1); 214 | 215 | CHECK_EQ(p.fen(), start_fen); 216 | } 217 | 218 | TEST_CASE("promotions") { 219 | Position p; 220 | SUBCASE("white promotion") { 221 | string start_fen = "rnbq2nr/1ppppPpp/k2b4/2p5/8/8/PPPPP1PP/RNBQKBNR w KQ - 0 1"; 222 | p.set_fen(start_fen); 223 | 224 | Move m1 = Move(f7, f8, PR_QUEEN); 225 | string m1_fen = "rnbq1Qnr/1pppp1pp/k2b4/2p5/8/8/PPPPP1PP/RNBQKBNR b KQ - 0 1"; 226 | 227 | p.play(m1); 228 | CHECK_EQ(p.fen(), m1_fen); 229 | p.undo(m1); 230 | 231 | CHECK_EQ(p.fen(), start_fen); 232 | } 233 | SUBCASE("white promotion-capture") { 234 | string start_fen = "rnbq2nr/1ppppPpp/k2b4/2p5/8/8/PPPPP1PP/RNBQKBNR w KQ - 0 1"; 235 | p.set_fen(start_fen); 236 | 237 | Move m1 = Move(f7, g8, PR_QUEEN | CAPTURE_TYPE); 238 | string m1_fen = "rnbq2Qr/1pppp1pp/k2b4/2p5/8/8/PPPPP1PP/RNBQKBNR b KQ - 0 1"; 239 | 240 | p.play(m1); 241 | CHECK_EQ(p.fen(), m1_fen); 242 | p.undo(m1); 243 | 244 | CHECK_EQ(p.fen(), start_fen); 245 | } 246 | SUBCASE("black promotion") { 247 | string start_fen = "rnbqkbnr/ppppp1pp/8/8/8/2KBP3/PPPPPpPP/RNBQ2NR b kq - 0 1"; 248 | p.set_fen(start_fen); 249 | 250 | Move m1 = Move(f2, f1, PR_QUEEN); 251 | string m1_fen = "rnbqkbnr/ppppp1pp/8/8/8/2KBP3/PPPPP1PP/RNBQ1qNR w kq - 0 1"; 252 | 253 | p.play(m1); 254 | CHECK_EQ(p.fen(), m1_fen); 255 | p.undo(m1); 256 | 257 | CHECK_EQ(p.fen(), start_fen); 258 | } 259 | SUBCASE("black promotion-capture") { 260 | string start_fen = "rnbqkbnr/ppppp1pp/8/8/8/2KBP3/PPPPPpPP/RNBQ2NR b kq - 0 1"; 261 | p.set_fen(start_fen); 262 | 263 | Move m1 = Move(f2, g1, PR_QUEEN | CAPTURE_TYPE); 264 | string m1_fen = "rnbqkbnr/ppppp1pp/8/8/8/2KBP3/PPPPP1PP/RNBQ2qR w kq - 0 1"; 265 | 266 | p.play(m1); 267 | CHECK_EQ(p.fen(), m1_fen); 268 | p.undo(m1); 269 | 270 | CHECK_EQ(p.fen(), start_fen); 271 | } 272 | } 273 | 274 | TEST_SUITE_END(); -------------------------------------------------------------------------------- /tests/hash.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 5/16/23. 3 | // 4 | #include "lib/doctests.h" 5 | #include "iostream" 6 | #include "../src/board/position.h" 7 | #include "../src/move_gen/move_generator.h" 8 | #include "../src/utils/fen_constants.h" 9 | 10 | TEST_SUITE_BEGIN("hash"); 11 | 12 | template 13 | void perft_hash(Position& p, unsigned int depth) { 14 | MoveList list(p); 15 | if (depth == 1) return; 16 | for (Move move : list) { 17 | ZobristHash initial_hash = p.hash(); 18 | Position b2(p.fen()); 19 | CHECK_EQ(p.hash(), b2.hash()); 20 | 21 | p.play(move); 22 | perft_hash<~Us>(p, depth - 1); 23 | p.undo(move); 24 | 25 | CHECK_EQ(initial_hash, p.hash()); 26 | CHECK_EQ(initial_hash, b2.hash()); 27 | } 28 | } 29 | 30 | void test_perft_hash(const std::string& fen, int depth) { 31 | Position p(fen); 32 | if (p.turn() == BLACK) perft_hash(p, depth); 33 | else perft_hash(p, depth); 34 | } 35 | 36 | TEST_CASE("side-to-move") { 37 | Position from_fen("rnbqkbnr/ppppp1pp/8/5p2/5P2/2N5/PPPPP1PP/R1BQKBNR b KQkq - 1 2"); 38 | 39 | Position board_1(START_FEN); 40 | board_1.play(Move(f2, f4, DOUBLE_PUSH)); 41 | board_1.play(Move(f7, f5, DOUBLE_PUSH)); 42 | board_1.play(Move(b1, c3, QUIET)); 43 | 44 | Position board_2(START_FEN); 45 | board_2.play(Move(f2, f3, QUIET)); 46 | board_2.play(Move(f7, f6, QUIET)); 47 | board_2.play(Move(f3, f4, QUIET)); 48 | board_2.play(Move(f6, f5, QUIET)); 49 | board_2.play(Move(b1, c3, QUIET)); 50 | 51 | CHECK_EQ(from_fen.hash(), board_1.hash()); 52 | CHECK_EQ(board_1.hash(), board_2.hash()); 53 | } 54 | 55 | TEST_CASE("castling") { 56 | Position from_fen("rnbqk2r/pppppp1p/5n2/2b3p1/2B3P1/5N2/PPPPPP1P/RNBQ1RK1 b kq - 0 3"); 57 | 58 | Position board_1("rnbqk2r/pppppppp/5n2/2b5/2B5/5N2/PPPPPPPP/RNBQK2R w KQkq - 0 1"); 59 | Position board_2("rnbqk2r/pppppppp/5n2/2b5/2B5/5N2/PPPPPPPP/RNBQK2R w KQkq - 0 1"); 60 | 61 | board_1.play(Move(g2, g4, DOUBLE_PUSH)); 62 | board_1.play(Move(g7, g5, DOUBLE_PUSH)); 63 | board_1.play(Move(e1, g1, OO)); 64 | 65 | board_2.play(Move(e1, g1, OO)); 66 | board_2.play(Move(g7, g6, QUIET)); 67 | board_2.play(Move(g2, g3, QUIET)); 68 | board_2.play(Move(g6, g5, QUIET)); 69 | board_2.play(Move(g3, g4, QUIET)); 70 | CHECK_EQ(from_fen.hash(), board_1.hash()); 71 | CHECK_EQ(board_1.hash(), board_2.hash()); 72 | } 73 | 74 | TEST_CASE("en-passant") { 75 | Position from_fen("rnbqkbnr/pppp1pp1/8/3Pp2p/8/8/PPP1PPPP/RNBQKBNR w KQkq e6 0 4"); 76 | 77 | Position board_1(START_FEN); 78 | Position board_2(START_FEN); 79 | 80 | board_1.play(Move(d2, d3, QUIET)); 81 | board_1.play(Move(h7, h6, QUIET)); 82 | board_1.play(Move(d3, d4, QUIET)); 83 | board_1.play(Move(h6, h5, QUIET)); 84 | board_1.play(Move(d4, d5, QUIET)); 85 | board_1.play(Move(e7, e5, DOUBLE_PUSH)); 86 | 87 | board_2.play(Move(d2, d4, DOUBLE_PUSH)); 88 | board_2.play(Move(h7, h5, DOUBLE_PUSH)); 89 | board_2.play(Move(d4, d5, QUIET)); 90 | board_2.play(Move(e7, e5, DOUBLE_PUSH)); 91 | 92 | CHECK_EQ(from_fen.hash(), board_1.hash()); 93 | CHECK_EQ(board_1.hash(), board_2.hash()); 94 | } 95 | 96 | TEST_CASE("TestConsitentHashInitialFen") { 97 | test_perft_hash(START_FEN, 6); 98 | } 99 | TEST_CASE("TestConsitentHashKiwiPete") { 100 | test_perft_hash(KIWIPETE_FEN, 5); 101 | } 102 | TEST_CASE("TestConsitentHashTalkchess") { 103 | test_perft_hash(TALKCHESS_FEN, 5); 104 | } 105 | 106 | TEST_CASE("TestTraiangulation") { 107 | Position p; 108 | p.set_fen("8/k7/3p4/p2P1p2/P2P1P2/8/8/K7 w - - 0 1"); 109 | ZobristHash initial_hash = p.hash(); 110 | 111 | Move m1 = Move(a1, a2, QUIET); 112 | Move m2 = Move(a7, b7, QUIET); 113 | Move m3 = Move(a2, b2, QUIET); 114 | Move m4 = Move(b7, a7, QUIET); 115 | Move m5 = Move(b2, a1, QUIET); 116 | 117 | p.play(m1); 118 | p.play(m2); 119 | p.play(m3); 120 | p.play(m4); 121 | p.play(m5); 122 | CHECK_NE(p.hash(), initial_hash); 123 | 124 | p.undo(m5); 125 | p.undo(m4); 126 | p.undo(m3); 127 | p.undo(m2); 128 | p.undo(m1); 129 | CHECK_EQ(p.hash(), initial_hash); 130 | } 131 | 132 | TEST_CASE("TestCastling") { 133 | Position p(INITIAL_BOARD_FEN); 134 | ZobristHash initial_hash = p.hash(); 135 | 136 | Move m1 = Move(e2, e4, DOUBLE_PUSH); 137 | Move m2 = Move(e7, e5, DOUBLE_PUSH); 138 | Move m3 = Move(g1, f3); 139 | Move m4 = Move(g8, f6); 140 | Move m5 = Move(f1, d3); 141 | Move m6 = Move(f8, d6); 142 | Move m7 = Move(e1, g1, OO); 143 | 144 | p.play(m1); 145 | p.play(m2); 146 | p.play(m3); 147 | p.play(m4); 148 | p.play(m5); 149 | p.play(m6); 150 | p.play(m7); 151 | 152 | Position p2(p.fen()); 153 | 154 | CHECK_EQ(p.hash(), p2.hash()); 155 | 156 | p.undo(m7); 157 | p.undo(m6); 158 | p.undo(m5); 159 | p.undo(m4); 160 | p.undo(m3); 161 | p.undo(m2); 162 | p.undo(m1); 163 | 164 | Position p3(p.fen()); 165 | CHECK_EQ(p.hash(), initial_hash); 166 | CHECK_EQ(p3.hash(), initial_hash); 167 | } 168 | 169 | TEST_CASE("TestEPFile") { 170 | Position p; 171 | 172 | p.set_fen(INITIAL_BOARD_FEN); 173 | ZobristHash initial_hash = p.hash(); 174 | Move m1 = Move(e2, e4, DOUBLE_PUSH); 175 | Move m2 = Move(d7, d5, DOUBLE_PUSH); 176 | Move m3 = Move(e4, e5); 177 | Move m4 = Move(f7, f5, DOUBLE_PUSH); 178 | p.play(m1); 179 | p.play(m2); 180 | p.play(m3); 181 | p.play(m4); 182 | Position p2(p.fen()); 183 | 184 | CHECK_EQ(p2.hash(), p.hash()); 185 | 186 | p.undo(m4); 187 | p.undo(m3); 188 | p.undo(m2); 189 | p.undo(m1); 190 | CHECK_EQ(p.hash(), initial_hash); 191 | } 192 | 193 | TEST_CASE("capture-hash") { 194 | Position p(START_FEN); 195 | p.play(Move(b1, a3, QUIET)); 196 | p.play(Move(b8, a6, QUIET)); 197 | p.play(Move(b2, b4, DOUBLE_PUSH)); 198 | p.play(Move(a6, b4, CAPTURE_TYPE)); 199 | 200 | Position p2("r1bqkbnr/pppppppp/8/8/1n6/N7/P1PPPPPP/R1BQKBNR w KQkq - 0 3"); 201 | 202 | std::cout << p2.fen() << std::endl; 203 | std::cout << p.fen() << std::endl; 204 | 205 | std::cout << p2.hash() << std::endl; 206 | std::cout << p.hash() << std::endl; 207 | } 208 | 209 | TEST_CASE("promo-hash") { 210 | Position final("rnbq1bQr/ppppp1pp/2k4p/8/8/8/PPPPP1PP/RNBQKBNR b KQ - 0 1"); 211 | 212 | Position step_before("rnbq1bnr/pppppPpp/2k4p/8/8/8/PPPPP1PP/RNBQKBNR w KQ - 0 1"); 213 | step_before.play(Move(f7, g8, PR_QUEEN | CAPTURE_TYPE)); 214 | 215 | std::cout << final.fen() << std::endl; 216 | std::cout << step_before.fen() << std::endl; 217 | 218 | std::cout << final.hash() << std::endl; 219 | std::cout << step_before.hash() << std::endl; 220 | } 221 | 222 | TEST_CASE("kiwipete-fen") { 223 | Position from_fen(KIWIPETE_FEN); 224 | from_fen.play(Move(e1, d1, QUIET)); 225 | from_fen.play(Move(c7, c5, DOUBLE_PUSH)); 226 | from_fen.play(Move(d5, c6, ENPASSANT)); 227 | 228 | Position as_fen("r3k2r/p2pqpb1/bnP1pnp1/4N3/1p2P3/2N2Q1p/PPPBBPPP/R2K3R b kq - 0 2"); 229 | 230 | CHECK_EQ(from_fen.hash(), as_fen.hash()); 231 | } 232 | 233 | TEST_SUITE_END(); -------------------------------------------------------------------------------- /tests/perft.cpp: -------------------------------------------------------------------------------- 1 | #include "lib/doctests.h" 2 | #include "../src/board/position.h" 3 | #include "../src/move_gen/move_generator.h" 4 | #include "iostream" 5 | #include "fstream" 6 | #include "../src/utils/helpers.h" 7 | #include 8 | 9 | template 10 | u64 perft_node_count(Position& p, i32 depth) { 11 | u64 nodes = 0; 12 | 13 | MoveList list(p); 14 | if (depth == 1) return list.size(); 15 | for (Move move : list) { 16 | p.play(move); 17 | nodes += perft_node_count<~Us>(p, depth - 1); 18 | p.undo(move); 19 | } 20 | return nodes; 21 | } 22 | 23 | u64 test_perft_node_count(const std::string& fen, i32 depth) { 24 | Position p(fen); 25 | if (p.turn() == WHITE) { 26 | return perft_node_count(p, depth); 27 | } else { 28 | return perft_node_count(p, depth); 29 | } 30 | } 31 | 32 | TEST_SUITE_BEGIN("perft-final"); 33 | 34 | TEST_CASE("PerftDepthSixDefaultFen") { 35 | CHECK_EQ(test_perft_node_count(START_FEN, 6), 119060324); 36 | } 37 | 38 | TEST_CASE("PerftTestPosition") { 39 | CHECK_EQ(test_perft_node_count("rn2kbnr/pp2pppp/2p5/8/5B2/N2P1B1P/PP3PP1/R4RK1 w kq - 0 13", 5), 18292958); 40 | } 41 | 42 | TEST_CASE("PerftTestPosition2") { 43 | CHECK_EQ(test_perft_node_count("8/8/8/3p4/4pn1N/6p1/8/5K1k w - - 10 73", 8), 24490963); 44 | } 45 | 46 | TEST_CASE("PerftTestPosition5") { 47 | CHECK_EQ(test_perft_node_count("rnb1kbnr/ppp1pppp/4q3/3pP3/8/4K3/PPPP1PPP/RNBQ1BNR w - d6 0 1", 1), 34); 48 | } 49 | 50 | TEST_CASE("PerftTestPosition6") { 51 | CHECK_EQ(test_perft_node_count("rnb1kbnr/ppp1pppp/4q3/3pP3/8/5K2/PPPP1PPP/RNBQ1BNR w - d6 0 1", 1), 28); 52 | } 53 | 54 | TEST_CASE("PerftTestPosition7") { 55 | CHECK_EQ(test_perft_node_count("rnb1kbnr/ppq1pppp/p7/3pP3/8/6K1/PPPP1PPP/RNBQ1BNR w - d6 0 1", 1), 31); 56 | } 57 | 58 | TEST_CASE("PerftTestPosition3") { 59 | CHECK_EQ(test_perft_node_count("8/8/8/3p4/5n2/4p3/6pk/4KN2 b - - 3 76", 1), 7); 60 | } 61 | 62 | TEST_CASE("PerftTestPositionKnightCheck"){ 63 | CHECK_EQ(test_perft_node_count("5n2/3KPk2/8/3p1N2/3P4/8/8/8 w - - 17 102", 8), 80433958); 64 | } 65 | 66 | TEST_CASE("perft-all-bulk") { 67 | std::string perft_file_path = "./tests/perft_results.txt"; 68 | std::ifstream input_file(perft_file_path); 69 | std::string input_line; 70 | 71 | auto start_time = std::chrono::high_resolution_clock::now(); 72 | 73 | u64 total_nodes = 0; 74 | while (std::getline(input_file, input_line)) { 75 | std::vector split_perft = split(input_line, ";"); 76 | std::string fen = split_perft[0]; 77 | for (usize i = 1; i < split_perft.size(); i++) { 78 | std::string expected = split_perft[i]; 79 | 80 | i32 depth = static_cast(i); 81 | i32 expected_node_count = std::stoi(split(expected)[2]); 82 | 83 | CHECK_EQ(test_perft_node_count(fen, depth), expected_node_count); 84 | 85 | total_nodes += expected_node_count; 86 | } 87 | } 88 | 89 | auto end_time = std::chrono::high_resolution_clock::now(); 90 | std::chrono::duration diff = end_time - start_time; 91 | double seconds_elapsed = diff.count(); 92 | auto elap = static_cast(seconds_elapsed * static_cast(1000)); 93 | 94 | std::cout << "Total Nodes: " << total_nodes << std::endl; 95 | std::cout << "Elapsed Time(ms): " << elap << std::endl; 96 | std::cout << "NPS: " << (total_nodes * 1000) / elap << std::endl; 97 | input_file.close(); 98 | } 99 | 100 | TEST_SUITE_END(); -------------------------------------------------------------------------------- /tests/perft_results.txt: -------------------------------------------------------------------------------- 1 | rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1; D1 20; D2 400; D3 8902; D4 197281; D5 4865609; D6 119060324 2 | r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1; D1 48; D2 2039; D3 97862; D4 4085603; D5 193690690 3 | 4k3/8/8/8/8/8/8/4K2R w K - 0 1; D1 15; D2 66; D3 1197; D4 7059; D5 133987; D6 764643 4 | 4k3/8/8/8/8/8/8/R3K3 w Q - 0 1; D1 16; D2 71; D3 1287; D4 7626; D5 145232; D6 846648 5 | 4k2r/8/8/8/8/8/8/4K3 w k - 0 1; D1 5; D2 75; D3 459; D4 8290; D5 47635; D6 899442 6 | r3k3/8/8/8/8/8/8/4K3 w q - 0 1; D1 5; D2 80; D3 493; D4 8897; D5 52710; D6 1001523 7 | 4k3/8/8/8/8/8/8/R3K2R w KQ - 0 1; D1 26; D2 112; D3 3189; D4 17945; D5 532933; D6 2788982 8 | r3k2r/8/8/8/8/8/8/4K3 w kq - 0 1; D1 5; D2 130; D3 782; D4 22180; D5 118882; D6 3517770 9 | 8/8/8/8/8/8/6k1/4K2R w K - 0 1; D1 12; D2 38; D3 564; D4 2219; D5 37735; D6 185867 10 | 8/8/8/8/8/8/1k6/R3K3 w Q - 0 1; D1 15; D2 65; D3 1018; D4 4573; D5 80619; D6 413018 11 | 4k2r/6K1/8/8/8/8/8/8 w k - 0 1; D1 3; D2 32; D3 134; D4 2073; D5 10485; D6 179869 12 | r3k3/1K6/8/8/8/8/8/8 w q - 0 1; D1 4; D2 49; D3 243; D4 3991; D5 20780; D6 367724 13 | r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1; D1 26; D2 568; D3 13744; D4 314346; D5 7594526; D6 179862938 14 | r3k2r/8/8/8/8/8/8/1R2K2R w Kkq - 0 1; D1 25; D2 567; D3 14095; D4 328965; D5 8153719; D6 195629489 15 | r3k2r/8/8/8/8/8/8/2R1K2R w Kkq - 0 1; D1 25; D2 548; D3 13502; D4 312835; D5 7736373; D6 184411439 16 | r3k2r/8/8/8/8/8/8/R3K1R1 w Qkq - 0 1; D1 25; D2 547; D3 13579; D4 316214; D5 7878456; D6 189224276 17 | 1r2k2r/8/8/8/8/8/8/R3K2R w KQk - 0 1; D1 26; D2 583; D3 14252; D4 334705; D5 8198901; D6 198328929 18 | 2r1k2r/8/8/8/8/8/8/R3K2R w KQk - 0 1; D1 25; D2 560; D3 13592; D4 317324; D5 7710115; D6 185959088 19 | r3k1r1/8/8/8/8/8/8/R3K2R w KQq - 0 1; D1 25; D2 560; D3 13607; D4 320792; D5 7848606; D6 190755813 20 | 4k3/8/8/8/8/8/8/4K2R b K - 0 1; D1 5; D2 75; D3 459; D4 8290; D5 47635; D6 899442 21 | 4k3/8/8/8/8/8/8/R3K3 b Q - 0 1; D1 5; D2 80; D3 493; D4 8897; D5 52710; D6 1001523 22 | 4k2r/8/8/8/8/8/8/4K3 b k - 0 1; D1 15; D2 66; D3 1197; D4 7059; D5 133987; D6 764643 23 | r3k3/8/8/8/8/8/8/4K3 b q - 0 1; D1 16; D2 71; D3 1287; D4 7626; D5 145232; D6 846648 24 | 4k3/8/8/8/8/8/8/R3K2R b KQ - 0 1; D1 5; D2 130; D3 782; D4 22180; D5 118882; D6 3517770 25 | r3k2r/8/8/8/8/8/8/4K3 b kq - 0 1; D1 26; D2 112; D3 3189; D4 17945; D5 532933; D6 2788982 26 | 8/8/8/8/8/8/6k1/4K2R b K - 0 1; D1 3; D2 32; D3 134; D4 2073; D5 10485; D6 179869 27 | 8/8/8/8/8/8/1k6/R3K3 b Q - 0 1; D1 4; D2 49; D3 243; D4 3991; D5 20780; D6 367724 28 | 4k2r/6K1/8/8/8/8/8/8 b k - 0 1; D1 12; D2 38; D3 564; D4 2219; D5 37735; D6 185867 29 | r3k3/1K6/8/8/8/8/8/8 b q - 0 1; D1 15; D2 65; D3 1018; D4 4573; D5 80619; D6 413018 30 | r3k2r/8/8/8/8/8/8/R3K2R b KQkq - 0 1; D1 26; D2 568; D3 13744; D4 314346; D5 7594526; D6 179862938 31 | r3k2r/8/8/8/8/8/8/1R2K2R b Kkq - 0 1; D1 26; D2 583; D3 14252; D4 334705; D5 8198901; D6 198328929 32 | r3k2r/8/8/8/8/8/8/2R1K2R b Kkq - 0 1; D1 25; D2 560; D3 13592; D4 317324; D5 7710115; D6 185959088 33 | r3k2r/8/8/8/8/8/8/R3K1R1 b Qkq - 0 1; D1 25; D2 560; D3 13607; D4 320792; D5 7848606; D6 190755813 34 | 1r2k2r/8/8/8/8/8/8/R3K2R b KQk - 0 1; D1 25; D2 567; D3 14095; D4 328965; D5 8153719; D6 195629489 35 | 2r1k2r/8/8/8/8/8/8/R3K2R b KQk - 0 1; D1 25; D2 548; D3 13502; D4 312835; D5 7736373; D6 184411439 36 | r3k1r1/8/8/8/8/8/8/R3K2R b KQq - 0 1; D1 25; D2 547; D3 13579; D4 316214; D5 7878456; D6 189224276 37 | 8/1n4N1/2k5/8/8/5K2/1N4n1/8 w - - 0 1; D1 14; D2 195; D3 2760; D4 38675; D5 570726; D6 8107539 38 | 8/1k6/8/5N2/8/4n3/8/2K5 w - - 0 1; D1 11; D2 156; D3 1636; D4 20534; D5 223507; D6 2594412 39 | 8/8/4k3/3Nn3/3nN3/4K3/8/8 w - - 0 1; D1 19; D2 289; D3 4442; D4 73584; D5 1198299; D6 19870403 40 | K7/8/2n5/1n6/8/8/8/k6N w - - 0 1; D1 3; D2 51; D3 345; D4 5301; D5 38348; D6 588695 41 | k7/8/2N5/1N6/8/8/8/K6n w - - 0 1; D1 17; D2 54; D3 835; D4 5910; D5 92250; D6 688780 42 | 8/1n4N1/2k5/8/8/5K2/1N4n1/8 b - - 0 1; D1 15; D2 193; D3 2816; D4 40039; D5 582642; D6 8503277 43 | 8/1k6/8/5N2/8/4n3/8/2K5 b - - 0 1; D1 16; D2 180; D3 2290; D4 24640; D5 288141; D6 3147566 44 | 8/8/3K4/3Nn3/3nN3/4k3/8/8 b - - 0 1; D1 4; D2 68; D3 1118; D4 16199; D5 281190; D6 4405103 45 | K7/8/2n5/1n6/8/8/8/k6N b - - 0 1; D1 17; D2 54; D3 835; D4 5910; D5 92250; D6 688780 46 | k7/8/2N5/1N6/8/8/8/K6n b - - 0 1; D1 3; D2 51; D3 345; D4 5301; D5 38348; D6 588695 47 | B6b/8/8/8/2K5/4k3/8/b6B w - - 0 1; D1 17; D2 278; D3 4607; D4 76778; D5 1320507; D6 22823890 48 | 8/8/1B6/7b/7k/8/2B1b3/7K w - - 0 1; D1 21; D2 316; D3 5744; D4 93338; D5 1713368; D6 28861171 49 | k7/B7/1B6/1B6/8/8/8/K6b w - - 0 1; D1 21; D2 144; D3 3242; D4 32955; D5 787524; D6 7881673 50 | K7/b7/1b6/1b6/8/8/8/k6B w - - 0 1; D1 7; D2 143; D3 1416; D4 31787; D5 310862; D6 7382896 51 | B6b/8/8/8/2K5/5k2/8/b6B b - - 0 1; D1 6; D2 106; D3 1829; D4 31151; D5 530585; D6 9250746 52 | 8/8/1B6/7b/7k/8/2B1b3/7K b - - 0 1; D1 17; D2 309; D3 5133; D4 93603; D5 1591064; D6 29027891 53 | k7/B7/1B6/1B6/8/8/8/K6b b - - 0 1; D1 7; D2 143; D3 1416; D4 31787; D5 310862; D6 7382896 54 | K7/b7/1b6/1b6/8/8/8/k6B b - - 0 1; D1 21; D2 144; D3 3242; D4 32955; D5 787524; D6 7881673 55 | 7k/RR6/8/8/8/8/rr6/7K w - - 0 1; D1 19; D2 275; D3 5300; D4 104342; D5 2161211; D6 44956585 56 | R6r/8/8/2K5/5k2/8/8/r6R w - - 0 1; D1 36; D2 1027; D3 29215; D4 771461; D5 20506480; D6 525169084 57 | 7k/RR6/8/8/8/8/rr6/7K b - - 0 1; D1 19; D2 275; D3 5300; D4 104342; D5 2161211; D6 44956585 58 | R6r/8/8/2K5/5k2/8/8/r6R b - - 0 1; D1 36; D2 1027; D3 29227; D4 771368; D5 20521342; D6 524966748 59 | 6kq/8/8/8/8/8/8/7K w - - 0 1; D1 2; D2 36; D3 143; D4 3637; D5 14893; D6 391507 60 | 6KQ/8/8/8/8/8/8/7k b - - 0 1; D1 2; D2 36; D3 143; D4 3637; D5 14893; D6 391507 61 | K7/8/8/3Q4/4q3/8/8/7k w - - 0 1; D1 6; D2 35; D3 495; D4 8349; D5 166741; D6 3370175 62 | 6qk/8/8/8/8/8/8/7K b - - 0 1; D1 22; D2 43; D3 1015; D4 4167; D5 105749; D6 419369 63 | 6KQ/8/8/8/8/8/8/7k b - - 0 1; D1 2; D2 36; D3 143; D4 3637; D5 14893; D6 391507 64 | K7/8/8/3Q4/4q3/8/8/7k b - - 0 1; D1 6; D2 35; D3 495; D4 8349; D5 166741; D6 3370175 65 | 8/8/8/8/8/K7/P7/k7 w - - 0 1; D1 3; D2 7; D3 43; D4 199; D5 1347; D6 6249 66 | 8/8/8/8/8/7K/7P/7k w - - 0 1; D1 3; D2 7; D3 43; D4 199; D5 1347; D6 6249 67 | K7/p7/k7/8/8/8/8/8 w - - 0 1; D1 1; D2 3; D3 12; D4 80; D5 342; D6 2343 68 | 7K/7p/7k/8/8/8/8/8 w - - 0 1; D1 1; D2 3; D3 12; D4 80; D5 342; D6 2343 69 | 8/2k1p3/3pP3/3P2K1/8/8/8/8 w - - 0 1; D1 7; D2 35; D3 210; D4 1091; D5 7028; D6 34834 70 | 8/8/8/8/8/K7/P7/k7 b - - 0 1; D1 1; D2 3; D3 12; D4 80; D5 342; D6 2343 71 | 8/8/8/8/8/7K/7P/7k b - - 0 1; D1 1; D2 3; D3 12; D4 80; D5 342; D6 2343 72 | K7/p7/k7/8/8/8/8/8 b - - 0 1; D1 3; D2 7; D3 43; D4 199; D5 1347; D6 6249 73 | 7K/7p/7k/8/8/8/8/8 b - - 0 1; D1 3; D2 7; D3 43; D4 199; D5 1347; D6 6249 74 | 8/2k1p3/3pP3/3P2K1/8/8/8/8 b - - 0 1; D1 5; D2 35; D3 182; D4 1091; D5 5408; D6 34822 75 | 8/8/8/8/8/4k3/4P3/4K3 w - - 0 1; D1 2; D2 8; D3 44; D4 282; D5 1814; D6 11848 76 | 4k3/4p3/4K3/8/8/8/8/8 b - - 0 1; D1 2; D2 8; D3 44; D4 282; D5 1814; D6 11848 77 | 8/8/7k/7p/7P/7K/8/8 w - - 0 1; D1 3; D2 9; D3 57; D4 360; D5 1969; D6 10724 78 | 8/8/k7/p7/P7/K7/8/8 w - - 0 1; D1 3; D2 9; D3 57; D4 360; D5 1969; D6 10724 79 | 8/8/3k4/3p4/3P4/3K4/8/8 w - - 0 1; D1 5; D2 25; D3 180; D4 1294; D5 8296; D6 53138 80 | 8/3k4/3p4/8/3P4/3K4/8/8 w - - 0 1; D1 8; D2 61; D3 483; D4 3213; D5 23599; D6 157093 81 | 8/8/3k4/3p4/8/3P4/3K4/8 w - - 0 1; D1 8; D2 61; D3 411; D4 3213; D5 21637; D6 158065 82 | k7/8/3p4/8/3P4/8/8/7K w - - 0 1; D1 4; D2 15; D3 90; D4 534; D5 3450; D6 20960 83 | 8/8/7k/7p/7P/7K/8/8 b - - 0 1; D1 3; D2 9; D3 57; D4 360; D5 1969; D6 10724 84 | 8/8/k7/p7/P7/K7/8/8 b - - 0 1; D1 3; D2 9; D3 57; D4 360; D5 1969; D6 10724 85 | 8/8/3k4/3p4/3P4/3K4/8/8 b - - 0 1; D1 5; D2 25; D3 180; D4 1294; D5 8296; D6 53138 86 | 8/3k4/3p4/8/3P4/3K4/8/8 b - - 0 1; D1 8; D2 61; D3 411; D4 3213; D5 21637; D6 158065 87 | 8/8/3k4/3p4/8/3P4/3K4/8 b - - 0 1; D1 8; D2 61; D3 483; D4 3213; D5 23599; D6 157093 88 | k7/8/3p4/8/3P4/8/8/7K b - - 0 1; D1 4; D2 15; D3 89; D4 537; D5 3309; D6 21104 89 | 7k/3p4/8/8/3P4/8/8/K7 w - - 0 1; D1 4; D2 19; D3 117; D4 720; D5 4661; D6 32191 90 | 7k/8/8/3p4/8/8/3P4/K7 w - - 0 1; D1 5; D2 19; D3 116; D4 716; D5 4786; D6 30980 91 | k7/8/8/7p/6P1/8/8/K7 w - - 0 1; D1 5; D2 22; D3 139; D4 877; D5 6112; D6 41874 92 | k7/8/7p/8/8/6P1/8/K7 w - - 0 1; D1 4; D2 16; D3 101; D4 637; D5 4354; D6 29679 93 | k7/8/8/6p1/7P/8/8/K7 w - - 0 1; D1 5; D2 22; D3 139; D4 877; D5 6112; D6 41874 94 | k7/8/6p1/8/8/7P/8/K7 w - - 0 1; D1 4; D2 16; D3 101; D4 637; D5 4354; D6 29679 95 | k7/8/8/3p4/4p3/8/8/7K w - - 0 1; D1 3; D2 15; D3 84; D4 573; D5 3013; D6 22886 96 | k7/8/3p4/8/8/4P3/8/7K w - - 0 1; D1 4; D2 16; D3 101; D4 637; D5 4271; D6 28662 97 | 7k/3p4/8/8/3P4/8/8/K7 b - - 0 1; D1 5; D2 19; D3 117; D4 720; D5 5014; D6 32167 98 | 7k/8/8/3p4/8/8/3P4/K7 b - - 0 1; D1 4; D2 19; D3 117; D4 712; D5 4658; D6 30749 99 | k7/8/8/7p/6P1/8/8/K7 b - - 0 1; D1 5; D2 22; D3 139; D4 877; D5 6112; D6 41874 100 | k7/8/7p/8/8/6P1/8/K7 b - - 0 1; D1 4; D2 16; D3 101; D4 637; D5 4354; D6 29679 101 | k7/8/8/6p1/7P/8/8/K7 b - - 0 1; D1 5; D2 22; D3 139; D4 877; D5 6112; D6 41874 102 | k7/8/6p1/8/8/7P/8/K7 b - - 0 1; D1 4; D2 16; D3 101; D4 637; D5 4354; D6 29679 103 | k7/8/8/3p4/4p3/8/8/7K b - - 0 1; D1 5; D2 15; D3 102; D4 569; D5 4337; D6 22579 104 | k7/8/3p4/8/8/4P3/8/7K b - - 0 1; D1 4; D2 16; D3 101; D4 637; D5 4271; D6 28662 105 | 7k/8/8/p7/1P6/8/8/7K w - - 0 1; D1 5; D2 22; D3 139; D4 877; D5 6112; D6 41874 106 | 7k/8/p7/8/8/1P6/8/7K w - - 0 1; D1 4; D2 16; D3 101; D4 637; D5 4354; D6 29679 107 | 7k/8/8/1p6/P7/8/8/7K w - - 0 1; D1 5; D2 22; D3 139; D4 877; D5 6112; D6 41874 108 | 7k/8/1p6/8/8/P7/8/7K w - - 0 1; D1 4; D2 16; D3 101; D4 637; D5 4354; D6 29679 109 | k7/7p/8/8/8/8/6P1/K7 w - - 0 1; D1 5; D2 25; D3 161; D4 1035; D5 7574; D6 55338 110 | k7/6p1/8/8/8/8/7P/K7 w - - 0 1; D1 5; D2 25; D3 161; D4 1035; D5 7574; D6 55338 111 | 3k4/3pp3/8/8/8/8/3PP3/3K4 w - - 0 1; D1 7; D2 49; D3 378; D4 2902; D5 24122; D6 199002 112 | 7k/8/8/p7/1P6/8/8/7K b - - 0 1; D1 5; D2 22; D3 139; D4 877; D5 6112; D6 41874 113 | 7k/8/p7/8/8/1P6/8/7K b - - 0 1; D1 4; D2 16; D3 101; D4 637; D5 4354; D6 29679 114 | 7k/8/8/1p6/P7/8/8/7K b - - 0 1; D1 5; D2 22; D3 139; D4 877; D5 6112; D6 41874 115 | 7k/8/1p6/8/8/P7/8/7K b - - 0 1; D1 4; D2 16; D3 101; D4 637; D5 4354; D6 29679 116 | k7/7p/8/8/8/8/6P1/K7 b - - 0 1; D1 5; D2 25; D3 161; D4 1035; D5 7574; D6 55338 117 | k7/6p1/8/8/8/8/7P/K7 b - - 0 1; D1 5; D2 25; D3 161; D4 1035; D5 7574; D6 55338 118 | 3k4/3pp3/8/8/8/8/3PP3/3K4 b - - 0 1; D1 7; D2 49; D3 378; D4 2902; D5 24122; D6 199002 119 | 8/Pk6/8/8/8/8/6Kp/8 w - - 0 1; D1 11; D2 97; D3 887; D4 8048; D5 90606; D6 1030499 120 | n1n5/1Pk5/8/8/8/8/5Kp1/5N1N w - - 0 1; D1 24; D2 421; D3 7421; D4 124608; D5 2193768; D6 37665329 121 | 8/PPPk4/8/8/8/8/4Kppp/8 w - - 0 1; D1 18; D2 270; D3 4699; D4 79355; D5 1533145; D6 28859283 122 | n1n5/PPPk4/8/8/8/8/4Kppp/5N1N w - - 0 1; D1 24; D2 496; D3 9483; D4 182838; D5 3605103; D6 71179139 123 | 8/Pk6/8/8/8/8/6Kp/8 b - - 0 1; D1 11; D2 97; D3 887; D4 8048; D5 90606; D6 1030499 124 | n1n5/1Pk5/8/8/8/8/5Kp1/5N1N b - - 0 1; D1 24; D2 421; D3 7421; D4 124608; D5 2193768; D6 37665329 125 | 8/PPPk4/8/8/8/8/4Kppp/8 b - - 0 1; D1 18; D2 270; D3 4699; D4 79355; D5 1533145; D6 28859283 126 | n1n5/PPPk4/8/8/8/8/4Kppp/5N1N b - - 0 1; D1 24; D2 496; D3 9483; D4 182838; D5 3605103; D6 71179139 127 | 4k3/8/8/1rpP2K1/8/8/8/8 w - c6 0 1; D1 9; D2 122; D3 878 128 | 4k3/8/8/1KpP2r1/8/8/8/8 w - c6 0 1; D1 8; D2 132; D3 821 129 | 4k3/3r4/8/2pP4/8/8/3K4/8 w - c6 0 3; D1 9; D2 133; D3 930 130 | 4k3/5b2/8/2pP4/8/8/K7/8 w - c6 0 3; D1 5; D2 50; D3 299 131 | 4k3/1b6/8/2pP4/8/8/6K1/8 w - c6 0 3; D1 9; D2 96; D3 630 132 | 4k3/8/8/1r1Pp1K1/8/8/8/8 w - e6 0 1; D1 8; D2 124; D3 827 133 | 4k3/8/8/1K1Pp1r1/8/8/8/8 w - e6 0 1; D1 9; D2 133; D3 923 134 | 4k3/3r4/8/3Pp3/8/8/3K4/8 w - e6 0 3; D1 9; D2 133; D3 930 135 | 4k3/5b2/8/3Pp3/8/8/K7/8 w - e6 0 3; D1 6; D2 57; D3 357 136 | 4k3/1b6/8/3Pp3/8/8/6K1/8 w - e6 0 3; D1 8; D2 88; D3 543 137 | 4k3/8/8/1rpP1nK1/8/8/8/8 w - c6 0 1; D1 8; D2 165; D3 1031 138 | 4k3/8/8/1qpP2K1/8/8/8/8 w - c6 0 1; D1 9; D2 194; D3 1266 139 | 6k1/3K4/8/2pP4/8/8/3r4/8 w - c6 0 3; D1 9; D2 149; D3 954 140 | 4k1K1/8/8/2pP4/8/1b6/8/8 w - c6 0 3; D1 3; D2 34; D3 173 141 | 4k1K1/8/8/2pP4/8/1q6/8/8 w - c6 0 3; D1 3; D2 76; D3 357 142 | 4k3/8/8/8/2pP4/8/8/4K3 b - d3 0 1; D1 7; D2 39; D3 283 143 | 4k3/8/8/3pP3/4K3/8/8/8 w - d6 0 2; D1 8; D2 44; D3 316 -------------------------------------------------------------------------------- /tests/stack.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 5/2/23. 3 | // 4 | 5 | #include "lib/doctests.h" 6 | #include "../src/utils/stack.h" 7 | 8 | TEST_SUITE_BEGIN("stack"); 9 | 10 | TEST_CASE("push-pop-peek") { 11 | constexpr u32 STACK_CAP = 100; 12 | Stack stack{}; 13 | 14 | for (usize i = 0; i < STACK_CAP; i++) { 15 | stack.push(static_cast(i)); 16 | CHECK_EQ(stack.peek(), i); 17 | CHECK_EQ(stack.size(), i + 1); 18 | CHECK_EQ(stack[i], i); 19 | } 20 | 21 | for (usize i = 0; i < STACK_CAP; i++) { 22 | auto popped_element = stack.pop(); 23 | CHECK_EQ(STACK_CAP - i - 1, popped_element); 24 | CHECK_EQ(stack.size(), STACK_CAP - i - 1); 25 | } 26 | } 27 | 28 | TEST_SUITE_END(); -------------------------------------------------------------------------------- /texel-tuner/base.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | using tune_t = double; 6 | 7 | using pair_t = std::array; 8 | using parameters_t = std::vector; 9 | 10 | using coefficients_t = std::vector; 11 | 12 | struct EvalResult { 13 | coefficients_t coefficients; 14 | tune_t score; 15 | }; 16 | 17 | enum class PhaseStages { 18 | Midgame = 0, 19 | Endgame = 1 20 | }; 21 | 22 | constexpr int32_t S_(const int32_t mg, const int32_t eg) { 23 | return static_cast(static_cast(eg) << 16) + mg; 24 | } 25 | 26 | static constexpr int32_t mg_score(int32_t score) { 27 | return static_cast(score); 28 | } 29 | 30 | static constexpr int32_t eg_score(int32_t score) { 31 | return static_cast((score + 0x8000) >> 16); 32 | } 33 | 34 | template 35 | void get_initial_parameter_single(parameters_t& parameters, const T& parameter) { 36 | const auto mg = mg_score(static_cast(parameter)); 37 | const auto eg = eg_score(static_cast(parameter)); 38 | const pair_t pair = { mg, eg }; 39 | parameters.push_back(pair); 40 | } 41 | 42 | template 43 | void get_initial_parameter_array(parameters_t& parameters, const T& parameter, const int size) { 44 | for (int i = 0; i < size; i++) { 45 | get_initial_parameter_single(parameters, parameter[i]); 46 | } 47 | } 48 | 49 | template 50 | void get_initial_parameter_array_2d(parameters_t& parameters, const T& parameter, const int size1, const int size2) { 51 | for (int i = 0; i < size1; i++) { 52 | get_initial_parameter_array(parameters, parameter[i], size2); 53 | } 54 | } 55 | 56 | template 57 | void get_coefficient_single(coefficients_t& coefficients, const T& trace) { 58 | coefficients.push_back(static_cast(trace[0] - trace[1])); 59 | } 60 | 61 | template 62 | void get_coefficient_array(coefficients_t& coefficients, const T& trace, const int size) { 63 | for (int i = 0; i < size; i++) { 64 | get_coefficient_single(coefficients, trace[i]); 65 | } 66 | } 67 | 68 | template 69 | void get_coefficient_array_2d(coefficients_t& coefficients, const T& trace, const int size1, const int size2) { 70 | for (int i = 0; i < size1; i++) { 71 | get_coefficient_array(coefficients, trace[i], size2); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /texel-tuner/config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "midnight.h" 4 | 5 | using TuneEval = Midnight::MidnightEval; 6 | constexpr int32_t thread_count = 12; 7 | constexpr double preferred_k = 0;//3.3661; 8 | constexpr bool retune_from_zero = false; 9 | -------------------------------------------------------------------------------- /texel-tuner/midnight.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Archishmaan Peyyety on 3/2/23. 3 | // 4 | 5 | #include "midnight.h" 6 | #include 7 | #include 8 | #include 9 | 10 | namespace Midnight { 11 | using enum EvalOutputs; 12 | 13 | template 14 | static auto fetch_texel_features(const string &fen = "") { 15 | Position p; 16 | Trace trace{}; 17 | if (!fen.empty()) { 18 | p.set_fen(fen); 19 | if (p.turn() == BLACK) trace = evaluate(p); 20 | else trace = evaluate(p); 21 | } 22 | 23 | std::tuple features = { 24 | TexelFeatures{"PAWN_TABLE", PSTS, PAWN_TABLE, trace.pawn_pst, NSQUARES}, 25 | TexelFeatures{"KNIGHT_TABLE", PSTS, KNIGHT_TABLE, trace.knight_pst, NSQUARES}, 26 | TexelFeatures{"BISHOP_TABLE", PSTS, BISHOP_TABLE, trace.bishop_pst, NSQUARES}, 27 | TexelFeatures{"ROOK_TABLE", PSTS, ROOK_TABLE, trace.rook_pst, NSQUARES}, 28 | TexelFeatures{"QUEEN_TABLE", PSTS, QUEEN_TABLE, trace.queen_pst, NSQUARES}, 29 | TexelFeatures{"KING_TABLE", PSTS, KING_TABLE, trace.king_pst, NSQUARES}, 30 | TexelFeatures{"PASSED_PAWN_BONUS", PSTS, PASSED_PAWN_BONUS, trace.passed_pawns, NSQUARES}, 31 | TexelFeatures{"BLOCKED_PASSED_PAWN_PENALTY", PSTS, BLOCKED_PASSED_PAWN_PENALTY, 32 | trace.blocked_passed_pawns, NSQUARES}, 33 | 34 | TexelFeatures{"PIECE_VALUES", MISC, PIECE_VALUES, trace.material, NEVAL_PTYPES}, 35 | 36 | TexelFeatures{"OPEN_FILE_BONUS", MISC, OPEN_FILE_BONUS, trace.open_files, NEVAL_PTYPES}, 37 | TexelFeatures{"SEMI_OPEN_FILE_BONUS", MISC, SEMI_OPEN_FILE_BONUS, trace.semi_open_files, NEVAL_PTYPES}, 38 | TexelFeatures{"PAWN_PROTECTION", MISC, PAWN_PROTECTION, trace.pawn_protection, NEVAL_PTYPES}, 39 | TexelFeatures{"ATTACKED_BY_PAWN", MISC, ATTACKED_BY_PAWN, trace.attacked_by_pawn, NEVAL_PTYPES}, 40 | TexelFeatures{"THREATS", MISC, THREATS, trace.threats, NEVAL_PTYPES * NEVAL_PTYPES, NTHREAT_COLS}, 41 | TexelFeatures{"CHECK_BONUS", MISC, CHECK_BONUS, trace.check_bonus, NEVAL_PTYPES}, 42 | TexelFeatures{"CENTER_CONTROL", MISC, CENTER_CONTROL, trace.center_control, NEVAL_PTYPES}, 43 | TexelFeatures{"KING_PAWN_SHIELD", MISC, KING_PAWN_SHIELD, trace.king_pawn_shield, 2}, 44 | 45 | TexelFeatures{"KNIGHT_MOBILITY", MISC, KNIGHT_MOBILITY, trace.knight_mobility, NKNIGHT_MOBILITY}, 46 | TexelFeatures{"BISHOP_MOBILITY", MISC, BISHOP_MOBILITY, trace.bishop_mobility, NBISHOP_MOBILITY}, 47 | TexelFeatures{"ROOK_MOBILITY", MISC, ROOK_MOBILITY, trace.rook_mobility, NROOK_MOBILITY}, 48 | TexelFeatures{"QUEEN_MOBILITY", MISC, QUEEN_MOBILITY, trace.queen_mobility, NQUEEN_MOBILITY}, 49 | 50 | TexelFeatures{"KNIGHT_FORWARD_MOBILITY", MISC, KNIGHT_FORWARD_MOBILITY, trace.knight_forward_mobility, NKNIGHT_MOBILITY}, 51 | TexelFeatures{"BISHOP_FORWARD_MOBILITY", MISC, BISHOP_FORWARD_MOBILITY, trace.bishop_forward_mobility, NBISHOP_MOBILITY}, 52 | TexelFeatures{"ROOK_FORWARD_MOBILITY", MISC, ROOK_FORWARD_MOBILITY, trace.rook_forward_mobility, NROOK_MOBILITY}, 53 | TexelFeatures{"QUEEN_FORWARD_MOBILITY", MISC, QUEEN_FORWARD_MOBILITY, trace.queen_forward_mobility, NQUEEN_MOBILITY}, 54 | 55 | TexelFeatures{"KING_RING_ATTACK_PAWN", MISC, KING_RING_ATTACK_PAWN, trace.king_ring_pawn, NQUEEN_MOBILITY}, 56 | TexelFeatures{"KING_RING_ATTACK_KNIGHT", MISC, KING_RING_ATTACK_KNIGHT, trace.king_ring_knight, NQUEEN_MOBILITY}, 57 | TexelFeatures{"KING_RING_ATTACK_BISHOP", MISC, KING_RING_ATTACK_BISHOP, trace.king_ring_bishop, NQUEEN_MOBILITY}, 58 | TexelFeatures{"KING_RING_ATTACK_ROOK", MISC, KING_RING_ATTACK_ROOK, trace.king_ring_rook, NQUEEN_MOBILITY}, 59 | TexelFeatures{"KING_RING_ATTACK_QUEEN", MISC, KING_RING_ATTACK_QUEEN, trace.king_ring_queen, NQUEEN_MOBILITY}, 60 | 61 | TexelFeatures{"KING_LINE_SAFETY", MISC, KING_LINE_SAFETY, trace.king_safe_line, NQUEEN_MOBILITY}, 62 | 63 | TexelFeatures{"PHALANX_PAWN", MISC, PHALANX_PAWN, trace.pawn_phalanx, NRANKS}, 64 | TexelFeatures{"CANDIDATE_PASSED_PAWN", MISC, CANDIDATE_PASSED_PAWN, trace.candidate_pawns, NRANKS}, 65 | 66 | TexelFeatures{"ISOLATED_PAWN_PENALTY", MISC, ISOLATED_PAWN_PENALTY, trace.isolated_pawns}, 67 | TexelFeatures{"DOUBLED_PAWN_PENALTY", MISC, DOUBLED_PAWN_PENALTY, trace.doubled_pawns}, 68 | TexelFeatures{"BISHOP_PAIR_BONUS", MISC, BISHOP_PAIR_BONUS, trace.bishop_bonus}, 69 | TexelFeatures{"PAWNLESS_KING_FLANK", MISC, PAWNLESS_KING_FLANK, trace.pawnless_king_flank}, 70 | 71 | 72 | TexelFeatures{"TEMPO", MISC, TEMPO, trace.tempo}, 73 | }; 74 | 75 | if constexpr (run_trace) { 76 | return std::pair { 77 | features, 78 | trace.score 79 | }; 80 | } else { 81 | return features; 82 | } 83 | } 84 | 85 | parameters_t Midnight::MidnightEval::get_initial_parameters() { 86 | parameters_t parameters; 87 | 88 | auto features = fetch_texel_features(); 89 | std::apply([¶meters](auto&&... args) { 90 | (push_param(parameters, std::forward(args)), ...); 91 | }, features); 92 | 93 | return parameters; 94 | } 95 | 96 | EvalResult Midnight::MidnightEval::get_fen_eval_result(const std::string &fen) { 97 | 98 | coefficients_t coefficients; 99 | auto features = fetch_texel_features(fen); 100 | std::apply([&coefficients](auto&&... args) { 101 | (push_coeff(coefficients, std::forward(args)), ...); 102 | }, features.first); 103 | 104 | EvalResult result; 105 | result.score = features.second; 106 | result.coefficients = coefficients; 107 | return result; 108 | } 109 | 110 | void Midnight::MidnightEval::print_parameters(const parameters_t ¶meters) { 111 | int index = 0; 112 | auto features = fetch_texel_features(); 113 | 114 | std::vector outputs{}; 115 | for (usize i = 0; i < NOUTPUTS; i++) { 116 | outputs.emplace_back(); 117 | print_constant_header(outputs[i]); 118 | } 119 | 120 | std::apply([&outputs, ¶meters, &index](auto&&... args) { 121 | (print_feature(outputs[static_cast(args.output)], parameters, index, args), ...); 122 | }, features); 123 | 124 | for (usize i = 0; i < NOUTPUTS; i++) { 125 | std::ofstream stream(paths[static_cast(i)], std::ofstream::trunc); 126 | stream << outputs[i].str() << "\n"; 127 | } 128 | } 129 | } -------------------------------------------------------------------------------- /texel-tuner/midnight.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "base.h" 3 | #include 4 | #include 5 | #include "string" 6 | #include "sstream" 7 | #include "iostream" 8 | #include "cmath" 9 | #include "midnight.h" 10 | #include "../src/board/position.h" 11 | #include "../src/evaluation/evaluate.h" 12 | #include 13 | 14 | namespace Midnight { 15 | 16 | constexpr int NOUTPUTS = 2; 17 | enum class EvalOutputs { 18 | PSTS = 0, 19 | MISC = 1 20 | }; 21 | 22 | static std::unordered_map paths = { 23 | {EvalOutputs::PSTS, "/Users/archishmaan/Documents/CodeProjects/chess-engine/src/evaluation/constants/psts.h"}, 24 | {EvalOutputs::MISC, "/Users/archishmaan/Documents/CodeProjects/chess-engine/src/evaluation/constants/misc.h"}, 25 | }; 26 | 27 | template 28 | struct TexelFeatures { 29 | string name; 30 | EvalOutputs output; 31 | U initial_params; 32 | T& trace; 33 | int size; 34 | int ncols; 35 | 36 | explicit TexelFeatures(string name_, EvalOutputs outputs_, U params_, T& trace_, int size_ = 1, int ncols_ = 8): 37 | name(std::move(name_)), output(outputs_), initial_params(params_), trace(trace_), size(size_), ncols(ncols_) {} 38 | TexelFeatures() = delete; 39 | }; 40 | 41 | inline void add_param(parameters_t& params, const Score score) { 42 | pair_t pair = { (double) mg_score(score), (double) eg_score(score) }; 43 | params.push_back(pair); 44 | } 45 | 46 | inline void add_params(parameters_t& params, const Score scores[], int size) { 47 | for (int i = 0; i < size; i++) { 48 | add_param(params, scores[i]); 49 | } 50 | } 51 | 52 | template 53 | void push_param(parameters_t& params, T&& arg){ 54 | if constexpr(std::is_pointer_v) add_params(params, arg.initial_params, arg.size); 55 | else add_param(params, arg.initial_params); 56 | } 57 | 58 | template 59 | void push_coeff(coefficients_t& coeffs, T&& arg){ 60 | if constexpr(std::is_pointer_v) get_coefficient_array(coeffs, arg.trace, arg.size); 61 | else get_coefficient_single(coeffs, arg.trace); 62 | } 63 | 64 | inline void print_constant_header(std::stringstream& ss) { 65 | ss << "#pragma once\n"; 66 | 67 | ss << "#include \"../../board/types/piece.h\"\n"; 68 | ss << "#include \"../types.h\"\n"; 69 | } 70 | 71 | inline void print_parameter(std::stringstream& ss, const pair_t parameter) { 72 | const auto mg = round(parameter[static_cast(PhaseStages::Midgame)]); 73 | const auto eg = round(parameter[static_cast(PhaseStages::Endgame)]); 74 | ss << "S(" << mg << ", " << eg << ")"; 75 | } 76 | 77 | inline void print_single(std::stringstream& ss, const parameters_t& parameters, int& index, const std::string& name) { 78 | ss << "constexpr Score " << name << " = "; 79 | print_parameter(ss, parameters[index]); 80 | index++; 81 | 82 | ss << ";" << std::endl; 83 | } 84 | 85 | inline void print_rows(std::stringstream& ss, const parameters_t& parameters, int& index, const std::string& name, int count, int ncols = 8) { 86 | ss << "constexpr Score " << name << "[] = {\n"; 87 | for (auto i = 0; i < count; i++) { 88 | if (i % ncols == 0) ss << std::left << std::setw(8) << ""; 89 | 90 | const auto mg = round(parameters[index][static_cast(PhaseStages::Midgame)]); 91 | const auto eg = round(parameters[index][static_cast(PhaseStages::Endgame)]); 92 | index++; 93 | 94 | std::stringstream score_string; 95 | score_string << "S(" << mg << ", " << eg << "),"; 96 | 97 | if (i % ncols != ncols - 1) { 98 | ss << std::left << std::setw(16) << score_string.str(); 99 | } else if (i != count - 1) { 100 | ss << score_string.str() << "\n"; 101 | } else ss << score_string.str(); 102 | } 103 | ss << "\n};" << std::endl; 104 | } 105 | 106 | template 107 | void print_feature(std::stringstream& ss, const parameters_t ¶meters, int& index, T&& arg){ 108 | if constexpr(std::is_pointer_v) { 109 | print_rows(ss, parameters, index, arg.name, arg.size, arg.ncols); 110 | } else print_single(ss, parameters, index, arg.name); 111 | } 112 | 113 | constexpr int NEVAL_PTYPES = NPIECE_TYPES - 1; 114 | constexpr int NKNIGHT_MOBILITY = 9; 115 | constexpr int NBISHOP_MOBILITY = 14; 116 | constexpr int NROOK_MOBILITY = 15; 117 | constexpr int NQUEEN_MOBILITY = 28; 118 | constexpr int NTHREAT_COLS = 6; 119 | 120 | class MidnightEval { 121 | public: 122 | constexpr static bool includes_additional_score = false; 123 | static parameters_t get_initial_parameters(); 124 | static EvalResult get_fen_eval_result(const std::string& fen); 125 | static void print_parameters(const parameters_t& parameters); 126 | }; 127 | } 128 | 129 | -------------------------------------------------------------------------------- /texel-tuner/sources.csv: -------------------------------------------------------------------------------- 1 | /Users/archishmaan/Documents/CodeProjects/data/quiet-labeled-small.epd,0,0 2 | /Users/archishmaan/Documents/CodeProjects/data/E12.33-1M-D12-Resolved.book,0,700000 3 | 4 | -------------------------------------------------------------------------------- /texel-tuner/threadpool.cpp: -------------------------------------------------------------------------------- 1 | #include "threadpool.h" 2 | 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | void ThreadPool::start(uint32_t thread_count) 9 | { 10 | stop(); 11 | should_stop = false; 12 | for (int thread_index = 0; thread_index < thread_count; thread_index++) 13 | { 14 | threads.emplace_back([this]() 15 | { 16 | thread_loop(); 17 | }); 18 | } 19 | } 20 | 21 | uint32_t ThreadPool::thread_count() const 22 | { 23 | return static_cast(threads.size()); 24 | } 25 | 26 | void ThreadPool::enqueue(const function& job) 27 | { 28 | { 29 | unique_lock lock(queue_mutex); 30 | jobs.push(job); 31 | } 32 | mutex_condition.notify_one(); 33 | } 34 | 35 | void ThreadPool::stop() 36 | { 37 | { 38 | unique_lock lock(queue_mutex); 39 | should_stop = true; 40 | } 41 | 42 | mutex_condition.notify_all(); 43 | 44 | for (thread& active_thread : threads) 45 | { 46 | active_thread.join(); 47 | } 48 | threads.clear(); 49 | } 50 | 51 | bool ThreadPool::is_idle() 52 | { 53 | unique_lock lock(queue_mutex); 54 | return jobs.empty() && running_job_count == 0; 55 | } 56 | 57 | void ThreadPool::wait_for_completion() 58 | { 59 | unique_lock lock(queue_mutex); 60 | while(!jobs.empty() || running_job_count > 0) 61 | { 62 | completion_condition.wait(lock, [this] 63 | { 64 | return jobs.empty() && running_job_count == 0; 65 | }); 66 | } 67 | } 68 | 69 | void ThreadPool::thread_loop() 70 | { 71 | while (true) 72 | { 73 | function job; 74 | { 75 | unique_lock lock(queue_mutex); 76 | mutex_condition.wait(lock, [this] 77 | { 78 | return !jobs.empty() || should_stop; 79 | }); 80 | 81 | if (should_stop) 82 | { 83 | return; 84 | } 85 | 86 | job = jobs.front(); 87 | jobs.pop(); 88 | running_job_count++; 89 | } 90 | 91 | job(); 92 | 93 | { 94 | unique_lock lock(queue_mutex); 95 | running_job_count--; 96 | completion_condition.notify_all(); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /texel-tuner/threadpool.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | class ThreadPool { 9 | public: 10 | void start(uint32_t thread_count); 11 | uint32_t thread_count() const; 12 | void enqueue(const std::function& job); 13 | void stop(); 14 | bool is_idle(); 15 | void wait_for_completion(); 16 | 17 | private: 18 | bool should_stop = false; 19 | uint32_t running_job_count = 0; 20 | std::mutex queue_mutex; 21 | std::condition_variable mutex_condition; 22 | std::condition_variable completion_condition; 23 | std::vector threads; 24 | std::queue> jobs; 25 | 26 | void thread_loop(); 27 | }; 28 | 29 | -------------------------------------------------------------------------------- /texel-tuner/tune_main.cpp: -------------------------------------------------------------------------------- 1 | #include "tuner.h" 2 | #include "../src/engine.h" 3 | 4 | #include "fstream" 5 | #include "iostream" 6 | #include "string" 7 | #include "sstream" 8 | #include "vector" 9 | #include "midnight.h" 10 | 11 | using namespace std; 12 | using namespace Tuner; 13 | 14 | int main(int argc, char** argv) { 15 | initialize_engine(); 16 | if(argc == 1) { 17 | cout << "Please provide a data source list file" << endl; 18 | return -1; 19 | } 20 | 21 | vector sources; 22 | const string csv_path = argv[1]; 23 | ifstream csv(csv_path); 24 | std::cout << csv_path << std::endl; 25 | if(!csv) { 26 | cout << "Unable to open data source list " << csv_path << endl; 27 | } 28 | 29 | while(!csv.eof()) { 30 | string line; 31 | getline(csv, line); 32 | 33 | if(line.empty()) { 34 | continue; 35 | } 36 | 37 | DataSource source; 38 | stringstream ss(line); 39 | if(!getline(ss, source.path, ',')) { 40 | cout << "CSV misformatted" << endl; 41 | return -1; 42 | } 43 | 44 | string flipped_wdl_str; 45 | if (!getline(ss, flipped_wdl_str, ',')) { 46 | cout << "CSV misformatted" << endl; 47 | return -1; 48 | } 49 | 50 | try { 51 | source.side_to_move_wdl = stoul(flipped_wdl_str); 52 | } catch (const std::invalid_argument&) { 53 | cout << flipped_wdl_str << " is not valid for a WDL flip flag"; 54 | return -1; 55 | } 56 | 57 | string position_limit_str; 58 | if (!getline(ss, flipped_wdl_str, ',')) { 59 | cout << "CSV misformatted" << endl; 60 | return -1; 61 | } 62 | 63 | try { 64 | source.position_limit = stoll(flipped_wdl_str); 65 | } catch (const std::invalid_argument&) { 66 | cout << position_limit_str << " is not a valid position limit"; 67 | return -1; 68 | } 69 | 70 | sources.push_back(source); 71 | } 72 | 73 | if(sources.empty()) { 74 | cout << "Data source list is empty"; 75 | return -1; 76 | } 77 | 78 | run(sources); 79 | 80 | return 0; 81 | } 82 | -------------------------------------------------------------------------------- /texel-tuner/tuner.cpp: -------------------------------------------------------------------------------- 1 | #include "tuner.h" 2 | #include "base.h" 3 | #include "config.h" 4 | #include "threadpool.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace std; 16 | using namespace std::chrono; 17 | using namespace Tuner; 18 | 19 | struct WdlMarker { 20 | string marker; 21 | tune_t wdl; 22 | }; 23 | 24 | struct CoefficientEntry { 25 | int16_t value; 26 | int16_t index; 27 | }; 28 | 29 | struct Entry { 30 | vector coefficients; 31 | tune_t wdl; 32 | bool white_to_move; 33 | tune_t additional_score; 34 | int32_t phase; 35 | }; 36 | 37 | static const array markers { 38 | WdlMarker{"1.0", 1}, 39 | WdlMarker{"0.5", 0.5}, 40 | WdlMarker{"0.0", 0}, 41 | 42 | WdlMarker{"1-0", 1}, 43 | WdlMarker{"1/2-1/2", 0.5}, 44 | WdlMarker{"0-1", 0} 45 | }; 46 | 47 | static tune_t get_fen_wdl(const string& fen, bool white_to_move, bool side_to_move_wdl) { 48 | tune_t wdl; 49 | bool marker_found = false; 50 | for (auto& marker : markers) { 51 | if (fen.find(marker.marker) != std::string::npos) { 52 | if (marker_found) { 53 | cout << "WDL marker already found on line " << fen << endl; 54 | throw std::runtime_error("WDL marker already found"); 55 | } 56 | marker_found = true; 57 | wdl = marker.wdl; 58 | } 59 | } 60 | 61 | if(!marker_found) { 62 | stringstream ss(fen); 63 | while (!ss.eof()) { 64 | string word; 65 | ss >> word; 66 | if (word.starts_with("0.")) { 67 | wdl = stod(word); 68 | marker_found = true; 69 | } 70 | } 71 | } 72 | 73 | if (!marker_found) { 74 | cout << "WDL marker not found on line " << fen << endl; 75 | throw std::runtime_error("WDL marker not found"); 76 | } 77 | 78 | if(!white_to_move && side_to_move_wdl) { 79 | wdl = 1 - wdl; 80 | } 81 | 82 | return wdl; 83 | } 84 | 85 | static bool get_fen_color_to_move(const string& fen) { 86 | return fen.find('w') != std::string::npos; 87 | } 88 | 89 | static void print_elapsed(high_resolution_clock::time_point start) { 90 | const auto now = high_resolution_clock::now(); 91 | const auto elapsed = now - start; 92 | const auto elapsed_seconds = duration_cast(elapsed).count(); 93 | cout << "[" << elapsed_seconds << "s] "; 94 | } 95 | 96 | static void get_coefficient_entries(const coefficients_t& coefficients, vector& coefficient_entries, int32_t parameter_count) { 97 | if(coefficients.size() != parameter_count) { 98 | throw runtime_error("Parameter count mismatch"); 99 | } 100 | 101 | for (int16_t i = 0; i < coefficients.size(); i++) { 102 | if (coefficients[i] == 0) { 103 | continue; 104 | } 105 | 106 | const auto coefficient_entry = CoefficientEntry{coefficients[i], i}; 107 | coefficient_entries.push_back(coefficient_entry); 108 | } 109 | } 110 | 111 | static tune_t linear_eval(const Entry& entry, const parameters_t& parameters) { 112 | tune_t score = entry.additional_score; 113 | tune_t midgame = 0; 114 | tune_t endgame = 0; 115 | for (const auto& coefficient : entry.coefficients) { 116 | midgame += coefficient.value * parameters[coefficient.index][static_cast(PhaseStages::Midgame)]; 117 | endgame += coefficient.value * parameters[coefficient.index][static_cast(PhaseStages::Endgame)]; 118 | } 119 | int mg_phase = std::min(24, entry.phase); 120 | score += (midgame * mg_phase + endgame * (24 - mg_phase)) / 24; 121 | return score; 122 | } 123 | 124 | static int32_t get_phase(const string& fen) { 125 | int32_t phase = 0; 126 | auto stop = false; 127 | for(const char ch : fen) { 128 | if(stop) break; 129 | 130 | switch (ch) { 131 | case 'n': 132 | case 'N': 133 | phase += 1; 134 | break; 135 | case 'b': 136 | case 'B': 137 | phase += 1; 138 | break; 139 | case 'r': 140 | case 'R': 141 | phase += 2; 142 | break; 143 | case 'q': 144 | case 'Q': 145 | phase += 4; 146 | break; 147 | case 'p': 148 | case '/': 149 | case '1': 150 | case '2': 151 | case '3': 152 | case '4': 153 | case '5': 154 | case '6': 155 | case '7': 156 | case '8': 157 | break; 158 | case ' ': 159 | stop = true; 160 | break; 161 | } 162 | } 163 | return phase; 164 | } 165 | 166 | static void print_statistics(const parameters_t& parameters, const vector& entries) { 167 | array wins{}; 168 | array draws{}; 169 | array losses{}; 170 | array total{}; 171 | array wdls{}; 172 | 173 | for(auto& entry : entries) { 174 | if(entry.wdl == 1) { 175 | wins[entry.white_to_move]++; 176 | } 177 | else if(entry.wdl == 0.5) { 178 | draws[entry.white_to_move]++; 179 | } 180 | else if (entry.wdl == 0.0) { 181 | losses[entry.white_to_move]++; 182 | } 183 | total[entry.white_to_move]++; 184 | wdls[entry.white_to_move] += entry.wdl; 185 | } 186 | 187 | cout << "Dataset statistics:" << endl; 188 | cout << "Total positions: " << entries.size() << endl; 189 | for(int color = 1; color >= 0; color--) { 190 | const auto color_name = color ? "White" : "Black"; 191 | cout << color_name << ": " << total[color] << " (" << (total[color] * 100.0 / entries.size()) << "%)" << endl; 192 | cout << color_name << " 1.0: " << wins[color] << " (" << (wins[color] * 100.0 / entries.size()) << "%)" << endl; 193 | cout << color_name << " 0.5: " << draws[color] << " (" << (draws[color] * 100.0 / entries.size()) << "%)" << endl; 194 | cout << color_name << " 0.0: " << losses[color] << " (" << (losses[color] * 100.0 / entries.size()) << "%)" << endl; 195 | cout << color_name << " avg: " << wdls[color] / total[color] << endl; 196 | } 197 | 198 | cout << endl; 199 | } 200 | 201 | static void load_fens(const DataSource& source, const parameters_t& parameters, 202 | const high_resolution_clock::time_point start, vector& entries) { 203 | cout << "Loading " << source.path; 204 | if(source.position_limit > 0) { 205 | cout << " (" << source.position_limit << " positions)"; 206 | } 207 | cout << "..." << endl; 208 | 209 | ifstream file(source.path); 210 | if(!file) { 211 | cout << "Failed to open " << source.path << endl; 212 | throw runtime_error("Failed to open data source"); 213 | } 214 | 215 | int64_t position_count = 0; 216 | string fen; 217 | while (!file.eof()) { 218 | if (source.position_limit > 0 && position_count >= source.position_limit) { 219 | break; 220 | } 221 | 222 | getline(file, fen); 223 | if (fen.empty()) { 224 | break; 225 | } 226 | 227 | const auto eval_result = TuneEval::get_fen_eval_result(fen); 228 | 229 | Entry entry; 230 | entry.white_to_move = get_fen_color_to_move(fen); 231 | entry.wdl = get_fen_wdl(fen, entry.white_to_move, source.side_to_move_wdl); 232 | get_coefficient_entries(eval_result.coefficients, entry.coefficients, static_cast(parameters.size())); 233 | entry.phase = get_phase(fen); 234 | entry.additional_score = 0; 235 | if constexpr (TuneEval::includes_additional_score) { 236 | const tune_t score = linear_eval(entry, parameters); 237 | if (int (score) != int(eval_result.score)) { 238 | break; 239 | } 240 | entry.additional_score = eval_result.score - score; 241 | } 242 | 243 | entries.push_back(entry); 244 | 245 | position_count++; 246 | if (position_count % 100000 == 0) { 247 | print_elapsed(start); 248 | std::cout << "Loaded " << position_count << " entries..." << std::endl; 249 | } 250 | } 251 | 252 | print_elapsed(start); 253 | std::cout << "Loaded " << position_count << " entries from " << source.path << ", " << entries.size() << " total" << std::endl; 254 | } 255 | 256 | static tune_t sigmoid(const tune_t K, const tune_t eval) { 257 | return static_cast(1) / (static_cast(1) + exp(-K * eval / static_cast(400))); 258 | } 259 | 260 | static tune_t get_average_error(ThreadPool& thread_pool, const vector& entries, const parameters_t& parameters, tune_t K) { 261 | array thread_errors; 262 | for(int thread_id = 0; thread_id < thread_count; thread_id++) { 263 | thread_pool.enqueue([thread_id, &thread_errors, &entries, ¶meters, K]() { 264 | const auto entries_per_thread = entries.size() / thread_count; 265 | const auto start = static_cast(thread_id * entries_per_thread); 266 | const auto end = static_cast((thread_id + 1) * entries_per_thread - 1); 267 | tune_t error = 0; 268 | for (int i = start; i < end; i++) { 269 | const auto& entry = entries[i]; 270 | const auto eval = linear_eval(entry, parameters); 271 | const auto sig = sigmoid(K, eval); 272 | const auto diff = entry.wdl - sig; 273 | const auto entry_error = pow(diff, 2); 274 | error += entry_error; 275 | } 276 | thread_errors[thread_id] = error; 277 | }); 278 | } 279 | 280 | thread_pool.wait_for_completion(); 281 | 282 | tune_t total_error = 0; 283 | for (int thread_id = 0; thread_id < thread_count; thread_id++) { 284 | total_error += thread_errors[thread_id]; 285 | } 286 | 287 | const tune_t avg_error = total_error / static_cast(entries.size()); 288 | return avg_error; 289 | } 290 | 291 | static tune_t find_optimal_k(ThreadPool& thread_pool, const vector& entries, const parameters_t& parameters) { 292 | constexpr tune_t rate = 10; 293 | constexpr tune_t delta = 1e-5; 294 | constexpr tune_t deviation_goal = 1e-6; 295 | tune_t K = 2.5; 296 | tune_t deviation = 1; 297 | 298 | while (fabs(deviation) > deviation_goal) { 299 | const tune_t up = get_average_error(thread_pool, entries, parameters, K + delta); 300 | const tune_t down = get_average_error(thread_pool, entries, parameters, K - delta); 301 | deviation = (up - down) / (2 * delta); 302 | cout << "Current K: " << K << ", up: " << up << ", down: " << down << ", deviation: " << deviation << endl; 303 | K -= deviation * rate; 304 | } 305 | 306 | return K; 307 | } 308 | 309 | static void update_single_gradient(parameters_t& gradient, const Entry& entry, const parameters_t& params, tune_t K) { 310 | 311 | const tune_t eval = linear_eval(entry, params); 312 | const tune_t sig = sigmoid(K, eval); 313 | const tune_t res = (entry.wdl - sig) * sig * (1 - sig); 314 | 315 | const auto mg_base = res * (entry.phase / static_cast(24)); 316 | const auto eg_base = res - mg_base; 317 | 318 | for (const auto& coefficient : entry.coefficients) { 319 | gradient[coefficient.index][static_cast(PhaseStages::Midgame)] += mg_base * coefficient.value; 320 | gradient[coefficient.index][static_cast(PhaseStages::Endgame)] += eg_base * coefficient.value; 321 | } 322 | } 323 | 324 | static void compute_gradient(ThreadPool& thread_pool, parameters_t& gradient, const vector& entries, const parameters_t& params, tune_t K) { 325 | array thread_gradients; 326 | for(int thread_id = 0; thread_id < thread_count; thread_id++) { 327 | thread_pool.enqueue([thread_id, &thread_gradients, &entries, ¶ms, K]() { 328 | const auto entries_per_thread = entries.size() / thread_count; 329 | const auto start = static_cast(thread_id * entries_per_thread); 330 | const auto end = static_cast((thread_id + 1) * entries_per_thread - 1); 331 | parameters_t gradient = parameters_t(params.size(), pair_t{}); 332 | for (int i = start; i < end; i++) { 333 | const auto& entry = entries[i]; 334 | update_single_gradient(gradient, entry, params, K); 335 | } 336 | thread_gradients[thread_id] = gradient; 337 | }); 338 | } 339 | 340 | thread_pool.wait_for_completion(); 341 | 342 | for (int thread_id = 0; thread_id < thread_count; thread_id++) { 343 | for(auto parameter_index = 0; parameter_index < params.size(); parameter_index++) { 344 | gradient[parameter_index][static_cast(PhaseStages::Midgame)] += thread_gradients[thread_id][parameter_index][static_cast(PhaseStages::Midgame)]; 345 | gradient[parameter_index][static_cast(PhaseStages::Endgame)] += thread_gradients[thread_id][parameter_index][static_cast(PhaseStages::Endgame)]; 346 | } 347 | } 348 | } 349 | 350 | void Tuner::run(const std::vector& sources) { 351 | cout << "Starting tuning" << endl << endl; 352 | const auto start = high_resolution_clock::now(); 353 | 354 | cout << "Starting thread pool..." << endl; 355 | ThreadPool thread_pool; 356 | thread_pool.start(thread_count); 357 | 358 | cout << "Getting initial parameters..." << endl; 359 | auto parameters = TuneEval::get_initial_parameters(); 360 | cout << "Got " << parameters.size() << " parameters" << endl; 361 | 362 | cout << "Initial parameters:" << endl; 363 | TuneEval::print_parameters(parameters); 364 | 365 | vector entries; 366 | 367 | for (const auto& source : sources) { 368 | load_fens(source, parameters, start, entries); 369 | } 370 | cout << "Data loading complete" << endl << endl; 371 | 372 | print_statistics(parameters, entries); 373 | 374 | if constexpr (retune_from_zero) { 375 | for (auto& parameter : parameters) { 376 | parameter[static_cast(PhaseStages::Midgame)] = static_cast(0); 377 | parameter[static_cast(PhaseStages::Endgame)] = static_cast(0); 378 | } 379 | } 380 | 381 | cout << "Initial parameters:" << endl; 382 | TuneEval::print_parameters(parameters); 383 | 384 | tune_t K; 385 | if constexpr (preferred_k <= 0) { 386 | cout << "Finding optimal K..." << endl; 387 | K = find_optimal_k(thread_pool, entries, parameters); 388 | } else { 389 | cout << "Using predefined K = " << preferred_k << endl; 390 | K = preferred_k; 391 | } 392 | cout << "K = " << K << endl; 393 | 394 | const auto avg_error = get_average_error(thread_pool, entries, parameters, K); 395 | cout << "Initial error = " << avg_error << endl; 396 | 397 | const auto loop_start = high_resolution_clock::now(); 398 | tune_t learning_rate = 1; 399 | parameters_t momentum(parameters.size(), pair_t{}); 400 | parameters_t velocity(parameters.size(), pair_t{}); 401 | 402 | for (int epoch = 1; epoch < 1000000; epoch++) { 403 | parameters_t gradient(parameters.size(), pair_t{}); 404 | 405 | compute_gradient(thread_pool, gradient, entries, parameters, K); 406 | 407 | constexpr tune_t beta1 = 0.9; 408 | constexpr tune_t beta2 = 0.999; 409 | 410 | for (int parameter_index = 0; parameter_index < parameters.size(); parameter_index++) { 411 | for(int phase_stage = 0; phase_stage < 2; phase_stage++) { 412 | const tune_t grad = -K / static_cast(400) * gradient[parameter_index][phase_stage] / static_cast(entries.size()); 413 | momentum[parameter_index][phase_stage] = beta1 * momentum[parameter_index][phase_stage] + (1 - beta1) * grad; 414 | velocity[parameter_index][phase_stage] = beta2 * velocity[parameter_index][phase_stage] + (1 - beta2) * pow(grad, 2); 415 | parameters[parameter_index][phase_stage] -= learning_rate * momentum[parameter_index][phase_stage] / (static_cast(1e-8) + sqrt(velocity[parameter_index][phase_stage])); 416 | } 417 | 418 | } 419 | 420 | if (epoch % 100 == 0) { 421 | const auto elapsed_ms = duration_cast(high_resolution_clock::now() - loop_start).count(); 422 | const auto epochs_per_second = epoch * 1000.0 / elapsed_ms; 423 | const tune_t error = get_average_error(thread_pool, entries, parameters, K); 424 | print_elapsed(start); 425 | cout << "Epoch " << epoch << " (" << epochs_per_second << " eps), error " << error << ", LR " << learning_rate << endl; 426 | TuneEval::print_parameters(parameters); 427 | } 428 | 429 | constexpr int lr_drop_interval = 10000; 430 | constexpr tune_t lr_drop_ratio = 1; 431 | if(epoch % lr_drop_interval == 0) { 432 | learning_rate *= lr_drop_ratio; 433 | } 434 | } 435 | } 436 | -------------------------------------------------------------------------------- /texel-tuner/tuner.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "cstdint" 3 | #include "string" 4 | #include "vector" 5 | 6 | namespace Tuner { 7 | struct DataSource { 8 | std::string path; 9 | bool side_to_move_wdl; 10 | int64_t position_limit; 11 | }; 12 | 13 | void run(const std::vector& sources); 14 | } 15 | --------------------------------------------------------------------------------