├── .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 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/run_configs/.midnight_tests_run/MidnightTests.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/run_configs/.midnight_tune_run/MidnightTune.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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