diff --git a/.gitignore b/.gitignore index 6192df9..93c3eb9 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ build/* !build/makefile-clang *~ .vscode/* +.idea/* +cmake-build-debug/* diff --git a/build/makefile b/build/makefile index 3e48d70..05d19ca 100644 --- a/build/makefile +++ b/build/makefile @@ -1,5 +1,5 @@ EXE = seer -SRC = ../src/seer.cc ../syzygy/tbprobe.c +SRC = ../src/seer.cc ../syzygy/tbprobe.c ../src/search/*.cc ../src/chess/*.cc ../src/engine/*.cc CC = g++ EVALFILE = weights/0xddf7a131.bin @@ -12,7 +12,7 @@ INCBIN = ../incbin SYZYGY = ../syzygy CXXFLAGS += -std=c++$(CXXSTANDARD) -CXXFLAGS += -O3 -g -DNDEBUG -march=native -mtune=native -fopenmp -flto +CXXFLAGS += -O3 -g -DNDEBUG -march=native -mtune=native -fopenmp -flto -fwhole-program CXXFLAGS += -Wall -Wextra CXXFLAGS += -fconstexpr-ops-limit=$(OPSLIMIT) CXXFLAGS += -I$(INCLUDE) -I$(INCBIN) -I$(SYZYGY) diff --git a/build/makefile-clang b/build/makefile-clang index a5e8765..4f31891 100644 --- a/build/makefile-clang +++ b/build/makefile-clang @@ -1,5 +1,5 @@ EXE = seer -SRC = ../src/seer.cc ../syzygy/tbprobe.c +SRC = ../src/seer.cc ../syzygy/tbprobe.c ../src/search/*.cc ../src/chess/*.cc ../src/engine/*.cc CC = clang++ EVALFILE = weights/0xddf7a131.bin @@ -12,7 +12,7 @@ INCBIN = ../incbin SYZYGY = ../syzygy CXXFLAGS += -std=c++$(CXXSTANDARD) -CXXFLAGS += -O3 -g -DNDEBUG -march=native -mtune=native -fopenmp +CXXFLAGS += -O3 -g -DNDEBUG -march=native -mtune=native -fopenmp -flto CXXFLAGS += -Wall -Wextra -Wpedantic CXXFLAGS += -fconstexpr-steps=$(OPSLIMIT) CXXFLAGS += -I$(INCLUDE) -I$(INCBIN) -I$(SYZYGY) diff --git a/include/board.h b/include/board.h deleted file mode 100644 index cc94ed2..0000000 --- a/include/board.h +++ /dev/null @@ -1,1042 +0,0 @@ -/* - Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle - Seer is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - Seer is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace chess { - -template -struct move_generator_mode { - static constexpr bool noisy = noisy_value; - static constexpr bool check = check_value; - static constexpr bool quiet = quiet_value; -}; - -namespace generation_mode { - -using noisy_and_check = move_generator_mode; -using quiet_and_check = move_generator_mode; -using noisy = move_generator_mode; -using check = move_generator_mode; -using quiet = move_generator_mode; -using all = move_generator_mode; - -} // namespace generation_mode - -template -constexpr T material_value(const piece_type& pt) { - constexpr std::array values = {100, 300, 300, 450, 900, std::numeric_limits::max()}; - return values[static_cast(pt)]; -} - -template -constexpr T phase_value(const piece_type& pt) { - constexpr std::array values = {0, 1, 1, 2, 4, 0}; - return values[static_cast(pt)]; -} - -struct move_generator_info { - square_set occ; - square_set last_rank; - square_set checkers; - square_set checker_rays; - square_set pinned; - square_set king_danger; - square_set king_diagonal; - square_set king_horizontal; -}; - -struct board { - sided_manifest man_{}; - sided_latent lat_{}; - - bool turn() const { return lat_.ply_count % 2 == 0; } - bool is_rule50_draw() const { return lat_.half_clock >= 100; } - - zobrist::hash_type hash() const { return man_.hash() ^ lat_.hash(); } - - template - std::tuple least_valuable_attacker(const square& tgt, const square_set& ignore) const { - const auto p_mask = pawn_attack_tbl>.look_up(tgt); - const auto p_attackers = p_mask & man_.us().pawn() & ~ignore; - if (p_attackers.any()) { return std::tuple(piece_type::pawn, *p_attackers.begin()); } - - const auto n_mask = knight_attack_tbl.look_up(tgt); - const auto n_attackers = n_mask & man_.us().knight() & ~ignore; - if (n_attackers.any()) { return std::tuple(piece_type::knight, *n_attackers.begin()); } - - const square_set occ = (man_.white.all() | man_.black.all()) & ~ignore; - - const auto b_mask = bishop_attack_tbl.look_up(tgt, occ); - const auto b_attackers = b_mask & man_.us().bishop() & ~ignore; - if (b_attackers.any()) { return std::tuple(piece_type::bishop, *b_attackers.begin()); } - - const auto r_mask = rook_attack_tbl.look_up(tgt, occ); - const auto r_attackers = r_mask & man_.us().rook() & ~ignore; - if (r_attackers.any()) { return std::tuple(piece_type::rook, *r_attackers.begin()); } - - const auto q_mask = b_mask | r_mask; - const auto q_attackers = q_mask & man_.us().queen() & ~ignore; - if (q_attackers.any()) { return std::tuple(piece_type::queen, *q_attackers.begin()); } - - const auto k_mask = king_attack_tbl.look_up(tgt); - const auto k_attackers = k_mask & man_.us().king() & ~ignore; - if (k_attackers.any()) { return std::tuple(piece_type::king, *k_attackers.begin()); } - - return std::tuple(piece_type::pawn, tgt); - } - - template - std::tuple checkers(const square_set& occ) const { - const auto b_check_mask = bishop_attack_tbl.look_up(man_.us().king().item(), occ); - const auto r_check_mask = rook_attack_tbl.look_up(man_.us().king().item(), occ); - const auto n_check_mask = knight_attack_tbl.look_up(man_.us().king().item()); - const auto p_check_mask = pawn_attack_tbl.look_up(man_.us().king().item()); - const auto q_check_mask = b_check_mask | r_check_mask; - - const auto b_checkers = (b_check_mask & (man_.them().bishop() | man_.them().queen())); - const auto r_checkers = (r_check_mask & (man_.them().rook() | man_.them().queen())); - - square_set checker_rays_{}; - for (const auto sq : b_checkers) { checker_rays_ |= bishop_attack_tbl.look_up(sq, occ) & b_check_mask; } - for (const auto sq : r_checkers) { checker_rays_ |= rook_attack_tbl.look_up(sq, occ) & r_check_mask; } - - const auto checkers_ = (b_check_mask & man_.them().bishop() & occ) | (r_check_mask & man_.them().rook() & occ) | - (n_check_mask & man_.them().knight() & occ) | (p_check_mask & man_.them().pawn() & occ) | - (q_check_mask & man_.them().queen() & occ); - return std::tuple(checkers_, checker_rays_); - } - - template - square_set threat_mask() const { - // idea from koivisto - const square_set occ = man_.white.all() | man_.black.all(); - - square_set threats{}; - square_set vulnerable = man_.them().all(); - - vulnerable &= ~man_.them().pawn(); - square_set pawn_attacks{}; - for (const auto sq : man_.us().pawn()) { pawn_attacks |= pawn_attack_tbl.look_up(sq); } - threats |= pawn_attacks & vulnerable; - - vulnerable &= ~(man_.them().knight() | man_.them().bishop()); - square_set minor_attacks{}; - for (const auto sq : man_.us().knight()) { minor_attacks |= knight_attack_tbl.look_up(sq); } - for (const auto sq : man_.us().bishop()) { minor_attacks |= bishop_attack_tbl.look_up(sq, occ); } - threats |= minor_attacks & vulnerable; - - vulnerable &= ~man_.them().rook(); - square_set rook_attacks{}; - for (const auto sq : man_.us().rook()) { rook_attacks |= rook_attack_tbl.look_up(sq, occ); } - threats |= rook_attacks & vulnerable; - - return threats; - } - - template - bool creates_threat_(const move& mv) const { - const square_set occ = man_.white.all() | man_.black.all(); - auto attacks = [&occ](const piece_type& piece, const square& sq) { - switch (piece) { - case piece_type::pawn: return pawn_attack_tbl.look_up(sq); - case piece_type::knight: return knight_attack_tbl.look_up(sq); - case piece_type::bishop: return bishop_attack_tbl.look_up(sq, occ); - case piece_type::rook: return rook_attack_tbl.look_up(sq, occ); - default: return square_set{}; - } - }; - - const square_set current_attacks = attacks(mv.piece(), mv.from()); - const square_set next_attacks = attacks(mv.piece(), mv.to()); - const square_set new_attacks = next_attacks & ~current_attacks; - - const square_set vulnerable = [&, this] { - switch (mv.piece()) { - case piece_type::pawn: return man_.them().all() & ~(man_.them().pawn() | man_.them().king()); - case piece_type::knight: return man_.them().rook() | man_.them().queen(); - case piece_type::bishop: return man_.them().rook() | man_.them().queen(); - case piece_type::rook: return man_.them().queen(); - default: return square_set{}; - } - }(); - - return (new_attacks & vulnerable).any(); - } - - bool creates_threat(const move& mv) const { return turn() ? creates_threat_(mv) : creates_threat_(mv); } - - template - square_set king_danger() const { - const square_set occ = (man_.white.all() | man_.black.all()) & ~man_.us().king(); - square_set k_danger{}; - for (const auto sq : man_.them().pawn()) { k_danger |= pawn_attack_tbl>.look_up(sq); } - for (const auto sq : man_.them().knight()) { k_danger |= knight_attack_tbl.look_up(sq); } - for (const auto sq : man_.them().king()) { k_danger |= king_attack_tbl.look_up(sq); } - for (const auto sq : man_.them().rook()) { k_danger |= rook_attack_tbl.look_up(sq, occ); } - for (const auto sq : man_.them().bishop()) { k_danger |= bishop_attack_tbl.look_up(sq, occ); } - for (const auto sq : man_.them().queen()) { - k_danger |= rook_attack_tbl.look_up(sq, occ); - k_danger |= bishop_attack_tbl.look_up(sq, occ); - } - return k_danger; - } - - template - square_set pinned() const { - const square_set occ = man_.white.all() | man_.black.all(); - const auto k_x_diag = bishop_attack_tbl.look_up(man_.us().king().item(), square_set{}); - const auto k_x_hori = rook_attack_tbl.look_up(man_.us().king().item(), square_set{}); - const auto b_check_mask = bishop_attack_tbl.look_up(man_.us().king().item(), occ); - const auto r_check_mask = rook_attack_tbl.look_up(man_.us().king().item(), occ); - square_set pinned_set{}; - for (const auto sq : (k_x_hori & (man_.them().queen() | man_.them().rook()))) { - pinned_set |= r_check_mask & rook_attack_tbl.look_up(sq, occ) & man_.us().all(); - } - for (const auto sq : (k_x_diag & (man_.them().queen() | man_.them().bishop()))) { - pinned_set |= b_check_mask & bishop_attack_tbl.look_up(sq, occ) & man_.us().all(); - } - return pinned_set; - } - - template - void add_en_passant(move_list& mv_ls) const { - if constexpr (!mode::noisy) { return; } - if (lat_.them().ep_mask().any()) { - const square_set occ = man_.white.all() | man_.black.all(); - const square ep_square = lat_.them().ep_mask().item(); - const square_set enemy_pawn_mask = pawn_push_tbl>.look_up(ep_square, square_set{}); - const square_set from_mask = pawn_attack_tbl>.look_up(ep_square) & man_.us().pawn(); - for (const auto from : from_mask) { - const square_set occ_ = (occ & ~square_set{from.bit_board()} & ~enemy_pawn_mask) | lat_.them().ep_mask(); - if (!std::get<0>(checkers(occ_)).any()) { - mv_ls.push(from, ep_square, piece_type::pawn, false, piece_type::pawn, true, enemy_pawn_mask.item()); - } - } - } - } - - template - void add_castle(const move_generator_info& info, move_list& result) const { - if constexpr (!mode::noisy) { return; } - if (lat_.us().oo() && !(castle_info.oo_mask & (info.king_danger | info.occ)).any()) { - result.push(castle_info.start_king, castle_info.oo_rook, piece_type::king, true, piece_type::rook); - } - if (lat_.us().ooo() && !(castle_info.ooo_danger_mask & info.king_danger).any() && !(castle_info.ooo_occ_mask & info.occ).any()) { - result.push(castle_info.start_king, castle_info.ooo_rook, piece_type::king, true, piece_type::rook); - } - } - - template - void add_normal_pawn(const move_generator_info& info, move_list& result) const { - for (const auto from : (man_.us().pawn() & ~info.pinned)) { - const auto to_quiet = pawn_push_tbl.look_up(from, info.occ); - const auto to_noisy = pawn_attack_tbl.look_up(from) & man_.them().all(); - if constexpr (mode::quiet) { - for (const auto to : (to_quiet & ~info.last_rank)) { result.push(from, to, piece_type::pawn); } - } - if constexpr (mode::noisy) { - for (const auto to : (to_noisy & ~info.last_rank)) { result.push(from, to, piece_type::pawn, true, man_.them().occ(to)); } - } - for (const auto to : (to_quiet & info.last_rank)) { - if constexpr (mode::quiet) { result.push_under_promotions(from, to, piece_type::pawn); } - if constexpr (mode::noisy) { result.push_queen_promotion(from, to, piece_type::pawn); } - } - for (const auto to : (to_noisy & info.last_rank)) { - // for historical reasons, underpromotion captures are considered quiet - if constexpr (mode::quiet) { result.push_under_promotions(from, to, piece_type::pawn, true, man_.them().occ(to)); } - if constexpr (mode::noisy) { result.push_queen_promotion(from, to, piece_type::pawn, true, man_.them().occ(to)); } - } - } - } - - template - void add_normal_knight(const move_generator_info& info, move_list& result) const { - for (const auto from : (man_.us().knight() & ~info.pinned)) { - const auto to_mask = knight_attack_tbl.look_up(from); - if constexpr (mode::quiet) { - for (const auto to : (to_mask & ~info.occ)) { result.push(from, to, piece_type::knight); } - } - if constexpr (mode::noisy) { - for (const auto to : (to_mask & man_.them().all())) { result.push(from, to, piece_type::knight, true, man_.them().occ(to)); } - } - } - } - - template - void add_normal_bishop(const move_generator_info& info, move_list& result) const { - for (const auto from : (man_.us().bishop() & ~info.pinned)) { - const auto to_mask = bishop_attack_tbl.look_up(from, info.occ); - if constexpr (mode::quiet) { - for (const auto to : (to_mask & ~info.occ)) { result.push(from, to, piece_type::bishop); } - } - if constexpr (mode::noisy) { - for (const auto to : (to_mask & man_.them().all())) { result.push(from, to, piece_type::bishop, true, man_.them().occ(to)); } - } - } - } - - template - void add_normal_rook(const move_generator_info& info, move_list& result) const { - for (const auto from : (man_.us().rook() & ~info.pinned)) { - const auto to_mask = rook_attack_tbl.look_up(from, info.occ); - if constexpr (mode::quiet) { - for (const auto to : (to_mask & ~info.occ)) { result.push(from, to, piece_type::rook); } - } - if constexpr (mode::noisy) { - for (const auto to : (to_mask & man_.them().all())) { result.push(from, to, piece_type::rook, true, man_.them().occ(to)); } - } - } - } - - template - void add_normal_queen(const move_generator_info& info, move_list& result) const { - for (const auto from : (man_.us().queen() & ~info.pinned)) { - const auto to_mask = bishop_attack_tbl.look_up(from, info.occ) | rook_attack_tbl.look_up(from, info.occ); - if constexpr (mode::quiet) { - for (const auto to : (to_mask & ~info.occ)) { result.push(from, to, piece_type::queen); } - } - if constexpr (mode::noisy) { - for (const auto to : (to_mask & man_.them().all())) { result.push(from, to, piece_type::queen, true, man_.them().occ(to)); } - } - } - } - - template - void add_pinned_pawn(const move_generator_info& info, move_list& result) const { - for (const auto from : (man_.us().pawn() & info.pinned & info.king_diagonal)) { - const auto to_mask = pawn_attack_tbl.look_up(from) & info.king_diagonal; - if constexpr (mode::noisy) { - for (const auto to : (to_mask & ~info.last_rank & man_.them().all())) { - result.push(from, to, piece_type::pawn, true, man_.them().occ(to)); - } - } - for (const auto to : (to_mask & info.last_rank & man_.them().all())) { - if constexpr (mode::quiet) { result.push_under_promotions(from, to, piece_type::pawn, true, man_.them().occ(to)); } - if constexpr (mode::noisy) { result.push_queen_promotion(from, to, piece_type::pawn, true, man_.them().occ(to)); } - } - } - for (const auto from : (man_.us().pawn() & info.pinned & info.king_horizontal)) { - const auto to_mask = pawn_push_tbl.look_up(from, info.occ) & info.king_horizontal; - if constexpr (mode::quiet) { - for (const auto to : (to_mask & ~info.last_rank)) { result.push(from, to, piece_type::pawn); } - } - for (const auto to : (to_mask & info.last_rank)) { - if constexpr (mode::quiet) { result.push_under_promotions(from, to, piece_type::pawn); } - if constexpr (mode::noisy) { result.push_queen_promotion(from, to, piece_type::pawn); } - } - } - } - - template - void add_pinned_bishop(const move_generator_info& info, move_list& result) const { - for (const auto from : (man_.us().bishop() & info.pinned & info.king_diagonal)) { - const auto to_mask = bishop_attack_tbl.look_up(from, info.occ) & info.king_diagonal; - if constexpr (mode::quiet) { - for (const auto to : (to_mask & ~info.occ)) { result.push(from, to, piece_type::bishop); } - } - if constexpr (mode::noisy) { - for (const auto to : (to_mask & man_.them().all())) { result.push(from, to, piece_type::bishop, true, man_.them().occ(to)); } - } - } - } - - template - void add_pinned_rook(const move_generator_info& info, move_list& result) const { - for (const auto from : (man_.us().rook() & info.pinned & info.king_horizontal)) { - const auto to_mask = rook_attack_tbl.look_up(from, info.occ) & info.king_horizontal; - if constexpr (mode::quiet) { - for (const auto to : (to_mask & ~info.occ)) { result.push(from, to, piece_type::rook); } - } - if constexpr (mode::noisy) { - for (const auto to : (to_mask & man_.them().all())) { result.push(from, to, piece_type::rook, true, man_.them().occ(to)); } - } - } - } - - template - void add_pinned_queen(const move_generator_info& info, move_list& result) const { - for (const auto from : (man_.us().queen() & info.pinned & info.king_diagonal)) { - const auto to_mask = bishop_attack_tbl.look_up(from, info.occ) & info.king_diagonal; - if constexpr (mode::quiet) { - for (const auto to : (to_mask & ~info.occ)) { result.push(from, to, piece_type::queen); } - } - if constexpr (mode::noisy) { - for (const auto to : (to_mask & man_.them().all())) { result.push(from, to, piece_type::queen, true, man_.them().occ(to)); } - } - } - for (const auto from : (man_.us().queen() & info.pinned & info.king_horizontal)) { - const auto to_mask = rook_attack_tbl.look_up(from, info.occ) & info.king_horizontal; - if constexpr (mode::quiet) { - for (const auto to : (to_mask & ~info.occ)) { result.push(from, to, piece_type::queen); } - } - if constexpr (mode::noisy) { - for (const auto to : (to_mask & man_.them().all())) { result.push(from, to, piece_type::queen, true, man_.them().occ(to)); } - } - } - } - - template - void add_checked_pawn(const move_generator_info& info, move_list& result) const { - for (const auto from : (man_.us().pawn() & ~info.pinned)) { - const auto to_quiet = info.checker_rays & pawn_push_tbl.look_up(from, info.occ); - const auto to_noisy = info.checkers & pawn_attack_tbl.look_up(from); - if constexpr (mode::check) { - for (const auto to : (to_quiet & ~info.last_rank)) { result.push(from, to, piece_type::pawn); } - } - if constexpr (mode::noisy) { - for (const auto to : (to_noisy & ~info.last_rank)) { result.push(from, to, piece_type::pawn, true, man_.them().occ(to)); } - } - for (const auto to : (to_quiet & info.last_rank)) { - if constexpr (mode::check) { result.push_under_promotions(from, to, piece_type::pawn); } - if constexpr (mode::noisy) { result.push_queen_promotion(from, to, piece_type::pawn); } - } - for (const auto to : (to_noisy & info.last_rank)) { - if constexpr (mode::check) { result.push_under_promotions(from, to, piece_type::pawn, true, man_.them().occ(to)); } - if constexpr (mode::noisy) { result.push_queen_promotion(from, to, piece_type::pawn, true, man_.them().occ(to)); } - } - } - } - - template - void add_checked_knight(const move_generator_info& info, move_list& result) const { - for (const auto from : (man_.us().knight() & ~info.pinned)) { - const auto to_mask = knight_attack_tbl.look_up(from); - const auto to_quiet = info.checker_rays & to_mask; - const auto to_noisy = info.checkers & to_mask; - if constexpr (mode::check) { - for (const auto to : to_quiet) { result.push(from, to, piece_type::knight); } - } - if constexpr (mode::noisy) { - for (const auto to : to_noisy) { result.push(from, to, piece_type::knight, true, man_.them().occ(to)); } - } - } - } - - template - void add_checked_rook(const move_generator_info& info, move_list& result) const { - for (const auto from : (man_.us().rook() & ~info.pinned)) { - const auto to_mask = rook_attack_tbl.look_up(from, info.occ); - const auto to_quiet = info.checker_rays & to_mask; - const auto to_noisy = info.checkers & to_mask; - if constexpr (mode::check) { - for (const auto to : to_quiet) { result.push(from, to, piece_type::rook); } - } - if constexpr (mode::noisy) { - for (const auto to : to_noisy) { result.push(from, to, piece_type::rook, true, man_.them().occ(to)); } - } - } - } - - template - void add_checked_bishop(const move_generator_info& info, move_list& result) const { - for (const auto from : (man_.us().bishop() & ~info.pinned)) { - const auto to_mask = bishop_attack_tbl.look_up(from, info.occ); - const auto to_quiet = info.checker_rays & to_mask; - const auto to_noisy = info.checkers & to_mask; - if constexpr (mode::check) { - for (const auto to : to_quiet) { result.push(from, to, piece_type::bishop); } - } - if constexpr (mode::noisy) { - for (const auto to : to_noisy) { result.push(from, to, piece_type::bishop, true, man_.them().occ(to)); } - } - } - } - - template - void add_checked_queen(const move_generator_info& info, move_list& result) const { - for (const auto from : (man_.us().queen() & ~info.pinned)) { - const auto to_mask = bishop_attack_tbl.look_up(from, info.occ) | rook_attack_tbl.look_up(from, info.occ); - const auto to_quiet = info.checker_rays & to_mask; - const auto to_noisy = info.checkers & to_mask; - if constexpr (mode::check) { - for (const auto to : to_quiet) { result.push(from, to, piece_type::queen); } - } - if constexpr (mode::noisy) { - for (const auto to : to_noisy) { result.push(from, to, piece_type::queen, true, man_.them().occ(to)); } - } - } - } - - template - void add_king(const move_generator_info& info, move_list& result) const { - const square_set to_mask = ~info.king_danger & king_attack_tbl.look_up(man_.us().king().item()); - if (info.checkers.any() ? mode::check : mode::quiet) { - for (const square to : (to_mask & ~info.occ)) { result.push(man_.us().king().item(), to, piece_type::king); } - } - if (mode::noisy) { - for (const square to : (to_mask & man_.them().all())) { - result.push(man_.us().king().item(), to, piece_type::king, true, man_.them().occ(to)); - } - } - } - - template - move_generator_info get_move_generator_info() const { - const auto [checkers_, checker_rays_] = checkers(man_.white.all() | man_.black.all()); - - const move_generator_info info{ - man_.white.all() | man_.black.all(), - pawn_delta::last_rank, - checkers_, - checker_rays_, - pinned(), - king_danger(), - bishop_attack_tbl.look_up(man_.us().king().item(), square_set{}), - rook_attack_tbl.look_up(man_.us().king().item(), square_set{}), - }; - - return info; - } - - template - move_list generate_moves_() const { - const move_generator_info info = get_move_generator_info(); - const size_t num_checkers = info.checkers.count(); - move_list result{}; - - if (num_checkers == 0) { - add_normal_pawn(info, result); - add_normal_knight(info, result); - add_normal_rook(info, result); - add_normal_bishop(info, result); - add_normal_queen(info, result); - add_castle(info, result); - if (info.pinned.any()) { - add_pinned_pawn(info, result); - add_pinned_bishop(info, result); - add_pinned_rook(info, result); - add_pinned_queen(info, result); - } - } else if (num_checkers == 1) { - add_checked_pawn(info, result); - add_checked_knight(info, result); - add_checked_rook(info, result); - add_checked_bishop(info, result); - add_checked_queen(info, result); - } - add_king(info, result); - add_en_passant(result); - return result; - } - - template - move_list generate_moves() const { - return turn() ? generate_moves_() : generate_moves_(); - } - - template - bool is_legal_(const move& mv) const { - if (mv.is_castle_oo() || mv.is_castle_ooo() || mv.is_enpassant()) { - const move_generator_info info = get_move_generator_info(); - move_list list{}; - add_castle(info, list); - add_en_passant(list); - return list.has(mv); - } - - if (!man_.us().all().is_member(mv.from())) { return false; } - if (man_.us().all().is_member(mv.to())) { return false; } - if (mv.piece() != man_.us().occ(mv.from())) { return false; } - - if (mv.is_capture() != man_.them().all().is_member(mv.to())) { return false; } - if (mv.is_capture() && mv.captured() != man_.them().occ(mv.to())) { return false; } - if (!mv.is_capture() && mv.captured() != static_cast(0)) { return false; } - - if (!mv.is_enpassant() && mv.enpassant_sq() != square::from_index(0)) { return false; } - if (!mv.is_promotion() && mv.promotion() != static_cast(0)) { return false; } - - const move_generator_info info = get_move_generator_info(); - - const bool is_noisy = (!mv.is_promotion() || mv.promotion() == chess::piece_type::queen) && (mv.is_capture() || mv.is_promotion()); - if (!mode::noisy && is_noisy) { return false; } - if (!mode::check && info.checkers.any() && !is_noisy) { return false; } - if (!mode::quiet && !info.checkers.any() && !is_noisy) { return false; } - - const square_set rook_mask = rook_attack_tbl.look_up(mv.from(), info.occ); - const square_set bishop_mask = bishop_attack_tbl.look_up(mv.from(), info.occ); - - const bool legal_from_to = [&] { - const auto pawn_mask = (mv.is_capture() ? pawn_attack_tbl.look_up(mv.from()) : pawn_push_tbl.look_up(mv.from(), info.occ)); - switch (mv.piece()) { - case piece_type::pawn: return pawn_mask.is_member(mv.to()); - case piece_type::knight: return knight_attack_tbl.look_up(mv.from()).is_member(mv.to()); - case piece_type::bishop: return bishop_mask.is_member(mv.to()); - case piece_type::rook: return rook_mask.is_member(mv.to()); - case piece_type::queen: return (bishop_mask | rook_mask).is_member(mv.to()); - case piece_type::king: return king_attack_tbl.look_up(mv.from()).is_member(mv.to()); - default: return false; - } - }(); - - if (!legal_from_to) { return false; } - - if (mv.piece() == piece_type::king && info.king_danger.is_member(mv.to())) { return false; } - if (info.checkers.any() && mv.piece() != piece_type::king) { - if (info.checkers.count() >= 2) { return false; } - if (info.pinned.is_member(mv.from())) { return false; } - if (!(info.checkers | info.checker_rays).is_member(mv.to())) { return false; } - } - - if (info.pinned.is_member(mv.from())) { - const square_set piece_diagonal = bishop_mask; - const square_set piece_horizontal = rook_mask; - const bool same_diagonal = info.king_diagonal.is_member(mv.from()) && (info.king_diagonal & piece_diagonal).is_member(mv.to()); - const bool same_horizontal = info.king_horizontal.is_member(mv.from()) && (info.king_horizontal & piece_horizontal).is_member(mv.to()); - if (!same_diagonal && !same_horizontal) { return false; } - } - - if (mv.is_promotion()) { - if (mv.piece() != piece_type::pawn) { return false; } - if (!info.last_rank.is_member(mv.to())) { return false; } - if (mv.promotion() <= piece_type::pawn || mv.promotion() > piece_type::queen) { return false; } - } - - return true; - } - - template - bool is_legal(const move& mv) const { - return turn() ? is_legal_(mv) : is_legal_(mv); - } - - template - bool is_check_() const { - return std::get<0>(checkers(man_.white.all() | man_.black.all())).any(); - } - - bool is_check() const { return turn() ? is_check_() : is_check_(); } - - square_set us_threat_mask() const { return turn() ? threat_mask() : threat_mask(); } - - square_set them_threat_mask() const { return turn() ? threat_mask() : threat_mask(); } - - template - bool see_ge_(const move& mv, const T& threshold) const { - const square tgt_sq = mv.to(); - auto used_mask = square_set{}; - - auto on_sq = mv.is_promotion() ? mv.promotion() : mv.piece(); - used_mask.insert(mv.from()); - - T value = [&] { - T val{-threshold}; - if (mv.is_promotion()) { val += material_value(mv.promotion()) - material_value(mv.piece()); } - if (mv.is_capture() && !mv.is_castle_ooo() && !mv.is_castle_oo()) { val += material_value(mv.captured()); } - return val; - }(); - - for (;;) { - if (value < 0) { return false; } - if ((value - material_value(on_sq)) >= 0) { return true; } - - { - const auto [p, sq] = least_valuable_attacker>(tgt_sq, used_mask); - if (sq == tgt_sq) { break; } - - value -= material_value(on_sq); - used_mask.insert(sq); - on_sq = p; - } - - if (value >= 0) { return true; } - if ((value + material_value(on_sq)) < 0) { return false; } - - { - const auto [p, sq] = least_valuable_attacker(tgt_sq, used_mask); - if (sq == tgt_sq) { break; } - - value += material_value(on_sq); - used_mask.insert(sq); - on_sq = p; - } - } - - return value >= 0; - } - - template - T see_ge(const move& mv, const T& threshold) const { - return turn() ? see_ge_(mv, threshold) : see_ge_(mv, threshold); - } - - template - T see_gt(const move& mv, const T& threshold) const { - return see_ge(mv, threshold + 1); - } - - bool has_non_pawn_material() const { - return man_.us(turn()).knight().any() || man_.us(turn()).bishop().any() || man_.us(turn()).rook().any() || man_.us(turn()).queen().any(); - } - - template - bool is_passed_push_(const move& mv) const { - return ((mv.piece() == piece_type::pawn && !mv.is_capture()) && !(man_.them().pawn() & passer_tbl.mask(mv.to())).any()); - } - - bool is_passed_push(const move& mv) const { return turn() ? is_passed_push_(mv) : is_passed_push_(mv); } - - template - size_t side_num_pieces() const { - return man_.us().all().count(); - } - - size_t num_pieces() const { return side_num_pieces() + side_num_pieces(); } - - bool is_trivially_drawn() const { - return (num_pieces() == 2) || - ((num_pieces() == 3) && (man_.white.knight() | man_.white.bishop() | man_.black.knight() | man_.black.bishop()).any()); - } - - template - T phase() const { - static_assert(std::is_floating_point_v); - constexpr T start_pos_value = static_cast(24); - T value{}; - over_types([&](const piece_type& pt) { value += phase_value(pt) * (man_.white.get_plane(pt) | man_.black.get_plane(pt)).count(); }); - return std::min(value, start_pos_value) / start_pos_value; - } - - template - board forward_(const move& mv) const { - board copy = *this; - if (mv.is_null()) { - assert(!is_check_()); - } else if (mv.is_castle_ooo()) { - copy.lat_.us().set_ooo(false).set_oo(false); - copy.man_.us().remove_piece(piece_type::king, castle_info.start_king); - copy.man_.us().remove_piece(piece_type::rook, castle_info.ooo_rook); - copy.man_.us().add_piece(piece_type::king, castle_info.after_ooo_king); - copy.man_.us().add_piece(piece_type::rook, castle_info.after_ooo_rook); - } else if (mv.is_castle_oo()) { - copy.lat_.us().set_ooo(false).set_oo(false); - copy.man_.us().remove_piece(piece_type::king, castle_info.start_king); - copy.man_.us().remove_piece(piece_type::rook, castle_info.oo_rook); - copy.man_.us().add_piece(piece_type::king, castle_info.after_oo_king); - copy.man_.us().add_piece(piece_type::rook, castle_info.after_oo_rook); - } else { - copy.man_.us().remove_piece(mv.piece(), mv.from()); - if (mv.is_promotion()) { - copy.man_.us().add_piece(mv.promotion(), mv.to()); - } else { - copy.man_.us().add_piece(mv.piece(), mv.to()); - } - if (mv.is_capture()) { - copy.man_.them().remove_piece(mv.captured(), mv.to()); - } else if (mv.is_enpassant()) { - copy.man_.them().remove_piece(piece_type::pawn, mv.enpassant_sq()); - } else if (mv.is_pawn_double()) { - const square ep = pawn_push_tbl>.look_up(mv.to(), square_set{}).item(); - if ((man_.them().pawn() & pawn_attack_tbl.look_up(ep)).any()) { copy.lat_.us().set_ep_mask(ep); } - } - if (mv.from() == castle_info.start_king) { - copy.lat_.us().set_ooo(false).set_oo(false); - } else if (mv.from() == castle_info.oo_rook) { - copy.lat_.us().set_oo(false); - } else if (mv.from() == castle_info.ooo_rook) { - copy.lat_.us().set_ooo(false); - } - if (mv.to() == castle_info>.oo_rook) { - copy.lat_.them().set_oo(false); - } else if (mv.to() == castle_info>.ooo_rook) { - copy.lat_.them().set_ooo(false); - } - } - copy.lat_.them().clear_ep_mask(); - ++copy.lat_.ply_count; - ++copy.lat_.half_clock; - if (mv.is_capture() || mv.piece() == piece_type::pawn) { copy.lat_.half_clock = 0; } - return copy; - } - - board forward(const move& mv) const { return turn() ? forward_(mv) : forward_(mv); } - - board mirrored() const { - board mirror{}; - // manifest - over_types([&mirror, this](const piece_type& pt) { - for (const auto sq : man_.white.get_plane(pt).mirrored()) { mirror.man_.black.add_piece(pt, sq); } - for (const auto sq : man_.black.get_plane(pt).mirrored()) { mirror.man_.white.add_piece(pt, sq); } - }); - // latent - mirror.lat_.white.set_ooo(lat_.black.ooo()); - mirror.lat_.black.set_ooo(lat_.white.ooo()); - mirror.lat_.white.set_oo(lat_.black.oo()); - mirror.lat_.black.set_oo(lat_.white.oo()); - if (lat_.black.ep_mask().any()) { mirror.lat_.white.set_ep_mask(lat_.black.ep_mask().mirrored().item()); } - if (lat_.white.ep_mask().any()) { mirror.lat_.black.set_ep_mask(lat_.white.ep_mask().mirrored().item()); } - mirror.lat_.ply_count = lat_.ply_count ^ static_cast(1); - mirror.lat_.half_clock = lat_.half_clock; - - return mirror; - } - - template - void feature_full_reset(T& sided_set) const { - namespace h_ka = feature::half_ka; - - const square white_king = man_.white.king().item(); - const square black_king = man_.black.king().item(); - - sided_set.white.clear(); - sided_set.black.clear(); - - over_types([&](const piece_type& pt) { - for (const auto sq : man_.white.get_plane(pt)) { - sided_set.white.insert(h_ka::index(white_king, pt, sq)); - sided_set.black.insert(h_ka::index(black_king, pt, sq)); - } - }); - - over_types([&](const piece_type& pt) { - for (const auto sq : man_.black.get_plane(pt)) { - sided_set.white.insert(h_ka::index(white_king, pt, sq)); - sided_set.black.insert(h_ka::index(black_king, pt, sq)); - } - }); - } - - template - void half_feature_partial_reset_(const move& mv, T0& feature_reset_cache, T1& sided_set) const { - namespace h_ka = feature::half_ka; - const square our_king = mv.to(); - - auto& entry = feature_reset_cache.template us().look_up(our_king); - sided_piece_configuration& config = entry.config; - - over_types([&](const piece_type& pt) { - const square_set them_entry_plane = config.them().get_plane(pt); - const square_set us_entry_plane = config.us().get_plane(pt); - - const square_set them_board_plane = man_.them().get_plane(pt).excluding(mv.to()); - const square_set us_board_plane = [&] { - if (pt == piece_type::king) { return square_set::of(our_king); } - return man_.us().get_plane(pt).excluding(mv.from()); - }(); - - for (const auto sq : them_entry_plane & ~them_board_plane) { entry.erase(h_ka::index>(our_king, pt, sq)); } - for (const auto sq : (us_entry_plane & ~us_board_plane)) { entry.erase(h_ka::index(our_king, pt, sq)); } - - for (const auto sq : them_board_plane & ~them_entry_plane) { entry.insert(h_ka::index>(our_king, pt, sq)); } - for (const auto sq : us_board_plane & ~us_entry_plane) { entry.insert(h_ka::index(our_king, pt, sq)); } - - config.them().set_plane(pt, them_board_plane); - config.us().set_plane(pt, us_board_plane); - }); - - entry.copy_state_to(sided_set.template us()); - } - - template - void half_feature_move_delta_(const move& mv, T& sided_set) const { - namespace h_ka = feature::half_ka; - const square our_king = man_.us().king().item(); - const size_t erase_idx_0 = h_ka::index(our_king, mv.piece(), mv.from()); - - const size_t insert_idx = [&] { - const piece_type on_to = mv.is_promotion

() ? mv.promotion() : mv.piece(); - return h_ka::index(our_king, on_to, mv.to()); - }(); - - if (mv.is_capture()) { - const size_t erase_idx_1 = h_ka::index>(our_king, mv.captured(), mv.to()); - sided_set.template us().copy_parent_insert_erase_erase(insert_idx, erase_idx_0, erase_idx_1); - return; - } - - if (mv.is_enpassant()) { - const size_t erase_idx_1 = h_ka::index>(our_king, piece_type::pawn, mv.enpassant_sq()); - sided_set.template us().copy_parent_insert_erase_erase(insert_idx, erase_idx_0, erase_idx_1); - return; - } - - sided_set.template us().copy_parent_insert_erase(insert_idx, erase_idx_0); - } - - template - void feature_move_delta_(const move& mv, T0& feature_reset_cache, T1& sided_set) const { - namespace h_ka = feature::half_ka; - - if (mv.is_castle_oo() || mv.is_castle_ooo()) { - forward_(mv).feature_full_reset(sided_set); - return; - } - - if (mv.is_king_move()) { - half_feature_partial_reset_(mv, feature_reset_cache, sided_set); - half_feature_move_delta_, c>(mv, sided_set); - return; - } - - half_feature_move_delta_(mv, sided_set); - half_feature_move_delta_, c>(mv, sided_set); - } - - template - void feature_move_delta(const move& mv, T0& feature_reset_cache, T1& sided_set) const { - if (turn()) { - feature_move_delta_(mv, feature_reset_cache, sided_set); - } else { - feature_move_delta_(mv, feature_reset_cache, sided_set); - } - } - - std::tuple after_uci_moves(const std::string& moves) const { - position_history history{}; - auto bd = *this; - std::istringstream move_stream(moves); - std::string move_name; - while (move_stream >> move_name) { - const move_list list = bd.generate_moves<>(); - const auto it = std::find_if(list.begin(), list.end(), [=](const move& mv) { return mv.name(bd.turn()) == move_name; }); - assert((it != list.end())); - history.push_(bd.hash()); - bd = bd.forward(*it); - } - return std::tuple(history, bd); - } - - std::string fen() const { - std::string fen{}; - constexpr size_t num_ranks = 8; - for (size_t i{0}; i < num_ranks; ++i) { - size_t j{0}; - over_rank(i, [&, this](const tbl_square& at_r) { - const tbl_square at = at_r.rotated(); - if (man_.white.all().occ(at.index())) { - const char letter = piece_letter(color::white, man_.white.occ(at)); - if (j != 0) { fen.append(std::to_string(j)); } - fen.push_back(letter); - j = 0; - } else if (man_.black.all().occ(at.index())) { - const char letter = piece_letter(color::black, man_.black.occ(at)); - if (j != 0) { fen.append(std::to_string(j)); } - fen.push_back(letter); - j = 0; - } else { - ++j; - } - }); - if (j != 0) { fen.append(std::to_string(j)); } - if (i != (num_ranks - 1)) { fen.push_back('/'); } - } - fen.push_back(' '); - fen.push_back(turn() ? 'w' : 'b'); - fen.push_back(' '); - std::string castle_rights{}; - if (lat_.white.oo()) { castle_rights.push_back('K'); } - if (lat_.white.ooo()) { castle_rights.push_back('Q'); } - if (lat_.black.oo()) { castle_rights.push_back('k'); } - if (lat_.black.ooo()) { castle_rights.push_back('q'); } - fen.append(castle_rights.empty() ? "-" : castle_rights); - fen.push_back(' '); - fen.append(lat_.them(turn()).ep_mask().any() ? lat_.them(turn()).ep_mask().item().name() : "-"); - fen.push_back(' '); - fen.append(std::to_string(lat_.half_clock)); - fen.push_back(' '); - fen.append(std::to_string(1 + (lat_.ply_count / 2))); - return fen; - } - - static board start_pos() { return parse_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); } - - static board parse_fen(const std::string& fen) { - auto fen_pos = board(); - std::stringstream ss(fen); - - std::string body; - ss >> body; - std::string side; - ss >> side; - std::string castle; - ss >> castle; - std::string ep_sq; - ss >> ep_sq; - std::string half_clock; - ss >> half_clock; - std::string move_count; - ss >> move_count; - { - std::stringstream body_s(body); - std::string rank; - for (int rank_idx{0}; std::getline(body_s, rank, '/'); ++rank_idx) { - int file_idx{0}; - for (const char c : rank) { - if (std::isdigit(c)) { - file_idx += static_cast(c - '0'); - } else { - const color side = color_from(c); - const piece_type type = type_from(c); - const tbl_square sq = tbl_square{file_idx, rank_idx}.rotated(); - fen_pos.man_.us(side).add_piece(type, sq); - ++file_idx; - } - } - } - } - fen_pos.lat_.white.set_oo(castle.find('K') != std::string::npos); - fen_pos.lat_.white.set_ooo(castle.find('Q') != std::string::npos); - fen_pos.lat_.black.set_oo(castle.find('k') != std::string::npos); - fen_pos.lat_.black.set_ooo(castle.find('q') != std::string::npos); - fen_pos.lat_.half_clock = std::stol(half_clock); - if (ep_sq != "-") { fen_pos.lat_.them(side == "w").set_ep_mask(tbl_square::from_name(ep_sq)); } - fen_pos.lat_.ply_count = static_cast(2 * (std::stol(move_count) - 1) + static_cast(side != "w")); - return fen_pos; - } -}; - -std::ostream& operator<<(std::ostream& ostr, const board& bd) { - ostr << std::boolalpha; - ostr << "board(hash=" << bd.hash(); - ostr << ", half_clock=" << bd.lat_.half_clock; - ostr << ", ply_count=" << bd.lat_.ply_count; - ostr << ", white.oo_=" << bd.lat_.white.oo(); - ostr << ", white.ooo_=" << bd.lat_.white.ooo(); - ostr << ", black.oo_=" << bd.lat_.black.oo(); - ostr << ", black.ooo_=" << bd.lat_.black.ooo(); - ostr << ",\nwhite.ep_mask=" << bd.lat_.white.ep_mask(); - ostr << ",\nblack.ep_mask=" << bd.lat_.black.ep_mask(); - ostr << "white.occ_table={"; - over_all([&ostr, bd](const tbl_square& sq) { ostr << piece_name(bd.man_.white.occ(sq)) << ", "; }); - ostr << "},\nblack.occ_table={"; - over_all([&ostr, bd](const tbl_square& sq) { ostr << piece_name(bd.man_.black.occ(sq)) << ", "; }); - ostr << "}\n"; - over_types([&ostr, bd](const piece_type& pt) { ostr << "white." << piece_name(pt) << "=" << bd.man_.white.get_plane(pt) << ",\n"; }); - ostr << "white.all=" << bd.man_.white.all() << ",\n"; - over_types([&ostr, bd](const piece_type& pt) { ostr << "black." << piece_name(pt) << "=" << bd.man_.black.get_plane(pt) << ",\n"; }); - ostr << "black.all=" << bd.man_.black.all() << ")"; - return ostr << std::noboolalpha; -} - -} // namespace chess \ No newline at end of file diff --git a/include/board_state.h b/include/board_state.h deleted file mode 100644 index 157af29..0000000 --- a/include/board_state.h +++ /dev/null @@ -1,218 +0,0 @@ -/* - Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle - - Seer is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Seer is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#pragma once - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -namespace chess { - -struct manifest_zobrist_src { - static constexpr size_t num_squares = 64; - using plane_t = std::array; - plane_t pawn_{}; - plane_t knight_{}; - plane_t bishop_{}; - plane_t rook_{}; - plane_t queen_{}; - plane_t king_{}; - - std::array& get_plane(const piece_type& pt) { return get_member(pt, *this); } - - const std::array& get_plane(const piece_type& pt) const { return get_member(pt, *this); } - - template - zobrist::hash_type get(const piece_type& pt, const S& at) const { - static_assert(is_square_v, "at must be of square type"); - return get_plane(pt)[at.index()]; - } - - manifest_zobrist_src() { - over_types([this](const piece_type pt) { - plane_t& pt_plane = get_plane(pt); - std::transform(pt_plane.begin(), pt_plane.end(), pt_plane.begin(), [](auto...) { return zobrist::random_bit_string(); }); - }); - } -}; - -struct manifest { - static constexpr size_t num_squares = 64; - - const manifest_zobrist_src* zobrist_src_; - zobrist::hash_type hash_{0}; - square_set pawn_{}; - square_set knight_{}; - square_set bishop_{}; - square_set rook_{}; - square_set queen_{}; - square_set king_{}; - square_set all_{}; - - zobrist::hash_type hash() const { return hash_; } - - square_set& get_plane(const piece_type pt) { return get_member(pt, *this); } - - piece_type occ(const tbl_square& at) const { return occ(at.to_square()); } - - piece_type occ(const square& at) const { - if (knight_.is_member(at)) { return piece_type::knight; } - if (bishop_.is_member(at)) { return piece_type::bishop; } - if (rook_.is_member(at)) { return piece_type::rook; } - if (queen_.is_member(at)) { return piece_type::queen; } - if (king_.is_member(at)) { return piece_type::king; } - return piece_type::pawn; - } - - const square_set& all() const { return all_; } - const square_set& pawn() const { return pawn_; } - const square_set& knight() const { return knight_; } - const square_set& bishop() const { return bishop_; } - const square_set& rook() const { return rook_; } - const square_set& queen() const { return queen_; } - const square_set& king() const { return king_; } - - const square_set& get_plane(const piece_type pt) const { return get_member(pt, *this); } - - template - manifest& add_piece(const piece_type& pt, const S& at) { - static_assert(is_square_v, "at must be of square type"); - hash_ ^= zobrist_src_->get(pt, at); - all_ |= at.bit_board(); - get_plane(pt) |= at.bit_board(); - return *this; - } - - template - manifest& remove_piece(const piece_type& pt, const S& at) { - static_assert(is_square_v, "at must be of square type"); - hash_ ^= zobrist_src_->get(pt, at); - all_ &= ~at.bit_board(); - get_plane(pt) &= ~at.bit_board(); - return *this; - } - - manifest(const manifest_zobrist_src* src) : zobrist_src_{src} {} -}; - -struct sided_manifest : sided { - static inline const manifest_zobrist_src w_manifest_src{}; - static inline const manifest_zobrist_src b_manifest_src{}; - - manifest white; - manifest black; - - zobrist::hash_type hash() const { return white.hash() ^ black.hash(); } - - sided_manifest() : white(&w_manifest_src), black(&b_manifest_src) {} -}; - -struct latent_zobrist_src { - static constexpr size_t num_squares = 64; - zobrist::hash_type oo_; - zobrist::hash_type ooo_; - std::array ep_mask_; - - zobrist::hash_type get_oo() const { return oo_; } - zobrist::hash_type get_ooo() const { return ooo_; } - - template - zobrist::hash_type get_ep_mask(const S& at) const { - static_assert(is_square_v, "at must be of square type"); - return ep_mask_[at.index()]; - } - - latent_zobrist_src() { - oo_ = zobrist::random_bit_string(); - ooo_ = zobrist::random_bit_string(); - std::transform(ep_mask_.begin(), ep_mask_.end(), ep_mask_.begin(), [](auto...) { return zobrist::random_bit_string(); }); - } -}; - -struct latent { - const latent_zobrist_src* zobrist_src_; - zobrist::hash_type hash_{0}; - bool oo_{true}; - bool ooo_{true}; - square_set ep_mask_{}; - - zobrist::hash_type hash() const { return hash_; } - - bool oo() const { return oo_; } - - bool ooo() const { return ooo_; } - - const square_set& ep_mask() const { return ep_mask_; } - - latent& set_oo(bool val) { - if (val ^ oo_) { hash_ ^= zobrist_src_->get_oo(); } - oo_ = val; - return *this; - } - - latent& set_ooo(bool val) { - if (val ^ ooo_) { hash_ ^= zobrist_src_->get_ooo(); } - ooo_ = val; - return *this; - } - - latent& clear_ep_mask() { - if (ep_mask_.any()) { hash_ ^= zobrist_src_->get_ep_mask(ep_mask_.item()); } - ep_mask_ = square_set{}; - return *this; - } - - template - latent& set_ep_mask(const S& at) { - static_assert(is_square_v, "at must be of square type"); - clear_ep_mask(); - hash_ ^= zobrist_src_->get_ep_mask(at); - ep_mask_.insert(at); - return *this; - } - - latent(const latent_zobrist_src* src) : zobrist_src_{src} {} -}; - -struct sided_latent : sided { - static inline const latent_zobrist_src w_latent_src{}; - static inline const latent_zobrist_src b_latent_src{}; - static inline const zobrist::hash_type turn_white_src = zobrist::random_bit_string(); - static inline const zobrist::hash_type turn_black_src = zobrist::random_bit_string(); - - size_t half_clock{0}; - size_t ply_count{0}; - latent white; - latent black; - - zobrist::hash_type hash() const { - const zobrist::hash_type result = white.hash() ^ black.hash(); - return ((ply_count % 2) == 0) ? (result ^ turn_white_src) : (result ^ turn_black_src); - } - - sided_latent() : white(&w_latent_src), black(&b_latent_src) {} -}; - -} // namespace chess diff --git a/include/chess/board.h b/include/chess/board.h new file mode 100644 index 0000000..18a2ca3 --- /dev/null +++ b/include/chess/board.h @@ -0,0 +1,314 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace chess { + +template +struct move_generator_mode { + static constexpr bool noisy = noisy_value; + static constexpr bool check = check_value; + static constexpr bool quiet = quiet_value; +}; + +namespace generation_mode { + +using noisy_and_check = move_generator_mode; +using quiet_and_check = move_generator_mode; +using noisy = move_generator_mode; +using check = move_generator_mode; +using quiet = move_generator_mode; +using all = move_generator_mode; + +} // namespace generation_mode + +struct move_generator_info { + square_set occ; + square_set last_rank; + square_set checkers; + square_set checker_rays; + square_set pinned; + square_set king_danger; + square_set king_diagonal; + square_set king_horizontal; +}; + +struct board { + static constexpr std::size_t num_fen_tokens = 6; + + sided_manifest man_{}; + sided_latent lat_{}; + + [[nodiscard]] inline bool turn() const noexcept { return lat_.ply_count % 2 == 0; } + [[nodiscard]] inline bool is_rule50_draw() const noexcept { return lat_.half_clock >= 100; } + [[nodiscard]] inline zobrist::hash_type hash() const noexcept { return man_.hash() ^ lat_.hash(); } + + template + [[nodiscard]] std::tuple least_valuable_attacker(const square& tgt, const square_set& ignore) const noexcept; + + template + [[nodiscard]] inline std::tuple checkers(const square_set& occ) const noexcept; + + template + [[nodiscard]] inline square_set threat_mask() const noexcept; + [[nodiscard]] square_set us_threat_mask() const noexcept; + [[nodiscard]] square_set them_threat_mask() const noexcept; + + template + [[nodiscard]] inline bool creates_threat_(const move& mv) const noexcept; + [[nodiscard]] bool creates_threat(const move& mv) const noexcept; + + template + [[nodiscard]] inline square_set king_danger() const noexcept; + + template + [[nodiscard]] inline square_set pinned() const noexcept; + + template + inline void add_en_passant(move_list& mv_ls) const noexcept; + + template + inline void add_castle(const move_generator_info& info, move_list& result) const noexcept; + + template + inline void add_normal_pawn(const move_generator_info& info, move_list& result) const noexcept; + + template + inline void add_normal_knight(const move_generator_info& info, move_list& result) const noexcept; + + template + inline void add_normal_bishop(const move_generator_info& info, move_list& result) const noexcept; + + template + inline void add_normal_rook(const move_generator_info& info, move_list& result) const noexcept; + + template + inline void add_normal_queen(const move_generator_info& info, move_list& result) const noexcept; + + template + inline void add_pinned_pawn(const move_generator_info& info, move_list& result) const noexcept; + + template + inline void add_pinned_bishop(const move_generator_info& info, move_list& result) const noexcept; + + template + inline void add_pinned_rook(const move_generator_info& info, move_list& result) const noexcept; + + template + inline void add_pinned_queen(const move_generator_info& info, move_list& result) const noexcept; + + template + inline void add_checked_pawn(const move_generator_info& info, move_list& result) const noexcept; + + template + inline void add_checked_knight(const move_generator_info& info, move_list& result) const noexcept; + + template + inline void add_checked_rook(const move_generator_info& info, move_list& result) const noexcept; + + template + inline void add_checked_bishop(const move_generator_info& info, move_list& result) const noexcept; + + template + inline void add_checked_queen(const move_generator_info& info, move_list& result) const noexcept; + + template + inline void add_king(const move_generator_info& info, move_list& result) const noexcept; + + template + [[nodiscard]] inline move_generator_info get_move_generator_info() const noexcept; + + template + [[nodiscard]] inline move_list generate_moves_() const noexcept; + + template + [[nodiscard]] move_list generate_moves() const noexcept; + + template + [[nodiscard]] inline bool is_legal_(const move& mv) const noexcept; + + template + [[nodiscard]] bool is_legal(const move& mv) const noexcept; + + template + [[nodiscard]] inline bool is_check_() const noexcept; + [[nodiscard]] bool is_check() const noexcept; + + template + [[nodiscard]] inline bool see_ge_(const move& mv, const T& threshold) const noexcept; + + template + [[nodiscard]] bool see_ge(const move& mv, const T& threshold) const noexcept; + + template + [[nodiscard]] bool see_gt(const move& mv, const T& threshold) const noexcept; + + template + [[nodiscard]] T phase() const noexcept; + + [[nodiscard]] bool has_non_pawn_material() const noexcept; + + template + [[nodiscard]] inline bool is_passed_push_(const move& mv) const noexcept; + [[nodiscard]] bool is_passed_push(const move& mv) const noexcept; + + template + [[nodiscard]] std::size_t side_num_pieces() const noexcept; + [[nodiscard]] std::size_t num_pieces() const noexcept; + + [[nodiscard]] bool is_trivially_drawn() const noexcept; + + template + [[nodiscard]] board forward_(const move& mv) const noexcept; + [[nodiscard]] board forward(const move& mv) const noexcept; + + [[nodiscard]] board mirrored() const noexcept; + + template + void feature_full_reset(T& sided_set) const { + namespace h_ka = feature::half_ka; + + const square white_king = man_.white.king().item(); + const square black_king = man_.black.king().item(); + + sided_set.white.clear(); + sided_set.black.clear(); + + over_types([&](const piece_type& pt) { + for (const auto sq : man_.white.get_plane(pt)) { + sided_set.white.insert(h_ka::index(white_king, pt, sq)); + sided_set.black.insert(h_ka::index(black_king, pt, sq)); + } + }); + + over_types([&](const piece_type& pt) { + for (const auto sq : man_.black.get_plane(pt)) { + sided_set.white.insert(h_ka::index(white_king, pt, sq)); + sided_set.black.insert(h_ka::index(black_king, pt, sq)); + } + }); + } + + template + void half_feature_partial_reset_(const move& mv, T0& feature_reset_cache, T1& sided_set) const { + namespace h_ka = feature::half_ka; + const square our_king = mv.to(); + + auto& entry = feature_reset_cache.template us().look_up(our_king); + sided_piece_configuration& config = entry.config; + + over_types([&](const piece_type& pt) { + const square_set them_entry_plane = config.them().get_plane(pt); + const square_set us_entry_plane = config.us().get_plane(pt); + + const square_set them_board_plane = man_.them().get_plane(pt).excluding(mv.to()); + const square_set us_board_plane = [&] { + if (pt == piece_type::king) { return square_set::of(our_king); } + return man_.us().get_plane(pt).excluding(mv.from()); + }(); + + for (const auto sq : them_entry_plane & ~them_board_plane) { entry.erase(h_ka::index>(our_king, pt, sq)); } + for (const auto sq : (us_entry_plane & ~us_board_plane)) { entry.erase(h_ka::index(our_king, pt, sq)); } + + for (const auto sq : them_board_plane & ~them_entry_plane) { entry.insert(h_ka::index>(our_king, pt, sq)); } + for (const auto sq : us_board_plane & ~us_entry_plane) { entry.insert(h_ka::index(our_king, pt, sq)); } + + config.them().set_plane(pt, them_board_plane); + config.us().set_plane(pt, us_board_plane); + }); + + entry.copy_state_to(sided_set.template us()); + } + + template + void half_feature_move_delta_(const move& mv, T& sided_set) const { + namespace h_ka = feature::half_ka; + const square our_king = man_.us().king().item(); + const std::size_t erase_idx_0 = h_ka::index(our_king, mv.piece(), mv.from()); + + const std::size_t insert_idx = [&] { + const piece_type on_to = mv.is_promotion

() ? mv.promotion() : mv.piece(); + return h_ka::index(our_king, on_to, mv.to()); + }(); + + if (mv.is_capture()) { + const std::size_t erase_idx_1 = h_ka::index>(our_king, mv.captured(), mv.to()); + sided_set.template us().copy_parent_insert_erase_erase(insert_idx, erase_idx_0, erase_idx_1); + return; + } + + if (mv.is_enpassant()) { + const std::size_t erase_idx_1 = h_ka::index>(our_king, piece_type::pawn, mv.enpassant_sq()); + sided_set.template us().copy_parent_insert_erase_erase(insert_idx, erase_idx_0, erase_idx_1); + return; + } + + sided_set.template us().copy_parent_insert_erase(insert_idx, erase_idx_0); + } + + template + void feature_move_delta_(const move& mv, T0& feature_reset_cache, T1& sided_set) const { + namespace h_ka = feature::half_ka; + + if (mv.is_castle_oo() || mv.is_castle_ooo()) { + forward_(mv).feature_full_reset(sided_set); + return; + } + + if (mv.is_king_move()) { + half_feature_partial_reset_(mv, feature_reset_cache, sided_set); + half_feature_move_delta_, c>(mv, sided_set); + return; + } + + half_feature_move_delta_(mv, sided_set); + half_feature_move_delta_, c>(mv, sided_set); + } + + template + void feature_move_delta(const move& mv, T0& feature_reset_cache, T1& sided_set) const { + if (turn()) { + feature_move_delta_(mv, feature_reset_cache, sided_set); + } else { + feature_move_delta_(mv, feature_reset_cache, sided_set); + } + } + + [[nodiscard]] std::tuple after_uci_moves(const std::string& moves) const noexcept; + + [[nodiscard]] std::string fen() const noexcept; + + [[nodiscard]] static board start_pos() noexcept; + + [[nodiscard]] static board parse_fen(const std::string& fen) noexcept; +}; + +[[maybe_unused]] std::ostream& operator<<(std::ostream& ostr, const board& bd) noexcept; + +} // namespace chess \ No newline at end of file diff --git a/include/chess/board_history.h b/include/chess/board_history.h new file mode 100644 index 0000000..a60f89c --- /dev/null +++ b/include/chess/board_history.h @@ -0,0 +1,53 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include + +#include +#include + +namespace chess { + +template +struct base_history { + using value_type = U; + std::vector history_; + + [[nodiscard]] constexpr T& cast() noexcept { return static_cast(*this); } + [[nodiscard]] const T& cast() const noexcept { return static_cast(*this); } + + [[maybe_unused]] T& clear() noexcept { + history_.clear(); + return cast(); + } + + [[maybe_unused]] T& push(const value_type& elem) noexcept { + history_.push_back(elem); + return cast(); + } + + base_history() noexcept : history_{} {} + explicit base_history(const std::vector& history) noexcept : history_{history} {} +}; + +struct board_history : base_history { + [[nodiscard]] std::size_t count(const zobrist::hash_type& hash) const noexcept { return std::count(history_.crbegin(), history_.crend(), hash); } +}; + +} // namespace chess diff --git a/include/chess/board_state.h b/include/chess/board_state.h new file mode 100644 index 0000000..004c34d --- /dev/null +++ b/include/chess/board_state.h @@ -0,0 +1,166 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include +#include + +#include + +namespace chess { + +struct manifest_zobrist_src { + static constexpr std::size_t num_squares = 64; + using plane_t = std::array; + plane_t pawn_{}; + plane_t knight_{}; + plane_t bishop_{}; + plane_t rook_{}; + plane_t queen_{}; + plane_t king_{}; + + [[nodiscard]] constexpr std::array& get_plane(const piece_type& pt) noexcept { return get_member(pt, *this); } + + [[nodiscard]] constexpr const std::array& get_plane(const piece_type& pt) const noexcept { + return get_member(pt, *this); + } + + template + [[nodiscard]] zobrist::hash_type get(const piece_type& pt, const S& at) const noexcept; + + manifest_zobrist_src() noexcept; +}; + +struct manifest { + const manifest_zobrist_src* zobrist_src_; + zobrist::hash_type hash_{0}; + square_set pawn_{}; + square_set knight_{}; + square_set bishop_{}; + square_set rook_{}; + square_set queen_{}; + square_set king_{}; + square_set all_{}; + + [[nodiscard]] constexpr zobrist::hash_type hash() const noexcept { return hash_; } + + [[nodiscard]] constexpr square_set& get_plane(const piece_type pt) noexcept { return get_member(pt, *this); } + + [[nodiscard]] constexpr piece_type occ(const tbl_square& at) const noexcept { return occ(at.to_square()); } + + [[nodiscard]] constexpr piece_type occ(const square& at) const { + if (knight_.is_member(at)) { return piece_type::knight; } + if (bishop_.is_member(at)) { return piece_type::bishop; } + if (rook_.is_member(at)) { return piece_type::rook; } + if (queen_.is_member(at)) { return piece_type::queen; } + if (king_.is_member(at)) { return piece_type::king; } + return piece_type::pawn; + } + + [[nodiscard]] constexpr const square_set& all() const noexcept { return all_; } + [[nodiscard]] constexpr const square_set& pawn() const noexcept { return pawn_; } + [[nodiscard]] constexpr const square_set& knight() const noexcept { return knight_; } + [[nodiscard]] constexpr const square_set& bishop() const noexcept { return bishop_; } + [[nodiscard]] constexpr const square_set& rook() const noexcept { return rook_; } + [[nodiscard]] constexpr const square_set& queen() const noexcept { return queen_; } + [[nodiscard]] constexpr const square_set& king() const noexcept { return king_; } + + [[nodiscard]] constexpr const square_set& get_plane(const piece_type pt) const noexcept { return get_member(pt, *this); } + + template + [[maybe_unused]] manifest& add_piece(const piece_type& pt, const S& at) noexcept; + + template + [[maybe_unused]] manifest& remove_piece(const piece_type& pt, const S& at) noexcept; + + manifest(const manifest_zobrist_src* src) noexcept : zobrist_src_{src} {} +}; + +struct sided_manifest : public sided { + static inline const manifest_zobrist_src w_manifest_src{}; + static inline const manifest_zobrist_src b_manifest_src{}; + + manifest white; + manifest black; + + [[nodiscard]] constexpr zobrist::hash_type hash() const noexcept { return white.hash() ^ black.hash(); } + + sided_manifest() noexcept : white(&w_manifest_src), black(&b_manifest_src) {} +}; + +struct latent_zobrist_src { + static constexpr std::size_t num_squares = 64; + zobrist::hash_type oo_; + zobrist::hash_type ooo_; + std::array ep_mask_; + + [[nodiscard]] zobrist::hash_type get_oo() const noexcept { return oo_; } + [[nodiscard]] zobrist::hash_type get_ooo() const noexcept { return ooo_; } + + template + [[nodiscard]] zobrist::hash_type get_ep_mask(const S& at) const noexcept; + + latent_zobrist_src() noexcept; +}; + +struct latent { + const latent_zobrist_src* zobrist_src_; + zobrist::hash_type hash_{0}; + bool oo_{true}; + bool ooo_{true}; + square_set ep_mask_{}; + + [[nodiscard]] constexpr const zobrist::hash_type& hash() const noexcept { return hash_; } + + [[nodiscard]] constexpr bool oo() const noexcept { return oo_; } + [[nodiscard]] constexpr bool ooo() const noexcept { return ooo_; } + + [[nodiscard]] constexpr const square_set& ep_mask() const noexcept { return ep_mask_; } + + [[maybe_unused]] latent& set_oo(const bool val) noexcept; + [[maybe_unused]] latent& set_ooo(const bool val) noexcept; + + [[maybe_unused]] latent& clear_ep_mask() noexcept; + + template + [[maybe_unused]] latent& set_ep_mask(const S& at) noexcept; + + latent(const latent_zobrist_src* src) noexcept : zobrist_src_{src} {} +}; + +struct sided_latent : public sided { + static inline const latent_zobrist_src w_latent_src{}; + static inline const latent_zobrist_src b_latent_src{}; + static inline const zobrist::hash_type turn_white_src = zobrist::random_bit_string(); + static inline const zobrist::hash_type turn_black_src = zobrist::random_bit_string(); + + std::size_t half_clock{0}; + std::size_t ply_count{0}; + latent white; + latent black; + + [[nodiscard]] inline zobrist::hash_type hash() const noexcept { + const zobrist::hash_type result = white.hash() ^ black.hash(); + return ((ply_count % 2) == 0) ? (result ^ turn_white_src) : (result ^ turn_black_src); + } + + sided_latent() noexcept : white(&w_latent_src), black(&b_latent_src) {} +}; + +} // namespace chess diff --git a/include/chess/castle_info.h b/include/chess/castle_info.h new file mode 100644 index 0000000..fff6bcd --- /dev/null +++ b/include/chess/castle_info.h @@ -0,0 +1,131 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include + +namespace chess { + +template +struct castle_info_ {}; + +template <> +struct castle_info_ { + static constexpr tbl_square oo_rook_tbl{0, 0}; + static constexpr tbl_square ooo_rook_tbl{7, 0}; + static constexpr tbl_square start_king_tbl{3, 0}; + + static constexpr tbl_square after_oo_rook_tbl{2, 0}; + static constexpr tbl_square after_ooo_rook_tbl{4, 0}; + static constexpr tbl_square after_oo_king_tbl{1, 0}; + static constexpr tbl_square after_ooo_king_tbl{5, 0}; + + square oo_rook; + square ooo_rook; + square start_king; + + square after_oo_rook; + square after_ooo_rook; + square after_oo_king; + square after_ooo_king; + + square_set oo_mask; + + square_set ooo_danger_mask; + square_set ooo_occ_mask; + + constexpr castle_info_() noexcept + : oo_rook{oo_rook_tbl.to_square()}, + ooo_rook{ooo_rook_tbl.to_square()}, + start_king{start_king_tbl.to_square()}, + after_oo_rook{after_oo_rook_tbl.to_square()}, + after_ooo_rook{after_ooo_rook_tbl.to_square()}, + after_oo_king{after_oo_king_tbl.to_square()}, + after_ooo_king{after_ooo_king_tbl.to_square()} { + constexpr delta ooo_delta{1, 0}; + constexpr delta oo_delta{-1, 0}; + + for (auto sq = start_king_tbl.add(oo_delta); true; sq = sq.add(oo_delta)) { + oo_mask.insert(sq); + if (sq == after_oo_king_tbl) { break; } + } + + for (auto sq = start_king_tbl.add(ooo_delta); true; sq = sq.add(ooo_delta)) { + ooo_danger_mask.insert(sq); + if (sq == after_ooo_king_tbl) { break; } + } + + for (auto sq = start_king_tbl.add(ooo_delta); sq != ooo_rook_tbl; sq = sq.add(ooo_delta)) { ooo_occ_mask.insert(sq); } + } +}; + +template <> +struct castle_info_ { + static constexpr tbl_square oo_rook_tbl{0, 7}; + static constexpr tbl_square ooo_rook_tbl{7, 7}; + static constexpr tbl_square start_king_tbl{3, 7}; + + static constexpr tbl_square after_oo_rook_tbl{2, 7}; + static constexpr tbl_square after_ooo_rook_tbl{4, 7}; + static constexpr tbl_square after_oo_king_tbl{1, 7}; + static constexpr tbl_square after_ooo_king_tbl{5, 7}; + + square oo_rook; + square ooo_rook; + square start_king; + + square after_oo_rook; + square after_ooo_rook; + square after_oo_king; + square after_ooo_king; + + square_set oo_mask; + + square_set ooo_danger_mask; + square_set ooo_occ_mask; + + constexpr castle_info_() noexcept + : oo_rook{oo_rook_tbl.to_square()}, + ooo_rook{ooo_rook_tbl.to_square()}, + start_king{start_king_tbl.to_square()}, + after_oo_rook{after_oo_rook_tbl.to_square()}, + after_ooo_rook{after_ooo_rook_tbl.to_square()}, + after_oo_king{after_oo_king_tbl.to_square()}, + after_ooo_king{after_ooo_king_tbl.to_square()} { + constexpr delta ooo_delta{1, 0}; + constexpr delta oo_delta{-1, 0}; + + for (auto sq = start_king_tbl.add(oo_delta); true; sq = sq.add(oo_delta)) { + oo_mask.insert(sq); + if (sq == after_oo_king_tbl) { break; } + } + + for (auto sq = start_king_tbl.add(ooo_delta); true; sq = sq.add(ooo_delta)) { + ooo_danger_mask.insert(sq); + if (sq == after_ooo_king_tbl) { break; } + } + + for (auto sq = start_king_tbl.add(ooo_delta); sq != ooo_rook_tbl; sq = sq.add(ooo_delta)) { ooo_occ_mask.insert(sq); } + } +}; + +template +inline constexpr castle_info_ castle_info = castle_info_{}; + +} // namespace chess \ No newline at end of file diff --git a/include/chess/move.h b/include/chess/move.h new file mode 100644 index 0000000..457aebb --- /dev/null +++ b/include/chess/move.h @@ -0,0 +1,145 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include + +namespace chess { + +inline constexpr std::array under_promotion_types = {piece_type::knight, piece_type::bishop, piece_type::rook}; + +struct move { + using from_ = util::bit_range; + using to_ = util::next_bit_range; + using piece_ = util::next_bit_range; + using is_capture_ = util::next_bit_flag; + using is_enpassant_ = util::next_bit_flag; + using captured_ = util::next_bit_range; + using enpassant_sq_ = util::next_bit_range; + using promotion_ = util::next_bit_range; + + static constexpr std::size_t width = promotion_::last; + using data_type = std::uint32_t; + + data_type data; + + template + [[nodiscard]] constexpr typename B::type get_field_() const noexcept { + return B::get(data); + } + + template + [[maybe_unused]] constexpr move& set_field_(const typename B::type info) noexcept { + B::set(data, info); + return *this; + } + + [[nodiscard]] constexpr square from() const noexcept { return square::from_index(get_field_()); } + [[nodiscard]] constexpr square to() const noexcept { return square::from_index(get_field_()); } + [[nodiscard]] constexpr piece_type piece() const noexcept { return get_field_(); } + [[nodiscard]] constexpr bool is_capture() const noexcept { return get_field_(); } + [[nodiscard]] constexpr bool is_enpassant() const noexcept { return get_field_(); } + [[nodiscard]] constexpr piece_type captured() const noexcept { return get_field_(); } + + template + [[nodiscard]] constexpr T mvv_lva_key() const noexcept { + constexpr T num_pieces = static_cast(6); + return num_pieces * static_cast(get_field_()) + num_pieces - static_cast(get_field_()); + } + + [[nodiscard]] constexpr square enpassant_sq() const noexcept { return square::from_index(get_field_()); } + [[nodiscard]] constexpr piece_type promotion() const noexcept { return get_field_(); } + + [[nodiscard]] constexpr bool is_null() const noexcept { return data == 0; } + [[nodiscard]] constexpr bool is_king_move() const noexcept { return piece() == piece_type::king; } + + template + [[nodiscard]] constexpr bool is_castle_oo() const noexcept { + return piece() == piece_type::king && from() == castle_info.start_king && to() == castle_info.oo_rook; + } + + template + [[nodiscard]] constexpr bool is_castle_ooo() const noexcept { + return piece() == piece_type::king && from() == castle_info.start_king && to() == castle_info.ooo_rook; + } + + template + [[nodiscard]] constexpr bool is_promotion() const noexcept { + return piece() == piece_type::pawn && pawn_info::last_rank.is_member(to()); + } + + [[nodiscard]] constexpr bool is_promotion() const noexcept { return is_promotion() || is_promotion(); } + + template + [[nodiscard]] constexpr bool is_pawn_double() const noexcept { + return piece() == piece_type::pawn && pawn_info::start_rank.is_member(from()) && pawn_info::double_rank.is_member(to()); + } + + [[nodiscard]] constexpr bool is_quiet() const noexcept { return !is_capture() && !(is_promotion() && piece_type::queen == promotion()); } + [[nodiscard]] constexpr bool is_noisy() const noexcept { return !is_quiet(); } + + template + [[nodiscard]] std::string name() const noexcept; + [[nodiscard]] std::string name(bool pov) const noexcept; + + move() = default; + + constexpr explicit move(const data_type& data) noexcept : data{data} {} + + constexpr move( + square from, + square to, + piece_type piece, + bool is_capture = false, + piece_type captured = piece_type::pawn, + bool is_enpassant = false, + square enpassant_sq = square::from_index(0), + piece_type promotion = piece_type::pawn) noexcept + : data{0} { + const auto from_idx = static_cast(from.index()); + const auto to_idx = static_cast(to.index()); + const auto ep_sq_idx = static_cast(enpassant_sq.index()); + set_field_(from_idx) + .set_field_(to_idx) + .set_field_(piece) + .set_field_(is_capture) + .set_field_(is_enpassant) + .set_field_(captured) + .set_field_(ep_sq_idx) + .set_field_(promotion); + } + + constexpr static move null() noexcept { return move{0}; } +}; + +constexpr bool operator==(const move& a, const move& b) { return a.data == b.data; } +constexpr bool operator!=(const move& a, const move& b) { return !(a == b); } +std::ostream& operator<<(std::ostream& ostr, const move& mv) noexcept; + +struct move_hash { + [[nodiscard]] std::size_t operator()(const move& mv) const noexcept { return std::hash{}(mv.data); } +}; + +} // namespace chess \ No newline at end of file diff --git a/include/chess/move_list.h b/include/chess/move_list.h new file mode 100644 index 0000000..acabfb1 --- /dev/null +++ b/include/chess/move_list.h @@ -0,0 +1,84 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include + +#include +#include +#include + +namespace chess { + +struct move_list { + static constexpr std::size_t max_branching_factor = 192; + + using iterator = std::array::iterator; + using const_iterator = std::array::const_iterator; + using size_type = std::array::size_type; + + size_type size_{0}; + std::array data{}; + + [[nodiscard]] constexpr iterator begin() noexcept { return data.begin(); } + [[nodiscard]] constexpr iterator end() noexcept { return data.begin() + size_; } + + [[nodiscard]] constexpr const_iterator begin() const noexcept { return data.cbegin(); } + [[nodiscard]] constexpr const_iterator end() const noexcept { return data.cbegin() + size_; } + + [[nodiscard]] constexpr const_iterator cbegin() const noexcept { return data.cbegin(); } + [[nodiscard]] constexpr const_iterator cend() const noexcept { return data.cbegin() + size_; } + + [[nodiscard]] inline bool has(const move& mv) const noexcept { return end() != std::find(begin(), end(), mv); } + + [[nodiscard]] constexpr size_type size() const noexcept { return size_; } + [[nodiscard]] constexpr bool empty() const noexcept { return size_ == 0; } + + [[nodiscard]] constexpr move& operator[](const size_type& idx) noexcept { return data[idx]; } + [[nodiscard]] constexpr const move& operator[](const size_type& idx) const noexcept { return data[idx]; } + + [[maybe_unused]] constexpr move_list& push(const move& mv) noexcept { + constexpr size_type last_idx = max_branching_factor - 1; + data[size_] = mv; + ++size_; + if (size_ > last_idx) { size_ = last_idx; } + return *this; + } + + template + [[maybe_unused]] constexpr move_list& push(const Ts&... ts) noexcept { + return push(move(ts...)); + } + + template + [[maybe_unused]] constexpr move_list& push_queen_promotion(const Ts&... ts) noexcept { + push(move(ts...).set_field_(piece_type::queen)); + return *this; + } + + template + [[maybe_unused]] constexpr move_list& push_under_promotions(const Ts&... ts) noexcept { + for (const auto& pt : under_promotion_types) { push(move(ts...).set_field_(pt)); } + return *this; + } +}; + +std::ostream& operator<<(std::ostream& ostr, const move_list& mv_ls) noexcept; + +} // namespace chess \ No newline at end of file diff --git a/include/chess/pawn_info.h b/include/chess/pawn_info.h new file mode 100644 index 0000000..280de9c --- /dev/null +++ b/include/chess/pawn_info.h @@ -0,0 +1,54 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include + +#include + +namespace chess { + +template +struct pawn_info {}; + +template <> +struct pawn_info { + static constexpr int start_rank_idx = 1; + static constexpr int last_rank_idx = 7; + static constexpr int double_rank_idx = 3; + static constexpr square_set start_rank = generate_rank(start_rank_idx); + static constexpr square_set last_rank = generate_rank(last_rank_idx); + static constexpr square_set double_rank = generate_rank(double_rank_idx); + static constexpr std::array attack = {delta{-1, 1}, delta{1, 1}}; + static constexpr delta step = delta{0, 1}; +}; + +template <> +struct pawn_info { + static constexpr int start_rank_idx = 6; + static constexpr int last_rank_idx = 0; + static constexpr int double_rank_idx = 4; + static constexpr square_set start_rank = generate_rank(start_rank_idx); + static constexpr square_set last_rank = generate_rank(last_rank_idx); + static constexpr square_set double_rank = generate_rank(double_rank_idx); + static constexpr std::array attack = {delta{-1, -1}, delta{1, -1}}; + static constexpr delta step = delta{0, -1}; +}; + +} // namespace chess \ No newline at end of file diff --git a/include/piece_configuration.h b/include/chess/piece_configuration.h similarity index 65% rename from include/piece_configuration.h rename to include/chess/piece_configuration.h index d2a822e..915c6a7 100644 --- a/include/piece_configuration.h +++ b/include/chess/piece_configuration.h @@ -1,6 +1,6 @@ /* Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle Seer is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,8 +17,8 @@ #pragma once -#include -#include +#include +#include namespace chess { @@ -30,15 +30,15 @@ struct piece_configuration { square_set queen_{}; square_set king_{}; - const square_set& get_plane(const piece_type& pt) const { return get_member(pt, *this); } - void set_plane(const piece_type& pt, const square_set& plane) { get_member(pt, *this) = plane; } + [[nodiscard]] constexpr const square_set& get_plane(const piece_type& pt) const noexcept { return get_member(pt, *this); } + constexpr void set_plane(const piece_type& pt, const square_set& plane) noexcept { get_member(pt, *this) = plane; } }; -struct sided_piece_configuration : sided { +struct sided_piece_configuration : public sided { piece_configuration white; piece_configuration black; - sided_piece_configuration() : white{}, black{} {} + constexpr sided_piece_configuration() noexcept : white{}, black{} {} }; } // namespace chess diff --git a/include/chess/square.h b/include/chess/square.h new file mode 100644 index 0000000..ff400de --- /dev/null +++ b/include/chess/square.h @@ -0,0 +1,235 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include +#include +#include + +namespace chess { + +[[nodiscard]] constexpr std::uint64_t pop_count(const std::uint64_t& x) noexcept { return static_cast(__builtin_popcountll(x)); } +[[nodiscard]] constexpr std::uint64_t count_trailing_zeros(const std::uint64_t& x) noexcept { return static_cast(__builtin_ctzll(x)); } + +struct square_set; + +struct square { + using data_type = std::uint64_t; + + data_type data; + + [[nodiscard]] constexpr const square::data_type& bit_board() const noexcept { return data; } + + [[nodiscard]] constexpr int index() const noexcept { return count_trailing_zeros(data); } + [[nodiscard]] constexpr int file() const noexcept { return index() % 8; } + [[nodiscard]] constexpr int rank() const noexcept { return index() / 8; } + + [[nodiscard]] constexpr bool operator==(const square& other) const noexcept { return other.data == data; } + [[nodiscard]] constexpr bool operator!=(const square& other) const noexcept { return !(*this == other); } + + [[nodiscard]] std::string name() const noexcept; + + constexpr explicit square(const data_type& bb) noexcept : data{bb} {} + + template + [[nodiscard]] constexpr static square from_index(const I& index) noexcept { + static_assert(std::is_integral_v, "square index must be of integral type"); + return square(static_cast(1) << static_cast(index)); + } +}; + +std::ostream& operator<<(std::ostream& ostr, const square& sq) noexcept; + +struct delta { + int x{0}; + int y{0}; +}; + +struct tbl_square { + int file{0}; + int rank{0}; + + [[nodiscard]] constexpr square::data_type index() const noexcept { return static_cast(rank * 8 + file); } + [[nodiscard]] constexpr bool is_valid() const noexcept { return 0 <= file && file < 8 && 0 <= rank && rank < 8; } + + [[nodiscard]] constexpr square::data_type bit_board() const noexcept { return static_cast(1) << index(); } + [[nodiscard]] constexpr square to_square() const noexcept { return square::from_index(index()); } + + [[nodiscard]] constexpr tbl_square rotated() const noexcept { return tbl_square{7 - file, 7 - rank}; } + [[nodiscard]] constexpr tbl_square add(delta d) const noexcept { return tbl_square{file + d.x, rank + d.y}; } + + template + [[nodiscard]] static constexpr tbl_square from_index(I index) noexcept { + return tbl_square{static_cast(index) % 8, static_cast(index) / 8}; + } + + [[nodiscard]] constexpr bool operator==(const tbl_square& other) const noexcept { return (other.rank) == rank && (other.file == file); } + [[nodiscard]] constexpr bool operator!=(const tbl_square& other) const noexcept { return !(*this == other); } + + [[nodiscard]] static tbl_square from_name(const std::string& name) noexcept; +}; + +template +inline constexpr bool is_square_v = std::is_same_v || std::is_same_v; + +struct square_set_iterator { + using difference_type = long; + using value_type = square; + using pointer = const square*; + using reference = const square&; + using iterator_category = std::output_iterator_tag; + + square::data_type remaining; + + [[maybe_unused]] constexpr square_set_iterator& operator++() noexcept { + remaining &= (remaining - static_cast(1)); + return *this; + } + + [[maybe_unused]] constexpr square_set_iterator operator++(int) noexcept { + auto retval = *this; + ++(*this); + return retval; + } + + [[nodiscard]] constexpr bool operator==(const square_set_iterator& other) const noexcept { return other.remaining == remaining; } + [[nodiscard]] constexpr bool operator!=(const square_set_iterator& other) const noexcept { return !(*this == other); } + + [[nodiscard]] constexpr square operator*() const { return square{remaining & ~(remaining - static_cast(1))}; } + + constexpr explicit square_set_iterator(const square::data_type& set) noexcept : remaining{set} {} +}; + +struct square_set { + static constexpr square::data_type one = static_cast(1); + using iterator = square_set_iterator; + square::data_type data; + + [[nodiscard]] constexpr iterator begin() const noexcept { return square_set_iterator(data); } + [[nodiscard]] constexpr iterator end() const noexcept { return square_set_iterator(static_cast(0)); } + + [[nodiscard]] constexpr square_set excluding(const square& sq) const noexcept { return square_set(data & ~sq.bit_board()); } + + [[maybe_unused]] constexpr square_set& insert(const tbl_square& tbl_sq) noexcept { + data |= one << static_cast(tbl_sq.index()); + return *this; + } + + [[maybe_unused]] constexpr square_set& insert(const square& sq) noexcept { + data |= sq.bit_board(); + return *this; + } + + [[nodiscard]] constexpr std::size_t count() const noexcept { return pop_count(data); } + [[nodiscard]] constexpr bool any() const noexcept { return data != 0; } + [[nodiscard]] constexpr square item() const noexcept { return square{data}; } + [[nodiscard]] constexpr bool is_member(const square& sq) const noexcept { return 0 != (sq.bit_board() & data); } + + [[maybe_unused]] constexpr square_set& operator|=(const square_set& other) noexcept { + data |= other.data; + return *this; + } + + [[maybe_unused]] constexpr square_set& operator&=(const square_set& other) noexcept { + data &= other.data; + return *this; + } + + [[maybe_unused]] constexpr square_set& operator^=(const square_set& other) noexcept { + data ^= other.data; + return *this; + } + + [[maybe_unused]] constexpr square_set& operator|=(const square::data_type& other) noexcept { + data |= other; + return *this; + } + + [[maybe_unused]] constexpr square_set& operator&=(const square::data_type& other) noexcept { + data &= other; + return *this; + } + + [[maybe_unused]] constexpr square_set& operator^=(const square::data_type& other) noexcept { + data ^= other; + return *this; + } + + [[nodiscard]] constexpr square_set mirrored() const noexcept { + return square_set{ + (data << 56) | ((data << 40) & static_cast(0x00ff000000000000)) | + ((data << 24) & static_cast(0x0000ff0000000000)) | ((data << 8) & static_cast(0x000000ff00000000)) | + ((data >> 8) & static_cast(0x00000000ff000000)) | ((data >> 24) & static_cast(0x0000000000ff0000)) | + ((data >> 40) & static_cast(0x000000000000ff00)) | (data >> 56)}; + } + + template + [[nodiscard]] constexpr bool occ(const I& idx) const noexcept { + static_assert(std::is_integral_v, "idx must be of integral type"); + return static_cast(data & (one << static_cast(idx))); + } + + template + [[nodiscard]] static constexpr square_set of(Ts&&... ts) noexcept { + auto bit_board = [](auto&& sq) { return sq.bit_board(); }; + auto bit_wise_or = [](auto&&... args) { return (args | ...); }; + return square_set(bit_wise_or(bit_board(std::forward(ts))...)); + } + + constexpr square_set() noexcept : data{0} {} + constexpr explicit square_set(const square::data_type& set) noexcept : data{set} {} +}; + +[[nodiscard]] constexpr square_set operator~(const square_set& ss) noexcept { return square_set(~ss.data); } +[[nodiscard]] constexpr square_set operator&(const square_set& a, const square_set& b) noexcept { return square_set(a.data & b.data); } +[[nodiscard]] constexpr square_set operator|(const square_set& a, const square_set& b) noexcept { return square_set(a.data | b.data); } +[[nodiscard]] constexpr square_set operator^(const square_set& a, const square_set& b) noexcept { return square_set(a.data ^ b.data); } + +std::ostream& operator<<(std::ostream& ostr, const square_set& ss) noexcept; + +template +constexpr void over_all(F&& f) noexcept { + for (int i(0); i < 8; ++i) { + for (int j(0); j < 8; ++j) { f(tbl_square{i, j}); } + } +} + +template +constexpr void over_rank(const int& rank, F&& f) noexcept { + for (auto sq = tbl_square{0, rank}; sq.is_valid(); sq = sq.add(delta{1, 0})) { f(sq); } +} + +template +constexpr void over_file(const int& file, F&& f) noexcept { + for (auto sq = tbl_square{file, 0}; sq.is_valid(); sq = sq.add(delta{0, 1})) { f(sq); } +} + +[[nodiscard]] constexpr square_set generate_rank(const int& rank) noexcept { + square_set ss{}; + over_rank(rank, [&ss](tbl_square& sq) { ss.insert(sq); }); + return ss; +} + +[[nodiscard]] constexpr square_set generate_file(const int& file) noexcept { + square_set ss{}; + over_file(file, [&ss](tbl_square& sq) { ss.insert(sq); }); + return ss; +} + +} // namespace chess diff --git a/include/table_generation.h b/include/chess/table_generation.h similarity index 58% rename from include/table_generation.h rename to include/chess/table_generation.h index 339a38d..fe19f56 100644 --- a/include/table_generation.h +++ b/include/chess/table_generation.h @@ -1,6 +1,6 @@ /* Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle Seer is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,8 +17,9 @@ #pragma once -#include -#include +#include +#include +#include #include #include @@ -61,7 +62,7 @@ constexpr std::array bishop_magics = { 0x0400000260142410ull, 0x0800633408100500ull, 0xFC087E8E4BB2F736ull, 0x43FF9E4EF4CA2C89ull, }; -constexpr std::uint64_t deposit(std::uint64_t src, std::uint64_t mask) { +[[nodiscard]] constexpr std::uint64_t deposit(std::uint64_t src, std::uint64_t mask) noexcept { std::uint64_t res{0}; for (std::uint64_t bb{1}; mask; bb += bb) { if (src & bb) { res |= mask & -mask; } @@ -70,60 +71,33 @@ constexpr std::uint64_t deposit(std::uint64_t src, std::uint64_t mask) { return res; } -constexpr std::array queen_deltas() { +[[nodiscard]] constexpr std::array queen_deltas() noexcept { using D = delta; return {D{1, 0}, D{0, 1}, D{-1, 0}, D{0, -1}, D{1, -1}, D{-1, 1}, D{-1, -1}, D{1, 1}}; } -constexpr std::array king_deltas() { +[[nodiscard]] constexpr std::array king_deltas() noexcept { using D = delta; return {D{1, 0}, D{0, 1}, D{-1, 0}, D{0, -1}, D{1, -1}, D{-1, 1}, D{-1, -1}, D{1, 1}}; } -constexpr std::array knight_deltas() { +[[nodiscard]] constexpr std::array knight_deltas() noexcept { using D = delta; return {D{1, 2}, D{2, 1}, D{-1, 2}, D{2, -1}, D{1, -2}, D{-2, 1}, D{-1, -2}, D{-2, -1}}; } -constexpr std::array bishop_deltas() { +[[nodiscard]] constexpr std::array bishop_deltas() noexcept { using D = delta; return {D{1, -1}, D{-1, 1}, D{-1, -1}, D{1, 1}}; } -constexpr std::array rook_deltas() { +[[nodiscard]] constexpr std::array rook_deltas() noexcept { using D = delta; return {D{1, 0}, D{0, 1}, D{-1, 0}, D{0, -1}}; } -template -struct pawn_delta {}; - -template <> -struct pawn_delta { - static constexpr int start_rank_idx = 1; - static constexpr int last_rank_idx = 7; - static constexpr int double_rank_idx = 3; - static constexpr square_set start_rank = gen_rank(start_rank_idx); - static constexpr square_set last_rank = gen_rank(last_rank_idx); - static constexpr square_set double_rank = gen_rank(double_rank_idx); - static constexpr std::array attack = {delta{-1, 1}, delta{1, 1}}; - static constexpr delta step = delta{0, 1}; -}; - -template <> -struct pawn_delta { - static constexpr int start_rank_idx = 6; - static constexpr int last_rank_idx = 0; - static constexpr int double_rank_idx = 4; - static constexpr square_set start_rank = gen_rank(start_rank_idx); - static constexpr square_set last_rank = gen_rank(last_rank_idx); - static constexpr square_set double_rank = gen_rank(double_rank_idx); - static constexpr std::array attack = {delta{-1, -1}, delta{1, -1}}; - static constexpr delta step = delta{0, -1}; -}; - template -constexpr void over_all_step_attacks(const D& deltas, F&& f) { +constexpr void over_all_step_attacks(const D& deltas, F&& f) noexcept { auto do_shift = [f](const tbl_square from, const delta d) { if (auto to = from.add(d); to.is_valid()) { f(from, to); } }; @@ -132,140 +106,43 @@ constexpr void over_all_step_attacks(const D& deltas, F&& f) { }); } -template -struct castle_info_ {}; - -template <> -struct castle_info_ { - static constexpr tbl_square oo_rook_tbl{0, 0}; - static constexpr tbl_square ooo_rook_tbl{7, 0}; - static constexpr tbl_square start_king_tbl{3, 0}; - - static constexpr tbl_square after_oo_rook_tbl{2, 0}; - static constexpr tbl_square after_ooo_rook_tbl{4, 0}; - static constexpr tbl_square after_oo_king_tbl{1, 0}; - static constexpr tbl_square after_ooo_king_tbl{5, 0}; - - square oo_rook; - square ooo_rook; - square start_king; - - square after_oo_rook; - square after_ooo_rook; - square after_oo_king; - square after_ooo_king; - - square_set oo_mask; - - square_set ooo_danger_mask; - square_set ooo_occ_mask; - - constexpr castle_info_() - : oo_rook{oo_rook_tbl.to_square()}, - ooo_rook{ooo_rook_tbl.to_square()}, - start_king{start_king_tbl.to_square()}, - after_oo_rook{after_oo_rook_tbl.to_square()}, - after_ooo_rook{after_ooo_rook_tbl.to_square()}, - after_oo_king{after_oo_king_tbl.to_square()}, - after_ooo_king{after_ooo_king_tbl.to_square()} { - constexpr delta ooo_delta{1, 0}; - constexpr delta oo_delta{-1, 0}; - for (auto sq = start_king_tbl.add(oo_delta); true; sq = sq.add(oo_delta)) { - oo_mask.insert(sq); - if (sq == after_oo_king_tbl) { break; } - } - for (auto sq = start_king_tbl.add(ooo_delta); true; sq = sq.add(ooo_delta)) { - ooo_danger_mask.insert(sq); - if (sq == after_ooo_king_tbl) { break; } - } - for (auto sq = start_king_tbl.add(ooo_delta); sq != ooo_rook_tbl; sq = sq.add(ooo_delta)) { ooo_occ_mask.insert(sq); } - } -}; - -template <> -struct castle_info_ { - static constexpr tbl_square oo_rook_tbl{0, 7}; - static constexpr tbl_square ooo_rook_tbl{7, 7}; - static constexpr tbl_square start_king_tbl{3, 7}; - - static constexpr tbl_square after_oo_rook_tbl{2, 7}; - static constexpr tbl_square after_ooo_rook_tbl{4, 7}; - static constexpr tbl_square after_oo_king_tbl{1, 7}; - static constexpr tbl_square after_ooo_king_tbl{5, 7}; - - square oo_rook; - square ooo_rook; - square start_king; - - square after_oo_rook; - square after_ooo_rook; - square after_oo_king; - square after_ooo_king; - - square_set oo_mask; - - square_set ooo_danger_mask; - square_set ooo_occ_mask; - - constexpr castle_info_() - : oo_rook{oo_rook_tbl.to_square()}, - ooo_rook{ooo_rook_tbl.to_square()}, - start_king{start_king_tbl.to_square()}, - after_oo_rook{after_oo_rook_tbl.to_square()}, - after_ooo_rook{after_ooo_rook_tbl.to_square()}, - after_oo_king{after_oo_king_tbl.to_square()}, - after_ooo_king{after_ooo_king_tbl.to_square()} { - constexpr delta ooo_delta{1, 0}; - constexpr delta oo_delta{-1, 0}; - for (auto sq = start_king_tbl.add(oo_delta); true; sq = sq.add(oo_delta)) { - oo_mask.insert(sq); - if (sq == after_oo_king_tbl) { break; } - } - for (auto sq = start_king_tbl.add(ooo_delta); true; sq = sq.add(ooo_delta)) { - ooo_danger_mask.insert(sq); - if (sq == after_ooo_king_tbl) { break; } - } - for (auto sq = start_king_tbl.add(ooo_delta); sq != ooo_rook_tbl; sq = sq.add(ooo_delta)) { ooo_occ_mask.insert(sq); } - } -}; - struct stepper_attack_tbl { - static constexpr size_t num_squares = 64; + static constexpr std::size_t num_squares = 64; piece_type type; std::array data{}; template - constexpr const square_set& look_up(const T& sq) const { + [[nodiscard]] constexpr const square_set& look_up(const T& sq) const noexcept { static_assert(is_square_v, "can only look up squares"); return data[sq.index()]; } template - constexpr stepper_attack_tbl(piece_type pt, const D& deltas) : type{pt} { + constexpr stepper_attack_tbl(piece_type pt, const D& deltas) noexcept : type{pt} { over_all_step_attacks(deltas, [this](const tbl_square& from, const tbl_square& to) { data[from.index()].insert(to); }); } }; template struct passer_tbl_ { - static constexpr size_t num_squares = 64; + static constexpr std::size_t num_squares = 64; std::array data{}; template - constexpr square_set mask(const T& sq) const { + [[nodiscard]] constexpr square_set mask(const T& sq) const noexcept { static_assert(is_square_v, "can only look up squares"); return data[sq.index()]; } - constexpr passer_tbl_() { + constexpr passer_tbl_() noexcept { over_all([this](const tbl_square& sq) { - for (auto left = sq.add(pawn_delta::attack[0]); left.is_valid(); left = left.add(pawn_delta::step)) { + for (auto left = sq.add(pawn_info::attack[0]); left.is_valid(); left = left.add(pawn_info::step)) { data[sq.index()].insert(left.to_square()); } - for (auto center = sq.add(pawn_delta::step); center.is_valid(); center = center.add(pawn_delta::step)) { + for (auto center = sq.add(pawn_info::step); center.is_valid(); center = center.add(pawn_info::step)) { data[sq.index()].insert(center.to_square()); } - for (auto right = sq.add(pawn_delta::attack[1]); right.is_valid(); right = right.add(pawn_delta::step)) { + for (auto right = sq.add(pawn_info::attack[1]); right.is_valid(); right = right.add(pawn_info::step)) { data[sq.index()].insert(right.to_square()); } }); @@ -274,13 +151,13 @@ struct passer_tbl_ { template struct pawn_push_tbl_ { - static constexpr size_t num_squares = 64; + static constexpr std::size_t num_squares = 64; piece_type type{piece_type::pawn}; std::array data{}; template - constexpr square_set look_up(const T& sq, const square_set& occ) const { + [[nodiscard]] constexpr square_set look_up(const T& sq, const square_set& occ) const noexcept { static_assert(is_square_v, "can only look up squares"); square_set occ_ = occ; if constexpr (c == color::white) { @@ -291,19 +168,19 @@ struct pawn_push_tbl_ { return data[sq.index()] & (~occ_); } - constexpr pawn_push_tbl_() { + constexpr pawn_push_tbl_() noexcept { over_all([this](const tbl_square& from) { - if (const tbl_square to = from.add(pawn_delta::step); to.is_valid()) { data[from.index()].insert(to); } + if (const tbl_square to = from.add(pawn_info::step); to.is_valid()) { data[from.index()].insert(to); } }); - over_rank(pawn_delta::start_rank_idx, [this](const tbl_square& from) { - const tbl_square to = from.add(pawn_delta::step).add(pawn_delta::step); + over_rank(pawn_info::start_rank_idx, [this](const tbl_square& from) { + const tbl_square to = from.add(pawn_info::step).add(pawn_info::step); if (to.is_valid()) { data[from.index()].insert(to); } }); } }; template -constexpr void over_all_slide_masks(const D& deltas, F&& f) { +constexpr void over_all_slide_masks(const D& deltas, F&& f) noexcept { auto do_ray = [f](const tbl_square from, const delta d) { for (auto to = from.add(d); to.add(d).is_valid(); to = to.add(d)) { f(from, to); } }; @@ -314,27 +191,27 @@ constexpr void over_all_slide_masks(const D& deltas, F&& f) { } struct slider_mask_tbl { - static constexpr size_t num_squares = 64; + static constexpr std::size_t num_squares = 64; piece_type type; std::array data{}; template - constexpr const square_set& look_up(const T& sq) const { + [[nodiscard]] constexpr const square_set& look_up(const T& sq) const noexcept { static_assert(is_square_v, "can only look up squares"); return data[sq.index()]; } template - constexpr slider_mask_tbl(const piece_type pt, const D& deltas) : type{pt} { + constexpr slider_mask_tbl(const piece_type pt, const D& deltas) noexcept : type{pt} { over_all_slide_masks(deltas, [this](const tbl_square& from, const tbl_square& to) { data[from.index()].insert(to); }); } }; -template +template struct slider_attack_tbl { - static constexpr size_t minor = 64; - static constexpr size_t major = static_cast(1) << max_num_blockers; - static constexpr size_t fixed_shift = 64 - max_num_blockers; + static constexpr std::size_t minor = 64; + static constexpr std::size_t major = static_cast(1) << max_num_blockers; + static constexpr std::size_t fixed_shift = 64 - max_num_blockers; static constexpr std::uint64_t one = static_cast(1); piece_type type; @@ -343,20 +220,20 @@ struct slider_attack_tbl { std::array data{}; template - constexpr size_t index_offset(const T& sq, const square_set& blocker) const { + [[nodiscard]] constexpr std::size_t index_offset(const T& sq, const square_set& blocker) const noexcept { static_assert(is_square_v, "can only look up squares"); return (magics[sq.index()] * blocker.data) >> fixed_shift; } template - constexpr square_set look_up(const T& sq, const square_set& blocker) const { + [[nodiscard]] constexpr square_set look_up(const T& sq, const square_set& blocker) const noexcept { static_assert(is_square_v, "can only look up squares"); const square_set mask = mask_tbl.look_up(sq); return data[sq.index() * major + index_offset(sq, blocker & mask)]; } template - constexpr square_set compute_rays(const tbl_square& from, const square_set& blocker, const D& deltas) const { + [[nodiscard]] constexpr square_set compute_rays(const tbl_square& from, const square_set& blocker, const D& deltas) const noexcept { square_set result{}; for (delta d : deltas) { for (auto to = from.add(d); to.is_valid(); to = to.add(d)) { @@ -368,7 +245,7 @@ struct slider_attack_tbl { } template - constexpr slider_attack_tbl(const piece_type pt, const D& deltas, const std::array& magic_tbl) + constexpr slider_attack_tbl(const piece_type pt, const D& deltas, const std::array& magic_tbl) noexcept : type{pt}, mask_tbl(pt, deltas), magics{magic_tbl} { over_all([&, this](const tbl_square from) { const square_set mask = mask_tbl.look_up(from); @@ -381,14 +258,11 @@ struct slider_attack_tbl { } }; -template -inline constexpr castle_info_ castle_info = castle_info_{}; - template inline constexpr pawn_push_tbl_ pawn_push_tbl = pawn_push_tbl_{}; template -inline constexpr stepper_attack_tbl pawn_attack_tbl = stepper_attack_tbl{piece_type::pawn, pawn_delta::attack}; +inline constexpr stepper_attack_tbl pawn_attack_tbl = stepper_attack_tbl{piece_type::pawn, pawn_info::attack}; template inline constexpr passer_tbl_ passer_tbl = passer_tbl_{}; diff --git a/include/chess_types.h b/include/chess/types.h similarity index 59% rename from include/chess_types.h rename to include/chess/types.h index 86bec9a..c9f6143 100644 --- a/include/chess_types.h +++ b/include/chess/types.h @@ -1,6 +1,6 @@ /* Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle Seer is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,6 +17,8 @@ #pragma once +#include + #include #include #include @@ -45,11 +47,11 @@ template struct sided { using return_type = U; - T& cast() { return static_cast(*this); } - const T& cast() const { return static_cast(*this); } + [[nodiscard]] constexpr T& cast() noexcept { return static_cast(*this); } + [[nodiscard]] constexpr const T& cast() const noexcept { return static_cast(*this); } template - return_type& us() { + [[nodiscard]] constexpr return_type& us() noexcept { if constexpr (c == color::white) { return cast().white; } else { @@ -58,7 +60,7 @@ struct sided { } template - const return_type& us() const { + [[nodiscard]] constexpr const return_type& us() const noexcept { if constexpr (c == color::white) { return cast().white; } else { @@ -67,43 +69,43 @@ struct sided { } template - return_type& them() { + [[nodiscard]] constexpr return_type& them() noexcept { return us>(); } template - const return_type& them() const { + [[nodiscard]] constexpr const return_type& them() const noexcept { return us>(); } - return_type& us(bool side) { return side ? us() : us(); } + [[nodiscard]] constexpr return_type& us(const bool side) noexcept { return side ? us() : us(); } - const return_type& us(bool side) const { return side ? us() : us(); } + [[nodiscard]] constexpr const return_type& us(const bool side) const noexcept { return side ? us() : us(); } - return_type& them(bool side) { return us(!side); } + [[nodiscard]] constexpr return_type& them(const bool side) noexcept { return us(!side); } - const return_type& them(bool side) const { return us(!side); } + [[nodiscard]] constexpr const return_type& them(const bool side) const noexcept { return us(!side); } - return_type& us(color side) { return us(side == color::white); } + [[nodiscard]] constexpr return_type& us(const color side) noexcept { return us(side == color::white); } - const return_type& us(color side) const { return us(side == color::white); } + [[nodiscard]] constexpr const return_type& us(const color side) const noexcept { return us(side == color::white); } - return_type& them(color side) { return us(side != color::white); } + [[nodiscard]] constexpr return_type& them(const color side) noexcept { return us(side != color::white); } - const return_type& them(color side) const { return us(side != color::white); } + [[nodiscard]] constexpr const return_type& them(const color side) const noexcept { return us(side != color::white); } private: - sided(){}; + constexpr sided() noexcept {}; friend T; }; -color color_from(char ch) { return std::isupper(ch) ? color::white : color::black; } +[[nodiscard]] inline color color_from(char ch) noexcept { return std::isupper(ch) ? color::white : color::black; } enum class player_type { white, black, none }; -constexpr player_type player_from(const bool& turn) { return turn ? player_type::white : player_type::black; } +[[nodiscard]] constexpr player_type player_from(const bool& turn) noexcept { return turn ? player_type::white : player_type::black; } -constexpr bool is_player(const player_type& player, const bool& turn) { +[[nodiscard]] constexpr bool is_player(const player_type& player, const bool& turn) noexcept { switch (player) { case player_type::white: return turn; case player_type::black: return !turn; @@ -113,7 +115,7 @@ constexpr bool is_player(const player_type& player, const bool& turn) { enum class piece_type : std::uint8_t { pawn, knight, bishop, rook, queen, king }; -piece_type type_from(const char& ch) { +[[nodiscard]] inline piece_type type_from(const char& ch) noexcept { switch (std::tolower(ch)) { case 'p': return piece_type::pawn; case 'n': return piece_type::knight; @@ -125,7 +127,7 @@ piece_type type_from(const char& ch) { } } -constexpr char piece_letter(const piece_type& p) { +[[nodiscard]] constexpr char piece_letter(const piece_type& p) noexcept { switch (p) { case piece_type::pawn: return 'p'; case piece_type::knight: return 'n'; @@ -133,20 +135,20 @@ constexpr char piece_letter(const piece_type& p) { case piece_type::rook: return 'r'; case piece_type::queen: return 'q'; case piece_type::king: return 'k'; - default: return '?'; + default: util::unreachable(); return '?'; } } -constexpr char piece_letter(const color& c, const piece_type& p) { +[[nodiscard]] constexpr char piece_letter(const color& c, const piece_type& p) noexcept { const char p_letter = piece_letter(p); switch (c) { case color::white: return std::toupper(p_letter); case color::black: return std::tolower(p_letter); - default: return p_letter; + default: util::unreachable(); return p_letter; } } -constexpr std::string_view piece_name(const piece_type& p) { +[[nodiscard]] constexpr std::string_view piece_name(const piece_type& p) noexcept { switch (p) { case piece_type::pawn: return "pawn"; case piece_type::knight: return "knight"; @@ -154,12 +156,12 @@ constexpr std::string_view piece_name(const piece_type& p) { case piece_type::rook: return "rook"; case piece_type::queen: return "queen"; case piece_type::king: return "king"; - default: return "?"; + default: util::unreachable(); return "king"; } } template -constexpr void over_types(F&& f) { +constexpr void over_types(F&& f) noexcept { f(piece_type::king); f(piece_type::queen); f(piece_type::rook); @@ -169,7 +171,7 @@ constexpr void over_types(F&& f) { } template -auto get_member(const piece_type& idx, T& set) -> decltype(set.pawn_)& { +[[nodiscard]] constexpr auto get_member(const piece_type& idx, T& set) noexcept -> decltype(set.pawn_)& { switch (idx) { case piece_type::pawn: return set.pawn_; case piece_type::knight: return set.knight_; @@ -177,12 +179,12 @@ auto get_member(const piece_type& idx, T& set) -> decltype(set.pawn_)& { case piece_type::rook: return set.rook_; case piece_type::queen: return set.queen_; case piece_type::king: return set.king_; - default: return set.king_; + default: util::unreachable(); return set.king_; } } template -auto get_member(const piece_type& idx, const T& set) -> const decltype(set.pawn_)& { +[[nodiscard]] constexpr auto get_member(const piece_type& idx, const T& set) noexcept -> const decltype(set.pawn_)& { switch (idx) { case piece_type::pawn: return set.pawn_; case piece_type::knight: return set.knight_; @@ -190,7 +192,7 @@ auto get_member(const piece_type& idx, const T& set) -> const decltype(set.pawn_ case piece_type::rook: return set.rook_; case piece_type::queen: return set.queen_; case piece_type::king: return set.king_; - default: return set.king_; + default: util::unreachable(); return set.king_; } } diff --git a/include/bench.h b/include/engine/bench.h similarity index 67% rename from include/bench.h rename to include/engine/bench.h index 7fc9bef..183f50d 100644 --- a/include/bench.h +++ b/include/engine/bench.h @@ -1,6 +1,6 @@ /* Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle Seer is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,11 +17,12 @@ #pragma once -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include @@ -36,7 +37,7 @@ namespace bench_config { constexpr search::depth_type init_depth = 1; constexpr search::depth_type bench_depth = 13; -constexpr size_t tt_mb_size = 16; +constexpr std::size_t tt_mb_size = 16; constexpr std::array fens = { "r3k2r/2pb1ppp/2pp1q2/p7/1nP1B3/1P2P3/P2N1PPP/R2QK2R w KQkq a6 0 14", @@ -93,47 +94,15 @@ constexpr std::array fens = { } // namespace bench_config struct bench_info { - size_t total_nodes{0}; - size_t nodes_per_second{0}; + std::size_t total_nodes{0}; + std::size_t nodes_per_second{0}; }; -std::ostream& operator<<(std::ostream& os, const bench_info& info) { return os << info.total_nodes << " nodes " << info.nodes_per_second << " nps"; } +[[maybe_unused]] std::ostream& operator<<(std::ostream& os, const bench_info& info) noexcept; -bench_info get_bench_info(const nnue::weights& weights) { - using worker_type = search::search_worker; - std::shared_ptr constants = std::make_shared(1); - std::shared_ptr tt = std::make_shared(bench_config::tt_mb_size); +[[nodiscard]] bench_info get_bench_info(const nnue::weights& weights) noexcept; - std::unique_ptr worker = std::make_unique(&weights, tt, constants, [&](const auto& w) { - if (w.depth() >= bench_config::bench_depth) { worker->stop(); } - }); - - simple_timer timer{}; - size_t total_nodes{}; - - for (const auto& fen : bench_config::fens) { - worker->go(chess::position_history{}, chess::board::parse_fen(std::string(fen)), bench_config::init_depth); - worker->iterative_deepening_loop(); - total_nodes += worker->nodes(); - } - - const size_t nodes_per_second = total_nodes * std::chrono::milliseconds(std::chrono::seconds(1)).count() / timer.elapsed().count(); - - return bench_info{total_nodes, nodes_per_second}; -} - -size_t perft(const chess::board& bd, const search::depth_type& depth) { - if (depth == 0) { return bd.generate_moves<>().size(); } - size_t result{0}; - for (const auto& mv : bd.generate_moves<>()) { result += perft(bd.forward(mv), depth - 1); } - return result; -} - -bench_info get_perft_info(const chess::board& bd, const search::depth_type& depth) { - simple_timer timer{}; - const size_t nodes = perft(bd, depth - 1); - const size_t nodes_per_second = nodes * std::chrono::nanoseconds(std::chrono::seconds(1)).count() / timer.elapsed().count(); - return bench_info{nodes, nodes_per_second}; -} +[[nodiscard]] std::size_t perft(const chess::board& bd, const search::depth_type& depth) noexcept; +[[nodiscard]] bench_info get_perft_info(const chess::board& bd, const search::depth_type& depth) noexcept; } // namespace engine \ No newline at end of file diff --git a/include/engine/command_lexer.h b/include/engine/command_lexer.h new file mode 100644 index 0000000..0246375 --- /dev/null +++ b/include/engine/command_lexer.h @@ -0,0 +1,120 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace engine { + +namespace lexer { +constexpr const char* delimiter = " "; +using token_type = std::string; +using token_sequence_type = std::vector; +} // namespace lexer + +struct lexed_command_view { + static constexpr std::string_view separator = " "; + + lexer::token_sequence_type::const_iterator begin_; + lexer::token_sequence_type::const_iterator end_; + + [[nodiscard]] lexer::token_sequence_type tokens() const { return lexer::token_sequence_type(begin_, end_); } + + template + void consume(const lexer::token_type& token, F&& receiver) const noexcept { + if (begin_ != end_ && *begin_ == token) { + const lexed_command_view next_view{std::next(begin_), end_}; + receiver(next_view); + } + } + + template + void emit(F&& receiver) const noexcept { + if (begin_ != end_) { + std::istringstream stream(*begin_); + + T value{}; + stream >> value; + + const lexed_command_view next_view{std::next(begin_), end_}; + receiver(value, next_view); + } + } + + template + void emit_n(F&& receiver) const noexcept { + if (std::distance(begin_, end_) >= N) { + const auto it = begin_ + N; + const lexed_command_view next_view{it, end_}; + receiver(util::string::join(begin_, it, std::string(separator)), next_view); + } + } + + template + void emit_all(F&& receiver) const noexcept { + const lexed_command_view next_view{end_, end_}; + receiver(util::string::join(begin_, end_, std::string(separator)), next_view); + } + + template + void extract_condition(const lexer::token_type& token, F&& receiver) const noexcept { + const bool exists = std::find(begin_, end_, token) != end_; + receiver(exists, *this); + } + + template + void extract_key(const lexer::token_type& token, F&& receiver) const noexcept { + const auto key_it = std::find(begin_, end_, token); + if (key_it != end_ && std::next(key_it) != end_) { + const auto value_it = std::next(key_it); + std::istringstream stream(*value_it); + + T value{}; + stream >> value; + + receiver(value, *this); + } + } +}; + +struct lexed_command { + lexer::token_sequence_type tokens_{}; + + [[nodiscard]] lexed_command_view view() const noexcept { return lexed_command_view{tokens_.cbegin(), tokens_.cend()}; } + + explicit lexed_command(const std::string& input) noexcept { + std::istringstream input_stream(input); + std::copy(std::istream_iterator(input_stream), std::istream_iterator(), std::back_inserter(tokens_)); + } +}; + +struct command_lexer { + [[nodiscard]] lexed_command lex(const std::string& input) const noexcept { return lexed_command(input); } +}; + +} // namespace engine \ No newline at end of file diff --git a/include/engine/option_parser.h b/include/engine/option_parser.h new file mode 100644 index 0000000..2560bf9 --- /dev/null +++ b/include/engine/option_parser.h @@ -0,0 +1,145 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace engine { + +struct string_option { + using type = std::string; + static constexpr std::string_view empty = ""; + + std::string name_; + std::optional default_{}; + + template + [[nodiscard]] auto processor_for(F&& target) const noexcept { + using namespace processor::def; + return sequential(consume("setoption"), consume("name"), consume(name_), consume("value"), emit, invoke(target)); + } + + explicit string_option(const std::string_view& name) noexcept : name_{name} {} + string_option(const std::string_view& name, const std::string_view& def) noexcept : name_{name}, default_{def} {} +}; + +struct spin_range { + int min, max; + + [[nodiscard]] constexpr int clamp(const int& x) const noexcept { return std::clamp(x, min, max); } + constexpr spin_range(const int& a, const int& b) noexcept : min{a}, max{b} {} +}; + +struct spin_option { + using type = int; + + std::string name_; + std::optional default_ = {}; + std::optional range_ = {}; + + template + [[nodiscard]] auto processor_for(F&& callback) const noexcept { + using namespace processor::def; + auto target = [=](const type& input) { callback(range_.has_value() ? range_->clamp(input) : input); }; + return sequential(consume("setoption"), consume("name"), consume(name_), consume("value"), emit, invoke(target)); + } + + explicit spin_option(const std::string_view& name) noexcept : name_{name} {} + spin_option(const std::string& name, const spin_range& range) noexcept : name_{name}, range_{range} {} + spin_option(const std::string& name, const int& def) noexcept : name_{name}, default_{def} {} + spin_option(const std::string& name, const int& def, const spin_range& range) noexcept : name_{name}, default_{def}, range_{range} {} +}; + +struct check_option { + using type = bool; + + std::string name_; + std::optional default_{std::nullopt}; + + template + [[nodiscard]] auto processor_for(F&& target) const noexcept { + // clang-format off + + using namespace processor::def; + return sequential(consume("setoption"), consume("name"), consume(name_), consume("value"), parallel( + sequential(consume("true"), invoke([=] { target(true); })), + sequential(consume("false"), invoke([=] { target(false); })) + )); + + // clang-format on + } + + explicit check_option(const std::string_view& name) noexcept : name_{name} {} + check_option(const std::string_view& name, const bool& def) noexcept : name_{name}, default_{def} {} +}; + +std::ostream& operator<<(std::ostream& ostr, const string_option& opt) noexcept; +std::ostream& operator<<(std::ostream& ostr, const spin_option& opt) noexcept; +std::ostream& operator<<(std::ostream& ostr, const check_option& opt) noexcept; + +template +inline constexpr bool is_option_v = std::is_same_v || std::is_same_v || std::is_same_v; + +template +struct option_callback { + static_assert(is_option_v, "T must be of option type"); + + T option_; + std::function callback_; + + auto processor() const noexcept { return option_.processor_for(callback_); } + + template + option_callback(const T& option, F&& f) noexcept : option_{option}, callback_{f} {} +}; + +template +struct uci_options { + std::tuple...> options_; + + auto processor() const noexcept { + auto convert = [](const auto& callback) { return callback.processor(); }; + + auto convert_many = [&convert](const auto&... callbacks) { + using namespace processor::def; + return parallel(convert(callbacks)...); + }; + + return std::apply(convert_many, options_); + } + + explicit uci_options(const option_callback&... options) noexcept : options_{options...} {} +}; + +template +inline std::ostream& operator<<(std::ostream& ostr, uci_options options) noexcept { + util::tuple::for_each(options.options_, [&ostr](const auto& opt) { ostr << opt.option_ << std::endl; }); + return ostr; +} + +} // namespace engine diff --git a/include/engine/processor/condition_type.h b/include/engine/processor/condition_type.h new file mode 100644 index 0000000..b1891fa --- /dev/null +++ b/include/engine/processor/condition_type.h @@ -0,0 +1,49 @@ + +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include +#include + +#include + +namespace engine::processor { + +struct condition_type { + lexer::token_type token_{}; + + template + void process(const lexed_command_view& view, const std::tuple& args, const F& receiver = def::null) const noexcept { + view.extract_condition(token_, [&](const bool& value, const lexed_command_view& next_view) { + const auto next_args = util::tuple::append(args, value); + receiver.process(next_view, next_args); + }); + } + + explicit condition_type(const lexer::token_type& token) noexcept : token_{token} {} +}; + +namespace def { + +[[nodiscard]] inline condition_type condition(const lexer::token_type& token) noexcept { return condition_type(token); } + +} // namespace def + +} // namespace engine::processor \ No newline at end of file diff --git a/include/engine/processor/consume_type.h b/include/engine/processor/consume_type.h new file mode 100644 index 0000000..bc37408 --- /dev/null +++ b/include/engine/processor/consume_type.h @@ -0,0 +1,45 @@ + +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include + +#include + +namespace engine::processor { + +struct consume_type { + lexer::token_type token_{}; + + template + void process(const lexed_command_view& view, const std::tuple& args, const F& receiver = def::null) const noexcept { + view.consume(token_, [&](const lexed_command_view& next_view) { receiver.process(next_view, args); }); + } + + explicit consume_type(const lexer::token_type& token) noexcept : token_{token} {} +}; + +namespace def { + +[[nodiscard]] inline consume_type consume(const lexer::token_type& token) noexcept { return consume_type(token); } + +} // namespace def + +} // namespace engine::processor \ No newline at end of file diff --git a/include/engine/processor/emit_all_type.h b/include/engine/processor/emit_all_type.h new file mode 100644 index 0000000..cce1f80 --- /dev/null +++ b/include/engine/processor/emit_all_type.h @@ -0,0 +1,45 @@ + +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include +#include + +#include + +namespace engine::processor { + +struct emit_all_type { + template + void process(const lexed_command_view& view, const std::tuple& args, const F& receiver = def::null) const noexcept { + view.emit_all([&](const std::string& value, const lexed_command_view& next_view) { + const auto next_args = util::tuple::append(args, value); + receiver.process(next_view, next_args); + }); + } +}; + +namespace def { + +constexpr auto emit_all = emit_all_type{}; + +} + +} // namespace engine::processor \ No newline at end of file diff --git a/include/engine/processor/emit_n_type.h b/include/engine/processor/emit_n_type.h new file mode 100644 index 0000000..76795a2 --- /dev/null +++ b/include/engine/processor/emit_n_type.h @@ -0,0 +1,48 @@ + +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include +#include + +#include +#include + +namespace engine::processor { + +template +struct emit_n_type { + template + void process(const lexed_command_view& view, const std::tuple& args, const F& receiver = def::null) const noexcept { + view.emit_n([&](const std::string& value, const lexed_command_view& next_view) { + const auto next_args = util::tuple::append(args, value); + receiver.process(next_view, next_args); + }); + } +}; + +namespace def { + +template +constexpr auto emit_n = emit_n_type{}; + +} + +} // namespace engine::processor \ No newline at end of file diff --git a/include/engine/processor/emit_type.h b/include/engine/processor/emit_type.h new file mode 100644 index 0000000..3a53a07 --- /dev/null +++ b/include/engine/processor/emit_type.h @@ -0,0 +1,47 @@ + +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include +#include + +#include + +namespace engine::processor { + +template +struct emit_type { + template + void process(const lexed_command_view& view, const std::tuple& args, const F& receiver = def::null) const noexcept { + view.emit([&](const T& value, const lexed_command_view& next_view) { + const auto next_args = util::tuple::append(args, value); + receiver.process(next_view, next_args); + }); + } +}; + +namespace def { + +template +constexpr auto emit = emit_type{}; + +} + +} // namespace engine::processor \ No newline at end of file diff --git a/include/engine/processor/invoke_type.h b/include/engine/processor/invoke_type.h new file mode 100644 index 0000000..8650e6f --- /dev/null +++ b/include/engine/processor/invoke_type.h @@ -0,0 +1,52 @@ + +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include +#include + +#include +#include + +namespace engine::processor { + +template +struct invoke_type { + I invokable_; + + template + void process(const lexed_command_view& view, const std::tuple& args, const F& receiver = def::null) const noexcept { + std::apply(invokable_, args); + receiver.process(view, args); + } + + constexpr explicit invoke_type(const I& invokable) noexcept : invokable_{invokable} {} +}; + +namespace def { + +template +[[nodiscard]] constexpr invoke_type invoke(const I& invokable) noexcept { + return invoke_type(invokable); +} + +} // namespace def + +} // namespace engine::processor \ No newline at end of file diff --git a/include/engine/processor/key_type.h b/include/engine/processor/key_type.h new file mode 100644 index 0000000..83c7153 --- /dev/null +++ b/include/engine/processor/key_type.h @@ -0,0 +1,53 @@ + +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include +#include + +#include + +namespace engine::processor { + +template +struct key_type { + lexer::token_type token_{}; + + template + void process(const lexed_command_view& view, const std::tuple& args, const F& receiver = def::null) const noexcept { + view.extract_key(token_, [&](const T& value, const lexed_command_view& next_view) { + const auto next_args = util::tuple::append(args, value); + receiver.process(next_view, next_args); + }); + } + + explicit key_type(const lexer::token_type& token) noexcept : token_{token} {} +}; + +namespace def { + +template +[[nodiscard]] inline key_type key(const lexer::token_type& token) noexcept { + return key_type(token); +} + +} // namespace def + +} // namespace engine::processor \ No newline at end of file diff --git a/include/engine/processor/null_type.h b/include/engine/processor/null_type.h new file mode 100644 index 0000000..b3d01f5 --- /dev/null +++ b/include/engine/processor/null_type.h @@ -0,0 +1,41 @@ + +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include + +#include + +namespace engine::processor { + +struct null_type { + template + void process(const lexed_command_view&, const std::tuple&) const noexcept {} + + template + void process(const lexed_command_view&, const std::tuple&, const F&) const noexcept {} +}; + +namespace def { + +constexpr auto null = null_type{}; + +} + +} // namespace engine::processor \ No newline at end of file diff --git a/include/engine/processor/parallel_combinator_type.h b/include/engine/processor/parallel_combinator_type.h new file mode 100644 index 0000000..0a5dda8 --- /dev/null +++ b/include/engine/processor/parallel_combinator_type.h @@ -0,0 +1,50 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include + +#include +#include + +namespace engine::processor { + +template +struct parallel_combinator_type { + std::tuple processors_; + + template + void process(const lexed_command_view& view, const std::tuple& args, const F& receiver = def::null) const noexcept { + util::tuple::for_each(processors_, [&](const auto& processor) { processor.process(view, args, receiver); }); + } + + constexpr explicit parallel_combinator_type(const std::tuple& processors) noexcept : processors_{processors} {} +}; + +namespace def { + +template +[[nodiscard]] constexpr auto parallel(Ts&&... ts) noexcept { + const auto processors = std::tuple{std::forward(ts)...}; + return parallel_combinator_type(processors); +} + +} // namespace def + +} // namespace engine::processor \ No newline at end of file diff --git a/include/engine/processor/receiver_combinator_type.h b/include/engine/processor/receiver_combinator_type.h new file mode 100644 index 0000000..7adbe3b --- /dev/null +++ b/include/engine/processor/receiver_combinator_type.h @@ -0,0 +1,41 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include +#include + +#include + +namespace engine::processor { + +template +struct receiver_combinator_type { + A processor_; + B receiver_; + + template + void process(const lexed_command_view& view, const std::tuple& args, const F& receiver = def::null) const noexcept { + processor_.process(view, args, parallel_combinator_type(std::tuple{receiver_, receiver})); + } + + constexpr receiver_combinator_type(const A& processor, const B& receiver) noexcept : processor_{processor}, receiver_{receiver} {} +}; + +} // namespace engine::processor \ No newline at end of file diff --git a/include/engine/processor/sequential_combinator_type.h b/include/engine/processor/sequential_combinator_type.h new file mode 100644 index 0000000..d7d91af --- /dev/null +++ b/include/engine/processor/sequential_combinator_type.h @@ -0,0 +1,59 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include +#include +#include + +#include + +namespace engine::processor { + +template +struct sequential_combinator_type { + std::tuple processors_; + + template + void process(const lexed_command_view& view, const std::tuple& args, const F& receiver = def::null) const noexcept { + if constexpr (std::is_same_v, std::tuple>) { + const auto head = util::tuple::head(processors_); + head.process(view, args, receiver); + } else { + const auto head = util::tuple::head(processors_); + const auto tail = sequential_combinator_type(util::tuple::tail(processors_)); + const auto next_receiver = receiver_combinator_type(tail, receiver); + head.process(view, args, next_receiver); + } + } + + constexpr explicit sequential_combinator_type(const std::tuple& processors) noexcept : processors_(processors) {} +}; + +namespace def { + +template +[[nodiscard]] constexpr auto sequential(Ts&&... ts) noexcept { + const auto processors = std::tuple{std::forward(ts)...}; + return sequential_combinator_type(processors); +} + +} // namespace def + +} // namespace engine::processor \ No newline at end of file diff --git a/include/engine/processor/types.h b/include/engine/processor/types.h new file mode 100644 index 0000000..23c687e --- /dev/null +++ b/include/engine/processor/types.h @@ -0,0 +1,31 @@ + +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/include/engine/time_manager.h b/include/engine/time_manager.h new file mode 100644 index 0000000..436edbf --- /dev/null +++ b/include/engine/time_manager.h @@ -0,0 +1,129 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include + +#include +#include +#include + +namespace engine { + +namespace go { +struct infinite {}; + +struct depth { + search::depth_type depth; +}; + +struct nodes { + std::size_t nodes; +}; + +struct move_time { + bool ponder; + int move_time; + + [[nodiscard]] constexpr std::chrono::milliseconds move_time_ms() const noexcept { + const int value = move_time; + return std::chrono::milliseconds(value); + } +}; + +struct timed_move_state { + bool ponder; + + int white_time; + int black_time; + + [[nodiscard]] constexpr std::chrono::milliseconds our_time_ms(const bool& pov) const noexcept { + const int value = pov ? white_time : black_time; + return std::chrono::milliseconds(value); + } +}; + +struct increment : public timed_move_state { + int white_increment; + int black_increment; + + [[nodiscard]] constexpr std::chrono::milliseconds our_increment_ms(const bool& pov) const noexcept { + const int value = pov ? white_increment : black_increment; + return std::chrono::milliseconds(value); + } +}; + +struct moves_to_go : public timed_move_state { + int moves_to_go; +}; + +} // namespace go + +struct update_info { + std::size_t nodes; +}; + +struct iter_info { + search::depth_type depth; + std::size_t best_move_percent; +}; + +struct time_manager { + static constexpr auto over_head = std::chrono::milliseconds(5); + + std::mutex access_mutex_; + + std::chrono::steady_clock::time_point search_start{}; + std::optional min_budget{}; + std::optional max_budget{}; + + std::optional depth_limit{}; + std::optional node_limit{}; + + bool ponder{}; + bool infinite{}; + + [[nodiscard]] constexpr bool is_pondering() const noexcept { return ponder; } + [[maybe_unused]] time_manager& ponder_hit() noexcept; + + [[maybe_unused]] time_manager& reset_() noexcept; + [[maybe_unused]] time_manager& init(const bool& pov, const go::infinite& data) noexcept; + [[maybe_unused]] time_manager& init(const bool& pov, const go::depth& data) noexcept; + [[maybe_unused]] time_manager& init(const bool& pov, const go::nodes& data) noexcept; + + [[maybe_unused]] time_manager& init(const bool& pov, const go::increment& data) noexcept; + [[maybe_unused]] time_manager& init(const bool& pov, const go::move_time& data) noexcept; + [[maybe_unused]] time_manager& init(const bool& pov, const go::moves_to_go& data) noexcept; + + [[nodiscard]] std::chrono::milliseconds elapsed() const noexcept; + [[nodiscard]] bool should_stop_on_update(const update_info& info) noexcept; + [[nodiscard]] bool should_stop_on_iter(const iter_info& info) noexcept; +}; + +template +struct simple_timer { + std::mutex start_mutex_; + std::chrono::steady_clock::time_point start_; + + [[nodiscard]] T elapsed() noexcept; + [[maybe_unused]] simple_timer& lap() noexcept; + + simple_timer() noexcept : start_{std::chrono::steady_clock::now()} {} +}; + +} // namespace engine diff --git a/include/engine/uci.h b/include/engine/uci.h new file mode 100644 index 0000000..10d2ab6 --- /dev/null +++ b/include/engine/uci.h @@ -0,0 +1,85 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace engine { + +struct uci { + static constexpr std::string_view default_weight_path = "EMBEDDED"; + static constexpr std::size_t default_thread_count = 1; + static constexpr std::size_t default_hash_size = 16; + static constexpr bool default_ponder = false; + + chess::board_history history{}; + chess::board position = chess::board::start_pos(); + + nnue::weights weights_{}; + search::worker_orchestrator orchestrator_; + + std::atomic_bool ponder_{false}; + std::atomic_bool should_quit_{false}; + + time_manager manager_; + simple_timer timer_; + + std::mutex mutex_{}; + std::ostream& os = std::cout; + + [[nodiscard]] auto options() noexcept; + [[nodiscard]] bool should_quit() const noexcept; + + void quit() noexcept; + + void new_game() noexcept; + void set_position(const chess::board& bd, const std::string& uci_moves = "") noexcept; + + void weights_info_string() noexcept; + void info_string(const search::search_worker& worker) noexcept; + + template + void go(Ts&&... args) noexcept; + + void ponder_hit() noexcept; + void stop() noexcept; + + void ready() noexcept; + void id_info() noexcept; + + void bench() noexcept; + void eval() noexcept; + void probe() noexcept; + void perft(const search::depth_type& depth) noexcept; + + void read(const std::string& line) noexcept; + + uci() noexcept; +}; + +} // namespace engine diff --git a/include/version.h b/include/engine/version.h similarity index 80% rename from include/version.h rename to include/engine/version.h index 94d36c8..a0d5fa9 100644 --- a/include/version.h +++ b/include/engine/version.h @@ -1,6 +1,6 @@ /* Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle Seer is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,12 +19,12 @@ #include -namespace version { +namespace engine::version { constexpr std::string_view engine_name = "Seer"; constexpr std::string_view author_name = "Connor McMonigle"; -constexpr size_t major = 2; -constexpr size_t minor = 6; -constexpr size_t patch = 0; +constexpr std::size_t major = 2; +constexpr std::size_t minor = 6; +constexpr std::size_t patch = 0; -} // namespace version +} // namespace engine::version \ No newline at end of file diff --git a/include/feature_util.h b/include/feature/util.h similarity index 52% rename from include/feature_util.h rename to include/feature/util.h index 74cbee2..14287a2 100644 --- a/include/feature_util.h +++ b/include/feature/util.h @@ -1,6 +1,6 @@ /* Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle Seer is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,37 +16,38 @@ */ #pragma once +#include +#include +#include -#include -#include +#include -namespace feature { -namespace half_ka { +namespace feature::half_ka { -constexpr size_t numel = 64 * 12 * 64; -constexpr size_t max_active_half_features = 32; +constexpr std::size_t numel = 64 * 12 * 64; +constexpr std::size_t max_active_half_features = 32; -constexpr size_t major = 64 * 12; -constexpr size_t minor = 64; +constexpr std::size_t major = 64 * 12; +constexpr std::size_t minor = 64; -constexpr size_t us_pawn_offset = 0; -constexpr size_t us_knight_offset = us_pawn_offset + minor; -constexpr size_t us_bishop_offset = us_knight_offset + minor; -constexpr size_t us_rook_offset = us_bishop_offset + minor; -constexpr size_t us_queen_offset = us_rook_offset + minor; -constexpr size_t us_king_offset = us_queen_offset + minor; +constexpr std::size_t us_pawn_offset = 0; +constexpr std::size_t us_knight_offset = us_pawn_offset + minor; +constexpr std::size_t us_bishop_offset = us_knight_offset + minor; +constexpr std::size_t us_rook_offset = us_bishop_offset + minor; +constexpr std::size_t us_queen_offset = us_rook_offset + minor; +constexpr std::size_t us_king_offset = us_queen_offset + minor; -constexpr size_t them_pawn_offset = us_king_offset + minor; -constexpr size_t them_knight_offset = them_pawn_offset + minor; -constexpr size_t them_bishop_offset = them_knight_offset + minor; -constexpr size_t them_rook_offset = them_bishop_offset + minor; -constexpr size_t them_queen_offset = them_rook_offset + minor; -constexpr size_t them_king_offset = them_queen_offset + minor; +constexpr std::size_t them_pawn_offset = us_king_offset + minor; +constexpr std::size_t them_knight_offset = them_pawn_offset + minor; +constexpr std::size_t them_bishop_offset = them_knight_offset + minor; +constexpr std::size_t them_rook_offset = them_bishop_offset + minor; +constexpr std::size_t them_queen_offset = them_rook_offset + minor; +constexpr std::size_t them_king_offset = them_queen_offset + minor; template constexpr int mirror_constant = (chess::color::white == us) ? 0 : 56; -constexpr size_t us_offset(const chess::piece_type& pt) { +[[nodiscard]] constexpr std::size_t us_offset(const chess::piece_type& pt) noexcept { switch (pt) { case chess::piece_type::pawn: return us_pawn_offset; case chess::piece_type::knight: return us_knight_offset; @@ -54,11 +55,11 @@ constexpr size_t us_offset(const chess::piece_type& pt) { case chess::piece_type::rook: return us_rook_offset; case chess::piece_type::queen: return us_queen_offset; case chess::piece_type::king: return us_king_offset; - default: return us_pawn_offset; + default: util::unreachable(); return us_pawn_offset; } } -constexpr size_t them_offset(const chess::piece_type& pt) { +[[nodiscard]] constexpr std::size_t them_offset(const chess::piece_type& pt) noexcept { switch (pt) { case chess::piece_type::pawn: return them_pawn_offset; case chess::piece_type::knight: return them_knight_offset; @@ -66,19 +67,18 @@ constexpr size_t them_offset(const chess::piece_type& pt) { case chess::piece_type::rook: return them_rook_offset; case chess::piece_type::queen: return them_queen_offset; case chess::piece_type::king: return them_king_offset; - default: return them_pawn_offset; + default: util::unreachable(); return them_pawn_offset; } } template -constexpr size_t offset(const chess::piece_type& pt) { +[[nodiscard]] constexpr std::size_t offset(const chess::piece_type& pt) noexcept { return (a == b) ? us_offset(pt) : them_offset(pt); } template -constexpr size_t index(const chess::square& ks, const chess::piece_type& pt, const chess::square& sq) { +[[nodiscard]] constexpr std::size_t index(const chess::square& ks, const chess::piece_type& pt, const chess::square& sq) noexcept { return major * (ks.index() ^ mirror_constant)+offset(pt) + (sq.index() ^ mirror_constant); } -} // namespace half_ka -} // namespace feature \ No newline at end of file +} // namespace feature::half_ka \ No newline at end of file diff --git a/include/history_heuristic.h b/include/history_heuristic.h deleted file mode 100644 index d7ad269..0000000 --- a/include/history_heuristic.h +++ /dev/null @@ -1,188 +0,0 @@ -/* - Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle - - Seer is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Seer is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#pragma once - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace search { - -namespace history { - -using value_type = search::counter_type; - -namespace constants { - -inline constexpr size_t num_squares = 64; -inline constexpr size_t num_pieces = 6; - -}; // namespace constants - -struct context { - chess::move follow; - chess::move counter; - chess::square_set threatened; -}; - -value_type formula(const value_type& x, const value_type& gain) { - constexpr value_type history_multiplier = 32; - constexpr value_type history_divisor = 512; - return (gain * history_multiplier) - (x * std::abs(gain) / history_divisor); -} - -struct butterfly_info { - static constexpr size_t N = constants::num_squares * constants::num_squares; - - static constexpr bool is_applicable(const context&, const chess::move& mv) { return mv.is_quiet(); } - - static constexpr size_t compute_index(const context&, const chess::move& mv) { - const size_t from = static_cast(mv.from().index()); - const size_t to = static_cast(mv.to().index()); - return from * constants::num_squares + to; - } -}; - -struct threatened_info { - static constexpr size_t N = constants::num_squares * constants::num_squares; - - static constexpr bool is_applicable(const context& ctxt, const chess::move& mv) { return ctxt.threatened.is_member(mv.from()) && mv.is_quiet(); } - - static constexpr size_t compute_index(const context&, const chess::move& mv) { - const size_t from = static_cast(mv.from().index()); - const size_t to = static_cast(mv.to().index()); - return from * constants::num_squares + to; - } -}; - -struct counter_info { - static constexpr size_t N = constants::num_squares * constants::num_pieces * constants::num_squares * constants::num_pieces; - - static constexpr bool is_applicable(const context& ctxt, const chess::move& mv) { return !ctxt.counter.is_null() && mv.is_quiet(); } - - static constexpr size_t compute_index(const context& ctxt, const chess::move& mv) { - const size_t p0 = static_cast(ctxt.counter.piece()); - const size_t to0 = static_cast(ctxt.counter.to().index()); - const size_t p1 = static_cast(mv.piece()); - const size_t to1 = static_cast(mv.to().index()); - return p0 * constants::num_squares * constants::num_pieces * constants::num_squares + to0 * constants::num_pieces * constants::num_squares + - p1 * constants::num_squares + to1; - } -}; - -struct follow_info { - static constexpr size_t N = constants::num_squares * constants::num_pieces * constants::num_squares * constants::num_pieces; - - static constexpr bool is_applicable(const context& ctxt, const chess::move& mv) { return !ctxt.follow.is_null() && mv.is_quiet(); } - - static constexpr size_t compute_index(const context& ctxt, const chess::move& mv) { - const size_t p0 = static_cast(ctxt.follow.piece()); - const size_t to0 = static_cast(ctxt.follow.to().index()); - const size_t p1 = static_cast(mv.piece()); - const size_t to1 = static_cast(mv.to().index()); - return p0 * constants::num_squares * constants::num_pieces * constants::num_squares + to0 * constants::num_pieces * constants::num_squares + - p1 * constants::num_squares + to1; - } -}; - -struct capture_info { - static constexpr size_t N = constants::num_squares * constants::num_pieces * constants::num_pieces; - - static constexpr bool is_applicable(const context&, const chess::move& mv) { return mv.is_capture(); } - - static constexpr size_t compute_index(const context&, const chess::move& mv) { - const size_t piece = static_cast(mv.piece()); - const size_t to = static_cast(mv.to().index()); - const size_t capture = static_cast(mv.captured()); - return piece * constants::num_squares * constants::num_pieces + to * constants::num_pieces + capture; - } -}; - -template -struct table { - std::array data_{}; - - constexpr bool is_applicable(const context& ctxt, const chess::move& mv) const { return T::is_applicable(ctxt, mv); } - - constexpr const value_type& at(const context& ctxt, const chess::move& mv) const { return data_[T::compute_index(ctxt, mv)]; } - constexpr value_type& at(const context& ctxt, const chess::move& mv) { return data_[T::compute_index(ctxt, mv)]; } - - constexpr void clear() { data_.fill(value_type{}); } -}; - -template -struct combined { - std::tuple...> tables_{}; - - constexpr combined& update(const context& ctxt, const chess::move& best_move, const chess::move_list& tried, const depth_type& depth) { - constexpr value_type history_max = 400; - - auto single_update = [&, this](const auto& mv, const value_type& gain) { - const value_type value = compute_value(ctxt, mv); - util::apply(tables_, [=](auto& tbl) { - if (tbl.is_applicable(ctxt, mv)) { tbl.at(ctxt, mv) += formula(value, gain); } - }); - }; - - const value_type gain = std::min(history_max, depth * depth); - std::for_each(tried.begin(), tried.end(), [single_update, gain](const chess::move& mv) { single_update(mv, -gain); }); - single_update(best_move, gain); - - return *this; - } - - constexpr void clear() { - util::apply(tables_, [](auto& tbl) { tbl.clear(); }); - } - - constexpr value_type compute_value(const context& ctxt, const chess::move& mv) const { - value_type result{}; - util::apply(tables_, [&](const auto& tbl) { - if (tbl.is_applicable(ctxt, mv)) { result += tbl.at(ctxt, mv); } - }); - return result; - } -}; - -} // namespace history - -using history_heuristic = - history::combined; - -struct sided_history_heuristic : chess::sided { - history_heuristic white; - history_heuristic black; - - sided_history_heuristic& clear() { - white.clear(); - black.clear(); - return *this; - } - - sided_history_heuristic() : white{}, black{} {} -}; - -} // namespace search diff --git a/include/move.h b/include/move.h deleted file mode 100644 index 7bf4447..0000000 --- a/include/move.h +++ /dev/null @@ -1,219 +0,0 @@ -/* - Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle - - Seer is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Seer is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#pragma once - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace chess { - -inline constexpr std::array under_promotion_types = {piece_type::knight, piece_type::bishop, piece_type::rook}; - -struct move { - using from_ = bit::range; - using to_ = bit::next_range; - using piece_ = bit::next_range; - using is_capture_ = bit::next_flag; - using is_enpassant_ = bit::next_flag; - using captured_ = bit::next_range; - using enpassant_sq_ = bit::next_range; - using promotion_ = bit::next_range; - - static constexpr size_t width = promotion_::last; - using data_type = std::uint32_t; - - data_type data; - - template - constexpr typename B::type get_field_() const { - return B::get(data); - } - - template - constexpr move& set_field_(const typename B::type info) { - B::set(data, info); - return *this; - } - - constexpr square from() const { return square::from_index(get_field_()); } - constexpr square to() const { return square::from_index(get_field_()); } - constexpr piece_type piece() const { return get_field_(); } - constexpr bool is_capture() const { return get_field_(); } - constexpr bool is_enpassant() const { return get_field_(); } - constexpr piece_type captured() const { return get_field_(); } - - template - constexpr T mvv_lva_key() const { - constexpr T num_pieces = static_cast(6); - return num_pieces * static_cast(get_field_()) + num_pieces - static_cast(get_field_()); - } - - constexpr square enpassant_sq() const { return square::from_index(get_field_()); } - constexpr piece_type promotion() const { return get_field_(); } - - constexpr bool is_null() const { return data == 0; } - constexpr bool is_king_move() const { return piece() == piece_type::king; } - - template - constexpr bool is_castle_oo() const { - return piece() == piece_type::king && from() == castle_info.start_king && to() == castle_info.oo_rook; - } - - template - constexpr bool is_castle_ooo() const { - return piece() == piece_type::king && from() == castle_info.start_king && to() == castle_info.ooo_rook; - } - - template - constexpr bool is_promotion() const { - return piece() == piece_type::pawn && pawn_delta::last_rank.is_member(to()); - } - - constexpr bool is_promotion() const { return is_promotion() || is_promotion(); } - - template - constexpr bool is_pawn_double() const { - return piece() == piece_type::pawn && pawn_delta::start_rank.is_member(from()) && pawn_delta::double_rank.is_member(to()); - } - - constexpr bool is_quiet() const { return !is_capture() && !(is_promotion() && piece_type::queen == promotion()); } - constexpr bool is_noisy() const { return !is_quiet(); } - - template - std::string name() const { - if (is_castle_oo()) { - return castle_info.start_king.name() + castle_info.after_oo_king.name(); - } else if (is_castle_ooo()) { - return castle_info.start_king.name() + castle_info.after_ooo_king.name(); - } - std::string base = from().name() + to().name(); - if (is_promotion()) { - return base + piece_letter(promotion()); - } else { - return base; - } - } - - std::string name(bool pov) const { return pov ? name() : name(); } - - move() = default; - - constexpr move(const data_type& data) : data{data} {} - - constexpr move( - square from, - square to, - piece_type piece, - bool is_capture = false, - piece_type captured = piece_type::pawn, - bool is_enpassant = false, - square enpassant_sq = square::from_index(0), - piece_type promotion = piece_type::pawn) - : data{0} { - const auto from_idx = static_cast(from.index()); - const auto to_idx = static_cast(to.index()); - const auto ep_sq_idx = static_cast(enpassant_sq.index()); - set_field_(from_idx) - .set_field_(to_idx) - .set_field_(piece) - .set_field_(is_capture) - .set_field_(is_enpassant) - .set_field_(captured) - .set_field_(ep_sq_idx) - .set_field_(promotion); - } - - constexpr static move null() { return move{0}; } -}; - -constexpr bool operator==(const move& a, const move& b) { return a.data == b.data; } - -constexpr bool operator!=(const move& a, const move& b) { return !(a == b); } - -std::ostream& operator<<(std::ostream& ostr, const move& mv) { - ostr << "move(from=" << mv.from().name() << ", to=" << mv.to().name() << ", piece=" << piece_name(mv.piece()) << ", is_capture=" << mv.is_capture() - << ", capture=" << piece_name(mv.captured()) << ", is_enpassant=" << mv.is_enpassant() << ", enpassant_sq=" << mv.enpassant_sq().name() - << ", promotion=" << piece_name(mv.promotion()) << ')'; - return ostr; -} - -struct move_hash { - size_t operator()(const move& mv) const { return std::hash{}(mv.data); } -}; - -struct move_list { - static constexpr size_t max_branching_factor = 192; - using iterator = std::array::iterator; - using const_iterator = std::array::const_iterator; - size_t size_{0}; - std::array data{}; - - iterator begin() { return data.begin(); } - iterator end() { return data.begin() + size_; } - const_iterator begin() const { return data.cbegin(); } - const_iterator end() const { return data.cbegin() + size_; } - - bool has(const move& mv) const { return end() != std::find(begin(), end(), mv); } - size_t size() const { return size_; } - bool empty() const { return size() == 0; } - - move& operator[](const size_t& idx) { return data[idx]; } - const move& operator[](const size_t& idx) const { return data[idx]; } - - move_list& push(const move& mv) { - constexpr size_t last_idx = max_branching_factor - 1; - data[size_] = mv; - ++size_; - if (size_ > last_idx) { size_ = last_idx; } - return *this; - } - - template - move_list& push(const Ts&... ts) { - return push(move(ts...)); - } - - template - move_list& push_queen_promotion(const Ts&... ts) { - assert((move(ts...).piece() == piece_type::pawn)); - push(move(ts...).set_field_(piece_type::queen)); - return *this; - } - - template - move_list& push_under_promotions(const Ts&... ts) { - assert((move(ts...).piece() == piece_type::pawn)); - for (const auto& pt : under_promotion_types) { push(move(ts...).set_field_(pt)); } - return *this; - } -}; - -std::ostream& operator<<(std::ostream& ostr, const move_list& mv_ls) { - for (size_t i(0); i < mv_ls.size(); ++i) { ostr << i << ". " << mv_ls[i] << '\n'; } - return ostr; -} - -} // namespace chess \ No newline at end of file diff --git a/include/move_orderer.h b/include/move_orderer.h deleted file mode 100644 index f84d4a7..0000000 --- a/include/move_orderer.h +++ /dev/null @@ -1,211 +0,0 @@ -/* - Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle - - Seer is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Seer is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#pragma once - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace search { - -constexpr std::uint32_t make_positive(const std::int32_t& x) { - constexpr std::uint32_t upper = static_cast(1) + std::numeric_limits::max(); - return upper + x; -} - -struct move_orderer_data { - chess::move killer{chess::move::null()}; - chess::move follow{chess::move::null()}; - chess::move counter{chess::move::null()}; - chess::move first{chess::move::null()}; - - chess::square_set threatened{}; - - const chess::board* bd; - const history_heuristic* hh; - - move_orderer_data& set_killer(const chess::move& mv) { - killer = mv; - return *this; - } - - move_orderer_data& set_follow(const chess::move& mv) { - follow = mv; - return *this; - } - - move_orderer_data& set_counter(const chess::move& mv) { - counter = mv; - return *this; - } - - move_orderer_data& set_first(const chess::move& mv) { - first = mv; - return *this; - } - - move_orderer_data& set_threatened(const chess::square_set& mask) { - threatened = mask; - return *this; - } - - move_orderer_data(const chess::board* bd_, const history_heuristic* hh_) : bd{bd_}, hh{hh_} {} -}; - -struct move_orderer_entry { - using value_ = bit::range; - using killer_ = bit::next_flag; - using positive_noisy_ = bit::next_flag; - using first_ = bit::next_flag; - - chess::move mv; - std::uint64_t data_; - - const std::uint64_t& sort_key() const { return data_; } - - move_orderer_entry() = default; - move_orderer_entry(const chess::move& mv_, bool is_positive_noisy, bool is_killer, std::int32_t value) : mv{mv_}, data_{0} { - positive_noisy_::set(data_, is_positive_noisy); - killer_::set(data_, is_killer); - value_::set(data_, make_positive(value)); - } - - static inline move_orderer_entry make_noisy(const chess::move& mv, const bool positive_noisy, const std::int32_t& history_value) { - return move_orderer_entry(mv, positive_noisy, false, positive_noisy ? mv.mvv_lva_key() : history_value); - } - - static inline move_orderer_entry make_quiet(const chess::move& mv, const chess::move& killer, const std::int32_t& history_value) { - return move_orderer_entry(mv, false, mv == killer, history_value); - } -}; - -struct move_orderer_stepper { - using entry_array_type = std::array; - - bool is_initialized_{false}; - - entry_array_type entries_; - entry_array_type::iterator begin_; - entry_array_type::iterator end_; - - void update_list_() { - auto comparator = [](const move_orderer_entry& a, const move_orderer_entry& b) { return a.sort_key() < b.sort_key(); }; - std::iter_swap(begin_, std::max_element(begin_, end_, comparator)); - } - - bool is_initialized() const { return is_initialized_; } - bool has_next() const { return begin_ != end_; } - chess::move current_move() const { return begin_->mv; } - - void next() { - ++begin_; - if (begin_ != end_) { update_list_(); } - } - - move_orderer_stepper& initialize(const move_orderer_data& data, const chess::move_list& list) { - const history::context ctxt{data.follow, data.counter, data.threatened}; - - end_ = std::transform(list.begin(), list.end(), entries_.begin(), [&data, &ctxt](const chess::move& mv) { - if (mv.is_noisy()) { return move_orderer_entry::make_noisy(mv, data.bd->see_gt(mv, 0), data.hh->compute_value(ctxt, mv)); } - return move_orderer_entry::make_quiet(mv, data.killer, data.hh->compute_value(ctxt, mv)); - }); - - end_ = std::remove_if(begin_, end_, [&data](const auto& entry) { return entry.mv == data.first; }); - - if (begin_ != end_) { update_list_(); } - is_initialized_ = true; - return *this; - } - - move_orderer_stepper() : begin_{entries_.begin()} {} - - move_orderer_stepper& operator=(const move_orderer_stepper& other) = delete; - move_orderer_stepper& operator=(move_orderer_stepper&& other) = delete; - move_orderer_stepper(const move_orderer_stepper& other) = delete; - move_orderer_stepper(move_orderer_stepper&& other) = delete; -}; - -struct move_orderer_iterator_end_tag {}; - -template -struct move_orderer_iterator { - using difference_type = std::ptrdiff_t; - using value_type = std::tuple; - using pointer = std::tuple*; - using reference = std::tuple&; - using iterator_category = std::output_iterator_tag; - - int idx{}; - move_orderer_stepper stepper_; - move_orderer_data data_; - - std::tuple operator*() const { - if (!stepper_.is_initialized()) { return std::tuple(idx, data_.first); } - return std::tuple(idx, stepper_.current_move()); - } - - move_orderer_iterator& operator++() { - if (!stepper_.is_initialized()) { - stepper_.initialize(data_, data_.bd->generate_moves()); - } else { - stepper_.next(); - } - - ++idx; - return *this; - } - - bool operator==(const move_orderer_iterator&) const { return false; } - bool operator==(const move_orderer_iterator_end_tag&) const { return stepper_.is_initialized() && !stepper_.has_next(); } - - template - bool operator!=(const T& other) const { - return !(*this == other); - } - - move_orderer_iterator(const move_orderer_data& data) : data_{data} { - if (data.first.is_null() || !data.bd->is_legal(data.first)) { stepper_.initialize(data, data.bd->generate_moves()); } - } -}; - -template -struct move_orderer { - using iterator = move_orderer_iterator; - - move_orderer_data data_; - - move_orderer_iterator begin() { return move_orderer_iterator(data_); } - move_orderer_iterator_end_tag end() { return move_orderer_iterator_end_tag(); } - - move_orderer& set_first(const chess::move& mv) { - data_.set_first(mv); - return *this; - } - - move_orderer(const move_orderer_data& data) : data_{data} {} -}; - -} // namespace search diff --git a/include/nnue/aligned_scratchpad.h b/include/nnue/aligned_scratchpad.h new file mode 100644 index 0000000..cc474ac --- /dev/null +++ b/include/nnue/aligned_scratchpad.h @@ -0,0 +1,39 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include + +#include +#include + +namespace nnue { + +template +struct aligned_scratchpad { + alignas(simd::alignment) T data[scratchpad_size]; + + template + [[nodiscard]] aligned_slice get_nth_slice(const std::size_t& n) noexcept { + static_assert(scratchpad_size % dim == 0); + return aligned_slice(data + n * dim); + } +}; + +} // namespace nnue \ No newline at end of file diff --git a/include/nnue/aligned_slice.h b/include/nnue/aligned_slice.h new file mode 100644 index 0000000..6349b0e --- /dev/null +++ b/include/nnue/aligned_slice.h @@ -0,0 +1,57 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include +#include + +namespace nnue { + +template +struct aligned_slice { + T* data; + + template + [[nodiscard]] aligned_slice slice() noexcept { + static_assert(offset + out_dim <= dim); + return aligned_slice{data + offset}; + } + + [[maybe_unused]] aligned_slice& copy_from(const T* other) noexcept { + std::memcpy(data, other, sizeof(T) * dim); + return *this; + } + + [[maybe_unused]] aligned_slice& copy_from(const aligned_slice& other) noexcept { + std::memcpy(data, other.data, sizeof(T) * dim); + return *this; + } + + aligned_slice(T* data) noexcept : data{data} {} +}; + +template +inline std::ostream& operator<<(std::ostream& ostr, const aligned_slice& vec) noexcept { + static_assert(dim != 0, "can't stream empty slice."); + ostr << "aligned_slice(["; + for (std::size_t i = 0; i < (dim - 1); ++i) { ostr << vec.data[i] << ", "; } + ostr << vec.data[dim - 1] << "])"; + return ostr; +} +} // namespace nnue \ No newline at end of file diff --git a/include/nnue/aligned_vector.h b/include/nnue/aligned_vector.h new file mode 100644 index 0000000..1407f79 --- /dev/null +++ b/include/nnue/aligned_vector.h @@ -0,0 +1,137 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include + +#include +#include +#include +#include +#include + +namespace nnue { + +template +struct aligned_vector { + alignas(simd::alignment) T data[dim]; + + template + [[nodiscard]] constexpr aligned_vector apply(F&& f) const noexcept { + return aligned_vector{*this}.apply_(std::forward(f)); + } + + template + [[maybe_unused]] inline aligned_vector& apply_(F&& f) noexcept { +#pragma omp simd + for (std::size_t i = 0; i < dim; ++i) { data[i] = f(data[i]); } + return *this; + } + + [[maybe_unused]] inline aligned_vector& softmax_() noexcept { + static_assert(dim != 0, "can't softmax empty vector."); + T maximum_value = data[0]; + for (std::size_t i = 0; i < dim; ++i) { + if (data[i] > maximum_value) { maximum_value = data[i]; } + } + apply_([maximum_value](const T& x) { return std::exp(x - maximum_value); }); + const T z = sum(); + apply_([z](const T& x) { return x / z; }); + return *this; + } + + [[maybe_unused]] inline aligned_vector& add_(const T* other) noexcept { + simd::add(data, other); + return *this; + } + + [[maybe_unused]] inline aligned_vector& sub_(const T* other) noexcept { + simd::sub(data, other); + return *this; + } + + [[maybe_unused]] inline aligned_vector& set_(const T* other) noexcept { +#pragma omp simd + for (std::size_t i = 0; i < dim; ++i) { data[i] = other[i]; } + return *this; + } + + [[nodiscard]] inline aligned_slice as_slice() noexcept { return aligned_slice(data); } + + [[nodiscard]] inline T item() const noexcept { + static_assert(dim == 1, "called item() on vector with dim != 1"); + return data[0]; + } + + [[nodiscard]] inline T sum() const noexcept { + T result{}; +#pragma omp simd + for (std::size_t i = 0; i < dim; ++i) { result += data[i]; } + return result; + } + + template + [[nodiscard]] inline aligned_vector dequantized(const U& scale) const noexcept { + static_assert(std::is_integral_v && std::is_floating_point_v); + aligned_vector result; +#pragma omp simd + for (std::size_t i = 0; i < dim; ++i) { result.data[i] = scale * static_cast(data[i]); } + return result; + } + + [[nodiscard]] static inline aligned_vector zeros() noexcept { + aligned_vector result{}; +#pragma omp simd + for (std::size_t i = 0; i < dim; ++i) { result.data[i] = T(0); } + return result; + } + + [[nodiscard]] static inline aligned_vector ones() noexcept { + aligned_vector result{}; +#pragma omp simd + for (std::size_t i = 0; i < dim; ++i) { result.data[i] = T(1); } + return result; + } + + [[nodiscard]] static inline aligned_vector from(const T* data) noexcept { + aligned_vector result{}; +#pragma omp simd + for (std::size_t i = 0; i < dim; ++i) { result.data[i] = data[i]; } + return result; + } +}; + +template +[[nodiscard]] inline aligned_vector concat(const aligned_vector& a, const aligned_vector& b) noexcept { + auto c = aligned_vector::zeros(); +#pragma omp simd + for (std::size_t i = 0; i < dim0; ++i) { c.data[i] = a.data[i]; } + for (std::size_t i = 0; i < dim1; ++i) { c.data[dim0 + i] = b.data[i]; } + return c; +} + +template +inline std::ostream& operator<<(std::ostream& ostr, const aligned_vector& vec) noexcept { + static_assert(dim != 0, "can't stream empty vector."); + ostr << "aligned_vector(["; + for (std::size_t i = 0; i < (dim - 1); ++i) { ostr << vec.data[i] << ", "; } + ostr << vec.data[dim - 1] << "])"; + return ostr; +} + +} // namespace nnue \ No newline at end of file diff --git a/include/nnue/dense_relu_affine_layer.h b/include/nnue/dense_relu_affine_layer.h new file mode 100644 index 0000000..ef04bb5 --- /dev/null +++ b/include/nnue/dense_relu_affine_layer.h @@ -0,0 +1,80 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include +#include +#include + +#include + +namespace nnue { + +template +struct dense_relu_affine_layer { + static constexpr std::size_t W_numel = dim0 * dim1; + static constexpr std::size_t b_numel = dim1; + + alignas(simd::alignment) T W[W_numel]; + alignas(simd::alignment) dot_type b[b_numel]; + + [[nodiscard]] constexpr std::size_t num_parameters() const noexcept { return W_numel + b_numel; } + + [[nodiscard]] inline aligned_vector, dim1> forward(const aligned_vector& x) const noexcept { + auto result = aligned_vector, dim1>::from(b); + simd::relu_matrix_vector_product(W, x.data, result.data); + return result; + } + + [[nodiscard]] inline aligned_vector, dim1> forward(const aligned_slice& x) const noexcept { + auto result = aligned_vector, dim1>::from(b); + simd::relu_matrix_vector_product(W, x.data, result.data); + return result; + } + + template + [[maybe_unused]] dense_relu_affine_layer& load_(streamer_type& ws) noexcept { + ws.template stream(W, W_numel).template stream>(b, b_numel); + return *this; + } + + [[nodiscard]] dense_relu_affine_layer half_input_flipped() const noexcept { + static_assert(dim0 % 2 == 0); + constexpr std::size_t half_dim0 = dim0 / 2; + + dense_relu_affine_layer result = *this; + for (std::size_t i(0); i < W_numel; i += dim0) { + for (std::size_t j(0); j < half_dim0; ++j) { std::iter_swap(result.W + i + j, result.W + half_dim0 + i + j); } + } + + return result; + } + + template + [[nodiscard]] dense_relu_affine_layer quantized(const T& weight_scale, const T& bias_scale) const noexcept { + static_assert(std::is_floating_point_v && std::is_integral_v); + dense_relu_affine_layer result{}; +#pragma omp simd + for (std::size_t i = 0; i < W_numel; ++i) { result.W[i] = static_cast(std::round(weight_scale * W[i])); } + for (std::size_t i = 0; i < b_numel; ++i) { result.b[i] = static_cast>(std::round(bias_scale * b[i])); } + return result; + } +}; + +} // namespace nnue \ No newline at end of file diff --git a/include/nnue/dot_type.h b/include/nnue/dot_type.h new file mode 100644 index 0000000..1dd5659 --- /dev/null +++ b/include/nnue/dot_type.h @@ -0,0 +1,55 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include + +namespace nnue { + +template +struct dot_type_impl {}; + +template <> +struct dot_type_impl { + using type = float; +}; + +template <> +struct dot_type_impl { + using type = double; +}; + +template <> +struct dot_type_impl { + using type = std::int16_t; +}; + +template <> +struct dot_type_impl { + using type = std::int32_t; +}; + +template <> +struct dot_type_impl { + using type = std::int64_t; +}; + +template +using dot_type = typename dot_type_impl::type; + +} \ No newline at end of file diff --git a/include/embedded_weights.h b/include/nnue/embedded_weights.h similarity index 87% rename from include/embedded_weights.h rename to include/nnue/embedded_weights.h index 9b15153..39261d6 100644 --- a/include/embedded_weights.h +++ b/include/nnue/embedded_weights.h @@ -1,6 +1,6 @@ /* Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle Seer is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,8 +21,10 @@ #define INCBIN_STYLE INCBIN_STYLE_SNAKE #include -namespace embed { +namespace nnue::embed { +extern "C" { INCBIN(weights_file, EVALFILE); +} -} \ No newline at end of file +} // namespace nnue::embed \ No newline at end of file diff --git a/include/nnue/eval.h b/include/nnue/eval.h new file mode 100644 index 0000000..96a6962 --- /dev/null +++ b/include/nnue/eval.h @@ -0,0 +1,97 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, + see . +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace nnue { + +struct eval : public chess::sided> { + static constexpr std::size_t base_dim = weights::base_dim; + static constexpr std::size_t feature_transformer_dim = 2 * base_dim; + static constexpr std::size_t scratchpad_depth = 256; + + using parameter_type = weights::parameter_type; + using quantized_parameter_type = weights::quantized_parameter_type; + using scratchpad_type = aligned_scratchpad; + + const weights* weights_; + scratchpad_type* scratchpad_; + + std::size_t scratchpad_idx_; + + aligned_slice parent_base_; + aligned_slice base_; + + feature_transformer white; + feature_transformer black; + + [[nodiscard]] inline parameter_type propagate(const bool pov) const noexcept { + const auto x1 = (pov ? weights_->white_quantized_fc0 : weights_->black_quantized_fc0) + .forward(base_) + .dequantized(weights::dequantization_scale); + + const auto x2 = concat(x1, weights_->fc1.forward(x1)); + const auto x3 = concat(x2, weights_->fc2.forward(x2)); + return weights_->fc3.forward(x3).item(); + } + + [[nodiscard]] inline search::score_type evaluate(const bool pov, const parameter_type& phase) const noexcept { + constexpr auto one = static_cast(1.0); + constexpr auto mg = static_cast(1.1); + constexpr auto eg = static_cast(0.7); + + const parameter_type prediction = propagate(pov); + const parameter_type eval = phase * mg * prediction + (one - phase) * eg * prediction; + + const parameter_type value = + search::logit_scale * std::clamp(eval, search::min_logit, search::max_logit); + return static_cast(value); + } + + [[nodiscard]] eval next_child() const noexcept { + const std::size_t next_scratchpad_idx = scratchpad_idx_ + 1; + return eval(weights_, scratchpad_, scratchpad_idx_, next_scratchpad_idx); + } + + eval(const weights* src, scratchpad_type* scratchpad, const std::size_t& parent_scratchpad_idx, const std::size_t& scratchpad_idx) noexcept + : weights_{src}, + scratchpad_{scratchpad}, + scratchpad_idx_{scratchpad_idx}, + parent_base_(scratchpad_->get_nth_slice(parent_scratchpad_idx)), + base_(scratchpad_->get_nth_slice(scratchpad_idx_)), + white{&src->quantized_shared, parent_base_.slice(), base_.slice()}, + black{&src->quantized_shared, parent_base_.slice(), base_.slice()} {} +}; + +} // namespace nnue diff --git a/include/nnue_eval_node.h b/include/nnue/eval_node.h similarity index 69% rename from include/nnue_eval_node.h rename to include/nnue/eval_node.h index dd3afa3..b10b945 100644 --- a/include/nnue_eval_node.h +++ b/include/nnue/eval_node.h @@ -1,6 +1,6 @@ /* Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle Seer is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,19 +16,10 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include +#include +#include +#include +#include namespace nnue { @@ -47,9 +38,9 @@ struct eval_node { eval eval_; } data_; - bool dirty() const { return dirty_; } + [[nodiscard]] bool dirty() const noexcept { return dirty_; } - const eval& evaluator() { + [[nodiscard]] const eval& evaluator() { if (!dirty_) { return data_.eval_; } dirty_ = false; const context ctxt = data_.context_; @@ -58,13 +49,13 @@ struct eval_node { return data_.eval_; } - eval_node dirty_child(sided_feature_reset_cache* reset_cache, const chess::board* bd, const chess::move& mv) { + [[nodiscard]] eval_node dirty_child(sided_feature_reset_cache* reset_cache, const chess::board* bd, const chess::move& mv) noexcept { return eval_node::dirty_node(context{reset_cache, this, bd, mv}); } - static eval_node dirty_node(const context& context) { return eval_node{true, {context}}; } + [[nodiscard]] static eval_node dirty_node(const context& context) noexcept { return eval_node{true, {context}}; } - static eval_node clean_node(const eval& eval) { + [[nodiscard]] static eval_node clean_node(const eval& eval) noexcept { eval_node result{}; result.dirty_ = false; result.data_.eval_ = eval; diff --git a/include/nnue_feature_reset_cache.h b/include/nnue/feature_reset_cache.h similarity index 53% rename from include/nnue_feature_reset_cache.h rename to include/nnue/feature_reset_cache.h index 9352023..0aa4986 100644 --- a/include/nnue_feature_reset_cache.h +++ b/include/nnue/feature_reset_cache.h @@ -1,6 +1,6 @@ /* Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle Seer is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,12 +17,12 @@ #pragma once -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include #include @@ -30,20 +30,20 @@ namespace nnue { struct feature_reset_cache_entry { - static constexpr size_t dim = weights::base_dim; + static constexpr std::size_t dim = weights::base_dim; using parameter_type = weights::quantized_parameter_type; - using weights_type = big_affine; + using weights_type = sparse_affine_layer; const weights_type* weights_; chess::sided_piece_configuration config; aligned_slice slice_; - void insert(const size_t& idx) { weights_->insert_idx(idx, slice_); } - void erase(const size_t& idx) { weights_->erase_idx(idx, slice_); } - void copy_state_to(feature_transformer& dst) const { dst.slice_.copy_from(slice_); } + void insert(const std::size_t& idx) const noexcept { weights_->insert_idx(idx, slice_); } + void erase(const std::size_t& idx) const noexcept { weights_->erase_idx(idx, slice_); } + void copy_state_to(feature_transformer& dst) const noexcept { dst.slice_.copy_from(slice_); } - void reinitialize(const weights_type* weights, const aligned_slice& slice) { + void reinitialize(const weights_type* weights, const aligned_slice& slice) noexcept { weights_ = weights; slice_ = slice; @@ -51,36 +51,36 @@ struct feature_reset_cache_entry { config = chess::sided_piece_configuration{}; } - feature_reset_cache_entry() : config{}, slice_{nullptr} {} + feature_reset_cache_entry() noexcept : config{}, slice_{nullptr} {} }; struct feature_reset_cache { using entry_type = feature_reset_cache_entry; - static constexpr size_t num_squares = 64; + static constexpr std::size_t num_squares = 64; - stack_scratchpad scratchpad_{}; + aligned_scratchpad scratchpad_{}; feature_reset_cache_entry entries_[num_squares]{}; - feature_reset_cache_entry& look_up(const chess::square& sq) { return entries_[sq.index()]; } + [[nodiscard]] feature_reset_cache_entry& look_up(const chess::square& sq) noexcept { return entries_[sq.index()]; } - void reinitialize(const weights* weights) { - for (size_t i(0); i < num_squares; ++i) { + void reinitialize(const weights* weights) noexcept { + for (std::size_t i(0); i < num_squares; ++i) { const auto slice = scratchpad_.get_nth_slice(i); entries_[i].reinitialize(&weights->quantized_shared, slice); } } }; -struct sided_feature_reset_cache : chess::sided { +struct sided_feature_reset_cache : public chess::sided { feature_reset_cache white; feature_reset_cache black; - void reinitialize(const weights* weights) { + void reinitialize(const weights* weights) noexcept { white.reinitialize(weights); black.reinitialize(weights); } - sided_feature_reset_cache() : white{}, black{} {} + sided_feature_reset_cache() noexcept : white{}, black{} {} }; } // namespace nnue diff --git a/include/nnue/feature_transformer.h b/include/nnue/feature_transformer.h new file mode 100644 index 0000000..ce58fc9 --- /dev/null +++ b/include/nnue/feature_transformer.h @@ -0,0 +1,62 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, + see . +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace nnue { + +template +struct feature_transformer { + const sparse_affine_layer* weights_; + + aligned_slice parent_slice_; + aligned_slice slice_; + + void clear() noexcept { slice_.copy_from(weights_->b); } + void copy_parent() noexcept { slice_.copy_from(parent_slice_); } + void insert(const std::size_t& idx) noexcept { weights_->insert_idx(idx, slice_); } + void erase(const std::size_t& idx) noexcept { weights_->erase_idx(idx, slice_); } + + void copy_parent_insert_erase(const std::size_t& insert_idx, const std::size_t& erase_idx) noexcept { + weights_->insert_erase_idx(insert_idx, erase_idx, parent_slice_, slice_); + } + + void copy_parent_insert_erase_erase(const std::size_t& insert_idx, const std::size_t& erase_idx_0, const std::size_t& erase_idx_1) noexcept { + weights_->insert_erase_erase_idx(insert_idx, erase_idx_0, erase_idx_1, parent_slice_, slice_); + } + + feature_transformer(const sparse_affine_layer* src, aligned_slice&& parent_slice, aligned_slice&& slice) noexcept + : weights_{src}, parent_slice_{parent_slice}, slice_{slice} {} +}; + +} // namespace nnue \ No newline at end of file diff --git a/include/simd.h b/include/nnue/simd.h similarity index 81% rename from include/simd.h rename to include/nnue/simd.h index 98d861b..b02c969 100644 --- a/include/simd.h +++ b/include/nnue/simd.h @@ -1,6 +1,6 @@ /* Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle Seer is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,6 +19,7 @@ #include +#include #include #include #include @@ -26,7 +27,7 @@ namespace simd { -void* aligned_alloc(size_t alignment, size_t size) { +[[nodiscard]] inline void* aligned_alloc(std::size_t alignment, std::size_t size) { #if defined(_WIN32) return _mm_malloc(size, alignment); #else @@ -34,7 +35,7 @@ void* aligned_alloc(size_t alignment, size_t size) { #endif } -void aligned_free(void* ptr) { +inline void aligned_free(void* ptr) { #if defined(_WIN32) _mm_free(ptr); #else @@ -47,7 +48,7 @@ struct vector_512 { using integral_type = __m512i; using float_type = __m512; static_assert(sizeof(integral_type) == sizeof(float_type)); - static constexpr size_t size = sizeof(integral_type); + static constexpr std::size_t size = sizeof(integral_type); }; #endif @@ -56,7 +57,7 @@ struct vector_256 { using integral_type = __m256i; using float_type = __m256; static_assert(sizeof(integral_type) == sizeof(float_type)); - static constexpr size_t size = sizeof(integral_type); + static constexpr std::size_t size = sizeof(integral_type); }; #endif @@ -65,25 +66,25 @@ struct vector_128 { using integral_type = __m128i; using float_type = __m128; static_assert(sizeof(integral_type) == sizeof(float_type)); - static constexpr size_t size = sizeof(integral_type); + static constexpr std::size_t size = sizeof(integral_type); }; #endif #if defined(__AVX512BW__) -constexpr size_t alignment = vector_512::size; +constexpr std::size_t alignment = vector_512::size; #elif defined(__AVX2__) -constexpr size_t alignment = vector_256::size; +constexpr std::size_t alignment = vector_256::size; #elif defined(__SSSE3__) -constexpr size_t alignment = vector_128::size; +constexpr std::size_t alignment = vector_128::size; #else -constexpr size_t default_alignment = 16; -constexpr size_t alignment = default_alignment; +constexpr std::size_t default_alignment = 16; +constexpr std::size_t alignment = default_alignment; #endif template -inline constexpr size_t per_unit = vector_x::size / sizeof(element_type); +inline constexpr std::size_t per_unit = vector_x::size / sizeof(element_type); -template +template static constexpr bool divides = A % B == 0; template @@ -92,7 +93,7 @@ struct overload_set {}; template struct overload_set { template - static auto f(Us&&... us) { + static auto f(Us&&... us) noexcept { if constexpr (T::available) { return T::f(std::forward(us)...); } else { @@ -104,51 +105,51 @@ struct overload_set { template struct overload_set { template - static auto f(Us&&... us) { + static auto f(Us&&... us) noexcept { return T::f(std::forward(us)...); } }; -template -inline void add(T* a, const T* b) { +template +inline void add(T* a, const T* b) noexcept { #pragma omp simd - for (size_t i = 0; i < dim; ++i) { a[i] += b[i]; } + for (std::size_t i = 0; i < dim; ++i) { a[i] += b[i]; } } -template -inline void sub(T* a, const T* b) { +template +inline void sub(T* a, const T* b) noexcept { #pragma omp simd - for (size_t i = 0; i < dim; ++i) { a[i] -= b[i]; } + for (std::size_t i = 0; i < dim; ++i) { a[i] -= b[i]; } } -template -inline void add_add_sub(const T* a_0, const T* a_1, const T* s_0, T* out) { +template +inline void add_add_sub(const T* a_0, const T* a_1, const T* s_0, T* out) noexcept { #pragma omp simd - for (size_t i = 0; i < dim; ++i) { out[i] = a_0[i] + a_1[i] - s_0[i]; } + for (std::size_t i = 0; i < dim; ++i) { out[i] = a_0[i] + a_1[i] - s_0[i]; } } -template -inline void add_add_sub_sub(const T* a_0, const T* a_1, const T* s_0, const T* s_1, T* out) { +template +inline void add_add_sub_sub(const T* a_0, const T* a_1, const T* s_0, const T* s_1, T* out) noexcept { #pragma omp simd - for (size_t i = 0; i < dim; ++i) { out[i] = a_0[i] - s_0[i] + a_1[i] - s_1[i]; } + for (std::size_t i = 0; i < dim; ++i) { out[i] = a_0[i] - s_0[i] + a_1[i] - s_1[i]; } } -template -inline void relu_matrix_vector_product(const T0* matrix, const T0* input, T1* output) { +template +inline void relu_matrix_vector_product(const T0* matrix, const T0* input, T1* output) noexcept { #pragma omp simd - for (size_t i = 0; i < dim1; ++i) { - for (size_t j = 0; j < dim0; ++j) { output[i] += static_cast(std::max(input[j], T0{0})) * static_cast((matrix + i * dim0)[j]); } + for (std::size_t i = 0; i < dim1; ++i) { + for (std::size_t j = 0; j < dim0; ++j) { output[i] += static_cast(std::max(input[j], T0{0})) * static_cast((matrix + i * dim0)[j]); } } } #if defined(__AVX512BW__) -template +template struct int16_add_x128 { - static constexpr size_t num_units = 4; + static constexpr std::size_t num_units = 4; static constexpr bool available = divides>; - static inline void f(std::int16_t* a, const std::int16_t* b) { - for (size_t i(0); i < dim; i += num_units * per_unit) { + static inline void f(std::int16_t* a, const std::int16_t* b) noexcept { + for (std::size_t i(0); i < dim; i += num_units * per_unit) { __m512i* a_0 = (__m512i*)(a + i + 0 * per_unit); *a_0 = _mm512_add_epi16(*a_0, _mm512_load_si512((__m512i*)(b + i + 0 * per_unit))); @@ -164,18 +165,18 @@ struct int16_add_x128 { } }; -template -inline void add(std::int16_t* a, const std::int16_t* b) { +template +inline void add(std::int16_t* a, const std::int16_t* b) noexcept { return overload_set>::f(a, b); } -template +template struct int16_sub_x128 { - static constexpr size_t num_units = 4; + static constexpr std::size_t num_units = 4; static constexpr bool available = divides>; - static inline void f(std::int16_t* a, const std::int16_t* b) { - for (size_t i(0); i < dim; i += num_units * per_unit) { + static inline void f(std::int16_t* a, const std::int16_t* b) noexcept { + for (std::size_t i(0); i < dim; i += num_units * per_unit) { __m512i* a_0 = (__m512i*)(a + i + 0 * per_unit); *a_0 = _mm512_sub_epi16(*a_0, _mm512_load_si512((__m512i*)(b + i + 0 * per_unit))); @@ -191,19 +192,18 @@ struct int16_sub_x128 { } }; -template +template inline void sub(std::int16_t* a, const std::int16_t* b) { return overload_set>::f(a, b); } - -template +template struct int16_add_add_sub_x128 { - static constexpr size_t num_units = 4; + static constexpr std::size_t num_units = 4; static constexpr bool available = divides>; - static inline void f(const std::int16_t* a_0, const std::int16_t* a_1, const std::int16_t* s_0, std::int16_t* out) { - for (size_t i(0); i < dim; i += num_units * per_unit) { + static inline void f(const std::int16_t* a_0, const std::int16_t* a_1, const std::int16_t* s_0, std::int16_t* out) noexcept { + for (std::size_t i(0); i < dim; i += num_units * per_unit) { { const __m512i a_0_0 = _mm512_load_si512((__m512i*)(a_0 + i + 0 * per_unit)); const __m512i a_1_0 = _mm512_load_si512((__m512i*)(a_1 + i + 0 * per_unit)); @@ -239,18 +239,19 @@ struct int16_add_add_sub_x128 { } }; -template -inline void add_add_sub(const std::int16_t* a_0, const std::int16_t* a_1, const std::int16_t* s_0, std::int16_t* out) { +template +inline void add_add_sub(const std::int16_t* a_0, const std::int16_t* a_1, const std::int16_t* s_0, std::int16_t* out) noexcept { return overload_set>::f(a_0, a_1, s_0, out); } -template +template struct int16_add_add_sub_sub_x128 { - static constexpr size_t num_units = 4; + static constexpr std::size_t num_units = 4; static constexpr bool available = divides>; - static inline void f(const std::int16_t* a_0, const std::int16_t* a_1, const std::int16_t* s_0, const std::int16_t* s_1, std::int16_t* out) { - for (size_t i(0); i < dim; i += num_units * per_unit) { + static inline void f( + const std::int16_t* a_0, const std::int16_t* a_1, const std::int16_t* s_0, const std::int16_t* s_1, std::int16_t* out) noexcept { + for (std::size_t i(0); i < dim; i += num_units * per_unit) { { const __m512i a_0_0 = _mm512_load_si512((__m512i*)(a_0 + i + 0 * per_unit)); const __m512i a_1_0 = _mm512_load_si512((__m512i*)(a_1 + i + 0 * per_unit)); @@ -290,21 +291,22 @@ struct int16_add_add_sub_sub_x128 { } }; -template -inline void add_add_sub_sub(const std::int16_t* a_0, const std::int16_t* a_1, const std::int16_t* s_0, const std::int16_t* s_1, std::int16_t* out) { +template +inline void add_add_sub_sub( + const std::int16_t* a_0, const std::int16_t* a_1, const std::int16_t* s_0, const std::int16_t* s_1, std::int16_t* out) noexcept { return overload_set>::f(a_0, a_1, s_0, s_1, out); } -template +template struct float_relu_matrix_vector_product_x8_x1 { static constexpr bool available = divides>; static inline void f(const float* matrix, const float* input, float* output) { const __m256 zero = _mm256_setzero_ps(); - for (size_t i(0); i < dim1; ++i) { + for (std::size_t i(0); i < dim1; ++i) { __m256 sum = _mm256_setzero_ps(); - for (size_t j(0); j < dim0; j += per_unit) { + for (std::size_t j(0); j < dim0; j += per_unit) { const __m256 input_region = _mm256_max_ps(zero, _mm256_load_ps(input + j)); sum = _mm256_add_ps(_mm256_mul_ps(_mm256_load_ps(matrix + i * dim0 + j), input_region), sum); } @@ -318,16 +320,16 @@ struct float_relu_matrix_vector_product_x8_x1 { } }; -template +template struct float_relu_matrix_vector_product_x8_x8 { - static constexpr size_t num_units = 8; + static constexpr std::size_t num_units = 8; static constexpr bool available = divides && divides>; - static inline void f(const float* matrix, const float* input, float* output) { + static inline void f(const float* matrix, const float* input, float* output) noexcept { const __m256 zero = _mm256_setzero_ps(); __m256* v_output = (__m256*)output; - constexpr size_t output_step = num_units / per_unit; - for (size_t i(0); i < dim1; i += num_units, v_output += output_step) { + constexpr std::size_t output_step = num_units / per_unit; + for (std::size_t i(0); i < dim1; i += num_units, v_output += output_step) { __m256 sum_0 = _mm256_setzero_ps(); __m256 sum_1 = _mm256_setzero_ps(); __m256 sum_2 = _mm256_setzero_ps(); @@ -337,7 +339,7 @@ struct float_relu_matrix_vector_product_x8_x8 { __m256 sum_6 = _mm256_setzero_ps(); __m256 sum_7 = _mm256_setzero_ps(); - for (size_t j(0); j < dim0; j += per_unit) { + for (std::size_t j(0); j < dim0; j += per_unit) { const __m256 input_region = _mm256_max_ps(zero, _mm256_load_ps(input + j)); sum_0 = _mm256_add_ps(_mm256_mul_ps(_mm256_load_ps(matrix + (i + 0) * dim0 + j), input_region), sum_0); sum_1 = _mm256_add_ps(_mm256_mul_ps(_mm256_load_ps(matrix + (i + 1) * dim0 + j), input_region), sum_1); @@ -364,12 +366,12 @@ struct float_relu_matrix_vector_product_x8_x8 { } }; -template +template struct int16_relu_matrix_vector_product_x32_x8 { - static constexpr size_t num_units = 8; + static constexpr std::size_t num_units = 8; static constexpr bool available = divides && divides>; - static inline void f(const std::int16_t* matrix, const std::int16_t* input, std::int32_t* output) { + static inline void f(const std::int16_t* matrix, const std::int16_t* input, std::int32_t* output) noexcept { const __m512i zero = _mm512_setzero_si512(); const __m512i mm512_unpacklo_epi128_permutationx2var = @@ -379,8 +381,8 @@ struct int16_relu_matrix_vector_product_x32_x8 { _mm512_set_epi32(0x1f, 0x1e, 0x1d, 0x1c, 0x0f, 0x0e, 0x0d, 0x0c, 0x1b, 0x1a, 0x19, 0x18, 0x0b, 0x0a, 0x09, 0x08); __m256i* v_output = (__m256i*)output; - constexpr size_t output_step = num_units / per_unit; - for (size_t i(0); i < dim1; i += num_units, v_output += output_step) { + constexpr std::size_t output_step = num_units / per_unit; + for (std::size_t i(0); i < dim1; i += num_units, v_output += output_step) { __m512i sum_0 = _mm512_setzero_si512(); __m512i sum_1 = _mm512_setzero_si512(); __m512i sum_2 = _mm512_setzero_si512(); @@ -390,7 +392,7 @@ struct int16_relu_matrix_vector_product_x32_x8 { __m512i sum_6 = _mm512_setzero_si512(); __m512i sum_7 = _mm512_setzero_si512(); - for (size_t j(0); j < dim0; j += per_unit) { + for (std::size_t j(0); j < dim0; j += per_unit) { const __m512i input_region = _mm512_max_epi16(zero, _mm512_load_si512((__m512i*)(input + j))); sum_0 = _mm512_add_epi32(_mm512_madd_epi16(_mm512_load_si512((__m512i*)(matrix + (i + 0) * dim0 + j)), input_region), sum_0); sum_1 = _mm512_add_epi32(_mm512_madd_epi16(_mm512_load_si512((__m512i*)(matrix + (i + 1) * dim0 + j)), input_region), sum_1); @@ -421,25 +423,25 @@ struct int16_relu_matrix_vector_product_x32_x8 { } }; -template -inline void relu_matrix_vector_product(const float* matrix, const float* input, float* output) { +template +inline void relu_matrix_vector_product(const float* matrix, const float* input, float* output) noexcept { return overload_set, float_relu_matrix_vector_product_x8_x1>::f( matrix, input, output); } -template -inline void relu_matrix_vector_product(const std::int16_t* matrix, const std::int16_t* input, std::int32_t* output) { +template +inline void relu_matrix_vector_product(const std::int16_t* matrix, const std::int16_t* input, std::int32_t* output) noexcept { return overload_set>::f(matrix, input, output); } #elif defined(__AVX2__) -template +template struct int16_add_x64 { - static constexpr size_t num_units = 4; + static constexpr std::size_t num_units = 4; static constexpr bool available = divides>; - static inline void f(std::int16_t* a, const std::int16_t* b) { - for (size_t i(0); i < dim; i += num_units * per_unit) { + static inline void f(std::int16_t* a, const std::int16_t* b) noexcept { + for (std::size_t i(0); i < dim; i += num_units * per_unit) { __m256i* a_0 = (__m256i*)(a + i + 0 * per_unit); *a_0 = _mm256_add_epi16(*a_0, _mm256_load_si256((__m256i*)(b + i + 0 * per_unit))); @@ -455,18 +457,18 @@ struct int16_add_x64 { } }; -template -inline void add(std::int16_t* a, const std::int16_t* b) { +template +inline void add(std::int16_t* a, const std::int16_t* b) noexcept { return overload_set>::f(a, b); } -template +template struct int16_sub_x64 { - static constexpr size_t num_units = 4; + static constexpr std::size_t num_units = 4; static constexpr bool available = divides>; - static inline void f(std::int16_t* a, const std::int16_t* b) { - for (size_t i(0); i < dim; i += num_units * per_unit) { + static inline void f(std::int16_t* a, const std::int16_t* b) noexcept { + for (std::size_t i(0); i < dim; i += num_units * per_unit) { __m256i* a_0 = (__m256i*)(a + i + 0 * per_unit); *a_0 = _mm256_sub_epi16(*a_0, _mm256_load_si256((__m256i*)(b + i + 0 * per_unit))); @@ -482,18 +484,18 @@ struct int16_sub_x64 { } }; -template -inline void sub(std::int16_t* a, const std::int16_t* b) { +template +inline void sub(std::int16_t* a, const std::int16_t* b) noexcept { return overload_set>::f(a, b); } -template +template struct int16_add_add_sub_x64 { - static constexpr size_t num_units = 4; + static constexpr std::size_t num_units = 4; static constexpr bool available = divides>; - static inline void f(const std::int16_t* a_0, const std::int16_t* a_1, const std::int16_t* s_0, std::int16_t* out) { - for (size_t i(0); i < dim; i += num_units * per_unit) { + static inline void f(const std::int16_t* a_0, const std::int16_t* a_1, const std::int16_t* s_0, std::int16_t* out) noexcept { + for (std::size_t i(0); i < dim; i += num_units * per_unit) { { const __m256i a_0_0 = _mm256_load_si256((__m256i*)(a_0 + i + 0 * per_unit)); const __m256i a_1_0 = _mm256_load_si256((__m256i*)(a_1 + i + 0 * per_unit)); @@ -529,18 +531,19 @@ struct int16_add_add_sub_x64 { } }; -template +template inline void add_add_sub(const std::int16_t* a_0, const std::int16_t* a_1, const std::int16_t* s_0, std::int16_t* out) { return overload_set>::f(a_0, a_1, s_0, out); } -template +template struct int16_add_add_sub_sub_x64 { - static constexpr size_t num_units = 4; + static constexpr std::size_t num_units = 4; static constexpr bool available = divides>; - static inline void f(const std::int16_t* a_0, const std::int16_t* a_1, const std::int16_t* s_0, const std::int16_t* s_1, std::int16_t* out) { - for (size_t i(0); i < dim; i += num_units * per_unit) { + static inline void f( + const std::int16_t* a_0, const std::int16_t* a_1, const std::int16_t* s_0, const std::int16_t* s_1, std::int16_t* out) noexcept { + for (std::size_t i(0); i < dim; i += num_units * per_unit) { { const __m256i a_0_0 = _mm256_load_si256((__m256i*)(a_0 + i + 0 * per_unit)); const __m256i a_1_0 = _mm256_load_si256((__m256i*)(a_1 + i + 0 * per_unit)); @@ -580,21 +583,22 @@ struct int16_add_add_sub_sub_x64 { } }; -template -inline void add_add_sub_sub(const std::int16_t* a_0, const std::int16_t* a_1, const std::int16_t* s_0, const std::int16_t* s_1, std::int16_t* out) { +template +inline void add_add_sub_sub( + const std::int16_t* a_0, const std::int16_t* a_1, const std::int16_t* s_0, const std::int16_t* s_1, std::int16_t* out) noexcept { return overload_set>::f(a_0, a_1, s_0, s_1, out); } -template +template struct float_relu_matrix_vector_product_x8_x1 { static constexpr bool available = divides>; static inline void f(const float* matrix, const float* input, float* output) { const __m256 zero = _mm256_setzero_ps(); - for (size_t i(0); i < dim1; ++i) { + for (std::size_t i(0); i < dim1; ++i) { __m256 sum = _mm256_setzero_ps(); - for (size_t j(0); j < dim0; j += per_unit) { + for (std::size_t j(0); j < dim0; j += per_unit) { const __m256 input_region = _mm256_max_ps(zero, _mm256_load_ps(input + j)); sum = _mm256_add_ps(_mm256_mul_ps(_mm256_load_ps(matrix + i * dim0 + j), input_region), sum); } @@ -608,16 +612,16 @@ struct float_relu_matrix_vector_product_x8_x1 { } }; -template +template struct float_relu_matrix_vector_product_x8_x8 { - static constexpr size_t num_units = 8; + static constexpr std::size_t num_units = 8; static constexpr bool available = divides && divides>; - static inline void f(const float* matrix, const float* input, float* output) { + static inline void f(const float* matrix, const float* input, float* output) noexcept { const __m256 zero = _mm256_setzero_ps(); __m256* v_output = (__m256*)output; - constexpr size_t output_step = num_units / per_unit; - for (size_t i(0); i < dim1; i += num_units, v_output += output_step) { + constexpr std::size_t output_step = num_units / per_unit; + for (std::size_t i(0); i < dim1; i += num_units, v_output += output_step) { __m256 sum_0 = _mm256_setzero_ps(); __m256 sum_1 = _mm256_setzero_ps(); __m256 sum_2 = _mm256_setzero_ps(); @@ -627,7 +631,7 @@ struct float_relu_matrix_vector_product_x8_x8 { __m256 sum_6 = _mm256_setzero_ps(); __m256 sum_7 = _mm256_setzero_ps(); - for (size_t j(0); j < dim0; j += per_unit) { + for (std::size_t j(0); j < dim0; j += per_unit) { const __m256 input_region = _mm256_max_ps(zero, _mm256_load_ps(input + j)); sum_0 = _mm256_add_ps(_mm256_mul_ps(_mm256_load_ps(matrix + (i + 0) * dim0 + j), input_region), sum_0); sum_1 = _mm256_add_ps(_mm256_mul_ps(_mm256_load_ps(matrix + (i + 1) * dim0 + j), input_region), sum_1); @@ -654,16 +658,16 @@ struct float_relu_matrix_vector_product_x8_x8 { } }; -template +template struct int16_relu_matrix_vector_product_x16_x8 { - static constexpr size_t num_units = 8; + static constexpr std::size_t num_units = 8; static constexpr bool available = divides && divides>; - static inline void f(const std::int16_t* matrix, const std::int16_t* input, std::int32_t* output) { + static inline void f(const std::int16_t* matrix, const std::int16_t* input, std::int32_t* output) noexcept { const __m256i zero = _mm256_setzero_si256(); __m256i* v_output = (__m256i*)output; - constexpr size_t output_step = num_units / per_unit; - for (size_t i(0); i < dim1; i += num_units, v_output += output_step) { + constexpr std::size_t output_step = num_units / per_unit; + for (std::size_t i(0); i < dim1; i += num_units, v_output += output_step) { __m256i sum_0 = _mm256_setzero_si256(); __m256i sum_1 = _mm256_setzero_si256(); __m256i sum_2 = _mm256_setzero_si256(); @@ -673,7 +677,7 @@ struct int16_relu_matrix_vector_product_x16_x8 { __m256i sum_6 = _mm256_setzero_si256(); __m256i sum_7 = _mm256_setzero_si256(); - for (size_t j(0); j < dim0; j += per_unit) { + for (std::size_t j(0); j < dim0; j += per_unit) { const __m256i input_region = _mm256_max_epi16(zero, _mm256_load_si256((__m256i*)(input + j))); sum_0 = _mm256_add_epi32(_mm256_madd_epi16(_mm256_load_si256((__m256i*)(matrix + (i + 0) * dim0 + j)), input_region), sum_0); sum_1 = _mm256_add_epi32(_mm256_madd_epi16(_mm256_load_si256((__m256i*)(matrix + (i + 1) * dim0 + j)), input_region), sum_1); @@ -701,31 +705,31 @@ struct int16_relu_matrix_vector_product_x16_x8 { } }; -template -inline void relu_matrix_vector_product(const float* matrix, const float* input, float* output) { +template +inline void relu_matrix_vector_product(const float* matrix, const float* input, float* output) noexcept { return overload_set, float_relu_matrix_vector_product_x8_x1>::f( matrix, input, output); } -template -inline void relu_matrix_vector_product(const std::int16_t* matrix, const std::int16_t* input, std::int32_t* output) { +template +inline void relu_matrix_vector_product(const std::int16_t* matrix, const std::int16_t* input, std::int32_t* output) noexcept { return overload_set>::f(matrix, input, output); } #elif defined(__SSSE3__) -template +template struct float_relu_matrix_vector_product_x8_x1 { - static constexpr size_t num_units = 2; - static constexpr size_t per_iteration = per_unit * num_units; + static constexpr std::size_t num_units = 2; + static constexpr std::size_t per_iteration = per_unit * num_units; static constexpr bool available = divides; - static inline void f(const float* matrix, const float* input, float* output) { + static inline void f(const float* matrix, const float* input, float* output) noexcept { const __m128 zero = _mm_setzero_ps(); - for (size_t i(0); i < dim1; ++i) { + for (std::size_t i(0); i < dim1; ++i) { __m128 sum_0 = _mm_setzero_ps(); __m128 sum_1 = _mm_setzero_ps(); - for (size_t j(0); j < dim0; j += per_iteration) { + for (std::size_t j(0); j < dim0; j += per_iteration) { const __m128 input_region_0 = _mm_max_ps(zero, _mm_load_ps(input + j + 0 * per_unit)); const __m128 input_region_1 = _mm_max_ps(zero, _mm_load_ps(input + j + 1 * per_unit)); @@ -741,16 +745,16 @@ struct float_relu_matrix_vector_product_x8_x1 { } }; -template +template struct float_relu_matrix_vector_product_x4_x8 { - static constexpr size_t num_units = 8; + static constexpr std::size_t num_units = 8; static constexpr bool available = divides && divides>; - static inline void f(const float* matrix, const float* input, float* output) { + static inline void f(const float* matrix, const float* input, float* output) noexcept { const __m128 zero = _mm_setzero_ps(); __m128* v_output = (__m128*)output; - constexpr size_t output_step = num_units / per_unit; - for (size_t i(0); i < dim1; i += num_units, v_output += output_step) { + constexpr std::size_t output_step = num_units / per_unit; + for (std::size_t i(0); i < dim1; i += num_units, v_output += output_step) { __m128 sum_0 = _mm_setzero_ps(); __m128 sum_1 = _mm_setzero_ps(); __m128 sum_2 = _mm_setzero_ps(); @@ -760,7 +764,7 @@ struct float_relu_matrix_vector_product_x4_x8 { __m128 sum_6 = _mm_setzero_ps(); __m128 sum_7 = _mm_setzero_ps(); - for (size_t j(0); j < dim0; j += per_unit) { + for (std::size_t j(0); j < dim0; j += per_unit) { const __m128 input_region = _mm_max_ps(zero, _mm_load_ps(input + j)); sum_0 = _mm_add_ps(_mm_mul_ps(_mm_load_ps(matrix + (i + 0) * dim0 + j), input_region), sum_0); sum_1 = _mm_add_ps(_mm_mul_ps(_mm_load_ps(matrix + (i + 1) * dim0 + j), input_region), sum_1); @@ -786,16 +790,16 @@ struct float_relu_matrix_vector_product_x4_x8 { } }; -template +template struct int16_relu_matrix_vector_product_x8_x8 { - static constexpr size_t num_units = 8; + static constexpr std::size_t num_units = 8; static constexpr bool available = divides && divides>; - static inline void f(const std::int16_t* matrix, const std::int16_t* input, std::int32_t* output) { + static inline void f(const std::int16_t* matrix, const std::int16_t* input, std::int32_t* output) noexcept { const __m128i zero = _mm_setzero_si128(); __m128i* v_output = (__m128i*)output; - constexpr size_t output_step = num_units / per_unit; - for (size_t i(0); i < dim1; i += num_units, v_output += output_step) { + constexpr std::size_t output_step = num_units / per_unit; + for (std::size_t i(0); i < dim1; i += num_units, v_output += output_step) { __m128i sum_0 = _mm_setzero_si128(); __m128i sum_1 = _mm_setzero_si128(); __m128i sum_2 = _mm_setzero_si128(); @@ -805,7 +809,7 @@ struct int16_relu_matrix_vector_product_x8_x8 { __m128i sum_6 = _mm_setzero_si128(); __m128i sum_7 = _mm_setzero_si128(); - for (size_t j(0); j < dim0; j += per_unit) { + for (std::size_t j(0); j < dim0; j += per_unit) { const __m128i input_region = _mm_max_epi16(zero, _mm_load_si128((__m128i*)(input + j))); sum_0 = _mm_add_epi32(_mm_madd_epi16(_mm_load_si128((__m128i*)(matrix + (i + 0) * dim0 + j)), input_region), sum_0); sum_1 = _mm_add_epi32(_mm_madd_epi16(_mm_load_si128((__m128i*)(matrix + (i + 1) * dim0 + j)), input_region), sum_1); @@ -831,14 +835,14 @@ struct int16_relu_matrix_vector_product_x8_x8 { } }; -template -inline void relu_matrix_vector_product(const float* matrix, const float* input, float* output) { +template +inline void relu_matrix_vector_product(const float* matrix, const float* input, float* output) noexcept { return overload_set, float_relu_matrix_vector_product_x8_x1>::f( matrix, input, output); } -template -inline void relu_matrix_vector_product(const std::int16_t* matrix, const std::int16_t* input, std::int32_t* output) { +template +inline void relu_matrix_vector_product(const std::int16_t* matrix, const std::int16_t* input, std::int32_t* output) noexcept { return overload_set>::f(matrix, input, output); } diff --git a/include/nnue/sparse_affine_layer.h b/include/nnue/sparse_affine_layer.h new file mode 100644 index 0000000..1c8a8f6 --- /dev/null +++ b/include/nnue/sparse_affine_layer.h @@ -0,0 +1,113 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include + +#include + +namespace nnue { + +template +struct sparse_affine_layer { + static constexpr std::size_t W_numel = dim0 * dim1; + static constexpr std::size_t b_numel = dim1; + + T* W{nullptr}; + alignas(simd::alignment) T b[b_numel]; + + [[nodiscard]] constexpr std::size_t num_parameters() const { return W_numel + b_numel; } + + void insert_idx(const std::size_t idx, aligned_slice x) const { + const T* mem_region = W + idx * dim1; + simd::add(x.data, mem_region); + } + + void erase_idx(const std::size_t idx, aligned_slice x) const { + const T* mem_region = W + idx * dim1; + simd::sub(x.data, mem_region); + } + + void insert_erase_idx( + const std::size_t insert_idx, const std::size_t erase_idx, const aligned_slice& src, aligned_slice dst) const { + const T* insert_mem_region = W + insert_idx * dim1; + const T* erase_mem_region = W + erase_idx * dim1; + simd::add_add_sub(src.data, insert_mem_region, erase_mem_region, dst.data); + } + + void insert_erase_erase_idx( + const std::size_t insert_idx, + const std::size_t erase_idx_0, + const std::size_t erase_idx_1, + const aligned_slice& src, + aligned_slice dst) const { + const T* insert_mem_region = W + insert_idx * dim1; + const T* erase_mem_region_0 = W + erase_idx_0 * dim1; + const T* erase_mem_region_1 = W + erase_idx_1 * dim1; + simd::add_add_sub_sub(src.data, insert_mem_region, erase_mem_region_0, erase_mem_region_1, dst.data); + } + + template + sparse_affine_layer& load_(streamer_type& ws) { + ws.template stream(W, W_numel).template stream(b, b_numel); + return *this; + } + + template + sparse_affine_layer quantized(const T& scale) const { + static_assert(std::is_floating_point_v && std::is_integral_v); + sparse_affine_layer result{}; +#pragma omp simd + for (std::size_t i = 0; i < W_numel; ++i) { result.W[i] = static_cast(std::round(scale * W[i])); } + for (std::size_t i = 0; i < b_numel; ++i) { result.b[i] = static_cast(std::round(scale * b[i])); } + return result; + } + + sparse_affine_layer& operator=(const sparse_affine_layer& other) { +#pragma omp simd + for (std::size_t i = 0; i < W_numel; ++i) { W[i] = other.W[i]; } + for (std::size_t i = 0; i < b_numel; ++i) { b[i] = other.b[i]; } + return *this; + } + + sparse_affine_layer& operator=(sparse_affine_layer&& other) noexcept { + std::swap(W, other.W); + std::swap(b, other.b); + return *this; + } + + sparse_affine_layer(const sparse_affine_layer& other) { + W = static_cast(simd::aligned_alloc(simd::alignment, sizeof(T) * W_numel)); +#pragma omp simd + for (std::size_t i = 0; i < W_numel; ++i) { W[i] = other.W[i]; } + for (std::size_t i = 0; i < b_numel; ++i) { b[i] = other.b[i]; } + } + + sparse_affine_layer(sparse_affine_layer&& other) noexcept { + std::swap(W, other.W); + std::swap(b, other.b); + } + + sparse_affine_layer() { W = static_cast(simd::aligned_alloc(simd::alignment, sizeof(T) * W_numel)); } + ~sparse_affine_layer() { + if (W != nullptr) { simd::aligned_free(W); } + } +}; + +} // namespace nnue \ No newline at end of file diff --git a/include/nnue/weights.h b/include/nnue/weights.h new file mode 100644 index 0000000..b5db8f1 --- /dev/null +++ b/include/nnue/weights.h @@ -0,0 +1,86 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See + the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, + see . +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace nnue { + +struct weights { + using parameter_type = float; + using quantized_parameter_type = std::int16_t; + + static constexpr std::size_t base_dim = 768; + + static constexpr parameter_type shared_quantization_scale = static_cast(512); + static constexpr parameter_type fc0_weight_quantization_scale = static_cast(1024); + static constexpr parameter_type fc0_bias_quantization_scale = shared_quantization_scale * fc0_weight_quantization_scale; + static constexpr parameter_type dequantization_scale = static_cast(1) / (shared_quantization_scale * fc0_weight_quantization_scale); + + weights_streamer::signature_type signature_{0}; + + sparse_affine_layer shared{}; + sparse_affine_layer quantized_shared{}; + + dense_relu_affine_layer fc0{}; + dense_relu_affine_layer white_quantized_fc0{}; + dense_relu_affine_layer black_quantized_fc0{}; + + dense_relu_affine_layer fc1{}; + dense_relu_affine_layer fc2{}; + dense_relu_affine_layer fc3{}; + + [[nodiscard]] constexpr const weights_streamer::signature_type& signature() const noexcept { return signature_; } + + [[nodiscard]] constexpr std::size_t num_parameters() const noexcept { + return shared.num_parameters() + fc0.num_parameters() + fc1.num_parameters() + fc2.num_parameters() + fc3.num_parameters(); + } + + template + [[maybe_unused]] weights& load(streamer_type& ws) noexcept { + shared.load_(ws); + fc0.load_(ws); + fc1.load_(ws); + fc2.load_(ws); + fc3.load_(ws); + signature_ = ws.signature(); + + quantized_shared = shared.quantized(shared_quantization_scale); + white_quantized_fc0 = fc0.quantized(fc0_weight_quantization_scale, fc0_bias_quantization_scale); + black_quantized_fc0 = white_quantized_fc0.half_input_flipped(); + + return *this; + } + + [[maybe_unused]] weights& load(const std::string& path) noexcept { + auto ws = weights_streamer(path); + return load(ws); + } +}; + +} // namespace nnue \ No newline at end of file diff --git a/include/weights_streamer.h b/include/nnue/weights_streamer.h similarity index 66% rename from include/weights_streamer.h rename to include/nnue/weights_streamer.h index 02249c0..1d168a7 100644 --- a/include/weights_streamer.h +++ b/include/nnue/weights_streamer.h @@ -1,6 +1,6 @@ /* Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle Seer is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -32,11 +32,11 @@ struct weights_streamer { signature_type signature_{0}; std::fstream reader; - template - weights_streamer& stream(T* dst, const size_t request) { - constexpr size_t signature_bytes = std::min(sizeof(signature_type), sizeof(T)); + template + [[maybe_unused]] weights_streamer& stream(T* dst, const std::size_t request) noexcept { + constexpr std::size_t signature_bytes = std::min(sizeof(signature_type), sizeof(T)); std::array single_element{}; - for (size_t i(0); i < request; ++i) { + for (std::size_t i(0); i < request; ++i) { reader.read(single_element.data(), single_element.size()); std::memcpy(dst + i, single_element.data(), single_element.size()); @@ -47,9 +47,9 @@ struct weights_streamer { return *this; } - signature_type signature() const { return signature_; } + [[nodiscard]] constexpr const signature_type& signature() const noexcept { return signature_; } - weights_streamer(const std::string& name) : reader(name, std::ios_base::in | std::ios_base::binary) {} + explicit weights_streamer(const std::string& name) noexcept : reader(name, std::ios_base::in | std::ios_base::binary) {} }; struct embedded_weight_streamer { @@ -59,11 +59,11 @@ struct embedded_weight_streamer { signature_type signature_{0}; const unsigned char* back_ptr; - template - embedded_weight_streamer& stream(T* dst, const size_t request) { - constexpr size_t signature_bytes = std::min(sizeof(signature_type), sizeof(T)); + template + [[maybe_unused]] embedded_weight_streamer& stream(T* dst, const std::size_t request) noexcept { + constexpr std::size_t signature_bytes = std::min(sizeof(signature_type), sizeof(T)); std::array single_element{}; - for (size_t i(0); i < request; ++i) { + for (std::size_t i(0); i < request; ++i) { std::memcpy(single_element.data(), back_ptr, single_element.size()); back_ptr += single_element.size(); std::memcpy(dst + i, single_element.data(), single_element.size()); @@ -75,9 +75,9 @@ struct embedded_weight_streamer { return *this; } - signature_type signature() const { return signature_; } + [[nodiscard]] constexpr const signature_type& signature() const noexcept { return signature_; } - embedded_weight_streamer(const unsigned char* data) : back_ptr{data} {} + explicit embedded_weight_streamer(const unsigned char* data) noexcept : back_ptr{data} {} }; } // namespace nnue diff --git a/include/nnue_model.h b/include/nnue_model.h deleted file mode 100644 index 6893897..0000000 --- a/include/nnue_model.h +++ /dev/null @@ -1,169 +0,0 @@ -/* - Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle - - Seer is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Seer is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See - the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, - see . -*/ - -#pragma once - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -namespace nnue { - -struct weights { - using parameter_type = float; - using quantized_parameter_type = std::int16_t; - - static constexpr size_t base_dim = 768; - - static constexpr parameter_type shared_quantization_scale = static_cast(512); - static constexpr parameter_type fc0_weight_quantization_scale = static_cast(1024); - static constexpr parameter_type fc0_bias_quantization_scale = shared_quantization_scale * fc0_weight_quantization_scale; - static constexpr parameter_type dequantization_scale = static_cast(1) / (shared_quantization_scale * fc0_weight_quantization_scale); - - weights_streamer::signature_type signature_{0}; - big_affine shared{}; - big_affine quantized_shared{}; - - stack_relu_affine fc0{}; - stack_relu_affine white_quantized_fc0{}; - stack_relu_affine black_quantized_fc0{}; - - stack_relu_affine fc1{}; - stack_relu_affine fc2{}; - stack_relu_affine fc3{}; - - size_t signature() const { return signature_; } - - size_t num_parameters() const { - return shared.num_parameters() + fc0.num_parameters() + fc1.num_parameters() + fc2.num_parameters() + fc3.num_parameters(); - } - - template - weights& load(streamer_type& ws) { - shared.load_(ws); - fc0.load_(ws); - fc1.load_(ws); - fc2.load_(ws); - fc3.load_(ws); - signature_ = ws.signature(); - - quantized_shared = shared.quantized(shared_quantization_scale); - white_quantized_fc0 = fc0.quantized(fc0_weight_quantization_scale, fc0_bias_quantization_scale); - black_quantized_fc0 = white_quantized_fc0.half_input_flipped(); - - return *this; - } - - weights& load(const std::string& path) { - auto ws = weights_streamer(path); - return load(ws); - } -}; - -template -struct feature_transformer { - const big_affine* weights_; - - aligned_slice parent_slice_; - aligned_slice slice_; - - void clear() { slice_.copy_from(weights_->b); } - void copy_parent() { slice_.copy_from(parent_slice_); } - void insert(const size_t& idx) { weights_->insert_idx(idx, slice_); } - void erase(const size_t& idx) { weights_->erase_idx(idx, slice_); } - - void copy_parent_insert_erase(const size_t& insert_idx, const size_t& erase_idx) { - weights_->insert_erase_idx(insert_idx, erase_idx, parent_slice_, slice_); - } - - void copy_parent_insert_erase_erase(const size_t& insert_idx, const size_t& erase_idx_0, const size_t& erase_idx_1) { - weights_->insert_erase_erase_idx(insert_idx, erase_idx_0, erase_idx_1, parent_slice_, slice_); - } - - feature_transformer( - const big_affine* src, - aligned_slice&& parent_slice, - aligned_slice&& slice) - : weights_{src}, parent_slice_{parent_slice}, slice_{slice} {} -}; - -struct eval : chess::sided> { - static constexpr size_t base_dim = weights::base_dim; - static constexpr size_t feature_transformer_dim = 2 * base_dim; - static constexpr size_t scratchpad_depth = 256; - - using parameter_type = weights::parameter_type; - using quantized_parameter_type = weights::quantized_parameter_type; - using scratchpad_type = stack_scratchpad; - - const weights* weights_; - scratchpad_type* scratchpad_; - - size_t scratchpad_idx_; - - aligned_slice parent_base_; - aligned_slice base_; - - feature_transformer white; - feature_transformer black; - - inline parameter_type propagate(const bool pov) const { - const auto x1 = (pov ? weights_->white_quantized_fc0 : weights_->black_quantized_fc0) - .forward(base_) - .dequantized(weights::dequantization_scale); - - const auto x2 = splice(x1, weights_->fc1.forward(x1)); - const auto x3 = splice(x2, weights_->fc2.forward(x2)); - return weights_->fc3.forward(x3).item(); - } - - inline search::score_type evaluate(const bool pov, const parameter_type& phase) const { - constexpr parameter_type one = static_cast(1.0); - constexpr parameter_type mg = static_cast(1.1); - constexpr parameter_type eg = static_cast(0.7); - - const parameter_type prediction = propagate(pov); - const parameter_type eval = phase * mg * prediction + (one - phase) * eg * prediction; - - const parameter_type value = - search::logit_scale * std::clamp(eval, search::min_logit, search::max_logit); - return static_cast(value); - } - - eval next_child() const { - const size_t next_scratchpad_idx = scratchpad_idx_ + 1; - return eval(weights_, scratchpad_, scratchpad_idx_, next_scratchpad_idx); - } - - eval(const weights* src, scratchpad_type* scratchpad, const size_t& parent_scratchpad_idx, const size_t& scratchpad_idx) - : weights_{src}, - scratchpad_{scratchpad}, - scratchpad_idx_{scratchpad_idx}, - parent_base_(scratchpad_->get_nth_slice(parent_scratchpad_idx)), - base_(scratchpad_->get_nth_slice(scratchpad_idx_)), - white{&src->quantized_shared, parent_base_.slice(), base_.slice()}, - black{&src->quantized_shared, parent_base_.slice(), base_.slice()} {} -}; - -} // namespace nnue diff --git a/include/nnue_util.h b/include/nnue_util.h deleted file mode 100644 index ca5d774..0000000 --- a/include/nnue_util.h +++ /dev/null @@ -1,348 +0,0 @@ -/* - Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle - - Seer is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Seer is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#pragma once - -#include - -#include -#include -#include -#include -#include -#include - -namespace nnue { - -template -struct dot_type_impl {}; - -template <> -struct dot_type_impl { - using type = float; -}; - -template <> -struct dot_type_impl { - using type = double; -}; - -template <> -struct dot_type_impl { - using type = std::int16_t; -}; - -template <> -struct dot_type_impl { - using type = std::int32_t; -}; - -template <> -struct dot_type_impl { - using type = std::int64_t; -}; - -template -using dot_type = typename dot_type_impl::type; - -template -struct aligned_slice { - T* data; - - template - aligned_slice slice() { - static_assert(offset + out_dim <= dim); - return aligned_slice{data + offset}; - } - - aligned_slice& copy_from(const T* other) { - std::memcpy(data, other, sizeof(T) * dim); - return *this; - } - - aligned_slice& copy_from(const aligned_slice& other) { - std::memcpy(data, other.data, sizeof(T) * dim); - return *this; - } - - aligned_slice(T* data) : data{data} {} -}; - -template -std::ostream& operator<<(std::ostream& ostr, const aligned_slice& vec) { - static_assert(dim != 0, "can't stream empty slice."); - ostr << "aligned_slice(["; - for (size_t i = 0; i < (dim - 1); ++i) { ostr << vec.data[i] << ", "; } - ostr << vec.data[dim - 1] << "])"; - return ostr; -} - -template -struct stack_scratchpad { - alignas(simd::alignment) T data[scratchpad_size]; - - template - aligned_slice get_nth_slice(const size_t& n) { - static_assert(scratchpad_size % dim == 0); - return aligned_slice(data + n * dim); - } -}; - -template -struct stack_vector { - alignas(simd::alignment) T data[dim]; - - template - constexpr stack_vector apply(F&& f) const { - return stack_vector{*this}.apply_(std::forward(f)); - } - - template - inline stack_vector& apply_(F&& f) { -#pragma omp simd - for (size_t i = 0; i < dim; ++i) { data[i] = f(data[i]); } - return *this; - } - - inline stack_vector& softmax_() { - static_assert(dim != 0, "can't softmax empty vector."); - T maximum_value = data[0]; - for (size_t i = 0; i < dim; ++i) { - if (data[i] > maximum_value) { maximum_value = data[i]; } - } - apply_([maximum_value](const T& x) { return std::exp(x - maximum_value); }); - const T z = sum(); - apply_([z](const T& x) { return x / z; }); - return *this; - } - - inline stack_vector& add_(const T* other) { - simd::add(data, other); - return *this; - } - - inline stack_vector& sub_(const T* other) { - simd::sub(data, other); - return *this; - } - - inline stack_vector& set_(const T* other) { -#pragma omp simd - for (size_t i = 0; i < dim; ++i) { data[i] = other[i]; } - return *this; - } - - inline aligned_slice as_slice() { return aligned_slice(data); } - - inline T item() const { - static_assert(dim == 1, "called item() on vector with dim != 1"); - return data[0]; - } - - inline T sum() const { - T result{}; -#pragma omp simd - for (size_t i = 0; i < dim; ++i) { result += data[i]; } - return result; - } - - template - inline stack_vector dequantized(const U& scale) const { - static_assert(std::is_integral_v && std::is_floating_point_v); - stack_vector result; -#pragma omp simd - for (size_t i = 0; i < dim; ++i) { result.data[i] = scale * static_cast(data[i]); } - return result; - } - - static inline stack_vector zeros() { - stack_vector result{}; -#pragma omp simd - for (size_t i = 0; i < dim; ++i) { result.data[i] = T(0); } - return result; - } - - static inline stack_vector ones() { - stack_vector result{}; -#pragma omp simd - for (size_t i = 0; i < dim; ++i) { result.data[i] = T(1); } - return result; - } - - static inline stack_vector from(const T* data) { - stack_vector result{}; -#pragma omp simd - for (size_t i = 0; i < dim; ++i) { result.data[i] = data[i]; } - return result; - } -}; - -template -std::ostream& operator<<(std::ostream& ostr, const stack_vector& vec) { - static_assert(dim != 0, "can't stream empty vector."); - ostr << "stack_vector(["; - for (size_t i = 0; i < (dim - 1); ++i) { ostr << vec.data[i] << ", "; } - ostr << vec.data[dim - 1] << "])"; - return ostr; -} - -template -inline stack_vector splice(const stack_vector& a, const stack_vector& b) { - auto c = stack_vector::zeros(); -#pragma omp simd - for (size_t i = 0; i < dim0; ++i) { c.data[i] = a.data[i]; } - for (size_t i = 0; i < dim1; ++i) { c.data[dim0 + i] = b.data[i]; } - return c; -} - -template -struct stack_relu_affine { - static constexpr size_t W_numel = dim0 * dim1; - static constexpr size_t b_numel = dim1; - - alignas(simd::alignment) T W[W_numel]; - alignas(simd::alignment) dot_type b[b_numel]; - - constexpr size_t num_parameters() const { return W_numel + b_numel; } - - inline stack_vector, dim1> forward(const stack_vector& x) const { - auto result = stack_vector, dim1>::from(b); - simd::relu_matrix_vector_product(W, x.data, result.data); - return result; - } - - inline stack_vector, dim1> forward(const aligned_slice& x) const { - auto result = stack_vector, dim1>::from(b); - simd::relu_matrix_vector_product(W, x.data, result.data); - return result; - } - - template - stack_relu_affine& load_(streamer_type& ws) { - ws.template stream(W, W_numel).template stream>(b, b_numel); - return *this; - } - - stack_relu_affine half_input_flipped() const { - static_assert(dim0 % 2 == 0); - constexpr size_t half_dim0 = dim0 / 2; - - stack_relu_affine result = *this; - for (size_t i(0); i < W_numel; i += dim0) { - for (size_t j(0); j < half_dim0; ++j) { std::iter_swap(result.W + i + j, result.W + half_dim0 + i + j); } - } - - return result; - } - - template - stack_relu_affine quantized(const T& weight_scale, const T& bias_scale) const { - static_assert(std::is_floating_point_v && std::is_integral_v); - stack_relu_affine result{}; -#pragma omp simd - for (size_t i = 0; i < W_numel; ++i) { result.W[i] = static_cast(std::round(weight_scale * W[i])); } - for (size_t i = 0; i < b_numel; ++i) { result.b[i] = static_cast>(std::round(bias_scale * b[i])); } - return result; - } -}; - -template -struct big_affine { - static constexpr size_t W_numel = dim0 * dim1; - static constexpr size_t b_numel = dim1; - - T* W{nullptr}; - alignas(simd::alignment) T b[b_numel]; - - constexpr size_t num_parameters() const { return W_numel + b_numel; } - - void insert_idx(const size_t idx, aligned_slice x) const { - const T* mem_region = W + idx * dim1; - simd::add(x.data, mem_region); - } - - void erase_idx(const size_t idx, aligned_slice x) const { - const T* mem_region = W + idx * dim1; - simd::sub(x.data, mem_region); - } - - void insert_erase_idx(const size_t insert_idx, const size_t erase_idx, const aligned_slice& src, aligned_slice dst) const { - const T* insert_mem_region = W + insert_idx * dim1; - const T* erase_mem_region = W + erase_idx * dim1; - simd::add_add_sub(src.data, insert_mem_region, erase_mem_region, dst.data); - } - - void insert_erase_erase_idx( - const size_t insert_idx, - const size_t erase_idx_0, - const size_t erase_idx_1, - const aligned_slice& src, - aligned_slice dst) const { - const T* insert_mem_region = W + insert_idx * dim1; - const T* erase_mem_region_0 = W + erase_idx_0 * dim1; - const T* erase_mem_region_1 = W + erase_idx_1 * dim1; - simd::add_add_sub_sub(src.data, insert_mem_region, erase_mem_region_0, erase_mem_region_1, dst.data); - } - - template - big_affine& load_(streamer_type& ws) { - ws.template stream(W, W_numel).template stream(b, b_numel); - return *this; - } - - template - big_affine quantized(const T& scale) const { - static_assert(std::is_floating_point_v && std::is_integral_v); - big_affine result{}; -#pragma omp simd - for (size_t i = 0; i < W_numel; ++i) { result.W[i] = static_cast(std::round(scale * W[i])); } - for (size_t i = 0; i < b_numel; ++i) { result.b[i] = static_cast(std::round(scale * b[i])); } - return result; - } - - big_affine& operator=(const big_affine& other) { -#pragma omp simd - for (size_t i = 0; i < W_numel; ++i) { W[i] = other.W[i]; } - for (size_t i = 0; i < b_numel; ++i) { b[i] = other.b[i]; } - return *this; - } - - big_affine& operator=(big_affine&& other) { - std::swap(W, other.W); - std::swap(b, other.b); - return *this; - } - - big_affine(const big_affine& other) { - W = static_cast(simd::aligned_alloc(simd::alignment, sizeof(T) * W_numel)); -#pragma omp simd - for (size_t i = 0; i < W_numel; ++i) { W[i] = other.W[i]; } - for (size_t i = 0; i < b_numel; ++i) { b[i] = other.b[i]; } - } - - big_affine(big_affine&& other) { - std::swap(W, other.W); - std::swap(b, other.b); - } - - big_affine() { W = static_cast(simd::aligned_alloc(simd::alignment, sizeof(T) * W_numel)); } - ~big_affine() { - if (W != nullptr) { simd::aligned_free(W); } - } -}; - -} // namespace nnue diff --git a/include/option_parser.h b/include/option_parser.h deleted file mode 100644 index 0f57077..0000000 --- a/include/option_parser.h +++ /dev/null @@ -1,169 +0,0 @@ -/* - Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle - - Seer is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Seer is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#pragma once - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace engine { - -struct string_option { - using type = std::string; - std::string name_; - std::optional default_ = {}; - - std::optional maybe_read(const std::string& cmd) const { - std::regex regex("setoption name " + name_ + " value (.*)"); - if (std::smatch matches{}; std::regex_search(cmd, matches, regex)) { - std::string match = matches.str(1); - return match.empty() ? default_ : match; - } - return std::nullopt; - } - - string_option(const std::string_view& name) : name_{name} {} - string_option(const std::string_view& name, const std::string& def) : name_{name}, default_{def} {} -}; - -struct spin_range { - int min, max; - - int clamp(const int& x) const { return std::clamp(x, min, max); } - - spin_range(const int& a, const int& b) : min{a}, max{b} {} -}; - -struct spin_option { - using type = int; - std::string name_; - std::optional default_ = {}; - std::optional range_ = {}; - - std::optional maybe_read(const std::string& cmd) const { - std::regex regex("setoption name " + name_ + " value (-?[0-9]+)"); - if (auto matches = std::smatch{}; std::regex_search(cmd, matches, regex)) { - const int raw = std::stoi(matches.str(1)); - return range_.has_value() ? range_.value().clamp(raw) : raw; - } - return std::nullopt; - } - - spin_option(const std::string_view& name) : name_{name} {} - spin_option(const std::string& name, const spin_range& range) : name_{name}, range_{range} {} - spin_option(const std::string& name, const int& def) : name_{name}, default_{def} {} - spin_option(const std::string& name, const int& def, const spin_range& range) : name_{name}, default_{def}, range_{range} {} -}; - -struct button_option { - using type = bool; - std::string name_; - - std::optional maybe_read(const std::string& cmd) const { - return (cmd == (std::string("setoption name ") + name_)) ? std::optional(true) : std::nullopt; - } - - button_option(const std::string_view& name) : name_{name} {} -}; - -struct check_option { - using type = bool; - std::string name_; - std::optional default_{std::nullopt}; - - std::optional maybe_read(const std::string& cmd) const { - std::regex regex("setoption name " + name_ + " value (true|false)"); - if (auto matches = std::smatch{}; std::regex_search(cmd, matches, regex)) { return "true" == matches.str(1); } - return std::nullopt; - } - - check_option(const std::string_view& name) : name_{name} {} - - check_option(const std::string_view& name, const bool& def) : name_{name}, default_{def} {} -}; - -std::ostream& operator<<(std::ostream& ostr, const string_option& opt) { - ostr << "option name " << opt.name_ << " type string"; - if (opt.default_.has_value()) { ostr << " default " << opt.default_.value(); } - return ostr; -} - -std::ostream& operator<<(std::ostream& ostr, const spin_option& opt) { - ostr << "option name " << opt.name_ << " type spin"; - if (opt.default_.has_value()) { ostr << " default " << opt.default_.value(); } - if (opt.range_.has_value()) { ostr << " min " << opt.range_.value().min << " max " << opt.range_.value().max; } - return ostr; -} - -std::ostream& operator<<(std::ostream& ostr, const button_option& opt) { - ostr << "option name " << opt.name_ << " type button"; - return ostr; -} - -std::ostream& operator<<(std::ostream& ostr, const check_option& opt) { - ostr << "option name " << opt.name_ << " type check"; - if (opt.default_.has_value()) { ostr << std::boolalpha << " default " << opt.default_.value(); } - return ostr; -} - -template -inline constexpr bool is_option_v = - std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v; - -template -struct option_callback { - static_assert(is_option_v, "T must be of option type"); - - T option_; - std::function callback_; - - void maybe_call(const std::string& cmd) { - std::optional read = option_.maybe_read(cmd); - if (read.has_value()) { callback_(read.value()); } - } - - template - option_callback(const T& option, F&& f) : option_{option}, callback_{f} {} -}; - -template -struct uci_options { - std::tuple...> options_; - - void update(const std::string& cmd) { - util::apply(options_, [cmd](auto& opt) { opt.maybe_call(cmd); }); - } - - uci_options(const option_callback&... options) : options_{options...} {} -}; - -template -std::ostream& operator<<(std::ostream& ostr, uci_options options) { - util::apply(options.options_, [&ostr](const auto& opt) { ostr << opt.option_ << std::endl; }); - return ostr; -} - -} // namespace engine diff --git a/include/position_history.h b/include/position_history.h deleted file mode 100644 index 50207d9..0000000 --- a/include/position_history.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle - - Seer is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Seer is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#pragma once - -#include -#include - -#include - -namespace chess { - -template -struct popper { - T* data_; - popper(T* data) : data_{data} {} - ~popper() { data_->pop_(); } -}; - -template -struct base_history { - using stored_type = U; - std::vector history_; - - T& cast() { return static_cast(*this); } - const T& cast() const { return static_cast(*this); } - - T& clear() { - history_.clear(); - return cast(); - } - - popper scoped_push_(const stored_type& elem) { - history_.push_back(elem); - return popper(&cast()); - } - - T& push_(const stored_type& elem) { - history_.push_back(elem); - return cast(); - } - - T& pop_() { - history_.pop_back(); - return cast(); - } - - stored_type back() const { return history_.back(); } - - size_t len() const { return history_.size(); } - - base_history() : history_{} {} - base_history(const std::vector& h) : history_{h} {} -}; - -struct position_history : base_history { - size_t occurrences(const zobrist::hash_type& hash) const { - size_t occurrences_{0}; - for (auto it = history_.crbegin(); it != history_.crend(); ++it) { occurrences_ += static_cast(*it == hash); } - return occurrences_; - } - - bool is_two_fold(const zobrist::hash_type& hash) const { return occurrences(hash) >= 1; } -}; - -} // namespace chess diff --git a/include/eval_cache.h b/include/search/eval_cache.h similarity index 60% rename from include/eval_cache.h rename to include/search/eval_cache.h index 34f2262..9b8e9bf 100644 --- a/include/eval_cache.h +++ b/include/search/eval_cache.h @@ -1,6 +1,6 @@ /* Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle Seer is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,8 +17,8 @@ #pragma once -#include -#include +#include +#include #include #include @@ -31,24 +31,25 @@ struct eval_cache_entry { }; struct eval_cache { - static constexpr size_t size_mb = 8; - static constexpr size_t N = (size_mb << 20) / sizeof(eval_cache_entry); + static constexpr std::size_t size_mb = 8; + static constexpr std::size_t N = (size_mb << 20) / sizeof(eval_cache_entry); static_assert((N != 0) && ((N & (N - 1)) == 0), "N must be a power of 2"); std::array data{}; - constexpr size_t hash_function(const zobrist::hash_type& hash) const { return hash & (N - 1); } + [[nodiscard]] static constexpr std::size_t hash_function(const zobrist::hash_type& hash) noexcept { return hash & (N - 1); } + inline void prefetch(const zobrist::hash_type& hash) const noexcept { __builtin_prefetch(data.data() + hash_function(hash)); } - void prefetch(const zobrist::hash_type& hash) const { __builtin_prefetch(data.data() + hash_function(hash)); } - - std::optional find(const zobrist::hash_type& hash) const { + [[nodiscard]] constexpr std::optional find(const zobrist::hash_type& hash) const noexcept { if (data[hash_function(hash)].hash == zobrist::upper_half(hash)) { return data[hash_function(hash)].eval; } return std::nullopt; } - void insert(const zobrist::hash_type& hash, const score_type& eval) { data[hash_function(hash)] = eval_cache_entry{zobrist::upper_half(hash), eval}; } + void insert(const zobrist::hash_type& hash, const score_type& eval) noexcept { + data[hash_function(hash)] = eval_cache_entry{zobrist::upper_half(hash), eval}; + } - void clear() { return data.fill(eval_cache_entry{}); } + void clear() noexcept { return data.fill(eval_cache_entry{}); } }; -} // namespace chess \ No newline at end of file +} // namespace search \ No newline at end of file diff --git a/include/search/history_heuristic.h b/include/search/history_heuristic.h new file mode 100644 index 0000000..7c135aa --- /dev/null +++ b/include/search/history_heuristic.h @@ -0,0 +1,193 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include + +namespace search { + +namespace history { + +using value_type = search::counter_type; + +namespace constants { + +inline constexpr std::size_t num_squares = 64; +inline constexpr std::size_t num_pieces = 6; + +}; // namespace constants + +struct context { + chess::move follow; + chess::move counter; + chess::square_set threatened; +}; + +[[nodiscard]] inline value_type formula(const value_type& x, const value_type& gain) noexcept { + constexpr value_type history_multiplier = 32; + constexpr value_type history_divisor = 512; + return (gain * history_multiplier) - (x * std::abs(gain) / history_divisor); +} + +struct butterfly_info { + static constexpr std::size_t N = constants::num_squares * constants::num_squares; + + [[nodiscard]] static constexpr bool is_applicable(const context&, const chess::move& mv) noexcept { return mv.is_quiet(); } + + [[nodiscard]] static constexpr std::size_t compute_index(const context&, const chess::move& mv) noexcept { + const auto from = static_cast(mv.from().index()); + const auto to = static_cast(mv.to().index()); + return from * constants::num_squares + to; + } +}; + +struct threatened_info { + static constexpr std::size_t N = constants::num_squares * constants::num_squares; + + [[nodiscard]] static constexpr bool is_applicable(const context& ctxt, const chess::move& mv) noexcept { + return ctxt.threatened.is_member(mv.from()) && mv.is_quiet(); + } + + [[nodiscard]] static constexpr std::size_t compute_index(const context&, const chess::move& mv) noexcept { + const auto from = static_cast(mv.from().index()); + const auto to = static_cast(mv.to().index()); + return from * constants::num_squares + to; + } +}; + +struct counter_info { + static constexpr std::size_t N = constants::num_squares * constants::num_pieces * constants::num_squares * constants::num_pieces; + + [[nodiscard]] static constexpr bool is_applicable(const context& ctxt, const chess::move& mv) noexcept { + return !ctxt.counter.is_null() && mv.is_quiet(); + } + + [[nodiscard]] static constexpr std::size_t compute_index(const context& ctxt, const chess::move& mv) noexcept { + const auto p0 = static_cast(ctxt.counter.piece()); + const auto to0 = static_cast(ctxt.counter.to().index()); + const auto p1 = static_cast(mv.piece()); + const auto to1 = static_cast(mv.to().index()); + return p0 * constants::num_squares * constants::num_pieces * constants::num_squares + to0 * constants::num_pieces * constants::num_squares + + p1 * constants::num_squares + to1; + } +}; + +struct follow_info { + static constexpr std::size_t N = constants::num_squares * constants::num_pieces * constants::num_squares * constants::num_pieces; + + [[nodiscard]] static constexpr bool is_applicable(const context& ctxt, const chess::move& mv) noexcept { + return !ctxt.follow.is_null() && mv.is_quiet(); + } + + [[nodiscard]] static constexpr std::size_t compute_index(const context& ctxt, const chess::move& mv) noexcept { + const auto p0 = static_cast(ctxt.follow.piece()); + const auto to0 = static_cast(ctxt.follow.to().index()); + const auto p1 = static_cast(mv.piece()); + const auto to1 = static_cast(mv.to().index()); + return p0 * constants::num_squares * constants::num_pieces * constants::num_squares + to0 * constants::num_pieces * constants::num_squares + + p1 * constants::num_squares + to1; + } +}; + +struct capture_info { + static constexpr std::size_t N = constants::num_squares * constants::num_pieces * constants::num_pieces; + + [[nodiscard]] static constexpr bool is_applicable(const context&, const chess::move& mv) noexcept { return mv.is_capture(); } + + [[nodiscard]] static constexpr std::size_t compute_index(const context&, const chess::move& mv) noexcept { + const auto piece = static_cast(mv.piece()); + const auto to = static_cast(mv.to().index()); + const auto capture = static_cast(mv.captured()); + return piece * constants::num_squares * constants::num_pieces + to * constants::num_pieces + capture; + } +}; + +template +struct table { + std::array data_{}; + + [[nodiscard]] constexpr bool is_applicable(const context& ctxt, const chess::move& mv) const noexcept { return T::is_applicable(ctxt, mv); } + + [[nodiscard]] constexpr const value_type& at(const context& ctxt, const chess::move& mv) const noexcept { + return data_[T::compute_index(ctxt, mv)]; + } + [[nodiscard]] constexpr value_type& at(const context& ctxt, const chess::move& mv) noexcept { return data_[T::compute_index(ctxt, mv)]; } + + constexpr void clear() noexcept { data_.fill(value_type{}); } +}; + +template +struct combined { + std::tuple...> tables_{}; + + [[maybe_unused]] constexpr combined& update( + const context& ctxt, const chess::move& best_move, const chess::move_list& tried, const depth_type& depth) noexcept { + constexpr value_type history_max = 400; + + auto single_update = [&, this](const auto& mv, const value_type& gain) { + const value_type value = compute_value(ctxt, mv); + util::tuple::for_each(tables_, [=](auto& tbl) { + if (tbl.is_applicable(ctxt, mv)) { tbl.at(ctxt, mv) += formula(value, gain); } + }); + }; + + const value_type gain = std::min(history_max, depth * depth); + std::for_each(tried.begin(), tried.end(), [single_update, gain](const chess::move& mv) { single_update(mv, -gain); }); + single_update(best_move, gain); + + return *this; + } + + constexpr void clear() noexcept { + util::tuple::for_each(tables_, [](auto& tbl) { tbl.clear(); }); + } + + [[nodiscard]] constexpr value_type compute_value(const context& ctxt, const chess::move& mv) const noexcept { + value_type result{}; + util::tuple::for_each(tables_, [&](const auto& tbl) { + if (tbl.is_applicable(ctxt, mv)) { result += tbl.at(ctxt, mv); } + }); + return result; + } +}; + +} // namespace history + +using history_heuristic = + history::combined; + +struct sided_history_heuristic : public chess::sided { + history_heuristic white; + history_heuristic black; + + constexpr void clear() noexcept { + white.clear(); + black.clear(); + } + + sided_history_heuristic() noexcept : white{}, black{} {} +}; + +} // namespace search diff --git a/include/search/move_orderer.h b/include/search/move_orderer.h new file mode 100644 index 0000000..17ca2fe --- /dev/null +++ b/include/search/move_orderer.h @@ -0,0 +1,179 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace search { + +[[nodiscard]] constexpr std::uint32_t make_positive(const std::int32_t& x) noexcept { + constexpr std::uint32_t upper = static_cast(1) + std::numeric_limits::max(); + return upper + x; +} + +struct move_orderer_data { + chess::move killer{chess::move::null()}; + chess::move follow{chess::move::null()}; + chess::move counter{chess::move::null()}; + chess::move first{chess::move::null()}; + + chess::square_set threatened{}; + + const chess::board* bd; + const history_heuristic* hh; + + [[maybe_unused]] constexpr move_orderer_data& set_killer(const chess::move& mv) noexcept { + killer = mv; + return *this; + } + + [[maybe_unused]] constexpr move_orderer_data& set_follow(const chess::move& mv) noexcept { + follow = mv; + return *this; + } + + [[maybe_unused]] constexpr move_orderer_data& set_counter(const chess::move& mv) noexcept { + counter = mv; + return *this; + } + + [[maybe_unused]] constexpr move_orderer_data& set_first(const chess::move& mv) noexcept { + first = mv; + return *this; + } + + [[maybe_unused]] constexpr move_orderer_data& set_threatened(const chess::square_set& mask) noexcept { + threatened = mask; + return *this; + } + + constexpr move_orderer_data(const chess::board* bd_, const history_heuristic* hh_) noexcept : bd{bd_}, hh{hh_} {} +}; + +struct move_orderer_entry { + using value_ = util::bit_range; + using killer_ = util::next_bit_flag; + using positive_noisy_ = util::next_bit_flag; + using first_ = util::next_bit_flag; + + chess::move mv; + std::uint64_t data_; + + const std::uint64_t& sort_key() const { return data_; } + + move_orderer_entry() = default; + + constexpr move_orderer_entry(const chess::move& mv_, bool is_positive_noisy, bool is_killer, std::int32_t value) noexcept : mv{mv_}, data_{0} { + positive_noisy_::set(data_, is_positive_noisy); + killer_::set(data_, is_killer); + value_::set(data_, make_positive(value)); + } + + [[nodiscard]] static constexpr move_orderer_entry make_noisy(const chess::move& mv, const bool positive_noisy, const std::int32_t& history_value) { + return move_orderer_entry(mv, positive_noisy, false, positive_noisy ? mv.mvv_lva_key() : history_value); + } + + [[nodiscard]] static constexpr move_orderer_entry make_quiet(const chess::move& mv, const chess::move& killer, const std::int32_t& history_value) { + return move_orderer_entry(mv, false, mv == killer, history_value); + } +}; + +struct move_orderer_stepper { + using entry_array_type = std::array; + + bool is_initialized_{false}; + + entry_array_type entries_; + entry_array_type::iterator begin_; + entry_array_type::iterator end_; + + [[nodiscard]] constexpr bool is_initialized() const noexcept { return is_initialized_; } + [[nodiscard]] constexpr bool has_next() const noexcept { return begin_ != end_; } + [[nodiscard]] constexpr chess::move current_move() const noexcept { return begin_->mv; } + + [[maybe_unused]] move_orderer_stepper& initialize(const move_orderer_data& data, const chess::move_list& list) noexcept; + inline void update_list_() const noexcept; + void next() noexcept; + + move_orderer_stepper() noexcept : begin_{entries_.begin()} {} + + move_orderer_stepper& operator=(const move_orderer_stepper& other) = delete; + move_orderer_stepper& operator=(move_orderer_stepper&& other) = delete; + move_orderer_stepper(const move_orderer_stepper& other) = delete; + move_orderer_stepper(move_orderer_stepper&& other) = delete; +}; + +struct move_orderer_iterator_end_tag {}; + +template +struct move_orderer_iterator { + using difference_type = std::ptrdiff_t; + using value_type = std::tuple; + using pointer = std::tuple*; + using reference = std::tuple&; + using iterator_category = std::input_iterator_tag; + + int idx{}; + move_orderer_stepper stepper_; + move_orderer_data data_; + + [[nodiscard]] std::tuple operator*() const noexcept; + [[maybe_unused]] move_orderer_iterator& operator++() noexcept; + + [[nodiscard]] constexpr bool operator==(const move_orderer_iterator&) const noexcept { return false; } + + [[nodiscard]] constexpr bool operator==(const move_orderer_iterator_end_tag&) const noexcept { + return stepper_.is_initialized() && !stepper_.has_next(); + } + + template + [[nodiscard]] constexpr bool operator!=(const T& other) const noexcept { + return !(*this == other); + } + + move_orderer_iterator(const move_orderer_data& data) noexcept; +}; + +template +struct move_orderer { + using iterator = move_orderer_iterator; + + move_orderer_data data_; + + [[nodiscard]] move_orderer_iterator begin() const noexcept { return move_orderer_iterator(data_); } + [[nodiscard]] move_orderer_iterator_end_tag end() const noexcept { return move_orderer_iterator_end_tag(); } + + [[maybe_unused]] move_orderer& set_first(const chess::move& mv) noexcept { + data_.set_first(mv); + return *this; + } + + move_orderer(const move_orderer_data& data) noexcept : data_{data} {} +}; + +} // namespace search diff --git a/include/search/search_constants.h b/include/search/search_constants.h new file mode 100644 index 0000000..ee8f6d8 --- /dev/null +++ b/include/search/search_constants.h @@ -0,0 +1,169 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include +#include +#include +#include + +namespace search { + +template +inline constexpr T max_logit = static_cast(8); + +template +inline constexpr T min_logit = static_cast(-8); + +template +inline constexpr T logit_scale = static_cast(1024); + +template +inline constexpr T wdl_scale = static_cast(1024); + +using depth_type = std::int32_t; + +inline constexpr depth_type max_depth = 128; + +inline constexpr depth_type max_depth_margin = 8; + +constexpr depth_type safe_depth = max_depth + max_depth_margin; + +using score_type = std::int32_t; + +using wdl_type = std::tuple; + +inline constexpr score_type big_number = 8 * logit_scale; + +inline constexpr score_type max_mate_score = -2 * big_number; + +inline constexpr score_type mate_score = max_mate_score - (max_depth + max_depth_margin); + +inline constexpr score_type tb_win_score = big_number + 1; + +inline constexpr score_type tb_loss_score = -tb_win_score; + +inline constexpr score_type draw_score = 0; + +inline constexpr score_type aspiration_delta = 20; + +using counter_type = std::int32_t; + +using see_type = std::int32_t; + +inline constexpr std::size_t nodes_per_update = 512; + +struct fixed_search_constants { + static constexpr bool tuning = false; + static constexpr depth_type lmr_tbl_dim = 64; + std::size_t thread_count_; + std::array lmr_tbl{}; + + [[nodiscard]] const std::size_t& thread_count() const noexcept { return thread_count_; } + + [[nodiscard]] constexpr depth_type reduce_depth() const noexcept { return 3; } + [[nodiscard]] constexpr depth_type aspiration_depth() const noexcept { return 4; } + [[nodiscard]] constexpr depth_type nmp_depth() const noexcept { return 2; } + [[nodiscard]] constexpr depth_type lmp_depth() const noexcept { return 7; } + [[nodiscard]] constexpr depth_type snmp_depth() const noexcept { return 7; } + [[nodiscard]] constexpr depth_type futility_prune_depth() const noexcept { return 6; } + [[nodiscard]] constexpr depth_type quiet_see_prune_depth() const noexcept { return 8; } + [[nodiscard]] constexpr depth_type noisy_see_prune_depth() const noexcept { return 6; } + [[nodiscard]] constexpr depth_type singular_extension_depth() const noexcept { return 6; } + [[nodiscard]] constexpr depth_type probcut_depth() const noexcept { return 5; } + [[nodiscard]] constexpr depth_type iir_depth() const noexcept { return 4; } + + [[nodiscard]] constexpr depth_type reduction(const depth_type& depth, const int& move_idx) const noexcept { + constexpr depth_type last_idx = lmr_tbl_dim - 1; + return lmr_tbl[std::min(last_idx, depth) * lmr_tbl_dim + std::min(last_idx, move_idx)]; + } + + [[nodiscard]] constexpr depth_type nmp_reduction(const depth_type& depth, const score_type& beta, const score_type& value) const noexcept { + return 4 + depth / 6 + std::min(3, (value - beta) / 256); + } + + [[nodiscard]] constexpr see_type nmp_see_threshold() const noexcept { return 200; } + + [[nodiscard]] constexpr depth_type singular_extension_depth_margin() const noexcept { return 3; } + + [[nodiscard]] constexpr depth_type singular_search_depth(const depth_type& depth) const noexcept { return depth / 2 - 1; } + + [[nodiscard]] constexpr score_type singular_beta(const score_type& tt_score, const depth_type& depth) const noexcept { + return tt_score - 2 * static_cast(depth); + } + + [[nodiscard]] constexpr score_type singular_double_extension_margin() const noexcept { return 160; } + + [[nodiscard]] constexpr score_type futility_margin(const depth_type& depth) const noexcept { + constexpr score_type m = 1536; + return m * static_cast(depth); + } + + [[nodiscard]] constexpr score_type snmp_margin(const bool& improving, const bool& threats, const depth_type& depth) const noexcept { + constexpr score_type m = 288; + constexpr score_type b = 128; + return m * static_cast(depth - (improving && !threats)) + (threats ? b : 0); + } + + [[nodiscard]] constexpr int lmp_count(const bool& improving, const depth_type& depth) const noexcept { + constexpr std::array improving_counts = {0, 5, 8, 12, 20, 30, 42, 65}; + constexpr std::array worsening_counts = {0, 3, 4, 8, 10, 13, 21, 31}; + return improving ? improving_counts[depth] : worsening_counts[depth]; + } + + [[nodiscard]] constexpr see_type quiet_see_prune_threshold(const depth_type& depth) const noexcept { return -50 * static_cast(depth); } + [[nodiscard]] constexpr see_type noisy_see_prune_threshold(const depth_type& depth) const noexcept { return -100 * static_cast(depth); } + + [[nodiscard]] constexpr counter_type history_prune_threshold(const depth_type& depth) const noexcept { + return -1024 * static_cast(depth * depth); + } + + [[nodiscard]] constexpr depth_type history_reduction(const counter_type& history_value) const noexcept { + constexpr depth_type limit = 2; + const depth_type raw = -static_cast(history_value / 5000); + return std::clamp(raw, -limit, limit); + } + + [[nodiscard]] constexpr score_type delta_margin() const noexcept { + constexpr score_type margin = 512; + return margin; + } + + [[nodiscard]] constexpr see_type good_capture_prune_see_margin() const noexcept { return 300; } + [[nodiscard]] constexpr score_type good_capture_prune_score_margin() const noexcept { return 256; } + + [[nodiscard]] constexpr depth_type probcut_search_depth(const depth_type& depth) const noexcept { return depth - 3; } + [[nodiscard]] constexpr score_type probcut_beta(const score_type& beta) const noexcept { return beta + 320; } + + [[maybe_unused]] fixed_search_constants& update_(const std::size_t& thread_count) noexcept { + thread_count_ = thread_count; + for (depth_type depth{1}; depth < lmr_tbl_dim; ++depth) { + for (depth_type played{1}; played < lmr_tbl_dim; ++played) { + lmr_tbl[depth * lmr_tbl_dim + played] = static_cast(0.75 + std::log(depth) * std::log(played) / 2.25); + } + } + return *this; + } + + explicit fixed_search_constants(const std::size_t& thread_count = 1) noexcept { update_(thread_count); } +}; + +using search_constants = fixed_search_constants; + +} // namespace search diff --git a/include/search/search_stack.h b/include/search/search_stack.h new file mode 100644 index 0000000..652a9d3 --- /dev/null +++ b/include/search/search_stack.h @@ -0,0 +1,149 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace search { + +struct stack_entry { + zobrist::hash_type hash_{}; + score_type eval_{}; + + chess::move played_{chess::move::null()}; + chess::move killer_{chess::move::null()}; + chess::move excluded_{chess::move::null()}; + + std::array pv_{}; + + stack_entry() noexcept { pv_.fill(chess::move::null()); } +}; + +struct search_stack { + depth_type selective_depth_{0}; + + chess::board_history past_; + chess::board present_; + std::array future_{}; + + [[nodiscard]] constexpr depth_type selective_depth() const noexcept { return selective_depth_; } + [[nodiscard]] constexpr const chess::board& root() const noexcept { return present_; } + + [[nodiscard]] constexpr stack_entry& at(const depth_type& height) noexcept { return future_[height]; } + + [[maybe_unused]] constexpr search_stack& update_selective_depth(const depth_type& height) noexcept { + selective_depth_ = std::max(selective_depth_, height); + return *this; + } + + [[nodiscard]] std::size_t count(const std::size_t& height, const zobrist::hash_type& hash) const noexcept; + + [[nodiscard]] std::string pv_string() const noexcept; + [[nodiscard]] chess::move ponder_move() const noexcept; + + [[maybe_unused]] search_stack& clear_future() noexcept; + + search_stack(const chess::board_history& past, const chess::board& present) noexcept; +}; + +struct stack_view { + search_stack* view_; + depth_type height_{}; + + [[nodiscard]] constexpr score_type loss_score() const noexcept { return mate_score + height_; } + [[nodiscard]] constexpr score_type win_score() const noexcept { return -mate_score - height_; } + + [[nodiscard]] constexpr bool reached_max_height() const noexcept { return height_ >= (safe_depth - 1); } + [[nodiscard]] constexpr depth_type height() const noexcept { return height_; } + + [[nodiscard]] constexpr const chess::board& root_position() const noexcept { return view_->root(); } + + [[nodiscard]] inline bool is_two_fold(const zobrist::hash_type& hash) const noexcept { return view_->count(height_, hash) >= 1; } + + [[nodiscard]] constexpr chess::move counter() const noexcept { + if (height_ <= 0) { return chess::move::null(); } + return view_->at(height_ - 1).played_; + } + + [[nodiscard]] constexpr chess::move follow() const noexcept { + if (height_ <= 1) { return chess::move::null(); } + return view_->at(height_ - 2).played_; + } + + [[nodiscard]] constexpr chess::move killer() const noexcept { return view_->at(height_).killer_; } + + [[nodiscard]] constexpr chess::move excluded() const noexcept { return view_->at(height_).excluded_; } + + [[nodiscard]] constexpr bool has_excluded() const noexcept { return !view_->at(height_).excluded_.is_null(); } + + [[nodiscard]] constexpr const std::array& pv() const { return view_->at(height_).pv_; } + + [[nodiscard]] constexpr bool nmp_valid() const noexcept { return !counter().is_null() && !follow().is_null(); } + + [[nodiscard]] constexpr bool improving() const noexcept { return (height_ >= 2) && view_->at(height_ - 2).eval_ < view_->at(height_).eval_; } + + [[maybe_unused]] constexpr const stack_view& set_hash(const zobrist::hash_type& hash) const noexcept { + view_->at(height_).hash_ = hash; + return *this; + } + + [[maybe_unused]] constexpr const stack_view& set_eval(const score_type& eval) const noexcept { + view_->at(height_).eval_ = eval; + return *this; + } + + [[maybe_unused]] constexpr const stack_view& set_played(const chess::move& played) const noexcept { + view_->at(height_).played_ = played; + return *this; + } + + [[maybe_unused]] inline const stack_view& prepend_to_pv(const chess::move& pv_mv) const noexcept { + const auto& child_pv = next().pv(); + auto output_iter = view_->at(height_).pv_.begin(); + *(output_iter++) = pv_mv; + std::copy(child_pv.begin(), child_pv.begin() + std::distance(output_iter, view_->at(height_).pv_.end()), output_iter); + return *this; + } + + [[maybe_unused]] constexpr const stack_view& set_killer(const chess::move& killer) const noexcept { + view_->at(height_).killer_ = killer; + return *this; + } + + [[maybe_unused]] constexpr const stack_view& set_excluded(const chess::move& excluded) const noexcept { + view_->at(height_).excluded_ = excluded; + return *this; + } + + [[nodiscard]] constexpr stack_view prev() const noexcept { return stack_view(view_, height_ - 1); } + [[nodiscard]] constexpr stack_view next() const noexcept { return stack_view(view_, height_ + 1); } + + constexpr stack_view(search_stack* view, const depth_type& height) : view_{view}, height_{height} { view_->update_selective_depth(height); } + [[nodiscard]] static constexpr stack_view root(search_stack& st) noexcept { return stack_view(&st, 0); } +}; + +} // namespace search diff --git a/include/search/search_worker.h b/include/search/search_worker.h new file mode 100644 index 0000000..03f277b --- /dev/null +++ b/include/search/search_worker.h @@ -0,0 +1,111 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace search { + +template +struct pv_search_result {}; + +template <> +struct pv_search_result { + using type = score_type; +}; + +template <> +struct pv_search_result { + using type = std::tuple; + +}; + +template +using pv_search_result_t = typename pv_search_result::type; + +struct search_worker { + search_worker_external_state external; + search_worker_internal_state internal{}; + + template + [[nodiscard]] score_type q_search( + const stack_view& ss, + nnue::eval_node& eval_node, + const chess::board& bd, + score_type alpha, + const score_type& beta, + const depth_type& elevation) noexcept; + + template + [[nodiscard]] pv_search_result_t pv_search( + const stack_view& ss, + nnue::eval_node& eval_node, + const chess::board& bd, + score_type alpha, + const score_type& beta, + depth_type depth, + const chess::player_type& reducer) noexcept; + + void iterative_deepening_loop() noexcept; + + [[nodiscard]] std::size_t best_move_percent() const noexcept { + constexpr std::size_t one_hundred = 100; + const auto iter = internal.node_distribution.find(chess::move{internal.best_move}); + return iter != internal.node_distribution.end() ? (one_hundred * iter->second / internal.nodes.load()) : one_hundred; + } + + [[nodiscard]] std::size_t nodes() const noexcept { return internal.nodes.load(); } + [[nodiscard]] std::size_t tb_hits() const noexcept { return internal.tb_hits.load(); } + [[nodiscard]] depth_type depth() const noexcept { return internal.depth.load(); } + [[nodiscard]] chess::move best_move() const noexcept { return chess::move{internal.best_move.load()}; } + [[nodiscard]] chess::move ponder_move() const noexcept { return chess::move{internal.ponder_move.load()}; } + [[nodiscard]] score_type score() const noexcept { return internal.score.load(); } + + void go(const chess::board_history& hist, const chess::board& bd, const depth_type& start_depth) noexcept { + internal.go.store(true); + internal.node_distribution.clear(); + internal.nodes.store(0); + internal.tb_hits.store(0); + internal.depth.store(start_depth); + internal.best_move.store(bd.generate_moves<>().begin()->data); + internal.ponder_move.store(chess::move::null().data); + internal.stack = search_stack(hist, bd); + } + + void stop() noexcept { internal.go.store(false); } + + search_worker( + const nnue::weights* weights, + std::shared_ptr tt, + std::shared_ptr constants, + std::function on_iter = [](auto&&...) {}, + std::function on_update = [](auto&&...) {}) noexcept + : external(weights, tt, constants, on_iter, on_update) {} +}; + +} // namespace search \ No newline at end of file diff --git a/include/search/search_worker_external_state.h b/include/search/search_worker_external_state.h new file mode 100644 index 0000000..64187f3 --- /dev/null +++ b/include/search/search_worker_external_state.h @@ -0,0 +1,46 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include +#include + +#include +#include + +namespace search { +struct search_worker; + +struct search_worker_external_state { + const nnue::weights* weights; + std::shared_ptr tt; + std::shared_ptr constants; + std::function on_iter; + std::function on_update; + + search_worker_external_state( + const nnue::weights* weights_, + std::shared_ptr tt_, + std::shared_ptr constants_, + std::function& on_iter_, + std::function on_update_) noexcept + : weights{weights_}, tt{tt_}, constants{constants_}, on_iter{on_iter_}, on_update{on_update_} {} +}; + +} // namespace search \ No newline at end of file diff --git a/include/search/search_worker_internal_state.h b/include/search/search_worker_internal_state.h new file mode 100644 index 0000000..d5260dd --- /dev/null +++ b/include/search/search_worker_internal_state.h @@ -0,0 +1,73 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include + +namespace search { + +struct search_worker_internal_state { + nnue::sided_feature_reset_cache reset_cache{}; + search_stack stack{chess::board_history{}, chess::board::start_pos()}; + nnue::eval::scratchpad_type scratchpad{}; + sided_history_heuristic hh{}; + eval_cache cache{}; + std::unordered_map node_distribution{}; + + std::atomic_bool go{false}; + std::atomic_size_t nodes{}; + std::atomic_size_t tb_hits{}; + std::atomic depth{}; + + std::atomic score{}; + + std::atomic best_move{}; + std::atomic ponder_move{}; + + [[nodiscard]] bool keep_going() const noexcept { return go.load(std::memory_order::memory_order_relaxed); } + + template + [[nodiscard]] inline bool one_of() const noexcept { + static_assert((N != 0) && ((N & (N - 1)) == 0), "N must be a power of 2"); + constexpr std::size_t bit_pattern = N - 1; + return (nodes & bit_pattern) == bit_pattern; + } + + void reset() noexcept { + stack = search_stack{chess::board_history{}, chess::board::start_pos()}; + hh.clear(); + cache.clear(); + node_distribution.clear(); + + go.store(false); + nodes.store(0); + tb_hits.store(0); + depth.store(0); + score.store(0); + best_move.store(chess::move::null().data); + } +}; + +} // namespace search diff --git a/include/search/search_worker_orchestrator.h b/include/search/search_worker_orchestrator.h new file mode 100644 index 0000000..6446b76 --- /dev/null +++ b/include/search/search_worker_orchestrator.h @@ -0,0 +1,64 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include + +#include +#include +#include +#include + +namespace search { + +struct worker_orchestrator { + static constexpr std::size_t primary_id = 0; + + const nnue::weights* weights_; + std::shared_ptr tt_{nullptr}; + std::shared_ptr constants_{nullptr}; + + std::mutex access_mutex_{}; + std::atomic_bool is_searching_{}; + std::vector> workers_{}; + std::vector threads_{}; + + void reset() noexcept; + void resize(const std::size_t& new_size) noexcept; + + void go(const chess::board_history& hist, const chess::board& bd) noexcept; + void stop() noexcept; + + [[nodiscard]] bool is_searching() noexcept; + + [[nodiscard]] std::size_t nodes() const noexcept; + [[nodiscard]] std::size_t tb_hits() const noexcept; + + [[nodiscard]] search_worker& primary_worker() noexcept; + + worker_orchestrator( + const nnue::weights* weights, + std::size_t hash_table_size, + std::function on_iter = [](auto&&...) {}, + std::function on_update = [](auto&&...) {}) noexcept; + + ~worker_orchestrator() noexcept; +}; + +} // namespace search \ No newline at end of file diff --git a/include/search/syzygy.h b/include/search/syzygy.h new file mode 100644 index 0000000..2b26a4b --- /dev/null +++ b/include/search/syzygy.h @@ -0,0 +1,60 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +extern "C" { +#include +} + +#include +#include + +#include + +namespace search::syzygy { + +enum class wdl_type { loss, draw, win }; + +struct tb_wdl_result { + bool success{}; + wdl_type wdl{wdl_type::draw}; + + [[nodiscard]] static constexpr tb_wdl_result failure() noexcept { return tb_wdl_result{false}; } + [[nodiscard]] static constexpr tb_wdl_result from_value(const unsigned int& value) noexcept { + if (value == TB_WIN) { return tb_wdl_result{true, wdl_type::win}; } + if (value == TB_DRAW) { return tb_wdl_result{true, wdl_type::draw}; } + if (value == TB_LOSS) { return tb_wdl_result{true, wdl_type::loss}; } + return failure(); + } +}; + +struct tb_dtz_result { + bool success{}; + search::score_type score{search::draw_score}; + chess::move move{chess::move::null()}; + + [[nodiscard]] static constexpr tb_dtz_result failure() noexcept { return tb_dtz_result{false}; } + [[nodiscard]] static tb_dtz_result from_value(const chess::board& bd, const unsigned int& value) noexcept; +}; + +[[nodiscard]] tb_wdl_result probe_wdl(const chess::board& bd) noexcept; +[[nodiscard]] tb_dtz_result probe_dtz(const chess::board& bd) noexcept; + +void init(const std::string& path) noexcept; + +} // namespace search \ No newline at end of file diff --git a/include/search/transposition_table.h b/include/search/transposition_table.h new file mode 100644 index 0000000..28cf09f --- /dev/null +++ b/include/search/transposition_table.h @@ -0,0 +1,157 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace search { + +constexpr std::size_t cache_line_size = 64; +enum class bound_type { upper, lower, exact }; + +struct transposition_table_entry { + static constexpr zobrist::hash_type empty_key = zobrist::hash_type{}; + static constexpr std::size_t gen_bits = 7; + using gen_type = std::uint8_t; + + using bound_ = util::bit_range; + using score_ = util::next_bit_range; + using best_move_ = util::next_bit_range; + using depth_ = util::next_bit_range; + using gen_ = util::next_bit_range; + using was_exact_or_lb_ = util::next_bit_flag; + + zobrist::hash_type key_{empty_key}; + zobrist::hash_type value_{}; + + [[nodiscard]] constexpr zobrist::hash_type key() const noexcept { return key_ ^ value_; } + + [[nodiscard]] constexpr bound_type bound() const noexcept { return bound_::get(value_); } + [[nodiscard]] constexpr score_type score() const noexcept { return static_cast(score_::get(value_)); } + [[nodiscard]] constexpr gen_type gen() const noexcept { return gen_::get(value_); } + [[nodiscard]] constexpr depth_type depth() const noexcept { return static_cast(depth_::get(value_)); } + [[nodiscard]] constexpr chess::move best_move() const noexcept { return chess::move{best_move_::get(value_)}; } + [[nodiscard]] constexpr bool was_exact_or_lb() const noexcept { return was_exact_or_lb_::get(value_); } + + [[nodiscard]] constexpr bool is_empty() const noexcept { return key_ == empty_key; } + [[nodiscard]] constexpr bool is_current(const gen_type& gen) const noexcept { return gen == gen_::get(value_); } + + [[maybe_unused]] constexpr transposition_table_entry& set_gen(const gen_type& gen) noexcept { + key_ ^= value_; + gen_::set(value_, gen); + key_ ^= value_; + return *this; + } + + [[maybe_unused]] constexpr transposition_table_entry& merge(const transposition_table_entry& other) noexcept { + if (bound() == bound_type::upper && other.was_exact_or_lb() && key() == other.key()) { + key_ ^= value_; + best_move_::set(value_, other.best_move().data); + was_exact_or_lb_::set(value_, true); + key_ ^= value_; + } + + return *this; + } + + constexpr transposition_table_entry( + const zobrist::hash_type& key, const bound_type& bound, const score_type& score, const chess::move& mv, const depth_type& depth) noexcept + : key_{key} { + bound_::set(value_, bound); + score_::set(value_, static_cast(score)); + best_move_::set(value_, mv.data); + depth_::set(value_, static_cast(depth)); + was_exact_or_lb_::set(value_, bound != bound_type::upper); + key_ ^= value_; + } + + constexpr transposition_table_entry() noexcept = default; +}; + +template +struct alignas(cache_line_size) bucket { + transposition_table_entry data[N]; + + [[nodiscard]] constexpr std::optional match( + const transposition_table_entry::gen_type& gen, const zobrist::hash_type& key) noexcept { + for (auto& elem : data) { + if (elem.key() == key) { return std::optional(elem.set_gen(gen)); } + } + + return std::nullopt; + } + + [[nodiscard]] constexpr transposition_table_entry* to_replace( + const transposition_table_entry::gen_type& gen, const zobrist::hash_type& key) noexcept { + auto worst = std::begin(data); + for (auto iter = std::begin(data); iter != std::end(data); ++iter) { + if (iter->key() == key) { return iter; } + + const bool is_worse = (!iter->is_current(gen) && worst->is_current(gen)) || (iter->is_empty() && !worst->is_empty()) || + ((iter->is_current(gen) == worst->is_current(gen)) && (iter->depth() < worst->depth())); + + if (is_worse) { worst = iter; } + } + + return worst; + } +}; + +struct transposition_table { + static constexpr std::size_t per_bucket = cache_line_size / sizeof(transposition_table_entry); + static constexpr std::size_t one_mb = (1 << 20) / cache_line_size; + + using bucket_type = bucket; + + static_assert(cache_line_size % sizeof(transposition_table_entry) == 0, "transposition_table_entry must divide cache_line_size"); + static_assert(sizeof(bucket_type) == cache_line_size && alignof(bucket_type) == cache_line_size, "bucket_type must be cache_line_size aligned"); + + std::atomic current_gen{0}; + std::vector data; + + [[nodiscard]] inline std::size_t hash_function(const zobrist::hash_type& hash) const noexcept { return hash % data.size(); } + inline void prefetch(const zobrist::hash_type& key) const noexcept { __builtin_prefetch(data.data() + hash_function(key)); } + + void clear() noexcept; + void resize(const std::size_t& size) noexcept; + void update_gen() noexcept; + + // __attribute__((no_sanitize("thread"))) + [[maybe_unused]] transposition_table& insert(const transposition_table_entry& entry) noexcept; + + // __attribute__((no_sanitize("thread"))) + [[nodiscard]] std::optional find(const zobrist::hash_type& key) noexcept; + + explicit transposition_table(const std::size_t& size) noexcept : data(size * one_mb) {} +}; + +} // namespace search diff --git a/include/search_constants.h b/include/search_constants.h deleted file mode 100644 index c748f3d..0000000 --- a/include/search_constants.h +++ /dev/null @@ -1,229 +0,0 @@ -/* - Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle - - Seer is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Seer is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#pragma once - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace search { - -template -inline constexpr T max_logit = static_cast(8); - -template -inline constexpr T min_logit = static_cast(-8); - -template -inline constexpr T logit_scale = static_cast(1024); - -template -inline constexpr T wdl_scale = static_cast(1024); - -using depth_type = std::int32_t; - -inline constexpr depth_type max_depth = 128; - -inline constexpr depth_type max_depth_margin = 8; - -constexpr depth_type safe_depth = max_depth + max_depth_margin; - -using score_type = std::int32_t; - -using wdl_type = std::tuple; - -inline constexpr score_type big_number = 8 * logit_scale; - -inline constexpr score_type max_mate_score = -2 * big_number; - -inline constexpr score_type mate_score = max_mate_score - (max_depth + max_depth_margin); - -inline constexpr score_type tb_win_score = big_number + 1; - -inline constexpr score_type tb_loss_score = -tb_win_score; - -inline constexpr score_type draw_score = 0; - -inline constexpr score_type aspiration_delta = 20; - -using counter_type = std::int32_t; - -using see_type = std::int32_t; - -inline constexpr size_t nodes_per_update = 512; - -struct fixed_search_constants { - static constexpr bool tuning = false; - static constexpr depth_type lmr_tbl_dim = 64; - size_t thread_count_; - std::array lmr_tbl{}; - - const size_t& thread_count() const { return thread_count_; } - - constexpr depth_type reduce_depth() const { return 3; } - constexpr depth_type aspiration_depth() const { return 4; } - constexpr depth_type nmp_depth() const { return 2; } - constexpr depth_type lmp_depth() const { return 7; } - constexpr depth_type snmp_depth() const { return 7; } - constexpr depth_type futility_prune_depth() const { return 6; } - constexpr depth_type quiet_see_prune_depth() const { return 8; } - constexpr depth_type noisy_see_prune_depth() const { return 6; } - constexpr depth_type singular_extension_depth() const { return 6; } - constexpr depth_type probcut_depth() const { return 5; } - constexpr depth_type iir_depth() const { return 4; } - - constexpr depth_type reduction(const depth_type& depth, const int& move_idx) const { - constexpr depth_type last_idx = lmr_tbl_dim - 1; - return lmr_tbl[std::min(last_idx, depth) * lmr_tbl_dim + std::min(last_idx, move_idx)]; - } - - constexpr depth_type nmp_reduction(const depth_type& depth, const score_type& beta, const score_type& value) const { - return 4 + depth / 6 + std::min(3, (value - beta) / 256); - } - - constexpr see_type nmp_see_threshold() const { return 200; } - - constexpr depth_type singular_extension_depth_margin() const { return 3; } - - constexpr depth_type singular_search_depth(const depth_type& depth) const { return depth / 2 - 1; } - - constexpr score_type singular_beta(const score_type& tt_score, const depth_type& depth) const { - return tt_score - 2 * static_cast(depth); - } - - constexpr score_type singular_double_extension_margin() const { return 160; } - - constexpr score_type futility_margin(const depth_type& depth) const { - assert(depth > 0); - constexpr score_type m = 1536; - return m * static_cast(depth); - } - - constexpr score_type snmp_margin(const bool& improving, const bool& threats, const depth_type& depth) const { - assert(depth > 0); - constexpr score_type m = 288; - constexpr score_type b = 128; - return m * static_cast(depth - (improving && !threats)) + (threats ? b : 0); - } - - constexpr int lmp_count(const bool& improving, const depth_type& depth) const { - constexpr std::array improving_counts = {0, 5, 8, 12, 20, 30, 42, 65}; - constexpr std::array worsening_counts = {0, 3, 4, 8, 10, 13, 21, 31}; - return improving ? improving_counts[depth] : worsening_counts[depth]; - } - - constexpr see_type quiet_see_prune_threshold(const depth_type& depth) const { return -50 * static_cast(depth); } - constexpr see_type noisy_see_prune_threshold(const depth_type& depth) const { return -100 * static_cast(depth); } - - constexpr counter_type history_prune_threshold(const depth_type& depth) const { return -1024 * static_cast(depth * depth); } - - constexpr depth_type history_reduction(const counter_type& history_value) const { - constexpr depth_type limit = 2; - const depth_type raw = -static_cast(history_value / 5000); - return std::clamp(raw, -limit, limit); - } - - constexpr score_type delta_margin() const { - constexpr score_type margin = 512; - return margin; - } - - constexpr see_type good_capture_prune_see_margin() const { return 300; } - constexpr score_type good_capture_prune_score_margin() const { return 256; } - - constexpr depth_type probcut_search_depth(const depth_type& depth) const { return depth - 3; } - constexpr score_type probcut_beta(const score_type& beta) const { return beta + 320; } - - fixed_search_constants& update_(const size_t& thread_count) { - thread_count_ = thread_count; - for (depth_type depth{1}; depth < lmr_tbl_dim; ++depth) { - for (depth_type played{1}; played < lmr_tbl_dim; ++played) { - lmr_tbl[depth * lmr_tbl_dim + played] = static_cast(0.75 + std::log(depth) * std::log(played) / 2.25); - } - } - return *this; - } - - auto options() { return engine::uci_options(); } - - fixed_search_constants(const size_t& thread_count = 1) { update_(thread_count); } -}; - -struct tuning_search_constants : fixed_search_constants { - static constexpr bool tuning = true; - static constexpr depth_type lmr_tbl_dim = 64; - - depth_type reduce_depth_{3}; - double lmr_tbl_bias_{0.75}; - double lmr_tbl_div_{2.25}; - - depth_type reduce_depth() const { return reduce_depth_; } - - depth_type reduction(const depth_type& depth, const int& move_idx) const { - constexpr depth_type last_idx = lmr_tbl_dim - 1; - return lmr_tbl[std::min(last_idx, depth) * lmr_tbl_dim + std::min(last_idx, move_idx)]; - } - - tuning_search_constants& update_(const size_t& thread_count) { - thread_count_ = thread_count; - for (depth_type depth{1}; depth < lmr_tbl_dim; ++depth) { - for (depth_type played{1}; played < lmr_tbl_dim; ++played) { - lmr_tbl[depth * lmr_tbl_dim + played] = static_cast(lmr_tbl_bias_ + std::log(depth) * std::log(played) / lmr_tbl_div_); - } - } - return *this; - } - - auto options() { - using namespace engine; - auto option_reduce_depth = - option_callback(spin_option("reduce_depth_", reduce_depth_, spin_range{2, 8}), [this](const int d) { reduce_depth_ = d; }); - - auto option_lmr_tbl_bias = option_callback(string_option("lmr_tbl_bias_", std::to_string(lmr_tbl_bias_)), [this](const std::string d) { - std::istringstream ss(d); - ss >> lmr_tbl_bias_; - update_(thread_count_); - }); - - auto option_lmr_tbl_div = option_callback(string_option("lmr_tbl_div_", std::to_string(lmr_tbl_div_)), [this](const std::string d) { - std::istringstream ss(d); - ss >> lmr_tbl_div_; - update_(thread_count_); - }); - - return uci_options(option_reduce_depth, option_lmr_tbl_bias, option_lmr_tbl_div); - } - - tuning_search_constants(const size_t& thread_count = 1) { update_(thread_count); } -}; - -#ifdef TUNE -using search_constants = tuning_search_constants; -#else -using search_constants = fixed_search_constants; -#endif - -} // namespace search diff --git a/include/search_stack.h b/include/search_stack.h deleted file mode 100644 index d2e7e0f..0000000 --- a/include/search_stack.h +++ /dev/null @@ -1,167 +0,0 @@ -/* - Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle - - Seer is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Seer is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#pragma once - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace search { - -struct stack_entry { - zobrist::hash_type hash_{}; - score_type eval_{}; - chess::move played_{chess::move::null()}; - chess::move killer_{chess::move::null()}; - chess::move excluded_{chess::move::null()}; - std::array pv_{}; - - stack_entry() { pv_.fill(chess::move::null()); } -}; - -struct search_stack { - depth_type sel_depth_{0}; - - chess::position_history past_; - chess::board present_; - std::array future_{}; - - stack_entry& at(const depth_type& height) { - sel_depth_ = std::max(sel_depth_, height); - return future_[height]; - } - - chess::board root_pos() const { return present_; } - depth_type sel_depth() const { return sel_depth_; } - - size_t occurrences(const size_t& height, const zobrist::hash_type& hash) const { - size_t occurrences_{0}; - for (auto it = future_.cbegin(); it != (future_.cbegin() + height); ++it) { occurrences_ += static_cast(it->hash_ == hash); } - return occurrences_ + past_.occurrences(hash); - } - - std::string pv_string() const { - auto bd = present_; - std::string result{}; - for (const auto& pv_mv : future_.begin()->pv_) { - if (!bd.generate_moves<>().has(pv_mv)) { break; } - result += pv_mv.name(bd.turn()) + " "; - bd = bd.forward(pv_mv); - } - return result; - } - - chess::move ponder_move() const { return *(future_.begin()->pv_.begin() + 1); } - - search_stack& clear_future() { - sel_depth_ = 0; - future_.fill(stack_entry{}); - return *this; - } - - search_stack(const chess::position_history& past, const chess::board& present) : past_{past}, present_{present} {} -}; - -struct stack_view { - search_stack* view_; - depth_type height_{}; - - constexpr score_type loss_score() const { return mate_score + height_; } - - constexpr score_type win_score() const { return -mate_score - height_; } - - bool reached_max_height() const { return height_ >= (safe_depth - 1); } - - depth_type height() const { return height_; } - - chess::board root_pos() const { return view_->root_pos(); } - - bool is_two_fold(const zobrist::hash_type& hash) const { return view_->occurrences(height_, hash) >= 1; } - - chess::move counter() const { - if (height_ <= 0) { return chess::move::null(); } - return view_->at(height_ - 1).played_; - } - - chess::move follow() const { - if (height_ <= 1) { return chess::move::null(); } - return view_->at(height_ - 2).played_; - } - - chess::move killer() const { return view_->at(height_).killer_; } - - chess::move excluded() const { return view_->at(height_).excluded_; } - - bool has_excluded() const { return !view_->at(height_).excluded_.is_null(); } - - const std::array& pv() const { return view_->at(height_).pv_; } - - bool nmp_valid() const { return !counter().is_null() && !follow().is_null(); } - - bool improving() const { return (height_ >= 2) && view_->at(height_ - 2).eval_ < view_->at(height_).eval_; } - - const stack_view& set_hash(const zobrist::hash_type& hash) const { - view_->at(height_).hash_ = hash; - return *this; - } - - const stack_view& set_eval(const score_type& eval) const { - view_->at(height_).eval_ = eval; - return *this; - } - - const stack_view& set_played(const chess::move& played) const { - view_->at(height_).played_ = played; - return *this; - } - - const stack_view& prepend_to_pv(const chess::move& pv_mv) const { - const auto& child_pv = next().pv(); - auto output_iter = view_->at(height_).pv_.begin(); - *(output_iter++) = pv_mv; - std::copy(child_pv.begin(), child_pv.begin() + std::distance(output_iter, view_->at(height_).pv_.end()), output_iter); - return *this; - } - - const stack_view& set_killer(const chess::move& killer) const { - view_->at(height_).killer_ = killer; - return *this; - } - - const stack_view& set_excluded(const chess::move& excluded) const { - view_->at(height_).excluded_ = excluded; - return *this; - } - - stack_view prev() const { return stack_view(view_, height_ - 1); } - - stack_view next() const { return stack_view(view_, height_ + 1); } - - stack_view(search_stack* view, const depth_type& height) : view_{view}, height_{height} { assert((height >= 0)); } - - static stack_view root(search_stack& st) { return stack_view(&st, 0); } -}; - -} // namespace search diff --git a/include/search_worker.h b/include/search_worker.h deleted file mode 100644 index 78539f2..0000000 --- a/include/search_worker.h +++ /dev/null @@ -1,647 +0,0 @@ -/* - Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle - - Seer is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Seer is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace search { - -template -struct pv_search_result {}; - -template <> -struct pv_search_result { - using type = score_type; -}; - -template <> -struct pv_search_result { - using type = std::tuple; -}; - -template -using pv_search_result_t = typename pv_search_result::type; - -struct search_worker; - -struct internal_state { - nnue::sided_feature_reset_cache reset_cache{}; - search_stack stack{chess::position_history{}, chess::board::start_pos()}; - nnue::eval::scratchpad_type scratchpad{}; - sided_history_heuristic hh{}; - eval_cache cache{}; - std::unordered_map node_distribution{}; - - std::atomic_bool go{false}; - std::atomic_size_t nodes{}; - std::atomic_size_t tb_hits{}; - std::atomic depth{}; - - std::atomic score{}; - - std::atomic best_move{}; - std::atomic ponder_move{}; - - bool keep_going() const { return go.load(std::memory_order::memory_order_relaxed); } - - template - inline bool one_of() const { - static_assert((N != 0) && ((N & (N - 1)) == 0), "N must be a power of 2"); - constexpr size_t bit_pattern = N - 1; - return (nodes & bit_pattern) == bit_pattern; - } - - void reset() { - stack = search_stack{chess::position_history{}, chess::board::start_pos()}; - hh.clear(); - cache.clear(); - node_distribution.clear(); - - go.store(false); - nodes.store(0); - tb_hits.store(0); - depth.store(0); - score.store(0); - best_move.store(chess::move::null().data); - } -}; - -struct external_state { - const nnue::weights* weights; - std::shared_ptr tt; - std::shared_ptr constants; - std::function on_iter; - std::function on_update; - - external_state( - const nnue::weights* weights_, - std::shared_ptr tt_, - std::shared_ptr constants_, - std::function& on_iter_, - std::function on_update_) - : weights{weights_}, tt{tt_}, constants{constants_}, on_iter{on_iter_}, on_update{on_update_} {} -}; - -struct search_worker { - external_state external; - internal_state internal{}; - - template - score_type q_search( - const stack_view& ss, - nnue::eval_node& eval_node, - const chess::board& bd, - score_type alpha, - const score_type& beta, - const depth_type& elevation) { - // callback on entering search function - const bool should_update = internal.keep_going() && internal.one_of(); - if (should_update) { external.on_update(*this); } - - ++internal.nodes; - const bool is_check = bd.is_check(); - - if (ss.is_two_fold(bd.hash())) { return draw_score; } - if (bd.is_trivially_drawn()) { return draw_score; } - - const std::optional maybe = external.tt->find(bd.hash()); - if (maybe.has_value()) { - const transposition_table_entry entry = maybe.value(); - const bool is_cutoff = (entry.bound() == bound_type::lower && entry.score() >= beta) || (entry.bound() == bound_type::exact) || - (entry.bound() == bound_type::upper && entry.score() <= alpha); - if (use_tt && is_cutoff) { return entry.score(); } - } - - const auto [static_value, value] = [&] { - const auto maybe_eval = internal.cache.find(bd.hash()); - const score_type static_value = is_check ? ss.loss_score() : - !is_pv && maybe_eval.has_value() ? - maybe_eval.value() : - eval_node.evaluator().evaluate(bd.turn(), bd.phase()); - - if (!is_check) { internal.cache.insert(bd.hash(), static_value); } - - score_type value = static_value; - if (use_tt && maybe.has_value()) { - if (maybe->bound() == bound_type::upper && static_value > maybe->score()) { value = maybe->score(); } - if (maybe->bound() == bound_type::lower && static_value < maybe->score()) { value = maybe->score(); } - } - - return std::tuple(static_value, value); - }(); - - if (!is_check && value >= beta) { return value; } - if (ss.reached_max_height()) { return value; } - - move_orderer orderer(move_orderer_data(&bd, &internal.hh.us(bd.turn()))); - if (maybe.has_value()) { orderer.set_first(maybe->best_move()); } - - alpha = std::max(alpha, value); - score_type best_score = value; - chess::move best_move = chess::move::null(); - - ss.set_hash(bd.hash()).set_eval(static_value); - int legal_count{0}; - for (const auto& [idx, mv] : orderer) { - assert((mv != chess::move::null())); - - ++legal_count; - if (!internal.keep_going()) { break; } - - if (!is_check && !bd.see_ge(mv, 0)) { continue; } - - const bool delta_prune = !is_pv && !is_check && !bd.see_gt(mv, 0) && ((value + external.constants->delta_margin()) < alpha); - if (delta_prune) { continue; } - - const bool good_capture_prune = !is_pv && !is_check && !maybe.has_value() && - bd.see_ge(mv, external.constants->good_capture_prune_see_margin()) && - value + external.constants->good_capture_prune_score_margin() > beta; - if (good_capture_prune) { return beta; } - - ss.set_played(mv); - - const chess::board bd_ = bd.forward(mv); - external.tt->prefetch(bd_.hash()); - internal.cache.prefetch(bd_.hash()); - nnue::eval_node eval_node_ = eval_node.dirty_child(&internal.reset_cache, &bd, mv); - - const score_type score = -q_search(ss.next(), eval_node_, bd_, -beta, -alpha, elevation + 1); - - if (score > best_score) { - best_score = score; - best_move = mv; - if (score > alpha) { - if (score < beta) { alpha = score; } - if constexpr (is_pv) { ss.prepend_to_pv(mv); } - } - } - - if (best_score >= beta) { break; } - } - - if (legal_count == 0 && is_check) { return ss.loss_score(); } - if (legal_count == 0) { return value; } - - if (use_tt && internal.keep_going()) { - const bound_type bound = best_score >= beta ? bound_type::lower : bound_type::upper; - const transposition_table_entry entry(bd.hash(), bound, best_score, best_move, 0); - external.tt->insert(entry); - } - - return best_score; - } - - template - auto pv_search( - const stack_view& ss, - nnue::eval_node& eval_node, - const chess::board& bd, - score_type alpha, - const score_type& beta, - depth_type depth, - const chess::player_type& reducer) -> pv_search_result_t { - auto make_result = [](const score_type& score, const chess::move& mv) { - if constexpr (is_root) { return pv_search_result_t{score, mv}; } - if constexpr (!is_root) { return score; } - }; - - static_assert(!is_root || is_pv); - assert(depth >= 0); - - // callback on entering search function - const bool should_update = internal.keep_going() && (is_root || internal.one_of()); - if (should_update) { external.on_update(*this); } - - // step 1. drop into qsearch if depth reaches zero - if (depth <= 0) { return make_result(q_search(ss, eval_node, bd, alpha, beta, 0), chess::move::null()); } - ++internal.nodes; - - // step 2. check if node is terminal - const bool is_check = bd.is_check(); - - if (!is_root && ss.is_two_fold(bd.hash())) { return make_result(draw_score, chess::move::null()); } - if (!is_root && bd.is_trivially_drawn()) { return make_result(draw_score, chess::move::null()); } - if (!is_root && bd.is_rule50_draw() && (!is_check || bd.generate_moves().size() != 0)) { - return make_result(draw_score, chess::move::null()); - } - - if constexpr (is_root) { - if (const syzygy::tb_dtz_result result = syzygy::probe_dtz(bd); result.success) { return make_result(result.score, result.move); } - } - - const score_type original_alpha = alpha; - - const std::optional maybe = !ss.has_excluded() ? external.tt->find(bd.hash()) : std::nullopt; - if (maybe.has_value()) { - const transposition_table_entry entry = maybe.value(); - const bool is_cutoff = !is_pv && entry.depth() >= depth && - ((entry.bound() == bound_type::lower && entry.score() >= beta) || entry.bound() == bound_type::exact || - (entry.bound() == bound_type::upper && entry.score() <= alpha)); - if (is_cutoff) { return make_result(entry.score(), entry.best_move()); } - } - - if (const syzygy::tb_wdl_result result = syzygy::probe_wdl(bd); !is_root && result.success) { - ++internal.tb_hits; - - switch (result.wdl) { - case syzygy::wdl_type::loss: return make_result(ss.loss_score(), chess::move::null()); - case syzygy::wdl_type::draw: return make_result(draw_score, chess::move::null()); - case syzygy::wdl_type::win: return make_result(ss.win_score(), chess::move::null()); - } - } - - // step 3. internal iterative reductions - const bool should_iir = !maybe.has_value() && !ss.has_excluded() && depth >= external.constants->iir_depth(); - if (should_iir) { --depth; } - - // step 4. compute static eval and adjust appropriately if there's a tt hit - const auto [static_value, value] = [&] { - const auto maybe_eval = internal.cache.find(bd.hash()); - const score_type static_value = is_check ? ss.loss_score() : - !is_pv && maybe_eval.has_value() ? - maybe_eval.value() : - eval_node.evaluator().evaluate(bd.turn(), bd.phase()); - - if (!is_check) { internal.cache.insert(bd.hash(), static_value); } - - score_type value = static_value; - if (maybe.has_value()) { - if (maybe->bound() == bound_type::upper && static_value > maybe->score()) { value = maybe->score(); } - if (maybe->bound() == bound_type::lower && static_value < maybe->score()) { value = maybe->score(); } - } - - return std::tuple(static_value, value); - }(); - - // step 5. return static eval if max depth was reached - if (ss.reached_max_height()) { return make_result(value, chess::move::null()); } - - // step 6. add position and static eval to stack - ss.set_hash(bd.hash()).set_eval(static_value); - const bool improving = !is_check && ss.improving(); - const chess::square_set threatened = bd.them_threat_mask(); - - // step 7. static null move pruning - const bool snm_prune = !is_pv && !ss.has_excluded() && !is_check && depth <= external.constants->snmp_depth() && - value > beta + external.constants->snmp_margin(improving, threatened.any(), depth) && value > ss.loss_score(); - - if (snm_prune) { return make_result(value, chess::move::null()); } - - // step 8. null move pruning - const bool try_nmp = - !is_pv && !ss.has_excluded() && !is_check && depth >= external.constants->nmp_depth() && value > beta && ss.nmp_valid() && - bd.has_non_pawn_material() && (!threatened.any() || depth >= 4) && - (!maybe.has_value() || (maybe->bound() == bound_type::lower && bd.is_legal(maybe->best_move()) && - !bd.see_gt(maybe->best_move(), external.constants->nmp_see_threshold()))); - - if (try_nmp) { - ss.set_played(chess::move::null()); - const depth_type adjusted_depth = std::max(0, depth - external.constants->nmp_reduction(depth, beta, value)); - const score_type nmp_score = - -pv_search(ss.next(), eval_node, bd.forward(chess::move::null()), -beta, -beta + 1, adjusted_depth, chess::player_from(!bd.turn())); - if (nmp_score >= beta) { return make_result(nmp_score, chess::move::null()); } - } - - // step 9. probcut pruning - const depth_type probcut_depth = external.constants->probcut_search_depth(depth); - const score_type probcut_beta = external.constants->probcut_beta(beta); - const bool try_probcut = !is_pv && depth >= external.constants->probcut_depth() && !(maybe.has_value() && maybe->best_move().is_quiet()) && - !(maybe.has_value() && maybe->depth() >= probcut_depth && maybe->score() < probcut_beta); - - if (try_probcut) { - move_orderer probcut_orderer(move_orderer_data(&bd, &internal.hh.us(bd.turn()))); - if (maybe.has_value()) { probcut_orderer.set_first(maybe->best_move()); } - - for (const auto& [idx, mv] : probcut_orderer) { - if (!internal.keep_going()) { break; } - if (mv == ss.excluded()) { continue; } - if (!bd.see_ge(mv, 0)) { continue; } - - ss.set_played(mv); - - const chess::board bd_ = bd.forward(mv); - external.tt->prefetch(bd_.hash()); - internal.cache.prefetch(bd_.hash()); - nnue::eval_node eval_node_ = eval_node.dirty_child(&internal.reset_cache, &bd, mv); - - auto pv_score = [&] { return -pv_search(ss.next(), eval_node_, bd_, -probcut_beta, -probcut_beta + 1, probcut_depth, reducer); }; - const score_type q_score = -q_search(ss.next(), eval_node_, bd_, -probcut_beta, -probcut_beta + 1, 0); - const score_type probcut_score = (q_score >= probcut_beta) ? pv_score() : q_score; - - if (probcut_score >= probcut_beta) { return make_result(probcut_score, mv); } - } - } - - // step 10. initialize move orderer (setting tt move first if applicable) - const chess::move killer = ss.killer(); - const chess::move follow = ss.follow(); - const chess::move counter = ss.counter(); - - move_orderer orderer( - move_orderer_data(&bd, &internal.hh.us(bd.turn())).set_killer(killer).set_follow(follow).set_counter(counter).set_threatened(threatened)); - - if (maybe.has_value()) { orderer.set_first(maybe->best_move()); } - - // list of attempted moves for updating histories - chess::move_list moves_tried{}; - - // move loop - score_type best_score = ss.loss_score(); - chess::move best_move = chess::move::null(); - - bool did_double_extend{false}; - int legal_count{0}; - - for (const auto& [idx, mv] : orderer) { - assert((mv != chess::move::null())); - - ++legal_count; - if (!internal.keep_going()) { break; } - if (mv == ss.excluded()) { continue; } - - const size_t nodes_before = internal.nodes.load(std::memory_order_relaxed); - ss.set_played(mv); - - const counter_type history_value = internal.hh.us(bd.turn()).compute_value(history::context{follow, counter, threatened}, mv); - - const chess::board bd_ = bd.forward(mv); - - const bool try_pruning = !is_root && idx >= 2 && best_score > max_mate_score; - - // step 11. pruning - if (try_pruning) { - const bool lm_prune = !bd_.is_check() && depth <= external.constants->lmp_depth() && idx > external.constants->lmp_count(improving, depth); - - if (lm_prune) { break; } - - const bool futility_prune = - mv.is_quiet() && depth <= external.constants->futility_prune_depth() && value + external.constants->futility_margin(depth) < alpha; - - if (futility_prune) { continue; } - - const bool quiet_see_prune = mv.is_quiet() && depth <= external.constants->quiet_see_prune_depth() && - !bd.see_ge(mv, external.constants->quiet_see_prune_threshold(depth)); - - if (quiet_see_prune) { continue; } - - const bool noisy_see_prune = mv.is_noisy() && depth <= external.constants->noisy_see_prune_depth() && - !bd.see_ge(mv, external.constants->noisy_see_prune_threshold(depth)); - - if (noisy_see_prune) { continue; } - - const bool history_prune = mv.is_quiet() && history_value <= external.constants->history_prune_threshold(depth); - - if (history_prune) { continue; } - } - - external.tt->prefetch(bd_.hash()); - internal.cache.prefetch(bd_.hash()); - nnue::eval_node eval_node_ = eval_node.dirty_child(&internal.reset_cache, &bd, mv); - - // step 12. extensions - bool multicut = false; - const depth_type extension = [&, mv = mv] { - const bool try_singular = !is_root && !ss.has_excluded() && depth >= external.constants->singular_extension_depth() && maybe.has_value() && - mv == maybe->best_move() && maybe->bound() != bound_type::upper && - maybe->depth() + external.constants->singular_extension_depth_margin() >= depth; - - if (try_singular) { - const depth_type singular_depth = external.constants->singular_search_depth(depth); - const score_type singular_beta = external.constants->singular_beta(maybe->score(), depth); - ss.set_excluded(mv); - const score_type excluded_score = pv_search(ss, eval_node, bd, singular_beta - 1, singular_beta, singular_depth, reducer); - ss.set_excluded(chess::move::null()); - - if (!is_pv && excluded_score + external.constants->singular_double_extension_margin() < singular_beta) { - did_double_extend = true; - return 2; - } - - if (excluded_score < singular_beta) { return 1; } - if (excluded_score >= beta) { multicut = true; } - if constexpr (!is_pv) { return -1; } - } - - return 0; - }(); - - if (!is_root && multicut) { return make_result(beta, chess::move::null()); } - - const score_type score = [&, this, idx = idx, mv = mv] { - const depth_type next_depth = depth + extension - 1; - - auto full_width = [&] { return -pv_search(ss.next(), eval_node_, bd_, -beta, -alpha, next_depth, reducer); }; - - auto zero_width = [&](const depth_type& zw_depth) { - const chess::player_type next_reducer = (is_pv || zw_depth < next_depth) ? chess::player_from(bd.turn()) : reducer; - return -pv_search(ss.next(), eval_node_, bd_, -alpha - 1, -alpha, zw_depth, next_reducer); - }; - - if (is_pv && idx == 0) { return full_width(); } - - depth_type lmr_depth; - score_type zw_score; - - // step 13. late move reductions - const bool try_lmr = !is_check && (mv.is_quiet() || !bd.see_ge(mv, 0)) && idx >= 2 && (depth >= external.constants->reduce_depth()); - if (try_lmr) { - depth_type reduction = external.constants->reduction(depth, idx); - - // adjust reduction - if (improving) { --reduction; } - if (bd_.is_check()) { --reduction; } - if (bd.is_passed_push(mv)) { --reduction; } - if (bd.creates_threat(mv)) { --reduction; } - if (mv == killer) { --reduction; } - - if (!is_pv) { ++reduction; } - if (did_double_extend) { ++reduction; } - if (!bd.see_ge(mv, 0) && mv.is_quiet()) { ++reduction; } - - // if our opponent is the reducing player, an errant fail low will, at worst, induce a re-search - // this idea is at least similar (maybe equivalent) to the "cutnode idea" found in Stockfish. - if (is_player(reducer, !bd.turn())) { ++reduction; } - - if (mv.is_quiet()) { reduction += external.constants->history_reduction(history_value); } - - reduction = std::max(0, reduction); - - lmr_depth = std::max(1, next_depth - reduction); - zw_score = zero_width(lmr_depth); - } - - // search again at full depth if necessary - if (!try_lmr || (zw_score > alpha && lmr_depth < next_depth)) { zw_score = zero_width(next_depth); } - - // search again with full window on pv nodes - return (is_pv && (alpha < zw_score && zw_score < beta)) ? full_width() : zw_score; - }(); - - if (score < beta && (mv.is_quiet() || !bd.see_gt(mv, 0))) { moves_tried.push(mv); } - - if (score > best_score) { - best_score = score; - best_move = mv; - if (score > alpha) { - if (score < beta) { alpha = score; } - if constexpr (is_pv) { ss.prepend_to_pv(mv); } - } - } - - if constexpr (is_root) { internal.node_distribution[mv] += (internal.nodes.load(std::memory_order_relaxed) - nodes_before); } - - if (best_score >= beta) { break; } - } - - if (legal_count == 0 && is_check) { return make_result(ss.loss_score(), chess::move::null()); } - if (legal_count == 0) { return make_result(draw_score, chess::move::null()); } - - // step 14. update histories if appropriate and maybe insert a new transposition_table_entry - if (internal.keep_going() && !ss.has_excluded()) { - const bound_type bound = [&] { - if (best_score >= beta) { return bound_type::lower; } - if (is_pv && best_score > original_alpha) { return bound_type::exact; } - return bound_type::upper; - }(); - - if (bound == bound_type::lower && (best_move.is_quiet() || !bd.see_gt(best_move, 0))) { - internal.hh.us(bd.turn()).update(history::context{follow, counter, threatened}, best_move, moves_tried, depth); - ss.set_killer(best_move); - } - - const transposition_table_entry entry(bd.hash(), bound, best_score, best_move, depth); - external.tt->insert(entry); - } - - return make_result(best_score, best_move); - } - - void iterative_deepening_loop() { - internal.reset_cache.reinitialize(external.weights); - nnue::eval_node root_node = nnue::eval_node::clean_node([this] { - nnue::eval result(external.weights, &internal.scratchpad, 0, 0); - internal.stack.root_pos().feature_full_reset(result); - return result; - }()); - - score_type alpha = -big_number; - score_type beta = big_number; - for (; internal.keep_going(); ++internal.depth) { - internal.depth = std::min(max_depth, internal.depth.load()); - // update aspiration window once reasonable evaluation is obtained - if (internal.depth >= external.constants->aspiration_depth()) { - const score_type previous_score = internal.score; - alpha = previous_score - aspiration_delta; - beta = previous_score + aspiration_delta; - } - - score_type delta = aspiration_delta; - depth_type failed_high_count{0}; - - for (;;) { - internal.stack.clear_future(); - - const depth_type adjusted_depth = std::max(1, internal.depth - failed_high_count); - const auto [search_score, search_move] = pv_search( - stack_view::root(internal.stack), root_node, internal.stack.root_pos(), alpha, beta, adjusted_depth, chess::player_type::none); - - if (!internal.keep_going()) { break; } - - // update aspiration window if failing low or high - if (search_score <= alpha) { - beta = (alpha + beta) / 2; - alpha = search_score - delta; - failed_high_count = 0; - } else if (search_score >= beta) { - beta = search_score + delta; - ++failed_high_count; - } else { - // store updated information - internal.score.store(search_score); - if (!search_move.is_null()) { - internal.best_move.store(search_move.data); - internal.ponder_move.store(internal.stack.ponder_move().data); - } - break; - } - - // exponentially grow window - delta += delta / 3; - } - - // callback on iteration completion - if (internal.keep_going()) { external.on_iter(*this); } - } - } - - size_t best_move_percent() const { - constexpr size_t one_hundred = 100; - const auto iter = internal.node_distribution.find(chess::move{internal.best_move}); - return iter != internal.node_distribution.end() ? (one_hundred * iter->second / internal.nodes.load()) : one_hundred; - } - - size_t nodes() const { return internal.nodes.load(); } - size_t tb_hits() const { return internal.tb_hits.load(); } - depth_type depth() const { return internal.depth.load(); } - chess::move best_move() const { return chess::move{internal.best_move.load()}; } - chess::move ponder_move() const { return chess::move{internal.ponder_move.load()}; } - - score_type score() const { return internal.score.load(); } - - void go(const chess::position_history& hist, const chess::board& bd, const depth_type& start_depth) { - internal.go.store(true); - internal.node_distribution.clear(); - internal.nodes.store(0); - internal.tb_hits.store(0); - internal.depth.store(start_depth); - internal.best_move.store(bd.generate_moves<>().begin()->data); - internal.ponder_move.store(chess::move::null().data); - internal.stack = search_stack(hist, bd); - } - - void stop() { internal.go.store(false); } - - search_worker( - const nnue::weights* weights, - std::shared_ptr tt, - std::shared_ptr constants, - std::function on_iter = [](auto&&...) {}, - std::function on_update = [](auto&&...) {}) - : external(weights, tt, constants, on_iter, on_update) {} -}; - -} // namespace search \ No newline at end of file diff --git a/include/search_worker_orchestrator.h b/include/search_worker_orchestrator.h deleted file mode 100644 index 0771b3c..0000000 --- a/include/search_worker_orchestrator.h +++ /dev/null @@ -1,114 +0,0 @@ -/* - Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle - - Seer is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Seer is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#pragma once - -#include - -#include -#include -#include -#include -#include - -namespace search { - -struct worker_orchestrator { - static constexpr size_t primary_id = 0; - - const nnue::weights* weights_; - std::shared_ptr tt_{nullptr}; - std::shared_ptr constants_{nullptr}; - - std::mutex access_mutex_{}; - std::atomic_bool is_searching_{}; - std::vector> workers_{}; - std::vector threads_{}; - - void reset() { - tt_->clear(); - for (auto& worker : workers_) { worker->internal.reset(); }; - } - - void resize(const size_t& new_size) { - constants_->update_(new_size); - const size_t old_size = workers_.size(); - workers_.resize(new_size); - for (size_t i(old_size); i < new_size; ++i) { workers_[i] = std::make_unique(weights_, tt_, constants_); } - } - - void go(const chess::position_history& hist, const chess::board& bd) { - std::lock_guard access_lock(access_mutex_); - std::for_each(workers_.begin(), workers_.end(), [](auto& worker) { worker->stop(); }); - std::for_each(threads_.begin(), threads_.end(), [](auto& thread) { thread.join(); }); - threads_.clear(); - - tt_->update_gen(); - for (size_t i(0); i < workers_.size(); ++i) { - const depth_type start_depth = 1 + static_cast(i % 2); - workers_[i]->go(hist, bd, start_depth); - } - - std::transform(workers_.begin(), workers_.end(), std::back_inserter(threads_), [](auto& worker) { - return std::thread([&worker] { worker->iterative_deepening_loop(); }); - }); - - is_searching_.store(true); - } - - void stop() { - std::lock_guard access_lock(access_mutex_); - std::for_each(workers_.begin(), workers_.end(), [](auto& worker) { worker->stop(); }); - is_searching_.store(false); - } - - bool is_searching() { - std::lock_guard access_lock(access_mutex_); - return is_searching_.load(); - } - - size_t nodes() const { - return std::accumulate( - workers_.begin(), workers_.end(), static_cast(0), [](const size_t& count, const auto& worker) { return count + worker->nodes(); }); - } - - size_t tb_hits() const { - return std::accumulate( - workers_.begin(), workers_.end(), static_cast(0), [](const size_t& count, const auto& worker) { return count + worker->tb_hits(); }); - } - - search_worker& primary_worker() { return *workers_[primary_id]; } - - worker_orchestrator( - const nnue::weights* weights, - size_t hash_table_size, - std::function on_iter = [](auto&&...) {}, - std::function on_update = [](auto&&...) {}) { - weights_ = weights; - tt_ = std::make_shared(hash_table_size); - constants_ = std::make_shared(); - workers_.push_back(std::make_unique(weights, tt_, constants_, on_iter, on_update)); - } - - ~worker_orchestrator() { - std::lock_guard access_lock(access_mutex_); - std::for_each(workers_.begin(), workers_.end(), [](auto& worker) { worker->stop(); }); - std::for_each(threads_.begin(), threads_.end(), [](auto& thread) { thread.join(); }); - } -}; - -} // namespace search \ No newline at end of file diff --git a/include/square.h b/include/square.h deleted file mode 100644 index 8c6f9a3..0000000 --- a/include/square.h +++ /dev/null @@ -1,280 +0,0 @@ -/* - Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle - - Seer is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Seer is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -namespace chess { - -constexpr std::uint64_t pop_count(const std::uint64_t& x) { return static_cast(__builtin_popcountll(x)); } - -constexpr std::uint64_t count_trailing_zeros(const std::uint64_t& x) { return static_cast(__builtin_ctzll(x)); } - -struct square_set; - -struct square { - std::uint64_t data; - - constexpr const std::uint64_t& bit_board() const { return data; } - - constexpr int index() const { - assert((data != 0)); - return count_trailing_zeros(data); - } - - constexpr int file() const { return index() % 8; } - constexpr int rank() const { return index() / 8; } - - constexpr bool operator==(const square& other) const { return other.data == data; } - - constexpr bool operator!=(const square& other) const { return !(*this == other); } - - std::string name() const { - constexpr std::array n_of_file = {'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a'}; - constexpr std::array n_of_rank = {'1', '2', '3', '4', '5', '6', '7', '8'}; - return std::string("") + n_of_file[file()] + n_of_rank[rank()]; - } - - template - constexpr static square from_index(const I& index) { - static_assert(std::is_integral_v, "square index must be of integral type"); - return square(static_cast(1) << static_cast(index)); - } - - constexpr square(const std::uint64_t& bb) : data{bb} {} -}; - -std::ostream& operator<<(std::ostream& ostr, const square& sq) { - std::cout << "square(data=" << sq.data << ")\n"; - constexpr std::uint64_t board_hw = 8; - auto is_set = [sq](std::uint64_t idx) { return static_cast((static_cast(1) << idx) & sq.data); }; - for (std::uint64_t rank{0}; rank < board_hw; ++rank) { - for (std::uint64_t file{0}; file < board_hw; ++file) { - const std::uint64_t idx = rank * board_hw + file; - ostr << (is_set(idx) ? '*' : '.') << ' '; - } - ostr << '\n'; - } - return ostr; -} - -struct delta { - int x{0}; - int y{0}; -}; - -struct tbl_square { - int file{0}; - int rank{0}; - - constexpr std::uint64_t index() const { return static_cast(rank * 8 + file); } - - constexpr bool is_valid() const { return 0 <= file && file < 8 && 0 <= rank && rank < 8; } - - constexpr std::uint64_t bit_board() const { return static_cast(1) << index(); } - - constexpr tbl_square rotated() const { return tbl_square{7 - file, 7 - rank}; } - - constexpr square to_square() const { return square::from_index(index()); } - - constexpr tbl_square add(delta d) const { return tbl_square{file + d.x, rank + d.y}; } - - template - static constexpr tbl_square from_index(I index) { - return tbl_square{static_cast(index) % 8, static_cast(index) / 8}; - } - - constexpr bool operator==(const tbl_square& other) const { return (other.rank) == rank && (other.file == file); } - - constexpr bool operator!=(const tbl_square& other) const { return !(*this == other); } - - static tbl_square from_name(const std::string& name) { return tbl_square{7 - static_cast(name[0] - 'a'), static_cast(name[1] - '1')}; } -}; - -template -inline constexpr bool is_square_v = std::is_same_v || std::is_same_v; - -struct square_set_iterator { - using difference_type = long; - using value_type = square; - using pointer = const square*; - using reference = const square&; - using iterator_category = std::output_iterator_tag; - - std::uint64_t remaining; - - constexpr square_set_iterator& operator++() { - remaining &= (remaining - static_cast(1)); - return *this; - } - - constexpr square_set_iterator operator++(int) { - auto retval = *this; - ++(*this); - return retval; - } - - constexpr bool operator==(const square_set_iterator& other) const { return other.remaining == remaining; } - - constexpr bool operator!=(const square_set_iterator& other) const { return !(*this == other); } - - constexpr square operator*() const { return square{remaining & ~(remaining - static_cast(1))}; } - - constexpr square_set_iterator(const std::uint64_t& set) : remaining{set} {} -}; - -struct square_set { - static constexpr std::uint64_t one = static_cast(1); - using iterator = square_set_iterator; - std::uint64_t data; - - constexpr iterator begin() const { return square_set_iterator(data); } - constexpr iterator end() const { return square_set_iterator(static_cast(0)); } - - constexpr square_set excluding(const square& sq) const { return square_set(data & ~sq.bit_board()); } - - constexpr square_set& insert(const tbl_square& tbl_sq) { - data |= one << static_cast(tbl_sq.index()); - return *this; - } - - constexpr square_set& insert(const square& sq) { - data |= sq.bit_board(); - return *this; - } - - constexpr size_t count() const { return pop_count(data); } - - constexpr bool any() const { return data != 0; } - - constexpr square item() const { return square{data}; } - - constexpr square_set& operator|=(const square_set& other) { - data |= other.data; - return *this; - } - - constexpr square_set& operator&=(const square_set& other) { - data &= other.data; - return *this; - } - - constexpr square_set& operator^=(const square_set& other) { - data ^= other.data; - return *this; - } - - constexpr bool is_member(const square& sq) const { return 0 != (sq.bit_board() & data); } - - constexpr square_set& operator|=(const std::uint64_t& other) { - data |= other; - return *this; - } - - constexpr square_set& operator&=(const std::uint64_t& other) { - data &= other; - return *this; - } - - constexpr square_set& operator^=(const std::uint64_t& other) { - data ^= other; - return *this; - } - - constexpr square_set mirrored() const { - return square_set{ - (data << 56) | ((data << 40) & static_cast(0x00ff000000000000)) | - ((data << 24) & static_cast(0x0000ff0000000000)) | ((data << 8) & static_cast(0x000000ff00000000)) | - ((data >> 8) & static_cast(0x00000000ff000000)) | ((data >> 24) & static_cast(0x0000000000ff0000)) | - ((data >> 40) & static_cast(0x000000000000ff00)) | (data >> 56)}; - } - - template - constexpr bool occ(const I& idx) const { - static_assert(std::is_integral_v, "idx must be of integral type"); - return static_cast(data & (one << static_cast(idx))); - } - - template - static constexpr square_set of(Ts&& ... ts) { - auto bit_board = [](auto&& sq) { return sq.bit_board(); }; - auto bit_wise_or = [](auto&& ... args) { return (args | ...); }; - return square_set(bit_wise_or(bit_board(std::forward(ts))...)); - } - - constexpr square_set() : data{0} {} - constexpr square_set(const std::uint64_t& set) : data{set} {} -}; - -constexpr square_set operator~(const square_set& ss) { return square_set(~ss.data); } - -constexpr square_set operator&(const square_set& a, const square_set& b) { return square_set(a.data & b.data); } - -constexpr square_set operator|(const square_set& a, const square_set& b) { return square_set(a.data | b.data); } - -constexpr square_set operator^(const square_set& a, const square_set& b) { return square_set(a.data ^ b.data); } - -std::ostream& operator<<(std::ostream& ostr, const square_set& ss) { - std::cout << "square_set(data=" << ss.data << ")\n"; - constexpr std::uint64_t board_hw = 8; - for (std::uint64_t rank{0}; rank < board_hw; ++rank) { - for (std::uint64_t file{0}; file < board_hw; ++file) { - const std::uint64_t idx = rank * board_hw + file; - ostr << (ss.occ(idx) ? '*' : '.') << ' '; - } - ostr << '\n'; - } - return ostr; -} - -template -constexpr void over_all(F&& f) { - for (int i(0); i < 8; ++i) { - for (int j(0); j < 8; ++j) { f(tbl_square{i, j}); } - } -} - -template -constexpr void over_rank(int rank, F&& f) { - for (auto sq = tbl_square{0, rank}; sq.is_valid(); sq = sq.add(delta{1, 0})) { f(sq); } -} - -template -constexpr void over_file(int file, F&& f) { - for (auto sq = tbl_square{file, 0}; sq.is_valid(); sq = sq.add(delta{0, 1})) { f(sq); } -} - -constexpr square_set gen_rank(int rank) { - square_set ss{}; - over_rank(rank, [&ss](tbl_square& sq) { ss.insert(sq); }); - return ss; -} - -constexpr square_set gen_file(int file) { - square_set ss{}; - over_file(file, [&ss](tbl_square& sq) { ss.insert(sq); }); - return ss; -} - -} // namespace chess diff --git a/include/syzygy.h b/include/syzygy.h deleted file mode 100644 index f9e8b5d..0000000 --- a/include/syzygy.h +++ /dev/null @@ -1,100 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include - -namespace syzygy { - -enum class wdl_type { loss, draw, win }; - -struct tb_wdl_result { - bool success; - wdl_type wdl{wdl_type::draw}; - - static constexpr tb_wdl_result failure() { return tb_wdl_result{false}; } - static constexpr tb_wdl_result from_value(const unsigned int& value) { - if (value == TB_WIN) { return tb_wdl_result{true, wdl_type::win}; } - if (value == TB_DRAW) { return tb_wdl_result{true, wdl_type::draw}; } - if (value == TB_LOSS) { return tb_wdl_result{true, wdl_type::loss}; } - return failure(); - } -}; - -struct tb_dtz_result { - bool success; - search::score_type score{search::draw_score}; - chess::move move{chess::move::null()}; - - static constexpr tb_dtz_result failure() { return tb_dtz_result{false}; } - static tb_dtz_result from_value(const chess::board& bd, const unsigned int& value) { - auto is_same_promo = [](const chess::move& mv, const int& promo) { - constexpr int num_pieces = 6; - return ((!mv.is_promotion() && promo == 0) || (mv.is_promotion() && (num_pieces - promo - 1) == static_cast(mv.promotion()))); - }; - - if (value == TB_RESULT_FAILED || value == TB_RESULT_CHECKMATE || value == TB_RESULT_STALEMATE) { failure(); } - const int wdl = TB_GET_WDL(value); - - const chess::move dtz_move = [&] { - const chess::move_list list = bd.generate_moves<>(); - const int promo = TB_GET_PROMOTES(value); - const int from = TB_GET_FROM(value); - const int to = TB_GET_TO(value); - const auto it = std::find_if(list.begin(), list.end(), [&](const chess::move& mv) { - return mv.from().index() == from && mv.to().index() == to && is_same_promo(mv, promo); - }); - if (it != list.end()) { return *it; } - return chess::move::null(); - }(); - - if (dtz_move == chess::move::null()) { return failure(); } - - if (wdl == TB_WIN) { return tb_dtz_result{true, search::tb_win_score, dtz_move}; } - if (wdl == TB_LOSS) { return tb_dtz_result{true, search::tb_loss_score, dtz_move}; } - return tb_dtz_result{true, search::draw_score, dtz_move}; - } -}; - -tb_wdl_result probe_wdl(const chess::board& bd) { - if (bd.num_pieces() > TB_LARGEST || bd.lat_.half_clock != 0) { return tb_wdl_result::failure(); } - if (bd.lat_.white.oo() || bd.lat_.white.ooo() || bd.lat_.black.oo() || bd.lat_.black.ooo()) { return tb_wdl_result::failure(); } - - constexpr unsigned int rule_50 = 0; - constexpr unsigned int castling_rights = 0; - const unsigned int ep = bd.lat_.them(bd.turn()).ep_mask().any() ? bd.lat_.them(bd.turn()).ep_mask().item().index() : 0; - const bool turn = bd.turn(); - - const unsigned value = tb_probe_wdl( - bd.man_.white.all().data, bd.man_.black.all().data, (bd.man_.white.king() | bd.man_.black.king()).data, - (bd.man_.white.queen() | bd.man_.black.queen()).data, (bd.man_.white.rook() | bd.man_.black.rook()).data, - (bd.man_.white.bishop() | bd.man_.black.bishop()).data, (bd.man_.white.knight() | bd.man_.black.knight()).data, - (bd.man_.white.pawn() | bd.man_.black.pawn()).data, rule_50, castling_rights, ep, turn); - - return tb_wdl_result::from_value(value); -} - -tb_dtz_result probe_dtz(const chess::board& bd) { - if (bd.num_pieces() > TB_LARGEST) { return tb_dtz_result::failure(); } - if (bd.lat_.white.oo() || bd.lat_.white.ooo() || bd.lat_.black.oo() || bd.lat_.black.ooo()) { return tb_dtz_result::failure(); } - - const unsigned int rule_50 = bd.lat_.half_clock; - constexpr unsigned int castling_rights = 0; - const unsigned int ep = bd.lat_.them(bd.turn()).ep_mask().any() ? bd.lat_.them(bd.turn()).ep_mask().item().index() : 0; - const bool turn = bd.turn(); - - const unsigned value = tb_probe_root( - bd.man_.white.all().data, bd.man_.black.all().data, (bd.man_.white.king() | bd.man_.black.king()).data, - (bd.man_.white.queen() | bd.man_.black.queen()).data, (bd.man_.white.rook() | bd.man_.black.rook()).data, - (bd.man_.white.bishop() | bd.man_.black.bishop()).data, (bd.man_.white.knight() | bd.man_.black.knight()).data, - (bd.man_.white.pawn() | bd.man_.black.pawn()).data, rule_50, castling_rights, ep, turn, nullptr); - - return tb_dtz_result::from_value(bd, value); -} - -void init(const std::string& path) { tb_init(path.c_str()); } - -} // namespace syzygy \ No newline at end of file diff --git a/include/time_manager.h b/include/time_manager.h deleted file mode 100644 index 398fbbb..0000000 --- a/include/time_manager.h +++ /dev/null @@ -1,273 +0,0 @@ -/* - Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle - - Seer is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Seer is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#pragma once - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace engine { - -namespace go { - -struct wtime { - static constexpr std::string_view name = "wtime"; - using type = int; -}; - -struct btime { - static constexpr std::string_view name = "btime"; - using type = int; -}; - -struct winc { - static constexpr std::string_view name = "winc"; - using type = int; -}; - -struct binc { - static constexpr std::string_view name = "binc"; - using type = int; -}; - -struct movetime { - static constexpr std::string_view name = "movetime"; - using type = int; -}; - -struct movestogo { - static constexpr std::string_view name = "movestogo"; - using type = int; -}; - -struct depth { - static constexpr std::string_view name = "depth"; - using type = search::depth_type; -}; - -struct nodes { - static constexpr std::string_view name = "nodes"; - using type = size_t; -}; - -struct ponder { - static constexpr std::string_view name = "ponder"; -}; - -struct infinite { - static constexpr std::string_view name = "infinite"; -}; - -} // namespace go - -template -struct named_numeric_param { - static constexpr std::string_view name_ = N::name; - std::optional data_{std::nullopt}; - - constexpr std::string_view name() const { return name_; } - std::optional data() const { return data_; } - void set_data(const typename N::type& value) { data_ = value; } - void clear_data() { data_ = std::nullopt; } - - void read(const std::string& line) { - std::regex pattern(".*" + std::string(name_) + " ([-+]?[0-9]+).*"); - std::smatch matches{}; - if (std::regex_search(line, matches, pattern)) { - typename N::type val{}; - std::stringstream(matches.str(1)) >> val; - data_ = val; - } else { - data_ = std::nullopt; - } - } -}; - -template -struct named_condition { - static constexpr std::string_view name_ = N::name; - bool data_{false}; - - constexpr std::string_view name() const { return name_; } - bool data() const { return data_; } - void set_data(const bool& value) { data_ = value; } - - void read(const std::string& line) { - std::regex pattern(".*" + std::string(name_) + ".*"); - std::smatch matches{}; - data_ = (std::regex_search(line, matches, pattern)); - } -}; - -struct update_info { - size_t nodes; -}; - -struct iter_info { - search::depth_type depth; - size_t best_move_percent; -}; - -struct time_manager { - std::mutex access_mutex_; - - std::tuple< - named_numeric_param, - named_numeric_param, - named_numeric_param, - named_numeric_param, - named_numeric_param, - named_numeric_param, - named_numeric_param, - named_numeric_param, - named_condition, - named_condition > - params_{}; - - std::chrono::steady_clock::time_point search_start{}; - std::optional min_budget{}; - std::optional max_budget{}; - - std::chrono::milliseconds elapsed() const { - return std::chrono::duration_cast(std::chrono::steady_clock::now() - search_start); - } - - template - auto& get() { - constexpr std::string_view name_idx = std::tuple_element::type::name_; - if constexpr (name_idx == N::name) { - return std::get(params_); - } else { - return get(); - } - } - - bool is_pondering() { return get().data(); } - - void ponder_hit() { - std::lock_guard access_lk(access_mutex_); - search_start = std::chrono::steady_clock::now(); - get().set_data(false); - } - - std::chrono::milliseconds our_time(const bool& pov) { - const auto x = pov ? get().data() : get().data(); - return std::chrono::milliseconds(x.value_or(10000)); - } - - std::chrono::milliseconds our_increment(const bool& pov) { - const auto x = pov ? get().data() : get().data(); - return std::chrono::milliseconds(x.value_or(0)); - } - - time_manager& read(const std::string& line) { - util::apply(params_, [&line](auto& param) { param.read(line); }); - return *this; - } - - time_manager& init(const bool& pov, const std::string& line) { - constexpr auto over_head = std::chrono::milliseconds(5); - - std::lock_guard access_lk(access_mutex_); - search_start = std::chrono::steady_clock::now(); - read(line); - - if (get().data().has_value() || get().data()) { - min_budget = std::nullopt; - max_budget = std::nullopt; - } else if (get().data().has_value()) { - min_budget = std::nullopt; - max_budget = std::chrono::milliseconds(*get().data()); - } else { - const auto remaining = our_time(pov); - const auto inc = our_increment(pov); - - if (get().data().has_value()) { - // handle cyclical time controls (x / y + z) - const go::movestogo::type moves_to_go = *get().data(); - min_budget = 2 * (remaining - over_head) / (3 * moves_to_go) + inc; - max_budget = 10 * (remaining - over_head) / (3 * moves_to_go) + inc; - } else { - // handle incremental time controls (x + z) - min_budget = (remaining - over_head + 25 * inc) / 25; - max_budget = (remaining - over_head + 25 * inc) / 10; - } - - // avoid time losses by capping budget to 4/5 remaining time - min_budget = std::min(4 * (remaining - over_head) / 5, *min_budget); - max_budget = std::min(4 * (remaining - over_head) / 5, *max_budget); - } - - return *this; - } - - bool should_stop_on_update(const update_info& info) { - std::lock_guard access_lk(access_mutex_); - if (get().data()) { return false; } - if (get().data()) { return false; } - if (get().data().has_value() && info.nodes >= *get().data()) { return true; } - if (max_budget.has_value() && elapsed() >= *max_budget) { return true; }; - return false; - } - - bool should_stop_on_iter(const iter_info& info) { - constexpr size_t numerator = 50; - constexpr size_t min_percent = 20; - - std::lock_guard access_lk(access_mutex_); - if (get().data()) { return false; } - if (get().data()) { return false; } - - if (info.depth >= search::max_depth) { return true; } - if (max_budget.has_value() && elapsed() >= *max_budget) { return true; } - if (min_budget.has_value() && elapsed() >= (*min_budget * numerator / std::max(info.best_move_percent, min_percent))) { return true; } - if (get().data().has_value() && info.depth >= *get().data()) { return true; } - return false; - } -}; - -template -struct simple_timer { - std::mutex start_mutex_; - std::chrono::steady_clock::time_point start_; - - T elapsed() { - std::lock_guard start_lk(start_mutex_); - return std::chrono::duration_cast(std::chrono::steady_clock::now() - start_); - } - - simple_timer& lap() { - std::lock_guard start_lk(start_mutex_); - start_ = std::chrono::steady_clock::now(); - return *this; - } - - simple_timer() : start_{std::chrono::steady_clock::now()} {} -}; - -} // namespace engine diff --git a/include/transposition_table.h b/include/transposition_table.h deleted file mode 100644 index f84ac4a..0000000 --- a/include/transposition_table.h +++ /dev/null @@ -1,179 +0,0 @@ -/* - Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle - - Seer is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Seer is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#pragma once - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace search { - -constexpr size_t cache_line_size = 64; -enum class bound_type { upper, lower, exact }; - -struct transposition_table_entry { - static constexpr zobrist::hash_type empty_key = zobrist::hash_type{}; - static constexpr size_t gen_bits = 7; - using gen_type = std::uint8_t; - - using bound_ = bit::range; - using score_ = bit::next_range; - using best_move_ = bit::next_range; - using depth_ = bit::next_range; - using gen_ = bit::next_range; - using was_exact_or_lb_ = bit::next_flag; - - zobrist::hash_type key_{empty_key}; - zobrist::hash_type value_{}; - - zobrist::hash_type key() const { return key_ ^ value_; } - - bound_type bound() const { return bound_::get(value_); } - score_type score() const { return static_cast(score_::get(value_)); } - gen_type gen() const { return gen_::get(value_); } - depth_type depth() const { return static_cast(depth_::get(value_)); } - chess::move best_move() const { return chess::move{best_move_::get(value_)}; } - bool was_exact_or_lb() const { return was_exact_or_lb_::get(value_); } - - bool is_empty() const { return key_ == empty_key; } - - bool is_current(const gen_type& gen) const { return gen == gen_::get(value_); } - - transposition_table_entry& set_gen(const gen_type& gen) { - key_ ^= value_; - gen_::set(value_, gen); - key_ ^= value_; - return *this; - } - - transposition_table_entry& merge(const transposition_table_entry& other) { - if (bound() == bound_type::upper && other.was_exact_or_lb() && key() == other.key()) { - key_ ^= value_; - best_move_::set(value_, other.best_move().data); - was_exact_or_lb_::set(value_, true); - key_ ^= value_; - } - - return *this; - } - - transposition_table_entry( - const zobrist::hash_type& key, const bound_type& bound, const score_type& score, const chess::move& mv, const depth_type& depth) - : key_{key} { - bound_::set(value_, bound); - score_::set(value_, static_cast(score)); - best_move_::set(value_, mv.data); - depth_::set(value_, static_cast(depth)); - was_exact_or_lb_::set(value_, bound != bound_type::upper); - key_ ^= value_; - } - - transposition_table_entry() {} -}; - -template -struct alignas(cache_line_size) bucket { - transposition_table_entry data[N]; - - std::optional match(const transposition_table_entry::gen_type& gen, const zobrist::hash_type& key) { - for (auto& elem : data) { - if (elem.key() == key) { return std::optional(elem.set_gen(gen)); } - } - return std::nullopt; - } - - transposition_table_entry* to_replace(const transposition_table_entry::gen_type& gen, const zobrist::hash_type& key) { - auto worst = std::begin(data); - for (auto iter = std::begin(data); iter != std::end(data); ++iter) { - if (iter->key() == key) { return iter; } - - const bool is_worse = (!iter->is_current(gen) && worst->is_current(gen)) || (iter->is_empty() && !worst->is_empty()) || - ((iter->is_current(gen) == worst->is_current(gen)) && (iter->depth() < worst->depth())); - - if (is_worse) { worst = iter; } - } - - return worst; - } -}; - -struct transposition_table { - static constexpr size_t per_bucket = cache_line_size / sizeof(transposition_table_entry); - static constexpr size_t one_mb = (1 << 20) / cache_line_size; - - using bucket_type = bucket; - - static_assert(cache_line_size % sizeof(transposition_table_entry) == 0, "transposition_table_entry must divide cache_line_size"); - static_assert(sizeof(bucket_type) == cache_line_size && alignof(bucket_type) == cache_line_size, "bucket_type must be cache_line_size aligned"); - - std::atomic current_gen{0}; - std::vector data; - - void prefetch(const zobrist::hash_type& key) const { __builtin_prefetch(data.data() + hash_function(key)); } - - void clear() { - for (auto& elem : data) { elem = bucket_type{}; } - } - - void resize(size_t size) { - clear(); - data.resize(size * one_mb, bucket_type{}); - } - - void update_gen() { - using gen_type = transposition_table_entry::gen_type; - constexpr gen_type limit = gen_type{1} << transposition_table_entry::gen_bits; - current_gen = (current_gen + 1) % limit; - } - - size_t hash_function(const zobrist::hash_type& hash) const { return hash % data.size(); } - - __attribute__((no_sanitize("thread"))) transposition_table& insert(const transposition_table_entry& entry) { - constexpr depth_type offset = 2; - const transposition_table_entry::gen_type gen = current_gen.load(std::memory_order_relaxed); - - transposition_table_entry* to_replace = data[hash_function(entry.key())].to_replace(gen, entry.key()); - - const bool should_replace = - (entry.bound() == bound_type::exact) || (entry.key() != to_replace->key()) || ((entry.depth() + offset) >= to_replace->depth()); - - if (should_replace) { *to_replace = transposition_table_entry(entry).set_gen(gen).merge(*to_replace); } - - return *this; - } - - __attribute__((no_sanitize("thread"))) std::optional find(const zobrist::hash_type& key) { - const transposition_table_entry::gen_type gen = current_gen.load(std::memory_order_relaxed); - return data[hash_function(key)].match(gen, key); - } - - transposition_table(size_t size) : data(size * one_mb) {} -}; - -} // namespace search diff --git a/include/uci.h b/include/uci.h deleted file mode 100644 index 4b4257e..0000000 --- a/include/uci.h +++ /dev/null @@ -1,295 +0,0 @@ -/* - Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle - - Seer is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Seer is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace engine { - -struct uci { - static constexpr size_t default_thread_count = 1; - static constexpr size_t default_hash_size = 16; - static constexpr std::string_view default_weight_path = "EMBEDDED"; - static constexpr std::string_view default_syzygy_path = ""; - static constexpr bool default_ponder = false; - - chess::position_history history{}; - chess::board position = chess::board::start_pos(); - - nnue::weights weights_{}; - search::worker_orchestrator orchestrator_; - - std::atomic_bool ponder_{false}; - std::atomic_bool should_quit_{false}; - - time_manager manager_; - simple_timer timer_; - - std::mutex os_mutex_{}; - std::ostream& os = std::cout; - - bool should_quit() const { return should_quit_.load(); } - - void weights_info_string() { - std::lock_guard os_lk(os_mutex_); - os << "info string loaded weights with signature 0x" << std::hex << weights_.signature() << std::dec << std::endl; - } - - auto options() { - auto weight_path = option_callback(string_option("Weights", std::string(default_weight_path)), [this](const std::string& path) { - if (path == std::string(default_weight_path)) { - nnue::embedded_weight_streamer embedded(embed::weights_file_data); - weights_.load(embedded); - } else { - weights_.load(path); - } - weights_info_string(); - }); - - auto hash_size = option_callback(spin_option("Hash", default_hash_size, spin_range{1, 262144}), [this](const int size) { - const auto new_size = static_cast(size); - orchestrator_.tt_->resize(new_size); - }); - - auto thread_count = option_callback(spin_option("Threads", default_thread_count, spin_range{1, 512}), [this](const int count) { - const auto new_count = static_cast(count); - orchestrator_.resize(new_count); - }); - - auto ponder = option_callback(check_option("Ponder", default_ponder), [this](const bool& value) { ponder_.store(value); }); - - auto syzygy_path = option_callback(string_option("SyzygyPath", std::string(default_syzygy_path)), [](const std::string& path) { syzygy::init(path); }); - - return uci_options(weight_path, hash_size, thread_count, ponder, syzygy_path); - } - - void uci_new_game() { - history.clear(); - position = chess::board::start_pos(); - orchestrator_.reset(); - } - - void set_position(const std::string& line) { - const std::regex startpos_with_moves("position startpos moves((?: [a-h][1-8][a-h][1-8]+(?:q|r|b|n)?)+)"); - const std::regex fen_with_moves("position fen (.*) moves((?: [a-h][1-8][a-h][1-8]+(?:q|r|b|n)?)+)"); - const std::regex startpos("position startpos"); - const std::regex fen("position fen (.*)"); - std::smatch matches{}; - - if (std::regex_search(line, matches, startpos_with_moves)) { - auto [h_, p_] = chess::board::start_pos().after_uci_moves(matches.str(1)); - history = h_; - position = p_; - } else if (std::regex_search(line, matches, fen_with_moves)) { - position = chess::board::parse_fen(matches.str(1)); - auto [h_, p_] = position.after_uci_moves(matches.str(2)); - history = h_; - position = p_; - } else if (std::regex_search(line, matches, startpos)) { - history.clear(); - position = chess::board::start_pos(); - } else if (std::regex_search(line, matches, fen)) { - history.clear(); - position = chess::board::parse_fen(matches.str(1)); - } - } - - template - void info_string(const T& worker) { - constexpr search::score_type raw_multiplier = 288; - constexpr search::score_type raw_divisor = 1024; - - std::lock_guard os_lk(os_mutex_); - - const search::score_type raw_score = worker.score(); - const search::score_type score = raw_score * raw_multiplier / raw_divisor; - - const search::depth_type depth = worker.depth(); - const size_t elapsed_ms = timer_.elapsed().count(); - const size_t nodes = orchestrator_.nodes(); - const size_t tb_hits = orchestrator_.tb_hits(); - const size_t nps = std::chrono::milliseconds(std::chrono::seconds(1)).count() * nodes / (1 + elapsed_ms); - - const bool should_report = orchestrator_.is_searching() && depth < search::max_depth; - if (should_report) { - os << "info depth " << depth << " seldepth " << worker.internal.stack.sel_depth() << " score cp " << score << " nodes " << nodes << " nps " - << nps << " time " << elapsed_ms << " tbhits " << tb_hits << " pv " << worker.internal.stack.pv_string() << std::endl; - } - } - - void go(const std::string& line) { - manager_.init(position.turn(), line); - timer_.lap(); - orchestrator_.go(history, position); - } - - void ponder_hit() { manager_.ponder_hit(); } - - void stop() { - std::lock_guard os_lk(os_mutex_); - - orchestrator_.stop(); - const chess::move best_move = orchestrator_.primary_worker().best_move(); - const chess::move ponder_move = orchestrator_.primary_worker().ponder_move(); - - const std::string ponder_move_string = [&] { - if (!position.forward(best_move).is_legal(ponder_move)) { return std::string{}; } - return std::string(" ponder ") + ponder_move.name(position.forward(best_move).turn()); - }(); - - os << "bestmove " << best_move.name(position.turn()) << ponder_move_string << std::endl; - } - - void ready() { - std::lock_guard os_lk(os_mutex_); - os << "readyok" << std::endl; - } - - void id_info() { - std::lock_guard os_lk(os_mutex_); - os << "id name " << version::engine_name << " " << version::major << '.' << version::minor << '.' << version::patch << std::endl; - os << "id author " << version::author_name << std::endl; - os << options(); - if constexpr (search::search_constants::tuning) { os << orchestrator_.constants_->options(); } - - os << "uciok" << std::endl; - } - - void bench() { - std::lock_guard os_lk(os_mutex_); - os << get_bench_info(weights_) << std::endl; - } - - void eval() { - std::lock_guard os_lk(os_mutex_); - - auto scratchpad = std::make_unique(); - auto evaluator = nnue::eval(&weights_, scratchpad.get(), 0, 0); - position.feature_full_reset(evaluator); - - os << "phase: " << position.phase() << std::endl; - os << "score(phase): " << evaluator.evaluate(position.turn(), position.phase()) << std::endl; - } - - void probe() { - std::lock_guard os_lk(os_mutex_); - if (position.is_rule50_draw()) { - std::cout << "rule 50 draw" << std::endl; - } else if (const syzygy::tb_wdl_result result = syzygy::probe_wdl(position); result.success) { - std::cout << "success: " << [&] { - switch (result.wdl) { - case syzygy::wdl_type::loss: return "loss"; - case syzygy::wdl_type::draw: return "draw"; - case syzygy::wdl_type::win: return "win"; - default: return "unknown"; - } - }() << std::endl; - } else { - std::cout << "fail" << std::endl; - } - } - - void perft(const std::string& line) { - std::lock_guard os_lk(os_mutex_); - const std::regex perft_with_depth("perft ([0-9]+)"); - if (std::smatch matches{}; std::regex_search(line, matches, perft_with_depth)) { - const search::depth_type depth = std::stoi(matches.str(1)); - std::cout << get_perft_info(position, depth) << std::endl; - } - } - - void quit() { should_quit_.store(true); } - - void read(const std::string& line) { - const std::regex position_rgx("position(.*)"); - const std::regex go_rgx("go(.*)"); - const std::regex perft_rgx("perft(.*)"); - - const bool is_searching = orchestrator_.is_searching(); - if (!is_searching && line == "uci") { - id_info(); - } else if (line == "isready") { - ready(); - } else if (!is_searching && line == "ucinewgame") { - uci_new_game(); - } else if (is_searching && line == "stop") { - stop(); - } else if (is_searching && line == "ponderhit") { - ponder_hit(); - } else if (line == "_internal_board") { - os << position << std::endl; - } else if (!is_searching && line == "bench") { - bench(); - } else if (!is_searching && line == "eval") { - eval(); - } else if (!is_searching && line == "probe") { - probe(); - } else if (!is_searching && std::regex_match(line, perft_rgx)) { - perft(line); - } else if (!is_searching && std::regex_match(line, go_rgx)) { - go(line); - } else if (!is_searching && std::regex_match(line, position_rgx)) { - set_position(line); - } else if (line == "quit") { - quit(); - } else if (!is_searching) { - options().update(line); - if constexpr (search::search_constants::tuning) { orchestrator_.constants_->options().update(line); } - } - } - - uci() - : orchestrator_( - &weights_, - default_hash_size, - [this](const auto& worker) { - info_string(worker); - if (manager_.should_stop_on_iter(iter_info{worker.depth(), worker.best_move_percent()})) { stop(); } - }, - [this](const auto& worker) { - if (manager_.should_stop_on_update(update_info{worker.nodes()})) { stop(); } - }) { - nnue::embedded_weight_streamer embedded(embed::weights_file_data); - weights_.load(embedded); - orchestrator_.resize(default_thread_count); - } -}; - -} // namespace engine diff --git a/include/bit_range.h b/include/util/bit_range.h similarity index 72% rename from include/bit_range.h rename to include/util/bit_range.h index ae2e1c5..d40ce3a 100644 --- a/include/bit_range.h +++ b/include/util/bit_range.h @@ -1,6 +1,6 @@ /* Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle Seer is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,20 +17,21 @@ #pragma once +#include #include #include -namespace bit { +namespace util { -template -struct range { +template +struct bit_range { static_assert(B0 < B1, "wrong bit order"); using type = T; - static constexpr size_t first = B0; - static constexpr size_t last = B1; + static constexpr std::size_t first = B0; + static constexpr std::size_t last = B1; template - static constexpr T get(const I& i) { + static constexpr T get(const I& i) noexcept { constexpr int num_bits = 8 * sizeof(I); static_assert(B1 < num_bits, "integral type accessed by bit::range::get has insufficient bits"); constexpr I one = static_cast(1); @@ -41,7 +42,7 @@ struct range { } template - static constexpr void set(I& i, const T& info) { + static constexpr void set(I& i, const T& info) noexcept { constexpr int num_bits = 8 * sizeof(I); static_assert(B1 < num_bits, "integral type accessed by bit::range::set has insufficient bits"); constexpr I one = static_cast(1); @@ -54,13 +55,13 @@ struct range { } }; -template -using flag = range; +template +using bit_flag = bit_range; -template -using next_flag = flag; +template +using next_bit_flag = bit_flag; -template -using next_range = range; +template +using next_bit_range = bit_range; -} // namespace bit +} // namespace util diff --git a/include/apply.h b/include/util/string.h similarity index 59% rename from include/apply.h rename to include/util/string.h index 6d1d3c0..db153b3 100644 --- a/include/apply.h +++ b/include/util/string.h @@ -1,6 +1,6 @@ /* Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle Seer is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,23 +18,19 @@ #pragma once #include +#include +#include +#include #include #include -namespace util { +namespace util::string { -template -constexpr void apply_impl(T&& data, F&& f, std::index_sequence) { - auto map = [&f](auto&& x) { - f(x); - return 0; - }; - [[maybe_unused]] const auto ignored = {map(std::get(data))...}; +template +[[nodiscard]] std::string join(const T& first, const T& last, const std::string& separator) noexcept { + std::ostringstream result{}; + std::copy(first, last, std::ostream_iterator(result, separator.c_str())); + return result.str(); } -template -constexpr void apply(T&& data, F&& f) { - apply_impl(std::forward(data), std::forward(f), std::make_index_sequence>>{}); -} - -} // namespace util \ No newline at end of file +} // namespace util::string \ No newline at end of file diff --git a/include/util/tuple.h b/include/util/tuple.h new file mode 100644 index 0000000..a1ed1bc --- /dev/null +++ b/include/util/tuple.h @@ -0,0 +1,62 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +#include +#include +#include + +namespace util::tuple { + +template +constexpr void for_each_impl(T&& data, F&& f, std::index_sequence) noexcept { + auto map = [&f](auto&& x) { + f(x); + return 0; + }; + + [[maybe_unused]] const auto ignored = {map(std::get(data))...}; +} + +template +constexpr void for_each(T&& data, F&& f) noexcept { + for_each_impl(std::forward(data), std::forward(f), std::make_index_sequence>>{}); +} + +template +constexpr std::tuple tail_impl(const std::tuple& tuple, std::index_sequence) noexcept { + return std::tuple{std::get(tuple)...}; +} + +template +constexpr std::tuple tail(const std::tuple& tuple) noexcept { + const auto sequence = std::make_index_sequence>>{}; + return tail_impl(tuple, sequence); +} + +template +constexpr T head(const std::tuple& tuple) noexcept { + return std::get<0>(tuple); +} + +template +constexpr std::tuple append(const std::tuple& tuple, const As&... args) noexcept { + return std::tuple_cat(tuple, std::tuple{args...}); +} + +} // namespace util::tuple \ No newline at end of file diff --git a/include/util/unreachable.h b/include/util/unreachable.h new file mode 100644 index 0000000..cbf5625 --- /dev/null +++ b/include/util/unreachable.h @@ -0,0 +1,24 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#pragma once + +namespace util { + +inline void unreachable() { __builtin_unreachable(); } + +} // namespace util \ No newline at end of file diff --git a/include/zobrist_util.h b/include/zobrist/util.h similarity index 97% rename from include/zobrist_util.h rename to include/zobrist/util.h index c640359..8b9af35 100644 --- a/include/zobrist_util.h +++ b/include/zobrist/util.h @@ -1,6 +1,6 @@ /* Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle Seer is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -32,7 +32,6 @@ constexpr std::mt19937::result_type seed = 0x019ec6dc; constexpr half_hash_type lower_half(const hash_type& hash) { return hash & std::numeric_limits::max(); } constexpr half_hash_type upper_half(const hash_type& hash) { return (hash >> 32) & std::numeric_limits::max(); } - inline hash_type random_bit_string() { static std::mt19937 gen(seed); constexpr hash_type a = std::numeric_limits::min(); diff --git a/src/chess/board.cc b/src/chess/board.cc new file mode 100644 index 0000000..4e17feb --- /dev/null +++ b/src/chess/board.cc @@ -0,0 +1,896 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace chess { + +template +constexpr T material_value(const piece_type& pt) { + constexpr std::array values = {100, 300, 300, 450, 900, std::numeric_limits::max()}; + return values[static_cast(pt)]; +} + +template +constexpr T phase_value(const piece_type& pt) { + constexpr std::array values = {0, 1, 1, 2, 4, 0}; + return values[static_cast(pt)]; +} + +template +std::tuple board::least_valuable_attacker(const square& tgt, const square_set& ignore) const noexcept { + const square_set p_mask = pawn_attack_tbl>.look_up(tgt); + const square_set p_attackers = p_mask & man_.us().pawn() & ~ignore; + if (p_attackers.any()) { return std::tuple(piece_type::pawn, *p_attackers.begin()); } + + const square_set n_mask = knight_attack_tbl.look_up(tgt); + const square_set n_attackers = n_mask & man_.us().knight() & ~ignore; + if (n_attackers.any()) { return std::tuple(piece_type::knight, *n_attackers.begin()); } + + const square_set occ = (man_.white.all() | man_.black.all()) & ~ignore; + + const square_set b_mask = bishop_attack_tbl.look_up(tgt, occ); + const square_set b_attackers = b_mask & man_.us().bishop() & ~ignore; + if (b_attackers.any()) { return std::tuple(piece_type::bishop, *b_attackers.begin()); } + + const square_set r_mask = rook_attack_tbl.look_up(tgt, occ); + const square_set r_attackers = r_mask & man_.us().rook() & ~ignore; + if (r_attackers.any()) { return std::tuple(piece_type::rook, *r_attackers.begin()); } + + const square_set q_mask = b_mask | r_mask; + const square_set q_attackers = q_mask & man_.us().queen() & ~ignore; + if (q_attackers.any()) { return std::tuple(piece_type::queen, *q_attackers.begin()); } + + const square_set k_mask = king_attack_tbl.look_up(tgt); + const square_set k_attackers = k_mask & man_.us().king() & ~ignore; + if (k_attackers.any()) { return std::tuple(piece_type::king, *k_attackers.begin()); } + + return std::tuple(piece_type::pawn, tgt); +} + +template +inline std::tuple board::checkers(const square_set& occ) const noexcept { + const square_set b_check_mask = bishop_attack_tbl.look_up(man_.us().king().item(), occ); + const square_set r_check_mask = rook_attack_tbl.look_up(man_.us().king().item(), occ); + const square_set n_check_mask = knight_attack_tbl.look_up(man_.us().king().item()); + const square_set p_check_mask = pawn_attack_tbl.look_up(man_.us().king().item()); + const square_set q_check_mask = b_check_mask | r_check_mask; + + const square_set b_checkers = (b_check_mask & (man_.them().bishop() | man_.them().queen())); + const square_set r_checkers = (r_check_mask & (man_.them().rook() | man_.them().queen())); + + square_set checker_rays_{}; + for (const auto sq : b_checkers) { checker_rays_ |= bishop_attack_tbl.look_up(sq, occ) & b_check_mask; } + for (const auto sq : r_checkers) { checker_rays_ |= rook_attack_tbl.look_up(sq, occ) & r_check_mask; } + + const auto checkers_ = (b_check_mask & man_.them().bishop() & occ) | (r_check_mask & man_.them().rook() & occ) | + (n_check_mask & man_.them().knight() & occ) | (p_check_mask & man_.them().pawn() & occ) | + (q_check_mask & man_.them().queen() & occ); + return std::tuple(checkers_, checker_rays_); +} + +template +inline square_set board::threat_mask() const noexcept { + // idea from koivisto + const square_set occ = man_.white.all() | man_.black.all(); + + square_set threats{}; + square_set vulnerable = man_.them().all(); + + vulnerable &= ~man_.them().pawn(); + square_set pawn_attacks{}; + for (const auto sq : man_.us().pawn()) { pawn_attacks |= pawn_attack_tbl.look_up(sq); } + threats |= pawn_attacks & vulnerable; + + vulnerable &= ~(man_.them().knight() | man_.them().bishop()); + square_set minor_attacks{}; + for (const auto sq : man_.us().knight()) { minor_attacks |= knight_attack_tbl.look_up(sq); } + for (const auto sq : man_.us().bishop()) { minor_attacks |= bishop_attack_tbl.look_up(sq, occ); } + threats |= minor_attacks & vulnerable; + + vulnerable &= ~man_.them().rook(); + square_set rook_attacks{}; + for (const auto sq : man_.us().rook()) { rook_attacks |= rook_attack_tbl.look_up(sq, occ); } + threats |= rook_attacks & vulnerable; + + return threats; +} + +square_set board::us_threat_mask() const noexcept { return turn() ? threat_mask() : threat_mask(); } +square_set board::them_threat_mask() const noexcept { return turn() ? threat_mask() : threat_mask(); } + +template +inline bool board::creates_threat_(const move& mv) const noexcept { + const square_set occ = man_.white.all() | man_.black.all(); + auto attacks = [&occ](const piece_type& piece, const square& sq) { + switch (piece) { + case piece_type::pawn: return pawn_attack_tbl.look_up(sq); + case piece_type::knight: return knight_attack_tbl.look_up(sq); + case piece_type::bishop: return bishop_attack_tbl.look_up(sq, occ); + case piece_type::rook: return rook_attack_tbl.look_up(sq, occ); + default: return square_set{}; + } + }; + + const square_set current_attacks = attacks(mv.piece(), mv.from()); + const square_set next_attacks = attacks(mv.piece(), mv.to()); + const square_set new_attacks = next_attacks & ~current_attacks; + + const square_set vulnerable = [&, this] { + switch (mv.piece()) { + case piece_type::pawn: return man_.them().all() & ~(man_.them().pawn() | man_.them().king()); + case piece_type::knight: return man_.them().rook() | man_.them().queen(); + case piece_type::bishop: return man_.them().rook() | man_.them().queen(); + case piece_type::rook: return man_.them().queen(); + default: return square_set{}; + } + }(); + + return (new_attacks & vulnerable).any(); +} + +bool board::creates_threat(const move& mv) const noexcept { return turn() ? creates_threat_(mv) : creates_threat_(mv); } + +template +inline square_set board::king_danger() const noexcept { + const square_set occ = (man_.white.all() | man_.black.all()) & ~man_.us().king(); + square_set k_danger{}; + for (const auto sq : man_.them().pawn()) { k_danger |= pawn_attack_tbl>.look_up(sq); } + for (const auto sq : man_.them().knight()) { k_danger |= knight_attack_tbl.look_up(sq); } + for (const auto sq : man_.them().king()) { k_danger |= king_attack_tbl.look_up(sq); } + for (const auto sq : man_.them().rook()) { k_danger |= rook_attack_tbl.look_up(sq, occ); } + for (const auto sq : man_.them().bishop()) { k_danger |= bishop_attack_tbl.look_up(sq, occ); } + for (const auto sq : man_.them().queen()) { + k_danger |= rook_attack_tbl.look_up(sq, occ); + k_danger |= bishop_attack_tbl.look_up(sq, occ); + } + return k_danger; +} + +template +inline square_set board::pinned() const noexcept { + const square_set occ = man_.white.all() | man_.black.all(); + const auto k_x_diag = bishop_attack_tbl.look_up(man_.us().king().item(), square_set{}); + const auto k_x_hori = rook_attack_tbl.look_up(man_.us().king().item(), square_set{}); + const auto b_check_mask = bishop_attack_tbl.look_up(man_.us().king().item(), occ); + const auto r_check_mask = rook_attack_tbl.look_up(man_.us().king().item(), occ); + square_set pinned_set{}; + for (const auto sq : (k_x_hori & (man_.them().queen() | man_.them().rook()))) { + pinned_set |= r_check_mask & rook_attack_tbl.look_up(sq, occ) & man_.us().all(); + } + for (const auto sq : (k_x_diag & (man_.them().queen() | man_.them().bishop()))) { + pinned_set |= b_check_mask & bishop_attack_tbl.look_up(sq, occ) & man_.us().all(); + } + return pinned_set; +} + +template +inline void board::add_en_passant(move_list& mv_ls) const noexcept { + if constexpr (!mode::noisy) { return; } + if (lat_.them().ep_mask().any()) { + const square_set occ = man_.white.all() | man_.black.all(); + const square ep_square = lat_.them().ep_mask().item(); + const square_set enemy_pawn_mask = pawn_push_tbl>.look_up(ep_square, square_set{}); + const square_set from_mask = pawn_attack_tbl>.look_up(ep_square) & man_.us().pawn(); + for (const auto from : from_mask) { + const square_set occ_ = (occ & ~square_set{from.bit_board()} & ~enemy_pawn_mask) | lat_.them().ep_mask(); + if (!std::get<0>(checkers(occ_)).any()) { + mv_ls.push(from, ep_square, piece_type::pawn, false, piece_type::pawn, true, enemy_pawn_mask.item()); + } + } + } +} + +template +inline void board::add_castle(const move_generator_info& info, move_list& result) const noexcept { + if constexpr (!mode::noisy) { return; } + if (lat_.us().oo() && !(castle_info.oo_mask & (info.king_danger | info.occ)).any()) { + result.push(castle_info.start_king, castle_info.oo_rook, piece_type::king, true, piece_type::rook); + } + if (lat_.us().ooo() && !(castle_info.ooo_danger_mask & info.king_danger).any() && !(castle_info.ooo_occ_mask & info.occ).any()) { + result.push(castle_info.start_king, castle_info.ooo_rook, piece_type::king, true, piece_type::rook); + } +} + +template +inline void board::add_normal_pawn(const move_generator_info& info, move_list& result) const noexcept { + for (const auto from : (man_.us().pawn() & ~info.pinned)) { + const auto to_quiet = pawn_push_tbl.look_up(from, info.occ); + const auto to_noisy = pawn_attack_tbl.look_up(from) & man_.them().all(); + if constexpr (mode::quiet) { + for (const auto to : (to_quiet & ~info.last_rank)) { result.push(from, to, piece_type::pawn); } + } + if constexpr (mode::noisy) { + for (const auto to : (to_noisy & ~info.last_rank)) { result.push(from, to, piece_type::pawn, true, man_.them().occ(to)); } + } + for (const auto to : (to_quiet & info.last_rank)) { + if constexpr (mode::quiet) { result.push_under_promotions(from, to, piece_type::pawn); } + if constexpr (mode::noisy) { result.push_queen_promotion(from, to, piece_type::pawn); } + } + for (const auto to : (to_noisy & info.last_rank)) { + // for historical reasons, underpromotion captures are considered quiet + if constexpr (mode::quiet) { result.push_under_promotions(from, to, piece_type::pawn, true, man_.them().occ(to)); } + if constexpr (mode::noisy) { result.push_queen_promotion(from, to, piece_type::pawn, true, man_.them().occ(to)); } + } + } +} + +template +inline void board::add_normal_knight(const move_generator_info& info, move_list& result) const noexcept { + for (const auto from : (man_.us().knight() & ~info.pinned)) { + const auto to_mask = knight_attack_tbl.look_up(from); + if constexpr (mode::quiet) { + for (const auto to : (to_mask & ~info.occ)) { result.push(from, to, piece_type::knight); } + } + if constexpr (mode::noisy) { + for (const auto to : (to_mask & man_.them().all())) { result.push(from, to, piece_type::knight, true, man_.them().occ(to)); } + } + } +} + +template +inline void board::add_normal_bishop(const move_generator_info& info, move_list& result) const noexcept { + for (const auto from : (man_.us().bishop() & ~info.pinned)) { + const auto to_mask = bishop_attack_tbl.look_up(from, info.occ); + if constexpr (mode::quiet) { + for (const auto to : (to_mask & ~info.occ)) { result.push(from, to, piece_type::bishop); } + } + if constexpr (mode::noisy) { + for (const auto to : (to_mask & man_.them().all())) { result.push(from, to, piece_type::bishop, true, man_.them().occ(to)); } + } + } +} + +template +inline void board::add_normal_rook(const move_generator_info& info, move_list& result) const noexcept { + for (const auto from : (man_.us().rook() & ~info.pinned)) { + const auto to_mask = rook_attack_tbl.look_up(from, info.occ); + if constexpr (mode::quiet) { + for (const auto to : (to_mask & ~info.occ)) { result.push(from, to, piece_type::rook); } + } + if constexpr (mode::noisy) { + for (const auto to : (to_mask & man_.them().all())) { result.push(from, to, piece_type::rook, true, man_.them().occ(to)); } + } + } +} + +template +inline void board::add_normal_queen(const move_generator_info& info, move_list& result) const noexcept { + for (const auto from : (man_.us().queen() & ~info.pinned)) { + const auto to_mask = bishop_attack_tbl.look_up(from, info.occ) | rook_attack_tbl.look_up(from, info.occ); + if constexpr (mode::quiet) { + for (const auto to : (to_mask & ~info.occ)) { result.push(from, to, piece_type::queen); } + } + if constexpr (mode::noisy) { + for (const auto to : (to_mask & man_.them().all())) { result.push(from, to, piece_type::queen, true, man_.them().occ(to)); } + } + } +} + +template +inline void board::add_pinned_pawn(const move_generator_info& info, move_list& result) const noexcept { + for (const auto from : (man_.us().pawn() & info.pinned & info.king_diagonal)) { + const auto to_mask = pawn_attack_tbl.look_up(from) & info.king_diagonal; + if constexpr (mode::noisy) { + for (const auto to : (to_mask & ~info.last_rank & man_.them().all())) { + result.push(from, to, piece_type::pawn, true, man_.them().occ(to)); + } + } + for (const auto to : (to_mask & info.last_rank & man_.them().all())) { + if constexpr (mode::quiet) { result.push_under_promotions(from, to, piece_type::pawn, true, man_.them().occ(to)); } + if constexpr (mode::noisy) { result.push_queen_promotion(from, to, piece_type::pawn, true, man_.them().occ(to)); } + } + } + for (const auto from : (man_.us().pawn() & info.pinned & info.king_horizontal)) { + const auto to_mask = pawn_push_tbl.look_up(from, info.occ) & info.king_horizontal; + if constexpr (mode::quiet) { + for (const auto to : (to_mask & ~info.last_rank)) { result.push(from, to, piece_type::pawn); } + } + for (const auto to : (to_mask & info.last_rank)) { + if constexpr (mode::quiet) { result.push_under_promotions(from, to, piece_type::pawn); } + if constexpr (mode::noisy) { result.push_queen_promotion(from, to, piece_type::pawn); } + } + } +} + +template +inline void board::add_pinned_bishop(const move_generator_info& info, move_list& result) const noexcept { + for (const auto from : (man_.us().bishop() & info.pinned & info.king_diagonal)) { + const auto to_mask = bishop_attack_tbl.look_up(from, info.occ) & info.king_diagonal; + if constexpr (mode::quiet) { + for (const auto to : (to_mask & ~info.occ)) { result.push(from, to, piece_type::bishop); } + } + if constexpr (mode::noisy) { + for (const auto to : (to_mask & man_.them().all())) { result.push(from, to, piece_type::bishop, true, man_.them().occ(to)); } + } + } +} + +template +inline void board::add_pinned_rook(const move_generator_info& info, move_list& result) const noexcept { + for (const auto from : (man_.us().rook() & info.pinned & info.king_horizontal)) { + const auto to_mask = rook_attack_tbl.look_up(from, info.occ) & info.king_horizontal; + if constexpr (mode::quiet) { + for (const auto to : (to_mask & ~info.occ)) { result.push(from, to, piece_type::rook); } + } + if constexpr (mode::noisy) { + for (const auto to : (to_mask & man_.them().all())) { result.push(from, to, piece_type::rook, true, man_.them().occ(to)); } + } + } +} + +template +inline void board::add_pinned_queen(const move_generator_info& info, move_list& result) const noexcept { + for (const auto from : (man_.us().queen() & info.pinned & info.king_diagonal)) { + const auto to_mask = bishop_attack_tbl.look_up(from, info.occ) & info.king_diagonal; + if constexpr (mode::quiet) { + for (const auto to : (to_mask & ~info.occ)) { result.push(from, to, piece_type::queen); } + } + if constexpr (mode::noisy) { + for (const auto to : (to_mask & man_.them().all())) { result.push(from, to, piece_type::queen, true, man_.them().occ(to)); } + } + } + for (const auto from : (man_.us().queen() & info.pinned & info.king_horizontal)) { + const auto to_mask = rook_attack_tbl.look_up(from, info.occ) & info.king_horizontal; + if constexpr (mode::quiet) { + for (const auto to : (to_mask & ~info.occ)) { result.push(from, to, piece_type::queen); } + } + if constexpr (mode::noisy) { + for (const auto to : (to_mask & man_.them().all())) { result.push(from, to, piece_type::queen, true, man_.them().occ(to)); } + } + } +} + +template +inline void board::add_checked_pawn(const move_generator_info& info, move_list& result) const noexcept { + for (const auto from : (man_.us().pawn() & ~info.pinned)) { + const auto to_quiet = info.checker_rays & pawn_push_tbl.look_up(from, info.occ); + const auto to_noisy = info.checkers & pawn_attack_tbl.look_up(from); + if constexpr (mode::check) { + for (const auto to : (to_quiet & ~info.last_rank)) { result.push(from, to, piece_type::pawn); } + } + if constexpr (mode::noisy) { + for (const auto to : (to_noisy & ~info.last_rank)) { result.push(from, to, piece_type::pawn, true, man_.them().occ(to)); } + } + for (const auto to : (to_quiet & info.last_rank)) { + if constexpr (mode::check) { result.push_under_promotions(from, to, piece_type::pawn); } + if constexpr (mode::noisy) { result.push_queen_promotion(from, to, piece_type::pawn); } + } + for (const auto to : (to_noisy & info.last_rank)) { + if constexpr (mode::check) { result.push_under_promotions(from, to, piece_type::pawn, true, man_.them().occ(to)); } + if constexpr (mode::noisy) { result.push_queen_promotion(from, to, piece_type::pawn, true, man_.them().occ(to)); } + } + } +} + +template +inline void board::add_checked_knight(const move_generator_info& info, move_list& result) const noexcept { + for (const auto from : (man_.us().knight() & ~info.pinned)) { + const auto to_mask = knight_attack_tbl.look_up(from); + const auto to_quiet = info.checker_rays & to_mask; + const auto to_noisy = info.checkers & to_mask; + if constexpr (mode::check) { + for (const auto to : to_quiet) { result.push(from, to, piece_type::knight); } + } + if constexpr (mode::noisy) { + for (const auto to : to_noisy) { result.push(from, to, piece_type::knight, true, man_.them().occ(to)); } + } + } +} + +template +inline void board::add_checked_rook(const move_generator_info& info, move_list& result) const noexcept { + for (const auto from : (man_.us().rook() & ~info.pinned)) { + const auto to_mask = rook_attack_tbl.look_up(from, info.occ); + const auto to_quiet = info.checker_rays & to_mask; + const auto to_noisy = info.checkers & to_mask; + if constexpr (mode::check) { + for (const auto to : to_quiet) { result.push(from, to, piece_type::rook); } + } + if constexpr (mode::noisy) { + for (const auto to : to_noisy) { result.push(from, to, piece_type::rook, true, man_.them().occ(to)); } + } + } +} + +template +inline void board::add_checked_bishop(const move_generator_info& info, move_list& result) const noexcept { + for (const auto from : (man_.us().bishop() & ~info.pinned)) { + const auto to_mask = bishop_attack_tbl.look_up(from, info.occ); + const auto to_quiet = info.checker_rays & to_mask; + const auto to_noisy = info.checkers & to_mask; + if constexpr (mode::check) { + for (const auto to : to_quiet) { result.push(from, to, piece_type::bishop); } + } + if constexpr (mode::noisy) { + for (const auto to : to_noisy) { result.push(from, to, piece_type::bishop, true, man_.them().occ(to)); } + } + } +} + +template +inline void board::add_checked_queen(const move_generator_info& info, move_list& result) const noexcept { + for (const auto from : (man_.us().queen() & ~info.pinned)) { + const auto to_mask = bishop_attack_tbl.look_up(from, info.occ) | rook_attack_tbl.look_up(from, info.occ); + const auto to_quiet = info.checker_rays & to_mask; + const auto to_noisy = info.checkers & to_mask; + if constexpr (mode::check) { + for (const auto to : to_quiet) { result.push(from, to, piece_type::queen); } + } + if constexpr (mode::noisy) { + for (const auto to : to_noisy) { result.push(from, to, piece_type::queen, true, man_.them().occ(to)); } + } + } +} + +template +inline void board::add_king(const move_generator_info& info, move_list& result) const noexcept { + const square_set to_mask = ~info.king_danger & king_attack_tbl.look_up(man_.us().king().item()); + if (info.checkers.any() ? mode::check : mode::quiet) { + for (const square to : (to_mask & ~info.occ)) { result.push(man_.us().king().item(), to, piece_type::king); } + } + if (mode::noisy) { + for (const square to : (to_mask & man_.them().all())) { + result.push(man_.us().king().item(), to, piece_type::king, true, man_.them().occ(to)); + } + } +} + +template +inline move_generator_info board::get_move_generator_info() const noexcept { + const auto [checkers_, checker_rays_] = checkers(man_.white.all() | man_.black.all()); + + const move_generator_info info{ + man_.white.all() | man_.black.all(), + pawn_info::last_rank, + checkers_, + checker_rays_, + pinned(), + king_danger(), + bishop_attack_tbl.look_up(man_.us().king().item(), square_set{}), + rook_attack_tbl.look_up(man_.us().king().item(), square_set{}), + }; + + return info; +} + +template +inline move_list board::generate_moves_() const noexcept { + const move_generator_info info = get_move_generator_info(); + const std::size_t num_checkers = info.checkers.count(); + move_list result{}; + + if (num_checkers == 0) { + add_normal_pawn(info, result); + add_normal_knight(info, result); + add_normal_rook(info, result); + add_normal_bishop(info, result); + add_normal_queen(info, result); + add_castle(info, result); + if (info.pinned.any()) { + add_pinned_pawn(info, result); + add_pinned_bishop(info, result); + add_pinned_rook(info, result); + add_pinned_queen(info, result); + } + } else if (num_checkers == 1) { + add_checked_pawn(info, result); + add_checked_knight(info, result); + add_checked_rook(info, result); + add_checked_bishop(info, result); + add_checked_queen(info, result); + } + add_king(info, result); + add_en_passant(result); + return result; +} + +template +move_list board::generate_moves() const noexcept { + return turn() ? generate_moves_() : generate_moves_(); +} + +template +inline bool board::is_legal_(const move& mv) const noexcept { + if (mv.is_castle_oo() || mv.is_castle_ooo() || mv.is_enpassant()) { + const move_generator_info info = get_move_generator_info(); + move_list list{}; + add_castle(info, list); + add_en_passant(list); + return list.has(mv); + } + + if (!man_.us().all().is_member(mv.from())) { return false; } + if (man_.us().all().is_member(mv.to())) { return false; } + if (mv.piece() != man_.us().occ(mv.from())) { return false; } + + if (mv.is_capture() != man_.them().all().is_member(mv.to())) { return false; } + if (mv.is_capture() && mv.captured() != man_.them().occ(mv.to())) { return false; } + if (!mv.is_capture() && mv.captured() != static_cast(0)) { return false; } + + if (!mv.is_enpassant() && mv.enpassant_sq() != square::from_index(0)) { return false; } + if (!mv.is_promotion() && mv.promotion() != static_cast(0)) { return false; } + + const move_generator_info info = get_move_generator_info(); + + const bool is_noisy = (!mv.is_promotion() || mv.promotion() == chess::piece_type::queen) && (mv.is_capture() || mv.is_promotion()); + if (!mode::noisy && is_noisy) { return false; } + if (!mode::check && info.checkers.any() && !is_noisy) { return false; } + if (!mode::quiet && !info.checkers.any() && !is_noisy) { return false; } + + const square_set rook_mask = rook_attack_tbl.look_up(mv.from(), info.occ); + const square_set bishop_mask = bishop_attack_tbl.look_up(mv.from(), info.occ); + + const bool legal_from_to = [&] { + const auto pawn_mask = (mv.is_capture() ? pawn_attack_tbl.look_up(mv.from()) : pawn_push_tbl.look_up(mv.from(), info.occ)); + switch (mv.piece()) { + case piece_type::pawn: return pawn_mask.is_member(mv.to()); + case piece_type::knight: return knight_attack_tbl.look_up(mv.from()).is_member(mv.to()); + case piece_type::bishop: return bishop_mask.is_member(mv.to()); + case piece_type::rook: return rook_mask.is_member(mv.to()); + case piece_type::queen: return (bishop_mask | rook_mask).is_member(mv.to()); + case piece_type::king: return king_attack_tbl.look_up(mv.from()).is_member(mv.to()); + default: return false; + } + }(); + + if (!legal_from_to) { return false; } + + if (mv.piece() == piece_type::king && info.king_danger.is_member(mv.to())) { return false; } + if (info.checkers.any() && mv.piece() != piece_type::king) { + if (info.checkers.count() >= 2) { return false; } + if (info.pinned.is_member(mv.from())) { return false; } + if (!(info.checkers | info.checker_rays).is_member(mv.to())) { return false; } + } + + if (info.pinned.is_member(mv.from())) { + const square_set piece_diagonal = bishop_mask; + const square_set piece_horizontal = rook_mask; + const bool same_diagonal = info.king_diagonal.is_member(mv.from()) && (info.king_diagonal & piece_diagonal).is_member(mv.to()); + const bool same_horizontal = info.king_horizontal.is_member(mv.from()) && (info.king_horizontal & piece_horizontal).is_member(mv.to()); + if (!same_diagonal && !same_horizontal) { return false; } + } + + if (mv.is_promotion()) { + if (mv.piece() != piece_type::pawn) { return false; } + if (!info.last_rank.is_member(mv.to())) { return false; } + if (mv.promotion() <= piece_type::pawn || mv.promotion() > piece_type::queen) { return false; } + } + + return true; +} + +template +bool board::is_legal(const move& mv) const noexcept { + return turn() ? is_legal_(mv) : is_legal_(mv); +} + +template +inline bool board::is_check_() const noexcept { + return std::get<0>(checkers(man_.white.all() | man_.black.all())).any(); +} + +bool board::is_check() const noexcept { return turn() ? is_check_() : is_check_(); } + +template +inline bool board::see_ge_(const move& mv, const T& threshold) const noexcept { + const square tgt_sq = mv.to(); + auto used_mask = square_set{}; + + auto on_sq = mv.is_promotion() ? mv.promotion() : mv.piece(); + used_mask.insert(mv.from()); + + T value = [&] { + T val{-threshold}; + if (mv.is_promotion()) { val += material_value(mv.promotion()) - material_value(mv.piece()); } + if (mv.is_capture() && !mv.is_castle_ooo() && !mv.is_castle_oo()) { val += material_value(mv.captured()); } + return val; + }(); + + for (;;) { + if (value < 0) { return false; } + if ((value - material_value(on_sq)) >= 0) { return true; } + + { + const auto [p, sq] = least_valuable_attacker>(tgt_sq, used_mask); + if (sq == tgt_sq) { break; } + + value -= material_value(on_sq); + used_mask.insert(sq); + on_sq = p; + } + + if (value >= 0) { return true; } + if ((value + material_value(on_sq)) < 0) { return false; } + + { + const auto [p, sq] = least_valuable_attacker(tgt_sq, used_mask); + if (sq == tgt_sq) { break; } + + value += material_value(on_sq); + used_mask.insert(sq); + on_sq = p; + } + } + + return value >= 0; +} + +template +bool board::see_ge(const move& mv, const T& threshold) const noexcept { + return turn() ? see_ge_(mv, threshold) : see_ge_(mv, threshold); +} + +template +bool board::see_gt(const move& mv, const T& threshold) const noexcept { + return see_ge(mv, threshold + 1); +} + +template +T board::phase() const noexcept { + static_assert(std::is_floating_point_v); + constexpr T start_pos_value = static_cast(24); + + T value{}; + over_types([&](const piece_type& pt) { value += phase_value(pt) * (man_.white.get_plane(pt) | man_.black.get_plane(pt)).count(); }); + return std::min(value, start_pos_value) / start_pos_value; +} + +bool board::has_non_pawn_material() const noexcept { + return man_.us(turn()).knight().any() || man_.us(turn()).bishop().any() || man_.us(turn()).rook().any() || man_.us(turn()).queen().any(); +} + +template +inline bool board::is_passed_push_(const move& mv) const noexcept { + return ((mv.piece() == piece_type::pawn && !mv.is_capture()) && !(man_.them().pawn() & passer_tbl.mask(mv.to())).any()); +} + +bool board::is_passed_push(const move& mv) const noexcept { return turn() ? is_passed_push_(mv) : is_passed_push_(mv); } + +template +std::size_t board::side_num_pieces() const noexcept { + return man_.us().all().count(); +} + +std::size_t board::num_pieces() const noexcept { return side_num_pieces() + side_num_pieces(); } + +bool board::is_trivially_drawn() const noexcept { + return (num_pieces() == 2) || + ((num_pieces() == 3) && (man_.white.knight() | man_.white.bishop() | man_.black.knight() | man_.black.bishop()).any()); +} + +template +board board::forward_(const move& mv) const noexcept { + board copy = *this; + if (mv.is_null()) { + assert(!is_check_()); + } else if (mv.is_castle_ooo()) { + copy.lat_.us().set_ooo(false).set_oo(false); + copy.man_.us().remove_piece(piece_type::king, castle_info.start_king); + copy.man_.us().remove_piece(piece_type::rook, castle_info.ooo_rook); + copy.man_.us().add_piece(piece_type::king, castle_info.after_ooo_king); + copy.man_.us().add_piece(piece_type::rook, castle_info.after_ooo_rook); + } else if (mv.is_castle_oo()) { + copy.lat_.us().set_ooo(false).set_oo(false); + copy.man_.us().remove_piece(piece_type::king, castle_info.start_king); + copy.man_.us().remove_piece(piece_type::rook, castle_info.oo_rook); + copy.man_.us().add_piece(piece_type::king, castle_info.after_oo_king); + copy.man_.us().add_piece(piece_type::rook, castle_info.after_oo_rook); + } else { + copy.man_.us().remove_piece(mv.piece(), mv.from()); + if (mv.is_promotion()) { + copy.man_.us().add_piece(mv.promotion(), mv.to()); + } else { + copy.man_.us().add_piece(mv.piece(), mv.to()); + } + if (mv.is_capture()) { + copy.man_.them().remove_piece(mv.captured(), mv.to()); + } else if (mv.is_enpassant()) { + copy.man_.them().remove_piece(piece_type::pawn, mv.enpassant_sq()); + } else if (mv.is_pawn_double()) { + const square ep = pawn_push_tbl>.look_up(mv.to(), square_set{}).item(); + if ((man_.them().pawn() & pawn_attack_tbl.look_up(ep)).any()) { copy.lat_.us().set_ep_mask(ep); } + } + if (mv.from() == castle_info.start_king) { + copy.lat_.us().set_ooo(false).set_oo(false); + } else if (mv.from() == castle_info.oo_rook) { + copy.lat_.us().set_oo(false); + } else if (mv.from() == castle_info.ooo_rook) { + copy.lat_.us().set_ooo(false); + } + if (mv.to() == castle_info>.oo_rook) { + copy.lat_.them().set_oo(false); + } else if (mv.to() == castle_info>.ooo_rook) { + copy.lat_.them().set_ooo(false); + } + } + copy.lat_.them().clear_ep_mask(); + ++copy.lat_.ply_count; + ++copy.lat_.half_clock; + if (mv.is_capture() || mv.piece() == piece_type::pawn) { copy.lat_.half_clock = 0; } + return copy; +} + +board board::forward(const move& mv) const noexcept { return turn() ? forward_(mv) : forward_(mv); } + +board board::mirrored() const noexcept { + board mirror{}; + // manifest + over_types([&mirror, this](const piece_type& pt) { + for (const auto sq : man_.white.get_plane(pt).mirrored()) { mirror.man_.black.add_piece(pt, sq); } + for (const auto sq : man_.black.get_plane(pt).mirrored()) { mirror.man_.white.add_piece(pt, sq); } + }); + // latent + mirror.lat_.white.set_ooo(lat_.black.ooo()); + mirror.lat_.black.set_ooo(lat_.white.ooo()); + mirror.lat_.white.set_oo(lat_.black.oo()); + mirror.lat_.black.set_oo(lat_.white.oo()); + if (lat_.black.ep_mask().any()) { mirror.lat_.white.set_ep_mask(lat_.black.ep_mask().mirrored().item()); } + if (lat_.white.ep_mask().any()) { mirror.lat_.black.set_ep_mask(lat_.white.ep_mask().mirrored().item()); } + mirror.lat_.ply_count = lat_.ply_count ^ static_cast(1); + mirror.lat_.half_clock = lat_.half_clock; + + return mirror; +} + +std::tuple board::after_uci_moves(const std::string& moves) const noexcept { + board_history history{}; + auto bd = *this; + + std::istringstream move_stream(moves); + std::string move_name; + + while (move_stream >> move_name) { + const move_list list = bd.generate_moves<>(); + const auto it = std::find_if(list.begin(), list.end(), [=](const move& mv) { return mv.name(bd.turn()) == move_name; }); + assert((it != list.end())); + history.push(bd.hash()); + bd = bd.forward(*it); + } + return std::tuple(history, bd); +} + +std::string board::fen() const noexcept { + std::string fen{}; + constexpr std::size_t num_ranks = 8; + for (std::size_t i{0}; i < num_ranks; ++i) { + std::size_t j{0}; + over_rank(i, [&, this](const tbl_square& at_r) { + const tbl_square at = at_r.rotated(); + if (man_.white.all().occ(at.index())) { + const char letter = piece_letter(color::white, man_.white.occ(at)); + if (j != 0) { fen.append(std::to_string(j)); } + fen.push_back(letter); + j = 0; + } else if (man_.black.all().occ(at.index())) { + const char letter = piece_letter(color::black, man_.black.occ(at)); + if (j != 0) { fen.append(std::to_string(j)); } + fen.push_back(letter); + j = 0; + } else { + ++j; + } + }); + if (j != 0) { fen.append(std::to_string(j)); } + if (i != (num_ranks - 1)) { fen.push_back('/'); } + } + fen.push_back(' '); + fen.push_back(turn() ? 'w' : 'b'); + fen.push_back(' '); + std::string castle_rights{}; + if (lat_.white.oo()) { castle_rights.push_back('K'); } + if (lat_.white.ooo()) { castle_rights.push_back('Q'); } + if (lat_.black.oo()) { castle_rights.push_back('k'); } + if (lat_.black.ooo()) { castle_rights.push_back('q'); } + fen.append(castle_rights.empty() ? "-" : castle_rights); + fen.push_back(' '); + fen.append(lat_.them(turn()).ep_mask().any() ? lat_.them(turn()).ep_mask().item().name() : "-"); + fen.push_back(' '); + fen.append(std::to_string(lat_.half_clock)); + fen.push_back(' '); + fen.append(std::to_string(1 + (lat_.ply_count / 2))); + return fen; +} + +board board::start_pos() noexcept { return parse_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); } + +board board::parse_fen(const std::string& fen) noexcept { + auto fen_pos = board(); + std::stringstream ss(fen); + + std::string body; + ss >> body; + std::string side; + ss >> side; + std::string castle; + ss >> castle; + std::string ep_sq; + ss >> ep_sq; + std::string half_clock; + ss >> half_clock; + std::string move_count; + ss >> move_count; + { + std::stringstream body_s(body); + std::string rank; + for (int rank_idx{0}; std::getline(body_s, rank, '/'); ++rank_idx) { + int file_idx{0}; + for (const char c : rank) { + if (std::isdigit(c)) { + file_idx += static_cast(c - '0'); + } else { + const color side = color_from(c); + const piece_type type = type_from(c); + const tbl_square sq = tbl_square{file_idx, rank_idx}.rotated(); + fen_pos.man_.us(side).add_piece(type, sq); + ++file_idx; + } + } + } + } + fen_pos.lat_.white.set_oo(castle.find('K') != std::string::npos); + fen_pos.lat_.white.set_ooo(castle.find('Q') != std::string::npos); + fen_pos.lat_.black.set_oo(castle.find('k') != std::string::npos); + fen_pos.lat_.black.set_ooo(castle.find('q') != std::string::npos); + fen_pos.lat_.half_clock = std::stol(half_clock); + if (ep_sq != "-") { fen_pos.lat_.them(side == "w").set_ep_mask(tbl_square::from_name(ep_sq)); } + fen_pos.lat_.ply_count = static_cast(2 * (std::stol(move_count) - 1) + static_cast(side != "w")); + return fen_pos; +} + +std::ostream& operator<<(std::ostream& ostr, const board& bd) noexcept { + ostr << std::boolalpha; + ostr << "board(hash=" << bd.hash(); + ostr << ", half_clock=" << bd.lat_.half_clock; + ostr << ", ply_count=" << bd.lat_.ply_count; + ostr << ", white.oo_=" << bd.lat_.white.oo(); + ostr << ", white.ooo_=" << bd.lat_.white.ooo(); + ostr << ", black.oo_=" << bd.lat_.black.oo(); + ostr << ", black.ooo_=" << bd.lat_.black.ooo(); + ostr << ",\nwhite.ep_mask=" << bd.lat_.white.ep_mask(); + ostr << ",\nblack.ep_mask=" << bd.lat_.black.ep_mask(); + ostr << "white.occ_table={"; + over_all([&ostr, bd](const tbl_square& sq) { ostr << piece_name(bd.man_.white.occ(sq)) << ", "; }); + ostr << "},\nblack.occ_table={"; + over_all([&ostr, bd](const tbl_square& sq) { ostr << piece_name(bd.man_.black.occ(sq)) << ", "; }); + ostr << "}\n"; + over_types([&ostr, bd](const piece_type& pt) { ostr << "white." << piece_name(pt) << "=" << bd.man_.white.get_plane(pt) << ",\n"; }); + ostr << "white.all=" << bd.man_.white.all() << ",\n"; + over_types([&ostr, bd](const piece_type& pt) { ostr << "black." << piece_name(pt) << "=" << bd.man_.black.get_plane(pt) << ",\n"; }); + ostr << "black.all=" << bd.man_.black.all() << ")"; + return ostr << std::noboolalpha; +} + +} // namespace chess + +template chess::move_list chess::board::generate_moves() const noexcept; +template chess::move_list chess::board::generate_moves() const noexcept; + +template bool chess::board::is_legal(const chess::move&) const noexcept; +template bool chess::board::is_legal(const chess::move&) const noexcept; + +template bool chess::board::see_ge(const chess::move&, const std::int32_t&) const noexcept; +template bool chess::board::see_gt(const chess::move&, const std::int32_t&) const noexcept; + +template float chess::board::phase() const noexcept; +template double chess::board::phase() const noexcept; diff --git a/src/chess/board_state.cc b/src/chess/board_state.cc new file mode 100644 index 0000000..e9a2d8c --- /dev/null +++ b/src/chess/board_state.cc @@ -0,0 +1,109 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include + +namespace chess { + +template +zobrist::hash_type manifest_zobrist_src::get(const piece_type& pt, const S& at) const noexcept { + static_assert(is_square_v, "at must be of square type"); + return get_plane(pt)[at.index()]; +} + +manifest_zobrist_src::manifest_zobrist_src() noexcept { + over_types([this](const piece_type pt) { + plane_t& pt_plane = get_plane(pt); + std::transform(pt_plane.begin(), pt_plane.end(), pt_plane.begin(), [](auto&&...) { return zobrist::random_bit_string(); }); + }); +} + +template +manifest& manifest::add_piece(const piece_type& pt, const S& at) noexcept { + static_assert(is_square_v, "at must be of square type"); + hash_ ^= zobrist_src_->get(pt, at); + all_ |= at.bit_board(); + get_plane(pt) |= at.bit_board(); + return *this; +} + +template +manifest& manifest::remove_piece(const piece_type& pt, const S& at) noexcept { + static_assert(is_square_v, "at must be of square type"); + hash_ ^= zobrist_src_->get(pt, at); + all_ &= ~at.bit_board(); + get_plane(pt) &= ~at.bit_board(); + return *this; +} + +template +zobrist::hash_type latent_zobrist_src::get_ep_mask(const S& at) const noexcept { + static_assert(is_square_v, "at must be of square type"); + return ep_mask_[at.index()]; +} + +latent_zobrist_src::latent_zobrist_src() noexcept { + oo_ = zobrist::random_bit_string(); + ooo_ = zobrist::random_bit_string(); + std::transform(ep_mask_.begin(), ep_mask_.end(), ep_mask_.begin(), [](auto&&...) { return zobrist::random_bit_string(); }); +} + +latent& latent::set_oo(const bool val) noexcept { + if (val ^ oo_) { hash_ ^= zobrist_src_->get_oo(); } + oo_ = val; + return *this; +} + +latent& latent::set_ooo(const bool val) noexcept { + if (val ^ ooo_) { hash_ ^= zobrist_src_->get_ooo(); } + ooo_ = val; + return *this; +} + +latent& latent::clear_ep_mask() noexcept { + if (ep_mask_.any()) { hash_ ^= zobrist_src_->get_ep_mask(ep_mask_.item()); } + ep_mask_ = square_set{}; + return *this; +} + +template +latent& latent::set_ep_mask(const S& at) noexcept { + static_assert(is_square_v, "at must be of square type"); + clear_ep_mask(); + hash_ ^= zobrist_src_->get_ep_mask(at); + ep_mask_.insert(at); + return *this; +} + +} // namespace chess + +template zobrist::hash_type chess::manifest_zobrist_src::get(const chess::piece_type&, const chess::tbl_square&) const noexcept; +template zobrist::hash_type chess::manifest_zobrist_src::get(const chess::piece_type&, const chess::square&) const noexcept; + +template chess::manifest& chess::manifest::add_piece(const piece_type&, const chess::tbl_square&) noexcept; +template chess::manifest& chess::manifest::add_piece(const piece_type&, const chess::square&) noexcept; + +template chess::manifest& chess::manifest::remove_piece(const piece_type&, const chess::tbl_square&) noexcept; +template chess::manifest& chess::manifest::remove_piece(const piece_type&, const chess::square&) noexcept; + +template zobrist::hash_type chess::latent_zobrist_src::get_ep_mask(const chess::tbl_square&) const noexcept; +template zobrist::hash_type chess::latent_zobrist_src::get_ep_mask(const chess::square&) const noexcept; + +template chess::latent& chess::latent::set_ep_mask(const chess::tbl_square&) noexcept; +template chess::latent& chess::latent::set_ep_mask(const chess::square&) noexcept; diff --git a/src/chess/move.cc b/src/chess/move.cc new file mode 100644 index 0000000..0ee913b --- /dev/null +++ b/src/chess/move.cc @@ -0,0 +1,47 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +namespace chess { + +template +std::string move::name() const noexcept { + if (is_castle_oo()) { + return castle_info.start_king.name() + castle_info.after_oo_king.name(); + } else if (is_castle_ooo()) { + return castle_info.start_king.name() + castle_info.after_ooo_king.name(); + } + + std::string base = from().name() + to().name(); + if (is_promotion()) { + return base + piece_letter(promotion()); + } else { + return base; + } +} + +std::string move::name(const bool pov) const noexcept { return pov ? name() : name(); } + +std::ostream& operator<<(std::ostream& ostr, const move& mv) noexcept { + ostr << "move(from=" << mv.from().name() << ", to=" << mv.to().name() << ", piece=" << piece_name(mv.piece()) << ", is_capture=" << mv.is_capture() + << ", capture=" << piece_name(mv.captured()) << ", is_enpassant=" << mv.is_enpassant() << ", enpassant_sq=" << mv.enpassant_sq().name() + << ", promotion=" << piece_name(mv.promotion()) << ')'; + return ostr; +} + +} // namespace chess \ No newline at end of file diff --git a/src/chess/move_list.cc b/src/chess/move_list.cc new file mode 100644 index 0000000..a8e83e7 --- /dev/null +++ b/src/chess/move_list.cc @@ -0,0 +1,27 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +namespace chess { + +std::ostream& operator<<(std::ostream& ostr, const move_list& mv_ls) noexcept { + for (std::size_t i(0); i < mv_ls.size(); ++i) { ostr << i << ". " << mv_ls[i] << '\n'; } + return ostr; +} + +} \ No newline at end of file diff --git a/src/chess/square.cc b/src/chess/square.cc new file mode 100644 index 0000000..2e8d132 --- /dev/null +++ b/src/chess/square.cc @@ -0,0 +1,48 @@ +#include + +#include + +namespace chess { + +std::string square::name() const noexcept { + constexpr std::array name_of_file = {'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a'}; + constexpr std::array name_of_rank = {'1', '2', '3', '4', '5', '6', '7', '8'}; + return std::string{} + name_of_file[file()] + name_of_rank[rank()]; +} + +std::ostream& operator<<(std::ostream& ostr, const square& sq) noexcept { + ostr << "square(data=" << sq.data << ")\n"; + + constexpr square::data_type board_hw = 8; + auto is_set = [sq](square::data_type idx) { return static_cast((static_cast(1) << idx) & sq.data); }; + + for (square::data_type rank{0}; rank < board_hw; ++rank) { + for (square::data_type file{0}; file < board_hw; ++file) { + const square::data_type idx = rank * board_hw + file; + ostr << (is_set(idx) ? '*' : '.') << ' '; + } + + ostr << '\n'; + } + + return ostr; +} + +tbl_square tbl_square::from_name(const std::string& name) noexcept { + return tbl_square{7 - static_cast(name[0] - 'a'), static_cast(name[1] - '1')}; +} + +std::ostream& operator<<(std::ostream& ostr, const square_set& ss) noexcept { + std::cout << "square_set(data=" << ss.data << ")\n"; + constexpr square::data_type board_hw = 8; + for (square::data_type rank{0}; rank < board_hw; ++rank) { + for (square::data_type file{0}; file < board_hw; ++file) { + const square::data_type idx = rank * board_hw + file; + ostr << (ss.occ(idx) ? '*' : '.') << ' '; + } + ostr << '\n'; + } + return ostr; +} + +} // namespace chess \ No newline at end of file diff --git a/src/engine/bench.cc b/src/engine/bench.cc new file mode 100644 index 0000000..391adac --- /dev/null +++ b/src/engine/bench.cc @@ -0,0 +1,63 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +namespace engine { + +std::ostream& operator<<(std::ostream& os, const bench_info& info) noexcept { + return os << info.total_nodes << " nodes " << info.nodes_per_second << " nps"; +} + +bench_info get_bench_info(const nnue::weights& weights) noexcept { + using worker_type = search::search_worker; + std::shared_ptr constants = std::make_shared(1); + std::shared_ptr tt = std::make_shared(bench_config::tt_mb_size); + + std::unique_ptr worker = std::make_unique(&weights, tt, constants, [&](const auto& w) { + if (w.depth() >= bench_config::bench_depth) { worker->stop(); } + }); + + simple_timer timer{}; + std::size_t total_nodes{}; + + for (const auto& fen : bench_config::fens) { + worker->go(chess::board_history{}, chess::board::parse_fen(std::string(fen)), bench_config::init_depth); + worker->iterative_deepening_loop(); + total_nodes += worker->nodes(); + } + + const std::size_t nodes_per_second = total_nodes * std::chrono::milliseconds(std::chrono::seconds(1)).count() / timer.elapsed().count(); + + return bench_info{total_nodes, nodes_per_second}; +} + +std::size_t perft(const chess::board& bd, const search::depth_type& depth) noexcept { + if (depth == 0) { return bd.generate_moves<>().size(); } + std::size_t result{0}; + for (const auto& mv : bd.generate_moves<>()) { result += perft(bd.forward(mv), depth - 1); } + return result; +} + +bench_info get_perft_info(const chess::board& bd, const search::depth_type& depth) noexcept { + simple_timer timer{}; + const std::size_t nodes = perft(bd, depth - 1); + const std::size_t nodes_per_second = nodes * std::chrono::nanoseconds(std::chrono::seconds(1)).count() / timer.elapsed().count(); + return bench_info{nodes, nodes_per_second}; +} + +} // namespace engine \ No newline at end of file diff --git a/src/engine/option_parser.cc b/src/engine/option_parser.cc new file mode 100644 index 0000000..2d3ca28 --- /dev/null +++ b/src/engine/option_parser.cc @@ -0,0 +1,41 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +namespace engine { + +std::ostream& operator<<(std::ostream& ostr, const string_option& opt) noexcept { + ostr << "option name " << opt.name_ << " type string"; + if (opt.default_.has_value()) { ostr << " default " << opt.default_.value(); } + return ostr; +} + +std::ostream& operator<<(std::ostream& ostr, const spin_option& opt) noexcept { + ostr << "option name " << opt.name_ << " type spin"; + if (opt.default_.has_value()) { ostr << " default " << opt.default_.value(); } + if (opt.range_.has_value()) { ostr << " min " << opt.range_.value().min << " max " << opt.range_.value().max; } + return ostr; +} + +std::ostream& operator<<(std::ostream& ostr, const check_option& opt) noexcept { + ostr << "option name " << opt.name_ << " type check"; + if (opt.default_.has_value()) { ostr << std::boolalpha << " default " << opt.default_.value(); } + return ostr; +} + +} // namespace engine diff --git a/src/engine/time_manager.cc b/src/engine/time_manager.cc new file mode 100644 index 0000000..a19895d --- /dev/null +++ b/src/engine/time_manager.cc @@ -0,0 +1,161 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +namespace engine { + +time_manager& time_manager::ponder_hit() noexcept { + std::lock_guard access_lk(access_mutex_); + + search_start = std::chrono::steady_clock::now(); + ponder = false; + + return *this; +} + +time_manager& time_manager::reset_() noexcept { + search_start = std::chrono::steady_clock::now(); + + min_budget = std::nullopt; + max_budget = std::nullopt; + + depth_limit = std::nullopt; + node_limit = std::nullopt; + + ponder = false; + infinite = false; + + return *this; +} + +time_manager& time_manager::init(const bool&, const go::infinite&) noexcept { + std::lock_guard access_lk(access_mutex_); + + reset_(); + infinite = true; + return *this; +} + +time_manager& time_manager::init(const bool&, const go::depth& data) noexcept { + std::lock_guard access_lk(access_mutex_); + + reset_(); + depth_limit = data.depth; + + return *this; +} + +time_manager& time_manager::init(const bool&, const go::nodes& data) noexcept { + std::lock_guard access_lk(access_mutex_); + + reset_(); + node_limit = data.nodes; + + return *this; +} + +time_manager& time_manager::init(const bool&, const go::move_time& data) noexcept { + std::lock_guard access_lk(access_mutex_); + + reset_(); + + ponder = data.ponder; + min_budget = std::nullopt; + max_budget = data.move_time_ms(); + + return *this; +} + +time_manager& time_manager::init(const bool& pov, const go::increment& data) noexcept { + std::lock_guard access_lk(access_mutex_); + + reset_(); + const auto remaining = data.our_time_ms(pov); + const auto inc = data.our_increment_ms(pov); + + ponder = data.ponder; + min_budget = (remaining - over_head + 25 * inc) / 25; + max_budget = (remaining - over_head + 25 * inc) / 10; + + min_budget = std::min(4 * (remaining - over_head) / 5, *min_budget); + max_budget = std::min(4 * (remaining - over_head) / 5, *max_budget); + + return *this; +} + +time_manager& time_manager::init(const bool& pov, const go::moves_to_go& data) noexcept { + std::lock_guard access_lk(access_mutex_); + + reset_(); + const auto remaining = data.our_time_ms(pov); + + ponder = data.ponder; + min_budget = 2 * (remaining - over_head) / (3 * data.moves_to_go); + max_budget = 10 * (remaining - over_head) / (3 * data.moves_to_go); + + return *this; +} + +std::chrono::milliseconds time_manager::elapsed() const noexcept { + return std::chrono::duration_cast(std::chrono::steady_clock::now() - search_start); +} + +bool time_manager::should_stop_on_update(const update_info& info) noexcept { + std::lock_guard access_lk(access_mutex_); + + if (infinite) { return false; } + if (ponder) { return false; } + + if (node_limit.has_value() && info.nodes >= *node_limit) { return true; } + if (max_budget.has_value() && elapsed() >= *max_budget) { return true; }; + return false; +} + +bool time_manager::should_stop_on_iter(const iter_info& info) noexcept { + constexpr std::size_t numerator = 50; + constexpr std::size_t min_percent = 20; + + std::lock_guard access_lk(access_mutex_); + + if (infinite) { return false; } + if (ponder) { return false; } + + if (info.depth >= search::max_depth) { return true; } + if (max_budget.has_value() && elapsed() >= *max_budget) { return true; } + if (min_budget.has_value() && elapsed() >= (*min_budget * numerator / std::max(info.best_move_percent, min_percent))) { return true; } + if (depth_limit.has_value() && info.depth >= *depth_limit) { return true; } + return false; +} + +template +T simple_timer::elapsed() noexcept { + std::lock_guard start_lk(start_mutex_); + return std::chrono::duration_cast(std::chrono::steady_clock::now() - start_); +} + +template +simple_timer& simple_timer::lap() noexcept { + std::lock_guard start_lk(start_mutex_); + start_ = std::chrono::steady_clock::now(); + return *this; +} + +} // namespace engine + +template struct engine::simple_timer; +template struct engine::simple_timer; diff --git a/src/engine/uci.cc b/src/engine/uci.cc new file mode 100644 index 0000000..cf41dab --- /dev/null +++ b/src/engine/uci.cc @@ -0,0 +1,280 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace engine { + +auto uci::options() noexcept { + auto weight_path = option_callback(string_option("Weights", std::string(default_weight_path)), [this](const std::string& path) { + if (path == std::string(default_weight_path)) { + nnue::embedded_weight_streamer embedded(nnue::embed::weights_file_data); + weights_.load(embedded); + } else { + weights_.load(path); + } + + weights_info_string(); + }); + + auto hash_size = option_callback(spin_option("Hash", default_hash_size, spin_range{1, 262144}), [this](const int size) { + const auto new_size = static_cast(size); + orchestrator_.tt_->resize(new_size); + }); + + auto thread_count = option_callback(spin_option("Threads", default_thread_count, spin_range{1, 512}), [this](const int count) { + const auto new_count = static_cast(count); + orchestrator_.resize(new_count); + }); + + auto ponder = option_callback(check_option("Ponder", default_ponder), [this](const bool& value) { ponder_.store(value); }); + auto syzygy_path = option_callback(string_option("SyzygyPath", string_option::empty), [](const std::string& path) { search::syzygy::init(path); }); + + return uci_options(weight_path, hash_size, thread_count, ponder, syzygy_path); +} + +bool uci::should_quit() const noexcept { return should_quit_.load(); } +void uci::quit() noexcept { should_quit_.store(true); } + +void uci::new_game() noexcept { + std::lock_guard lock(mutex_); + if (orchestrator_.is_searching()) { return; } + + history.clear(); + position = chess::board::start_pos(); + orchestrator_.reset(); +} + +void uci::set_position(const chess::board& bd, const std::string& uci_moves) noexcept { + std::lock_guard lock(mutex_); + if (orchestrator_.is_searching()) { return; } + + auto [history_value, position_value] = bd.after_uci_moves(uci_moves); + history = history_value; + position = position_value; +} + +void uci::weights_info_string() noexcept { + std::lock_guard lock(mutex_); + os << "info string loaded weights with signature 0x" << std::hex << weights_.signature() << std::dec << std::endl; +} + +void uci::info_string(const search::search_worker& worker) noexcept { + std::lock_guard lock(mutex_); + + constexpr search::score_type raw_multiplier = 288; + constexpr search::score_type raw_divisor = 1024; + + const search::score_type raw_score = worker.score(); + const search::score_type score = raw_score * raw_multiplier / raw_divisor; + + const search::depth_type depth = worker.depth(); + const std::size_t elapsed_ms = timer_.elapsed().count(); + const std::size_t nodes = orchestrator_.nodes(); + const std::size_t tb_hits = orchestrator_.tb_hits(); + const std::size_t nps = std::chrono::milliseconds(std::chrono::seconds(1)).count() * nodes / (1 + elapsed_ms); + + const bool should_report = orchestrator_.is_searching() && depth < search::max_depth; + if (should_report) { + os << "info depth " << depth << " seldepth " << worker.internal.stack.selective_depth() << " score cp " << score << " nodes " << nodes << " nps " + << nps << " time " << elapsed_ms << " tbhits " << tb_hits << " pv " << worker.internal.stack.pv_string() << std::endl; + } +} + +template +void uci::go(Ts&&... args) noexcept { + std::lock_guard lock(mutex_); + if (orchestrator_.is_searching()) { return; } + + manager_.init(position.turn(), T{std::forward(args)...}); + timer_.lap(); + + orchestrator_.go(history, position); +} + +void uci::ponder_hit() noexcept { + std::lock_guard lock(mutex_); + if (!orchestrator_.is_searching()) { return; } + + manager_.ponder_hit(); +} + +void uci::stop() noexcept { + std::lock_guard lock(mutex_); + if (!orchestrator_.is_searching()) { return; } + + orchestrator_.stop(); + const chess::move best_move = orchestrator_.primary_worker().best_move(); + const chess::move ponder_move = orchestrator_.primary_worker().ponder_move(); + + const std::string ponder_move_string = [&] { + if (!position.forward(best_move).is_legal(ponder_move)) { return std::string{}; } + return std::string(" ponder ") + ponder_move.name(position.forward(best_move).turn()); + }(); + + os << "bestmove " << best_move.name(position.turn()) << ponder_move_string << std::endl; +} + +void uci::ready() noexcept { + std::lock_guard lock(mutex_); + os << "readyok" << std::endl; +} + +void uci::id_info() noexcept { + std::lock_guard lock(mutex_); + if (orchestrator_.is_searching()) { return; } + + os << "id name " << version::engine_name << " " << version::major << '.' << version::minor << '.' << version::patch << std::endl; + os << "id author " << version::author_name << std::endl; + os << options(); + os << "uciok" << std::endl; +} + +void uci::bench() noexcept { + std::lock_guard lock(mutex_); + if (orchestrator_.is_searching()) { return; } + + os << get_bench_info(weights_) << std::endl; +} + +void uci::eval() noexcept { + std::lock_guard lock(mutex_); + if (orchestrator_.is_searching()) { return; } + + auto scratchpad = std::make_unique(); + auto evaluator = nnue::eval(&weights_, scratchpad.get(), 0, 0); + position.feature_full_reset(evaluator); + + os << "phase: " << position.phase() << std::endl; + os << "score(phase): " << evaluator.evaluate(position.turn(), position.phase()) << std::endl; +} + +void uci::probe() noexcept { + std::lock_guard lock(mutex_); + if (orchestrator_.is_searching()) { return; } + + if (position.is_rule50_draw()) { + std::cout << "rule 50 draw" << std::endl; + } else if (const search::syzygy::tb_wdl_result result = search::syzygy::probe_wdl(position); result.success) { + std::cout << "success: " << [&] { + switch (result.wdl) { + case search::syzygy::wdl_type::loss: return "loss"; + case search::syzygy::wdl_type::draw: return "draw"; + case search::syzygy::wdl_type::win: return "win"; + default: return "unknown"; + } + }() << std::endl; + } else { + std::cout << "fail" << std::endl; + } +} + +void uci::perft(const search::depth_type& depth) noexcept { + std::lock_guard lock(mutex_); + if (orchestrator_.is_searching()) { return; } + std::cout << get_perft_info(position, depth) << std::endl; +} + +void uci::read(const std::string& line) noexcept { + using namespace processor::def; + + // clang-format off + + const auto processor = parallel( + sequential(consume("uci"), invoke([&] { id_info(); })), + sequential(consume("isready"), invoke([&] { ready(); })), + options().processor(), + + sequential(consume("ucinewgame"), invoke([&] { new_game(); })), + sequential(consume("position"), parallel( + sequential(consume("startpos"), parallel( + sequential(invoke([&] { set_position(chess::board::start_pos()); })), + sequential(consume("moves"), emit_all, invoke([&] (const std::string& moves) { set_position(chess::board::start_pos(), moves); })) + )), + + sequential(consume("fen"), emit_n, parallel( + sequential(invoke([&] (const std::string& fen) { + const auto board = chess::board::parse_fen(fen); + set_position(board); + })), + + sequential(consume("moves"), emit_all, invoke([&] (const std::string& fen, const std::string& moves) { + const auto board = chess::board::parse_fen(fen); + set_position(board, moves); + })) + )) + )), + + sequential(consume("go"), parallel( + sequential(consume("infinite"), invoke([&] { go(); })), + sequential(consume("nodes"), emit, invoke([&] (const std::size_t& nodes) { go(nodes); })), + sequential(consume("depth"), emit, invoke([&] (const search::depth_type& depth) { go(depth); })), + + sequential(condition("ponder"), parallel( + sequential(key("movetime"), invoke([&](const auto& ... args) { go(args...); })), + + sequential(key("wtime"), key("btime"), parallel( + sequential(key("movestogo"), invoke([&](const auto& ... args) { go(args...); })), + sequential(key("winc"), key("binc"), invoke([&](const auto& ... args) { go(args...); })) + )) + )) + )), + + sequential(consume("ponderhit"), invoke([&] { ponder_hit(); })), + sequential(consume("stop"), invoke([&] { stop(); })), + sequential(consume("quit"), invoke([&] { quit(); })), + + // extensions + sequential(consume("perft"), emit, invoke([&] (const search::depth_type& depth) { perft(depth); })), + sequential(consume("bench"), invoke([&] { bench(); })), + sequential(consume("probe"), invoke([&] { probe(); })), + sequential(consume("eval"), invoke([&] { eval(); })) + ); + + // clang-format on + + const auto lexed = command_lexer{}.lex(line); + processor.process(lexed.view(), {}); +} + +uci::uci() noexcept + : orchestrator_( + &weights_, + default_hash_size, + [this](const auto& worker) { + info_string(worker); + if (manager_.should_stop_on_iter(iter_info{worker.depth(), worker.best_move_percent()})) { stop(); } + }, + [this](const auto& worker) { + if (manager_.should_stop_on_update(update_info{worker.nodes()})) { stop(); } + }) { + nnue::embedded_weight_streamer embedded(nnue::embed::weights_file_data); + weights_.load(embedded); + orchestrator_.resize(default_thread_count); +} + +} // namespace engine \ No newline at end of file diff --git a/src/search/move_orderer.cc b/src/search/move_orderer.cc new file mode 100644 index 0000000..1435266 --- /dev/null +++ b/src/search/move_orderer.cc @@ -0,0 +1,75 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include + +namespace search { + +void move_orderer_stepper::update_list_() const noexcept { + auto comparator = [](const move_orderer_entry& a, const move_orderer_entry& b) { return a.sort_key() < b.sort_key(); }; + std::iter_swap(begin_, std::max_element(begin_, end_, comparator)); +} + +void move_orderer_stepper::next() noexcept { + ++begin_; + if (begin_ != end_) { update_list_(); } +} + +move_orderer_stepper& move_orderer_stepper::initialize(const move_orderer_data& data, const chess::move_list& list) noexcept { + const history::context ctxt{data.follow, data.counter, data.threatened}; + + end_ = std::transform(list.begin(), list.end(), entries_.begin(), [&data, &ctxt](const chess::move& mv) { + if (mv.is_noisy()) { return move_orderer_entry::make_noisy(mv, data.bd->see_gt(mv, 0), data.hh->compute_value(ctxt, mv)); } + return move_orderer_entry::make_quiet(mv, data.killer, data.hh->compute_value(ctxt, mv)); + }); + + end_ = std::remove_if(begin_, end_, [&data](const auto& entry) { return entry.mv == data.first; }); + + if (begin_ != end_) { update_list_(); } + is_initialized_ = true; + return *this; +} + +template +std::tuple move_orderer_iterator::operator*() const noexcept { + if (!stepper_.is_initialized()) { return std::tuple(idx, data_.first); } + return std::tuple(idx, stepper_.current_move()); +} + +template +move_orderer_iterator& move_orderer_iterator::operator++() noexcept { + if (!stepper_.is_initialized()) { + stepper_.initialize(data_, data_.bd->generate_moves()); + } else { + stepper_.next(); + } + + ++idx; + return *this; +} + +template +move_orderer_iterator::move_orderer_iterator(const move_orderer_data& data) noexcept : data_{data} { + if (data.first.is_null() || !data.bd->is_legal(data.first)) { stepper_.initialize(data, data.bd->generate_moves()); } +} + +} // namespace search + +template struct search::move_orderer_iterator; +template struct search::move_orderer_iterator; \ No newline at end of file diff --git a/src/search/search_stack.cc b/src/search/search_stack.cc new file mode 100644 index 0000000..8655357 --- /dev/null +++ b/src/search/search_stack.cc @@ -0,0 +1,53 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include + +namespace search { + +std::size_t search_stack::count(const std::size_t& height, const zobrist::hash_type& hash) const noexcept { + const std::size_t future_count = + std::count_if(future_.cbegin(), future_.cbegin() + height, [&](const stack_entry& entry) { return hash == entry.hash_; }); + + return future_count + past_.count(hash); +} + +std::string search_stack::pv_string() const noexcept { + auto bd = present_; + std::string result{}; + + for (const auto& pv_mv : future_.begin()->pv_) { + if (!bd.generate_moves<>().has(pv_mv)) { break; } + result += pv_mv.name(bd.turn()) + " "; + bd = bd.forward(pv_mv); + } + return result; +} + +chess::move search_stack::ponder_move() const noexcept { return *(future_.begin()->pv_.begin() + 1); } + +search_stack& search_stack::clear_future() noexcept { + selective_depth_ = 0; + future_.fill(stack_entry{}); + return *this; +} + +search_stack::search_stack(const chess::board_history& past, const chess::board& present) noexcept : past_{past}, present_{present} {} + +} // namespace search diff --git a/src/search/search_worker.cc b/src/search/search_worker.cc new file mode 100644 index 0000000..adff174 --- /dev/null +++ b/src/search/search_worker.cc @@ -0,0 +1,506 @@ + +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include + +namespace search { + +template +score_type search_worker::q_search( + const stack_view& ss, + nnue::eval_node& eval_node, + const chess::board& bd, + score_type alpha, + const score_type& beta, + const depth_type& elevation) noexcept { + // callback on entering search function + const bool should_update = internal.keep_going() && internal.one_of(); + if (should_update) { external.on_update(*this); } + + ++internal.nodes; + const bool is_check = bd.is_check(); + + if (ss.is_two_fold(bd.hash())) { return draw_score; } + if (bd.is_trivially_drawn()) { return draw_score; } + + const std::optional maybe = external.tt->find(bd.hash()); + if (maybe.has_value()) { + const transposition_table_entry entry = maybe.value(); + const bool is_cutoff = (entry.bound() == bound_type::lower && entry.score() >= beta) || (entry.bound() == bound_type::exact) || + (entry.bound() == bound_type::upper && entry.score() <= alpha); + if (use_tt && is_cutoff) { return entry.score(); } + } + + const auto [static_value, value] = [&] { + const auto maybe_eval = internal.cache.find(bd.hash()); + const score_type static_value = is_check ? ss.loss_score() : + !is_pv && maybe_eval.has_value() ? + maybe_eval.value() : + eval_node.evaluator().evaluate(bd.turn(), bd.phase()); + + if (!is_check) { internal.cache.insert(bd.hash(), static_value); } + + score_type value = static_value; + if (use_tt && maybe.has_value()) { + if (maybe->bound() == bound_type::upper && static_value > maybe->score()) { value = maybe->score(); } + if (maybe->bound() == bound_type::lower && static_value < maybe->score()) { value = maybe->score(); } + } + + return std::tuple(static_value, value); + }(); + + if (!is_check && value >= beta) { return value; } + if (ss.reached_max_height()) { return value; } + + move_orderer orderer(move_orderer_data(&bd, &internal.hh.us(bd.turn()))); + if (maybe.has_value()) { orderer.set_first(maybe->best_move()); } + + alpha = std::max(alpha, value); + score_type best_score = value; + chess::move best_move = chess::move::null(); + + ss.set_hash(bd.hash()).set_eval(static_value); + int legal_count{0}; + for (const auto& [idx, mv] : orderer) { + ++legal_count; + if (!internal.keep_going()) { break; } + + if (!is_check && !bd.see_ge(mv, 0)) { continue; } + + const bool delta_prune = !is_pv && !is_check && !bd.see_gt(mv, 0) && ((value + external.constants->delta_margin()) < alpha); + if (delta_prune) { continue; } + + const bool good_capture_prune = !is_pv && !is_check && !maybe.has_value() && bd.see_ge(mv, external.constants->good_capture_prune_see_margin()) && + value + external.constants->good_capture_prune_score_margin() > beta; + if (good_capture_prune) { return beta; } + + ss.set_played(mv); + + const chess::board bd_ = bd.forward(mv); + external.tt->prefetch(bd_.hash()); + internal.cache.prefetch(bd_.hash()); + nnue::eval_node eval_node_ = eval_node.dirty_child(&internal.reset_cache, &bd, mv); + + const score_type score = -q_search(ss.next(), eval_node_, bd_, -beta, -alpha, elevation + 1); + + if (score > best_score) { + best_score = score; + best_move = mv; + if (score > alpha) { + if (score < beta) { alpha = score; } + if constexpr (is_pv) { ss.prepend_to_pv(mv); } + } + } + + if (best_score >= beta) { break; } + } + + if (legal_count == 0 && is_check) { return ss.loss_score(); } + if (legal_count == 0) { return value; } + + if (use_tt && internal.keep_going()) { + const bound_type bound = best_score >= beta ? bound_type::lower : bound_type::upper; + const transposition_table_entry entry(bd.hash(), bound, best_score, best_move, 0); + external.tt->insert(entry); + } + + return best_score; +} + +template +pv_search_result_t search_worker::pv_search( + const stack_view& ss, + nnue::eval_node& eval_node, + const chess::board& bd, + score_type alpha, + const score_type& beta, + depth_type depth, + const chess::player_type& reducer) noexcept { + static_assert(!is_root || is_pv); + + auto make_result = [](const score_type& score, const chess::move& mv) { + if constexpr (is_root) { return pv_search_result_t{score, mv}; } + if constexpr (!is_root) { return score; } + }; + + // callback on entering search function + const bool should_update = internal.keep_going() && (is_root || internal.one_of()); + if (should_update) { external.on_update(*this); } + + // step 1. drop into qsearch if depth reaches zero + if (depth <= 0) { return make_result(q_search(ss, eval_node, bd, alpha, beta, 0), chess::move::null()); } + ++internal.nodes; + + // step 2. check if node is terminal + const bool is_check = bd.is_check(); + + if (!is_root && ss.is_two_fold(bd.hash())) { return make_result(draw_score, chess::move::null()); } + if (!is_root && bd.is_trivially_drawn()) { return make_result(draw_score, chess::move::null()); } + if (!is_root && bd.is_rule50_draw() && (!is_check || bd.generate_moves().size() != 0)) { + return make_result(draw_score, chess::move::null()); + } + + if constexpr (is_root) { + if (const syzygy::tb_dtz_result result = syzygy::probe_dtz(bd); result.success) { return make_result(result.score, result.move); } + } + + const score_type original_alpha = alpha; + + const std::optional maybe = !ss.has_excluded() ? external.tt->find(bd.hash()) : std::nullopt; + if (maybe.has_value()) { + const transposition_table_entry entry = maybe.value(); + const bool is_cutoff = !is_pv && entry.depth() >= depth && + ((entry.bound() == bound_type::lower && entry.score() >= beta) || entry.bound() == bound_type::exact || + (entry.bound() == bound_type::upper && entry.score() <= alpha)); + if (is_cutoff) { return make_result(entry.score(), entry.best_move()); } + } + + if (const syzygy::tb_wdl_result result = syzygy::probe_wdl(bd); !is_root && result.success) { + ++internal.tb_hits; + + switch (result.wdl) { + case syzygy::wdl_type::loss: return make_result(ss.loss_score(), chess::move::null()); + case syzygy::wdl_type::draw: return make_result(draw_score, chess::move::null()); + case syzygy::wdl_type::win: return make_result(ss.win_score(), chess::move::null()); + } + } + + // step 3. internal iterative reductions + const bool should_iir = !maybe.has_value() && !ss.has_excluded() && depth >= external.constants->iir_depth(); + if (should_iir) { --depth; } + + // step 4. compute static eval and adjust appropriately if there's a tt hit + const auto [static_value, value] = [&] { + const auto maybe_eval = internal.cache.find(bd.hash()); + const score_type static_value = is_check ? ss.loss_score() : + !is_pv && maybe_eval.has_value() ? + maybe_eval.value() : + eval_node.evaluator().evaluate(bd.turn(), bd.phase()); + + if (!is_check) { internal.cache.insert(bd.hash(), static_value); } + + score_type value = static_value; + if (maybe.has_value()) { + if (maybe->bound() == bound_type::upper && static_value > maybe->score()) { value = maybe->score(); } + if (maybe->bound() == bound_type::lower && static_value < maybe->score()) { value = maybe->score(); } + } + + return std::tuple(static_value, value); + }(); + + // step 5. return static eval if max depth was reached + if (ss.reached_max_height()) { return make_result(value, chess::move::null()); } + + // step 6. add position and static eval to stack + ss.set_hash(bd.hash()).set_eval(static_value); + const bool improving = !is_check && ss.improving(); + const chess::square_set threatened = bd.them_threat_mask(); + + // step 7. static null move pruning + const bool snm_prune = !is_pv && !ss.has_excluded() && !is_check && depth <= external.constants->snmp_depth() && + value > beta + external.constants->snmp_margin(improving, threatened.any(), depth) && value > ss.loss_score(); + + if (snm_prune) { return make_result(value, chess::move::null()); } + + // step 8. null move pruning + const bool try_nmp = !is_pv && !ss.has_excluded() && !is_check && depth >= external.constants->nmp_depth() && value > beta && ss.nmp_valid() && + bd.has_non_pawn_material() && (!threatened.any() || depth >= 4) && + (!maybe.has_value() || (maybe->bound() == bound_type::lower && bd.is_legal(maybe->best_move()) && + !bd.see_gt(maybe->best_move(), external.constants->nmp_see_threshold()))); + + if (try_nmp) { + ss.set_played(chess::move::null()); + const depth_type adjusted_depth = std::max(0, depth - external.constants->nmp_reduction(depth, beta, value)); + const score_type nmp_score = + -pv_search(ss.next(), eval_node, bd.forward(chess::move::null()), -beta, -beta + 1, adjusted_depth, chess::player_from(!bd.turn())); + if (nmp_score >= beta) { return make_result(nmp_score, chess::move::null()); } + } + + // step 9. probcut pruning + const depth_type probcut_depth = external.constants->probcut_search_depth(depth); + const score_type probcut_beta = external.constants->probcut_beta(beta); + const bool try_probcut = !is_pv && depth >= external.constants->probcut_depth() && !(maybe.has_value() && maybe->best_move().is_quiet()) && + !(maybe.has_value() && maybe->depth() >= probcut_depth && maybe->score() < probcut_beta); + + if (try_probcut) { + move_orderer probcut_orderer(move_orderer_data(&bd, &internal.hh.us(bd.turn()))); + if (maybe.has_value()) { probcut_orderer.set_first(maybe->best_move()); } + + for (const auto& [idx, mv] : probcut_orderer) { + if (!internal.keep_going()) { break; } + if (mv == ss.excluded()) { continue; } + if (!bd.see_ge(mv, 0)) { continue; } + + ss.set_played(mv); + + const chess::board bd_ = bd.forward(mv); + external.tt->prefetch(bd_.hash()); + internal.cache.prefetch(bd_.hash()); + nnue::eval_node eval_node_ = eval_node.dirty_child(&internal.reset_cache, &bd, mv); + + auto pv_score = [&] { return -pv_search(ss.next(), eval_node_, bd_, -probcut_beta, -probcut_beta + 1, probcut_depth, reducer); }; + const score_type q_score = -q_search(ss.next(), eval_node_, bd_, -probcut_beta, -probcut_beta + 1, 0); + const score_type probcut_score = (q_score >= probcut_beta) ? pv_score() : q_score; + + if (probcut_score >= probcut_beta) { return make_result(probcut_score, mv); } + } + } + + // step 10. initialize move orderer (setting tt move first if applicable) + const chess::move killer = ss.killer(); + const chess::move follow = ss.follow(); + const chess::move counter = ss.counter(); + + move_orderer orderer( + move_orderer_data(&bd, &internal.hh.us(bd.turn())).set_killer(killer).set_follow(follow).set_counter(counter).set_threatened(threatened)); + + if (maybe.has_value()) { orderer.set_first(maybe->best_move()); } + + // list of attempted moves for updating histories + chess::move_list moves_tried{}; + + // move loop + score_type best_score = ss.loss_score(); + chess::move best_move = chess::move::null(); + + bool did_double_extend{false}; + int legal_count{0}; + + for (const auto& [idx, mv] : orderer) { + ++legal_count; + if (!internal.keep_going()) { break; } + if (mv == ss.excluded()) { continue; } + + const std::size_t nodes_before = internal.nodes.load(std::memory_order_relaxed); + ss.set_played(mv); + + const counter_type history_value = internal.hh.us(bd.turn()).compute_value(history::context{follow, counter, threatened}, mv); + + const chess::board bd_ = bd.forward(mv); + + const bool try_pruning = !is_root && idx >= 2 && best_score > max_mate_score; + + // step 11. pruning + if (try_pruning) { + const bool lm_prune = !bd_.is_check() && depth <= external.constants->lmp_depth() && idx > external.constants->lmp_count(improving, depth); + + if (lm_prune) { break; } + + const bool futility_prune = + mv.is_quiet() && depth <= external.constants->futility_prune_depth() && value + external.constants->futility_margin(depth) < alpha; + + if (futility_prune) { continue; } + + const bool quiet_see_prune = mv.is_quiet() && depth <= external.constants->quiet_see_prune_depth() && + !bd.see_ge(mv, external.constants->quiet_see_prune_threshold(depth)); + + if (quiet_see_prune) { continue; } + + const bool noisy_see_prune = mv.is_noisy() && depth <= external.constants->noisy_see_prune_depth() && + !bd.see_ge(mv, external.constants->noisy_see_prune_threshold(depth)); + + if (noisy_see_prune) { continue; } + + const bool history_prune = mv.is_quiet() && history_value <= external.constants->history_prune_threshold(depth); + + if (history_prune) { continue; } + } + + external.tt->prefetch(bd_.hash()); + internal.cache.prefetch(bd_.hash()); + nnue::eval_node eval_node_ = eval_node.dirty_child(&internal.reset_cache, &bd, mv); + + // step 12. extensions + bool multicut = false; + const depth_type extension = [&, mv = mv] { + const bool try_singular = !is_root && !ss.has_excluded() && depth >= external.constants->singular_extension_depth() && maybe.has_value() && + mv == maybe->best_move() && maybe->bound() != bound_type::upper && + maybe->depth() + external.constants->singular_extension_depth_margin() >= depth; + + if (try_singular) { + const depth_type singular_depth = external.constants->singular_search_depth(depth); + const score_type singular_beta = external.constants->singular_beta(maybe->score(), depth); + ss.set_excluded(mv); + const score_type excluded_score = pv_search(ss, eval_node, bd, singular_beta - 1, singular_beta, singular_depth, reducer); + ss.set_excluded(chess::move::null()); + + if (!is_pv && excluded_score + external.constants->singular_double_extension_margin() < singular_beta) { + did_double_extend = true; + return 2; + } + + if (excluded_score < singular_beta) { return 1; } + if (excluded_score >= beta) { multicut = true; } + if constexpr (!is_pv) { return -1; } + } + + return 0; + }(); + + if (!is_root && multicut) { return make_result(beta, chess::move::null()); } + + const score_type score = [&, this, idx = idx, mv = mv] { + const depth_type next_depth = depth + extension - 1; + + auto full_width = [&] { return -pv_search(ss.next(), eval_node_, bd_, -beta, -alpha, next_depth, reducer); }; + + auto zero_width = [&](const depth_type& zw_depth) { + const chess::player_type next_reducer = (is_pv || zw_depth < next_depth) ? chess::player_from(bd.turn()) : reducer; + return -pv_search(ss.next(), eval_node_, bd_, -alpha - 1, -alpha, zw_depth, next_reducer); + }; + + if (is_pv && idx == 0) { return full_width(); } + + depth_type lmr_depth; + score_type zw_score; + + // step 13. late move reductions + const bool try_lmr = !is_check && (mv.is_quiet() || !bd.see_ge(mv, 0)) && idx >= 2 && (depth >= external.constants->reduce_depth()); + if (try_lmr) { + depth_type reduction = external.constants->reduction(depth, idx); + + // adjust reduction + if (improving) { --reduction; } + if (bd_.is_check()) { --reduction; } + if (bd.is_passed_push(mv)) { --reduction; } + if (bd.creates_threat(mv)) { --reduction; } + if (mv == killer) { --reduction; } + + if (!is_pv) { ++reduction; } + if (did_double_extend) { ++reduction; } + if (!bd.see_ge(mv, 0) && mv.is_quiet()) { ++reduction; } + + // if our opponent is the reducing player, an errant fail low will, at worst, induce a re-search + // this idea is at least similar (maybe equivalent) to the "cutnode idea" found in Stockfish. + if (is_player(reducer, !bd.turn())) { ++reduction; } + + if (mv.is_quiet()) { reduction += external.constants->history_reduction(history_value); } + + reduction = std::max(0, reduction); + + lmr_depth = std::max(1, next_depth - reduction); + zw_score = zero_width(lmr_depth); + } + + // search again at full depth if necessary + if (!try_lmr || (zw_score > alpha && lmr_depth < next_depth)) { zw_score = zero_width(next_depth); } + + // search again with full window on pv nodes + return (is_pv && (alpha < zw_score && zw_score < beta)) ? full_width() : zw_score; + }(); + + if (score < beta && (mv.is_quiet() || !bd.see_gt(mv, 0))) { moves_tried.push(mv); } + + if (score > best_score) { + best_score = score; + best_move = mv; + if (score > alpha) { + if (score < beta) { alpha = score; } + if constexpr (is_pv) { ss.prepend_to_pv(mv); } + } + } + + if constexpr (is_root) { internal.node_distribution[mv] += (internal.nodes.load(std::memory_order_relaxed) - nodes_before); } + + if (best_score >= beta) { break; } + } + + if (legal_count == 0 && is_check) { return make_result(ss.loss_score(), chess::move::null()); } + if (legal_count == 0) { return make_result(draw_score, chess::move::null()); } + + // step 14. update histories if appropriate and maybe insert a new transposition_table_entry + if (internal.keep_going() && !ss.has_excluded()) { + const bound_type bound = [&] { + if (best_score >= beta) { return bound_type::lower; } + if (is_pv && best_score > original_alpha) { return bound_type::exact; } + return bound_type::upper; + }(); + + if (bound == bound_type::lower && (best_move.is_quiet() || !bd.see_gt(best_move, 0))) { + internal.hh.us(bd.turn()).update(history::context{follow, counter, threatened}, best_move, moves_tried, depth); + ss.set_killer(best_move); + } + + const transposition_table_entry entry(bd.hash(), bound, best_score, best_move, depth); + external.tt->insert(entry); + } + + return make_result(best_score, best_move); +} + +void search_worker::iterative_deepening_loop() noexcept { + internal.reset_cache.reinitialize(external.weights); + nnue::eval_node root_node = nnue::eval_node::clean_node([this] { + nnue::eval result(external.weights, &internal.scratchpad, 0, 0); + internal.stack.root().feature_full_reset(result); + return result; + }()); + + score_type alpha = -big_number; + score_type beta = big_number; + for (; internal.keep_going(); ++internal.depth) { + internal.depth = std::min(max_depth, internal.depth.load()); + // update aspiration window once reasonable evaluation is obtained + if (internal.depth >= external.constants->aspiration_depth()) { + const score_type previous_score = internal.score; + alpha = previous_score - aspiration_delta; + beta = previous_score + aspiration_delta; + } + + score_type delta = aspiration_delta; + depth_type consecutive_failed_high_count{0}; + + for (;;) { + internal.stack.clear_future(); + + const depth_type adjusted_depth = std::max(1, internal.depth - consecutive_failed_high_count); + const auto [search_score, search_move] = pv_search( + stack_view::root(internal.stack), root_node, internal.stack.root(), alpha, beta, adjusted_depth, chess::player_type::none); + + if (!internal.keep_going()) { break; } + + // update aspiration window if failing low or high + if (search_score <= alpha) { + beta = (alpha + beta) / 2; + alpha = search_score - delta; + consecutive_failed_high_count = 0; + } else if (search_score >= beta) { + beta = search_score + delta; + ++consecutive_failed_high_count; + } else { + // store updated information + internal.score.store(search_score); + if (!search_move.is_null()) { + internal.best_move.store(search_move.data); + internal.ponder_move.store(internal.stack.ponder_move().data); + } + break; + } + + // exponentially grow window + delta += delta / 3; + } + + // callback on iteration completion + if (internal.keep_going()) { external.on_iter(*this); } + } +} + +} // namespace search \ No newline at end of file diff --git a/src/search/search_worker_orchestrator.cc b/src/search/search_worker_orchestrator.cc new file mode 100644 index 0000000..b10bf57 --- /dev/null +++ b/src/search/search_worker_orchestrator.cc @@ -0,0 +1,97 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include + +namespace search { + +void worker_orchestrator::reset() noexcept { + tt_->clear(); + for (auto& worker : workers_) { worker->internal.reset(); }; +} + +void worker_orchestrator::resize(const std::size_t& new_size) noexcept { + constants_->update_(new_size); + const std::size_t old_size = workers_.size(); + workers_.resize(new_size); + for (std::size_t i(old_size); i < new_size; ++i) { workers_[i] = std::make_unique(weights_, tt_, constants_); } +} + +void worker_orchestrator::go(const chess::board_history& hist, const chess::board& bd) noexcept { + std::lock_guard access_lock(access_mutex_); + std::for_each(workers_.begin(), workers_.end(), [](auto& worker) { worker->stop(); }); + std::for_each(threads_.begin(), threads_.end(), [](auto& thread) { thread.join(); }); + threads_.clear(); + + tt_->update_gen(); + for (std::size_t i(0); i < workers_.size(); ++i) { + const depth_type start_depth = 1 + static_cast(i % 2); + workers_[i]->go(hist, bd, start_depth); + } + + std::transform(workers_.begin(), workers_.end(), std::back_inserter(threads_), [](auto& worker) { + return std::thread([&worker] { worker->iterative_deepening_loop(); }); + }); + + is_searching_.store(true); +} + +void worker_orchestrator::stop() noexcept { + std::lock_guard access_lock(access_mutex_); + std::for_each(workers_.begin(), workers_.end(), [](auto& worker) { worker->stop(); }); + is_searching_.store(false); +} + +bool worker_orchestrator::is_searching() noexcept { + std::lock_guard access_lock(access_mutex_); + return is_searching_.load(); +} + +std::size_t worker_orchestrator::nodes() const noexcept { + return std::accumulate(workers_.begin(), workers_.end(), static_cast(0), [](const std::size_t& count, const auto& worker) { + return count + worker->nodes(); + }); +} + +std::size_t worker_orchestrator::tb_hits() const noexcept { + return std::accumulate(workers_.begin(), workers_.end(), static_cast(0), [](const std::size_t& count, const auto& worker) { + return count + worker->tb_hits(); + }); +} + +search_worker& worker_orchestrator::primary_worker() noexcept { return *workers_[primary_id]; } + +worker_orchestrator::worker_orchestrator( + const nnue::weights* weights, + std::size_t hash_table_size, + std::function on_iter, + std::function on_update) noexcept { + weights_ = weights; + tt_ = std::make_shared(hash_table_size); + constants_ = std::make_shared(); + workers_.push_back(std::make_unique(weights, tt_, constants_, on_iter, on_update)); +} + +worker_orchestrator::~worker_orchestrator() noexcept { + std::lock_guard access_lock(access_mutex_); + std::for_each(workers_.begin(), workers_.end(), [](auto& worker) { worker->stop(); }); + std::for_each(threads_.begin(), threads_.end(), [](auto& thread) { thread.join(); }); +} + +} // namespace search \ No newline at end of file diff --git a/src/search/syzygy.cc b/src/search/syzygy.cc new file mode 100644 index 0000000..abfced7 --- /dev/null +++ b/src/search/syzygy.cc @@ -0,0 +1,88 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +namespace search::syzygy { + +tb_dtz_result tb_dtz_result::from_value(const chess::board& bd, const unsigned int& value) noexcept { + auto is_same_promo = [](const chess::move& mv, const int& promo) { + constexpr int num_pieces = 6; + return ((!mv.is_promotion() && promo == 0) || (mv.is_promotion() && (num_pieces - promo - 1) == static_cast(mv.promotion()))); + }; + + if (value == TB_RESULT_FAILED || value == TB_RESULT_CHECKMATE || value == TB_RESULT_STALEMATE) { return failure(); } + const int wdl = TB_GET_WDL(value); + + const chess::move dtz_move = [&] { + const chess::move_list list = bd.generate_moves<>(); + const int promo = TB_GET_PROMOTES(value); + const int from = TB_GET_FROM(value); + const int to = TB_GET_TO(value); + const auto it = std::find_if(list.begin(), list.end(), [&](const chess::move& mv) { + return mv.from().index() == from && mv.to().index() == to && is_same_promo(mv, promo); + }); + if (it != list.end()) { return *it; } + return chess::move::null(); + }(); + + if (dtz_move == chess::move::null()) { return failure(); } + + if (wdl == TB_WIN) { return tb_dtz_result{true, search::tb_win_score, dtz_move}; } + if (wdl == TB_LOSS) { return tb_dtz_result{true, search::tb_loss_score, dtz_move}; } + return tb_dtz_result{true, search::draw_score, dtz_move}; +} + +tb_wdl_result probe_wdl(const chess::board& bd) noexcept { + if (bd.num_pieces() > TB_LARGEST || bd.lat_.half_clock != 0) { return tb_wdl_result::failure(); } + if (bd.lat_.white.oo() || bd.lat_.white.ooo() || bd.lat_.black.oo() || bd.lat_.black.ooo()) { return tb_wdl_result::failure(); } + + constexpr unsigned int rule_50 = 0; + constexpr unsigned int castling_rights = 0; + const unsigned int ep = bd.lat_.them(bd.turn()).ep_mask().any() ? bd.lat_.them(bd.turn()).ep_mask().item().index() : 0; + const bool turn = bd.turn(); + + const unsigned value = tb_probe_wdl( + bd.man_.white.all().data, bd.man_.black.all().data, (bd.man_.white.king() | bd.man_.black.king()).data, + (bd.man_.white.queen() | bd.man_.black.queen()).data, (bd.man_.white.rook() | bd.man_.black.rook()).data, + (bd.man_.white.bishop() | bd.man_.black.bishop()).data, (bd.man_.white.knight() | bd.man_.black.knight()).data, + (bd.man_.white.pawn() | bd.man_.black.pawn()).data, rule_50, castling_rights, ep, turn); + + return tb_wdl_result::from_value(value); +} + +tb_dtz_result probe_dtz(const chess::board& bd) noexcept { + if (bd.num_pieces() > TB_LARGEST) { return tb_dtz_result::failure(); } + if (bd.lat_.white.oo() || bd.lat_.white.ooo() || bd.lat_.black.oo() || bd.lat_.black.ooo()) { return tb_dtz_result::failure(); } + + const unsigned int rule_50 = bd.lat_.half_clock; + constexpr unsigned int castling_rights = 0; + const unsigned int ep = bd.lat_.them(bd.turn()).ep_mask().any() ? bd.lat_.them(bd.turn()).ep_mask().item().index() : 0; + const bool turn = bd.turn(); + + const unsigned value = tb_probe_root( + bd.man_.white.all().data, bd.man_.black.all().data, (bd.man_.white.king() | bd.man_.black.king()).data, + (bd.man_.white.queen() | bd.man_.black.queen()).data, (bd.man_.white.rook() | bd.man_.black.rook()).data, + (bd.man_.white.bishop() | bd.man_.black.bishop()).data, (bd.man_.white.knight() | bd.man_.black.knight()).data, + (bd.man_.white.pawn() | bd.man_.black.pawn()).data, rule_50, castling_rights, ep, turn, nullptr); + + return tb_dtz_result::from_value(bd, value); +} + +void init(const std::string& path) noexcept { tb_init(path.c_str()); } + +} // namespace search \ No newline at end of file diff --git a/src/search/transposition_table.cc b/src/search/transposition_table.cc new file mode 100644 index 0000000..1615398 --- /dev/null +++ b/src/search/transposition_table.cc @@ -0,0 +1,66 @@ +/* + Seer is a UCI chess engine by Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle + + Seer is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Seer is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +namespace search { + +void transposition_table::clear() noexcept { + for (auto& elem : data) { elem = bucket_type{}; } +} + +void transposition_table::resize(const std::size_t& size) noexcept { + clear(); + data.resize(size * one_mb, bucket_type{}); +} + +void transposition_table::update_gen() noexcept { + using gen_type = transposition_table_entry::gen_type; + constexpr gen_type limit = gen_type{1} << transposition_table_entry::gen_bits; + current_gen = (current_gen + 1) % limit; +} + +// clang-format off + +__attribute__((no_sanitize("thread"))) +transposition_table& transposition_table::insert(const transposition_table_entry& entry) noexcept { + constexpr depth_type offset = 2; + const transposition_table_entry::gen_type gen = current_gen.load(std::memory_order_relaxed); + + transposition_table_entry* to_replace = data[hash_function(entry.key())].to_replace(gen, entry.key()); + + const bool should_replace = + (entry.bound() == bound_type::exact) || (entry.key() != to_replace->key()) || ((entry.depth() + offset) >= to_replace->depth()); + + if (should_replace) { *to_replace = transposition_table_entry(entry).set_gen(gen).merge(*to_replace); } + + return *this; +} + +// clang-format on + +// clang-format off + +__attribute__((no_sanitize("thread"))) +std::optional transposition_table::find(const zobrist::hash_type& key) noexcept { + const transposition_table_entry::gen_type gen = current_gen.load(std::memory_order_relaxed); + return data[hash_function(key)].match(gen, key); +} + +// clang-format on + +} // namespace search \ No newline at end of file diff --git a/src/seer.cc b/src/seer.cc index 2ecb546..a5002fa 100644 --- a/src/seer.cc +++ b/src/seer.cc @@ -1,6 +1,6 @@ /* Seer is a UCI chess engine by Connor McMonigle - Copyright (C) 2021 Connor McMonigle + Copyright (C) 2021-2023 Connor McMonigle Seer is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -15,13 +15,12 @@ along with this program. If not, see . */ -#include +#include -#include #include #include -int main(int argc, char* argv[]) { +int main(const int argc, const char* argv[]) { engine::uci uci{}; const bool perform_bench = (argc == 2) && (std::string(argv[1]) == "bench"); diff --git a/syzygy/tbchess.c b/syzygy/tbchess.c index 3b84040..f0b703d 100644 --- a/syzygy/tbchess.c +++ b/syzygy/tbchess.c @@ -303,15 +303,15 @@ static const uint64_t anti2board_table[15] = 0x0001020408102040ull, }; -static inline size_t diag2index(uint64_t b) +static inline std::size_t diag2index(uint64_t b) { b *= 0x0101010101010101ull; b >>= 56; b >>= 1; - return (size_t)b; + return (std::size_t)b; } -static inline size_t anti2index(uint64_t b) +static inline std::size_t anti2index(uint64_t b) { return diag2index(b); } @@ -327,8 +327,8 @@ static uint64_t bishop_attacks(unsigned sq, uint64_t occ) unsigned d = diag(sq), a = anti(sq); uint64_t d_occ = occ & (diag2board(d) & ~BOARD_EDGE); uint64_t a_occ = occ & (anti2board(a) & ~BOARD_EDGE); - size_t d_idx = diag2index(d_occ); - size_t a_idx = anti2index(a_occ); + std::size_t d_idx = diag2index(d_occ); + std::size_t a_idx = anti2index(a_occ); uint64_t d_attacks = diag_attacks_table[sq][d_idx]; uint64_t a_attacks = anti_attacks_table[sq][a_idx]; return d_attacks | a_attacks; @@ -399,20 +399,20 @@ static void bishop_attacks_init(void) static uint64_t rank_attacks_table[64][64]; static uint64_t file_attacks_table[64][64]; -static inline size_t rank2index(uint64_t b, unsigned r) +static inline std::size_t rank2index(uint64_t b, unsigned r) { b >>= (8 * r); b >>= 1; - return (size_t)b; + return (std::size_t)b; } -static inline size_t file2index(uint64_t b, unsigned f) +static inline std::size_t file2index(uint64_t b, unsigned f) { b >>= f; b *= 0x0102040810204080ull; b >>= 56; b >>= 1; - return (size_t)b; + return (std::size_t)b; } #define rank2board(r) (0xFFull << (8 * (r))) @@ -424,8 +424,8 @@ static uint64_t rook_attacks(unsigned sq, uint64_t occ) unsigned r = rank(sq), f = file(sq); uint64_t r_occ = occ & (rank2board(r) & ~BOARD_RANK_EDGE); uint64_t f_occ = occ & (file2board(f) & ~BOARD_FILE_EDGE); - size_t r_idx = rank2index(r_occ, r); - size_t f_idx = file2index(f_occ, f); + std::size_t r_idx = rank2index(r_occ, r); + std::size_t f_idx = file2index(f_occ, f); uint64_t r_attacks = rank_attacks_table[sq][r_idx]; uint64_t f_attacks = file_attacks_table[sq][f_idx]; return r_attacks | f_attacks; diff --git a/syzygy/tbprobe.c b/syzygy/tbprobe.c index 4bfa446..4bcd6ca 100644 --- a/syzygy/tbprobe.c +++ b/syzygy/tbprobe.c @@ -57,7 +57,7 @@ using namespace std; #define SEP_CHAR ':' #define FD int #define FD_ERR -1 -typedef size_t map_t; +typedef std::size_t map_t; #else #ifndef NOMINMAX #define NOMINMAX @@ -257,13 +257,13 @@ inline static uint16_t read_le_u16(void *p) return from_le_u16(*(uint16_t *)p); } -static size_t file_size(FD fd) { +static std::size_t file_size(FD fd) { #ifdef _WIN32 LARGE_INTEGER fileSize; if (GetFileSizeEx(fd, &fileSize)==0) { return 0; } - return (size_t)fileSize.QuadPart; + return (std::size_t)fileSize.QuadPart; #else struct stat buf; if (fstat(fd,&buf)) { @@ -304,7 +304,7 @@ static FD open_tb(const char *str, const char *suffix) #else #ifdef _UNICODE wchar_t ucode_name[4096]; - size_t len; + std::size_t len; mbstowcs_s(&len, ucode_name, 4096, file, _TRUNCATE); fd = CreateFile(ucode_name, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); @@ -421,7 +421,7 @@ struct PairsData { struct EncInfo { struct PairsData *precomp; - size_t factor[TB_PIECES]; + std::size_t factor[TB_PIECES]; uint8_t pieces[TB_PIECES]; uint8_t norm[TB_PIECES]; }; @@ -667,7 +667,7 @@ static bool test_tb(const char *str, const char *suffix) { FD fd = open_tb(str, suffix); if (fd != FD_ERR) { - size_t size = file_size(fd); + std::size_t size = file_size(fd); close_tb(fd); if ((size & 63) != 16) { fprintf(stderr, "Incomplete tablebase file %s.%s\n", str, suffix); @@ -913,7 +913,7 @@ bool tb_init(const char *path) } // 6- and 7-piece TBs make sense only with a 64-bit address space - if (sizeof(size_t) < 8 || TB_PIECES < 6) + if (sizeof(std::size_t) < 8 || TB_PIECES < 6) goto finished; for (i = 0; i < 5; i++) @@ -1171,10 +1171,10 @@ static const uint8_t FileToFile[] = { 0, 1, 2, 3, 3, 2, 1, 0 }; static const int WdlToMap[5] = { 1, 3, 0, 2, 0 }; static const uint8_t PAFlags[5] = { 8, 0, 0, 0, 4 }; -static size_t Binomial[7][64]; -static size_t PawnIdx[2][6][24]; -static size_t PawnFactorFile[6][4]; -static size_t PawnFactorRank[6][6]; +static std::size_t Binomial[7][64]; +static std::size_t PawnIdx[2][6][24]; +static std::size_t PawnFactorFile[6][4]; +static std::size_t PawnFactorRank[6][6]; static void init_indices(void) { @@ -1183,8 +1183,8 @@ static void init_indices(void) // Binomial[k][n] = Bin(n, k) for (i = 0; i < 7; i++) for (j = 0; j < 64; j++) { - size_t f = 1; - size_t l = 1; + std::size_t f = 1; + std::size_t l = 1; for (k = 0; k < i; k++) { f *= (j - k); l *= (k + 1); @@ -1193,7 +1193,7 @@ static void init_indices(void) } for (i = 0; i < 6; i++) { - size_t s = 0; + std::size_t s = 0; for (j = 0; j < 24; j++) { PawnIdx[0][i][j] = s; s += Binomial[i][PawnTwist[0][(1 + (j % 6)) * 8 + (j / 6)]]; @@ -1205,7 +1205,7 @@ static void init_indices(void) } for (i = 0; i < 6; i++) { - size_t s = 0; + std::size_t s = 0; for (j = 0; j < 24; j++) { PawnIdx[1][i][j] = s; s += Binomial[i][PawnTwist[1][(1 + (j / 4)) * 8 + (j % 4)]]; @@ -1226,11 +1226,11 @@ int leading_pawn(int *p, struct BaseEntry *be, const int enc) return enc == FILE_ENC ? FileToFile[p[0] & 7] : (p[0] - 8) >> 3; } -size_t encode(int *p, struct EncInfo *ei, struct BaseEntry *be, +std::size_t encode(int *p, struct EncInfo *ei, struct BaseEntry *be, const int enc) { int n = be->num; - size_t idx; + std::size_t idx; int k; if (p[0] & 0x04) @@ -1286,7 +1286,7 @@ size_t encode(int *p, struct EncInfo *ei, struct BaseEntry *be, for (int i = k; i < t; i++) for (int j = i + 1; j < t; j++) if (p[i] > p[j]) Swap(p[i], p[j]); - size_t s = 0; + std::size_t s = 0; for (int i = k; i < t; i++) { int sq = p[i]; int skips = 0; @@ -1304,7 +1304,7 @@ size_t encode(int *p, struct EncInfo *ei, struct BaseEntry *be, for (int i = k; i < t; i++) for (int j = i + 1; j < t; j++) if (p[i] > p[j]) Swap(p[i], p[j]); - size_t s = 0; + std::size_t s = 0; for (int i = k; i < t; i++) { int sq = p[i]; int skips = 0; @@ -1319,27 +1319,27 @@ size_t encode(int *p, struct EncInfo *ei, struct BaseEntry *be, return idx; } -static size_t encode_piece(int *p, struct EncInfo *ei, struct BaseEntry *be) +static std::size_t encode_piece(int *p, struct EncInfo *ei, struct BaseEntry *be) { return encode(p, ei, be, PIECE_ENC); } -static size_t encode_pawn_f(int *p, struct EncInfo *ei, struct BaseEntry *be) +static std::size_t encode_pawn_f(int *p, struct EncInfo *ei, struct BaseEntry *be) { return encode(p, ei, be, FILE_ENC); } -static size_t encode_pawn_r(int *p, struct EncInfo *ei, struct BaseEntry *be) +static std::size_t encode_pawn_r(int *p, struct EncInfo *ei, struct BaseEntry *be) { return encode(p, ei, be, RANK_ENC); } // Count number of placements of k like pieces on n squares -static size_t subfactor(size_t k, size_t n) +static std::size_t subfactor(std::size_t k, std::size_t n) { - size_t f = n; - size_t l = 1; - for (size_t i = 1; i < k; i++) { + std::size_t f = n; + std::size_t l = 1; + for (std::size_t i = 1; i < k; i++) { f *= n - i; l *= i + 1; } @@ -1347,7 +1347,7 @@ static size_t subfactor(size_t k, size_t n) return f / l; } -static size_t init_enc_info(struct EncInfo *ei, struct BaseEntry *be, +static std::size_t init_enc_info(struct EncInfo *ei, struct BaseEntry *be, uint8_t *tb, int shift, int t, const int enc) { bool morePawns = enc != PIECE_ENC && be->pawns[1] > 0; @@ -1373,7 +1373,7 @@ static size_t init_enc_info(struct EncInfo *ei, struct BaseEntry *be, ei->norm[i]++; int n = 64 - k; - size_t f = 1; + std::size_t f = 1; for (int i = 0; k < be->num || i == order || i == order2; i++) { if (i == order) { @@ -1410,8 +1410,8 @@ static void calc_symLen(struct PairsData *d, uint32_t s, char *tmp) tmp[s] = 1; } -static struct PairsData *setup_pairs(uint8_t **ptr, size_t tb_size, - size_t *size, uint8_t *flags, int type) +static struct PairsData *setup_pairs(uint8_t **ptr, std::size_t tb_size, + std::size_t *size, uint8_t *flags, int type) { struct PairsData *d; uint8_t *data = *ptr; @@ -1444,10 +1444,10 @@ static struct PairsData *setup_pairs(uint8_t **ptr, size_t tb_size, d->minLen = minLen; *ptr = &data[12 + 2 * h + 3 * numSyms + (numSyms & 1)]; - size_t num_indices = (tb_size + (1ULL << idxBits) - 1) >> idxBits; + std::size_t num_indices = (tb_size + (1ULL << idxBits) - 1) >> idxBits; size[0] = 6ULL * num_indices; size[1] = 2ULL * numBlocks; - size[2] = (size_t)realNumBlocks << blockSize; + size[2] = (std::size_t)realNumBlocks << blockSize; assert(numSyms < TB_MAX_SYMS); char tmp[TB_MAX_SYMS]; @@ -1490,7 +1490,7 @@ static bool init_table(struct BaseEntry *be, const char *str, int type) data += 5; - size_t tb_size[6][2]; + std::size_t tb_size[6][2]; int num = num_tables(be, type); struct EncInfo *ei = first_ei(be, type); int enc = !be->hasPawns ? PIECE_ENC : type != DTM ? FILE_ENC : RANK_ENC; @@ -1503,7 +1503,7 @@ static bool init_table(struct BaseEntry *be, const char *str, int type) } data += (uintptr_t)data & 1; - size_t size[6][2][3]; + std::size_t size[6][2][3]; for (int t = 0; t < num; t++) { uint8_t flags; ei[t].precomp = setup_pairs(&data, tb_size[t][0], size[t][0], &flags, type); @@ -1600,13 +1600,13 @@ static bool init_table(struct BaseEntry *be, const char *str, int type) return true; } -static uint8_t *decompress_pairs(struct PairsData *d, size_t idx) +static uint8_t *decompress_pairs(struct PairsData *d, std::size_t idx) { if (!d->idxBits) return d->constValue; uint32_t mainIdx = (uint32_t)(idx >> d->idxBits); - int litIdx = (idx & (((size_t)1 << d->idxBits) - 1)) - ((size_t)1 << (d->idxBits - 1)); + int litIdx = (idx & (((std::size_t)1 << d->idxBits) - 1)) - ((std::size_t)1 << (d->idxBits - 1)); uint32_t block; memcpy(&block, d->indexTable + 6 * mainIdx, sizeof(block)); block = from_le_u32(block); @@ -1621,7 +1621,7 @@ static uint8_t *decompress_pairs(struct PairsData *d, size_t idx) while (litIdx > d->sizeTable[block]) litIdx -= d->sizeTable[block++] + 1; - uint32_t *ptr = (uint32_t *)(d->data + ((size_t)block << d->blockSize)); + uint32_t *ptr = (uint32_t *)(d->data + ((std::size_t)block << d->blockSize)); int m = d->minLen; uint16_t *offset = d->offset; @@ -1765,7 +1765,7 @@ int probe_table(const Pos *pos, int s, int *success, const int type) struct EncInfo *ei = first_ei(be, type); int p[TB_PIECES]; - size_t idx; + std::size_t idx; int t = 0; uint8_t flags = 0; // initialize to fix GCC warning @@ -2476,8 +2476,8 @@ static uint16_t probe_root(Pos *pos, int *score, unsigned *results) uint16_t moves0[MAX_MOVES]; uint16_t *moves = moves0; uint16_t *end = gen_moves(pos, moves); - size_t len = end - moves; - size_t num_draw = 0; + std::size_t len = end - moves; + std::size_t num_draw = 0; unsigned j = 0; for (unsigned i = 0; i < len; i++) { @@ -2571,7 +2571,7 @@ static uint16_t probe_root(Pos *pos, int *score, unsigned *results) // Select a "random" move that preserves the draw. // Uses calc_key as the PRNG. - size_t count = calc_key(pos, !pos->turn) % num_draw; + std::size_t count = calc_key(pos, !pos->turn) % num_draw; for (unsigned i = 0; i < len; i++) { int v = scores[i];