From a24191ca89e378b17fb5445eab35f1cee997ee27 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Mon, 5 Jan 2026 20:48:52 +0700 Subject: [PATCH 01/50] Delete Makefile --- Makefile | 32 -------------------------------- 1 file changed, 32 deletions(-) delete mode 100644 Makefile diff --git a/Makefile b/Makefile deleted file mode 100644 index 36d7111..0000000 --- a/Makefile +++ /dev/null @@ -1,32 +0,0 @@ -# Compiler and Flags -CXX = g++ - -# Source files and object files -SRC = main.cpp eval.cpp search.cpp tt.cpp movepick.cpp timeman.cpp uci.cpp ucioptions.cpp -OBJ = $(SRC:.cpp=.o) - -# Output file -EXEC = chess_engine - -# Default target: build the project -all: $(EXEC) - -# Linking the object files into the executable -$(EXEC): $(OBJ) - $(CXX) $(LDFLAGS) -o $(EXEC) $(OBJ) - -# Rule to build object files from source files -%.o: %.cpp - $(CXX) $(CXXFLAGS) -std=c++17 -c $< -o $@ - -# Clean up the compiled files -clean: - rm -f $(OBJ) $(EXEC) - -# To run the program after compilation -run: $(EXEC) - ./$(EXEC) - -# Rebuild the project -rebuild: clean all - From 51d4c29aeac5a2e5bb2b6da0b3d70100a57763c5 Mon Sep 17 00:00:00 2001 From: winapiadmin Date: Tue, 3 Mar 2026 20:56:19 +0700 Subject: [PATCH 02/50] integrated library --- .gitignore | 3 +- CMakeLists.txt | 96 +- chess.hpp | 5195 ------------------------------------------------ eval.cpp | 908 ++------- eval.h | 37 + eval.hpp | 18 - main.cpp | 31 +- movepick.cpp | 191 +- movepick.h | 5 + movepick.hpp | 18 - score.cpp | 22 + score.h | 66 + search.cpp | 484 ++--- search.h | 13 + search.hpp | 17 - timeman.cpp | 163 +- timeman.h | 55 + timeman.hpp | 31 - tt.cpp | 59 +- tt.h | 179 ++ tt.hpp | 122 -- tune.cpp | 126 ++ tune.h | 192 ++ uci.cpp | 407 ++-- uci.h | 37 + uci.hpp | 11 - ucioption.cpp | 211 ++ ucioption.h | 106 + ucioptions.cpp | 168 -- ucioptions.hpp | 47 - 30 files changed, 1718 insertions(+), 7300 deletions(-) delete mode 100644 chess.hpp create mode 100644 eval.h delete mode 100644 eval.hpp create mode 100644 movepick.h delete mode 100644 movepick.hpp create mode 100644 score.cpp create mode 100644 score.h create mode 100644 search.h delete mode 100644 search.hpp create mode 100644 timeman.h delete mode 100644 timeman.hpp create mode 100644 tt.h delete mode 100644 tt.hpp create mode 100644 tune.cpp create mode 100644 tune.h create mode 100644 uci.h delete mode 100644 uci.hpp create mode 100644 ucioption.cpp create mode 100644 ucioption.h delete mode 100644 ucioptions.cpp delete mode 100644 ucioptions.hpp diff --git a/.gitignore b/.gitignore index a54deda..fb96e91 100644 --- a/.gitignore +++ b/.gitignore @@ -3,10 +3,9 @@ # But not these directories and files !*.cpp -!*.hpp !*.h !Makefile !README.md !LICENSE !.gitignore -!CMakeLists.txt \ No newline at end of file +!CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f8f427..d9eb054 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,76 +1,42 @@ cmake_minimum_required(VERSION 3.10) -project(chess_engine) +project(cppchess_engine) # Use C++17 set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) - -# Optional: enable warnings (MSVC specific flags) -if (MSVC) - add_compile_options(/W4 /permissive-) -else() - add_compile_options(-Wall -Wextra -pedantic) -endif() - -# Add the source files -set(SOURCES - main.cpp - eval.cpp - search.cpp - tt.cpp - movepick.cpp - uci.cpp - ucioptions.cpp - timeman.cpp +include(FetchContent) +FetchContent_Declare( + chesslib + GIT_REPOSITORY https://github.com/winapiadmin/chesslib.git + GIT_TAG maintaince_1 +) +FetchContent_MakeAvailable(chesslib) +add_executable(engine "main.cpp" "timeman.cpp" "timeman.h" "eval.h" "eval.cpp" "tune.h" "ucioption.h" "tune.cpp" "ucioption.cpp" "tt.h" "tt.cpp" "uci.cpp" "uci.h" "search.h" "search.cpp" "score.h" "score.cpp" "movepick.h" "movepick.cpp") +target_link_libraries(engine PRIVATE chesslib) +target_include_directories(engine PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${chesslib_SOURCE_DIR}) +execute_process( + COMMAND git rev-parse --short HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_SHA + OUTPUT_STRIP_TRAILING_WHITESPACE ) -# Define the executable -add_executable(${PROJECT_NAME} ${SOURCES}) - -# Threading support -find_package(Threads REQUIRED) -target_link_libraries(${PROJECT_NAME} Threads::Threads) - -# Platform-specific threading configurations -if(WIN32) - if(MSVC) - # Windows with MSVC - uses Windows threading API - target_compile_definitions(${PROJECT_NAME} PRIVATE WIN32_LEAN_AND_MEAN) - elseif(MINGW) - # MinGW on Windows - uses pthread - target_compile_definitions(${PROJECT_NAME} PRIVATE _WIN32_WINNT=0x0601) - # MinGW might need explicit pthread linking - if(CMAKE_THREAD_LIBS_INIT) - target_link_libraries(${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT}) - endif() - endif() -elseif(UNIX) - # Unix/Linux systems - uses pthread - if(CMAKE_THREAD_LIBS_INIT) - target_link_libraries(${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT}) - endif() - # Some older systems might need explicit -pthread flag - if(CMAKE_USE_PTHREADS_INIT) - target_compile_options(${PROJECT_NAME} PRIVATE -pthread) - target_link_options(${PROJECT_NAME} PRIVATE -pthread) - endif() -endif() +execute_process( + COMMAND git describe --tags --exact-match + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_TAG + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE +) -# Optional: Add atomic library support for older compilers -if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "4.9") - target_link_libraries(${PROJECT_NAME} atomic) +if(CMAKE_BUILD_TYPE STREQUAL "Debug" OR NOT CMAKE_BUILD_TYPE) + set(BUILD_VERSION "debug-${GIT_SHA}") +elseif(GIT_TAG) + set(BUILD_VERSION "${GIT_TAG}") +else() + set(BUILD_VERSION "release-${GIT_SHA}") endif() -# Set default build type if none is specified -if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the build type (Debug, Release, etc.)" FORCE) -endif() +message(STATUS "Build Version: ${BUILD_VERSION}") +add_definitions(-DBUILD_VERSION="${BUILD_VERSION}") -# Optional: Add specific compile flags per build type -if(NOT MSVC) - set(CMAKE_CXX_FLAGS_DEBUG "-g -march=native -mtune=native") - set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG -march=native -mtune=native -funroll-loops -ftree-vectorize -fomit-frame-pointer") -else() - set(CMAKE_CXX_FLAGS_DEBUG "/Zi /Od") - set(CMAKE_CXX_FLAGS_RELEASE "/O2 /DNDEBUG") -endif() diff --git a/chess.hpp b/chess.hpp deleted file mode 100644 index c2c7698..0000000 --- a/chess.hpp +++ /dev/null @@ -1,5195 +0,0 @@ -/* -MIT License - -Copyright (c) 2023 disservin - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -THIS FILE IS AUTO GENERATED DO NOT CHANGE MANUALLY. - -Source: https://github.com/Disservin/chess-library - -VERSION: 0.8.13 -*/ - -#ifndef CHESS_HPP -#define CHESS_HPP - - -#include -#include - - -#include -#ifdef CHESS_USE_PEXT -# include -#endif - - -#if __cpp_lib_bitops >= 201907L -# include -#endif -#include -#include -#include -#include -#include - -#if defined(_MSC_VER) -# include -# include -#endif - - -#include - - -#include - -namespace chess { - -class Color { - public: - enum class underlying : std::int8_t { WHITE = 0, BLACK = 1, NONE = -1 }; - - constexpr Color() : color(underlying::NONE) {} - constexpr Color(underlying c) : color(c) { assert(isValid(static_cast(c))); } - constexpr Color(int c) : Color(static_cast(c)) { assert(isValid(c)); } - constexpr Color(std::string_view str) - : color(str == "w" ? underlying::WHITE - : str == "b" ? underlying::BLACK - : underlying::NONE) {} - - /** - * @brief Gets the long string representation of the color - * @return "White" for WHITE, "Black" for BLACK - * "None" for NONE - */ - [[nodiscard]] std::string longStr() const { - switch (color) { - case underlying::WHITE: - return "White"; - case underlying::BLACK: - return "Black"; - default: - return "None"; - } - } - - constexpr Color operator~() const noexcept { - assert(color != underlying::NONE); - return Color(static_cast(static_cast(color) ^ 1)); - } - - constexpr bool operator==(const Color& rhs) const noexcept { return color == rhs.color; } - constexpr bool operator!=(const Color& rhs) const noexcept { return color != rhs.color; } - - constexpr operator int() const noexcept { return static_cast(color); } - - explicit operator std::string() const { - return color == underlying::WHITE ? "w" : color == underlying::BLACK ? "b" : "NONE"; - } - - [[nodiscard]] constexpr underlying internal() const noexcept { return color; } - - friend std::ostream& operator<<(std::ostream& os, const Color& color) { - return os << static_cast(color); - } - - static constexpr underlying WHITE = underlying::WHITE; - static constexpr underlying BLACK = underlying::BLACK; - static constexpr underlying NONE = underlying::NONE; - - private: - underlying color; - - static constexpr bool isValid(int c) { return c == 0 || c == 1 || c == -1; } -}; - -constexpr Color::underlying operator~(Color::underlying color) { - return color == Color::underlying::WHITE ? Color::underlying::BLACK - : color == Color::underlying::BLACK ? Color::underlying::WHITE - : Color::underlying::NONE; -} - -} // namespace chess - -#include - -namespace chess { -namespace utils { - -// Split a string by a delimiter -[[nodiscard]] inline std::vector splitString(std::string_view string, const char &delimiter) { - std::vector result; - size_t start = 0; - size_t end = string.find(delimiter); - - while (end != std::string_view::npos) { - result.push_back(string.substr(start, end - start)); - start = end + 1; - end = string.find(delimiter, start); - } - - // Add the last chunk (or the only chunk if there are no delimiters) - result.push_back(string.substr(start)); - - return result; -} - -constexpr char tolower(char c) { return (c >= 'A' && c <= 'Z') ? (c - 'A' + 'a') : c; } - -} // namespace utils - -} // namespace chess - -namespace chess { - -#define CHESS_DECLARE_RANK(N) \ - static constexpr auto SQ_A##N = underlying::SQ_A##N; \ - static constexpr auto SQ_B##N = underlying::SQ_B##N; \ - static constexpr auto SQ_C##N = underlying::SQ_C##N; \ - static constexpr auto SQ_D##N = underlying::SQ_D##N; \ - static constexpr auto SQ_E##N = underlying::SQ_E##N; \ - static constexpr auto SQ_F##N = underlying::SQ_F##N; \ - static constexpr auto SQ_G##N = underlying::SQ_G##N; \ - static constexpr auto SQ_H##N = underlying::SQ_H##N; - -class File { - public: - enum class underlying : std::uint8_t { FILE_A, FILE_B, FILE_C, FILE_D, FILE_E, FILE_F, FILE_G, FILE_H, NO_FILE }; - - constexpr File() : file(underlying::NO_FILE) {} - constexpr File(underlying file) : file(file) {} - constexpr File(int file) : file(static_cast(file)) {} - constexpr File(std::string_view sw) - : file(static_cast(static_cast(utils::tolower(static_cast(sw[0]))) - 'a')) {} - - [[nodiscard]] constexpr underlying internal() const noexcept { return file; } - - constexpr bool operator==(const File& rhs) const noexcept { return file == rhs.file; } - constexpr bool operator!=(const File& rhs) const noexcept { return file != rhs.file; } - - constexpr bool operator==(const underlying& rhs) const noexcept { return file == rhs; } - constexpr bool operator!=(const underlying& rhs) const noexcept { return file != rhs; } - - constexpr bool operator>=(const File& rhs) const noexcept { - return static_cast(file) >= static_cast(rhs.file); - } - constexpr bool operator<=(const File& rhs) const noexcept { - return static_cast(file) <= static_cast(rhs.file); - } - - constexpr bool operator>(const File& rhs) const noexcept { - return static_cast(file) > static_cast(rhs.file); - } - - constexpr bool operator<(const File& rhs) const noexcept { - return static_cast(file) < static_cast(rhs.file); - } - - constexpr File& operator+=(int rhs) noexcept { - file = underlying(static_cast(file) + rhs); - return *this; - } - - constexpr operator int() const noexcept { return static_cast(file); } - - explicit operator std::string() const { return std::string(1, static_cast(static_cast(file) + 'a')); } - - static constexpr underlying FILE_A = underlying::FILE_A; - static constexpr underlying FILE_B = underlying::FILE_B; - static constexpr underlying FILE_C = underlying::FILE_C; - static constexpr underlying FILE_D = underlying::FILE_D; - static constexpr underlying FILE_E = underlying::FILE_E; - static constexpr underlying FILE_F = underlying::FILE_F; - static constexpr underlying FILE_G = underlying::FILE_G; - static constexpr underlying FILE_H = underlying::FILE_H; - static constexpr underlying NO_FILE = underlying::NO_FILE; - - private: - underlying file; -}; - -class Rank { - public: - enum class underlying { RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, NO_RANK }; - - constexpr Rank() : rank_(underlying::NO_RANK) {} - constexpr Rank(underlying rank) : rank_(rank) {} - constexpr Rank(int rank) : rank_(static_cast(rank)) {} - constexpr Rank(std::string_view sw) - : rank_(static_cast(static_cast(utils::tolower(static_cast(sw[0]))) - '1')) {} - - [[nodiscard]] constexpr underlying internal() const noexcept { return rank_; } - - constexpr bool operator==(const Rank& rhs) const noexcept { return rank_ == rhs.rank_; } - constexpr bool operator!=(const Rank& rhs) const noexcept { return rank_ != rhs.rank_; } - - constexpr bool operator==(const underlying& rhs) const noexcept { return rank_ == rhs; } - constexpr bool operator!=(const underlying& rhs) const noexcept { return rank_ != rhs; } - - constexpr bool operator>=(const Rank& rhs) const noexcept { - return static_cast(rank_) >= static_cast(rhs.rank_); - } - constexpr bool operator<=(const Rank& rhs) const noexcept { - return static_cast(rank_) <= static_cast(rhs.rank_); - } - - constexpr Rank& operator+=(int rhs) noexcept { - rank_ = underlying(static_cast(rank_) + rhs); - return *this; - } - - operator std::string() const { return std::string(1, static_cast(static_cast(rank_) + '1')); } - - constexpr operator int() const noexcept { return static_cast(rank_); } - - [[nodiscard]] constexpr std::uint64_t bb() const noexcept { return 0xffULL << (8 * static_cast(rank_)); } - - [[nodiscard]] static constexpr bool back_rank(Rank r, Color color) noexcept { - return r == Rank(static_cast(color) * 7); - } - - [[nodiscard]] static constexpr Rank rank(Rank r, Color color) noexcept { - return Rank((static_cast(r) ^ (static_cast(color) * 7))); - } - - static constexpr underlying RANK_1 = underlying::RANK_1; - static constexpr underlying RANK_2 = underlying::RANK_2; - static constexpr underlying RANK_3 = underlying::RANK_3; - static constexpr underlying RANK_4 = underlying::RANK_4; - static constexpr underlying RANK_5 = underlying::RANK_5; - static constexpr underlying RANK_6 = underlying::RANK_6; - static constexpr underlying RANK_7 = underlying::RANK_7; - static constexpr underlying RANK_8 = underlying::RANK_8; - static constexpr underlying NO_RANK = underlying::NO_RANK; - - private: - underlying rank_; -}; - -class Square { - public: - // clang-format off - enum class underlying { - SQ_A1, SQ_B1, SQ_C1, SQ_D1, SQ_E1, SQ_F1, SQ_G1, SQ_H1, - SQ_A2, SQ_B2, SQ_C2, SQ_D2, SQ_E2, SQ_F2, SQ_G2, SQ_H2, - SQ_A3, SQ_B3, SQ_C3, SQ_D3, SQ_E3, SQ_F3, SQ_G3, SQ_H3, - SQ_A4, SQ_B4, SQ_C4, SQ_D4, SQ_E4, SQ_F4, SQ_G4, SQ_H4, - SQ_A5, SQ_B5, SQ_C5, SQ_D5, SQ_E5, SQ_F5, SQ_G5, SQ_H5, - SQ_A6, SQ_B6, SQ_C6, SQ_D6, SQ_E6, SQ_F6, SQ_G6, SQ_H6, - SQ_A7, SQ_B7, SQ_C7, SQ_D7, SQ_E7, SQ_F7, SQ_G7, SQ_H7, - SQ_A8, SQ_B8, SQ_C8, SQ_D8, SQ_E8, SQ_F8, SQ_G8, SQ_H8, - NO_SQ - }; - // clang-format on - -// when c++20 -#if __cplusplus >= 202002L || (defined(_MSC_VER) && _MSVC_LANG >= 202002L) - using enum underlying; -#else - - CHESS_DECLARE_RANK(1) - CHESS_DECLARE_RANK(2) - CHESS_DECLARE_RANK(3) - CHESS_DECLARE_RANK(4) - CHESS_DECLARE_RANK(5) - CHESS_DECLARE_RANK(6) - CHESS_DECLARE_RANK(7) - CHESS_DECLARE_RANK(8) - - static constexpr auto NO_SQ = underlying::NO_SQ; - -#endif - - constexpr Square() : sq(underlying::NO_SQ) {} - - constexpr Square(int sq) : sq(static_cast(sq)) { assert(sq <= 64 && sq >= 0); } - constexpr Square(File file, Rank rank) : sq(static_cast(file + rank * 8)) {} - constexpr Square(Rank rank, File file) : sq(static_cast(file + rank * 8)) {} - constexpr Square(underlying sq) : sq(sq) {} - constexpr Square(std::string_view str) : sq(static_cast((str[0] - 'a') + (str[1] - '1') * 8)) { - assert(str.size() >= 2); - } - - constexpr Square operator^(const Square& s) const noexcept { - return Square(static_cast(static_cast(sq) ^ s.index())); - }; - - constexpr bool operator==(const Square& rhs) const noexcept { return sq == rhs.sq; } - - constexpr bool operator!=(const Square& rhs) const noexcept { return sq != rhs.sq; } - - constexpr bool operator>(const Square& rhs) const noexcept { - return static_cast(sq) > static_cast(rhs.sq); - } - - constexpr bool operator>=(const Square& rhs) const noexcept { - return static_cast(sq) >= static_cast(rhs.sq); - } - - constexpr bool operator<(const Square& rhs) const noexcept { - return static_cast(sq) < static_cast(rhs.sq); - } - - constexpr bool operator<=(const Square& rhs) const noexcept { - return static_cast(sq) <= static_cast(rhs.sq); - } - - constexpr Square operator+(const Square& rhs) const noexcept { - return Square(static_cast(static_cast(sq) + static_cast(rhs.sq))); - } - - constexpr Square operator-(const Square& rhs) const noexcept { - return Square(static_cast(static_cast(sq) - static_cast(rhs.sq))); - } - - constexpr Square& operator++() noexcept { - sq = static_cast(static_cast(sq) + 1); - return *this; - } - - constexpr Square operator++(int) noexcept { - Square tmp(*this); - operator++(); - return tmp; - } - - constexpr Square& operator--() noexcept { - sq = static_cast(static_cast(sq) - 1); - return *this; - } - - constexpr Square operator--(int) noexcept { - Square tmp(*this); - operator--(); - return tmp; - } - - /** - * @brief Get a string representation of the square. - */ - [[nodiscard]] operator std::string() const { - std::string str; - str += static_cast(file()); - str += static_cast(rank()); - return str; - } - - [[nodiscard]] constexpr int index() const noexcept { return static_cast(sq); } - - [[nodiscard]] constexpr File file() const noexcept { return File(index() & 7); } - [[nodiscard]] constexpr Rank rank() const noexcept { return Rank(index() >> 3); } - - /** - * @brief Check if the square is light. - * @return - */ - [[nodiscard]] constexpr bool is_light() const noexcept { return (file() + rank()) & 1; - } - - /** - * @brief Check if the square is dark. - * @return - */ - [[nodiscard]] constexpr bool is_dark() const noexcept { return !is_light(); } - - /** - * @brief Check if the square is vali.d - * @return - */ - [[nodiscard]] constexpr bool is_valid() const noexcept { return static_cast(sq) < 64; } - - /** - * @brief Check if the square is valid. - * @param r - * @param f - * @return - */ - [[nodiscard]] constexpr static bool is_valid(Rank r, File f) noexcept { - return r >= Rank::RANK_1 && r <= Rank::RANK_8 && f >= File::FILE_A && f <= File::FILE_H; - } - - /** - * @brief Get the chebyshev distance between two squares. - * @param sq - * @param sq2 - * @return - */ - [[nodiscard]] static int distance(Square sq, Square sq2) noexcept { - return std::max(std::abs(sq.file() - sq2.file()), std::abs(sq.rank() - sq2.rank())); - } - - /** - * @brief Absolute value of sq - sq2. - * @param sq - * @param sq2 - * @return - */ - [[nodiscard]] static int value_distance(Square sq, Square sq2) noexcept { - return std::abs(sq.index() - sq2.index()); - } - - /** - * @brief Check if the squares share the same color. I.e. if they are both light or dark. - * @param sq - * @param sq2 - * @return - */ - [[nodiscard]] static constexpr bool same_color(Square sq, Square sq2) noexcept { - return ((9 * (sq ^ sq2).index()) & 8) == 0; - } - - /** - * @brief Check if the square is on the back rank. - * @param sq - * @param color - * @return - */ - [[nodiscard]] static constexpr bool back_rank(Square sq, Color color) noexcept { - return Rank::back_rank(sq.rank(), color); - } - - /** - * @brief Flips the square vertically. - * @return - */ - constexpr Square& flip() noexcept { - sq = static_cast(static_cast(sq) ^ 56); - return *this; - } - - /** - * @brief Flips the square vertically, depending on the color. - * @param c - * @return - */ - [[nodiscard]] constexpr Square relative_square(Color c) const noexcept { - return Square(static_cast(sq) ^ (static_cast(c) * 56)); - } - - [[nodiscard]] constexpr int diagonal_of() const noexcept { return 7 + rank() - file(); } - - [[nodiscard]] constexpr int antidiagonal_of() const noexcept { return rank() + file(); } - - /** - * @brief Get the en passant square. Should only be called for valid ep positions. - * @return - */ - [[nodiscard]] constexpr Square ep_square() const noexcept { - assert(rank() == Rank::RANK_3 // capture - || rank() == Rank::RANK_4 // push - || rank() == Rank::RANK_5 // push - || rank() == Rank::RANK_6 // capture - ); - return Square(static_cast(sq) ^ 8); - } - - /** - * @brief Get the destination square of the king after castling. - * @param is_king_side - * @param c - * @return - */ - [[nodiscard]] static constexpr Square castling_king_square(bool is_king_side, Color c) noexcept { - return Square(is_king_side ? Square::SQ_G1 : Square::SQ_C1).relative_square(c); - } - - /** - * @brief Get the destination square of the rook after castling. - * @param is_king_side - * @param c - * @return - */ - [[nodiscard]] static constexpr Square castling_rook_square(bool is_king_side, Color c) noexcept { - return Square(is_king_side ? Square::SQ_F1 : Square::SQ_D1).relative_square(c); - } - - /** - * @brief Maximum number of squares. - * @return - */ - [[nodiscard]] static constexpr int max() noexcept { return 64; } - - [[nodiscard]] static bool is_valid_string_sq(std::string_view str) noexcept { - return str.size() == 2 && str[0] >= 'a' && str[0] <= 'h' && str[1] >= '1' && str[1] <= '8'; - } - - [[nodiscard]] static constexpr bool is_valid_sq(int sq) noexcept { return sq >= 0 && sq < 64; } - - private: - underlying sq; -}; - -inline std::ostream& operator<<(std::ostream& os, const Square& sq) { - os << static_cast(sq); - return os; -} - -enum class Direction : std::int8_t { - NORTH = 8, - WEST = -1, - SOUTH = -8, - EAST = 1, - NORTH_EAST = 9, - NORTH_WEST = 7, - SOUTH_WEST = -9, - SOUTH_EAST = -7 -}; - -[[nodiscard]] constexpr Direction make_direction(Direction dir, Color c) noexcept { - if (c == Color::BLACK) return static_cast(-static_cast(dir)); - return dir; -} - -constexpr Square operator+(Square sq, Direction dir) { - return static_cast(sq.index() + static_cast(dir)); -} - -#undef CHESS_DECLARE_RANK - -} // namespace chess - -namespace chess { - -class Bitboard { - public: - constexpr Bitboard() : bits(0) {} - constexpr Bitboard(std::uint64_t bits) : bits(bits) {} - constexpr Bitboard(File file) : bits(0) { - assert(file != File::NO_FILE); - bits = 0x0101010101010101ULL << static_cast(file.internal()); - } - constexpr Bitboard(Rank rank) : bits(0) { - assert(rank != Rank::NO_RANK); - bits = 0xFFULL << (8 * static_cast(rank.internal())); - } - - explicit operator bool() const noexcept { return bits != 0; } - - explicit operator std::string() const { - std::bitset<64> b(bits); - std::string str_bitset = b.to_string(); - - std::string str; - - for (int i = 0; i < 64; i += 8) { - std::string x = str_bitset.substr(i, 8); - std::reverse(x.begin(), x.end()); - str += x + '\n'; - } - return str; - } - - constexpr Bitboard operator&&(bool rhs) const noexcept { return Bitboard(bits && rhs); } - - constexpr Bitboard operator&(std::uint64_t rhs) const noexcept { return Bitboard(bits & rhs); } - constexpr Bitboard operator|(std::uint64_t rhs) const noexcept { return Bitboard(bits | rhs); } - constexpr Bitboard operator^(std::uint64_t rhs) const noexcept { return Bitboard(bits ^ rhs); } - constexpr Bitboard operator<<(std::uint64_t rhs) const noexcept { return Bitboard(bits << rhs); } - constexpr Bitboard operator>>(std::uint64_t rhs) const noexcept { return Bitboard(bits >> rhs); } - constexpr bool operator==(std::uint64_t rhs) const noexcept { return bits == rhs; } - constexpr bool operator!=(std::uint64_t rhs) const noexcept { return bits != rhs; } - - constexpr Bitboard operator&(const Bitboard& rhs) const noexcept { return Bitboard(bits & rhs.bits); } - constexpr Bitboard operator|(const Bitboard& rhs) const noexcept { return Bitboard(bits | rhs.bits); } - constexpr Bitboard operator^(const Bitboard& rhs) const noexcept { return Bitboard(bits ^ rhs.bits); } - constexpr Bitboard operator~() const noexcept { return Bitboard(~bits); } - - constexpr Bitboard& operator&=(const Bitboard& rhs) noexcept { - bits &= rhs.bits; - return *this; - } - - constexpr Bitboard& operator|=(const Bitboard& rhs) noexcept { - bits |= rhs.bits; - return *this; - } - - constexpr Bitboard& operator^=(const Bitboard& rhs) noexcept { - bits ^= rhs.bits; - return *this; - } - - constexpr bool operator==(const Bitboard& rhs) const noexcept { return bits == rhs.bits; } - constexpr bool operator!=(const Bitboard& rhs) const noexcept { return bits != rhs.bits; } - constexpr bool operator||(const Bitboard& rhs) const noexcept { return bits || rhs.bits; } - constexpr bool operator&&(const Bitboard& rhs) const noexcept { return bits && rhs.bits; } - - constexpr Bitboard& set(int index) noexcept { - assert(index >= 0 && index < 64); - bits |= (1ULL << index); - return *this; - } - - [[nodiscard]] constexpr bool check(int index) const noexcept { - assert(index >= 0 && index < 64); - return bits & (1ULL << index); - } - - constexpr Bitboard& clear(int index) noexcept { - assert(index >= 0 && index < 64); - bits &= ~(1ULL << index); - return *this; - } - - constexpr Bitboard& clear() noexcept { - bits = 0; - return *this; - } - - [[nodiscard]] static constexpr Bitboard fromSquare(int index) noexcept { - assert(index >= 0 && index < 64); - return Bitboard(1ULL << index); - } - - [[nodiscard]] static constexpr Bitboard fromSquare(Square sq) noexcept { - assert(sq.index() >= 0 && sq.index() < 64); - return Bitboard(1ULL << sq.index()); - } - - [[nodiscard]] constexpr bool empty() const noexcept { return bits == 0; } - - [[nodiscard]] -#if __cpp_lib_bitops >= 201907L - constexpr -#endif - int lsb() const noexcept { - assert(bits != 0); -#if __cpp_lib_bitops >= 201907L - return std::countr_zero(bits); -#else -# if defined(__GNUC__) - return __builtin_ctzll(bits); -# elif defined(_MSC_VER) - unsigned long idx; - _BitScanForward64(&idx, bits); - return static_cast(idx); -# else -# error "Compiler not supported." -# endif -#endif - } - - [[nodiscard]] -#if __cpp_lib_bitops >= 201907L - constexpr -#endif - int msb() const noexcept { - assert(bits != 0); - -#if __cpp_lib_bitops >= 201907L - return std::countl_zero(bits) ^ 63; -#else -# if defined(__GNUC__) - return 63 ^ __builtin_clzll(bits); -# elif defined(_MSC_VER) - unsigned long idx; - _BitScanReverse64(&idx, bits); - return static_cast(idx); -# else -# error "Compiler not supported." -# endif -#endif - } - - [[nodiscard]] -#if __cpp_lib_bitops >= 201907L - constexpr -#endif - int count() const noexcept { -#if __cpp_lib_bitops >= 201907L - return std::popcount(bits); -#else -# if defined(_MSC_VER) || defined(__INTEL_COMPILER) - return static_cast(_mm_popcnt_u64(bits)); -# else - return __builtin_popcountll(bits); -# endif -#endif - } - - [[nodiscard]] -#if __cpp_lib_bitops >= 201907L - constexpr -#endif - std::uint8_t pop() noexcept { - assert(bits != 0); - std::uint8_t index = lsb(); - bits &= bits - 1; - return index; - } - - [[nodiscard]] constexpr std::uint64_t getBits() const noexcept { return bits; } - - friend std::ostream& operator<<(std::ostream& os, const Bitboard& bb); - - private: - std::uint64_t bits; -}; - -inline std::ostream& operator<<(std::ostream& os, const Bitboard& bb) { - os << std::string(bb); - return os; -} - -constexpr Bitboard operator&(std::uint64_t lhs, const Bitboard& rhs) { return rhs & lhs; } -constexpr Bitboard operator|(std::uint64_t lhs, const Bitboard& rhs) { return rhs | lhs; } -} // namespace chess - -namespace chess { -class Board; -} // namespace chess - - - -namespace chess { - -class PieceType { - public: - enum class underlying : std::uint8_t { - PAWN, - KNIGHT, - BISHOP, - ROOK, - QUEEN, - KING, - NONE, - }; - - constexpr PieceType() : pt(underlying::NONE) {} - constexpr PieceType(underlying pt) : pt(pt) {} - constexpr explicit PieceType(std::string_view type) : pt(underlying::NONE) { - assert(type.size() > 0); - - char c = type[0]; - - if (c == 'P' || c == 'p') - pt = underlying::PAWN; - else if (c == 'N' || c == 'n') - pt = underlying::KNIGHT; - else if (c == 'B' || c == 'b') - pt = underlying::BISHOP; - else if (c == 'R' || c == 'r') - pt = underlying::ROOK; - else if (c == 'Q' || c == 'q') - pt = underlying::QUEEN; - else if (c == 'K' || c == 'k') - pt = underlying::KING; - else - pt = underlying::NONE; - } - - explicit operator std::string() const { - if (pt == underlying::NONE) return " "; - constexpr static const char* pieceTypeStr[] = {"p", "n", "b", "r", "q", "k"}; - return pieceTypeStr[static_cast(pt)]; - } - - constexpr bool operator==(const PieceType& rhs) const noexcept { return pt == rhs.pt; } - constexpr bool operator!=(const PieceType& rhs) const noexcept { return pt != rhs.pt; } - - constexpr operator int() const noexcept { return static_cast(pt); } - - [[nodiscard]] constexpr underlying internal() const noexcept { return pt; } - - static constexpr underlying PAWN = underlying::PAWN; - static constexpr underlying KNIGHT = underlying::KNIGHT; - static constexpr underlying BISHOP = underlying::BISHOP; - static constexpr underlying ROOK = underlying::ROOK; - static constexpr underlying QUEEN = underlying::QUEEN; - static constexpr underlying KING = underlying::KING; - static constexpr underlying NONE = underlying::NONE; - - private: - underlying pt; -}; - -inline std::ostream& operator<<(std::ostream& os, const PieceType& pt) { - os << static_cast(pt); - return os; -} - -class Piece { - public: - enum class underlying : std::uint8_t { - WHITEPAWN, - WHITEKNIGHT, - WHITEBISHOP, - WHITEROOK, - WHITEQUEEN, - WHITEKING, - BLACKPAWN, - BLACKKNIGHT, - BLACKBISHOP, - BLACKROOK, - BLACKQUEEN, - BLACKKING, - NONE - }; - - constexpr Piece() : piece(underlying::NONE) {} - constexpr Piece(underlying piece) : piece(piece) {} - constexpr Piece(PieceType type, Color color) - : piece(color == Color::NONE ? Piece::NONE - : type == PieceType::NONE ? Piece::NONE - : static_cast(static_cast(color.internal()) * 6 + type)) {} - constexpr Piece(Color color, PieceType type) - : piece(color == Color::NONE ? Piece::NONE - : type == PieceType::NONE ? Piece::NONE - : static_cast(static_cast(color.internal()) * 6 + type)) {} - constexpr Piece(std::string_view p) : piece(underlying::NONE) { piece = convertCharToUnderlying(p[0]); } - - constexpr bool operator<(const Piece& rhs) const noexcept { return piece < rhs.piece; } - constexpr bool operator>(const Piece& rhs) const noexcept { return piece > rhs.piece; } - constexpr bool operator==(const Piece& rhs) const noexcept { return piece == rhs.piece; } - constexpr bool operator!=(const Piece& rhs) const noexcept { return piece != rhs.piece; } - - constexpr bool operator==(const underlying& rhs) const noexcept { return piece == rhs; } - constexpr bool operator!=(const underlying& rhs) const noexcept { return piece != rhs; } - - constexpr bool operator==(const PieceType& rhs) const noexcept { return type() == rhs; } - constexpr bool operator!=(const PieceType& rhs) const noexcept { return type() != rhs; } - - explicit operator std::string() const { - constexpr static const char* pieceStr[] = {"P", "N", "B", "R", "Q", "K", // - "p", "n", "b", "r", "q", "k"}; - if (piece == NONE) return "."; - return pieceStr[static_cast(piece)]; - } - - constexpr operator int() const noexcept { return static_cast(piece); } - - [[nodiscard]] constexpr PieceType type() const noexcept { - if (piece == NONE) return PieceType::NONE; - // return static_cast(int(piece) % 6); - return static_cast(static_cast(piece) > 5 ? static_cast(piece) - 6 - : static_cast(piece)); - } - - [[nodiscard]] constexpr Color color() const noexcept { - if (piece == NONE) return Color::NONE; - return static_cast(static_cast(piece) / 6); - } - - [[nodiscard]] constexpr underlying internal() const noexcept { return piece; } - - static constexpr underlying NONE = underlying::NONE; - static constexpr underlying WHITEPAWN = underlying::WHITEPAWN; - static constexpr underlying WHITEKNIGHT = underlying::WHITEKNIGHT; - static constexpr underlying WHITEBISHOP = underlying::WHITEBISHOP; - static constexpr underlying WHITEROOK = underlying::WHITEROOK; - static constexpr underlying WHITEQUEEN = underlying::WHITEQUEEN; - static constexpr underlying WHITEKING = underlying::WHITEKING; - static constexpr underlying BLACKPAWN = underlying::BLACKPAWN; - static constexpr underlying BLACKKNIGHT = underlying::BLACKKNIGHT; - static constexpr underlying BLACKBISHOP = underlying::BLACKBISHOP; - static constexpr underlying BLACKROOK = underlying::BLACKROOK; - static constexpr underlying BLACKQUEEN = underlying::BLACKQUEEN; - static constexpr underlying BLACKKING = underlying::BLACKKING; - - private: - underlying piece; - - [[nodiscard]] constexpr static underlying convertCharToUnderlying(char c) { - switch (c) { - case 'P': - return WHITEPAWN; - case 'N': - return WHITEKNIGHT; - case 'B': - return WHITEBISHOP; - case 'R': - return WHITEROOK; - case 'Q': - return WHITEQUEEN; - case 'K': - return WHITEKING; - case 'p': - return BLACKPAWN; - case 'n': - return BLACKKNIGHT; - case 'b': - return BLACKBISHOP; - case 'r': - return BLACKROOK; - case 'q': - return BLACKQUEEN; - case 'k': - return BLACKKING; - default: - return NONE; - } - } -}; -} // namespace chess - -namespace chess { -class attacks { - using U64 = std::uint64_t; - -#ifdef CHESS_USE_PEXT - struct Magic { - U64 mask; - Bitboard *attacks; - U64 operator()(Bitboard b) const noexcept { return _pext_u64(b.getBits(), mask); } - }; -#else - struct Magic { - U64 mask; - U64 magic; - Bitboard *attacks; - U64 shift; - U64 operator()(Bitboard b) const noexcept { return (((b & mask)).getBits() * magic) >> shift; } - }; -#endif - - // Slow function to calculate bishop and rook attacks - template - [[nodiscard]] static Bitboard sliderAttacks(Square sq, Bitboard occupied) noexcept; - - // Initializes the magic bitboard tables for sliding pieces - static void initSliders(Square sq, Magic table[], U64 magic, - const std::function &attacks); - - // clang-format off - // pre-calculated lookup table for pawn attacks - static constexpr Bitboard PawnAttacks[2][64] = { - // white pawn attacks - { 0x200, 0x500, 0xa00, 0x1400, - 0x2800, 0x5000, 0xa000, 0x4000, - 0x20000, 0x50000, 0xa0000, 0x140000, - 0x280000, 0x500000, 0xa00000, 0x400000, - 0x2000000, 0x5000000, 0xa000000, 0x14000000, - 0x28000000, 0x50000000, 0xa0000000, 0x40000000, - 0x200000000, 0x500000000, 0xa00000000, 0x1400000000, - 0x2800000000, 0x5000000000, 0xa000000000, 0x4000000000, - 0x20000000000, 0x50000000000, 0xa0000000000, 0x140000000000, - 0x280000000000, 0x500000000000, 0xa00000000000, 0x400000000000, - 0x2000000000000, 0x5000000000000, 0xa000000000000, 0x14000000000000, - 0x28000000000000, 0x50000000000000, 0xa0000000000000, 0x40000000000000, - 0x200000000000000, 0x500000000000000, 0xa00000000000000, 0x1400000000000000, - 0x2800000000000000, 0x5000000000000000, 0xa000000000000000, 0x4000000000000000, - 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0 }, - - // black pawn attacks - { 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, - 0x2, 0x5, 0xa, 0x14, - 0x28, 0x50, 0xa0, 0x40, - 0x200, 0x500, 0xa00, 0x1400, - 0x2800, 0x5000, 0xa000, 0x4000, - 0x20000, 0x50000, 0xa0000, 0x140000, - 0x280000, 0x500000, 0xa00000, 0x400000, - 0x2000000, 0x5000000, 0xa000000, 0x14000000, - 0x28000000, 0x50000000, 0xa0000000, 0x40000000, - 0x200000000, 0x500000000, 0xa00000000, 0x1400000000, - 0x2800000000, 0x5000000000, 0xa000000000, 0x4000000000, - 0x20000000000, 0x50000000000, 0xa0000000000, 0x140000000000, - 0x280000000000, 0x500000000000, 0xa00000000000, 0x400000000000, - 0x2000000000000, 0x5000000000000, 0xa000000000000, 0x14000000000000, - 0x28000000000000, 0x50000000000000, 0xa0000000000000, 0x40000000000000 - } - }; - - // clang-format on - - // pre-calculated lookup table for knight attacks - static constexpr Bitboard KnightAttacks[64] = { - 0x0000000000020400, 0x0000000000050800, 0x00000000000A1100, 0x0000000000142200, 0x0000000000284400, - 0x0000000000508800, 0x0000000000A01000, 0x0000000000402000, 0x0000000002040004, 0x0000000005080008, - 0x000000000A110011, 0x0000000014220022, 0x0000000028440044, 0x0000000050880088, 0x00000000A0100010, - 0x0000000040200020, 0x0000000204000402, 0x0000000508000805, 0x0000000A1100110A, 0x0000001422002214, - 0x0000002844004428, 0x0000005088008850, 0x000000A0100010A0, 0x0000004020002040, 0x0000020400040200, - 0x0000050800080500, 0x00000A1100110A00, 0x0000142200221400, 0x0000284400442800, 0x0000508800885000, - 0x0000A0100010A000, 0x0000402000204000, 0x0002040004020000, 0x0005080008050000, 0x000A1100110A0000, - 0x0014220022140000, 0x0028440044280000, 0x0050880088500000, 0x00A0100010A00000, 0x0040200020400000, - 0x0204000402000000, 0x0508000805000000, 0x0A1100110A000000, 0x1422002214000000, 0x2844004428000000, - 0x5088008850000000, 0xA0100010A0000000, 0x4020002040000000, 0x0400040200000000, 0x0800080500000000, - 0x1100110A00000000, 0x2200221400000000, 0x4400442800000000, 0x8800885000000000, 0x100010A000000000, - 0x2000204000000000, 0x0004020000000000, 0x0008050000000000, 0x00110A0000000000, 0x0022140000000000, - 0x0044280000000000, 0x0088500000000000, 0x0010A00000000000, 0x0020400000000000}; - - // pre-calculated lookup table for king attacks - static constexpr Bitboard KingAttacks[64] = { - 0x0000000000000302, 0x0000000000000705, 0x0000000000000E0A, 0x0000000000001C14, 0x0000000000003828, - 0x0000000000007050, 0x000000000000E0A0, 0x000000000000C040, 0x0000000000030203, 0x0000000000070507, - 0x00000000000E0A0E, 0x00000000001C141C, 0x0000000000382838, 0x0000000000705070, 0x0000000000E0A0E0, - 0x0000000000C040C0, 0x0000000003020300, 0x0000000007050700, 0x000000000E0A0E00, 0x000000001C141C00, - 0x0000000038283800, 0x0000000070507000, 0x00000000E0A0E000, 0x00000000C040C000, 0x0000000302030000, - 0x0000000705070000, 0x0000000E0A0E0000, 0x0000001C141C0000, 0x0000003828380000, 0x0000007050700000, - 0x000000E0A0E00000, 0x000000C040C00000, 0x0000030203000000, 0x0000070507000000, 0x00000E0A0E000000, - 0x00001C141C000000, 0x0000382838000000, 0x0000705070000000, 0x0000E0A0E0000000, 0x0000C040C0000000, - 0x0003020300000000, 0x0007050700000000, 0x000E0A0E00000000, 0x001C141C00000000, 0x0038283800000000, - 0x0070507000000000, 0x00E0A0E000000000, 0x00C040C000000000, 0x0302030000000000, 0x0705070000000000, - 0x0E0A0E0000000000, 0x1C141C0000000000, 0x3828380000000000, 0x7050700000000000, 0xE0A0E00000000000, - 0xC040C00000000000, 0x0203000000000000, 0x0507000000000000, 0x0A0E000000000000, 0x141C000000000000, - 0x2838000000000000, 0x5070000000000000, 0xA0E0000000000000, 0x40C0000000000000}; - - static constexpr U64 RookMagics[64] = { - 0x8a80104000800020ULL, 0x140002000100040ULL, 0x2801880a0017001ULL, 0x100081001000420ULL, - 0x200020010080420ULL, 0x3001c0002010008ULL, 0x8480008002000100ULL, 0x2080088004402900ULL, - 0x800098204000ULL, 0x2024401000200040ULL, 0x100802000801000ULL, 0x120800800801000ULL, - 0x208808088000400ULL, 0x2802200800400ULL, 0x2200800100020080ULL, 0x801000060821100ULL, - 0x80044006422000ULL, 0x100808020004000ULL, 0x12108a0010204200ULL, 0x140848010000802ULL, - 0x481828014002800ULL, 0x8094004002004100ULL, 0x4010040010010802ULL, 0x20008806104ULL, - 0x100400080208000ULL, 0x2040002120081000ULL, 0x21200680100081ULL, 0x20100080080080ULL, - 0x2000a00200410ULL, 0x20080800400ULL, 0x80088400100102ULL, 0x80004600042881ULL, - 0x4040008040800020ULL, 0x440003000200801ULL, 0x4200011004500ULL, 0x188020010100100ULL, - 0x14800401802800ULL, 0x2080040080800200ULL, 0x124080204001001ULL, 0x200046502000484ULL, - 0x480400080088020ULL, 0x1000422010034000ULL, 0x30200100110040ULL, 0x100021010009ULL, - 0x2002080100110004ULL, 0x202008004008002ULL, 0x20020004010100ULL, 0x2048440040820001ULL, - 0x101002200408200ULL, 0x40802000401080ULL, 0x4008142004410100ULL, 0x2060820c0120200ULL, - 0x1001004080100ULL, 0x20c020080040080ULL, 0x2935610830022400ULL, 0x44440041009200ULL, - 0x280001040802101ULL, 0x2100190040002085ULL, 0x80c0084100102001ULL, 0x4024081001000421ULL, - 0x20030a0244872ULL, 0x12001008414402ULL, 0x2006104900a0804ULL, 0x1004081002402ULL}; - - static constexpr U64 BishopMagics[64] = { - 0x40040844404084ULL, 0x2004208a004208ULL, 0x10190041080202ULL, 0x108060845042010ULL, - 0x581104180800210ULL, 0x2112080446200010ULL, 0x1080820820060210ULL, 0x3c0808410220200ULL, - 0x4050404440404ULL, 0x21001420088ULL, 0x24d0080801082102ULL, 0x1020a0a020400ULL, - 0x40308200402ULL, 0x4011002100800ULL, 0x401484104104005ULL, 0x801010402020200ULL, - 0x400210c3880100ULL, 0x404022024108200ULL, 0x810018200204102ULL, 0x4002801a02003ULL, - 0x85040820080400ULL, 0x810102c808880400ULL, 0xe900410884800ULL, 0x8002020480840102ULL, - 0x220200865090201ULL, 0x2010100a02021202ULL, 0x152048408022401ULL, 0x20080002081110ULL, - 0x4001001021004000ULL, 0x800040400a011002ULL, 0xe4004081011002ULL, 0x1c004001012080ULL, - 0x8004200962a00220ULL, 0x8422100208500202ULL, 0x2000402200300c08ULL, 0x8646020080080080ULL, - 0x80020a0200100808ULL, 0x2010004880111000ULL, 0x623000a080011400ULL, 0x42008c0340209202ULL, - 0x209188240001000ULL, 0x400408a884001800ULL, 0x110400a6080400ULL, 0x1840060a44020800ULL, - 0x90080104000041ULL, 0x201011000808101ULL, 0x1a2208080504f080ULL, 0x8012020600211212ULL, - 0x500861011240000ULL, 0x180806108200800ULL, 0x4000020e01040044ULL, 0x300000261044000aULL, - 0x802241102020002ULL, 0x20906061210001ULL, 0x5a84841004010310ULL, 0x4010801011c04ULL, - 0xa010109502200ULL, 0x4a02012000ULL, 0x500201010098b028ULL, 0x8040002811040900ULL, - 0x28000010020204ULL, 0x6000020202d0240ULL, 0x8918844842082200ULL, 0x4010011029020020ULL}; - - static inline Bitboard RookAttacks[0x19000] = {}; - static inline Bitboard BishopAttacks[0x1480] = {}; - - static inline Magic RookTable[64] = {}; - static inline Magic BishopTable[64] = {}; - - public: - static constexpr Bitboard MASK_RANK[8] = {0xff, 0xff00, 0xff0000, 0xff000000, - 0xff00000000, 0xff0000000000, 0xff000000000000, 0xff00000000000000}; - - static constexpr Bitboard MASK_FILE[8] = { - 0x101010101010101, 0x202020202020202, 0x404040404040404, 0x808080808080808, - 0x1010101010101010, 0x2020202020202020, 0x4040404040404040, 0x8080808080808080, - }; - - /** - * @brief Shifts a bitboard in a given direction - * @tparam direction - * @param b - * @return - */ - template - [[nodiscard]] static constexpr Bitboard shift(const Bitboard b); - - /** - * @brief - * @tparam c - * @param pawns - * @return - */ - template - [[nodiscard]] static Bitboard pawnLeftAttacks(const Bitboard pawns); - - /** - * @brief Generate the right side pawn attacks. - * @tparam c - * @param pawns - * @return - */ - template - [[nodiscard]] static Bitboard pawnRightAttacks(const Bitboard pawns); - - /** - * @brief Returns the pawn attacks for a given color and square - * @param c - * @param sq - * @return - */ - [[nodiscard]] static Bitboard pawn(Color c, Square sq) noexcept; - - /** - * @brief Returns the knight attacks for a given square - * @param sq - * @return - */ - [[nodiscard]] static Bitboard knight(Square sq) noexcept; - - /** - * @brief Returns the bishop attacks for a given square - * @param sq - * @param occupied - * @return - */ - [[nodiscard]] static Bitboard bishop(Square sq, Bitboard occupied) noexcept; - - /** - * @brief Returns the rook attacks for a given square - * @param sq - * @param occupied - * @return - */ - [[nodiscard]] static Bitboard rook(Square sq, Bitboard occupied) noexcept; - - /** - * @brief Returns the queen attacks for a given square - * @param sq - * @param occupied - * @return - */ - [[nodiscard]] static Bitboard queen(Square sq, Bitboard occupied) noexcept; - - /** - * @brief Returns the king attacks for a given square - * @param sq - * @return - */ - [[nodiscard]] static Bitboard king(Square sq) noexcept; - - /** - * @brief Returns the attacks for a given piece on a given square - * @param board - * @param color - * @param square - * @return - */ - [[nodiscard]] static Bitboard attackers(const Board &board, Color color, Square square) noexcept; - - /** - * @brief Returns the slider attacks for a given square - * @param sq - * @param occupied - * @tparam pt - * @return - */ - template - [[nodiscard]] static Bitboard slider(Square sq, Bitboard occupied) noexcept; - - /** - * @brief [Internal Usage] Initializes the attacks for the bishop and rook. Called once at startup. - */ - static inline void initAttacks(); -}; -} // namespace chess - -#include -#include -#include - -// check if charconv header is available -#if __has_include() -# define CHESS_USE_CHARCONV -# include -#else -# include -#endif - - - -namespace chess::constants { - -constexpr Bitboard DEFAULT_CHECKMASK = Bitboard(0xFFFFFFFFFFFFFFFFull); -constexpr auto STARTPOS = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; -constexpr auto MAX_MOVES = 256; -} // namespace chess::constants - - - -namespace chess { - -class Move { - public: - Move() = default; - constexpr Move(std::uint16_t move) : move_(move), score_(0) {} - - /** - * @brief Creates a move from a source and target square. - * @tparam MoveType - * @param source - * @param target - * @param pt leave this empty if it is not a promotion move, otherwise pass the piece type of the new piece. - * @return - */ - template - [[nodiscard]] static constexpr Move make(Square source, Square target, PieceType pt = PieceType::KNIGHT) noexcept { - assert(pt >= PieceType(PieceType::KNIGHT) && pt <= PieceType(PieceType::QUEEN)); - - std::uint16_t bits_promotion = static_cast(pt - PieceType(PieceType::KNIGHT)); - - return Move(MoveType + (bits_promotion << 12) + (source.index() << 6) + target.index()); - } - - /** - * @brief Get the source square of the move. - * @return - */ - [[nodiscard]] constexpr Square from() const noexcept { return static_cast((move_ >> 6) & 0x3F); } - - /** - * @brief Get the target square of the move. - * @return - */ - [[nodiscard]] constexpr Square to() const noexcept { return static_cast(move_ & 0x3F); } - - /** - * @brief Get the type of the move. Can be NORMAL, PROMOTION, ENPASSANT or CASTLING. - * @return - */ - [[nodiscard]] constexpr std::uint16_t typeOf() const noexcept { - return static_cast(move_ & (3 << 14)); - } - - /** - * @brief Get the promotion piece of the move, should only be used if typeOf() returns PROMOTION. - * @return - */ - [[nodiscard]] constexpr PieceType promotionType() const noexcept { - return static_cast(((move_ >> 12) & 3) + PieceType(PieceType::KNIGHT)); - } - - /** - * @brief Set the score for a move. Useful if you later want to sort the moves. - * @param score - */ - constexpr void setScore(std::int16_t score) noexcept { score_ = score; } - - [[nodiscard]] constexpr std::uint16_t move() const noexcept { return move_; } - [[nodiscard]] constexpr std::int16_t score() const noexcept { return score_; } - - constexpr bool operator==(const Move &rhs) const noexcept { return move_ == rhs.move_; } - constexpr bool operator!=(const Move &rhs) const noexcept { return move_ != rhs.move_; } - - static constexpr std::uint16_t NO_MOVE = 0; - static constexpr std::uint16_t NULL_MOVE = 65; - static constexpr std::uint16_t NORMAL = 0; - static constexpr std::uint16_t PROMOTION = 1 << 14; - static constexpr std::uint16_t ENPASSANT = 2 << 14; - static constexpr std::uint16_t CASTLING = 3 << 14; - - private: - std::uint16_t move_; - std::int16_t score_; -}; - -inline std::ostream &operator<<(std::ostream &os, const Move &move) { - Square from_sq = move.from(); - Square to_sq = move.to(); - - os << from_sq << to_sq; - - if (move.typeOf() == Move::PROMOTION) { - os << static_cast(move.promotionType()); - } - - return os; -} -} // namespace chess - - - -#include -#include -#include - - -namespace chess { -class Movelist { - public: - using value_type = Move; - using size_type = int; - using difference_type = std::ptrdiff_t; - using reference = value_type&; - using const_reference = const value_type&; - using pointer = value_type*; - using const_pointer = const value_type*; - - using iterator = value_type*; - using const_iterator = const value_type*; - - using reverse_iterator = std::reverse_iterator; - using const_reverse_iterator = std::reverse_iterator; - - // Element access - - [[nodiscard]] constexpr reference at(size_type pos) { -#ifndef CHESS_NO_EXCEPTIONS - if (pos >= size_) { - throw std::out_of_range("Movelist::at: pos (which is " + std::to_string(pos) + ") >= size (which is " + - std::to_string(size_) + ")"); - } -#endif - return moves_[pos]; - } - - [[nodiscard]] constexpr const_reference at(size_type pos) const { -#ifndef CHESS_NO_EXCEPTIONS - if (pos >= size_) { - throw std::out_of_range("Movelist::at: pos (which is " + std::to_string(pos) + ") >= size (which is " + - std::to_string(size_) + ")"); - } -#endif - return moves_[pos]; - } - - [[nodiscard]] constexpr reference operator[](size_type pos) noexcept { return moves_[pos]; } - [[nodiscard]] constexpr const_reference operator[](size_type pos) const noexcept { return moves_[pos]; } - - [[nodiscard]] constexpr reference front() noexcept { return moves_[0]; } - [[nodiscard]] constexpr const_reference front() const noexcept { return moves_[0]; } - - [[nodiscard]] constexpr reference back() noexcept { return moves_[size_ - 1]; } - [[nodiscard]] constexpr const_reference back() const noexcept { return moves_[size_ - 1]; } - - // Iterators - - [[nodiscard]] constexpr iterator begin() noexcept { return &moves_[0]; } - [[nodiscard]] constexpr const_iterator begin() const noexcept { return &moves_[0]; } - - [[nodiscard]] constexpr iterator end() noexcept { return &moves_[0] + size_; } - [[nodiscard]] constexpr const_iterator end() const noexcept { return &moves_[0] + size_; } - - // Capacity - - /** - * @brief Checks if the movelist is empty. - * @return - */ - [[nodiscard]] constexpr bool empty() const noexcept { return size_ == 0; } - - /** - * @brief Return the number of moves in the movelist. - * @return - */ - [[nodiscard]] constexpr size_type size() const noexcept { return size_; } - - // Modifiers - - /** - * @brief Clears the movelist. - */ - constexpr void clear() noexcept { size_ = 0; } - - /** - * @brief Add a move to the end of the movelist. - * @param move - */ - constexpr void add(const_reference move) noexcept { - assert(size_ < constants::MAX_MOVES); - moves_[size_++] = move; - } - - /** - * @brief Add a move to the end of the movelist. - * @param move - */ - constexpr void add(value_type&& move) noexcept { - assert(size_ < constants::MAX_MOVES); - moves_[size_++] = move; - } - - // Other - - /** - * @brief Checks if a move is in the movelist, returns the index of the move if it is found, otherwise -1. - * @param move - * @return - */ - [[nodiscard]] [[deprecated("Use std::find() instead.")]] constexpr size_type find(value_type move) const noexcept { - for (size_type i = 0; i < size_; ++i) { - if (moves_[i] == move) { - return i; - } - } - - return -1; - } - - private: - std::array moves_; - size_type size_ = 0; -}; -} // namespace chess - -namespace chess { -enum PieceGenType { - PAWN = 1, - KNIGHT = 2, - BISHOP = 4, - ROOK = 8, - QUEEN = 16, - KING = 32, -}; - -class Board; - -class movegen { - public: - enum class MoveGenType : std::uint8_t { ALL, CAPTURE, QUIET }; - - /** - * @brief Generates all legal moves for a position. - * @tparam mt - * @param movelist - * @param board - * @param pieces - */ - template - void static legalmoves(Movelist &movelist, const Board &board, - int pieces = PieceGenType::PAWN | PieceGenType::KNIGHT | PieceGenType::BISHOP | - PieceGenType::ROOK | PieceGenType::QUEEN | PieceGenType::KING); - - private: - static auto init_squares_between(); - static const std::array, 64> SQUARES_BETWEEN_BB; - - // Generate the checkmask. Returns a bitboard where the attacker path between the king and enemy piece is set. - template - [[nodiscard]] static std::pair checkMask(const Board &board, Square sq); - - // Generate the pin mask for horizontal and vertical pins -> PieceType::ROOK - // Generate the pin mask for diagonal pins. -> PieceType::BISHOP - // Returns a bitboard where the ray between the king and the pinner is set. - template - [[nodiscard]] static Bitboard pinMask(const Board &board, Square sq, Bitboard occ_enemy, Bitboard occ_us) noexcept; - - // Returns the squares that are attacked by the enemy - template - [[nodiscard]] static Bitboard seenSquares(const Board &board, Bitboard enemy_empty); - - // Generate pawn moves. - template - static void generatePawnMoves(const Board &board, Movelist &moves, Bitboard pin_d, Bitboard pin_hv, - Bitboard checkmask, Bitboard occ_enemy); - - [[nodiscard]] static std::array generateEPMove(const Board &board, Bitboard checkmask, Bitboard pin_d, - Bitboard pawns_lr, Square ep, Color c); - - [[nodiscard]] static Bitboard generateKnightMoves(Square sq); - - [[nodiscard]] static Bitboard generateBishopMoves(Square sq, Bitboard pin_d, Bitboard occ_all); - - [[nodiscard]] static Bitboard generateRookMoves(Square sq, Bitboard pin_hv, Bitboard occ_all); - - [[nodiscard]] static Bitboard generateQueenMoves(Square sq, Bitboard pin_d, Bitboard pin_hv, Bitboard occ_all); - - [[nodiscard]] static Bitboard generateKingMoves(Square sq, Bitboard seen, Bitboard movable_square); - - template - [[nodiscard]] static Bitboard generateCastleMoves(const Board &board, Square sq, Bitboard seen, Bitboard pinHV) noexcept; - - template - static void whileBitboardAdd(Movelist &movelist, Bitboard mask, T func); - - template - static void legalmoves(Movelist &movelist, const Board &board, int pieces); - - template - static bool isEpSquareValid(const Board &board, Square ep); - - [[nodiscard]] static Bitboard between(Square sq1, Square sq2) noexcept; - - friend class Board; -}; - -} // namespace chess - - - -namespace chess { -class Zobrist { - using U64 = std::uint64_t; - static constexpr U64 RANDOM_ARRAY[781] = { - 0x9D39247E33776D41, 0x2AF7398005AAA5C7, 0x44DB015024623547, 0x9C15F73E62A76AE2, 0x75834465489C0C89, - 0x3290AC3A203001BF, 0x0FBBAD1F61042279, 0xE83A908FF2FB60CA, 0x0D7E765D58755C10, 0x1A083822CEAFE02D, - 0x9605D5F0E25EC3B0, 0xD021FF5CD13A2ED5, 0x40BDF15D4A672E32, 0x011355146FD56395, 0x5DB4832046F3D9E5, - 0x239F8B2D7FF719CC, 0x05D1A1AE85B49AA1, 0x679F848F6E8FC971, 0x7449BBFF801FED0B, 0x7D11CDB1C3B7ADF0, - 0x82C7709E781EB7CC, 0xF3218F1C9510786C, 0x331478F3AF51BBE6, 0x4BB38DE5E7219443, 0xAA649C6EBCFD50FC, - 0x8DBD98A352AFD40B, 0x87D2074B81D79217, 0x19F3C751D3E92AE1, 0xB4AB30F062B19ABF, 0x7B0500AC42047AC4, - 0xC9452CA81A09D85D, 0x24AA6C514DA27500, 0x4C9F34427501B447, 0x14A68FD73C910841, 0xA71B9B83461CBD93, - 0x03488B95B0F1850F, 0x637B2B34FF93C040, 0x09D1BC9A3DD90A94, 0x3575668334A1DD3B, 0x735E2B97A4C45A23, - 0x18727070F1BD400B, 0x1FCBACD259BF02E7, 0xD310A7C2CE9B6555, 0xBF983FE0FE5D8244, 0x9F74D14F7454A824, - 0x51EBDC4AB9BA3035, 0x5C82C505DB9AB0FA, 0xFCF7FE8A3430B241, 0x3253A729B9BA3DDE, 0x8C74C368081B3075, - 0xB9BC6C87167C33E7, 0x7EF48F2B83024E20, 0x11D505D4C351BD7F, 0x6568FCA92C76A243, 0x4DE0B0F40F32A7B8, - 0x96D693460CC37E5D, 0x42E240CB63689F2F, 0x6D2BDCDAE2919661, 0x42880B0236E4D951, 0x5F0F4A5898171BB6, - 0x39F890F579F92F88, 0x93C5B5F47356388B, 0x63DC359D8D231B78, 0xEC16CA8AEA98AD76, 0x5355F900C2A82DC7, - 0x07FB9F855A997142, 0x5093417AA8A7ED5E, 0x7BCBC38DA25A7F3C, 0x19FC8A768CF4B6D4, 0x637A7780DECFC0D9, - 0x8249A47AEE0E41F7, 0x79AD695501E7D1E8, 0x14ACBAF4777D5776, 0xF145B6BECCDEA195, 0xDABF2AC8201752FC, - 0x24C3C94DF9C8D3F6, 0xBB6E2924F03912EA, 0x0CE26C0B95C980D9, 0xA49CD132BFBF7CC4, 0xE99D662AF4243939, - 0x27E6AD7891165C3F, 0x8535F040B9744FF1, 0x54B3F4FA5F40D873, 0x72B12C32127FED2B, 0xEE954D3C7B411F47, - 0x9A85AC909A24EAA1, 0x70AC4CD9F04F21F5, 0xF9B89D3E99A075C2, 0x87B3E2B2B5C907B1, 0xA366E5B8C54F48B8, - 0xAE4A9346CC3F7CF2, 0x1920C04D47267BBD, 0x87BF02C6B49E2AE9, 0x092237AC237F3859, 0xFF07F64EF8ED14D0, - 0x8DE8DCA9F03CC54E, 0x9C1633264DB49C89, 0xB3F22C3D0B0B38ED, 0x390E5FB44D01144B, 0x5BFEA5B4712768E9, - 0x1E1032911FA78984, 0x9A74ACB964E78CB3, 0x4F80F7A035DAFB04, 0x6304D09A0B3738C4, 0x2171E64683023A08, - 0x5B9B63EB9CEFF80C, 0x506AACF489889342, 0x1881AFC9A3A701D6, 0x6503080440750644, 0xDFD395339CDBF4A7, - 0xEF927DBCF00C20F2, 0x7B32F7D1E03680EC, 0xB9FD7620E7316243, 0x05A7E8A57DB91B77, 0xB5889C6E15630A75, - 0x4A750A09CE9573F7, 0xCF464CEC899A2F8A, 0xF538639CE705B824, 0x3C79A0FF5580EF7F, 0xEDE6C87F8477609D, - 0x799E81F05BC93F31, 0x86536B8CF3428A8C, 0x97D7374C60087B73, 0xA246637CFF328532, 0x043FCAE60CC0EBA0, - 0x920E449535DD359E, 0x70EB093B15B290CC, 0x73A1921916591CBD, 0x56436C9FE1A1AA8D, 0xEFAC4B70633B8F81, - 0xBB215798D45DF7AF, 0x45F20042F24F1768, 0x930F80F4E8EB7462, 0xFF6712FFCFD75EA1, 0xAE623FD67468AA70, - 0xDD2C5BC84BC8D8FC, 0x7EED120D54CF2DD9, 0x22FE545401165F1C, 0xC91800E98FB99929, 0x808BD68E6AC10365, - 0xDEC468145B7605F6, 0x1BEDE3A3AEF53302, 0x43539603D6C55602, 0xAA969B5C691CCB7A, 0xA87832D392EFEE56, - 0x65942C7B3C7E11AE, 0xDED2D633CAD004F6, 0x21F08570F420E565, 0xB415938D7DA94E3C, 0x91B859E59ECB6350, - 0x10CFF333E0ED804A, 0x28AED140BE0BB7DD, 0xC5CC1D89724FA456, 0x5648F680F11A2741, 0x2D255069F0B7DAB3, - 0x9BC5A38EF729ABD4, 0xEF2F054308F6A2BC, 0xAF2042F5CC5C2858, 0x480412BAB7F5BE2A, 0xAEF3AF4A563DFE43, - 0x19AFE59AE451497F, 0x52593803DFF1E840, 0xF4F076E65F2CE6F0, 0x11379625747D5AF3, 0xBCE5D2248682C115, - 0x9DA4243DE836994F, 0x066F70B33FE09017, 0x4DC4DE189B671A1C, 0x51039AB7712457C3, 0xC07A3F80C31FB4B4, - 0xB46EE9C5E64A6E7C, 0xB3819A42ABE61C87, 0x21A007933A522A20, 0x2DF16F761598AA4F, 0x763C4A1371B368FD, - 0xF793C46702E086A0, 0xD7288E012AEB8D31, 0xDE336A2A4BC1C44B, 0x0BF692B38D079F23, 0x2C604A7A177326B3, - 0x4850E73E03EB6064, 0xCFC447F1E53C8E1B, 0xB05CA3F564268D99, 0x9AE182C8BC9474E8, 0xA4FC4BD4FC5558CA, - 0xE755178D58FC4E76, 0x69B97DB1A4C03DFE, 0xF9B5B7C4ACC67C96, 0xFC6A82D64B8655FB, 0x9C684CB6C4D24417, - 0x8EC97D2917456ED0, 0x6703DF9D2924E97E, 0xC547F57E42A7444E, 0x78E37644E7CAD29E, 0xFE9A44E9362F05FA, - 0x08BD35CC38336615, 0x9315E5EB3A129ACE, 0x94061B871E04DF75, 0xDF1D9F9D784BA010, 0x3BBA57B68871B59D, - 0xD2B7ADEEDED1F73F, 0xF7A255D83BC373F8, 0xD7F4F2448C0CEB81, 0xD95BE88CD210FFA7, 0x336F52F8FF4728E7, - 0xA74049DAC312AC71, 0xA2F61BB6E437FDB5, 0x4F2A5CB07F6A35B3, 0x87D380BDA5BF7859, 0x16B9F7E06C453A21, - 0x7BA2484C8A0FD54E, 0xF3A678CAD9A2E38C, 0x39B0BF7DDE437BA2, 0xFCAF55C1BF8A4424, 0x18FCF680573FA594, - 0x4C0563B89F495AC3, 0x40E087931A00930D, 0x8CFFA9412EB642C1, 0x68CA39053261169F, 0x7A1EE967D27579E2, - 0x9D1D60E5076F5B6F, 0x3810E399B6F65BA2, 0x32095B6D4AB5F9B1, 0x35CAB62109DD038A, 0xA90B24499FCFAFB1, - 0x77A225A07CC2C6BD, 0x513E5E634C70E331, 0x4361C0CA3F692F12, 0xD941ACA44B20A45B, 0x528F7C8602C5807B, - 0x52AB92BEB9613989, 0x9D1DFA2EFC557F73, 0x722FF175F572C348, 0x1D1260A51107FE97, 0x7A249A57EC0C9BA2, - 0x04208FE9E8F7F2D6, 0x5A110C6058B920A0, 0x0CD9A497658A5698, 0x56FD23C8F9715A4C, 0x284C847B9D887AAE, - 0x04FEABFBBDB619CB, 0x742E1E651C60BA83, 0x9A9632E65904AD3C, 0x881B82A13B51B9E2, 0x506E6744CD974924, - 0xB0183DB56FFC6A79, 0x0ED9B915C66ED37E, 0x5E11E86D5873D484, 0xF678647E3519AC6E, 0x1B85D488D0F20CC5, - 0xDAB9FE6525D89021, 0x0D151D86ADB73615, 0xA865A54EDCC0F019, 0x93C42566AEF98FFB, 0x99E7AFEABE000731, - 0x48CBFF086DDF285A, 0x7F9B6AF1EBF78BAF, 0x58627E1A149BBA21, 0x2CD16E2ABD791E33, 0xD363EFF5F0977996, - 0x0CE2A38C344A6EED, 0x1A804AADB9CFA741, 0x907F30421D78C5DE, 0x501F65EDB3034D07, 0x37624AE5A48FA6E9, - 0x957BAF61700CFF4E, 0x3A6C27934E31188A, 0xD49503536ABCA345, 0x088E049589C432E0, 0xF943AEE7FEBF21B8, - 0x6C3B8E3E336139D3, 0x364F6FFA464EE52E, 0xD60F6DCEDC314222, 0x56963B0DCA418FC0, 0x16F50EDF91E513AF, - 0xEF1955914B609F93, 0x565601C0364E3228, 0xECB53939887E8175, 0xBAC7A9A18531294B, 0xB344C470397BBA52, - 0x65D34954DAF3CEBD, 0xB4B81B3FA97511E2, 0xB422061193D6F6A7, 0x071582401C38434D, 0x7A13F18BBEDC4FF5, - 0xBC4097B116C524D2, 0x59B97885E2F2EA28, 0x99170A5DC3115544, 0x6F423357E7C6A9F9, 0x325928EE6E6F8794, - 0xD0E4366228B03343, 0x565C31F7DE89EA27, 0x30F5611484119414, 0xD873DB391292ED4F, 0x7BD94E1D8E17DEBC, - 0xC7D9F16864A76E94, 0x947AE053EE56E63C, 0xC8C93882F9475F5F, 0x3A9BF55BA91F81CA, 0xD9A11FBB3D9808E4, - 0x0FD22063EDC29FCA, 0xB3F256D8ACA0B0B9, 0xB03031A8B4516E84, 0x35DD37D5871448AF, 0xE9F6082B05542E4E, - 0xEBFAFA33D7254B59, 0x9255ABB50D532280, 0xB9AB4CE57F2D34F3, 0x693501D628297551, 0xC62C58F97DD949BF, - 0xCD454F8F19C5126A, 0xBBE83F4ECC2BDECB, 0xDC842B7E2819E230, 0xBA89142E007503B8, 0xA3BC941D0A5061CB, - 0xE9F6760E32CD8021, 0x09C7E552BC76492F, 0x852F54934DA55CC9, 0x8107FCCF064FCF56, 0x098954D51FFF6580, - 0x23B70EDB1955C4BF, 0xC330DE426430F69D, 0x4715ED43E8A45C0A, 0xA8D7E4DAB780A08D, 0x0572B974F03CE0BB, - 0xB57D2E985E1419C7, 0xE8D9ECBE2CF3D73F, 0x2FE4B17170E59750, 0x11317BA87905E790, 0x7FBF21EC8A1F45EC, - 0x1725CABFCB045B00, 0x964E915CD5E2B207, 0x3E2B8BCBF016D66D, 0xBE7444E39328A0AC, 0xF85B2B4FBCDE44B7, - 0x49353FEA39BA63B1, 0x1DD01AAFCD53486A, 0x1FCA8A92FD719F85, 0xFC7C95D827357AFA, 0x18A6A990C8B35EBD, - 0xCCCB7005C6B9C28D, 0x3BDBB92C43B17F26, 0xAA70B5B4F89695A2, 0xE94C39A54A98307F, 0xB7A0B174CFF6F36E, - 0xD4DBA84729AF48AD, 0x2E18BC1AD9704A68, 0x2DE0966DAF2F8B1C, 0xB9C11D5B1E43A07E, 0x64972D68DEE33360, - 0x94628D38D0C20584, 0xDBC0D2B6AB90A559, 0xD2733C4335C6A72F, 0x7E75D99D94A70F4D, 0x6CED1983376FA72B, - 0x97FCAACBF030BC24, 0x7B77497B32503B12, 0x8547EDDFB81CCB94, 0x79999CDFF70902CB, 0xCFFE1939438E9B24, - 0x829626E3892D95D7, 0x92FAE24291F2B3F1, 0x63E22C147B9C3403, 0xC678B6D860284A1C, 0x5873888850659AE7, - 0x0981DCD296A8736D, 0x9F65789A6509A440, 0x9FF38FED72E9052F, 0xE479EE5B9930578C, 0xE7F28ECD2D49EECD, - 0x56C074A581EA17FE, 0x5544F7D774B14AEF, 0x7B3F0195FC6F290F, 0x12153635B2C0CF57, 0x7F5126DBBA5E0CA7, - 0x7A76956C3EAFB413, 0x3D5774A11D31AB39, 0x8A1B083821F40CB4, 0x7B4A38E32537DF62, 0x950113646D1D6E03, - 0x4DA8979A0041E8A9, 0x3BC36E078F7515D7, 0x5D0A12F27AD310D1, 0x7F9D1A2E1EBE1327, 0xDA3A361B1C5157B1, - 0xDCDD7D20903D0C25, 0x36833336D068F707, 0xCE68341F79893389, 0xAB9090168DD05F34, 0x43954B3252DC25E5, - 0xB438C2B67F98E5E9, 0x10DCD78E3851A492, 0xDBC27AB5447822BF, 0x9B3CDB65F82CA382, 0xB67B7896167B4C84, - 0xBFCED1B0048EAC50, 0xA9119B60369FFEBD, 0x1FFF7AC80904BF45, 0xAC12FB171817EEE7, 0xAF08DA9177DDA93D, - 0x1B0CAB936E65C744, 0xB559EB1D04E5E932, 0xC37B45B3F8D6F2BA, 0xC3A9DC228CAAC9E9, 0xF3B8B6675A6507FF, - 0x9FC477DE4ED681DA, 0x67378D8ECCEF96CB, 0x6DD856D94D259236, 0xA319CE15B0B4DB31, 0x073973751F12DD5E, - 0x8A8E849EB32781A5, 0xE1925C71285279F5, 0x74C04BF1790C0EFE, 0x4DDA48153C94938A, 0x9D266D6A1CC0542C, - 0x7440FB816508C4FE, 0x13328503DF48229F, 0xD6BF7BAEE43CAC40, 0x4838D65F6EF6748F, 0x1E152328F3318DEA, - 0x8F8419A348F296BF, 0x72C8834A5957B511, 0xD7A023A73260B45C, 0x94EBC8ABCFB56DAE, 0x9FC10D0F989993E0, - 0xDE68A2355B93CAE6, 0xA44CFE79AE538BBE, 0x9D1D84FCCE371425, 0x51D2B1AB2DDFB636, 0x2FD7E4B9E72CD38C, - 0x65CA5B96B7552210, 0xDD69A0D8AB3B546D, 0x604D51B25FBF70E2, 0x73AA8A564FB7AC9E, 0x1A8C1E992B941148, - 0xAAC40A2703D9BEA0, 0x764DBEAE7FA4F3A6, 0x1E99B96E70A9BE8B, 0x2C5E9DEB57EF4743, 0x3A938FEE32D29981, - 0x26E6DB8FFDF5ADFE, 0x469356C504EC9F9D, 0xC8763C5B08D1908C, 0x3F6C6AF859D80055, 0x7F7CC39420A3A545, - 0x9BFB227EBDF4C5CE, 0x89039D79D6FC5C5C, 0x8FE88B57305E2AB6, 0xA09E8C8C35AB96DE, 0xFA7E393983325753, - 0xD6B6D0ECC617C699, 0xDFEA21EA9E7557E3, 0xB67C1FA481680AF8, 0xCA1E3785A9E724E5, 0x1CFC8BED0D681639, - 0xD18D8549D140CAEA, 0x4ED0FE7E9DC91335, 0xE4DBF0634473F5D2, 0x1761F93A44D5AEFE, 0x53898E4C3910DA55, - 0x734DE8181F6EC39A, 0x2680B122BAA28D97, 0x298AF231C85BAFAB, 0x7983EED3740847D5, 0x66C1A2A1A60CD889, - 0x9E17E49642A3E4C1, 0xEDB454E7BADC0805, 0x50B704CAB602C329, 0x4CC317FB9CDDD023, 0x66B4835D9EAFEA22, - 0x219B97E26FFC81BD, 0x261E4E4C0A333A9D, 0x1FE2CCA76517DB90, 0xD7504DFA8816EDBB, 0xB9571FA04DC089C8, - 0x1DDC0325259B27DE, 0xCF3F4688801EB9AA, 0xF4F5D05C10CAB243, 0x38B6525C21A42B0E, 0x36F60E2BA4FA6800, - 0xEB3593803173E0CE, 0x9C4CD6257C5A3603, 0xAF0C317D32ADAA8A, 0x258E5A80C7204C4B, 0x8B889D624D44885D, - 0xF4D14597E660F855, 0xD4347F66EC8941C3, 0xE699ED85B0DFB40D, 0x2472F6207C2D0484, 0xC2A1E7B5B459AEB5, - 0xAB4F6451CC1D45EC, 0x63767572AE3D6174, 0xA59E0BD101731A28, 0x116D0016CB948F09, 0x2CF9C8CA052F6E9F, - 0x0B090A7560A968E3, 0xABEEDDB2DDE06FF1, 0x58EFC10B06A2068D, 0xC6E57A78FBD986E0, 0x2EAB8CA63CE802D7, - 0x14A195640116F336, 0x7C0828DD624EC390, 0xD74BBE77E6116AC7, 0x804456AF10F5FB53, 0xEBE9EA2ADF4321C7, - 0x03219A39EE587A30, 0x49787FEF17AF9924, 0xA1E9300CD8520548, 0x5B45E522E4B1B4EF, 0xB49C3B3995091A36, - 0xD4490AD526F14431, 0x12A8F216AF9418C2, 0x001F837CC7350524, 0x1877B51E57A764D5, 0xA2853B80F17F58EE, - 0x993E1DE72D36D310, 0xB3598080CE64A656, 0x252F59CF0D9F04BB, 0xD23C8E176D113600, 0x1BDA0492E7E4586E, - 0x21E0BD5026C619BF, 0x3B097ADAF088F94E, 0x8D14DEDB30BE846E, 0xF95CFFA23AF5F6F4, 0x3871700761B3F743, - 0xCA672B91E9E4FA16, 0x64C8E531BFF53B55, 0x241260ED4AD1E87D, 0x106C09B972D2E822, 0x7FBA195410E5CA30, - 0x7884D9BC6CB569D8, 0x0647DFEDCD894A29, 0x63573FF03E224774, 0x4FC8E9560F91B123, 0x1DB956E450275779, - 0xB8D91274B9E9D4FB, 0xA2EBEE47E2FBFCE1, 0xD9F1F30CCD97FB09, 0xEFED53D75FD64E6B, 0x2E6D02C36017F67F, - 0xA9AA4D20DB084E9B, 0xB64BE8D8B25396C1, 0x70CB6AF7C2D5BCF0, 0x98F076A4F7A2322E, 0xBF84470805E69B5F, - 0x94C3251F06F90CF3, 0x3E003E616A6591E9, 0xB925A6CD0421AFF3, 0x61BDD1307C66E300, 0xBF8D5108E27E0D48, - 0x240AB57A8B888B20, 0xFC87614BAF287E07, 0xEF02CDD06FFDB432, 0xA1082C0466DF6C0A, 0x8215E577001332C8, - 0xD39BB9C3A48DB6CF, 0x2738259634305C14, 0x61CF4F94C97DF93D, 0x1B6BACA2AE4E125B, 0x758F450C88572E0B, - 0x959F587D507A8359, 0xB063E962E045F54D, 0x60E8ED72C0DFF5D1, 0x7B64978555326F9F, 0xFD080D236DA814BA, - 0x8C90FD9B083F4558, 0x106F72FE81E2C590, 0x7976033A39F7D952, 0xA4EC0132764CA04B, 0x733EA705FAE4FA77, - 0xB4D8F77BC3E56167, 0x9E21F4F903B33FD9, 0x9D765E419FB69F6D, 0xD30C088BA61EA5EF, 0x5D94337FBFAF7F5B, - 0x1A4E4822EB4D7A59, 0x6FFE73E81B637FB3, 0xDDF957BC36D8B9CA, 0x64D0E29EEA8838B3, 0x08DD9BDFD96B9F63, - 0x087E79E5A57D1D13, 0xE328E230E3E2B3FB, 0x1C2559E30F0946BE, 0x720BF5F26F4D2EAA, 0xB0774D261CC609DB, - 0x443F64EC5A371195, 0x4112CF68649A260E, 0xD813F2FAB7F5C5CA, 0x660D3257380841EE, 0x59AC2C7873F910A3, - 0xE846963877671A17, 0x93B633ABFA3469F8, 0xC0C0F5A60EF4CDCF, 0xCAF21ECD4377B28C, 0x57277707199B8175, - 0x506C11B9D90E8B1D, 0xD83CC2687A19255F, 0x4A29C6465A314CD1, 0xED2DF21216235097, 0xB5635C95FF7296E2, - 0x22AF003AB672E811, 0x52E762596BF68235, 0x9AEBA33AC6ECC6B0, 0x944F6DE09134DFB6, 0x6C47BEC883A7DE39, - 0x6AD047C430A12104, 0xA5B1CFDBA0AB4067, 0x7C45D833AFF07862, 0x5092EF950A16DA0B, 0x9338E69C052B8E7B, - 0x455A4B4CFE30E3F5, 0x6B02E63195AD0CF8, 0x6B17B224BAD6BF27, 0xD1E0CCD25BB9C169, 0xDE0C89A556B9AE70, - 0x50065E535A213CF6, 0x9C1169FA2777B874, 0x78EDEFD694AF1EED, 0x6DC93D9526A50E68, 0xEE97F453F06791ED, - 0x32AB0EDB696703D3, 0x3A6853C7E70757A7, 0x31865CED6120F37D, 0x67FEF95D92607890, 0x1F2B1D1F15F6DC9C, - 0xB69E38A8965C6B65, 0xAA9119FF184CCCF4, 0xF43C732873F24C13, 0xFB4A3D794A9A80D2, 0x3550C2321FD6109C, - 0x371F77E76BB8417E, 0x6BFA9AAE5EC05779, 0xCD04F3FF001A4778, 0xE3273522064480CA, 0x9F91508BFFCFC14A, - 0x049A7F41061A9E60, 0xFCB6BE43A9F2FE9B, 0x08DE8A1C7797DA9B, 0x8F9887E6078735A1, 0xB5B4071DBFC73A66, - 0x230E343DFBA08D33, 0x43ED7F5A0FAE657D, 0x3A88A0FBBCB05C63, 0x21874B8B4D2DBC4F, 0x1BDEA12E35F6A8C9, - 0x53C065C6C8E63528, 0xE34A1D250E7A8D6B, 0xD6B04D3B7651DD7E, 0x5E90277E7CB39E2D, 0x2C046F22062DC67D, - 0xB10BB459132D0A26, 0x3FA9DDFB67E2F199, 0x0E09B88E1914F7AF, 0x10E8B35AF3EEAB37, 0x9EEDECA8E272B933, - 0xD4C718BC4AE8AE5F, 0x81536D601170FC20, 0x91B534F885818A06, 0xEC8177F83F900978, 0x190E714FADA5156E, - 0xB592BF39B0364963, 0x89C350C893AE7DC1, 0xAC042E70F8B383F2, 0xB49B52E587A1EE60, 0xFB152FE3FF26DA89, - 0x3E666E6F69AE2C15, 0x3B544EBE544C19F9, 0xE805A1E290CF2456, 0x24B33C9D7ED25117, 0xE74733427B72F0C1, - 0x0A804D18B7097475, 0x57E3306D881EDB4F, 0x4AE7D6A36EB5DBCB, 0x2D8D5432157064C8, 0xD1E649DE1E7F268B, - 0x8A328A1CEDFE552C, 0x07A3AEC79624C7DA, 0x84547DDC3E203C94, 0x990A98FD5071D263, 0x1A4FF12616EEFC89, - 0xF6F7FD1431714200, 0x30C05B1BA332F41C, 0x8D2636B81555A786, 0x46C9FEB55D120902, 0xCCEC0A73B49C9921, - 0x4E9D2827355FC492, 0x19EBB029435DCB0F, 0x4659D2B743848A2C, 0x963EF2C96B33BE31, 0x74F85198B05A2E7D, - 0x5A0F544DD2B1FB18, 0x03727073C2E134B1, 0xC7F6AA2DE59AEA61, 0x352787BAA0D7C22F, 0x9853EAB63B5E0B35, - 0xABBDCDD7ED5C0860, 0xCF05DAF5AC8D77B0, 0x49CAD48CEBF4A71E, 0x7A4C10EC2158C4A6, 0xD9E92AA246BF719E, - 0x13AE978D09FE5557, 0x730499AF921549FF, 0x4E4B705B92903BA4, 0xFF577222C14F0A3A, 0x55B6344CF97AAFAE, - 0xB862225B055B6960, 0xCAC09AFBDDD2CDB4, 0xDAF8E9829FE96B5F, 0xB5FDFC5D3132C498, 0x310CB380DB6F7503, - 0xE87FBB46217A360E, 0x2102AE466EBB1148, 0xF8549E1A3AA5E00D, 0x07A69AFDCC42261A, 0xC4C118BFE78FEAAE, - 0xF9F4892ED96BD438, 0x1AF3DBE25D8F45DA, 0xF5B4B0B0D2DEEEB4, 0x962ACEEFA82E1C84, 0x046E3ECAAF453CE9, - 0xF05D129681949A4C, 0x964781CE734B3C84, 0x9C2ED44081CE5FBD, 0x522E23F3925E319E, 0x177E00F9FC32F791, - 0x2BC60A63A6F3B3F2, 0x222BBFAE61725606, 0x486289DDCC3D6780, 0x7DC7785B8EFDFC80, 0x8AF38731C02BA980, - 0x1FAB64EA29A2DDF7, 0xE4D9429322CD065A, 0x9DA058C67844F20C, 0x24C0E332B70019B0, 0x233003B5A6CFE6AD, - 0xD586BD01C5C217F6, 0x5E5637885F29BC2B, 0x7EBA726D8C94094B, 0x0A56A5F0BFE39272, 0xD79476A84EE20D06, - 0x9E4C1269BAA4BF37, 0x17EFEE45B0DEE640, 0x1D95B0A5FCF90BC6, 0x93CBE0B699C2585D, 0x65FA4F227A2B6D79, - 0xD5F9E858292504D5, 0xC2B5A03F71471A6F, 0x59300222B4561E00, 0xCE2F8642CA0712DC, 0x7CA9723FBB2E8988, - 0x2785338347F2BA08, 0xC61BB3A141E50E8C, 0x150F361DAB9DEC26, 0x9F6A419D382595F4, 0x64A53DC924FE7AC9, - 0x142DE49FFF7A7C3D, 0x0C335248857FA9E7, 0x0A9C32D5EAE45305, 0xE6C42178C4BBB92E, 0x71F1CE2490D20B07, - 0xF1BCC3D275AFE51A, 0xE728E8C83C334074, 0x96FBF83A12884624, 0x81A1549FD6573DA5, 0x5FA7867CAF35E149, - 0x56986E2EF3ED091B, 0x917F1DD5F8886C61, 0xD20D8C88C8FFE65F, 0x31D71DCE64B2C310, 0xF165B587DF898190, - 0xA57E6339DD2CF3A0, 0x1EF6E6DBB1961EC9, 0x70CC73D90BC26E24, 0xE21A6B35DF0C3AD7, 0x003A93D8B2806962, - 0x1C99DED33CB890A1, 0xCF3145DE0ADD4289, 0xD0E4427A5514FB72, 0x77C621CC9FB3A483, 0x67A34DAC4356550B, - 0xF8D626AAAF278509}; - - static constexpr std::array castlingKey = []() constexpr { - auto generateCastlingKey = [](int index) constexpr -> U64 { - constexpr int RANDOM_OFFSET = 768; - constexpr int RANDOM_COUNT = 4; - - U64 key = 0; - - for (int i = 0; i < RANDOM_COUNT; ++i) { - if (index & (1 << i)) { - key ^= RANDOM_ARRAY[RANDOM_OFFSET + i]; - } - } - - return key; - }; - - std::array arr{}; - - for (int i = 0; i < 16; ++i) arr[i] = generateCastlingKey(i); - - return arr; - }(); - - static constexpr int MAP_HASH_PIECE[12] = {1, 3, 5, 7, 9, 11, 0, 2, 4, 6, 8, 10}; - - [[nodiscard]] static U64 piece(Piece piece, Square square) noexcept { - assert(piece < 12); - return RANDOM_ARRAY[64 * MAP_HASH_PIECE[piece] + square.index()]; - } - - [[nodiscard]] static U64 enpassant(File file) noexcept { - assert(static_cast(file) < 8); - return RANDOM_ARRAY[772 + file]; - } - - [[nodiscard]] static U64 castling(int castling) noexcept { - assert(castling >= 0 && castling < 16); - return castlingKey[castling]; - } - - [[nodiscard]] static U64 castlingIndex(int idx) noexcept { - assert(idx >= 0 && idx < 4); - return RANDOM_ARRAY[768 + idx]; - } - - [[nodiscard]] static U64 sideToMove() noexcept { return RANDOM_ARRAY[780]; } - - public: - friend class Board; -}; - -} // namespace chess - -namespace chess { - -namespace detail { -inline std::optional parseStringViewToInt(std::string_view sv) { - if (sv.empty()) return std::nullopt; - - std::string_view parsed_sv = sv; - if (parsed_sv.back() == ';') parsed_sv.remove_suffix(1); - - if (parsed_sv.empty()) return std::nullopt; - -#ifdef CHESS_USE_CHARCONV - int result; - const char *begin = parsed_sv.data(); - const char *end = begin + parsed_sv.size(); - - auto [ptr, ec] = std::from_chars(begin, end, result); - - if (ec == std::errc() && ptr == end) { - return result; - } -#else - std::string str(parsed_sv); - std::stringstream ss(str); - - ss.exceptions(std::ios::goodbit); - - int value; - ss >> value; - - if (!ss.fail() && (ss.eof() || (ss >> std::ws).eof())) { - return value; - } -#endif - - return std::nullopt; -} -} // namespace detail - -enum class GameResult { WIN, LOSE, DRAW, NONE }; - -enum class GameResultReason { - CHECKMATE, - STALEMATE, - INSUFFICIENT_MATERIAL, - FIFTY_MOVE_RULE, - THREEFOLD_REPETITION, - NONE -}; - -enum class CheckType { NO_CHECK, DIRECT_CHECK, DISCOVERY_CHECK }; - -// A compact representation of the board in 24 bytes, -// does not include the half-move clock or full move number. -using PackedBoard = std::array; - -class Board { - using U64 = std::uint64_t; - - public: - class CastlingRights { - public: - enum class Side : std::uint8_t { KING_SIDE, QUEEN_SIDE }; - - constexpr void setCastlingRight(Color color, Side castle, File rook_file) { - rooks[color][static_cast(castle)] = rook_file; - } - - constexpr void clear() { rooks[0][0] = rooks[0][1] = rooks[1][0] = rooks[1][1] = File::NO_FILE; } - - constexpr int clear(Color color, Side castle) { - rooks[color][static_cast(castle)] = File::NO_FILE; - return color * 2 + static_cast(castle); - } - - constexpr void clear(Color color) { rooks[color][0] = rooks[color][1] = File::NO_FILE; } - - constexpr bool has(Color color, Side castle) const { - return rooks[color][static_cast(castle)] != File::NO_FILE; - } - - constexpr bool has(Color color) const { return has(color, Side::KING_SIDE) || has(color, Side::QUEEN_SIDE); } - - constexpr File getRookFile(Color color, Side castle) const { return rooks[color][static_cast(castle)]; } - - constexpr int hashIndex() const { - return has(Color::WHITE, Side::KING_SIDE) + 2 * has(Color::WHITE, Side::QUEEN_SIDE) + - 4 * has(Color::BLACK, Side::KING_SIDE) + 8 * has(Color::BLACK, Side::QUEEN_SIDE); - } - - constexpr bool isEmpty() const { return !has(Color::WHITE) && !has(Color::BLACK); } - - template - static constexpr Side closestSide(T sq, T pred) { - return sq > pred ? Side::KING_SIDE : Side::QUEEN_SIDE; - } - - private: - std::array, 2> rooks; - }; - - private: - struct State { - U64 hash; - CastlingRights castling; - Square enpassant; - std::uint8_t half_moves; - Piece captured_piece; - - State(const U64 &hash, const CastlingRights &castling, const Square &enpassant, const std::uint8_t &half_moves, - const Piece &captured_piece) - : hash(hash), - castling(castling), - enpassant(enpassant), - half_moves(half_moves), - captured_piece(captured_piece) {} - }; - - enum class PrivateCtor { CREATE }; - - // private constructor to avoid initialization - Board(PrivateCtor) {} - - public: - explicit Board(std::string_view fen = constants::STARTPOS, bool chess960 = false) { - prev_states_.reserve(256); - chess960_ = chess960; - assert(setFenInternal(constants::STARTPOS)); - setFenInternal(fen); - } - - static Board fromFen(std::string_view fen) { return Board(fen); } - static Board fromEpd(std::string_view epd) { - Board board; - board.setEpd(epd); - return board; - } - - /** - * @brief Returns true if the given FEN was successfully parsed and set. - * @param fen - * @return - */ - virtual bool setFen(std::string_view fen) { return setFenInternal(fen); } - - /** - * @brief Returns true if the given EPD was successfully parsed and set. - * @param epd - * @return - */ - bool setEpd(const std::string_view epd) { - auto parts = utils::splitString(epd, ' '); - - if (parts.size() < 4) return false; - - int hm = 0; - int fm = 1; - - if (auto it = std::find(parts.begin(), parts.end(), "hmvc"); it != parts.end()) { - if (std::distance(it, parts.end()) > 1) { - auto num = *(it + 1); - auto parsed = detail::parseStringViewToInt(num); - if (parsed) hm = *parsed; - } else { - return false; - } - } - - if (auto it = std::find(parts.begin(), parts.end(), "fmvn"); it != parts.end()) { - if (std::distance(it, parts.end()) > 1) { - auto num = *(it + 1); - auto parsed = detail::parseStringViewToInt(num); - if (parsed && *parsed > 0) - fm = *parsed; - else - return false; - } else { - return false; - } - } - - auto fen = std::string(parts[0]) + " " + std::string(parts[1]) + " " + std::string(parts[2]) + " " + - std::string(parts[3]) + " " + std::to_string(hm) + " " + std::to_string(fm); - - return setFen(fen); - } - - /** - * @brief Get the current FEN string. - * @param move_counters - * @return - */ - [[nodiscard]] std::string getFen(bool move_counters = true) const { - std::string ss; - ss.reserve(100); - - // Loop through the ranks of the board in reverse order - for (int rank = 7; rank >= 0; rank--) { - std::uint32_t free_space = 0; - - // Loop through the files of the board - for (int file = 0; file < 8; file++) { - // Calculate the square index - const int sq = rank * 8 + file; - - // If there is a piece at the current square - if (Piece piece = at(Square(sq)); piece != Piece::NONE) { - // If there were any empty squares before this piece, - // append the number of empty squares to the FEN string - if (free_space) { - ss += std::to_string(free_space); - free_space = 0; - } - - // Append the character representing the piece to the FEN string - ss += static_cast(piece); - } else { - // If there is no piece at the current square, increment the - // counter for the number of empty squares - free_space++; - } - } - - // If there are any empty squares at the end of the rank, - // append the number of empty squares to the FEN string - if (free_space != 0) { - ss += std::to_string(free_space); - } - - // Append a "/" character to the FEN string, unless this is the last rank - ss += (rank > 0 ? "/" : ""); - } - - // Append " w " or " b " to the FEN string, depending on which player's turn it is - ss += ' '; - ss += (stm_ == Color::WHITE ? 'w' : 'b'); - - // Append the appropriate characters to the FEN string to indicate - // whether castling is allowed for each player - if (cr_.isEmpty()) - ss += " -"; - else { - ss += ' '; - ss += getCastleString(); - } - - // Append information about the en passant square (if any) - // and the half-move clock and full move number to the FEN string - if (ep_sq_ == Square::NO_SQ) - ss += " -"; - else { - ss += ' '; - ss += static_cast(ep_sq_); - } - - if (move_counters) { - ss += ' '; - ss += std::to_string(halfMoveClock()); - ss += ' '; - ss += std::to_string(fullMoveNumber()); - } - - // Return the resulting FEN string - return ss; - } - - [[nodiscard]] std::string getEpd() const { - std::string ss; - ss.reserve(100); - - ss += getFen(false); - ss += " hmvc "; - ss += std::to_string(halfMoveClock()) + ";"; - ss += " fmvn "; - ss += std::to_string(fullMoveNumber()) + ";"; - - return ss; - } - - /** - * @brief Make a move on the board. The move must be legal otherwise the - * behavior is undefined. EXACT can be set to true to only record - * the enpassant square if the enemy can legally capture the pawn on their - * next move. - * @tparam EXACT - * @param move - */ - template - void makeMove(const Move move) { - const auto capture = at(move.to()) != Piece::NONE && move.typeOf() != Move::CASTLING; - const auto captured = at(move.to()); - const auto pt = at(move.from()); - - // Validate side to move - assert((at(move.from()) < Piece::BLACKPAWN) == (stm_ == Color::WHITE)); - - prev_states_.emplace_back(key_, cr_, ep_sq_, hfm_, captured); - - hfm_++; - plies_++; - - if (ep_sq_ != Square::NO_SQ) key_ ^= Zobrist::enpassant(ep_sq_.file()); - ep_sq_ = Square::NO_SQ; - - if (capture) { - removePiece(captured, move.to()); - - hfm_ = 0; - key_ ^= Zobrist::piece(captured, move.to()); - - // remove castling rights if rook is captured - if (captured.type() == PieceType::ROOK && Rank::back_rank(move.to().rank(), ~stm_)) { - const auto king_sq = kingSq(~stm_); - const auto file = CastlingRights::closestSide(move.to(), king_sq); - - if (cr_.getRookFile(~stm_, file) == move.to().file()) { - key_ ^= Zobrist::castlingIndex(cr_.clear(~stm_, file)); - } - } - } - - // remove castling rights if king moves - if (pt == PieceType::KING && cr_.has(stm_)) { - key_ ^= Zobrist::castling(cr_.hashIndex()); - cr_.clear(stm_); - key_ ^= Zobrist::castling(cr_.hashIndex()); - } else if (pt == PieceType::ROOK && Square::back_rank(move.from(), stm_)) { - const auto king_sq = kingSq(stm_); - const auto file = CastlingRights::closestSide(move.from(), king_sq); - - // remove castling rights if rook moves from back rank - if (cr_.getRookFile(stm_, file) == move.from().file()) { - key_ ^= Zobrist::castlingIndex(cr_.clear(stm_, file)); - } - } else if (pt == PieceType::PAWN) { - hfm_ = 0; - - // double push - if (Square::value_distance(move.to(), move.from()) == 16) { - // imaginary attacks from the ep square from the pawn which moved - Bitboard ep_mask = attacks::pawn(stm_, move.to().ep_square()); - - // add enpassant hash if enemy pawns are attacking the square - if (static_cast(ep_mask & pieces(PieceType::PAWN, ~stm_))) { - int found = -1; - - // check if the enemy can legally capture the pawn on the next move - if constexpr (EXACT) { - const auto piece = at(move.from()); - - found = 0; - - removePieceInternal(piece, move.from()); - placePieceInternal(piece, move.to()); - - stm_ = ~stm_; - - bool valid; - - if (stm_ == Color::WHITE) { - valid = movegen::isEpSquareValid(*this, move.to().ep_square()); - } else { - valid = movegen::isEpSquareValid(*this, move.to().ep_square()); - } - - if (valid) found = 1; - - // undo - stm_ = ~stm_; - - removePieceInternal(piece, move.to()); - placePieceInternal(piece, move.from()); - } - - if (found != 0) { - assert(at(move.to().ep_square()) == Piece::NONE); - ep_sq_ = move.to().ep_square(); - key_ ^= Zobrist::enpassant(move.to().ep_square().file()); - } - } - } - } - - if (move.typeOf() == Move::CASTLING) { - assert(at(move.from()) == PieceType::KING); - assert(at(move.to()) == PieceType::ROOK); - - const bool king_side = move.to() > move.from(); - const auto rookTo = Square::castling_rook_square(king_side, stm_); - const auto kingTo = Square::castling_king_square(king_side, stm_); - - const auto king = at(move.from()); - const auto rook = at(move.to()); - - removePiece(king, move.from()); - removePiece(rook, move.to()); - - assert(king == Piece(PieceType::KING, stm_)); - assert(rook == Piece(PieceType::ROOK, stm_)); - - placePiece(king, kingTo); - placePiece(rook, rookTo); - - key_ ^= Zobrist::piece(king, move.from()) ^ Zobrist::piece(king, kingTo); - key_ ^= Zobrist::piece(rook, move.to()) ^ Zobrist::piece(rook, rookTo); - } else if (move.typeOf() == Move::PROMOTION) { - const auto piece_pawn = Piece(PieceType::PAWN, stm_); - const auto piece_prom = Piece(move.promotionType(), stm_); - - removePiece(piece_pawn, move.from()); - placePiece(piece_prom, move.to()); - - key_ ^= Zobrist::piece(piece_pawn, move.from()) ^ Zobrist::piece(piece_prom, move.to()); - } else { - assert(at(move.from()) != Piece::NONE); - assert(at(move.to()) == Piece::NONE); - - const auto piece = at(move.from()); - - removePiece(piece, move.from()); - placePiece(piece, move.to()); - - key_ ^= Zobrist::piece(piece, move.from()) ^ Zobrist::piece(piece, move.to()); - } - - if (move.typeOf() == Move::ENPASSANT) { - assert(at(move.to().ep_square()) == PieceType::PAWN); - - const auto piece = Piece(PieceType::PAWN, ~stm_); - - removePiece(piece, move.to().ep_square()); - - key_ ^= Zobrist::piece(piece, move.to().ep_square()); - } - - key_ ^= Zobrist::sideToMove(); - stm_ = ~stm_; - } - - void unmakeMove(const Move move) { - const auto &prev = prev_states_.back(); - - ep_sq_ = prev.enpassant; - cr_ = prev.castling; - hfm_ = prev.half_moves; - stm_ = ~stm_; - plies_--; - - if (move.typeOf() == Move::CASTLING) { - const bool king_side = move.to() > move.from(); - const auto rook_from_sq = Square(king_side ? File::FILE_F : File::FILE_D, move.from().rank()); - const auto king_to_sq = Square(king_side ? File::FILE_G : File::FILE_C, move.from().rank()); - - assert(at(rook_from_sq) == PieceType::ROOK); - assert(at(king_to_sq) == PieceType::KING); - - const auto rook = at(rook_from_sq); - const auto king = at(king_to_sq); - - removePiece(rook, rook_from_sq); - removePiece(king, king_to_sq); - - assert(king == Piece(PieceType::KING, stm_)); - assert(rook == Piece(PieceType::ROOK, stm_)); - - placePiece(king, move.from()); - placePiece(rook, move.to()); - - } else if (move.typeOf() == Move::PROMOTION) { - const auto pawn = Piece(PieceType::PAWN, stm_); - const auto piece = at(move.to()); - - assert(piece.type() == move.promotionType()); - assert(piece.type() != PieceType::PAWN); - assert(piece.type() != PieceType::KING); - assert(piece.type() != PieceType::NONE); - - removePiece(piece, move.to()); - placePiece(pawn, move.from()); - - if (prev.captured_piece != Piece::NONE) { - assert(at(move.to()) == Piece::NONE); - placePiece(prev.captured_piece, move.to()); - } - - } else { - assert(at(move.to()) != Piece::NONE); - assert(at(move.from()) == Piece::NONE); - - const auto piece = at(move.to()); - - removePiece(piece, move.to()); - placePiece(piece, move.from()); - - if (move.typeOf() == Move::ENPASSANT) { - const auto pawn = Piece(PieceType::PAWN, ~stm_); - const auto pawnTo = static_cast(ep_sq_ ^ 8); - - assert(at(pawnTo) == Piece::NONE); - - placePiece(pawn, pawnTo); - } else if (prev.captured_piece != Piece::NONE) { - assert(at(move.to()) == Piece::NONE); - - placePiece(prev.captured_piece, move.to()); - } - } - - key_ = prev.hash; - prev_states_.pop_back(); - } - - /** - * @brief Make a null move. (Switches the side to move) - */ - void makeNullMove() { - prev_states_.emplace_back(key_, cr_, ep_sq_, hfm_, Piece::NONE); - - key_ ^= Zobrist::sideToMove(); - if (ep_sq_ != Square::NO_SQ) key_ ^= Zobrist::enpassant(ep_sq_.file()); - ep_sq_ = Square::NO_SQ; - - stm_ = ~stm_; - - plies_++; - } - - /** - * @brief Unmake a null move. (Switches the side to move) - */ - void unmakeNullMove() { - const auto &prev = prev_states_.back(); - - ep_sq_ = prev.enpassant; - cr_ = prev.castling; - hfm_ = prev.half_moves; - key_ = prev.hash; - - plies_--; - - stm_ = ~stm_; - - prev_states_.pop_back(); - } - - /** - * @brief Get the occupancy bitboard for the color. - * @param color - * @return - */ - [[nodiscard]] Bitboard us(Color color) const noexcept { return occ_bb_[color]; } - - /** - * @brief Get the occupancy bitboard for the opposite color. - * @param color - * @return - */ - [[nodiscard]] Bitboard them(Color color) const noexcept { return us(~color); } - - /** - * @brief Get the occupancy bitboard for both colors. - * Faster than calling all() or us(Color::WHITE) | us(Color::BLACK). - * @return - */ - [[nodiscard]] Bitboard occ() const noexcept { return occ_bb_[0] | occ_bb_[1]; } - - /** - * @brief Get the occupancy bitboard for all pieces, should be only used internally. - * @return - */ - [[nodiscard]] Bitboard all() const noexcept { return us(Color::WHITE) | us(Color::BLACK); } - - /** - * @brief Returns the square of the king for a certain color - * @param color - * @return - */ - [[nodiscard]] Square kingSq(Color color) const noexcept { - assert(pieces(PieceType::KING, color) != 0ull); - return pieces(PieceType::KING, color).lsb(); - } - - /** - * @brief Returns all pieces of a certain type and color - * @param type - * @param color - * @return - */ - [[nodiscard]] Bitboard pieces(PieceType type, Color color) const noexcept { - return pieces_bb_[type] & occ_bb_[color]; - } - - /** - * @brief Returns all pieces of a certain type - * @param type - * @return - */ - [[nodiscard]] Bitboard pieces(PieceType type) const noexcept { return pieces_bb_[type]; } - - template && ...)>> - [[nodiscard]] Bitboard pieces(Pieces... pieces) const noexcept { - return (pieces_bb_[static_cast(pieces)] | ...); - } - - /** - * @brief Returns either the piece or the piece type on a square - * @tparam T - * @param sq - * @return - */ - template - [[nodiscard]] T at(Square sq) const noexcept { - assert(sq.is_valid()); - - if constexpr (std::is_same_v) { - return board_[sq.index()].type(); - } else { - return board_[sq.index()]; - } - } - - /** - * @brief Checks if a move is a capture, enpassant moves are also considered captures. - * @param move - * @return - */ - bool isCapture(const Move move) const noexcept { - return (at(move.to()) != Piece::NONE && move.typeOf() != Move::CASTLING) || move.typeOf() == Move::ENPASSANT; - } - - /** - * @brief Get the current zobrist hash key of the board - * @return - */ - [[nodiscard]] U64 hash() const noexcept { return key_; } - [[nodiscard]] Color sideToMove() const noexcept { return stm_; } - [[nodiscard]] Square enpassantSq() const noexcept { return ep_sq_; } - [[nodiscard]] CastlingRights castlingRights() const noexcept { return cr_; } - [[nodiscard]] std::uint32_t halfMoveClock() const noexcept { return hfm_; } - [[nodiscard]] std::uint32_t fullMoveNumber() const noexcept { return 1 + plies_ / 2; } - - void set960(bool is960) { - chess960_ = is960; - if (!original_fen_.empty()) setFen(original_fen_); - } - - /** - * @brief Checks if the current position is a chess960, aka. FRC/DFRC position. - * @return - */ - [[nodiscard]] bool chess960() const noexcept { return chess960_; } - - /** - * @brief Get the castling rights as a string - * @return - */ - [[nodiscard]] std::string getCastleString() const { - static const auto get_file = [](const CastlingRights &cr, Color c, CastlingRights::Side side) { - auto file = static_cast(cr.getRookFile(c, side)); - return c == Color::WHITE ? std::toupper(file[0]) : file[0]; - }; - - if (chess960_) { - std::string ss; - - for (auto color : {Color::WHITE, Color::BLACK}) - for (auto side : {CastlingRights::Side::KING_SIDE, CastlingRights::Side::QUEEN_SIDE}) - if (cr_.has(color, side)) ss += get_file(cr_, color, side); - - return ss; - } - - std::string ss; - - if (cr_.has(Color::WHITE, CastlingRights::Side::KING_SIDE)) ss += 'K'; - if (cr_.has(Color::WHITE, CastlingRights::Side::QUEEN_SIDE)) ss += 'Q'; - if (cr_.has(Color::BLACK, CastlingRights::Side::KING_SIDE)) ss += 'k'; - if (cr_.has(Color::BLACK, CastlingRights::Side::QUEEN_SIDE)) ss += 'q'; - - return ss; - } - - /** - * @brief Checks if the current position is a repetition, set this to 1 if - * you are writing a chess engine. - * @param count - * @return - */ - [[nodiscard]] bool isRepetition(int count = 2) const noexcept { - std::uint8_t c = 0; - - // We start the loop from the back and go forward in moves, at most to the - // last move which reset the half-move counter because repetitions cant - // be across half-moves. - const auto size = static_cast(prev_states_.size()); - - for (int i = size - 2; i >= 0 && i >= size - hfm_ - 1; i -= 2) { - if (prev_states_[i].hash == key_) c++; - if (c == count) return true; - } - - return false; - } - - /** - * @brief Checks if the current position is a draw by 50 move rule. - * Keep in mind that by the rules of chess, if the position has 50 half - * moves it's not necessarily a draw, since checkmate has higher priority, - * call getHalfMoveDrawType, - * to determine whether the position is a draw or checkmate. - * @return - */ - [[nodiscard]] bool isHalfMoveDraw() const noexcept { return hfm_ >= 100; } - - /** - * @brief Only call this function if isHalfMoveDraw() returns true. - * @return - */ - [[nodiscard]] std::pair getHalfMoveDrawType() const noexcept { - Movelist movelist; - movegen::legalmoves(movelist, *this); - - if (movelist.empty() && inCheck()) { - return {GameResultReason::CHECKMATE, GameResult::LOSE}; - } - - return {GameResultReason::FIFTY_MOVE_RULE, GameResult::DRAW}; - } - - /** - * @brief Basic check if the current position is a draw by insufficient material. - * @return - */ - [[nodiscard]] bool isInsufficientMaterial() const noexcept { - const auto count = occ().count(); - - // only kings, draw - if (count == 2) return true; - - // only bishop + knight, cant mate - if (count == 3) { - if (pieces(PieceType::BISHOP, Color::WHITE) || pieces(PieceType::BISHOP, Color::BLACK)) return true; - if (pieces(PieceType::KNIGHT, Color::WHITE) || pieces(PieceType::KNIGHT, Color::BLACK)) return true; - } - - // same colored bishops, cant mate - if (count == 4) { - if (pieces(PieceType::BISHOP, Color::WHITE) && pieces(PieceType::BISHOP, Color::BLACK) && - Square::same_color(pieces(PieceType::BISHOP, Color::WHITE).lsb(), - pieces(PieceType::BISHOP, Color::BLACK).lsb())) - return true; - - // one side with two bishops which have the same color - auto white_bishops = pieces(PieceType::BISHOP, Color::WHITE); - auto black_bishops = pieces(PieceType::BISHOP, Color::BLACK); - - if (white_bishops.count() == 2) { - if (Square::same_color(white_bishops.lsb(), white_bishops.msb())) return true; - } else if (black_bishops.count() == 2) { - if (Square::same_color(black_bishops.lsb(), black_bishops.msb())) return true; - } - } - - return false; - } - - /** - * @brief Checks if the game is over. Returns GameResultReason::NONE if the game is not over. - * This function calculates all legal moves for the current position to check if the game is over. - * If you are writing a chess engine you should not use this function. - * @return - */ - [[nodiscard]] std::pair isGameOver() const noexcept { - if (isHalfMoveDraw()) return getHalfMoveDrawType(); - if (isInsufficientMaterial()) return {GameResultReason::INSUFFICIENT_MATERIAL, GameResult::DRAW}; - if (isRepetition()) return {GameResultReason::THREEFOLD_REPETITION, GameResult::DRAW}; - - Movelist movelist; - movegen::legalmoves(movelist, *this); - - if (movelist.empty()) { - if (inCheck()) return {GameResultReason::CHECKMATE, GameResult::LOSE}; - return {GameResultReason::STALEMATE, GameResult::DRAW}; - } - - return {GameResultReason::NONE, GameResult::NONE}; - } - - /** - * @brief Checks if a square is attacked by the given color. - * @param square - * @param color - * @return - */ - [[nodiscard]] bool isAttacked(Square square, Color color) const noexcept { - // cheap checks first - if (attacks::pawn(~color, square) & pieces(PieceType::PAWN, color)) return true; - if (attacks::knight(square) & pieces(PieceType::KNIGHT, color)) return true; - if (attacks::king(square) & pieces(PieceType::KING, color)) return true; - if (attacks::bishop(square, occ()) & pieces(PieceType::BISHOP, PieceType::QUEEN) & us(color)) return true; - if (attacks::rook(square, occ()) & pieces(PieceType::ROOK, PieceType::QUEEN) & us(color)) return true; - - return false; - } - - /** - * @brief Checks if the current side to move is in check - * @return - */ - [[nodiscard]] bool inCheck() const noexcept { return isAttacked(kingSq(stm_), ~stm_); } - - [[nodiscard]] CheckType givesCheck(const Move &m) const noexcept; - - /** - * @brief Checks if the given color has at least 1 piece thats not pawn and not king - * @param color - * @return - */ - [[nodiscard]] bool hasNonPawnMaterial(Color color) const noexcept { - return bool(us(color) ^ (pieces(PieceType::PAWN, PieceType::KING) & us(color))); - } - - /** - * @brief Calculates the zobrist hash key of the board, expensive! Prefer using hash(). - * @return - */ - [[nodiscard]] U64 zobrist() const { - U64 hash_key = 0ULL; - - auto pieces = occ(); - - while (pieces) { - const Square sq = pieces.pop(); - hash_key ^= Zobrist::piece(at(sq), sq); - } - - U64 ep_hash = 0ULL; - if (ep_sq_ != Square::NO_SQ) ep_hash ^= Zobrist::enpassant(ep_sq_.file()); - - U64 stm_hash = 0ULL; - if (stm_ == Color::WHITE) stm_hash ^= Zobrist::sideToMove(); - - U64 castling_hash = 0ULL; - castling_hash ^= Zobrist::castling(cr_.hashIndex()); - - return hash_key ^ ep_hash ^ stm_hash ^ castling_hash; - } - - [[nodiscard]] Bitboard getCastlingPath(Color c, bool isKingSide) const noexcept { - return castling_path[c][isKingSide]; - } - - friend std::ostream &operator<<(std::ostream &os, const Board &board); - - /** - * @brief Compresses the board into a PackedBoard. - */ - class Compact { - friend class Board; - Compact() = default; - - public: - /** - * @brief Compresses the board into a PackedBoard - * @param board - * @return - */ - static PackedBoard encode(const Board &board) { return encodeState(board); } - - static PackedBoard encode(std::string_view fen, bool chess960 = false) { return encodeState(fen, chess960); } - - /** - * @brief Creates a Board object from a PackedBoard - * @param compressed - * @param chess960 If the board is a chess960 position, set this to true - * @return - */ - static Board decode(const PackedBoard &compressed, bool chess960 = false) { - Board board = Board(PrivateCtor::CREATE); - board.chess960_ = chess960; - decode(board, compressed); - return board; - } - - private: - /** - * A compact board representation can be achieved in 24 bytes, - * we use 8 bytes (64bit) to store the occupancy bitboard, - * and 16 bytes (128bit) to store the pieces (plus some special information). - * - * Each of the 16 bytes can store 2 pieces, since chess only has 12 different pieces, - * we can represent the pieces from 0 to 11 in 4 bits (a nibble) and use the other 4 bit for - * the next piece. - * Since we need to store information about enpassant, castling rights and the side to move, - * we can use the remaining 4 bits to store this information. - * - * However we need to store the information and the piece information together. - * This means in our case that - * 12 -> enpassant + a pawn, we can deduce the color of the pawn from the rank of the square - * 13 -> white rook with castling rights, we later use the file to deduce if it's a short or long castle - * 14 -> black rook with castling rights, we later use the file to deduce if it's a short or long castle - * 15 -> black king and black is side to move - * - * We will later deduce the square of the pieces from the occupancy bitboard. - */ - static PackedBoard encodeState(const Board &board) { - PackedBoard packed{}; - - packed[0] = board.occ().getBits() >> 56; - packed[1] = (board.occ().getBits() >> 48) & 0xFF; - packed[2] = (board.occ().getBits() >> 40) & 0xFF; - packed[3] = (board.occ().getBits() >> 32) & 0xFF; - packed[4] = (board.occ().getBits() >> 24) & 0xFF; - packed[5] = (board.occ().getBits() >> 16) & 0xFF; - packed[6] = (board.occ().getBits() >> 8) & 0xFF; - packed[7] = board.occ().getBits() & 0xFF; - - auto offset = 8 * 2; - auto occ = board.occ(); - - while (occ) { - // we now fill the packed array, since our convertedpiece only actually needs 4 bits, - // we can store 2 pieces in one byte. - const auto sq = Square(occ.pop()); - const auto shift = (offset % 2 == 0 ? 4 : 0); - const auto meaning = convertMeaning(board.cr_, board.sideToMove(), board.ep_sq_, sq, board.at(sq)); - const auto nibble = meaning << shift; - - packed[offset / 2] |= nibble; - offset++; - } - - return packed; - } - - static PackedBoard encodeState(std::string_view fen, bool chess960 = false) { - // fallback to slower method - if (chess960) { - return encodeState(Board(fen, true)); - } - - PackedBoard packed{}; - - while (fen[0] == ' ') fen.remove_prefix(1); - - const auto params = split_string_view<6>(fen); - const auto position = params[0].has_value() ? *params[0] : ""; - const auto move_right = params[1].has_value() ? *params[1] : "w"; - const auto castling = params[2].has_value() ? *params[2] : "-"; - const auto en_passant = params[3].has_value() ? *params[3] : "-"; - - const auto ep = en_passant == "-" ? Square::NO_SQ : Square(en_passant); - const auto stm = (move_right == "w") ? Color::WHITE : Color::BLACK; - - CastlingRights cr; - - for (char i : castling) { - if (i == '-') break; - - const auto king_side = CastlingRights::Side::KING_SIDE; - const auto queen_side = CastlingRights::Side::QUEEN_SIDE; - - if (i == 'K') cr.setCastlingRight(Color::WHITE, king_side, File::FILE_H); - if (i == 'Q') cr.setCastlingRight(Color::WHITE, queen_side, File::FILE_A); - if (i == 'k') cr.setCastlingRight(Color::BLACK, king_side, File::FILE_H); - if (i == 'q') cr.setCastlingRight(Color::BLACK, queen_side, File::FILE_A); - - assert(i == 'K' || i == 'Q' || i == 'k' || i == 'q'); - - continue; - } - - const auto parts = split_string_view<8>(position, '/'); - - int offset = 8 * 2; - int square = 0; - Bitboard occ = 0ull; - - for (auto i = parts.rbegin(); i != parts.rend(); i++) { - auto part = *i; - - for (char curr : *part) { - if (isdigit(curr)) { - square += (curr - '0'); - } else if (curr == '/') { - square++; - } else { - const auto p = Piece(std::string_view(&curr, 1)); - const auto shift = (offset % 2 == 0 ? 4 : 0); - - packed[offset / 2] |= convertMeaning(cr, stm, ep, Square(square), p) << shift; - offset++; - - occ.set(square); - ++square; - } - } - } - - packed[0] = occ.getBits() >> 56; - packed[1] = (occ.getBits() >> 48) & 0xFF; - packed[2] = (occ.getBits() >> 40) & 0xFF; - packed[3] = (occ.getBits() >> 32) & 0xFF; - packed[4] = (occ.getBits() >> 24) & 0xFF; - packed[5] = (occ.getBits() >> 16) & 0xFF; - packed[6] = (occ.getBits() >> 8) & 0xFF; - packed[7] = occ.getBits() & 0xFF; - - return packed; - } - - static void decode(Board &board, const PackedBoard &compressed) { - Bitboard occupied = 0ull; - - for (int i = 0; i < 8; i++) { - occupied |= Bitboard(compressed[i]) << (56 - i * 8); - } - - int offset = 16; - int white_castle_idx = 0, black_castle_idx = 0; - File white_castle[2] = {File::NO_FILE, File::NO_FILE}; - File black_castle[2] = {File::NO_FILE, File::NO_FILE}; - - // clear board state - - board.hfm_ = 0; - board.plies_ = 0; - - board.stm_ = Color::WHITE; - - board.cr_.clear(); - board.prev_states_.clear(); - board.original_fen_.clear(); - - board.occ_bb_.fill(0ULL); - board.pieces_bb_.fill(0ULL); - board.board_.fill(Piece::NONE); - - // place pieces back on the board - while (occupied) { - const auto sq = Square(occupied.pop()); - const auto nibble = compressed[offset / 2] >> (offset % 2 == 0 ? 4 : 0) & 0b1111; - const auto piece = convertPiece(nibble); - - if (piece != Piece::NONE) { - board.placePiece(piece, sq); - - offset++; - continue; - } - - // Piece has a special meaning, interpret it from the raw integer - // pawn with ep square behind it - if (nibble == 12) { - board.ep_sq_ = sq.ep_square(); - // depending on the rank this is a white or black pawn - auto color = sq.rank() == Rank::RANK_4 ? Color::WHITE : Color::BLACK; - board.placePiece(Piece(PieceType::PAWN, color), sq); - } - // castling rights for white - else if (nibble == 13) { - assert(white_castle_idx < 2); - white_castle[white_castle_idx++] = sq.file(); - board.placePiece(Piece(PieceType::ROOK, Color::WHITE), sq); - } - // castling rights for black - else if (nibble == 14) { - assert(black_castle_idx < 2); - black_castle[black_castle_idx++] = sq.file(); - board.placePiece(Piece(PieceType::ROOK, Color::BLACK), sq); - } - // black to move - else if (nibble == 15) { - board.stm_ = Color::BLACK; - board.placePiece(Piece(PieceType::KING, Color::BLACK), sq); - } - - offset++; - } - - // reapply castling - for (int i = 0; i < 2; i++) { - if (white_castle[i] != File::NO_FILE) { - const auto king_sq = board.kingSq(Color::WHITE); - const auto file = white_castle[i]; - const auto side = CastlingRights::closestSide(file, king_sq.file()); - - board.cr_.setCastlingRight(Color::WHITE, side, file); - } - - if (black_castle[i] != File::NO_FILE) { - const auto king_sq = board.kingSq(Color::BLACK); - const auto file = black_castle[i]; - const auto side = CastlingRights::closestSide(file, king_sq.file()); - - board.cr_.setCastlingRight(Color::BLACK, side, file); - } - } - - if (board.stm_ == Color::BLACK) { - board.plies_++; - } - - board.key_ = board.zobrist(); - } - - // 1:1 mapping of Piece::internal() to the compressed piece - static std::uint8_t convertPiece(Piece piece) { return static_cast(piece.internal()); } - - // for pieces with a special meaning return Piece::NONE since this is otherwise not used - static Piece convertPiece(std::uint8_t piece) { - if (piece >= 12) return Piece::NONE; - return Piece(Piece::underlying(piece)); - } - - // 12 => theres an ep square behind the pawn, rank will be deduced from the rank - // 13 => any white rook with castling rights, side will be deduced from the file - // 14 => any black rook with castling rights, side will be deduced from the file - // 15 => black king and black is side to move - static std::uint8_t convertMeaning(const CastlingRights &cr, Color stm, Square ep, Square sq, Piece piece) { - if (piece.type() == PieceType::PAWN && ep != Square::NO_SQ) { - if (Square(static_cast(sq.index()) ^ 8) == ep) return 12; - } - - if (piece.type() == PieceType::ROOK) { - if (piece.color() == Color::WHITE && Square::back_rank(sq, Color::WHITE) && - (cr.getRookFile(Color::WHITE, CastlingRights::Side::KING_SIDE) == sq.file() || - cr.getRookFile(Color::WHITE, CastlingRights::Side::QUEEN_SIDE) == sq.file())) - return 13; - if (piece.color() == Color::BLACK && Square::back_rank(sq, Color::BLACK) && - (cr.getRookFile(Color::BLACK, CastlingRights::Side::KING_SIDE) == sq.file() || - cr.getRookFile(Color::BLACK, CastlingRights::Side::QUEEN_SIDE) == sq.file())) - return 14; - } - - if (piece.type() == PieceType::KING && piece.color() == Color::BLACK && stm == Color::BLACK) { - return 15; - } - - return convertPiece(piece); - } - }; - - protected: - virtual void placePiece(Piece piece, Square sq) { placePieceInternal(piece, sq); } - - virtual void removePiece(Piece piece, Square sq) { removePieceInternal(piece, sq); } - - std::vector prev_states_; - - std::array pieces_bb_ = {}; - std::array occ_bb_ = {}; - std::array board_ = {}; - - U64 key_ = 0ULL; - CastlingRights cr_ = {}; - std::uint16_t plies_ = 0; - Color stm_ = Color::WHITE; - Square ep_sq_ = Square::NO_SQ; - std::uint8_t hfm_ = 0; - - bool chess960_ = false; - - std::array, 2> castling_path = {}; - - private: - void removePieceInternal(Piece piece, Square sq) { - assert(board_[sq.index()] == piece && piece != Piece::NONE); - - auto type = piece.type(); - auto color = piece.color(); - auto index = sq.index(); - - assert(type != PieceType::NONE); - assert(color != Color::NONE); - assert(index >= 0 && index < 64); - - pieces_bb_[type].clear(index); - occ_bb_[color].clear(index); - board_[index] = Piece::NONE; - } - - void placePieceInternal(Piece piece, Square sq) { - assert(board_[sq.index()] == Piece::NONE); - - auto type = piece.type(); - auto color = piece.color(); - auto index = sq.index(); - - assert(type != PieceType::NONE); - assert(color != Color::NONE); - assert(index >= 0 && index < 64); - - pieces_bb_[type].set(index); - occ_bb_[color].set(index); - board_[index] = piece; - } - - template - bool setFenInternal(std::string_view fen) { - original_fen_ = fen; - - reset(); - - while (!fen.empty() && fen[0] == ' ') fen.remove_prefix(1); - - if (fen.empty()) return false; - - const auto params = split_string_view<6>(fen); - const auto position = params[0].has_value() ? *params[0] : ""; - const auto move_right = params[1].has_value() ? *params[1] : "w"; - const auto castling = params[2].has_value() ? *params[2] : "-"; - const auto en_passant = params[3].has_value() ? *params[3] : "-"; - const auto half_move = params[4].has_value() ? *params[4] : "0"; - const auto full_move = params[5].has_value() ? *params[5] : "1"; - - if (position.empty()) return false; - - if (move_right != "w" && move_right != "b") return false; - - const auto half_move_opt = detail::parseStringViewToInt(half_move).value_or(0); - hfm_ = half_move_opt; - - const auto full_move_opt = detail::parseStringViewToInt(full_move).value_or(1); - plies_ = full_move_opt; - - plies_ = plies_ * 2 - 2; - - if (en_passant != "-") { - if (!Square::is_valid_string_sq(en_passant)) { - return false; - } - - ep_sq_ = Square(en_passant); - if (ep_sq_ == Square::NO_SQ) return false; - } - - stm_ = (move_right == "w") ? Color::WHITE : Color::BLACK; - - if (stm_ == Color::BLACK) { - plies_++; - } else { - key_ ^= Zobrist::sideToMove(); - } - - auto square = 56; - for (char curr : position) { - if (isdigit(curr)) { - square += (curr - '0'); - } else if (curr == '/') { - square -= 16; - } else { - auto p = Piece(std::string_view(&curr, 1)); - if (p == Piece::NONE || !Square::is_valid_sq(square) || at(square) != Piece::NONE) return false; - - if constexpr (ctor) { - placePieceInternal(p, Square(square)); - } else { - placePiece(p, square); - } - - key_ ^= Zobrist::piece(p, Square(square)); - ++square; - } - } - - static const auto find_rook = [](const Board &board, CastlingRights::Side side, Color color) -> File { - const auto king_side = CastlingRights::Side::KING_SIDE; - const auto king_sq = board.kingSq(color); - const auto sq_corner = Square(side == king_side ? Square::SQ_H1 : Square::SQ_A1).relative_square(color); - - const auto start = side == king_side ? king_sq + 1 : king_sq - 1; - - for (Square sq = start; (side == king_side ? sq <= sq_corner : sq >= sq_corner); - (side == king_side ? sq++ : sq--)) { - if (board.at(sq) == PieceType::ROOK && board.at(sq).color() == color) { - return sq.file(); - } - } - - return File(File::NO_FILE); - }; - - // Parse castling rights - for (char i : castling) { - if (i == '-') break; - - const auto king_side = CastlingRights::Side::KING_SIDE; - const auto queen_side = CastlingRights::Side::QUEEN_SIDE; - - if (!chess960_) { - if (i == 'K') - cr_.setCastlingRight(Color::WHITE, king_side, File::FILE_H); - else if (i == 'Q') - cr_.setCastlingRight(Color::WHITE, queen_side, File::FILE_A); - else if (i == 'k') - cr_.setCastlingRight(Color::BLACK, king_side, File::FILE_H); - else if (i == 'q') - cr_.setCastlingRight(Color::BLACK, queen_side, File::FILE_A); - else - return false; - - continue; - } - - // chess960 castling detection - const auto color = isupper(i) ? Color::WHITE : Color::BLACK; - const auto king_sq = kingSq(color); - - if (i == 'K' || i == 'k') { - auto file = find_rook(*this, king_side, color); - if (file == File::NO_FILE) return false; - cr_.setCastlingRight(color, king_side, file); - } else if (i == 'Q' || i == 'q') { - auto file = find_rook(*this, queen_side, color); - if (file == File::NO_FILE) return false; - cr_.setCastlingRight(color, queen_side, file); - } else { - const auto file = File(std::string_view(&i, 1)); - if (file == File::NO_FILE) return false; - const auto side = CastlingRights::closestSide(file, king_sq.file()); - cr_.setCastlingRight(color, side, file); - } - } - - if (ep_sq_ != Square::NO_SQ && !((ep_sq_.rank() == Rank::RANK_3 && stm_ == Color::BLACK) || - (ep_sq_.rank() == Rank::RANK_6 && stm_ == Color::WHITE))) { - ep_sq_ = Square::NO_SQ; - } - - if (ep_sq_ != Square::NO_SQ) { - bool valid; - - if (stm_ == Color::WHITE) { - valid = movegen::isEpSquareValid(*this, ep_sq_); - } else { - valid = movegen::isEpSquareValid(*this, ep_sq_); - } - - if (!valid) - ep_sq_ = Square::NO_SQ; - else - key_ ^= Zobrist::enpassant(ep_sq_.file()); - } - - key_ ^= Zobrist::castling(cr_.hashIndex()); - - assert(key_ == zobrist()); - - // init castling_path - for (Color c : {Color::WHITE, Color::BLACK}) { - const auto king_from = kingSq(c); - - for (const auto side : {CastlingRights::Side::KING_SIDE, CastlingRights::Side::QUEEN_SIDE}) { - if (!cr_.has(c, side)) continue; - - const auto rook_from = Square(cr_.getRookFile(c, side), king_from.rank()); - const auto king_to = Square::castling_king_square(side == CastlingRights::Side::KING_SIDE, c); - const auto rook_to = Square::castling_rook_square(side == CastlingRights::Side::KING_SIDE, c); - - castling_path[c][side == CastlingRights::Side::KING_SIDE] = - (movegen::between(rook_from, rook_to) | movegen::between(king_from, king_to)) & - ~(Bitboard::fromSquare(king_from) | Bitboard::fromSquare(rook_from)); - } - } - - return true; - } - - template - std::array, N> static split_string_view(std::string_view fen, - char delimiter = ' ') { - std::array, N> arr = {}; - - std::size_t start = 0; - std::size_t end = 0; - - for (std::size_t i = 0; i < N; i++) { - end = fen.find(delimiter, start); - if (end == std::string::npos) { - arr[i] = fen.substr(start); - break; - } - arr[i] = fen.substr(start, end - start); - start = end + 1; - } - - return arr; - } - - void reset() { - occ_bb_.fill(0ULL); - pieces_bb_.fill(0ULL); - board_.fill(Piece::NONE); - - stm_ = Color::WHITE; - ep_sq_ = Square::NO_SQ; - hfm_ = 0; - plies_ = 1; - key_ = 0ULL; - cr_.clear(); - prev_states_.clear(); - } - - // store the original fen string - // useful when setting up a frc position and the user called set960(true) afterwards - std::string original_fen_; -}; - -inline std::ostream &operator<<(std::ostream &os, const Board &b) { - for (int i = 63; i >= 0; i -= 8) { - for (int j = 7; j >= 0; j--) { - os << " " << static_cast(b.board_[i - j]); - } - - os << "\n"; - } - - os << "\n\n"; - os << "Side to move: " << static_cast(b.stm_.internal()) << "\n"; - os << "Castling rights: " << b.getCastleString() << "\n"; - os << "Halfmoves: " << b.halfMoveClock() << "\n"; - os << "Fullmoves: " << b.fullMoveNumber() << "\n"; - os << "EP: " << b.ep_sq_.index() << "\n"; - os << "Hash: " << b.key_ << "\n"; - - os << std::endl; - - return os; -} - -inline CheckType Board::givesCheck(const Move &m) const noexcept { - const static auto getSniper = [](const Board *board, Square ksq, Bitboard oc) { - const auto us_occ = board->us(board->sideToMove()); - const auto bishop = attacks::bishop(ksq, oc) & board->pieces(PieceType::BISHOP, PieceType::QUEEN) & us_occ; - const auto rook = attacks::rook(ksq, oc) & board->pieces(PieceType::ROOK, PieceType::QUEEN) & us_occ; - return (bishop | rook); - }; - - assert(at(m.from()).color() == stm_); - - const Square from = m.from(); - const Square to = m.to(); - const Square ksq = kingSq(~stm_); - const Bitboard toBB = Bitboard::fromSquare(to); - const PieceType pt = at(from).type(); - - Bitboard fromKing = 0ull; - - if (pt == PieceType::PAWN) { - fromKing = attacks::pawn(~stm_, ksq); - } else if (pt == PieceType::KNIGHT) { - fromKing = attacks::knight(ksq); - } else if (pt == PieceType::BISHOP) { - fromKing = attacks::bishop(ksq, occ()); - } else if (pt == PieceType::ROOK) { - fromKing = attacks::rook(ksq, occ()); - } else if (pt == PieceType::QUEEN) { - fromKing = attacks::queen(ksq, occ()); - } - - if (fromKing & toBB) return CheckType::DIRECT_CHECK; - - // Discovery check - const Bitboard fromBB = Bitboard::fromSquare(from); - const Bitboard oc = occ() ^ fromBB; - - Bitboard sniper = getSniper(this, ksq, oc); - - while (sniper) { - Square sq = sniper.pop(); - return (!(movegen::between(ksq, sq) & toBB) || m.typeOf() == Move::CASTLING) ? CheckType::DISCOVERY_CHECK - : CheckType::NO_CHECK; - } - - switch (m.typeOf()) { - case Move::NORMAL: - return CheckType::NO_CHECK; - - case Move::PROMOTION: { - Bitboard attacks = 0ull; - - switch (m.promotionType()) { - case static_cast(PieceType::KNIGHT): - attacks = attacks::knight(to); - break; - case static_cast(PieceType::BISHOP): - attacks = attacks::bishop(to, oc); - break; - case static_cast(PieceType::ROOK): - attacks = attacks::rook(to, oc); - break; - case static_cast(PieceType::QUEEN): - attacks = attacks::queen(to, oc); - } - - return (attacks & pieces(PieceType::KING, ~stm_)) ? CheckType::DIRECT_CHECK : CheckType::NO_CHECK; - } - - case Move::ENPASSANT: { - Square capSq(to.file(), from.rank()); - return (getSniper(this, ksq, (oc ^ Bitboard::fromSquare(capSq)) | toBB)) ? CheckType::DISCOVERY_CHECK - : CheckType::NO_CHECK; - } - - case Move::CASTLING: { - Square rookTo = Square::castling_rook_square(to > from, stm_); - return (attacks::rook(ksq, occ()) & Bitboard::fromSquare(rookTo)) ? CheckType::DISCOVERY_CHECK - : CheckType::NO_CHECK; - } - } - - assert(false); - return CheckType::NO_CHECK; // Prevent a compiler warning -} - -} // namespace chess - -namespace chess { - -template -[[nodiscard]] inline constexpr Bitboard attacks::shift(const Bitboard b) { - switch (direction) { - case Direction::NORTH: - return b << 8; - case Direction::SOUTH: - return b >> 8; - case Direction::NORTH_WEST: - return (b & ~MASK_FILE[0]) << 7; - case Direction::WEST: - return (b & ~MASK_FILE[0]) >> 1; - case Direction::SOUTH_WEST: - return (b & ~MASK_FILE[0]) >> 9; - case Direction::NORTH_EAST: - return (b & ~MASK_FILE[7]) << 9; - case Direction::EAST: - return (b & ~MASK_FILE[7]) << 1; - case Direction::SOUTH_EAST: - return (b & ~MASK_FILE[7]) >> 7; - } - - assert(false); - - return {}; -} - -template -[[nodiscard]] inline Bitboard attacks::pawnLeftAttacks(const Bitboard pawns) { - return c == Color::WHITE ? (pawns << 7) & ~MASK_FILE[static_cast(File::FILE_H)] - : (pawns >> 7) & ~MASK_FILE[static_cast(File::FILE_A)]; -} - -template -[[nodiscard]] inline Bitboard attacks::pawnRightAttacks(const Bitboard pawns) { - return c == Color::WHITE ? (pawns << 9) & ~MASK_FILE[static_cast(File::FILE_A)] - : (pawns >> 9) & ~MASK_FILE[static_cast(File::FILE_H)]; -} - -[[nodiscard]] inline Bitboard attacks::pawn(Color c, Square sq) noexcept { return PawnAttacks[c][sq.index()]; } - -[[nodiscard]] inline Bitboard attacks::knight(Square sq) noexcept { return KnightAttacks[sq.index()]; } - -[[nodiscard]] inline Bitboard attacks::bishop(Square sq, Bitboard occupied) noexcept { - return BishopTable[sq.index()].attacks[BishopTable[sq.index()](occupied)]; -} - -[[nodiscard]] inline Bitboard attacks::rook(Square sq, Bitboard occupied) noexcept { - return RookTable[sq.index()].attacks[RookTable[sq.index()](occupied)]; -} - -[[nodiscard]] inline Bitboard attacks::queen(Square sq, Bitboard occupied) noexcept { - return bishop(sq, occupied) | rook(sq, occupied); -} - -[[nodiscard]] inline Bitboard attacks::king(Square sq) noexcept { return KingAttacks[sq.index()]; } - -[[nodiscard]] inline Bitboard attacks::attackers(const Board &board, Color color, Square square) noexcept { - const auto queens = board.pieces(PieceType::QUEEN, color); - const auto occupied = board.occ(); - - // using the fact that if we can attack PieceType from square, they can attack us back - auto atks = (pawn(~color, square) & board.pieces(PieceType::PAWN, color)); - atks |= (knight(square) & board.pieces(PieceType::KNIGHT, color)); - atks |= (bishop(square, occupied) & (board.pieces(PieceType::BISHOP, color) | queens)); - atks |= (rook(square, occupied) & (board.pieces(PieceType::ROOK, color) | queens)); - atks |= (king(square) & board.pieces(PieceType::KING, color)); - - return atks & occupied; -} - -template -[[nodiscard]] inline Bitboard attacks::slider(Square sq, Bitboard occupied) noexcept { - static_assert(pt == PieceType::BISHOP || pt == PieceType::ROOK || pt == PieceType::QUEEN, - "PieceType must be a slider!"); - - if constexpr (pt == PieceType::BISHOP) return bishop(sq, occupied); - if constexpr (pt == PieceType::ROOK) return rook(sq, occupied); - if constexpr (pt == PieceType::QUEEN) return queen(sq, occupied); -} - -template -[[nodiscard]] inline Bitboard attacks::sliderAttacks(Square sq, Bitboard occupied) noexcept { - static constexpr int dirs[2][4][2] = {{1, 1, 1, -1, -1, -1, -1, 1}, {1, 0, 0, -1, -1, 0, 0, 1}}; - - Bitboard attacks = 0ull; - - File pf = sq.file(); - Rank pr = sq.rank(); - - for (int i = 0; i < 4; ++i) { - int off_f = dirs[ISROOK][i][0]; - int off_r = dirs[ISROOK][i][1]; - - File f; - Rank r; - for (f = pf + off_f, r = pr + off_r; Square::is_valid(r, f); f += off_f, r += off_r) { - const auto index = Square(f, r).index(); - attacks.set(index); - if (occupied.check(index)) break; - } - } - - return attacks; -} - -inline void attacks::initSliders(Square sq, Magic table[], U64 magic, - const std::function &attacks) { - // The edges of the board are not considered for the attacks - // i.e. for the sq h7 edges will be a1-h1, a1-a8, a8-h8, ignoring the edge of the current square - const Bitboard edges = ((Bitboard(Rank::RANK_1) | Bitboard(Rank::RANK_8)) & ~Bitboard(sq.rank())) | - ((Bitboard(File::FILE_A) | Bitboard(File::FILE_H)) & ~Bitboard(sq.file())); - - U64 occ = 0ULL; - - auto &table_sq = table[sq.index()]; - -#ifndef CHESS_USE_PEXT - table_sq.magic = magic; -#endif - table_sq.mask = (attacks(sq, occ) & ~edges).getBits(); -#ifndef CHESS_USE_PEXT - table_sq.shift = 64 - Bitboard(table_sq.mask).count(); -#endif - - if (sq < 64 - 1) { - table[sq.index() + 1].attacks = table_sq.attacks + (1ull << Bitboard(table_sq.mask).count()); - } - - do { - table_sq.attacks[table_sq(occ)] = attacks(sq, occ); - occ = (occ - table_sq.mask) & table_sq.mask; - } while (occ); -} - -inline void attacks::initAttacks() { - BishopTable[0].attacks = BishopAttacks; - RookTable[0].attacks = RookAttacks; - - for (int i = 0; i < 64; i++) { - initSliders(static_cast(i), BishopTable, BishopMagics[i], sliderAttacks); - initSliders(static_cast(i), RookTable, RookMagics[i], sliderAttacks); - } -} -} // namespace chess - - - -namespace chess { - -inline auto movegen::init_squares_between() { - std::array, 64> squares_between_bb{}; - - auto att = [](PieceType pt, Square sq, Bitboard occ) { - return (pt == PieceType::BISHOP) ? attacks::bishop(sq, occ) : attacks::rook(sq, occ); - }; - - for (int sq1 = 0; sq1 < 64; ++sq1) { - for (PieceType pt : {PieceType::BISHOP, PieceType::ROOK}) { - for (int sq2 = 0; sq2 < 64; ++sq2) { - if (att(pt, sq1, 0).check(sq2)) { - squares_between_bb[sq1][sq2] = - att(pt, sq1, Bitboard::fromSquare(sq2)) & att(pt, sq2, Bitboard::fromSquare(sq1)); - } - squares_between_bb[sq1][sq2].set(sq2); - } - } - } - - return squares_between_bb; -} - -template -[[nodiscard]] inline std::pair movegen::checkMask(const Board &board, Square sq) { - const auto opp_knight = board.pieces(PieceType::KNIGHT, ~c); - const auto opp_bishop = board.pieces(PieceType::BISHOP, ~c); - const auto opp_rook = board.pieces(PieceType::ROOK, ~c); - const auto opp_queen = board.pieces(PieceType::QUEEN, ~c); - - const auto opp_pawns = board.pieces(PieceType::PAWN, ~c); - - int checks = 0; - - // check for knight checks - Bitboard knight_attacks = attacks::knight(sq) & opp_knight; - checks += bool(knight_attacks); - - Bitboard mask = knight_attacks; - - // check for pawn checks - Bitboard pawn_attacks = attacks::pawn(board.sideToMove(), sq) & opp_pawns; - mask |= pawn_attacks; - checks += bool(pawn_attacks); - - // check for bishop checks - Bitboard bishop_attacks = attacks::bishop(sq, board.occ()) & (opp_bishop | opp_queen); - - if (bishop_attacks) { - mask |= between(sq, bishop_attacks.lsb()); - checks++; - } - - Bitboard rook_attacks = attacks::rook(sq, board.occ()) & (opp_rook | opp_queen); - - if (rook_attacks) { - if (rook_attacks.count() > 1) { - checks = 2; - return {mask, checks}; - } - - mask |= between(sq, rook_attacks.lsb()); - checks++; - } - - if (!mask) { - return {constants::DEFAULT_CHECKMASK, checks}; - } - - return {mask, checks}; -} - -template -[[nodiscard]] inline Bitboard movegen::pinMask(const Board &board, Square sq, Bitboard occ_opp, - Bitboard occ_us) noexcept { - static_assert(pt == PieceType::BISHOP || pt == PieceType::ROOK, "Only bishop or rook allowed!"); - - const auto opp_pt_queen = board.pieces(pt, PieceType::QUEEN) & board.us(~c); - - auto pt_attacks = attacks::slider(sq, occ_opp) & opp_pt_queen; - - Bitboard pin = 0ull; - - while (pt_attacks) { - const auto possible_pin = between(sq, pt_attacks.pop()); - if ((possible_pin & occ_us).count() == 1) pin |= possible_pin; - } - - return pin; -} - -template -[[nodiscard]] inline Bitboard movegen::seenSquares(const Board &board, Bitboard enemy_empty) { - auto king_sq = board.kingSq(~c); - Bitboard map_king_atk = attacks::king(king_sq) & enemy_empty; - - if (map_king_atk == Bitboard(0ull) && !board.chess960()) return 0ull; - - auto occ = board.occ() ^ Bitboard::fromSquare(king_sq); - auto queens = board.pieces(PieceType::QUEEN, c); - auto pawns = board.pieces(PieceType::PAWN, c); - auto knights = board.pieces(PieceType::KNIGHT, c); - auto bishops = board.pieces(PieceType::BISHOP, c) | queens; - auto rooks = board.pieces(PieceType::ROOK, c) | queens; - - Bitboard seen = attacks::pawnLeftAttacks(pawns) | attacks::pawnRightAttacks(pawns); - - while (knights) { - seen |= attacks::knight(knights.pop()); - } - - while (bishops) { - seen |= attacks::bishop(bishops.pop(), occ); - } - - while (rooks) { - seen |= attacks::rook(rooks.pop(), occ); - } - - seen |= attacks::king(board.kingSq(c)); - - return seen; -} - -template -inline void movegen::generatePawnMoves(const Board &board, Movelist &moves, Bitboard pin_d, Bitboard pin_hv, - Bitboard checkmask, Bitboard occ_opp) { - // flipped for black - - constexpr auto UP = make_direction(Direction::NORTH, c); - constexpr auto DOWN = make_direction(Direction::SOUTH, c); - constexpr auto DOWN_LEFT = make_direction(Direction::SOUTH_WEST, c); - constexpr auto DOWN_RIGHT = make_direction(Direction::SOUTH_EAST, c); - constexpr auto UP_LEFT = make_direction(Direction::NORTH_WEST, c); - constexpr auto UP_RIGHT = make_direction(Direction::NORTH_EAST, c); - - constexpr auto RANK_B_PROMO = Rank::rank(Rank::RANK_7, c).bb(); - constexpr auto RANK_PROMO = Rank::rank(Rank::RANK_8, c).bb(); - constexpr auto DOUBLE_PUSH_RANK = Rank::rank(Rank::RANK_3, c).bb(); - - const auto pawns = board.pieces(PieceType::PAWN, c); - - // These pawns can maybe take Left or Right - const Bitboard pawns_lr = pawns & ~pin_hv; - const Bitboard unpinned_pawns_lr = pawns_lr & ~pin_d; - const Bitboard pinned_pawns_lr = pawns_lr & pin_d; - - auto l_pawns = attacks::shift(unpinned_pawns_lr) | (attacks::shift(pinned_pawns_lr) & pin_d); - auto r_pawns = attacks::shift(unpinned_pawns_lr) | (attacks::shift(pinned_pawns_lr) & pin_d); - - // Prune moves that don't capture a piece and are not on the checkmask. - l_pawns &= occ_opp & checkmask; - r_pawns &= occ_opp & checkmask; - - // These pawns can walk Forward - const auto pawns_hv = pawns & ~pin_d; - - const auto pawns_pinned_hv = pawns_hv & pin_hv; - const auto pawns_unpinned_hv = pawns_hv & ~pin_hv; - - // Prune moves that are blocked by a piece - const auto single_push_unpinned = attacks::shift(pawns_unpinned_hv) & ~board.occ(); - const auto single_push_pinned = attacks::shift(pawns_pinned_hv) & pin_hv & ~board.occ(); - - // Prune moves that are not on the checkmask. - Bitboard single_push = (single_push_unpinned | single_push_pinned) & checkmask; - - Bitboard double_push = ((attacks::shift(single_push_unpinned & DOUBLE_PUSH_RANK) & ~board.occ()) | - (attacks::shift(single_push_pinned & DOUBLE_PUSH_RANK) & ~board.occ())) & - checkmask; - - if (pawns & RANK_B_PROMO) { - Bitboard promo_left = l_pawns & RANK_PROMO; - Bitboard promo_right = r_pawns & RANK_PROMO; - Bitboard promo_push = single_push & RANK_PROMO; - - // Skip capturing promotions if we are only generating quiet moves. - // Generates at ALL and CAPTURE - while (mt != MoveGenType::QUIET && promo_left) { - const auto index = promo_left.pop(); - moves.add(Move::make(index + DOWN_RIGHT, index, PieceType::QUEEN)); - moves.add(Move::make(index + DOWN_RIGHT, index, PieceType::ROOK)); - moves.add(Move::make(index + DOWN_RIGHT, index, PieceType::BISHOP)); - moves.add(Move::make(index + DOWN_RIGHT, index, PieceType::KNIGHT)); - } - - // Skip capturing promotions if we are only generating quiet moves. - // Generates at ALL and CAPTURE - while (mt != MoveGenType::QUIET && promo_right) { - const auto index = promo_right.pop(); - moves.add(Move::make(index + DOWN_LEFT, index, PieceType::QUEEN)); - moves.add(Move::make(index + DOWN_LEFT, index, PieceType::ROOK)); - moves.add(Move::make(index + DOWN_LEFT, index, PieceType::BISHOP)); - moves.add(Move::make(index + DOWN_LEFT, index, PieceType::KNIGHT)); - } - - // Skip quiet promotions if we are only generating captures. - // Generates at ALL and QUIET - while (mt != MoveGenType::CAPTURE && promo_push) { - const auto index = promo_push.pop(); - moves.add(Move::make(index + DOWN, index, PieceType::QUEEN)); - moves.add(Move::make(index + DOWN, index, PieceType::ROOK)); - moves.add(Move::make(index + DOWN, index, PieceType::BISHOP)); - moves.add(Move::make(index + DOWN, index, PieceType::KNIGHT)); - } - } - - single_push &= ~RANK_PROMO; - l_pawns &= ~RANK_PROMO; - r_pawns &= ~RANK_PROMO; - - while (mt != MoveGenType::QUIET && l_pawns) { - const auto index = l_pawns.pop(); - moves.add(Move::make(index + DOWN_RIGHT, index)); - } - - while (mt != MoveGenType::QUIET && r_pawns) { - const auto index = r_pawns.pop(); - moves.add(Move::make(index + DOWN_LEFT, index)); - } - - while (mt != MoveGenType::CAPTURE && single_push) { - const auto index = single_push.pop(); - moves.add(Move::make(index + DOWN, index)); - } - - while (mt != MoveGenType::CAPTURE && double_push) { - const auto index = double_push.pop(); - moves.add(Move::make(index + DOWN + DOWN, index)); - } - - if constexpr (mt == MoveGenType::QUIET) return; - - const Square ep = board.enpassantSq(); - - if (ep != Square::NO_SQ) { - auto m = generateEPMove(board, checkmask, pin_d, pawns_lr, ep, c); - - for (const auto &move : m) { - if (move != Move::NO_MOVE) moves.add(move); - } - } -} - -[[nodiscard]] inline std::array movegen::generateEPMove(const Board &board, Bitboard checkmask, Bitboard pin_d, - Bitboard pawns_lr, Square ep, Color c) { - assert((ep.rank() == Rank::RANK_3 && board.sideToMove() == Color::BLACK) || - (ep.rank() == Rank::RANK_6 && board.sideToMove() == Color::WHITE)); - - std::array moves = {Move::NO_MOVE, Move::NO_MOVE}; - int i = 0; - - const auto DOWN = make_direction(Direction::SOUTH, c); - const auto epPawnSq = ep + DOWN; - - /* - In case the en passant square and the enemy pawn - that just moved are not on the checkmask - en passant is not available. - */ - if ((checkmask & (Bitboard::fromSquare(epPawnSq) | Bitboard::fromSquare(ep))) == 0ull) return moves; - - const Square kSQ = board.kingSq(c); - const Bitboard kingMask = Bitboard::fromSquare(kSQ) & epPawnSq.rank().bb(); - const Bitboard enemyQueenRook = board.pieces(PieceType::ROOK, PieceType::QUEEN) & board.us(~c); - - auto epBB = attacks::pawn(~c, ep) & pawns_lr; - - // For one en passant square two pawns could potentially take there. - while (epBB) { - const auto from = epBB.pop(); - const auto to = ep; - - /* - If the pawn is pinned but the en passant square is not on the - pin mask then the move is illegal. - */ - if ((Bitboard::fromSquare(from) & pin_d) && !(pin_d & Bitboard::fromSquare(ep))) continue; - - const auto connectingPawns = Bitboard::fromSquare(epPawnSq) | Bitboard::fromSquare(from); - - /* - 7k/4p3/8/2KP3r/8/8/8/8 b - - 0 1 - If e7e5 there will be a potential ep square for us on e6. - However, we cannot take en passant because that would put our king - in check. For this scenario we check if there's an enemy rook/queen - that would give check if the two pawns were removed. - If that's the case then the move is illegal and we can break immediately. - */ - const auto isPossiblePin = kingMask && enemyQueenRook; - - if (isPossiblePin && (attacks::rook(kSQ, board.occ() ^ connectingPawns) & enemyQueenRook) != 0ull) break; - - moves[i++] = Move::make(from, to); - } - - return moves; -} - -[[nodiscard]] inline Bitboard movegen::generateKnightMoves(Square sq) { return attacks::knight(sq); } - -[[nodiscard]] inline Bitboard movegen::generateBishopMoves(Square sq, Bitboard pin_d, Bitboard occ_all) { - // The Bishop is pinned diagonally thus can only move diagonally. - if (pin_d & Bitboard::fromSquare(sq)) return attacks::bishop(sq, occ_all) & pin_d; - return attacks::bishop(sq, occ_all); -} - -[[nodiscard]] inline Bitboard movegen::generateRookMoves(Square sq, Bitboard pin_hv, Bitboard occ_all) { - // The Rook is pinned horizontally thus can only move horizontally. - if (pin_hv & Bitboard::fromSquare(sq)) return attacks::rook(sq, occ_all) & pin_hv; - return attacks::rook(sq, occ_all); -} - -[[nodiscard]] inline Bitboard movegen::generateQueenMoves(Square sq, Bitboard pin_d, Bitboard pin_hv, - Bitboard occ_all) { - Bitboard moves = 0ULL; - - if (pin_d & Bitboard::fromSquare(sq)) - moves |= attacks::bishop(sq, occ_all) & pin_d; - else if (pin_hv & Bitboard::fromSquare(sq)) - moves |= attacks::rook(sq, occ_all) & pin_hv; - else { - moves |= attacks::rook(sq, occ_all); - moves |= attacks::bishop(sq, occ_all); - } - - return moves; -} - -[[nodiscard]] inline Bitboard movegen::generateKingMoves(Square sq, Bitboard seen, Bitboard movable_square) { - return attacks::king(sq) & movable_square & ~seen; -} - -template -[[nodiscard]] inline Bitboard movegen::generateCastleMoves(const Board &board, Square sq, Bitboard seen, - Bitboard pin_hv) noexcept { - if (!Square::back_rank(sq, c) || !board.castlingRights().has(c)) return 0ull; - - const auto rights = board.castlingRights(); - - Bitboard moves = 0ull; - - for (const auto side : {Board::CastlingRights::Side::KING_SIDE, Board::CastlingRights::Side::QUEEN_SIDE}) { - if (!rights.has(c, side)) continue; - - const auto is_king_side = side == Board::CastlingRights::Side::KING_SIDE; - - // No pieces on the castling path - if (board.occ() & board.getCastlingPath(c, is_king_side)) continue; - - // No attacks on the king path - const auto king_to = Square::castling_king_square(is_king_side, c); - if (between(sq, king_to) & seen) continue; - - // Chess960: Rook is pinned on the backrank. - const auto from_rook_bb = Bitboard::fromSquare(Square(rights.getRookFile(c, side), sq.rank())); - if (board.chess960() && (pin_hv & board.us(board.sideToMove()) & from_rook_bb)) continue; - - moves |= from_rook_bb; - } - - return moves; -} - -template -inline void movegen::whileBitboardAdd(Movelist &movelist, Bitboard mask, T func) { - while (mask) { - const Square from = mask.pop(); - auto moves = func(from); - while (moves) { - const Square to = moves.pop(); - movelist.add(Move::make(from, to)); - } - } -} - -template -inline void movegen::legalmoves(Movelist &movelist, const Board &board, int pieces) { - /* - The size of the movelist might not - be 0! This is done on purpose since it enables - you to append new move types to any movelist. - */ - auto king_sq = board.kingSq(c); - - Bitboard occ_us = board.us(c); - Bitboard occ_opp = board.us(~c); - Bitboard occ_all = occ_us | occ_opp; - - Bitboard opp_empty = ~occ_us; - - const auto [checkmask, checks] = checkMask(board, king_sq); - const auto pin_hv = pinMask(board, king_sq, occ_opp, occ_us); - const auto pin_d = pinMask(board, king_sq, occ_opp, occ_us); - - assert(checks <= 2); - - Bitboard movable_square; - - // Slider, Knights and King moves can only go to enemy or empty squares. - if constexpr (mt == MoveGenType::ALL) - movable_square = opp_empty; - else if constexpr (mt == MoveGenType::CAPTURE) - movable_square = occ_opp; - else // QUIET moves - movable_square = ~occ_all; - - if (pieces & PieceGenType::KING) { - Bitboard seen = seenSquares<~c>(board, opp_empty); - - whileBitboardAdd(movelist, Bitboard::fromSquare(king_sq), - [&](Square sq) { return generateKingMoves(sq, seen, movable_square); }); - - if (mt != MoveGenType::CAPTURE && checks == 0) { - Bitboard moves_bb = generateCastleMoves(board, king_sq, seen, pin_hv); - - while (moves_bb) { - Square to = moves_bb.pop(); - movelist.add(Move::make(king_sq, to)); - } - } - } - - // Early return for double check as described earlier - if (checks == 2) return; - - // Moves have to be on the checkmask - movable_square &= checkmask; - - // Add the moves to the movelist. - if (pieces & PieceGenType::PAWN) { - generatePawnMoves(board, movelist, pin_d, pin_hv, checkmask, occ_opp); - } - - if (pieces & PieceGenType::KNIGHT) { - // Prune knights that are pinned since these cannot move. - Bitboard knights_mask = board.pieces(PieceType::KNIGHT, c) & ~(pin_d | pin_hv); - - whileBitboardAdd(movelist, knights_mask, [&](Square sq) { return generateKnightMoves(sq) & movable_square; }); - } - - if (pieces & PieceGenType::BISHOP) { - // Prune horizontally pinned bishops - Bitboard bishops_mask = board.pieces(PieceType::BISHOP, c) & ~pin_hv; - - whileBitboardAdd(movelist, bishops_mask, - [&](Square sq) { return generateBishopMoves(sq, pin_d, occ_all) & movable_square; }); - } - - if (pieces & PieceGenType::ROOK) { - // Prune diagonally pinned rooks - Bitboard rooks_mask = board.pieces(PieceType::ROOK, c) & ~pin_d; - - whileBitboardAdd(movelist, rooks_mask, - [&](Square sq) { return generateRookMoves(sq, pin_hv, occ_all) & movable_square; }); - } - - if (pieces & PieceGenType::QUEEN) { - // Prune double pinned queens - Bitboard queens_mask = board.pieces(PieceType::QUEEN, c) & ~(pin_d & pin_hv); - - whileBitboardAdd(movelist, queens_mask, - [&](Square sq) { return generateQueenMoves(sq, pin_d, pin_hv, occ_all) & movable_square; }); - } -} - -template -inline void movegen::legalmoves(Movelist &movelist, const Board &board, int pieces) { - movelist.clear(); - - if (board.sideToMove() == Color::WHITE) - legalmoves(movelist, board, pieces); - else - legalmoves(movelist, board, pieces); -} - -template -inline bool movegen::isEpSquareValid(const Board &board, Square ep) { - const auto stm = board.sideToMove(); - - Bitboard occ_us = board.us(stm); - Bitboard occ_opp = board.us(~stm); - auto king_sq = board.kingSq(stm); - - const auto [checkmask, checks] = movegen::checkMask(board, king_sq); - const auto pin_hv = movegen::pinMask(board, king_sq, occ_opp, occ_us); - const auto pin_d = movegen::pinMask(board, king_sq, occ_opp, occ_us); - - const auto pawns = board.pieces(PieceType::PAWN, stm); - const auto pawns_lr = pawns & ~pin_hv; - const auto m = movegen::generateEPMove(board, checkmask, pin_d, pawns_lr, ep, stm); - bool found = false; - - for (const auto &move : m) { - if (move != Move::NO_MOVE) { - found = true; - break; - } - } - - return found; -} - -[[nodiscard]] inline Bitboard movegen::between(Square sq1, Square sq2) noexcept { - return SQUARES_BETWEEN_BB[sq1.index()][sq2.index()]; -} - -inline const std::array, 64> movegen::SQUARES_BETWEEN_BB = [] { - attacks::initAttacks(); - return movegen::init_squares_between(); -}(); - -} // namespace chess - -#include - -namespace chess::pgn { - -namespace detail { - -/** - * @brief Private class - */ -class StringBuffer { - public: - bool empty() const noexcept { return index_ == 0; } - - void clear() noexcept { index_ = 0; } - - std::string_view get() const noexcept { return std::string_view(buffer_.data(), index_); } - - bool add(char c) { - if (index_ >= N) { - return false; - } - - buffer_[index_] = c; - - ++index_; - - return true; - } - - private: - // PGN String Tokens are limited to 255 characters - static constexpr int N = 255; - - std::array buffer_ = {}; - - std::size_t index_ = 0; -}; - -/** - * @brief Private class - * @tparam BUFFER_SIZE - */ -template -class StreamBuffer { - private: - static constexpr std::size_t N = BUFFER_SIZE; - using BufferType = std::array; - - public: - StreamBuffer(std::istream &stream) : stream_(stream) {} - - // Get the current character, skip carriage returns - std::optional some() { - while (true) { - if (buffer_index_ < bytes_read_) { - const auto c = buffer_[buffer_index_]; - - if (c == '\r') { - ++buffer_index_; - continue; - } - - return c; - } - - if (!fill()) { - return std::nullopt; - } - } - } - - // Assume that the current character is already the opening_delim - bool skipUntil(char open_delim, char close_delim) { - int stack = 0; - - while (true) { - const auto ret = some(); - advance(); - - if (!ret.has_value()) { - return false; - } - - if (*ret == open_delim) { - ++stack; - } else if (*ret == close_delim) { - if (stack == 0) { - // Mismatched closing delimiter - return false; - } else { - --stack; - if (stack == 0) { - // Matching closing delimiter found - return true; - } - } - } - } - - // If we reach this point, there are unmatched opening delimiters - return false; - } - - bool fill() { - buffer_index_ = 0; - - stream_.read(buffer_.data(), N * N); - bytes_read_ = stream_.gcount(); - - return bytes_read_ > 0; - } - - void advance() { - if (buffer_index_ >= bytes_read_) { - fill(); - } - - ++buffer_index_; - } - - char peek() { - if (buffer_index_ + 1 >= bytes_read_) { - return stream_.peek(); - } - - return buffer_[buffer_index_ + 1]; - } - - std::optional current() { - if (buffer_index_ >= bytes_read_) { - return fill() ? std::optional(buffer_[buffer_index_]) : std::nullopt; - } - - return buffer_[buffer_index_]; - } - - private: - std::istream &stream_; - BufferType buffer_; - std::streamsize bytes_read_ = 0; - std::streamsize buffer_index_ = 0; -}; - -} // namespace detail - -/** - * @brief Visitor interface for parsing PGN files - */ -class Visitor { - public: - virtual ~Visitor() {}; - - /** - * @brief When true, the current PGN will be skipped and only - * endPgn will be called, this will also reset the skip flag to false. - * Has to be called after startPgn. - * @param skip - */ - void skipPgn(bool skip) { skip_ = skip; } - bool skip() { return skip_; } - - /** - * @brief Called when a new PGN starts - */ - virtual void startPgn() = 0; - - /** - * @brief Called for each header - * @param key - * @param value - */ - virtual void header(std::string_view key, std::string_view value) = 0; - - /** - * @brief Called before the first move of a game - */ - virtual void startMoves() = 0; - - /** - * @brief Called for each move of a game - * @param move - * @param comment - */ - virtual void move(std::string_view move, std::string_view comment) = 0; - - /** - * @brief Called when a game ends - */ - virtual void endPgn() = 0; - - private: - bool skip_ = false; -}; - -class StreamParserError { - public: - enum Code { - None, - ExceededMaxStringLength, - InvalidHeaderMissingClosingBracket, - InvalidHeaderMissingClosingQuote, - NotEnoughData - }; - - StreamParserError() : code_(None) {} - - StreamParserError(Code code) : code_(code) {} - - Code code() const { return code_; } - - bool hasError() const { return code_ != None; } - - std::string message() const { - switch (code_) { - case None: - return "No error"; - case InvalidHeaderMissingClosingBracket: - return "Invalid header: missing closing bracket"; - case InvalidHeaderMissingClosingQuote: - return "Invalid header: missing closing quote"; - case NotEnoughData: - return "Not enough data"; - default: - assert(false); - return "Unknown error"; - } - } - - bool operator==(Code code) const { return code_ == code; } - bool operator!=(Code code) const { return code_ != code; } - bool operator==(const StreamParserError &other) const { return code_ == other.code_; } - bool operator!=(const StreamParserError &other) const { return code_ != other.code_; } - - operator bool() const { return code_ != None; } - - private: - Code code_; -}; - -template -class StreamParser { - public: - StreamParser(std::istream &stream) : stream_buffer(stream) {} - - StreamParserError readGames(Visitor &vis) { - visitor = &vis; - - if (!stream_buffer.fill()) { - return StreamParserError::NotEnoughData; - } - - while (auto c = stream_buffer.some()) { - if (in_header) { - visitor->skipPgn(false); - - if (*c == '[') { - visitor->startPgn(); - pgn_end = false; - - processHeader(); - - if (error != StreamParserError::None) { - return error; - } - } - - } else if (in_body) { - processBody(); - - if (error != StreamParserError::None) { - return error; - } - } - - if (!dont_advance_after_body) stream_buffer.advance(); - dont_advance_after_body = false; - } - - if (!pgn_end) { - onEnd(); - } - - return error; - } - - private: - void reset_trackers() { - header.first.clear(); - header.second.clear(); - - move.clear(); - comment.clear(); - - in_header = true; - in_body = false; - } - - void callVisitorMoveFunction() { - if (!move.empty()) { - if (!visitor->skip()) visitor->move(move.get(), comment); - - move.clear(); - comment.clear(); - } - } - - void processHeader() { - bool backslash = false; - - while (auto c = stream_buffer.some()) { - switch (*c) { - // tag start - case '[': - stream_buffer.advance(); - - while (auto k = stream_buffer.some()) { - if (is_space(*k)) { - break; - } else { - if (!header.first.add(*k)) { - error = StreamParserError::ExceededMaxStringLength; - return; - } - - stream_buffer.advance(); - } - } - - stream_buffer.advance(); - break; - case '"': - stream_buffer.advance(); - - while (auto k = stream_buffer.some()) { - if (*k == '\\') { - backslash = true; - // don't add backslash to header, is this really correct? - stream_buffer.advance(); - } else if (*k == '"' && !backslash) { - stream_buffer.advance(); - - // we should be now at ] - if (stream_buffer.current().value_or('\0') != ']') { - error = StreamParserError::InvalidHeaderMissingClosingBracket; - return; - } - - stream_buffer.advance(); - - break; - } else if (*k == '\n') { - // we missed the closing quote and read until the newline character - // this is an invalid pgn, let's throw an error - error = StreamParserError::InvalidHeaderMissingClosingQuote; - return; - } else { - backslash = false; - - if (!header.second.add(*k)) { - error = StreamParserError::ExceededMaxStringLength; - return; - } - - stream_buffer.advance(); - } - } - - // manually skip carriage return, otherwise we would be in the body - // ideally we should completely skip all carriage returns and newlines to avoid this - if (stream_buffer.current() == '\r') { - stream_buffer.advance(); - } - - if (!visitor->skip()) visitor->header(header.first.get(), header.second.get()); - - header.first.clear(); - header.second.clear(); - - stream_buffer.advance(); - break; - case '\n': - in_header = false; - in_body = true; - - if (!visitor->skip()) visitor->startMoves(); - - return; - default: - // this should normally not happen - // lets just go into the body, will this always be save? - in_header = false; - in_body = true; - - if (!visitor->skip()) visitor->startMoves(); - - return; - } - } - } - - void processBody() { - auto is_termination_symbol = false; - auto has_comment = false; - - start: - /* - Skip first move number or game termination - Also skip - * / to fix games - which directly start with a game termination - this https://github.com/Disservin/chess-library/issues/68 - */ - - while (auto c = stream_buffer.some()) { - if (*c == ' ' || is_digit(*c)) { - stream_buffer.advance(); - } else if (*c == '-' || *c == '*' || c == '/') { - is_termination_symbol = true; - stream_buffer.advance(); - } else if (*c == '{') { - has_comment = true; - - // reading comment - stream_buffer.advance(); - - while (auto k = stream_buffer.some()) { - stream_buffer.advance(); - - if (*k == '}') { - break; - } - - comment += *k; - } - - // the game has no moves, but a comment followed by a game termination - if (!visitor->skip()) { - visitor->move("", comment); - - comment.clear(); - } - } else { - break; - } - } - - // we need to reparse the termination symbol - if (has_comment && !is_termination_symbol) { - goto start; - } - - // game had no moves, so we can skip it and call endPgn - if (is_termination_symbol) { - onEnd(); - return; - } - - while (auto c = stream_buffer.some()) { - if (is_space(*c)) { - stream_buffer.advance(); - continue; - } - - break; - } - - while (auto cd = stream_buffer.some()) { - // Pgn are build up in the following way. - // {move_number} {move} {comment} {move} {comment} {move_number} ... - // So we need to skip the move_number then start reading the move, then save the comment - // then read the second move in the group. After that a move_number will follow again. - - // [ is unexpected here, it probably is a new pgn and the current one is finished - if (*cd == '[') { - onEnd(); - dont_advance_after_body = true; - // break; - break; - } - - // skip move number digits - while (auto c = stream_buffer.some()) { - if (is_space(*c) || is_digit(*c)) { - stream_buffer.advance(); - } else { - break; - } - } - - // skip dots - while (auto c = stream_buffer.some()) { - if (*c == '.') { - stream_buffer.advance(); - } else { - break; - } - } - - // skip spaces - while (auto c = stream_buffer.some()) { - if (is_space(*c)) { - stream_buffer.advance(); - } else { - break; - } - } - - // parse move - if (parseMove()) { - break; - } - - // skip spaces - while (auto c = stream_buffer.some()) { - if (is_space(*c)) { - stream_buffer.advance(); - } else { - break; - } - } - - // game termination - auto curr = stream_buffer.current(); - - if (!curr.has_value()) { - onEnd(); - break; - } - - // game termination - if (*curr == '*') { - onEnd(); - stream_buffer.advance(); - - break; - } - - const auto peek = stream_buffer.peek(); - - if (*curr == '1') { - if (peek == '-') { - stream_buffer.advance(); - stream_buffer.advance(); - - onEnd(); - break; - } else if (peek == '/') { - for (size_t i = 0; i <= 6; ++i) { - stream_buffer.advance(); - } - - onEnd(); - break; - } - } - - // might be 0-1 (game termination) or 0-0-0/0-0 (castling) - if (*curr == '0' && stream_buffer.peek() == '-') { - stream_buffer.advance(); - stream_buffer.advance(); - - const auto c = stream_buffer.current(); - if (!c.has_value()) { - onEnd(); - - break; - } - - // game termination - if (*c == '1') { - onEnd(); - stream_buffer.advance(); - - break; - } - // castling - else { - if (!move.add('0') || !move.add('-')) { - error = StreamParserError::ExceededMaxStringLength; - return; - } - - if (parseMove()) { - stream_buffer.advance(); - break; - } - } - } - } - } - - bool parseMove() { - // reading move - while (auto c = stream_buffer.some()) { - if (is_space(*c)) { - break; - } - - if (!move.add(*c)) { - error = StreamParserError::ExceededMaxStringLength; - return true; - } - - stream_buffer.advance(); - } - - return parseMoveAppendix(); - } - - bool parseMoveAppendix() { - while (true) { - auto curr = stream_buffer.current(); - - if (!curr.has_value()) { - onEnd(); - return true; - } - - switch (*curr) { - case '{': { - // reading comment - stream_buffer.advance(); - - while (auto c = stream_buffer.some()) { - stream_buffer.advance(); - - if (*c == '}') { - break; - } - - comment += *c; - } - - break; - } - case '(': { - stream_buffer.skipUntil('(', ')'); - break; - } - case '$': { - while (auto c = stream_buffer.some()) { - if (is_space(*c)) { - break; - } - - stream_buffer.advance(); - } - - break; - } - case ' ': { - while (auto c = stream_buffer.some()) { - if (!is_space(*c)) { - break; - } - - stream_buffer.advance(); - } - - break; - } - default: - callVisitorMoveFunction(); - return false; - } - } - } - - void onEnd() { - callVisitorMoveFunction(); - visitor->endPgn(); - visitor->skipPgn(false); - - reset_trackers(); - - pgn_end = true; - } - - bool is_space(const char c) noexcept { - switch (c) { - case ' ': - case '\t': - case '\n': - case '\r': - return true; - default: - return false; - } - } - - bool is_digit(const char c) noexcept { - switch (c) { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - return true; - default: - return false; - } - } - - detail::StreamBuffer stream_buffer; - - Visitor *visitor = nullptr; - - // one time allocations - std::pair header = {detail::StringBuffer{}, detail::StringBuffer{}}; - - detail::StringBuffer move = {}; - std::string comment = {}; - - // State - - StreamParserError error = StreamParserError::None; - - bool in_header = true; - bool in_body = false; - - bool pgn_end = true; - - bool dont_advance_after_body = false; -}; -} // namespace chess::pgn - -#include - - -namespace chess { -class uci { - public: - /** - * @brief Converts an internal move to a UCI string - * @param move - * @param chess960 - * @return - */ - [[nodiscard]] static std::string moveToUci(const Move &move, bool chess960 = false) noexcept(false) { - // Get the from and to squares - Square from_sq = move.from(); - Square to_sq = move.to(); - - // If the move is not a chess960 castling move and is a king moving more than one square, - // update the to square to be the correct square for a regular castling move - if (!chess960 && move.typeOf() == Move::CASTLING) { - to_sq = Square(to_sq > from_sq ? File::FILE_G : File::FILE_C, from_sq.rank()); - } - - std::stringstream ss; - - // Add the from and to squares to the string stream - ss << from_sq << to_sq; - - // If the move is a promotion, add the promoted piece to the string stream - if (move.typeOf() == Move::PROMOTION) { - ss << static_cast(move.promotionType()); - } - - return ss.str(); - } - - /** - * @brief Converts a UCI string to an internal move. - * @param board - * @param uci - * @return - */ - [[nodiscard]] static Move uciToMove(const Board &board, const std::string &uci) noexcept(false) { - if (uci.length() < 4) { - return Move::NO_MOVE; - } - - Square source = Square(uci.substr(0, 2)); - Square target = Square(uci.substr(2, 2)); - - if (!source.is_valid() || !target.is_valid()) { - return Move::NO_MOVE; - } - - auto pt = board.at(source).type(); - - // castling in chess960 - if (board.chess960() && pt == PieceType::KING && board.at(target).type() == PieceType::ROOK && - board.at(target).color() == board.sideToMove()) { - return Move::make(source, target); - } - - // convert to king captures rook - // in chess960 the move should be sent as king captures rook already! - if (!board.chess960() && pt == PieceType::KING && Square::distance(target, source) == 2) { - target = Square(target > source ? File::FILE_H : File::FILE_A, source.rank()); - return Move::make(source, target); - } - - // en passant - if (pt == PieceType::PAWN && target == board.enpassantSq()) { - return Move::make(source, target); - } - - // promotion - if (pt == PieceType::PAWN && uci.length() == 5 && Square::back_rank(target, ~board.sideToMove())) { - auto promotion = PieceType(uci.substr(4, 1)); - - if (promotion == PieceType::NONE || promotion == PieceType::KING || promotion == PieceType::PAWN) { - return Move::NO_MOVE; - } - - return Move::make(source, target, PieceType(uci.substr(4, 1))); - } - - return (uci.length() == 4) ? Move::make(source, target) : Move::NO_MOVE; - } - - /** - * @brief Converts a move to a SAN string - * @param board - * @param move - * @return - */ - [[nodiscard]] static std::string moveToSan(const Board &board, const Move &move) noexcept(false) { - std::string san; - moveToRep(board, move, san); - return san; - } - - /** - * @brief Converts a move to a LAN string - * @param board - * @param move - * @return - */ - [[nodiscard]] static std::string moveToLan(const Board &board, const Move &move) noexcept(false) { - std::string lan; - moveToRep(board, move, lan); - return lan; - } - - class SanParseError : public std::exception { - public: - explicit SanParseError(const char *message) : msg_(message) {} - - explicit SanParseError(const std::string &message) : msg_(message) {} - - virtual ~SanParseError() noexcept {} - - virtual const char *what() const noexcept { return msg_.c_str(); } - - protected: - std::string msg_; - }; - - class AmbiguousMoveError : public std::exception { - public: - explicit AmbiguousMoveError(const char *message) : msg_(message) {} - - explicit AmbiguousMoveError(const std::string &message) : msg_(message) {} - - virtual ~AmbiguousMoveError() noexcept {} - - virtual const char *what() const noexcept { return msg_.c_str(); } - - protected: - std::string msg_; - }; - - /** - * @brief Parse a san string and return the move. - * This function will throw a SanParseError if the san string is invalid. - * @param board - * @param san - * @return - */ - [[nodiscard]] static Move parseSan(const Board &board, std::string_view san) noexcept(false) { - Movelist moves; - - return parseSan(board, san, moves); - } - - /** - * @brief Parse a san string and return the move. - * This function will throw a SanParseError if the san string is invalid. - * @param board - * @param san - * @param moves - * @return - */ - [[nodiscard]] static Move parseSan(const Board &board, std::string_view san, Movelist &moves) noexcept(false) { - if (san.empty()) { - return Move::NO_MOVE; - } - - static constexpr auto pt_to_pgt = [](PieceType pt) { return 1 << (pt); }; - const SanMoveInformation info = parseSanInfo(san); - - if (info.capture) { - movegen::legalmoves(moves, board, pt_to_pgt(info.piece)); - } else { - movegen::legalmoves(moves, board, pt_to_pgt(info.piece)); - } - - if (info.castling_short || info.castling_long) { - for (const auto &move : moves) { - if (move.typeOf() == Move::CASTLING) { - if ((info.castling_short && move.to() > move.from()) || - (info.castling_long && move.to() < move.from())) { - return move; - } - } - } - -#ifndef CHESS_NO_EXCEPTIONS - throw SanParseError("Failed to parse san. At step 2: " + std::string(san) + " " + board.getFen()); -#endif - } - - Move matchingMove = Move::NO_MOVE; - bool foundMatch = false; - - for (const auto &move : moves) { - // Skip all moves that are not to the correct square - // or are castling moves - if (move.to() != info.to || move.typeOf() == Move::CASTLING) { - continue; - } - - // Handle promotion moves - if (info.promotion != PieceType::NONE) { - if (move.typeOf() != Move::PROMOTION || info.promotion != move.promotionType() || - move.from().file() != info.from_file) { - continue; - } - } - // Handle en passant moves - else if (move.typeOf() == Move::ENPASSANT) { - if (move.from().file() != info.from_file) { - continue; - } - } - // Handle moves with specific from square - else if (info.from != Square::NO_SQ) { - if (move.from() != info.from) { - continue; - } - } - // Handle moves with partial from information (rank or file) - else if (info.from_rank != Rank::NO_RANK || info.from_file != File::NO_FILE) { - if ((info.from_file != File::NO_FILE && move.from().file() != info.from_file) || - (info.from_rank != Rank::NO_RANK && move.from().rank() != info.from_rank)) { - continue; - } - } - - // If we get here, the move matches our criteria - if (foundMatch) { -#ifndef CHESS_NO_EXCEPTIONS - throw AmbiguousMoveError("Ambiguous san: " + std::string(san) + " in " + board.getFen()); -#endif - } - - matchingMove = move; - foundMatch = true; - } - - if (!foundMatch) { -#ifndef CHESS_NO_EXCEPTIONS - throw SanParseError("Failed to parse san. At step 3: " + std::string(san) + " " + board.getFen()); -#endif - } - - return matchingMove; - } - - /** - * @brief Check if a string is a valid UCI move. Must also have the correct length. - * @param move - * @return - */ - static bool isUciMove(const std::string &move) noexcept { - bool is_uci = false; - - static constexpr auto is_digit = [](char c) { return c >= '1' && c <= '8'; }; - static constexpr auto is_file = [](char c) { return c >= 'a' && c <= 'h'; }; - static constexpr auto is_promotion = [](char c) { return c == 'n' || c == 'b' || c == 'r' || c == 'q'; }; - - // assert that the move is in uci format, [abcdefgh][1-8][abcdefgh][1-8][nbrq] - if (move.size() >= 4) { - is_uci = is_file(move[0]) && is_digit(move[1]) && is_file(move[2]) && is_digit(move[3]); - } - - if (move.size() == 5) { - is_uci = is_uci && is_promotion(move[4]); - } - - if (move.size() > 5) { - return false; - } - - return is_uci; - } - - private: - struct SanMoveInformation { - File from_file = File::NO_FILE; - Rank from_rank = Rank::NO_RANK; - - PieceType promotion = PieceType::NONE; - - Square from = Square::NO_SQ; - // a valid move always has a to square - Square to = Square::NO_SQ; - - // a valid move always has a piece - PieceType piece = PieceType::NONE; - - bool castling_short = false; - bool castling_long = false; - - bool capture = false; - }; - - [[nodiscard]] static SanMoveInformation parseSanInfo(std::string_view san) noexcept(false) { -#ifndef CHESS_NO_EXCEPTIONS - if (san.length() < 2) { - throw SanParseError("Failed to parse san. At step 0: " + std::string(san)); - } -#endif - constexpr auto parse_castle = [](std::string_view &san, SanMoveInformation &info, char castling_char) { - info.piece = PieceType::KING; - - san.remove_prefix(3); - - info.castling_short = san.length() == 0 || (san.length() >= 1 && san[0] != '-'); - info.castling_long = san.length() >= 2 && san[0] == '-' && san[1] == castling_char; - - assert((info.castling_short && !info.castling_long) || (!info.castling_short && info.castling_long) || - (!info.castling_short && !info.castling_long)); - }; - - static constexpr auto isRank = [](char c) { return c >= '1' && c <= '8'; }; - static constexpr auto isFile = [](char c) { return c >= 'a' && c <= 'h'; }; - static constexpr auto sw = [](const char &c) { return std::string_view(&c, 1); }; - - SanMoveInformation info; - - // set to 1 to skip piece type offset - std::size_t index = 1; - - if (san[0] == 'O' || san[0] == '0') { - parse_castle(san, info, san[0]); - return info; - } else if (isFile(san[0])) { - index--; - info.piece = PieceType::PAWN; - } else { - info.piece = PieceType(san); - } - - File file_to = File::NO_FILE; - Rank rank_to = Rank::NO_RANK; - - // check if san starts with a file, if so it will be start file - if (index < san.size() && isFile(san[index])) { - info.from_file = File(sw(san[index])); - index++; - } - - // check if san starts with a rank, if so it will be start rank - if (index < san.size() && isRank(san[index])) { - info.from_rank = Rank(sw(san[index])); - index++; - } - - // skip capture sign - if (index < san.size() && san[index] == 'x') { - info.capture = true; - index++; - } - - // to file - if (index < san.size() && isFile(san[index])) { - file_to = File(sw(san[index])); - index++; - } - - // to rank - if (index < san.size() && isRank(san[index])) { - rank_to = Rank(sw(san[index])); - index++; - } - - // promotion - if (index < san.size() && san[index] == '=') { - index++; - info.promotion = PieceType(sw(san[index])); - -#ifndef CHESS_NO_EXCEPTIONS - if (info.promotion == PieceType::KING || info.promotion == PieceType::PAWN || - info.promotion == PieceType::NONE) - throw SanParseError("Failed to parse promotion, during san conversion." + std::string(san)); -#endif - - index++; - } - - // for simple moves like Nf3, e4, etc. all the information is contained - // in the from file and rank. Thus we need to move it to the to file and rank. - if (file_to == File::NO_FILE && rank_to == Rank::NO_RANK) { - file_to = info.from_file; - rank_to = info.from_rank; - - info.from_file = File::NO_FILE; - info.from_rank = Rank::NO_RANK; - } - - // pawns which are not capturing stay on the same file - if (info.piece == PieceType::PAWN && info.from_file == File::NO_FILE && !info.capture) { - info.from_file = file_to; - } - - info.to = Square(file_to, rank_to); - - if (info.from_file != File::NO_FILE && info.from_rank != Rank::NO_RANK) { - info.from = Square(info.from_file, info.from_rank); - } - - return info; - } - - template - static void moveToRep(Board board, const Move &move, std::string &str) { - if (handleCastling(move, str)) { - board.makeMove(move); - if (board.inCheck()) appendCheckSymbol(board, str); - return; - } - - const PieceType pt = board.at(move.from()).type(); - const bool isCapture = board.at(move.to()) != Piece::NONE || move.typeOf() == Move::ENPASSANT; - - assert(pt != PieceType::NONE); - - if (pt != PieceType::PAWN) { - appendPieceSymbol(pt, str); - } - - if constexpr (LAN) { - appendSquare(move.from(), str); - } else { - if (pt == PieceType::PAWN) { - str += isCapture ? static_cast(move.from().file()) : ""; - } else { - resolveAmbiguity(board, move, pt, str); - } - } - - if (isCapture) { - str += 'x'; - } - - appendSquare(move.to(), str); - - if (move.typeOf() == Move::PROMOTION) appendPromotion(move, str); - - board.makeMove(move); - - if (board.inCheck()) appendCheckSymbol(board, str); - } - - static bool handleCastling(const Move &move, std::string &str) { - if (move.typeOf() != Move::CASTLING) return false; - - str = (move.to().file() > move.from().file()) ? "O-O" : "O-O-O"; - return true; - } - - static void appendPieceSymbol(PieceType pieceType, std::string &str) { - str += std::toupper(static_cast(pieceType)[0]); - } - - static void appendSquare(Square square, std::string &str) { - str += static_cast(square.file()); - str += static_cast(square.rank()); - } - - static void appendPromotion(const Move &move, std::string &str) { - str += '='; - str += std::toupper(static_cast(move.promotionType())[0]); - } - - static void appendCheckSymbol(Board &board, std::string &str) { - const auto gameState = board.isGameOver().second; - str += (gameState == GameResult::LOSE) ? '#' : '+'; - } - - static void resolveAmbiguity(const Board &board, const Move &move, PieceType pieceType, std::string &str) { - Movelist moves; - movegen::legalmoves(moves, board, 1 << pieceType); - - bool needFile = false; - bool needRank = false; - bool hasAmbiguousMove = false; - - for (const auto &m : moves) { - if (m != move && m.to() == move.to()) { - hasAmbiguousMove = true; - - /* - First, if the moving pieces can be distinguished by their originating files, the originating - file letter of the moving piece is inserted immediately after the moving piece letter. - - Second (when the first step fails), if the moving pieces can be distinguished by their - originating ranks, the originating rank digit of the moving piece is inserted immediately after - the moving piece letter. - - Third (when both the first and the second steps fail), the two character square coordinate of - the originating square of the moving piece is inserted immediately after the moving piece - letter. - */ - - if (isIdentifiableByType(moves, move, move.from().file())) { - needFile = true; - break; - } - - if (isIdentifiableByType(moves, move, move.from().rank())) { - needRank = true; - break; - } - } - } - - if (needFile) str += static_cast(move.from().file()); - if (needRank) str += static_cast(move.from().rank()); - - // we weren't able to disambiguate the move by either file or rank, so we need to use both - if (hasAmbiguousMove && !needFile && !needRank) { - appendSquare(move.from(), str); - } - } - - template - static bool isIdentifiableByType(const Movelist &moves, const Move move, CoordinateType type) { - static_assert(std::is_same_v || std::is_same_v, - "CoordinateType must be either File or Rank"); - - for (const auto &m : moves) { - if (m == move || m.to() != move.to()) { - continue; - } - - // file - if constexpr (std::is_same_v) { - if (type == m.from().file()) return false; - } - // rank - else { - if (type == m.from().rank()) return false; - } - } - - return true; - } -}; -} // namespace chess - -#endif diff --git a/eval.cpp b/eval.cpp index a0ba6cd..33403f9 100644 --- a/eval.cpp +++ b/eval.cpp @@ -1,86 +1,46 @@ -#include "eval.hpp" +#include "eval.h" +#include #include -#define EvaluationResult int16_t -// 0. Evaluation result - -struct EvalBreakdown -{ - EvaluationResult phase; - EvaluationResult Material; - EvaluationResult Doubled; - EvaluationResult Isolated; - EvaluationResult Backward; - EvaluationResult Passed; - EvaluationResult Center; - EvaluationResult Mobility; - EvaluationResult Shield; - EvaluationResult KingTropism; - EvaluationResult Space; - EvaluationResult MGPSQT; - EvaluationResult EGPSQT; -}; - -EvalBreakdown wEval{}, bEval{}; -// 1. Weights, in case of tuning (centipawns) -// 1.1. Material weights -constexpr int16_t PawnValue = 100, KnightValue = 325, BishopValue = 350, RookValue = 500, QueenValue = 900; -// 1.2. Pawn structure -constexpr int16_t Doubled = -100, Isolated = -80, Backward = -100, wpassed = 100, cpassed = 50, - Center = 100; -// 1.3. Mobility - -// Mobility weights per piece type -constexpr int MOBILITY_WEIGHTS[] = { - 0, // EMPTY / None - 1, // PAWN (often ignored or handled separately) - 4, // KNIGHT - 4, // BISHOP - 6, // ROOK - 8, // QUEEN - 0 // KING (usually ignored) -}; -// 1.3. King safety -constexpr int16_t pawnShield = 20, KingTropism = 5; -// 1.4. Space -constexpr int16_t Space = 10; -// 1.5. Tempo -constexpr int16_t Tempo = 28; -// 1.6. PQST (source: Chessprogramming Wiki) -// clang-format off -int16_t mg_pawn_table[64] = { - 0, 0, 0, 0, 0, 0, 0, 0, - 98, 134, 61, 95, 68, 126, 34, -11, - -6, 7, 26, 31, 65, 56, 25, -20, - -14, 13, 6, 21, 23, 12, 17, -23, - -27, -2, -5, 12, 17, 6, 10, -25, - -26, -4, -4, -10, 3, 3, 33, -12, - -35, -1, -20, -23, -15, 24, 38, -22, - 0, 0, 0, 0, 0, 0, 0, 0, -}; +#include +using namespace chess; +using namespace engine::eval; +namespace engine::eval{ + constexpr int16_t PawnValue = 100, KnightValue = 325, BishopValue = 350, RookValue = 500, QueenValue = 900; + + int mg_pawn_table[64] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 98, 134, 61, 95, 68, 126, 34, -11, + -6, 7, 26, 31, 65, 56, 25, -20, + -14, 13, 6, 21, 23, 12, 17, -23, + -27, -2, -5, 12, 17, 6, 10, -25, + -26, -4, -4, -10, 3, 3, 33, -12, + -35, -1, -20, -23, -15, 24, 38, -22, + 0, 0, 0, 0, 0, 0, 0, 0, + }; -int16_t eg_pawn_table[64] = { - 0, 0, 0, 0, 0, 0, 0, 0, - 178, 173, 158, 134, 147, 132, 165, 187, - 94, 100, 85, 67, 56, 53, 82, 84, - 32, 24, 13, 5, -2, 4, 17, 17, - 13, 9, -3, -7, -7, -8, 3, -1, - 4, 7, -6, 1, 0, -5, -1, -8, - 13, 8, 8, 10, 13, 0, 2, -7, - 0, 0, 0, 0, 0, 0, 0, 0, -}; + int eg_pawn_table[64] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 178, 173, 158, 134, 147, 132, 165, 187, + 94, 100, 85, 67, 56, 53, 82, 84, + 32, 24, 13, 5, -2, 4, 17, 17, + 13, 9, -3, -7, -7, -8, 3, -1, + 4, 7, -6, 1, 0, -5, -1, -8, + 13, 8, 8, 10, 13, 0, 2, -7, + 0, 0, 0, 0, 0, 0, 0, 0, + }; -int16_t mg_knight_table[64] = { - -167, -89, -34, -49, 61, -97, -15, -107, - -73, -41, 72, 36, 23, 62, 7, -17, - -47, 60, 37, 65, 84, 129, 73, 44, - -9, 17, 19, 53, 37, 69, 18, 22, - -13, 4, 16, 13, 28, 19, 21, -8, - -23, -9, 12, 10, 19, 17, 25, -16, - -29, -53, -12, -3, -1, 18, -14, -19, + int mg_knight_table[64] = { + -167, -89, -34, -49, 61, -97, -15, -107, + -73, -41, 72, 36, 23, 62, 7, -17, + -47, 60, 37, 65, 84, 129, 73, 44, + -9, 17, 19, 53, 37, 69, 18, 22, + -13, 4, 16, 13, 28, 19, 21, -8, + -23, -9, 12, 10, 19, 17, 25, -16, + -29, -53, -12, -3, -1, 18, -14, -19, -105, -21, -58, -33, -17, -28, -19, -23, -}; + }; -int16_t eg_knight_table[64] = { + int eg_knight_table[64] = { -58, -38, -13, -28, -31, -27, -63, -99, -25, -8, -25, -2, -9, -25, -24, -52, -24, -20, 10, 9, -1, -9, -19, -41, @@ -89,689 +49,159 @@ int16_t eg_knight_table[64] = { -23, -3, -1, 15, 10, -3, -20, -22, -42, -20, -10, -5, -2, -20, -23, -44, -29, -51, -23, -15, -22, -18, -50, -64, -}; + }; -int16_t mg_bishop_table[64] = { - -29, 4, -82, -37, -25, -42, 7, -8, - -26, 16, -18, -13, 30, 59, 18, -47, - -16, 37, 43, 40, 35, 50, 37, -2, - -4, 5, 19, 50, 37, 37, 7, -2, - -6, 13, 13, 26, 34, 12, 10, 4, - 0, 15, 15, 15, 14, 27, 18, 10, - 4, 15, 16, 0, 7, 21, 33, 1, - -33, -3, -14, -21, -13, -12, -39, -21, -}; + int mg_bishop_table[64] = { + -29, 4, -82, -37, -25, -42, 7, -8, + -26, 16, -18, -13, 30, 59, 18, -47, + -16, 37, 43, 40, 35, 50, 37, -2, + -4, 5, 19, 50, 37, 37, 7, -2, + -6, 13, 13, 26, 34, 12, 10, 4, + 0, 15, 15, 15, 14, 27, 18, 10, + 4, 15, 16, 0, 7, 21, 33, 1, + -33, -3, -14, -21, -13, -12, -39, -21, + }; -int16_t eg_bishop_table[64] = { - -14, -21, -11, -8, -7, -9, -17, -24, - -8, -4, 7, -12, -3, -13, -4, -14, - 2, -8, 0, -1, -2, 6, 0, 4, - -3, 9, 12, 9, 14, 10, 3, 2, - -6, 3, 13, 19, 7, 10, -3, -9, - -12, -3, 8, 10, 13, 3, -7, -15, - -14, -18, -7, -1, 4, -9, -15, -27, - -23, -9, -23, -5, -9, -16, -5, -17, -}; + int eg_bishop_table[64] = { + -14, -21, -11, -8, -7, -9, -17, -24, + -8, -4, 7, -12, -3, -13, -4, -14, + 2, -8, 0, -1, -2, 6, 0, 4, + -3, 9, 12, 9, 14, 10, 3, 2, + -6, 3, 13, 19, 7, 10, -3, -9, + -12, -3, 8, 10, 13, 3, -7, -15, + -14, -18, -7, -1, 4, -9, -15, -27, + -23, -9, -23, -5, -9, -16, -5, -17, + }; -int16_t mg_rook_table[64] = { - 32, 42, 32, 51, 63, 9, 31, 43, - 27, 32, 58, 62, 80, 67, 26, 44, - -5, 19, 26, 36, 17, 45, 61, 16, - -24, -11, 7, 26, 24, 35, -8, -20, - -36, -26, -12, -1, 9, -7, 6, -23, - -45, -25, -16, -17, 3, 0, -5, -33, - -44, -16, -20, -9, -1, 11, -6, -71, - -19, -13, 1, 17, 16, 7, -37, -26, -}; + int mg_rook_table[64] = { + 32, 42, 32, 51, 63, 9, 31, 43, + 27, 32, 58, 62, 80, 67, 26, 44, + -5, 19, 26, 36, 17, 45, 61, 16, + -24, -11, 7, 26, 24, 35, -8, -20, + -36, -26, -12, -1, 9, -7, 6, -23, + -45, -25, -16, -17, 3, 0, -5, -33, + -44, -16, -20, -9, -1, 11, -6, -71, + -19, -13, 1, 17, 16, 7, -37, -26, + }; -int16_t eg_rook_table[64] = { + int eg_rook_table[64] = { 13, 10, 18, 15, 12, 12, 8, 5, 11, 13, 13, 11, -3, 3, 8, 3, - 7, 7, 7, 5, 4, -3, -5, -3, - 4, 3, 13, 1, 2, 1, -1, 2, - 3, 5, 8, 4, -5, -6, -8, -11, + 7, 7, 7, 5, 4, -3, -5, -3, + 4, 3, 13, 1, 2, 1, -1, 2, + 3, 5, 8, 4, -5, -6, -8, -11, -4, 0, -5, -1, -7, -12, -8, -16, -6, -6, 0, 2, -9, -9, -11, -3, -9, 2, 3, -1, -5, -13, 4, -20, -}; - -int16_t mg_queen_table[64] = { - -28, 0, 29, 12, 59, 44, 43, 45, - -24, -39, -5, 1, -16, 57, 28, 54, - -13, -17, 7, 8, 29, 56, 47, 57, - -27, -27, -16, -16, -1, 17, -2, 1, - -9, -26, -9, -10, -2, -4, 3, -3, - -14, 2, -11, -2, -5, 2, 14, 5, - -35, -8, 11, 2, 8, 15, -3, 1, - -1, -18, -9, 10, -15, -25, -31, -50, -}; - -int16_t eg_queen_table[64] = { - -9, 22, 22, 27, 27, 19, 10, 20, - -17, 20, 32, 41, 58, 25, 30, 0, - -20, 6, 9, 49, 47, 35, 19, 9, - 3, 22, 24, 45, 57, 40, 57, 36, - -18, 28, 19, 47, 31, 34, 39, 23, - -16, -27, 15, 6, 9, 17, 10, 5, - -22, -23, -30, -16, -16, -23, -36, -32, - -33, -28, -22, -43, -5, -32, -20, -41, -}; - -int16_t mg_king_table[64] = { - -65, 23, 16, -15, -56, -34, 2, 13, - 29, -1, -20, -7, -8, -4, -38, -29, - -9, 24, 2, -16, -20, 6, 22, -22, - -17, -20, -12, -27, -30, -25, -14, -36, - -49, -1, -27, -39, -46, -44, -33, -51, - -14, -14, -22, -46, -44, -30, -15, -27, - 1, 7, -8, -64, -43, -16, 9, 8, - -15, 36, 12, -54, 8, -28, 24, 14, -}; - -int16_t eg_king_table[64] = { - -74, -35, -18, -18, -11, 15, 4, -17, - -12, 17, 14, 17, 17, 38, 23, 11, - 10, 17, 23, 15, 20, 45, 44, 13, - -8, 22, 24, 27, 26, 33, 26, 3, - -18, -4, 21, 24, 27, 23, 9, -11, - -19, -3, 11, 21, 23, 16, 7, -9, - -27, -11, 4, 13, 14, 4, -5, -17, - -53, -34, -21, -11, -28, -14, -24, -43 -}; - -int16_t* mg_pesto_table[6] = -{ - mg_pawn_table, - mg_knight_table, - mg_bishop_table, - mg_rook_table, - mg_queen_table, - mg_king_table -}; - -int16_t* eg_pesto_table[6] = -{ - eg_pawn_table, - eg_knight_table, - eg_bishop_table, - eg_rook_table, - eg_queen_table, - eg_king_table -}; - -// clang-format on -// 2. Evaluation functions -// 2.1. Phase -Evaluation int phase(const chess::Board &pos) -{ - constexpr int KnightPhase = 1; - constexpr int BishopPhase = 1; - constexpr int RookPhase = 2; - constexpr int QueenPhase = 4; - constexpr int TotalPhase = KnightPhase * 4 + BishopPhase * 4 + RookPhase * 4 + QueenPhase * 2; - - int phase = (pos.pieces(chess::PieceType::KNIGHT, chess::Color::WHITE).count() + - pos.pieces(chess::PieceType::KNIGHT, chess::Color::BLACK).count()) * - KnightPhase + - (pos.pieces(chess::PieceType::BISHOP, chess::Color::WHITE).count() + - pos.pieces(chess::PieceType::BISHOP, chess::Color::BLACK).count()) * - BishopPhase + - (pos.pieces(chess::PieceType::ROOK, chess::Color::WHITE).count() + - pos.pieces(chess::PieceType::ROOK, chess::Color::BLACK).count()) * - RookPhase + - (pos.pieces(chess::PieceType::QUEEN, chess::Color::WHITE).count() + - pos.pieces(chess::PieceType::QUEEN, chess::Color::BLACK).count()) * - QueenPhase; - - return wEval.phase = (phase * 256 + TotalPhase / 2) / TotalPhase; -} -// 2.2. Material (one-line) (all phases) -AllPhases inline int16_t material(const chess::Board &pos) -{ // Precompute piece counts (avoid multiple function calls) - int pieceCount[10] = { - pos.pieces(chess::PieceType::PAWN, chess::Color::WHITE).count(), - pos.pieces(chess::PieceType::KNIGHT, chess::Color::WHITE).count(), - pos.pieces(chess::PieceType::BISHOP, chess::Color::WHITE).count(), - pos.pieces(chess::PieceType::ROOK, chess::Color::WHITE).count(), - pos.pieces(chess::PieceType::QUEEN, chess::Color::WHITE).count(), - pos.pieces(chess::PieceType::PAWN, chess::Color::BLACK).count(), - pos.pieces(chess::PieceType::KNIGHT, chess::Color::BLACK).count(), - pos.pieces(chess::PieceType::BISHOP, chess::Color::BLACK).count(), - pos.pieces(chess::PieceType::ROOK, chess::Color::BLACK).count(), - pos.pieces(chess::PieceType::QUEEN, chess::Color::BLACK).count(), }; - wEval.Material = pieceCount[0] * PawnValue + pieceCount[1] * KnightValue + pieceCount[2] * BishopValue + - pieceCount[3] * RookValue + pieceCount[4] * QueenValue; - bEval.Material = pieceCount[5] * PawnValue + pieceCount[6] * KnightValue + pieceCount[7] * BishopValue + - pieceCount[8] * RookValue + pieceCount[9] * QueenValue; - return wEval.Material - bEval.Material; -} -// 2.3. Pawn structure -// 2.3.1. Doubled pawns -Middlegame int16_t doubled(const chess::Board &pos) -{ - wEval.Doubled = bEval.Doubled = 0; - chess::Bitboard P = pos.pieces(chess::PieceType::PAWN, chess::Color::WHITE), - p = pos.pieces(chess::PieceType::PAWN, chess::Color::BLACK); - // We use bitwise tricks to avoid conditionals - for (int i = 0; i < 8; ++i) - { - chess::Bitboard white = P & chess::attacks::MASK_FILE[i], black = p & chess::attacks::MASK_FILE[i]; - if (white.count() > 1) - wEval.Doubled += (white.count() - 1) * Doubled; - if (black.count() > 1) - bEval.Doubled += (black.count() - 1) * Doubled; - } - return wEval.Doubled - bEval.Doubled; -} -// 2.3.2. Isolated pawns -int16_t isolated(const chess::Board &pos) -{ - chess::Bitboard P = pos.pieces(chess::PieceType::PAWN, chess::Color::WHITE); - chess::Bitboard p = pos.pieces(chess::PieceType::PAWN, chess::Color::BLACK); - - constexpr chess::Bitboard noAFile = 0xFEFEFEFEFEFEFEFEULL; // mask out file A - constexpr chess::Bitboard noHFile = 0x7F7F7F7F7F7F7F7FULL; // mask out file H - - chess::Bitboard adjP = ((P & noHFile) << 1) | ((P & noAFile) >> 1); - chess::Bitboard adjp = ((p & noHFile) << 1) | ((p & noAFile) >> 1); - - chess::Bitboard Pi = P & ~adjP; - chess::Bitboard pi = p & ~adjp; - - wEval.Isolated = Pi.count() * Isolated; - bEval.Isolated = pi.count() * Isolated; - - return wEval.Isolated - bEval.Isolated; -} -// 2.3.3. Backward (https://www.chessprogramming.org/Backward_Pawns_(Bitboards)) -Middlegame inline int16_t backward(const chess::Board &pos) -{ - chess::Bitboard P = pos.pieces(chess::PieceType::PAWN, chess::Color::WHITE), - p = pos.pieces(chess::PieceType::PAWN, chess::Color::BLACK), wstop = P << 8, bstop = p << 8, - wAttacks = chess::attacks::pawnLeftAttacks(P) | - chess::attacks::pawnRightAttacks(P), - bAttacks = chess::attacks::pawnLeftAttacks(p) | - chess::attacks::pawnRightAttacks(p), - wback = (wstop & bAttacks & ~wAttacks) >> 8, bback = (bstop & wAttacks & ~bAttacks) >> 8; - wEval.Backward = wback.count() * Backward; - bEval.Backward = bback.count() * Backward; - return wEval.Backward - bEval.Backward; -} -// 2.3.4. Passed pawns (including candidate ones) (considered in both middlegame and endgame) - -AllPhases int16_t passed(const chess::Board &pos) -{ - chess::Bitboard whitePawns = pos.pieces(chess::PieceType::PAWN, chess::Color::WHITE); - chess::Bitboard blackPawns = pos.pieces(chess::PieceType::PAWN, chess::Color::BLACK); - - auto compute_score = [](chess::Bitboard P, chess::Bitboard p, bool isWhite) -> int16_t - { - chess::Bitboard mask = p; - mask |= (p << 1 | p >> 1) & 0x6F6F6F6F6F6F6F6F; // ~FILE_A&~FILE_H - - if (isWhite) - { - mask |= (mask << 8); - mask |= (mask << 16); - mask |= (mask << 32); - } - else - { - mask |= (mask >> 8); - mask |= (mask >> 16); - mask |= (mask >> 32); - } - - chess::Bitboard passed = P & ~mask; - chess::Bitboard candidate = P & mask; - - return wpassed * passed.count() + cpassed * candidate.count(); + int mg_queen_table[64] = { + -28, 0, 29, 12, 59, 44, 43, 45, + -24, -39, -5, 1, -16, 57, 28, 54, + -13, -17, 7, 8, 29, 56, 47, 57, + -27, -27, -16, -16, -1, 17, -2, 1, + -9, -26, -9, -10, -2, -4, 3, -3, + -14, 2, -11, -2, -5, 2, 14, 5, + -35, -8, 11, 2, 8, 15, -3, 1, + -1, -18, -9, 10, -15, -25, -31, -50, }; - wEval.Passed = compute_score(whitePawns, blackPawns, true); - bEval.Passed = compute_score(blackPawns, whitePawns, false); - - return wEval.Passed - bEval.Passed; -} - -Middlegame int16_t center(const chess::Board &pos) -{ - const chess::Square centerSquares[] = {chess::Square::SQ_E4, chess::Square::SQ_D4, chess::Square::SQ_E5, - chess::Square::SQ_D5}; - - int w = 0, b = 0; - - for (chess::Square sq : centerSquares) - { - // Count attackers - w += chess::attacks::attackers(pos, chess::Color::WHITE, sq).count(); - b += chess::attacks::attackers(pos, chess::Color::BLACK, sq).count(); - auto p = pos.at(sq); - if (p.color() == chess::Color::WHITE) - ++w; - if (p.color() == chess::Color::BLACK) - ++b; - } - - wEval.Center = w * Center; - bEval.Center = b * Center; - return wEval.Center - bEval.Center; -} -/* Pawn cache */ -struct PawnEntry -{ - uint64_t key; // Full pawn hash key (Zobrist) - int16_t evalWhite; // Pawn eval score for White - int16_t evalBlack; // Pawn eval score for Black -}; - -std::vector pawnHashTable(1 << 17); -template -AllPhases int pawn(const chess::Board &pos) -{ - if (trace) - { - return doubled(pos) + isolated(pos) + backward(pos) + center(pos); - } - else - { - // what? Nah. - auto a = pos.hash(); - auto entry = pawnHashTable[a & 131071]; - if (entry.key == a) - return entry.evalWhite - entry.evalBlack; - else - { - int16_t score = doubled(pos) + isolated(pos) + backward(pos) + center(pos); - PawnEntry entry = {a, - (int16_t)(wEval.Doubled + wEval.Isolated + wEval.Backward + wEval.Center), - (int16_t)(bEval.Doubled + bEval.Isolated + bEval.Backward + bEval.Center)}; - pawnHashTable[a & 131071] = entry; - return score; - } - } -} -// 2.4. Mobility -AllPhases int16_t mobility(const chess::Board &board) -{ - using namespace chess; - Bitboard occupied = board.occ(); - int mobilityScore[2] = {0, 0}; - const Color sides[2] = {Color::WHITE, Color::BLACK}; - - // Lookup table for attack functions per piece type - auto attackFuncs = [](Square sq, Bitboard occ, PieceType pt) -> Bitboard - { - switch (pt) - { - case (int)PieceType::KNIGHT: - return attacks::knight(sq); - case (int)PieceType::BISHOP: - return attacks::bishop(sq, occ); - case (int)PieceType::ROOK: - return attacks::rook(sq, occ); - case (int)PieceType::QUEEN: - return attacks::queen(sq, occ); - default: - return 0; - } + int eg_queen_table[64] = { + -9, 22, 22, 27, 27, 19, 10, 20, + -17, 20, 32, 41, 58, 25, 30, 0, + -20, 6, 9, 49, 47, 35, 19, 9, + 3, 22, 24, 45, 57, 40, 57, 36, + -18, 28, 19, 47, 31, 34, 39, 23, + -16, -27, 15, 6, 9, 17, 10, 5, + -22, -23, -30, -16, -16, -23, -36, -32, + -33, -28, -22, -43, -5, -32, -20, -41, }; - for (Color side : sides) - { - Bitboard us = board.us(side); - - for (PieceType pt : {PieceType::KNIGHT, PieceType::BISHOP, PieceType::ROOK, PieceType::QUEEN}) - { - int ptIndex = static_cast(pt); - Bitboard pieces = board.pieces(pt) & us; - while (pieces) - { - Square sq = pieces.pop(); - Bitboard attacks = attackFuncs(sq, occupied, pt); - attacks &= ~us; // remove friendly squares - mobilityScore[static_cast(side)] += MOBILITY_WEIGHTS[ptIndex] * attacks.count(); - } - } - } - - wEval.Mobility = mobilityScore[0]; - bEval.Mobility = mobilityScore[1]; - return mobilityScore[0] - mobilityScore[1]; -} - -// 2.5. King safety -// 2.5.1. Pawn shield -Middlegame int16_t pawn_shield(const chess::Board &board) -{ - constexpr chess::Bitboard SHIELD = 0x00ff00000000ff00ULL; // rank 2 and 7 for pawn shield - constexpr chess::Bitboard SHIELD2 = 0x0000ff0000ff0000ULL; // rank 3 and 6 for isolated pawns - chess::Bitboard wkingAttacksBB = chess::attacks::king(board.kingSq(chess::Color::WHITE)), - bKingAttacksBB = chess::attacks::king(board.kingSq(chess::Color::BLACK)); - int wshieldCount1 = (wkingAttacksBB & SHIELD).count(), wshieldCount2 = (wkingAttacksBB & SHIELD2).count(); - int bshieldCount1 = (bKingAttacksBB & SHIELD).count(), bshieldCount2 = (bKingAttacksBB & SHIELD2).count(); - wEval.Shield = (!wshieldCount1 * wshieldCount2 + wshieldCount1) * pawnShield; - bEval.Shield = (!bshieldCount1 * bshieldCount2 + bshieldCount1) * pawnShield; - return wEval.Shield - bEval.Shield; -} - -Middlegame int16_t king_tropism(const chess::Board &board) -{ - using namespace chess; - const Square wKing = board.kingSq(Color::WHITE); - const Square bKing = board.kingSq(Color::BLACK); + int mg_king_table[64] = { + -65, 23, 16, -15, -56, -34, 2, 13, + 29, -1, -20, -7, -8, -4, -38, -29, + -9, 24, 2, -16, -20, 6, 22, -22, + -17, -20, -12, -27, -30, -25, -14, -36, + -49, -1, -27, -39, -46, -44, -33, -51, + -14, -14, -22, -46, -44, -30, -15, -27, + 1, 7, -8, -64, -43, -16, 9, 8, + -15, 36, 12, -54, 8, -28, 24, 14, + }; - int wScore = 0, bScore = 0; + int eg_king_table[64] = { + -74, -35, -18, -18, -11, 15, 4, -17, + -12, 17, 14, 17, 17, 38, 23, 11, + 10, 17, 23, 15, 20, 45, 44, 13, + -8, 22, 24, 27, 26, 33, 26, 3, + -18, -4, 21, 24, 27, 23, 9, -11, + -19, -3, 11, 21, 23, 16, 7, -9, + -27, -11, 4, 13, 14, 4, -5, -17, + -53, -34, -21, -11, -28, -14, -24, -43 + }; - // Consider only attacking pieces - for (PieceType pt : {PieceType::QUEEN, PieceType::ROOK, PieceType::BISHOP, PieceType::KNIGHT}) + int *mg_pesto_table[] = { - Bitboard whiteAttackers = board.pieces(pt, Color::WHITE); - Bitboard blackAttackers = board.pieces(pt, Color::BLACK); - - while (whiteAttackers) - { - Square sq = whiteAttackers.pop(); - wScore += 7 - chess::Square::value_distance(sq, bKing); // closer = higher danger - } - - while (blackAttackers) - { - Square sq = blackAttackers.pop(); - bScore += 7 - chess::Square::value_distance(sq, wKing); - } - } - - // Scale the result - wEval.KingTropism = wScore * KingTropism; - bEval.KingTropism = bScore * KingTropism; - - return wEval.KingTropism - bEval.KingTropism; -} - -Middlegame inline int16_t king_safety(const chess::Board &board) { return pawn_shield(board) + king_tropism(board); } -// 2.6. Space -AllPhases int space(const chess::Board &board) -{ - int spaceS[2] = {0, 0}; // [WHITE, BLACK] + {0}, + mg_pawn_table, + mg_knight_table, + mg_bishop_table, + mg_rook_table, + mg_queen_table, + mg_king_table + }; - // Evaluate space control for both sides - for (chess::Color color : {chess::Color::WHITE, chess::Color::BLACK}) + int *eg_pesto_table[] = { - chess::Color opponent = ~color; - - // Space Evaluation: Count controlled squares in the opponent's half - chess::Bitboard my_pawns = board.pieces(chess::PieceType::PAWN, color); + {0}, + eg_pawn_table, + eg_knight_table, + eg_bishop_table, + eg_rook_table, + eg_queen_table, + eg_king_table + }; - while (my_pawns) + Value eval(const chess::Board &board){ + int pieceCount[10] = { + board.count(), + board.count(), + board.count(), + board.count(), + board.count(), + board.count(), + board.count(), + board.count(), + board.count(), + board.count(), + }; + int material = (pieceCount[0] * PawnValue + pieceCount[1] * KnightValue + pieceCount[2] * BishopValue + pieceCount[3] * RookValue + pieceCount[4] * QueenValue) + - (pieceCount[5] * PawnValue + pieceCount[6] * KnightValue + pieceCount[7] * BishopValue + pieceCount[8] * RookValue + pieceCount[9] * QueenValue); + constexpr int KnightPhase = 1; + constexpr int BishopPhase = 1; + constexpr int RookPhase = 2; + constexpr int QueenPhase = 4; + constexpr int TotalPhase = KnightPhase * 4 + BishopPhase * 4 + RookPhase * 4 + QueenPhase * 2; + + int phase = (pieceCount[1] + pieceCount[6]) * KnightPhase + + (pieceCount[2] + pieceCount[7]) * BishopPhase + + (pieceCount[3] + pieceCount[8]) * RookPhase + + (pieceCount[4] + pieceCount[9]) * QueenPhase; + + phase = (phase * 256 + TotalPhase / 2) / TotalPhase; + const int sign = board.sideToMove() == chess::Color::WHITE ? 1 : -1; + + int mgScore = material; + int egScore = material; { - chess::Square sq = my_pawns.pop(); - chess::Bitboard pawn_attacks = chess::attacks::pawn(color, sq); - - while (pawn_attacks) - { - chess::Square target = pawn_attacks.pop(); - // Check if square is in opponent's half - bool in_opponent_half = - (color == chess::Color::WHITE && target >= 32) || (color == chess::Color::BLACK && target < 32); - - if (in_opponent_half && !board.isAttacked(target, opponent) && !board.at(target)) - { - spaceS[static_cast(color)]++; - } + Bitboard occ = board.occ(); + while (occ) { + Square i = (Square)pop_lsb(occ); + auto p = board.at(i); + mgScore += mg_pesto_table[piece_of(p)][color_of(p) == BLACK ? square_mirror(i) : i]; + egScore += eg_pesto_table[piece_of(p)][color_of(p) == BLACK ? square_mirror(i) : i]; } } + int finalScore = ((mgScore * phase) + (egScore * (256 - phase))) / 256 * sign; + return finalScore; } - wEval.Space = spaceS[0] * Space; - bEval.Space = spaceS[1] * Space; - return wEval.Space - bEval.Space; -} -// 2.7. Tempo (simple) (all phases) -AllPhases inline int16_t tempo(const chess::Board &board) -{ - return (board.sideToMove() == chess::Color::WHITE) ? Tempo : -Tempo; -} -// 2.8. Endgame -Endgame bool draw(const chess::Board &board) -{ - // KPvK with Rule of the Square - chess::Square wk = board.kingSq(chess::Color::WHITE), bk = board.kingSq(chess::Color::BLACK); - chess::Bitboard p = board.pieces(chess::PieceType::PAWN); - if (!board.hasNonPawnMaterial(chess::Color::WHITE) && !board.hasNonPawnMaterial(chess::Color::BLACK) && p.count() == 1) - { - chess::Square pawn = p.pop(); - auto c = board.at(pawn).color(); - chess::Square promoSq(pawn.file(), chess::Rank::rank(chess::Rank::RANK_8, c)); - if (chess::Square::value_distance((c ? bk : wk), promoSq) <= chess::Square::value_distance(pawn, promoSq)) - return 1; - } - return board.isInsufficientMaterial(); -} -template -AllPhases int16_t psqt_eval(const chess::Board &board) -{ - auto pieces = board.us(chess::Color::WHITE); - - while (pieces) - { - int sq = pieces.pop(); - chess::PieceType piece = board.at(sq); - if constexpr (Midgame) - wEval.MGPSQT += mg_pesto_table[(int)piece][sq]; - else - wEval.EGPSQT += eg_pesto_table[(int)piece][sq]; - } - pieces = board.us(chess::Color::BLACK); - - while (pieces) - { - chess::Square sq = pieces.pop(); - chess::PieceType piece = board.at(sq); - sq.flip(); - if constexpr (Midgame) - bEval.MGPSQT += mg_pesto_table[(int)piece][sq.index()]; - else - bEval.EGPSQT += eg_pesto_table[(int)piece][sq.index()]; - } - if constexpr (Midgame) - return wEval.MGPSQT - bEval.MGPSQT; // Negate for Black - else - return wEval.EGPSQT - bEval.EGPSQT; // Negate for Black -} -// huge table for distances, idk but it gives O(1) access -static const unsigned char chebyshev_distance[64][64] = { - {0, 1, 2, 3, 4, 5, 6, 7, 1, 1, 2, 3, 4, 5, 6, 7, 2, 2, 2, 3, 4, 5, 6, 7, 3, 3, 3, 3, 4, 5, 6, 7, 4, 4, 4, 4, 4, 5, 6, 7, 5, 5, 5, 5, 5, 5, 6, 7, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7}, - {1, 0, 1, 2, 3, 4, 5, 6, 1, 1, 1, 2, 3, 4, 5, 6, 2, 2, 2, 2, 3, 4, 5, 6, 3, 3, 3, 3, 3, 4, 5, 6, 4, 4, 4, 4, 4, 4, 5, 6, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7}, - {2, 1, 0, 1, 2, 3, 4, 5, 2, 1, 1, 1, 2, 3, 4, 5, 2, 2, 2, 2, 2, 3, 4, 5, 3, 3, 3, 3, 3, 3, 4, 5, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7}, - {3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7}, - {4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7}, - {5, 4, 3, 2, 1, 0, 1, 2, 5, 4, 3, 2, 1, 1, 1, 2, 5, 4, 3, 2, 2, 2, 2, 2, 5, 4, 3, 3, 3, 3, 3, 3, 5, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7}, - {6, 5, 4, 3, 2, 1, 0, 1, 6, 5, 4, 3, 2, 1, 1, 1, 6, 5, 4, 3, 2, 2, 2, 2, 6, 5, 4, 3, 3, 3, 3, 3, 6, 5, 4, 4, 4, 4, 4, 4, 6, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7}, - {7, 6, 5, 4, 3, 2, 1, 0, 7, 6, 5, 4, 3, 2, 1, 1, 7, 6, 5, 4, 3, 2, 2, 2, 7, 6, 5, 4, 3, 3, 3, 3, 7, 6, 5, 4, 4, 4, 4, 4, 7, 6, 5, 5, 5, 5, 5, 5, 7, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7}, - {1, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, 1, 1, 2, 3, 4, 5, 6, 7, 2, 2, 2, 3, 4, 5, 6, 7, 3, 3, 3, 3, 4, 5, 6, 7, 4, 4, 4, 4, 4, 5, 6, 7, 5, 5, 5, 5, 5, 5, 6, 7, 6, 6, 6, 6, 6, 6, 6, 7}, - {1, 1, 1, 2, 3, 4, 5, 6, 1, 0, 1, 2, 3, 4, 5, 6, 1, 1, 1, 2, 3, 4, 5, 6, 2, 2, 2, 2, 3, 4, 5, 6, 3, 3, 3, 3, 3, 4, 5, 6, 4, 4, 4, 4, 4, 4, 5, 6, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6}, - {2, 1, 1, 1, 2, 3, 4, 5, 2, 1, 0, 1, 2, 3, 4, 5, 2, 1, 1, 1, 2, 3, 4, 5, 2, 2, 2, 2, 2, 3, 4, 5, 3, 3, 3, 3, 3, 3, 4, 5, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6}, - {3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6}, - {4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6}, - {5, 4, 3, 2, 1, 1, 1, 2, 5, 4, 3, 2, 1, 0, 1, 2, 5, 4, 3, 2, 1, 1, 1, 2, 5, 4, 3, 2, 2, 2, 2, 2, 5, 4, 3, 3, 3, 3, 3, 3, 5, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6}, - {6, 5, 4, 3, 2, 1, 1, 1, 6, 5, 4, 3, 2, 1, 0, 1, 6, 5, 4, 3, 2, 1, 1, 1, 6, 5, 4, 3, 2, 2, 2, 2, 6, 5, 4, 3, 3, 3, 3, 3, 6, 5, 4, 4, 4, 4, 4, 4, 6, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6}, - {7, 6, 5, 4, 3, 2, 1, 1, 7, 6, 5, 4, 3, 2, 1, 0, 7, 6, 5, 4, 3, 2, 1, 1, 7, 6, 5, 4, 3, 2, 2, 2, 7, 6, 5, 4, 3, 3, 3, 3, 7, 6, 5, 4, 4, 4, 4, 4, 7, 6, 5, 5, 5, 5, 5, 5, 7, 6, 6, 6, 6, 6, 6, 6}, - {2, 2, 2, 3, 4, 5, 6, 7, 1, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, 1, 1, 2, 3, 4, 5, 6, 7, 2, 2, 2, 3, 4, 5, 6, 7, 3, 3, 3, 3, 4, 5, 6, 7, 4, 4, 4, 4, 4, 5, 6, 7, 5, 5, 5, 5, 5, 5, 6, 7}, - {2, 2, 2, 2, 3, 4, 5, 6, 1, 1, 1, 2, 3, 4, 5, 6, 1, 0, 1, 2, 3, 4, 5, 6, 1, 1, 1, 2, 3, 4, 5, 6, 2, 2, 2, 2, 3, 4, 5, 6, 3, 3, 3, 3, 3, 4, 5, 6, 4, 4, 4, 4, 4, 4, 5, 6, 5, 5, 5, 5, 5, 5, 5, 6}, - {2, 2, 2, 2, 2, 3, 4, 5, 2, 1, 1, 1, 2, 3, 4, 5, 2, 1, 0, 1, 2, 3, 4, 5, 2, 1, 1, 1, 2, 3, 4, 5, 2, 2, 2, 2, 2, 3, 4, 5, 3, 3, 3, 3, 3, 3, 4, 5, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5}, - {3, 2, 2, 2, 2, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5}, - {4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5}, - {5, 4, 3, 2, 2, 2, 2, 2, 5, 4, 3, 2, 1, 1, 1, 2, 5, 4, 3, 2, 1, 0, 1, 2, 5, 4, 3, 2, 1, 1, 1, 2, 5, 4, 3, 2, 2, 2, 2, 2, 5, 4, 3, 3, 3, 3, 3, 3, 5, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5}, - {6, 5, 4, 3, 2, 2, 2, 2, 6, 5, 4, 3, 2, 1, 1, 1, 6, 5, 4, 3, 2, 1, 0, 1, 6, 5, 4, 3, 2, 1, 1, 1, 6, 5, 4, 3, 2, 2, 2, 2, 6, 5, 4, 3, 3, 3, 3, 3, 6, 5, 4, 4, 4, 4, 4, 4, 6, 5, 5, 5, 5, 5, 5, 5}, - {7, 6, 5, 4, 3, 2, 2, 2, 7, 6, 5, 4, 3, 2, 1, 1, 7, 6, 5, 4, 3, 2, 1, 0, 7, 6, 5, 4, 3, 2, 1, 1, 7, 6, 5, 4, 3, 2, 2, 2, 7, 6, 5, 4, 3, 3, 3, 3, 7, 6, 5, 4, 4, 4, 4, 4, 7, 6, 5, 5, 5, 5, 5, 5}, - {3, 3, 3, 3, 4, 5, 6, 7, 2, 2, 2, 3, 4, 5, 6, 7, 1, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, 1, 1, 2, 3, 4, 5, 6, 7, 2, 2, 2, 3, 4, 5, 6, 7, 3, 3, 3, 3, 4, 5, 6, 7, 4, 4, 4, 4, 4, 5, 6, 7}, - {3, 3, 3, 3, 3, 4, 5, 6, 2, 2, 2, 2, 3, 4, 5, 6, 1, 1, 1, 2, 3, 4, 5, 6, 1, 0, 1, 2, 3, 4, 5, 6, 1, 1, 1, 2, 3, 4, 5, 6, 2, 2, 2, 2, 3, 4, 5, 6, 3, 3, 3, 3, 3, 4, 5, 6, 4, 4, 4, 4, 4, 4, 5, 6}, - {3, 3, 3, 3, 3, 3, 4, 5, 2, 2, 2, 2, 2, 3, 4, 5, 2, 1, 1, 1, 2, 3, 4, 5, 2, 1, 0, 1, 2, 3, 4, 5, 2, 1, 1, 1, 2, 3, 4, 5, 2, 2, 2, 2, 2, 3, 4, 5, 3, 3, 3, 3, 3, 3, 4, 5, 4, 4, 4, 4, 4, 4, 4, 5}, - {3, 3, 3, 3, 3, 3, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4}, - {4, 3, 3, 3, 3, 3, 3, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4}, - {5, 4, 3, 3, 3, 3, 3, 3, 5, 4, 3, 2, 2, 2, 2, 2, 5, 4, 3, 2, 1, 1, 1, 2, 5, 4, 3, 2, 1, 0, 1, 2, 5, 4, 3, 2, 1, 1, 1, 2, 5, 4, 3, 2, 2, 2, 2, 2, 5, 4, 3, 3, 3, 3, 3, 3, 5, 4, 4, 4, 4, 4, 4, 4}, - {6, 5, 4, 3, 3, 3, 3, 3, 6, 5, 4, 3, 2, 2, 2, 2, 6, 5, 4, 3, 2, 1, 1, 1, 6, 5, 4, 3, 2, 1, 0, 1, 6, 5, 4, 3, 2, 1, 1, 1, 6, 5, 4, 3, 2, 2, 2, 2, 6, 5, 4, 3, 3, 3, 3, 3, 6, 5, 4, 4, 4, 4, 4, 4}, - {7, 6, 5, 4, 3, 3, 3, 3, 7, 6, 5, 4, 3, 2, 2, 2, 7, 6, 5, 4, 3, 2, 1, 1, 7, 6, 5, 4, 3, 2, 1, 0, 7, 6, 5, 4, 3, 2, 1, 1, 7, 6, 5, 4, 3, 2, 2, 2, 7, 6, 5, 4, 3, 3, 3, 3, 7, 6, 5, 4, 4, 4, 4, 4}, - {4, 4, 4, 4, 4, 5, 6, 7, 3, 3, 3, 3, 4, 5, 6, 7, 2, 2, 2, 3, 4, 5, 6, 7, 1, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, 1, 1, 2, 3, 4, 5, 6, 7, 2, 2, 2, 3, 4, 5, 6, 7, 3, 3, 3, 3, 4, 5, 6, 7}, - {4, 4, 4, 4, 4, 4, 5, 6, 3, 3, 3, 3, 3, 4, 5, 6, 2, 2, 2, 2, 3, 4, 5, 6, 1, 1, 1, 2, 3, 4, 5, 6, 1, 0, 1, 2, 3, 4, 5, 6, 1, 1, 1, 2, 3, 4, 5, 6, 2, 2, 2, 2, 3, 4, 5, 6, 3, 3, 3, 3, 3, 4, 5, 6}, - {4, 4, 4, 4, 4, 4, 4, 5, 3, 3, 3, 3, 3, 3, 4, 5, 2, 2, 2, 2, 2, 3, 4, 5, 2, 1, 1, 1, 2, 3, 4, 5, 2, 1, 0, 1, 2, 3, 4, 5, 2, 1, 1, 1, 2, 3, 4, 5, 2, 2, 2, 2, 2, 3, 4, 5, 3, 3, 3, 3, 3, 3, 4, 5}, - {4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 3, 3, 3, 3, 3, 3, 4}, - {4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 3, 3, 3, 3, 3, 3}, - {5, 4, 4, 4, 4, 4, 4, 4, 5, 4, 3, 3, 3, 3, 3, 3, 5, 4, 3, 2, 2, 2, 2, 2, 5, 4, 3, 2, 1, 1, 1, 2, 5, 4, 3, 2, 1, 0, 1, 2, 5, 4, 3, 2, 1, 1, 1, 2, 5, 4, 3, 2, 2, 2, 2, 2, 5, 4, 3, 3, 3, 3, 3, 3}, - {6, 5, 4, 4, 4, 4, 4, 4, 6, 5, 4, 3, 3, 3, 3, 3, 6, 5, 4, 3, 2, 2, 2, 2, 6, 5, 4, 3, 2, 1, 1, 1, 6, 5, 4, 3, 2, 1, 0, 1, 6, 5, 4, 3, 2, 1, 1, 1, 6, 5, 4, 3, 2, 2, 2, 2, 6, 5, 4, 3, 3, 3, 3, 3}, - {7, 6, 5, 4, 4, 4, 4, 4, 7, 6, 5, 4, 3, 3, 3, 3, 7, 6, 5, 4, 3, 2, 2, 2, 7, 6, 5, 4, 3, 2, 1, 1, 7, 6, 5, 4, 3, 2, 1, 0, 7, 6, 5, 4, 3, 2, 1, 1, 7, 6, 5, 4, 3, 2, 2, 2, 7, 6, 5, 4, 3, 3, 3, 3}, - {5, 5, 5, 5, 5, 5, 6, 7, 4, 4, 4, 4, 4, 5, 6, 7, 3, 3, 3, 3, 4, 5, 6, 7, 2, 2, 2, 3, 4, 5, 6, 7, 1, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, 1, 1, 2, 3, 4, 5, 6, 7, 2, 2, 2, 3, 4, 5, 6, 7}, - {5, 5, 5, 5, 5, 5, 5, 6, 4, 4, 4, 4, 4, 4, 5, 6, 3, 3, 3, 3, 3, 4, 5, 6, 2, 2, 2, 2, 3, 4, 5, 6, 1, 1, 1, 2, 3, 4, 5, 6, 1, 0, 1, 2, 3, 4, 5, 6, 1, 1, 1, 2, 3, 4, 5, 6, 2, 2, 2, 2, 3, 4, 5, 6}, - {5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 5, 3, 3, 3, 3, 3, 3, 4, 5, 2, 2, 2, 2, 2, 3, 4, 5, 2, 1, 1, 1, 2, 3, 4, 5, 2, 1, 0, 1, 2, 3, 4, 5, 2, 1, 1, 1, 2, 3, 4, 5, 2, 2, 2, 2, 2, 3, 4, 5}, - {5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4}, - {5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 2, 2, 2, 2, 3}, - {5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 5, 4, 3, 3, 3, 3, 3, 3, 5, 4, 3, 2, 2, 2, 2, 2, 5, 4, 3, 2, 1, 1, 1, 2, 5, 4, 3, 2, 1, 0, 1, 2, 5, 4, 3, 2, 1, 1, 1, 2, 5, 4, 3, 2, 2, 2, 2, 2}, - {6, 5, 5, 5, 5, 5, 5, 5, 6, 5, 4, 4, 4, 4, 4, 4, 6, 5, 4, 3, 3, 3, 3, 3, 6, 5, 4, 3, 2, 2, 2, 2, 6, 5, 4, 3, 2, 1, 1, 1, 6, 5, 4, 3, 2, 1, 0, 1, 6, 5, 4, 3, 2, 1, 1, 1, 6, 5, 4, 3, 2, 2, 2, 2}, - {7, 6, 5, 5, 5, 5, 5, 5, 7, 6, 5, 4, 4, 4, 4, 4, 7, 6, 5, 4, 3, 3, 3, 3, 7, 6, 5, 4, 3, 2, 2, 2, 7, 6, 5, 4, 3, 2, 1, 1, 7, 6, 5, 4, 3, 2, 1, 0, 7, 6, 5, 4, 3, 2, 1, 1, 7, 6, 5, 4, 3, 2, 2, 2}, - {6, 6, 6, 6, 6, 6, 6, 7, 5, 5, 5, 5, 5, 5, 6, 7, 4, 4, 4, 4, 4, 5, 6, 7, 3, 3, 3, 3, 4, 5, 6, 7, 2, 2, 2, 3, 4, 5, 6, 7, 1, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, 1, 1, 2, 3, 4, 5, 6, 7}, - {6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 6, 4, 4, 4, 4, 4, 4, 5, 6, 3, 3, 3, 3, 3, 4, 5, 6, 2, 2, 2, 2, 3, 4, 5, 6, 1, 1, 1, 2, 3, 4, 5, 6, 1, 0, 1, 2, 3, 4, 5, 6, 1, 1, 1, 2, 3, 4, 5, 6}, - {6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 5, 3, 3, 3, 3, 3, 3, 4, 5, 2, 2, 2, 2, 2, 3, 4, 5, 2, 1, 1, 1, 2, 3, 4, 5, 2, 1, 0, 1, 2, 3, 4, 5, 2, 1, 1, 1, 2, 3, 4, 5}, - {6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4}, - {6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3}, - {6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 5, 4, 3, 3, 3, 3, 3, 3, 5, 4, 3, 2, 2, 2, 2, 2, 5, 4, 3, 2, 1, 1, 1, 2, 5, 4, 3, 2, 1, 0, 1, 2, 5, 4, 3, 2, 1, 1, 1, 2}, - {6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 6, 5, 4, 4, 4, 4, 4, 4, 6, 5, 4, 3, 3, 3, 3, 3, 6, 5, 4, 3, 2, 2, 2, 2, 6, 5, 4, 3, 2, 1, 1, 1, 6, 5, 4, 3, 2, 1, 0, 1, 6, 5, 4, 3, 2, 1, 1, 1}, - {7, 6, 6, 6, 6, 6, 6, 6, 7, 6, 5, 5, 5, 5, 5, 5, 7, 6, 5, 4, 4, 4, 4, 4, 7, 6, 5, 4, 3, 3, 3, 3, 7, 6, 5, 4, 3, 2, 2, 2, 7, 6, 5, 4, 3, 2, 1, 1, 7, 6, 5, 4, 3, 2, 1, 0, 7, 6, 5, 4, 3, 2, 1, 1}, - {7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 7, 5, 5, 5, 5, 5, 5, 6, 7, 4, 4, 4, 4, 4, 5, 6, 7, 3, 3, 3, 3, 4, 5, 6, 7, 2, 2, 2, 3, 4, 5, 6, 7, 1, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7}, - {7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 6, 4, 4, 4, 4, 4, 4, 5, 6, 3, 3, 3, 3, 3, 4, 5, 6, 2, 2, 2, 2, 3, 4, 5, 6, 1, 1, 1, 2, 3, 4, 5, 6, 1, 0, 1, 2, 3, 4, 5, 6}, - {7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 5, 3, 3, 3, 3, 3, 3, 4, 5, 2, 2, 2, 2, 2, 3, 4, 5, 2, 1, 1, 1, 2, 3, 4, 5, 2, 1, 0, 1, 2, 3, 4, 5}, - {7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4}, - {7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3}, - {7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 5, 4, 3, 3, 3, 3, 3, 3, 5, 4, 3, 2, 2, 2, 2, 2, 5, 4, 3, 2, 1, 1, 1, 2, 5, 4, 3, 2, 1, 0, 1, 2}, - {7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 6, 5, 4, 4, 4, 4, 4, 4, 6, 5, 4, 3, 3, 3, 3, 3, 6, 5, 4, 3, 2, 2, 2, 2, 6, 5, 4, 3, 2, 1, 1, 1, 6, 5, 4, 3, 2, 1, 0, 1}, - {7, 7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 7, 6, 5, 5, 5, 5, 5, 5, 7, 6, 5, 4, 4, 4, 4, 4, 7, 6, 5, 4, 3, 3, 3, 3, 7, 6, 5, 4, 3, 2, 2, 2, 7, 6, 5, 4, 3, 2, 1, 1, 7, 6, 5, 4, 3, 2, 1, 0} -}; -inline int distance_to_corner(uint8_t sq){ - return std::min({chebyshev_distance[sq][0], // A1 - chebyshev_distance[sq][56], // A8 - chebyshev_distance[sq][7], // H1 - chebyshev_distance[sq][63]});// H8 -} -inline int distance_from_center(uint8_t sq){ - return std::min({chebyshev_distance[sq][34], // E4 - chebyshev_distance[sq][35], // D4 - chebyshev_distance[sq][42], // E5 - chebyshev_distance[sq][43]});// D5 -} -int mopUp(const chess::Board& pos) { - chess::Square whiteKingSq = pos.kingSq(chess::Color::WHITE); - chess::Square blackKingSq = pos.kingSq(chess::Color::BLACK); - - int whiteMaterial = wEval.Material; - int blackMaterial = bEval.Material; - int materialAdvantage = whiteMaterial - blackMaterial; - - // If neither side has a big material advantage, skip mop-up - if (std::abs(materialAdvantage) < 300) return 0; - - if (wEval.phase>64) return 0; - - // Distance to corner for both kings - int whiteCornerDist = distance_to_corner(whiteKingSq.index()); - int blackCornerDist = distance_to_corner(blackKingSq.index()); - - // Distance from center for both kings - int whiteCenterDist = distance_from_center(whiteKingSq.index()); - int blackCenterDist = distance_from_center(blackKingSq.index()); - - // White mop-up score (push black king to corner, bring white king to center) - int whiteMopUp = blackCornerDist * 4 - whiteCenterDist * 3; - - // Black mop-up score (push white king to corner, bring black king to center) - int blackMopUp = whiteCornerDist * 4 - blackCenterDist * 3; - - // Return net mop-up score favoring White (+ means White advantage) - return whiteMopUp - blackMopUp; -} - -// 3. Main evaluation -inline int16_t eg(const chess::Board &pos) -{ - if (draw(pos)) - return 0; - return space(pos) + tempo(pos) + material(pos) + passed(pos) + psqt_eval(pos) + mobility(pos) + king_safety(pos) + mopUp(pos); -} -template -inline int16_t mg(const chess::Board &pos) -{ - return material(pos) + space(pos) + tempo(pos) + mobility(pos) + king_safety(pos) + psqt_eval(pos) + pawn(pos); -} -struct CachedEvalEntry -{ - uint64_t key; // Full pawn hash key (Zobrist) - int16_t eval; -}; - -std::vector evalCache(1 << 17); -template -int16_t eval(const chess::Board &pos) -{ - auto hash = pos.hash(); - auto &entry = evalCache[hash & 131071]; - if constexpr (!trace) - if (entry.key == hash) - return entry.eval; - memset(&wEval, 0, sizeof(wEval)); - memset(&bEval, 0, sizeof(bEval)); - const int phase = ::phase(pos); - const int sign = pos.sideToMove() == chess::Color::WHITE ? 1 : -1; - - int mgScore = mg(pos); - int egScore = eg(pos); - int finalScore = ((mgScore * phase) + (egScore * (256 - phase))) / 256 * sign; - - return finalScore; -} -int16_t eval(const chess::Board &pos) -{ - return eval(pos); -} -void traceEvaluationResults(const char *label = nullptr, int16_t _eval = 0) -{ - if (label) - printf("\n[Trace: %s]\n", label); - - printf("+----------------+-------+-------+\n"); - printf("| Factor | White | Black |\n"); - printf("+----------------+-------+-------+\n"); - printf("| Phase | %13d |\n", wEval.phase); - printf("| Material | %5d | %5d |\n", wEval.Material, bEval.Material); - printf("| Doubled | %5d | %5d |\n", wEval.Doubled, bEval.Doubled); - printf("| Isolated | %5d | %5d |\n", wEval.Isolated, bEval.Isolated); - printf("| Backward | %5d | %5d |\n", wEval.Backward, bEval.Backward); - printf("| Passed | %5d | %5d |\n", wEval.Passed, bEval.Passed); - printf("| King tropism | %5d | %5d |\n", wEval.KingTropism, bEval.KingTropism); - printf("| Center control | %5d | %5d |\n", wEval.Center, bEval.Center); - printf("| Mobility | %5d | %5d |\n", wEval.Mobility, bEval.Mobility); - printf("| Shield | %5d | %5d |\n", wEval.Shield, bEval.Shield); - printf("| Space | %5d | %5d |\n", wEval.Space, bEval.Space); - printf("| MiddlegamePSQT | %5d | %5d |\n", wEval.MGPSQT, bEval.MGPSQT); - printf("| EndgamePSQT | %5d | %5d |\n", wEval.EGPSQT, bEval.EGPSQT); - printf("| Total | %13d |\n", _eval); - printf("+----------------+-------+-------+\n"); -} -void trace(const chess::Board &pos) { traceEvaluationResults("Handcrafted", eval(pos)); } -int16_t piece_value(chess::PieceType p) -{ - switch ((int)p) - { - case (int)chess::PieceType::PAWN: - return PawnValue; - case (int)chess::PieceType::KNIGHT: - return KnightValue; - case (int)chess::PieceType::BISHOP: - return BishopValue; - case (int)chess::PieceType::ROOK: - return RookValue; - case (int)chess::PieceType::QUEEN: - return QueenValue; - default: - return 0; - } -} +} \ No newline at end of file diff --git a/eval.h b/eval.h new file mode 100644 index 0000000..9c46b48 --- /dev/null +++ b/eval.h @@ -0,0 +1,37 @@ +#pragma once +#include +#include +#include +using Value = int; +namespace engine{ + + constexpr int MAX_PLY = 64; + constexpr Value VALUE_ZERO = 0; + constexpr Value VALUE_DRAW = 0; + constexpr Value VALUE_NONE = 32002; + constexpr Value VALUE_INFINITE = 32001; + + constexpr Value VALUE_MATE = 32000; + constexpr Value VALUE_MATE_IN_MAX_PLY = VALUE_MATE - MAX_PLY; + constexpr Value VALUE_MATED_IN_MAX_PLY = -VALUE_MATE_IN_MAX_PLY; + + constexpr Value VALUE_TB = VALUE_MATE_IN_MAX_PLY - 1; + constexpr Value VALUE_TB_WIN_IN_MAX_PLY = VALUE_TB - MAX_PLY; + constexpr Value VALUE_TB_LOSS_IN_MAX_PLY = -VALUE_TB_WIN_IN_MAX_PLY; + constexpr bool is_valid(Value value) { return value != VALUE_NONE; } + + constexpr bool is_win(Value value) { + return value >= VALUE_TB_WIN_IN_MAX_PLY; + } + + constexpr bool is_loss(Value value) { + return value <= VALUE_TB_LOSS_IN_MAX_PLY; + } + + constexpr bool is_decisive(Value value) { return is_win(value) || is_loss(value); } + constexpr Value MATE(int i) { return VALUE_MATE - i; } + constexpr Value MATE_DISTANCE(int i) { return VALUE_MATE - (i<0?-i:i); } + namespace eval { + Value eval(const chess::Board &board); + } +} \ No newline at end of file diff --git a/eval.hpp b/eval.hpp deleted file mode 100644 index 08f98db..0000000 --- a/eval.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once -#include "chess.hpp" -#include -#include - -constexpr int MAX_PLY = 64; -constexpr int MAX_MATE = 32'000; -constexpr int MAX_SCORE_CP = 31'000; -inline int16_t MATE(int i) { return MAX_MATE - i; } -inline int16_t MATE_DISTANCE(int16_t i) { return MAX_MATE - abs(i); } -int16_t eval(const chess::Board &board); -void trace(const chess::Board &board); -int16_t piece_value(chess::PieceType); -// some clarity for documentation -#define Evaluation [[nodiscard]] -#define AllPhases Evaluation -#define Middlegame Evaluation -#define Endgame Evaluation diff --git a/main.cpp b/main.cpp index 1ec23e2..296b478 100644 --- a/main.cpp +++ b/main.cpp @@ -1,15 +1,20 @@ -#include "chess.hpp" -#include "search.hpp" -#include "ucioptions.hpp" -#include "uci.hpp" +#include "ucioption.h" +#include "uci.h" +#include "search.h" #include -#include // for setbuf +using namespace engine; int main() { - // Some quirks competition programming only for flushing buffers - setbuf(stdout, NULL); - UCIOptions::addSpin("Hash", 16, 1, 1024, [](const UCIOptions::Option& opt)->void { - search::tt.resize(std::get(opt.value)); - }); - uci_loop(); - return 0; -} + options.add("Move Overhead", Option(10, 0, 1000)); + options.add( // + "Hash", Option(16, 1, 1<<25, [](const Option &o) { + search::tt.resize((o.operator int())); + return std::nullopt; + })); + + options.add( // + "Clear Hash", Option(+[](const Option &) { + search::tt.clear(); + return std::nullopt; + })); + loop(); +} \ No newline at end of file diff --git a/movepick.cpp b/movepick.cpp index 74452c0..9d26651 100644 --- a/movepick.cpp +++ b/movepick.cpp @@ -1,185 +1,10 @@ -#include "movepick.hpp" - -namespace movepick -{ - // 1. SEE - - // Return type changed to chess::Square for consistency - chess::Square select_least_valuable(chess::Board &board, chess::Bitboard bb) - { - if (bb.empty()) - return chess::Square::NO_SQ; // Use NO_SQ sentinel for no square - - int least_value = 5000; - chess::Square least_valuable_square = chess::Square::NO_SQ; - - while (bb) - { - int idx = bb.pop(); // idx is int index of bitboard square - chess::Square sq(idx); - int value = piece_value(board.at(sq)); - if (value < least_value) - { - least_value = value; - least_valuable_square = sq; - } - } - return least_valuable_square; - } - - int SEE(chess::Board &board, chess::Move move) - { - constexpr int max_gain_size = MAX_PLY + 1; // To avoid overflow in gain[d + 1] - std::array gain{}; - int d = 0; - chess::Color side = board.sideToMove(); - chess::Bitboard occ = board.occ(); - - chess::Square target_sq = move.to(); - - // Initial gain setup: - if (move.typeOf() & chess::Move::PROMOTION) - { - gain[0] = piece_value(move.promotionType()); - if (board.isCapture(move)) - gain[0] += piece_value(board.at(target_sq)); - } - else if (move.typeOf() == chess::Move::ENPASSANT) - { - // We'll treat it normally below, so just assign captured pawn value now: - gain[0] = piece_value(chess::PieceType::PAWN); - } - else - { - gain[0] = piece_value(board.at(target_sq)); - } - - chess::Square from_sq = move.from(); - - // Remove the moving piece from occupancy: - occ.clear(from_sq.index()); - - // Special case for en passant: remove the captured pawn square here: - if (move.typeOf() == chess::Move::ENPASSANT) - { - chess::Square ep_captured_sq(target_sq.file(), from_sq.rank()); // square behind target - occ.clear(ep_captured_sq.index()); - } - - chess::Square next_attacker_sq = from_sq; - - while (++d < MAX_PLY) - { - side = ~side; - - // Find all attackers of target square from side to move: - chess::Bitboard attackers = chess::attacks::attackers(board, side, target_sq) & occ; - - // Include x-ray attacks for sliding pieces: - chess::Bitboard rook_attacks = chess::attacks::rook(target_sq, occ); - chess::Bitboard bishop_attacks = chess::attacks::bishop(target_sq, occ); - - attackers |= rook_attacks & (board.pieces(chess::PieceType::ROOK, side) | board.pieces(chess::PieceType::QUEEN, side)); - attackers |= bishop_attacks & (board.pieces(chess::PieceType::BISHOP, side) | board.pieces(chess::PieceType::QUEEN, side)); - - if (attackers.empty()) - break; - - next_attacker_sq = select_least_valuable(board, attackers); - if (next_attacker_sq == chess::Square::NO_SQ) - break; - - int captured_value = piece_value(board.at(next_attacker_sq)); - gain[d] = -gain[d - 1] + captured_value; - - if (std::max(-gain[d - 1], gain[d]) < 0) - break; - - occ.clear(next_attacker_sq.index()); - } - - while (--d > 0) - { - gain[d] = std::max(-gain[d + 1], gain[d]); - } - - return gain[0]; - } - - // 2. Killer moves - chess::Move killerMoves[MAX_PLY][2] = {}; - - void updateKillerMoves(chess::Move m, int ply) - { - if (killerMoves[ply][0] != m) - { - killerMoves[ply][1] = killerMoves[ply][0]; - killerMoves[ply][0] = m; - } - } - - // 3. History heuristic - int historyHeuristic[64][64] = {}; // from-square to to-square - - void updateHistoryHeuristic(chess::Move m, int depth) - { - historyHeuristic[m.from().index()][m.to().index()] += depth * depth; - } - - void orderMoves(chess::Board &board, chess::Movelist &moves, chess::Move ttMove, chess::Move pvMove, int ply) - { - for (auto &move : moves) - { - if (move == ttMove) - move.setScore(10000); - else if (move == pvMove) - move.setScore(9000); - else if (board.isCapture(move)) - move.setScore(SEE(board, move)); - else if (move == killerMoves[ply][0]) - move.setScore(8500); - else if (move == killerMoves[ply][1]) - move.setScore(8000); - else - move.setScore(historyHeuristic[move.from().index()][move.to().index()]); - } - // Sort moves by score in descending order - std::stable_sort(moves.begin(), moves.end(), - [](const chess::Move &a, const chess::Move &b) - { return a.score() > b.score(); }); - } - - void qOrderMoves(chess::Board &board, chess::Movelist &moves) - { - // clang-format off - int16_t promotion_table[4][8] = { - {-58, -38, -13, -28, -31, -27, -63, -99}, // N - {-14, -21, -11, -8, -7, -9, -17, -24}, // B - { 13, 10, 18, 15, 12, 12, 8, 5}, // R - { -9, 22, 22, 27, 27, 19, 10, 20}, // Q - }; - // clang-format on - - for (auto &move : moves) - { - if (board.isCapture(move)) // Simple MVV-LVA - { - move.setScore(SEE(board, move)); - } - else if (move.typeOf() & chess::Move::PROMOTION) - { - int promoValue = piece_value(move.promotionType()); - int captureValue = board.isCapture(move) ? piece_value(board.at(move.to())) : 0; - move.setScore(promoValue + captureValue + promotion_table[move.promotionType() - 1][move.to().file()]); - } - else - { - move.setScore(historyHeuristic[move.from().index()][move.to().index()]); - } - } - // Sort moves by score in descending order - std::stable_sort(moves.begin(), moves.end(), - [](const chess::Move &a, const chess::Move &b) - { return a.score() > b.score(); }); +#include "movepick.h" +#include "eval.h" +#include +using namespace chess; +namespace engine { + void movepick::orderMoves(chess::Board & board, chess::Movelist & moves, chess::Move ttMove, int ply) + { + std::vector> moves_; } } diff --git a/movepick.h b/movepick.h new file mode 100644 index 0000000..956e5e7 --- /dev/null +++ b/movepick.h @@ -0,0 +1,5 @@ +#pragma once +#include +namespace engine::movepick { + void orderMoves(chess::Board &, chess::Movelist &, chess::Move, int); +} \ No newline at end of file diff --git a/movepick.hpp b/movepick.hpp deleted file mode 100644 index ce46757..0000000 --- a/movepick.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once -#include "chess.hpp" -#include "eval.hpp" -namespace movepick -{ - // Static Exchange Evaluation - evaluates material exchange on a square - int SEE(chess::Board &, chess::Move); - // Updates killer moves table for move ordering - void updateKillerMoves(chess::Move, int); - // Updates history heuristic table for move ordering - void updateHistoryHeuristic(chess::Move, int); - // Orders moves for regular search (with TT move, killers at given ply) - void orderMoves(chess::Board &, chess::Movelist &, chess::Move, chess::Move, int); - // Orders moves for quiescence search (captures/promotions) - void qOrderMoves(chess::Board &, chess::Movelist &); - // Killer moves table: [ply][slot] for move ordering heuristic - extern chess::Move killerMoves[MAX_PLY][2]; -} \ No newline at end of file diff --git a/score.cpp b/score.cpp new file mode 100644 index 0000000..678d857 --- /dev/null +++ b/score.cpp @@ -0,0 +1,22 @@ +#include "score.h" +#include +namespace engine { + Score::Score(Value v) { + assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); + + if (!is_decisive(v)) + { + score = InternalUnits{ v }; + } + else if (std::abs(v) <= VALUE_TB) + { + auto distance = VALUE_TB - std::abs(v); + score = (v > 0) ? Tablebase{ distance, true } : Tablebase{ -distance, false }; + } + else + { + auto distance = VALUE_MATE - std::abs(v); + score = (v > 0) ? Mate{ distance } : Mate{ -distance }; + } + } +} \ No newline at end of file diff --git a/score.h b/score.h new file mode 100644 index 0000000..8949ab2 --- /dev/null +++ b/score.h @@ -0,0 +1,66 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) + + Stockfish 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. + + Stockfish 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 . +*/ + +#ifndef SCORE_H_INCLUDED +#define SCORE_H_INCLUDED + +#include +#include +#include "eval.h" +namespace engine { + + class Score { + public: + struct Mate { + int plies; + }; + + struct Tablebase { + int plies; + bool win; + }; + + struct InternalUnits { + int value; + }; + + Score() = default; + Score(Value v); + + template + bool is() const { + return std::holds_alternative(score); + } + + template + T get() const { + return std::get(score); + } + + template + decltype(auto) visit(F &&f) const { + return std::visit(std::forward(f), score); + } + + private: + std::variant score; + }; + +} + +#endif // #ifndef SCORE_H_INCLUDED \ No newline at end of file diff --git a/search.cpp b/search.cpp index 7ca18ff..abbd412 100644 --- a/search.cpp +++ b/search.cpp @@ -1,355 +1,191 @@ -#include "search.hpp" -#include "timeman.hpp" -#include -#include -#include -#include -#include - -namespace search +#include "search.h" +#include "eval.h" +#include "timeman.h" +#include "uci.h" +#include +#include +#include +using namespace chess; +namespace engine { - struct PV - { - int8_t cmove; - chess::Move pv[MAX_PLY]; - }; - - constexpr int LMR_BONUS = 2; - std::atomic stop_requested(false); - PV stablePV; - TranspositionTable tt; + TranspositionTable search::tt(16); + std::atomic stopSearch{false}; + void search::stop() + { + tt.clear(); + stopSearch.store(true, std::memory_order_relaxed); + } + struct Session + { + timeman::TimeManagement tm; + timeman::LimitsType tc; int seldepth = 0; - - struct SearchAbortException : public std::exception + uint64_t nodes = 0; + chess::Move pv[MAX_PLY][MAX_PLY]; + }; + void update_pv(Move *pv, Move move, const Move *childPv) + { + + for (*pv++ = move; childPv && *childPv != Move::none();) + *pv++ = *childPv++; + *pv = Move::none(); + } + Value doSearch(Board &board, int depth, Session &session, int ply = 0) + { + std::fill(std::begin(session.pv[ply + 1]), std::end(session.pv[ply + 1]), + Move::none()); + if (session.tm.elapsed() >= + session.tm.optimum() || + stopSearch.load(std::memory_order_relaxed)) + return VALUE_NONE; + if (board.is_draw(3)) { - SearchAbortException() = default; - }; - - inline bool isMateScore(int16_t score) + session.nodes++; + session.pv[ply][0] = Move::none(); + return 0; + } + session.seldepth = std::max(session.seldepth, ply); + uint64_t hash = board.hash(); + Move preferred = Move::none(); + if (TTEntry *entry = search::tt.lookup(hash)) { - return std::abs(score) > MAX_SCORE_CP; + if (entry->getDepth() >= depth) + { + preferred = Move(entry->getMove()); + } } - - int16_t adjustMateScore(int16_t score, int ply) + if (depth == 0) { - return isMateScore(score) ? (score > 0 ? score - ply : score + ply) : score; + session.nodes++; + return eval::eval(board); } - - int16_t restoreMateScore(int16_t score, int ply) + Value maxScore = -VALUE_INFINITE; + Movelist moves; + board.legals(moves); + if (!moves.size()) { - return isMateScore(score) ? (score > 0 ? score + ply : score - ply) : score; + // Checkmate or stalemate handling should go here if you implement it + session.pv[ply][0] = Move::none(); + return board.checkers() ? -MATE(ply) : 0; } - int16_t quiescence(chess::Board &board, int16_t alpha, int16_t beta, int ply) + for (Move move : moves) { - seldepth = std::max(seldepth, ply); - if (timeman::check_time() || stop_requested.load(std::memory_order_relaxed)) - throw SearchAbortException(); - ++nodes; + board.doMove(move); - int16_t stand_pat = eval(board); - if (stand_pat >= beta) - return beta; - if (alpha < stand_pat) - alpha = stand_pat; + // ---- RECURSIVE CALL ---- + Value childScore = doSearch(board, depth - 1, session, ply + 1); - chess::Movelist moves; - chess::movegen::legalmoves(moves, board); - if (moves.empty()) - return board.inCheck() ? -MATE(ply) : 0; + board.undoMove(); - chess::Movelist tacticalMoves; - for (auto mv : moves) - { - if (board.isCapture(mv) || (mv.typeOf() & chess::Move::PROMOTION) || board.givesCheck(mv) != chess::CheckType::NO_CHECK) - tacticalMoves.add(mv); - } + // ---- ABORT PROPAGATION ---- + if (childScore == VALUE_NONE) + return VALUE_NONE; - movepick::qOrderMoves(board, tacticalMoves); - for (auto mv : tacticalMoves) - { - // --- BEGIN SEE FILTER --- - int gain = mv.score(); // Or use precomputed SEE - if (board.isCapture(mv) && gain < 0) - continue; - // --- END SEE FILTER --- + Value score = -childScore; - // --- BEGIN DELTA PRUNING --- - int margin = 50; - if (stand_pat + gain + margin < alpha) - continue; - // --- END DELTA PRUNING --- + if (score > maxScore) + { + maxScore = score; + update_pv(session.pv[ply], move, session.pv[ply + 1]); + } - board.makeMove(mv); - int16_t score = -quiescence(board, -beta, -alpha, ply + 1); - board.unmakeMove(mv); - - if (score >= beta) - return beta; - if (score > alpha) - alpha = score; - } - return alpha; + // Local time check (optional but fine) + if (session.tm.elapsed() >= + session.tm.optimum() || + stopSearch.load(std::memory_order_relaxed)) + return VALUE_NONE; } - int16_t alpha_beta(chess::Board &board, int depth, int16_t alpha, int16_t beta, int ply, PV *pv, int ext = 0) + // Store only if valid + if (maxScore != -VALUE_INFINITE) + search::tt.store(hash, session.pv[ply][0], maxScore, depth, TTFlag::EXACT); + + return maxScore; + } + void search::search(const chess::Board &board, + const timeman::LimitsType timecontrol) + { + static double originalTimeAdjust = -1; + Session session; + session.tc = timecontrol; + session.tm.init(session.tc, board.sideToMove(), 0, originalTimeAdjust); + InfoFull lastInfo{}; + chess::Move lastPV[MAX_PLY]{}; + for (int i = 1; i < timecontrol.depth; i++) { - pv->cmove = 0; - if (timeman::check_time() || stop_requested.load(std::memory_order_relaxed)) - throw SearchAbortException(); - - uint64_t h = board.hash(); - TTEntry *entry = tt.lookup(h); - chess::Move hash_move = entry ? entry->bestMove : chess::Move::NULL_MOVE; - - if (entry && entry->hash == h && entry->depth() >= depth) - { - int16_t score = adjustMateScore(entry->score(), ply); - if (entry->flag() == TTFlag::LOWERBOUND && score >= beta) - return score; - if (entry->flag() == TTFlag::UPPERBOUND && score <= alpha) - return score; - if (entry->flag() == TTFlag::EXACT) - return score; - } - - chess::Movelist moves; - chess::movegen::legalmoves(moves, board); - if (moves.empty()) - return board.inCheck() ? -MATE(ply) : 0; - - if (depth <= 0){ - ++nodes; - return quiescence(board, alpha, beta, ply); - } - - bool in_check = board.inCheck(); - int16_t evalScore = eval(board); - - // --- BEGIN RAZORING --- - if (depth <= 2 && !in_check) - { - if (evalScore + 150 < alpha) - return quiescence(board, alpha, beta, ply); - } - // --- END RAZORING --- - - bool can_do_null = (!in_check) && (depth >= 3); - if (can_do_null) - { - board.makeNullMove(); - int R = depth / 6; - int16_t score = -alpha_beta(board, depth - 1 - R, -beta, -beta + 1, ply + 1, pv); - board.unmakeNullMove(); - if (score >= beta) - { - tt.store(h, chess::Move::NULL_MOVE, restoreMateScore(score, ply), depth, TTFlag::LOWERBOUND); - return score; - } - } - - bool has_hash = (hash_move != chess::Move::NULL_MOVE); - if (!has_hash && depth >= 4) - { - PV temp; - temp.cmove = 0; - alpha_beta(board, depth - 2, alpha, beta, ply, &temp); - TTEntry *iid = tt.lookup(board.hash()); - if (iid && iid->bestMove != chess::Move::NULL_MOVE) - hash_move = iid->bestMove; - } - - movepick::orderMoves(board, moves, hash_move, stablePV.pv[0], ply); - - chess::Move best_move = chess::Move(); - int16_t best_score = -MATE(0); - - for (int i = 0; i < moves.size(); ++i) - { - chess::Move mv = moves[i]; - PV sub_pv; - sub_pv.cmove = 0; - - bool isCap = board.isCapture(mv); - bool do_lmr = (i > 0) && !isCap && !in_check; - bool givesCheck = board.givesCheck(mv) != chess::CheckType::NO_CHECK; - bool isPromo = mv.typeOf() & chess::Move::PROMOTION; - - // --- BEGIN FUTILITY PRUNING --- - if (depth <= 3 && !in_check && !isCap && !isPromo && !givesCheck) - { - const int margin = 100 * depth; - if (evalScore + margin <= alpha) - continue; - } - // --- END FUTILITY PRUNING --- - - // --- BEGIN EXTENSIONS --- - int extension = 0; - if (givesCheck) - extension = 1; - - if (board.at(mv.from()) == chess::PieceType::PAWN && (mv.to().rank() >= chess::Rank::RANK_6)) - { - int rank = board.sideToMove() == chess::Color::WHITE ? (int)mv.to().rank() : 7 - mv.to().rank(); - if (rank >= 5) - extension += 1; - } - - if (moves.size() == 1) - extension += 1; - - if (ext + extension > 16) - extension = 0; - - int new_depth = depth - 1 + extension; - // --- END EXTENSIONS --- - - board.makeMove(mv); - int16_t score; - - if (do_lmr && !extension && depth >= 3) - { - score = -alpha_beta(board, new_depth - LMR_BONUS, -alpha - 1, -alpha, ply + 1, &sub_pv, ext); - if (score > alpha) - score = -alpha_beta(board, new_depth, -beta, -alpha, ply + 1, &sub_pv, ext); - } - else - { - if (i == 0) - score = -alpha_beta(board, new_depth, -beta, -alpha, ply + 1, &sub_pv, ext + extension); - else - { - score = -alpha_beta(board, new_depth, -alpha - 1, -alpha, ply + 1, &sub_pv, ext + extension); - if (score > alpha) - score = -alpha_beta(board, new_depth, -beta, -alpha, ply + 1, &sub_pv, ext + extension); - } - } - - board.unmakeMove(mv); - - if (score > best_score) - { - best_score = score; - best_move = mv; - if (score > alpha) - { - alpha = score; - pv->pv[0] = mv; - std::memcpy(&pv->pv[1], sub_pv.pv, sizeof(chess::Move) * sub_pv.cmove); - pv->cmove = sub_pv.cmove + 1; - } - } - - if (score >= beta) - { - if (!isCap) - { - movepick::updateKillerMoves(mv, ply); - movepick::updateHistoryHeuristic(mv, depth); - } - break; - } - } - - if (best_score != -MATE(0)) - { - TTFlag flag = (best_score >= beta) ? TTFlag::LOWERBOUND - : (best_score > alpha) ? TTFlag::EXACT - : TTFlag::UPPERBOUND; - tt.store(h, best_move, restoreMateScore(best_score, ply), depth, flag); - } - - return best_score; + session.nodes = 0; + auto board_ = board; + Value score_ = doSearch(board_, i, session); + if (session.tm.elapsed() >= + session.tm.optimum() || + stopSearch.load(std::memory_order_relaxed) || score_ == VALUE_NONE) + break; + InfoFull info{}; + info.depth = i; + info.selDepth = session.seldepth; + info.hashfull = tt.hashfull(); + info.nodes = session.nodes; + info.nps = session.nodes * 1000 / + std::max(session.tm.elapsed(), + (timeman::TimePoint)1); + info.timeMs = session.tm.elapsed(); + info.multiPV = 1; + info.score = score_; + std::string pv = ""; + for (Move *m = session.pv[0]; *m != Move::none(); m++) + pv += chess::uci::moveToUci(*m, board.chess960()) + " "; + info.pv = pv; + report(info); + lastInfo = info; + std::copy(session.pv[0], &session.pv[0][MAX_PLY], lastPV); } - - void run_search(const chess::Board &board, const TimeControl &tc) + if (lastPV[0].is_ok()) + report(chess::uci::moveToUci(lastPV[0])); + else { - nodes = 0, seldepth = 0; - stop_requested.store(false, std::memory_order_relaxed); - timeman::setLimits(tc); - - PV child_pv; - child_pv.cmove = 0; - stablePV.cmove = 0; - - chess::Movelist rootMoves; - chess::movegen::legalmoves(rootMoves, board); - - int16_t last_score = 0; - int max_iter = std::min(tc.infinite ? MAX_PLY : tc.depth, (int)MAX_PLY); - - for (int current_depth = 1; current_depth <= max_iter; ++current_depth) + // try to TT probe it + TTEntry *entry = tt.lookup(board.hash()); + if (entry && entry->getMove() != Move::none().raw()) + report(chess::uci::moveToUci(Move(entry->getMove()), board.chess960())); + else + { + Movelist moves; + board.legals(moves); + + if (moves.size()) { - chess::Board _board = board; - child_pv.cmove = 0; - int16_t alpha = last_score - 30; - int16_t beta = last_score + 30; - - try - { - last_score = alpha_beta(_board, current_depth, alpha, beta, 0, &child_pv); - - if (last_score <= alpha || last_score >= beta) - { - alpha = -MATE(0); - beta = MATE(0); - last_score = alpha_beta(_board, current_depth, alpha, beta, 0, &child_pv); - } - - stablePV = child_pv; - auto elapsed = std::chrono::duration_cast( - std::chrono::high_resolution_clock::now() - timeman::start_time) - .count(); - - if (isMateScore(last_score)){ - // Confusement: break - printf("info depth %d seldepth %d nodes %" PRIu64 " nps %" PRIu64 " time %u score mate %d pv ", - current_depth, - seldepth, - nodes.load(), - (elapsed == 0 ? 0 : nodes.load() / elapsed * 1000), - elapsed, - last_score > 0 ? (MAX_MATE - last_score) : -(MAX_MATE + last_score)); - } - else - printf("info depth %d seldepth %d nodes %" PRIu64 " nps %" PRIu64 " time %u score cp %d pv ", - current_depth, - seldepth, - nodes.load(), - (elapsed == 0 ? 0 : nodes.load() / elapsed * 1000), - elapsed, - last_score); - - for (int j = 0; j < child_pv.cmove; ++j) - printf("%s ", chess::uci::moveToUci(child_pv.pv[j], board.chess960()).c_str()); - printf("\n"); - - // [DEBUG] - if (isMateScore(last_score)) break; - if (timeman::check_time()) - break; - } - catch (SearchAbortException &) + Board board_ = board; + Move best = moves[0]; + Value bestScore = -VALUE_INFINITE; + for (Move move : moves) + { + board_.doMove(move); + Value score = -eval::eval(board_); + if (score > bestScore) { - break; + bestScore = score; + best = move; } - } - - if (stablePV.cmove > 0) - { - printf("bestmove %s\n", - chess::uci::moveToUci(stablePV.pv[0], board.chess960()).c_str()); - } - else if (!rootMoves.empty()) - { - printf("bestmove %s\n", - chess::uci::moveToUci(rootMoves[0], board.chess960()).c_str()); + board_.undoMove(); + } + + InfoFull info{}; + info.depth = 1; + info.nodes = 1; + info.score = 0; + info.multiPV = 1; + info.pv = chess::uci::moveToUci(best, board.chess960()); + report(info); + + report(chess::uci::moveToUci(best, board.chess960())); } else { - printf("bestmove 0000\n"); + report("0000"); } + } } -} + } +} // namespace engine diff --git a/search.h b/search.h new file mode 100644 index 0000000..50b3a84 --- /dev/null +++ b/search.h @@ -0,0 +1,13 @@ +#pragma once +#include +#include "tt.h" +namespace engine { + namespace timeman { + struct LimitsType; + } +} +namespace engine::search { + void stop(); + void search(const chess::Board&, const timeman::LimitsType); + extern engine::TranspositionTable tt; +} \ No newline at end of file diff --git a/search.hpp b/search.hpp deleted file mode 100644 index 0733daf..0000000 --- a/search.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once -#include "chess.hpp" -#include "timeman.hpp" -#include "tt.hpp" -#include "movepick.hpp" -#include "eval.hpp" -#include "ucioptions.hpp" -#include -#include -#include -namespace search -{ - inline std::atomic nodes{0}; - void run_search(const chess::Board& board, const TimeControl& tc); - extern std::atomic stop_requested; - extern TranspositionTable tt; -} diff --git a/timeman.cpp b/timeman.cpp index fbafa58..68318ce 100644 --- a/timeman.cpp +++ b/timeman.cpp @@ -1,110 +1,91 @@ -#include "timeman.hpp" +#include "timeman.h" +#include "uci.h" +#include "ucioption.h" #include #include #include #include #include -namespace timeman +namespace engine::timeman { - using namespace std::chrono; - - // Global state - TimeControl current_tc; // store the whole TimeControl struct globally - time_point start_time; - milliseconds inc = milliseconds(0); - milliseconds time_limit = milliseconds(0); - int moves_to_go = 40; - int depth = 0; - std::array times{}; - bool infinite = false; - - constexpr int MIN_SAFE_BUFFER_MS = 100; - constexpr double BUFFER_PERCENTAGE = 0.05; - constexpr int MIN_PER_MOVE = 400; - constexpr int MAX_PER_MOVE = 3000; - constexpr double PHASE_SCALE_OPENING = 0.7; - constexpr double PHASE_SCALE_MIDDLE = 1.0; - constexpr double PHASE_SCALE_ENDGAME = 1.25; - constexpr int OPENING_DEPTH = 5; - constexpr int MIDDLE_DEPTH = 10; - - double get_phase_scale(int current_depth) - { - if (current_depth <= OPENING_DEPTH) - return PHASE_SCALE_OPENING; - else if (current_depth <= MIDDLE_DEPTH) - return PHASE_SCALE_MIDDLE; - else - return PHASE_SCALE_ENDGAME; - } - - void reset_start_time() - { - start_time = high_resolution_clock::now(); - } -void setLimits(const TimeControl &tc) -{ - current_tc = tc; - infinite = tc.infinite; - moves_to_go = std::max(1, tc.movestogo); - depth = tc.depth; - - if (infinite && tc.depth > 0) { - time_limit = milliseconds::max(); - inc = milliseconds(0); - std::cout << "[TimeMan] Infinite or fixed depth mode.\n"; - } - else if (tc.movetime >= 0 && tc.movetime < INFINITE_TIME) { - // UCI: movetime overrides all other time settings - time_limit = milliseconds(tc.movetime); - inc = milliseconds(0); - std::cout << "[TimeMan] Using fixed movetime: " << tc.movetime << " ms\n"; - } - else { - // Fall back to clock-based time control - int time_left = tc.white_to_move ? tc.wtime : tc.btime; - int inc_ms = tc.white_to_move ? tc.winc : tc.binc; - - if (time_left != INFINITE_TIME) { - int base_time = time_left / moves_to_go; - int safe_time = base_time / 16 + inc_ms; - safe_time = std::clamp(safe_time, MIN_PER_MOVE, MAX_PER_MOVE); - double scale = get_phase_scale(depth); - safe_time = static_cast(safe_time * scale); + TimePoint TimeManagement::optimum() const { return optimumTime; } + TimePoint TimeManagement::maximum() const { return maximumTime; } - time_limit = milliseconds(safe_time); - inc = milliseconds(inc_ms); - - std::cout << "[TimeMan] Using clock. Left: " << time_left - << " ms, inc: " << inc_ms << " ms, moves_to_go: " - << moves_to_go << ", scaled: " << safe_time << " ms\n"; - } + void TimeManagement::clear() { } - reset_start_time(); -} - - - bool check_time() - { - if (infinite) - return false; + // Called at the beginning of the search and calculates + // the bounds of time allowed for the current game ply. We currently support: + // 1) x basetime (+ z increment) + // 2) x moves in y seconds (+ z increment) + void TimeManagement::init(LimitsType &limits, + chess::Color us, + int ply, + double &originalTimeAdjust) { + + // If we have no time, we don't need to fully initialize TM. + // startTime is used by movetime and useNodesTime is used in elapsed calls. + startTime = limits.startTime; + + if (limits.time[us] == 0) + return; + + // optScale is a percentage of available time to use for the current move. + // maxScale is a multiplier applied to optimumTime. + double optScale, maxScale; + + // These numbers are used where multiplications, divisions or comparisons + // with constants are involved. + const TimePoint time = limits.time[us]; + const int moveOverhead = options["Move Overhead"]; + // Maximum move horizon + int centiMTG = limits.movestogo ? std::min(limits.movestogo * 100, 5000) : 5051; + + // If less than one second, gradually reduce mtg + if (time < 1000) + centiMTG = int(time * 5.051); + + // Make sure timeLeft is > 0 since we may use it as a divisor + TimePoint timeLeft = + std::max(TimePoint(1), + limits.time[us] + + (limits.inc[us] * (centiMTG - 100) - moveOverhead * (200 + centiMTG)) / 100); + + // x basetime (+ z increment) + // If there is a healthy increment, timeLeft can exceed the actual available + // game time for the current move, so also cap to a percentage of available game time. + if (limits.movestogo == 0) + { + // Extra time according to timeLeft + if (originalTimeAdjust < 0) + originalTimeAdjust = 0.3128 * std::log10(timeLeft) - 0.4354; - int elapsed = duration_cast(high_resolution_clock::now() - start_time).count(); + // Calculate time constants based on current time left. + double logTimeInSec = std::log10(time / 1000.0); + double optConstant = std::min(0.0032116 + 0.000321123 * logTimeInSec, 0.00508017); + double maxConstant = std::max(3.3977 + 3.03950 * logTimeInSec, 2.94761); - int dynamic_buffer = std::max(MIN_SAFE_BUFFER_MS, static_cast(time_limit.count() * BUFFER_PERCENTAGE)); + optScale = std::min(0.0121431 + std::pow(ply + 2.94693, 0.461073) * optConstant, + 0.213035 * limits.time[us] / timeLeft) + * originalTimeAdjust; - bool out_of_time = elapsed + dynamic_buffer >= time_limit.count(); + maxScale = std::min(6.67704, maxConstant + ply / 11.9847); + } - if (out_of_time) + // x moves in y seconds (+ z increment) + else { - std::cout << "[TimeMan] Out of time! Elapsed: " << elapsed - << " ms, limit: " << time_limit.count() - << " ms, buffer: " << dynamic_buffer << " ms" << std::endl; + optScale = + std::min((0.88 + ply / 116.4) / (centiMTG / 100.0), 0.88 * limits.time[us] / timeLeft); + maxScale = 1.3 + 0.11 * (centiMTG / 100.0); } - return out_of_time; + // Limit the maximum possible time for this move + optimumTime = TimePoint(optScale * timeLeft); + maximumTime = + TimePoint(std::min(0.825179 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10; } -} + +} \ No newline at end of file diff --git a/timeman.h b/timeman.h new file mode 100644 index 0000000..f059245 --- /dev/null +++ b/timeman.h @@ -0,0 +1,55 @@ +#pragma once +#include +#include +#include +namespace engine::timeman +{ + using TimePoint = std::chrono::milliseconds::rep; + constexpr TimePoint INFINITE_TIME = 864000000; + // LimitsType struct stores information sent by the caller about the analysis required. + inline TimePoint now() { + return std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()) + .count(); + } + struct LimitsType { + + // Init explicitly due to broken value-initialization of non POD in MSVC + LimitsType() { + time[chess::WHITE] = time[chess::BLACK] = inc[chess::WHITE] = inc[chess::BLACK] = movetime = TimePoint(0); + movestogo = mate = perft = infinite = 0; + depth = 64; + nodes = 0; + ponderMode = false; + } + + bool use_time_management() const { return time[chess::WHITE] || time[chess::BLACK]; } + + std::vector searchmoves; + TimePoint time[chess::COLOR_NB], inc[chess::COLOR_NB], movetime, startTime; + int movestogo, depth, mate, perft, infinite; + uint64_t nodes; + bool ponderMode; + }; + class TimeManagement { + public: + void init(LimitsType & limits, + chess::Color us, + int ply, + double &originalTimeAdjust); + + TimePoint optimum() const; + TimePoint maximum() const; + TimePoint elapsed() const { + return elapsed_time(); + } + TimePoint elapsed_time() const { return now() - startTime; }; + + void clear(); + + private: + TimePoint startTime; + TimePoint optimumTime; + TimePoint maximumTime; + }; +} \ No newline at end of file diff --git a/timeman.hpp b/timeman.hpp deleted file mode 100644 index e24641e..0000000 --- a/timeman.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once -#include -#include "eval.hpp" -constexpr int INFINITE_TIME = 86400000; -struct TimeControl -{ - int wtime = INFINITE_TIME; - int btime = INFINITE_TIME; - int winc = 0; - int binc = 0; - int movestogo = 40; - int movetime = INFINITE_TIME; - uint8_t depth = MAX_PLY; - bool infinite = false; - bool white_to_move = true; -}; - -namespace timeman -{ - extern std::chrono::time_point start_time; - extern std::chrono::milliseconds time_buffer; - extern std::chrono::milliseconds inc; - extern std::chrono::milliseconds time_limit; - extern int moves_to_go; - extern bool infinite; - - bool check_time(); - - // Set time control limits using the TimeControl struct - void setLimits(const TimeControl &tc); -} diff --git a/tt.cpp b/tt.cpp index ee69361..e0030bc 100644 --- a/tt.cpp +++ b/tt.cpp @@ -1,8 +1,34 @@ -#include "tt.hpp" +#include "tt.h" +#include + +#if defined(_MSC_VER) +# include +#endif +using namespace engine; +static inline uint64_t index_for_hash(uint64_t hash, uint64_t buckets) +{ + if (buckets == 0) + return 0; + +#if defined(_MSC_VER) + // MSVC: use _umul128 to get high 64 bits of 128-bit product + unsigned long long high = 0; + (void)_umul128((unsigned long long)hash, (unsigned long long)buckets, &high); + return (uint64_t)high; +#elif defined(__SIZEOF_INT128__) + // GCC/Clang: use __uint128_t + __uint128_t prod = ( __uint128_t)hash * ( __uint128_t)buckets; + return (uint64_t)(prod >> 64); +#else + // Portable fallback (rare): fall back to modulo if no 128-bit or _umul128 available. + // This is only used on very uncommon toolchains; primary implementations above avoid division. + return hash % buckets; +#endif +} void TranspositionTable::newSearch() { - time = 0; + time++; } void TranspositionTable::store(uint64_t hash, chess::Move best, int16_t score, int8_t depth, TTFlag flag) @@ -10,19 +36,20 @@ void TranspositionTable::store(uint64_t hash, chess::Move best, int16_t score, i // 2 entries per bucket if (buckets == 0) return; - uint64_t index = hash % buckets; + + uint64_t index = index_for_hash(hash, buckets); if (index >= buckets - 1) index = buckets - 2; // Ensure we don't overflow + TTEntry &e0 = table[index], &e1 = table[index + 1]; // Store the entry - - ++time; // monotonically increasing stamp - for (TTEntry *e : {&e0, &e1}) + for (TTEntry *e : { &e0, &e1 }) { - if (!e->valid() || e->hash == hash || e->depth() < depth) + if (e->key == hash || e->getDepth() < depth) { - e->hash = hash; - e->set(depth, flag, score, time, true, best); + e->key = hash; + e->setPackedFields(score, depth, flag, best.raw(), time); + return; } } @@ -30,8 +57,8 @@ void TranspositionTable::store(uint64_t hash, chess::Move best, int16_t score, i // Find the oldest entry TTEntry *oldest = (e0.timestamp() < e1.timestamp()) ? &e0 : &e1; // Evict it - oldest->hash = hash; - oldest->set(depth, flag, score, time, true, best); + oldest->key = hash; + oldest->setPackedFields(score, depth, flag, best.raw(), time); } TTEntry *TranspositionTable::lookup(uint64_t hash) @@ -39,15 +66,17 @@ TTEntry *TranspositionTable::lookup(uint64_t hash) // 2 entries per bucket if (buckets == 0) return nullptr; - uint64_t index = hash % buckets; + + uint64_t index = index_for_hash(hash, buckets); if (index >= buckets - 1) index = buckets - 2; // Ensure we don't overflow + TTEntry &e0 = table[index], &e1 = table[index + 1]; // Check the entries - for (TTEntry *e : {&e0, &e1}) + for (TTEntry *e : { &e0, &e1 }) { - if (e->valid() && e->hash == hash) + if (e->key == hash) return e; } return nullptr; -} +} \ No newline at end of file diff --git a/tt.h b/tt.h new file mode 100644 index 0000000..09ecacb --- /dev/null +++ b/tt.h @@ -0,0 +1,179 @@ +#pragma once +#include +#include +#include +namespace engine { + enum TTFlag : uint8_t { + EXACT = 0, + LOWERBOUND = 1, + UPPERBOUND = 2 + }; + + struct TTEntry { + uint64_t key; + uint64_t pack; // 16-bit score, 8-bit depth, 3-bit flags, 16-bit move, 21 bits for generation + + // bit layout constants + static constexpr unsigned SCORE_SHIFT = 0; + static constexpr unsigned SCORE_BITS = 16; + static constexpr uint64_t SCORE_MASK = ((uint64_t(1) << SCORE_BITS) - 1) << SCORE_SHIFT; + + static constexpr unsigned DEPTH_SHIFT = 16; + static constexpr unsigned DEPTH_BITS = 8; + static constexpr uint64_t DEPTH_MASK = ((uint64_t(1) << DEPTH_BITS) - 1) << DEPTH_SHIFT; + + static constexpr unsigned FLAG_SHIFT = 24; + static constexpr unsigned FLAG_BITS = 3; + static constexpr uint64_t FLAG_MASK = ((uint64_t(1) << FLAG_BITS) - 1) << FLAG_SHIFT; + + static constexpr unsigned MOVE_SHIFT = 27; + static constexpr unsigned MOVE_BITS = 16; + static constexpr uint64_t MOVE_MASK = ((uint64_t(1) << MOVE_BITS) - 1) << MOVE_SHIFT; + + static constexpr unsigned GEN_SHIFT = 43; + static constexpr unsigned GEN_BITS = 21; + static constexpr uint64_t GEN_MASK = ((uint64_t(1) << GEN_BITS) - 1) << GEN_SHIFT; + + // getters + inline uint16_t getScore() const noexcept { + return static_cast((pack & SCORE_MASK) >> SCORE_SHIFT); + } + + inline uint8_t getDepth() const noexcept { + return static_cast((pack & DEPTH_MASK) >> DEPTH_SHIFT); + } + + inline TTFlag getFlag() const noexcept { + return static_cast((pack & FLAG_MASK) >> FLAG_SHIFT); + } + + inline uint16_t getMove() const noexcept { + return static_cast((pack & MOVE_MASK) >> MOVE_SHIFT); + } + + inline uint32_t getGeneration() const noexcept { + return static_cast((pack & GEN_MASK) >> GEN_SHIFT); + } + + // setters + inline void setScore(int16_t score) noexcept { + // preserve two's complement by casting through uint16_t + const uint64_t v = (static_cast(static_cast(score)) << SCORE_SHIFT) & SCORE_MASK; + pack = (pack & ~SCORE_MASK) | v; + } + + inline void setDepth(uint8_t depth) noexcept { + const uint64_t v = (static_cast(depth) << DEPTH_SHIFT) & DEPTH_MASK; + pack = (pack & ~DEPTH_MASK) | v; + } + + inline void setFlag(TTFlag flag) noexcept { + const uint64_t v = (static_cast(static_cast(flag)) << FLAG_SHIFT) & FLAG_MASK; + pack = (pack & ~FLAG_MASK) | v; + } + + inline void setMove(uint16_t move) noexcept { + const uint64_t v = (static_cast(move) << MOVE_SHIFT) & MOVE_MASK; + pack = (pack & ~MOVE_MASK) | v; + } + + inline void setGeneration(uint32_t gen) noexcept { + const uint64_t v = (static_cast(gen) << GEN_SHIFT) & GEN_MASK; + pack = (pack & ~GEN_MASK) | v; + } + + // convenience: set all packed fields at once + inline void setPackedFields(int16_t score, uint8_t depth, TTFlag flag, uint16_t move, uint32_t gen) noexcept { + const uint64_t s = (static_cast(static_cast(score)) << SCORE_SHIFT) & SCORE_MASK; + const uint64_t d = (static_cast(depth) << DEPTH_SHIFT) & DEPTH_MASK; + const uint64_t f = (static_cast(static_cast(flag)) << FLAG_SHIFT) & FLAG_MASK; + const uint64_t m = (static_cast(move) << MOVE_SHIFT) & MOVE_MASK; + const uint64_t g = (static_cast(gen) << GEN_SHIFT) & GEN_MASK; + pack = s | d | f | m | g; + } + + inline uint32_t timestamp() const noexcept { + return getGeneration(); + } + }; + class TranspositionTable + { + TTEntry *table; + int buckets; // number of buckets (pairs) + uint32_t time; + + public: + int size; // total number of TTEntry elements (must be even) + TranspositionTable() : table(nullptr), buckets(0), time(0), size(0) {} + + TranspositionTable(int sizeInMB) : time(0) + { + size = sizeInMB * 1048576 / sizeof(TTEntry); + if (size % 2 != 0) + size--; // Ensure even size + buckets = size / 2; + table = new TTEntry[size]; + clear(); + } + + ~TranspositionTable() + { + delete[] table; + } + + void resize(int sizeInMB) + { + TTEntry *old_table = table; + int old_size = size; + + size = sizeInMB * 1048576 / sizeof(TTEntry); + if (size % 2 != 0) + size--; + buckets = size / 2; + + TTEntry *new_table = new (std::nothrow) TTEntry[size]; + if (!new_table) + { + // Restore old values on failure + table = old_table; + size = old_size; + buckets = old_size / 2; + throw std::bad_alloc(); + } + + std::memcpy(new_table, old_table, + sizeof(TTEntry) * std::min(size, old_size)); + delete[] old_table; + table = new_table; + buckets = size / 2; + } + + void newSearch(); + void store(uint64_t hash, chess::Move best, int16_t score, int8_t depth, TTFlag flag); + + inline void clear() + { + std::fill_n(table, size, TTEntry{}); + } + + TTEntry *lookup(uint64_t hash); + + // Returns hash usage as percentage [0..100] based on occupied buckets. + inline int hashfull() const noexcept + { + if (!table || buckets <= 0) + return 0; + + int used = 0; + for (int i = 0; i < buckets; ++i) + { + const TTEntry &a = table[2 * i]; + const TTEntry &b = table[2 * i + 1]; + if (a.key != 0 || b.key != 0) + ++used; + } + + return (used * 1000) / buckets; + } + }; +} \ No newline at end of file diff --git a/tt.hpp b/tt.hpp deleted file mode 100644 index 0ec4a93..0000000 --- a/tt.hpp +++ /dev/null @@ -1,122 +0,0 @@ -#pragma once -#include "chess.hpp" -#include -#include -enum class TTFlag : uint8_t -{ - EXACT = 0, - LOWERBOUND = 1, - UPPERBOUND = 2, - // 3 is unused or reserved -}; - -struct TTEntry -{ - uint64_t hash; - chess::Move bestMove; - uint64_t packed; - - // Getters - uint8_t depth() const - { - return ((packed >> 58) & 0x3F) + 1; // 6 bits depth-1 - } - - TTFlag flag() const - { - return static_cast((packed >> 56) & 0x3); - } - - uint64_t timestamp() const - { - return (packed >> 17) & 0x7FFFFFFFFFULL; // 39 bits - } - - bool valid() const - { - return (packed & 1) != 0; - } - - int16_t score() const - { - return bestMove.score(); - } - - // Setter - void set(uint8_t d, TTFlag f, int16_t s, uint64_t ts, bool v, chess::Move move) - { - // Store depth - 1 in 6 bits, clamp at 63 - uint8_t depth_encoded = (d > 0) ? (d - 1) : 0; - if (depth_encoded > 63) - depth_encoded = 63; - - packed = (uint64_t(depth_encoded) << 58) | - (uint64_t((int)f & 0x3) << 56) | - ((ts & 0x7FFFFFFFFFULL) << 17) | - (v); - bestMove=move; - bestMove.setScore(s); - } -}; -class TranspositionTable -{ - TTEntry *table; - int buckets; // number of buckets (pairs) - uint32_t time; - -public: - int size; // total number of TTEntry elements (must be even) - TranspositionTable() : table(nullptr), buckets(0), time(0), size(0) {} - - TranspositionTable(int sizeInMB) : time(0) - { - size = sizeInMB * 1048576 / sizeof(TTEntry); - if (size % 2 != 0) - size--; // Ensure even size - buckets = size / 2; - table = new TTEntry[size]; - clear(); - } - - ~TranspositionTable() - { - delete[] table; - } - - void resize(int sizeInMB) - { - TTEntry *old_table = table; - int old_size = size; - - size = sizeInMB * 1048576 / sizeof(TTEntry); - if (size % 2 != 0) - size--; - buckets = size / 2; - - TTEntry *new_table = new (std::nothrow) TTEntry[size]; - if (!new_table) - { - // Restore old values on failure - table = old_table; - size = old_size; - buckets = old_size / 2; - throw std::bad_alloc(); - } - - std::memcpy(new_table, old_table, - sizeof(TTEntry) * std::min(size, old_size)); - delete[] old_table; - table = new_table; - buckets = size / 2; - } - - void newSearch(); - void store(uint64_t hash, chess::Move best, int16_t score, int8_t depth, TTFlag flag); - - inline void clear() - { - std::fill_n(table, size, TTEntry{}); - } - - TTEntry *lookup(uint64_t hash); -}; diff --git a/tune.cpp b/tune.cpp new file mode 100644 index 0000000..5286b8c --- /dev/null +++ b/tune.cpp @@ -0,0 +1,126 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish 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. + + Stockfish 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 "tune.h" + +#include +#include +#include +#include +#include +#include + +#include "ucioption.h" + +using std::string; + +namespace engine { + +bool Tune::update_on_last; +const Option* LastOption = nullptr; +OptionsMap* Tune::options; +namespace { +std::map TuneResults; + +std::optional on_tune(const Option& o) { + + if (!Tune::update_on_last || LastOption == &o) + Tune::read_options(); + + return std::nullopt; +} +} + +void Tune::make_option(OptionsMap* opts, const string& n, int v, const SetRange& r) { + + // Do not generate option when there is nothing to tune (ie. min = max) + if (r(v).first == r(v).second) + return; + + if (TuneResults.count(n)) + v = TuneResults[n]; + + opts->add(n, Option(v, r(v).first, r(v).second, on_tune)); + LastOption = &((*opts)[n]); + + // Print formatted parameters, ready to be copy-pasted in Fishtest + std::cout << n << "," // + << v << "," // + << r(v).first << "," // + << r(v).second << "," // + << (r(v).second - r(v).first) / 20.0 << "," // + << "0.0020" << std::endl; +} + +string Tune::next(string& names, bool pop) { + + string name; + + do + { + string token = names.substr(0, names.find(',')); + + if (pop) + names.erase(0, token.size() + 1); + + std::stringstream ws(token); + name += (ws >> token, token); // Remove trailing whitespace + + } while (std::count(name.begin(), name.end(), '(') - std::count(name.begin(), name.end(), ')')); + + return name; +} + + +template<> +void Tune::Entry::init_option() { + make_option(options, name, value, range); +} + +template<> +void Tune::Entry::read_option() { + if (options->count(name)) + value = int((*options)[name]); +} + +// Instead of a variable here we have a PostUpdate function: just call it +template<> +void Tune::Entry::init_option() {} +template<> +void Tune::Entry::read_option() { + value(); +} + +} // namespace engine + + +// Init options with tuning session results instead of default values. Useful to +// get correct bench signature after a tuning session or to test tuned values. +// Just copy fishtest tuning results in a result.txt file and extract the +// values with: +// +// cat results.txt | sed 's/^param: \([^,]*\), best: \([^,]*\).*/ TuneResults["\1"] = int(round(\2));/' +// +// Then paste the output below, as the function body + + +namespace engine { + +void Tune::read_results() { /* ...insert your values here... */ } + +} // namespace engine diff --git a/tune.h b/tune.h new file mode 100644 index 0000000..347d2a7 --- /dev/null +++ b/tune.h @@ -0,0 +1,192 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish 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. + + Stockfish 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 . +*/ + +#ifndef TUNE_H_INCLUDED +#define TUNE_H_INCLUDED + +#include +#include +#include +#include // IWYU pragma: keep +#include +#include + +namespace engine { + +class OptionsMap; + +using Range = std::pair; // Option's min-max values +using RangeFun = Range(int); + +// Default Range function, to calculate Option's min-max values +inline Range default_range(int v) { return v > 0 ? Range(0, 2 * v) : Range(2 * v, 0); } + +struct SetRange { + explicit SetRange(RangeFun f) : + fun(f) {} + SetRange(int min, int max) : + fun(nullptr), + range(min, max) {} + Range operator()(int v) const { return fun ? fun(v) : range; } + + RangeFun* fun; + Range range; +}; + +#define SetDefaultRange SetRange(default_range) + + +// Tune class implements the 'magic' code that makes the setup of a fishtest tuning +// session as easy as it can be. Mainly you have just to remove const qualifiers +// from the variables you want to tune and flag them for tuning, so if you have: +// +// const Value myValue[][2] = { { V(100), V(20) }, { V(7), V(78) } }; +// +// If you have a my_post_update() function to run after values have been updated, +// and a my_range() function to set custom Option's min-max values, then you just +// remove the 'const' qualifiers and write somewhere below in the file: +// +// TUNE(SetRange(my_range), myValue, my_post_update); +// +// You can also set the range directly, and restore the default at the end +// +// TUNE(SetRange(-100, 100), myValue, SetDefaultRange); +// +// In case update function is slow and you have many parameters, you can add: +// +// UPDATE_ON_LAST(); +// +// And the values update, including post update function call, will be done only +// once, after the engine receives the last UCI option, that is the one defined +// and created as the last one, so the GUI should send the options in the same +// order in which have been defined. + +class Tune { + + using PostUpdate = void(); // Post-update function + + Tune() { read_results(); } + Tune(const Tune&) = delete; + void operator=(const Tune&) = delete; + void read_results(); + + static Tune& instance() { + static Tune t; + return t; + } // Singleton + + // Use polymorphism to accommodate Entry of different types in the same vector + struct EntryBase { + virtual ~EntryBase() = default; + virtual void init_option() = 0; + virtual void read_option() = 0; + }; + + template + struct Entry: public EntryBase { + + static_assert(!std::is_const_v, "Parameter cannot be const!"); + + static_assert(std::is_same_v || std::is_same_v, + "Parameter type not supported!"); + + Entry(const std::string& n, T& v, const SetRange& r) : + name(n), + value(v), + range(r) {} + void operator=(const Entry&) = delete; // Because 'value' is a reference + void init_option() override; + void read_option() override; + + std::string name; + T& value; + SetRange range; + }; + + // Our facility to fill the container, each Entry corresponds to a parameter + // to tune. We use variadic templates to deal with an unspecified number of + // entries, each one of a possible different type. + static std::string next(std::string& names, bool pop = true); + + int add(const SetRange&, std::string&&) { return 0; } + + template + int add(const SetRange& range, std::string&& names, T& value, Args&&... args) { + list.push_back(std::unique_ptr(new Entry(next(names), value, range))); + return add(range, std::move(names), args...); + } + + // Template specialization for arrays: recursively handle multi-dimensional arrays + template + int add(const SetRange& range, std::string&& names, T (&value)[N], Args&&... args) { + for (size_t i = 0; i < N; i++) + add(range, next(names, i == N - 1) + "[" + std::to_string(i) + "]", value[i]); + return add(range, std::move(names), args...); + } + + // Template specialization for SetRange + template + int add(const SetRange&, std::string&& names, SetRange& value, Args&&... args) { + return add(value, (next(names), std::move(names)), args...); + } + + static void make_option(OptionsMap* options, const std::string& n, int v, const SetRange& r); + + std::vector> list; + + public: + template + static int add(const std::string& names, Args&&... args) { + return instance().add(SetDefaultRange, names.substr(1, names.size() - 2), + args...); // Remove trailing parenthesis + } + static void init(OptionsMap& o) { + options = &o; + for (auto& e : instance().list) + e->init_option(); + read_options(); + } // Deferred, due to UCIEngine::Options access + static void read_options() { + for (auto& e : instance().list) + e->read_option(); + } + + static bool update_on_last; + static OptionsMap* options; +}; + +template +constexpr void tune_check_args(Args&&...) { + static_assert((!std::is_fundamental_v && ...), "TUNE macro arguments wrong"); +} + +// Some macro magic :-) we define a dummy int variable that the compiler initializes calling Tune::add() +#define STRINGIFY(x) #x +#define UNIQUE2(x, y) x##y +#define UNIQUE(x, y) UNIQUE2(x, y) // Two indirection levels to expand __LINE__ +#define TUNE(...) \ + int UNIQUE(p, __LINE__) = []() -> int { \ + tune_check_args(__VA_ARGS__); \ + return engine::Tune::add(STRINGIFY((__VA_ARGS__)), __VA_ARGS__); \ + }(); + +#define UPDATE_ON_LAST() bool UNIQUE(p, __LINE__) = Tune::update_on_last = true + +} // namespace engine + +#endif // #ifndef TUNE_H_INCLUDED diff --git a/uci.cpp b/uci.cpp index d27dd8f..01bad17 100644 --- a/uci.cpp +++ b/uci.cpp @@ -1,270 +1,195 @@ -#include "uci.hpp" +#include +#include +#include +#include "uci.h" +#include "search.h" +#include "timeman.h" +#include "ucioption.h" +#include +using namespace engine; +chess::Position pos; +OptionsMap engine::options; +void handlePosition(std::istringstream &is) { + std::string token, fen; + + is >> token; -chess::Board board; -// Thread handle -static std::thread search_thread; -void handleSetOption(const std::string &input) -{ - std::istringstream iss(input); - std::string token, name, value; - bool reading_name = false, reading_value = false; - - while (iss >> token) - { - if (token == "setoption") - continue; - if (token == "name") - { - reading_name = true; - reading_value = false; - name.clear(); - value.clear(); - continue; - } - if (token == "value") - { - reading_value = true; - reading_name = false; - continue; - } - - if (reading_name) - { - if (!name.empty()) - name += " "; - name += token; - } - else if (reading_value) - { - if (!value.empty()) - value += " "; - value += token; - } - } - - // Clean up name - auto pos = name.find_last_not_of(" \n\r\t"); - if (pos != std::string::npos) + if (token == "startpos") { - name.erase(pos + 1); + fen = chess::Position::START_FEN; + is >> token; // Consume the "moves" token, if any } + else if (token == "fen") + while (is >> token && token != "moves") + fen += token + " "; else - { - name.clear(); // String contains only whitespace - } + return; + pos.setFen(fen); - if (!UCIOptions::options.count(name)) + while (is >> token) { - std::cerr << "info string Unknown option: " << name << "\n"; - return; + pos.push_uci(token); } - - UCIOptions::setOption(name, value); -} -void handle_uci() -{ - std::cout << "id name cppchess_engine\n"; - std::cout << "id author winapiadmin\n"; - UCIOptions::printOptions(); - std::cout << "uciok\n"; -} - -inline void handle_isready() { std::cout << "readyok\n"; } - -void handle_quit() -{ - search::stop_requested = true; - if (search_thread.joinable()) - search_thread.join(); - std::exit(0); } +timeman::LimitsType parse_limits(std::istream &is) { + timeman::LimitsType limits; + std::string token; -inline void handle_stop() { search::stop_requested = true; } - -inline void handle_display() { std::cout << board << std::endl; } + limits.startTime = timeman::now(); // The search starts as early as possible -inline void handle_eval() { trace(board); } -void handle_position(std::istringstream &iss) -{ - std::string token; - iss >> token; - - if (token == "startpos") - { - board.setFen(chess::constants::STARTPOS); - iss >> token; - } - else if (token == "fen") - { - std::vector fen_parts; - while (iss >> token && token != "moves") - { - fen_parts.push_back(token); - } - - // Assign default values if necessary - while (fen_parts.size() < 6) - { - switch (fen_parts.size()) - { - case 1: - fen_parts.push_back("w"); - break; // Active color - case 2: - fen_parts.push_back("-"); - break; // Castling availability - case 3: - fen_parts.push_back("-"); - break; // En passant target square - case 4: - fen_parts.push_back("0"); - break; // Halfmove clock - case 5: - fen_parts.push_back("1"); - break; // Fullmove number + while (is >> token) + if (token == "searchmoves") // Needs to be the last command on the line + while (is >> token) { + std::transform(token.begin(), token.end(), token.begin(), [](auto c) { return std::tolower(c); }); + limits.searchmoves.push_back(token); } - } + else if (token == "wtime") + is >> limits.time[chess::WHITE]; + else if (token == "btime") + is >> limits.time[chess::BLACK]; + else if (token == "winc") + is >> limits.inc[chess::WHITE]; + else if (token == "binc") + is >> limits.inc[chess::BLACK]; + else if (token == "movestogo") + is >> limits.movestogo; + else if (token == "depth") + is >> limits.depth; + else if (token == "nodes") + is >> limits.nodes; + else if (token == "movetime") + is >> limits.movetime; + else if (token == "mate") + is >> limits.mate; + else if (token == "perft") + is >> limits.perft; + else if (token == "infinite") + limits.infinite = 1; + else if (token == "ponder"); + //std::cerr << "Pondering not supported!" << std::endl; + + return limits; +} +void handleGo(std::istringstream &ss) { + search::search(pos, parse_limits(ss)); +} +template +struct overload : Ts... { + using Ts::operator()...; +}; +template +overload(Ts...) -> overload; + +std::string engine::format_score(const Score &s) { + const auto format = + overload{ [](Score::Mate mate) -> std::string { + auto m = (mate.plies > 0 ? (mate.plies + 1) : mate.plies) / 2; + return std::string("mate ") + std::to_string(m); + }, + [](Score::Tablebase tb) -> std::string { + constexpr int TB_CP = 20000; + return std::string("cp ") + + std::to_string((tb.win ? TB_CP - tb.plies : -TB_CP - tb.plies)); + }, + [](Score::InternalUnits units) -> std::string { + return std::string("cp ") + std::to_string(units.value); + } }; + + return s.visit(format); +} - // Reconstruct the FEN string - std::string fen = fen_parts[0]; - for (size_t i = 1; i < 6; ++i) - { - fen += " " + fen_parts[i]; - } +void engine::report(const InfoShort &info) { + std::cout << "info depth " << info.depth << " score " << format_score(info.score) << std::endl; +} - board.setFen(fen); - } +void engine::report(const InfoFull &info, bool showWDL) { + std::stringstream ss; - if (token == "moves") - { - while (iss >> token) - { - chess::Move mv = chess::uci::uciToMove(board, token); - board.makeMove(mv); - } - } + ss << "info"; + ss << " depth " << info.depth // + << " seldepth " << info.selDepth // + << " multipv " << info.multiPV // + << " score " << format_score(info.score); // - search::tt.clear(); -} + if (!info.bound.empty()) + ss << " " << info.bound; -void handle_bench() -{ - uint64_t nodes = 0; - std::vector fen_list = {"r7/pp3kb1/7p/2nr4/4p3/7P/PP1N1PP1/R1B1K2R b KQ - 1 19", - "r1bqk2r/pppp1ppp/2n5/4p3/4P3/2N5/PPPP1PPP/R1BQK2R w KQkq - 0 1", - "rnbqkb1r/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", - "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR b KQkq - 0 1", - "8/pp3k2/7p/2nR4/4p3/7P/Pb3PP1/6K1 b - - 1 25", - "8/p4k2/1p1R3p/2n5/4p3/7P/Pb3PP1/6K1 b - - 1 26", - "8/p4k2/1p1R1b1p/2n5/4p3/7P/P4PP1/5K2 b - - 3 27", - "8/p3k3/1pR2b1p/2n5/4p3/7P/P4PP1/5K2 b - - 5 28", - "1R6/8/1p1knb1p/p7/4p3/7P/P3KPP1/8 b - - 3 32", - "3R4/8/1p1k3p/p7/3bp2P/6Pn/P3KP2/8 b - - 2 35", - "8/4R3/1p6/p5PP/3b4/2n2K2/P2kp3/8 w - - 1 46", - "rnbqkb1r/ppp1pppp/1n6/8/8/2N2N2/PPPP1PPP/R1BQKB1R w KQkq - 2 5", - "r2qr1k1/1ppb1pbp/np4p1/3Pp3/4N3/P2B1N1P/1PP2PP1/2RQR1K1 b - - 6 16", - "8/8/5k2/8/8/4Q1K1/PPP1PPPP/3R1BR1 w - - 67 88"}; - std::vector boards(fen_list.size()); - auto start_time = std::chrono::high_resolution_clock::now(); - for (size_t i = 0; i < fen_list.size(); ++i) - { - std::cout << "Position " << i + 1 << "/" << fen_list.size() << std::endl; - boards[i].setFen(fen_list[i]); - TimeControl tc{}; - tc.movestogo = 1; - tc.depth = 8; - tc.infinite = false; - // All other fields remain at default (0 or -1) + if (showWDL) + ss << " wdl " << info.wdl; - search::run_search(boards[i], tc); + ss << " nodes " << info.nodes // + << " nps " << info.nps // + << " hashfull " << info.hashfull // + << " tbhits " << info.tbHits // + << " time " << info.timeMs // + << " pv " << info.pv; // - nodes += search::nodes; - } - auto end_time = std::chrono::high_resolution_clock::now(); - std::chrono::duration elapsed = end_time - start_time; - - std::cout << "===========================\n"; - std::cout << "Total time (ms) : " << static_cast(elapsed.count() * 1000) << "\n"; - std::cout << "Nodes searched : " << nodes << "\n"; - std::cout << "Nodes/second : " << static_cast(nodes / elapsed.count()) << std::endl; + std::cout << ss.str() << std::endl; } -void handle_go(std::istringstream &iss) -{ - TimeControl tc; - bool white = (board.sideToMove() == chess::Color::WHITE); - std::string sub; - while (iss >> sub) - { - if (sub == "depth") - iss >> tc.depth; - else if (sub == "movetime") - iss >> tc.movetime; - else if (sub == "wtime") - iss >> tc.wtime; - else if (sub == "btime") - iss >> tc.btime; - else if (sub == "winc") - iss >> tc.winc; - else if (sub == "binc") - iss >> tc.binc; - else if (sub == "movestogo") - iss >> tc.movestogo; - else if (sub == "infinite") - { - tc.infinite = true; - tc.depth = MAX_PLY; - } - } - tc.white_to_move=white; - if (search_thread.joinable()) - { - search::stop_requested = true; - search_thread.join(); - } +void engine::report(const InfoIteration &info) { + std::stringstream ss; - search::stop_requested = false; + ss << "info"; + ss << " depth " << info.depth // + << " currmove " << info.currmove // + << " currmovenumber " << info.currmovenumber; // - search_thread = std::thread(search::run_search, board, tc); // keep joinable + std::cout << ss.str() << std::endl; } -void handle_ucinewgame() -{ - board.setFen(chess::constants::STARTPOS); - search::tt.clear(); - search::tt.newSearch(); +void engine::report(std::string_view bestmove) { + std::cout << "bestmove " << bestmove; + std::cout << std::endl; } -void uci_loop() -{ +void engine::loop() { std::string line; - while (std::getline(std::cin, line)) - { - std::istringstream iss(line); + pos.setFen(pos.START_FEN); + std::cout << "cppchess_engine version " << BUILD_VERSION << '\n'; + while (std::getline(std::cin, line)) { + std::istringstream ss(line); std::string token; - iss >> token; - if (token == "ucinewgame") - handle_ucinewgame(); - else if (token == "uci") - handle_uci(); - else if (token == "isready") - handle_isready(); - else if (token == "quit") - handle_quit(); - else if (token == "stop") - handle_stop(); - else if (token == "setoption") - handleSetOption(line); - else if (token == "position") - handle_position(iss); - else if (token == "go") - handle_go(iss); - else if (token == "d") - handle_display(); - else if (token == "bench") - handle_bench(); - else if (token == "eval") - handle_eval(); - else - std::cerr << "[DEBUG] Unknown command: " << token << "\n"; + while (ss >> token) { + if (token == "uci") { + std::cout << "id name cppchess_engine\n"; + std::cout << "id author winapiadmin\n"; + std::cout << options << '\n'; + std::cout << "uciok\n"; + std::cout.flush(); + break; + } + else if (token == "isready") { + std::cout << "readyok\n"; + std::cout.flush(); + break; + } + else if (token == "position") { + handlePosition(ss); + break; // rest belongs to position + } + else if (token == "go") { + handleGo(ss); + break; // rest belongs to go + } + else if (token == "ucinewgame") { + search::tt.clear(); + break; + } + else if (token == "stop") { + search::stop(); + break; + } + else if (token == "quit") { + return; + } + else if (token == "setoption") { + options.setoption(ss); + break; + } + else if (token=="visualize") { + std::cout << pos << std::endl; + break; + } + } } } diff --git a/uci.h b/uci.h new file mode 100644 index 0000000..c9557f6 --- /dev/null +++ b/uci.h @@ -0,0 +1,37 @@ +#pragma once +#include +#include +#include "score.h" +namespace engine { + struct InfoShort { + int depth; + Score score; + }; + + struct InfoFull : InfoShort { + int selDepth; + size_t multiPV; + std::string_view wdl; + std::string_view bound; + size_t timeMs; + size_t nodes; + size_t nps; + size_t tbHits; + std::string_view pv; + int hashfull; + }; + + struct InfoIteration { + int depth; + std::string_view currmove; + size_t currmovenumber; + }; + std::string format_score(const Score &s); + void report(const InfoFull &info, bool showWDL=false); + void report(const InfoShort &info); + void report(const InfoIteration &info); + void report(std::string_view bestmove); + void loop(); + class OptionsMap; + extern OptionsMap options; +} \ No newline at end of file diff --git a/uci.hpp b/uci.hpp deleted file mode 100644 index 8ea9bb4..0000000 --- a/uci.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include "chess.hpp" -#include "search.hpp" -void uci_loop(); diff --git a/ucioption.cpp b/ucioption.cpp new file mode 100644 index 0000000..dcf272e --- /dev/null +++ b/ucioption.cpp @@ -0,0 +1,211 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish 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. + + Stockfish 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 "ucioption.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace engine { + +bool CaseInsensitiveLess::operator()(const std::string& s1, const std::string& s2) const { + + return std::lexicographical_compare( + s1.begin(), s1.end(), s2.begin(), s2.end(), + [](char c1, char c2) { return std::tolower(c1) < std::tolower(c2); }); +} + +void OptionsMap::add_info_listener(InfoListener&& message_func) { info = std::move(message_func); } + +void OptionsMap::setoption(std::istringstream& is) { + std::string token, name, value; + + is >> token; // Consume the "name" token + + // Read the option name (can contain spaces) + while (is >> token && token != "value") + name += (name.empty() ? "" : " ") + token; + + // Read the option value (can contain spaces) + while (is >> token) + value += (value.empty() ? "" : " ") + token; + + if (options_map.count(name)) + options_map[name] = value; + else + std::cerr << "No such option: " << name << std::endl; +} + +const Option& OptionsMap::operator[](const std::string& name) const { + auto it = options_map.find(name); + assert(it != options_map.end()); + return it->second; +} + +// Inits options and assigns idx in the correct printing order +void OptionsMap::add(const std::string& name, const Option& option) { + if (!options_map.count(name)) + { + static size_t insert_order = 0; + + options_map[name] = option; + + options_map[name].parent = this; + options_map[name].idx = insert_order++; + } + else + { + std::cerr << "Option \"" << name << "\" was already added!" << std::endl; + std::exit(EXIT_FAILURE); + } +} + + +std::size_t OptionsMap::count(const std::string& name) const { return options_map.count(name); } + +Option::Option(const OptionsMap* map) : + parent(map) {} + +Option::Option(const char* v, OnChange f) : + type("string"), + min(0), + max(0), + on_change(std::move(f)) { + defaultValue = currentValue = v; +} + +Option::Option(bool v, OnChange f) : + type("check"), + min(0), + max(0), + on_change(std::move(f)) { + defaultValue = currentValue = (v ? "true" : "false"); +} + +Option::Option(OnChange f) : + type("button"), + min(0), + max(0), + on_change(std::move(f)) {} + +Option::Option(int v, int minv, int maxv, OnChange f) : + type("spin"), + min(minv), + max(maxv), + on_change(std::move(f)) { + defaultValue = currentValue = std::to_string(v); +} + +Option::Option(const char* v, const char* cur, OnChange f) : + type("combo"), + min(0), + max(0), + on_change(std::move(f)) { + defaultValue = v; + currentValue = cur; +} + +Option::operator int() const { + assert(type == "check" || type == "spin"); + return (type == "spin" ? std::stoi(currentValue) : currentValue == "true"); +} + +Option::operator std::string() const { + assert(type == "string"); + return currentValue; +} + +bool Option::operator==(const char* s) const { + assert(type == "combo"); + return !CaseInsensitiveLess()(currentValue, s) && !CaseInsensitiveLess()(s, currentValue); +} + +bool Option::operator!=(const char* s) const { return !(*this == s); } + + +// Updates currentValue and triggers on_change() action. It's up to +// the GUI to check for option's limits, but we could receive the new value +// from the user by console window, so let's check the bounds anyway. +Option& Option::operator=(const std::string& v) { + + assert(!type.empty()); + + if ((type != "button" && type != "string" && v.empty()) + || (type == "check" && v != "true" && v != "false") + || (type == "spin" && (std::stoi(v) < min || std::stoi(v) > max))) + return *this; + + if (type == "combo") + { + OptionsMap comboMap; // To have case insensitive compare + std::string token; + std::istringstream ss(defaultValue); + while (ss >> token) + comboMap.add(token, Option()); + if (!comboMap.count(v) || v == "var") + return *this; + } + + if (type == "string") + currentValue = v == "" ? "" : v; + else if (type != "button") + currentValue = v; + + if (on_change) + { + const auto ret = on_change(*this); + + if (ret && parent != nullptr && parent->info != nullptr) + parent->info(ret); + } + + return *this; +} + +std::ostream& operator<<(std::ostream& os, const OptionsMap& om) { + for (size_t idx = 0; idx < om.options_map.size(); ++idx) + for (const auto& it : om.options_map) + if (it.second.idx == idx) + { + const Option& o = it.second; + os << "\noption name " << it.first << " type " << o.type; + + if (o.type == "check" || o.type == "combo") + os << " default " << o.defaultValue; + + else if (o.type == "string") + { + std::string defaultValue = o.defaultValue.empty() ? "" : o.defaultValue; + os << " default " << defaultValue; + } + + else if (o.type == "spin") + os << " default " << stoi(o.defaultValue) << " min " << o.min << " max " + << o.max; + + break; + } + + return os; +} +} diff --git a/ucioption.h b/ucioption.h new file mode 100644 index 0000000..f13a544 --- /dev/null +++ b/ucioption.h @@ -0,0 +1,106 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish 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. + + Stockfish 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 . +*/ + +#ifndef UCIOPTION_H_INCLUDED +#define UCIOPTION_H_INCLUDED + +#include +#include +#include +#include +#include +#include + +namespace engine { +// Define a custom comparator, because the UCI options should be case-insensitive +struct CaseInsensitiveLess { + bool operator()(const std::string&, const std::string&) const; +}; + +class OptionsMap; + +// The Option class implements each option as specified by the UCI protocol +class Option { + public: + using OnChange = std::function(const Option&)>; + + Option(const OptionsMap*); + Option(OnChange = nullptr); + Option(bool v, OnChange = nullptr); + Option(const char* v, OnChange = nullptr); + Option(int v, int minv, int maxv, OnChange = nullptr); + Option(const char* v, const char* cur, OnChange = nullptr); + + Option& operator=(const std::string&); + operator int() const; + operator std::string() const; + bool operator==(const char*) const; + bool operator!=(const char*) const; + + friend std::ostream& operator<<(std::ostream&, const OptionsMap&); + + int operator<<(const Option&) = delete; + + private: + friend class OptionsMap; + friend class Engine; + friend class Tune; + + + std::string defaultValue, currentValue, type; + int min, max; + size_t idx; + OnChange on_change; + const OptionsMap* parent = nullptr; +}; + +class OptionsMap { + public: + using InfoListener = std::function)>; + + OptionsMap() = default; + OptionsMap(const OptionsMap&) = delete; + OptionsMap(OptionsMap&&) = delete; + OptionsMap& operator=(const OptionsMap&) = delete; + OptionsMap& operator=(OptionsMap&&) = delete; + + void add_info_listener(InfoListener&&); + + void setoption(std::istringstream&); + + const Option& operator[](const std::string&) const; + + void add(const std::string&, const Option& option); + + std::size_t count(const std::string&) const; + + private: + friend class Engine; + friend class Option; + + friend std::ostream& operator<<(std::ostream&, const OptionsMap&); + + // The options container is defined as a std::map + using OptionsStore = std::map; + + OptionsStore options_map; + InfoListener info; +}; + +} +#endif // #ifndef UCIOPTION_H_INCLUDED diff --git a/ucioptions.cpp b/ucioptions.cpp deleted file mode 100644 index 4a4f22c..0000000 --- a/ucioptions.cpp +++ /dev/null @@ -1,168 +0,0 @@ -#include "ucioptions.hpp" -#include -#include -#include - -namespace UCIOptions -{ - - // Global options map - std::unordered_map options; - - // Constructors with callback support - Option::Option(Type t, int def, int min_, int max_, std::function cb) - : type(t), min(min_), max(max_), value(def), on_change(std::move(cb)) {} - - Option::Option(Type t, const std::string &def, std::function cb) - : type(t), default_str(def), value(def), on_change(std::move(cb)) {} - - Option::Option(Type t, const std::string &def, const std::vector &options, std::function cb) - : type(t), default_str(def), vars(options), value(def), on_change(std::move(cb)) {} - - // Adders with optional callbacks - void addSpin(const std::string &name, int defaultValue, int min, int max, std::function cb) - { - options[name] = Option(Option::SPIN, defaultValue, min, max, std::move(cb)); - if (options[name].on_change) - options[name].on_change(options[name]); - } - - void addCheck(const std::string &name, bool defaultValue, std::function cb) - { - options[name] = Option(Option::CHECK, defaultValue ? 1 : 0, 0, 1, std::move(cb)); - if (options[name].on_change) - options[name].on_change(options[name]); - } - - void addString(const std::string &name, const std::string &defaultValue, std::function cb) - { - options[name] = Option(Option::STRING, defaultValue, std::move(cb)); - if (options[name].on_change) - options[name].on_change(options[name]); - } - - void addCombo(const std::string &name, const std::string &defaultValue, const std::vector &vars, std::function cb) - { - options[name] = Option(Option::COMBO, defaultValue, vars, std::move(cb)); - if (options[name].on_change) - options[name].on_change(options[name]); - } - - void addButton(const std::string &name, std::function cb) - { - options[name] = Option(Option::BUTTON, "", {}, std::move(cb)); - if (options[name].on_change) - options[name].on_change(options[name]); - } - - void setOption(const std::string &name, const std::string &val) - { - const auto it = options.find(name); - if (it == options.end()) { - std::cerr << "info string Unknown option " << name << '\n'; - return; // Early-out – nothing to update - } - auto &opt = it->second; - - switch (opt.type) - { - case Option::CHECK: - if (val == "true" || val == "1") - opt.value = 1; - else if (val == "false" || val == "0") - opt.value = 0; - else { - std::cerr << "info string Invalid value for option " << name - << ": " << val << " (expected true/false or 1/0)\n"; - return; - } - break; - case Option::SPIN: - { - try - { - int v{}; - auto [p, ec] = std::from_chars(val.data(), val.data() + val.size(), v); - if (ec != std::errc() || p != val.data() + val.size()) { - throw std::invalid_argument("not an int"); - } - opt.value = std::clamp(v, opt.min, opt.max); - } - catch (const std::exception &e) - { - std::cerr << "info string Invalid value for option " << name << ": " << val << "\n"; - return; - } - break; - } - case Option::STRING: - opt.value = val; - break; - case Option::COMBO: - if (std::find(opt.vars.begin(), opt.vars.end(), val) != opt.vars.end()) { - opt.value = val; - } else { - std::cerr << "info string Illegal value \"" << val - << "\" for option " << name << "\n"; - return; - } - break; - case Option::BUTTON: - // no value change - break; - } - - // Invoke the callback if present - if (opt.on_change) - opt.on_change(opt); - } - - int getInt(const std::string &name) - { - const auto &opt = options.at(name); - if (auto pval = std::get_if(&opt.value)) - return *pval; - throw std::runtime_error("Option " + name + " is not an integer"); - } - - std::string getString(const std::string &name) - { - const auto &opt = options.at(name); - if (auto pval = std::get_if(&opt.value)) - return *pval; - if (auto pval = std::get_if(&opt.value)) - return std::to_string(*pval); - return ""; - } - - void printOptions() - { - for (const auto &[name, opt] : options) - { - std::cout << "option name " << name << " type "; - switch (opt.type) - { - case Option::CHECK: - std::cout << "check default " << (std::get(opt.value) ? "true" : "false") << "\n"; - break; - case Option::SPIN: - std::cout << "spin default " << std::get(opt.value) - << " min " << opt.min << " max " << opt.max << "\n"; - break; - case Option::STRING: - std::cout << "string default " << std::get(opt.value) << "\n"; - break; - case Option::COMBO: - std::cout << "combo default " << std::get(opt.value); - for (const auto &var : opt.vars) - std::cout << " var " << var; - std::cout << "\n"; - break; - case Option::BUTTON: - std::cout << "button\n"; - break; - } - } - } - -} // namespace UCIOptions diff --git a/ucioptions.hpp b/ucioptions.hpp deleted file mode 100644 index 79130bd..0000000 --- a/ucioptions.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -namespace UCIOptions { - -struct Option { - enum Type { CHECK, SPIN, COMBO, BUTTON, STRING } type; - - std::string default_str; - std::vector vars; // for COMBO - int min = 0, max = 0; - - std::variant value; - - // Optional callback when the option is set - std::function on_change; - - Option() = default; - Option(Type t, int def, int min_ = 0, int max_ = 0, std::function cb = {}); - Option(Type t, const std::string& def, std::function cb = {}); - Option(Type t, const std::string& def, const std::vector& options, std::function cb = {}); -}; - -// Declare the global options map -extern std::unordered_map options; - -// Adders with optional callback support -void addSpin(const std::string& name, int defaultValue, int min, int max, std::function cb = {}); -void addCheck(const std::string& name, bool defaultValue, std::function cb = {}); -void addString(const std::string& name, const std::string& defaultValue, std::function cb = {}); -void addCombo(const std::string& name, const std::string& defaultValue, const std::vector& vars, std::function cb = {}); -void addButton(const std::string& name, std::function cb = {}); - -void setOption(const std::string& name, const std::string& val); - -int getInt(const std::string& name); -std::string getString(const std::string& name); - -void printOptions(); - -} // namespace UCIOptions From 9492960a172f617843a85f4d9338e54c34cc5133 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Tue, 3 Mar 2026 21:04:23 +0700 Subject: [PATCH 03/50] Create games.yml --- .github/workflows/games.yml | 68 +++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 .github/workflows/games.yml diff --git a/.github/workflows/games.yml b/.github/workflows/games.yml new file mode 100644 index 0000000..4034641 --- /dev/null +++ b/.github/workflows/games.yml @@ -0,0 +1,68 @@ +name: Fastchess SPRT Test (Linux) + +on: + workflow_dispatch: + +jobs: + fastchess: + runs-on: ubuntu-latest + + steps: + - name: Checkout main engine + uses: actions/checkout@v4 + + - name: Checkout HandcraftedEngine branch + uses: actions/checkout@v4 + with: + repository: ${{ github.repository }} + ref: HandcraftedEngine + path: cppchess_engine + + - name: Install dependencies + run: | + sudo apt update + sudo apt install -y build-essential cmake wget unzip + + - name: Build new engine + run: | + mkdir build + cd build + cmake .. + make -j$(nproc) + + - name: Build base engine (HandcraftedEngine) + run: | + cd cppchess_engine + mkdir build + cd build + cmake .. + make -j$(nproc) + + - name: Download fastchess + run: | + wget https://github.com/Disservin/fastchess/releases/download/v1.8.0-alpha/fastchess-linux-x86-64.tar + cp fastchess-linux-x86-64/fastchess . + chmod +x fastchess + + - name: Download UHO openings + run: | + wget https://github.com/official-stockfish/books/raw/refs/heads/master/UHO_Lichess_4852_v1.epd.zip + unzip UHO_Lichess_4852_v1.epd.zip + + - name: Run fastchess SPRT + run: | + ./fastchess \ + -engine cmd=build/engine name=new \ + -engine cmd=cppchess_engine/build/chess_engine name=base \ + -openings file=UHO_Lichess_4852_v1.epd format=epd order=random \ + -each tc=10+0.5 \ + -rounds 600 \ + -concurrency 1 \ + -pgnout notation=san nodes=true file=games1.pgn \ + -sprt elo0=0 elo1=2 alpha=0.05 beta=0.05 + + - name: Upload PGN + uses: actions/upload-artifact@v4 + with: + name: games + path: games1.pgn From c423d792e2bb73f57f167179cf34d74b389752d4 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Tue, 3 Mar 2026 21:12:11 +0700 Subject: [PATCH 04/50] Update games.yml --- .github/workflows/games.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/games.yml b/.github/workflows/games.yml index 4034641..e5fcc22 100644 --- a/.github/workflows/games.yml +++ b/.github/workflows/games.yml @@ -27,7 +27,7 @@ jobs: run: | mkdir build cd build - cmake .. + cmake .. -DCMAKE_BUILD_TYPE=Release make -j$(nproc) - name: Build base engine (HandcraftedEngine) @@ -35,7 +35,7 @@ jobs: cd cppchess_engine mkdir build cd build - cmake .. + cmake .. -DCMAKE_BUILD_TYPE=Release make -j$(nproc) - name: Download fastchess From 047a0b5a76bfe57f981b85d18a8b5326af644688 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Tue, 3 Mar 2026 21:13:42 +0700 Subject: [PATCH 05/50] Update timeman.cpp --- timeman.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/timeman.cpp b/timeman.cpp index 68318ce..0920253 100644 --- a/timeman.cpp +++ b/timeman.cpp @@ -6,7 +6,7 @@ #include #include #include - +#include namespace engine::timeman { @@ -88,4 +88,4 @@ namespace engine::timeman TimePoint(std::min(0.825179 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10; } -} \ No newline at end of file +} From e1987a3cf6709e0ba1aa4db9d3216223f7efdaf5 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Tue, 3 Mar 2026 21:21:55 +0700 Subject: [PATCH 06/50] Update games.yml --- .github/workflows/games.yml | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/.github/workflows/games.yml b/.github/workflows/games.yml index e5fcc22..3ef9937 100644 --- a/.github/workflows/games.yml +++ b/.github/workflows/games.yml @@ -23,6 +23,18 @@ jobs: sudo apt update sudo apt install -y build-essential cmake wget unzip + - name: Download fastchess + run: | + wget https://github.com/Disservin/fastchess/releases/download/v1.8.0-alpha/fastchess-linux-x86-64.tar + ls -R + cp fastchess-linux-x86-64/fastchess . + chmod +x fastchess + + - name: Download UHO openings + run: | + wget https://github.com/official-stockfish/books/raw/refs/heads/master/UHO_Lichess_4852_v1.epd.zip + unzip UHO_Lichess_4852_v1.epd.zip + - name: Build new engine run: | mkdir build @@ -38,17 +50,6 @@ jobs: cmake .. -DCMAKE_BUILD_TYPE=Release make -j$(nproc) - - name: Download fastchess - run: | - wget https://github.com/Disservin/fastchess/releases/download/v1.8.0-alpha/fastchess-linux-x86-64.tar - cp fastchess-linux-x86-64/fastchess . - chmod +x fastchess - - - name: Download UHO openings - run: | - wget https://github.com/official-stockfish/books/raw/refs/heads/master/UHO_Lichess_4852_v1.epd.zip - unzip UHO_Lichess_4852_v1.epd.zip - - name: Run fastchess SPRT run: | ./fastchess \ From 9461633f9c75a7cee2455d224d32029008981e0f Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Tue, 3 Mar 2026 21:35:05 +0700 Subject: [PATCH 07/50] Update games.yml --- .github/workflows/games.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/games.yml b/.github/workflows/games.yml index 3ef9937..bcd5cc7 100644 --- a/.github/workflows/games.yml +++ b/.github/workflows/games.yml @@ -10,6 +10,8 @@ jobs: steps: - name: Checkout main engine uses: actions/checkout@v4 + with: + path: test_chesslib - name: Checkout HandcraftedEngine branch uses: actions/checkout@v4 @@ -26,7 +28,7 @@ jobs: - name: Download fastchess run: | wget https://github.com/Disservin/fastchess/releases/download/v1.8.0-alpha/fastchess-linux-x86-64.tar - ls -R + tar -xf fastchess-linux-x86-64.tar cp fastchess-linux-x86-64/fastchess . chmod +x fastchess @@ -37,6 +39,7 @@ jobs: - name: Build new engine run: | + cd test_chesslib mkdir build cd build cmake .. -DCMAKE_BUILD_TYPE=Release From 503549572f65fc9b131311eefd16bc6a79c0ce6b Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Tue, 3 Mar 2026 21:40:53 +0700 Subject: [PATCH 08/50] Update games.yml --- .github/workflows/games.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/games.yml b/.github/workflows/games.yml index bcd5cc7..006708d 100644 --- a/.github/workflows/games.yml +++ b/.github/workflows/games.yml @@ -56,7 +56,7 @@ jobs: - name: Run fastchess SPRT run: | ./fastchess \ - -engine cmd=build/engine name=new \ + -engine cmd=test_chesslib/build/engine name=new \ -engine cmd=cppchess_engine/build/chess_engine name=base \ -openings file=UHO_Lichess_4852_v1.epd format=epd order=random \ -each tc=10+0.5 \ From ca092764f22f0a2054c899118df4e25f08cfb912 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Tue, 3 Mar 2026 22:27:48 +0700 Subject: [PATCH 09/50] Update games.yml --- .github/workflows/games.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/games.yml b/.github/workflows/games.yml index 006708d..8d26b6e 100644 --- a/.github/workflows/games.yml +++ b/.github/workflows/games.yml @@ -55,12 +55,12 @@ jobs: - name: Run fastchess SPRT run: | - ./fastchess \ + ./fastchess -recover \ -engine cmd=test_chesslib/build/engine name=new \ -engine cmd=cppchess_engine/build/chess_engine name=base \ -openings file=UHO_Lichess_4852_v1.epd format=epd order=random \ -each tc=10+0.5 \ - -rounds 600 \ + -rounds 6000 \ -concurrency 1 \ -pgnout notation=san nodes=true file=games1.pgn \ -sprt elo0=0 elo1=2 alpha=0.05 beta=0.05 From 3503dcf9bf605795ada7eeafc7f56da1fb1d31b3 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Tue, 3 Mar 2026 22:31:08 +0700 Subject: [PATCH 10/50] Update games.yml --- .github/workflows/games.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/games.yml b/.github/workflows/games.yml index 8d26b6e..95bf412 100644 --- a/.github/workflows/games.yml +++ b/.github/workflows/games.yml @@ -61,7 +61,7 @@ jobs: -openings file=UHO_Lichess_4852_v1.epd format=epd order=random \ -each tc=10+0.5 \ -rounds 6000 \ - -concurrency 1 \ + -concurrency $(nproc) \ -pgnout notation=san nodes=true file=games1.pgn \ -sprt elo0=0 elo1=2 alpha=0.05 beta=0.05 From 76b2f00b4e7b119864d85deb72056eea4130da3f Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 4 Mar 2026 11:38:37 +0700 Subject: [PATCH 11/50] Update games.yml --- .github/workflows/games.yml | 103 ++++++++++++++++++++++++------------ 1 file changed, 69 insertions(+), 34 deletions(-) diff --git a/.github/workflows/games.yml b/.github/workflows/games.yml index 95bf412..4fe51a0 100644 --- a/.github/workflows/games.yml +++ b/.github/workflows/games.yml @@ -3,39 +3,21 @@ name: Fastchess SPRT Test (Linux) on: workflow_dispatch: +permissions: + contents: write + jobs: - fastchess: + + build-new: runs-on: ubuntu-latest steps: - - name: Checkout main engine - uses: actions/checkout@v4 + - uses: actions/checkout@v4 with: path: test_chesslib - - name: Checkout HandcraftedEngine branch - uses: actions/checkout@v4 - with: - repository: ${{ github.repository }} - ref: HandcraftedEngine - path: cppchess_engine - - - name: Install dependencies - run: | - sudo apt update - sudo apt install -y build-essential cmake wget unzip - - - name: Download fastchess - run: | - wget https://github.com/Disservin/fastchess/releases/download/v1.8.0-alpha/fastchess-linux-x86-64.tar - tar -xf fastchess-linux-x86-64.tar - cp fastchess-linux-x86-64/fastchess . - chmod +x fastchess - - - name: Download UHO openings - run: | - wget https://github.com/official-stockfish/books/raw/refs/heads/master/UHO_Lichess_4852_v1.epd.zip - unzip UHO_Lichess_4852_v1.epd.zip + - name: Install deps + run: sudo apt update && sudo apt install -y build-essential cmake - name: Build new engine run: | @@ -45,19 +27,68 @@ jobs: cmake .. -DCMAKE_BUILD_TYPE=Release make -j$(nproc) - - name: Build base engine (HandcraftedEngine) + - name: Upload new engine + uses: actions/upload-artifact@v4 + with: + name: new-engine + path: test_chesslib/build/engine + + + build-base: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + ref: HandcraftedEngine + path: handcrafted + + - name: Install deps + run: sudo apt update && sudo apt install -y build-essential cmake + + - name: Build base engine run: | - cd cppchess_engine + cd handcrafted mkdir build cd build cmake .. -DCMAKE_BUILD_TYPE=Release make -j$(nproc) - - name: Run fastchess SPRT + - name: Upload base engine + uses: actions/upload-artifact@v4 + with: + name: base-engine + path: handcrafted/build/chess_engine + + + test: + runs-on: ubuntu-latest + needs: [build-new, build-base] + + steps: + - name: Download engines + uses: actions/download-artifact@v4 + + - name: Install tools + run: | + sudo apt update + sudo apt install -y wget unzip + wget https://github.com/Disservin/fastchess/releases/download/v1.8.0-alpha/fastchess-linux-x86-64.tar + tar -xf fastchess-linux-x86-64.tar + chmod +x fastchess-linux-x86-64 + mv fastchess-linux-x86-64 fastchess + + wget https://github.com/official-stockfish/books/raw/refs/heads/master/UHO_Lichess_4852_v1.epd.zip + unzip UHO_Lichess_4852_v1.epd.zip + + wget https://github.com/michiguel/Ordo/releases/download/1.0/ordo-linux64 + chmod +x ordo-linux64 + + - name: Run fastchess run: | ./fastchess -recover \ - -engine cmd=test_chesslib/build/engine name=new \ - -engine cmd=cppchess_engine/build/chess_engine name=base \ + -engine cmd=new-engine/engine name=new \ + -engine cmd=base-engine/chess_engine name=base \ -openings file=UHO_Lichess_4852_v1.epd format=epd order=random \ -each tc=10+0.5 \ -rounds 6000 \ @@ -65,8 +96,12 @@ jobs: -pgnout notation=san nodes=true file=games1.pgn \ -sprt elo0=0 elo1=2 alpha=0.05 beta=0.05 - - name: Upload PGN + ./ordo-linux64 -o ratings.txt -- games1.pgn + + - name: Upload results uses: actions/upload-artifact@v4 with: - name: games - path: games1.pgn + name: results + path: | + games1.pgn + ratings.txt From 567967bf7c3d3754bfb804331c0116a1034e1a2a Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 4 Mar 2026 11:40:29 +0700 Subject: [PATCH 12/50] Update eval.cpp --- eval.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/eval.cpp b/eval.cpp index 33403f9..aa977eb 100644 --- a/eval.cpp +++ b/eval.cpp @@ -197,11 +197,16 @@ namespace engine::eval{ while (occ) { Square i = (Square)pop_lsb(occ); auto p = board.at(i); - mgScore += mg_pesto_table[piece_of(p)][color_of(p) == BLACK ? square_mirror(i) : i]; - egScore += eg_pesto_table[piece_of(p)][color_of(p) == BLACK ? square_mirror(i) : i]; + int sign=1; + if (color_of(p)==BLACK){ + _sign=-1; + i = square_mirror(i); + } + mgScore += _sign*mg_pesto_table[piece_of(p)][i]; + egScore += _sign*eg_pesto_table[piece_of(p)][i]; } } int finalScore = ((mgScore * phase) + (egScore * (256 - phase))) / 256 * sign; return finalScore; } -} \ No newline at end of file +} From 66ef091d80b5a02211bba1fff8ba376188bb4235 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 4 Mar 2026 11:57:18 +0700 Subject: [PATCH 13/50] Update eval.cpp --- eval.cpp | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/eval.cpp b/eval.cpp index aa977eb..ab9636d 100644 --- a/eval.cpp +++ b/eval.cpp @@ -5,9 +5,9 @@ using namespace chess; using namespace engine::eval; namespace engine::eval{ - constexpr int16_t PawnValue = 100, KnightValue = 325, BishopValue = 350, RookValue = 500, QueenValue = 900; + constexpr Value PawnValue = 100, KnightValue = 325, BishopValue = 350, RookValue = 500, QueenValue = 900; - int mg_pawn_table[64] = { + Value mg_pawn_table[64] = { 0, 0, 0, 0, 0, 0, 0, 0, 98, 134, 61, 95, 68, 126, 34, -11, -6, 7, 26, 31, 65, 56, 25, -20, @@ -18,7 +18,7 @@ namespace engine::eval{ 0, 0, 0, 0, 0, 0, 0, 0, }; - int eg_pawn_table[64] = { + Value eg_pawn_table[64] = { 0, 0, 0, 0, 0, 0, 0, 0, 178, 173, 158, 134, 147, 132, 165, 187, 94, 100, 85, 67, 56, 53, 82, 84, @@ -29,7 +29,7 @@ namespace engine::eval{ 0, 0, 0, 0, 0, 0, 0, 0, }; - int mg_knight_table[64] = { + Value mg_knight_table[64] = { -167, -89, -34, -49, 61, -97, -15, -107, -73, -41, 72, 36, 23, 62, 7, -17, -47, 60, 37, 65, 84, 129, 73, 44, @@ -40,7 +40,7 @@ namespace engine::eval{ -105, -21, -58, -33, -17, -28, -19, -23, }; - int eg_knight_table[64] = { + Value eg_knight_table[64] = { -58, -38, -13, -28, -31, -27, -63, -99, -25, -8, -25, -2, -9, -25, -24, -52, -24, -20, 10, 9, -1, -9, -19, -41, @@ -51,7 +51,7 @@ namespace engine::eval{ -29, -51, -23, -15, -22, -18, -50, -64, }; - int mg_bishop_table[64] = { + Value mg_bishop_table[64] = { -29, 4, -82, -37, -25, -42, 7, -8, -26, 16, -18, -13, 30, 59, 18, -47, -16, 37, 43, 40, 35, 50, 37, -2, @@ -62,7 +62,7 @@ namespace engine::eval{ -33, -3, -14, -21, -13, -12, -39, -21, }; - int eg_bishop_table[64] = { + Value eg_bishop_table[64] = { -14, -21, -11, -8, -7, -9, -17, -24, -8, -4, 7, -12, -3, -13, -4, -14, 2, -8, 0, -1, -2, 6, 0, 4, @@ -73,7 +73,7 @@ namespace engine::eval{ -23, -9, -23, -5, -9, -16, -5, -17, }; - int mg_rook_table[64] = { + Value mg_rook_table[64] = { 32, 42, 32, 51, 63, 9, 31, 43, 27, 32, 58, 62, 80, 67, 26, 44, -5, 19, 26, 36, 17, 45, 61, 16, @@ -84,7 +84,7 @@ namespace engine::eval{ -19, -13, 1, 17, 16, 7, -37, -26, }; - int eg_rook_table[64] = { + Value eg_rook_table[64] = { 13, 10, 18, 15, 12, 12, 8, 5, 11, 13, 13, 11, -3, 3, 8, 3, 7, 7, 7, 5, 4, -3, -5, -3, @@ -95,7 +95,7 @@ namespace engine::eval{ -9, 2, 3, -1, -5, -13, 4, -20, }; - int mg_queen_table[64] = { + Value mg_queen_table[64] = { -28, 0, 29, 12, 59, 44, 43, 45, -24, -39, -5, 1, -16, 57, 28, 54, -13, -17, 7, 8, 29, 56, 47, 57, @@ -106,7 +106,7 @@ namespace engine::eval{ -1, -18, -9, 10, -15, -25, -31, -50, }; - int eg_queen_table[64] = { + Value eg_queen_table[64] = { -9, 22, 22, 27, 27, 19, 10, 20, -17, 20, 32, 41, 58, 25, 30, 0, -20, 6, 9, 49, 47, 35, 19, 9, @@ -117,7 +117,7 @@ namespace engine::eval{ -33, -28, -22, -43, -5, -32, -20, -41, }; - int mg_king_table[64] = { + Value mg_king_table[64] = { -65, 23, 16, -15, -56, -34, 2, 13, 29, -1, -20, -7, -8, -4, -38, -29, -9, 24, 2, -16, -20, 6, 22, -22, @@ -128,7 +128,7 @@ namespace engine::eval{ -15, 36, 12, -54, 8, -28, 24, 14, }; - int eg_king_table[64] = { + Value eg_king_table[64] = { -74, -35, -18, -18, -11, 15, 4, -17, -12, 17, 14, 17, 17, 38, 23, 11, 10, 17, 23, 15, 20, 45, 44, 13, @@ -139,7 +139,7 @@ namespace engine::eval{ -53, -34, -21, -11, -28, -14, -24, -43 }; - int *mg_pesto_table[] = + Value *mg_pesto_table[] = { {0}, mg_pawn_table, @@ -150,7 +150,7 @@ namespace engine::eval{ mg_king_table }; - int *eg_pesto_table[] = + Value *eg_pesto_table[] = { {0}, eg_pawn_table, @@ -174,7 +174,7 @@ namespace engine::eval{ board.count(), board.count(), }; - int material = (pieceCount[0] * PawnValue + pieceCount[1] * KnightValue + pieceCount[2] * BishopValue + pieceCount[3] * RookValue + pieceCount[4] * QueenValue) + Value material = (pieceCount[0] * PawnValue + pieceCount[1] * KnightValue + pieceCount[2] * BishopValue + pieceCount[3] * RookValue + pieceCount[4] * QueenValue) - (pieceCount[5] * PawnValue + pieceCount[6] * KnightValue + pieceCount[7] * BishopValue + pieceCount[8] * RookValue + pieceCount[9] * QueenValue); constexpr int KnightPhase = 1; constexpr int BishopPhase = 1; @@ -206,7 +206,11 @@ namespace engine::eval{ egScore += _sign*eg_pesto_table[piece_of(p)][i]; } } - int finalScore = ((mgScore * phase) + (egScore * (256 - phase))) / 256 * sign; + Value finalScore = ((mgScore * phase) + (egScore * (256 - phase))) / 256 * sign; return finalScore; } + Value piece_value(PieceType pt){ + Value pieces[]={0,PawnValue,KnightValue,BishopValue,RookValue,QueenValue}; + return pieces[pt]; + } } From 55eafd96b7f04d988043fc697d95e3e20ca247e2 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 4 Mar 2026 11:57:50 +0700 Subject: [PATCH 14/50] Update eval.h --- eval.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eval.h b/eval.h index 9c46b48..198b3eb 100644 --- a/eval.h +++ b/eval.h @@ -33,5 +33,6 @@ namespace engine{ constexpr Value MATE_DISTANCE(int i) { return VALUE_MATE - (i<0?-i:i); } namespace eval { Value eval(const chess::Board &board); + Value piece_value(PieceType pt); } -} \ No newline at end of file +} From 5f2e240bd5628279f5df4ffc83bfbd26f2b4b416 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 4 Mar 2026 12:00:40 +0700 Subject: [PATCH 15/50] Update movepick.cpp --- movepick.cpp | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/movepick.cpp b/movepick.cpp index 9d26651..afdc872 100644 --- a/movepick.cpp +++ b/movepick.cpp @@ -3,8 +3,39 @@ #include using namespace chess; namespace engine { + Value historyHeuristic[SQUARE_NB][SQUARE_NB]{}; + Move killerMoves[MAX_PLY][2]; void movepick::orderMoves(chess::Board & board, chess::Movelist & moves, chess::Move ttMove, int ply) { - std::vector> moves_; + std::vector> scoredMoves; + scoredMoves.reserve(moves.size()); + + for (const auto& move : moves) + { + Value score = 0; + + if (move == ttMove) + score = 10000; + else if (board.isCapture(move)) + score = + ((move.type() & EN_PASSANT)==0?piece_value(board.at(move.to())):piece_value(PAWN))*10 - piece_value(board.at(move.from())); + else if (move == killerMoves[ply][0]) + score = 8500; + else if (move == killerMoves[ply][1]) + score = 8000; + else + score = historyHeuristic[move.from().index()][move.to().index()]; + + scoredMoves.emplace_back(move, score); + } + + std::stable_sort(scoredMoves.begin(), scoredMoves.end(), + [](const auto& a, const auto& b) + { + return a.second > b.second; + }); + + for (size_t i = 0; i < scoredMoves.size(); ++i) + moves[i] = scoredMoves[i].first; } } From 87018d5bed169d0cf2d7bb5d0112043b6ef03033 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 4 Mar 2026 12:04:19 +0700 Subject: [PATCH 16/50] Update movepick.h --- movepick.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/movepick.h b/movepick.h index 956e5e7..0b18108 100644 --- a/movepick.h +++ b/movepick.h @@ -2,4 +2,6 @@ #include namespace engine::movepick { void orderMoves(chess::Board &, chess::Movelist &, chess::Move, int); -} \ No newline at end of file + extern Value historyHeuristic[64][64]{}; + extern Move killerMoves[256][2]; +} From e0113c402a90a403e3dfeffd8e74f0643a0fa4fc Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 4 Mar 2026 12:07:57 +0700 Subject: [PATCH 17/50] Update movepick.cpp --- movepick.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/movepick.cpp b/movepick.cpp index afdc872..4d745d0 100644 --- a/movepick.cpp +++ b/movepick.cpp @@ -2,10 +2,10 @@ #include "eval.h" #include using namespace chess; -namespace engine { +namespace engine::movepick { Value historyHeuristic[SQUARE_NB][SQUARE_NB]{}; Move killerMoves[MAX_PLY][2]; - void movepick::orderMoves(chess::Board & board, chess::Movelist & moves, chess::Move ttMove, int ply) + void orderMoves(chess::Board & board, chess::Movelist & moves, chess::Move ttMove, int ply) { std::vector> scoredMoves; scoredMoves.reserve(moves.size()); From 6a3b428efec003a5ae49314248e395c0b8d7e2a5 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 4 Mar 2026 12:09:20 +0700 Subject: [PATCH 18/50] Update search.cpp --- search.cpp | 57 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/search.cpp b/search.cpp index abbd412..16bf5b0 100644 --- a/search.cpp +++ b/search.cpp @@ -2,6 +2,7 @@ #include "eval.h" #include "timeman.h" #include "uci.h" +#include "movepick.h" #include #include #include @@ -30,7 +31,7 @@ namespace engine *pv++ = *childPv++; *pv = Move::none(); } - Value doSearch(Board &board, int depth, Session &session, int ply = 0) + Value doSearch(Board &board, int depth, Value alpha, Value beta, Session &session, int ply = 0) { std::fill(std::begin(session.pv[ply + 1]), std::end(session.pv[ply + 1]), Move::none()); @@ -49,9 +50,20 @@ namespace engine Move preferred = Move::none(); if (TTEntry *entry = search::tt.lookup(hash)) { + preferred = Move(entry->getMove()); if (entry->getDepth() >= depth) { - preferred = Move(entry->getMove()); + Value ttScore = entry->getScore(); + TTFlag flag = entry->getFlag(); + + if (flag == TTFlag::EXACT) + return ttScore; + + if (flag == TTFlag::LOWERBOUND && ttScore >= beta) + return ttScore; + + if (flag == TTFlag::UPPERBOUND && ttScore <= alpha) + return ttScore; } } if (depth == 0) @@ -64,18 +76,16 @@ namespace engine board.legals(moves); if (!moves.size()) { - // Checkmate or stalemate handling should go here if you implement it session.pv[ply][0] = Move::none(); return board.checkers() ? -MATE(ply) : 0; } - + movepick::orderMoves(board, moves, preferred, ply); for (Move move : moves) { board.doMove(move); - // ---- RECURSIVE CALL ---- - Value childScore = doSearch(board, depth - 1, session, ply + 1); + Value childScore = doSearch(board, depth - 1, -beta, -alpha, session, ply + 1); board.undoMove(); @@ -91,17 +101,43 @@ namespace engine update_pv(session.pv[ply], move, session.pv[ply + 1]); } - // Local time check (optional but fine) + if (score > alpha){ + alpha = score; + if (!board.isCapture(move)) + historyHeuristic[from][to] += depth * depth; + } + if (alpha >= beta) + { + if (!board.isCapture(move)) + { + if (killerMoves[ply][0] != move) + { + killerMoves[ply][1] = killerMoves[ply][0]; + killerMoves[ply][0] = move; + } + } + + break; + } + if (session.tm.elapsed() >= session.tm.optimum() || stopSearch.load(std::memory_order_relaxed)) return VALUE_NONE; } - // Store only if valid if (maxScore != -VALUE_INFINITE) - search::tt.store(hash, session.pv[ply][0], maxScore, depth, TTFlag::EXACT); + TTFlag flag; + if (maxScore <= alphaOrig) + flag = TTFlag::UPPERBOUND; + else if (maxScore >= beta) + flag = TTFlag::LOWERBOUND; + else + flag = TTFlag::EXACT; + + search::tt.store(hash, session.pv[ply][0], maxScore, depth, flag); + } return maxScore; } void search::search(const chess::Board &board, @@ -115,9 +151,10 @@ namespace engine chess::Move lastPV[MAX_PLY]{}; for (int i = 1; i < timecontrol.depth; i++) { + for (int i=0;i<64;i++)for (int j=0;j<64;j++)movepick::historyHeuristic[i][j]/=2; session.nodes = 0; auto board_ = board; - Value score_ = doSearch(board_, i, session); + Value score_ = doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); if (session.tm.elapsed() >= session.tm.optimum() || stopSearch.load(std::memory_order_relaxed) || score_ == VALUE_NONE) From c6259a6f8b5b1cd7f6cf051e180d9af14d348046 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 4 Mar 2026 12:10:36 +0700 Subject: [PATCH 19/50] Update eval.h --- eval.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eval.h b/eval.h index 198b3eb..be6eed9 100644 --- a/eval.h +++ b/eval.h @@ -33,6 +33,6 @@ namespace engine{ constexpr Value MATE_DISTANCE(int i) { return VALUE_MATE - (i<0?-i:i); } namespace eval { Value eval(const chess::Board &board); - Value piece_value(PieceType pt); + Value piece_value(chess::PieceType pt); } } From 112efae352c2a1caa64c650c41cc655e91fb50eb Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 4 Mar 2026 12:12:08 +0700 Subject: [PATCH 20/50] Update eval.cpp --- eval.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eval.cpp b/eval.cpp index ab9636d..19cc7bc 100644 --- a/eval.cpp +++ b/eval.cpp @@ -197,7 +197,7 @@ namespace engine::eval{ while (occ) { Square i = (Square)pop_lsb(occ); auto p = board.at(i); - int sign=1; + int _sign=1; if (color_of(p)==BLACK){ _sign=-1; i = square_mirror(i); From 5a6469962a6b4d8d2e657dd43f16eaaa3fa7313d Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 4 Mar 2026 12:14:47 +0700 Subject: [PATCH 21/50] Update movepick.h --- movepick.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/movepick.h b/movepick.h index 0b18108..6fed1c4 100644 --- a/movepick.h +++ b/movepick.h @@ -2,6 +2,6 @@ #include namespace engine::movepick { void orderMoves(chess::Board &, chess::Movelist &, chess::Move, int); - extern Value historyHeuristic[64][64]{}; - extern Move killerMoves[256][2]; + extern int historyHeuristic[64][64]; + extern chess::Move killerMoves[256][2]; } From cfa8687de7cc63a4bea35cc0c1de58c73df46406 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 4 Mar 2026 12:17:59 +0700 Subject: [PATCH 22/50] Update search.cpp --- search.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/search.cpp b/search.cpp index 16bf5b0..230ea99 100644 --- a/search.cpp +++ b/search.cpp @@ -33,6 +33,7 @@ namespace engine } Value doSearch(Board &board, int depth, Value alpha, Value beta, Session &session, int ply = 0) { + Value alphaOrig=alpha; std::fill(std::begin(session.pv[ply + 1]), std::end(session.pv[ply + 1]), Move::none()); if (session.tm.elapsed() >= @@ -104,16 +105,16 @@ namespace engine if (score > alpha){ alpha = score; if (!board.isCapture(move)) - historyHeuristic[from][to] += depth * depth; + movepick::historyHeuristic[from][to] += depth * depth; } if (alpha >= beta) { if (!board.isCapture(move)) { - if (killerMoves[ply][0] != move) + if (movepick::killerMoves[ply][0] != move) { - killerMoves[ply][1] = killerMoves[ply][0]; - killerMoves[ply][0] = move; + movepick::killerMoves[ply][1] = killerMoves[ply][0]; + movepick::killerMoves[ply][0] = move; } } From e60c4a2c226acdf576004caaaa8c930b013e107d Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 4 Mar 2026 12:53:31 +0700 Subject: [PATCH 23/50] Update search.cpp --- search.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/search.cpp b/search.cpp index 230ea99..0b0974d 100644 --- a/search.cpp +++ b/search.cpp @@ -105,7 +105,7 @@ namespace engine if (score > alpha){ alpha = score; if (!board.isCapture(move)) - movepick::historyHeuristic[from][to] += depth * depth; + movepick::historyHeuristic[(int)move.from()][(int)move.to()] += depth * depth; } if (alpha >= beta) { @@ -113,7 +113,7 @@ namespace engine { if (movepick::killerMoves[ply][0] != move) { - movepick::killerMoves[ply][1] = killerMoves[ply][0]; + movepick::killerMoves[ply][1] = movepick::killerMoves[ply][0]; movepick::killerMoves[ply][0] = move; } } @@ -127,7 +127,7 @@ namespace engine return VALUE_NONE; } - if (maxScore != -VALUE_INFINITE) + if (maxScore != -VALUE_INFINITE){ TTFlag flag; if (maxScore <= alphaOrig) From 069362e07d14729eac4ae545bcc6610cd9ef2c22 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 4 Mar 2026 12:56:01 +0700 Subject: [PATCH 24/50] Update movepick.cpp --- movepick.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/movepick.cpp b/movepick.cpp index 4d745d0..a7d1975 100644 --- a/movepick.cpp +++ b/movepick.cpp @@ -24,7 +24,7 @@ namespace engine::movepick { else if (move == killerMoves[ply][1]) score = 8000; else - score = historyHeuristic[move.from().index()][move.to().index()]; + score = historyHeuristic[move.from()][move.to()]; scoredMoves.emplace_back(move, score); } From 5d8846f6835c843295a8ed2328fadae3ce72c20f Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 4 Mar 2026 13:00:32 +0700 Subject: [PATCH 25/50] Update movepick.cpp --- movepick.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/movepick.cpp b/movepick.cpp index a7d1975..be0bd73 100644 --- a/movepick.cpp +++ b/movepick.cpp @@ -1,10 +1,11 @@ #include "movepick.h" #include "eval.h" #include +#include using namespace chess; namespace engine::movepick { Value historyHeuristic[SQUARE_NB][SQUARE_NB]{}; - Move killerMoves[MAX_PLY][2]; + Move killerMoves[64][2]; void orderMoves(chess::Board & board, chess::Movelist & moves, chess::Move ttMove, int ply) { std::vector> scoredMoves; From 391b6f3c1eaaeca5d28b8020ed740f7e9de67ac6 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 4 Mar 2026 13:05:49 +0700 Subject: [PATCH 26/50] Update movepick.cpp --- movepick.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/movepick.cpp b/movepick.cpp index be0bd73..95cf087 100644 --- a/movepick.cpp +++ b/movepick.cpp @@ -3,6 +3,7 @@ #include #include using namespace chess; +using engine::eval::piece_value; namespace engine::movepick { Value historyHeuristic[SQUARE_NB][SQUARE_NB]{}; Move killerMoves[64][2]; From 5b33054cb6d3aa5876f8912b38056d849017af1d Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 4 Mar 2026 13:06:06 +0700 Subject: [PATCH 27/50] Update movepick.cpp --- movepick.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/movepick.cpp b/movepick.cpp index 95cf087..5984106 100644 --- a/movepick.cpp +++ b/movepick.cpp @@ -6,7 +6,7 @@ using namespace chess; using engine::eval::piece_value; namespace engine::movepick { Value historyHeuristic[SQUARE_NB][SQUARE_NB]{}; - Move killerMoves[64][2]; + Move killerMoves[256][2]; void orderMoves(chess::Board & board, chess::Movelist & moves, chess::Move ttMove, int ply) { std::vector> scoredMoves; From 9a01ca5dfc113e3b6249f1513d21b590941d46cf Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 4 Mar 2026 13:08:12 +0700 Subject: [PATCH 28/50] Update movepick.cpp --- movepick.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/movepick.cpp b/movepick.cpp index 5984106..6960b35 100644 --- a/movepick.cpp +++ b/movepick.cpp @@ -20,7 +20,7 @@ namespace engine::movepick { score = 10000; else if (board.isCapture(move)) score = - ((move.type() & EN_PASSANT)==0?piece_value(board.at(move.to())):piece_value(PAWN))*10 - piece_value(board.at(move.from())); + ((move.typeOf() & EN_PASSANT)==0?piece_value(board.at(move.to())):piece_value(PAWN))*10 - piece_value(board.at(move.from())); else if (move == killerMoves[ply][0]) score = 8500; else if (move == killerMoves[ply][1]) From 14a9ecce97184ca9c883223396b737aa5880e14b Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 4 Mar 2026 13:15:14 +0700 Subject: [PATCH 29/50] Update games.yml --- .github/workflows/games.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/games.yml b/.github/workflows/games.yml index 4fe51a0..77b419d 100644 --- a/.github/workflows/games.yml +++ b/.github/workflows/games.yml @@ -75,8 +75,8 @@ jobs: sudo apt install -y wget unzip wget https://github.com/Disservin/fastchess/releases/download/v1.8.0-alpha/fastchess-linux-x86-64.tar tar -xf fastchess-linux-x86-64.tar - chmod +x fastchess-linux-x86-64 - mv fastchess-linux-x86-64 fastchess + chmod +x fastchess-linux-x86-64/fastchess-linux-x86-64 + mv fastchess-linux-x86-64/fastchess-linux-x86-64 fastchess wget https://github.com/official-stockfish/books/raw/refs/heads/master/UHO_Lichess_4852_v1.epd.zip unzip UHO_Lichess_4852_v1.epd.zip From f16cf329cdb5d9a4dddfc983d4ab041029e13b19 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 4 Mar 2026 13:18:36 +0700 Subject: [PATCH 30/50] Update games.yml --- .github/workflows/games.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/games.yml b/.github/workflows/games.yml index 77b419d..7bd343c 100644 --- a/.github/workflows/games.yml +++ b/.github/workflows/games.yml @@ -75,8 +75,8 @@ jobs: sudo apt install -y wget unzip wget https://github.com/Disservin/fastchess/releases/download/v1.8.0-alpha/fastchess-linux-x86-64.tar tar -xf fastchess-linux-x86-64.tar - chmod +x fastchess-linux-x86-64/fastchess-linux-x86-64 - mv fastchess-linux-x86-64/fastchess-linux-x86-64 fastchess + chmod +x fastchess-linux-x86-64/fastchess + mv fastchess-linux-x86-64/fastchess fastchess wget https://github.com/official-stockfish/books/raw/refs/heads/master/UHO_Lichess_4852_v1.epd.zip unzip UHO_Lichess_4852_v1.epd.zip From 3e18c92f2dbce2b5aa4bfd1d231429e2dcefd811 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 4 Mar 2026 13:22:36 +0700 Subject: [PATCH 31/50] Update games.yml --- .github/workflows/games.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/games.yml b/.github/workflows/games.yml index 7bd343c..67d6d32 100644 --- a/.github/workflows/games.yml +++ b/.github/workflows/games.yml @@ -83,6 +83,8 @@ jobs: wget https://github.com/michiguel/Ordo/releases/download/1.0/ordo-linux64 chmod +x ordo-linux64 + chmod +x new-engine/engine + chmod +x base-engine/chess_engine - name: Run fastchess run: | From 957b18b866a86ae2fa57b17c13371cf1c1f12e07 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 4 Mar 2026 13:27:59 +0700 Subject: [PATCH 32/50] extend time control (6*time) --- .github/workflows/games.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/games.yml b/.github/workflows/games.yml index 67d6d32..1e9fccd 100644 --- a/.github/workflows/games.yml +++ b/.github/workflows/games.yml @@ -92,7 +92,7 @@ jobs: -engine cmd=new-engine/engine name=new \ -engine cmd=base-engine/chess_engine name=base \ -openings file=UHO_Lichess_4852_v1.epd format=epd order=random \ - -each tc=10+0.5 \ + -each tc=60+0.5 \ -rounds 6000 \ -concurrency $(nproc) \ -pgnout notation=san nodes=true file=games1.pgn \ From bda9208a8fa310bf14d4126143580000d86cfed2 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 4 Mar 2026 21:58:36 +0700 Subject: [PATCH 33/50] Update games.yml --- .github/workflows/games.yml | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/games.yml b/.github/workflows/games.yml index 1e9fccd..5565427 100644 --- a/.github/workflows/games.yml +++ b/.github/workflows/games.yml @@ -2,7 +2,15 @@ name: Fastchess SPRT Test (Linux) on: workflow_dispatch: - + inputs: + rounds: + description: "Number of SPRT rounds" + required: false + default: "6000" + tc: + description: "Time control" + required: false + default: "60+0.5" permissions: contents: write @@ -92,8 +100,8 @@ jobs: -engine cmd=new-engine/engine name=new \ -engine cmd=base-engine/chess_engine name=base \ -openings file=UHO_Lichess_4852_v1.epd format=epd order=random \ - -each tc=60+0.5 \ - -rounds 6000 \ + -each tc=${{ github.event.inputs.tc }} \ + -rounds ${{ github.event.inputs.rounds }} \ -concurrency $(nproc) \ -pgnout notation=san nodes=true file=games1.pgn \ -sprt elo0=0 elo1=2 alpha=0.05 beta=0.05 From 3f12d43c70808f1f74c70141dd6e6d76787c9e2d Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 4 Mar 2026 21:58:52 +0700 Subject: [PATCH 34/50] set pv if really hit TT --- search.cpp | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/search.cpp b/search.cpp index 0b0974d..7cc11b4 100644 --- a/search.cpp +++ b/search.cpp @@ -49,23 +49,31 @@ namespace engine session.seldepth = std::max(session.seldepth, ply); uint64_t hash = board.hash(); Move preferred = Move::none(); - if (TTEntry *entry = search::tt.lookup(hash)) + if (entry->getDepth() >= depth) { - preferred = Move(entry->getMove()); - if (entry->getDepth() >= depth) - { Value ttScore = entry->getScore(); TTFlag flag = entry->getFlag(); - + if (flag == TTFlag::EXACT) + { + session.pv[ply][0] = Move(entry->getMove()); + session.pv[ply][1] = Move::none(); return ttScore; - + } + if (flag == TTFlag::LOWERBOUND && ttScore >= beta) + { + session.pv[ply][0] = Move(entry->getMove()); + session.pv[ply][1] = Move::none(); return ttScore; - + } + if (flag == TTFlag::UPPERBOUND && ttScore <= alpha) + { + session.pv[ply][0] = Move(entry->getMove()); + session.pv[ply][1] = Move::none(); return ttScore; - } + } } if (depth == 0) { From 9e86d6751086ecc6e94b122a958c1e24c0a3f9a5 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 4 Mar 2026 22:02:54 +0700 Subject: [PATCH 35/50] Update games.yml --- .github/workflows/games.yml | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/.github/workflows/games.yml b/.github/workflows/games.yml index 5565427..63374f7 100644 --- a/.github/workflows/games.yml +++ b/.github/workflows/games.yml @@ -3,14 +3,19 @@ name: Fastchess SPRT Test (Linux) on: workflow_dispatch: inputs: + base_ref: + description: "Base branch, tag, or commit" + required: false + default: "HandcraftedEngine" rounds: - description: "Number of SPRT rounds" + description: "Max rounds for SPRT" required: false default: "6000" tc: - description: "Time control" + description: "Time control (e.g. 60+0.5 or 10+0.1)" required: false default: "60+0.5" + permissions: contents: write @@ -48,7 +53,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - ref: HandcraftedEngine + ref: ${{ github.event.inputs.base_ref }} path: handcrafted - name: Install deps @@ -81,6 +86,7 @@ jobs: run: | sudo apt update sudo apt install -y wget unzip + wget https://github.com/Disservin/fastchess/releases/download/v1.8.0-alpha/fastchess-linux-x86-64.tar tar -xf fastchess-linux-x86-64.tar chmod +x fastchess-linux-x86-64/fastchess @@ -91,6 +97,7 @@ jobs: wget https://github.com/michiguel/Ordo/releases/download/1.0/ordo-linux64 chmod +x ordo-linux64 + chmod +x new-engine/engine chmod +x base-engine/chess_engine @@ -103,15 +110,15 @@ jobs: -each tc=${{ github.event.inputs.tc }} \ -rounds ${{ github.event.inputs.rounds }} \ -concurrency $(nproc) \ - -pgnout notation=san nodes=true file=games1.pgn \ + -pgnout notation=san nodes=true file=games.pgn \ -sprt elo0=0 elo1=2 alpha=0.05 beta=0.05 - ./ordo-linux64 -o ratings.txt -- games1.pgn + ./ordo-linux64 -o ratings.txt -- games.pgn - name: Upload results uses: actions/upload-artifact@v4 with: name: results path: | - games1.pgn + games.pgn ratings.txt From 84af61b9e4fdd1e4e0d4c533099f05e9c03acb50 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 4 Mar 2026 22:12:20 +0700 Subject: [PATCH 36/50] Update search.cpp --- search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/search.cpp b/search.cpp index 7cc11b4..584686d 100644 --- a/search.cpp +++ b/search.cpp @@ -49,7 +49,7 @@ namespace engine session.seldepth = std::max(session.seldepth, ply); uint64_t hash = board.hash(); Move preferred = Move::none(); - if (entry->getDepth() >= depth) + if (TTEntry *entry = search::tt.lookup(hash);entry->getDepth() >= depth) { Value ttScore = entry->getScore(); TTFlag flag = entry->getFlag(); From e16df82335a1b44047e05906dd1208eeb72dc8ae Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 4 Mar 2026 22:18:01 +0700 Subject: [PATCH 37/50] Update games.yml --- .github/workflows/games.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/games.yml b/.github/workflows/games.yml index 63374f7..fa4a54b 100644 --- a/.github/workflows/games.yml +++ b/.github/workflows/games.yml @@ -15,6 +15,10 @@ on: description: "Time control (e.g. 60+0.5 or 10+0.1)" required: false default: "60+0.5" + output_exec: + description: "Executable output file name" + required: false + default: "chess_engine" permissions: contents: write @@ -71,7 +75,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: base-engine - path: handcrafted/build/chess_engine + path: handcrafted/build/${{ github.event.inputs.output_exec }} test: From 40c769b58be61fcf4ef52cc74a66b7b2a03cc8ce Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 4 Mar 2026 22:34:00 +0700 Subject: [PATCH 38/50] Update games.yml --- .github/workflows/games.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/games.yml b/.github/workflows/games.yml index fa4a54b..ad8e2d9 100644 --- a/.github/workflows/games.yml +++ b/.github/workflows/games.yml @@ -103,13 +103,13 @@ jobs: chmod +x ordo-linux64 chmod +x new-engine/engine - chmod +x base-engine/chess_engine + chmod +x base-engine/${{ github.event.inputs.output_exec }} - name: Run fastchess run: | ./fastchess -recover \ -engine cmd=new-engine/engine name=new \ - -engine cmd=base-engine/chess_engine name=base \ + -engine cmd=base-engine/${{ github.event.inputs.output_exec }} name=base \ -openings file=UHO_Lichess_4852_v1.epd format=epd order=random \ -each tc=${{ github.event.inputs.tc }} \ -rounds ${{ github.event.inputs.rounds }} \ From 608c8ecd6e05feb731daabc822333087ca79bfa0 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 4 Mar 2026 22:38:06 +0700 Subject: [PATCH 39/50] fix regression of null pointer deref --- search.cpp | 53 +++++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/search.cpp b/search.cpp index 584686d..ab4846e 100644 --- a/search.cpp +++ b/search.cpp @@ -49,32 +49,33 @@ namespace engine session.seldepth = std::max(session.seldepth, ply); uint64_t hash = board.hash(); Move preferred = Move::none(); - if (TTEntry *entry = search::tt.lookup(hash);entry->getDepth() >= depth) - { - Value ttScore = entry->getScore(); - TTFlag flag = entry->getFlag(); - - if (flag == TTFlag::EXACT) - { - session.pv[ply][0] = Move(entry->getMove()); - session.pv[ply][1] = Move::none(); - return ttScore; - } - - if (flag == TTFlag::LOWERBOUND && ttScore >= beta) - { - session.pv[ply][0] = Move(entry->getMove()); - session.pv[ply][1] = Move::none(); - return ttScore; - } - - if (flag == TTFlag::UPPERBOUND && ttScore <= alpha) - { - session.pv[ply][0] = Move(entry->getMove()); - session.pv[ply][1] = Move::none(); - return ttScore; - } - } + if (TTEntry *entry = search::tt.lookup(hash)) + if (entry->getDepth() >= depth) + { + Value ttScore = entry->getScore(); + TTFlag flag = entry->getFlag(); + + if (flag == TTFlag::EXACT) + { + session.pv[ply][0] = Move(entry->getMove()); + session.pv[ply][1] = Move::none(); + return ttScore; + } + + if (flag == TTFlag::LOWERBOUND && ttScore >= beta) + { + session.pv[ply][0] = Move(entry->getMove()); + session.pv[ply][1] = Move::none(); + return ttScore; + } + + if (flag == TTFlag::UPPERBOUND && ttScore <= alpha) + { + session.pv[ply][0] = Move(entry->getMove()); + session.pv[ply][1] = Move::none(); + return ttScore; + } + } if (depth == 0) { session.nodes++; From 66be7b749f33c1811e333c1f76dbeb6c147e123d Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Wed, 4 Mar 2026 23:05:10 +0700 Subject: [PATCH 40/50] clear PV --- search.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/search.cpp b/search.cpp index ab4846e..b2545da 100644 --- a/search.cpp +++ b/search.cpp @@ -161,7 +161,11 @@ namespace engine chess::Move lastPV[MAX_PLY]{}; for (int i = 1; i < timecontrol.depth; i++) { - for (int i=0;i<64;i++)for (int j=0;j<64;j++)movepick::historyHeuristic[i][j]/=2; + for (int i=0;i<64;i++)for (int j=0;j<64;j++){ + movepick::historyHeuristic[i][j]/=2; + // since MAX_PLY=64 + session.pv[i][j]=Move::none(); + } session.nodes = 0; auto board_ = board; Value score_ = doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); From be920c117d35080925daca37daeaba13b0a145bb Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Thu, 5 Mar 2026 17:04:16 +0700 Subject: [PATCH 41/50] Update search.cpp --- search.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/search.cpp b/search.cpp index b2545da..4b9cab0 100644 --- a/search.cpp +++ b/search.cpp @@ -49,7 +49,7 @@ namespace engine session.seldepth = std::max(session.seldepth, ply); uint64_t hash = board.hash(); Move preferred = Move::none(); - if (TTEntry *entry = search::tt.lookup(hash)) + if (TTEntry *entry = search::tt.lookup(hash)){ if (entry->getDepth() >= depth) { Value ttScore = entry->getScore(); @@ -76,6 +76,8 @@ namespace engine return ttScore; } } + preferred = Move(entry->getMove()); + } if (depth == 0) { session.nodes++; From 66d3364bf7406768cdeccccca6c5b64b21ac3814 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Thu, 5 Mar 2026 17:09:32 +0700 Subject: [PATCH 42/50] fix overflow --- search.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/search.cpp b/search.cpp index 4b9cab0..392e45e 100644 --- a/search.cpp +++ b/search.cpp @@ -33,6 +33,7 @@ namespace engine } Value doSearch(Board &board, int depth, Value alpha, Value beta, Session &session, int ply = 0) { + if (ply >= MAX_PLY-1) return eval::eval(board); Value alphaOrig=alpha; std::fill(std::begin(session.pv[ply + 1]), std::end(session.pv[ply + 1]), Move::none()); From c056095c6d72cd02a92137540083607e48172e7b Mon Sep 17 00:00:00 2001 From: winapiadmin Date: Mon, 16 Mar 2026 21:48:02 +0700 Subject: [PATCH 43/50] implemented quiescence and fix illegal PV (variable shadowing) --- README.md | 2 +- search.cpp | 31 ++++++++++++++++++++++++++----- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 40d1af3..177730c 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,4 @@ A minimal UCI-compliant chess engine that have time control. -**Library used**: [Disservin/chess-library](https://github.com/Disservin/chess-library) +**Library used**: [winapiadmin/chesslib](https://github.com/winapiadmin/chesslib) diff --git a/search.cpp b/search.cpp index 392e45e..ec97442 100644 --- a/search.cpp +++ b/search.cpp @@ -31,10 +31,32 @@ namespace engine *pv++ = *childPv++; *pv = Move::none(); } + Value qsearch(Board &board, Value alpha, Value beta, Session &session, int ply = 0){ + session.nodes++; + int standPat=eval::eval(board); + Value maxScore = standPat; + if( maxScore >= beta ) + return maxScore; + if( maxScore > alpha ) + alpha = maxScore; + Movelist moves; + board.legals(moves); + for (Move move:moves){ + board.doMove(move); + Value score=-qsearch(board, -beta, -alpha, session, ply+1); + board.undoMove(); + if (score>=beta)return score; + if (score>maxScore) maxScore=score; + if (score>alpha) alpha=score; + } + return maxScore; + } Value doSearch(Board &board, int depth, Value alpha, Value beta, Session &session, int ply = 0) { if (ply >= MAX_PLY-1) return eval::eval(board); Value alphaOrig=alpha; + std::fill(std::begin(session.pv[ply]), std::end(session.pv[ply]), + Move::none()); std::fill(std::begin(session.pv[ply + 1]), std::end(session.pv[ply + 1]), Move::none()); if (session.tm.elapsed() >= @@ -81,8 +103,7 @@ namespace engine } if (depth == 0) { - session.nodes++; - return eval::eval(board); + return qsearch(board, alpha, beta, session, ply+1); } Value maxScore = -VALUE_INFINITE; Movelist moves; @@ -164,10 +185,10 @@ namespace engine chess::Move lastPV[MAX_PLY]{}; for (int i = 1; i < timecontrol.depth; i++) { - for (int i=0;i<64;i++)for (int j=0;j<64;j++){ - movepick::historyHeuristic[i][j]/=2; + for (int _=0;_<64;_++)for (int j=0;j<64;j++){ + movepick::historyHeuristic[_][j]/=2; // since MAX_PLY=64 - session.pv[i][j]=Move::none(); + session.pv[_][j]=Move::none(); } session.nodes = 0; auto board_ = board; From 5202e7c09ec35a32ed941918705196c3742f3010 Mon Sep 17 00:00:00 2001 From: winapiadmin Date: Tue, 17 Mar 2026 08:26:38 +0700 Subject: [PATCH 44/50] no functional changes --- .github/workflows/clang-format.yml | 35 +++ .github/workflows/games.yml | 3 +- .gitignore | 2 + eval.cpp | 365 ++++++++++-------------- eval.h | 54 ++-- main.cpp | 28 +- movepick.cpp | 72 ++--- movepick.h | 8 +- score.cpp | 31 +- score.h | 61 ++-- search.cpp | 440 ++++++++++++++--------------- search.h | 16 +- timeman.cpp | 165 ++++++----- timeman.h | 89 +++--- tt.cpp | 111 ++++---- tt.h | 352 +++++++++++------------ tune.cpp | 99 +++---- tune.h | 247 ++++++++-------- uci.cpp | 302 ++++++++++---------- uci.h | 64 ++--- ucioption.cpp | 246 ++++++++-------- ucioption.h | 110 ++++---- 22 files changed, 1406 insertions(+), 1494 deletions(-) create mode 100644 .github/workflows/clang-format.yml diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml new file mode 100644 index 0000000..3dcc630 --- /dev/null +++ b/.github/workflows/clang-format.yml @@ -0,0 +1,35 @@ +name: clang-format + +on: + pull_request: + branches: [ "main" ] + push: + branches-ignore: + - main +jobs: + format: + if: github.actor != 'github-actions[bot]' + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Install clang-format + run: sudo apt-get update && sudo apt-get install -y clang-format + + - name: Run clang-format + run: | + clang-format -i $(git ls-files '*.cpp' '*.hpp' '*.h' '*.c') + + - name: Commit and push changes + run: | + if ! git diff --quiet; then + git config user.email "actions@github.com" + git config user.name "GitHub Actions" + git commit -am "Apply clang-format" + git push + fi diff --git a/.github/workflows/games.yml b/.github/workflows/games.yml index ad8e2d9..702a6e1 100644 --- a/.github/workflows/games.yml +++ b/.github/workflows/games.yml @@ -115,9 +115,10 @@ jobs: -rounds ${{ github.event.inputs.rounds }} \ -concurrency $(nproc) \ -pgnout notation=san nodes=true file=games.pgn \ - -sprt elo0=0 elo1=2 alpha=0.05 beta=0.05 + -sprt elo0=0 elo1=2 alpha=0.05 beta=0.05 | tee results.txt ./ordo-linux64 -o ratings.txt -- games.pgn + tac results.txt | sed -n '/Total Time/,/^--------------------------------------------------$/p' | tac >> ratings.txtv - name: Upload results uses: actions/upload-artifact@v4 diff --git a/.gitignore b/.gitignore index fb96e91..ec84a85 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ !LICENSE !.gitignore !CMakeLists.txt +!.github/ +!.github/** \ No newline at end of file diff --git a/eval.cpp b/eval.cpp index 19cc7bc..a9b3722 100644 --- a/eval.cpp +++ b/eval.cpp @@ -1,216 +1,161 @@ #include "eval.h" -#include #include +#include #include using namespace chess; using namespace engine::eval; -namespace engine::eval{ - constexpr Value PawnValue = 100, KnightValue = 325, BishopValue = 350, RookValue = 500, QueenValue = 900; - - Value mg_pawn_table[64] = { - 0, 0, 0, 0, 0, 0, 0, 0, - 98, 134, 61, 95, 68, 126, 34, -11, - -6, 7, 26, 31, 65, 56, 25, -20, - -14, 13, 6, 21, 23, 12, 17, -23, - -27, -2, -5, 12, 17, 6, 10, -25, - -26, -4, -4, -10, 3, 3, 33, -12, - -35, -1, -20, -23, -15, 24, 38, -22, - 0, 0, 0, 0, 0, 0, 0, 0, - }; - - Value eg_pawn_table[64] = { - 0, 0, 0, 0, 0, 0, 0, 0, - 178, 173, 158, 134, 147, 132, 165, 187, - 94, 100, 85, 67, 56, 53, 82, 84, - 32, 24, 13, 5, -2, 4, 17, 17, - 13, 9, -3, -7, -7, -8, 3, -1, - 4, 7, -6, 1, 0, -5, -1, -8, - 13, 8, 8, 10, 13, 0, 2, -7, - 0, 0, 0, 0, 0, 0, 0, 0, - }; - - Value mg_knight_table[64] = { - -167, -89, -34, -49, 61, -97, -15, -107, - -73, -41, 72, 36, 23, 62, 7, -17, - -47, 60, 37, 65, 84, 129, 73, 44, - -9, 17, 19, 53, 37, 69, 18, 22, - -13, 4, 16, 13, 28, 19, 21, -8, - -23, -9, 12, 10, 19, 17, 25, -16, - -29, -53, -12, -3, -1, 18, -14, -19, - -105, -21, -58, -33, -17, -28, -19, -23, - }; - - Value eg_knight_table[64] = { - -58, -38, -13, -28, -31, -27, -63, -99, - -25, -8, -25, -2, -9, -25, -24, -52, - -24, -20, 10, 9, -1, -9, -19, -41, - -17, 3, 22, 22, 22, 11, 8, -18, - -18, -6, 16, 25, 16, 17, 4, -18, - -23, -3, -1, 15, 10, -3, -20, -22, - -42, -20, -10, -5, -2, -20, -23, -44, - -29, -51, -23, -15, -22, -18, -50, -64, - }; - - Value mg_bishop_table[64] = { - -29, 4, -82, -37, -25, -42, 7, -8, - -26, 16, -18, -13, 30, 59, 18, -47, - -16, 37, 43, 40, 35, 50, 37, -2, - -4, 5, 19, 50, 37, 37, 7, -2, - -6, 13, 13, 26, 34, 12, 10, 4, - 0, 15, 15, 15, 14, 27, 18, 10, - 4, 15, 16, 0, 7, 21, 33, 1, - -33, -3, -14, -21, -13, -12, -39, -21, - }; - - Value eg_bishop_table[64] = { - -14, -21, -11, -8, -7, -9, -17, -24, - -8, -4, 7, -12, -3, -13, -4, -14, - 2, -8, 0, -1, -2, 6, 0, 4, - -3, 9, 12, 9, 14, 10, 3, 2, - -6, 3, 13, 19, 7, 10, -3, -9, - -12, -3, 8, 10, 13, 3, -7, -15, - -14, -18, -7, -1, 4, -9, -15, -27, - -23, -9, -23, -5, -9, -16, -5, -17, - }; - - Value mg_rook_table[64] = { - 32, 42, 32, 51, 63, 9, 31, 43, - 27, 32, 58, 62, 80, 67, 26, 44, - -5, 19, 26, 36, 17, 45, 61, 16, - -24, -11, 7, 26, 24, 35, -8, -20, - -36, -26, -12, -1, 9, -7, 6, -23, - -45, -25, -16, -17, 3, 0, -5, -33, - -44, -16, -20, -9, -1, 11, -6, -71, - -19, -13, 1, 17, 16, 7, -37, -26, - }; - - Value eg_rook_table[64] = { - 13, 10, 18, 15, 12, 12, 8, 5, - 11, 13, 13, 11, -3, 3, 8, 3, - 7, 7, 7, 5, 4, -3, -5, -3, - 4, 3, 13, 1, 2, 1, -1, 2, - 3, 5, 8, 4, -5, -6, -8, -11, - -4, 0, -5, -1, -7, -12, -8, -16, - -6, -6, 0, 2, -9, -9, -11, -3, - -9, 2, 3, -1, -5, -13, 4, -20, - }; - - Value mg_queen_table[64] = { - -28, 0, 29, 12, 59, 44, 43, 45, - -24, -39, -5, 1, -16, 57, 28, 54, - -13, -17, 7, 8, 29, 56, 47, 57, - -27, -27, -16, -16, -1, 17, -2, 1, - -9, -26, -9, -10, -2, -4, 3, -3, - -14, 2, -11, -2, -5, 2, 14, 5, - -35, -8, 11, 2, 8, 15, -3, 1, - -1, -18, -9, 10, -15, -25, -31, -50, - }; - - Value eg_queen_table[64] = { - -9, 22, 22, 27, 27, 19, 10, 20, - -17, 20, 32, 41, 58, 25, 30, 0, - -20, 6, 9, 49, 47, 35, 19, 9, - 3, 22, 24, 45, 57, 40, 57, 36, - -18, 28, 19, 47, 31, 34, 39, 23, - -16, -27, 15, 6, 9, 17, 10, 5, - -22, -23, -30, -16, -16, -23, -36, -32, - -33, -28, -22, -43, -5, -32, -20, -41, - }; - - Value mg_king_table[64] = { - -65, 23, 16, -15, -56, -34, 2, 13, - 29, -1, -20, -7, -8, -4, -38, -29, - -9, 24, 2, -16, -20, 6, 22, -22, - -17, -20, -12, -27, -30, -25, -14, -36, - -49, -1, -27, -39, -46, -44, -33, -51, - -14, -14, -22, -46, -44, -30, -15, -27, - 1, 7, -8, -64, -43, -16, 9, 8, - -15, 36, 12, -54, 8, -28, 24, 14, - }; - - Value eg_king_table[64] = { - -74, -35, -18, -18, -11, 15, 4, -17, - -12, 17, 14, 17, 17, 38, 23, 11, - 10, 17, 23, 15, 20, 45, 44, 13, - -8, 22, 24, 27, 26, 33, 26, 3, - -18, -4, 21, 24, 27, 23, 9, -11, - -19, -3, 11, 21, 23, 16, 7, -9, - -27, -11, 4, 13, 14, 4, -5, -17, - -53, -34, -21, -11, -28, -14, -24, -43 - }; - - Value *mg_pesto_table[] = - { - {0}, - mg_pawn_table, - mg_knight_table, - mg_bishop_table, - mg_rook_table, - mg_queen_table, - mg_king_table - }; - - Value *eg_pesto_table[] = - { - {0}, - eg_pawn_table, - eg_knight_table, - eg_bishop_table, - eg_rook_table, - eg_queen_table, - eg_king_table - }; - - Value eval(const chess::Board &board){ - int pieceCount[10] = { - board.count(), - board.count(), - board.count(), - board.count(), - board.count(), - board.count(), - board.count(), - board.count(), - board.count(), - board.count(), - }; - Value material = (pieceCount[0] * PawnValue + pieceCount[1] * KnightValue + pieceCount[2] * BishopValue + pieceCount[3] * RookValue + pieceCount[4] * QueenValue) - - (pieceCount[5] * PawnValue + pieceCount[6] * KnightValue + pieceCount[7] * BishopValue + pieceCount[8] * RookValue + pieceCount[9] * QueenValue); - constexpr int KnightPhase = 1; - constexpr int BishopPhase = 1; - constexpr int RookPhase = 2; - constexpr int QueenPhase = 4; - constexpr int TotalPhase = KnightPhase * 4 + BishopPhase * 4 + RookPhase * 4 + QueenPhase * 2; - - int phase = (pieceCount[1] + pieceCount[6]) * KnightPhase + - (pieceCount[2] + pieceCount[7]) * BishopPhase + - (pieceCount[3] + pieceCount[8]) * RookPhase + - (pieceCount[4] + pieceCount[9]) * QueenPhase; - - phase = (phase * 256 + TotalPhase / 2) / TotalPhase; - const int sign = board.sideToMove() == chess::Color::WHITE ? 1 : -1; - - int mgScore = material; - int egScore = material; - { - Bitboard occ = board.occ(); - while (occ) { - Square i = (Square)pop_lsb(occ); - auto p = board.at(i); - int _sign=1; - if (color_of(p)==BLACK){ - _sign=-1; - i = square_mirror(i); - } - mgScore += _sign*mg_pesto_table[piece_of(p)][i]; - egScore += _sign*eg_pesto_table[piece_of(p)][i]; - } - } - Value finalScore = ((mgScore * phase) + (egScore * (256 - phase))) / 256 * sign; - return finalScore; - } - Value piece_value(PieceType pt){ - Value pieces[]={0,PawnValue,KnightValue,BishopValue,RookValue,QueenValue}; - return pieces[pt]; +namespace engine::eval { +constexpr Value PawnValue = 100, KnightValue = 325, BishopValue = 350, + RookValue = 500, QueenValue = 900; + +Value mg_pawn_table[64] = { + 0, 0, 0, 0, 0, 0, 0, 0, 98, 134, 61, 95, 68, 126, 34, -11, + -6, 7, 26, 31, 65, 56, 25, -20, -14, 13, 6, 21, 23, 12, 17, -23, + -27, -2, -5, 12, 17, 6, 10, -25, -26, -4, -4, -10, 3, 3, 33, -12, + -35, -1, -20, -23, -15, 24, 38, -22, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +Value eg_pawn_table[64] = { + 0, 0, 0, 0, 0, 0, 0, 0, 178, 173, 158, 134, 147, 132, 165, 187, + 94, 100, 85, 67, 56, 53, 82, 84, 32, 24, 13, 5, -2, 4, 17, 17, + 13, 9, -3, -7, -7, -8, 3, -1, 4, 7, -6, 1, 0, -5, -1, -8, + 13, 8, 8, 10, 13, 0, 2, -7, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +Value mg_knight_table[64] = { + -167, -89, -34, -49, 61, -97, -15, -107, -73, -41, 72, 36, 23, + 62, 7, -17, -47, 60, 37, 65, 84, 129, 73, 44, -9, 17, + 19, 53, 37, 69, 18, 22, -13, 4, 16, 13, 28, 19, 21, + -8, -23, -9, 12, 10, 19, 17, 25, -16, -29, -53, -12, -3, + -1, 18, -14, -19, -105, -21, -58, -33, -17, -28, -19, -23, +}; + +Value eg_knight_table[64] = { + -58, -38, -13, -28, -31, -27, -63, -99, -25, -8, -25, -2, -9, + -25, -24, -52, -24, -20, 10, 9, -1, -9, -19, -41, -17, 3, + 22, 22, 22, 11, 8, -18, -18, -6, 16, 25, 16, 17, 4, + -18, -23, -3, -1, 15, 10, -3, -20, -22, -42, -20, -10, -5, + -2, -20, -23, -44, -29, -51, -23, -15, -22, -18, -50, -64, +}; + +Value mg_bishop_table[64] = { + -29, 4, -82, -37, -25, -42, 7, -8, -26, 16, -18, -13, 30, 59, 18, -47, + -16, 37, 43, 40, 35, 50, 37, -2, -4, 5, 19, 50, 37, 37, 7, -2, + -6, 13, 13, 26, 34, 12, 10, 4, 0, 15, 15, 15, 14, 27, 18, 10, + 4, 15, 16, 0, 7, 21, 33, 1, -33, -3, -14, -21, -13, -12, -39, -21, +}; + +Value eg_bishop_table[64] = { + -14, -21, -11, -8, -7, -9, -17, -24, -8, -4, 7, -12, -3, -13, -4, -14, + 2, -8, 0, -1, -2, 6, 0, 4, -3, 9, 12, 9, 14, 10, 3, 2, + -6, 3, 13, 19, 7, 10, -3, -9, -12, -3, 8, 10, 13, 3, -7, -15, + -14, -18, -7, -1, 4, -9, -15, -27, -23, -9, -23, -5, -9, -16, -5, -17, +}; + +Value mg_rook_table[64] = { + 32, 42, 32, 51, 63, 9, 31, 43, 27, 32, 58, 62, 80, 67, 26, 44, + -5, 19, 26, 36, 17, 45, 61, 16, -24, -11, 7, 26, 24, 35, -8, -20, + -36, -26, -12, -1, 9, -7, 6, -23, -45, -25, -16, -17, 3, 0, -5, -33, + -44, -16, -20, -9, -1, 11, -6, -71, -19, -13, 1, 17, 16, 7, -37, -26, +}; + +Value eg_rook_table[64] = { + 13, 10, 18, 15, 12, 12, 8, 5, 11, 13, 13, 11, -3, 3, 8, 3, + 7, 7, 7, 5, 4, -3, -5, -3, 4, 3, 13, 1, 2, 1, -1, 2, + 3, 5, 8, 4, -5, -6, -8, -11, -4, 0, -5, -1, -7, -12, -8, -16, + -6, -6, 0, 2, -9, -9, -11, -3, -9, 2, 3, -1, -5, -13, 4, -20, +}; + +Value mg_queen_table[64] = { + -28, 0, 29, 12, 59, 44, 43, 45, -24, -39, -5, 1, -16, 57, 28, 54, + -13, -17, 7, 8, 29, 56, 47, 57, -27, -27, -16, -16, -1, 17, -2, 1, + -9, -26, -9, -10, -2, -4, 3, -3, -14, 2, -11, -2, -5, 2, 14, 5, + -35, -8, 11, 2, 8, 15, -3, 1, -1, -18, -9, 10, -15, -25, -31, -50, +}; + +Value eg_queen_table[64] = { + -9, 22, 22, 27, 27, 19, 10, 20, -17, 20, 32, 41, 58, + 25, 30, 0, -20, 6, 9, 49, 47, 35, 19, 9, 3, 22, + 24, 45, 57, 40, 57, 36, -18, 28, 19, 47, 31, 34, 39, + 23, -16, -27, 15, 6, 9, 17, 10, 5, -22, -23, -30, -16, + -16, -23, -36, -32, -33, -28, -22, -43, -5, -32, -20, -41, +}; + +Value mg_king_table[64] = { + -65, 23, 16, -15, -56, -34, 2, 13, 29, -1, -20, -7, -8, + -4, -38, -29, -9, 24, 2, -16, -20, 6, 22, -22, -17, -20, + -12, -27, -30, -25, -14, -36, -49, -1, -27, -39, -46, -44, -33, + -51, -14, -14, -22, -46, -44, -30, -15, -27, 1, 7, -8, -64, + -43, -16, 9, 8, -15, 36, 12, -54, 8, -28, 24, 14, +}; + +Value eg_king_table[64] = {-74, -35, -18, -18, -11, 15, 4, -17, -12, 17, 14, + 17, 17, 38, 23, 11, 10, 17, 23, 15, 20, 45, + 44, 13, -8, 22, 24, 27, 26, 33, 26, 3, -18, + -4, 21, 24, 27, 23, 9, -11, -19, -3, 11, 21, + 23, 16, 7, -9, -27, -11, 4, 13, 14, 4, -5, + -17, -53, -34, -21, -11, -28, -14, -24, -43}; + +Value *mg_pesto_table[] = { + {0}, mg_pawn_table, mg_knight_table, mg_bishop_table, + mg_rook_table, mg_queen_table, mg_king_table}; + +Value *eg_pesto_table[] = { + {0}, eg_pawn_table, eg_knight_table, eg_bishop_table, + eg_rook_table, eg_queen_table, eg_king_table}; + +Value eval(const chess::Board &board) { + int pieceCount[10] = { + board.count(), board.count(), + board.count(), board.count(), + board.count(), board.count(), + board.count(), board.count(), + board.count(), board.count(), + }; + Value material = (pieceCount[0] * PawnValue + pieceCount[1] * KnightValue + + pieceCount[2] * BishopValue + pieceCount[3] * RookValue + + pieceCount[4] * QueenValue) - + (pieceCount[5] * PawnValue + pieceCount[6] * KnightValue + + pieceCount[7] * BishopValue + pieceCount[8] * RookValue + + pieceCount[9] * QueenValue); + constexpr int KnightPhase = 1; + constexpr int BishopPhase = 1; + constexpr int RookPhase = 2; + constexpr int QueenPhase = 4; + constexpr int TotalPhase = + KnightPhase * 4 + BishopPhase * 4 + RookPhase * 4 + QueenPhase * 2; + + int phase = (pieceCount[1] + pieceCount[6]) * KnightPhase + + (pieceCount[2] + pieceCount[7]) * BishopPhase + + (pieceCount[3] + pieceCount[8]) * RookPhase + + (pieceCount[4] + pieceCount[9]) * QueenPhase; + + phase = (phase * 256 + TotalPhase / 2) / TotalPhase; + const int sign = board.sideToMove() == chess::Color::WHITE ? 1 : -1; + + int mgScore = material; + int egScore = material; + { + Bitboard occ = board.occ(); + while (occ) { + Square i = (Square)pop_lsb(occ); + auto p = board.at(i); + int _sign = 1; + if (color_of(p) == BLACK) { + _sign = -1; + i = square_mirror(i); + } + mgScore += _sign * mg_pesto_table[piece_of(p)][i]; + egScore += _sign * eg_pesto_table[piece_of(p)][i]; } -} + } + Value finalScore = + ((mgScore * phase) + (egScore * (256 - phase))) / 256 * sign; + return finalScore; +} +Value piece_value(PieceType pt) { + Value pieces[] = {0, PawnValue, KnightValue, + BishopValue, RookValue, QueenValue}; + return pieces[pt]; +} +} // namespace engine::eval diff --git a/eval.h b/eval.h index be6eed9..eb881e6 100644 --- a/eval.h +++ b/eval.h @@ -1,38 +1,38 @@ #pragma once -#include #include #include +#include using Value = int; -namespace engine{ +namespace engine { - constexpr int MAX_PLY = 64; - constexpr Value VALUE_ZERO = 0; - constexpr Value VALUE_DRAW = 0; - constexpr Value VALUE_NONE = 32002; - constexpr Value VALUE_INFINITE = 32001; +constexpr int MAX_PLY = 64; +constexpr Value VALUE_ZERO = 0; +constexpr Value VALUE_DRAW = 0; +constexpr Value VALUE_NONE = 32002; +constexpr Value VALUE_INFINITE = 32001; - constexpr Value VALUE_MATE = 32000; - constexpr Value VALUE_MATE_IN_MAX_PLY = VALUE_MATE - MAX_PLY; - constexpr Value VALUE_MATED_IN_MAX_PLY = -VALUE_MATE_IN_MAX_PLY; +constexpr Value VALUE_MATE = 32000; +constexpr Value VALUE_MATE_IN_MAX_PLY = VALUE_MATE - MAX_PLY; +constexpr Value VALUE_MATED_IN_MAX_PLY = -VALUE_MATE_IN_MAX_PLY; - constexpr Value VALUE_TB = VALUE_MATE_IN_MAX_PLY - 1; - constexpr Value VALUE_TB_WIN_IN_MAX_PLY = VALUE_TB - MAX_PLY; - constexpr Value VALUE_TB_LOSS_IN_MAX_PLY = -VALUE_TB_WIN_IN_MAX_PLY; - constexpr bool is_valid(Value value) { return value != VALUE_NONE; } +constexpr Value VALUE_TB = VALUE_MATE_IN_MAX_PLY - 1; +constexpr Value VALUE_TB_WIN_IN_MAX_PLY = VALUE_TB - MAX_PLY; +constexpr Value VALUE_TB_LOSS_IN_MAX_PLY = -VALUE_TB_WIN_IN_MAX_PLY; +constexpr bool is_valid(Value value) { return value != VALUE_NONE; } - constexpr bool is_win(Value value) { - return value >= VALUE_TB_WIN_IN_MAX_PLY; - } +constexpr bool is_win(Value value) { return value >= VALUE_TB_WIN_IN_MAX_PLY; } - constexpr bool is_loss(Value value) { - return value <= VALUE_TB_LOSS_IN_MAX_PLY; - } +constexpr bool is_loss(Value value) { + return value <= VALUE_TB_LOSS_IN_MAX_PLY; +} - constexpr bool is_decisive(Value value) { return is_win(value) || is_loss(value); } - constexpr Value MATE(int i) { return VALUE_MATE - i; } - constexpr Value MATE_DISTANCE(int i) { return VALUE_MATE - (i<0?-i:i); } - namespace eval { - Value eval(const chess::Board &board); - Value piece_value(chess::PieceType pt); - } +constexpr bool is_decisive(Value value) { + return is_win(value) || is_loss(value); } +constexpr Value MATE(int i) { return VALUE_MATE - i; } +constexpr Value MATE_DISTANCE(int i) { return VALUE_MATE - (i < 0 ? -i : i); } +namespace eval { +Value eval(const chess::Board &board); +Value piece_value(chess::PieceType pt); +} // namespace eval +} // namespace engine diff --git a/main.cpp b/main.cpp index 296b478..6b45b33 100644 --- a/main.cpp +++ b/main.cpp @@ -1,20 +1,20 @@ -#include "ucioption.h" -#include "uci.h" #include "search.h" +#include "uci.h" +#include "ucioption.h" #include using namespace engine; int main() { - options.add("Move Overhead", Option(10, 0, 1000)); - options.add( // - "Hash", Option(16, 1, 1<<25, [](const Option &o) { - search::tt.resize((o.operator int())); - return std::nullopt; - })); + options.add("Move Overhead", Option(10, 0, 1000)); + options.add( // + "Hash", Option(16, 1, 1 << 25, [](const Option &o) { + search::tt.resize((o.operator int())); + return std::nullopt; + })); - options.add( // - "Clear Hash", Option(+[](const Option &) { - search::tt.clear(); - return std::nullopt; - })); - loop(); + options.add( // + "Clear Hash", Option(+[](const Option &) { + search::tt.clear(); + return std::nullopt; + })); + loop(); } \ No newline at end of file diff --git a/movepick.cpp b/movepick.cpp index 6960b35..14bb196 100644 --- a/movepick.cpp +++ b/movepick.cpp @@ -1,43 +1,43 @@ #include "movepick.h" #include "eval.h" -#include #include +#include using namespace chess; using engine::eval::piece_value; namespace engine::movepick { - Value historyHeuristic[SQUARE_NB][SQUARE_NB]{}; - Move killerMoves[256][2]; - void orderMoves(chess::Board & board, chess::Movelist & moves, chess::Move ttMove, int ply) - { - std::vector> scoredMoves; - scoredMoves.reserve(moves.size()); - - for (const auto& move : moves) - { - Value score = 0; - - if (move == ttMove) - score = 10000; - else if (board.isCapture(move)) - score = - ((move.typeOf() & EN_PASSANT)==0?piece_value(board.at(move.to())):piece_value(PAWN))*10 - piece_value(board.at(move.from())); - else if (move == killerMoves[ply][0]) - score = 8500; - else if (move == killerMoves[ply][1]) - score = 8000; - else - score = historyHeuristic[move.from()][move.to()]; - - scoredMoves.emplace_back(move, score); - } - - std::stable_sort(scoredMoves.begin(), scoredMoves.end(), - [](const auto& a, const auto& b) - { - return a.second > b.second; - }); - - for (size_t i = 0; i < scoredMoves.size(); ++i) - moves[i] = scoredMoves[i].first; - } +Value historyHeuristic[SQUARE_NB][SQUARE_NB]{}; +Move killerMoves[256][2]; +void orderMoves(chess::Board &board, chess::Movelist &moves, chess::Move ttMove, + int ply) { + std::vector> scoredMoves; + scoredMoves.reserve(moves.size()); + + for (const auto &move : moves) { + Value score = 0; + + if (move == ttMove) + score = 10000; + else if (board.isCapture(move)) + score = ((move.typeOf() & EN_PASSANT) == 0 + ? piece_value(board.at(move.to())) + : piece_value(PAWN)) * + 10 - + piece_value(board.at(move.from())); + else if (move == killerMoves[ply][0]) + score = 8500; + else if (move == killerMoves[ply][1]) + score = 8000; + else + score = historyHeuristic[move.from()][move.to()]; + + scoredMoves.emplace_back(move, score); + } + + std::stable_sort( + scoredMoves.begin(), scoredMoves.end(), + [](const auto &a, const auto &b) { return a.second > b.second; }); + + for (size_t i = 0; i < scoredMoves.size(); ++i) + moves[i] = scoredMoves[i].first; } +} // namespace engine::movepick diff --git a/movepick.h b/movepick.h index 6fed1c4..a789802 100644 --- a/movepick.h +++ b/movepick.h @@ -1,7 +1,7 @@ #pragma once #include namespace engine::movepick { - void orderMoves(chess::Board &, chess::Movelist &, chess::Move, int); - extern int historyHeuristic[64][64]; - extern chess::Move killerMoves[256][2]; -} +void orderMoves(chess::Board &, chess::Movelist &, chess::Move, int); +extern int historyHeuristic[64][64]; +extern chess::Move killerMoves[256][2]; +} // namespace engine::movepick diff --git a/score.cpp b/score.cpp index 678d857..2fdc2c5 100644 --- a/score.cpp +++ b/score.cpp @@ -1,22 +1,17 @@ #include "score.h" #include namespace engine { - Score::Score(Value v) { - assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); +Score::Score(Value v) { + assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); - if (!is_decisive(v)) - { - score = InternalUnits{ v }; - } - else if (std::abs(v) <= VALUE_TB) - { - auto distance = VALUE_TB - std::abs(v); - score = (v > 0) ? Tablebase{ distance, true } : Tablebase{ -distance, false }; - } - else - { - auto distance = VALUE_MATE - std::abs(v); - score = (v > 0) ? Mate{ distance } : Mate{ -distance }; - } - } -} \ No newline at end of file + if (!is_decisive(v)) { + score = InternalUnits{v}; + } else if (std::abs(v) <= VALUE_TB) { + auto distance = VALUE_TB - std::abs(v); + score = (v > 0) ? Tablebase{distance, true} : Tablebase{-distance, false}; + } else { + auto distance = VALUE_MATE - std::abs(v); + score = (v > 0) ? Mate{distance} : Mate{-distance}; + } +} +} // namespace engine \ No newline at end of file diff --git a/score.h b/score.h index 8949ab2..2b7b0e1 100644 --- a/score.h +++ b/score.h @@ -19,48 +19,43 @@ #ifndef SCORE_H_INCLUDED #define SCORE_H_INCLUDED -#include -#include #include "eval.h" +#include +#include namespace engine { - class Score { - public: - struct Mate { - int plies; - }; +class Score { +public: + struct Mate { + int plies; + }; - struct Tablebase { - int plies; - bool win; - }; + struct Tablebase { + int plies; + bool win; + }; - struct InternalUnits { - int value; - }; + struct InternalUnits { + int value; + }; - Score() = default; - Score(Value v); + Score() = default; + Score(Value v); - template - bool is() const { - return std::holds_alternative(score); - } + template bool is() const { + return std::holds_alternative(score); + } - template - T get() const { - return std::get(score); - } + template T get() const { return std::get(score); } - template - decltype(auto) visit(F &&f) const { - return std::visit(std::forward(f), score); - } + template decltype(auto) visit(F &&f) const { + return std::visit(std::forward(f), score); + } - private: - std::variant score; - }; +private: + std::variant score; +}; -} +} // namespace engine -#endif // #ifndef SCORE_H_INCLUDED \ No newline at end of file +#endif // #ifndef SCORE_H_INCLUDED \ No newline at end of file diff --git a/search.cpp b/search.cpp index ec97442..07b9af5 100644 --- a/search.cpp +++ b/search.cpp @@ -1,266 +1,246 @@ #include "search.h" #include "eval.h" +#include "movepick.h" #include "timeman.h" #include "uci.h" -#include "movepick.h" #include #include #include using namespace chess; -namespace engine -{ - TranspositionTable search::tt(16); - std::atomic stopSearch{false}; - void search::stop() - { - tt.clear(); - stopSearch.store(true, std::memory_order_relaxed); - } - struct Session - { - timeman::TimeManagement tm; - timeman::LimitsType tc; - int seldepth = 0; - uint64_t nodes = 0; - chess::Move pv[MAX_PLY][MAX_PLY]; - }; - void update_pv(Move *pv, Move move, const Move *childPv) - { +namespace engine { +TranspositionTable search::tt(16); +std::atomic stopSearch{false}; +void search::stop() { + tt.clear(); + stopSearch.store(true, std::memory_order_relaxed); +} +struct Session { + timeman::TimeManagement tm; + timeman::LimitsType tc; + int seldepth = 0; + uint64_t nodes = 0; + chess::Move pv[MAX_PLY][MAX_PLY]; +}; +void update_pv(Move *pv, Move move, const Move *childPv) { - for (*pv++ = move; childPv && *childPv != Move::none();) - *pv++ = *childPv++; - *pv = Move::none(); + for (*pv++ = move; childPv && *childPv != Move::none();) + *pv++ = *childPv++; + *pv = Move::none(); +} +Value qsearch(Board &board, Value alpha, Value beta, Session &session, + int ply = 0) { + session.nodes++; + int standPat = eval::eval(board); + Value maxScore = standPat; + if (maxScore >= beta) + return maxScore; + if (maxScore > alpha) + alpha = maxScore; + Movelist moves; + board.legals(moves); + for (Move move : moves) { + board.doMove(move); + Value score = -qsearch(board, -beta, -alpha, session, ply + 1); + board.undoMove(); + if (score >= beta) + return score; + if (score > maxScore) + maxScore = score; + if (score > alpha) + alpha = score; } - Value qsearch(Board &board, Value alpha, Value beta, Session &session, int ply = 0){ + return maxScore; +} +Value doSearch(Board &board, int depth, Value alpha, Value beta, + Session &session, int ply = 0) { + if (ply >= MAX_PLY - 1) + return eval::eval(board); + Value alphaOrig = alpha; + std::fill(std::begin(session.pv[ply]), std::end(session.pv[ply]), + Move::none()); + std::fill(std::begin(session.pv[ply + 1]), std::end(session.pv[ply + 1]), + Move::none()); + if (session.tm.elapsed() >= session.tm.optimum() || + stopSearch.load(std::memory_order_relaxed)) + return VALUE_NONE; + if (board.is_draw(3)) { session.nodes++; - int standPat=eval::eval(board); - Value maxScore = standPat; - if( maxScore >= beta ) - return maxScore; - if( maxScore > alpha ) - alpha = maxScore; - Movelist moves; - board.legals(moves); - for (Move move:moves){ - board.doMove(move); - Value score=-qsearch(board, -beta, -alpha, session, ply+1); - board.undoMove(); - if (score>=beta)return score; - if (score>maxScore) maxScore=score; - if (score>alpha) alpha=score; - } - return maxScore; + session.pv[ply][0] = Move::none(); + return 0; } - Value doSearch(Board &board, int depth, Value alpha, Value beta, Session &session, int ply = 0) - { - if (ply >= MAX_PLY-1) return eval::eval(board); - Value alphaOrig=alpha; - std::fill(std::begin(session.pv[ply]), std::end(session.pv[ply]), - Move::none()); - std::fill(std::begin(session.pv[ply + 1]), std::end(session.pv[ply + 1]), - Move::none()); - if (session.tm.elapsed() >= - session.tm.optimum() || - stopSearch.load(std::memory_order_relaxed)) - return VALUE_NONE; - if (board.is_draw(3)) - { - session.nodes++; - session.pv[ply][0] = Move::none(); - return 0; - } - session.seldepth = std::max(session.seldepth, ply); - uint64_t hash = board.hash(); - Move preferred = Move::none(); - if (TTEntry *entry = search::tt.lookup(hash)){ - if (entry->getDepth() >= depth) - { - Value ttScore = entry->getScore(); - TTFlag flag = entry->getFlag(); - - if (flag == TTFlag::EXACT) - { - session.pv[ply][0] = Move(entry->getMove()); - session.pv[ply][1] = Move::none(); - return ttScore; - } - - if (flag == TTFlag::LOWERBOUND && ttScore >= beta) - { - session.pv[ply][0] = Move(entry->getMove()); - session.pv[ply][1] = Move::none(); - return ttScore; - } - - if (flag == TTFlag::UPPERBOUND && ttScore <= alpha) - { - session.pv[ply][0] = Move(entry->getMove()); - session.pv[ply][1] = Move::none(); - return ttScore; - } + session.seldepth = std::max(session.seldepth, ply); + uint64_t hash = board.hash(); + Move preferred = Move::none(); + if (TTEntry *entry = search::tt.lookup(hash)) { + if (entry->getDepth() >= depth) { + Value ttScore = entry->getScore(); + TTFlag flag = entry->getFlag(); + + if (flag == TTFlag::EXACT) { + session.pv[ply][0] = Move(entry->getMove()); + session.pv[ply][1] = Move::none(); + return ttScore; + } + + if (flag == TTFlag::LOWERBOUND && ttScore >= beta) { + session.pv[ply][0] = Move(entry->getMove()); + session.pv[ply][1] = Move::none(); + return ttScore; + } + + if (flag == TTFlag::UPPERBOUND && ttScore <= alpha) { + session.pv[ply][0] = Move(entry->getMove()); + session.pv[ply][1] = Move::none(); + return ttScore; } - preferred = Move(entry->getMove()); - } - if (depth == 0) - { - return qsearch(board, alpha, beta, session, ply+1); - } - Value maxScore = -VALUE_INFINITE; - Movelist moves; - board.legals(moves); - if (!moves.size()) - { - session.pv[ply][0] = Move::none(); - return board.checkers() ? -MATE(ply) : 0; } - movepick::orderMoves(board, moves, preferred, ply); - for (Move move : moves) - { + preferred = Move(entry->getMove()); + } + if (depth == 0) { + return qsearch(board, alpha, beta, session, ply + 1); + } + Value maxScore = -VALUE_INFINITE; + Movelist moves; + board.legals(moves); + if (!moves.size()) { + session.pv[ply][0] = Move::none(); + return board.checkers() ? -MATE(ply) : 0; + } + movepick::orderMoves(board, moves, preferred, ply); + for (Move move : moves) { - board.doMove(move); + board.doMove(move); - Value childScore = doSearch(board, depth - 1, -beta, -alpha, session, ply + 1); + Value childScore = + doSearch(board, depth - 1, -beta, -alpha, session, ply + 1); - board.undoMove(); + board.undoMove(); - // ---- ABORT PROPAGATION ---- - if (childScore == VALUE_NONE) - return VALUE_NONE; + // ---- ABORT PROPAGATION ---- + if (childScore == VALUE_NONE) + return VALUE_NONE; - Value score = -childScore; + Value score = -childScore; - if (score > maxScore) - { - maxScore = score; - update_pv(session.pv[ply], move, session.pv[ply + 1]); - } + if (score > maxScore) { + maxScore = score; + update_pv(session.pv[ply], move, session.pv[ply + 1]); + } - if (score > alpha){ - alpha = score; - if (!board.isCapture(move)) - movepick::historyHeuristic[(int)move.from()][(int)move.to()] += depth * depth; - } - if (alpha >= beta) - { - if (!board.isCapture(move)) - { - if (movepick::killerMoves[ply][0] != move) - { - movepick::killerMoves[ply][1] = movepick::killerMoves[ply][0]; - movepick::killerMoves[ply][0] = move; - } - } - - break; + if (score > alpha) { + alpha = score; + if (!board.isCapture(move)) + movepick::historyHeuristic[(int)move.from()][(int)move.to()] += + depth * depth; + } + if (alpha >= beta) { + if (!board.isCapture(move)) { + if (movepick::killerMoves[ply][0] != move) { + movepick::killerMoves[ply][1] = movepick::killerMoves[ply][0]; + movepick::killerMoves[ply][0] = move; + } } - - if (session.tm.elapsed() >= - session.tm.optimum() || - stopSearch.load(std::memory_order_relaxed)) - return VALUE_NONE; + + break; } - if (maxScore != -VALUE_INFINITE){ - TTFlag flag; + if (session.tm.elapsed() >= session.tm.optimum() || + stopSearch.load(std::memory_order_relaxed)) + return VALUE_NONE; + } + + if (maxScore != -VALUE_INFINITE) { + TTFlag flag; - if (maxScore <= alphaOrig) - flag = TTFlag::UPPERBOUND; - else if (maxScore >= beta) - flag = TTFlag::LOWERBOUND; - else - flag = TTFlag::EXACT; - - search::tt.store(hash, session.pv[ply][0], maxScore, depth, flag); - } - return maxScore; + if (maxScore <= alphaOrig) + flag = TTFlag::UPPERBOUND; + else if (maxScore >= beta) + flag = TTFlag::LOWERBOUND; + else + flag = TTFlag::EXACT; + + search::tt.store(hash, session.pv[ply][0], maxScore, depth, flag); } - void search::search(const chess::Board &board, - const timeman::LimitsType timecontrol) - { - static double originalTimeAdjust = -1; - Session session; - session.tc = timecontrol; - session.tm.init(session.tc, board.sideToMove(), 0, originalTimeAdjust); - InfoFull lastInfo{}; - chess::Move lastPV[MAX_PLY]{}; - for (int i = 1; i < timecontrol.depth; i++) - { - for (int _=0;_<64;_++)for (int j=0;j<64;j++){ - movepick::historyHeuristic[_][j]/=2; + return maxScore; +} +void search::search(const chess::Board &board, + const timeman::LimitsType timecontrol) { + static double originalTimeAdjust = -1; + Session session; + session.tc = timecontrol; + session.tm.init(session.tc, board.sideToMove(), 0, originalTimeAdjust); + InfoFull lastInfo{}; + chess::Move lastPV[MAX_PLY]{}; + for (int i = 1; i < timecontrol.depth; i++) { + for (int _ = 0; _ < 64; _++) + for (int j = 0; j < 64; j++) { + movepick::historyHeuristic[_][j] /= 2; // since MAX_PLY=64 - session.pv[_][j]=Move::none(); + session.pv[_][j] = Move::none(); } - session.nodes = 0; - auto board_ = board; - Value score_ = doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); - if (session.tm.elapsed() >= - session.tm.optimum() || - stopSearch.load(std::memory_order_relaxed) || score_ == VALUE_NONE) - break; - InfoFull info{}; - info.depth = i; - info.selDepth = session.seldepth; - info.hashfull = tt.hashfull(); - info.nodes = session.nodes; - info.nps = session.nodes * 1000 / - std::max(session.tm.elapsed(), - (timeman::TimePoint)1); - info.timeMs = session.tm.elapsed(); - info.multiPV = 1; - info.score = score_; - std::string pv = ""; - for (Move *m = session.pv[0]; *m != Move::none(); m++) - pv += chess::uci::moveToUci(*m, board.chess960()) + " "; - info.pv = pv; - report(info); - lastInfo = info; - std::copy(session.pv[0], &session.pv[0][MAX_PLY], lastPV); - } - if (lastPV[0].is_ok()) - report(chess::uci::moveToUci(lastPV[0])); - else - { - // try to TT probe it - TTEntry *entry = tt.lookup(board.hash()); - if (entry && entry->getMove() != Move::none().raw()) - report(chess::uci::moveToUci(Move(entry->getMove()), board.chess960())); - else - { - Movelist moves; - board.legals(moves); + session.nodes = 0; + auto board_ = board; + Value score_ = + doSearch(board_, i, -VALUE_INFINITE, VALUE_INFINITE, session); + if (session.tm.elapsed() >= session.tm.optimum() || + stopSearch.load(std::memory_order_relaxed) || score_ == VALUE_NONE) + break; + InfoFull info{}; + info.depth = i; + info.selDepth = session.seldepth; + info.hashfull = tt.hashfull(); + info.nodes = session.nodes; + info.nps = session.nodes * 1000 / + std::max(session.tm.elapsed(), (timeman::TimePoint)1); + info.timeMs = session.tm.elapsed(); + info.multiPV = 1; + info.score = score_; + std::string pv = ""; + for (Move *m = session.pv[0]; *m != Move::none(); m++) + pv += chess::uci::moveToUci(*m, board.chess960()) + " "; + info.pv = pv; + report(info); + lastInfo = info; + std::copy(session.pv[0], &session.pv[0][MAX_PLY], lastPV); + } + if (lastPV[0].is_ok()) + report(chess::uci::moveToUci(lastPV[0])); + else { + // try to TT probe it + TTEntry *entry = tt.lookup(board.hash()); + if (entry && entry->getMove() != Move::none().raw()) + report(chess::uci::moveToUci(Move(entry->getMove()), board.chess960())); + else { + Movelist moves; + board.legals(moves); - if (moves.size()) - { - Board board_ = board; - Move best = moves[0]; - Value bestScore = -VALUE_INFINITE; - for (Move move : moves) - { - board_.doMove(move); - Value score = -eval::eval(board_); - if (score > bestScore) - { - bestScore = score; - best = move; - } - board_.undoMove(); + if (moves.size()) { + Board board_ = board; + Move best = moves[0]; + Value bestScore = -VALUE_INFINITE; + for (Move move : moves) { + board_.doMove(move); + Value score = -eval::eval(board_); + if (score > bestScore) { + bestScore = score; + best = move; } + board_.undoMove(); + } - InfoFull info{}; - info.depth = 1; - info.nodes = 1; - info.score = 0; - info.multiPV = 1; - info.pv = chess::uci::moveToUci(best, board.chess960()); - report(info); + InfoFull info{}; + info.depth = 1; + info.nodes = 1; + info.score = 0; + info.multiPV = 1; + info.pv = chess::uci::moveToUci(best, board.chess960()); + report(info); - report(chess::uci::moveToUci(best, board.chess960())); - } - else - { - report("0000"); - } + report(chess::uci::moveToUci(best, board.chess960())); + } else { + report("0000"); } } } +} } // namespace engine diff --git a/search.h b/search.h index 50b3a84..64afe0e 100644 --- a/search.h +++ b/search.h @@ -1,13 +1,13 @@ #pragma once -#include #include "tt.h" +#include namespace engine { - namespace timeman { - struct LimitsType; - } +namespace timeman { +struct LimitsType; } +} // namespace engine namespace engine::search { - void stop(); - void search(const chess::Board&, const timeman::LimitsType); - extern engine::TranspositionTable tt; -} \ No newline at end of file +void stop(); +void search(const chess::Board &, const timeman::LimitsType); +extern engine::TranspositionTable tt; +} // namespace engine::search \ No newline at end of file diff --git a/timeman.cpp b/timeman.cpp index 0920253..eb955d9 100644 --- a/timeman.cpp +++ b/timeman.cpp @@ -1,91 +1,90 @@ #include "timeman.h" #include "uci.h" #include "ucioption.h" -#include #include #include #include -#include #include -namespace engine::timeman -{ - - TimePoint TimeManagement::optimum() const { return optimumTime; } - TimePoint TimeManagement::maximum() const { return maximumTime; } - - void TimeManagement::clear() { - } - - // Called at the beginning of the search and calculates - // the bounds of time allowed for the current game ply. We currently support: - // 1) x basetime (+ z increment) - // 2) x moves in y seconds (+ z increment) - void TimeManagement::init(LimitsType &limits, - chess::Color us, - int ply, - double &originalTimeAdjust) { - - // If we have no time, we don't need to fully initialize TM. - // startTime is used by movetime and useNodesTime is used in elapsed calls. - startTime = limits.startTime; - - if (limits.time[us] == 0) - return; - - // optScale is a percentage of available time to use for the current move. - // maxScale is a multiplier applied to optimumTime. - double optScale, maxScale; - - // These numbers are used where multiplications, divisions or comparisons - // with constants are involved. - const TimePoint time = limits.time[us]; - const int moveOverhead = options["Move Overhead"]; - // Maximum move horizon - int centiMTG = limits.movestogo ? std::min(limits.movestogo * 100, 5000) : 5051; - - // If less than one second, gradually reduce mtg - if (time < 1000) - centiMTG = int(time * 5.051); - - // Make sure timeLeft is > 0 since we may use it as a divisor - TimePoint timeLeft = - std::max(TimePoint(1), - limits.time[us] - + (limits.inc[us] * (centiMTG - 100) - moveOverhead * (200 + centiMTG)) / 100); - - // x basetime (+ z increment) - // If there is a healthy increment, timeLeft can exceed the actual available - // game time for the current move, so also cap to a percentage of available game time. - if (limits.movestogo == 0) - { - // Extra time according to timeLeft - if (originalTimeAdjust < 0) - originalTimeAdjust = 0.3128 * std::log10(timeLeft) - 0.4354; - - // Calculate time constants based on current time left. - double logTimeInSec = std::log10(time / 1000.0); - double optConstant = std::min(0.0032116 + 0.000321123 * logTimeInSec, 0.00508017); - double maxConstant = std::max(3.3977 + 3.03950 * logTimeInSec, 2.94761); - - optScale = std::min(0.0121431 + std::pow(ply + 2.94693, 0.461073) * optConstant, - 0.213035 * limits.time[us] / timeLeft) - * originalTimeAdjust; - - maxScale = std::min(6.67704, maxConstant + ply / 11.9847); - } - - // x moves in y seconds (+ z increment) - else - { - optScale = - std::min((0.88 + ply / 116.4) / (centiMTG / 100.0), 0.88 * limits.time[us] / timeLeft); - maxScale = 1.3 + 0.11 * (centiMTG / 100.0); - } - - // Limit the maximum possible time for this move - optimumTime = TimePoint(optScale * timeLeft); - maximumTime = - TimePoint(std::min(0.825179 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10; - } - +#include +#include +namespace engine::timeman { + +TimePoint TimeManagement::optimum() const { return optimumTime; } +TimePoint TimeManagement::maximum() const { return maximumTime; } + +void TimeManagement::clear() {} + +// Called at the beginning of the search and calculates +// the bounds of time allowed for the current game ply. We currently support: +// 1) x basetime (+ z increment) +// 2) x moves in y seconds (+ z increment) +void TimeManagement::init(LimitsType &limits, chess::Color us, int ply, + double &originalTimeAdjust) { + + // If we have no time, we don't need to fully initialize TM. + // startTime is used by movetime and useNodesTime is used in elapsed calls. + startTime = limits.startTime; + + if (limits.time[us] == 0) + return; + + // optScale is a percentage of available time to use for the current move. + // maxScale is a multiplier applied to optimumTime. + double optScale, maxScale; + + // These numbers are used where multiplications, divisions or comparisons + // with constants are involved. + const TimePoint time = limits.time[us]; + const int moveOverhead = options["Move Overhead"]; + // Maximum move horizon + int centiMTG = + limits.movestogo ? std::min(limits.movestogo * 100, 5000) : 5051; + + // If less than one second, gradually reduce mtg + if (time < 1000) + centiMTG = int(time * 5.051); + + // Make sure timeLeft is > 0 since we may use it as a divisor + TimePoint timeLeft = std::max( + TimePoint(1), limits.time[us] + (limits.inc[us] * (centiMTG - 100) - + moveOverhead * (200 + centiMTG)) / + 100); + + // x basetime (+ z increment) + // If there is a healthy increment, timeLeft can exceed the actual available + // game time for the current move, so also cap to a percentage of available + // game time. + if (limits.movestogo == 0) { + // Extra time according to timeLeft + if (originalTimeAdjust < 0) + originalTimeAdjust = 0.3128 * std::log10(timeLeft) - 0.4354; + + // Calculate time constants based on current time left. + double logTimeInSec = std::log10(time / 1000.0); + double optConstant = + std::min(0.0032116 + 0.000321123 * logTimeInSec, 0.00508017); + double maxConstant = std::max(3.3977 + 3.03950 * logTimeInSec, 2.94761); + + optScale = + std::min(0.0121431 + std::pow(ply + 2.94693, 0.461073) * optConstant, + 0.213035 * limits.time[us] / timeLeft) * + originalTimeAdjust; + + maxScale = std::min(6.67704, maxConstant + ply / 11.9847); + } + + // x moves in y seconds (+ z increment) + else { + optScale = std::min((0.88 + ply / 116.4) / (centiMTG / 100.0), + 0.88 * limits.time[us] / timeLeft); + maxScale = 1.3 + 0.11 * (centiMTG / 100.0); + } + + // Limit the maximum possible time for this move + optimumTime = TimePoint(optScale * timeLeft); + maximumTime = TimePoint(std::min(0.825179 * limits.time[us] - moveOverhead, + maxScale * optimumTime)) - + 10; } + +} // namespace engine::timeman diff --git a/timeman.h b/timeman.h index f059245..ca90fe2 100644 --- a/timeman.h +++ b/timeman.h @@ -1,55 +1,54 @@ #pragma once #include -#include #include -namespace engine::timeman -{ - using TimePoint = std::chrono::milliseconds::rep; - constexpr TimePoint INFINITE_TIME = 864000000; - // LimitsType struct stores information sent by the caller about the analysis required. - inline TimePoint now() { - return std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch()) - .count(); - } - struct LimitsType { +#include +namespace engine::timeman { +using TimePoint = std::chrono::milliseconds::rep; +constexpr TimePoint INFINITE_TIME = 864000000; +// LimitsType struct stores information sent by the caller about the analysis +// required. +inline TimePoint now() { + return std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()) + .count(); +} +struct LimitsType { - // Init explicitly due to broken value-initialization of non POD in MSVC - LimitsType() { - time[chess::WHITE] = time[chess::BLACK] = inc[chess::WHITE] = inc[chess::BLACK] = movetime = TimePoint(0); - movestogo = mate = perft = infinite = 0; - depth = 64; - nodes = 0; - ponderMode = false; - } + // Init explicitly due to broken value-initialization of non POD in MSVC + LimitsType() { + time[chess::WHITE] = time[chess::BLACK] = inc[chess::WHITE] = + inc[chess::BLACK] = movetime = TimePoint(0); + movestogo = mate = perft = infinite = 0; + depth = 64; + nodes = 0; + ponderMode = false; + } - bool use_time_management() const { return time[chess::WHITE] || time[chess::BLACK]; } + bool use_time_management() const { + return time[chess::WHITE] || time[chess::BLACK]; + } - std::vector searchmoves; - TimePoint time[chess::COLOR_NB], inc[chess::COLOR_NB], movetime, startTime; - int movestogo, depth, mate, perft, infinite; - uint64_t nodes; - bool ponderMode; - }; - class TimeManagement { - public: - void init(LimitsType & limits, - chess::Color us, - int ply, + std::vector searchmoves; + TimePoint time[chess::COLOR_NB], inc[chess::COLOR_NB], movetime, startTime; + int movestogo, depth, mate, perft, infinite; + uint64_t nodes; + bool ponderMode; +}; +class TimeManagement { +public: + void init(LimitsType &limits, chess::Color us, int ply, double &originalTimeAdjust); - TimePoint optimum() const; - TimePoint maximum() const; - TimePoint elapsed() const { - return elapsed_time(); - } - TimePoint elapsed_time() const { return now() - startTime; }; + TimePoint optimum() const; + TimePoint maximum() const; + TimePoint elapsed() const { return elapsed_time(); } + TimePoint elapsed_time() const { return now() - startTime; }; - void clear(); + void clear(); - private: - TimePoint startTime; - TimePoint optimumTime; - TimePoint maximumTime; - }; -} \ No newline at end of file +private: + TimePoint startTime; + TimePoint optimumTime; + TimePoint maximumTime; +}; +} // namespace engine::timeman \ No newline at end of file diff --git a/tt.cpp b/tt.cpp index e0030bc..bd400ef 100644 --- a/tt.cpp +++ b/tt.cpp @@ -2,81 +2,74 @@ #include #if defined(_MSC_VER) -# include +#include #endif using namespace engine; -static inline uint64_t index_for_hash(uint64_t hash, uint64_t buckets) -{ - if (buckets == 0) - return 0; +static inline uint64_t index_for_hash(uint64_t hash, uint64_t buckets) { + if (buckets == 0) + return 0; #if defined(_MSC_VER) - // MSVC: use _umul128 to get high 64 bits of 128-bit product - unsigned long long high = 0; - (void)_umul128((unsigned long long)hash, (unsigned long long)buckets, &high); - return (uint64_t)high; + // MSVC: use _umul128 to get high 64 bits of 128-bit product + unsigned long long high = 0; + (void)_umul128((unsigned long long)hash, (unsigned long long)buckets, &high); + return (uint64_t)high; #elif defined(__SIZEOF_INT128__) - // GCC/Clang: use __uint128_t - __uint128_t prod = ( __uint128_t)hash * ( __uint128_t)buckets; - return (uint64_t)(prod >> 64); + // GCC/Clang: use __uint128_t + __uint128_t prod = (__uint128_t)hash * (__uint128_t)buckets; + return (uint64_t)(prod >> 64); #else - // Portable fallback (rare): fall back to modulo if no 128-bit or _umul128 available. - // This is only used on very uncommon toolchains; primary implementations above avoid division. - return hash % buckets; + // Portable fallback (rare): fall back to modulo if no 128-bit or _umul128 + // available. This is only used on very uncommon toolchains; primary + // implementations above avoid division. + return hash % buckets; #endif } -void TranspositionTable::newSearch() -{ - time++; -} +void TranspositionTable::newSearch() { time++; } -void TranspositionTable::store(uint64_t hash, chess::Move best, int16_t score, int8_t depth, TTFlag flag) -{ - // 2 entries per bucket - if (buckets == 0) - return; +void TranspositionTable::store(uint64_t hash, chess::Move best, int16_t score, + int8_t depth, TTFlag flag) { + // 2 entries per bucket + if (buckets == 0) + return; - uint64_t index = index_for_hash(hash, buckets); - if (index >= buckets - 1) - index = buckets - 2; // Ensure we don't overflow + uint64_t index = index_for_hash(hash, buckets); + if (index >= buckets - 1) + index = buckets - 2; // Ensure we don't overflow - TTEntry &e0 = table[index], &e1 = table[index + 1]; - // Store the entry - for (TTEntry *e : { &e0, &e1 }) - { - if (e->key == hash || e->getDepth() < depth) - { - e->key = hash; - e->setPackedFields(score, depth, flag, best.raw(), time); + TTEntry &e0 = table[index], &e1 = table[index + 1]; + // Store the entry + for (TTEntry *e : {&e0, &e1}) { + if (e->key == hash || e->getDepth() < depth) { + e->key = hash; + e->setPackedFields(score, depth, flag, best.raw(), time); - return; - } + return; } - // If we get here, we need to evict an entry - // Find the oldest entry - TTEntry *oldest = (e0.timestamp() < e1.timestamp()) ? &e0 : &e1; - // Evict it - oldest->key = hash; - oldest->setPackedFields(score, depth, flag, best.raw(), time); + } + // If we get here, we need to evict an entry + // Find the oldest entry + TTEntry *oldest = (e0.timestamp() < e1.timestamp()) ? &e0 : &e1; + // Evict it + oldest->key = hash; + oldest->setPackedFields(score, depth, flag, best.raw(), time); } -TTEntry *TranspositionTable::lookup(uint64_t hash) -{ - // 2 entries per bucket - if (buckets == 0) - return nullptr; +TTEntry *TranspositionTable::lookup(uint64_t hash) { + // 2 entries per bucket + if (buckets == 0) + return nullptr; - uint64_t index = index_for_hash(hash, buckets); - if (index >= buckets - 1) - index = buckets - 2; // Ensure we don't overflow + uint64_t index = index_for_hash(hash, buckets); + if (index >= buckets - 1) + index = buckets - 2; // Ensure we don't overflow - TTEntry &e0 = table[index], &e1 = table[index + 1]; - // Check the entries - for (TTEntry *e : { &e0, &e1 }) - { - if (e->key == hash) - return e; - } - return nullptr; + TTEntry &e0 = table[index], &e1 = table[index + 1]; + // Check the entries + for (TTEntry *e : {&e0, &e1}) { + if (e->key == hash) + return e; + } + return nullptr; } \ No newline at end of file diff --git a/tt.h b/tt.h index 09ecacb..f2e2496 100644 --- a/tt.h +++ b/tt.h @@ -1,179 +1,179 @@ #pragma once -#include -#include #include +#include +#include namespace engine { - enum TTFlag : uint8_t { - EXACT = 0, - LOWERBOUND = 1, - UPPERBOUND = 2 - }; - - struct TTEntry { - uint64_t key; - uint64_t pack; // 16-bit score, 8-bit depth, 3-bit flags, 16-bit move, 21 bits for generation - - // bit layout constants - static constexpr unsigned SCORE_SHIFT = 0; - static constexpr unsigned SCORE_BITS = 16; - static constexpr uint64_t SCORE_MASK = ((uint64_t(1) << SCORE_BITS) - 1) << SCORE_SHIFT; - - static constexpr unsigned DEPTH_SHIFT = 16; - static constexpr unsigned DEPTH_BITS = 8; - static constexpr uint64_t DEPTH_MASK = ((uint64_t(1) << DEPTH_BITS) - 1) << DEPTH_SHIFT; - - static constexpr unsigned FLAG_SHIFT = 24; - static constexpr unsigned FLAG_BITS = 3; - static constexpr uint64_t FLAG_MASK = ((uint64_t(1) << FLAG_BITS) - 1) << FLAG_SHIFT; - - static constexpr unsigned MOVE_SHIFT = 27; - static constexpr unsigned MOVE_BITS = 16; - static constexpr uint64_t MOVE_MASK = ((uint64_t(1) << MOVE_BITS) - 1) << MOVE_SHIFT; - - static constexpr unsigned GEN_SHIFT = 43; - static constexpr unsigned GEN_BITS = 21; - static constexpr uint64_t GEN_MASK = ((uint64_t(1) << GEN_BITS) - 1) << GEN_SHIFT; - - // getters - inline uint16_t getScore() const noexcept { - return static_cast((pack & SCORE_MASK) >> SCORE_SHIFT); - } - - inline uint8_t getDepth() const noexcept { - return static_cast((pack & DEPTH_MASK) >> DEPTH_SHIFT); - } - - inline TTFlag getFlag() const noexcept { - return static_cast((pack & FLAG_MASK) >> FLAG_SHIFT); - } - - inline uint16_t getMove() const noexcept { - return static_cast((pack & MOVE_MASK) >> MOVE_SHIFT); - } - - inline uint32_t getGeneration() const noexcept { - return static_cast((pack & GEN_MASK) >> GEN_SHIFT); - } - - // setters - inline void setScore(int16_t score) noexcept { - // preserve two's complement by casting through uint16_t - const uint64_t v = (static_cast(static_cast(score)) << SCORE_SHIFT) & SCORE_MASK; - pack = (pack & ~SCORE_MASK) | v; - } - - inline void setDepth(uint8_t depth) noexcept { - const uint64_t v = (static_cast(depth) << DEPTH_SHIFT) & DEPTH_MASK; - pack = (pack & ~DEPTH_MASK) | v; - } - - inline void setFlag(TTFlag flag) noexcept { - const uint64_t v = (static_cast(static_cast(flag)) << FLAG_SHIFT) & FLAG_MASK; - pack = (pack & ~FLAG_MASK) | v; - } - - inline void setMove(uint16_t move) noexcept { - const uint64_t v = (static_cast(move) << MOVE_SHIFT) & MOVE_MASK; - pack = (pack & ~MOVE_MASK) | v; - } - - inline void setGeneration(uint32_t gen) noexcept { - const uint64_t v = (static_cast(gen) << GEN_SHIFT) & GEN_MASK; - pack = (pack & ~GEN_MASK) | v; - } - - // convenience: set all packed fields at once - inline void setPackedFields(int16_t score, uint8_t depth, TTFlag flag, uint16_t move, uint32_t gen) noexcept { - const uint64_t s = (static_cast(static_cast(score)) << SCORE_SHIFT) & SCORE_MASK; - const uint64_t d = (static_cast(depth) << DEPTH_SHIFT) & DEPTH_MASK; - const uint64_t f = (static_cast(static_cast(flag)) << FLAG_SHIFT) & FLAG_MASK; - const uint64_t m = (static_cast(move) << MOVE_SHIFT) & MOVE_MASK; - const uint64_t g = (static_cast(gen) << GEN_SHIFT) & GEN_MASK; - pack = s | d | f | m | g; - } - - inline uint32_t timestamp() const noexcept { - return getGeneration(); - } - }; - class TranspositionTable - { - TTEntry *table; - int buckets; // number of buckets (pairs) - uint32_t time; - - public: - int size; // total number of TTEntry elements (must be even) - TranspositionTable() : table(nullptr), buckets(0), time(0), size(0) {} - - TranspositionTable(int sizeInMB) : time(0) - { - size = sizeInMB * 1048576 / sizeof(TTEntry); - if (size % 2 != 0) - size--; // Ensure even size - buckets = size / 2; - table = new TTEntry[size]; - clear(); - } - - ~TranspositionTable() - { - delete[] table; - } - - void resize(int sizeInMB) - { - TTEntry *old_table = table; - int old_size = size; - - size = sizeInMB * 1048576 / sizeof(TTEntry); - if (size % 2 != 0) - size--; - buckets = size / 2; - - TTEntry *new_table = new (std::nothrow) TTEntry[size]; - if (!new_table) - { - // Restore old values on failure - table = old_table; - size = old_size; - buckets = old_size / 2; - throw std::bad_alloc(); - } - - std::memcpy(new_table, old_table, - sizeof(TTEntry) * std::min(size, old_size)); - delete[] old_table; - table = new_table; - buckets = size / 2; - } - - void newSearch(); - void store(uint64_t hash, chess::Move best, int16_t score, int8_t depth, TTFlag flag); - - inline void clear() - { - std::fill_n(table, size, TTEntry{}); - } - - TTEntry *lookup(uint64_t hash); - - // Returns hash usage as percentage [0..100] based on occupied buckets. - inline int hashfull() const noexcept - { - if (!table || buckets <= 0) - return 0; - - int used = 0; - for (int i = 0; i < buckets; ++i) - { - const TTEntry &a = table[2 * i]; - const TTEntry &b = table[2 * i + 1]; - if (a.key != 0 || b.key != 0) - ++used; - } - - return (used * 1000) / buckets; - } - }; -} \ No newline at end of file +enum TTFlag : uint8_t { EXACT = 0, LOWERBOUND = 1, UPPERBOUND = 2 }; + +struct TTEntry { + uint64_t key; + uint64_t pack; // 16-bit score, 8-bit depth, 3-bit flags, 16-bit move, 21 bits + // for generation + + // bit layout constants + static constexpr unsigned SCORE_SHIFT = 0; + static constexpr unsigned SCORE_BITS = 16; + static constexpr uint64_t SCORE_MASK = ((uint64_t(1) << SCORE_BITS) - 1) + << SCORE_SHIFT; + + static constexpr unsigned DEPTH_SHIFT = 16; + static constexpr unsigned DEPTH_BITS = 8; + static constexpr uint64_t DEPTH_MASK = ((uint64_t(1) << DEPTH_BITS) - 1) + << DEPTH_SHIFT; + + static constexpr unsigned FLAG_SHIFT = 24; + static constexpr unsigned FLAG_BITS = 3; + static constexpr uint64_t FLAG_MASK = ((uint64_t(1) << FLAG_BITS) - 1) + << FLAG_SHIFT; + + static constexpr unsigned MOVE_SHIFT = 27; + static constexpr unsigned MOVE_BITS = 16; + static constexpr uint64_t MOVE_MASK = ((uint64_t(1) << MOVE_BITS) - 1) + << MOVE_SHIFT; + + static constexpr unsigned GEN_SHIFT = 43; + static constexpr unsigned GEN_BITS = 21; + static constexpr uint64_t GEN_MASK = ((uint64_t(1) << GEN_BITS) - 1) + << GEN_SHIFT; + + // getters + inline uint16_t getScore() const noexcept { + return static_cast((pack & SCORE_MASK) >> SCORE_SHIFT); + } + + inline uint8_t getDepth() const noexcept { + return static_cast((pack & DEPTH_MASK) >> DEPTH_SHIFT); + } + + inline TTFlag getFlag() const noexcept { + return static_cast((pack & FLAG_MASK) >> FLAG_SHIFT); + } + + inline uint16_t getMove() const noexcept { + return static_cast((pack & MOVE_MASK) >> MOVE_SHIFT); + } + + inline uint32_t getGeneration() const noexcept { + return static_cast((pack & GEN_MASK) >> GEN_SHIFT); + } + + // setters + inline void setScore(int16_t score) noexcept { + // preserve two's complement by casting through uint16_t + const uint64_t v = + (static_cast(static_cast(score)) << SCORE_SHIFT) & + SCORE_MASK; + pack = (pack & ~SCORE_MASK) | v; + } + + inline void setDepth(uint8_t depth) noexcept { + const uint64_t v = + (static_cast(depth) << DEPTH_SHIFT) & DEPTH_MASK; + pack = (pack & ~DEPTH_MASK) | v; + } + + inline void setFlag(TTFlag flag) noexcept { + const uint64_t v = + (static_cast(static_cast(flag)) << FLAG_SHIFT) & + FLAG_MASK; + pack = (pack & ~FLAG_MASK) | v; + } + + inline void setMove(uint16_t move) noexcept { + const uint64_t v = (static_cast(move) << MOVE_SHIFT) & MOVE_MASK; + pack = (pack & ~MOVE_MASK) | v; + } + + inline void setGeneration(uint32_t gen) noexcept { + const uint64_t v = (static_cast(gen) << GEN_SHIFT) & GEN_MASK; + pack = (pack & ~GEN_MASK) | v; + } + + // convenience: set all packed fields at once + inline void setPackedFields(int16_t score, uint8_t depth, TTFlag flag, + uint16_t move, uint32_t gen) noexcept { + const uint64_t s = + (static_cast(static_cast(score)) << SCORE_SHIFT) & + SCORE_MASK; + const uint64_t d = + (static_cast(depth) << DEPTH_SHIFT) & DEPTH_MASK; + const uint64_t f = + (static_cast(static_cast(flag)) << FLAG_SHIFT) & + FLAG_MASK; + const uint64_t m = (static_cast(move) << MOVE_SHIFT) & MOVE_MASK; + const uint64_t g = (static_cast(gen) << GEN_SHIFT) & GEN_MASK; + pack = s | d | f | m | g; + } + + inline uint32_t timestamp() const noexcept { return getGeneration(); } +}; +class TranspositionTable { + TTEntry *table; + int buckets; // number of buckets (pairs) + uint32_t time; + +public: + int size; // total number of TTEntry elements (must be even) + TranspositionTable() : table(nullptr), buckets(0), time(0), size(0) {} + + TranspositionTable(int sizeInMB) : time(0) { + size = sizeInMB * 1048576 / sizeof(TTEntry); + if (size % 2 != 0) + size--; // Ensure even size + buckets = size / 2; + table = new TTEntry[size]; + clear(); + } + + ~TranspositionTable() { delete[] table; } + + void resize(int sizeInMB) { + TTEntry *old_table = table; + int old_size = size; + + size = sizeInMB * 1048576 / sizeof(TTEntry); + if (size % 2 != 0) + size--; + buckets = size / 2; + + TTEntry *new_table = new (std::nothrow) TTEntry[size]; + if (!new_table) { + // Restore old values on failure + table = old_table; + size = old_size; + buckets = old_size / 2; + throw std::bad_alloc(); + } + + std::memcpy(new_table, old_table, + sizeof(TTEntry) * std::min(size, old_size)); + delete[] old_table; + table = new_table; + buckets = size / 2; + } + + void newSearch(); + void store(uint64_t hash, chess::Move best, int16_t score, int8_t depth, + TTFlag flag); + + inline void clear() { std::fill_n(table, size, TTEntry{}); } + + TTEntry *lookup(uint64_t hash); + + // Returns hash usage as percentage [0..100] based on occupied buckets. + inline int hashfull() const noexcept { + if (!table || buckets <= 0) + return 0; + + int used = 0; + for (int i = 0; i < buckets; ++i) { + const TTEntry &a = table[2 * i]; + const TTEntry &b = table[2 * i + 1]; + if (a.key != 0 || b.key != 0) + ++used; + } + + return (used * 1000) / buckets; + } +}; +} // namespace engine \ No newline at end of file diff --git a/tune.cpp b/tune.cpp index 5286b8c..4f77b77 100644 --- a/tune.cpp +++ b/tune.cpp @@ -31,96 +31,89 @@ using std::string; namespace engine { -bool Tune::update_on_last; -const Option* LastOption = nullptr; -OptionsMap* Tune::options; +bool Tune::update_on_last; +const Option *LastOption = nullptr; +OptionsMap *Tune::options; namespace { std::map TuneResults; -std::optional on_tune(const Option& o) { +std::optional on_tune(const Option &o) { - if (!Tune::update_on_last || LastOption == &o) - Tune::read_options(); + if (!Tune::update_on_last || LastOption == &o) + Tune::read_options(); - return std::nullopt; -} + return std::nullopt; } +} // namespace -void Tune::make_option(OptionsMap* opts, const string& n, int v, const SetRange& r) { +void Tune::make_option(OptionsMap *opts, const string &n, int v, + const SetRange &r) { - // Do not generate option when there is nothing to tune (ie. min = max) - if (r(v).first == r(v).second) - return; + // Do not generate option when there is nothing to tune (ie. min = max) + if (r(v).first == r(v).second) + return; - if (TuneResults.count(n)) - v = TuneResults[n]; + if (TuneResults.count(n)) + v = TuneResults[n]; - opts->add(n, Option(v, r(v).first, r(v).second, on_tune)); - LastOption = &((*opts)[n]); + opts->add(n, Option(v, r(v).first, r(v).second, on_tune)); + LastOption = &((*opts)[n]); - // Print formatted parameters, ready to be copy-pasted in Fishtest - std::cout << n << "," // - << v << "," // - << r(v).first << "," // - << r(v).second << "," // - << (r(v).second - r(v).first) / 20.0 << "," // - << "0.0020" << std::endl; + // Print formatted parameters, ready to be copy-pasted in Fishtest + std::cout << n << "," // + << v << "," // + << r(v).first << "," // + << r(v).second << "," // + << (r(v).second - r(v).first) / 20.0 << "," // + << "0.0020" << std::endl; } -string Tune::next(string& names, bool pop) { +string Tune::next(string &names, bool pop) { - string name; + string name; - do - { - string token = names.substr(0, names.find(',')); + do { + string token = names.substr(0, names.find(',')); - if (pop) - names.erase(0, token.size() + 1); + if (pop) + names.erase(0, token.size() + 1); - std::stringstream ws(token); - name += (ws >> token, token); // Remove trailing whitespace + std::stringstream ws(token); + name += (ws >> token, token); // Remove trailing whitespace - } while (std::count(name.begin(), name.end(), '(') - std::count(name.begin(), name.end(), ')')); + } while (std::count(name.begin(), name.end(), '(') - + std::count(name.begin(), name.end(), ')')); - return name; + return name; } - -template<> -void Tune::Entry::init_option() { - make_option(options, name, value, range); +template <> void Tune::Entry::init_option() { + make_option(options, name, value, range); } -template<> -void Tune::Entry::read_option() { - if (options->count(name)) - value = int((*options)[name]); +template <> void Tune::Entry::read_option() { + if (options->count(name)) + value = int((*options)[name]); } // Instead of a variable here we have a PostUpdate function: just call it -template<> -void Tune::Entry::init_option() {} -template<> -void Tune::Entry::read_option() { - value(); -} - -} // namespace engine +template <> void Tune::Entry::init_option() {} +template <> void Tune::Entry::read_option() { value(); } +} // namespace engine // Init options with tuning session results instead of default values. Useful to // get correct bench signature after a tuning session or to test tuned values. // Just copy fishtest tuning results in a result.txt file and extract the // values with: // -// cat results.txt | sed 's/^param: \([^,]*\), best: \([^,]*\).*/ TuneResults["\1"] = int(round(\2));/' +// cat results.txt | sed 's/^param: \([^,]*\), best: \([^,]*\).*/ +// TuneResults["\1"] = int(round(\2));/' // // Then paste the output below, as the function body - namespace engine { void Tune::read_results() { /* ...insert your values here... */ } -} // namespace engine +} // namespace engine diff --git a/tune.h b/tune.h index 347d2a7..d6a0022 100644 --- a/tune.h +++ b/tune.h @@ -22,7 +22,7 @@ #include #include #include -#include // IWYU pragma: keep +#include // IWYU pragma: keep #include #include @@ -30,36 +30,36 @@ namespace engine { class OptionsMap; -using Range = std::pair; // Option's min-max values +using Range = std::pair; // Option's min-max values using RangeFun = Range(int); // Default Range function, to calculate Option's min-max values -inline Range default_range(int v) { return v > 0 ? Range(0, 2 * v) : Range(2 * v, 0); } +inline Range default_range(int v) { + return v > 0 ? Range(0, 2 * v) : Range(2 * v, 0); +} struct SetRange { - explicit SetRange(RangeFun f) : - fun(f) {} - SetRange(int min, int max) : - fun(nullptr), - range(min, max) {} - Range operator()(int v) const { return fun ? fun(v) : range; } - - RangeFun* fun; - Range range; + explicit SetRange(RangeFun f) : fun(f) {} + SetRange(int min, int max) : fun(nullptr), range(min, max) {} + Range operator()(int v) const { return fun ? fun(v) : range; } + + RangeFun *fun; + Range range; }; #define SetDefaultRange SetRange(default_range) - -// Tune class implements the 'magic' code that makes the setup of a fishtest tuning -// session as easy as it can be. Mainly you have just to remove const qualifiers -// from the variables you want to tune and flag them for tuning, so if you have: +// Tune class implements the 'magic' code that makes the setup of a fishtest +// tuning session as easy as it can be. Mainly you have just to remove const +// qualifiers from the variables you want to tune and flag them for tuning, so +// if you have: // // const Value myValue[][2] = { { V(100), V(20) }, { V(7), V(78) } }; // -// If you have a my_post_update() function to run after values have been updated, -// and a my_range() function to set custom Option's min-max values, then you just -// remove the 'const' qualifiers and write somewhere below in the file: +// If you have a my_post_update() function to run after values have been +// updated, and a my_range() function to set custom Option's min-max values, +// then you just remove the 'const' qualifiers and write somewhere below in the +// file: // // TUNE(SetRange(my_range), myValue, my_post_update); // @@ -78,115 +78,120 @@ struct SetRange { class Tune { - using PostUpdate = void(); // Post-update function - - Tune() { read_results(); } - Tune(const Tune&) = delete; - void operator=(const Tune&) = delete; - void read_results(); - - static Tune& instance() { - static Tune t; - return t; - } // Singleton - - // Use polymorphism to accommodate Entry of different types in the same vector - struct EntryBase { - virtual ~EntryBase() = default; - virtual void init_option() = 0; - virtual void read_option() = 0; - }; - - template - struct Entry: public EntryBase { - - static_assert(!std::is_const_v, "Parameter cannot be const!"); - - static_assert(std::is_same_v || std::is_same_v, - "Parameter type not supported!"); - - Entry(const std::string& n, T& v, const SetRange& r) : - name(n), - value(v), - range(r) {} - void operator=(const Entry&) = delete; // Because 'value' is a reference - void init_option() override; - void read_option() override; - - std::string name; - T& value; - SetRange range; - }; - - // Our facility to fill the container, each Entry corresponds to a parameter - // to tune. We use variadic templates to deal with an unspecified number of - // entries, each one of a possible different type. - static std::string next(std::string& names, bool pop = true); - - int add(const SetRange&, std::string&&) { return 0; } - - template - int add(const SetRange& range, std::string&& names, T& value, Args&&... args) { - list.push_back(std::unique_ptr(new Entry(next(names), value, range))); - return add(range, std::move(names), args...); - } - - // Template specialization for arrays: recursively handle multi-dimensional arrays - template - int add(const SetRange& range, std::string&& names, T (&value)[N], Args&&... args) { - for (size_t i = 0; i < N; i++) - add(range, next(names, i == N - 1) + "[" + std::to_string(i) + "]", value[i]); - return add(range, std::move(names), args...); - } - - // Template specialization for SetRange - template - int add(const SetRange&, std::string&& names, SetRange& value, Args&&... args) { - return add(value, (next(names), std::move(names)), args...); - } - - static void make_option(OptionsMap* options, const std::string& n, int v, const SetRange& r); - - std::vector> list; - - public: - template - static int add(const std::string& names, Args&&... args) { - return instance().add(SetDefaultRange, names.substr(1, names.size() - 2), - args...); // Remove trailing parenthesis - } - static void init(OptionsMap& o) { - options = &o; - for (auto& e : instance().list) - e->init_option(); - read_options(); - } // Deferred, due to UCIEngine::Options access - static void read_options() { - for (auto& e : instance().list) - e->read_option(); - } - - static bool update_on_last; - static OptionsMap* options; + using PostUpdate = void(); // Post-update function + + Tune() { read_results(); } + Tune(const Tune &) = delete; + void operator=(const Tune &) = delete; + void read_results(); + + static Tune &instance() { + static Tune t; + return t; + } // Singleton + + // Use polymorphism to accommodate Entry of different types in the same vector + struct EntryBase { + virtual ~EntryBase() = default; + virtual void init_option() = 0; + virtual void read_option() = 0; + }; + + template struct Entry : public EntryBase { + + static_assert(!std::is_const_v, "Parameter cannot be const!"); + + static_assert(std::is_same_v || std::is_same_v, + "Parameter type not supported!"); + + Entry(const std::string &n, T &v, const SetRange &r) + : name(n), value(v), range(r) {} + void operator=(const Entry &) = delete; // Because 'value' is a reference + void init_option() override; + void read_option() override; + + std::string name; + T &value; + SetRange range; + }; + + // Our facility to fill the container, each Entry corresponds to a parameter + // to tune. We use variadic templates to deal with an unspecified number of + // entries, each one of a possible different type. + static std::string next(std::string &names, bool pop = true); + + int add(const SetRange &, std::string &&) { return 0; } + + template + int add(const SetRange &range, std::string &&names, T &value, + Args &&...args) { + list.push_back( + std::unique_ptr(new Entry(next(names), value, range))); + return add(range, std::move(names), args...); + } + + // Template specialization for arrays: recursively handle multi-dimensional + // arrays + template + int add(const SetRange &range, std::string &&names, T (&value)[N], + Args &&...args) { + for (size_t i = 0; i < N; i++) + add(range, next(names, i == N - 1) + "[" + std::to_string(i) + "]", + value[i]); + return add(range, std::move(names), args...); + } + + // Template specialization for SetRange + template + int add(const SetRange &, std::string &&names, SetRange &value, + Args &&...args) { + return add(value, (next(names), std::move(names)), args...); + } + + static void make_option(OptionsMap *options, const std::string &n, int v, + const SetRange &r); + + std::vector> list; + +public: + template + static int add(const std::string &names, Args &&...args) { + return instance().add(SetDefaultRange, names.substr(1, names.size() - 2), + args...); // Remove trailing parenthesis + } + static void init(OptionsMap &o) { + options = &o; + for (auto &e : instance().list) + e->init_option(); + read_options(); + } // Deferred, due to UCIEngine::Options access + static void read_options() { + for (auto &e : instance().list) + e->read_option(); + } + + static bool update_on_last; + static OptionsMap *options; }; -template -constexpr void tune_check_args(Args&&...) { - static_assert((!std::is_fundamental_v && ...), "TUNE macro arguments wrong"); +template constexpr void tune_check_args(Args &&...) { + static_assert((!std::is_fundamental_v && ...), + "TUNE macro arguments wrong"); } -// Some macro magic :-) we define a dummy int variable that the compiler initializes calling Tune::add() +// Some macro magic :-) we define a dummy int variable that the compiler +// initializes calling Tune::add() #define STRINGIFY(x) #x #define UNIQUE2(x, y) x##y -#define UNIQUE(x, y) UNIQUE2(x, y) // Two indirection levels to expand __LINE__ -#define TUNE(...) \ - int UNIQUE(p, __LINE__) = []() -> int { \ - tune_check_args(__VA_ARGS__); \ - return engine::Tune::add(STRINGIFY((__VA_ARGS__)), __VA_ARGS__); \ - }(); +#define UNIQUE(x, y) UNIQUE2(x, y) // Two indirection levels to expand __LINE__ +#define TUNE(...) \ + int UNIQUE(p, __LINE__) = []() -> int { \ + tune_check_args(__VA_ARGS__); \ + return engine::Tune::add(STRINGIFY((__VA_ARGS__)), __VA_ARGS__); \ + }(); #define UPDATE_ON_LAST() bool UNIQUE(p, __LINE__) = Tune::update_on_last = true -} // namespace engine +} // namespace engine -#endif // #ifndef TUNE_H_INCLUDED +#endif // #ifndef TUNE_H_INCLUDED diff --git a/uci.cpp b/uci.cpp index 01bad17..4ee298b 100644 --- a/uci.cpp +++ b/uci.cpp @@ -1,195 +1,183 @@ -#include -#include -#include #include "uci.h" #include "search.h" #include "timeman.h" #include "ucioption.h" +#include +#include #include +#include using namespace engine; chess::Position pos; OptionsMap engine::options; void handlePosition(std::istringstream &is) { - std::string token, fen; - - is >> token; - - if (token == "startpos") - { - fen = chess::Position::START_FEN; - is >> token; // Consume the "moves" token, if any - } - else if (token == "fen") - while (is >> token && token != "moves") - fen += token + " "; - else - return; - pos.setFen(fen); - - while (is >> token) - { - pos.push_uci(token); - } + std::string token, fen; + + is >> token; + + if (token == "startpos") { + fen = chess::Position::START_FEN; + is >> token; // Consume the "moves" token, if any + } else if (token == "fen") + while (is >> token && token != "moves") + fen += token + " "; + else + return; + pos.setFen(fen); + + while (is >> token) { + pos.push_uci(token); + } } timeman::LimitsType parse_limits(std::istream &is) { - timeman::LimitsType limits; - std::string token; - - limits.startTime = timeman::now(); // The search starts as early as possible - - while (is >> token) - if (token == "searchmoves") // Needs to be the last command on the line - while (is >> token) { - std::transform(token.begin(), token.end(), token.begin(), [](auto c) { return std::tolower(c); }); - limits.searchmoves.push_back(token); - } - else if (token == "wtime") - is >> limits.time[chess::WHITE]; - else if (token == "btime") - is >> limits.time[chess::BLACK]; - else if (token == "winc") - is >> limits.inc[chess::WHITE]; - else if (token == "binc") - is >> limits.inc[chess::BLACK]; - else if (token == "movestogo") - is >> limits.movestogo; - else if (token == "depth") - is >> limits.depth; - else if (token == "nodes") - is >> limits.nodes; - else if (token == "movetime") - is >> limits.movetime; - else if (token == "mate") - is >> limits.mate; - else if (token == "perft") - is >> limits.perft; - else if (token == "infinite") - limits.infinite = 1; - else if (token == "ponder"); - //std::cerr << "Pondering not supported!" << std::endl; - - return limits; -} -void handleGo(std::istringstream &ss) { - search::search(pos, parse_limits(ss)); + timeman::LimitsType limits; + std::string token; + + limits.startTime = timeman::now(); // The search starts as early as possible + + while (is >> token) + if (token == "searchmoves") // Needs to be the last command on the line + while (is >> token) { + std::transform(token.begin(), token.end(), token.begin(), + [](auto c) { return std::tolower(c); }); + limits.searchmoves.push_back(token); + } + else if (token == "wtime") + is >> limits.time[chess::WHITE]; + else if (token == "btime") + is >> limits.time[chess::BLACK]; + else if (token == "winc") + is >> limits.inc[chess::WHITE]; + else if (token == "binc") + is >> limits.inc[chess::BLACK]; + else if (token == "movestogo") + is >> limits.movestogo; + else if (token == "depth") + is >> limits.depth; + else if (token == "nodes") + is >> limits.nodes; + else if (token == "movetime") + is >> limits.movetime; + else if (token == "mate") + is >> limits.mate; + else if (token == "perft") + is >> limits.perft; + else if (token == "infinite") + limits.infinite = 1; + else if (token == "ponder") + ; + // std::cerr << "Pondering not supported!" << std::endl; + + return limits; } -template -struct overload : Ts... { - using Ts::operator()...; +void handleGo(std::istringstream &ss) { search::search(pos, parse_limits(ss)); } +template struct overload : Ts... { + using Ts::operator()...; }; -template -overload(Ts...) -> overload; +template overload(Ts...) -> overload; std::string engine::format_score(const Score &s) { - const auto format = - overload{ [](Score::Mate mate) -> std::string { - auto m = (mate.plies > 0 ? (mate.plies + 1) : mate.plies) / 2; - return std::string("mate ") + std::to_string(m); - }, - [](Score::Tablebase tb) -> std::string { - constexpr int TB_CP = 20000; - return std::string("cp ") - + std::to_string((tb.win ? TB_CP - tb.plies : -TB_CP - tb.plies)); - }, - [](Score::InternalUnits units) -> std::string { - return std::string("cp ") + std::to_string(units.value); - } }; - - return s.visit(format); + const auto format = overload{ + [](Score::Mate mate) -> std::string { + auto m = (mate.plies > 0 ? (mate.plies + 1) : mate.plies) / 2; + return std::string("mate ") + std::to_string(m); + }, + [](Score::Tablebase tb) -> std::string { + constexpr int TB_CP = 20000; + return std::string("cp ") + + std::to_string((tb.win ? TB_CP - tb.plies : -TB_CP - tb.plies)); + }, + [](Score::InternalUnits units) -> std::string { + return std::string("cp ") + std::to_string(units.value); + }}; + + return s.visit(format); } void engine::report(const InfoShort &info) { - std::cout << "info depth " << info.depth << " score " << format_score(info.score) << std::endl; + std::cout << "info depth " << info.depth << " score " + << format_score(info.score) << std::endl; } void engine::report(const InfoFull &info, bool showWDL) { - std::stringstream ss; + std::stringstream ss; - ss << "info"; - ss << " depth " << info.depth // - << " seldepth " << info.selDepth // - << " multipv " << info.multiPV // - << " score " << format_score(info.score); // + ss << "info"; + ss << " depth " << info.depth // + << " seldepth " << info.selDepth // + << " multipv " << info.multiPV // + << " score " << format_score(info.score); // - if (!info.bound.empty()) - ss << " " << info.bound; + if (!info.bound.empty()) + ss << " " << info.bound; - if (showWDL) - ss << " wdl " << info.wdl; + if (showWDL) + ss << " wdl " << info.wdl; - ss << " nodes " << info.nodes // - << " nps " << info.nps // - << " hashfull " << info.hashfull // - << " tbhits " << info.tbHits // - << " time " << info.timeMs // - << " pv " << info.pv; // + ss << " nodes " << info.nodes // + << " nps " << info.nps // + << " hashfull " << info.hashfull // + << " tbhits " << info.tbHits // + << " time " << info.timeMs // + << " pv " << info.pv; // - std::cout << ss.str() << std::endl; + std::cout << ss.str() << std::endl; } void engine::report(const InfoIteration &info) { - std::stringstream ss; + std::stringstream ss; - ss << "info"; - ss << " depth " << info.depth // - << " currmove " << info.currmove // - << " currmovenumber " << info.currmovenumber; // + ss << "info"; + ss << " depth " << info.depth // + << " currmove " << info.currmove // + << " currmovenumber " << info.currmovenumber; // - std::cout << ss.str() << std::endl; + std::cout << ss.str() << std::endl; } void engine::report(std::string_view bestmove) { - std::cout << "bestmove " << bestmove; - std::cout << std::endl; + std::cout << "bestmove " << bestmove; + std::cout << std::endl; } void engine::loop() { - std::string line; - pos.setFen(pos.START_FEN); - std::cout << "cppchess_engine version " << BUILD_VERSION << '\n'; - while (std::getline(std::cin, line)) { - std::istringstream ss(line); - std::string token; - while (ss >> token) { - if (token == "uci") { - std::cout << "id name cppchess_engine\n"; - std::cout << "id author winapiadmin\n"; - std::cout << options << '\n'; - std::cout << "uciok\n"; - std::cout.flush(); - break; - } - else if (token == "isready") { - std::cout << "readyok\n"; - std::cout.flush(); - break; - } - else if (token == "position") { - handlePosition(ss); - break; // rest belongs to position - } - else if (token == "go") { - handleGo(ss); - break; // rest belongs to go - } - else if (token == "ucinewgame") { - search::tt.clear(); - break; - } - else if (token == "stop") { - search::stop(); - break; - } - else if (token == "quit") { - return; - } - else if (token == "setoption") { - options.setoption(ss); - break; - } - else if (token=="visualize") { - std::cout << pos << std::endl; - break; - } - } + std::string line; + pos.setFen(pos.START_FEN); + std::cout << "cppchess_engine version " << BUILD_VERSION << '\n'; + while (std::getline(std::cin, line)) { + std::istringstream ss(line); + std::string token; + while (ss >> token) { + if (token == "uci") { + std::cout << "id name cppchess_engine\n"; + std::cout << "id author winapiadmin\n"; + std::cout << options << '\n'; + std::cout << "uciok\n"; + std::cout.flush(); + break; + } else if (token == "isready") { + std::cout << "readyok\n"; + std::cout.flush(); + break; + } else if (token == "position") { + handlePosition(ss); + break; // rest belongs to position + } else if (token == "go") { + handleGo(ss); + break; // rest belongs to go + } else if (token == "ucinewgame") { + search::tt.clear(); + break; + } else if (token == "stop") { + search::stop(); + break; + } else if (token == "quit") { + return; + } else if (token == "setoption") { + options.setoption(ss); + break; + } else if (token == "visualize") { + std::cout << pos << std::endl; + break; + } } + } } diff --git a/uci.h b/uci.h index c9557f6..dcf45eb 100644 --- a/uci.h +++ b/uci.h @@ -1,37 +1,37 @@ #pragma once -#include -#include #include "score.h" +#include +#include namespace engine { - struct InfoShort { - int depth; - Score score; - }; +struct InfoShort { + int depth; + Score score; +}; - struct InfoFull : InfoShort { - int selDepth; - size_t multiPV; - std::string_view wdl; - std::string_view bound; - size_t timeMs; - size_t nodes; - size_t nps; - size_t tbHits; - std::string_view pv; - int hashfull; - }; +struct InfoFull : InfoShort { + int selDepth; + size_t multiPV; + std::string_view wdl; + std::string_view bound; + size_t timeMs; + size_t nodes; + size_t nps; + size_t tbHits; + std::string_view pv; + int hashfull; +}; - struct InfoIteration { - int depth; - std::string_view currmove; - size_t currmovenumber; - }; - std::string format_score(const Score &s); - void report(const InfoFull &info, bool showWDL=false); - void report(const InfoShort &info); - void report(const InfoIteration &info); - void report(std::string_view bestmove); - void loop(); - class OptionsMap; - extern OptionsMap options; -} \ No newline at end of file +struct InfoIteration { + int depth; + std::string_view currmove; + size_t currmovenumber; +}; +std::string format_score(const Score &s); +void report(const InfoFull &info, bool showWDL = false); +void report(const InfoShort &info); +void report(const InfoIteration &info); +void report(std::string_view bestmove); +void loop(); +class OptionsMap; +extern OptionsMap options; +} // namespace engine \ No newline at end of file diff --git a/ucioption.cpp b/ucioption.cpp index dcf272e..68fc9e6 100644 --- a/ucioption.cpp +++ b/ucioption.cpp @@ -28,184 +28,166 @@ namespace engine { -bool CaseInsensitiveLess::operator()(const std::string& s1, const std::string& s2) const { +bool CaseInsensitiveLess::operator()(const std::string &s1, + const std::string &s2) const { - return std::lexicographical_compare( + return std::lexicographical_compare( s1.begin(), s1.end(), s2.begin(), s2.end(), [](char c1, char c2) { return std::tolower(c1) < std::tolower(c2); }); } -void OptionsMap::add_info_listener(InfoListener&& message_func) { info = std::move(message_func); } +void OptionsMap::add_info_listener(InfoListener &&message_func) { + info = std::move(message_func); +} -void OptionsMap::setoption(std::istringstream& is) { - std::string token, name, value; +void OptionsMap::setoption(std::istringstream &is) { + std::string token, name, value; - is >> token; // Consume the "name" token + is >> token; // Consume the "name" token - // Read the option name (can contain spaces) - while (is >> token && token != "value") - name += (name.empty() ? "" : " ") + token; + // Read the option name (can contain spaces) + while (is >> token && token != "value") + name += (name.empty() ? "" : " ") + token; - // Read the option value (can contain spaces) - while (is >> token) - value += (value.empty() ? "" : " ") + token; + // Read the option value (can contain spaces) + while (is >> token) + value += (value.empty() ? "" : " ") + token; - if (options_map.count(name)) - options_map[name] = value; - else - std::cerr << "No such option: " << name << std::endl; + if (options_map.count(name)) + options_map[name] = value; + else + std::cerr << "No such option: " << name << std::endl; } -const Option& OptionsMap::operator[](const std::string& name) const { - auto it = options_map.find(name); - assert(it != options_map.end()); - return it->second; +const Option &OptionsMap::operator[](const std::string &name) const { + auto it = options_map.find(name); + assert(it != options_map.end()); + return it->second; } // Inits options and assigns idx in the correct printing order -void OptionsMap::add(const std::string& name, const Option& option) { - if (!options_map.count(name)) - { - static size_t insert_order = 0; - - options_map[name] = option; - - options_map[name].parent = this; - options_map[name].idx = insert_order++; - } - else - { - std::cerr << "Option \"" << name << "\" was already added!" << std::endl; - std::exit(EXIT_FAILURE); - } +void OptionsMap::add(const std::string &name, const Option &option) { + if (!options_map.count(name)) { + static size_t insert_order = 0; + + options_map[name] = option; + + options_map[name].parent = this; + options_map[name].idx = insert_order++; + } else { + std::cerr << "Option \"" << name << "\" was already added!" << std::endl; + std::exit(EXIT_FAILURE); + } } +std::size_t OptionsMap::count(const std::string &name) const { + return options_map.count(name); +} -std::size_t OptionsMap::count(const std::string& name) const { return options_map.count(name); } - -Option::Option(const OptionsMap* map) : - parent(map) {} +Option::Option(const OptionsMap *map) : parent(map) {} -Option::Option(const char* v, OnChange f) : - type("string"), - min(0), - max(0), - on_change(std::move(f)) { - defaultValue = currentValue = v; +Option::Option(const char *v, OnChange f) + : type("string"), min(0), max(0), on_change(std::move(f)) { + defaultValue = currentValue = v; } -Option::Option(bool v, OnChange f) : - type("check"), - min(0), - max(0), - on_change(std::move(f)) { - defaultValue = currentValue = (v ? "true" : "false"); +Option::Option(bool v, OnChange f) + : type("check"), min(0), max(0), on_change(std::move(f)) { + defaultValue = currentValue = (v ? "true" : "false"); } -Option::Option(OnChange f) : - type("button"), - min(0), - max(0), - on_change(std::move(f)) {} - -Option::Option(int v, int minv, int maxv, OnChange f) : - type("spin"), - min(minv), - max(maxv), - on_change(std::move(f)) { - defaultValue = currentValue = std::to_string(v); +Option::Option(OnChange f) + : type("button"), min(0), max(0), on_change(std::move(f)) {} + +Option::Option(int v, int minv, int maxv, OnChange f) + : type("spin"), min(minv), max(maxv), on_change(std::move(f)) { + defaultValue = currentValue = std::to_string(v); } -Option::Option(const char* v, const char* cur, OnChange f) : - type("combo"), - min(0), - max(0), - on_change(std::move(f)) { - defaultValue = v; - currentValue = cur; +Option::Option(const char *v, const char *cur, OnChange f) + : type("combo"), min(0), max(0), on_change(std::move(f)) { + defaultValue = v; + currentValue = cur; } Option::operator int() const { - assert(type == "check" || type == "spin"); - return (type == "spin" ? std::stoi(currentValue) : currentValue == "true"); + assert(type == "check" || type == "spin"); + return (type == "spin" ? std::stoi(currentValue) : currentValue == "true"); } Option::operator std::string() const { - assert(type == "string"); - return currentValue; + assert(type == "string"); + return currentValue; } -bool Option::operator==(const char* s) const { - assert(type == "combo"); - return !CaseInsensitiveLess()(currentValue, s) && !CaseInsensitiveLess()(s, currentValue); +bool Option::operator==(const char *s) const { + assert(type == "combo"); + return !CaseInsensitiveLess()(currentValue, s) && + !CaseInsensitiveLess()(s, currentValue); } -bool Option::operator!=(const char* s) const { return !(*this == s); } - +bool Option::operator!=(const char *s) const { return !(*this == s); } // Updates currentValue and triggers on_change() action. It's up to // the GUI to check for option's limits, but we could receive the new value // from the user by console window, so let's check the bounds anyway. -Option& Option::operator=(const std::string& v) { - - assert(!type.empty()); - - if ((type != "button" && type != "string" && v.empty()) - || (type == "check" && v != "true" && v != "false") - || (type == "spin" && (std::stoi(v) < min || std::stoi(v) > max))) - return *this; - - if (type == "combo") - { - OptionsMap comboMap; // To have case insensitive compare - std::string token; - std::istringstream ss(defaultValue); - while (ss >> token) - comboMap.add(token, Option()); - if (!comboMap.count(v) || v == "var") - return *this; - } - - if (type == "string") - currentValue = v == "" ? "" : v; - else if (type != "button") - currentValue = v; - - if (on_change) - { - const auto ret = on_change(*this); - - if (ret && parent != nullptr && parent->info != nullptr) - parent->info(ret); - } +Option &Option::operator=(const std::string &v) { + + assert(!type.empty()); + if ((type != "button" && type != "string" && v.empty()) || + (type == "check" && v != "true" && v != "false") || + (type == "spin" && (std::stoi(v) < min || std::stoi(v) > max))) return *this; + + if (type == "combo") { + OptionsMap comboMap; // To have case insensitive compare + std::string token; + std::istringstream ss(defaultValue); + while (ss >> token) + comboMap.add(token, Option()); + if (!comboMap.count(v) || v == "var") + return *this; + } + + if (type == "string") + currentValue = v == "" ? "" : v; + else if (type != "button") + currentValue = v; + + if (on_change) { + const auto ret = on_change(*this); + + if (ret && parent != nullptr && parent->info != nullptr) + parent->info(ret); + } + + return *this; } -std::ostream& operator<<(std::ostream& os, const OptionsMap& om) { - for (size_t idx = 0; idx < om.options_map.size(); ++idx) - for (const auto& it : om.options_map) - if (it.second.idx == idx) - { - const Option& o = it.second; - os << "\noption name " << it.first << " type " << o.type; +std::ostream &operator<<(std::ostream &os, const OptionsMap &om) { + for (size_t idx = 0; idx < om.options_map.size(); ++idx) + for (const auto &it : om.options_map) + if (it.second.idx == idx) { + const Option &o = it.second; + os << "\noption name " << it.first << " type " << o.type; - if (o.type == "check" || o.type == "combo") - os << " default " << o.defaultValue; + if (o.type == "check" || o.type == "combo") + os << " default " << o.defaultValue; - else if (o.type == "string") - { - std::string defaultValue = o.defaultValue.empty() ? "" : o.defaultValue; - os << " default " << defaultValue; - } + else if (o.type == "string") { + std::string defaultValue = + o.defaultValue.empty() ? "" : o.defaultValue; + os << " default " << defaultValue; + } - else if (o.type == "spin") - os << " default " << stoi(o.defaultValue) << " min " << o.min << " max " - << o.max; + else if (o.type == "spin") + os << " default " << stoi(o.defaultValue) << " min " << o.min + << " max " << o.max; - break; - } + break; + } - return os; -} + return os; } +} // namespace engine diff --git a/ucioption.h b/ucioption.h index f13a544..012bc22 100644 --- a/ucioption.h +++ b/ucioption.h @@ -27,80 +27,80 @@ #include namespace engine { -// Define a custom comparator, because the UCI options should be case-insensitive +// Define a custom comparator, because the UCI options should be +// case-insensitive struct CaseInsensitiveLess { - bool operator()(const std::string&, const std::string&) const; + bool operator()(const std::string &, const std::string &) const; }; class OptionsMap; // The Option class implements each option as specified by the UCI protocol class Option { - public: - using OnChange = std::function(const Option&)>; - - Option(const OptionsMap*); - Option(OnChange = nullptr); - Option(bool v, OnChange = nullptr); - Option(const char* v, OnChange = nullptr); - Option(int v, int minv, int maxv, OnChange = nullptr); - Option(const char* v, const char* cur, OnChange = nullptr); - - Option& operator=(const std::string&); - operator int() const; - operator std::string() const; - bool operator==(const char*) const; - bool operator!=(const char*) const; - - friend std::ostream& operator<<(std::ostream&, const OptionsMap&); - - int operator<<(const Option&) = delete; - - private: - friend class OptionsMap; - friend class Engine; - friend class Tune; - - - std::string defaultValue, currentValue, type; - int min, max; - size_t idx; - OnChange on_change; - const OptionsMap* parent = nullptr; +public: + using OnChange = std::function(const Option &)>; + + Option(const OptionsMap *); + Option(OnChange = nullptr); + Option(bool v, OnChange = nullptr); + Option(const char *v, OnChange = nullptr); + Option(int v, int minv, int maxv, OnChange = nullptr); + Option(const char *v, const char *cur, OnChange = nullptr); + + Option &operator=(const std::string &); + operator int() const; + operator std::string() const; + bool operator==(const char *) const; + bool operator!=(const char *) const; + + friend std::ostream &operator<<(std::ostream &, const OptionsMap &); + + int operator<<(const Option &) = delete; + +private: + friend class OptionsMap; + friend class Engine; + friend class Tune; + + std::string defaultValue, currentValue, type; + int min, max; + size_t idx; + OnChange on_change; + const OptionsMap *parent = nullptr; }; class OptionsMap { - public: - using InfoListener = std::function)>; +public: + using InfoListener = std::function)>; - OptionsMap() = default; - OptionsMap(const OptionsMap&) = delete; - OptionsMap(OptionsMap&&) = delete; - OptionsMap& operator=(const OptionsMap&) = delete; - OptionsMap& operator=(OptionsMap&&) = delete; + OptionsMap() = default; + OptionsMap(const OptionsMap &) = delete; + OptionsMap(OptionsMap &&) = delete; + OptionsMap &operator=(const OptionsMap &) = delete; + OptionsMap &operator=(OptionsMap &&) = delete; - void add_info_listener(InfoListener&&); + void add_info_listener(InfoListener &&); - void setoption(std::istringstream&); + void setoption(std::istringstream &); - const Option& operator[](const std::string&) const; + const Option &operator[](const std::string &) const; - void add(const std::string&, const Option& option); + void add(const std::string &, const Option &option); - std::size_t count(const std::string&) const; + std::size_t count(const std::string &) const; - private: - friend class Engine; - friend class Option; +private: + friend class Engine; + friend class Option; - friend std::ostream& operator<<(std::ostream&, const OptionsMap&); + friend std::ostream &operator<<(std::ostream &, const OptionsMap &); - // The options container is defined as a std::map - using OptionsStore = std::map; + // The options container is defined as a std::map + using OptionsStore = std::map; - OptionsStore options_map; - InfoListener info; + OptionsStore options_map; + InfoListener info; }; -} -#endif // #ifndef UCIOPTION_H_INCLUDED +} // namespace engine +#endif // #ifndef UCIOPTION_H_INCLUDED From 9295dec5f17162382705a04656e4eb68d7c2c7d8 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Tue, 17 Mar 2026 09:40:37 +0700 Subject: [PATCH 45/50] fix typo --- .github/workflows/games.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/games.yml b/.github/workflows/games.yml index 702a6e1..8733a3a 100644 --- a/.github/workflows/games.yml +++ b/.github/workflows/games.yml @@ -118,7 +118,7 @@ jobs: -sprt elo0=0 elo1=2 alpha=0.05 beta=0.05 | tee results.txt ./ordo-linux64 -o ratings.txt -- games.pgn - tac results.txt | sed -n '/Total Time/,/^--------------------------------------------------$/p' | tac >> ratings.txtv + tac results.txt | sed -n '/Total Time/,/^--------------------------------------------------$/p' | tac >> ratings.txt - name: Upload results uses: actions/upload-artifact@v4 From 559d840e000992440543c9f4b517c842e74ae99d Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Tue, 17 Mar 2026 12:57:37 +0700 Subject: [PATCH 46/50] Update games.yml --- .github/workflows/games.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/games.yml b/.github/workflows/games.yml index 8733a3a..989f1ba 100644 --- a/.github/workflows/games.yml +++ b/.github/workflows/games.yml @@ -118,7 +118,7 @@ jobs: -sprt elo0=0 elo1=2 alpha=0.05 beta=0.05 | tee results.txt ./ordo-linux64 -o ratings.txt -- games.pgn - tac results.txt | sed -n '/Total Time/,/^--------------------------------------------------$/p' | tac >> ratings.txt + tac results.txt | sed -n '/Results of new vs base/,/^--------------------------------------------------$/p' results.txt | tac >> ratings.txt - name: Upload results uses: actions/upload-artifact@v4 From 2f962a90bb58ddf831589e8ed9b21ac98469861e Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Tue, 17 Mar 2026 13:06:30 +0700 Subject: [PATCH 47/50] Update games.yml --- .github/workflows/games.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/games.yml b/.github/workflows/games.yml index 989f1ba..d53e182 100644 --- a/.github/workflows/games.yml +++ b/.github/workflows/games.yml @@ -118,8 +118,9 @@ jobs: -sprt elo0=0 elo1=2 alpha=0.05 beta=0.05 | tee results.txt ./ordo-linux64 -o ratings.txt -- games.pgn - tac results.txt | sed -n '/Results of new vs base/,/^--------------------------------------------------$/p' results.txt | tac >> ratings.txt - + sed -n '/Results of new vs base/,/^--------------------------------------------------$/p' results.txt >> ratings.txt + sed -n '/Results of new vs base/,/^--------------------------------------------------$/p' results.txt >> ratings.txt + sed -n '/Total Time/,/^--------------------------------------------------$/p' results.txt >> ratings.txt - name: Upload results uses: actions/upload-artifact@v4 with: From e6b67a0c8d696c4f8de37731f8751f83259a732a Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Tue, 17 Mar 2026 13:10:37 +0700 Subject: [PATCH 48/50] Update games.yml --- .github/workflows/games.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/games.yml b/.github/workflows/games.yml index d53e182..0f04925 100644 --- a/.github/workflows/games.yml +++ b/.github/workflows/games.yml @@ -119,7 +119,6 @@ jobs: ./ordo-linux64 -o ratings.txt -- games.pgn sed -n '/Results of new vs base/,/^--------------------------------------------------$/p' results.txt >> ratings.txt - sed -n '/Results of new vs base/,/^--------------------------------------------------$/p' results.txt >> ratings.txt sed -n '/Total Time/,/^--------------------------------------------------$/p' results.txt >> ratings.txt - name: Upload results uses: actions/upload-artifact@v4 From 8a7f646b24de6ac4f5863b126cef05931aa1f389 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Sat, 28 Mar 2026 18:17:41 +0700 Subject: [PATCH 49/50] update source --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d9eb054..f9c26e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ include(FetchContent) FetchContent_Declare( chesslib GIT_REPOSITORY https://github.com/winapiadmin/chesslib.git - GIT_TAG maintaince_1 + GIT_TAG main ) FetchContent_MakeAvailable(chesslib) add_executable(engine "main.cpp" "timeman.cpp" "timeman.h" "eval.h" "eval.cpp" "tune.h" "ucioption.h" "tune.cpp" "ucioption.cpp" "tt.h" "tt.cpp" "uci.cpp" "uci.h" "search.h" "search.cpp" "score.h" "score.cpp" "movepick.h" "movepick.cpp") From 4e5461e438379cb624f674bbec542cd6ec317fb5 Mon Sep 17 00:00:00 2001 From: winapiadmin <138602885+winapiadmin@users.noreply.github.com> Date: Sat, 28 Mar 2026 18:21:02 +0700 Subject: [PATCH 50/50] Update uci.cpp --- uci.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/uci.cpp b/uci.cpp index 4ee298b..4cef54d 100644 --- a/uci.cpp +++ b/uci.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include using namespace engine; chess::Position pos;