From 4b7f54b77db1803b4d03bed848540dada37e7a0b Mon Sep 17 00:00:00 2001 From: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> Date: Sat, 4 May 2024 19:10:45 +0200 Subject: [PATCH 01/91] Remove all code so we can start the rewrite --- CMakeLists.txt | 42 +- senjo/BackgroundCommand.cpp | 277 --------- senjo/BackgroundCommand.h | 173 ------ senjo/CMakeLists.txt | 9 - senjo/ChessEngine.cpp | 74 --- senjo/ChessEngine.h | 279 --------- senjo/EngineOption.cpp | 157 ----- senjo/EngineOption.h | 223 ------- senjo/GoParams.h | 45 -- senjo/Output.cpp | 58 -- senjo/Output.h | 134 ----- senjo/Parameters.cpp | 112 ---- senjo/Parameters.h | 144 ----- senjo/Platform.h | 140 ----- senjo/README.md | 112 ---- senjo/SearchStats.h | 56 -- senjo/Thread.cpp | 75 --- senjo/Thread.h | 102 ---- senjo/UCIAdapter.cpp | 667 --------------------- senjo/UCIAdapter.h | 88 --- src/bitboard.cpp | 891 ---------------------------- src/bitboard.h | 438 -------------- src/bitwise.cpp | 338 ----------- src/bitwise.h | 125 ---- src/constants.h | 92 --- src/engine.cpp | 256 -------- src/engine.h | 123 ---- src/evaluate.cpp | 773 ------------------------ src/evaluate.h | 72 --- src/features.cpp | 181 ------ src/features.h | 187 ------ src/magics.cpp | 450 -------------- src/magics.h | 92 --- src/main.cpp | 227 +------ src/movegen.cpp | 572 ------------------ src/movelist_pool.cpp | 72 --- src/movelist_pool.h | 49 -- src/movepicker.cpp | 48 -- src/movepicker.h | 50 -- src/pst.cpp | 314 ---------- src/pst.h | 44 -- src/search.cpp | 538 ----------------- src/search.h | 59 -- src/timemanager.cpp | 103 ---- src/timemanager.h | 33 -- src/tt.cpp | 168 ------ src/tt.h | 106 ---- src/tuner.cpp | 400 ------------- src/tuner.h | 59 -- src/types.h | 185 ------ tests/tests_main.cpp => src/uci.cpp | 14 - src/{movegen.h => uci.h} | 14 +- src/utils.cpp | 72 --- src/utils.h | 97 --- tests/ep_tests.cpp | 44 -- tests/eval_tests.cpp | 174 ------ tests/perft_tests.cpp | 109 ---- tests/see_tests.cpp | 125 ---- 58 files changed, 24 insertions(+), 10637 deletions(-) delete mode 100644 senjo/BackgroundCommand.cpp delete mode 100644 senjo/BackgroundCommand.h delete mode 100644 senjo/CMakeLists.txt delete mode 100644 senjo/ChessEngine.cpp delete mode 100644 senjo/ChessEngine.h delete mode 100644 senjo/EngineOption.cpp delete mode 100644 senjo/EngineOption.h delete mode 100644 senjo/GoParams.h delete mode 100644 senjo/Output.cpp delete mode 100644 senjo/Output.h delete mode 100644 senjo/Parameters.cpp delete mode 100644 senjo/Parameters.h delete mode 100644 senjo/Platform.h delete mode 100644 senjo/README.md delete mode 100644 senjo/SearchStats.h delete mode 100644 senjo/Thread.cpp delete mode 100644 senjo/Thread.h delete mode 100644 senjo/UCIAdapter.cpp delete mode 100644 senjo/UCIAdapter.h delete mode 100644 src/bitboard.cpp delete mode 100644 src/bitboard.h delete mode 100644 src/bitwise.cpp delete mode 100644 src/bitwise.h delete mode 100644 src/constants.h delete mode 100644 src/engine.cpp delete mode 100644 src/engine.h delete mode 100644 src/evaluate.cpp delete mode 100644 src/evaluate.h delete mode 100644 src/features.cpp delete mode 100644 src/features.h delete mode 100644 src/magics.cpp delete mode 100644 src/magics.h delete mode 100644 src/movegen.cpp delete mode 100644 src/movelist_pool.cpp delete mode 100644 src/movelist_pool.h delete mode 100644 src/movepicker.cpp delete mode 100644 src/movepicker.h delete mode 100644 src/pst.cpp delete mode 100644 src/pst.h delete mode 100644 src/search.cpp delete mode 100644 src/search.h delete mode 100644 src/timemanager.cpp delete mode 100644 src/timemanager.h delete mode 100644 src/tt.cpp delete mode 100644 src/tt.h delete mode 100644 src/tuner.cpp delete mode 100644 src/tuner.h delete mode 100644 src/types.h rename tests/tests_main.cpp => src/uci.cpp (68%) rename src/{movegen.h => uci.h} (72%) delete mode 100644 src/utils.cpp delete mode 100644 src/utils.h delete mode 100644 tests/ep_tests.cpp delete mode 100644 tests/eval_tests.cpp delete mode 100644 tests/perft_tests.cpp delete mode 100644 tests/see_tests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c894b28..434aa478 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,9 +20,9 @@ # along with Zagreus. If not, see . cmake_minimum_required(VERSION 3.25) -set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD 23) project(Zagreus) -set(ZAGREUS_VERSION_MAJOR "5") +set(ZAGREUS_VERSION_MAJOR "6") set(ZAGREUS_VERSION_MINOR "0") # Default values @@ -200,27 +200,27 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${BUILD_FLAGS}") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${BUILD_FLAGS}") file(GLOB inc_zagreus "src/*.h" "src/*.cpp") -file(GLOB inc_senjo "senjo/*.h" "senjo/*.cpp") -add_executable(Zagreus src/main.cpp ${inc_senjo} ${inc_zagreus}) +add_executable(Zagreus src/main.cpp ${inc_zagreus}) target_compile_definitions(Zagreus PRIVATE ZAGREUS_VERSION_MAJOR="${ZAGREUS_VERSION_MAJOR}") target_compile_definitions(Zagreus PRIVATE ZAGREUS_VERSION_MINOR="${ZAGREUS_VERSION_MINOR}") -if (ENABLE_TESTS) - file(GLOB tests_folder "tests/*.h" "tests/*.cpp") - - # Remove main from inc_zagreus - list(REMOVE_ITEM inc_zagreus "${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp") - - add_executable(zagreus-tests ${tests_folder} ${inc_senjo} ${inc_zagreus}) - target_link_libraries(zagreus-tests PRIVATE Catch2::Catch2WithMain) - - target_compile_definitions(zagreus-tests PRIVATE ZAGREUS_VERSION_MAJOR="${ZAGREUS_VERSION_MAJOR}") - target_compile_definitions(zagreus-tests PRIVATE ZAGREUS_VERSION_MINOR="${ZAGREUS_VERSION_MINOR}") - - list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras) - include(CTest) - include(Catch) - catch_discover_tests(zagreus-tests) -endif () \ No newline at end of file +# TODO: re-enable +#if (ENABLE_TESTS) +# file(GLOB tests_folder "tests/*.h" "tests/*.cpp") +# +# # Remove main from inc_zagreus +# list(REMOVE_ITEM inc_zagreus "${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp") +# +# add_executable(zagreus-tests ${tests_folder} ${inc_zagreus}) +# target_link_libraries(zagreus-tests PRIVATE Catch2::Catch2WithMain) +# +# target_compile_definitions(zagreus-tests PRIVATE ZAGREUS_VERSION_MAJOR="${ZAGREUS_VERSION_MAJOR}") +# target_compile_definitions(zagreus-tests PRIVATE ZAGREUS_VERSION_MINOR="${ZAGREUS_VERSION_MINOR}") +# +# list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras) +# include(CTest) +# include(Catch) +# catch_discover_tests(zagreus-tests) +#endif () diff --git a/senjo/BackgroundCommand.cpp b/senjo/BackgroundCommand.cpp deleted file mode 100644 index 1de45c9d..00000000 --- a/senjo/BackgroundCommand.cpp +++ /dev/null @@ -1,277 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2015-2019 Shawn Chidester -// -// 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. -//----------------------------------------------------------------------------- - -#include "BackgroundCommand.h" -#include "Output.h" -#include - -namespace senjo { - -//----------------------------------------------------------------------------- - bool BackgroundCommand::parseAndExecute(Parameters ¶ms) { - if (!parse(params)) { - return false; - } - - if (isRunning()) { - Output() << "Another background command is still active, can't execute"; - return false; - } - - if (!engine.isInitialized()) { - engine.initialize(); - } - - return run(); - } - -//----------------------------------------------------------------------------- - bool RegisterCommandHandle::parse(Parameters ¶ms) { - later = false; - code = ""; - name = ""; - - params.popParam("later", later); - params.popString("name", name, "code"); - params.popString("code", code); - - if (params.size()) { - Output() << "Unexpected token: " << params.front(); - return false; - } - - return true; - } - -//----------------------------------------------------------------------------- - void RegisterCommandHandle::doWork() { - static const std::string registrationOK = "registration ok"; - - Output(Output::NoPrefix) << "registration checking"; - if (engine.isRegistered()) { - Output(Output::NoPrefix) << registrationOK; - } else if (later) { - engine.registerLater(); - Output(Output::NoPrefix) << registrationOK; - } else if (engine.doRegistration(name, code)) { - Output(Output::NoPrefix) << registrationOK; - } else { - Output(Output::NoPrefix) << "registration error"; - } - } - -//----------------------------------------------------------------------------- - bool GoCommandHandle::parse(Parameters ¶ms) { - goParams = GoParams(); // reset all params to default values - - bool invalid = false; - while (!invalid && params.size()) { - if (params.firstParamIs("searchmoves")) { - Output() << "searchmoves not implemented!"; // TODO - break; - } - if (params.popParam("infinite", goParams.infinite) || - params.popParam("ponder", goParams.ponder) || - params.popNumber("depth", goParams.depth) || - params.popNumber("movestogo", goParams.movestogo) || - params.popNumber("binc", goParams.binc) || - params.popNumber("btime", goParams.btime) || - params.popNumber("movetime", goParams.movetime) || - params.popNumber("nodes", goParams.nodes) || - params.popNumber("winc", goParams.winc) || - params.popNumber("wtime", goParams.wtime)) { - continue; - } - Output() << "Unexpected token: " << params.front(); - return false; - } - - if (invalid) { - Output() << "usage: " << usage(); - return false; - } - - return true; - } - -//----------------------------------------------------------------------------- - void GoCommandHandle::doWork() { - std::string ponderMove; - std::string bestMove = engine.go(goParams, &ponderMove); - - if (bestMove.empty()) { - bestMove = "none"; - ponderMove.clear(); - } - - if (ponderMove.size()) { - Output(Output::NoPrefix) << "bestmove " << bestMove - << " ponder " << ponderMove; - } else { - Output(Output::NoPrefix) << "bestmove " << bestMove; - } - } - -//----------------------------------------------------------------------------- - const std::string PerftCommandHandle::_TEST_FILE = "epd/perftsuite.epd"; - -//----------------------------------------------------------------------------- - bool PerftCommandHandle::parse(Parameters ¶ms) { - count = 0; - skip = 0; - maxDepth = 0; - maxLeafs = 0; - fileName = ""; - - bool epd = false; - bool invalid = false; - - while (params.size() && !invalid) { - if (params.popParam("epd", epd) || - params.popNumber("count", count, invalid) || - params.popNumber("skip", skip, invalid) || - params.popNumber("depth", maxDepth, invalid) || - params.popNumber("leafs", maxLeafs, invalid) || - params.popString("file", fileName)) { - continue; - } - Output() << "Unexpected token: " << params.front(); - return false; - } - - if (invalid) { - Output() << "usage: " << usage(); - return false; - } - - if (epd && fileName.empty()) { - fileName = _TEST_FILE; - } - - return true; - } - -//----------------------------------------------------------------------------- - void PerftCommandHandle::doWork() { - if (fileName.empty()) { - engine.perft(maxDepth); - return; - } - - std::ifstream fs(fileName); - - const TimePoint start = now(); - uint64_t pcount = 0; - bool done = false; - int positions = 0; - int line = 0; - - std::string fen; - fen.reserve(16384); - - while (!done && std::getline(fs, fen)) { - line++; - - size_t i = fen.find_first_not_of(" \t\r\n"); - if ((i == std::string::npos) || (fen[i] == '#')) { - continue; - } - - positions++; - if ((skip > 0) && (positions <= skip)) { - continue; - } - - Output() << fileName << " line " << line << ' ' << fen; - std::string remain; - if (!engine.setPosition(fen, &remain)) { - break; - } - - // process "D " parameters (e.g. D5 4865609) - Parameters params(remain); - while (!done && params.size()) { - std::string depthToken = trim(params.popString(), " ;"); - if (depthToken.empty() || (depthToken.at(0) != 'D')) { - continue; - } - - int16_t depth = toNumber(depthToken.substr(1)); - if (depth < 1) { - Output() << "--- invalid depth: " << depthToken; - break; - } - - if (params.empty()) { - Output() << "--- missing expected leaf count"; - break; - } - - uint64_t leafs = params.popNumber(); - if (leafs < 1) { - Output() << "--- invalid expected leaf count"; - break; - } - - done |= !process(depth, leafs, pcount); - } - - done |= ((count > 0) && (positions >= count)); - } - - double msecs = getMsecs(start, now()); - double kLeafs = (static_cast(pcount) / 1000); - - Output() << "Total Perft " << pcount << ' ' - << rate(kLeafs, msecs) << " KLeafs/sec"; - } - -//----------------------------------------------------------------------------- -//! \brief Perform [q]perft search, \p params format = 'D ' -//! \param[in] depth The depth to search to -//! \param[in] expected_leaf_count The expected leaf count at \p depth -//! \param[out] leaf_count The actual leaf count at \p depth -//! \return false if leaf_count count does not match expected leaf count -//----------------------------------------------------------------------------- - bool PerftCommandHandle::process(const int16_t depth, - const uint64_t expected_leaf_count, - uint64_t &leaf_count) { - if ((maxDepth > 0) && (depth > maxDepth)) { - return true; - } - - if ((maxLeafs > 0) && (expected_leaf_count > maxLeafs)) { - return true; - } - - Output() << "--- " << depth << " => " << expected_leaf_count; - uint64_t perft_count = engine.perft(depth); - leaf_count += perft_count; - - if (perft_count != expected_leaf_count) { - Output() << "--- " << perft_count << " != " << expected_leaf_count; - return false; - } - - return true; - } -} // namespace senjo diff --git a/senjo/BackgroundCommand.h b/senjo/BackgroundCommand.h deleted file mode 100644 index 17616018..00000000 --- a/senjo/BackgroundCommand.h +++ /dev/null @@ -1,173 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2015-2019 Shawn Chidester -// -// 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. -//----------------------------------------------------------------------------- - -#ifndef SENJO_BACKGROUND_COMMAND_H -#define SENJO_BACKGROUND_COMMAND_H - -#include "ChessEngine.h" -#include "Parameters.h" -#include "GoParams.h" -#include "Thread.h" - -namespace senjo { - -//----------------------------------------------------------------------------- -//! \brief Base class for a command that should run on a background thread -//----------------------------------------------------------------------------- - class BackgroundCommand : public Thread { - public: - //-------------------------------------------------------------------------- - //! \brief Constructor - //! \param[in] engine The chess engine to use while executing - //-------------------------------------------------------------------------- - BackgroundCommand(ChessEngine &chessEngine) - : engine(chessEngine) {} - - //-------------------------------------------------------------------------- - //! \brief Parse command parameters and execute on the given thread - //! \param[in] params The command parameters - //! \return true if parameters are valid and execution started - //-------------------------------------------------------------------------- - virtual bool parseAndExecute(Parameters ¶ms); - - //-------------------------------------------------------------------------- - //! \brief Provide usage syntax for this command - //! For example: "command_name [boolean_option_name] [option_name ]" - //! \return Usage syntax string for this command - //-------------------------------------------------------------------------- - virtual std::string usage() const = 0; - - //-------------------------------------------------------------------------- - //! \brief Provide a brief description of this command - //! \return A brief description of this command - //-------------------------------------------------------------------------- - virtual std::string description() const = 0; - - //-------------------------------------------------------------------------- - //! \brief Parse command parameters - //! \param[in] params The command parameters - //! \return true if the given parameters are valid - //-------------------------------------------------------------------------- - virtual bool parse(Parameters ¶ms) = 0; - - protected: - ChessEngine &engine; - }; - -//----------------------------------------------------------------------------- -//! \brief Wrapper for the UCI "register" command -//----------------------------------------------------------------------------- - class RegisterCommandHandle : public BackgroundCommand { - public: - RegisterCommandHandle(ChessEngine &eng) : BackgroundCommand(eng) {} - - std::string usage() const { - return "register [later] [name ] [code ]"; - } - - std::string description() const { - return "Register the chess engine to enable full functionality."; - } - - void stop() {} - - protected: - bool parse(Parameters ¶ms); - - void doWork(); - - private: - bool later; - std::string code; - std::string name; - }; - -//----------------------------------------------------------------------------- -//! \brief Wrapper for the UCI "go" command -//----------------------------------------------------------------------------- - class GoCommandHandle : public BackgroundCommand { - public: - GoCommandHandle(ChessEngine &eng) : BackgroundCommand(eng) {} - - std::string usage() const { - return "go [infinite] [ponder] [depth ] [nodes ] " - "[wtime ] [btime ] [winc ] [binc ] " - "[movetime ] [movestogo ] [searchmoves ]"; - } - - std::string description() const { - return "Find the best move for the current position."; - } - - void stop() { - engine.stopSearching(); - } - - protected: - bool parse(Parameters ¶ms); - - void doWork(); - - private: - GoParams goParams; - }; - -//----------------------------------------------------------------------------- -//! \brief Wrapper for the "perft" command (not a UCI command) -//----------------------------------------------------------------------------- - class PerftCommandHandle : public BackgroundCommand { - public: - PerftCommandHandle(ChessEngine &eng) : BackgroundCommand(eng) {} - - std::string usage() const { - return "perft [unsorted] [depth ] [count ] [skip ] [leafs ] " - "[epd] [file (default=" + _TEST_FILE + ")]"; - } - - std::string description() const { - return "Execute performance test."; - } - - void stop() { - engine.stopSearching(); - } - - protected: - bool parse(Parameters ¶ms); - - void doWork(); - - private: - bool process(const int16_t depth, const uint64_t expected_leaf_count, - uint64_t &leaf_count); - - static const std::string _TEST_FILE; - - int count; - int skip; - int maxDepth; - uint64_t maxLeafs; - std::string fileName; - }; -} // namespace senjo - -#endif // SENJO_BACKGROUND_COMMAND_H diff --git a/senjo/CMakeLists.txt b/senjo/CMakeLists.txt deleted file mode 100644 index a8b55207..00000000 --- a/senjo/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -cmake_minimum_required(VERSION 3.1) -project(senjo CXX) - -file(GLOB OBJ_HDR *.h) -file(GLOB OBJ_SRC *.cpp) - -include_directories(.) -add_library(${PROJECT_NAME} STATIC ${OBJ_HDR} ${OBJ_SRC}) - diff --git a/senjo/ChessEngine.cpp b/senjo/ChessEngine.cpp deleted file mode 100644 index ce8ca801..00000000 --- a/senjo/ChessEngine.cpp +++ /dev/null @@ -1,74 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2015-2019 Shawn Chidester -// -// 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. -//----------------------------------------------------------------------------- - -#include "ChessEngine.h" - -namespace senjo { - -//----------------------------------------------------------------------------- - const std::string ChessEngine::STARTPOS = - "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; - -//----------------------------------------------------------------------------- - std::string ChessEngine::getEmailAddress() { - return ""; - } - -//----------------------------------------------------------------------------- - std::string ChessEngine::getCountryName() { - return ""; - } - -//----------------------------------------------------------------------------- - bool ChessEngine::isRegistered() { - return true; - } - -//----------------------------------------------------------------------------- - void ChessEngine::registerLater() { - } - -//----------------------------------------------------------------------------- - bool ChessEngine::doRegistration(const std::string & /*name*/, - const std::string & /*code*/) { - return true; - } - -//----------------------------------------------------------------------------- - bool ChessEngine::isCopyProtected() { - return false; - } - -//----------------------------------------------------------------------------- - bool ChessEngine::copyIsOK() { - return true; - } - -//----------------------------------------------------------------------------- - void ChessEngine::resetEngineStats() { - } - -//----------------------------------------------------------------------------- - void ChessEngine::showEngineStats() { - } - -} // namespace senjo diff --git a/senjo/ChessEngine.h b/senjo/ChessEngine.h deleted file mode 100644 index 92f29f73..00000000 --- a/senjo/ChessEngine.h +++ /dev/null @@ -1,279 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2015-2019 Shawn Chidester -// -// 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. -//----------------------------------------------------------------------------- - -#ifndef SENJO_CHESS_ENGINE_H -#define SENJO_CHESS_ENGINE_H - -#include "EngineOption.h" -#include "GoParams.h" -#include "SearchStats.h" - -namespace senjo { - -//----------------------------------------------------------------------------- -//! \brief Base class for Senjo compatible chess engines -//! Derive from this class to create a chess engine that may be used with -//! the Senjo UCIAdapter. -//----------------------------------------------------------------------------- - class ChessEngine { - public: - virtual ~ChessEngine() {} - - //--------------------------------------------------------------------------- - //! \brief FEN string of standard chess start position - //! See http://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation - //--------------------------------------------------------------------------- - static const std::string STARTPOS; - - //--------------------------------------------------------------------------- - //! \brief Get the engine name - //! \return The engine name - //--------------------------------------------------------------------------- - virtual std::string getEngineName() = 0; - - //--------------------------------------------------------------------------- - //! \brief Get the engine version (e.g. "major.minor.build" e.g. "1.0.0") - //! \return The engine version - //--------------------------------------------------------------------------- - virtual std::string getEngineVersion() = 0; - - //--------------------------------------------------------------------------- - //! \brief Get the engine author name(s) - //! \return The engine author name(s) - //--------------------------------------------------------------------------- - virtual std::string getAuthorName() = 0; - - //--------------------------------------------------------------------------- - //! \brief Get email address(es) for use with this engine - //! Return an empty string if you don't wish to report an email address. - //! \return An email address(es) for use with this engine - //--------------------------------------------------------------------------- - virtual std::string getEmailAddress() = 0; - - //--------------------------------------------------------------------------- - //! \brief Get the name of the country this engine originates from - //! Return an empty string if you don't wish to report a country - //! \return The name of the country this engine originates from - //--------------------------------------------------------------------------- - virtual std::string getCountryName() = 0; - - //--------------------------------------------------------------------------- - //! \brief Get options supported by the engine, and their current values - //! \return A list of the engine's options and their current values - //--------------------------------------------------------------------------- - virtual std::list getOptions() = 0; - - //--------------------------------------------------------------------------- - //! \brief Set a particular option to a given value - //! Option value may be empty, particularly if the option type is Button - //! \param[in] optionName The option name - //! \param[in] optionValue The new option value - //! \return false if the option name or value is invalid - //--------------------------------------------------------------------------- - virtual bool setEngineOption(const std::string &optionName, - const std::string &optionValue) = 0; - - //--------------------------------------------------------------------------- - //! \brief Initialize the engine - //--------------------------------------------------------------------------- - virtual void initialize() = 0; - - //--------------------------------------------------------------------------- - //! \brief Is the engine initialized? - //! \return true if the engine is initialized - //--------------------------------------------------------------------------- - virtual bool isInitialized() = 0; - - //--------------------------------------------------------------------------- - //! \brief Set the board position according to a given FEN string - //! The engine should use Output() to report errors in the FEN string. - //! Only use position info from the given FEN string, don't process any moves - //! or other data present in the FEN string. - //! \param[in] fen The FEN string on input - //! \param[out] remain If not NULL populated with tail portion of \p fen - //! string that was not used to set the position. - //! \return false if the FEN string does not contain a valid position - //--------------------------------------------------------------------------- - virtual bool setPosition(const std::string &fen, - std::string* remain = nullptr) = 0; - - //--------------------------------------------------------------------------- - //! \brief Execute a single move on the current position - //! Determine whether the given string is a valid move - //! and if it is apply the move to the current position. - //! Moves should be in coordinate notation (e.g. "e2e4", "g8f6", "e7f8q"). - //! \param[in] move A string containing move coordinate notation - //! \return false if the given string isn't a valid move - //--------------------------------------------------------------------------- - virtual bool makeMove(const std::string &move) = 0; - - //--------------------------------------------------------------------------- - //! \brief Get a FEN string representation of the current board position - //! \return A FEN string representation of the current board postiion - //--------------------------------------------------------------------------- - virtual std::string getFEN() = 0; - - //--------------------------------------------------------------------------- - //! \brief Output a text representation of the current board position - //--------------------------------------------------------------------------- - virtual void printBoard() = 0; - - //--------------------------------------------------------------------------- - //! \brief Is it white to move in the current position? - //! \return true if it is white to move in the current position - //--------------------------------------------------------------------------- - virtual bool whiteToMove() = 0; - - //--------------------------------------------------------------------------- - //! \brief Clear any engine data that can persist between searches - //! Examples of search data are the transposition transpositionTable and killer moves. - //--------------------------------------------------------------------------- - virtual void clearSearchData() = 0; - - //--------------------------------------------------------------------------- - //! \brief The last ponder move was played - //! The Go() method may return a ponder move which is the expected response - //! to the bestmove returned by Go(). If pondering is enabled the UCI - //! adapter may tell the engine to ponder this move, e.g. start searching - //! for a reply to the ponder move. If, while the engine is pondering, the - //! ponder move is played this method will be called. In general the engine - //! should make what it has learned from its pondering available for the next - //! Go() call. - //--------------------------------------------------------------------------- - virtual void ponderHit() = 0; - - //--------------------------------------------------------------------------- - //! \brief Is the engine registered? - //! \return true if the engine is registered - //--------------------------------------------------------------------------- - virtual bool isRegistered() = 0; - - //--------------------------------------------------------------------------- - //! \brief Register the engine later - //! The engine should still function, but may cripple itself in some fashion. - //--------------------------------------------------------------------------- - virtual void registerLater() = 0; - - //--------------------------------------------------------------------------- - //! \brief Register the engine now - //! If this fails the engine should still function, but may cripple itself - //! in some fashion. - //! \param[in] name The name to register the engine to - //! \param[in] code The code to use for registration - //! \return true if successful - //--------------------------------------------------------------------------- - virtual bool doRegistration(const std::string &name, - const std::string &code) = 0; - - //--------------------------------------------------------------------------- - //! \brief Does this engine use copy protection? - //! \return true if the engine uses copy protection - //--------------------------------------------------------------------------- - virtual bool isCopyProtected() = 0; - - //--------------------------------------------------------------------------- - //! \brief Determine whether this is a legitimate copy of the engine - //! This method will be called if IsCopyProtected() returns true. This is - //! where your engine should try to determine whether it is a legitimate - //! copy or not. - //! \return true if the engine is a legitimate copy - //--------------------------------------------------------------------------- - virtual bool copyIsOK() = 0; - - //-------------------------------------------------------------------------- - //! \brief Set the engine's debug mode on or off - //! \param[in] flag true to enable debug mode, false to disable debug mode - //--------------------------------------------------------------------------- - virtual void setDebug(const bool flag) = 0; - - //--------------------------------------------------------------------------- - //! \brief Is debug mode enabled? - //! \return true if debug mode is enabled - //--------------------------------------------------------------------------- - virtual bool isDebugOn() = 0; - - //--------------------------------------------------------------------------- - //! \brief Is the engine currently executing the Go() method? - //! It is not recommended to set this to true while Perft() is executing. - //! \return true if the engine is searching - //--------------------------------------------------------------------------- - virtual bool isSearching() = 0; - - //--------------------------------------------------------------------------- - //! \brief Tell the engine to stop searching - //! Exit Perft()/Go() methods as quickly as possible. - //--------------------------------------------------------------------------- - virtual void stopSearching() = 0; - - //-------------------------------------------------------------------------- - //! \brief Was stopSearching() called after the last go() or perft() call? - //! \return true if stopSearching() called after the last go() or perft() call - //-------------------------------------------------------------------------- - virtual bool stopRequested() = 0; - - //--------------------------------------------------------------------------- - //! \brief Block execution on the calling thread until the engine is - //! finished searching. Return immediateky if no search in progress. - //--------------------------------------------------------------------------- - virtual void waitForSearchFinish() = 0; - - //--------------------------------------------------------------------------- - //! \brief Do performance test on the current position - //! \param[in] depth How many half-moves (plies) to search - //! \return The number of leaf nodes visited at \p depth - //--------------------------------------------------------------------------- - virtual uint64_t perft(const int16_t depth) = 0; - - //--------------------------------------------------------------------------- - //! \brief Execute search on current position to find best move - //! \param[in] params UCI "go" command parameters - //! \param[out] ponder If not null set to the move engine should ponder next - //! \return Best move in coordinate notation (e.g. "e2e4", "g8f6", "e7f8q") - //--------------------------------------------------------------------------- - virtual std::string go(GoParams ¶ms, - std::string* ponder = nullptr) = 0; - - //-------------------------------------------------------------------------- - //! \brief Get statistics about the last (or current) search - //! \param[in] count The maximum number of lines to get stats for - //! \return a SearchStats struct updated with the latest search stats - //-------------------------------------------------------------------------- - virtual SearchStats getSearchStats() = 0; - - //-------------------------------------------------------------------------- - //! \brief Reset custom engine statistical counter totals - //! \remark Override if you desire custom engine stats to be output when - //! the "test" command is run. - //-------------------------------------------------------------------------- - virtual void resetEngineStats() = 0; - - //-------------------------------------------------------------------------- - //! \brief Output engine stats collected since last resetEngineStats call - //! \remark Override if you desire custom engine stats to be output when - //! the "test" command is run. - //-------------------------------------------------------------------------- - virtual void showEngineStats() = 0; - }; - -} // namespace senjo - -#endif // SENJO_CHESS_ENGINE_H diff --git a/senjo/EngineOption.cpp b/senjo/EngineOption.cpp deleted file mode 100644 index 4b8ab3a2..00000000 --- a/senjo/EngineOption.cpp +++ /dev/null @@ -1,157 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2015-2019 Shawn Chidester -// -// 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. -//----------------------------------------------------------------------------- - -#include "EngineOption.h" - -namespace senjo { - - static const char* OPT_BUTTON_NAME = "button"; - static const char* OPT_CHECK_NAME = "check"; - static const char* OPT_COMBO_NAME = "combo"; - static const char* OPT_SPIN_NAME = "spin"; - static const char* OPT_STRING_NAME = "string"; - static const char* OPT_UNKNOWN_NAME = "unknown"; - -//----------------------------------------------------------------------------- - EngineOption::OptionType EngineOption::toOptionType(const std::string &name) { - if (!iEqual(name, OPT_BUTTON_NAME)) { - return Button; - } - if (!iEqual(name, OPT_CHECK_NAME)) { - return Checkbox; - } - if (!iEqual(name, OPT_COMBO_NAME)) { - return ComboBox; - } - if (!iEqual(name, OPT_SPIN_NAME)) { - return Spin; - } - if (!iEqual(name, OPT_STRING_NAME)) { - return String; - } - return Unknown; - } - -//----------------------------------------------------------------------------- - std::string EngineOption::getTypeName(const OptionType type) { - switch (type) { - case Button: - return OPT_BUTTON_NAME; - case Checkbox: - return OPT_CHECK_NAME; - case ComboBox: - return OPT_COMBO_NAME; - case Spin: - return OPT_SPIN_NAME; - case String: - return OPT_STRING_NAME; - default: - break; - } - return OPT_UNKNOWN_NAME; - } - -//----------------------------------------------------------------------------- - EngineOption::EngineOption(const std::string &optName, - const std::string &defaultValue, - const OptionType optType, - const int64_t minValue, - const int64_t maxValue, - const std::set &comboValues) - : optType(optType), - optName(optName), - optValue(defaultValue), - defaultValue(defaultValue), - minValue(minValue), - maxValue(maxValue), - comboValues(comboValues) {} - -//----------------------------------------------------------------------------- - int64_t EngineOption::getIntValue() const { - return toNumber(optValue); - } - -//----------------------------------------------------------------------------- - int64_t EngineOption::getDefaultIntValue() const { - return toNumber(defaultValue); - } - -//----------------------------------------------------------------------------- - std::set EngineOption::getIntComboValues() const { - std::set values; - for (auto value : comboValues) { - int64_t n = toNumber(value, -1); - if (n >= 0) { - values.insert(n); - } - } - return values; - } - -//----------------------------------------------------------------------------- - bool EngineOption::setValue(const std::string &value) { - switch (optType) { - case Checkbox: - if (!iEqual(value, "true") && !iEqual(value, "false")) { - return false; - } - break; - case ComboBox: - if (!comboValues.count(value)) { - return false; - } - break; - case Spin: { - int64_t intval = toNumber(value, minValue - 1); - if (intval < minValue || intval > maxValue) { - return false; - } - break; - } - case String: - break; - default: - return false; - } - optValue = value; - return true; - } - -//----------------------------------------------------------------------------- - bool EngineOption::setValue(const int64_t value) { - return setValue(std::to_string(value)); - } - -//----------------------------------------------------------------------------- - void EngineOption::setDefaultValue(const int64_t value) { - setDefaultValue(std::to_string(value)); - } - -//----------------------------------------------------------------------------- - void EngineOption::setComboValues(const std::set &values) { - comboValues.clear(); - for (auto value : values) { - comboValues.insert(std::to_string(value)); - } - } - -} // namespace senjo diff --git a/senjo/EngineOption.h b/senjo/EngineOption.h deleted file mode 100644 index 5a9451ca..00000000 --- a/senjo/EngineOption.h +++ /dev/null @@ -1,223 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2015-2019 Shawn Chidester -// -// 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. -//----------------------------------------------------------------------------- - -#ifndef SENJO_ENGINE_OPTION_H -#define SENJO_ENGINE_OPTION_H - -#include "Platform.h" - -namespace senjo { - -//----------------------------------------------------------------------------- -//! \brief Simple representaion of a single chess engine option -//! Provides details such as option type, name, default/min/max values. -//----------------------------------------------------------------------------- - class EngineOption { - public: - virtual ~EngineOption() {} - - enum OptionType { - Unknown, ///< Unknown option type - Button, ///< Option does not have a value - Checkbox, ///< Option value must be "true" or "false" - ComboBox, ///< Option value must be one of a predefined set - Spin, ///< Option value must be an integer between min and max - String ///< Option value must be a string - }; - - //-------------------------------------------------------------------------- - //! \brief Convert option type name to OptionType - //! \param[in] name The option type name - //! \return Unknown if \p name is not known - //-------------------------------------------------------------------------- - static OptionType toOptionType(const std::string &name); - - //-------------------------------------------------------------------------- - //! \brief Get a string representation of a given option type - //! \param[in] type The option type - //! \return A string representation of \p type - //-------------------------------------------------------------------------- - static std::string getTypeName(const OptionType type); - - //-------------------------------------------------------------------------- - //! \brief Constructor - //! \param[in] name The option name (default = empty) - //! \param[in] defaultValue The option default value (default = empty) - //! \param[in] type The option type (default = String) - //! \param[in] minValue The min value for Spin options (default = INT64_MIN) - //! \param[in] maxValue The max value for Spin options (default = INT64_MAX) - //! \param[in] combo Set of legal ComboBox values (default = empty) - //-------------------------------------------------------------------------- - EngineOption(const std::string &name = std::string(), - const std::string &defaultValue = std::string(), - const OptionType type = String, - const int64_t minValue = INT64_MIN, - const int64_t maxValue = INT64_MAX, - const std::set &combo = std::set()); - - //-------------------------------------------------------------------------- - //! \brief Get the option type - //! \return The option type - //-------------------------------------------------------------------------- - OptionType getType() const { return optType; } - - //-------------------------------------------------------------------------- - //! \brief Get a string representation of the option type - //! \return A string representation of the option type - //-------------------------------------------------------------------------- - virtual std::string getTypeName() const { return getTypeName(optType); } - - //-------------------------------------------------------------------------- - //! \brief Get the option name - //! \return The option name - //-------------------------------------------------------------------------- - virtual std::string getName() const { return optName; } - - //-------------------------------------------------------------------------- - //! \brief Get the option's current value in string form - //! \return The option's current value in string form - //-------------------------------------------------------------------------- - virtual std::string getValue() const { return optValue; } - - //-------------------------------------------------------------------------- - //! \brief Get the option's current value in integer form - //! \return The option's current value in integer form, 0 if not an integer - //-------------------------------------------------------------------------- - virtual int64_t getIntValue() const; - - //-------------------------------------------------------------------------- - //! \brief Get the option's default value in string form - //! \return The option's default value in string form - //-------------------------------------------------------------------------- - virtual std::string getDefaultValue() const { return defaultValue; } - - //-------------------------------------------------------------------------- - //! \brief Get the option's default value in integer form - //! \return The option's default value in integer form, 0 if not an integer - //-------------------------------------------------------------------------- - virtual int64_t getDefaultIntValue() const; - - //-------------------------------------------------------------------------- - //! \brief Get the option's minimum value (applicable to Spin type only) - //! \return The option's minimum value - //-------------------------------------------------------------------------- - virtual int64_t getMinValue() const { return minValue; } - - //-------------------------------------------------------------------------- - //! \brief Get the option's maximum value (applicable to Spin type only) - //! \return The option's maximum value - //-------------------------------------------------------------------------- - virtual int64_t getMaxValue() const { return maxValue; } - - //-------------------------------------------------------------------------- - //! \brief Get set of legal values for ComboBox options in string form - //! \return set of legal values for ComboBox options in string form - //-------------------------------------------------------------------------- - virtual const std::set &getComboValues() const { - return comboValues; - } - - //-------------------------------------------------------------------------- - //! \brief Get set of legal values for ComboBox options in integer form - //! \return set of legal values for ComboBox options in integer form - //-------------------------------------------------------------------------- - virtual std::set getIntComboValues() const; - - //-------------------------------------------------------------------------- - //! \brief Set the option type - //! \param[in] type The new option type - //-------------------------------------------------------------------------- - virtual void setType(const OptionType type) { optType = type; } - - //-------------------------------------------------------------------------- - //! \brief Set the option name - //! \param[in] name The new option name - //-------------------------------------------------------------------------- - virtual void setName(const std::string &name) { optName = name; } - - //-------------------------------------------------------------------------- - //! \brief Set the option's current value - //! \param[in] value The new option value - //-------------------------------------------------------------------------- - virtual bool setValue(const std::string &value); - - //-------------------------------------------------------------------------- - //! \brief Set the option's current value - //! \param[in] value The new option value - //! \return true if \p value is valid, otherwise false - //-------------------------------------------------------------------------- - virtual bool setValue(const int64_t value); - - //-------------------------------------------------------------------------- - //! \brief Set the option's default value - //! \param[in] value The new default value - //! \return true if \p value is valid, otherwise false - //-------------------------------------------------------------------------- - virtual void setDefaultValue(const std::string &value) { - defaultValue = value; - } - - //-------------------------------------------------------------------------- - //! \brief Set the option's default value - //! \param[in] value The new default value - //-------------------------------------------------------------------------- - virtual void setDefaultValue(const int64_t value); - - //-------------------------------------------------------------------------- - //! \brief Set the option's minimum value (applicable to Spin type only) - //! \param[in] min The new minimum value - //-------------------------------------------------------------------------- - virtual void setMinValue(const int64_t min) { minValue = min; } - - //-------------------------------------------------------------------------- - //! \brief Set the option's maximum value (applicable to Spin type only) - //! \param[in] max The new maximum value - //-------------------------------------------------------------------------- - virtual void setMaxValue(const int64_t max) { maxValue = max; } - - //-------------------------------------------------------------------------- - //! \brief Set legal values for ComboBox options - //! \param[in] values The set of legal values - //-------------------------------------------------------------------------- - virtual void setComboValues(const std::set &values) { - comboValues = values; - } - - //-------------------------------------------------------------------------- - //! \brief Set legal values for ComboBox options - //! \param[in] values The set of legal values - //-------------------------------------------------------------------------- - virtual void setComboValues(const std::set &values); - - private: - OptionType optType; - std::string optName; - std::string optValue; - std::string defaultValue; - int64_t minValue; - int64_t maxValue; - std::set comboValues; - }; - -} // namespace - -#endif // SENJO_ENGINE_OPTION_H diff --git a/senjo/GoParams.h b/senjo/GoParams.h deleted file mode 100644 index 2da7c56a..00000000 --- a/senjo/GoParams.h +++ /dev/null @@ -1,45 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2019 Shawn Chidester -// -// 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. -//----------------------------------------------------------------------------- - -#ifndef SENJO_GO_PARAMS_H -#define SENJO_GO_PARAMS_H - -#include "Platform.h" - -namespace senjo { - - struct GoParams { - bool infinite = false; // SearchManager until the "stop" command - bool ponder = false; // Start searching in pondering mode - int16_t depth = 0; // Maximum number of half-moves (plies) to search - int movestogo = 0; // Number of moves remaining until next time control - uint64_t binc = 0; // BLACK increment per move in milliseconds - uint64_t btime = 0; // Milliseconds remaining on black's clock - uint64_t movetime = 0; // Maximum milliseconds to spend on this move - uint64_t nodes = 0; // Maximum number of nodes to search - uint64_t winc = 0; // WHITE increment per move in milliseconds - uint64_t wtime = 0; // Milliseconds remaining on white's clock - }; - -} // namespace senjo - -#endif // SENJO_GO_PARAMS_H diff --git a/senjo/Output.cpp b/senjo/Output.cpp deleted file mode 100644 index cb8da25a..00000000 --- a/senjo/Output.cpp +++ /dev/null @@ -1,58 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2015-2019 Shawn Chidester -// -// 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. -//----------------------------------------------------------------------------- - -#include "Output.h" - -namespace senjo { - -//----------------------------------------------------------------------------- -// static variables -//----------------------------------------------------------------------------- - std::mutex Output::_mutex; - TimePoint Output::_lastOutput = now(); - -//----------------------------------------------------------------------------- - TimePoint Output::lastOutput() { - return _lastOutput; - } - -//----------------------------------------------------------------------------- - Output::Output(const OutputPrefix prefix) { - _mutex.lock(); - switch (prefix) { - case InfoPrefix: - std::cout << "info string "; - break; - case NoPrefix: - break; - } - } - -//----------------------------------------------------------------------------- - Output::~Output() { - std::cout << '\n'; - std::cout.flush(); - _lastOutput = now(); - _mutex.unlock(); - } - -} // namespace senjo diff --git a/senjo/Output.h b/senjo/Output.h deleted file mode 100644 index 41a80c6e..00000000 --- a/senjo/Output.h +++ /dev/null @@ -1,134 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2015-2019 Shawn Chidester -// -// 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. -//----------------------------------------------------------------------------- - -#ifndef SENJO_OUTPUT_H -#define SENJO_OUTPUT_H - -#include "Platform.h" -#include -#include - -namespace senjo { - -//----------------------------------------------------------------------------- -//! \brief Thread safe stdout stream -//! Instantiating this class will obtain a lock on a mtuex guarding stdout. -//! The stdout mutex is released when the instantiated object is destroyed. -//! -//! \e Important: '\n' is automatically appended when the object is destroyed. -//! \e Important: The UCI protocol requires that lines end with a single -//! new-line character: '\n'. So you should *not* use std::endl, as this will -//! output '\r\n' on windows systems and '\r' on some other systems. -//! -//! Example: -//! -//! Output() << "Hello, world!"; -//! -//! Notice it is not necessary to add '\n' on the end. -//! -//! Example of multiple line outout: -//! -//! Output() << "Line 1"; -//! Output() << "Line 2"; -//! -//! Notice it is not necessary to add '\n' on the end of either line. -//! Notice it is possible that other threads could output something between -//! "Line 1" and "Line 2". -//! -//! Example of multiple line outout without allowing another thread to output -//! between each line: -//! -//! Output() << "Line 1\n" -//! << "info string Line 2"; -//! or -//! Output() << "Line 1\ninfo string Line 2"; -//! or -//! Output() << "Line " << 1 << '\n' << "info string " << "Line " << 2; -//! etc.. -//! -//! Notice each case uses a single Output() instance. -//! Notice it is not necessary to add '\n' after the last line. -//! Notice it is necessary to explicitly prefix all but the first line with -//! "info string ". If you know what you're doing concerning the UCI protocol -//! you can omit "info string " where appropriate. -//! -//! To maintain exclusive access to stdout so that processing can be done -//! between lines of output without allowing another thread to output between -//! lines: -//! -//! { -//! Output out; -//! out << "line 1\n"; -//! ... do some processing ... -//! out << "info string line 2"; -//! } -//! -//! Notice the code is enclosed in { } to clearly define the scope of 'out'. -//! Notice it is not necessary to add '\n' after the last line. -//! Notice it is necessary to explicitly prefix all but the first line with -//! "info string ". If you know what you're doing concerning the UCI protocol -//! you can omit "info string " where appropriate. -//----------------------------------------------------------------------------- - class Output { - public: - enum OutputPrefix { - NoPrefix, ///< Don't prefix output with "info string " - InfoPrefix ///< Prefix output with "info string " - }; - - //-------------------------------------------------------------------------- - //! \brief Constructor - //! If \p param is InfoPrefix then output is prefixed with "info string ". - //! \param[in] prefix NoPrefix or InfoPrefix (InfoPrefix is the default) - //-------------------------------------------------------------------------- - Output(const OutputPrefix prefix = InfoPrefix); - - //-------------------------------------------------------------------------- - //! \brief Destructor - //-------------------------------------------------------------------------- - virtual ~Output(); - - //-------------------------------------------------------------------------- - //! \brief Get timestamp of the last time an Output class was destroyed - //! \return Timestamp of last Output class destruction, 0 if none - //-------------------------------------------------------------------------- - static TimePoint lastOutput(); - - //-------------------------------------------------------------------------- - //! \brief Insertion operator - //! All data types supported by std::cout are supported here. - //! \return Reference to self. - //-------------------------------------------------------------------------- - template - Output &operator<<(const T &x) { - std::cout << x; - return *this; - } - - private: - static std::mutex _mutex; - static TimePoint _lastOutput; - }; - -} // namespace senjo - -#endif // SENJO_OUTPUT_H diff --git a/senjo/Parameters.cpp b/senjo/Parameters.cpp deleted file mode 100644 index 586b091c..00000000 --- a/senjo/Parameters.cpp +++ /dev/null @@ -1,112 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2019 Shawn Chidester -// -// 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. -//----------------------------------------------------------------------------- - -#include "Parameters.h" - -namespace senjo { - -//----------------------------------------------------------------------------- - Parameters::Parameters(const std::string &commandLine) { - parse(commandLine); - } - -//----------------------------------------------------------------------------- - void Parameters::parse(const std::string &str) { - std::stringstream ss(str); - std::string param; - clear(); - while (ss >> param) { - push_back(param); - } - } - -//----------------------------------------------------------------------------- - std::string Parameters::toString() const { - std::stringstream ss; - bool first = true; - for (std::string param : *this) { - if (first) { - first = false; - } else { - ss << ' '; - } - ss << param; - } - return ss.str(); - } - -//----------------------------------------------------------------------------- - bool Parameters::firstParamIs(const std::string ¶mName) const { - return size() && iEqual(paramName, front()); - } - -//----------------------------------------------------------------------------- - bool Parameters::popParam(const std::string ¶mName) { - if (firstParamIs(paramName)) { - pop_front(); - return true; - } - - return false; - } - -//----------------------------------------------------------------------------- - bool Parameters::popParam(const std::string ¶mName, bool &exists) { - if (popParam(paramName)) { - exists = true; - return true; - } - return false; - } - -//----------------------------------------------------------------------------- - std::string Parameters::popString() { - if (empty()) { - return ""; - } - - std::string str = front(); - pop_front(); - return str; - } - -//----------------------------------------------------------------------------- - bool Parameters::popString(const std::string ¶mName, std::string &value, - const std::string &next) { - if (size() < 2 || !iEqual(paramName, front())) { - return false; - } - - pop_front(); - while (size() && (next.empty() || !iEqual(next, front()))) { - if (value.size()) { - value += " "; - } - value += front(); - pop_front(); - } - - return value.size() > 0; - } - -} // namespace senjo - diff --git a/senjo/Parameters.h b/senjo/Parameters.h deleted file mode 100644 index 43bf1bf4..00000000 --- a/senjo/Parameters.h +++ /dev/null @@ -1,144 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2019 Shawn Chidester -// -// 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. -//----------------------------------------------------------------------------- - -#ifndef SENJO_PARAMETERS_H -#define SENJO_PARAMETERS_H - -#include "Platform.h" - -namespace senjo { - - class Parameters : public std::list { - public: - Parameters() = default; - - Parameters(const std::string &commandLine); - - //--------------------------------------------------------------------------- - //! \brief Initialize this instance from the given string - //--------------------------------------------------------------------------- - void parse(const std::string &str); - - //--------------------------------------------------------------------------- - //! \brief Join the parameters together into a single space delimited string - //--------------------------------------------------------------------------- - std::string toString() const; - - //--------------------------------------------------------------------------- - //! \brief Test whether the specified paramName matches the first param - //! \param[in] paramName The parameter name to check for - //! \return true if the specified paramName matches the first param - //--------------------------------------------------------------------------- - bool firstParamIs(const std::string ¶mName) const; - - //--------------------------------------------------------------------------- - //! \brief Pop the specified paramName - //! \param[in] paramName The parameter name to pop - //! \return true if the specified paramName was popped - //--------------------------------------------------------------------------- - bool popParam(const std::string ¶mName); - - //--------------------------------------------------------------------------- - //! \brief Pop the specified paramName - //! \param[in] paramName The parameter name to pop - //! \param[out] exists Set to true if the specified paramName was popped - //! \return true if the specified paramName was popped - //--------------------------------------------------------------------------- - bool popParam(const std::string ¶mName, bool &exists); - - //----------------------------------------------------------------------------- - //! \brief Pop the first string - //! \return an empty string if the parameters list is empty - //----------------------------------------------------------------------------- - std::string popString(); - - //----------------------------------------------------------------------------- - //! \brief Pop the specified paramName and the following string value - //! \param[in] paramName The name of the string parameter to pop - //! \param[out] value Populated with the string value of \p paramName - //! \param[in] next The next expected parameter name - //! \return true If a non-empty value was assigned to \p value - //----------------------------------------------------------------------------- - bool popString(const std::string ¶mName, std::string &value, - const std::string &next = ""); - - //----------------------------------------------------------------------------- - //! \brief Pop the first param as a number - //! \return \defaultValue if the parameters list is empty - //! or the first param could not be converted to the number type - //----------------------------------------------------------------------------- - template - T popNumber(const T &defaultValue = 0) { - if (empty()) { - return defaultValue; - } - - std::stringstream ss(front()); - T value; - if (ss >> value) { - pop_front(); - return value; - } - - return defaultValue; - } - - //----------------------------------------------------------------------------- - //! \brief Pop the specified paramName and the following number value - //! \param[in] paramName The name of the numeric parameter to pop - //! \param[out] value Populated with the numeric value of \p paramName - //! \param[out] invalid Set to true if paramName has a non-numeric value - //! \return true if a number value was assigned to \p number - //----------------------------------------------------------------------------- - template - bool popNumber(const std::string ¶mName, T &value, bool &invalid) { - if (size() < 2 || !iEqual(paramName, front())) { - return false; - } - - pop_front(); - std::stringstream ss(front()); - if (ss >> value) { - pop_front(); - return true; - } - - invalid = true; - return false; - } - - //----------------------------------------------------------------------------- - //! \brief Pop the specified paramName and the following number value - //! \param[in] paramName The name of the numeric parameter to pop - //! \param[out] value Populated with the numeric value of \p paramName - //! \return true if a number value was assigned to \p number - //----------------------------------------------------------------------------- - template - bool popNumber(const std::string ¶mName, T &value) { - bool invalid; - return popNumber(paramName, value, invalid); - } - }; - -} // namespace senjo - -#endif // SENJO_PARAMETERS_H diff --git a/senjo/Platform.h b/senjo/Platform.h deleted file mode 100644 index 8376eec3..00000000 --- a/senjo/Platform.h +++ /dev/null @@ -1,140 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2015-2019 Shawn Chidester -// -// 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. -//----------------------------------------------------------------------------- - -#ifndef SENJO_PLATFORM_H -#define SENJO_PLATFORM_H - -#ifdef WIN32 - -#include - -#undef max -#undef min -#endif - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace senjo { - -//----------------------------------------------------------------------------- - typedef std::chrono::system_clock::time_point TimePoint; - -//----------------------------------------------------------------------------- - inline TimePoint now() { - return std::chrono::system_clock::now(); - } - -//----------------------------------------------------------------------------- - inline TimePoint maxTime() { - return TimePoint::max(); - } - -//----------------------------------------------------------------------------- - inline TimePoint addMsecs(const TimePoint &begin, const uint64_t msecs) { - return (begin + std::chrono::milliseconds(msecs)); - } - -//----------------------------------------------------------------------------- - inline uint64_t getMsecs(const TimePoint &begin, const TimePoint &end = now()) { - auto duration = (end - begin); - auto msecs = std::chrono::duration_cast(duration); - return static_cast(msecs.count()); - } - -//----------------------------------------------------------------------------- - template - inline double average(const T total, const T count) { - return (count != 0.0) ? (static_cast(total) / static_cast(count)) : 0; - } - -//----------------------------------------------------------------------------- - template - inline double rate(const T count, const T msecs) { - return (msecs != 0.0) ? ((static_cast(count) / static_cast(msecs)) * 1000) : 0; - } - -//----------------------------------------------------------------------------- - template - inline double percent(const T top, const T bottom) { - return bottom ? (100 * (static_cast(top) / static_cast(bottom))) : 0; - } - -//----------------------------------------------------------------------------- - template - inline T toNumber(const std::string &str, const T defaultValue = 0) { - T number; - return (std::stringstream(str) >> number) ? number : defaultValue; - } - -//----------------------------------------------------------------------------- - inline std::string trimLeft(const std::string &str, - const std::string &chars = " ") { - size_t i = str.find_first_not_of(chars); - return (i == std::string::npos) ? str : str.substr(i); - } - -//----------------------------------------------------------------------------- - inline std::string trimRight(const std::string &str, - const std::string &chars = " ") { - size_t i = str.size(); - while ((i > 0) && (chars.find_first_of(str[i - 1]) != std::string::npos)) { - --i; - } - return str.substr(0, i); - } - -//----------------------------------------------------------------------------- - inline std::string trim(const std::string &str, - const std::string &chars = " ") { - return trimRight(trimLeft(str, chars), chars); - } - -//----------------------------------------------------------------------------- - inline bool iEqual(const std::string &a, const std::string &b) { - return ((a.size() == b.size()) && - std::equal(a.begin(), a.end(), b.begin(), [](char c1, char c2) { - return (c1 == c2) || (std::toupper(c1) == std::toupper(c2)); - })); - } - -//----------------------------------------------------------------------------- - inline bool isMove(const std::string &str) { - return (str.size() >= 4) && - (str[0] >= 'a') && (str[0] <= 'h') && - (str[1] >= '1') && (str[1] <= '8') && - (str[2] >= 'a') && (str[2] <= 'h') && - (str[3] >= '1') && (str[3] <= '8') && - ((str.size() == 4) || - (str[4] == 'n') || (str[4] == 'b') || - (str[4] == 'r') || (str[4] == 'q')); - } - -} // namespace senjo - -#endif // SENJO_PLATFORM_H diff --git a/senjo/README.md b/senjo/README.md deleted file mode 100644 index 55f860ea..00000000 --- a/senjo/README.md +++ /dev/null @@ -1,112 +0,0 @@ -Senjo -===== - -Universal Chess Interface (UCI) adapter by Shawn Chidester . - -Just write your chess engine and let Senjo's UCIAdapter deal with the UCI protocol. -See [Clubfoot](https://github.com/zd3nik/Clubfoot) for an example chess engine that uses Senjo. - -See the [senjo-light](https://github.com/zd3nik/SenjoUCIAdapter/tree/senjo-light) branch for a version that does not -include a built-in `test` command. - -Description ------------ - -Senjo is a UCI adapter for C++ chess engines. It handles the interaction between your chess engine and any UCI compliant -user interface. All you have to do is implement a ChessEngine class that does the "thinking" parts, Senjo will deal with -the rest. - -The Senjo UCI adapter comes with a few extra commands that are not part of the UCI specification. Here are some -examples: - - * help - * fen - * print - * perft - * test - -In particular the *perft* and *test* commands are very handy for testing and tuning. A few EPD files are included in -this repository for use with these commands. But of course you can use any EPD file(s) you prefer. - -How-To ------- - -To create a chess engine named "Trout" using the Senjo UCI adapter do the following: - - 1. Extend the "ChessEngine" class. - - // TroutEngine.h - #include "senjo/ChessEngine.h" - - class TroutEngine : public senjo::ChessEngine { - // implement required ChessEngine methods - // see ChessEngine.h for documentation - }; - - 2. Wrap TroutEngine in a Senjo UCIAdapter and feed it one line of input from stdin at a time. - - // TroutMain.cpp - #include "TroutEngine.h" - #include "senjo/UCIAdapter.h" - #include "senjo/Output.h" - - int main(int /*argc*/, char** /*argv*/) { - try { - TroutEngine engine; - senjo::UCIAdapter adapter(engine); - - std::string line; - line.reserve(16384); - - while (std::getline(std::coin, line)) { - if (!adapter.doCommand(line)) { - break; - } - } - - return 0; - } - catch (const std::exception& e) { - senjo::Output() << "ERROR: " << e.what(); - return 1; - } - } - - -Notes ------ - -This example uses `std::getline` to obtain one line of input at a time from stdin. This is only an example. You may get -input any way you prefer. All that is required is that you assign each line of input to a std::string, pass it to the -senjo::UCIAdapter's doCommand() method, and exit the input loop if doCommand() returns false. - -The `senjo::Output` class (from Output.h) is very useful for debugging. Use it anywhere; it's thread safe and it -prefixes your output with "string info " so it won't confuse UCI compliant user interfaces. See `Output.h` for more -details. - -The senjo source directory contains a `CMakelists.txt` file, which is a cmake project file. If you're using cmake simply -add the senjo directory to your project with `add_subirectory(senjo)`. If you're not using cmake simply remove he -CMakeLists.txt file and include the senjo source files in your project in whatever way is most convenient for you. - -License -------- - -Copyright (c) 2015-2019 Shawn Chidester - -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. diff --git a/senjo/SearchStats.h b/senjo/SearchStats.h deleted file mode 100644 index 2ff3b874..00000000 --- a/senjo/SearchStats.h +++ /dev/null @@ -1,56 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2019 Shawn Chidester -// -// 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. -//----------------------------------------------------------------------------- - -#ifndef SENJO_SEARCH_STATS_H -#define SENJO_SEARCH_STATS_H - -#include "Platform.h" - -namespace senjo { - -//----------------------------------------------------------------------------- - struct SearchStats { - int16_t depth = 0; // The current search depth - int seldepth = 0; // The maximum selective depth reached on "move" - uint64_t nodes = 0; // The number of nodes searched so far - uint64_t qnodes = 0; // The number of quiescence nodes searched so far - uint64_t msecs = 0; // The number of milliseconds spent searching so far - int score = 0; - std::string pv = ""; - }; - -//----------------------------------------------------------------------------- - inline std::ostream &operator<<(std::ostream &os, const SearchStats &stats) { - os << "info depth " << stats.depth - << " seldepth " << stats.seldepth - << " score cp " << stats.score - << " nodes " << stats.nodes + stats.qnodes - << " time " << stats.msecs - << " nps " << static_cast((stats.nodes + stats.qnodes) / std::max(stats.msecs / 1000.0, 1.0)) - << " pv " << stats.pv; - - return os; - } - -} // namespace senjo - -#endif // SENJO_SEARCH_STATS_H diff --git a/senjo/Thread.cpp b/senjo/Thread.cpp deleted file mode 100644 index a85a658e..00000000 --- a/senjo/Thread.cpp +++ /dev/null @@ -1,75 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2015-2019 Shawn Chidester -// -// 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. -//----------------------------------------------------------------------------- - -#include "Thread.h" -#include "Output.h" - -namespace senjo { - -//----------------------------------------------------------------------------- - Thread::Thread(int id) - : id(id) {} - -//----------------------------------------------------------------------------- - Thread::~Thread() { - waitForFinish(); - } - -//----------------------------------------------------------------------------- - bool Thread::run() { - std::lock_guard lock(mutex); - if (thread && thread->joinable()) { - return false; - } - - thread.reset(new std::thread(staticRun, this)); - return true; - } - -//----------------------------------------------------------------------------- - bool Thread::isRunning() { - std::lock_guard lock(mutex); - return thread && thread->joinable(); - } - -//----------------------------------------------------------------------------- - void Thread::waitForFinish() { - std::lock_guard lock(mutex); - if (thread && thread->joinable()) { - thread->join(); - } - } - -//----------------------------------------------------------------------------- - void Thread::staticRun(Thread* thread) { - if (thread) { - try { - thread->doWork(); - } catch (const std::exception &ex) { - Output() << "ERROR: Thread(" << thread->getInfoPair() << ") " << ex.what(); - } catch (...) { - Output() << "ERROR: Thread(" << thread->getInfoPair() << ") unhandled exception"; - } - } - } - -} // namespace senjo diff --git a/senjo/Thread.h b/senjo/Thread.h deleted file mode 100644 index 83ab99ad..00000000 --- a/senjo/Thread.h +++ /dev/null @@ -1,102 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2015-2019 Shawn Chidester -// -// 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. -//----------------------------------------------------------------------------- - -#ifndef SENJO_THREADING_H -#define SENJO_THREADING_H - -#include "Platform.h" -#include -#include - -namespace senjo { - -//----------------------------------------------------------------------------- -//! \brief Base class for a single background task. -//----------------------------------------------------------------------------- - class Thread { - protected: - std::shared_ptr thread; - std::mutex mutex; - - //--------------------------------------------------------------------------- - //! \brief Constructor - //--------------------------------------------------------------------------- - explicit Thread(int id = -1); - - //--------------------------------------------------------------------------- - //! This method is called once when the thread is run. - //! When this method exits the thread is finished. - //--------------------------------------------------------------------------- - virtual void doWork() = 0; - - public: - //--------------------------------------------------------------------------- - // Delete copy operations - //--------------------------------------------------------------------------- - Thread(const Thread &) = delete; - - Thread &operator=(const Thread &) = delete; - - //--------------------------------------------------------------------------- - //! \brief Destructor - //--------------------------------------------------------------------------- - virtual ~Thread(); - - //--------------------------------------------------------------------------- - //! \brief Get the id of this thread. - //! \return the id of this thread - //--------------------------------------------------------------------------- - int getInfoPair() const { return id; } - - //--------------------------------------------------------------------------- - //! \brief Run this thread. - //! \return false if the thread is already running - //--------------------------------------------------------------------------- - virtual bool run(); - - //--------------------------------------------------------------------------- - //! \brief Tell this thread to exit the doWork() method as soon as possible. - //! Should do nothing if the thread is already stopped. - //--------------------------------------------------------------------------- - virtual void stop() = 0; - - //--------------------------------------------------------------------------- - //! \brief Is this thread running? - //! \return true If this thread is running - //--------------------------------------------------------------------------- - bool isRunning(); - - //--------------------------------------------------------------------------- - //! \brief Wait for this thread to finish running. - //! If this thread isn't running this method returns immediately. - //--------------------------------------------------------------------------- - void waitForFinish(); - - private: - static void staticRun(Thread*); - - int id; - }; - -} // namespace senjo - -#endif // SENJO_THREADING_H diff --git a/senjo/UCIAdapter.cpp b/senjo/UCIAdapter.cpp deleted file mode 100644 index ef07f757..00000000 --- a/senjo/UCIAdapter.cpp +++ /dev/null @@ -1,667 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2015-2019 Shawn Chidester -// -// 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. -//----------------------------------------------------------------------------- - -#include "UCIAdapter.h" -#include "Output.h" - -namespace senjo { - -//----------------------------------------------------------------------------- - namespace token { - static const std::string Debug("debug"); - static const std::string Exit("exit"); - static const std::string Fen("fen"); - static const std::string Go("go"); - static const std::string Help("help"); - static const std::string IsReady("isready"); - static const std::string Moves("moves"); - static const std::string Name("name"); - static const std::string New("new"); - static const std::string Opts("opts"); - static const std::string Perft("perft"); - static const std::string PonderHit("ponderhit"); - static const std::string Position("position"); - static const std::string Print("print"); - static const std::string Quit("quit"); - static const std::string Register("register"); - static const std::string SetOption("setoption"); - static const std::string StartPos("startpos"); - static const std::string Stop("stop"); - static const std::string Test("test"); - static const std::string Uci("uci"); - static const std::string UciNewGame("ucinewgame"); - static const std::string Value("value"); - } - -//----------------------------------------------------------------------------- - UCIAdapter::UCIAdapter(ChessEngine &chessEngine) - : engine(chessEngine) {} - -//----------------------------------------------------------------------------- - UCIAdapter::~UCIAdapter() { - if (lastCommand) { - lastCommand->waitForFinish(); - } - } - -//----------------------------------------------------------------------------- - bool UCIAdapter::doCommand(const std::string &line) { - Parameters params(line); - if (params.empty()) { - return true; // ignore empty lines - } - - if (engine.isDebugOn()) { - Output() << "received command: " << line; - } - - std::string command = params.popString(); - if (iEqual(token::Go, command)) { - doStopCommand(); - execute(std::shared_ptr(new GoCommandHandle(engine)), params); - } else if (iEqual(token::Position, command)) { - doStopCommand(); - doPositionCommand(line, params); - } else if (iEqual(token::Stop, command)) { - doStopCommand(params); - } else if (iEqual(token::SetOption, command)) { - doSetOptionCommand(params); - } else if (iEqual(token::IsReady, command)) { - doIsReadyCommand(params); - } else if (iEqual(token::Uci, command)) { - doUCICommand(params); - } else if (iEqual(token::UciNewGame, command)) { - doStopCommand(); - doUCINewGameCommand(params); - } else if (iEqual(token::New, command)) { - doStopCommand(); - doNewCommand(params); - } else if (iEqual(token::Debug, command)) { - doDebugCommand(params); - } else if (iEqual(token::Register, command)) { - doStopCommand(params); - execute(std::shared_ptr(new RegisterCommandHandle(engine)), params); - } else if (iEqual(token::PonderHit, command)) { - doPonderHitCommand(params); - } else if (iEqual(token::Fen, command)) { - doFENCommand(params); - } else if (iEqual(token::Print, command)) { - doPrintCommand(params); - } else if (iEqual(token::Perft, command)) { - doStopCommand(); - execute(std::shared_ptr(new PerftCommandHandle(engine)), params); - } else if (iEqual(token::Opts, command)) { - doOptsCommand(params); - } else if (iEqual(token::Help, command)) { - doHelpCommand(params); - } else if (iEqual(token::Exit, command) || - iEqual(token::Quit, command)) { - if (doQuitCommand(params)) { - return false; - } - } else if (isMove(command)) { - doStopCommand(); - params.push_front(command); - doMoveCommand(params); - } else { - Output() << "Unknown command: '" << command << "'"; - Output() << "Enter 'help' for a list of commands"; - } - return true; - } - -//----------------------------------------------------------------------------- -//! \brief Output list of available commands (not a UCI command) -//----------------------------------------------------------------------------- - void UCIAdapter::doHelpCommand(Parameters & /*params*/) { - Output() << engine.getEngineName() << ' ' << engine.getEngineVersion() - << " by " << engine.getAuthorName(); - Output() << "UCI commands:"; - Output() << " " << token::Debug; - Output() << " " << token::Go; - Output() << " " << token::IsReady; - Output() << " " << token::Position; - Output() << " " << token::Quit; - Output() << " " << token::SetOption; - Output() << " " << token::Stop; - Output() << " " << token::Uci; - Output() << " " << token::UciNewGame; - Output() << "Additional commands:"; - Output() << " " << token::Exit; - Output() << " " << token::Fen; - Output() << " " << token::Help; - Output() << " " << token::New; - Output() << " " << token::Perft; - Output() << " " << token::Print; - Output() << "Also try ' help' for help on a specific command"; - Output() << "Or enter move(s) in coordinate notation, e.g. d2d4 g8f6"; - } - -//----------------------------------------------------------------------------- -//! \brief Execute the given background command thread -//----------------------------------------------------------------------------- - void UCIAdapter::execute(std::shared_ptr command, - Parameters ¶ms) { - if (!command) { - return; - } - - if (params.firstParamIs(token::Help)) { - Output() << "usage: " << command->usage(); - Output() << command->description(); - return; - } - - if (lastCommand) { - lastCommand->stop(); - lastCommand->waitForFinish(); - } - - if (command->parseAndExecute(params)) { - lastCommand.swap(command); - } - } - -//----------------------------------------------------------------------------- -//! \brief Do the "fen" command (not a UCI command) -//! Print the FEN string for the current board position -//----------------------------------------------------------------------------- - void UCIAdapter::doFENCommand(Parameters ¶ms) { - if (params.firstParamIs(token::Help)) { - Output() << "usage: " << token::Fen; - Output() << "Output FEN string of the current position."; - return; - } - - if (lastCommand) { - lastCommand->stop(); - lastCommand->waitForFinish(); - } - - if (!engine.isInitialized()) { - engine.initialize(); - } - - Output() << engine.getFEN(); - } - -//----------------------------------------------------------------------------- -//! \brief Do the "print" command (not a UCI command) -//! Output an ascii representation of the current board position -//----------------------------------------------------------------------------- - void UCIAdapter::doPrintCommand(Parameters ¶ms) { - if (params.firstParamIs(token::Help)) { - Output() << "usage: " << token::Print; - Output() << "Output text representation of the current position."; - return; - } - - if (!engine.isInitialized()) { - engine.initialize(); - } - - engine.printBoard(); - } - -//----------------------------------------------------------------------------- -//! \brief Do the "new" command (not a UCI command) -//! Clear search data, set position, and apply moves (if any given). -//----------------------------------------------------------------------------- - void UCIAdapter::doNewCommand(Parameters ¶ms) { - if (params.firstParamIs(token::Help)) { - Output() << "usage: " << token::New << " [" << token::StartPos << "|" - << token::Fen << " ] [" << token::Moves - << "] "; - Output() << "Clear search data, set position, and apply ."; - Output() << "If no position is specified " << token::StartPos - << " is assumed."; - return; - } - - doUCINewGameCommand(); - - if (params.empty() || - params.popParam(token::StartPos) || - params.popParam(token::Moves)) { - if (!engine.setPosition(ChessEngine::STARTPOS)) { - return; - } - } else { - // consume "fen" token if present - params.popParam(token::Fen); - std::string remain; - if (!engine.setPosition(params.toString(), &remain)) { - return; - } - params.parse(remain); - } - - // consume "moves" token if present - params.popParam(token::Moves); - - // apply moves (if any) - while (params.size() && isMove(params.front())) { - std::string move = params.popString(); - if (!engine.makeMove(move)) { - Output() << "Invalid move: " << move; - break; - } - } - - if (engine.isDebugOn()) { - engine.printBoard(); - } - } - -//----------------------------------------------------------------------------- -//! \brief Do the "opts" command (not a UCI command) -//! Output current engine option values -//----------------------------------------------------------------------------- - void UCIAdapter::doOptsCommand(Parameters & /*params*/) { - for (auto opt : engine.getOptions()) { - switch (opt.getType()) { - case EngineOption::Checkbox: - case EngineOption::Spin: - case EngineOption::String: - Output() << opt.getTypeName() << ':' << opt.getName() << ' ' - << opt.getValue(); - break; - case EngineOption::ComboBox: { - const std::set &values = opt.getComboValues(); - Output out; - out << opt.getTypeName() << ':' << opt.getName(); - for (auto value : values) { - out << ' ' << value; - } - break; - } - default: - break; - } - } - } - -//----------------------------------------------------------------------------- -//! \brief Execute the given move(s) on the current position -//----------------------------------------------------------------------------- - void UCIAdapter::doMoveCommand(Parameters ¶ms) { - if (!engine.isInitialized()) { - engine.initialize(); - } - - if (lastCommand) { - lastCommand->stop(); - lastCommand->waitForFinish(); - } - - lastPosition.clear(); - while (params.size()) { - std::string move = params.popString(); - if (!isMove(move) || !engine.makeMove(move)) { - Output() << "Invalid move: " << move; - return; - } - - if (engine.isDebugOn()) { - engine.printBoard(); - } - } - } - -//----------------------------------------------------------------------------- -//! \brief Do the UCI "quit" command -//! UCI specification: -//! Quit the program as soon as possible. -//! \return true if quit requested, otherwise false -//----------------------------------------------------------------------------- - bool UCIAdapter::doQuitCommand(Parameters ¶ms) { - if (params.firstParamIs(token::Help)) { - Output() << "usage: " << token::Quit; - Output() << "Stop engine and terminate program."; - return false; - } - - engine.stopSearching(); - - if (lastCommand) { - lastCommand->stop(); - lastCommand->waitForFinish(); - } - - return true; - } - -//----------------------------------------------------------------------------- -//! \brief Do the UCI "debug" command -//! UCI specification: -//! Switch the debug mode of the engine on and off. In debug mode the engine -//! should send additional infos to the GUI, e.g. with the "info string" -//! command, to help debugging, e.g. the commands that the engine has -//! received etc. This mode should be switched off by default and this -//! command can be sent any time, also when the engine is thinking. -//----------------------------------------------------------------------------- - void UCIAdapter::doDebugCommand(Parameters ¶ms) { - if (params.firstParamIs(token::Help)) { - Output() << "usage: " << token::Debug; - Output() << "Toggle debug mode."; - return; - } - - engine.setDebug(!engine.isDebugOn()); - Output() << "debug " << (engine.isDebugOn() ? "on" : "off"); - } - -//----------------------------------------------------------------------------- -//! \brief Do the UCI "isready" command -//! UCI specification: -//! This is used to synchronize the engine with the GUI. When the GUI has -//! sent a command or multiple commands that can take some time to complete, -//! this command can be used to wait for the engine to be ready again or -//! to ping the engine to find out if it is still alive. E.g. this should be -//! sent after setting the path to the tablebases as this can take some time. -//! This command is also required once before the engine is asked to do any -//! search to wait for the engine to finish initializing. This command must -//! always be answered with "readyok" and can be sent also when the engine -//! is calculating in which case the engine should also immediately answer -//! with "readyok" without stopping the search. -//----------------------------------------------------------------------------- - void UCIAdapter::doIsReadyCommand(Parameters ¶ms) { - if (params.firstParamIs(token::Help)) { - Output() << "usage: " << token::IsReady; - Output() << "Output readyok when engine is ready to receive input."; - return; - } - - if (!engine.isInitialized()) { - engine.initialize(); - } - - if (lastCommand) { - // don't set stop flag - lastCommand->waitForFinish(); - } - - Output(Output::NoPrefix) << "readyok"; - } - -//----------------------------------------------------------------------------- -//! \brief Do the UCI "stop" command -//! UCI specification: -//! Stop calculating as soon as possible, don't forget the "bestmove" and -//! possibly the "ponder" token when finishing the search. -//----------------------------------------------------------------------------- - void UCIAdapter::doStopCommand(Parameters params) { - if (params.firstParamIs(token::Help)) { - Output() << "usage: " << token::Stop; - Output() << "Stop engine if it is calculating."; - return; - } - - engine.stopSearching(); - } - -//----------------------------------------------------------------------------- -//! \brief Do the UCI "uci" command -//! UCI specification: -//! Tell engine to use the uci (universal chess interface), this will be sent -//! once as a first command after program boot to tell the engine to switch -//! to uci mode. After receiving the uci command the engine must identify -//! itself with the "id" command and send the "option" commands to tell the -//! GUI which engine settings the engine supports if any. After that the -//! engine should send "uciok" to acknowledge the uci mode. If no uciok is -//! sent within a certain time period, the engine task will be killed by the -//! GUI. -//----------------------------------------------------------------------------- - void UCIAdapter::doUCICommand(Parameters ¶ms) { - if (params.firstParamIs(token::Help)) { - Output() << "usage: " << token::Uci; - Output() << "Output engine info and options followed by uciok."; - return; - } - - Output(Output::NoPrefix) << "id name " << engine.getEngineName() - << ' ' << engine.getEngineVersion(); - - const std::string author = engine.getAuthorName(); - if (author.size()) { - Output(Output::NoPrefix) << "id author " << author; - } - - const std::string email = engine.getEmailAddress(); - if (email.size()) { - Output(Output::NoPrefix) << "id email " << email; - } - - const std::string country = engine.getCountryName(); - if (country.size()) { - Output(Output::NoPrefix) << "id country " << country; - } - - for (auto opt : engine.getOptions()) { - Output out(Output::NoPrefix); - out << "option name " << opt.getName() << " type " << opt.getTypeName(); - if (opt.getDefaultValue().size()) { - out << " default " << opt.getDefaultValue(); - } - if (opt.getMinValue() > INT64_MIN) { - out << " min " << opt.getMinValue(); - } - if (opt.getMaxValue() < INT64_MAX) { - out << " max " << opt.getMaxValue(); - } - for (auto val : opt.getComboValues()) { - out << " var " << val; - } - } - - Output(Output::NoPrefix) << "uciok"; - - if (engine.isCopyProtected()) { - Output(Output::NoPrefix) << "copyprotection checking"; - if (engine.copyIsOK()) { - Output(Output::NoPrefix) << "copyprotection ok"; - } else { - Output(Output::NoPrefix) << "copyprotection error"; - } - } - - if (!engine.isRegistered()) { - Output(Output::NoPrefix) << "registration error"; - } - } - -//----------------------------------------------------------------------------- -//! \brief Do the UCI "ucinewgame" command -//! UCI specification: -//! This is sent to the engine when the next search (started with "position" -//! and "go") will be from a different game. This can be a new game the -//! engine should play or a new game it should analyse but also the next -//! position from a testsuite with positions only. If the GUI hasn't sent a -//! "ucinewgame" before the first "position" command, the engine shouldn't -//! expect any further ucinewgame commands as the GUI is probably not -//! supporting the ucinewgame command. So the engine should not rely on this -//! command even though all new GUIs should support it. As the engine's -//! reaction to "ucinewgame" can take some time the GUI should always send -//! "isready" after "ucinewgame" to wait for the engine to finish its -//! operation. -//----------------------------------------------------------------------------- - void UCIAdapter::doUCINewGameCommand(Parameters params) { - if (params.firstParamIs(token::Help)) { - Output() << "usage: " << token::UciNewGame; - Output() << "Clear all search data."; - return; - } - - if (!engine.isInitialized()) { - engine.initialize(); - } - - if (lastCommand) { - lastCommand->stop(); - lastCommand->waitForFinish(); - } - - lastPosition.clear(); - engine.clearSearchData(); - } - -//----------------------------------------------------------------------------- -//! \brief Do the UCI "position" command -//! UCI specification: -//! Set up the position described in fenstring on the internal board and -//! play the moves on the internal chess board. If the game was played from -//! the start position the string "startpos" will be sent. -//! Note: no "new" command is needed. However, if this position is from a -//! different game than the last position sent to the engine, the GUI should -//! have sent a "ucinewgame" inbetween. -//----------------------------------------------------------------------------- - void UCIAdapter::doPositionCommand(const std::string &fenstring, - Parameters ¶ms) { - if (params.empty() || params.firstParamIs(token::Help)) { - Output() << "usage: " << token::Position << " {" << token::StartPos << "|" - << token::Fen << " } []"; - Output() << "Set a new position and apply (if given)."; - return; - } - - if (!engine.isInitialized()) { - engine.initialize(); - lastPosition.clear(); - } - - if (lastCommand) { - lastCommand->stop(); - lastCommand->waitForFinish(); - } - - if (lastPosition.size() && - lastPosition == fenstring.substr(0, lastPosition.size())) { - // continue from current position - params.parse(fenstring.substr(lastPosition.size() + 1)); - } else { - if (params.firstParamIs(token::StartPos)) { - if (!engine.setPosition(ChessEngine::STARTPOS)) { - return; - } - } else { - // consume "fen" token if present - params.popParam(token::Fen); - std::string remain; - if (!engine.setPosition(params.toString(), &remain)) { - return; - } - params.parse(remain); - } - } - - // remember this position command for next time - lastPosition = fenstring; - - // consume "moves" token if present - params.popParam(token::StartPos); - params.popParam(token::Moves); - - // apply moves (if any) - while (params.size() && isMove(params.front())) { - std::string move = params.popString(); - if (!engine.makeMove(move)) { - Output() << "Invalid move: " << move; - lastPosition.clear(); - break; - } - } - - if (engine.isDebugOn()) { - engine.printBoard(); - } - } - -//----------------------------------------------------------------------------- -//! \brief Do the UCI "setoption" command -//! UCI specification: -//! This is sent to the engine when the user wants to change the internal -//! parameters of the engine. For the "button" type no value is needed. -//! One string will be sent for each parameter and this will only be sent -//! when the engine is waiting. The name and value of the option in -//! should not be case sensitive and can inlude spaces. The substrings -//! "value" and "name" should be avoided in and to allow unambiguous -//! parsing, for example do not use = "draw value". -//! Here are some strings for the example below: -//! "setoption name Nullmove value true\n" -//! "setoption name Selectivity value 3\n" -//! "setoption name Style value Risky\n" -//! "setoption name Clear Hash\n" -//! "setoption name NalimovPath value c:\chess\tb\4;c:\chess\tb\5\n" -//----------------------------------------------------------------------------- - void UCIAdapter::doSetOptionCommand(Parameters ¶ms) { - if (params.empty() || params.firstParamIs(token::Help)) { - Output() << "usage: " << token::SetOption << ' ' << token::Name - << " [" << token::Value << " ]"; - Output() << "Set the value of the specified option name."; - Output() << "If no value specified the option's default value is used,"; - Output() << "or the option will be triggered if it's a button option."; - return; - } - - std::string name; - std::string value; - - if (!params.firstParamIs(token::Name)) { - Output() << "Missing name token"; - return; - } - - if (!params.popString(token::Name, name, token::Value)) { - Output() << "Missing name value"; - return; - } - - if (params.firstParamIs(token::Value)) { - if (!params.popString(token::Value, value)) { - Output() << "Missing value"; - return; - } - } - - if (params.size()) { - Output() << "Unexpected token: " << params.front(); - return; - } - - if (!engine.setEngineOption(name, value)) { - Output() << "Unknown option name '" << name - << "' or invalid option value '" << value << "'"; - } - } - -//----------------------------------------------------------------------------- -//! \brief Do the UCI "ponderhit" command -//! UCI specification: -//! The user has played the expected move. This will be sent if the engine -//! was told to ponder on the same move the user has played. The engine -//! should continue searching but switch from pondering to normal search. -//----------------------------------------------------------------------------- - void UCIAdapter::doPonderHitCommand(Parameters & /*params*/) { - engine.ponderHit(); - } - -} // namespace senjo diff --git a/senjo/UCIAdapter.h b/senjo/UCIAdapter.h deleted file mode 100644 index af021601..00000000 --- a/senjo/UCIAdapter.h +++ /dev/null @@ -1,88 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2015-2019 Shawn Chidester -// -// 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. -//----------------------------------------------------------------------------- - -#ifndef SENJO_UCI_ADAPTER_H -#define SENJO_UCI_ADAPTER_H - -#include "ChessEngine.h" -#include "Parameters.h" -#include "BackgroundCommand.h" - -namespace senjo { - -//----------------------------------------------------------------------------- -//! \brief Convenience class to handle UCI communication for a ChessEngine -//----------------------------------------------------------------------------- - class UCIAdapter { - public: - UCIAdapter(ChessEngine &engine); - - ~UCIAdapter(); - - //-------------------------------------------------------------------------- - //! \brief Execute the given one-line command - //! \param[in] cmd The command to execute - //! \return false when the program should exit, true to continue processing - //-------------------------------------------------------------------------- - bool doCommand(const std::string &command); - - private: - void doHelpCommand(Parameters ¶ms); - - void doFENCommand(Parameters ¶ms); - - void doMoveCommand(Parameters ¶ms); - - void doNewCommand(Parameters ¶ms); - - void doOptsCommand(Parameters ¶ms); - - void doPrintCommand(Parameters ¶ms); - - bool doQuitCommand(Parameters ¶ms); - - void doDebugCommand(Parameters ¶ms); - - void doIsReadyCommand(Parameters ¶ms); - - void doPonderHitCommand(Parameters ¶ms); - - void doSetOptionCommand(Parameters ¶ms); - - void doStopCommand(Parameters params = {}); - - void doUCICommand(Parameters ¶ms); - - void doUCINewGameCommand(Parameters params = {}); - - void doPositionCommand(const std::string &line, Parameters ¶ms); - - void execute(std::shared_ptr command, Parameters ¶ms); - - ChessEngine &engine; - std::string lastPosition; - std::shared_ptr lastCommand; - }; - -} // namespace senjo - -#endif // SENJO_UCI_ADAPTER_H diff --git a/src/bitboard.cpp b/src/bitboard.cpp deleted file mode 100644 index fd294f5f..00000000 --- a/src/bitboard.cpp +++ /dev/null @@ -1,891 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#include "bitboard.h" - -#include - -#include "../senjo/Output.h" -#include "bitwise.h" -#include "magics.h" -#include "movegen.h" -#include "pst.h" -#include "utils.h" - -namespace Zagreus { -uint64_t Bitboard::getOccupiedBoard() const { return occupiedBB; } - -uint64_t Bitboard::getEmptyBoard() const { return ~occupiedBB; } - -PieceColor Bitboard::getMovingColor() const { return movingColor; } - -void Bitboard::setMovingColor(PieceColor movingColor) { this->movingColor = movingColor; } - -PieceType Bitboard::getPieceOnSquare(int8_t square) { - return pieceSquareMapping[square]; -} - -uint64_t Bitboard::getQueenAttacks(int8_t square) { - return getBishopAttacks(square) | getRookAttacks(square); -} - -uint64_t Bitboard::getQueenAttacks(int8_t square, uint64_t occupancy) { - return getBishopAttacks(square, occupancy) | getRookAttacks(square, occupancy); -} - -uint64_t Bitboard::getBishopAttacks(int8_t square) { - uint64_t occupancy = getOccupiedBoard(); - occupancy &= getBishopMask(square); - occupancy *= getBishopMagic(square); - occupancy >>= 64 - BBits[square]; - - return getBishopMagicAttacks(square, occupancy); -} - -uint64_t Bitboard::getBishopAttacks(int8_t square, uint64_t occupancy) { - occupancy &= getBishopMask(square); - occupancy *= getBishopMagic(square); - occupancy >>= 64 - BBits[square]; - - return getBishopMagicAttacks(square, occupancy); -} - -uint64_t Bitboard::getRookAttacks(int8_t square) { - uint64_t occupancy = getOccupiedBoard(); - occupancy &= getRookMask(square); - occupancy *= getRookMagic(square); - occupancy >>= 64 - RBits[square]; - - return getRookMagicAttacks(square, occupancy); -} - -uint64_t Bitboard::getRookAttacks(int8_t square, uint64_t occupancy) { - occupancy &= getRookMask(square); - occupancy *= getRookMagic(square); - occupancy >>= 64 - RBits[square]; - - return getRookMagicAttacks(square, occupancy); -} - -void Bitboard::setPiece(int8_t square, PieceType piece) { - pieceBB[piece] |= 1ULL << square; - occupiedBB |= 1ULL << square; - colorBB[piece % 2] |= 1ULL << square; - pieceSquareMapping[square] = piece; - zobristHash ^= getPieceZobristConstant(piece, square); - materialCount[piece] += 1; - pstValues[piece % 2] += getMidgamePstValue(piece, square); - pstValues[piece % 2 + 2] += getEndgamePstValue(piece, square); -} - -void Bitboard::removePiece(int8_t square, PieceType piece) { - pieceBB[piece] &= ~(1ULL << square); - occupiedBB &= ~(1ULL << square); - colorBB[piece % 2] &= ~(1ULL << square); - pieceSquareMapping[square] = EMPTY; - zobristHash ^= getPieceZobristConstant(piece, square); - materialCount[piece] -= 1; - pstValues[piece % 2] -= getMidgamePstValue(piece, square); - pstValues[piece % 2 + 2] -= getEndgamePstValue(piece, square); -} - -void Bitboard::makeMove(Move& move) { - PieceType capturedPiece = getPieceOnSquare(move.to); - - undoStack[ply].capturedPiece = capturedPiece; - undoStack[ply].halfMoveClock = halfMoveClock; - undoStack[ply].enPassantSquare = enPassantSquare; - undoStack[ply].castlingRights = castlingRights; - undoStack[ply].moveType = REGULAR; - undoStack[ply].zobristHash = zobristHash; - undoStack[ply].previousMove = previousMove; - - halfMoveClock += 1; - - if (capturedPiece != EMPTY) { - halfMoveClock = 0; - removePiece(move.to, capturedPiece); - } - - if (move.piece == WHITE_PAWN || move.piece == BLACK_PAWN) { - halfMoveClock = 0; - } - - removePiece(move.from, move.piece); - - if (enPassantSquare != NO_SQUARE) { - zobristHash ^= getEnPassantZobristConstant(enPassantSquare % 8); - } - - if (move.piece == WHITE_PAWN || move.piece == BLACK_PAWN) { - halfMoveClock = 0; - - if (move.to - move.from == 16) { - enPassantSquare = move.to - 8; - } else if (move.to - move.from == -16) { - enPassantSquare = move.to + 8; - } else if ((std::abs(move.to - move.from) == 7 || std::abs(move.to - move.from) == 9) && - move.to == enPassantSquare) { - int8_t enPassantCaptureSquare = move.to - (movingColor == WHITE ? 8 : -8); - removePiece(enPassantCaptureSquare, getPieceOnSquare(enPassantCaptureSquare)); - undoStack[ply].moveType = EN_PASSANT; - enPassantSquare = NO_SQUARE; - } else { - enPassantSquare = NO_SQUARE; - } - } else { - enPassantSquare = NO_SQUARE; - } - - if (enPassantSquare != NO_SQUARE) { - zobristHash ^= getEnPassantZobristConstant(enPassantSquare % 8); - } - - if (move.piece == WHITE_KING || move.piece == BLACK_KING) { - if (std::abs(move.to - move.from) == 2) { - if (move.to == G1) { - removePiece(H1, WHITE_ROOK); - setPiece(F1, WHITE_ROOK); - } else if (move.to == C1) { - removePiece(A1, WHITE_ROOK); - setPiece(D1, WHITE_ROOK); - } else if (move.to == G8) { - removePiece(H8, BLACK_ROOK); - setPiece(F8, BLACK_ROOK); - } else if (move.to == C8) { - removePiece(A8, BLACK_ROOK); - setPiece(D8, BLACK_ROOK); - } - - undoStack[ply].moveType = CASTLING; - } - - if (move.piece == WHITE_KING) { - if (castlingRights & WHITE_KINGSIDE) { - zobristHash ^= getCastleZobristConstant(ZOBRIST_WHITE_KINGSIDE_INDEX); - castlingRights &= ~WHITE_KINGSIDE; - } - - if (castlingRights & WHITE_QUEENSIDE) { - zobristHash ^= getCastleZobristConstant(ZOBRIST_WHITE_QUEENSIDE_INDEX); - castlingRights &= ~WHITE_QUEENSIDE; - } - } else { - if (castlingRights & BLACK_KINGSIDE) { - zobristHash ^= getCastleZobristConstant(ZOBRIST_BLACK_KINGSIDE_INDEX); - castlingRights &= ~BLACK_KINGSIDE; - } - - if (castlingRights & BLACK_QUEENSIDE) { - zobristHash ^= getCastleZobristConstant(ZOBRIST_BLACK_QUEENSIDE_INDEX); - castlingRights &= ~BLACK_QUEENSIDE; - } - } - } - - if (move.piece == WHITE_ROOK) { - if (move.from == A1 && (castlingRights & WHITE_QUEENSIDE)) { - zobristHash ^= getCastleZobristConstant(ZOBRIST_WHITE_QUEENSIDE_INDEX); - castlingRights &= ~WHITE_QUEENSIDE; - } else if (move.from == H1 && (castlingRights & WHITE_KINGSIDE)) { - zobristHash ^= getCastleZobristConstant(ZOBRIST_WHITE_KINGSIDE_INDEX); - castlingRights &= ~WHITE_KINGSIDE; - } - } else if (move.piece == BLACK_ROOK) { - if (move.from == A8 && (castlingRights & BLACK_QUEENSIDE)) { - zobristHash ^= getCastleZobristConstant(ZOBRIST_BLACK_QUEENSIDE_INDEX); - castlingRights &= ~BLACK_QUEENSIDE; - } else if (move.from == H8 && (castlingRights & BLACK_KINGSIDE)) { - zobristHash ^= getCastleZobristConstant(ZOBRIST_BLACK_KINGSIDE_INDEX); - castlingRights &= ~BLACK_KINGSIDE; - } - } - - if (move.promotionPiece != EMPTY) { - setPiece(move.to, move.promotionPiece); - } else { - setPiece(move.to, move.piece); - } - - if (movingColor == BLACK) { - fullmoveClock += 1; - } - - movingColor = getOppositeColor(movingColor); - zobristHash ^= getMovingColorZobristConstant(); - ply += 1; - moveHistory[ply] = getZobristHash(); - previousMove = move; -} - -int Bitboard::getHalfMoveClock() { - return halfMoveClock; -} - -void Bitboard::unmakeMove(Move& move) { - moveHistory[ply] = 0; - UndoData undoData = undoStack[ply - 1]; - - if (move.promotionPiece != EMPTY) { - removePiece(move.to, move.promotionPiece); - } else { - removePiece(move.to, move.piece); - } - - if (undoData.capturedPiece != EMPTY) { - setPiece(move.to, undoData.capturedPiece); - } - - setPiece(move.from, move.piece); - - if (undoData.moveType == EN_PASSANT) { - int8_t enPassantCaptureSquare = move.to - (getOppositeColor(movingColor) == WHITE ? 8 : -8); - setPiece(enPassantCaptureSquare, - getOppositeColor(movingColor) == WHITE ? BLACK_PAWN : WHITE_PAWN); - } - - if (undoData.moveType == CASTLING) { - if (move.to == G1) { - removePiece(F1, WHITE_ROOK); - setPiece(H1, WHITE_ROOK); - } else if (move.to == C1) { - removePiece(D1, WHITE_ROOK); - setPiece(A1, WHITE_ROOK); - } else if (move.to == G8) { - removePiece(F8, BLACK_ROOK); - setPiece(H8, BLACK_ROOK); - } else if (move.to == C8) { - removePiece(D8, BLACK_ROOK); - setPiece(A8, BLACK_ROOK); - } - } - - ply -= 1; - halfMoveClock = undoData.halfMoveClock; - enPassantSquare = undoData.enPassantSquare; - castlingRights = undoData.castlingRights; - movingColor = getOppositeColor(movingColor); - zobristHash = undoData.zobristHash; - previousMove = undoData.previousMove; - - if (movingColor == BLACK) { - fullmoveClock -= 1; - } -} - -void Bitboard::makeNullMove() { - undoStack[ply].capturedPiece = EMPTY; - undoStack[ply].halfMoveClock = halfMoveClock; - undoStack[ply].enPassantSquare = enPassantSquare; - undoStack[ply].castlingRights = castlingRights; - undoStack[ply].moveType = REGULAR; - undoStack[ply].zobristHash = zobristHash; - undoStack[ply].previousMove = previousMove; - - if (enPassantSquare != NO_SQUARE) { - zobristHash ^= getEnPassantZobristConstant(enPassantSquare % 8); - enPassantSquare = NO_SQUARE; - } - - movingColor = getOppositeColor(movingColor); - zobristHash ^= getMovingColorZobristConstant(); - previousMove = {NO_SQUARE, NO_SQUARE, EMPTY, 0, EMPTY, 0}; - ply += 1; -} - -void Bitboard::unmakeNullMove() { - ply -= 1; - UndoData undoData = undoStack[ply]; - - enPassantSquare = undoData.enPassantSquare; - castlingRights = undoData.castlingRights; - movingColor = getOppositeColor(movingColor); - zobristHash = undoData.zobristHash; - previousMove = undoData.previousMove; -} - -const Move& Bitboard::getPreviousMove() const { return previousMove; } - -bool Bitboard::hasMinorOrMajorPieces() { - return hasMinorOrMajorPieces() || hasMinorOrMajorPieces(); -} - -int Bitboard::getAmountOfMinorOrMajorPieces() { - return getAmountOfMinorOrMajorPieces() + getAmountOfMinorOrMajorPieces(); -} - -void Bitboard::print() { - std::cout << " ---------------------------------"; - - for (int index = 0; index < 64; index++) { - if (index % 8 == 0) { - std::cout << std::endl << index / 8 + 1 << " | "; - } - - std::cout << getCharacterForPieceType(pieceSquareMapping[index]) << " | "; - } - - std::cout << std::endl << " ---------------------------------" << std::endl; - std::cout << " a b c d e f g h " << std::endl; -} - -void Bitboard::printAvailableMoves(MoveList* moves) { - std::cout << " ---------------------------------"; - - for (int index = 0; index < 64; index++) { - if (index % 8 == 0) { - std::cout << std::endl << index / 8 + 1 << " | "; - } - - bool didPrint = false; - - for (int i = 0; i < moves->size; i++) { - Move move = moves->moves[i]; - - if (move.to == index) { - std::cout << 'X' << " | "; - didPrint = true; - break; - } - } - - if (!didPrint) { - std::cout << getCharacterForPieceType(pieceSquareMapping[index]) << " | "; - } - } - - std::cout << std::endl << " ---------------------------------" << std::endl; - std::cout << " a b c d e f g h " << std::endl; -} - -bool Bitboard::setFromFen(const std::string& fen) { - int index = A8; - int spaces = 0; - - for (PieceType& type : pieceSquareMapping) { - type = EMPTY; - } - - for (uint64_t& bb : pieceBB) { - bb = 0; - } - - for (uint64_t& bb : colorBB) { - bb = 0; - } - - for (uint64_t& hash : moveHistory) { - hash = 0; - } - - for (UndoData& undo : undoStack) { - undo = {}; - } - - for (int& count : materialCount) { - count = 0; - } - - occupiedBB = 0; - movingColor = NONE; - ply = 0; - halfMoveClock = 0; - fullmoveClock = 1; - enPassantSquare = NO_SQUARE; - castlingRights = 0; - zobristHash = 0; - pstValues[0] = 0; - pstValues[1] = 0; - pstValues[2] = 0; - pstValues[3] = 0; - - for (char character : fen) { - if (character == ' ') { - spaces++; - continue; - } - - if (character == ',') { - break; - } - - if (spaces == 0) { - if (character == '/') { - index -= 16; - continue; - } - - if (character >= '1' && character <= '8') { - index += character - '0'; - continue; - } - - if (character >= 'A' && character <= 'z') { - setPieceFromFENChar(character, index); - index++; - } else { - senjo::Output(senjo::Output::InfoPrefix) << "Invalid piece character!"; - return false; - } - } - - if (spaces == 1) { - if (tolower(character) == 'w') { - movingColor = WHITE; - } else if (tolower(character) == 'b') { - movingColor = BLACK; - zobristHash ^= getMovingColorZobristConstant(); - } else { - senjo::Output(senjo::Output::InfoPrefix) << "Invalid color to move!"; - return false; - } - } - - if (spaces == 2) { - if (character == '-') { - continue; - } else if (character == 'K') { - castlingRights |= WHITE_KINGSIDE; - zobristHash ^= getCastleZobristConstant(ZOBRIST_WHITE_KINGSIDE_INDEX); - continue; - } else if (character == 'Q') { - castlingRights |= WHITE_QUEENSIDE; - zobristHash ^= getCastleZobristConstant(ZOBRIST_WHITE_QUEENSIDE_INDEX); - continue; - } else if (character == 'k') { - castlingRights |= BLACK_KINGSIDE; - zobristHash ^= getCastleZobristConstant(ZOBRIST_BLACK_KINGSIDE_INDEX); - continue; - } else if (character == 'q') { - castlingRights |= BLACK_QUEENSIDE; - zobristHash ^= getCastleZobristConstant(ZOBRIST_BLACK_QUEENSIDE_INDEX); - continue; - } - - senjo::Output(senjo::Output::InfoPrefix) << "Invalid castling rights!"; - return false; - } - - if (spaces == 3) { - if (character == '-') { - continue; - } - - if (tolower(character) < 'a' || tolower(character) > 'h') { - continue; - } - - int8_t file = tolower(character) - 'a'; - // NOLINT(cppcoreguidelines-narrowing-conversions) - int8_t rank = getOppositeColor(movingColor) == WHITE ? 2 : 5; - - if (file < 0 || file > 7) { - senjo::Output(senjo::Output::InfoPrefix) << "Invalid en passant file!"; - return false; - } - - enPassantSquare = rank * 8 + file; - zobristHash ^= getEnPassantZobristConstant(enPassantSquare % 8); - - index += 2; - } - - if (spaces == 4) { - halfMoveClock = character - '0'; - } - - if (spaces == 5) { - fullmoveClock = character - '0'; - } - } - - moveHistory[ply] = getZobristHash(); - return true; -} - -bool Bitboard::setFromFenTuner(const std::string& fen) { - int index = A8; - int spaces = 0; - - for (PieceType& type : pieceSquareMapping) { - type = EMPTY; - } - - for (uint64_t& bb : pieceBB) { - bb = 0; - } - - for (uint64_t& bb : colorBB) { - bb = 0; - } - - for (uint64_t& hash : moveHistory) { - hash = 0; - } - - for (UndoData& undo : undoStack) { - undo = {}; - } - - for (int& count : materialCount) { - count = 0; - } - - occupiedBB = 0; - movingColor = NONE; - ply = 0; - halfMoveClock = 0; - fullmoveClock = 1; - enPassantSquare = NO_SQUARE; - castlingRights = 0; - zobristHash = 0; - pstValues[0] = 0; - pstValues[1] = 0; - pstValues[2] = 0; - pstValues[3] = 0; - - for (char character : fen) { - if (character == ' ') { - spaces++; - continue; - } - - if (character == ',') { - break; - } - - if (spaces == 0) { - if (character == '/') { - index -= 16; - continue; - } - - if (character >= '1' && character <= '8') { - index += character - '0'; - continue; - } - - if (character >= 'A' && character <= 'z') { - setPieceFromFENChar(character, index); - index++; - } else { - senjo::Output(senjo::Output::InfoPrefix) << "Invalid piece character!"; - return false; - } - } - - if (spaces == 1) { - if (tolower(character) == 'w') { - movingColor = WHITE; - } else if (tolower(character) == 'b') { - movingColor = BLACK; - } else { - senjo::Output(senjo::Output::InfoPrefix) << "Invalid color to move!"; - return false; - } - } - - if (spaces == 2) { - if (character == '-') { - continue; - } else if (character == 'K') { - castlingRights |= WHITE_KINGSIDE; - continue; - } else if (character == 'Q') { - castlingRights |= WHITE_QUEENSIDE; - continue; - } else if (character == 'k') { - castlingRights |= BLACK_KINGSIDE; - continue; - } else if (character == 'q') { - castlingRights |= BLACK_QUEENSIDE; - continue; - } - - senjo::Output(senjo::Output::InfoPrefix) << "Invalid castling rights!"; - return false; - } - - if (spaces == 3) { - if (character == '-') { - continue; - } - - if (tolower(character) < 'a' || tolower(character) > 'h') { - continue; - } - - int8_t file = tolower(character) - 'a'; - // NOLINT(cppcoreguidelines-narrowing-conversions) - int8_t rank = getOppositeColor(movingColor) == WHITE ? 2 : 5; - - if (file < 0 || file > 7) { - senjo::Output(senjo::Output::InfoPrefix) << "Invalid en passant file!"; - return false; - } - - enPassantSquare = rank * 8 + file; - index += 2; - } - - if (spaces == 4) { - halfMoveClock = character - '0'; - } - - if (spaces == 5) { - fullmoveClock = character - '0'; - } - } - - return true; -} - -bool Bitboard::isDraw() { - if (halfMoveClock >= 100) { - return true; - } - - if (isInsufficientMaterial()) { - return true; - } - - // Check if the same position has occurred 3 times using the movehistory array - int samePositionCount = 0; - uint64_t boardHash = getZobristHash(); - - for (int i = ply; i >= 0; i--) { - if (moveHistory[i] == boardHash) { - samePositionCount++; - } - - if (samePositionCount >= 3) { - return true; - } - } - - return false; -} - -uint64_t Bitboard::getZobristHash() const { return zobristHash; } - -void Bitboard::setZobristHash(uint64_t zobristHash) { Bitboard::zobristHash = zobristHash; } - -bool Bitboard::isInsufficientMaterial() { - uint64_t kingBB = getPieceBoard(WHITE_KING) | getPieceBoard(BLACK_KING); - uint64_t piecesBB = getOccupiedBoard(); - uint64_t piecesWithoutKings = piecesBB & ~kingBB; - uint64_t sufficientPieces = getPieceBoard(WHITE_QUEEN) | getPieceBoard(BLACK_QUEEN) | - getPieceBoard(WHITE_ROOK) | getPieceBoard(BLACK_ROOK) | - getPieceBoard(WHITE_PAWN) | getPieceBoard(BLACK_PAWN); - - // If there are queens, rooks or pawns on the board, it can't be a draw - if (sufficientPieces) { - return false; - } - - int pieceCountWithoutKings = popcnt(piecesWithoutKings); - - // If there is only one minor piece on the board, it's a draw. Covers KBvK, KNvK and KvK - if (pieceCountWithoutKings == 1 || pieceCountWithoutKings == 0) { - return true; - } - - return false; -} - -void Bitboard::setPieceFromFENChar(char character, int index) { - // Uppercase = WHITE, lowercase = black - switch (character) { - case 'P': - setPiece(index, WHITE_PAWN); - break; - case 'p': - setPiece(index, BLACK_PAWN); - break; - case 'N': - setPiece(index, WHITE_KNIGHT); - break; - case 'n': - setPiece(index, BLACK_KNIGHT); - break; - case 'B': - setPiece(index, WHITE_BISHOP); - break; - case 'b': - setPiece(index, BLACK_BISHOP); - break; - case 'R': - setPiece(index, WHITE_ROOK); - break; - case 'r': - setPiece(index, BLACK_ROOK); - break; - case 'Q': - setPiece(index, WHITE_QUEEN); - break; - case 'q': - setPiece(index, BLACK_QUEEN); - break; - case 'K': - setPiece(index, WHITE_KING); - break; - case 'k': - setPiece(index, BLACK_KING); - break; - } -} - -uint64_t Bitboard::getSquareAttacks(int8_t square) { - uint64_t queenBB = getPieceBoard(WHITE_QUEEN) | getPieceBoard(BLACK_QUEEN); - uint64_t straightSlidingPieces = getPieceBoard(WHITE_ROOK) | getPieceBoard(BLACK_ROOK) | - queenBB; - uint64_t diagonalSlidingPieces = - getPieceBoard(WHITE_BISHOP) | getPieceBoard(BLACK_BISHOP) | queenBB; - - uint64_t pawnAttacks = getPawnAttacks(square) & getPieceBoard(WHITE_PAWN); - pawnAttacks |= getPawnAttacks(square) & getPieceBoard(BLACK_PAWN); - uint64_t rookAttacks = getRookAttacks(square) & straightSlidingPieces; - uint64_t bishopAttacks = getBishopAttacks(square) & diagonalSlidingPieces; - uint64_t knightAttacks = - getKnightAttacks(square) & (getPieceBoard(WHITE_KNIGHT) | getPieceBoard(BLACK_KNIGHT)); - uint64_t kingAttacks = - getKingAttacks(square) & (getPieceBoard(WHITE_KING) | getPieceBoard(BLACK_KING)); - - return pawnAttacks | rookAttacks | bishopAttacks | knightAttacks | kingAttacks; -} - -uint8_t Bitboard::getCastlingRights() const { return castlingRights; } - -void Bitboard::setCastlingRights(uint8_t castlingRights) { - Bitboard::castlingRights = castlingRights; -} - -int8_t Bitboard::getEnPassantSquare() const { return enPassantSquare; } - -void Bitboard::setEnPassantSquare(int8_t enPassantSquare) { - Bitboard::enPassantSquare = enPassantSquare; -} - -uint64_t Bitboard::getFile(int8_t square) { - return getRayAttack(square, NORTH) | getRayAttack(square, SOUTH) | 1ULL << square; -} - -bool Bitboard::makeStrMove(const std::string& strMove) { - int8_t fromSquare = getSquareFromString(strMove.substr(0, 2)); - int8_t toSquare = getSquareFromString(strMove.substr(2, 2)); - PieceType promotionPiece = EMPTY; - - if (strMove.length() == 5) { - if (strMove.ends_with("q")) { - promotionPiece = getMovingColor() == WHITE ? WHITE_QUEEN : BLACK_QUEEN; - } else if (strMove.ends_with("r")) { - promotionPiece = getMovingColor() == WHITE ? WHITE_ROOK : BLACK_ROOK; - } else if (strMove.ends_with("b")) { - promotionPiece = getMovingColor() == WHITE ? WHITE_BISHOP : BLACK_BISHOP; - } else if (strMove.ends_with("n")) { - promotionPiece = getMovingColor() == WHITE ? WHITE_KNIGHT : BLACK_KNIGHT; - } - } - - PieceType movingPiece = getPieceOnSquare(fromSquare); - PieceType capturedPiece = getPieceOnSquare(toSquare); - int captureScore = -1; - - if (capturedPiece != EMPTY) { - captureScore = mvvlva(movingPiece, capturedPiece); - } - - Move move = {fromSquare, toSquare, movingPiece, captureScore, promotionPiece}; - makeMove(move); - return true; -} - -Line Bitboard::getPvLine() { return pvLine; } - -void Bitboard::setPvLine(Line& pvLine) { - Bitboard::pvLine = pvLine; -} - -uint16_t Bitboard::getPly() const { return ply; } - -void Bitboard::setPly(uint16_t ply) { this->ply = ply; } - -bool Bitboard::isOpenFile(int8_t square) { - uint64_t fileMask = getFile(square); - uint64_t occupied = getPieceBoard(WHITE_PAWN) | getPieceBoard(BLACK_PAWN); - - return fileMask == (fileMask & ~occupied); -} - -int Bitboard::getWhiteMidgamePst() const { return pstValues[0]; } - -int Bitboard::getWhiteEndgamePst() const { return pstValues[2]; } - -int Bitboard::getBlackMidgamePst() const { return pstValues[1]; } - -int Bitboard::getBlackEndgamePst() const { return pstValues[3]; } - -uint64_t Bitboard::getPieceBoard(PieceType pieceType) { return pieceBB[pieceType]; } - -template -bool Bitboard::isWinner() { - if (color == WHITE) { - if (!isKingInCheck()) { - return false; - } - } else { - if (!isKingInCheck()) { - return false; - } - } - - MoveListPool* moveListPool = MoveListPool::getInstance(); - MoveList* moveList = moveListPool->getMoveList(); - generateMoves(*this, moveList); - - for (int i = 0; i < moveList->size; i++) { - Move move = moveList->moves[i]; - - makeMove(move); - - if (color == WHITE) { - if (isKingInCheck()) { - unmakeMove(move); - continue; - } - } else { - if (isKingInCheck()) { - unmakeMove(move); - continue; - } - } - - unmakeMove(move); - moveListPool->releaseMoveList(moveList); - return false; - } - - moveListPool->releaseMoveList(moveList); - return true; -} - -template bool Bitboard::isWinner(); -template bool Bitboard::isWinner(); -} // namespace Zagreus \ No newline at end of file diff --git a/src/bitboard.h b/src/bitboard.h deleted file mode 100644 index 43bcd2f8..00000000 --- a/src/bitboard.h +++ /dev/null @@ -1,438 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion" -#pragma once - -#include - -#include -#include -#include - -#include "bitwise.h" -#include "movelist_pool.h" -#include "types.h" -#include "utils.h" - -namespace Zagreus { -class Bitboard { -private: - uint64_t pieceBB[12]{}; - PieceType pieceSquareMapping[64]{}; - uint64_t colorBB[2]{}; - uint64_t occupiedBB{0ULL}; - - PieceColor movingColor = NONE; - uint16_t ply = 0; - uint8_t halfMoveClock = 0; - uint8_t fullmoveClock = 1; - int8_t enPassantSquare = NO_SQUARE; - uint8_t castlingRights = 0b00001111; - - uint64_t zobristHash = 0ULL; - - UndoData undoStack[MAX_PLY]{}; - uint64_t moveHistory[MAX_PLY]{}; - Line pvLine{}; - - int pstValues[4]{}; - - Move previousMove{}; - int materialCount[12]{}; - -public: - uint64_t getPieceBoard(PieceType pieceType); - - template - uint64_t getColorBoard() { - return colorBB[color]; - } - - template - uint64_t getPawnDoublePush(uint64_t pawns) { - const uint64_t singlePush = getPawnSinglePush(pawns); - - if (color == WHITE) { - return singlePush | (nortOne(singlePush) & getEmptyBoard() & RANK_4); - } - - if (color == BLACK) { - return singlePush | (soutOne(singlePush) & getEmptyBoard() & RANK_5); - } - - return 0; - } - - template - uint64_t getPawnSinglePush(uint64_t pawns) { - if (color == WHITE) { - return nortOne(pawns) & getEmptyBoard(); - } - - if (color == BLACK) { - return soutOne(pawns) & getEmptyBoard(); - } - - return 0; - } - - uint64_t getOccupiedBoard() const; - - uint64_t getEmptyBoard() const; - - PieceType getPieceOnSquare(int8_t square); - - uint64_t getQueenAttacks(int8_t square); - - uint64_t getQueenAttacks(int8_t square, uint64_t occupancy); - - uint64_t getBishopAttacks(int8_t square); - - static uint64_t getBishopAttacks(int8_t square, uint64_t occupancy); - - uint64_t getRookAttacks(int8_t square); - - static uint64_t getRookAttacks(int8_t square, uint64_t occupancy); - - void setPiece(int8_t square, PieceType piece); - - void removePiece(int8_t square, PieceType piece); - - template - int getMaterialCount() { - return materialCount[piece]; - } - - void makeMove(Move& move); - int getHalfMoveClock(); - - void unmakeMove(Move& move); - - void print(); - - void printAvailableMoves(MoveList* moves); - - bool setFromFen(const std::string& fen); - - bool setFromFenTuner(const std::string& fen); - - bool isDraw(); - - template - bool isWinner(); - - void setPieceFromFENChar(char character, int index); - - PieceColor getMovingColor() const; - - void setMovingColor(PieceColor movingColor); - - uint64_t getSquareAttacks(int8_t square); - - template - uint64_t getSquareAttackersByColor(int8_t square) { - if (color == WHITE) { - uint64_t queenBB = getPieceBoard(WHITE_QUEEN); - uint64_t rookBB = getPieceBoard(WHITE_ROOK); - uint64_t bishopBB = getPieceBoard(WHITE_BISHOP); - - uint64_t pawnAttacks = getPawnAttacks(square) & getPieceBoard(WHITE_PAWN); - uint64_t bishopAttacks = getBishopAttacks(square) & bishopBB; - uint64_t knightAttacks = getKnightAttacks(square) & getPieceBoard(WHITE_KNIGHT); - uint64_t kingAttacks = getKingAttacks(square) & getPieceBoard(WHITE_KING); - uint64_t rookAttacks = getRookAttacks(square) & rookBB; - uint64_t queenAttacks = getQueenAttacks(square) & queenBB; - - return pawnAttacks | bishopAttacks | knightAttacks | rookAttacks | queenAttacks | - kingAttacks; - } else { - uint64_t queenBB = getPieceBoard(BLACK_QUEEN); - uint64_t rookBB = getPieceBoard(BLACK_ROOK); - uint64_t bishopBB = getPieceBoard(BLACK_BISHOP); - - uint64_t pawnAttacks = getPawnAttacks(square) & getPieceBoard(BLACK_PAWN); - uint64_t bishopAttacks = getBishopAttacks(square) & bishopBB; - uint64_t knightAttacks = getKnightAttacks(square) & getPieceBoard(BLACK_KNIGHT); - uint64_t rookAttacks = getRookAttacks(square) & rookBB; - uint64_t queenAttacks = getQueenAttacks(square) & queenBB; - uint64_t kingAttacks = getKingAttacks(square) & getPieceBoard(BLACK_KING); - - return pawnAttacks | bishopAttacks | knightAttacks | rookAttacks | queenAttacks | - kingAttacks; - } - } - - template - bool isSquareAttackedByColor(int8_t square) { - return getSquareAttackersByColor(square) != 0; - } - - template - bool isKingInCheck() { - constexpr PieceColor OPPOSITE_COLOR = color == WHITE ? BLACK : WHITE; - uint64_t kingBB = getPieceBoard(color == WHITE ? WHITE_KING : BLACK_KING); - int8_t kingLocation = bitscanForward(kingBB); - - return isSquareAttackedByColor(kingLocation); - } - - int8_t getEnPassantSquare() const; - - void setEnPassantSquare(int8_t enPassantSquare); - - uint8_t getCastlingRights() const; - - void setCastlingRights(uint8_t castlingRights); - - bool isInsufficientMaterial(); - - uint64_t getZobristHash() const; - - void setZobristHash(uint64_t zobristHash); - - bool makeStrMove(const std::string& strMove); - - Line getPvLine(); - - void setPvLine(Line& pvLine); - - uint16_t getPly() const; - - void setPly(uint16_t ply); - - bool isOpenFile(int8_t square); - - template - bool isSemiOpenFile(int8_t square) { - uint64_t fileMask = getFile(square); - if (color == WHITE) { - uint64_t ownOccupied = getPieceBoard(WHITE_PAWN); - uint64_t opponentOccupied = getPieceBoard(BLACK_PAWN); - - return fileMask == (fileMask & ~ownOccupied) && fileMask != ( - fileMask & ~opponentOccupied); - } else { - uint64_t ownOccupied = getPieceBoard(BLACK_PAWN); - uint64_t opponentOccupied = getPieceBoard(WHITE_PAWN); - - return fileMask == (fileMask & ~ownOccupied) && fileMask != ( - fileMask & ~opponentOccupied); - } - } - - // Also returns true when it is an open file - template - bool isSemiOpenFileLenient(int8_t square) { - uint64_t fileMask = getFile(square); - - if (color == WHITE) { - uint64_t ownOccupied = getPieceBoard(WHITE_PAWN); - return fileMask == (fileMask & ~ownOccupied); - } else { - uint64_t ownOccupied = getPieceBoard(BLACK_PAWN); - return fileMask == (fileMask & ~ownOccupied); - } - } - - template - int seeCapture(int8_t fromSquare, int8_t toSquare) { - constexpr PieceColor OPPOSITE_COLOR = attackingColor == WHITE ? BLACK : WHITE; - PieceType movingPiece = pieceSquareMapping[fromSquare]; - PieceType capturedPieceType = pieceSquareMapping[toSquare]; - int captureScore = mvvlva(movingPiece, capturedPieceType); - Move move{fromSquare, toSquare, movingPiece, captureScore}; - - makeMove(move); - int score = getPieceWeight(capturedPieceType) - see(toSquare); - unmakeMove(move); - - return score; - } - - template - int8_t getSmallestAttackerSquare(int8_t square) { - uint64_t attacks = getSquareAttackersByColor(square); - int8_t smallestAttackerSquare = NO_SQUARE; - int smallestAttackerWeight = 999999999; - - while (attacks) { - int attackerSquare = popLsb(attacks); - PieceType pieceType = pieceSquareMapping[attackerSquare]; - int weight = getPieceWeight(pieceType); - - if (weight < smallestAttackerWeight) { - smallestAttackerWeight = weight; - smallestAttackerSquare = attackerSquare; - } - } - - return smallestAttackerSquare; - } - - template - int see(int8_t square) { - constexpr PieceColor OPPOSITE_COLOR = attackingColor == WHITE ? BLACK : WHITE; - int score = 0; - int8_t smallestAttackerSquare = getSmallestAttackerSquare(square); - - if (smallestAttackerSquare != NO_SQUARE) { - PieceType movingPiece = pieceSquareMapping[smallestAttackerSquare]; - PieceType capturedPieceType = pieceSquareMapping[square]; - int captureScore = mvvlva(movingPiece, capturedPieceType); - Move move{smallestAttackerSquare, square, movingPiece, captureScore}; - makeMove(move); - score = std::max(0, getPieceWeight(capturedPieceType) - see(square)); - unmakeMove(move); - } - - return score; - } - - // See, but after a move has already been made. We just check if the opponent can win material. - // This method is basically seeCapture where the move has already been made. It is used to analyze a move already made to see if it appears to be safe. - template - int seeOpponent(int8_t square) { - // moved color is the color that just moved - constexpr PieceColor OPPOSITE_COLOR = movedColor == WHITE ? BLACK : WHITE; - int score = NO_CAPTURE_SCORE; - int8_t smallestAttackerSquare = getSmallestAttackerSquare(square); - - if (smallestAttackerSquare != NO_SQUARE) { - PieceType movingPiece = pieceSquareMapping[smallestAttackerSquare]; - PieceType capturedPieceType = pieceSquareMapping[square]; - int captureScore = mvvlva(movingPiece, capturedPieceType); - Move move{smallestAttackerSquare, square, movingPiece, captureScore}; - makeMove(move); - score = getPieceWeight(capturedPieceType) - see(square); - unmakeMove(move); - } - - return score; - } - - [[nodiscard]] const Move& getPreviousMove() const; - - uint64_t getFile(int8_t square); - - template - uint64_t getPawnsOnSameFile(int8_t square) { - return pieceBB[WHITE_PAWN + color] & getFile(square); - } - - template - bool isIsolatedPawn(int8_t square) { - uint64_t neighborMask = 0; - - if (square % 8 != 0) { - neighborMask |= getFile(square - 1); - } - - if (square % 8 != 7) { - neighborMask |= getFile(square + 1); - } - - if (color == WHITE) { - return !(neighborMask & getPieceBoard(WHITE_PAWN)); - } else { - return !(neighborMask & getPieceBoard(BLACK_PAWN)); - } - } - - template - bool isPassedPawn(int8_t square) { - Direction direction = color == WHITE ? NORTH : SOUTH; - uint64_t neighborMask = getRayAttack(square, direction); - uint64_t pawnBB = getPieceBoard(color == WHITE ? WHITE_PAWN : BLACK_PAWN); - - if (neighborMask & pawnBB) { - return false; - } - - if (square % 8 != 0) { - // neighboring file - neighborMask |= getRayAttack(square - 1, direction); - } - - if (square % 8 != 7) { - neighborMask |= getRayAttack(square + 1, direction); - } - - if (color == WHITE) { - return !(neighborMask & getPieceBoard(BLACK_PAWN)); - } else { - return !(neighborMask & getPieceBoard(WHITE_PAWN)); - } - } - - bool hasMinorOrMajorPieces(); - - template - bool hasMinorOrMajorPieces() { - if (color == WHITE) { - return getColorBoard() & ~( - getPieceBoard(WHITE_PAWN) | getPieceBoard(WHITE_KING)); - } else { - return getColorBoard() & ~( - getPieceBoard(BLACK_PAWN) | getPieceBoard(BLACK_KING)); - } - } - - template - int getAmountOfMinorOrMajorPieces() { - if (color == WHITE) { - return popcnt(getColorBoard() & - ~(getPieceBoard(WHITE_PAWN) | getPieceBoard(WHITE_KING))); - } else { - return popcnt(getColorBoard() & - ~(getPieceBoard(BLACK_PAWN) | getPieceBoard(BLACK_KING))); - } - } - - template - int getAmountOfPawns() { - return popcnt(getColorBoard() & getPieceBoard(color == WHITE - ? WHITE_PAWN - : BLACK_PAWN)); - } - - template - int getAmountOfPieces() { - return popcnt(getColorBoard()); - } - - void makeNullMove(); - - void unmakeNullMove(); - - [[nodiscard]] int getWhiteMidgamePst() const; - - [[nodiscard]] int getWhiteEndgamePst() const; - - [[nodiscard]] int getBlackMidgamePst() const; - - [[nodiscard]] int getBlackEndgamePst() const; - - int getAmountOfMinorOrMajorPieces(); -}; -} // namespace Zagreus - -#pragma clang diagnostic pop \ No newline at end of file diff --git a/src/bitwise.cpp b/src/bitwise.cpp deleted file mode 100644 index 2c948bd2..00000000 --- a/src/bitwise.cpp +++ /dev/null @@ -1,338 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#include "bitwise.h" - -#include -#include -#include - -namespace Zagreus { -static uint64_t kingAttacks[64]{}; -static uint64_t knightAttacks[64]{}; -static uint64_t pawnAttacks[2][64]{}; -static uint64_t rayAttacks[8][64]{}; -static uint64_t betweenTable[64][64]{}; - -static uint64_t zobristPieceConstants[PIECE_TYPES][SQUARES]{}; -static uint64_t zobristMovingColorConstant{}; -static uint64_t zobristCastleConstants[4]{}; -static uint64_t zobristEnPassantConstants[8]{}; - -uint64_t soutOne(uint64_t b) { return b >> 8ULL; } - -uint64_t nortOne(uint64_t b) { return b << 8ULL; } - -uint64_t eastOne(uint64_t b) { return b << 1ULL & NOT_A_FILE; } - -uint64_t noEaOne(uint64_t b) { return b << 9ULL & NOT_A_FILE; } - -uint64_t soEaOne(uint64_t b) { return b >> 7ULL & NOT_A_FILE; } - -uint64_t westOne(uint64_t b) { return b >> 1ULL & NOT_H_FILE; } - -uint64_t soWeOne(uint64_t b) { return b >> 9ULL & NOT_H_FILE; } - -uint64_t noWeOne(uint64_t b) { return b << 7ULL & NOT_H_FILE; } - -uint64_t noNoEa(uint64_t b) { return b << 17ULL & NOT_A_FILE; } - -uint64_t noEaEa(uint64_t b) { return b << 10ULL & NOT_AB_FILE; } - -uint64_t soEaEa(uint64_t b) { return b >> 6ULL & NOT_AB_FILE; } - -uint64_t soSoEa(uint64_t b) { return b >> 15ULL & NOT_A_FILE; } - -uint64_t noNoWe(uint64_t b) { return b << 15ULL & NOT_H_FILE; } - -uint64_t noWeWe(uint64_t b) { return b << 6ULL & NOT_GH_FILE; } - -uint64_t nortFill(uint64_t gen) { - gen |= gen << 8; - gen |= gen << 16; - gen |= gen << 32; - - return gen; -} - -uint64_t soutFill(uint64_t gen) { - gen |= gen >> 8; - gen |= gen >> 16; - gen |= gen >> 32; - - return gen; -} - -uint64_t whiteFrontSpans(uint64_t pawns) { return nortOne(nortFill(pawns)); } - -uint64_t whiteRearSpans(uint64_t pawns) { return soutOne(soutFill(pawns)); } - -uint64_t blackRearSpans(uint64_t pawns) { return nortOne(nortFill(pawns)); } - -uint64_t blackFrontSpans(uint64_t pawns) { return soutOne(soutFill(pawns)); } - -uint64_t soutOccl(uint64_t pieceBB, uint64_t empty) { - pieceBB |= empty & pieceBB >> 8ULL; - empty &= empty >> 8ULL; - pieceBB |= empty & pieceBB >> 16ULL; - empty &= empty >> 16ULL; - pieceBB |= empty & pieceBB >> 32ULL; - return soutOne(pieceBB); -} - -uint64_t nortOccl(uint64_t pieceBB, uint64_t empty) { - pieceBB |= empty & pieceBB << 8ULL; - empty &= empty << 8ULL; - pieceBB |= empty & pieceBB << 16ULL; - empty &= empty << 16ULL; - pieceBB |= empty & pieceBB << 32ULL; - return nortOne(pieceBB); -} - -uint64_t eastOccl(uint64_t pieceBB, uint64_t empty) { - empty &= NOT_A_FILE; - pieceBB |= empty & pieceBB << 1ULL; - empty &= empty << 1ULL; - pieceBB |= empty & pieceBB << 2ULL; - empty &= empty << 2ULL; - pieceBB |= empty & pieceBB << 4ULL; - return eastOne(pieceBB); -} - -uint64_t noEaOccl(uint64_t pieceBB, uint64_t empty) { - empty &= NOT_A_FILE; - pieceBB |= empty & pieceBB << 9ULL; - empty &= empty << 9ULL; - pieceBB |= empty & pieceBB << 18ULL; - empty &= empty << 18ULL; - pieceBB |= empty & pieceBB << 36ULL; - return noEaOne(pieceBB); -} - -uint64_t soEaOccl(uint64_t pieceBB, uint64_t empty) { - empty &= NOT_A_FILE; - pieceBB |= empty & pieceBB >> 7ULL; - empty &= empty >> 7ULL; - pieceBB |= empty & pieceBB >> 14ULL; - empty &= empty >> 14ULL; - pieceBB |= empty & pieceBB >> 28ULL; - return soEaOne(pieceBB); -} - -uint64_t westOccl(uint64_t pieceBB, uint64_t empty) { - empty &= NOT_H_FILE; - pieceBB |= empty & pieceBB >> 1ULL; - empty &= empty >> 1ULL; - pieceBB |= empty & pieceBB >> 2ULL; - empty &= empty >> 2ULL; - pieceBB |= empty & pieceBB >> 4ULL; - return westOne(pieceBB); -} - -uint64_t soWeOccl(uint64_t pieceBB, uint64_t empty) { - empty &= NOT_H_FILE; - pieceBB |= empty & pieceBB >> 9ULL; - empty &= empty >> 9ULL; - pieceBB |= empty & pieceBB >> 18ULL; - empty &= empty >> 18ULL; - pieceBB |= empty & pieceBB >> 36ULL; - return soWeOne(pieceBB); -} - -uint64_t noWeOccl(uint64_t pieceBB, uint64_t empty) { - empty &= NOT_H_FILE; - pieceBB |= empty & pieceBB << 7ULL; - empty &= empty << 7ULL; - pieceBB |= empty & pieceBB << 14ULL; - empty &= empty << 14ULL; - pieceBB |= empty & pieceBB << 28ULL; - return noWeOne(pieceBB); -} - -template -uint64_t calculatePawnEastAttacks(uint64_t pawns) { - if (color == WHITE) { - return noEaOne(pawns); - } - return soEaOne(pawns); -} - -template -uint64_t calculatePawnWestAttacks(uint64_t pawns) { - if (color == WHITE) { - return noWeOne(pawns); - } - return soWeOne(pawns); -} - -uint64_t calculateKnightAttacks(uint64_t knights) { - uint64_t l1 = knights >> 1ULL & NOT_H_FILE; - uint64_t l2 = knights >> 2ULL & NOT_GH_FILE; - uint64_t r1 = knights << 1ULL & NOT_A_FILE; - uint64_t r2 = knights << 2ULL & NOT_AB_FILE; - uint64_t h1 = l1 | r1; - uint64_t h2 = l2 | r2; - return h1 << 16ULL | h1 >> 16ULL | h2 << 8ULL | h2 >> 8ULL; -} - -uint64_t calculateKingAttacks(uint64_t kingSet) { - uint64_t attacks = eastOne(kingSet) | westOne(kingSet); - kingSet |= attacks; - attacks |= nortOne(kingSet) | soutOne(kingSet); - - return attacks; -} - -void initializeBitboardConstants() { - std::mt19937_64 gen(0x6C7CCC580A348E7BULL); - std::uniform_int_distribution dis(1ULL, UINT64_MAX); - std::vector generatedZobristConstants(ZOBRIST_CONSTANT_SIZE); - - for (int pieceType = 0; pieceType < PIECE_TYPES; pieceType++) { - for (int square = 0; square < SQUARES; square++) { - uint64_t zobristConstant = dis(gen); - zobristPieceConstants[pieceType][square] = zobristConstant; - - // if constant already generated, generate a new one - if (std::ranges::find(generatedZobristConstants, zobristConstant) != - generatedZobristConstants.end()) { - zobristConstant = dis(gen); - } - - generatedZobristConstants.push_back(zobristConstant); - } - } - - zobristMovingColorConstant = dis(gen); - - for (int i = 0; i < 4; i++) { - zobristCastleConstants[i] = dis(gen); - } - - for (int i = 0; i < 8; i++) { - zobristEnPassantConstants[i] = dis(gen); - } - - uint64_t sqBB = 1ULL; - for (int8_t sq = 0; sq < 64; sq++, sqBB <<= 1ULL) { - kingAttacks[sq] = calculateKingAttacks(sqBB) & ~sqBB; - } - - sqBB = 1ULL; - for (int8_t sq = 0; sq < 64; sq++, sqBB <<= 1ULL) { - knightAttacks[sq] = calculateKnightAttacks(sqBB) & ~sqBB; - } - - sqBB = 1ULL; - for (int8_t sq = 0; sq < 64; sq++, sqBB <<= 1ULL) { - pawnAttacks[WHITE][sq] = calculatePawnAttacks(sqBB) & ~sqBB; - pawnAttacks[BLACK][sq] = calculatePawnAttacks(sqBB) & ~sqBB; - } - - initializeBetweenLookup(); - initializeRayAttacks(); -} - -void initializeBetweenLookup() { - for (int from = 0; from < 64; from++) { - for (int to = 0; to < 64; to++) { - uint64_t m1 = -1ULL; - uint64_t a2a7 = 0x0001010101010100ULL; - uint64_t b2g7 = 0x0040201008040200ULL; - uint64_t h1b7 = 0x0002040810204080ULL; - uint64_t btwn, line, rank, file; - - btwn = m1 << from ^ m1 << to; - file = (to & 7) - (from & 7); - rank = ((to | 7) - from) >> 3; - line = (file & 7) - 1 & a2a7; /* a2a7 if same file */ - line += 2 * (((rank & 7) - 1) >> 58); /* b1g1 if same rank */ - line += (rank - file & 15) - 1 & b2g7; /* b2g7 if same diagonal */ - line += (rank + file & 15) - 1 & h1b7; /* h1b7 if same antidiag */ - line *= btwn & -btwn; /* mul acts like shift by smaller square */ - - betweenTable[from][to] = line & btwn; /* return the bits on that line in-between */ - } - } -} - -void initializeRayAttacks() { - uint64_t sqBB = 1ULL; - - for (int sq = 0; sq < 64; sq++, sqBB <<= 1ULL) { - rayAttacks[NORTH][sq] = nortOccl(sqBB, ~0ULL) & ~sqBB; - rayAttacks[SOUTH][sq] = soutOccl(sqBB, ~0ULL) & ~sqBB; - rayAttacks[EAST][sq] = eastOccl(sqBB, ~0ULL) & ~sqBB; - rayAttacks[WEST][sq] = westOccl(sqBB, ~0ULL) & ~sqBB; - rayAttacks[NORTH_EAST][sq] = noEaOccl(sqBB, ~0ULL) & ~sqBB; - rayAttacks[NORTH_WEST][sq] = noWeOccl(sqBB, ~0ULL) & ~sqBB; - rayAttacks[SOUTH_EAST][sq] = soEaOccl(sqBB, ~0ULL) & ~sqBB; - rayAttacks[SOUTH_WEST][sq] = soWeOccl(sqBB, ~0ULL) & ~sqBB; - } -} - -uint64_t getRayAttack(int8_t square, Direction direction) { - return rayAttacks[direction][square]; -} - -uint64_t getKingAttacks(int8_t square) { - return kingAttacks[square]; -} - -uint64_t getKnightAttacks(int8_t square) { - return knightAttacks[square]; -} - -uint64_t getBetweenSquares(int8_t from, int8_t to) { - return betweenTable[from][to]; -} - -uint64_t getPieceZobristConstant(PieceType pieceType, int8_t square) { - return zobristPieceConstants[pieceType][square]; -} - -uint64_t getMovingColorZobristConstant() { - return zobristMovingColorConstant; -} - -uint64_t getCastleZobristConstant(uint8_t index) { - return zobristCastleConstants[index]; -} - -uint64_t getEnPassantZobristConstant(uint8_t file) { - return zobristEnPassantConstants[file]; -} - -template -uint64_t calculatePawnAttacks(uint64_t bb) { - return calculatePawnEastAttacks(bb) | calculatePawnWestAttacks(bb); -} - -template uint64_t calculatePawnAttacks(uint64_t); -template uint64_t calculatePawnAttacks(uint64_t); - -template -uint64_t getPawnAttacks(int8_t square) { - return pawnAttacks[color][square]; -} - -template uint64_t getPawnAttacks(int8_t square); -template uint64_t getPawnAttacks(int8_t square); -} // namespace Zagreus \ No newline at end of file diff --git a/src/bitwise.h b/src/bitwise.h deleted file mode 100644 index 5858379b..00000000 --- a/src/bitwise.h +++ /dev/null @@ -1,125 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#pragma once - -#include - -#include "types.h" - -namespace Zagreus { -uint64_t soutOne(uint64_t b); - -uint64_t nortOne(uint64_t b); - -uint64_t eastOne(uint64_t b); - -uint64_t noEaOne(uint64_t b); - -uint64_t soEaOne(uint64_t b); - -uint64_t westOne(uint64_t b); - -uint64_t soWeOne(uint64_t b); - -uint64_t noWeOne(uint64_t b); - -uint64_t noNoEa(uint64_t b); - -uint64_t noEaEa(uint64_t b); - -uint64_t soEaEa(uint64_t b); - -uint64_t soSoEa(uint64_t b); - -uint64_t noNoWe(uint64_t b); - -uint64_t noWeWe(uint64_t b); - -uint64_t soWeWe(uint64_t b); - -uint64_t soSoWe(uint64_t b); - -uint64_t soutOccl(uint64_t pieceBB, uint64_t empty); - -uint64_t nortOccl(uint64_t pieceBB, uint64_t empty); - -uint64_t eastOccl(uint64_t pieceBB, uint64_t empty); - -uint64_t noEaOccl(uint64_t pieceBB, uint64_t empty); - -uint64_t soEaOccl(uint64_t pieceBB, uint64_t empty); - -uint64_t westOccl(uint64_t rooks, uint64_t empty); - -uint64_t soWeOccl(uint64_t bishops, uint64_t empty); - -uint64_t noWeOccl(uint64_t bishops, uint64_t empty); - -uint64_t nortFill(uint64_t gen); - -uint64_t soutFill(uint64_t gen); - -uint64_t whiteFrontSpans(uint64_t pawns); - -uint64_t whiteRearSpans(uint64_t pawns); - -uint64_t blackRearSpans(uint64_t pawns); - -uint64_t blackFrontSpans(uint64_t pawns); - -template -uint64_t calculatePawnEastAttacks(uint64_t pawns); - -template -uint64_t calculatePawnWestAttacks(uint64_t pawns); - -uint64_t calculateKnightAttacks(uint64_t knights); - -uint64_t calculateKingAttacks(uint64_t kingSet); - -void initializeBetweenLookup(); - -void initializeRayAttacks(); - -void initializeBitboardConstants(); - -uint64_t getKingAttacks(int8_t square); - -uint64_t getKnightAttacks(int8_t square); - -uint64_t getRayAttack(int8_t square, Direction direction); - -uint64_t getBetweenSquares(int8_t from, int8_t to); - -uint64_t getPieceZobristConstant(PieceType pieceType, int8_t square); - -uint64_t getMovingColorZobristConstant(); - -uint64_t getCastleZobristConstant(uint8_t index); - -uint64_t getEnPassantZobristConstant(uint8_t file); - -template -uint64_t getPawnAttacks(int8_t square); - -template -uint64_t calculatePawnAttacks(uint64_t bb); -} // namespace Zagreus \ No newline at end of file diff --git a/src/constants.h b/src/constants.h deleted file mode 100644 index 1f60974b..00000000 --- a/src/constants.h +++ /dev/null @@ -1,92 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#pragma once - -#include - -static constexpr uint16_t MAX_PLY = 750; -static constexpr uint8_t MAX_MOVES = 255; -static constexpr int ZOBRIST_CONSTANT_SIZE = 781; - -static constexpr int ZOBRIST_WHITE_KINGSIDE_INDEX = 0; -static constexpr int ZOBRIST_WHITE_QUEENSIDE_INDEX = 1; -static constexpr int ZOBRIST_BLACK_KINGSIDE_INDEX = 2; -static constexpr int ZOBRIST_BLACK_QUEENSIDE_INDEX = 3; - -static constexpr int MATE_SCORE = 20000; -static constexpr int MAX_POSITIVE = 1000000; -static constexpr int MAX_NEGATIVE = -1000000; -static constexpr int DRAW_SCORE = 0; - -static constexpr uint64_t A_FILE = 0x0101010101010101ULL; -static constexpr uint64_t B_FILE = 0x0202020202020202ULL; -static constexpr uint64_t C_FILE = 0x0404040404040404ULL; -static constexpr uint64_t D_FILE = 0x0808080808080808ULL; -static constexpr uint64_t E_FILE = 0x1010101010101010ULL; -static constexpr uint64_t F_FILE = 0x2020202020202020ULL; -static constexpr uint64_t G_FILE = 0x4040404040404040ULL; -static constexpr uint64_t H_FILE = 0x8080808080808080ULL; -static constexpr uint64_t DE_FILE = 0x01818181818181818ULL; -static constexpr uint64_t NOT_A_FILE = 0XFEFEFEFEFEFEFEFEULL; -static constexpr uint64_t NOT_AB_FILE = 0XFCFCFCFCFCFCFCFCULL; -static constexpr uint64_t NOT_GH_FILE = 0X3F3F3F3F3F3F3F3FULL; -static constexpr uint64_t NOT_H_FILE = 0X7F7F7F7F7F7F7F7FULL; -static constexpr uint64_t RANK_1 = 0xFFULL; -static constexpr uint64_t RANK_2 = 0xFF00ULL; -static constexpr uint64_t RANK_3 = 0xFF0000ULL; -static constexpr uint64_t RANK_4 = 0xFF000000ULL; -static constexpr uint64_t RANK_5 = 0xFF00000000ULL; -static constexpr uint64_t RANK_6 = 0xFF0000000000ULL; -static constexpr uint64_t RANK_7 = 0xFF000000000000ULL; -static constexpr uint64_t RANK_8 = 0xFF00000000000000ULL; -static constexpr uint64_t A1_H8_DIAG = 0x8040201008040201ULL; -static constexpr uint64_t H1_A8_DIAG = 0x0102040810204080ULL; -static constexpr uint64_t LIGHT_SQUARES = 0x55AA55AA55AA55AAULL; -static constexpr uint64_t DARK_SQUARES = 0xAA55AA55AA55AA55ULL; -static constexpr uint64_t CENTER_SQUARES = 0x0000001818000000ULL; -static constexpr uint64_t EXTENDED_CENTER_SQUARES = 0x00003C3C3C3C0000ULL; -static constexpr uint64_t PROMOTION_SQUARES = 0xFF000000000000FFULL; - -static constexpr uint64_t WHITE_KING_SIDE_BETWEEN = 0x60ULL; -static constexpr uint64_t WHITE_QUEEN_SIDE_BETWEEN = 0xEULL; -static constexpr uint64_t BLACK_KING_SIDE_BETWEEN = 0x6000000000000000ULL; -static constexpr uint64_t BLACK_QUEEN_SIDE_BETWEEN = 0xE00000000000000ULL; - -static constexpr int NO_CAPTURE_SCORE = -1; - -static constexpr int COLORS = 2; -static constexpr int PIECE_TYPES = 12; -static constexpr int SQUARES = 64; - -static constexpr int MVVLVA_TABLE[12][12] = { - {105, 105, 205, 205, 305, 305, 405, 405, 505, 505, 605, 605}, - {104, 104, 204, 204, 304, 304, 404, 404, 504, 504, 604, 604}, - {103, 103, 203, 203, 303, 303, 403, 403, 503, 503, 603, 603}, - {102, 102, 202, 202, 302, 302, 402, 402, 502, 502, 602, 602}, - {101, 101, 201, 201, 301, 301, 401, 401, 501, 501, 601, 601}, - {100, 100, 200, 200, 300, 300, 400, 400, 500, 500, 600, 600}, - {105, 105, 205, 205, 305, 305, 405, 405, 505, 505, 605, 605}, - {104, 104, 204, 204, 304, 304, 404, 404, 504, 504, 604, 604}, - {103, 103, 203, 203, 303, 303, 403, 403, 503, 503, 603, 603}, - {102, 102, 202, 202, 302, 302, 402, 402, 502, 502, 602, 602}, - {101, 101, 201, 201, 301, 301, 401, 401, 501, 501, 601, 601}, - {100, 100, 200, 200, 300, 300, 400, 400, 500, 500, 600, 600}, -}; \ No newline at end of file diff --git a/src/engine.cpp b/src/engine.cpp deleted file mode 100644 index 27fa93e1..00000000 --- a/src/engine.cpp +++ /dev/null @@ -1,256 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#include "engine.h" - -#include - -#include "../senjo/ChessEngine.h" -#include "../senjo/Output.h" -#include "../senjo/GoParams.h" -#include "../senjo/SearchStats.h" -#include "bitboard.h" -#include "movegen.h" -#include "search.h" -#include "tt.h" -#include "types.h" -#include "utils.h" - -namespace Zagreus { -uint64_t ZagreusEngine::doPerft(Bitboard& perftBoard, PieceColor color, int16_t depth, - int startingDepth) { - uint64_t nodes = 0ULL; - - if (depth == 0) { - return 1ULL; - } - - MoveListPool* moveListPool = MoveListPool::getInstance(); - MoveList* moves = moveListPool->getMoveList(); - - if (color == WHITE) { - generateMoves(perftBoard, moves); - } else if (color == BLACK) { - generateMoves(perftBoard, moves); - } else { - moveListPool->releaseMoveList(moves); - return 0; - } - - for (int i = 0; i < moves->size; i++) { - Move move = moves->moves[i]; - - perftBoard.makeMove(move); - - if (color == WHITE) { - if (perftBoard.isKingInCheck()) { - perftBoard.unmakeMove(move); - continue; - } - } else { - if (perftBoard.isKingInCheck()) { - perftBoard.unmakeMove(move); - continue; - } - } - - uint64_t nodeAmount = doPerft(perftBoard, getOppositeColor(color), depth - 1, - startingDepth); - nodes += nodeAmount; - perftBoard.unmakeMove(move); - - if (depth == startingDepth && nodeAmount > 0ULL) { - std::string notation = getNotation(move.from) + getNotation(move.to); - senjo::Output(senjo::Output::NoPrefix) << notation << ": " << nodeAmount; - } - } - - moveListPool->releaseMoveList(moves); - return nodes; -} - -std::string ZagreusEngine::getEngineName() { return "Zagreus"; } - -std::string majorVersion = ZAGREUS_VERSION_MAJOR; -std::string minorVersion = ZAGREUS_VERSION_MINOR; - -std::string ZagreusEngine::getEngineVersion() { - if (majorVersion != "dev") { - return "v" + majorVersion + "." + minorVersion; - } - return majorVersion + "-" + minorVersion; -} - -std::string ZagreusEngine::getAuthorName() { return "Danny Jelsma"; } - -std::string ZagreusEngine::getEmailAddress() { return ""; } - -std::string ZagreusEngine::getCountryName() { return "The Netherlands"; } - -std::list ZagreusEngine::getOptions() { return options; } - -senjo::EngineOption ZagreusEngine::getOption(const std::string& optionName) { - for (senjo::EngineOption& option : options) { - if (option.getName() == optionName) { - return option; - } - } - - return senjo::EngineOption("Invalid Option"); -} - -bool ZagreusEngine::setEngineOption(const std::string& optionName, const std::string& optionValue) { - for (senjo::EngineOption& option : options) { - if (option.getName() == optionName) { - option.setValue(optionValue); - - if (option.getName() == "Hash") { - TranspositionTable::getTT()->setTableSize(option.getIntValue()); - } - - return true; - } - } - - return false; -} - -void ZagreusEngine::initialize() { - stoppingSearch = false; - board = Bitboard{}; - TranspositionTable::getTT()->setTableSize(getOption("Hash").getIntValue()); - isEngineInitialized = true; -} - -bool ZagreusEngine::isInitialized() { return isEngineInitialized; } - -bool ZagreusEngine::setPosition(const std::string& fen, std::string* remain) { - stoppingSearch = false; - board = {}; - return board.setFromFen(fen); -} - -bool ZagreusEngine::makeMove(const std::string& move) { - stoppingSearch = false; - return board.makeStrMove(move); -} - -std::string ZagreusEngine::getFEN() { - // TODO: implement - return ""; -} - -void ZagreusEngine::printBoard() { board.print(); } - -bool ZagreusEngine::whiteToMove() { return board.getMovingColor() == WHITE; } - -void ZagreusEngine::clearSearchData() { -} - -void ZagreusEngine::ponderHit() { -} - -bool ZagreusEngine::isRegistered() { return true; } - -void ZagreusEngine::registerLater() { -} - -bool ZagreusEngine::doRegistration(const std::string& name, const std::string& code) { - return true; -} - -bool ZagreusEngine::isCopyProtected() { return false; } - -bool ZagreusEngine::copyIsOK() { return true; } - -void ZagreusEngine::setDebug(const bool flag) { -} - -bool ZagreusEngine::isDebugOn() { return false; } - -bool ZagreusEngine::isSearching() { - return searching; -} - -void ZagreusEngine::stopSearching() { stoppingSearch = true; } - -bool ZagreusEngine::stopRequested() { return stoppingSearch; } - -void ZagreusEngine::waitForSearchFinish() { - while (isSearching()) { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } -} - -uint64_t ZagreusEngine::perft(const int16_t depth) { - stoppingSearch = false; - auto start = std::chrono::steady_clock::now(); - uint64_t nodes = doPerft(board, board.getMovingColor(), depth, depth); - auto end = std::chrono::steady_clock::now(); - std::chrono::duration elapsedSeconds = end - start; - - senjo::Output(senjo::Output::InfoPrefix) - << "Depth " << depth << " Nodes: " << nodes - << ", Took: " << elapsedSeconds.count() << "s"; - return nodes; -} - -std::string ZagreusEngine::go(senjo::GoParams& params, std::string* ponder) { - stoppingSearch = false; - searching = true; - searchStats = {}; - Move bestMove; - - if (board.getMovingColor() == WHITE) { - bestMove = getBestMove(params, *this, board, searchStats); - } else { - bestMove = getBestMove(params, *this, board, searchStats); - } - - if (bestMove.promotionPiece != EMPTY) { - std::string result = getNotation(bestMove.from) + getNotation(bestMove.to) + - getCharacterForPieceType(bestMove.promotionPiece); - - std::transform(result.begin(), result.end(), result.begin(), - [](unsigned char c) { return std::tolower(c); }); - - searching = false; - return result; - } - - std::string result = getNotation(bestMove.from) + getNotation(bestMove.to); - - searching = false; - searching = true; - return getNotation(bestMove.from) + getNotation(bestMove.to); -} - -senjo::SearchStats ZagreusEngine::getSearchStats() { return searchStats; } - -void ZagreusEngine::resetEngineStats() { -} - -void ZagreusEngine::showEngineStats() { -} - -bool ZagreusEngine::isTuning() const { return tuning; } - -void ZagreusEngine::setTuning(bool tuning) { ZagreusEngine::tuning = tuning; } -} // namespace Zagreus \ No newline at end of file diff --git a/src/engine.h b/src/engine.h deleted file mode 100644 index 2f573171..00000000 --- a/src/engine.h +++ /dev/null @@ -1,123 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#pragma once - -#include - -#include "../senjo/ChessEngine.h" -#include "bitboard.h" -#include "types.h" - -namespace Zagreus { -class ZagreusEngine : public senjo::ChessEngine { -private: - Bitboard board{}; - bool isEngineInitialized = false; - senjo::SearchStats searchStats{}; - bool stoppingSearch = false; - bool searching = false; - bool tuning = false; - - std::list options{ - senjo::EngineOption("MoveOverhead", "50", senjo::EngineOption::OptionType::Spin, 0, 5000), - senjo::EngineOption("Hash", "512", senjo::EngineOption::OptionType::Spin, 1, 33554432), - senjo::EngineOption("Threads", "1", senjo::EngineOption::OptionType::Spin, 1, 1), - senjo::EngineOption("SyzygyPath", "", senjo::EngineOption::OptionType::String), - senjo::EngineOption("SyzygyProbeLimit", "0", senjo::EngineOption::OptionType::Spin, 0, 100), - }; - -public: - // uint64_t doPerft(Zagreus::Bitboard &board, Zagreus::PieceColor color, int16_t depth, int - // startingDepth); - - std::string getEngineName() override; - - std::string getEngineVersion() override; - - std::string getAuthorName() override; - - std::string getEmailAddress() override; - - std::string getCountryName() override; - - std::list getOptions() override; - - bool setEngineOption(const std::string& optionName, const std::string& optionValue) override; - - void initialize() override; - - bool isInitialized() override; - - bool setPosition(const std::string& fen, std::string* remain) override; - - bool makeMove(const std::string& move) override; - - std::string getFEN() override; - - void printBoard() override; - - bool whiteToMove() override; - - void clearSearchData() override; - - void ponderHit() override; - - bool isRegistered() override; - - void registerLater() override; - - bool doRegistration(const std::string& name, const std::string& code) override; - - bool isCopyProtected() override; - - bool copyIsOK() override; - - void setDebug(const bool flag) override; - - bool isDebugOn() override; - - bool isSearching() override; - - void stopSearching() override; - - bool stopRequested() override; - - void waitForSearchFinish() override; - - uint64_t perft(const int16_t depth) override; - - std::string go(senjo::GoParams& params, std::string* ponder) override; - - senjo::SearchStats getSearchStats() override; - - void resetEngineStats() override; - - void showEngineStats() override; - - senjo::EngineOption getOption(const std::string& optionName); - - uint64_t doPerft(Bitboard& perftBoard, PieceColor color, int16_t depth, int startingDepth); - - bool isTuning() const; - - void setTuning(bool tuning); -}; -} // namespace Zagreus \ No newline at end of file diff --git a/src/evaluate.cpp b/src/evaluate.cpp deleted file mode 100644 index b481c5cb..00000000 --- a/src/evaluate.cpp +++ /dev/null @@ -1,773 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#include "evaluate.h" - -#include - -#include "features.h" - -namespace Zagreus { -void Evaluation::initEvalContext(Bitboard& bitboard) { - uint64_t whitePawns = bitboard.getPieceBoard(WHITE_PAWN); - while (whitePawns) { - uint8_t square = popLsb(whitePawns); - uint64_t attacks = getPawnAttacks(square); - - attacksByPiece[WHITE_PAWN] |= attacks; - attackedBy2[WHITE] |= (attacks & attacksByColor[WHITE]); - attacksByColor[WHITE] |= attacks; - attacksFrom[square] |= attacks; - } - - uint64_t whiteKnights = bitboard.getPieceBoard(WHITE_KNIGHT); - while (whiteKnights) { - uint8_t square = popLsb(whiteKnights); - uint64_t attacks = getKnightAttacks(square); - - attacksByPiece[WHITE_KNIGHT] |= attacks; - attackedBy2[WHITE] |= (attacks & attacksByColor[WHITE]); - attacksByColor[WHITE] |= attacks; - attacksFrom[square] |= attacks; - } - - uint64_t whiteBishops = bitboard.getPieceBoard(WHITE_BISHOP); - while (whiteBishops) { - uint8_t square = popLsb(whiteBishops); - uint64_t attacks = bitboard.getBishopAttacks(square); - - attacksByPiece[WHITE_BISHOP] |= attacks; - attackedBy2[WHITE] |= (attacks & attacksByColor[WHITE]); - attacksByColor[WHITE] |= attacks; - attacksFrom[square] |= attacks; - } - - uint64_t whiteRooks = bitboard.getPieceBoard(WHITE_ROOK); - while (whiteRooks) { - uint8_t square = popLsb(whiteRooks); - uint64_t attacks = bitboard.getRookAttacks(square); - - attacksByPiece[WHITE_ROOK] |= attacks; - attackedBy2[WHITE] |= (attacks & attacksByColor[WHITE]); - attacksByColor[WHITE] |= attacks; - attacksFrom[square] |= attacks; - } - - uint64_t whiteQueens = bitboard.getPieceBoard(WHITE_QUEEN); - while (whiteQueens) { - uint8_t square = popLsb(whiteQueens); - uint64_t attacks = bitboard.getQueenAttacks(square); - - attacksByPiece[WHITE_QUEEN] |= attacks; - attackedBy2[WHITE] |= (attacks & attacksByColor[WHITE]); - attacksByColor[WHITE] |= attacks; - attacksFrom[square] |= attacks; - } - - { - uint64_t whiteKing = bitboard.getPieceBoard(WHITE_KING); - uint8_t square = popLsb(whiteKing); - uint64_t attacks = getKingAttacks(square); - - attacksByPiece[WHITE_KING] |= attacks; - attacksByColor[WHITE] |= attacks; - attacksFrom[square] |= attacks; - } - - uint64_t blackPawns = bitboard.getPieceBoard(BLACK_PAWN); - while (blackPawns) { - uint8_t square = popLsb(blackPawns); - uint64_t attacks = getPawnAttacks(square); - - attacksByPiece[BLACK_PAWN] |= attacks; - attackedBy2[BLACK] |= (attacks & attacksByColor[BLACK]); - attacksByColor[BLACK] |= attacks; - attacksFrom[square] |= attacks; - } - - uint64_t blackKnights = bitboard.getPieceBoard(BLACK_KNIGHT); - while (blackKnights) { - uint8_t square = popLsb(blackKnights); - uint64_t attacks = getKnightAttacks(square); - - attacksByPiece[BLACK_KNIGHT] |= attacks; - attackedBy2[BLACK] |= (attacks & attacksByColor[BLACK]); - attacksByColor[BLACK] |= attacks; - attacksFrom[square] |= attacks; - } - - uint64_t blackBishops = bitboard.getPieceBoard(BLACK_BISHOP); - while (blackBishops) { - uint8_t square = popLsb(blackBishops); - uint64_t attacks = bitboard.getBishopAttacks(square); - - attacksByPiece[BLACK_BISHOP] |= attacks; - attackedBy2[BLACK] |= (attacks & attacksByColor[BLACK]); - attacksByColor[BLACK] |= attacks; - attacksFrom[square] |= attacks; - } - - uint64_t blackRooks = bitboard.getPieceBoard(BLACK_ROOK); - while (blackRooks) { - uint8_t square = popLsb(blackRooks); - uint64_t attacks = bitboard.getRookAttacks(square); - - attacksByPiece[BLACK_ROOK] |= attacks; - attackedBy2[BLACK] |= (attacks & attacksByColor[BLACK]); - attacksByColor[BLACK] |= attacks; - attacksFrom[square] |= attacks; - } - - uint64_t blackQueens = bitboard.getPieceBoard(BLACK_QUEEN); - while (blackQueens) { - uint8_t square = popLsb(blackQueens); - uint64_t attacks = bitboard.getQueenAttacks(square); - - attacksByPiece[BLACK_QUEEN] |= attacks; - attackedBy2[BLACK] |= (attacks & attacksByColor[BLACK]); - attacksByColor[BLACK] |= attacks; - attacksFrom[square] |= attacks; - } - - { - uint64_t blackKing = bitboard.getPieceBoard(BLACK_KING); - uint8_t square = popLsb(blackKing); - uint64_t attacks = getKingAttacks(square); - - attacksByPiece[BLACK_KING] |= attacks; - attacksByColor[BLACK] |= attacks; - attacksFrom[square] |= attacks; - } -} - -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 Evaluation::getPhase() { - int phase = totalPhase; - - phase -= (bitboard.getMaterialCount() + bitboard.getMaterialCount()) - * knightPhase; - phase -= (bitboard.getMaterialCount() + bitboard.getMaterialCount()) - * bishopPhase; - phase -= (bitboard.getMaterialCount() + bitboard.getMaterialCount()) * - rookPhase; - phase -= (bitboard.getMaterialCount() + bitboard.getMaterialCount()) * - queenPhase; - - return (phase * 256 + (totalPhase / 2)) / totalPhase; -} - -void Evaluation::addMobilityScoreForPiece(PieceType pieceType, int mobility) { - switch (pieceType) { - case WHITE_KNIGHT: - whiteMidgameScore += mobility * getEvalValue(MIDGAME_KNIGHT_MOBILITY); - whiteEndgameScore += mobility * getEvalValue(ENDGAME_KNIGHT_MOBILITY); - break; - case BLACK_KNIGHT: - blackMidgameScore += mobility * getEvalValue(MIDGAME_KNIGHT_MOBILITY); - blackEndgameScore += mobility * getEvalValue(ENDGAME_KNIGHT_MOBILITY); - break; - case WHITE_BISHOP: - whiteMidgameScore += mobility * getEvalValue(MIDGAME_BISHOP_MOBILITY); - whiteEndgameScore += mobility * getEvalValue(ENDGAME_BISHOP_MOBILITY); - break; - case BLACK_BISHOP: - blackMidgameScore += mobility * getEvalValue(MIDGAME_BISHOP_MOBILITY); - blackEndgameScore += mobility * getEvalValue(ENDGAME_BISHOP_MOBILITY); - break; - case WHITE_ROOK: - whiteMidgameScore += mobility * getEvalValue(MIDGAME_ROOK_MOBILITY); - whiteEndgameScore += mobility * getEvalValue(ENDGAME_ROOK_MOBILITY); - break; - case BLACK_ROOK: - blackMidgameScore += mobility * getEvalValue(MIDGAME_ROOK_MOBILITY); - blackEndgameScore += mobility * getEvalValue(ENDGAME_ROOK_MOBILITY); - break; - case WHITE_QUEEN: - whiteMidgameScore += mobility * getEvalValue(MIDGAME_QUEEN_MOBILITY); - whiteEndgameScore += mobility * getEvalValue(ENDGAME_QUEEN_MOBILITY); - break; - case BLACK_QUEEN: - blackMidgameScore += mobility * getEvalValue(MIDGAME_QUEEN_MOBILITY); - blackEndgameScore += mobility * getEvalValue(ENDGAME_QUEEN_MOBILITY); - break; - } -} - -void Evaluation::addKingAttackScore(PieceType pieceType, int attackCount) { - switch (pieceType) { - case WHITE_PAWN: - blackMidgameScore += attackCount * getEvalValue(MIDGAME_KING_ATTACK_PAWN_PENALTY); - blackEndgameScore += attackCount * getEvalValue(ENDGAME_KING_ATTACK_PAWN_PENALTY); - break; - case BLACK_PAWN: - whiteMidgameScore += attackCount * getEvalValue(MIDGAME_KING_ATTACK_PAWN_PENALTY); - whiteEndgameScore += attackCount * getEvalValue(ENDGAME_KING_ATTACK_PAWN_PENALTY); - break; - case WHITE_KNIGHT: - blackMidgameScore += attackCount * getEvalValue(MIDGAME_KING_ATTACK_KNIGHT_PENALTY); - blackEndgameScore += attackCount * getEvalValue(ENDGAME_KING_ATTACK_KNIGHT_PENALTY); - break; - case BLACK_KNIGHT: - whiteMidgameScore += attackCount * getEvalValue(MIDGAME_KING_ATTACK_KNIGHT_PENALTY); - whiteEndgameScore += attackCount * getEvalValue(ENDGAME_KING_ATTACK_KNIGHT_PENALTY); - break; - case WHITE_BISHOP: - blackMidgameScore += attackCount * getEvalValue(MIDGAME_KING_ATTACK_BISHOP_PENALTY); - blackEndgameScore += attackCount * getEvalValue(ENDGAME_KING_ATTACK_BISHOP_PENALTY); - break; - case BLACK_BISHOP: - whiteMidgameScore += attackCount * getEvalValue(MIDGAME_KING_ATTACK_BISHOP_PENALTY); - whiteEndgameScore += attackCount * getEvalValue(ENDGAME_KING_ATTACK_BISHOP_PENALTY); - break; - case WHITE_ROOK: - blackMidgameScore += attackCount * getEvalValue(MIDGAME_KING_ATTACK_ROOK_PENALTY); - blackEndgameScore += attackCount * getEvalValue(ENDGAME_KING_ATTACK_ROOK_PENALTY); - break; - case BLACK_ROOK: - whiteMidgameScore += attackCount * getEvalValue(MIDGAME_KING_ATTACK_ROOK_PENALTY); - whiteEndgameScore += attackCount * getEvalValue(ENDGAME_KING_ATTACK_ROOK_PENALTY); - break; - case WHITE_QUEEN: - blackMidgameScore += attackCount * getEvalValue(MIDGAME_KING_ATTACK_QUEEN_PENALTY); - blackEndgameScore += attackCount * getEvalValue(ENDGAME_KING_ATTACK_QUEEN_PENALTY); - break; - case BLACK_QUEEN: - whiteMidgameScore += attackCount * getEvalValue(MIDGAME_KING_ATTACK_QUEEN_PENALTY); - whiteEndgameScore += attackCount * getEvalValue(ENDGAME_KING_ATTACK_QUEEN_PENALTY); - break; - } -} - -template -void Evaluation::evaluatePieces() { - uint64_t colorBoard = bitboard.getColorBoard(); - int8_t blackKingSquare = bitscanForward(bitboard.getPieceBoard(BLACK_KING)); - int8_t whiteKingSquare = bitscanForward(bitboard.getPieceBoard(WHITE_KING)); - uint64_t blackKingAttacks = attacksFrom[blackKingSquare]; - uint64_t whiteKingAttacks = attacksFrom[whiteKingSquare]; - - while (colorBoard) { - uint8_t index = popLsb(colorBoard); - PieceType pieceType = bitboard.getPieceOnSquare(index); - - // Mobility - if (isNotPawnOrKing(pieceType)) { - uint64_t mobilitySquares = attacksFrom[index]; - - if (color == WHITE) { - uint64_t weakSquares = attackedBy2[BLACK] & ~attackedBy2[WHITE]; - - // Exclude own pieces and attacks by opponent pawns - mobilitySquares &= ~(bitboard.getColorBoard() | attacksByPiece[BLACK_PAWN]); - - // If pieceType == queen, exclude tiles attacked by opponent bishop, knight and rook - if (pieceType == WHITE_QUEEN) { - mobilitySquares &= ~( - attacksByPiece[BLACK_BISHOP] | attacksByPiece[BLACK_KNIGHT] | - attacksByPiece[BLACK_ROOK]); - } - - // If pieceType == rook, exclude tiles attacked by opponent bishop and knight - if (pieceType == WHITE_ROOK) { - mobilitySquares &= ~( - attacksByPiece[BLACK_BISHOP] | attacksByPiece[BLACK_KNIGHT]); - } - - mobilitySquares &= ~weakSquares; - } else { - uint64_t weakSquares = attackedBy2[WHITE] & ~attackedBy2[BLACK]; - - // Exclude own pieces and attacks by opponent pawns - mobilitySquares &= ~(bitboard.getColorBoard() | attacksByPiece[WHITE_PAWN]); - - // If pieceType == queen, exclude tiles attacked by opponent bishop, knight and rook - if (pieceType == BLACK_QUEEN) { - mobilitySquares &= ~( - attacksByPiece[WHITE_BISHOP] | attacksByPiece[WHITE_KNIGHT] | - attacksByPiece[WHITE_ROOK]); - } - - // If pieceType == rook, exclude tiles attacked by opponent bishop and knight - if (pieceType == BLACK_ROOK) { - mobilitySquares &= ~( - attacksByPiece[WHITE_BISHOP] | attacksByPiece[WHITE_KNIGHT]); - } - - mobilitySquares &= ~weakSquares; - } - - uint8_t mobility = popcnt(mobilitySquares); - addMobilityScoreForPiece(pieceType, mobility); - } - - // King safety - Attacks around king - if (!isKing(pieceType)) { - uint64_t attackSquares = attacksFrom[index]; - uint64_t opponentKingAttacks = color == WHITE ? blackKingAttacks : whiteKingAttacks; - uint64_t attacksAroundKing = attackSquares & opponentKingAttacks; - uint8_t attackCount = popcnt(attacksAroundKing); - - addKingAttackScore(pieceType, attackCount); - } - - // Other King safety - if (isKing(pieceType)) { - if (color == WHITE) { - // Pawn Shield - uint64_t kingBB = bitboard.getPieceBoard(WHITE_KING); - uint64_t pawnBB = bitboard.getPieceBoard(WHITE_PAWN); - uint64_t pawnShieldMask = nortOne(kingBB) | noEaOne(kingBB) | noWeOne(kingBB); - pawnShieldMask |= nortOne(pawnShieldMask); - uint64_t pawnShield = pawnBB & pawnShieldMask; - uint8_t pawnShieldCount = std::min(popcnt(pawnShield), 3ULL); - - whiteMidgameScore += getEvalValue(MIDGAME_PAWN_SHIELD) * pawnShieldCount; - whiteEndgameScore += getEvalValue(ENDGAME_PAWN_SHIELD) * pawnShieldCount; - - // Virtual mobility - Get queen attacks from king position, with only occupied squares by - // own pieces. We also ignore the squares around the king. - uint64_t virtualMobilitySquares = - bitboard.getQueenAttacks(index, bitboard.getColorBoard()) & - ~(attacksFrom[index] | bitboard.getColorBoard()); - whiteMidgameScore += - popcnt(virtualMobilitySquares) * getEvalValue( - MIDGAME_KING_VIRTUAL_MOBILITY_PENALTY); - whiteEndgameScore += - popcnt(virtualMobilitySquares) * getEvalValue( - ENDGAME_KING_VIRTUAL_MOBILITY_PENALTY); - } else { - // Pawn Shield - uint64_t kingBB = bitboard.getPieceBoard(BLACK_KING); - uint64_t pawnBB = bitboard.getPieceBoard(BLACK_PAWN); - uint64_t pawnShieldMask = soutOne(kingBB) | soEaOne(kingBB) | soWeOne(kingBB); - pawnShieldMask |= soutOne(pawnShieldMask); - uint64_t pawnShield = pawnBB & pawnShieldMask; - uint8_t pawnShieldCount = std::min(popcnt(pawnShield), 3ULL); - - blackMidgameScore += getEvalValue(MIDGAME_PAWN_SHIELD) * pawnShieldCount; - blackEndgameScore += getEvalValue(ENDGAME_PAWN_SHIELD) * pawnShieldCount; - - // Virtual mobility - Get queen attacks from king position, with only occupied squares by - // own pieces. We also ignore the squares around the king. - uint64_t virtualMobilitySquares = - bitboard.getQueenAttacks(index, bitboard.getColorBoard()) & - ~(attacksFrom[index] | bitboard.getColorBoard()); - blackMidgameScore += - popcnt(virtualMobilitySquares) * getEvalValue( - MIDGAME_KING_VIRTUAL_MOBILITY_PENALTY); - blackEndgameScore += - popcnt(virtualMobilitySquares) * getEvalValue( - ENDGAME_KING_VIRTUAL_MOBILITY_PENALTY); - } - } - - // Per piece evaluation - if (isPawn(pieceType)) { - // Doubled pawn - uint64_t pawnBB = bitboard.getPieceBoard(pieceType); - Direction direction = color == WHITE ? NORTH : SOUTH; - Direction oppositeDirection = color == WHITE ? SOUTH : NORTH; - uint64_t frontMask = getRayAttack(index, direction); - uint64_t behindMask = getRayAttack(index, oppositeDirection); - uint64_t doubledPawns = pawnBB & frontMask; - - if (doubledPawns) { - if (color == WHITE) { - whiteMidgameScore += getEvalValue(MIDGAME_DOUBLED_PAWN_PENALTY); - whiteEndgameScore += getEvalValue(ENDGAME_DOUBLED_PAWN_PENALTY); - } else { - blackMidgameScore += getEvalValue(MIDGAME_DOUBLED_PAWN_PENALTY); - blackEndgameScore += getEvalValue(ENDGAME_DOUBLED_PAWN_PENALTY); - } - } - - // Passed pawn - if (bitboard.isPassedPawn(index)) { - if (color == WHITE) { - whiteMidgameScore += getEvalValue(MIDGAME_PASSED_PAWN); - whiteEndgameScore += getEvalValue(ENDGAME_PASSED_PAWN); - } else { - blackMidgameScore += getEvalValue(MIDGAME_PASSED_PAWN); - blackEndgameScore += getEvalValue(ENDGAME_PASSED_PAWN); - } - - // Tarrasch rule - if (color == WHITE) { - // Rook in front of own passed pawn penalty - if (frontMask & bitboard.getPieceBoard(WHITE_ROOK)) { - whiteMidgameScore += getEvalValue(MIDGAME_TARRASCH_OWN_ROOK_PENALTY); - whiteEndgameScore += getEvalValue(ENDGAME_TARRASCH_OWN_ROOK_PENALTY); - } - - // Rook behind own passed pawn bonus - if (behindMask & bitboard.getPieceBoard(WHITE_ROOK)) { - whiteMidgameScore += getEvalValue(MIDGAME_TARRASCH_OWN_ROOK_DEFEND); - whiteEndgameScore += getEvalValue(ENDGAME_TARRASCH_OWN_ROOK_DEFEND); - } - - // Opponent rook behind own passed pawn penalty - if (behindMask & bitboard.getPieceBoard(BLACK_ROOK)) { - whiteMidgameScore += getEvalValue(MIDGAME_TARRASCH_OPPONENT_ROOK_PENALTY); - whiteEndgameScore += getEvalValue(ENDGAME_TARRASCH_OPPONENT_ROOK_PENALTY); - } - } else { - // Rook in front of own passed pawn penalty - if (frontMask & bitboard.getPieceBoard(BLACK_ROOK)) { - blackMidgameScore += getEvalValue(MIDGAME_TARRASCH_OWN_ROOK_PENALTY); - blackEndgameScore += getEvalValue(ENDGAME_TARRASCH_OWN_ROOK_PENALTY); - } - - // Rook behind own passed pawn bonus - if (behindMask & bitboard.getPieceBoard(BLACK_ROOK)) { - blackMidgameScore += getEvalValue(MIDGAME_TARRASCH_OWN_ROOK_DEFEND); - blackEndgameScore += getEvalValue(ENDGAME_TARRASCH_OWN_ROOK_DEFEND); - } - - // Opponent rook behind own passed pawn penalty - if (behindMask & bitboard.getPieceBoard(WHITE_ROOK)) { - blackMidgameScore += getEvalValue(MIDGAME_TARRASCH_OPPONENT_ROOK_PENALTY); - blackEndgameScore += getEvalValue(ENDGAME_TARRASCH_OPPONENT_ROOK_PENALTY); - } - } - } - - // Isolated pawn - if (bitboard.isIsolatedPawn(index)) { - if (color == WHITE) { - whiteMidgameScore += getEvalValue(MIDGAME_ISOLATED_PAWN_PENALTY); - whiteEndgameScore += getEvalValue(ENDGAME_ISOLATED_PAWN_PENALTY); - } else { - blackMidgameScore += getEvalValue(MIDGAME_ISOLATED_PAWN_PENALTY); - blackEndgameScore += getEvalValue(ENDGAME_ISOLATED_PAWN_PENALTY); - } - - if (bitboard.isSemiOpenFile(index)) { - if (color == WHITE) { - whiteMidgameScore += getEvalValue(MIDGAME_ISOLATED_SEMI_OPEN_PAWN_PENALTY); - whiteEndgameScore += getEvalValue(ENDGAME_ISOLATED_SEMI_OPEN_PAWN_PENALTY); - } else { - blackMidgameScore += getEvalValue(MIDGAME_ISOLATED_SEMI_OPEN_PAWN_PENALTY); - blackEndgameScore += getEvalValue(ENDGAME_ISOLATED_SEMI_OPEN_PAWN_PENALTY); - } - } - - if ((1ULL << index) & DE_FILE) { - if (color == WHITE) { - whiteMidgameScore += getEvalValue(MIDGAME_ISOLATED_CENTRAL_PAWN_PENALTY); - whiteEndgameScore += getEvalValue(ENDGAME_ISOLATED_CENTRAL_PAWN_PENALTY); - } else { - blackMidgameScore += getEvalValue(MIDGAME_ISOLATED_CENTRAL_PAWN_PENALTY); - blackEndgameScore += getEvalValue(ENDGAME_ISOLATED_CENTRAL_PAWN_PENALTY); - } - } - } - } - - // Knight eval - if (isKnight(pieceType)) { - // Penalize the knight's value for each missing pawn - uint64_t pawnBB = bitboard.getPieceBoard(color == WHITE ? WHITE_PAWN : BLACK_PAWN); - uint8_t pawnCount = popcnt(pawnBB); - - if (color == WHITE) { - whiteMidgameScore += getEvalValue(MIDGAME_KNIGHT_MISSING_PAWN_PENALTY) * ( - 8 - pawnCount); - whiteEndgameScore += getEvalValue(ENDGAME_KNIGHT_MISSING_PAWN_PENALTY) * ( - 8 - pawnCount); - } else { - blackMidgameScore += getEvalValue(MIDGAME_KNIGHT_MISSING_PAWN_PENALTY) * ( - 8 - pawnCount); - blackEndgameScore += getEvalValue(ENDGAME_KNIGHT_MISSING_PAWN_PENALTY) * ( - 8 - pawnCount); - } - - // Slight bonus for knights defended by a pawn - uint64_t pawnAttacks = attacksByPiece[color == WHITE ? WHITE_PAWN : BLACK_PAWN]; - - if ((1ULL << index) & pawnAttacks) { - if (color == WHITE) { - whiteMidgameScore += getEvalValue(MIDGAME_KNIGHT_DEFENDED_BY_PAWN); - whiteEndgameScore += getEvalValue(ENDGAME_KNIGHT_DEFENDED_BY_PAWN); - } else { - blackMidgameScore += getEvalValue(MIDGAME_KNIGHT_DEFENDED_BY_PAWN); - blackEndgameScore += getEvalValue(ENDGAME_KNIGHT_DEFENDED_BY_PAWN); - } - } - } - - // Bishop eval - if (isBishop(pieceType)) { - // Bad bishop - uint64_t bishopAttacks = attacksFrom[index]; - uint64_t pawnBB = bitboard.getPieceBoard(color == WHITE ? WHITE_PAWN : BLACK_PAWN); - uint64_t forwardMobility; - - if (color == WHITE) { - forwardMobility = getRayAttack(index, NORTH_WEST) | getRayAttack(index, NORTH_EAST); - } else { - forwardMobility = getRayAttack(index, SOUTH_WEST) | getRayAttack(index, SOUTH_EAST); - } - - bishopAttacks &= forwardMobility; - - // If one of our own pawns is on the same diagonal as the bishop and the bishop has <= 3 - // squares of mobility, it's a bad bishop - if (bishopAttacks & pawnBB) { - uint64_t bishopAttacksWithoutPawns = bishopAttacks & ~pawnBB; - uint64_t attackCount = popcnt(bishopAttacksWithoutPawns); - - if (attackCount <= 3) { - if (color == WHITE) { - whiteMidgameScore += getEvalValue(MIDGAME_BAD_BISHOP_PENALTY); - whiteEndgameScore += getEvalValue(ENDGAME_BAD_BISHOP_PENALTY); - } else { - blackMidgameScore += getEvalValue(MIDGAME_BAD_BISHOP_PENALTY); - blackEndgameScore += getEvalValue(ENDGAME_BAD_BISHOP_PENALTY); - } - } - } - - // Only one bishop (no bishop pair) - if (color == WHITE) { - if (bitboard.getMaterialCount() == 1) { - whiteMidgameScore += getEvalValue(MIDGAME_MISSING_BISHOP_PAIR_PENALTY); - whiteEndgameScore += getEvalValue(ENDGAME_MISSING_BISHOP_PAIR_PENALTY); - } - } else { - if (bitboard.getMaterialCount() == 1) { - blackMidgameScore += getEvalValue(MIDGAME_MISSING_BISHOP_PAIR_PENALTY); - blackEndgameScore += getEvalValue(ENDGAME_MISSING_BISHOP_PAIR_PENALTY); - } - } - - // Fianchetto - if (color == WHITE) { - if (index == G2 || index == B2) { - uint64_t fianchettoPattern = - nortOne(1ULL << index) | westOne(1ULL << index) | eastOne(1ULL << index); - uint64_t antiPattern = noWeOne(1ULL << index) | noEaOne(1ULL << index); - - if (popcnt(pawnBB & fianchettoPattern) == 3 && !(pawnBB & antiPattern)) { - whiteMidgameScore += getEvalValue(MIDGAME_BISHOP_FIANCHETTO); - whiteEndgameScore += getEvalValue(ENDGAME_BISHOP_FIANCHETTO); - } - } - } else { - if (index == G7 || index == B7) { - uint64_t fianchettoPattern = - soutOne(1ULL << index) | westOne(1ULL << index) | eastOne(1ULL << index); - uint64_t antiPattern = soWeOne(1ULL << index) | soEaOne(1ULL << index); - - if (popcnt(pawnBB & fianchettoPattern) == 3 && !(pawnBB & antiPattern)) { - blackMidgameScore += getEvalValue(MIDGAME_BISHOP_FIANCHETTO); - blackEndgameScore += getEvalValue(ENDGAME_BISHOP_FIANCHETTO); - } - } - } - } - - // Rook eval - if (isRook(pieceType)) { - // Increase in value as pawns disappear - uint64_t pawnBB = bitboard.getPieceBoard(color == WHITE ? WHITE_PAWN : BLACK_PAWN); - uint8_t pawnCount = popcnt(pawnBB); - - if (color == WHITE) { - whiteMidgameScore += getEvalValue(MIDGAME_ROOK_PAWN_COUNT) * (8 - pawnCount); - whiteEndgameScore += getEvalValue(ENDGAME_ROOK_PAWN_COUNT) * (8 - pawnCount); - } else { - blackMidgameScore += getEvalValue(MIDGAME_ROOK_PAWN_COUNT) * (8 - pawnCount); - blackEndgameScore += getEvalValue(ENDGAME_ROOK_PAWN_COUNT) * (8 - pawnCount); - } - - // Rook on open file - if (bitboard.isOpenFile(index)) { - if (color == WHITE) { - whiteMidgameScore += getEvalValue(MIDGAME_ROOK_ON_OPEN_FILE); - whiteEndgameScore += getEvalValue(ENDGAME_ROOK_ON_OPEN_FILE); - } else { - blackMidgameScore += getEvalValue(MIDGAME_ROOK_ON_OPEN_FILE); - blackEndgameScore += getEvalValue(ENDGAME_ROOK_ON_OPEN_FILE); - } - } else if (bitboard.isSemiOpenFile(index)) { - if (color == WHITE) { - whiteMidgameScore += getEvalValue(MIDGAME_ROOK_ON_SEMI_OPEN_FILE); - whiteEndgameScore += getEvalValue(ENDGAME_ROOK_ON_SEMI_OPEN_FILE); - } else { - blackMidgameScore += getEvalValue(MIDGAME_ROOK_ON_SEMI_OPEN_FILE); - blackEndgameScore += getEvalValue(ENDGAME_ROOK_ON_SEMI_OPEN_FILE); - } - } - - // Rook on 7th or 8th rank (or 2nd or 1st rank for black) - if (color == WHITE) { - if ((1ULL << index) & (RANK_8 | RANK_7)) { - whiteMidgameScore += getEvalValue(MIDGAME_ROOK_ON_7TH_RANK); - whiteEndgameScore += getEvalValue(ENDGAME_ROOK_ON_7TH_RANK); - } - } else { - if ((1ULL << index) & (RANK_1 | RANK_2)) { - blackMidgameScore += getEvalValue(MIDGAME_ROOK_ON_7TH_RANK); - blackEndgameScore += getEvalValue(ENDGAME_ROOK_ON_7TH_RANK); - } - } - - uint64_t file = bitboard.getFile(index); - uint64_t opponentQueens = bitboard.getPieceBoard( - color == WHITE ? BLACK_QUEEN : WHITE_QUEEN); - - // Bonus for rook with enemy queen on same file - if (file & opponentQueens) { - if (color == WHITE) { - whiteMidgameScore += getEvalValue(MIDGAME_ROOK_ON_QUEEN_FILE); - whiteEndgameScore += getEvalValue(ENDGAME_ROOK_ON_QUEEN_FILE); - } else { - blackMidgameScore += getEvalValue(MIDGAME_ROOK_ON_QUEEN_FILE); - blackEndgameScore += getEvalValue(ENDGAME_ROOK_ON_QUEEN_FILE); - } - } - } - - // Undefended minor pieces - if (isKnight(pieceType) || isBishop(pieceType)) { - if (!((1ULL << index) & attacksByColor[color])) { - // Penalize a minor piece for not being defended - if (color == WHITE) { - whiteMidgameScore += getEvalValue(MIDGAME_MINOR_PIECE_NOT_DEFENDED_PENALTY); - whiteEndgameScore += getEvalValue(ENDGAME_MINOR_PIECE_NOT_DEFENDED_PENALTY); - } else { - blackMidgameScore += getEvalValue(MIDGAME_MINOR_PIECE_NOT_DEFENDED_PENALTY); - blackEndgameScore += getEvalValue(ENDGAME_MINOR_PIECE_NOT_DEFENDED_PENALTY); - } - } - - uint64_t weakSquares; - - if (color == WHITE) { - weakSquares = attackedBy2[BLACK] & ~attackedBy2[WHITE]; - - if ((1ULL << index) & weakSquares) { - whiteMidgameScore += getEvalValue(MIDGAME_MINOR_PIECE_ON_WEAK_SQUARE_PENALTY); - whiteEndgameScore += getEvalValue(ENDGAME_MINOR_PIECE_ON_WEAK_SQUARE_PENALTY); - } - } else { - weakSquares = attackedBy2[WHITE] & ~attackedBy2[BLACK]; - - if ((1ULL << index) & weakSquares) { - blackMidgameScore += getEvalValue(MIDGAME_MINOR_PIECE_ON_WEAK_SQUARE_PENALTY); - blackEndgameScore += getEvalValue(ENDGAME_MINOR_PIECE_ON_WEAK_SQUARE_PENALTY); - } - } - } - } -} - -int Evaluation::evaluate() { - int phase = getPhase(); - int modifier = bitboard.getMovingColor() == WHITE ? 1 : -1; - - initEvalContext(bitboard); - - evaluateMaterial(); - evaluateMaterial(); - - evaluatePst(); - evaluatePst(); - - evaluatePieces(); - evaluatePieces(); - - int whiteScore = ((whiteMidgameScore * (256 - phase)) + (whiteEndgameScore * phase)) / 256; - int blackScore = ((blackMidgameScore * (256 - phase)) + (blackEndgameScore * phase)) / 256; - - return (whiteScore - blackScore) * modifier; -} - -template -void Evaluation::evaluateMaterial() { - if (color == WHITE) { - whiteMidgameScore += - bitboard.getMaterialCount() * getEvalValue(MIDGAME_PAWN_MATERIAL); - whiteEndgameScore += - bitboard.getMaterialCount() * getEvalValue(ENDGAME_PAWN_MATERIAL); - - whiteMidgameScore += - bitboard.getMaterialCount() * getEvalValue(MIDGAME_KNIGHT_MATERIAL); - whiteEndgameScore += - bitboard.getMaterialCount() * getEvalValue(ENDGAME_KNIGHT_MATERIAL); - - whiteMidgameScore += - bitboard.getMaterialCount() * getEvalValue(MIDGAME_BISHOP_MATERIAL); - whiteEndgameScore += - bitboard.getMaterialCount() * getEvalValue(ENDGAME_BISHOP_MATERIAL); - - whiteMidgameScore += - bitboard.getMaterialCount() * getEvalValue(MIDGAME_ROOK_MATERIAL); - whiteEndgameScore += - bitboard.getMaterialCount() * getEvalValue(ENDGAME_ROOK_MATERIAL); - - whiteMidgameScore += - bitboard.getMaterialCount() * getEvalValue(MIDGAME_QUEEN_MATERIAL); - whiteEndgameScore += - bitboard.getMaterialCount() * getEvalValue(ENDGAME_QUEEN_MATERIAL); - } else { - blackMidgameScore += - bitboard.getMaterialCount() * getEvalValue(MIDGAME_PAWN_MATERIAL); - blackEndgameScore += - bitboard.getMaterialCount() * getEvalValue(ENDGAME_PAWN_MATERIAL); - - blackMidgameScore += - bitboard.getMaterialCount() * getEvalValue(MIDGAME_KNIGHT_MATERIAL); - blackEndgameScore += - bitboard.getMaterialCount() * getEvalValue(ENDGAME_KNIGHT_MATERIAL); - - blackMidgameScore += - bitboard.getMaterialCount() * getEvalValue(MIDGAME_BISHOP_MATERIAL); - blackEndgameScore += - bitboard.getMaterialCount() * getEvalValue(ENDGAME_BISHOP_MATERIAL); - - blackMidgameScore += - bitboard.getMaterialCount() * getEvalValue(MIDGAME_ROOK_MATERIAL); - blackEndgameScore += - bitboard.getMaterialCount() * getEvalValue(ENDGAME_ROOK_MATERIAL); - - blackMidgameScore += - bitboard.getMaterialCount() * getEvalValue(MIDGAME_QUEEN_MATERIAL); - blackEndgameScore += - bitboard.getMaterialCount() * getEvalValue(ENDGAME_QUEEN_MATERIAL); - } -} - -template -void Evaluation::evaluatePst() { - if (color == WHITE) { - whiteMidgameScore += bitboard.getWhiteMidgamePst(); - whiteEndgameScore += bitboard.getWhiteEndgamePst(); - } else { - blackMidgameScore += bitboard.getBlackMidgamePst(); - blackEndgameScore += bitboard.getBlackEndgamePst(); - } -} -} // namespace Zagreus \ No newline at end of file diff --git a/src/evaluate.h b/src/evaluate.h deleted file mode 100644 index e4567261..00000000 --- a/src/evaluate.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#pragma once - -#include - -#include "bitboard.h" -#include "constants.h" -#include "features.h" - -namespace Zagreus { -enum TraceMetric { -}; - -class Evaluation { -public: - Evaluation(Bitboard& bitboard) - : bitboard(bitboard) { - } - - int evaluate(); - -private: - Bitboard& bitboard; - std::map traceMetrics{}; - - uint64_t attacksByPiece[PIECE_TYPES]{}; - uint64_t attacksByColor[COLORS]{}; - uint64_t attackedBy2[COLORS]{}; - uint64_t attacksFrom[SQUARES]{}; - - int whiteMidgameScore = 0; - int whiteEndgameScore = 0; - int blackMidgameScore = 0; - int blackEndgameScore = 0; - - int getPhase(); - - template - void evaluateMaterial(); - - template - void evaluatePst(); - - template - void evaluatePieces(); - - inline void addMobilityScoreForPiece(PieceType pieceType, int mobility); - - inline void addKingAttackScore(PieceType pieceType, int attackCount); - - void initEvalContext(Bitboard& bitboard); -}; -} // namespace Zagreus \ No newline at end of file diff --git a/src/features.cpp b/src/features.cpp deleted file mode 100644 index 46d104e4..00000000 --- a/src/features.cpp +++ /dev/null @@ -1,181 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023-2024 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#include "features.h" - -#include - -#include "pst.h" - -namespace Zagreus { -int evalValues[72] = {83, 96, 398, 349, 429, 356, 574, 561, 1063, 1054, 8, 2, 5, 1, 2, 4, 4, 5, 24, - -5, -6, 0, -19, 26, 4, 4, -15, 6, -24, 6, -15, -6, -8, -15, -16, 41, -3, -6, - -15, -4, -15, -7, -5, -11, 7, 2, -8, -1, -3, -29, -23, -27, 15, 8, -4, -5, 44, - 2, 15, 15, -28, 3, -2, 2, 19, 17, -9, -31, 13, -17, -29, -31,}; - - -int baseEvalValues[72] = { - 100, // MIDGAME_PAWN_MATERIAL - 100, // ENDGAME_PAWN_MATERIAL - 350, // MIDGAME_KNIGHT_MATERIAL - 350, // ENDGAME_KNIGHT_MATERIAL - 350, // MIDGAME_BISHOP_MATERIAL - 350, // ENDGAME_BISHOP_MATERIAL - 525, // MIDGAME_ROOK_MATERIAL - 525, // ENDGAME_ROOK_MATERIAL - 1000, // MIDGAME_QUEEN_MATERIAL - 1000, // ENDGAME_QUEEN_MATERIAL - 4, // MIDGAME_KNIGHT_MOBILITY - 2, // ENDGAME_KNIGHT_MOBILITY - 6, // MIDGAME_BISHOP_MOBILITY - 3, // ENDGAME_BISHOP_MOBILITY - 2, // MIDGAME_ROOK_MOBILITY - 5, // ENDGAME_ROOK_MOBILITY - 4, // MIDGAME_QUEEN_MOBILITY - 6, // ENDGAME_QUEEN_MOBILITY - 5, // MIDGAME_PAWN_SHIELD - 0, // ENDGAME_PAWN_SHIELD - -3, // MIDGAME_KING_VIRTUAL_MOBILITY_PENALTY - 0, // ENDGAME_KING_VIRTUAL_MOBILITY_PENALTY - -1, // MIDGAME_KING_ATTACK_PAWN_PENALTY - -1, // ENDGAME_KING_ATTACK_PAWN_PENALTY - -3, // MIDGAME_KING_ATTACK_KNIGHT_PENALTY - -5, // ENDGAME_KING_ATTACK_KNIGHT_PENALTY - -4, // MIDGAME_KING_ATTACK_BISHOP_PENALTY - -6, // ENDGAME_KING_ATTACK_BISHOP_PENALTY - -6, // MIDGAME_KING_ATTACK_ROOK_PENALTY - -8, // ENDGAME_KING_ATTACK_ROOK_PENALTY - -10, // MIDGAME_KING_ATTACK_QUEEN_PENALTY - -13, // ENDGAME_KING_ATTACK_QUEEN_PENALTY - -4, // MIDGAME_floatD_PAWN_PENALTY - -8, // ENDGAME_floatD_PAWN_PENALTY - 5, // MIDGAME_PASSED_PAWN - 10, // ENDGAME_PASSED_PAWN - -3, // MIDGAME_ISOLATED_SEMI_OPEN_PAWN_PENALTY - -6, // ENDGAME_ISOLATED_SEMI_OPEN_PAWN_PENALTY - -6, // MIDGAME_ISOLATED_PAWN_PENALTY - -10, // ENDGAME_ISOLATED_PAWN_PENALTY - -2, // MIDGAME_ISOLATED_CENTRAL_PAWN_PENALTY - -4, // ENDGAME_ISOLATED_CENTRAL_PAWN_PENALTY - -1, // MIDGAME_KNIGHT_MISSING_PAWN_PENALTY - -3, // ENDGAME_KNIGHT_MISSING_PAWN_PENALTY - 2, // MIDGAME_KNIGHT_DEFENDED_BY_PAWN - 2, // ENDGAME_KNIGHT_DEFENDED_BY_PAWN - -5, // MIDGAME_MINOR_PIECE_NOT_DEFENDED_PENALTY - -5, // ENDGAME_MINOR_PIECE_NOT_DEFENDED_PENALTY - -10, // MIDGAME_BAD_BISHOP_PENALTY - -15, // ENDGAME_BAD_BISHOP_PENALTY - -50, // MIDGAME_MISSING_BISHOP_PAIR_PENALTY - -50, // ENDGAME_MISSING_BISHOP_PAIR_PENALTY - 5, // MIDGAME_BISHOP_FIANCHETTO - 0, // ENDGAME_BISHOP_FIANCHETTO - 1, // MIDGAME_ROOK_PAWN_COUNT - 3, // ENDGAME_ROOK_PAWN_COUNT - 5, // MIDGAME_ROOK_ON_OPEN_FILE - 10, // ENDGAME_ROOK_ON_OPEN_FILE - 2, // MIDGAME_ROOK_ON_SEMI_OPEN_FILE - 4, // ENDGAME_ROOK_ON_SEMI_OPEN_FILE - 5, // MIDGAME_ROOK_ON_7TH_RANK - 8, // MIDGAME_ROOK_ON_7TH_RANK - 0, // MIDGAME_TARRASCH_OWN_ROOK_PENALTY - -10, // ENDGAME_TARRASCH_OWN_ROOK_PENALTY - 0, // MIDGAME_TARRASCH_OWN_ROOK_DEFEND - 10, // ENDGAME_TARRASCH_OWN_ROOK_DEFEND - 0, // MIDGAME_TARRASCH_OPPONENT_ROOK_PENALTY - -10, // ENDGAME_TARRASCH_OPPONENT_ROOK_PENALTY - 4, // MIDGAME_ROOK_ON_QUEEN_FILE - 4, // ENDGAME_ROOK_ON_QUEEN_FILE - -4, // MIDGAME_MINOR_PIECE_ON_WEAK_SQUARE_PENALTY - -4, // ENDGAME_MINOR_PIECE_ON_WEAK_SQUARE_PENALTY -}; - -void printEvalValues() { - for (int i = 0; i < getEvalFeatureSize(); i++) { - std::cout << evalFeatureNames[i] << ": " << evalValues[i] << std::endl; - } -} - -int getEvalValue(EvalFeature feature) { return evalValues[feature]; } - -int getEvalFeatureSize() { return sizeof(evalValues) / sizeof(evalValues[0]); } - -// Some sane default values for tuning -std::vector getBaseEvalValues() { - std::vector values; - - values.reserve(getEvalFeatureSize()); - for (int i = 0; i < getEvalFeatureSize(); i++) { - values.emplace_back(baseEvalValues[i]); - } - - for (int i : getBaseMidgameValues()) { - values.emplace_back(i); - } - - for (int i : getBaseEndgameValues()) { - values.emplace_back(i); - } - - return values; -} - -std::vector getEvalValues() { - std::vector values; - - values.reserve(getEvalFeatureSize()); - for (int i = 0; i < getEvalFeatureSize(); i++) { - values.emplace_back(evalValues[i]); - } - - for (int i : getMidgameValues()) { - values.emplace_back(i); - } - - for (int i : getEndgameValues()) { - values.emplace_back(i); - } - - return values; -} - -void updateEvalValues(std::vector& newValues) { - int evalFeatureSize = getEvalFeatureSize(); - size_t pstSize = getMidgameValues().size(); - - for (int i = 0; i < evalFeatureSize; i++) { - evalValues[i] = static_cast(newValues[i]); - } - - for (int i = 0; i < 6; i++) { - for (int8_t j = 0; j < 64; j++) { - int pieceIndex = i * 2; - - setMidgamePstValue(static_cast(pieceIndex), 63 - j, - static_cast(newValues[evalFeatureSize + i * 64 + j])); - setMidgamePstValue(static_cast(pieceIndex + 1), j, - static_cast(newValues[evalFeatureSize + i * 64 + j])); - setEndgamePstValue(static_cast(pieceIndex), 63 - j, - static_cast(newValues[evalFeatureSize + pstSize + i * 64 + j])); - setEndgamePstValue(static_cast(pieceIndex + 1), j, - static_cast(newValues[evalFeatureSize + pstSize + i * 64 + j])); - } - } -} -} // namespace Zagreus \ No newline at end of file diff --git a/src/features.h b/src/features.h deleted file mode 100644 index 9aba5dfe..00000000 --- a/src/features.h +++ /dev/null @@ -1,187 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023-2024 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#pragma once - -#include - -namespace Zagreus { -enum EvalFeature { - MIDGAME_PAWN_MATERIAL, - ENDGAME_PAWN_MATERIAL, - MIDGAME_KNIGHT_MATERIAL, - ENDGAME_KNIGHT_MATERIAL, - MIDGAME_BISHOP_MATERIAL, - ENDGAME_BISHOP_MATERIAL, - MIDGAME_ROOK_MATERIAL, - ENDGAME_ROOK_MATERIAL, - MIDGAME_QUEEN_MATERIAL, - ENDGAME_QUEEN_MATERIAL, - MIDGAME_KNIGHT_MOBILITY, - ENDGAME_KNIGHT_MOBILITY, - MIDGAME_BISHOP_MOBILITY, - ENDGAME_BISHOP_MOBILITY, - MIDGAME_ROOK_MOBILITY, - ENDGAME_ROOK_MOBILITY, - MIDGAME_QUEEN_MOBILITY, - ENDGAME_QUEEN_MOBILITY, - MIDGAME_PAWN_SHIELD, - ENDGAME_PAWN_SHIELD, - MIDGAME_KING_VIRTUAL_MOBILITY_PENALTY, - ENDGAME_KING_VIRTUAL_MOBILITY_PENALTY, - MIDGAME_KING_ATTACK_PAWN_PENALTY, - ENDGAME_KING_ATTACK_PAWN_PENALTY, - MIDGAME_KING_ATTACK_KNIGHT_PENALTY, - ENDGAME_KING_ATTACK_KNIGHT_PENALTY, - MIDGAME_KING_ATTACK_BISHOP_PENALTY, - ENDGAME_KING_ATTACK_BISHOP_PENALTY, - MIDGAME_KING_ATTACK_ROOK_PENALTY, - ENDGAME_KING_ATTACK_ROOK_PENALTY, - MIDGAME_KING_ATTACK_QUEEN_PENALTY, - ENDGAME_KING_ATTACK_QUEEN_PENALTY, - MIDGAME_DOUBLED_PAWN_PENALTY, - ENDGAME_DOUBLED_PAWN_PENALTY, - MIDGAME_PASSED_PAWN, - ENDGAME_PASSED_PAWN, - MIDGAME_ISOLATED_SEMI_OPEN_PAWN_PENALTY, - ENDGAME_ISOLATED_SEMI_OPEN_PAWN_PENALTY, - MIDGAME_ISOLATED_PAWN_PENALTY, - ENDGAME_ISOLATED_PAWN_PENALTY, - MIDGAME_ISOLATED_CENTRAL_PAWN_PENALTY, - ENDGAME_ISOLATED_CENTRAL_PAWN_PENALTY, - MIDGAME_KNIGHT_MISSING_PAWN_PENALTY, - ENDGAME_KNIGHT_MISSING_PAWN_PENALTY, - MIDGAME_KNIGHT_DEFENDED_BY_PAWN, - ENDGAME_KNIGHT_DEFENDED_BY_PAWN, - MIDGAME_MINOR_PIECE_NOT_DEFENDED_PENALTY, - ENDGAME_MINOR_PIECE_NOT_DEFENDED_PENALTY, - MIDGAME_BAD_BISHOP_PENALTY, - ENDGAME_BAD_BISHOP_PENALTY, - MIDGAME_MISSING_BISHOP_PAIR_PENALTY, - ENDGAME_MISSING_BISHOP_PAIR_PENALTY, - MIDGAME_BISHOP_FIANCHETTO, - ENDGAME_BISHOP_FIANCHETTO, - MIDGAME_ROOK_PAWN_COUNT, - ENDGAME_ROOK_PAWN_COUNT, - MIDGAME_ROOK_ON_OPEN_FILE, - ENDGAME_ROOK_ON_OPEN_FILE, - MIDGAME_ROOK_ON_SEMI_OPEN_FILE, - ENDGAME_ROOK_ON_SEMI_OPEN_FILE, - MIDGAME_ROOK_ON_7TH_RANK, - ENDGAME_ROOK_ON_7TH_RANK, - MIDGAME_TARRASCH_OWN_ROOK_PENALTY, - ENDGAME_TARRASCH_OWN_ROOK_PENALTY, - MIDGAME_TARRASCH_OWN_ROOK_DEFEND, - ENDGAME_TARRASCH_OWN_ROOK_DEFEND, - MIDGAME_TARRASCH_OPPONENT_ROOK_PENALTY, - ENDGAME_TARRASCH_OPPONENT_ROOK_PENALTY, - MIDGAME_ROOK_ON_QUEEN_FILE, - ENDGAME_ROOK_ON_QUEEN_FILE, - MIDGAME_MINOR_PIECE_ON_WEAK_SQUARE_PENALTY, - ENDGAME_MINOR_PIECE_ON_WEAK_SQUARE_PENALTY, -}; - -static std::vector evalFeatureNames = { - "MIDGAME_PAWN_MATERIAL", - "ENDGAME_PAWN_MATERIAL", - "MIDGAME_KNIGHT_MATERIAL", - "ENDGAME_KNIGHT_MATERIAL", - "MIDGAME_BISHOP_MATERIAL", - "ENDGAME_BISHOP_MATERIAL", - "MIDGAME_ROOK_MATERIAL", - "ENDGAME_ROOK_MATERIAL", - "MIDGAME_QUEEN_MATERIAL", - "ENDGAME_QUEEN_MATERIAL", - "MIDGAME_KNIGHT_MOBILITY", - "ENDGAME_KNIGHT_MOBILITY", - "MIDGAME_BISHOP_MOBILITY", - "ENDGAME_BISHOP_MOBILITY", - "MIDGAME_ROOK_MOBILITY", - "ENDGAME_ROOK_MOBILITY", - "MIDGAME_QUEEN_MOBILITY", - "ENDGAME_QUEEN_MOBILITY", - "MIDGAME_PAWN_SHIELD", - "ENDGAME_PAWN_SHIELD", - "MIDGAME_KING_VIRTUAL_MOBILITY_PENALTY", - "ENDGAME_KING_VIRTUAL_MOBILITY_PENALTY", - "MIDGAME_KING_ATTACK_PAWN_PENALTY", - "ENDGAME_KING_ATTACK_PAWN_PENALTY", - "MIDGAME_KING_ATTACK_KNIGHT_PENALTY", - "ENDGAME_KING_ATTACK_KNIGHT_PENALTY", - "MIDGAME_KING_ATTACK_BISHOP_PENALTY", - "ENDGAME_KING_ATTACK_BISHOP_PENALTY", - "MIDGAME_KING_ATTACK_ROOK_PENALTY", - "ENDGAME_KING_ATTACK_ROOK_PENALTY", - "MIDGAME_KING_ATTACK_QUEEN_PENALTY", - "ENDGAME_KING_ATTACK_QUEEN_PENALTY", - "MIDGAME_DOUBLED_PAWN_PENALTY", - "ENDGAME_DOUBLED_PAWN_PENALTY", - "MIDGAME_PASSED_PAWN", - "ENDGAME_PASSED_PAWN", - "MIDGAME_ISOLATED_SEMI_OPEN_PAWN", - "ENDGAME_ISOLATED_SEMI_OPEN_PAWN", - "MIDGAME_ISOLATED_PAWN", - "ENDGAME_ISOLATED_PAWN", - "MIDGAME_ISOLATED_CENTRAL_PAWN_PENALTY", - "ENDGAME_ISOLATED_CENTRAL_PAWN_PENALTY", - "MIDGAME_KNIGHT_MISSING_PAWN_PENALTY", - "ENDGAME_KNIGHT_MISSING_PAWN_PENALTY", - "MIDGAME_KNIGHT_DEFENDED_BY_PAWN", - "ENDGAME_KNIGHT_DEFENDED_BY_PAWN", - "MIDGAME_MINOR_PIECE_NOT_DEFENDED_PENALTY", - "ENDGAME_MINOR_PIECE_NOT_DEFENDED_PENALTY", - "MIDGAME_BAD_BISHOP_PENALTY", - "ENDGAME_BAD_BISHOP_PENALTY", - "MIDGAME_MISSING_BISHOP_PAIR_PENALTY", - "ENDGAME_MISSING_BISHOP_PAIR_PENALTY", - "MIDGAME_BISHOP_FIANCHETTO", - "ENDGAME_BISHOP_FIANCHETTO", - "MIDGAME_ROOK_PAWN_COUNT", - "ENDGAME_ROOK_PAWN_COUNT", - "MIDGAME_ROOK_ON_OPEN_FILE", - "ENDGAME_ROOK_ON_OPEN_FILE", - "MIDGAME_ROOK_ON_SEMI_OPEN_FILE", - "ENDGAME_ROOK_ON_SEMI_OPEN_FILE", - "MIDGAME_ROOK_ON_7TH_RANK", - "MIDGAME_ROOK_ON_7TH_RANK", - "MIDGAME_TARRASCH_OWN_ROOK_PENALTY", - "ENDGAME_TARRASCH_OWN_ROOK_PENALTY", - "MIDGAME_TARRASCH_OWN_ROOK_DEFEND", - "ENDGAME_TARRASCH_OWN_ROOK_DEFEND", - "MIDGAME_TARRASCH_OPPONENT_ROOK_PENALTY", - "ENDGAME_TARRASCH_OPPONENT_ROOK_PENALTY", - "MIDGAME_ROOK_ON_QUEEN_FILE", - "ENDGAME_ROOK_ON_QUEEN_FILE", - "MIDGAME_MINOR_PIECE_ON_WEAK_SQUARE_PENALTY", - "ENDGAME_MINOR_PIECE_ON_WEAK_SQUARE_PENALTY", -}; - -void printEvalValues(); - -int getEvalValue(EvalFeature feature); - -int getEvalFeatureSize(); - -std::vector getEvalValues(); - -std::vector getBaseEvalValues(); - -void updateEvalValues(std::vector& newValues); -} // namespace Zagreus \ No newline at end of file diff --git a/src/magics.cpp b/src/magics.cpp deleted file mode 100644 index 1ca7e4df..00000000 --- a/src/magics.cpp +++ /dev/null @@ -1,450 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a chess engine that supports the UCI protocol - Copyright (C) 2023 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#include "magics.h" - -#include -#include -#include -#include - -// Code for magic generation https://www.chessprogramming.org/Looking_for_Magics -namespace Zagreus { -std::random_device rd; -std::mt19937_64 gen(rd()); -std::uniform_int_distribution dis; - -auto rookMagics = new uint64_t[64]{}; -auto bishopMagics = new uint64_t[64]{}; - -auto bishop_masks = new uint64_t[64]{}; -auto rook_masks = new uint64_t[64]{}; - -auto bishop_attacks = new uint64_t[64][512]{}; -auto rook_attacks = new uint64_t[64][4096]{}; - -uint64_t getRookMagic(int sq) { return rookMagics[sq]; } - -uint64_t getBishopMagic(int sq) { return bishopMagics[sq]; } - -uint64_t getRookMask(int sq) { return rook_masks[sq]; } - -uint64_t getBishopMask(int sq) { return bishop_masks[sq]; } - -uint64_t getRookMagicAttacks(int sq, uint64_t index) { return rook_attacks[sq][index]; } - -uint64_t getBishopMagicAttacks(int sq, uint64_t index) { return bishop_attacks[sq][index]; } - -uint64_t random_uint64_t_fewbits() { return dis(gen) & dis(gen) & dis(gen); } - -int count_1s(uint64_t b) { - int r; - for (r = 0; b; r++, b &= b - 1); - return r; -} - -const int BitTable[64] = {63, 30, 3, 32, 25, 41, 22, 33, 15, 50, 42, 13, 11, 53, 19, 34, - 61, 29, 2, 51, 21, 43, 45, 10, 18, 47, 1, 54, 9, 57, 0, 35, - 62, 31, 40, 4, 49, 5, 52, 26, 60, 6, 23, 44, 46, 27, 56, 16, - 7, 39, 48, 24, 59, 14, 12, 55, 38, 28, 58, 20, 37, 17, 36, 8}; - -int pop_1st_bit(uint64_t* bb) { - uint64_t b = *bb ^ (*bb - 1); - unsigned int fold = static_cast((b & 0xffffffff) ^ (b >> 32)); - *bb &= (*bb - 1); - return BitTable[(fold * 0x783a9b23) >> 26]; -} - -uint64_t index_to_uint64_t(int index, int bits, uint64_t m) { - int i, j; - uint64_t result = 0ULL; - for (i = 0; i < bits; i++) { - j = pop_1st_bit(&m); - if (index & (1 << i)) result |= (1ULL << j); - } - return result; -} - -uint64_t rmask(int sq) { - uint64_t result = 0ULL; - int rk = sq / 8, fl = sq % 8, r, f; - for (r = rk + 1; r <= 6; r++) result |= (1ULL << (fl + r * 8)); - for (r = rk - 1; r >= 1; r--) result |= (1ULL << (fl + r * 8)); - for (f = fl + 1; f <= 6; f++) result |= (1ULL << (f + rk * 8)); - for (f = fl - 1; f >= 1; f--) result |= (1ULL << (f + rk * 8)); - return result; -} - -uint64_t bmask(int sq) { - uint64_t result = 0ULL; - int rk = sq / 8, fl = sq % 8, r, f; - for (r = rk + 1, f = fl + 1; r <= 6 && f <= 6; r++, f++) result |= (1ULL << (f + r * 8)); - for (r = rk + 1, f = fl - 1; r <= 6 && f >= 1; r++, f--) result |= (1ULL << (f + r * 8)); - for (r = rk - 1, f = fl + 1; r >= 1 && f <= 6; r--, f++) result |= (1ULL << (f + r * 8)); - for (r = rk - 1, f = fl - 1; r >= 1 && f >= 1; r--, f--) result |= (1ULL << (f + r * 8)); - return result; -} - -uint64_t ratt(int sq, uint64_t block) { - uint64_t result = 0ULL; - int rk = sq / 8, fl = sq % 8, r, f; - for (r = rk + 1; r <= 7; r++) { - result |= (1ULL << (fl + r * 8)); - if (block & (1ULL << (fl + r * 8))) break; - } - for (r = rk - 1; r >= 0; r--) { - result |= (1ULL << (fl + r * 8)); - if (block & (1ULL << (fl + r * 8))) break; - } - for (f = fl + 1; f <= 7; f++) { - result |= (1ULL << (f + rk * 8)); - if (block & (1ULL << (f + rk * 8))) break; - } - for (f = fl - 1; f >= 0; f--) { - result |= (1ULL << (f + rk * 8)); - if (block & (1ULL << (f + rk * 8))) break; - } - return result; -} - -uint64_t batt(int sq, uint64_t block) { - uint64_t result = 0ULL; - int rk = sq / 8, fl = sq % 8, r, f; - for (r = rk + 1, f = fl + 1; r <= 7 && f <= 7; r++, f++) { - result |= (1ULL << (f + r * 8)); - if (block & (1ULL << (f + r * 8))) break; - } - for (r = rk + 1, f = fl - 1; r <= 7 && f >= 0; r++, f--) { - result |= (1ULL << (f + r * 8)); - if (block & (1ULL << (f + r * 8))) break; - } - for (r = rk - 1, f = fl + 1; r >= 0 && f <= 7; r--, f++) { - result |= (1ULL << (f + r * 8)); - if (block & (1ULL << (f + r * 8))) break; - } - for (r = rk - 1, f = fl - 1; r >= 0 && f >= 0; r--, f--) { - result |= (1ULL << (f + r * 8)); - if (block & (1ULL << (f + r * 8))) break; - } - return result; -} - -int transform(uint64_t b, uint64_t magic, int bits) { -#if defined(USE_32_BIT_MULTIPLICATIONS) - return (unsigned)((int)b * (int)magic ^ (int)(b >> 32) * (int)(magic >> 32)) >> (32 - bits); -#else - return static_cast((b * magic) >> (64 - bits)); -#endif -} - -uint64_t find_magic(int sq, int m, int bishop) { - uint64_t mask, b[4096], a[4096], used[4096], magic; - int i, j, k, n, fail; - - mask = bishop ? bmask(sq) : rmask(sq); - n = count_1s(mask); - - for (i = 0; i < (1 << n); i++) { - b[i] = index_to_uint64_t(i, n, mask); - a[i] = bishop ? batt(sq, b[i]) : ratt(sq, b[i]); - } - for (k = 0; k < 100000000; k++) { - magic = random_uint64_t_fewbits(); - if (count_1s((mask * magic) & 0xFF00000000000000ULL) < 6) continue; - for (i = 0; i < 4096; i++) used[i] = 0ULL; - for (i = 0, fail = 0; !fail && i < (1 << n); i++) { - j = transform(b[i], magic, m); - if (used[j] == 0ULL) - used[j] = a[i]; - else if (used[j] != a[i]) - fail = 1; - } - if (!fail) return magic; - } - printf("***Failed***\n"); - return 0ULL; -} - -int count_bits(uint64_t bitboard) { - // bit size - int count = 0; - - // pop bits untill bitboard is empty - while (bitboard) { - // increment size - count++; - - // consecutively reset least significant 1st bit - bitboard &= bitboard - 1; - } - - // return bit size - return count; -} - -// get index of LS1B in bitboard -int get_ls1b_index(uint64_t bitboard) { - // make sure bitboard is not empty - if (bitboard != 0) - // convert trailing zeros before LS1B to ones and size them - return count_bits((bitboard & -bitboard) - 1); - - // otherwise - // return illegal index - return -1; -} - -uint64_t set_occupancy(int index, int bits_in_mask, uint64_t attack_mask) { - // occupancy map - uint64_t occupancy = 0ULL; - - // loop over the range of bits within attack mask - for (int count = 0; count < bits_in_mask; count++) { - // get LS1B index of attacks mask - int8_t square = get_ls1b_index(attack_mask); - - // pop LS1B in attack map - pop_bit(attack_mask, square); - - // make sure occupancy is on board - if (index & (1 << count)) - // populate occupancy map - occupancy |= (1ULL << square); - } - - // return occupancy map - return occupancy; -} - -uint64_t mask_bishop_attacks(int8_t square) { - // attack bitboard - uint64_t attacks = 0ULL; - - // init files & ranks - int f, r; - - // init target files & ranks - int tr = square / 8; - int tf = square % 8; - - // generate attacks - for (r = tr + 1, f = tf + 1; r <= 6 && f <= 6; r++, f++) attacks |= (1ULL << (r * 8 + f)); - for (r = tr + 1, f = tf - 1; r <= 6 && f >= 1; r++, f--) attacks |= (1ULL << (r * 8 + f)); - for (r = tr - 1, f = tf + 1; r >= 1 && f <= 6; r--, f++) attacks |= (1ULL << (r * 8 + f)); - for (r = tr - 1, f = tf - 1; r >= 1 && f >= 1; r--, f--) attacks |= (1ULL << (r * 8 + f)); - - // return attack map for bishop on a given square - return attacks; -} - -// mask rook attacks -uint64_t mask_rook_attacks(int8_t square) { - // attacks bitboard - uint64_t attacks = 0ULL; - - // init files & ranks - int f, r; - - // init target files & ranks - int tr = square / 8; - int tf = square % 8; - - // generate attacks - for (r = tr + 1; r <= 6; r++) attacks |= (1ULL << (r * 8 + tf)); - for (r = tr - 1; r >= 1; r--) attacks |= (1ULL << (r * 8 + tf)); - for (f = tf + 1; f <= 6; f++) attacks |= (1ULL << (tr * 8 + f)); - for (f = tf - 1; f >= 1; f--) attacks |= (1ULL << (tr * 8 + f)); - - // return attack map for bishop on a given square - return attacks; -} - -uint64_t bishop_attacks_on_the_fly(int8_t square, uint64_t block) { - // attack bitboard - uint64_t attacks = 0ULL; - - // init files & ranks - int f, r; - - // init target files & ranks - int tr = square / 8; - int tf = square % 8; - - // generate attacks - for (r = tr + 1, f = tf + 1; r <= 7 && f <= 7; r++, f++) { - attacks |= (1ULL << (r * 8 + f)); - if (block & (1ULL << (r * 8 + f))) break; - } - - for (r = tr + 1, f = tf - 1; r <= 7 && f >= 0; r++, f--) { - attacks |= (1ULL << (r * 8 + f)); - if (block & (1ULL << (r * 8 + f))) break; - } - - for (r = tr - 1, f = tf + 1; r >= 0 && f <= 7; r--, f++) { - attacks |= (1ULL << (r * 8 + f)); - if (block & (1ULL << (r * 8 + f))) break; - } - - for (r = tr - 1, f = tf - 1; r >= 0 && f >= 0; r--, f--) { - attacks |= (1ULL << (r * 8 + f)); - if (block & (1ULL << (r * 8 + f))) break; - } - - // return attack map for bishop on a given square - return attacks; -} - -// rook attacks -uint64_t rook_attacks_on_the_fly(int8_t square, uint64_t block) { - // attacks bitboard - uint64_t attacks = 0ULL; - - // init files & ranks - int f, r; - - // init target files & ranks - int tr = square / 8; - int tf = square % 8; - - // generate attacks - for (r = tr + 1; r <= 7; r++) { - attacks |= (1ULL << (r * 8 + tf)); - if (block & (1ULL << (r * 8 + tf))) break; - } - - for (r = tr - 1; r >= 0; r--) { - attacks |= (1ULL << (r * 8 + tf)); - if (block & (1ULL << (r * 8 + tf))) break; - } - - for (f = tf + 1; f <= 7; f++) { - attacks |= (1ULL << (tr * 8 + f)); - if (block & (1ULL << (tr * 8 + f))) break; - } - - for (f = tf - 1; f >= 0; f--) { - attacks |= (1ULL << (tr * 8 + f)); - if (block & (1ULL << (tr * 8 + f))) break; - } - - // return attack map for bishop on a given square - return attacks; -} - -void init_sliders_attacks(int is_bishop) { - // loop over 64 board squares - for (int8_t square = 0; square < 64; square++) { - // init bishop & rook masks - bishop_masks[square] = mask_bishop_attacks(square); - rook_masks[square] = mask_rook_attacks(square); - - // init current mask - uint64_t mask = is_bishop ? mask_bishop_attacks(square) : mask_rook_attacks(square); - - // size attack mask bits - int bit_count = count_bits(mask); - - // occupancy variations size - int occupancy_variations = 1 << bit_count; - - // loop over occupancy variations - for (int count = 0; count < occupancy_variations; count++) { - // bishop - if (is_bishop) { - // init occupancies, magic index & attacks - uint64_t occupancy = set_occupancy(count, bit_count, mask); - uint64_t magic_index = occupancy * bishopMagics[square] >> (64 - BBits[square]); - bishop_attacks[square][magic_index] = bishop_attacks_on_the_fly(square, occupancy); - } - - // rook - else { - // init occupancies, magic index & attacks - uint64_t occupancy = set_occupancy(count, bit_count, mask); - uint64_t magic_index = occupancy * rookMagics[square] >> (64 - RBits[square]); - rook_attacks[square][magic_index] = rook_attacks_on_the_fly(square, occupancy); - } - } - } -} - -void initializeMagicBitboards() { - generateMagics(); - - // init bishop attacks - init_sliders_attacks(1); - // init rook attacks - init_sliders_attacks(0); -} - -void generateMagics() { - gen.seed(generatorSeed); - - // printf("const uint64_t rookMagics[64] = {\n"); - for (int8_t square = 0; square < 64; square++) { - rookMagics[square] = find_magic(square, RBits[square], 0); - // printf(" 0x%llxULL,\n", find_magic(square, RBits[square], 0)); - } - // printf("};\n\n"); - - // printf("const uint64_t bishopMagics[64] = {\n"); - for (int8_t square = 0; square < 64; square++) { - bishopMagics[square] = find_magic(square, BBits[square], 1); - // printf(" 0x%llxULL,\n", find_magic(square, BBits[square], 1)); - } - // printf("};\n\n"); -} - -void findFastestSeed() { - // variable with max double - uint64_t max = std::numeric_limits::max(); - std::random_device seedRd; - std::mt19937_64 seedGen(seedRd()); - std::uniform_int_distribution seedDis; - - while (true) { - uint64_t seed = seedDis(seedGen); - uint64_t total = 0; - - for (int i = 0; i < 50; i++) { - gen.seed(seedDis(seedGen)); - std::chrono::time_point start = - std::chrono::steady_clock::now(); - - generateMagics(); - - std::chrono::time_point now = - std::chrono::steady_clock::now(); - uint64_t elapsed = std::chrono::duration_cast(now - start). - count(); - total += elapsed; - } - - uint64_t average = total / 50; - if (average < max) { - max = average; - std::cout << "New fastest seed: 0x" << std::uppercase << std::hex << seed << - std::nouppercase - << std::dec << "ULL, took: " << average << "ms (average)" << std::endl; - } - } -} -} // namespace Zagreus \ No newline at end of file diff --git a/src/magics.h b/src/magics.h deleted file mode 100644 index 9d6dd131..00000000 --- a/src/magics.h +++ /dev/null @@ -1,92 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#pragma once -#include - -namespace Zagreus { -constexpr uint64_t generatorSeed = 0x1C6FE234A7121C08ULL; - -#define get_bit(bitboard, square) (bitboard & (1ULL << square)) -#define pop_bit(bitboard, square) (get_bit(bitboard, square) ? (bitboard ^= (1ULL << square)) : 0) - -static int RBits[64] = {12, 11, 11, 11, 11, 11, 11, 12, 11, 10, 10, 10, 10, 10, 10, 11, - 11, 10, 10, 10, 10, 10, 10, 11, 11, 10, 10, 10, 10, 10, 10, 11, - 11, 10, 10, 10, 10, 10, 10, 11, 11, 10, 10, 10, 10, 10, 10, 11, - 11, 10, 10, 10, 10, 10, 10, 11, 12, 11, 11, 11, 11, 11, 11, 12}; - -static int BBits[64] = {6, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 7, 7, 7, 7, - 5, 5, 5, 5, 7, 9, 9, 7, 5, 5, 5, 5, 7, 9, 9, 7, 5, 5, 5, 5, 7, 7, - 7, 7, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 5, 5, 6}; - -uint64_t getRookMagic(int sq); - -uint64_t getBishopMagic(int sq); - -uint64_t getRookMask(int sq); - -uint64_t getBishopMask(int sq); - -uint64_t getRookMagicAttacks(int sq, uint64_t block); - -uint64_t getBishopMagicAttacks(int sq, uint64_t block); - -uint64_t random_uint64_t_fewbits(); - -int count_1s(uint64_t b); - -int pop_1st_bit(uint64_t* bb); - -uint64_t index_to_uint64_t(int index, int bits, uint64_t m); - -uint64_t rmask(int sq); - -uint64_t bmask(int sq); - -uint64_t ratt(int sq, uint64_t block); - -uint64_t batt(int sq, uint64_t block); - -int transform(uint64_t b, uint64_t magic, int bits); - -uint64_t find_magic(int sq, int m, int bishop); - -void generateMagics(); - -void findFastestSeed(); - -void init_sliders_attacks(int is_bishop); - -uint64_t set_occupancy(int index, int bits_in_mask, uint64_t attack_mask); - -int get_ls1b_index(uint64_t bitboard); - -uint64_t mask_rook_attacks(int8_t square); - -uint64_t mask_bishop_attacks(int8_t square); - -int count_bits(uint64_t bitboard); - -uint64_t rook_attacks_on_the_fly(int8_t square, uint64_t block); - -uint64_t bishop_attacks_on_the_fly(int8_t square, uint64_t block); - -void initializeMagicBitboards(); -} // namespace Zagreus \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index e1f59eb0..84dd5e80 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,7 +2,7 @@ This file is part of Zagreus. Zagreus is a UCI chess engine - Copyright (C) 2023 Danny Jelsma + Copyright (C) 2023-2024 Danny Jelsma Zagreus is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -18,229 +18,4 @@ along with Zagreus. If not, see . */ -#include -#include -#include -#include -#include "../senjo/Output.h" -#include "../senjo/UCIAdapter.h" -#include "bitboard.h" -#include "engine.h" -#include "evaluate.h" -#include "features.h" -#include "magics.h" -#include "pst.h" -#include "search.h" -#include "tt.h" -#include "tuner.h" - -using namespace Zagreus; - -void benchmark(bool fast); - -// Some of these benchmark positions are taken from Stockfish's benchmark.cpp: -// https://github.com/official-stockfish/Stockfish/blob/master/src/benchmark.cpp -const std::vector BENCHMARK_POSITIONS = { - "8/8/1n3k2/8/3P3P/5K2/8/1N4Q1 w - -", - "1rb1kbnr/p1q1pppp/np4B1/2pp4/P1PP4/2N1P3/1P3PPP/R1BQK1NR w Kk", - "1rbk1bnr/pp1p1ppp/n1pq4/4pP1Q/P1B1P3/2P4P/1P1P2P1/RNB2KNR b - -", - "r1bn1r2/2k3p1/6p1/pP3p2/4P2P/1P1P3R/2P1NB2/Q3K1n1 b - -", - "1rb1kb1r/p3p1pp/n4p2/1pppNn2/4P3/2PP3P/PB4P1/RN1QKB1R b KQk -", - "8/3P4/p4k2/P2Q4/N7/7B/4p3/4K3 w - -", - "1B1N3k/4r3/5b1P/2n1P3/3K2p1/6P1/4b3/4R3 b - ", - "r1n1k1r1/1pb2p2/5P2/pPPp2p1/5p1R/P1N1N3/2P1P3/B1R1KB2 w - -", - "8/8/1qB1k3/1p6/3p4/1p6/8/4K3 b - -", - "6nr/1pq1b1k1/1N6/5Ppp/pp2B1P1/B1P4P/P2pK3/3R2NR b - -", - "rnbqk2r/1p1p1pbp/4p2n/p1p3p1/2PPP1P1/5P1N/PP1K2BP/RNBQR3 w kq -", - "2bq1b2/4k3/1p3ppr/2pp1Q1N/3p3P/2P5/PpN2PP1/1RB1KBR1 w - -", - "8/2k5/4p1B1/4P2p/4P2P/1K6/7N/1q6 w - -", - "1N1k4/P5b1/4p1p1/2PP3p/R4BrP/8/2nKb2R/1r6 w - -", - "2kN1r2/2n5/2Q5/7p/7P/1K1RB3/8/8 w - -", - "8/7b/p1P1Pn2/P1k3N1/7p/K1p5/4B1P1/3N2R1 w - -", - "rnbqkbnr/1p2pp1p/3p4/6p1/P1p2PP1/1p1P4/2P1P2P/RNBQKBNR w Kkq -", - "1n6/8/2k3K1/2p5/2PpP1nb/3P4/8/1b6 w - -", - "8/2R5/8/k7/N5PP/2K5/8/5b2 w - -", - "r3k2r/1b1nb2p/p1p3pn/3Np3/1PPp1B2/7B/P2KPq2/RQ4NR w kq -", - "1n6/2B2nK1/2k5/2p5/2Pp4/8/8/5b2 b - -", - "3Kb3/8/8/P1R5/8/8/8/7k w - -", - "1B1k2n1/6b1/r4npq/5p1r/pP2PP1P/2P5/R2NR3/3K4 b - -", - "rnb4r/4b2k/1R1pP3/4N2P/pP2nP1P/p1N5/3B4/1K5R b - -", - "8/1pp5/3R2P1/1pkn1p1p/5P2/r6b/8/1N2K1N1 b - -", - "rn3bnr/pBp1pk1p/5p2/1p4p1/6b1/N3Q3/PP1P1PPP/R1B1K1NR b - -", - "8/8/P7/2k4n/1pR4n/1B5P/3R4/3K2N1 b - -", - "q7/p1rbn1p1/P1ppR3/1PP2P1k/1B1PN2p/1Q5P/1R2K3/8 b - -", - "6r1/3k2N1/2n5/3pbP2/8/8/8/3K1n2 b - -", - "bnk4r/7p/R3q3/p1Pp4/P7/3p4/3P2Kn/1NB5 w - -", - "r7/n3k1b1/p2p4/P1pP4/2K5/P6N/3BB2R/6Q1 b - -", - "3k4/8/8/1NP5/6B1/8/8/K7 b - -", - "3nb3/5k2/4p3/4P2p/Qp2P2P/8/3K2B1/2N5 w - -", - "6b1/k7/2KbR3/8/7P/8/8/8 b - -", - "rnb5/4b1kr/1R1pP3/4N2P/pP2nP1P/p1N5/8/1KB4R w - -", - "4r1k1/r1q2ppp/ppp2n2/4P3/5Rb1/1N1BQ3/PPP3PP/R5K1 w - - 1 17", - "2rqkb1r/ppp2p2/2npb1p1/1N1Nn2p/2P1PP2/8/PP2B1PP/R1BQK2R b KQ - 0 11", - "r1bbk1nr/pp3p1p/2n5/1N4p1/2Np1B2/8/PPP2PPP/2KR1B1R w kq - 0 13", - "6k1/3b3r/1p1p4/p1n2p2/1PPNpP1q/P3Q1p1/1R1RB1P1/5K2 b - - 0 1", - "r2r1n2/pp2bk2/2p1p2p/3q4/3PN1QP/2P3R1/P4PP1/5RK1 w - - 0 1", - "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 10", - "8/8/8/8/8/6k1/6p1/6K1 w - -", - "7k/7P/6K1/8/3B4/8/8/8 b - -", - "8/8/8/8/5kp1/P7/8/1K1N4 w - - 0 1", - "8/3k4/8/8/8/4B3/4KB2/2B5 w - - 0 1", - "8/8/1P6/5pr1/8/4R3/7k/2K5 w - - 0 1", - "8/8/3P3k/8/1p6/8/1P6/1K3n2 b - - 0 1", - "8/R7/2q5/8/6k1/8/1P5p/K6R w - - 0 124" -}; - -// So valgrind doesn't take ages... -const std::vector FAST_BENCHMARK_POSITIONS = { - "8/3k4/8/8/8/4B3/4KB2/2B5 w - - 0 1", - "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 10", - "rn3bnr/pBp1pk1p/5p2/1p4p1/6b1/N3Q3/PP1P1PPP/R1B1K1NR b - -", - "8/7b/p1P1Pn2/P1k3N1/7p/K1p5/4B1P1/3N2R1 w - -", - "r1bn1r2/2k3p1/6p1/pP3p2/4P2P/1P1P3R/2P1NB2/Q3K1n1 b - -", - "1n6/8/2k3K1/2p5/2PpP1nb/3P4/8/1b6 w - -", - "rnbqkbnr/1p2pp1p/3p4/6p1/P1p2PP1/1p1P4/2P1P2P/RNBQKBNR w Kkq -", - "1rb1kbnr/p1q1pppp/np4B1/2pp4/P1PP4/2N1P3/1P3PPP/R1BQK1NR w Kk", - "r1bbk1nr/pp3p1p/2n5/1N4p1/2Np1B2/8/PPP2PPP/2KR1B1R w kq - 0 13", - "8/2R5/8/k7/N5PP/2K5/8/5b2 w - -", - "rnbqk2r/1p1p1pbp/4p2n/p1p3p1/2PPP1P1/5P1N/PP1K2BP/RNBQR3 w kq -", - "8/R7/2q5/8/6k1/8/1P5p/K6R w - - 0 124" -}; - -int main(int argc, char* argv[]) { - initializeBitboardConstants(); - initializeSearch(); - initializeMagicBitboards(); - initializePst(); - - senjo::Output(senjo::Output::NoPrefix) << "Zagreus Copyright (C) 2023 Danny Jelsma"; - senjo::Output(senjo::Output::NoPrefix) << ""; - senjo::Output(senjo::Output::NoPrefix) << "This program comes with ABSOLUTELY NO WARRANTY."; - senjo::Output(senjo::Output::NoPrefix) - << "This is free software, and you are welcome to redistribute it"; - senjo::Output(senjo::Output::NoPrefix) - << "under the conditions of the GNU Affero General Public License v3.0 or later."; - senjo::Output(senjo::Output::NoPrefix) - << "You should have received a copy of the GNU Affero General Public License"; - senjo::Output(senjo::Output::NoPrefix) - << "along with this program. If not, see ."; - senjo::Output(senjo::Output::NoPrefix) << ""; - senjo::Output(senjo::Output::NoPrefix) << " ______ "; - senjo::Output(senjo::Output::NoPrefix) << " |___ / "; - senjo::Output(senjo::Output::NoPrefix) << " / / __ _ __ _ _ __ ___ _ _ ___ "; - senjo::Output(senjo::Output::NoPrefix) << " / / / _` | / _` || '__|/ _ \\| | | |/ __|"; - senjo::Output(senjo::Output::NoPrefix) << " / /__| (_| || (_| || | | __/| |_| |\\__ \\"; - senjo::Output(senjo::Output::NoPrefix) << R"( /_____|\__,_| \__, ||_| \___| \__,_||___/)"; - senjo::Output(senjo::Output::NoPrefix) << " __/ | "; - senjo::Output(senjo::Output::NoPrefix) << " |___/ "; - senjo::Output(senjo::Output::NoPrefix) << ""; - - std::string majorVersion = ZAGREUS_VERSION_MAJOR; - std::string minorVersion = ZAGREUS_VERSION_MINOR; - std::string versionString = "v" + majorVersion + "." + minorVersion; - - if (majorVersion == "dev") { - versionString = majorVersion + "-" + minorVersion; - } - - senjo::Output(senjo::Output::NoPrefix) << "Zagreus UCI chess engine " << versionString - << " by Danny Jelsma (https://github.com/Dannyj1/Zagreus)"; - - if (argc >= 2) { - if (strcmp(argv[1], "bench") == 0) { - senjo::Output(senjo::Output::NoPrefix) << "Starting benchmark..."; - - benchmark(false); - return 0; - } else if (strcmp(argv[1], "fastbench") == 0) { - senjo::Output(senjo::Output::NoPrefix) << "Starting fast benchmark..."; - - benchmark(true); - return 0; - } else if (strcmp(argv[1], "tune") == 0) { - startTuning(argv[2]); - return 0; - } else if (strcmp(argv[1], "printeval") == 0) { - printEvalValues(); - return 0; - } - - senjo::Output(senjo::Output::NoPrefix) << "Unknown argument!"; - return 0; - } - - try { - ZagreusEngine engine; - senjo::UCIAdapter adapter(engine); - - std::string line; - line.reserve(16384); - - while (std::getline(std::cin, line)) { - try { - if (!adapter.doCommand(line)) { - break; - } - } catch (const std::exception& e) { - senjo::Output(senjo::Output::NoPrefix) << "ERROR: " << e.what(); - return -1; - } - } - - return 0; - } catch (const std::exception& e) { - senjo::Output(senjo::Output::NoPrefix) << "ERROR: " << e.what(); - return 1; - } -} - -void benchmark(bool fast) { - ZagreusEngine engine; - senjo::UCIAdapter adapter(engine); - uint64_t nodes = 0; - double totalMs = 0; - Bitboard bb{}; - - engine.initialize(); - TranspositionTable::getTT()->setTableSize(512); - std::vector positions = fast ? FAST_BENCHMARK_POSITIONS : BENCHMARK_POSITIONS; - - for (const std::string& position : positions) { - for (int i = 0; i < 2; i++) { - TranspositionTable::getTT()->reset(); - PieceColor color = i == 0 ? WHITE : BLACK; - - bb.setFromFen(position); - bb.setMovingColor(color); - - senjo::GoParams params{}; - senjo::SearchStats searchStats{}; - params.depth = fast ? 5 : 6; - - auto start = std::chrono::steady_clock::now(); - - if (color == WHITE) { - getBestMove(params, engine, bb, searchStats); - } else { - getBestMove(params, engine, bb, searchStats); - } - - auto end = std::chrono::steady_clock::now(); - std::chrono::duration elapsed = end - start; - - nodes += searchStats.nodes + searchStats.qnodes; - totalMs += elapsed.count(); - } - } - - if (nodes == 0 || totalMs == 0) { - senjo::Output(senjo::Output::NoPrefix) << "0 nodes 0 nps"; - return; - } - - double secondsSpent = totalMs / 1000.0; - auto nodesPerSecond = static_cast(static_cast(nodes) / secondsSpent); - - senjo::Output(senjo::Output::NoPrefix) << nodes << " nodes " << nodesPerSecond << " nps"; -} \ No newline at end of file diff --git a/src/movegen.cpp b/src/movegen.cpp deleted file mode 100644 index 4ebb5e68..00000000 --- a/src/movegen.cpp +++ /dev/null @@ -1,572 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#include "movegen.h" - -#include - -#include -#include - -#include "bitboard.h" -#include "tt.h" -#include "utils.h" - -namespace Zagreus { -void addMoveToList(MoveList* moveList, int8_t from, int8_t to = 0, PieceType piece = EMPTY, - int captureScore = 0, PieceType promotionPiece = EMPTY, int score = 0) { - moveList->moves[moveList->size].from = from; - moveList->moves[moveList->size].to = to; - moveList->moves[moveList->size].piece = piece; - moveList->moves[moveList->size].captureScore = captureScore; - moveList->moves[moveList->size].score = score; - moveList->moves[moveList->size].promotionPiece = promotionPiece; - moveList->size++; -} - -int scoreMove(int ply, uint32_t pvMoveCode, Move* move, - Move& previousMove, uint32_t moveCode, uint32_t bestMoveCode, - TranspositionTable* tt) { - if (moveCode == pvMoveCode) { - return 500000; - } - - if (moveCode == bestMoveCode) { - return 250000; - } - - if (move->captureScore >= 0) { - return 100000 + move->captureScore; - } - - if (tt->killerMoves[0][ply] == moveCode) { - return 50000; - } - - if (tt->killerMoves[1][ply] == moveCode) { - return 40000; - } - - if (tt->killerMoves[2][ply] == moveCode) { - return 30000; - } - - if (previousMove.piece != EMPTY && previousMove.to != NO_SQUARE - && tt->counterMoves[previousMove.piece][previousMove.to] == moveCode) { - return 20000; - } - - // No capture score is -1, so we need to use < -1 - if (move->captureScore < -1) { - return move->captureScore - 5000; - } - - return tt->historyMoves[move->piece][move->to]; -} - -template -void generateMoves(Bitboard& bitboard, MoveList* moveList) { - constexpr PieceColor OPPOSITE_COLOR = color == WHITE ? BLACK : WHITE; - uint64_t evasionSquaresBB = 0; - - if (type == EVASIONS) { - int8_t kingSquare = bitscanForward(bitboard.getPieceBoard(color == WHITE - ? WHITE_KING - : BLACK_KING)); - uint64_t kingAttackers = bitboard.getSquareAttackersByColor(kingSquare); - - // Only generate king moves if there is more than one attacker - if (popcnt(kingAttackers) == 1) { - int8_t attackerSquare = popLsb(kingAttackers); - PieceType attackerPiece = bitboard.getPieceOnSquare(attackerSquare); - - if (isSlidingPiece(attackerPiece)) { - evasionSquaresBB |= getBetweenSquares(attackerSquare, kingSquare); - } - - evasionSquaresBB |= 1ULL << attackerSquare; - } - } - - generatePawnMoves(bitboard, moveList, evasionSquaresBB); - generateKnightMoves(bitboard, moveList, evasionSquaresBB); - generateBishopMoves(bitboard, moveList, evasionSquaresBB); - generateRookMoves(bitboard, moveList, evasionSquaresBB); - generateQueenMoves(bitboard, moveList, evasionSquaresBB); - generateKingMoves(bitboard, moveList); - - TranspositionTable* tt = TranspositionTable::getTT(); - Line previousPv = bitboard.getPvLine(); - uint32_t bestMoveCode = 0; - uint64_t zobristHash = bitboard.getZobristHash(); - uint32_t validationHash = bitboard.getZobristHash() >> 32; - TTEntry* ttEntry = tt->getEntry(bitboard.getZobristHash()); - - if (ttEntry->validationHash == validationHash) { - bestMoveCode = ttEntry->bestMoveCode; - } - - int ply = bitboard.getPly(); - int pvFrom = ply - previousPv.startPly; - Move pvMove = previousPv.moves[pvFrom]; - uint32_t pvMoveCode = encodeMove(&pvMove); - Move previousMove = bitboard.getPreviousMove(); - - for (int i = 0; i < moveList->size; i++) { - Move* move = &moveList->moves[i]; - move->score = scoreMove(ply, pvMoveCode, move, previousMove, encodeMove(move), - bestMoveCode, tt); - } -} - -template -void generatePawnMoves(Bitboard& bitboard, MoveList* moveList, uint64_t evasionSquaresBB) { - constexpr PieceColor OPPOSITE_COLOR = color == WHITE ? BLACK : WHITE; - uint64_t pawnBB; - - if (color == WHITE) { - pawnBB = bitboard.getPieceBoard(WHITE_PAWN); - } else { - pawnBB = bitboard.getPieceBoard(BLACK_PAWN); - } - - while (pawnBB) { - int8_t from = popLsb(pawnBB); - uint64_t genBB = bitboard.getPawnDoublePush(1ULL << from); - uint64_t attackableSquares = bitboard.getColorBoard(); - - if (bitboard.getEnPassantSquare() != NO_SQUARE) { - attackableSquares |= 1ULL << bitboard.getEnPassantSquare(); - } - - genBB |= getPawnAttacks(from) & attackableSquares; - genBB &= ~(bitboard.getColorBoard() | bitboard.getPieceBoard(WHITE_KING) | - bitboard.getPieceBoard(BLACK_KING)); - - if (type == QSEARCH) { - genBB &= (bitboard.getColorBoard() | PROMOTION_SQUARES); - } - - if (type == EVASIONS) { - genBB &= evasionSquaresBB; - } - - while (genBB) { - int8_t to = popLsb(genBB); - PieceType capturedPiece = bitboard.getPieceOnSquare(to); - int captureScore = NO_CAPTURE_SCORE; - - if (color == WHITE) { - if (capturedPiece != EMPTY) { - captureScore = type == QSEARCH - ? bitboard.seeCapture(from, to) - : mvvlva(WHITE_PAWN, capturedPiece); - } - - if (to >= A8) { - addMoveToList(moveList, from, to, WHITE_PAWN, captureScore, WHITE_QUEEN); - - if (type == NORMAL) { - addMoveToList(moveList, from, to, WHITE_PAWN, captureScore, WHITE_ROOK); - addMoveToList(moveList, from, to, WHITE_PAWN, captureScore, - WHITE_BISHOP); - addMoveToList(moveList, from, to, WHITE_PAWN, captureScore, - WHITE_KNIGHT); - } - } else { - addMoveToList(moveList, from, to, WHITE_PAWN, captureScore); - } - } else { - if (capturedPiece != EMPTY) { - captureScore = type == QSEARCH - ? bitboard.seeCapture(from, to) - : mvvlva(BLACK_PAWN, capturedPiece); - } - - if (to <= H1) { - addMoveToList(moveList, from, to, BLACK_PAWN, captureScore, BLACK_QUEEN); - - if (type == NORMAL) { - addMoveToList(moveList, from, to, BLACK_PAWN, captureScore, BLACK_ROOK); - addMoveToList(moveList, from, to, BLACK_PAWN, captureScore, - BLACK_BISHOP); - addMoveToList(moveList, from, to, BLACK_PAWN, captureScore, - BLACK_KNIGHT); - } - } else { - addMoveToList(moveList, from, to, BLACK_PAWN, captureScore); - } - } - } - } -} - -template -void generateKnightMoves(Bitboard& bitboard, MoveList* moveList, uint64_t evasionSquaresBB) { - constexpr PieceColor OPPOSITE_COLOR = color == WHITE ? BLACK : WHITE; - uint64_t knightBB; - - if (color == WHITE) { - knightBB = bitboard.getPieceBoard(WHITE_KNIGHT); - } else { - knightBB = bitboard.getPieceBoard(BLACK_KNIGHT); - } - - while (knightBB) { - int8_t from = popLsb(knightBB); - uint64_t genBB = getKnightAttacks(from); - - genBB &= ~(bitboard.getColorBoard() | bitboard.getPieceBoard(WHITE_KING) | - bitboard.getPieceBoard(BLACK_KING)); - - if (type == QSEARCH) { - genBB &= (bitboard.getColorBoard()); - } - - if (type == EVASIONS) { - genBB &= evasionSquaresBB; - } - - while (genBB) { - int8_t to = popLsb(genBB); - PieceType capturedPiece = bitboard.getPieceOnSquare(to); - int captureScore = NO_CAPTURE_SCORE; - - if (color == WHITE) { - if (capturedPiece != EMPTY) { - captureScore = type == QSEARCH - ? bitboard.seeCapture(from, to) - : mvvlva(WHITE_KNIGHT, capturedPiece); - } - - addMoveToList(moveList, from, to, WHITE_KNIGHT, captureScore); - } else { - if (capturedPiece != EMPTY) { - captureScore = type == QSEARCH - ? bitboard.seeCapture(from, to) - : mvvlva(BLACK_KNIGHT, capturedPiece); - } - - addMoveToList(moveList, from, to, BLACK_KNIGHT, captureScore); - } - } - } -} - -template -void generateBishopMoves(Bitboard& bitboard, MoveList* moveList, uint64_t evasionSquaresBB) { - constexpr PieceColor OPPOSITE_COLOR = color == WHITE ? BLACK : WHITE; - uint64_t bishopBB; - - if (color == WHITE) { - bishopBB = bitboard.getPieceBoard(WHITE_BISHOP); - } else { - bishopBB = bitboard.getPieceBoard(BLACK_BISHOP); - } - - while (bishopBB) { - int8_t from = popLsb(bishopBB); - uint64_t genBB = bitboard.getBishopAttacks(from); - - genBB &= ~(bitboard.getColorBoard() | bitboard.getPieceBoard(WHITE_KING) | - bitboard.getPieceBoard(BLACK_KING)); - - if (type == QSEARCH) { - genBB &= (bitboard.getColorBoard()); - } - - if (type == EVASIONS) { - genBB &= evasionSquaresBB; - } - - while (genBB) { - int8_t to = popLsb(genBB); - PieceType capturedPiece = bitboard.getPieceOnSquare(to); - int captureScore = NO_CAPTURE_SCORE; - - if (color == WHITE) { - if (capturedPiece != EMPTY) { - captureScore = type == QSEARCH - ? bitboard.seeCapture(from, to) - : mvvlva(WHITE_BISHOP, capturedPiece); - } - - addMoveToList(moveList, from, to, WHITE_BISHOP, captureScore); - } else { - if (capturedPiece != EMPTY) { - captureScore = type == QSEARCH - ? bitboard.seeCapture(from, to) - : mvvlva(BLACK_BISHOP, capturedPiece); - } - - addMoveToList(moveList, from, to, BLACK_BISHOP, captureScore); - } - } - } -} - -template -void generateRookMoves(Bitboard& bitboard, MoveList* moveList, uint64_t evasionSquaresBB) { - constexpr PieceColor OPPOSITE_COLOR = color == WHITE ? BLACK : WHITE; - uint64_t rookBB; - - if (color == WHITE) { - rookBB = bitboard.getPieceBoard(WHITE_ROOK); - } else { - rookBB = bitboard.getPieceBoard(BLACK_ROOK); - } - - while (rookBB) { - int8_t from = popLsb(rookBB); - uint64_t genBB = bitboard.getRookAttacks(from); - - genBB &= ~(bitboard.getColorBoard() | bitboard.getPieceBoard(WHITE_KING) | - bitboard.getPieceBoard(BLACK_KING)); - - if (type == QSEARCH) { - genBB &= (bitboard.getColorBoard()); - } - - if (type == EVASIONS) { - genBB &= evasionSquaresBB; - } - - while (genBB) { - int8_t to = popLsb(genBB); - PieceType capturedPiece = bitboard.getPieceOnSquare(to); - int captureScore = NO_CAPTURE_SCORE; - - if (color == WHITE) { - if (capturedPiece != EMPTY) { - captureScore = type == QSEARCH - ? bitboard.seeCapture(from, to) - : mvvlva(WHITE_ROOK, capturedPiece); - } - - addMoveToList(moveList, from, to, WHITE_ROOK, captureScore); - } else { - if (capturedPiece != EMPTY) { - captureScore = type == QSEARCH - ? bitboard.seeCapture(from, to) - : mvvlva(BLACK_ROOK, capturedPiece); - } - - addMoveToList(moveList, from, to, BLACK_ROOK, captureScore); - } - } - } -} - -template -void generateQueenMoves(Bitboard& bitboard, MoveList* moveList, uint64_t evasionSquaresBB) { - constexpr PieceColor OPPOSITE_COLOR = color == WHITE ? BLACK : WHITE; - uint64_t queenBB; - - if (color == WHITE) { - queenBB = bitboard.getPieceBoard(WHITE_QUEEN); - } else { - queenBB = bitboard.getPieceBoard(BLACK_QUEEN); - } - - while (queenBB) { - int8_t from = popLsb(queenBB); - uint64_t genBB = bitboard.getQueenAttacks(from); - - genBB &= ~(bitboard.getColorBoard() | bitboard.getPieceBoard(WHITE_KING) | - bitboard.getPieceBoard(BLACK_KING)); - - if (type == QSEARCH) { - genBB &= (bitboard.getColorBoard()); - } - - if (type == EVASIONS) { - genBB &= evasionSquaresBB; - } - - while (genBB) { - int8_t to = popLsb(genBB); - PieceType capturedPiece = bitboard.getPieceOnSquare(to); - int captureScore = NO_CAPTURE_SCORE; - - if (color == WHITE) { - if (capturedPiece != EMPTY) { - captureScore = type == QSEARCH - ? bitboard.seeCapture(from, to) - : mvvlva(WHITE_QUEEN, capturedPiece); - } - - addMoveToList(moveList, from, to, WHITE_QUEEN, captureScore); - } else { - if (capturedPiece != EMPTY) { - captureScore = type == QSEARCH - ? bitboard.seeCapture(from, to) - : mvvlva(BLACK_QUEEN, capturedPiece); - } - - addMoveToList(moveList, from, to, BLACK_QUEEN, captureScore); - } - } - } -} - -template -void generateKingMoves(Bitboard& bitboard, MoveList* moveList) { - constexpr PieceColor OPPOSITE_COLOR = color == WHITE ? BLACK : WHITE; - uint64_t kingBB; - uint64_t opponentKingBB; - - if (color == WHITE) { - kingBB = bitboard.getPieceBoard(WHITE_KING); - opponentKingBB = bitboard.getPieceBoard(BLACK_KING); - } else { - kingBB = bitboard.getPieceBoard(BLACK_KING); - opponentKingBB = bitboard.getPieceBoard(WHITE_KING); - } - - int8_t from = bitscanForward(kingBB); - uint64_t genBB = getKingAttacks(from); - int8_t opponentKingSquare = bitscanForward(opponentKingBB); - - genBB &= ~(bitboard.getColorBoard() | getKingAttacks(opponentKingSquare)); - - if (type == QSEARCH) { - genBB &= bitboard.getColorBoard(); - } - - while (genBB) { - int8_t to = popLsb(genBB); - PieceType capturedPiece = bitboard.getPieceOnSquare(to); - int captureScore = NO_CAPTURE_SCORE; - - if (color == WHITE) { - if (capturedPiece != EMPTY) { - captureScore = type == QSEARCH - ? bitboard.seeCapture(from, to) - : mvvlva(WHITE_KING, capturedPiece); - } - - addMoveToList(moveList, from, to, WHITE_KING, captureScore); - } else { - if (capturedPiece != EMPTY) { - captureScore = type == QSEARCH - ? bitboard.seeCapture(from, to) - : mvvlva(BLACK_KING, capturedPiece); - } - - addMoveToList(moveList, from, to, BLACK_KING, captureScore); - } - } - - if (type == QSEARCH || type == EVASIONS) { - return; - } - - uint64_t occupiedBB = bitboard.getOccupiedBoard(); - - if (color == WHITE) { - if (bitboard.getCastlingRights() & WHITE_KINGSIDE && - (occupiedBB & WHITE_KING_SIDE_BETWEEN) == 0 && - bitboard.getPieceOnSquare(H1) == WHITE_ROOK && !bitboard.isKingInCheck()) { - uint64_t tilesToCheck = WHITE_KING_SIDE_BETWEEN; - bool canCastle = true; - - while (tilesToCheck) { - int8_t tilefrom = popLsb(tilesToCheck); - - if (bitboard.isSquareAttackedByColor(tilefrom)) { - canCastle = false; - break; - } - } - - if (canCastle) { - addMoveToList(moveList, from, G1, WHITE_KING, -1); - } - } - - if (bitboard.getCastlingRights() & WHITE_QUEENSIDE && - (occupiedBB & WHITE_QUEEN_SIDE_BETWEEN) == 0 && - bitboard.getPieceOnSquare(A1) == WHITE_ROOK && !bitboard.isKingInCheck()) { - uint64_t tilesToCheck = WHITE_QUEEN_SIDE_BETWEEN & ~(1ULL << B1); - bool canCastle = true; - - while (tilesToCheck) { - int8_t tilefrom = popLsb(tilesToCheck); - - if (bitboard.isSquareAttackedByColor(tilefrom)) { - canCastle = false; - break; - } - } - - if (canCastle) { - addMoveToList(moveList, from, C1, WHITE_KING, -1); - } - } - } else { - if (bitboard.getCastlingRights() & BLACK_KINGSIDE && - (occupiedBB & BLACK_KING_SIDE_BETWEEN) == 0 && - bitboard.getPieceOnSquare(H8) == BLACK_ROOK && !bitboard.isKingInCheck()) { - uint64_t tilesToCheck = BLACK_KING_SIDE_BETWEEN; - bool canCastle = true; - - while (tilesToCheck) { - int8_t tilefrom = popLsb(tilesToCheck); - - if (bitboard.isSquareAttackedByColor(tilefrom)) { - canCastle = false; - break; - } - } - - if (canCastle) { - addMoveToList(moveList, from, G8, BLACK_KING, -1); - } - } - - if (bitboard.getCastlingRights() & BLACK_QUEENSIDE && - (occupiedBB & BLACK_QUEEN_SIDE_BETWEEN) == 0 && - bitboard.getPieceOnSquare(A8) == BLACK_ROOK && !bitboard.isKingInCheck()) { - uint64_t tilesToCheck = BLACK_QUEEN_SIDE_BETWEEN & ~(1ULL << B8); - bool canCastle = true; - - while (tilesToCheck) { - int8_t tilefrom = popLsb(tilesToCheck); - - if (bitboard.isSquareAttackedByColor(tilefrom)) { - canCastle = false; - break; - } - } - - if (canCastle) { - addMoveToList(moveList, from, C8, BLACK_KING, -1); - } - } - } -} - -template void generateMoves(Bitboard& bitboard, MoveList* moveList); -template void generateMoves(Bitboard& bitboard, MoveList* moveList); -template void generateMoves(Bitboard& bitboard, MoveList* moveList); -template void generateMoves(Bitboard& bitboard, MoveList* moveList); -template void generateMoves(Bitboard& bitboard, MoveList* moveList); -template void generateMoves(Bitboard& bitboard, MoveList* moveList); -} // namespace Zagreus \ No newline at end of file diff --git a/src/movelist_pool.cpp b/src/movelist_pool.cpp deleted file mode 100644 index 0077b2e7..00000000 --- a/src/movelist_pool.cpp +++ /dev/null @@ -1,72 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#include "movelist_pool.h" - -#include "types.h" - -namespace Zagreus { -MoveListPool::MoveListPool() { - for (int i = 0; i < INITIAL_POOL_SIZE; ++i) { - MoveList* moveList = createMoveList(); - pool.push_back(moveList); - } -} - -MoveListPool::~MoveListPool() { - for (MoveList* moveList : pool) { - destroyMoveList(moveList); - } -} - -MoveListPool* MoveListPool::getInstance() { - static MoveListPool instance{}; - return &instance; -} - -MoveList* MoveListPool::getMoveList() { - if (pool.empty()) { - MoveList* moveList = createMoveList(); - return moveList; - } - - MoveList* moveList = pool.back(); - pool.pop_back(); - moveList->size = 0; - return moveList; -} - -void MoveListPool::releaseMoveList(MoveList* moveList) { - moveList->size = 0; - pool.push_back(moveList); -} - -MoveList* MoveListPool::createMoveList() { - auto* moveList = new MoveList(); - - for (auto& move : moveList->moves) { - move = Move(); - } - - return moveList; -} - -void MoveListPool::destroyMoveList(MoveList* moveList) { delete moveList; } -} // namespace Zagreus \ No newline at end of file diff --git a/src/movelist_pool.h b/src/movelist_pool.h deleted file mode 100644 index f60ee9b7..00000000 --- a/src/movelist_pool.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#pragma once - -#include - -#include "types.h" - -namespace Zagreus { -static constexpr int INITIAL_POOL_SIZE = 100; - -class MoveListPool { -public: - static MoveListPool* getInstance(); - - MoveList* getMoveList(); - - void releaseMoveList(MoveList* moveList); - - ~MoveListPool(); - -private: - std::vector pool{}; - - MoveListPool(); - - static MoveList* createMoveList(); - - static void destroyMoveList(MoveList* moveList); -}; -} // namespace Zagreus \ No newline at end of file diff --git a/src/movepicker.cpp b/src/movepicker.cpp deleted file mode 100644 index e89f6b55..00000000 --- a/src/movepicker.cpp +++ /dev/null @@ -1,48 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#include "movepicker.h" - -namespace Zagreus { -Move MovePicker::getNextMove() { - int highestScore = moveList->moves[searchStartIndex].score; - int moveIndex = searchStartIndex; - - for (int i = searchStartIndex + 1; i < moveList->size; i++) { - int currentScore = moveList->moves[i].score; - if (currentScore > highestScore) { - highestScore = currentScore; - moveIndex = i; - } - } - - std::swap(moveList->moves[searchStartIndex], moveList->moves[moveIndex]); - searchStartIndex++; - return moveList->moves[searchStartIndex - 1]; -} - -bool MovePicker::hasNext() { return searchStartIndex < moveList->size; } - -int MovePicker::size() { return moveList->size; } - -int MovePicker::remaining() { return moveList->size - searchStartIndex; } - -int MovePicker::movesSearched() { return searchStartIndex; } -} // namespace Zagreus \ No newline at end of file diff --git a/src/movepicker.h b/src/movepicker.h deleted file mode 100644 index f9859184..00000000 --- a/src/movepicker.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#pragma once - -#include - -#include "types.h" - -namespace Zagreus { -class MovePicker { -private: - MoveList* moveList = nullptr; - int searchStartIndex = 0; - -public: - MovePicker() = delete; - - MovePicker(MoveList* moveList) - : moveList(moveList) { - }; - - Move getNextMove(); - - bool hasNext(); - - int size(); - - int remaining(); - - int movesSearched(); -}; -} // namespace Zagreus \ No newline at end of file diff --git a/src/pst.cpp b/src/pst.cpp deleted file mode 100644 index 8f48cfe0..00000000 --- a/src/pst.cpp +++ /dev/null @@ -1,314 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#include - -#include "types.h" - -namespace Zagreus { -int midgamePawnTable[64] = {0, 0, 0, 0, 0, 0, 0, 0, 81, 90, 53, 95, 70, 108, 35, 5, -11, 4, 37, 24, - 25, 50, 8, -18, -15, 0, 0, 23, 17, 3, 0, -10, -37, -15, 0, 20, 18, 4, - -12, -26, -18, 0, 0, 5, 3, 0, -2, -18, -17, 14, 0, -8, -3, 4, 16, -12, - 0, 0, 0, 0, 0, 0, 0, 0}; -int endgamePawnTable[64] = {0, 0, 0, 0, 0, 0, 0, 0, 187, 179, 164, 139, 150, 150, 192, 211, 112, - 106, 85, 84, 77, 76, 102, 106, 30, 27, 17, 6, 7, 14, 24, 24, 0, 0, -8, - -9, -11, -12, 1, -3, -8, -7, -6, 1, -1, -10, -8, -6, 0, -7, 3, 15, 2, 4, - -7, -2, 0, 0, 0, 0, 0, 0, 0, 0}; - -int midgameKnightTable[64] = {-168, -86, -46, -37, 48, -77, -17, -119, -59, -24, 76, 59, 41, 74, -8, - -25, -30, 47, 35, 61, 85, 83, 55, 34, 18, 17, 20, 38, 34, 27, 14, 33, - -7, 5, 16, 16, 21, 20, 18, 0, -30, 5, 10, 18, 20, 9, 5, -20, -3, -21, - -6, 22, 21, 15, 2, 4, -94, -12, -29, -17, -10, -18, -12, -34}; -int endgameKnightTable[64] = {-55, -39, -22, -14, -27, -20, -58, -88, -28, -16, -30, -6, -12, -23, - -24, -43, -17, -32, 6, 0, -6, -12, -29, -29, -12, 9, 18, 21, 25, 15, - 9, -12, -4, 4, 14, 29, 20, 10, -10, -12, -14, -5, -15, 11, 9, -8, -23, - -20, -34, -11, -10, -13, -15, -22, -20, -27, -29, -32, -8, -8, -12, - -14, -35, -57}; - -int midgameBishopTable[64] = {-27, 0, -63, -42, -36, -48, 0, 0, -17, -4, -13, -1, 11, 28, 3, -20, 0, - 23, 38, 22, 16, 49, 23, 3, -16, 0, -3, 17, 18, -2, 8, -15, -6, 3, 0, - 25, 37, 4, 9, -10, 20, 21, 23, 12, 15, 34, 17, 30, 14, 46, 20, 19, 23, - 22, 50, -8, -3, -13, 3, -12, 1, 10, -20, 0}; -int endgameBishopTable[64] = {-11, -27, -6, -8, -12, -10, -27, -17, -18, -10, 0, -5, -21, -13, -7, - -15, -3, -7, 1, 0, 3, 0, -9, -3, 0, 9, 1, 9, 14, 5, 1, -7, -14, -1, - 22, 11, 0, 20, -9, -10, -15, -11, 13, 17, 18, 4, -5, -19, -10, -13, - -6, 9, 0, -9, -9, -17, -18, -14, -4, 0, -6, -11, -5, -16}; - -int midgameRookTable[64] = {35, 37, 34, 46, 60, 15, 33, 40, 10, 12, 56, 48, 73, 50, 21, 39, 13, 12, - 20, 23, 28, 29, 55, 6, -15, -7, 0, 26, 7, 20, 0, -19, -30, -25, -22, - -13, -2, -20, 0, -26, -35, -22, -18, -8, -1, -2, -8, -30, -50, -7, -16, - 0, -7, -16, -9, -37, 0, -11, 11, 9, 8, 10, -5, -5}; -int endgameRookTable[64] = {10, 6, 5, 9, -2, 13, 9, 14, 10, 17, 5, 3, -3, 11, 18, 3, 7, 8, 0, -1, 1, - 3, -5, 0, 6, 10, 8, -3, 4, 0, 7, 12, 4, 9, 7, 0, 8, 6, -4, -2, 1, 3, 0, - -2, -5, -9, -11, -6, -1, -2, 1, -4, -1, 1, -4, 6, 0, 9, 0, -2, -8, 0, 0, - 0}; - -int midgameQueenTable[64] = {-15, -4, 26, 4, 51, 27, 33, 13, -17, -59, -17, -17, -42, 21, -27, 16, - 5, 5, 9, 6, 19, 27, 31, 21, 0, -13, -27, -18, -28, 2, -14, -2, -6, -13, - -16, -14, -19, -8, 5, -6, -18, 5, 7, -3, -3, 0, 2, 0, -13, 7, 13, 13, - 17, 12, 23, 5, 1, -4, 0, 22, 18, -6, -9, -5}; -int endgameQueenTable[64] = {18, 6, 23, 20, 17, 11, 8, -3, 5, 35, 54, 41, 43, 17, 8, -8, -8, 18, 19, - 59, 47, 21, 5, -6, 18, 42, 34, 45, 54, 25, 43, 29, 6, 32, 18, 45, 37, - 25, 33, 21, -8, -12, 14, 0, 12, 29, 16, 6, -1, -20, -18, 6, 0, -26, - -23, -12, -22, -18, -23, -28, -13, -10, -14, -9}; - -int midgameKingTable[64] = {-58, 29, 23, -9, -48, -28, 0, 9, 23, 4, -10, 0, -4, 3, -29, -30, 1, 34, - 10, -5, -16, 12, 28, -31, -16, -22, 0, -26, -26, -6, -13, -39, -41, 1, - -37, -38, -44, -36, -31, -47, -19, -17, -26, -57, -50, -28, -6, -27, 22, - 16, -28, -69, -64, -35, 16, 5, 12, 19, -37, -2, -6, -35, 14, 0}; -int endgameKingTable[64] = {-56, -30, -13, -14, 0, 0, 3, -31, -7, 23, 20, 9, 6, 28, 30, 1, 5, 26, - 25, 20, 22, 33, 32, 11, -12, 13, 14, 25, 20, 18, 10, 0, -17, -9, 14, 22, - 24, 14, 0, -13, -16, 0, 12, 30, 23, 15, 0, -17, -29, -6, 16, 30, 28, 19, - 0, -19, -56, -30, -2, -21, -16, 0, -30, -56}; - -// Base tables from https://www.chessprogramming.org/PeSTO%27s_Evaluation_Function -int baseMidgamePawnTable[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, -}; - -int baseEndgamePawnTable[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 baseMidgameKnightTable[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, -}; - -int baseEndgameKnightTable[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, -}; - -int baseMidgameBishopTable[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 baseEndgameBishopTable[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 baseMidgameRookTable[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 baseEndgameRookTable[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, -}; - -int baseMidgameQueenTable[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, -}; - -int baseEndgameQueenTable[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, -}; - -int baseMidgameKingTable[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 baseEndgameKingTable[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}; - -static int midgamePst[12][64]{}; -static int endgamePst[12][64]{}; -static int baseMidgamePst[12][64]{}; -static int baseEndgamePst[12][64]{}; - -int getMidgamePstValue(PieceType piece, int8_t square) { - return midgamePst[piece][square]; -} - -int getEndgamePstValue(PieceType piece, int8_t square) { - return endgamePst[piece][square]; -} - -void setMidgamePstValue(PieceType piece, int8_t square, int value) { - midgamePst[piece][square] = value; -} - -void setEndgamePstValue(PieceType piece, int8_t square, int value) { - endgamePst[piece][square] = value; -} - -std::vector getBaseMidgameValues() { - std::vector values; - - for (int i = 1; i < 12; i += 2) { - for (int j = 0; j < 64; j++) { - values.emplace_back(baseMidgamePst[i][j]); - } - } - - return values; -} - -std::vector getBaseEndgameValues() { - std::vector values; - - for (int i = 1; i < 12; i += 2) { - for (int j = 0; j < 64; j++) { - values.emplace_back(baseEndgamePst[i][j]); - } - } - - return values; -} - -std::vector getMidgameValues() { - std::vector values; - - for (int i = 1; i < 12; i += 2) { - for (int j = 0; j < 64; j++) { - values.emplace_back(midgamePst[i][j]); - } - } - - return values; -} - -std::vector getEndgameValues() { - std::vector values; - - for (int i = 1; i < 12; i += 2) { - for (int j = 0; j < 64; j++) { - values.emplace_back(endgamePst[i][j]); - } - } - - return values; -} - -void initializePst() { - for (int piece = 0; piece < 12; piece++) { - PieceType pieceType = static_cast(piece); - - for (int8_t square = 0; square < 64; square++) { - switch (pieceType) { - case WHITE_PAWN: - midgamePst[pieceType][square] = midgamePawnTable[square ^ 56]; - endgamePst[pieceType][square] = endgamePawnTable[square ^ 56]; - baseMidgamePst[pieceType][square] = baseMidgamePawnTable[square ^ 56]; - baseEndgamePst[pieceType][square] = baseEndgamePawnTable[square ^ 56]; - break; - case BLACK_PAWN: - midgamePst[pieceType][square] = midgamePawnTable[square]; - endgamePst[pieceType][square] = endgamePawnTable[square]; - baseMidgamePst[pieceType][square] = baseMidgamePawnTable[square]; - baseEndgamePst[pieceType][square] = baseEndgamePawnTable[square]; - break; - case WHITE_KNIGHT: - midgamePst[pieceType][square] = midgameKnightTable[square ^ 56]; - endgamePst[pieceType][square] = endgameKnightTable[square ^ 56]; - baseMidgamePst[pieceType][square] = baseMidgameKnightTable[square ^ 56]; - baseEndgamePst[pieceType][square] = baseEndgameKnightTable[square ^ 56]; - break; - case BLACK_KNIGHT: - midgamePst[pieceType][square] = midgameKnightTable[square]; - endgamePst[pieceType][square] = endgameKnightTable[square]; - baseMidgamePst[pieceType][square] = baseMidgameKnightTable[square]; - baseEndgamePst[pieceType][square] = baseEndgameKnightTable[square]; - break; - case WHITE_BISHOP: - midgamePst[pieceType][square] = midgameBishopTable[square ^ 56]; - endgamePst[pieceType][square] = endgameBishopTable[square ^ 56]; - baseMidgamePst[pieceType][square] = baseMidgameBishopTable[square ^ 56]; - baseEndgamePst[pieceType][square] = baseEndgameBishopTable[square ^ 56]; - break; - case BLACK_BISHOP: - midgamePst[pieceType][square] = midgameBishopTable[square]; - endgamePst[pieceType][square] = endgameBishopTable[square]; - baseMidgamePst[pieceType][square] = baseMidgameBishopTable[square]; - baseEndgamePst[pieceType][square] = baseEndgameBishopTable[square]; - break; - case WHITE_ROOK: - midgamePst[pieceType][square] = midgameRookTable[square ^ 56]; - endgamePst[pieceType][square] = endgameRookTable[square ^ 56]; - baseMidgamePst[pieceType][square] = baseMidgameRookTable[square ^ 56]; - baseEndgamePst[pieceType][square] = baseEndgameRookTable[square ^ 56]; - break; - case BLACK_ROOK: - midgamePst[pieceType][square] = midgameRookTable[square]; - endgamePst[pieceType][square] = endgameRookTable[square]; - baseMidgamePst[pieceType][square] = baseMidgameRookTable[square]; - baseEndgamePst[pieceType][square] = baseEndgameRookTable[square]; - break; - case WHITE_QUEEN: - midgamePst[pieceType][square] = midgameQueenTable[square ^ 56]; - endgamePst[pieceType][square] = endgameQueenTable[square ^ 56]; - baseMidgamePst[pieceType][square] = baseMidgameQueenTable[square ^ 56]; - baseEndgamePst[pieceType][square] = baseEndgameQueenTable[square ^ 56]; - break; - case BLACK_QUEEN: - midgamePst[pieceType][square] = midgameQueenTable[square]; - endgamePst[pieceType][square] = endgameQueenTable[square]; - baseMidgamePst[pieceType][square] = baseMidgameQueenTable[square]; - baseEndgamePst[pieceType][square] = baseEndgameQueenTable[square]; - break; - case WHITE_KING: - midgamePst[pieceType][square] = midgameKingTable[square ^ 56]; - endgamePst[pieceType][square] = endgameKingTable[square ^ 56]; - baseMidgamePst[pieceType][square] = baseMidgameKingTable[square ^ 56]; - baseEndgamePst[pieceType][square] = baseEndgameKingTable[square ^ 56]; - break; - case BLACK_KING: - midgamePst[pieceType][square] = midgameKingTable[square]; - endgamePst[pieceType][square] = endgameKingTable[square]; - baseMidgamePst[pieceType][square] = baseMidgameKingTable[square]; - baseEndgamePst[pieceType][square] = baseEndgameKingTable[square]; - break; - } - } - } -} -} // namespace Zagreus \ No newline at end of file diff --git a/src/pst.h b/src/pst.h deleted file mode 100644 index 2a044872..00000000 --- a/src/pst.h +++ /dev/null @@ -1,44 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#pragma once - -#include "types.h" - -namespace Zagreus { -// PST tables from https://www.chessprogramming.org/Simplified_Evaluation_Function -void initializePst(); - -int getMidgamePstValue(PieceType piece, int8_t square); - -int getEndgamePstValue(PieceType piece, int8_t square); - -void setMidgamePstValue(PieceType piece, int8_t square, int value); - -void setEndgamePstValue(PieceType piece, int8_t square, int value); - -std::vector getMidgameValues(); - -std::vector getEndgameValues(); - -std::vector getBaseMidgameValues(); - -std::vector getBaseEndgameValues(); -} // namespace Zagreus \ No newline at end of file diff --git a/src/search.cpp b/src/search.cpp deleted file mode 100644 index 5b9b9309..00000000 --- a/src/search.cpp +++ /dev/null @@ -1,538 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#include "search.h" - -#include - -#include "../senjo/Output.h" -#include "evaluate.h" -#include "features.h" -#include "movegen.h" -#include "movelist_pool.h" -#include "movepicker.h" -#include "timemanager.h" -#include "tt.h" - -namespace Zagreus { -TranspositionTable* tt = TranspositionTable::getTT(); -static int lmrReductions[MAX_PLY][MAX_MOVES]{}; - -void initializeSearch() { - for (int depth = 0; depth < MAX_PLY; depth++) { - for (int movesPlayed = 0; movesPlayed < MAX_MOVES; movesPlayed++) { - // Formula from ethereal: https://github.com/AndyGrant/Ethereal/blob/a7a7a8ed69cbbb4e9a3b02fc5d3d0d9facfa1526/src/search.c#L155C13-L155C21 - // Will probably tune the constants using SPSA at some point - lmrReductions[depth][movesPlayed] = static_cast(0.78 + std::log(depth) * log(movesPlayed) / 2.47); - } - } -} - -template -Move getBestMove(senjo::GoParams params, ZagreusEngine& engine, Bitboard& board, - senjo::SearchStats& searchStats) { - MoveListPool* moveListPool = MoveListPool::getInstance(); - auto startTime = std::chrono::steady_clock::now(); - SearchContext searchContext{}; - searchContext.startTime = startTime; - int depth = 0; - int bestScore = MAX_NEGATIVE; - Line bestPvLine{}; - Line pvLine{}; - pvLine.startPly = board.getPly(); - - tt->ageHistoryTable(); - - while (!engine.stopRequested()) { - // Update the endtime using new data - searchContext.endTime = getEndTime(searchContext, params, engine, board.getMovingColor()); - - auto currentTime = std::chrono::steady_clock::now(); - if (currentTime > searchContext.endTime) { - engine.stopSearching(); - break; - } - - depth += 1; - searchStats.depth = depth; - searchStats.seldepth = 0; - - if (board.getPly() + depth >= MAX_PLY + 1) { - break; - } - - // If the go command has a max depth argument, terminate when reaching the desired depth. - if (params.depth > 0 && depth > params.depth) { - return pvLine.moves[0]; - } - - int score = search(board, MAX_NEGATIVE, MAX_POSITIVE, depth, searchContext, - searchStats, pvLine); - - currentTime = std::chrono::steady_clock::now(); - if (currentTime > searchContext.endTime) { - engine.stopSearching(); - break; - } - - Move bestMove = pvLine.moves[0]; - Move previousBestMove = board.getPvLine().moves[0]; - - // If bestScore is positive and iterationScore is 0 or negative or vice versa, set suddenScoreSwing to true - if (depth > 1 && ((bestScore > 0 && score < 0) || (bestScore < 0 && score > 0))) { - searchContext.suddenScoreSwing = true; - } - - // If the iterationScore suddenly dropped by 150 or more from bestScore, set suddenScoreDrop to true - if (depth > 1 && score - bestScore <= -150) { - searchContext.suddenScoreDrop = true; - } - - // If bestMove changes, increment context.pvChanges - if (depth > 1 && (bestMove.from != previousBestMove.from || bestMove.to != previousBestMove. - to)) { - searchContext.pvChanges += 1; - } - - if (score > bestScore) { - bestScore = score; - } - - bestPvLine = pvLine; - board.setPvLine(bestPvLine); - searchStats.score = score; - printPv(searchStats, startTime, bestPvLine); - } - - engine.stopSearching(); - Move bestMove = bestPvLine.moves[0]; - MoveList* legalMoves = moveListPool->getMoveList(); - generateMoves(board, legalMoves); - - // Check if bestMove is a legal move (sometimes in endgames that drag on for long time, the PV is empty) - for (int i = 0; i < legalMoves->size; i++) { - if (legalMoves->moves[i].from == bestMove.from && legalMoves->moves[i].to == bestMove.to) { - moveListPool->releaseMoveList(legalMoves); - return bestMove; - } - } - - return legalMoves->moves[0]; -} - -template Move getBestMove(senjo::GoParams params, ZagreusEngine& engine, Bitboard& board, - senjo::SearchStats& searchStats); -template Move getBestMove(senjo::GoParams params, ZagreusEngine& engine, Bitboard& board, - senjo::SearchStats& searchStats); - -template -int search(Bitboard& board, int alpha, int beta, int16_t depth, - SearchContext& context, - senjo::SearchStats& searchStats, Line& pvLine) { - constexpr bool IS_PV_NODE = nodeType == PV || nodeType == ROOT; - constexpr bool IS_ROOT_NODE = nodeType == ROOT; - constexpr PieceColor OPPOSITE_COLOR = color == WHITE ? BLACK : WHITE; - - if (board.isDraw()) { - return DRAW_SCORE; - } - - auto currentTime = std::chrono::steady_clock::now(); - if (!IS_ROOT_NODE && (currentTime > context.endTime || board.getPly() >= MAX_PLY)) { - pvLine.moveCount = 0; - return beta; - } - - searchStats.nodes += 1; - - bool ownKingInCheck = board.isKingInCheck(); - if (ownKingInCheck) { - int see = board.seeOpponent(board.getPreviousMove().to); - - if (see >= NO_CAPTURE_SCORE) { - depth += 1; - } - } - - if (depth <= 0) { - pvLine.moveCount = 0; - return qsearch(board, alpha, beta, depth, context, searchStats); - } - - if (!IS_PV_NODE && board.getHalfMoveClock() < 80) { - int ttScore = tt->getScore(board.getZobristHash(), depth, alpha, - beta, board.getPly()); - - if (ttScore != INT32_MIN) { - return ttScore; - } - } - - constexpr bool isPreviousMoveNull = nodeType == NULL_MOVE; - - // Null move pruning - if (!IS_PV_NODE && depth >= 3 && !isPreviousMoveNull && board. - getAmountOfMinorOrMajorPieces< - color>() > 0) { - if (!ownKingInCheck && Evaluation(board).evaluate() >= beta) { - int r = 3 + (depth >= 6) + (depth >= 12); - - Line nullLine{}; - SearchContext nullContext{}; - nullContext.startTime = context.startTime; - nullContext.endTime = context.endTime; - board.makeNullMove(); - int nullScore = -search(board, -beta, -beta + 1, depth - r, - nullContext, searchStats, nullLine); - board.unmakeNullMove(); - int mateScores = MATE_SCORE - MAX_PLY; - - if (nullScore >= beta && nullScore < mateScores) { - return nullScore; - } - } - } - - bool doPvSearch = true; - MoveListPool* moveListPool = MoveListPool::getInstance(); - MoveList* moves = moveListPool->getMoveList(); - - if (ownKingInCheck) { - generateMoves(board, moves); - } else { - generateMoves(board, moves); - } - - auto movePicker = MovePicker(moves); - int legalMoveCount = 0; - Line nodeLine{}; - nodeLine.startPly = board.getPly(); - int bestScore = MAX_NEGATIVE; - Move bestMove = {NO_SQUARE, NO_SQUARE}; - - while (movePicker.hasNext()) { - Move move = movePicker.getNextMove(); - board.makeMove(move); - - if (board.isKingInCheck()) { - board.unmakeMove(move); - continue; - } - - legalMoveCount += 1; - - int score = 0; - bool didLmr = false; - bool shouldFullSearch = false; - - // Late Move Reduction (LMR, not in Root nodes) - if (!IS_ROOT_NODE && depth >= 3 && move.captureScore == NO_CAPTURE_SCORE && move. - promotionPiece == EMPTY && legalMoveCount > 1) { - int R = std::max(0, lmrReductions[depth][legalMoveCount]); - - // Increase reduction for non-PV nodes - R += !IS_PV_NODE; - - // Decrease reduction when in check - R -= ownKingInCheck; - - // Decrease reduction for killer moves - uint64_t moveCode = encodeMove(&move); - if (tt->killerMoves[0][board.getPly()] == moveCode - || tt->killerMoves[1][board.getPly()] == moveCode - || tt->killerMoves[2][board.getPly()] == moveCode) { - R -= 1; - } - - // Decrease for counter moves - if (tt->counterMoves[move.piece][move.to] == moveCode) { - R -= 1; - } - - // Don't drop into qsearch - R = std::min(depth - 1, std::max(1, R)); - - // Depth - 1 (R = 1) is the "default" search, so skip LMR - if (R > 1) { - score = -search( - board, -alpha - 1, -alpha, depth - R, context, searchStats, nodeLine); - - didLmr = true; - - if (score > alpha) { - shouldFullSearch = true; - } - } - } - - if (!didLmr || shouldFullSearch) { - if (IS_PV_NODE && doPvSearch) { - score = -search(board, -beta, -alpha, depth - 1, - context, - searchStats, nodeLine); - } else { - score = -search(board, -alpha - 1, -alpha, - depth - 1, context, - searchStats, nodeLine); - - if (score > alpha && score < beta) { - score = -search(board, -beta, -alpha, - depth - 1, context, - searchStats, nodeLine); - } - } - } - - board.unmakeMove(move); - - if (score > bestScore) { - bestScore = score; - - if (score > alpha) { - bestMove = move; - - if (score >= beta) { - if (move.captureScore == NO_CAPTURE_SCORE && move.promotionPiece == EMPTY) { - uint64_t moveCode = encodeMove(&move); - tt->killerMoves[2][board.getPly()] = tt->killerMoves[1][board.getPly()]; - tt->killerMoves[1][board.getPly()] = tt->killerMoves[0][board.getPly()]; - tt->killerMoves[0][board.getPly()] = moveCode; - tt->historyMoves[move.piece][move.to] += depth * depth; - - if (!isPreviousMoveNull) { - tt->counterMoves[board.getPreviousMove().piece][board.getPreviousMove(). - to] = moveCode; - } - } - - moveListPool->releaseMoveList(moves); - if (!IS_ROOT_NODE) { - uint32_t bestMoveCode = encodeMove(&bestMove); - tt->addPosition(board.getZobristHash(), depth, score, FAIL_HIGH_NODE, - bestMoveCode, board.getPly(), context); - } - return score; - } - - alpha = score; - doPvSearch = false; - - pvLine.moves[0] = move; - std::memcpy(pvLine.moves + 1, nodeLine.moves, nodeLine.moveCount * sizeof(Move)); - pvLine.moveCount = nodeLine.moveCount + 1; - } - } - } - - moveListPool->releaseMoveList(moves); - - if (!legalMoveCount) { - if (ownKingInCheck) { - alpha = -MATE_SCORE + board.getPly(); - } else { - alpha = DRAW_SCORE; - } - } - - TTNodeType ttNodeType = FAIL_LOW_NODE; - - if (IS_PV_NODE && (bestMove.from != NO_SQUARE && bestMove.to != NO_SQUARE)) { - ttNodeType = EXACT_NODE; - } - - if (!IS_ROOT_NODE) { - uint32_t bestMoveCode = encodeMove(&bestMove); - tt->addPosition(board.getZobristHash(), depth, alpha, ttNodeType, board.getPly(), - bestMoveCode, context); - } - - return alpha; -} - -template -int qsearch(Bitboard& board, int alpha, int beta, int16_t depth, - SearchContext& context, - senjo::SearchStats& searchStats) { - constexpr PieceColor OPPOSITE_COLOR = color == WHITE ? BLACK : WHITE; - constexpr TTNodeType IS_PV_NODE = nodeType == PV ? EXACT_NODE : FAIL_LOW_NODE; - - if (board.isDraw()) { - return DRAW_SCORE; - } - - auto currentTime = std::chrono::steady_clock::now(); - if (currentTime > context.endTime || board.getPly() >= MAX_PLY) { - return beta; - } - - if (!IS_PV_NODE && board.getHalfMoveClock() < 80) { - int ttScore = TranspositionTable::getTT()->getScore(board.getZobristHash(), depth, alpha, - beta, board.getPly()); - - if (ttScore != INT32_MIN) { - return ttScore; - } - } - - searchStats.qnodes += 1; - - bool inCheck = board.isKingInCheck(); - Move previousMove = board.getPreviousMove(); - - if (!inCheck) { - int standPat = Evaluation(board).evaluate(); - - if (standPat >= beta) { - tt->addPosition(board.getZobristHash(), depth, standPat, FAIL_HIGH_NODE, 0, - board.getPly(), context); - return standPat; - } - - if (board.getAmountOfMinorOrMajorPieces() >= 2 && board.getAmountOfMinorOrMajorPieces - () >= 2 && board.getAmountOfPawns() > 0 && board.getAmountOfPawns - () > 0) { - int queenDelta = std::max(getEvalValue(ENDGAME_QUEEN_MATERIAL), - getEvalValue(MIDGAME_QUEEN_MATERIAL)); - int minPawnValue = std::min(getEvalValue(ENDGAME_PAWN_MATERIAL), - getEvalValue(MIDGAME_PAWN_MATERIAL)); - - if (previousMove.promotionPiece != EMPTY) { - queenDelta += getPieceWeight(previousMove.promotionPiece) - minPawnValue; - } - - if (standPat < alpha - queenDelta) { - return alpha; - } - } - - if (alpha < standPat) { - alpha = standPat; - } - } - - MoveListPool* moveListPool = MoveListPool::getInstance(); - MoveList* moves = moveListPool->getMoveList(); - - if (inCheck) { - generateMoves(board, moves); - } else { - generateMoves(board, moves); - } - - auto movePicker = MovePicker(moves); - int legalMoveCount = 0; - previousMove = {}; - int bestScore = MAX_NEGATIVE; - Move bestMove = {NO_SQUARE, NO_SQUARE}; - - while (movePicker.hasNext()) { - Move move = movePicker.getNextMove(); - - if (!inCheck && move.captureScore < NO_CAPTURE_SCORE) { - continue; - } - - board.makeMove(move); - - if (board.isKingInCheck()) { - board.unmakeMove(move); - continue; - } - - legalMoveCount += 1; - - int score = -qsearch(board, -beta, -alpha, depth - 1, context, - searchStats); - board.unmakeMove(move); - - if (score > bestScore) { - bestScore = score; - - if (score > alpha) { - bestMove = move; - - if (score >= beta) { - moveListPool->releaseMoveList(moves); - uint32_t bestMoveCode = encodeMove(&bestMove); - tt->addPosition(board.getZobristHash(), depth, score, FAIL_HIGH_NODE, - bestMoveCode, board.getPly(), context); - return beta; - } - - alpha = score; - } - } - } - - moveListPool->releaseMoveList(moves); - - if (legalMoveCount == 0 && inCheck) { - return -MATE_SCORE + board.getPly(); - } - - TTNodeType ttNodeType = FAIL_LOW_NODE; - - if (IS_PV_NODE && (bestMove.from != NO_SQUARE && bestMove.to != NO_SQUARE)) { - ttNodeType = EXACT_NODE; - } - - uint32_t bestMoveCode = encodeMove(&bestMove); - tt->addPosition(board.getZobristHash(), depth, alpha, ttNodeType, bestMoveCode, board.getPly(), - context); - return alpha; -} - -template int qsearch(Bitboard& board, int alpha, int beta, - int16_t depth, SearchContext& context, - senjo::SearchStats& searchStats); -template int qsearch(Bitboard& board, int alpha, int beta, - int16_t depth, SearchContext& context, - senjo::SearchStats& searchStats); - -void printPv(senjo::SearchStats& searchStats, std::chrono::steady_clock::time_point& startTime, - Line& pvLine) { - searchStats.pv = ""; - for (int i = 0; i < pvLine.moveCount; i++) { - Move move = pvLine.moves[i]; - searchStats.pv += getNotation(move.from) + getNotation(move.to); - - if (move.promotionPiece != EMPTY) { - if (move.promotionPiece == WHITE_QUEEN || move.promotionPiece == BLACK_QUEEN) { - searchStats.pv += "q"; - } else if (move.promotionPiece == WHITE_ROOK || move.promotionPiece == BLACK_ROOK) { - searchStats.pv += "r"; - } else if (move.promotionPiece == WHITE_BISHOP || move.promotionPiece == BLACK_BISHOP) { - searchStats.pv += "b"; - } else if (move.promotionPiece == WHITE_KNIGHT || move.promotionPiece == BLACK_KNIGHT) { - searchStats.pv += "n"; - } - } - - if (i != pvLine.moveCount - 1) { - searchStats.pv += " "; - } - } - - searchStats.msecs = - duration_cast(std::chrono::steady_clock::now() - startTime) - .count(); - senjo::Output(senjo::Output::NoPrefix) << searchStats; -} -} // namespace Zagreus \ No newline at end of file diff --git a/src/search.h b/src/search.h deleted file mode 100644 index 71ff235d..00000000 --- a/src/search.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#pragma once -#include - -#include "bitboard.h" -#include "engine.h" -#include "types.h" -#include "../senjo/GoParams.h" - -namespace Zagreus { -struct SearchContext { - std::chrono::time_point startTime; - std::chrono::time_point endTime; - int pvChanges = 0; - // A boolean variable that keeps track if the score suddenly went from positive to negative or - // vice versa - bool suddenScoreSwing = false; - // A boolean variable that keeps track if the score suddenly had a big drop (-150 or more) - bool suddenScoreDrop = false; -}; - -void initializeSearch(); - -template -Move getBestMove(senjo::GoParams params, ZagreusEngine& engine, Bitboard& board, - senjo::SearchStats& searchStats); - -template -int search(Bitboard& board, int alpha, int beta, int16_t depth, - SearchContext& context, - senjo::SearchStats& searchStats, Line& pvLine); - -template -int qsearch(Bitboard& board, int alpha, int beta, int16_t depth, - SearchContext& context, - senjo::SearchStats& searchStats); - -void printPv(senjo::SearchStats& searchStats, std::chrono::steady_clock::time_point& startTime, - Line& pvLine); -} // namespace Zagreus \ No newline at end of file diff --git a/src/timemanager.cpp b/src/timemanager.cpp deleted file mode 100644 index 668b3d56..00000000 --- a/src/timemanager.cpp +++ /dev/null @@ -1,103 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#include "timemanager.h" - -#include -#include - -#include "../senjo/GoParams.h" -#include "engine.h" -#include "types.h" - -namespace Zagreus { -std::chrono::time_point getEndTime(SearchContext& context, - senjo::GoParams& params, - ZagreusEngine& engine, - PieceColor movingColor) { - if (params.infinite || params.depth > 0 || params.nodes > 0) { - return std::chrono::time_point::max(); - } - - if (params.movetime > 0) { - return context.startTime + - std::chrono::milliseconds(params.movetime - - engine.getOption("MoveOverhead").getIntValue()); - } - - int movesToGo = params.movestogo ? params.movestogo : 50ULL; - - uint64_t timeLeft = 0; - - if (movingColor == WHITE) { - timeLeft += params.wtime; - timeLeft += params.winc * movesToGo; - } else { - timeLeft += params.btime; - timeLeft += params.binc * movesToGo; - } - - uint64_t moveOverhead = engine.getOption("MoveOverhead").getIntValue(); - timeLeft -= moveOverhead * movesToGo; - - timeLeft = std::max((uint64_t)timeLeft, (uint64_t)1ULL); - uint64_t maxTime; - - if (movingColor == WHITE) { - if (params.wtime > moveOverhead) { - maxTime = (params.wtime - moveOverhead) / 100 * 80; - } else { - maxTime = params.wtime / 2 / 100 * 80; - } - } else { - if (params.btime > moveOverhead) { - maxTime = (params.btime - moveOverhead) / 100 * 80; - } else { - maxTime = params.btime / 2 / 100 * 80; - } - } - - uint64_t timePerMove = timeLeft / movesToGo; - - // Based on context.pvChanges, scale timePerMove between 1.0 and 1.5. After 5 or more move - // changes, timePerMove will be 1.5 times as long. - if (context.pvChanges > 0) { - timePerMove = - timePerMove * (1.0 + std::min(static_cast(context.pvChanges), 5.0) / 10.0); - } - - // if the score suddenly went from positive to negative or vice versa, increase timePerMove by 50% - if (context.suddenScoreSwing) { - timePerMove = timePerMove * 1.5; - } - - // if the score suddenly dropped by 100cp or more, increase timePerMove by 50% - if (context.suddenScoreDrop) { - timePerMove = timePerMove * 1.5; - } - - if (timePerMove > maxTime) { - timePerMove = maxTime; - } - - timePerMove = std::max((uint64_t)timePerMove, (uint64_t)1ULL); - return context.startTime + std::chrono::milliseconds(timePerMove); -} -} // namespace Zagreus \ No newline at end of file diff --git a/src/timemanager.h b/src/timemanager.h deleted file mode 100644 index 1f575d21..00000000 --- a/src/timemanager.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#pragma once - -#include "../senjo/GoParams.h" -#include "engine.h" -#include "search.h" -#include "types.h" - -namespace Zagreus { -std::chrono::time_point getEndTime(SearchContext& context, - senjo::GoParams& params, - ZagreusEngine& engine, - PieceColor movingColor); -} // namespace Zagreus \ No newline at end of file diff --git a/src/tt.cpp b/src/tt.cpp deleted file mode 100644 index 629c0dfe..00000000 --- a/src/tt.cpp +++ /dev/null @@ -1,168 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a chess engine that supports the UCI protocol - Copyright (C) 2023 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#include "tt.h" - -#include -#include - -#include "search.h" - -namespace Zagreus { -void TranspositionTable::addPosition(uint64_t zobristHash, int16_t depth, int score, - TTNodeType nodeType, uint32_t bestMoveCode, int ply, - SearchContext& context) { - // current time - auto currentTime = std::chrono::steady_clock::now(); - if (score > MAX_POSITIVE || score < MAX_NEGATIVE || currentTime > context.endTime) { - return; - } - - if (depth < INT8_MIN || depth > INT8_MAX) { - return; - } - - uint64_t index = zobristHash & hashSize; - TTEntry* entry = &transpositionTable[index]; - - if (depth > entry->depth) { - int adjustedScore = score; - - if (adjustedScore >= (MATE_SCORE - MAX_PLY)) { - adjustedScore += ply; - } else if (adjustedScore <= (-MATE_SCORE + MAX_PLY)) { - adjustedScore -= ply; - } - - entry->validationHash = zobristHash >> 32; - entry->depth = static_cast(depth); - entry->bestMoveCode = bestMoveCode; - entry->score = adjustedScore; - entry->nodeType = nodeType; - } -} - -int TranspositionTable::getScore(uint64_t zobristHash, int16_t depth, int alpha, int beta, - int ply) { - if (depth < INT8_MIN || depth > INT8_MAX) { - return INT32_MIN; - } - - uint64_t index = zobristHash & hashSize; - uint32_t validationHash = zobristHash >> 32; - TTEntry* entry = &transpositionTable[index]; - - if (entry->validationHash == validationHash && entry->depth >= depth) { - bool returnScore = false; - - if (entry->nodeType == EXACT_NODE) { - returnScore = true; - } else if (entry->nodeType == FAIL_LOW_NODE) { - if (entry->score <= alpha) { - returnScore = true; - } - } else if (entry->nodeType == FAIL_HIGH_NODE) { - if (entry->score >= beta) { - returnScore = true; - } - } - - if (returnScore) { - int adjustedScore = entry->score; - - if (adjustedScore >= MATE_SCORE) { - adjustedScore -= ply; - } else if (adjustedScore <= -MATE_SCORE) { - adjustedScore += ply; - } - - return adjustedScore; - } - } - - return INT32_MIN; -} - -TTEntry* TranspositionTable::getEntry(uint64_t zobristHash) { - uint64_t index = zobristHash & hashSize; - - return &transpositionTable[index]; -} - -void TranspositionTable::setTableSize(int megaBytes) { - if ((megaBytes & (megaBytes - 1)) != 0) { - megaBytes = 1 << static_cast(log2(megaBytes)); - } - - uint64_t byteSize = megaBytes * 1024 * 1024; - uint64_t entryCount = byteSize / sizeof(TTEntry); - - delete[] transpositionTable; - transpositionTable = new TTEntry[entryCount]{}; - hashSize = entryCount - 1; - - for (uint64_t i = 0; i < entryCount; i++) { - transpositionTable[i] = {}; - } -} - -TranspositionTable* TranspositionTable::getTT() { - static TranspositionTable instance{}; - return &instance; -} - -void TranspositionTable::ageHistoryTable() { - for (int i = 0; i < PIECE_TYPES; i++) { - for (int j = 0; j < SQUARES; j++) { - historyMoves[i][j] /= 8; - } - } -} - -void TranspositionTable::reset() { - for (int i = 0; i < 3; i++) { - delete[] killerMoves[i]; - } - - for (int i = 0; i < 12; i++) { - delete[] historyMoves[i]; - } - - for (int i = 0; i < PIECE_TYPES; i++) { - delete[] counterMoves[i]; - } - - for (uint64_t i = 0; i < hashSize; i++) { - transpositionTable[i] = {}; - } - - for (int i = 0; i < 3; i++) { - killerMoves[i] = new uint32_t[MAX_PLY]{}; - } - - for (int i = 0; i < PIECE_TYPES; i++) { - historyMoves[i] = new uint32_t[64]{}; - } - - for (int i = 0; i < PIECE_TYPES; i++) { - counterMoves[i] = new uint32_t[64]{}; - } -} -} // namespace Zagreus \ No newline at end of file diff --git a/src/tt.h b/src/tt.h deleted file mode 100644 index acb16692..00000000 --- a/src/tt.h +++ /dev/null @@ -1,106 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#pragma once - -#include -#include - -#include "search.h" -#include "types.h" - -namespace Zagreus { -enum TTNodeType : uint8_t { - EXACT_NODE, // Not a PV node - FAIL_LOW_NODE, // Alpha score - FAIL_HIGH_NODE // Beta score -}; - -struct TTEntry { - int score = 0; - uint32_t bestMoveCode = 0; - uint32_t validationHash = 0; - int8_t depth = INT8_MIN; - TTNodeType nodeType = EXACT_NODE; -}; - -class TranspositionTable { -public: - TTEntry* transpositionTable = new TTEntry[1]{}; - uint32_t** killerMoves = new uint32_t*[3]{}; - uint32_t** historyMoves = new uint32_t*[PIECE_TYPES]{}; - uint32_t** counterMoves = new uint32_t*[PIECE_TYPES]{}; - - uint64_t hashSize = 0; - - TranspositionTable() { - for (int i = 0; i < 3; i++) { - killerMoves[i] = new uint32_t[MAX_PLY]{}; - } - - for (int i = 0; i < PIECE_TYPES; i++) { - historyMoves[i] = new uint32_t[64]{}; - } - - for (int i = 0; i < PIECE_TYPES; i++) { - counterMoves[i] = new uint32_t[64]{}; - } - } - - ~TranspositionTable() { - delete[] transpositionTable; - - for (int i = 0; i < 3; i++) { - delete[] killerMoves[i]; - } - - for (int i = 0; i < 12; i++) { - delete[] historyMoves[i]; - } - - for (int i = 0; i < PIECE_TYPES; i++) { - delete[] counterMoves[i]; - } - - delete[] killerMoves; - delete[] historyMoves; - delete[] counterMoves; - } - - TranspositionTable(TranspositionTable& other) = delete; - - void operator=(const TranspositionTable&) = delete; - - static TranspositionTable* getTT(); - - void setTableSize(int megaBytes); - - void addPosition(uint64_t zobristHash, int16_t depth, int score, TTNodeType nodeType, - uint32_t bestMoveCode, int ply, SearchContext& context); - - int getScore(uint64_t zobristHash, int16_t depth, int alpha, int beta, int ply); - - TTEntry* getEntry(uint64_t zobristHash); - - void ageHistoryTable(); - - void reset(); -}; -} // namespace Zagreus \ No newline at end of file diff --git a/src/tuner.cpp b/src/tuner.cpp deleted file mode 100644 index a62b71f0..00000000 --- a/src/tuner.cpp +++ /dev/null @@ -1,400 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023-2024 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#include "tuner.h" - -#include -#include -#include -#include -#include - -#include "../senjo/UCIAdapter.h" -#include "bitboard.h" -#include "evaluate.h" -#include "features.h" -#include "pst.h" -#include "search.h" - -namespace Zagreus { -int epochs = 10; -float K = 0.0; - -int batchSize = 256; -float learningRate = 0.1; -float delta = 1.0; -float optimizerEpsilon = 1e-6; -float beta1 = 0.9; -float beta2 = 0.999; -// 0 = random seed -long seed = 0; - -Bitboard tunerBoard{}; - -std::vector> createBatches(std::vector& positions) { - // Create random batches of batchSize positions - std::vector> batches; - batches.reserve(positions.size() / batchSize); - - for (int i = 0; i < positions.size(); i += batchSize) { - std::vector batch; - for (int j = i; j < i + batchSize && j < positions.size(); j++) { - batch.push_back(positions[j]); - } - batches.push_back(batch); - } - - return batches; -} - -float sigmoid(float x) { - return 1.0f / (1.0f + pow(10.0f, -K * x / 400.0f)); -} - -float evaluationLoss(std::vector& positions) { - float totalLoss = 0.0f; - - for (TunePosition& pos : positions) { - tunerBoard.setFromFenTuner(pos.fen); - int evalScore = Evaluation(tunerBoard).evaluate(); - - // All scores are from white's perspective - if (tunerBoard.getMovingColor() == BLACK) { - evalScore *= -1; - } - - float loss = std::pow(pos.result - sigmoid(evalScore), 2.0f); - totalLoss += loss; - } - - return (1.0f / static_cast(positions.size())) * totalLoss; -} - -float findOptimalK(std::vector& positions) { - const float phi = (1.0f + sqrt(5.0f)) / 2.0f; - const float tolerance = 1e-6f; - - float a = 0.0f; - float b = 2.0f; - - float x1 = b - (b - a) / phi; - float x2 = a + (b - a) / phi; - - K = x1; - float f1 = evaluationLoss(positions); - K = x2; - float f2 = evaluationLoss(positions); - - while (std::abs(b - a) > tolerance) { - if (f1 < f2) { - b = x2; - x2 = x1; - x1 = b - (b - a) / phi; - f2 = f1; - K = x1; - f1 = evaluationLoss(positions); - } else { - a = x1; - x1 = x2; - x2 = a + (b - a) / phi; - f1 = f2; - K = x2; - f2 = evaluationLoss(positions); - } - } - - return (a + b) / 2.0f; -} - -std::vector loadPositions( - char* filePath, std::chrono::time_point& maxEndTime, - std::mt19937_64 gen) { - std::cout << "Loading positions..." << std::endl; - std::vector positions; - std::vector lines; - std::ifstream fin(filePath); - int win = 0; - int loss = 0; - int draw = 0; - - std::string line; - while (std::getline(fin, line)) { - lines.emplace_back(line); - } - - positions.reserve(lines.size()); - - for (std::string& posLine : lines) { - if (posLine.empty() || posLine == " ") { - continue; - } - - float result; - std::string resultStr = posLine.substr(posLine.find(" c9 ") + 4, posLine.find(" c9 ") + 4); - std::string fen = posLine.substr(0, posLine.find(" c9 ")); - - if (!tunerBoard.setFromFen(fen) || tunerBoard.isDraw() || tunerBoard.isWinner() - || tunerBoard.isWinner()) { - continue; - } - - // Remove " and ; from result - std::erase(resultStr, '"'); - std::erase(resultStr, ';'); - - if (resultStr == "1" || resultStr == "1-0") { - result = 1.0; - win++; - } else if (resultStr == "0" || resultStr == "0-1") { - result = 0.0; - loss++; - } else { - result = 0.5; - draw++; - } - - int evalScore = Evaluation(tunerBoard).evaluate(); - - // All scores are from white's perspective - if (tunerBoard.getMovingColor() == BLACK) { - evalScore *= -1; - } - - TunePosition tunePos{fen, result, evalScore}; - positions.emplace_back(tunePos); - } - - // Reduce the biggest two classes to the size of the smallest class - int smallestClassSize = std::min(win, std::min(loss, draw)); - std::vector newPositions; - int newWin = 0; - int newLoss = 0; - int newDraw = 0; - - // Shuffle positions - std::shuffle(positions.begin(), positions.end(), gen); - - for (TunePosition& pos : positions) { - if (pos.result == 1.0 && newWin < smallestClassSize) { - newPositions.emplace_back(pos); - newWin++; - } else if (pos.result == 0.0 && newLoss < smallestClassSize) { - newPositions.emplace_back(pos); - newLoss++; - } else if (pos.result == 0.5 && newDraw < smallestClassSize) { - newPositions.emplace_back(pos); - newDraw++; - } - - if (newWin >= smallestClassSize && newLoss >= smallestClassSize && - newDraw >= smallestClassSize) { - break; - } - } - - // Write all newPositions to a file by their fen strings - std::ofstream fout("cleaned_positions.epd"); - for (TunePosition& pos : newPositions) { - std::string result; - - if (pos.result == 1.0) { - result = "1-0"; - } else if (pos.result == 0.0) { - result = "0-1"; - } else { - result = "1/2-1/2"; - } - - fout << pos.fen << " c9 \"" << result << "\";" << std::endl; - } - - fout.close(); - - std::cout << "Loaded " << newPositions.size() << " positions." << std::endl; - std::cout << "Win: " << newWin << ", Loss: " << newLoss << ", Draw: " << newDraw << std::endl; - return positions; -} - -void exportNewEvalValues(std::vector& bestParams, int epoch, float validationLoss) { - std::ofstream fout("tuned_params_epoch_" + std::to_string(epoch) + ".txt"); - - fout << "Epoch: " << epoch << ", Val Loss: " << validationLoss << std::endl << std::endl; - - // Declare pieceNames - std::string pieceNames[6] = {"Pawn", "Knight", "Bishop", "Rook", "Queen", "King"}; - - fout << "int evalValues[" << getEvalFeatureSize() << "] = { "; - for (int i = 0; i < getEvalFeatureSize(); i++) { - fout << static_cast(bestParams[i]); - - if (i != bestParams.size() - 1) { - fout << ", "; - } - } - fout << " };" << std::endl << std::endl; - - // Write the 6 piece square tables to a file (2 tables per piece, a midgame and endgame table - // declared like this: int midgamePawnTable[64] and int endgamePawnTable[64] The 6 midgame tables - // are declared first, then the 6 endgame tables - int pstSize = getMidgameValues().size(); - for (int i = 0; i < 6; i++) { - fout << "int midgame" << pieceNames[i] << "Table[64] = { "; - for (int j = 0; j < 64; j++) { - fout << static_cast(bestParams[getEvalFeatureSize() + i * 64 + j]); - - if (j != 63) { - fout << ", "; - } - } - fout << " };" << std::endl; - - fout << "int endgame" << pieceNames[i] << "Table[64] = { "; - for (int j = 0; j < 64; j++) { - fout << static_cast(bestParams[getEvalFeatureSize() + pstSize + i * 64 + j]); - - if (j != 63) { - fout << ", "; - } - } - fout << " };" << std::endl << std::endl; - } -} - -void startTuning(char* filePath) { - std::random_device rd; - std::mt19937_64 gen; // NOLINT(*-msc51-cpp) - - if (seed == 0) { - seed = rd(); - } - - gen = std::mt19937_64(seed); - std::cout << "Using seed: " << seed << std::endl; - - ZagreusEngine engine; - senjo::UCIAdapter adapter(engine); - auto maxEndTime = std::chrono::time_point::max(); - std::vector positions = loadPositions(filePath, maxEndTime, gen); - - engine.setTuning(false); - - std::vector bestParameters = getBaseEvalValues(); - updateEvalValues(bestParameters); - - std::cout << "Finding the optimal K value..." << std::endl; - K = findOptimalK(positions); - std::cout << "Optimal K value: " << K << std::endl; - - std::shuffle(positions.begin(), positions.end(), gen); - - std::vector validationPositions(positions.begin() + positions.size() * 0.9, - positions.end()); - positions.erase(positions.begin() + positions.size() * 0.9, positions.end()); - exportNewEvalValues(bestParameters, 0, evaluationLoss(positions)); - - std::cout << "Starting tuning..." << std::endl; - std::vector m(bestParameters.size(), 0.0); - std::vector v(bestParameters.size(), 0.0); - - std::cout << "Calculating the initial loss..." << std::endl; - float bestLoss = evaluationLoss(validationPositions); - - std::cout << "Initial loss: " << bestLoss << std::endl; - std::cout << "Finding the best parameters. This may take a while..." << std::endl; - std::vector> batches = createBatches(positions); - int epoch = 1; - int iteration = 0; - - while (epoch <= epochs) { - std::shuffle(positions.begin(), positions.end(), gen); - batches = createBatches(positions); - int totalIterations = batches.size(); - std::vector gradients(bestParameters.size(), 0.0f); - float beta1Corrected = 0; - float beta2Corrected = 0; - - for (std::vector& batch : batches) { - iteration++; - - if (iteration == 1) { - beta1Corrected = beta1; - beta2Corrected = beta2; - } else { - beta1Corrected *= beta1; - beta2Corrected *= beta2; - } - - int percentDone = static_cast( - ((iteration % batches.size()) / static_cast(totalIterations)) * 100); - std::cout << "Epoch: " << epoch << ", Iteration: " << (iteration % batches.size() + 1) - << "/" << - totalIterations << " (" << percentDone << "%)" << std::endl; - std::ranges::fill(gradients, 0.0f); - - for (int paramIndex = 0; paramIndex < bestParameters.size(); paramIndex++) { - float oldParam = bestParameters[paramIndex]; - - bestParameters[paramIndex] = oldParam + delta; - updateEvalValues(bestParameters); - float fPlusDelta = evaluationLoss(batch); - - bestParameters[paramIndex] = oldParam - delta; - updateEvalValues(bestParameters); - float fMinusDelta = evaluationLoss(batch); - - bestParameters[paramIndex] = oldParam + 2 * delta; - updateEvalValues(bestParameters); - float fPlus2Delta = evaluationLoss(batch); - - bestParameters[paramIndex] = oldParam - 2 * delta; - updateEvalValues(bestParameters); - float fMinus2Delta = evaluationLoss(batch); - - gradients[paramIndex] += ( - -fPlus2Delta + 8.0f * fPlusDelta - 8.0f * fMinusDelta + fMinus2Delta) / ( - 12.0f * delta); - - // reset - bestParameters[paramIndex] = oldParam; - } - - for (int paramIndex = 0; paramIndex < bestParameters.size(); paramIndex++) { - m[paramIndex] = beta1 * m[paramIndex] + (1.0f - beta1) * gradients[paramIndex]; - v[paramIndex] = beta2 * v[paramIndex] + (1.0f - beta2) * std::pow( - gradients[paramIndex], 2.0f); - float mCorrected = m[paramIndex] / (1.0f - beta1Corrected); - float vCorrected = v[paramIndex] / (1.0f - beta2Corrected); - bestParameters[paramIndex] -= learningRate * mCorrected / ( - sqrt(vCorrected) + optimizerEpsilon); - } - } - - updateEvalValues(bestParameters); - float validationLoss = evaluationLoss(validationPositions); - - exportNewEvalValues(bestParameters, epoch, validationLoss); - - std::cout << "======== Epoch " << epoch << " Done ========" << std::endl; - std::cout << "Epoch: " << epoch << ", Val Loss: " << validationLoss << std::endl; - std::cout << "==============================" << std::endl; - epoch++; - } -} -} // namespace Zagreus \ No newline at end of file diff --git a/src/tuner.h b/src/tuner.h deleted file mode 100644 index d86a7e66..00000000 --- a/src/tuner.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#pragma once - -#include -#include - -#include "bitboard.h" - -namespace Zagreus { -struct TunePosition { - std::string fen; - float result = 0.0f; - int score = 0; -}; - -class ExponentialMovingAverage { -private: - double alpha; - double ma; - bool initialized; - -public: - ExponentialMovingAverage(size_t period) - : alpha(2.0 / (period + 1)), ma(0), initialized(false) { - } - - void add(double value) { - if (!initialized) { - ma = value; - initialized = true; - } else { - ma = alpha * value + (1 - alpha) * ma; - } - } - - double getMA() const { return ma; } -}; - -void startTuning(char* filePath); -} // namespace Zagreus \ No newline at end of file diff --git a/src/types.h b/src/types.h deleted file mode 100644 index 4f787c81..00000000 --- a/src/types.h +++ /dev/null @@ -1,185 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#pragma once - -#include "constants.h" - -namespace Zagreus { -enum NodeType { - ROOT, - PV, - NO_PV, - NULL_MOVE, -}; - -enum PieceType { - EMPTY = -1, - WHITE_PAWN = 0, - BLACK_PAWN = 1, - WHITE_KNIGHT = 2, - BLACK_KNIGHT = 3, - WHITE_BISHOP = 4, - BLACK_BISHOP = 5, - WHITE_ROOK = 6, - BLACK_ROOK = 7, - WHITE_QUEEN = 8, - BLACK_QUEEN = 9, - WHITE_KING = 10, - BLACK_KING = 11 -}; - -enum PieceColor { NONE = -1, WHITE = 0, BLACK = 1 }; - -// clang-format off -enum Square { - A1, - B1, - C1, - D1, - E1, - F1, - G1, - H1, - A2, - B2, - C2, - D2, - E2, - F2, - G2, - H2, - A3, - B3, - C3, - D3, - E3, - F3, - G3, - H3, - A4, - B4, - C4, - D4, - E4, - F4, - G4, - H4, - A5, - B5, - C5, - D5, - E5, - F5, - G5, - H5, - A6, - B6, - C6, - D6, - E6, - F6, - G6, - H6, - A7, - B7, - C7, - D7, - E7, - F7, - G7, - H7, - A8, - B8, - C8, - D8, - E8, - F8, - G8, - H8, - NO_SQUARE = -1 -}; - -// clang-format on - -struct Move { - int8_t from = 0; - int8_t to = 0; - PieceType piece = EMPTY; - int captureScore = NO_CAPTURE_SCORE; - PieceType promotionPiece = EMPTY; - int score = 0; -}; - -enum MoveType { REGULAR, EN_PASSANT, CASTLING }; - -struct UndoData { - uint8_t halfMoveClock = 0; - int8_t enPassantSquare = NO_SQUARE; - uint8_t castlingRights = 0; - PieceType capturedPiece = EMPTY; - MoveType moveType = REGULAR; - uint64_t zobristHash = 0ULL; - Move previousMove{}; -}; - -struct MoveList { - Move moves[MAX_MOVES]{}; - uint8_t size = 0; -}; - -enum CastlingRights { - WHITE_KINGSIDE = 1 << 0, - WHITE_QUEENSIDE = 1 << 1, - BLACK_KINGSIDE = 1 << 2, - BLACK_QUEENSIDE = 1 << 3, -}; - -enum Direction { NORTH, SOUTH, EAST, WEST, NORTH_EAST, NORTH_WEST, SOUTH_EAST, SOUTH_WEST }; - -struct Line { - int moveCount = 0; - int startPly = 0; - Move moves[MAX_MOVES]{}; -}; - -struct EvalContext { - int phase = 0; - int whiteMidgameScore = 0; - int blackMidgameScore = 0; - int whiteEndgameScore = 0; - int blackEndgameScore = 0; - uint64_t whitePawnAttacks = 0; - uint64_t whiteKnightAttacks = 0; - uint64_t whiteBishopAttacks = 0; - uint64_t whiteRookAttacks = 0; - uint64_t whiteQueenAttacks = 0; - uint64_t whiteKingAttacks = 0; - uint64_t whiteCombinedAttacks = 0; - uint64_t blackPawnAttacks = 0; - uint64_t blackKnightAttacks = 0; - uint64_t blackBishopAttacks = 0; - uint64_t blackRookAttacks = 0; - uint64_t blackQueenAttacks = 0; - uint64_t blackKingAttacks = 0; - uint64_t blackCombinedAttacks = 0; - uint64_t attacksFrom[64] = {0ULL}; -}; -} // namespace Zagreus \ No newline at end of file diff --git a/tests/tests_main.cpp b/src/uci.cpp similarity index 68% rename from tests/tests_main.cpp rename to src/uci.cpp index 57073b6b..84dd5e80 100644 --- a/tests/tests_main.cpp +++ b/src/uci.cpp @@ -18,18 +18,4 @@ along with Zagreus. If not, see . */ -#include -#include "../src/bitboard.h" -#include "../src/magics.h" -#include "../src/pst.h" - -int main(int argc, char* argv[]) { - Zagreus::initializeBitboardConstants(); - Zagreus::initializeMagicBitboards(); - Zagreus::initializePst(); - - int result = Catch::Session().run(argc, argv); - - return result; -} diff --git a/src/movegen.h b/src/uci.h similarity index 72% rename from src/movegen.h rename to src/uci.h index 7faf93c9..5b3e55cd 100644 --- a/src/movegen.h +++ b/src/uci.h @@ -1,8 +1,9 @@ + /* This file is part of Zagreus. Zagreus is a UCI chess engine - Copyright (C) 2023 Danny Jelsma + Copyright (C) 2023-2024 Danny Jelsma Zagreus is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -20,15 +21,4 @@ #pragma once -#include "bitboard.h" - -namespace Zagreus { -enum GenerationType { - NORMAL, - QSEARCH, - EVASIONS, -}; -template -void generateMoves(Bitboard& bitboard, MoveList* moveList); -} // namespace Zagreus \ No newline at end of file diff --git a/src/utils.cpp b/src/utils.cpp deleted file mode 100644 index 4d5c7dd2..00000000 --- a/src/utils.cpp +++ /dev/null @@ -1,72 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#include "utils.h" - -#include - -namespace Zagreus { -std::string getNotation(int8_t square) { - std::string notation = ""; - - notation += static_cast(square % 8 + 'a'); - notation += static_cast(square / 8 + '1'); - - return notation; -} - -int8_t getSquareFromString(std::string move) { - int file = move[0] - 'a'; - int rank = move[1] - '1'; - - return file + rank * 8; -} - -char getCharacterForPieceType(PieceType pieceType) { - switch (pieceType) { - case WHITE_PAWN: - return 'P'; - case BLACK_PAWN: - return 'p'; - case WHITE_KNIGHT: - return 'N'; - case BLACK_KNIGHT: - return 'n'; - case WHITE_BISHOP: - return 'B'; - case BLACK_BISHOP: - return 'b'; - case WHITE_ROOK: - return 'R'; - case BLACK_ROOK: - return 'r'; - case WHITE_QUEEN: - return 'Q'; - case BLACK_QUEEN: - return 'q'; - case WHITE_KING: - return 'K'; - case BLACK_KING: - return 'k'; - case EMPTY: - return ' '; - } -} -} // namespace Zagreus \ No newline at end of file diff --git a/src/utils.h b/src/utils.h deleted file mode 100644 index 2a6972a3..00000000 --- a/src/utils.h +++ /dev/null @@ -1,97 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#pragma once - -#include -#include - -#include "types.h" - -namespace Zagreus { -inline static PieceColor getOppositeColor(PieceColor color) { - return static_cast(color ^ 1); -} - -static constexpr uint16_t pieceWeights[12] = {100, 100, 350, 350, 350, 350, - 525, 525, 1000, 1000, 65535, 65535}; - -inline uint64_t popcnt(uint64_t b) { return __builtin_popcountll(b); } - -inline int8_t bitscanForward(uint64_t b) { return __builtin_ctzll(b); } - -inline int8_t bitscanReverse(uint64_t b) { return 63 ^ __builtin_clzll(b); } - -inline int8_t popLsb(uint64_t& b) { - int8_t lsb = bitscanForward(b); - b &= b - 1; - return lsb; -} - -inline uint32_t encodeMove(Move* move) { - return static_cast(move->promotionPiece << 24) - | static_cast(move->piece << 16) - | static_cast(move->to << 8) - | static_cast(move->from); -} - -inline uint16_t getPieceWeight(PieceType type) { return pieceWeights[type]; } - -inline int mvvlva(PieceType attacker, PieceType victim) { return MVVLVA_TABLE[attacker][victim]; } - -std::string getNotation(int8_t square); - -int8_t getSquareFromString(std::string move); - -char getCharacterForPieceType(PieceType pieceType); - -inline bool isNotPawnOrKing(PieceType pieceType) { - return pieceType != WHITE_PAWN && pieceType != BLACK_PAWN && pieceType != WHITE_KING && - pieceType != BLACK_KING; -} - -inline bool isPawn(PieceType pieceType) { - return pieceType == WHITE_PAWN || pieceType == BLACK_PAWN; -} - -inline bool isKnight(PieceType pieceType) { - return pieceType == WHITE_KNIGHT || pieceType == BLACK_KNIGHT; -} - -inline bool isBishop(PieceType pieceType) { - return pieceType == WHITE_BISHOP || pieceType == BLACK_BISHOP; -} - -inline bool isRook(PieceType pieceType) { - return pieceType == WHITE_ROOK || pieceType == BLACK_ROOK; -} - -inline bool isQueen(PieceType pieceType) { - return pieceType == WHITE_QUEEN || pieceType == BLACK_QUEEN; -} - -inline bool isKing(PieceType pieceType) { - return pieceType == WHITE_KING || pieceType == BLACK_KING; -} - -inline bool isSlidingPiece(PieceType pieceType) { - return isBishop(pieceType) || isRook(pieceType) || isQueen(pieceType); -} -} // namespace Zagreus \ No newline at end of file diff --git a/tests/ep_tests.cpp b/tests/ep_tests.cpp deleted file mode 100644 index c2163541..00000000 --- a/tests/ep_tests.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023-2024 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#include "catch2/catch_test_macros.hpp" - -#include "../src/bitboard.h" -#include "../src/engine.h" -#include "../src/evaluate.h" - -TEST_CASE("Test en-passant", "[ep]") { - Zagreus::ZagreusEngine engine{}; - Zagreus::Bitboard bb{}; - - SECTION("6k1/p2q4/1p3pp1/2b4p/4p2P/P2P2P1/3Q1PK1/5B2 w - - 0 31") { - Zagreus::Move move{Zagreus::F2, Zagreus::F4, Zagreus::WHITE_PAWN}; - bb.makeMove(move); - int8_t epSquare = bb.getEnPassantSquare(); - REQUIRE(epSquare == Zagreus::F3); - Zagreus::Move epMove{Zagreus::E4, Zagreus::F3, Zagreus::BLACK_PAWN}; - bb.makeMove(epMove); - Zagreus::PieceType pieceOnEpSquare = bb.getPieceOnSquare(Zagreus::F3); - Zagreus::PieceType pieceOnCaptureSquare = bb.getPieceOnSquare(Zagreus::F4); - REQUIRE(pieceOnEpSquare == Zagreus::BLACK_PAWN); - REQUIRE(pieceOnCaptureSquare == Zagreus::EMPTY); - } -} - diff --git a/tests/eval_tests.cpp b/tests/eval_tests.cpp deleted file mode 100644 index f01a84cd..00000000 --- a/tests/eval_tests.cpp +++ /dev/null @@ -1,174 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023-2024 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#include "catch2/catch_test_macros.hpp" - -#include "../src/bitboard.h" -#include "../src/engine.h" -#include "../src/evaluate.h" - -TEST_CASE("Evaluation is symmetric (on mirrored positions)", "[eval_symmetry]") { - Zagreus::ZagreusEngine engine{}; - Zagreus::Bitboard bb{}; - - SECTION("r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1") { - bb.setFromFen("r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1"); - int eval1 = Zagreus::Evaluation(bb).evaluate(); - bb.setFromFen("r2q1rk1/pP1p2pp/Q4n2/bbp1p3/Np6/1B3NBn/pPPP1PPP/R3K2R b KQ - 0 1"); - int eval2 = Zagreus::Evaluation(bb).evaluate(); - - REQUIRE(eval1 == eval2); - } - - SECTION("r1bqk2r/pp1pppbp/1nn3p1/4P3/5B2/2PQ1N2/PPB2PPP/RN2K2R b KQkq - 2 10") { - bb.setFromFen("r1bqk2r/pp1pppbp/1nn3p1/4P3/5B2/2PQ1N2/PPB2PPP/RN2K2R b KQkq - 2 10"); - int eval1 = Zagreus::Evaluation(bb).evaluate(); - bb.setFromFen("rn2k2r/ppb2ppp/2pq1n2/5b2/4p3/1NN3P1/PP1PPPBP/R1BQK2R w kqKQ - 2 10"); - int eval2 = Zagreus::Evaluation(bb).evaluate(); - - REQUIRE(eval1 == eval2); - } - - SECTION("8/1kp5/8/p1pq4/4p1p1/1P2P3/P1PP2R1/5K1R w - - 3 39") { - bb.setFromFen("8/1kp5/8/p1pq4/4p1p1/1P2P3/P1PP2R1/5K1R w - - 3 39"); - int eval1 = Zagreus::Evaluation(bb).evaluate(); - bb.setFromFen("5k1r/p1pp2r1/1p2p3/4P1P1/P1PQ4/8/1KP5/8 b - - 3 39"); - int eval2 = Zagreus::Evaluation(bb).evaluate(); - - REQUIRE(eval1 == eval2); - } - - SECTION("2rrb1k1/ppq1bppp/5n2/2p1NR2/3P4/1QN1P3/PP4PP/2R2BK1 b - - 1 18") { - bb.setFromFen("2rrb1k1/ppq1bppp/5n2/2p1NR2/3P4/1QN1P3/PP4PP/2R2BK1 b - - 1 18"); - int eval1 = Zagreus::Evaluation(bb).evaluate(); - bb.setFromFen("2r2bk1/pp4pp/1qn1p3/3p4/2P1nr2/5N2/PPQ1BPPP/2RRB1K1 w - - 1 18"); - int eval2 = Zagreus::Evaluation(bb).evaluate(); - - REQUIRE(eval1 == eval2); - } - - SECTION("2n2b1r/pkpq2p1/5pn1/1P1Pp2p/P3P3/3P2Pb/1B2NP2/R2QK2R w KQ - 0 21") { - bb.setFromFen("2n2b1r/pkpq2p1/5pn1/1P1Pp2p/P3P3/3P2Pb/1B2NP2/R2QK2R w KQ - 0 21"); - int eval1 = Zagreus::Evaluation(bb).evaluate(); - bb.setFromFen("r2qk2r/1b2np2/3p2pB/p3p3/1p1pP2P/5PN1/PKPQ2P1/2N2B1R b kq - 0 21"); - int eval2 = Zagreus::Evaluation(bb).evaluate(); - - REQUIRE(eval1 == eval2); - } - - SECTION("r2q2k1/1pb2rp1/p1n1R2p/5p2/8/B1P4P/P1B1QPP1/R5K1 w - - 5 23") { - bb.setFromFen("r2q2k1/1pb2rp1/p1n1R2p/5p2/8/B1P4P/P1B1QPP1/R5K1 w - - 5 23"); - int eval1 = Zagreus::Evaluation(bb).evaluate(); - bb.setFromFen("r5k1/p1b1qpp1/b1p4p/8/5P2/P1N1r2P/1PB2RP1/R2Q2K1 b - - 5 23"); - int eval2 = Zagreus::Evaluation(bb).evaluate(); - - REQUIRE(eval1 == eval2); - } - - SECTION("r4rk1/2B2ppp/4p3/1qb5/1p2B3/p4N2/2Q2PPP/5RK1 w - - 0 22") { - bb.setFromFen("r4rk1/2B2ppp/4p3/1qb5/1p2B3/p4N2/2Q2PPP/5RK1 w - - 0 22"); - int eval1 = Zagreus::Evaluation(bb).evaluate(); - bb.setFromFen("5rk1/2q2ppp/P4n2/1P2b3/1QB5/4P3/2b2PPP/R4RK1 b - - 0 22"); - int eval2 = Zagreus::Evaluation(bb).evaluate(); - - REQUIRE(eval1 == eval2); - } - - SECTION("2b2rk1/rp3pbp/1N2p3/1BPp4/Pq6/4Q3/5PKP/3R2R1 b - - 1 25") { - bb.setFromFen("2b2rk1/rp3pbp/1N2p3/1BPp4/Pq6/4Q3/5PKP/3R2R1 b - - 1 25"); - int eval1 = Zagreus::Evaluation(bb).evaluate(); - bb.setFromFen("3r2r1/5pkp/4q3/pQ6/1bpP4/1n2P3/RP3PBP/2B2RK1 w - - 1 25"); - int eval2 = Zagreus::Evaluation(bb).evaluate(); - - REQUIRE(eval1 == eval2); - } - - SECTION("1KR5/4bkp1/1p3r2/1P6/8/8/8/8 b - - 7 59") { - bb.setFromFen("1KR5/4bkp1/1p3r2/1P6/8/8/8/8 b - - 7 59"); - int eval1 = Zagreus::Evaluation(bb).evaluate(); - bb.setFromFen("8/8/8/8/1p6/1P3R2/4BKP1/1kr5 w - - 7 59"); - int eval2 = Zagreus::Evaluation(bb).evaluate(); - - REQUIRE(eval1 == eval2); - } - - SECTION("3r1rk1/1ppq1pbp/p3p1p1/3bP1N1/P1BP1P1P/1n2B3/4Q1P1/2RR2K1 w - - 0 21") { - bb.setFromFen("3r1rk1/1ppq1pbp/p3p1p1/3bP1N1/P1BP1P1P/1n2B3/4Q1P1/2RR2K1 w - - 0 21"); - int eval1 = Zagreus::Evaluation(bb).evaluate(); - bb.setFromFen("2rr2k1/4q1p1/1N2b3/p1bp1p1p/3Bp1n1/P3P1P1/1PPQ1PBP/3R1RK1 b - - 0 21"); - int eval2 = Zagreus::Evaluation(bb).evaluate(); - - REQUIRE(eval1 == eval2); - } - - SECTION("8/6pk/P4p2/4b2p/2R5/3Q2P1/q4PKP/8 b - - 2 47") { - bb.setFromFen("8/6pk/P4p2/4b2p/2R5/3Q2P1/q4PKP/8 b - - 2 47"); - int eval1 = Zagreus::Evaluation(bb).evaluate(); - bb.setFromFen("8/Q4pkp/3q2p1/2r5/4B2P/p4P2/6PK/8 w - - 2 47"); - int eval2 = Zagreus::Evaluation(bb).evaluate(); - - REQUIRE(eval1 == eval2); - } - - SECTION("3r1q1k/pb4p1/1pp2b1p/2P5/3P1r2/1P4PB/PB2QP1P/4RRK1 b - - 0 27") { - bb.setFromFen("3r1q1k/pb4p1/1pp2b1p/2P5/3P1r2/1P4PB/PB2QP1P/4RRK1 b - - 0 27"); - int eval1 = Zagreus::Evaluation(bb).evaluate(); - bb.setFromFen("4rrk1/pb2qp1p/1p4pb/3p1R2/2p5/1PP2B1P/PB4P1/3R1Q1K w - - 0 27"); - int eval2 = Zagreus::Evaluation(bb).evaluate(); - - REQUIRE(eval1 == eval2); - } - - SECTION("4k2r/8/8/8/8/8/8/4K3 w k - 0 1") { - bb.setFromFen("4k2r/8/8/8/8/8/8/4K3 w k - 0 1"); - int eval1 = Zagreus::Evaluation(bb).evaluate(); - bb.setFromFen("4k3/8/8/8/8/8/8/4K2R b K - 0 1"); - int eval2 = Zagreus::Evaluation(bb).evaluate(); - - REQUIRE(eval1 == eval2); - } - - SECTION("8/7p/8/3k1b2/pP3P2/P3K3/7P/8 b - - 3 47") { - bb.setFromFen("8/7p/8/3k1b2/pP3P2/P3K3/7P/8 b - - 3 47"); - int eval1 = Zagreus::Evaluation(bb).evaluate(); - bb.setFromFen("8/7p/p3k3/Pp3p2/3K1B2/8/7P/8 w - - 3 47"); - int eval2 = Zagreus::Evaluation(bb).evaluate(); - - REQUIRE(eval1 == eval2); - } - - SECTION("r4rk1/5pp1/P1pp1q1p/2b1p3/4P1b1/1PNP1N2/1P3PPP/R2Q1RK1 w - - 0 15") { - bb.setFromFen("r4rk1/5pp1/P1pp1q1p/2b1p3/4P1b1/1PNP1N2/1P3PPP/R2Q1RK1 w - - 0 15"); - int eval1 = Zagreus::Evaluation(bb).evaluate(); - bb.setFromFen("r2q1rk1/1p3ppp/1pnp1n2/4p1B1/2B1P3/p1PP1Q1P/5PP1/R4RK1 b - - 0 15"); - int eval2 = Zagreus::Evaluation(bb).evaluate(); - - REQUIRE(eval1 == eval2); - } - - SECTION("4k3/1R2b1Bp/1p6/3p1p2/P2Pp3/1P2P2P/5PP1/2n3K1 b - - 2 29") { - bb.setFromFen("4k3/1R2b1Bp/1p6/3p1p2/P2Pp3/1P2P2P/5PP1/2n3K1 b - - 2 29"); - int eval1 = Zagreus::Evaluation(bb).evaluate(); - bb.setFromFen("2N3k1/5pp1/1p2p2p/p2pP3/3P1P2/1P6/1r2B1bP/4K3 w - - 2 29"); - int eval2 = Zagreus::Evaluation(bb).evaluate(); - - REQUIRE(eval1 == eval2); - } -} diff --git a/tests/perft_tests.cpp b/tests/perft_tests.cpp deleted file mode 100644 index 03658958..00000000 --- a/tests/perft_tests.cpp +++ /dev/null @@ -1,109 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023-2024 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#include "catch2/catch_test_macros.hpp" - -#include "../src/bitboard.h" -#include "../src/engine.h" - -TEST_CASE("'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1' at depth 5 == 4865609", "[perft]") { - Zagreus::ZagreusEngine engine{}; - Zagreus::Bitboard bb{}; - - bb.setFromFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); - uint64_t perft = engine.doPerft(bb, bb.getMovingColor(), 5, 5); - REQUIRE(perft == 4865609); -} - -TEST_CASE( - "'r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq -' at depth 4 == 4085603", "[perft]") { - Zagreus::ZagreusEngine engine{}; - Zagreus::Bitboard bb{}; - - bb.setFromFen("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq -"); - uint64_t perft = engine.doPerft(bb, bb.getMovingColor(), 4, 4); - REQUIRE(perft == 4085603); -} - -TEST_CASE("'8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - -' at depth 6 == 11030083", "[perft]") { - Zagreus::ZagreusEngine engine{}; - Zagreus::Bitboard bb{}; - - bb.setFromFen("8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - -"); - uint64_t perft = engine.doPerft(bb, bb.getMovingColor(), 6, 6); - REQUIRE(perft == 11030083); -} - -TEST_CASE( - "'r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1' at depth 5 == 15833292", "[perft]") { - Zagreus::ZagreusEngine engine{}; - Zagreus::Bitboard bb{}; - - bb.setFromFen("r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1"); - uint64_t perft = engine.doPerft(bb, bb.getMovingColor(), 5, 5); - REQUIRE(perft == 15833292); -} - -TEST_CASE( - "'r2q1rk1/pP1p2pp/Q4n2/bbp1p3/Np6/1B3NBn/pPPP1PPP/R3K2R b KQ - 0 1' at depth 5 == 15833292", "[perft]") { - Zagreus::ZagreusEngine engine{}; - Zagreus::Bitboard bb{}; - - bb.setFromFen("r2q1rk1/pP1p2pp/Q4n2/bbp1p3/Np6/1B3NBn/pPPP1PPP/R3K2R b KQ - 0 1"); - uint64_t perft = engine.doPerft(bb, bb.getMovingColor(), 5, 5); - REQUIRE(perft == 15833292); -} - -TEST_CASE("'rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8' at depth 4 == 2103487", "[perft]") { - Zagreus::ZagreusEngine engine{}; - Zagreus::Bitboard bb{}; - - bb.setFromFen("rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8"); - uint64_t perft = engine.doPerft(bb, bb.getMovingColor(), 4, 4); - REQUIRE(perft == 2103487); -} - -TEST_CASE( - "'r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10' at depth 4 == 3894594", "[perft]") { - Zagreus::ZagreusEngine engine{}; - Zagreus::Bitboard bb{}; - - bb.setFromFen("r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10"); - uint64_t perft = engine.doPerft(bb, bb.getMovingColor(), 4, 4); - REQUIRE(perft == 3894594); -} - -TEST_CASE("'4k2r/8/8/8/8/8/8/4K3 w k - 0 1' at depth 6 == 899442", "[perft]") { - Zagreus::ZagreusEngine engine{}; - Zagreus::Bitboard bb{}; - - bb.setFromFen("4k2r/8/8/8/8/8/8/4K3 w k - 0 1"); - uint64_t perft = engine.doPerft(bb, bb.getMovingColor(), 6, 6); - REQUIRE(perft == 899442); -} - -TEST_CASE("'rnbqkb1r/ppppp1pp/7n/4Pp2/8/8/PPPP1PPP/RNBQKBNR w KQkq f6 0 3' at depth 5 == 11139762", "[perft]") { - Zagreus::ZagreusEngine engine{}; - Zagreus::Bitboard bb{}; - - bb.setFromFen("rnbqkb1r/ppppp1pp/7n/4Pp2/8/8/PPPP1PPP/RNBQKBNR w KQkq f6 0 3"); - uint64_t perft = engine.doPerft(bb, bb.getMovingColor(), 5, 5); - REQUIRE(perft == 11139762); -} diff --git a/tests/see_tests.cpp b/tests/see_tests.cpp deleted file mode 100644 index 88c9cd54..00000000 --- a/tests/see_tests.cpp +++ /dev/null @@ -1,125 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023-2024 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#include "catch2/catch_test_macros.hpp" - -#include "../src/bitboard.h" -#include "../src/engine.h" -#include "../src/evaluate.h" -#include "../src/features.h" -#include "../src/search.h" - -// Positions from: https://github.com/zzzzz151/Starzix/blob/main/tests%2FSEE.txt -TEST_CASE("Test SEE function", "[see]") { - Zagreus::ZagreusEngine engine{}; - Zagreus::Bitboard bb{}; - std::vector oldParameters = Zagreus::getEvalValues(); - std::vector bestParameters = Zagreus::getBaseEvalValues(); - Zagreus::updateEvalValues(bestParameters); - - int pawnValue = Zagreus::getEvalValue(Zagreus::MIDGAME_PAWN_MATERIAL); - int knightValue = Zagreus::getEvalValue(Zagreus::MIDGAME_KNIGHT_MATERIAL); - int bishopValue = Zagreus::getEvalValue(Zagreus::MIDGAME_BISHOP_MATERIAL); - int rookValue = Zagreus::getEvalValue(Zagreus::MIDGAME_ROOK_MATERIAL); - int queenValue = Zagreus::getEvalValue(Zagreus::MIDGAME_QUEEN_MATERIAL); - - // cases - SECTION("6k1/1pp4p/p1pb4/6q1/3P1pRr/2P4P/PP1Br1P1/5RKN w - - | f1f4 | P - R + B") { - int expected = pawnValue - rookValue + bishopValue; - bb.setFromFen("6k1/1pp4p/p1pb4/6q1/3P1pRr/2P4P/PP1Br1P1/5RKN w - -"); - int see = bb.seeCapture(Zagreus::F1, Zagreus::F4); - - REQUIRE(see == expected); - } - - SECTION("4r1k1/5pp1/nbp4p/1p2p2q/1P2P1b1/1BP2N1P/1B2QPPK/3R4 b - - | g4f3 | 0") { - int expected = 0; - bb.setFromFen("4r1k1/5pp1/nbp4p/1p2p2q/1P2P1b1/1BP2N1P/1B2QPPK/3R4 b - -"); - int see = bb.seeCapture(Zagreus::G4, Zagreus::F3); - - REQUIRE(see == expected); - } - - SECTION("2r1r1k1/pp1bppbp/3p1np1/q3P3/2P2P2/1P2B3/P1N1B1PP/2RQ1RK1 b - - | d6e5 | P") { - int expected = pawnValue; - bb.setFromFen("2r1r1k1/pp1bppbp/3p1np1/q3P3/2P2P2/1P2B3/P1N1B1PP/2RQ1RK1 b - -"); - int see = bb.seeCapture(Zagreus::D6, Zagreus::E5); - - REQUIRE(see == expected); - } - - SECTION("3r3k/3r4/2n1n3/8/3p4/2PR4/1B1Q4/3R3K w - - | d3d4 | P - R + N - P + N - B + R - Q + R") { - int expected = pawnValue - rookValue + knightValue - pawnValue + knightValue - bishopValue + rookValue - queenValue + rookValue; - bb.setFromFen("3r3k/3r4/2n1n3/8/3p4/2PR4/1B1Q4/3R3K w - -"); - int see = bb.seeCapture(Zagreus::D3, Zagreus::D4); - - REQUIRE(see == expected); - } - - SECTION("r2qkbn1/ppp1pp1p/3p1rp1/3Pn3/4P1b1/2N2N2/PPP2PPP/R1BQKB1R b KQq - | g4f3 | N - B + P") { - int expected = knightValue - bishopValue + pawnValue; - bb.setFromFen("r2qkbn1/ppp1pp1p/3p1rp1/3Pn3/4P1b1/2N2N2/PPP2PPP/R1BQKB1R b KQq -"); - int see = bb.seeCapture(Zagreus::G4, Zagreus::F3); - - REQUIRE(see == expected); - } - - SECTION("r1bqkb1r/2pp1ppp/p1n5/1p2p3/3Pn3/1B3N2/PPP2PPP/RNBQ1RK1 b kq - | c6d4 | P - N + N - P") { - int expected = pawnValue - knightValue + knightValue - pawnValue; - bb.setFromFen("r1bqkb1r/2pp1ppp/p1n5/1p2p3/3Pn3/1B3N2/PPP2PPP/RNBQ1RK1 b kq -"); - int see = bb.seeCapture(Zagreus::C6, Zagreus::D4); - - REQUIRE(see == expected); - } - - SECTION("1r3r2/5p2/4p2p/2k1n1P1/2PN1nP1/1P3P2/8/2KR1B1R b - - | b8b3 | P - R") { - int expected = pawnValue - rookValue; - bb.setFromFen("1r3r2/5p2/4p2p/2k1n1P1/2PN1nP1/1P3P2/8/2KR1B1R b - -"); - int see = bb.seeCapture(Zagreus::B8, Zagreus::B3); - - REQUIRE(see == expected); - } - - SECTION("1r3r1k/p4pp1/2p1p2p/qpQP3P/2P5/3R4/PP3PP1/1K1R4 b - - | a5a2 | P - Q") { - int expected = pawnValue - queenValue; - bb.setFromFen("1r3r1k/p4pp1/2p1p2p/qpQP3P/2P5/3R4/PP3PP1/1K1R4 b - -"); - int see = bb.seeCapture(Zagreus::A5, Zagreus::A2); - - REQUIRE(see == expected); - } - - SECTION("8/8/1k6/8/8/2N1N3/4p1K1/3n4 w - - | c3d1 | N - (N + Q - P) + Q") { - int expected = knightValue - (knightValue + queenValue - pawnValue) + queenValue; - bb.setFromFen("8/8/1k6/8/8/2N1N3/4p1K1/3n4 w - -"); - int see = bb.seeCapture(Zagreus::C3, Zagreus::D1); - - REQUIRE(see == expected); - } - - SECTION("2r1k3/pbr3pp/5p1b/2KB3n/1N2N3/3P1PB1/PPP1P1PP/R2Q3R w - - | d5c6 | -B + B - N") { - int expected = -bishopValue + bishopValue - knightValue; - bb.setFromFen("2r1k3/pbr3pp/5p1b/2KB3n/1N2N3/3P1PB1/PPP1P1PP/R2Q3R w - -"); - int see = bb.seeCapture(Zagreus::D5, Zagreus::C6); - - REQUIRE(see == expected); - } - - Zagreus::updateEvalValues(oldParameters); -} From 68416c2f8a0a4a0a95c748a716cae2315560219d Mon Sep 17 00:00:00 2001 From: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> Date: Thu, 23 May 2024 10:10:05 +0200 Subject: [PATCH 02/91] Started implementing UCI --- src/bitboard.cpp | 123 +++++++++ src/bitboard.h | 55 ++++ src/bitwise.cpp | 61 +++++ src/bitwise.h | 33 +++ src/board.cpp | 26 ++ src/board.h | 26 ++ src/main.cpp | 7 + src/types.h | 33 +++ src/uci-specification.txt | 544 ++++++++++++++++++++++++++++++++++++++ src/uci.cpp | 402 +++++++++++++++++++++++++++- src/uci.h | 95 ++++++- 11 files changed, 1402 insertions(+), 3 deletions(-) create mode 100644 src/bitboard.cpp create mode 100644 src/bitboard.h create mode 100644 src/bitwise.cpp create mode 100644 src/bitwise.h create mode 100644 src/board.cpp create mode 100644 src/board.h create mode 100644 src/types.h create mode 100644 src/uci-specification.txt diff --git a/src/bitboard.cpp b/src/bitboard.cpp new file mode 100644 index 00000000..7c543598 --- /dev/null +++ b/src/bitboard.cpp @@ -0,0 +1,123 @@ +/* + This file is part of Zagreus. + + Zagreus is a UCI chess engine + Copyright (C) 2023-2024 Danny Jelsma + + Zagreus is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Zagreus 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Zagreus. If not, see . + */ + +#include "bitboard.h" +#include "bitwise.h" +#include "types.h" + +namespace Zagreus { +uint64_t Bitboard::getValue() const { + return this->bitboard; +} + +void Bitboard::setValue(uint64_t value) { + this->bitboard = value; +} + +uint64_t Bitboard::popcnt() { + return Bitwise::popcnt(this->bitboard); +} + +uint64_t Bitboard::popLsb() { + return Bitwise::popLsb(this->bitboard); +} + +int Bitboard::bitscanForward() { + return Bitwise::bitscanForward(this->bitboard); +} + +int Bitboard::bitscanReverse() { + return Bitwise::bitscanReverse(this->bitboard); +} + +template +Bitboard shift() { + Bitboard result{}; + + return result; +} + +Bitboard Bitboard::operator&(const Bitboard& other) { + Bitboard result{}; + + result.setValue(this->getValue() & other.getValue()); + return result; +} + +Bitboard Bitboard::operator|(const Bitboard& other) { + Bitboard result{}; + + result.setValue(this->getValue() | other.getValue()); + return result; +} + +Bitboard Bitboard::operator^(const Bitboard& other) { + Bitboard result{}; + + result.setValue(this->getValue() ^ other.getValue()); + return result; +} + +Bitboard Bitboard::operator~() { + Bitboard result{}; + + result.setValue(~this->getValue()); + return result; +} + +Bitboard Bitboard::operator<<(int shift) { + Bitboard result{}; + + result.setValue(this->getValue() << shift); + return result; +} + +Bitboard Bitboard::operator>>(int shift) { + Bitboard result{}; + + result.setValue(this->getValue() >> shift); + return result; +} + +Bitboard& Bitboard::operator&=(const Bitboard& other) { + this->setValue(this->getValue() & other.getValue()); + + return *this; +} + +Bitboard& Bitboard::operator|=(const Bitboard& other) { + this->setValue(this->getValue() | other.getValue()); + + return *this; +} + +Bitboard& Bitboard::operator^=(const Bitboard& other) { + this->setValue(this->getValue() ^ other.getValue()); + + return *this; +} + +Bitboard squareToBitboard(int index) { + Bitboard result{}; + + result.setValue(1ULL << index); + return result; +} +} // namespace Zagreus \ No newline at end of file diff --git a/src/bitboard.h b/src/bitboard.h new file mode 100644 index 00000000..e629437d --- /dev/null +++ b/src/bitboard.h @@ -0,0 +1,55 @@ + +/* + This file is part of Zagreus. + + Zagreus is a UCI chess engine + Copyright (C) 2023-2024 Danny Jelsma + + Zagreus is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Zagreus 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Zagreus. If not, see . + */ + +#pragma once +#include + +#include "types.h" + +namespace Zagreus { +class Bitboard { +private: + uint64_t bitboard = 0ULL; +public: + uint64_t getValue() const; + void setValue(uint64_t value); + + inline uint64_t popcnt(); + inline uint64_t popLsb(); + inline int bitscanForward(); + inline int bitscanReverse(); + + template + Bitboard shift(); + + inline Bitboard operator&(const Bitboard& other); + inline Bitboard operator|(const Bitboard& other); + inline Bitboard operator^(const Bitboard& other); + inline Bitboard operator~(); + inline Bitboard operator<<(int shift); + inline Bitboard operator>>(int shift); + inline Bitboard& operator&=(const Bitboard& other); + inline Bitboard& operator|=(const Bitboard& other); + inline Bitboard& operator^=(const Bitboard& other); +}; + +Bitboard squareToBitboard(int index); +} // namespace Zagreus diff --git a/src/bitwise.cpp b/src/bitwise.cpp new file mode 100644 index 00000000..38d28001 --- /dev/null +++ b/src/bitwise.cpp @@ -0,0 +1,61 @@ +/* + This file is part of Zagreus. + + Zagreus is a UCI chess engine + Copyright (C) 2023-2024 Danny Jelsma + + Zagreus is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Zagreus 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Zagreus. If not, see . + */ + +#include "bitwise.h" + +#include + +namespace Zagreus::Bitwise { +uint64_t popcnt(uint64_t bb) { +#ifdef _MSC_VER + return __popcnt64(bb); +#else + return __builtin_popcountll(bb); +#endif +} + +int bitscanForward(uint64_t bb) { +#ifdef _MSC_VER + unsigned long index; + _BitScanForward64(&index, bb); + return (int) index; +#else + return __builtin_ctzll(bb); +#endif +} + +int bitscanReverse(uint64_t bb) { +#ifdef _MSC_VER + unsigned long index; + _BitScanReverse64(&index, bb); + return (int) index; +#else + return 63 ^ __builtin_clzll(bb); +#endif +} + +uint64_t popLsb(uint64_t& bb) { + int lsb = bitscanForward(bb); + + bb &= bb - 1; + return lsb; +} +} // namespace Zagreus::Bitwise + diff --git a/src/bitwise.h b/src/bitwise.h new file mode 100644 index 00000000..c06f5c38 --- /dev/null +++ b/src/bitwise.h @@ -0,0 +1,33 @@ + +/* + This file is part of Zagreus. + + Zagreus is a UCI chess engine + Copyright (C) 2023-2024 Danny Jelsma + + Zagreus is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Zagreus 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Zagreus. If not, see . + */ + +#pragma once +#include + +namespace Zagreus::Bitwise { +inline uint64_t popcnt(uint64_t bb); + +inline int bitscanForward(uint64_t bb); + +inline int bitscanReverse(uint64_t bb); + +inline uint64_t popLsb(uint64_t& bb); +} // namespace Zagreus::Bitwise diff --git a/src/board.cpp b/src/board.cpp new file mode 100644 index 00000000..eb7b8491 --- /dev/null +++ b/src/board.cpp @@ -0,0 +1,26 @@ +/* + This file is part of Zagreus. + + Zagreus is a UCI chess engine + Copyright (C) 2023-2024 Danny Jelsma + + Zagreus is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Zagreus 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Zagreus. If not, see . + */ + +#include "board.h" + +namespace Zagreus { + +} // namespace Zagreus + diff --git a/src/board.h b/src/board.h new file mode 100644 index 00000000..fa96d008 --- /dev/null +++ b/src/board.h @@ -0,0 +1,26 @@ + +/* + This file is part of Zagreus. + + Zagreus is a UCI chess engine + Copyright (C) 2023-2024 Danny Jelsma + + Zagreus is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Zagreus 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Zagreus. If not, see . + */ + +#pragma once + +namespace Zagreus { + +} // namespace Zagreus diff --git a/src/main.cpp b/src/main.cpp index 84dd5e80..70ef64dd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,4 +18,11 @@ along with Zagreus. If not, see . */ +#include "uci.h" +int main(int argc, char *argv[]) { + Zagreus::Engine engine; + + engine.startUci(); + return 0; +} diff --git a/src/types.h b/src/types.h new file mode 100644 index 00000000..514c40ec --- /dev/null +++ b/src/types.h @@ -0,0 +1,33 @@ + +/* + This file is part of Zagreus. + + Zagreus is a UCI chess engine + Copyright (C) 2023-2024 Danny Jelsma + + Zagreus is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Zagreus 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Zagreus. If not, see . + */ + +#pragma once + +enum Direction { + NORTH, + SOUTH, + EAST, + WEST, + NORTH_EAST, + NORTH_WEST, + SOUTH_EAST, + SOUTH_WEST +}; diff --git a/src/uci-specification.txt b/src/uci-specification.txt new file mode 100644 index 00000000..df57f389 --- /dev/null +++ b/src/uci-specification.txt @@ -0,0 +1,544 @@ + + +Description of the universal chess interface (UCI) April 2006 +================================================================= + +* The specification is independent of the operating system. For Windows, + the engine is a normal exe file, either a console or "real" windows application. + +* all communication is done via standard input and output with text commands, + +* The engine should boot and wait for input from the GUI, + the engine should wait for the "isready" or "setoption" command to set up its internal parameters + as the boot process should be as quick as possible. + +* the engine must always be able to process input from stdin, even while thinking. + +* all command strings the engine receives will end with '\n', + also all commands the GUI receives should end with '\n', + Note: '\n' can be 0x0d or 0x0a0d or any combination depending on your OS. + If you use Engine and GUI in the same OS this should be no problem if you communicate in text mode, + but be aware of this when for example running a Linux engine in a Windows GUI. + +* arbitrary white space between tokens is allowed + Example: "debug on\n" and " debug on \n" and "\t debug \t \t\ton\t \n" + all set the debug mode of the engine on. + +* The engine will always be in forced mode which means it should never start calculating + or pondering without receiving a "go" command first. + +* Before the engine is asked to search on a position, there will always be a position command + to tell the engine about the current position. + +* by default all the opening book handling is done by the GUI, + but there is an option for the engine to use its own book ("OwnBook" option, see below) + +* if the engine or the GUI receives an unknown command or token it should just ignore it and try to + parse the rest of the string in this line. + Examples: "joho debug on\n" should switch the debug mode on given that joho is not defined, + "debug joho on\n" will be undefined however. + +* if the engine receives a command which is not supposed to come, for example "stop" when the engine is + not calculating, it should also just ignore it. + + +Move format: +------------ + +The move format is in long algebraic notation. +A nullmove from the Engine to the GUI should be sent as 0000. +Examples: e2e4, e7e5, e1g1 (white short castling), e7e8q (for promotion) + + + +GUI to engine: +-------------- + +These are all the command the engine gets from the interface. + +* uci + tell engine to use the uci (universal chess interface), + this will be sent once as a first command after program boot + to tell the engine to switch to uci mode. + After receiving the uci command the engine must identify itself with the "id" command + and send the "option" commands to tell the GUI which engine settings the engine supports if any. + After that the engine should send "uciok" to acknowledge the uci mode. + If no uciok is sent within a certain time period, the engine task will be killed by the GUI. + +* debug [ on | off ] + switch the debug mode of the engine on and off. + In debug mode the engine should send additional infos to the GUI, e.g. with the "info string" command, + to help debugging, e.g. the commands that the engine has received etc. + This mode should be switched off by default and this command can be sent + any time, also when the engine is thinking. + +* isready + this is used to synchronize the engine with the GUI. When the GUI has sent a command or + multiple commands that can take some time to complete, + this command can be used to wait for the engine to be ready again or + to ping the engine to find out if it is still alive. + E.g. this should be sent after setting the path to the tablebases as this can take some time. + This command is also required once before the engine is asked to do any search + to wait for the engine to finish initializing. + This command must always be answered with "readyok" and can be sent also when the engine is calculating + in which case the engine should also immediately answer with "readyok" without stopping the search. + +* setoption name [value ] + this is sent to the engine when the user wants to change the internal parameters + of the engine. For the "button" type no value is needed. + One string will be sent for each parameter and this will only be sent when the engine is waiting. + The name and value of the option in should not be case sensitive and can inlude spaces. + The substrings "value" and "name" should be avoided in and to allow unambiguous parsing, + for example do not use = "draw value". + Here are some strings for the example below: + "setoption name Nullmove value true\n" + "setoption name Selectivity value 3\n" + "setoption name Style value Risky\n" + "setoption name Clear Hash\n" + "setoption name NalimovPath value c:\chess\tb\4;c:\chess\tb\5\n" + +* register + this is the command to try to register an engine or to tell the engine that registration + will be done later. This command should always be sent if the engine has sent "registration error" + at program startup. + The following tokens are allowed: + * later + the user doesn't want to register the engine now. + * name + the engine should be registered with the name + * code + the engine should be registered with the code + Example: + "register later" + "register name Stefan MK code 4359874324" + +* ucinewgame + this is sent to the engine when the next search (started with "position" and "go") will be from + a different game. This can be a new game the engine should play or a new game it should analyse but + also the next position from a testsuite with positions only. + If the GUI hasn't sent a "ucinewgame" before the first "position" command, the engine shouldn't + expect any further ucinewgame commands as the GUI is probably not supporting the ucinewgame command. + So the engine should not rely on this command even though all new GUIs should support it. + As the engine's reaction to "ucinewgame" can take some time the GUI should always send "isready" + after "ucinewgame" to wait for the engine to finish its operation. + +* position [fen | startpos ] moves .... + set up the position described in fenstring on the internal board and + play the moves on the internal chess board. + if the game was played from the start position the string "startpos" will be sent + Note: no "new" command is needed. However, if this position is from a different game than + the last position sent to the engine, the GUI should have sent a "ucinewgame" inbetween. + +* go + start calculating on the current position set up with the "position" command. + There are a number of commands that can follow this command, all will be sent in the same string. + If one command is not sent its value should be interpreted as it would not influence the search. + * searchmoves .... + restrict search to this moves only + Example: After "position startpos" and "go infinite searchmoves e2e4 d2d4" + the engine should only search the two moves e2e4 and d2d4 in the initial position. + * ponder + start searching in pondering mode. + Do not exit the search in ponder mode, even if it's mate! + This means that the last move sent in in the position string is the ponder move. + The engine can do what it wants to do, but after a "ponderhit" command + it should execute the suggested move to ponder on. This means that the ponder move sent by + the GUI can be interpreted as a recommendation about which move to ponder. However, if the + engine decides to ponder on a different move, it should not display any mainlines as they are + likely to be misinterpreted by the GUI because the GUI expects the engine to ponder + on the suggested move. + * wtime + white has x msec left on the clock + * btime + black has x msec left on the clock + * winc + white increment per move in mseconds if x > 0 + * binc + black increment per move in mseconds if x > 0 + * movestogo + there are x moves to the next time control, + this will only be sent if x > 0, + if you don't get this and get the wtime and btime it's sudden death + * depth + search x plies only. + * nodes + search x nodes only, + * mate + search for a mate in x moves + * movetime + search exactly x mseconds + * infinite + search until the "stop" command. Do not exit the search without being told so in this mode! + +* stop + stop calculating as soon as possible, + don't forget the "bestmove" and possibly the "ponder" token when finishing the search + +* ponderhit + the user has played the expected move. This will be sent if the engine was told to ponder on the same move + the user has played. The engine should continue searching but switch from pondering to normal search. + +* quit + quit the program as soon as possible + + +Engine to GUI: +-------------- + +* id + * name + this must be sent after receiving the "uci" command to identify the engine, + e.g. "id name Shredder X.Y\n" + * author + this must be sent after receiving the "uci" command to identify the engine, + e.g. "id author Stefan MK\n" + +* uciok + Must be sent after the id and optional options to tell the GUI that the engine + has sent all infos and is ready in uci mode. + +* readyok + This must be sent when the engine has received an "isready" command and has + processed all input and is ready to accept new commands now. + It is usually sent after a command that can take some time to be able to wait for the engine, + but it can be used anytime, even when the engine is searching, + and must always be answered with "isready". + +* bestmove [ ponder ] + the engine has stopped searching and found the move best in this position. + the engine can send the move it likes to ponder on. The engine must not start pondering automatically. + this command must always be sent if the engine stops searching, also in pondering mode if there is a + "stop" command, so for every "go" command a "bestmove" command is needed! + Directly before that the engine should send a final "info" command with the final search information, + the the GUI has the complete statistics about the last search. + +* copyprotection + this is needed for copyprotected engines. After the uciok command the engine can tell the GUI, + that it will check the copy protection now. This is done by "copyprotection checking". + If the check is ok the engine should send "copyprotection ok", otherwise "copyprotection error". + If there is an error the engine should not function properly but should not quit alone. + If the engine reports "copyprotection error" the GUI should not use this engine + and display an error message instead! + The code in the engine can look like this + TellGUI("copyprotection checking\n"); + // ... check the copy protection here ... + if(ok) + TellGUI("copyprotection ok\n"); + else + TellGUI("copyprotection error\n"); + +* registration + this is needed for engines that need a username and/or a code to function with all features. + Analog to the "copyprotection" command the engine can send "registration checking" + after the uciok command followed by either "registration ok" or "registration error". + Also after every attempt to register the engine it should answer with "registration checking" + and then either "registration ok" or "registration error". + In contrast to the "copyprotection" command, the GUI can use the engine after the engine has + reported an error, but should inform the user that the engine is not properly registered + and might not use all its features. + In addition the GUI should offer to open a dialog to + enable registration of the engine. To try to register an engine the GUI can send + the "register" command. + The GUI has to always answer with the "register" command if the engine sends "registration error" + at engine startup (this can also be done with "register later") + and tell the user somehow that the engine is not registered. + This way the engine knows that the GUI can deal with the registration procedure and the user + will be informed that the engine is not properly registered. + +* info + the engine wants to send information to the GUI. This should be done whenever one of the info has changed. + The engine can send only selected infos or multiple infos with one info command, + e.g. "info currmove e2e4 currmovenumber 1" or + "info depth 12 nodes 123456 nps 100000". + Also all infos belonging to the pv should be sent together + e.g. "info depth 2 score cp 214 time 1242 nodes 2124 nps 34928 pv e2e4 e7e5 g1f3" + I suggest to start sending "currmove", "currmovenumber", "currline" and "refutation" only after one second + to avoid too much traffic. + Additional info: + * depth + search depth in plies + * seldepth + selective search depth in plies, + if the engine sends seldepth there must also be a "depth" present in the same string. + * time + the time searched in ms, this should be sent together with the pv. + * nodes + x nodes searched, the engine should send this info regularly + * pv ... + the best line found + * multipv + this for the multi pv mode. + for the best move/pv add "multipv 1" in the string when you send the pv. + in k-best mode always send all k variants in k strings together. + * score + * cp + the score from the engine's point of view in centipawns. + * mate + mate in y moves, not plies. + If the engine is getting mated use negative values for y. + * lowerbound + the score is just a lower bound. + * upperbound + the score is just an upper bound. + * currmove + currently searching this move + * currmovenumber + currently searching move number x, for the first move x should be 1 not 0. + * hashfull + the hash is x permill full, the engine should send this info regularly + * nps + x nodes per second searched, the engine should send this info regularly + * tbhits + x positions where found in the endgame table bases + * sbhits + x positions where found in the shredder endgame databases + * cpuload + the cpu usage of the engine is x permill. + * string + any string str which will be displayed be the engine, + if there is a string command the rest of the line will be interpreted as . + * refutation ... + move is refuted by the line ... , i can be any number >= 1. + Example: after move d1h5 is searched, the engine can send + "info refutation d1h5 g6h5" + if g6h5 is the best answer after d1h5 or if g6h5 refutes the move d1h5. + if there is no refutation for d1h5 found, the engine should just send + "info refutation d1h5" + The engine should only send this if the option "UCI_ShowRefutations" is set to true. + * currline ... + this is the current line the engine is calculating. is the number of the cpu if + the engine is running on more than one cpu. = 1,2,3.... + if the engine is just using one cpu, can be omitted. + If is greater than 1, always send all k lines in k strings together. + The engine should only send this if the option "UCI_ShowCurrLine" is set to true. + + +* option + This command tells the GUI which parameters can be changed in the engine. + This should be sent once at engine startup after the "uci" and the "id" commands + if any parameter can be changed in the engine. + The GUI should parse this and build a dialog for the user to change the settings. + Note that not every option needs to appear in this dialog as some options like + "Ponder", "UCI_AnalyseMode", etc. are better handled elsewhere or are set automatically. + If the user wants to change some settings, the GUI will send a "setoption" command to the engine. + Note that the GUI need not send the setoption command when starting the engine for every option if + it doesn't want to change the default value. + For all allowed combinations see the examples below, + as some combinations of this tokens don't make sense. + One string will be sent for each parameter. + * name + The option has the name id. + Certain options have a fixed value for , which means that the semantics of this option is fixed. + Usually those options should not be displayed in the normal engine options window of the GUI but + get a special treatment. "Pondering" for example should be set automatically when pondering is + enabled or disabled in the GUI options. The same for "UCI_AnalyseMode" which should also be set + automatically by the GUI. All those certain options have the prefix "UCI_" except for the + first 6 options below. If the GUI gets an unknown Option with the prefix "UCI_", it should just + ignore it and not display it in the engine's options dialog. + * = Hash, type is spin + the value in MB for memory for hash tables can be changed, + this should be answered with the first "setoptions" command at program boot + if the engine has sent the appropriate "option name Hash" command, + which should be supported by all engines! + So the engine should use a very small hash first as default. + * = NalimovPath, type string + this is the path on the hard disk to the Nalimov compressed format. + Multiple directories can be concatenated with ";" + * = NalimovCache, type spin + this is the size in MB for the cache for the nalimov table bases + These last two options should also be present in the initial options exchange dialog + when the engine is booted if the engine supports it + * = Ponder, type check + this means that the engine is able to ponder. + The GUI will send this whenever pondering is possible or not. + Note: The engine should not start pondering on its own if this is enabled, this option is only + needed because the engine might change its time management algorithm when pondering is allowed. + * = OwnBook, type check + this means that the engine has its own book which is accessed by the engine itself. + if this is set, the engine takes care of the opening book and the GUI will never + execute a move out of its book for the engine. If this is set to false by the GUI, + the engine should not access its own book. + * = MultiPV, type spin + the engine supports multi best line or k-best mode. the default value is 1 + * = UCI_ShowCurrLine, type check, should be false by default, + the engine can show the current line it is calculating. see "info currline" above. + * = UCI_ShowRefutations, type check, should be false by default, + the engine can show a move and its refutation in a line. see "info refutations" above. + * = UCI_LimitStrength, type check, should be false by default, + The engine is able to limit its strength to a specific Elo number, + This should always be implemented together with "UCI_Elo". + * = UCI_Elo, type spin + The engine can limit its strength in Elo within this interval. + If UCI_LimitStrength is set to false, this value should be ignored. + If UCI_LimitStrength is set to true, the engine should play with this specific strength. + This should always be implemented together with "UCI_LimitStrength". + * = UCI_AnalyseMode, type check + The engine wants to behave differently when analysing or playing a game. + For example when playing it can use some kind of learning. + This is set to false if the engine is playing a game, otherwise it is true. + * = UCI_Opponent, type string + With this command the GUI can send the name, title, elo and if the engine is playing a human + or computer to the engine. + The format of the string has to be [GM|IM|FM|WGM|WIM|none] [|none] [computer|human] + Examples: + "setoption name UCI_Opponent value GM 2800 human Gary Kasparov" + "setoption name UCI_Opponent value none none computer Shredder" + * = UCI_EngineAbout, type string + With this command, the engine tells the GUI information about itself, for example a license text, + usually it doesn't make sense that the GUI changes this text with the setoption command. + Example: + "option name UCI_EngineAbout type string default Shredder by Stefan Meyer-Kahlen, see www.shredderchess.com" + * = UCI_ShredderbasesPath, type string + this is either the path to the folder on the hard disk containing the Shredder endgame databases or + the path and filename of one Shredder endgame datbase. + * = UCI_SetPositionValue, type string + the GUI can send this to the engine to tell the engine to use a certain value in centipawns from white's + point of view if evaluating this specifix position. + The string can have the formats: + + | clear + | clearall + + * type + The option has type t. + There are 5 different types of options the engine can send + * check + a checkbox that can either be true or false + * spin + a spin wheel that can be an integer in a certain range + * combo + a combo box that can have different predefined strings as a value + * button + a button that can be pressed to send a command to the engine + * string + a text field that has a string as a value, + an empty string has the value "" + * default + the default value of this parameter is x + * min + the minimum value of this parameter is x + * max + the maximum value of this parameter is x + * var + a predefined value of this parameter is x + Examples: + Here are 5 strings for each of the 5 possible types of options + "option name Nullmove type check default true\n" + "option name Selectivity type spin default 2 min 0 max 4\n" + "option name Style type combo default Normal var Solid var Normal var Risky\n" + "option name NalimovPath type string default c:\\n" + "option name Clear Hash type button\n" + + + +Examples: +--------- + +This is how the communication when the engine boots can look like: + +GUI engine + +// tell the engine to switch to UCI mode +uci + +// engine identify + id name Shredder + id author Stefan MK + +// engine sends the options it can change +// the engine can change the hash size from 1 to 128 MB + option name Hash type spin default 1 min 1 max 128 + +// the engine supports Nalimov endgame tablebases + option name NalimovPath type string default + option name NalimovCache type spin default 1 min 1 max 32 + +// the engine can switch off Nullmove and set the playing style + option name Nullmove type check default true + option name Style type combo default Normal var Solid var Normal var Risky + +// the engine has sent all parameters and is ready + uciok + +// Note: here the GUI can already send a "quit" command if it just wants to find out +// details about the engine, so the engine should not initialize its internal +// parameters before here. +// now the GUI sets some values in the engine +// set hash to 32 MB +setoption name Hash value 32 + +// init tbs +setoption name NalimovCache value 1 +setoption name NalimovPath value d:\tb;c\tb + +// waiting for the engine to finish initializing +// this command and the answer is required here! +isready + +// engine has finished setting up the internal values + readyok + +// now we are ready to go + +// if the GUI is supporting it, tell the engine that is is +// searching on a game that it hasn't searched on before +ucinewgame + +// if the engine supports the "UCI_AnalyseMode" option and the next search is supposed to +// be an analysis, the GUI should set "UCI_AnalyseMode" to true if it is currently +// set to false with this engine +setoption name UCI_AnalyseMode value true + +// tell the engine to search infinite from the start position after 1.e4 e5 +position startpos moves e2e4 e7e5 +go infinite + +// the engine starts sending infos about the search to the GUI +// (only some examples are given) + + + info depth 1 seldepth 0 + info score cp 13 depth 1 nodes 13 time 15 pv f1b5 + info depth 2 seldepth 2 + info nps 15937 + info score cp 14 depth 2 nodes 255 time 15 pv f1c4 f8c5 + info depth 2 seldepth 7 nodes 255 + info depth 3 seldepth 7 + info nps 26437 + info score cp 20 depth 3 nodes 423 time 15 pv f1c4 g8f6 b1c3 + info nps 41562 + .... + + +// here the user has seen enough and asks to stop the searching +stop + +// the engine has finished searching and is sending the bestmove command +// which is needed for every "go" command sent to tell the GUI +// that the engine is ready again + bestmove g1f3 ponder d8f6 + + + +Chess960 +======== + +UCI could easily be extended to support Chess960 (also known as Fischer Random Chess). + +The engine has to tell the GUI that it is capable of playing Chess960 and the GUI has to tell +the engine that is should play according to the Chess960 rules. +This is done by the special engine option UCI_Chess960. If the engine knows about Chess960 +it should send the command 'option name UCI_Chess960 type check default false' +to the GUI at program startup. +Whenever a Chess960 game is played, the GUI should set this engine option to 'true'. + +Castling is different in Chess960 and the white king move when castling short is not always e1g1. +A king move could both be the castling king move or just a normal king move. +This is why castling moves are sent in the form king "takes" his own rook. +Example: e1h1 for the white short castle move in the normal chess start position. + +In EPD and FEN position strings specifying the castle rights with w and q is not enough as +there could be more than one rook on the right or left side of the king. +This is why the castle rights are specified with the letter of the castle rook's line. +Upper case letters for white's and lower case letters for black's castling rights. +Example: The normal chess position would be: +rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w AHah - + diff --git a/src/uci.cpp b/src/uci.cpp index 84dd5e80..ce82fffe 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -10,7 +10,7 @@ (at your option) any later version. Zagreus is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of + but WITHOUstd::string ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. @@ -18,4 +18,404 @@ along with Zagreus. If not, see . */ +// ReSharper disable CppRedundantControlFlowJump +#include "uci.h" +#include +#include +#include + +namespace Zagreus { +void Engine::doSetup() { + // According to the UCI specification, bitboard, magic bitboards and other stuff should be done only when "isready" or "setoption" is called + if (didSetup) { + return; + } + + // TODO: setup here + + didSetup = true; +} + +void Engine::printStartupMessage() { + sendMessage("Zagreus Copyright (C) 2023-2024 Danny Jelsma"); + sendMessage(""); + sendMessage("This program comes with ABSOLUTELY NO WARRANTY."); + sendMessage("This is free software, and you are welcome to redistribute it"); + sendMessage("under the conditions of the GNU Affero General Public License v3.0 or later."); + sendMessage("You should have received a copy of the GNU Affero General Public License"); + sendMessage("along with this program. If not, see ."); + sendMessage(""); + sendMessage(" ______ "); + sendMessage(" |___ / "); + sendMessage(" / / __ _ __ _ _ __ ___ _ _ ___ "); + sendMessage(" / / / _` | / _` || '__|/ _ \\| | | |/ __|"); + sendMessage(" / /__| (_| || (_| || | | __/| |_| |\\__ \\"); + sendMessage(R"( /_____|\__,_| \__, ||_| \___| \__,_||___/)"); + sendMessage(" __/ | "); + sendMessage(" |___/ "); + sendMessage(""); + + std::string majorVersion = ZAGREUS_VERSION_MAJOR; + std::string minorVersion = ZAGREUS_VERSION_MINOR; + std::string versionString = "v" + majorVersion + "." + minorVersion; + + if (majorVersion == "dev") { + versionString = majorVersion + "-" + minorVersion; + } + + sendMessage("Zagreus UCI chess engine " + versionString + " by Danny Jelsma (https://github.com/Dannyj1/Zagreus)"); +} + +void Engine::handleUciCommand() { + std::string majorVersion = ZAGREUS_VERSION_MAJOR; + std::string minorVersion = ZAGREUS_VERSION_MINOR; + std::string versionString = "v" + majorVersion + "." + minorVersion; + + if (majorVersion == "dev") { + versionString = majorVersion + "-" + minorVersion; + } + + sendMessage("id name Zagreus " + versionString); + sendMessage("id author Danny Jelsma"); + + if (!this->options.empty()) { + for (auto& [name, option] : this->options) { + sendMessage(option.toString()); + } + } + + sendMessage("uciok"); +} + +void Engine::handleDebugCommand(const std::string& args) { + sendMessage("Debug mode is currently not implemented."); +} + +void Engine::handleIsReadyCommand(const std::string& args) { + if (!didSetup) { + doSetup(); + } + + sendMessage("readyok"); +} + +void Engine::handleSetOptionCommand(const std::string& args) { + if (!didSetup) { + doSetup(); + } + + std::istringstream iss(args); + std::string word; + std::string section = ""; + std::string name; + std::string value; + + while (iss >> word) { + std::string lowercaseWord = word; + std::ranges::transform(lowercaseWord, lowercaseWord.begin(), tolower); + + if (lowercaseWord == "name") { + section = word; + continue; + } + + if (lowercaseWord == "value") { + section = word; + continue; + } + + if (section == "name") { + if (name.empty()) { + name = word; + } else { + name += " " + word; + } + } + + if (section == "value") { + if (value.empty()) { + value = word; + } else { + value += " " + word; + } + } + } + + if (name.empty()) { + sendMessage("ERROR: No option name provided."); + return; + } + + if (!hasOption(name)) { + sendMessage("ERROR: Option " + name + " does not exist."); + return; + } + + UCIOption& option = this->getOption(name); + + if (value.empty()) { + if (option.getOptionType() == Button) { + if (option.getValue() == "true") { + value = "false"; + } else { + value = "true"; + } + } else { + sendMessage("ERROR: No option value provided."); + return; + } + } + + option.setValue(value); +} + +void Engine::handleUciNewGameCommand(const std::string& args) { + +} + +void Engine::handlePositionCommand(const std::string& args) { + +} + +void Engine::handleGoCommand(const std::string& args) { + +} + +void Engine::handleStopCommand(const std::string& args) { + +} + +void Engine::handlePonderHitCommand(const std::string& args) { + +} + +void Engine::handleQuitCommand(const std::string& args) { + +} + +void Engine::processCommand(const std::string& command, const std::string& args) { + if (command == "uci") { + handleUciCommand(); + } else if (command == "debug") { + handleDebugCommand(args); + } else if (command == "isready") { + handleIsReadyCommand(args); + } else if (command == "setoption") { + handleSetOptionCommand(args); + } else if (command == "register") { + // Not relevant for our engine + return; + } else if (command == "ucinewgame") { + handleUciNewGameCommand(args); + } else if (command == "position") { + handlePositionCommand(args); + } else if (command == "go") { + handleGoCommand(args); + } else if (command == "stop") { + handleStopCommand(args); + } else if (command == "ponderhit") { + handlePonderHitCommand(args); + } else if (command == "quit") { + handleQuitCommand(args); + } else { + // If unknown, we must skip it and process the rest. + if (args.empty() || args == " " || args == "\n") { + std::cout << "Unknown command: " << command << std::endl; + return; + } + + std::string newCommand; + std::string newArgs; + + if (args.find(' ') != std::string::npos) { + newCommand = args.substr(0, args.find(' ')); + newArgs = args.substr(args.find(' ') + 1); + } else { + newCommand = args; + } + + processCommand(newCommand, newArgs); + } +} + +void Engine::addOption(UCIOption& option) { + this->options[option.getName()] = option; +} + +UCIOption& Engine::getOption(const std::string& name) { + return this->options[name]; +} + +bool Engine::hasOption(const std::string& name) { + return this->options.contains(name); +} + +void Engine::startUci() { + printStartupMessage(); + + // TODO: make sure that anything that is not related to UCI input reading is done in a separate thread, as "the engine must be able to read input even when thinking" + std::string line; + + while (std::getline(std::cin, line)) { + line = removeRedundantSpaces(line); + std::string command = line.substr(0, line.find(' ')); + std::string args = line.substr(line.find(' ') + 1); + + if (args == "\n" || args == " ") { + args = ""; + } + + processCommand(command, args); + } +} + +void Engine::sendInfoMessage(const std::string& message) { + std::cout << "info " << message << std::endl; +} + +void Engine::sendMessage(const std::string& message) { + std::cout << message << std::endl; +} + +std::string removeRedundantSpaces(const std::string& input) { + std::string result; + bool inSpace = false; // Track if we are in a sequence of spaces/tabs + + for (size_t i = 0; i < input.length(); ++i) { + char current = input[i]; + + if (current == '\r' || current == '\t') { + current = ' '; + } + + if (std::isspace(current)) { + if (current == '\n') { + // Add newline and reset inSpace + result += '\n'; + inSpace = false; + } else if (!inSpace) { + // Only add space if it's the first whitespace in a sequence + if (!result.empty() && result.back() != '\n' && result.back() != ' ') { + result += ' '; + } + inSpace = true; + } + } else { + // Add non-space character and reset inSpace + result += current; + inSpace = false; + } + } + + // Trim trailing space if any + if (!result.empty() && result.back() == ' ') { + result.pop_back(); + } + + return result; +} + +UCIOptionType UCIOption::getOptionType() { + return this->optionType; +} + +std::string UCIOption::getName() { + return this->name; +} + +std::string UCIOption::getValue() { + return this->value; +} + +void UCIOption::setValue(std::string value) { + this->value = value; +} + +std::string UCIOption::getDefaultValue() { + return this->defaultValue; +} + +void UCIOption::setDefaultValue(std::string value) { + this->defaultValue = value; +} + +std::string UCIOption::getMinValue() { + return this->minValue; +} + +void UCIOption::setMinValue(std::string value) { + this->minValue = value; +} + +std::string UCIOption::getMaxValue() { + return this->maxValue; +} + +void UCIOption::setMaxValue(std::string value) { + this->maxValue = value; +} + +std::string getUciOptionTypeAsString(UCIOptionType type) { + switch (type) { + case Check: + return "check"; + case Spin: + return "spin"; + case Combo: + return "combo"; + case Button: + return "button"; + case String: + return "string"; + } +} + +std::string UCIOption::toString() { + std::string result = "option name " + this->name + " type " + getUciOptionTypeAsString(this->optionType); + + if (!this->defaultValue.empty()) { + result += " default " + this->defaultValue; + } + + if (!this->minValue.empty()) { + result += " min " + this->minValue; + } + + if (!this->maxValue.empty()) { + result += " max " + this->maxValue; + } + + if (!this->var.empty()) { + for (const std::string& value : this->var) { + result += " var " + value; + } + } + + return result; +} + +void UCIOption::addVar(std::string value) { + this->var.push_back(value); +} + +void UCIOption::setVar(std::vector values) { + this->var = values; +} + +void UCIOption::removeVar(std::string value) { + this->var.erase(std::remove(this->var.begin(), this->var.end(), value), this->var.end()); +} + +void UCIOption::clearVar() { + this->var.clear(); +} + +std::string UCIOption::getVar(int index) { + return this->var.at(index); +} + +std::vector UCIOption::getVar() { + return this->var; +} +} // namespace Zagreus diff --git a/src/uci.h b/src/uci.h index 5b3e55cd..04868886 100644 --- a/src/uci.h +++ b/src/uci.h @@ -1,4 +1,3 @@ - /* This file is part of Zagreus. @@ -11,7 +10,7 @@ (at your option) any later version. Zagreus is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of + but WITHOUstd::string ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. @@ -21,4 +20,96 @@ #pragma once +#include +#include +#include + +namespace Zagreus { +class UCIOption; + +class Engine { +private: + bool didSetup = false; + std::map options; + + void handleUciCommand(); + void handleDebugCommand(const std::string& args); + void handleIsReadyCommand(const std::string& args); + void handleSetOptionCommand(const std::string& args); + void handleUciNewGameCommand(const std::string& args); + void handlePositionCommand(const std::string& args); + void handleGoCommand(const std::string& args); + void handleStopCommand(const std::string& args); + void handlePonderHitCommand(const std::string& args); + void handleQuitCommand(const std::string& args); + void processCommand(const std::string& string, const std::string& args); +public: + void startUci(); + void sendInfoMessage(const std::string& message); + void sendMessage(const std::string& message); + void doSetup(); + void printStartupMessage(); + void addOption(UCIOption& option); + UCIOption& getOption(const std::string& name); + bool hasOption(const std::string& name); +}; + +enum UCIOptionType { + Check, + Spin, + Combo, + Button, + String +}; + +class UCIOption { +private: + std::string name; + UCIOptionType optionType; + std::string value; + std::string defaultValue; + std::string minValue; + std::string maxValue; + std::vector var; +public: + UCIOption() = default; + + UCIOption(std::string name, UCIOptionType optionType) : name(std::move(name)), optionType(optionType) {} + + UCIOptionType getOptionType(); + + std::string getName(); + + std::string getValue(); + + void setValue(std::string value); + + std::string getDefaultValue(); + + void setDefaultValue(std::string value); + + std::string getMinValue(); + + void setMinValue(std::string value); + + std::string getMaxValue(); + + void setMaxValue(std::string value); + + std::string toString(); + + void addVar(std::string value); + + void setVar(std::vector values); + + void removeVar(std::string value); + + void clearVar(); + + std::string getVar(int index); + + std::vector getVar(); +}; +std::string removeRedundantSpaces(const std::string& input); +} // namespace Zagreus \ No newline at end of file From bb32362c76e75d19b54a242b8ef9f6a6b1a71017 Mon Sep 17 00:00:00 2001 From: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> Date: Thu, 22 Aug 2024 15:28:35 +0200 Subject: [PATCH 03/91] Push old changes that weren't pushed yet --- src/bitboard.cpp | 29 ++++++++++++++++++++++++++++- src/bitwise.cpp | 33 +++++++++++++++++++++++++++++++++ src/bitwise.h | 16 ++++++++++++++++ src/constants.h | 25 +++++++++++++++++++++++++ 4 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 src/constants.h diff --git a/src/bitboard.cpp b/src/bitboard.cpp index 7c543598..46d948a9 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -48,9 +48,36 @@ int Bitboard::bitscanReverse() { } template -Bitboard shift() { +Bitboard Bitboard::shift() { Bitboard result{}; + switch (direction) { + case NORTH: + result.setValue(Bitwise::shiftNorth(this->bitboard)); + break; + case SOUTH: + result.setValue(Bitwise::shiftSouth(this->bitboard)); + break; + case EAST: + result.setValue(Bitwise::shiftEast(this->bitboard)); + break; + case WEST: + result.setValue(Bitwise::shiftWest(this->bitboard)); + break; + case NORTH_EAST: + result.setValue(Bitwise::shiftNorthEast(this->bitboard)); + break; + case NORTH_WEST: + result.setValue(Bitwise::shiftNorthWest(this->bitboard)); + break; + case SOUTH_EAST: + result.setValue(Bitwise::shiftSouthEast(this->bitboard)); + break; + case SOUTH_WEST: + result.setValue(Bitwise::shiftSouthWest(this->bitboard)); + break; + } + return result; } diff --git a/src/bitwise.cpp b/src/bitwise.cpp index 38d28001..67d768e9 100644 --- a/src/bitwise.cpp +++ b/src/bitwise.cpp @@ -19,6 +19,7 @@ */ #include "bitwise.h" +#include "constants.h" #include @@ -57,5 +58,37 @@ uint64_t popLsb(uint64_t& bb) { bb &= bb - 1; return lsb; } + +uint64_t shiftNorth(uint64_t bb) { + return bb << 8; +} + +uint64_t shiftSouth(uint64_t bb) { + return bb >> 8; +} + +uint64_t shiftEast(uint64_t bb) { + return (bb << 1) & NOT_A_FILE; +} + +uint64_t shiftWest(uint64_t bb) { + return (bb >> 1) & NOT_H_FILE; +} + +uint64_t shiftNorthEast(uint64_t bb) { + return (bb << 9) & NOT_A_FILE; +} + +uint64_t shiftNorthWest(uint64_t bb) { + return (bb << 7) & NOT_H_FILE; +} + +uint64_t shiftSouthEast(uint64_t bb) { + return (bb >> 7) & NOT_A_FILE; +} + +uint64_t shiftSouthWest(uint64_t bb) { + return (bb >> 9) & NOT_H_FILE; +} } // namespace Zagreus::Bitwise diff --git a/src/bitwise.h b/src/bitwise.h index c06f5c38..3eb77b9f 100644 --- a/src/bitwise.h +++ b/src/bitwise.h @@ -30,4 +30,20 @@ inline int bitscanForward(uint64_t bb); inline int bitscanReverse(uint64_t bb); inline uint64_t popLsb(uint64_t& bb); + +inline uint64_t shiftNorth(uint64_t bb); + +inline uint64_t shiftSouth(uint64_t bb); + +inline uint64_t shiftEast(uint64_t bb); + +inline uint64_t shiftWest(uint64_t bb); + +inline uint64_t shiftNorthEast(uint64_t bb); + +inline uint64_t shiftNorthWest(uint64_t bb); + +inline uint64_t shiftSouthEast(uint64_t bb); + +inline uint64_t shiftSouthWest(uint64_t bb); } // namespace Zagreus::Bitwise diff --git a/src/constants.h b/src/constants.h new file mode 100644 index 00000000..73a6ff77 --- /dev/null +++ b/src/constants.h @@ -0,0 +1,25 @@ + +/* + This file is part of Zagreus. + + Zagreus is a UCI chess engine + Copyright (C) 2023-2024 Danny Jelsma + + Zagreus is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Zagreus 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Zagreus. If not, see . + */ + +#pragma once + +constexpr uint64_t NOT_A_FILE = 0xfefefefefefefefe; +constexpr uint64_t NOT_H_FILE = 0x7f7f7f7f7f7f7f7f; From 73aedd8ad704449361c260e80627c77a52b1d26e Mon Sep 17 00:00:00 2001 From: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> Date: Tue, 17 Sep 2024 09:16:47 +0200 Subject: [PATCH 04/91] Changed board implementation --- .idea/.gitignore | 8 +++ src/bitboard.cpp | 124 +++++++------------------------------------- src/bitboard.h | 32 +++--------- src/bitwise.cpp | 36 +++++++++---- src/bitwise.h | 12 +++++ src/board.cpp | 9 +++- src/board.h | 13 ++++- src/constants.h | 5 ++ src/move_gen.cpp | 30 +++++++++++ src/move_gen.h | 30 +++++++++++ src/move_picker.cpp | 27 ++++++++++ src/move_picker.h | 35 +++++++++++++ src/types.h | 29 ++++++++++- src/uci.cpp | 9 ++-- src/uci.h | 4 +- 15 files changed, 256 insertions(+), 147 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 src/move_gen.cpp create mode 100644 src/move_gen.h create mode 100644 src/move_picker.cpp create mode 100644 src/move_picker.h diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..13566b81 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/src/bitboard.cpp b/src/bitboard.cpp index 46d948a9..b6fea194 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -23,128 +23,44 @@ #include "types.h" namespace Zagreus { -uint64_t Bitboard::getValue() const { - return this->bitboard; +uint64_t popcnt(const uint64_t bitboard) { + return Bitwise::popcnt(bitboard); } -void Bitboard::setValue(uint64_t value) { - this->bitboard = value; +uint64_t popLsb(uint64_t bitboard) { + return Bitwise::popLsb(bitboard); } -uint64_t Bitboard::popcnt() { - return Bitwise::popcnt(this->bitboard); +int bitscanForward(const uint64_t bitboard) { + return Bitwise::bitscanForward(bitboard); } -uint64_t Bitboard::popLsb() { - return Bitwise::popLsb(this->bitboard); -} - -int Bitboard::bitscanForward() { - return Bitwise::bitscanForward(this->bitboard); -} - -int Bitboard::bitscanReverse() { - return Bitwise::bitscanReverse(this->bitboard); +int bitscanReverse(const uint64_t bitboard) { + return Bitwise::bitscanReverse(bitboard); } template -Bitboard Bitboard::shift() { - Bitboard result{}; - +uint64_t shift(const uint64_t bitboard) { switch (direction) { + using enum Direction; case NORTH: - result.setValue(Bitwise::shiftNorth(this->bitboard)); - break; + return Bitwise::shiftNorth(bitboard); case SOUTH: - result.setValue(Bitwise::shiftSouth(this->bitboard)); - break; + return Bitwise::shiftSouth(bitboard); case EAST: - result.setValue(Bitwise::shiftEast(this->bitboard)); - break; + return Bitwise::shiftEast(bitboard); case WEST: - result.setValue(Bitwise::shiftWest(this->bitboard)); - break; + return Bitwise::shiftWest(bitboard); case NORTH_EAST: - result.setValue(Bitwise::shiftNorthEast(this->bitboard)); - break; + return Bitwise::shiftNorthEast(bitboard); case NORTH_WEST: - result.setValue(Bitwise::shiftNorthWest(this->bitboard)); - break; + return Bitwise::shiftNorthWest(bitboard); case SOUTH_EAST: - result.setValue(Bitwise::shiftSouthEast(this->bitboard)); - break; + return Bitwise::shiftSouthEast(bitboard); case SOUTH_WEST: - result.setValue(Bitwise::shiftSouthWest(this->bitboard)); - break; + return Bitwise::shiftSouthWest(bitboard); } - return result; -} - -Bitboard Bitboard::operator&(const Bitboard& other) { - Bitboard result{}; - - result.setValue(this->getValue() & other.getValue()); - return result; -} - -Bitboard Bitboard::operator|(const Bitboard& other) { - Bitboard result{}; - - result.setValue(this->getValue() | other.getValue()); - return result; -} - -Bitboard Bitboard::operator^(const Bitboard& other) { - Bitboard result{}; - - result.setValue(this->getValue() ^ other.getValue()); - return result; -} - -Bitboard Bitboard::operator~() { - Bitboard result{}; - - result.setValue(~this->getValue()); - return result; -} - -Bitboard Bitboard::operator<<(int shift) { - Bitboard result{}; - - result.setValue(this->getValue() << shift); - return result; -} - -Bitboard Bitboard::operator>>(int shift) { - Bitboard result{}; - - result.setValue(this->getValue() >> shift); - return result; -} - -Bitboard& Bitboard::operator&=(const Bitboard& other) { - this->setValue(this->getValue() & other.getValue()); - - return *this; -} - -Bitboard& Bitboard::operator|=(const Bitboard& other) { - this->setValue(this->getValue() | other.getValue()); - - return *this; -} - -Bitboard& Bitboard::operator^=(const Bitboard& other) { - this->setValue(this->getValue() ^ other.getValue()); - - return *this; -} - -Bitboard squareToBitboard(int index) { - Bitboard result{}; - - result.setValue(1ULL << index); - return result; + return bitboard; } -} // namespace Zagreus \ No newline at end of file +} // namespace Zagreus diff --git a/src/bitboard.h b/src/bitboard.h index e629437d..7cea0919 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -20,36 +20,18 @@ */ #pragma once + #include +#include "constants.h" #include "types.h" namespace Zagreus { -class Bitboard { -private: - uint64_t bitboard = 0ULL; -public: - uint64_t getValue() const; - void setValue(uint64_t value); - - inline uint64_t popcnt(); - inline uint64_t popLsb(); - inline int bitscanForward(); - inline int bitscanReverse(); + inline uint64_t popcnt(uint64_t bitboard); + inline uint64_t popLsb(uint64_t bitboard); + inline int bitscanForward(uint64_t bitboard); + inline int bitscanReverse(uint64_t bitboard); template - Bitboard shift(); - - inline Bitboard operator&(const Bitboard& other); - inline Bitboard operator|(const Bitboard& other); - inline Bitboard operator^(const Bitboard& other); - inline Bitboard operator~(); - inline Bitboard operator<<(int shift); - inline Bitboard operator>>(int shift); - inline Bitboard& operator&=(const Bitboard& other); - inline Bitboard& operator|=(const Bitboard& other); - inline Bitboard& operator^=(const Bitboard& other); -}; - -Bitboard squareToBitboard(int index); + uint64_t shift(uint64_t bitboard); } // namespace Zagreus diff --git a/src/bitwise.cpp b/src/bitwise.cpp index 67d768e9..82057337 100644 --- a/src/bitwise.cpp +++ b/src/bitwise.cpp @@ -42,7 +42,7 @@ int bitscanForward(uint64_t bb) { #endif } -int bitscanReverse(uint64_t bb) { +int bitscanReverse(const uint64_t bb) { #ifdef _MSC_VER unsigned long index; _BitScanReverse64(&index, bb); @@ -59,36 +59,54 @@ uint64_t popLsb(uint64_t& bb) { return lsb; } -uint64_t shiftNorth(uint64_t bb) { +uint64_t shiftNorth(const uint64_t bb) { return bb << 8; } -uint64_t shiftSouth(uint64_t bb) { +uint64_t shiftSouth(const uint64_t bb) { return bb >> 8; } -uint64_t shiftEast(uint64_t bb) { +uint64_t shiftEast(const uint64_t bb) { return (bb << 1) & NOT_A_FILE; } -uint64_t shiftWest(uint64_t bb) { +uint64_t shiftWest(const uint64_t bb) { return (bb >> 1) & NOT_H_FILE; } -uint64_t shiftNorthEast(uint64_t bb) { +uint64_t shiftNorthEast(const uint64_t bb) { return (bb << 9) & NOT_A_FILE; } -uint64_t shiftNorthWest(uint64_t bb) { +uint64_t shiftNorthWest(const uint64_t bb) { return (bb << 7) & NOT_H_FILE; } -uint64_t shiftSouthEast(uint64_t bb) { +uint64_t shiftSouthEast(const uint64_t bb) { return (bb >> 7) & NOT_A_FILE; } -uint64_t shiftSouthWest(uint64_t bb) { +uint64_t shiftSouthWest(const uint64_t bb) { return (bb >> 9) & NOT_H_FILE; } + +uint64_t whitePawnSinglePush(uint64_t bb, uint64_t empty) { +} + +uint64_t whitePawnDoublePush(uint64_t bb, uint64_t empty) { +} + +uint64_t whitePawnAttack(uint64_t bb) { +} + +uint64_t blackPawnSinglePush(uint64_t bb, uint64_t empty) { +} + +uint64_t blackPawnDoublePush(uint64_t bb, uint64_t empty) { +} + +uint64_t blackPawnAttack(uint64_t bb) { +} } // namespace Zagreus::Bitwise diff --git a/src/bitwise.h b/src/bitwise.h index 3eb77b9f..01dde87a 100644 --- a/src/bitwise.h +++ b/src/bitwise.h @@ -46,4 +46,16 @@ inline uint64_t shiftNorthWest(uint64_t bb); inline uint64_t shiftSouthEast(uint64_t bb); inline uint64_t shiftSouthWest(uint64_t bb); + +inline uint64_t whitePawnSinglePush(uint64_t bb, uint64_t empty); + +inline uint64_t whitePawnDoublePush(uint64_t bb, uint64_t empty); + +inline uint64_t whitePawnAttack(uint64_t bb); + +inline uint64_t blackPawnSinglePush(uint64_t bb, uint64_t empty); + +inline uint64_t blackPawnDoublePush(uint64_t bb, uint64_t empty); + +inline uint64_t blackPawnAttack(uint64_t bb); } // namespace Zagreus::Bitwise diff --git a/src/board.cpp b/src/board.cpp index eb7b8491..7299d840 100644 --- a/src/board.cpp +++ b/src/board.cpp @@ -20,7 +20,14 @@ #include "board.h" +#include + namespace Zagreus { +uint64_t Board::getBitboard(Piece piece) const { + return bitboards[std::to_underlying(piece)]; +} +Piece Board::getPieceOnSquare(int square) const { + return board[square]; +} } // namespace Zagreus - diff --git a/src/board.h b/src/board.h index fa96d008..6d52865a 100644 --- a/src/board.h +++ b/src/board.h @@ -21,6 +21,17 @@ #pragma once -namespace Zagreus { +#include + +#include "bitboard.h" +namespace Zagreus { +class Board { +private: + std::array board{}; + std::array bitboards{}; +public: + [[nodiscard]] uint64_t getBitboard(Piece piece) const; + [[nodiscard]] Piece getPieceOnSquare(int square) const; +}; } // namespace Zagreus diff --git a/src/constants.h b/src/constants.h index 73a6ff77..732087d1 100644 --- a/src/constants.h +++ b/src/constants.h @@ -23,3 +23,8 @@ constexpr uint64_t NOT_A_FILE = 0xfefefefefefefefe; constexpr uint64_t NOT_H_FILE = 0x7f7f7f7f7f7f7f7f; + +constexpr int SQUARES = 64; +constexpr int PIECES = 12; + +constexpr int MAX_MOVES = 256; diff --git a/src/move_gen.cpp b/src/move_gen.cpp new file mode 100644 index 00000000..d40dce18 --- /dev/null +++ b/src/move_gen.cpp @@ -0,0 +1,30 @@ +/* + This file is part of Zagreus. + + Zagreus is a UCI chess engine + Copyright (C) 2023-2024 Danny Jelsma + + Zagreus is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Zagreus 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Zagreus. If not, see . + */ + +#include "move_gen.h" +#include "board.h" +#include "types.h" + +namespace Zagreus { +template +void generatePawnMoves(const Board& board, const MoveList moves) { + +} +} // namespace Zagreus diff --git a/src/move_gen.h b/src/move_gen.h new file mode 100644 index 00000000..8b099d4b --- /dev/null +++ b/src/move_gen.h @@ -0,0 +1,30 @@ + +/* + This file is part of Zagreus. + + Zagreus is a UCI chess engine + Copyright (C) 2023-2024 Danny Jelsma + + Zagreus is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Zagreus 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Zagreus. If not, see . + */ + +#pragma once + +#include "board.h" +#include "types.h" + +namespace Zagreus { +template +void generatePawnMoves(const Board& board, MoveList moves); +} // namespace Zagreus diff --git a/src/move_picker.cpp b/src/move_picker.cpp new file mode 100644 index 00000000..d8605dc0 --- /dev/null +++ b/src/move_picker.cpp @@ -0,0 +1,27 @@ +/* + This file is part of Zagreus. + + Zagreus is a UCI chess engine + Copyright (C) 2023-2024 Danny Jelsma + + Zagreus is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Zagreus 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Zagreus. If not, see . + */ + +#include "move_picker.h" + +namespace Zagreus { +MoveList& MovePicker::getMoveList() { + return this->moveList; +} +} // namespace Zagreus diff --git a/src/move_picker.h b/src/move_picker.h new file mode 100644 index 00000000..ad6f72bc --- /dev/null +++ b/src/move_picker.h @@ -0,0 +1,35 @@ +/* + This file is part of Zagreus. + + Zagreus is a UCI chess engine + Copyright (C) 2023-2024 Danny Jelsma + + Zagreus is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Zagreus 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Zagreus. If not, see . + */ + +#pragma once + +#include + +#include "constants.h" +#include "types.h" + +namespace Zagreus { +class MovePicker { +private: + MoveList moveList{}; +public: + MoveList& getMoveList(); +}; +} // namespace Zagreus diff --git a/src/types.h b/src/types.h index 514c40ec..1546b09d 100644 --- a/src/types.h +++ b/src/types.h @@ -21,7 +21,7 @@ #pragma once -enum Direction { +enum class Direction { NORTH, SOUTH, EAST, @@ -31,3 +31,30 @@ enum Direction { SOUTH_EAST, SOUTH_WEST }; + +enum class PieceType { PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, EMPTY = -1 }; + +enum class PieceColor { WHITE, BLACK, EMPTY = -1 }; + +enum class Piece { + WHITE_PAWN, + WHITE_KNIGHT, + WHITE_BISHOP, + WHITE_ROOK, + WHITE_QUEEN, + WHITE_KING, + BLACK_PAWN, + BLACK_KNIGHT, + BLACK_BISHOP, + BLACK_ROOK, + BLACK_QUEEN, + BLACK_KING, + EMPTY = -1 +}; + +using Move = uint16_t; + +struct MoveList { + std::array moveList{}; + int moveCount = 0; +}; diff --git a/src/uci.cpp b/src/uci.cpp index ce82fffe..eac49172 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -21,6 +21,7 @@ // ReSharper disable CppRedundantControlFlowJump #include "uci.h" +#include #include #include #include @@ -88,7 +89,7 @@ void Engine::handleUciCommand() { sendMessage("uciok"); } -void Engine::handleDebugCommand(const std::string& args) { +void Engine::handleDebugCommand(std::string_view args) { sendMessage("Debug mode is currently not implemented."); } @@ -228,7 +229,7 @@ void Engine::processCommand(const std::string& command, const std::string& args) std::string newCommand; std::string newArgs; - if (args.find(' ') != std::string::npos) { + if (args.contains(' ')) { newCommand = args.substr(0, args.find(' ')); newArgs = args.substr(args.find(' ') + 1); } else { @@ -356,7 +357,7 @@ void UCIOption::setMaxValue(std::string value) { this->maxValue = value; } -std::string getUciOptionTypeAsString(UCIOptionType type) { +std::string getUciOptionTypeAsString(const UCIOptionType type) { switch (type) { case Check: return "check"; @@ -411,7 +412,7 @@ void UCIOption::clearVar() { this->var.clear(); } -std::string UCIOption::getVar(int index) { +std::string UCIOption::getVar(const int index) { return this->var.at(index); } diff --git a/src/uci.h b/src/uci.h index 04868886..6f0d3b7a 100644 --- a/src/uci.h +++ b/src/uci.h @@ -33,7 +33,7 @@ class Engine { std::map options; void handleUciCommand(); - void handleDebugCommand(const std::string& args); + void handleDebugCommand(std::string_view args); void handleIsReadyCommand(const std::string& args); void handleSetOptionCommand(const std::string& args); void handleUciNewGameCommand(const std::string& args); @@ -74,7 +74,7 @@ class UCIOption { public: UCIOption() = default; - UCIOption(std::string name, UCIOptionType optionType) : name(std::move(name)), optionType(optionType) {} + UCIOption(std::string name, const UCIOptionType optionType) : name(std::move(name)), optionType(optionType) {} UCIOptionType getOptionType(); From 6b789c8b297369fdd0235f5dc5fb894165b41454 Mon Sep 17 00:00:00 2001 From: Dannyj1 <73849717+Dannyj1@users.noreply.github.com> Date: Tue, 1 Oct 2024 09:25:25 +0200 Subject: [PATCH 05/91] Don't copy MoveList to move_gen, use reference instead --- src/move_gen.cpp | 2 +- src/move_gen.h | 2 +- src/types.h | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/move_gen.cpp b/src/move_gen.cpp index d40dce18..4f45e7d1 100644 --- a/src/move_gen.cpp +++ b/src/move_gen.cpp @@ -24,7 +24,7 @@ namespace Zagreus { template -void generatePawnMoves(const Board& board, const MoveList moves) { +void generatePawnMoves(const Board& board, const MoveList& moves) { } } // namespace Zagreus diff --git a/src/move_gen.h b/src/move_gen.h index 8b099d4b..48ac0ce1 100644 --- a/src/move_gen.h +++ b/src/move_gen.h @@ -26,5 +26,5 @@ namespace Zagreus { template -void generatePawnMoves(const Board& board, MoveList moves); +void generatePawnMoves(const Board& board, const MoveList& moves); } // namespace Zagreus diff --git a/src/types.h b/src/types.h index 1546b09d..9b81915d 100644 --- a/src/types.h +++ b/src/types.h @@ -1,4 +1,3 @@ - /* This file is part of Zagreus. @@ -21,6 +20,8 @@ #pragma once +#include + enum class Direction { NORTH, SOUTH, From 425d82edc91d3229438f90d904ac003d5fd8c70d Mon Sep 17 00:00:00 2001 From: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> Date: Tue, 1 Oct 2024 17:15:26 +0200 Subject: [PATCH 06/91] Implement very basic pawn move generation --- src/bitboard.cpp | 2 +- src/bitwise.cpp | 82 ++++++++++++++++++++++++++++++++++++++++++---- src/bitwise.h | 25 ++++++++++++-- src/board.cpp | 18 ++++++++-- src/board.h | 14 +++++++- src/constants.h | 14 +++++--- src/move.h | 39 ++++++++++++++++++++++ src/move_gen.cpp | 83 +++++++++++++++++++++++++++++++++++++++++++++-- src/move_gen.h | 10 +++++- src/move_picker.h | 2 ++ src/types.h | 13 ++------ 11 files changed, 272 insertions(+), 30 deletions(-) create mode 100644 src/move.h diff --git a/src/bitboard.cpp b/src/bitboard.cpp index b6fea194..cc224aed 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -42,7 +42,7 @@ int bitscanReverse(const uint64_t bitboard) { template uint64_t shift(const uint64_t bitboard) { switch (direction) { - using enum Direction; + using enum Direction; case NORTH: return Bitwise::shiftNorth(bitboard); case SOUTH: diff --git a/src/bitwise.cpp b/src/bitwise.cpp index 82057337..098e7612 100644 --- a/src/bitwise.cpp +++ b/src/bitwise.cpp @@ -91,22 +91,92 @@ uint64_t shiftSouthWest(const uint64_t bb) { return (bb >> 9) & NOT_H_FILE; } -uint64_t whitePawnSinglePush(uint64_t bb, uint64_t empty) { +template +uint64_t shift(const uint64_t bb) { + switch (direction) { + case Direction::NORTH: + return shiftNorth(bb); + case Direction::SOUTH: + return shiftSouth(bb); + case Direction::EAST: + return shiftEast(bb); + case Direction::WEST: + return shiftWest(bb); + case Direction::NORTH_EAST: + return shiftNorthEast(bb); + case Direction::NORTH_WEST: + return shiftNorthWest(bb); + case Direction::SOUTH_EAST: + return shiftSouthEast(bb); + case Direction::SOUTH_WEST: + return shiftSouthWest(bb); + default: + return bb; + } } -uint64_t whitePawnDoublePush(uint64_t bb, uint64_t empty) { +uint64_t whitePawnSinglePush(const uint64_t bb, const uint64_t empty) { + return shiftNorth(bb) & empty; } -uint64_t whitePawnAttack(uint64_t bb) { +uint64_t whitePawnDoublePush(const uint64_t bb, const uint64_t empty) { + const uint64_t singlePush = whitePawnSinglePush(bb, empty); + + return shiftNorth(singlePush) & empty & RANK_4; +} + +uint64_t whitePawnWestAttacks(uint64_t bb) { + return shiftNorthWest(bb); +} + +uint64_t whitePawnEastAttacks(uint64_t bb) { + return shiftNorthEast(bb); +} + +uint64_t whitePawnAttacks(const uint64_t bb) { + return whitePawnWestAttacks(bb) | whitePawnEastAttacks(bb); +} + +uint64_t whitePushablePawns(const uint64_t bb, const uint64_t empty) { + return shiftSouth(empty) & bb; +} + +uint64_t whiteDoublePushablePawns(const uint64_t bb, const uint64_t empty) { + const uint64_t emptyRank3 = shiftSouth(empty & RANK_4) & empty; + + return whitePushablePawns(bb, emptyRank3); +} + +uint64_t blackPawnSinglePush(const uint64_t bb, const uint64_t empty) { + return shiftSouth(bb) & empty; +} + +uint64_t blackPawnDoublePush(const uint64_t bb, const uint64_t empty) { + const uint64_t singlePush = blackPawnSinglePush(bb, empty); + + return shiftSouth(singlePush) & empty & RANK_5; } -uint64_t blackPawnSinglePush(uint64_t bb, uint64_t empty) { +uint64_t blackPawnWestAttacks(const uint64_t bb) { + return shiftSouthWest(bb); } -uint64_t blackPawnDoublePush(uint64_t bb, uint64_t empty) { +uint64_t blackPawnEastAttacks(const uint64_t bb) { + return shiftSouthEast(bb); } -uint64_t blackPawnAttack(uint64_t bb) { +uint64_t blackPawnAttacks(const uint64_t bb) { + return blackPawnWestAttacks(bb) | blackPawnEastAttacks(bb); +} + +uint64_t blackPushablePawns(const uint64_t bb, const uint64_t empty) { + return shiftNorth(empty) & bb; +} + +uint64_t blackDoublePushablePawns(const uint64_t bb, const uint64_t empty) { + const uint64_t emptyRank6 = shiftNorth(empty & RANK_5) & empty; + + return blackPushablePawns(bb, emptyRank6); } } // namespace Zagreus::Bitwise diff --git a/src/bitwise.h b/src/bitwise.h index 01dde87a..1e3545e8 100644 --- a/src/bitwise.h +++ b/src/bitwise.h @@ -22,6 +22,8 @@ #pragma once #include +#include "types.h" + namespace Zagreus::Bitwise { inline uint64_t popcnt(uint64_t bb); @@ -47,15 +49,34 @@ inline uint64_t shiftSouthEast(uint64_t bb); inline uint64_t shiftSouthWest(uint64_t bb); +template +inline uint64_t shift(uint64_t bb); + inline uint64_t whitePawnSinglePush(uint64_t bb, uint64_t empty); inline uint64_t whitePawnDoublePush(uint64_t bb, uint64_t empty); -inline uint64_t whitePawnAttack(uint64_t bb); +inline uint64_t whitePawnWestAttacks(uint64_t bb); + +inline uint64_t whitePawnEastAttacks(uint64_t bb); + +inline uint64_t whitePawnAttacks(uint64_t bb); + +inline uint64_t whitePushablePawns(uint64_t bb, uint64_t empty); + +inline uint64_t whiteDoublePushablePawns(uint64_t bb, uint64_t empty); inline uint64_t blackPawnSinglePush(uint64_t bb, uint64_t empty); inline uint64_t blackPawnDoublePush(uint64_t bb, uint64_t empty); -inline uint64_t blackPawnAttack(uint64_t bb); +inline uint64_t blackPawnWestAttacks(uint64_t bb); + +inline uint64_t blackPawnEastAttacks(uint64_t bb); + +inline uint64_t blackPawnAttacks(uint64_t bb); + +inline uint64_t blackPushablePawns(uint64_t bb, uint64_t empty); + +inline uint64_t blackDoublePushablePawns(uint64_t bb, uint64_t empty); } // namespace Zagreus::Bitwise diff --git a/src/board.cpp b/src/board.cpp index 7299d840..cb4d0222 100644 --- a/src/board.cpp +++ b/src/board.cpp @@ -23,11 +23,25 @@ #include namespace Zagreus { -uint64_t Board::getBitboard(Piece piece) const { +template +uint64_t Board::getBitboard() const { return bitboards[std::to_underlying(piece)]; } -Piece Board::getPieceOnSquare(int square) const { +template +uint64_t Board::getColorBitboard() const { + return colorBoards[std::to_underlying(color)]; +} + +Piece Board::getPieceOnSquare(const int square) const { return board[square]; } + +uint64_t Board::getOccupiedBitboard() const { + return occupied; +} + +uint64_t Board::getEmptyBitboard() const { + return ~occupied; +} } // namespace Zagreus diff --git a/src/board.h b/src/board.h index 6d52865a..8c4b4fbf 100644 --- a/src/board.h +++ b/src/board.h @@ -30,8 +30,20 @@ class Board { private: std::array board{}; std::array bitboards{}; + uint64_t occupied = 0; + std::array colorBoards{}; + public: - [[nodiscard]] uint64_t getBitboard(Piece piece) const; + template + [[nodiscard]] uint64_t getBitboard() const; + [[nodiscard]] Piece getPieceOnSquare(int square) const; + + template + [[nodiscard]] uint64_t getColorBitboard() const; + + [[nodiscard]] uint64_t getOccupiedBitboard() const; + + [[nodiscard]] uint64_t getEmptyBitboard() const; }; } // namespace Zagreus diff --git a/src/constants.h b/src/constants.h index 732087d1..40d8f297 100644 --- a/src/constants.h +++ b/src/constants.h @@ -21,10 +21,14 @@ #pragma once -constexpr uint64_t NOT_A_FILE = 0xfefefefefefefefe; -constexpr uint64_t NOT_H_FILE = 0x7f7f7f7f7f7f7f7f; +constexpr uint64_t NOT_A_FILE = 0xFEFEFEFEFEFEFEFEULL; +constexpr uint64_t NOT_H_FILE = 0x7F7F7F7F7F7F7F7FULL; -constexpr int SQUARES = 64; -constexpr int PIECES = 12; +constexpr uint64_t RANK_4 = 0x00000000FF000000ULL; +constexpr uint64_t RANK_5 = 0x000000FF00000000ULL; -constexpr int MAX_MOVES = 256; +constexpr uint8_t SQUARES = 64; +constexpr uint8_t PIECES = 12; +constexpr uint8_t COLORS = 2; + +constexpr uint8_t MAX_MOVES = 255; diff --git a/src/move.h b/src/move.h new file mode 100644 index 00000000..9c905cab --- /dev/null +++ b/src/move.h @@ -0,0 +1,39 @@ + +/* + This file is part of Zagreus. + + Zagreus is a UCI chess engine + Copyright (C) 2023-2024 Danny Jelsma + + Zagreus is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Zagreus 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Zagreus. If not, see . + */ + +#pragma once +#include "constants.h" + + +namespace Zagreus { +using Move = uint16_t; + +struct MoveList { + std::array list{}; + int size = 0; +}; + +// bits 0-5: from square (0-63) +// bits 6-11: to square (0-63) +inline Move encodeMove(const uint8_t fromSquare, const uint8_t toSquare) { + return (fromSquare << 6) | toSquare; +} +} // namespace Zagreus diff --git a/src/move_gen.cpp b/src/move_gen.cpp index 4f45e7d1..26d733ff 100644 --- a/src/move_gen.cpp +++ b/src/move_gen.cpp @@ -19,12 +19,91 @@ */ #include "move_gen.h" + +#include <__utility/to_underlying.h> + +#include "bitwise.h" #include "board.h" #include "types.h" namespace Zagreus { -template -void generatePawnMoves(const Board& board, const MoveList& moves) { +template +void generatePawnMoves(const Board& board, MoveList& moves) { + // TODO: Implement en passant + // TODO: Handle promotions + // TODO: Move attacks to table lookup + // TODO: Implement GenerationType logic using a mask that is computed based on type + constexpr Piece pawn = color == PieceColor::WHITE ? Piece::WHITE_PAWN : Piece::BLACK_PAWN; + constexpr PieceColor opponentColor = color == PieceColor::WHITE + ? PieceColor::BLACK + : PieceColor::WHITE; + + const uint64_t pawnBB = board.getBitboard(); + const uint64_t emptyBB = board.getEmptyBitboard(); + const uint64_t opponentPieces = board.getColorBitboard(); + uint64_t pawnSinglePushes; + uint64_t pawnDoublePushes; + uint64_t pawnWestAttacks; + uint64_t pawnEastAttacks; + + if constexpr (color == PieceColor::WHITE) { + pawnSinglePushes = Bitwise::whitePawnSinglePush(pawnBB, emptyBB); + pawnDoublePushes = Bitwise::whitePawnDoublePush(pawnBB, emptyBB); + pawnWestAttacks = Bitwise::whitePawnWestAttacks(pawnBB); + pawnEastAttacks = Bitwise::whitePawnEastAttacks(pawnBB); + } else { + pawnSinglePushes = Bitwise::blackPawnSinglePush(pawnBB, emptyBB); + pawnDoublePushes = Bitwise::blackPawnDoublePush(pawnBB, emptyBB); + pawnWestAttacks = Bitwise::blackPawnWestAttacks(pawnBB); + pawnEastAttacks = Bitwise::blackPawnEastAttacks(pawnBB); + } + + pawnWestAttacks &= opponentPieces; + pawnEastAttacks &= opponentPieces; + constexpr Direction fromPushDirection = color == PieceColor::WHITE + ? Direction::SOUTH + : Direction::NORTH; + constexpr Direction fromSqWestAttackDirection = color == PieceColor::WHITE + ? Direction::SOUTH_WEST + : Direction::NORTH_WEST; + constexpr Direction fromSqEastAttackDirection = color == PieceColor::WHITE + ? Direction::SOUTH_EAST + : Direction::NORTH_EAST; + + while (pawnSinglePushes) { + const uint64_t squareTo = Bitwise::popLsb(pawnSinglePushes); + const uint64_t squareFrom = Bitwise::shift(squareTo); + const Move move = encodeMove(squareFrom, squareTo); + + moves.list[moves.size] = move; + moves.size++; + } + + while (pawnDoublePushes) { + const uint64_t squareTo = Bitwise::popLsb(pawnDoublePushes); + const uint64_t squareFrom = Bitwise::shift(squareTo); + const Move move = encodeMove(squareFrom, squareTo); + + moves.list[moves.size] = move; + moves.size++; + } + + while (pawnWestAttacks) { + const uint64_t squareTo = Bitwise::popLsb(pawnWestAttacks); + const uint64_t squareFrom = Bitwise::shift(squareTo); + const Move move = encodeMove(squareFrom, squareTo); + + moves.list[moves.size] = move; + moves.size++; + } + + while (pawnEastAttacks) { + const uint64_t squareTo = Bitwise::popLsb(pawnEastAttacks); + const uint64_t squareFrom = Bitwise::shift(squareTo); + const Move move = encodeMove(squareFrom, squareTo); + moves.list[moves.size] = move; + moves.size++; + } } } // namespace Zagreus diff --git a/src/move_gen.h b/src/move_gen.h index 48ac0ce1..34075cdd 100644 --- a/src/move_gen.h +++ b/src/move_gen.h @@ -22,9 +22,17 @@ #pragma once #include "board.h" +#include "move.h" #include "types.h" namespace Zagreus { -template +enum class GenerationType { + All, + Quiet, + Capture, + Evasions +}; + +template void generatePawnMoves(const Board& board, const MoveList& moves); } // namespace Zagreus diff --git a/src/move_picker.h b/src/move_picker.h index ad6f72bc..ff130e91 100644 --- a/src/move_picker.h +++ b/src/move_picker.h @@ -23,12 +23,14 @@ #include #include "constants.h" +#include "move.h" #include "types.h" namespace Zagreus { class MovePicker { private: MoveList moveList{}; + public: MoveList& getMoveList(); }; diff --git a/src/types.h b/src/types.h index 9b81915d..bba0f5a6 100644 --- a/src/types.h +++ b/src/types.h @@ -33,11 +33,11 @@ enum class Direction { SOUTH_WEST }; -enum class PieceType { PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, EMPTY = -1 }; +enum class PieceType : int8_t { PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, EMPTY = -1 }; -enum class PieceColor { WHITE, BLACK, EMPTY = -1 }; +enum class PieceColor : int8_t { WHITE, BLACK, EMPTY = -1 }; -enum class Piece { +enum class Piece : int8_t { WHITE_PAWN, WHITE_KNIGHT, WHITE_BISHOP, @@ -52,10 +52,3 @@ enum class Piece { BLACK_KING, EMPTY = -1 }; - -using Move = uint16_t; - -struct MoveList { - std::array moveList{}; - int moveCount = 0; -}; From c7cec56996be4f7df99c012c18d73962fa1b3acd Mon Sep 17 00:00:00 2001 From: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> Date: Fri, 4 Oct 2024 19:23:29 +0200 Subject: [PATCH 07/91] Implement knight move generation --- src/bitwise.cpp | 45 ++++- src/bitwise.h | 20 ++- src/constants.h | 2 + src/magics.cpp | 450 +++++++++++++++++++++++++++++++++++++++++++++++ src/magics.h | 92 ++++++++++ src/move_gen.cpp | 23 ++- src/move_gen.h | 7 +- src/uci.cpp | 33 ++-- src/uci.h | 2 +- 9 files changed, 651 insertions(+), 23 deletions(-) create mode 100644 src/magics.cpp create mode 100644 src/magics.h diff --git a/src/bitwise.cpp b/src/bitwise.cpp index 098e7612..3aac9b06 100644 --- a/src/bitwise.cpp +++ b/src/bitwise.cpp @@ -21,7 +21,9 @@ #include "bitwise.h" #include "constants.h" +#ifdef _MSC_VER #include +#endif namespace Zagreus::Bitwise { uint64_t popcnt(uint64_t bb) { @@ -91,6 +93,38 @@ uint64_t shiftSouthWest(const uint64_t bb) { return (bb >> 9) & NOT_H_FILE; } +uint64_t shiftNorthNorthEast(const uint64_t bb) { + return (bb << 17) & NOT_A_FILE; +} + +uint64_t shiftNorthEastEast(const uint64_t bb) { + return (bb << 10) & NOT_AB_FILE; +} + +uint64_t shiftSouthEastEast(const uint64_t bb) { + return (bb >> 6) & NOT_AB_FILE; +} + +uint64_t shiftSouthSouthEast(const uint64_t bb) { + return (bb >> 15) & NOT_A_FILE; +} + +uint64_t shiftNorthNorthWest(const uint64_t bb) { + return (bb << 15) & NOT_H_FILE; +} + +uint64_t shiftNorthWestWest(const uint64_t bb) { + return (bb << 6) & NOT_GH_FILE; +} + +uint64_t shiftSouthWestWest(const uint64_t bb) { + return (bb >> 10) & NOT_GH_FILE; +} + +uint64_t shiftSouthSouthWest(const uint64_t bb) { + return (bb >> 17) & NOT_H_FILE; +} + template uint64_t shift(const uint64_t bb) { switch (direction) { @@ -125,11 +159,11 @@ uint64_t whitePawnDoublePush(const uint64_t bb, const uint64_t empty) { return shiftNorth(singlePush) & empty & RANK_4; } -uint64_t whitePawnWestAttacks(uint64_t bb) { +uint64_t whitePawnWestAttacks(const uint64_t bb) { return shiftNorthWest(bb); } -uint64_t whitePawnEastAttacks(uint64_t bb) { +uint64_t whitePawnEastAttacks(const uint64_t bb) { return shiftNorthEast(bb); } @@ -178,5 +212,12 @@ uint64_t blackDoublePushablePawns(const uint64_t bb, const uint64_t empty) { return blackPushablePawns(bb, emptyRank6); } + +uint64_t knightAttacks(uint64_t bb) { + return shiftNorthNorthEast(bb) | shiftNorthEastEast(bb) | shiftSouthEastEast(bb) | + shiftSouthSouthEast(bb) + | shiftSouthSouthWest(bb) | shiftSouthWestWest(bb) | shiftNorthWestWest(bb) | + shiftNorthNorthWest(bb); +} } // namespace Zagreus::Bitwise diff --git a/src/bitwise.h b/src/bitwise.h index 1e3545e8..b24a798b 100644 --- a/src/bitwise.h +++ b/src/bitwise.h @@ -49,8 +49,24 @@ inline uint64_t shiftSouthEast(uint64_t bb); inline uint64_t shiftSouthWest(uint64_t bb); +inline uint64_t shiftNorthNorthEast(uint64_t bb); + +inline uint64_t shiftNorthEastEast(uint64_t bb); + +inline uint64_t shiftSouthEastEast(uint64_t bb); + +inline uint64_t shiftSouthSouthEast(uint64_t bb); + +inline uint64_t shiftNorthNorthWest(uint64_t bb); + +inline uint64_t shiftNorthWestWest(uint64_t bb); + +inline uint64_t shiftSouthWestWest(uint64_t bb); + +inline uint64_t shiftSouthSouthWest(uint64_t bb); + template -inline uint64_t shift(uint64_t bb); +uint64_t shift(uint64_t bb); inline uint64_t whitePawnSinglePush(uint64_t bb, uint64_t empty); @@ -79,4 +95,6 @@ inline uint64_t blackPawnAttacks(uint64_t bb); inline uint64_t blackPushablePawns(uint64_t bb, uint64_t empty); inline uint64_t blackDoublePushablePawns(uint64_t bb, uint64_t empty); + +inline uint64_t knightAttacks(uint64_t bb); } // namespace Zagreus::Bitwise diff --git a/src/constants.h b/src/constants.h index 40d8f297..f90e779e 100644 --- a/src/constants.h +++ b/src/constants.h @@ -22,6 +22,8 @@ #pragma once constexpr uint64_t NOT_A_FILE = 0xFEFEFEFEFEFEFEFEULL; +constexpr uint64_t NOT_AB_FILE = 0xFCFCFCFCFCFCFCFCULL; +constexpr uint64_t NOT_GH_FILE = 0x3F3F3F3F3F3F3F3FULL; constexpr uint64_t NOT_H_FILE = 0x7F7F7F7F7F7F7F7FULL; constexpr uint64_t RANK_4 = 0x00000000FF000000ULL; diff --git a/src/magics.cpp b/src/magics.cpp new file mode 100644 index 00000000..97eb7c04 --- /dev/null +++ b/src/magics.cpp @@ -0,0 +1,450 @@ +/* + This file is part of Zagreus. + + Zagreus is a chess engine that supports the UCI protocol + Copyright (C) 2023 Danny Jelsma + + Zagreus is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Zagreus 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Zagreus. If not, see . + */ + +#include "magics.h" + +#include +#include +#include +#include + +// Code for magic generation https://www.chessprogramming.org/Looking_for_Magics +namespace Zagreus { +std::random_device rd; +std::mt19937_64 gen(rd()); +std::uniform_int_distribution dis; + +auto rookMagics = new uint64_t[64]{}; +auto bishopMagics = new uint64_t[64]{}; + +auto bishop_masks = new uint64_t[64]{}; +auto rook_masks = new uint64_t[64]{}; + +auto bishop_attacks = new uint64_t[64][512]{}; +auto rook_attacks = new uint64_t[64][4096]{}; + +uint64_t getRookMagic(int sq) { return rookMagics[sq]; } + +uint64_t getBishopMagic(int sq) { return bishopMagics[sq]; } + +uint64_t getRookMask(int sq) { return rook_masks[sq]; } + +uint64_t getBishopMask(int sq) { return bishop_masks[sq]; } + +uint64_t getRookMagicAttacks(int sq, uint64_t index) { return rook_attacks[sq][index]; } + +uint64_t getBishopMagicAttacks(int sq, uint64_t index) { return bishop_attacks[sq][index]; } + +uint64_t random_uint64_t_fewbits() { return dis(gen) & dis(gen) & dis(gen); } + +int count_1s(uint64_t b) { + int r; + for (r = 0; b; r++, b &= b - 1); + return r; +} + +const int BitTable[64] = {63, 30, 3, 32, 25, 41, 22, 33, 15, 50, 42, 13, 11, 53, 19, 34, + 61, 29, 2, 51, 21, 43, 45, 10, 18, 47, 1, 54, 9, 57, 0, 35, + 62, 31, 40, 4, 49, 5, 52, 26, 60, 6, 23, 44, 46, 27, 56, 16, + 7, 39, 48, 24, 59, 14, 12, 55, 38, 28, 58, 20, 37, 17, 36, 8}; + +int pop_1st_bit(uint64_t* bb) { + uint64_t b = *bb ^ (*bb - 1); + unsigned int fold = static_cast((b & 0xffffffff) ^ (b >> 32)); + *bb &= (*bb - 1); + return BitTable[(fold * 0x783a9b23) >> 26]; +} + +uint64_t index_to_uint64_t(int index, int bits, uint64_t m) { + int i, j; + uint64_t result = 0ULL; + for (i = 0; i < bits; i++) { + j = pop_1st_bit(&m); + if (index & (1 << i)) result |= (1ULL << j); + } + return result; +} + +uint64_t rmask(int sq) { + uint64_t result = 0ULL; + int rk = sq / 8, fl = sq % 8, r, f; + for (r = rk + 1; r <= 6; r++) result |= (1ULL << (fl + r * 8)); + for (r = rk - 1; r >= 1; r--) result |= (1ULL << (fl + r * 8)); + for (f = fl + 1; f <= 6; f++) result |= (1ULL << (f + rk * 8)); + for (f = fl - 1; f >= 1; f--) result |= (1ULL << (f + rk * 8)); + return result; +} + +uint64_t bmask(int sq) { + uint64_t result = 0ULL; + int rk = sq / 8, fl = sq % 8, r, f; + for (r = rk + 1, f = fl + 1; r <= 6 && f <= 6; r++, f++) result |= (1ULL << (f + r * 8)); + for (r = rk + 1, f = fl - 1; r <= 6 && f >= 1; r++, f--) result |= (1ULL << (f + r * 8)); + for (r = rk - 1, f = fl + 1; r >= 1 && f <= 6; r--, f++) result |= (1ULL << (f + r * 8)); + for (r = rk - 1, f = fl - 1; r >= 1 && f >= 1; r--, f--) result |= (1ULL << (f + r * 8)); + return result; +} + +uint64_t ratt(int sq, uint64_t block) { + uint64_t result = 0ULL; + int rk = sq / 8, fl = sq % 8, r, f; + for (r = rk + 1; r <= 7; r++) { + result |= (1ULL << (fl + r * 8)); + if (block & (1ULL << (fl + r * 8))) break; + } + for (r = rk - 1; r >= 0; r--) { + result |= (1ULL << (fl + r * 8)); + if (block & (1ULL << (fl + r * 8))) break; + } + for (f = fl + 1; f <= 7; f++) { + result |= (1ULL << (f + rk * 8)); + if (block & (1ULL << (f + rk * 8))) break; + } + for (f = fl - 1; f >= 0; f--) { + result |= (1ULL << (f + rk * 8)); + if (block & (1ULL << (f + rk * 8))) break; + } + return result; +} + +uint64_t batt(int sq, uint64_t block) { + uint64_t result = 0ULL; + int rk = sq / 8, fl = sq % 8, r, f; + for (r = rk + 1, f = fl + 1; r <= 7 && f <= 7; r++, f++) { + result |= (1ULL << (f + r * 8)); + if (block & (1ULL << (f + r * 8))) break; + } + for (r = rk + 1, f = fl - 1; r <= 7 && f >= 0; r++, f--) { + result |= (1ULL << (f + r * 8)); + if (block & (1ULL << (f + r * 8))) break; + } + for (r = rk - 1, f = fl + 1; r >= 0 && f <= 7; r--, f++) { + result |= (1ULL << (f + r * 8)); + if (block & (1ULL << (f + r * 8))) break; + } + for (r = rk - 1, f = fl - 1; r >= 0 && f >= 0; r--, f--) { + result |= (1ULL << (f + r * 8)); + if (block & (1ULL << (f + r * 8))) break; + } + return result; +} + +int transform(uint64_t b, uint64_t magic, int bits) { +#if defined(USE_32_BIT_MULTIPLICATIONS) + return (unsigned)((int)b * (int)magic ^ (int)(b >> 32) * (int)(magic >> 32)) >> (32 - bits); +#else + return static_cast((b * magic) >> (64 - bits)); +#endif +} + +uint64_t find_magic(int sq, int m, int bishop) { + uint64_t mask, b[4096], a[4096], used[4096], magic; + int i, j, k, n, fail; + + mask = bishop ? bmask(sq) : rmask(sq); + n = count_1s(mask); + + for (i = 0; i < (1 << n); i++) { + b[i] = index_to_uint64_t(i, n, mask); + a[i] = bishop ? batt(sq, b[i]) : ratt(sq, b[i]); + } + for (k = 0; k < 100000000; k++) { + magic = random_uint64_t_fewbits(); + if (count_1s((mask * magic) & 0xFF00000000000000ULL) < 6) continue; + for (i = 0; i < 4096; i++) used[i] = 0ULL; + for (i = 0, fail = 0; !fail && i < (1 << n); i++) { + j = transform(b[i], magic, m); + if (used[j] == 0ULL) + used[j] = a[i]; + else if (used[j] != a[i]) + fail = 1; + } + if (!fail) return magic; + } + printf("***Failed***\n"); + return 0ULL; +} + +int count_bits(uint64_t bitboard) { + // bit size + int count = 0; + + // pop bits untill bitboard is empty + while (bitboard) { + // increment size + count++; + + // consecutively reset least significant 1st bit + bitboard &= bitboard - 1; + } + + // return bit size + return count; +} + +// get index of LS1B in bitboard +int get_ls1b_index(uint64_t bitboard) { + // make sure bitboard is not empty + if (bitboard != 0) + // convert trailing zeros before LS1B to ones and size them + return count_bits((bitboard & -bitboard) - 1); + + // otherwise + // return illegal index + return -1; +} + +uint64_t set_occupancy(int index, int bits_in_mask, uint64_t attack_mask) { + // occupancy map + uint64_t occupancy = 0ULL; + + // loop over the range of bits within attack mask + for (int count = 0; count < bits_in_mask; count++) { + // get LS1B index of attacks mask + int8_t square = get_ls1b_index(attack_mask); + + // pop LS1B in attack map + pop_bit(attack_mask, square); + + // make sure occupancy is on board + if (index & (1 << count)) + // populate occupancy map + occupancy |= (1ULL << square); + } + + // return occupancy map + return occupancy; +} + +uint64_t mask_bishop_attacks(int8_t square) { + // attack bitboard + uint64_t attacks = 0ULL; + + // init files & ranks + int f, r; + + // init target files & ranks + int tr = square / 8; + int tf = square % 8; + + // generate attacks + for (r = tr + 1, f = tf + 1; r <= 6 && f <= 6; r++, f++) attacks |= (1ULL << (r * 8 + f)); + for (r = tr + 1, f = tf - 1; r <= 6 && f >= 1; r++, f--) attacks |= (1ULL << (r * 8 + f)); + for (r = tr - 1, f = tf + 1; r >= 1 && f <= 6; r--, f++) attacks |= (1ULL << (r * 8 + f)); + for (r = tr - 1, f = tf - 1; r >= 1 && f >= 1; r--, f--) attacks |= (1ULL << (r * 8 + f)); + + // return attack map for bishop on a given square + return attacks; +} + +// mask rook attacks +uint64_t mask_rook_attacks(int8_t square) { + // attacks bitboard + uint64_t attacks = 0ULL; + + // init files & ranks + int f, r; + + // init target files & ranks + int tr = square / 8; + int tf = square % 8; + + // generate attacks + for (r = tr + 1; r <= 6; r++) attacks |= (1ULL << (r * 8 + tf)); + for (r = tr - 1; r >= 1; r--) attacks |= (1ULL << (r * 8 + tf)); + for (f = tf + 1; f <= 6; f++) attacks |= (1ULL << (tr * 8 + f)); + for (f = tf - 1; f >= 1; f--) attacks |= (1ULL << (tr * 8 + f)); + + // return attack map for bishop on a given square + return attacks; +} + +uint64_t bishop_attacks_on_the_fly(int8_t square, uint64_t block) { + // attack bitboard + uint64_t attacks = 0ULL; + + // init files & ranks + int f, r; + + // init target files & ranks + int tr = square / 8; + int tf = square % 8; + + // generate attacks + for (r = tr + 1, f = tf + 1; r <= 7 && f <= 7; r++, f++) { + attacks |= (1ULL << (r * 8 + f)); + if (block & (1ULL << (r * 8 + f))) break; + } + + for (r = tr + 1, f = tf - 1; r <= 7 && f >= 0; r++, f--) { + attacks |= (1ULL << (r * 8 + f)); + if (block & (1ULL << (r * 8 + f))) break; + } + + for (r = tr - 1, f = tf + 1; r >= 0 && f <= 7; r--, f++) { + attacks |= (1ULL << (r * 8 + f)); + if (block & (1ULL << (r * 8 + f))) break; + } + + for (r = tr - 1, f = tf - 1; r >= 0 && f >= 0; r--, f--) { + attacks |= (1ULL << (r * 8 + f)); + if (block & (1ULL << (r * 8 + f))) break; + } + + // return attack map for bishop on a given square + return attacks; +} + +// rook attacks +uint64_t rook_attacks_on_the_fly(int8_t square, uint64_t block) { + // attacks bitboard + uint64_t attacks = 0ULL; + + // init files & ranks + int f, r; + + // init target files & ranks + int tr = square / 8; + int tf = square % 8; + + // generate attacks + for (r = tr + 1; r <= 7; r++) { + attacks |= (1ULL << (r * 8 + tf)); + if (block & (1ULL << (r * 8 + tf))) break; + } + + for (r = tr - 1; r >= 0; r--) { + attacks |= (1ULL << (r * 8 + tf)); + if (block & (1ULL << (r * 8 + tf))) break; + } + + for (f = tf + 1; f <= 7; f++) { + attacks |= (1ULL << (tr * 8 + f)); + if (block & (1ULL << (tr * 8 + f))) break; + } + + for (f = tf - 1; f >= 0; f--) { + attacks |= (1ULL << (tr * 8 + f)); + if (block & (1ULL << (tr * 8 + f))) break; + } + + // return attack map for bishop on a given square + return attacks; +} + +void init_sliders_attacks(bool is_bishop) { + // loop over 64 board squares + for (int8_t square = 0; square < 64; square++) { + // init bishop & rook masks + bishop_masks[square] = mask_bishop_attacks(square); + rook_masks[square] = mask_rook_attacks(square); + + // init current mask + uint64_t mask = is_bishop ? mask_bishop_attacks(square) : mask_rook_attacks(square); + + // size attack mask bits + int bit_count = count_bits(mask); + + // occupancy variations size + int occupancy_variations = 1 << bit_count; + + // loop over occupancy variations + for (int count = 0; count < occupancy_variations; count++) { + // bishop + if (is_bishop) { + // init occupancies, magic index & attacks + uint64_t occupancy = set_occupancy(count, bit_count, mask); + uint64_t magic_index = occupancy * bishopMagics[square] >> (64 - BBits[square]); + bishop_attacks[square][magic_index] = bishop_attacks_on_the_fly(square, occupancy); + } + + // rook + else { + // init occupancies, magic index & attacks + uint64_t occupancy = set_occupancy(count, bit_count, mask); + uint64_t magic_index = occupancy * rookMagics[square] >> (64 - RBits[square]); + rook_attacks[square][magic_index] = rook_attacks_on_the_fly(square, occupancy); + } + } + } +} + +void initializeMagicBitboards() { + generateMagics(); + + // init bishop attacks + init_sliders_attacks(true); + // init rook attacks + init_sliders_attacks(false); +} + +void generateMagics() { + gen.seed(generatorSeed); + + // printf("const uint64_t rookMagics[64] = {\n"); + for (int8_t square = 0; square < 64; square++) { + rookMagics[square] = find_magic(square, RBits[square], 0); + // printf(" 0x%llxULL,\n", find_magic(square, RBits[square], 0)); + } + // printf("};\n\n"); + + // printf("const uint64_t bishopMagics[64] = {\n"); + for (int8_t square = 0; square < 64; square++) { + bishopMagics[square] = find_magic(square, BBits[square], 1); + // printf(" 0x%llxULL,\n", find_magic(square, BBits[square], 1)); + } + // printf("};\n\n"); +} + +void findFastestSeed() { + // variable with max double + uint64_t max = std::numeric_limits::max(); + std::random_device seedRd; + std::mt19937_64 seedGen(seedRd()); + std::uniform_int_distribution seedDis; + + while (true) { + uint64_t seed = seedDis(seedGen); + uint64_t total = 0; + + for (int i = 0; i < 50; i++) { + gen.seed(seedDis(seedGen)); + std::chrono::time_point start = + std::chrono::steady_clock::now(); + + generateMagics(); + + std::chrono::time_point now = + std::chrono::steady_clock::now(); + uint64_t elapsed = std::chrono::duration_cast(now - start). + count(); + total += elapsed; + } + + uint64_t average = total / 50; + if (average < max) { + max = average; + std::cout << "New fastest seed: 0x" << std::uppercase << std::hex << seed << + std::nouppercase + << std::dec << "ULL, took: " << average << "ms (average)" << std::endl; + } + } +} +} // namespace Zagreus \ No newline at end of file diff --git a/src/magics.h b/src/magics.h new file mode 100644 index 00000000..9d6dd131 --- /dev/null +++ b/src/magics.h @@ -0,0 +1,92 @@ +/* + This file is part of Zagreus. + + Zagreus is a UCI chess engine + Copyright (C) 2023 Danny Jelsma + + Zagreus is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Zagreus 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Zagreus. If not, see . + */ + +#pragma once +#include + +namespace Zagreus { +constexpr uint64_t generatorSeed = 0x1C6FE234A7121C08ULL; + +#define get_bit(bitboard, square) (bitboard & (1ULL << square)) +#define pop_bit(bitboard, square) (get_bit(bitboard, square) ? (bitboard ^= (1ULL << square)) : 0) + +static int RBits[64] = {12, 11, 11, 11, 11, 11, 11, 12, 11, 10, 10, 10, 10, 10, 10, 11, + 11, 10, 10, 10, 10, 10, 10, 11, 11, 10, 10, 10, 10, 10, 10, 11, + 11, 10, 10, 10, 10, 10, 10, 11, 11, 10, 10, 10, 10, 10, 10, 11, + 11, 10, 10, 10, 10, 10, 10, 11, 12, 11, 11, 11, 11, 11, 11, 12}; + +static int BBits[64] = {6, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 7, 7, 7, 7, + 5, 5, 5, 5, 7, 9, 9, 7, 5, 5, 5, 5, 7, 9, 9, 7, 5, 5, 5, 5, 7, 7, + 7, 7, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 5, 5, 6}; + +uint64_t getRookMagic(int sq); + +uint64_t getBishopMagic(int sq); + +uint64_t getRookMask(int sq); + +uint64_t getBishopMask(int sq); + +uint64_t getRookMagicAttacks(int sq, uint64_t block); + +uint64_t getBishopMagicAttacks(int sq, uint64_t block); + +uint64_t random_uint64_t_fewbits(); + +int count_1s(uint64_t b); + +int pop_1st_bit(uint64_t* bb); + +uint64_t index_to_uint64_t(int index, int bits, uint64_t m); + +uint64_t rmask(int sq); + +uint64_t bmask(int sq); + +uint64_t ratt(int sq, uint64_t block); + +uint64_t batt(int sq, uint64_t block); + +int transform(uint64_t b, uint64_t magic, int bits); + +uint64_t find_magic(int sq, int m, int bishop); + +void generateMagics(); + +void findFastestSeed(); + +void init_sliders_attacks(int is_bishop); + +uint64_t set_occupancy(int index, int bits_in_mask, uint64_t attack_mask); + +int get_ls1b_index(uint64_t bitboard); + +uint64_t mask_rook_attacks(int8_t square); + +uint64_t mask_bishop_attacks(int8_t square); + +int count_bits(uint64_t bitboard); + +uint64_t rook_attacks_on_the_fly(int8_t square, uint64_t block); + +uint64_t bishop_attacks_on_the_fly(int8_t square, uint64_t block); + +void initializeMagicBitboards(); +} // namespace Zagreus \ No newline at end of file diff --git a/src/move_gen.cpp b/src/move_gen.cpp index 26d733ff..20eafb76 100644 --- a/src/move_gen.cpp +++ b/src/move_gen.cpp @@ -20,8 +20,6 @@ #include "move_gen.h" -#include <__utility/to_underlying.h> - #include "bitwise.h" #include "board.h" #include "types.h" @@ -106,4 +104,25 @@ void generatePawnMoves(const Board& board, MoveList& moves) { moves.size++; } } + +template +void generateKnightMoves(const Board& board, MoveList& moves) { + // TODO: Move to table calculations + constexpr Piece knight = color == PieceColor::WHITE ? Piece::WHITE_KNIGHT : Piece::BLACK_KNIGHT; + const uint64_t ownPieces = board.getColorBitboard(); + uint64_t knightBB = board.getBitboard(); + + while (knightBB) { + const uint64_t fromSquare = Bitwise::popLsb(knightBB); + uint64_t genBB = Bitwise::knightAttacks(fromSquare) & ~ownPieces; + + while (genBB) { + const uint64_t toSquare = Bitwise::popLsb(genBB); + const Move move = encodeMove(fromSquare, toSquare); + + moves.list[moves.size] = move; + moves.size++; + } + } +} } // namespace Zagreus diff --git a/src/move_gen.h b/src/move_gen.h index 34075cdd..1037f83b 100644 --- a/src/move_gen.h +++ b/src/move_gen.h @@ -33,6 +33,9 @@ enum class GenerationType { Evasions }; -template -void generatePawnMoves(const Board& board, const MoveList& moves); +template +void generatePawnMoves(const Board& board, MoveList& moves); + +template +void generateKnightMoves(const Board& board, MoveList& moves); } // namespace Zagreus diff --git a/src/uci.cpp b/src/uci.cpp index eac49172..7d0f0d9a 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -19,13 +19,14 @@ */ // ReSharper disable CppRedundantControlFlowJump -#include "uci.h" - #include #include #include #include +#include "magics.h" +#include "uci.h" + namespace Zagreus { void Engine::doSetup() { // According to the UCI specification, bitboard, magic bitboards and other stuff should be done only when "isready" or "setoption" is called @@ -34,19 +35,12 @@ void Engine::doSetup() { } // TODO: setup here + initializeMagicBitboards(); didSetup = true; } void Engine::printStartupMessage() { - sendMessage("Zagreus Copyright (C) 2023-2024 Danny Jelsma"); - sendMessage(""); - sendMessage("This program comes with ABSOLUTELY NO WARRANTY."); - sendMessage("This is free software, and you are welcome to redistribute it"); - sendMessage("under the conditions of the GNU Affero General Public License v3.0 or later."); - sendMessage("You should have received a copy of the GNU Affero General Public License"); - sendMessage("along with this program. If not, see ."); - sendMessage(""); sendMessage(" ______ "); sendMessage(" |___ / "); sendMessage(" / / __ _ __ _ _ __ ___ _ _ ___ "); @@ -56,9 +50,17 @@ void Engine::printStartupMessage() { sendMessage(" __/ | "); sendMessage(" |___/ "); sendMessage(""); + sendMessage("Zagreus Copyright (C) 2023-2024 Danny Jelsma"); + sendMessage(""); + sendMessage("This program comes with ABSOLUTELY NO WARRANTY."); + sendMessage("This is free software, and you are welcome to redistribute it"); + sendMessage("under the conditions of the GNU Affero General Public License v3.0 or later."); + sendMessage("You should have received a copy of the GNU Affero General Public License"); + sendMessage("along with this program. If not, see ."); + sendMessage(""); - std::string majorVersion = ZAGREUS_VERSION_MAJOR; - std::string minorVersion = ZAGREUS_VERSION_MINOR; + const std::string majorVersion = ZAGREUS_VERSION_MAJOR; + const std::string minorVersion = ZAGREUS_VERSION_MINOR; std::string versionString = "v" + majorVersion + "." + minorVersion; if (majorVersion == "dev") { @@ -66,11 +68,12 @@ void Engine::printStartupMessage() { } sendMessage("Zagreus UCI chess engine " + versionString + " by Danny Jelsma (https://github.com/Dannyj1/Zagreus)"); + sendMessage(""); } void Engine::handleUciCommand() { - std::string majorVersion = ZAGREUS_VERSION_MAJOR; - std::string minorVersion = ZAGREUS_VERSION_MINOR; + const std::string majorVersion = ZAGREUS_VERSION_MAJOR; + const std::string minorVersion = ZAGREUS_VERSION_MINOR; std::string versionString = "v" + majorVersion + "." + minorVersion; if (majorVersion == "dev") { @@ -329,7 +332,7 @@ std::string UCIOption::getValue() { return this->value; } -void UCIOption::setValue(std::string value) { +void UCIOption::setValue(std::string& value) { this->value = value; } diff --git a/src/uci.h b/src/uci.h index 6f0d3b7a..04a3adfb 100644 --- a/src/uci.h +++ b/src/uci.h @@ -82,7 +82,7 @@ class UCIOption { std::string getValue(); - void setValue(std::string value); + void setValue(std::string& value); std::string getDefaultValue(); From 83739e2f2ea1a3be26f70aeb50f95bc28081e91f Mon Sep 17 00:00:00 2001 From: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> Date: Fri, 4 Oct 2024 19:59:19 +0200 Subject: [PATCH 08/91] Attempts at fixing CI --- .github/workflows/ctest.yml | 2 +- .github/workflows/release.yml | 8 ++++---- .github/workflows/valgrind.yml | 2 +- src/constants.h | 2 ++ 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ctest.yml b/.github/workflows/ctest.yml index 055f37aa..55976e2f 100644 --- a/.github/workflows/ctest.yml +++ b/.github/workflows/ctest.yml @@ -21,7 +21,7 @@ jobs: - name: Setup Clang uses: KyleMayes/install-llvm-action@v2 with: - version: "17" + version: "18" arch: "x64" - name: Configure CMake & Build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3d89a6b3..58789eeb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,7 +12,7 @@ jobs: fail-fast: false matrix: include: - - os: ubuntu-22.04 + - os: ubuntu-latest build-name: Linux artifact-extension: .tar.gz config-command: cmake -DCMAKE_BUILD_TYPE=Release -DAPPEND_VERSION_USE_GIT=OFF . @@ -39,9 +39,9 @@ jobs: - name: Setup Clang (Linux) if: runner.os == 'Linux' - uses: KyleMayes/install-llvm-action@v1 + uses: KyleMayes/install-llvm-action@v2 with: - version: "17" + version: "18" - name: Setup msys and install required packages if: runner.os == 'Windows' @@ -91,7 +91,7 @@ jobs: create-release: needs: build - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - name: Download artifacts uses: actions/download-artifact@v4 diff --git a/.github/workflows/valgrind.yml b/.github/workflows/valgrind.yml index 55e4cfde..c862f870 100644 --- a/.github/workflows/valgrind.yml +++ b/.github/workflows/valgrind.yml @@ -21,7 +21,7 @@ jobs: - name: Setup Clang uses: KyleMayes/install-llvm-action@v2 with: - version: "17" + version: "18" arch: "x64" - name: Install valgrind diff --git a/src/constants.h b/src/constants.h index f90e779e..ea857466 100644 --- a/src/constants.h +++ b/src/constants.h @@ -21,6 +21,8 @@ #pragma once +#include + constexpr uint64_t NOT_A_FILE = 0xFEFEFEFEFEFEFEFEULL; constexpr uint64_t NOT_AB_FILE = 0xFCFCFCFCFCFCFCFCULL; constexpr uint64_t NOT_GH_FILE = 0x3F3F3F3F3F3F3F3FULL; From bfebd6d2166c24b308abf3881f3db4783cd469a1 Mon Sep 17 00:00:00 2001 From: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> Date: Sat, 5 Oct 2024 12:55:15 +0200 Subject: [PATCH 09/91] Apply suggestions from code review Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> --- src/move.h | 4 ++-- src/move_picker.cpp | 2 +- src/uci.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/move.h b/src/move.h index 9c905cab..c3c46c9e 100644 --- a/src/move.h +++ b/src/move.h @@ -31,8 +31,8 @@ struct MoveList { int size = 0; }; -// bits 0-5: from square (0-63) -// bits 6-11: to square (0-63) +// bits 0-5: to square (0-63) +// bits 6-11: from square (0-63) inline Move encodeMove(const uint8_t fromSquare, const uint8_t toSquare) { return (fromSquare << 6) | toSquare; } diff --git a/src/move_picker.cpp b/src/move_picker.cpp index d8605dc0..cb06cb66 100644 --- a/src/move_picker.cpp +++ b/src/move_picker.cpp @@ -21,7 +21,7 @@ #include "move_picker.h" namespace Zagreus { -MoveList& MovePicker::getMoveList() { +const MoveList& MovePicker::getMoveList() const { return this->moveList; } } // namespace Zagreus diff --git a/src/uci.h b/src/uci.h index 04a3adfb..59ece902 100644 --- a/src/uci.h +++ b/src/uci.h @@ -10,7 +10,7 @@ (at your option) any later version. Zagreus is distributed in the hope that it will be useful, - but WITHOUstd::string ANY WARRANTY; without even the implied warranty of + but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. From e2c7a3eabe07731a691aa0436eea2bf79ace0cf5 Mon Sep 17 00:00:00 2001 From: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> Date: Sat, 5 Oct 2024 15:16:52 +0200 Subject: [PATCH 10/91] Addressed several code review points, improved structuring of certain parts of the codebase. --- README.md | 6 -- src/bitboard.cpp | 66 ------------- src/bitboard.h | 171 +++++++++++++++++++++++++++++++-- src/bitwise.cpp | 223 -------------------------------------------- src/bitwise.h | 111 ++++++++-------------- src/board.cpp | 22 +---- src/board.h | 30 ++++-- src/main.cpp | 12 ++- src/move.h | 2 +- src/move_gen.cpp | 38 ++++---- src/move_gen.h | 2 +- src/move_picker.cpp | 4 +- src/move_picker.h | 4 +- src/types.h | 8 +- src/uci.cpp | 52 +++++++---- src/uci.h | 29 +++--- 16 files changed, 308 insertions(+), 472 deletions(-) delete mode 100644 src/bitboard.cpp delete mode 100644 src/bitwise.cpp diff --git a/README.md b/README.md index 81e70c51..f303ae54 100644 --- a/README.md +++ b/README.md @@ -78,9 +78,6 @@ cmake --build . # Credits Thanks to: - -- [zd3nik](https://github.com/zd3nik) for the [Senjo UCI Adapter](https://github.com/zd3nik/SenjoUCIAdapter), which I - slightly modified - [AndyGrant](https://github.com/AndyGrant) for the easy to use and open source [OpenBench Testing Framework](https://github.com/AndyGrant/OpenBench), making testing my engine SO much easier and allowing you to distribute test over multiple devices (I use a private instance of OpenBench) @@ -107,6 +104,3 @@ GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with Zagreus. If not, see . - -This project uses the [Senjo UCI Adapter](https://github.com/zd3nik/SenjoUCIAdapter) by zd3nik which is licensed under -the MIT license. All files from the Senjo UCI Adapter retain their original copyright and license notices. diff --git a/src/bitboard.cpp b/src/bitboard.cpp deleted file mode 100644 index cc224aed..00000000 --- a/src/bitboard.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023-2024 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#include "bitboard.h" -#include "bitwise.h" -#include "types.h" - -namespace Zagreus { -uint64_t popcnt(const uint64_t bitboard) { - return Bitwise::popcnt(bitboard); -} - -uint64_t popLsb(uint64_t bitboard) { - return Bitwise::popLsb(bitboard); -} - -int bitscanForward(const uint64_t bitboard) { - return Bitwise::bitscanForward(bitboard); -} - -int bitscanReverse(const uint64_t bitboard) { - return Bitwise::bitscanReverse(bitboard); -} - -template -uint64_t shift(const uint64_t bitboard) { - switch (direction) { - using enum Direction; - case NORTH: - return Bitwise::shiftNorth(bitboard); - case SOUTH: - return Bitwise::shiftSouth(bitboard); - case EAST: - return Bitwise::shiftEast(bitboard); - case WEST: - return Bitwise::shiftWest(bitboard); - case NORTH_EAST: - return Bitwise::shiftNorthEast(bitboard); - case NORTH_WEST: - return Bitwise::shiftNorthWest(bitboard); - case SOUTH_EAST: - return Bitwise::shiftSouthEast(bitboard); - case SOUTH_WEST: - return Bitwise::shiftSouthWest(bitboard); - } - - return bitboard; -} -} // namespace Zagreus diff --git a/src/bitboard.h b/src/bitboard.h index 7cea0919..3dae8c6c 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -1,20 +1,20 @@ /* This file is part of Zagreus. - + Zagreus is a UCI chess engine Copyright (C) 2023-2024 Danny Jelsma - + Zagreus is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - + Zagreus 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 Affero General Public License for more details. - + You should have received a copy of the GNU Affero General Public License along with Zagreus. If not, see . */ @@ -27,11 +27,162 @@ #include "types.h" namespace Zagreus { - inline uint64_t popcnt(uint64_t bitboard); - inline uint64_t popLsb(uint64_t bitboard); - inline int bitscanForward(uint64_t bitboard); - inline int bitscanReverse(uint64_t bitboard); +inline uint64_t shiftNorth(const uint64_t bb) { + return bb << 8; +} + +inline uint64_t shiftSouth(const uint64_t bb) { + return bb >> 8; +} + +inline uint64_t shiftEast(const uint64_t bb) { + return (bb << 1) & NOT_A_FILE; +} + +inline uint64_t shiftWest(const uint64_t bb) { + return (bb >> 1) & NOT_H_FILE; +} + +inline uint64_t shiftNorthEast(const uint64_t bb) { + return (bb << 9) & NOT_A_FILE; +} + +inline uint64_t shiftNorthWest(const uint64_t bb) { + return (bb << 7) & NOT_H_FILE; +} + +inline uint64_t shiftSouthEast(const uint64_t bb) { + return (bb >> 7) & NOT_A_FILE; +} + +inline uint64_t shiftSouthWest(const uint64_t bb) { + return (bb >> 9) & NOT_H_FILE; +} + +inline uint64_t shiftNorthNorthEast(const uint64_t bb) { + return (bb << 17) & NOT_A_FILE; +} + +inline uint64_t shiftNorthEastEast(const uint64_t bb) { + return (bb << 10) & NOT_AB_FILE; +} + +inline uint64_t shiftSouthEastEast(const uint64_t bb) { + return (bb >> 6) & NOT_AB_FILE; +} + +inline uint64_t shiftSouthSouthEast(const uint64_t bb) { + return (bb >> 15) & NOT_A_FILE; +} + +inline uint64_t shiftNorthNorthWest(const uint64_t bb) { + return (bb << 15) & NOT_H_FILE; +} + +inline uint64_t shiftNorthWestWest(const uint64_t bb) { + return (bb << 6) & NOT_GH_FILE; +} + +inline uint64_t shiftSouthWestWest(const uint64_t bb) { + return (bb >> 10) & NOT_GH_FILE; +} + +inline uint64_t shiftSouthSouthWest(const uint64_t bb) { + return (bb >> 17) & NOT_H_FILE; +} + +template +constexpr uint64_t shift(const uint64_t bb) { + switch (direction) { + using enum Direction; + case NORTH: + return shiftNorth(bb); + case SOUTH: + return shiftSouth(bb); + case EAST: + return shiftEast(bb); + case WEST: + return shiftWest(bb); + case NORTH_EAST: + return shiftNorthEast(bb); + case NORTH_WEST: + return shiftNorthWest(bb); + case SOUTH_EAST: + return shiftSouthEast(bb); + case SOUTH_WEST: + return shiftSouthWest(bb); + default: + return bb; + } +} + +inline uint64_t whitePawnSinglePush(const uint64_t bb, const uint64_t empty) { + return shiftNorth(bb) & empty; +} + +inline uint64_t whitePawnDoublePush(const uint64_t bb, const uint64_t empty) { + const uint64_t singlePush = whitePawnSinglePush(bb, empty); + + return shiftNorth(singlePush) & empty & RANK_4; +} + +inline uint64_t whitePawnWestAttacks(const uint64_t bb) { + return shiftNorthWest(bb); +} + +inline uint64_t whitePawnEastAttacks(const uint64_t bb) { + return shiftNorthEast(bb); +} + +inline uint64_t whitePawnAttacks(const uint64_t bb) { + return whitePawnWestAttacks(bb) | whitePawnEastAttacks(bb); +} + +inline uint64_t whitePushablePawns(const uint64_t bb, const uint64_t empty) { + return shiftSouth(empty) & bb; +} + +inline uint64_t whiteDoublePushablePawns(const uint64_t bb, const uint64_t empty) { + const uint64_t emptyRank3 = shiftSouth(empty & RANK_4) & empty; + + return whitePushablePawns(bb, emptyRank3); +} + +inline uint64_t blackPawnSinglePush(const uint64_t bb, const uint64_t empty) { + return shiftSouth(bb) & empty; +} + +inline uint64_t blackPawnDoublePush(const uint64_t bb, const uint64_t empty) { + const uint64_t singlePush = blackPawnSinglePush(bb, empty); + + return shiftSouth(singlePush) & empty & RANK_5; +} + +inline uint64_t blackPawnWestAttacks(const uint64_t bb) { + return shiftSouthWest(bb); +} + +inline uint64_t blackPawnEastAttacks(const uint64_t bb) { + return shiftSouthEast(bb); +} + +inline uint64_t blackPawnAttacks(const uint64_t bb) { + return blackPawnWestAttacks(bb) | blackPawnEastAttacks(bb); +} + +inline uint64_t blackPushablePawns(const uint64_t bb, const uint64_t empty) { + return shiftNorth(empty) & bb; +} + +inline uint64_t blackDoublePushablePawns(const uint64_t bb, const uint64_t empty) { + const uint64_t emptyRank6 = shiftNorth(empty & RANK_5) & empty; + + return blackPushablePawns(bb, emptyRank6); +} - template - uint64_t shift(uint64_t bitboard); +inline uint64_t knightAttacks(const uint64_t bb) { + return shiftNorthNorthEast(bb) | shiftNorthEastEast(bb) | shiftSouthEastEast(bb) | + shiftSouthSouthEast(bb) | shiftSouthSouthWest(bb) | shiftSouthWestWest(bb) | + shiftNorthWestWest(bb) |shiftNorthNorthWest(bb); +} } // namespace Zagreus diff --git a/src/bitwise.cpp b/src/bitwise.cpp deleted file mode 100644 index 3aac9b06..00000000 --- a/src/bitwise.cpp +++ /dev/null @@ -1,223 +0,0 @@ -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023-2024 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#include "bitwise.h" -#include "constants.h" - -#ifdef _MSC_VER -#include -#endif - -namespace Zagreus::Bitwise { -uint64_t popcnt(uint64_t bb) { -#ifdef _MSC_VER - return __popcnt64(bb); -#else - return __builtin_popcountll(bb); -#endif -} - -int bitscanForward(uint64_t bb) { -#ifdef _MSC_VER - unsigned long index; - _BitScanForward64(&index, bb); - return (int) index; -#else - return __builtin_ctzll(bb); -#endif -} - -int bitscanReverse(const uint64_t bb) { -#ifdef _MSC_VER - unsigned long index; - _BitScanReverse64(&index, bb); - return (int) index; -#else - return 63 ^ __builtin_clzll(bb); -#endif -} - -uint64_t popLsb(uint64_t& bb) { - int lsb = bitscanForward(bb); - - bb &= bb - 1; - return lsb; -} - -uint64_t shiftNorth(const uint64_t bb) { - return bb << 8; -} - -uint64_t shiftSouth(const uint64_t bb) { - return bb >> 8; -} - -uint64_t shiftEast(const uint64_t bb) { - return (bb << 1) & NOT_A_FILE; -} - -uint64_t shiftWest(const uint64_t bb) { - return (bb >> 1) & NOT_H_FILE; -} - -uint64_t shiftNorthEast(const uint64_t bb) { - return (bb << 9) & NOT_A_FILE; -} - -uint64_t shiftNorthWest(const uint64_t bb) { - return (bb << 7) & NOT_H_FILE; -} - -uint64_t shiftSouthEast(const uint64_t bb) { - return (bb >> 7) & NOT_A_FILE; -} - -uint64_t shiftSouthWest(const uint64_t bb) { - return (bb >> 9) & NOT_H_FILE; -} - -uint64_t shiftNorthNorthEast(const uint64_t bb) { - return (bb << 17) & NOT_A_FILE; -} - -uint64_t shiftNorthEastEast(const uint64_t bb) { - return (bb << 10) & NOT_AB_FILE; -} - -uint64_t shiftSouthEastEast(const uint64_t bb) { - return (bb >> 6) & NOT_AB_FILE; -} - -uint64_t shiftSouthSouthEast(const uint64_t bb) { - return (bb >> 15) & NOT_A_FILE; -} - -uint64_t shiftNorthNorthWest(const uint64_t bb) { - return (bb << 15) & NOT_H_FILE; -} - -uint64_t shiftNorthWestWest(const uint64_t bb) { - return (bb << 6) & NOT_GH_FILE; -} - -uint64_t shiftSouthWestWest(const uint64_t bb) { - return (bb >> 10) & NOT_GH_FILE; -} - -uint64_t shiftSouthSouthWest(const uint64_t bb) { - return (bb >> 17) & NOT_H_FILE; -} - -template -uint64_t shift(const uint64_t bb) { - switch (direction) { - case Direction::NORTH: - return shiftNorth(bb); - case Direction::SOUTH: - return shiftSouth(bb); - case Direction::EAST: - return shiftEast(bb); - case Direction::WEST: - return shiftWest(bb); - case Direction::NORTH_EAST: - return shiftNorthEast(bb); - case Direction::NORTH_WEST: - return shiftNorthWest(bb); - case Direction::SOUTH_EAST: - return shiftSouthEast(bb); - case Direction::SOUTH_WEST: - return shiftSouthWest(bb); - default: - return bb; - } -} - -uint64_t whitePawnSinglePush(const uint64_t bb, const uint64_t empty) { - return shiftNorth(bb) & empty; -} - -uint64_t whitePawnDoublePush(const uint64_t bb, const uint64_t empty) { - const uint64_t singlePush = whitePawnSinglePush(bb, empty); - - return shiftNorth(singlePush) & empty & RANK_4; -} - -uint64_t whitePawnWestAttacks(const uint64_t bb) { - return shiftNorthWest(bb); -} - -uint64_t whitePawnEastAttacks(const uint64_t bb) { - return shiftNorthEast(bb); -} - -uint64_t whitePawnAttacks(const uint64_t bb) { - return whitePawnWestAttacks(bb) | whitePawnEastAttacks(bb); -} - -uint64_t whitePushablePawns(const uint64_t bb, const uint64_t empty) { - return shiftSouth(empty) & bb; -} - -uint64_t whiteDoublePushablePawns(const uint64_t bb, const uint64_t empty) { - const uint64_t emptyRank3 = shiftSouth(empty & RANK_4) & empty; - - return whitePushablePawns(bb, emptyRank3); -} - -uint64_t blackPawnSinglePush(const uint64_t bb, const uint64_t empty) { - return shiftSouth(bb) & empty; -} - -uint64_t blackPawnDoublePush(const uint64_t bb, const uint64_t empty) { - const uint64_t singlePush = blackPawnSinglePush(bb, empty); - - return shiftSouth(singlePush) & empty & RANK_5; -} - -uint64_t blackPawnWestAttacks(const uint64_t bb) { - return shiftSouthWest(bb); -} - -uint64_t blackPawnEastAttacks(const uint64_t bb) { - return shiftSouthEast(bb); -} - -uint64_t blackPawnAttacks(const uint64_t bb) { - return blackPawnWestAttacks(bb) | blackPawnEastAttacks(bb); -} - -uint64_t blackPushablePawns(const uint64_t bb, const uint64_t empty) { - return shiftNorth(empty) & bb; -} - -uint64_t blackDoublePushablePawns(const uint64_t bb, const uint64_t empty) { - const uint64_t emptyRank6 = shiftNorth(empty & RANK_5) & empty; - - return blackPushablePawns(bb, emptyRank6); -} - -uint64_t knightAttacks(uint64_t bb) { - return shiftNorthNorthEast(bb) | shiftNorthEastEast(bb) | shiftSouthEastEast(bb) | - shiftSouthSouthEast(bb) - | shiftSouthSouthWest(bb) | shiftSouthWestWest(bb) | shiftNorthWestWest(bb) | - shiftNorthNorthWest(bb); -} -} // namespace Zagreus::Bitwise - diff --git a/src/bitwise.h b/src/bitwise.h index b24a798b..064ef0c4 100644 --- a/src/bitwise.h +++ b/src/bitwise.h @@ -20,81 +20,46 @@ */ #pragma once +#include #include #include "types.h" -namespace Zagreus::Bitwise { -inline uint64_t popcnt(uint64_t bb); - -inline int bitscanForward(uint64_t bb); - -inline int bitscanReverse(uint64_t bb); - -inline uint64_t popLsb(uint64_t& bb); - -inline uint64_t shiftNorth(uint64_t bb); - -inline uint64_t shiftSouth(uint64_t bb); - -inline uint64_t shiftEast(uint64_t bb); - -inline uint64_t shiftWest(uint64_t bb); - -inline uint64_t shiftNorthEast(uint64_t bb); - -inline uint64_t shiftNorthWest(uint64_t bb); - -inline uint64_t shiftSouthEast(uint64_t bb); - -inline uint64_t shiftSouthWest(uint64_t bb); - -inline uint64_t shiftNorthNorthEast(uint64_t bb); - -inline uint64_t shiftNorthEastEast(uint64_t bb); - -inline uint64_t shiftSouthEastEast(uint64_t bb); - -inline uint64_t shiftSouthSouthEast(uint64_t bb); - -inline uint64_t shiftNorthNorthWest(uint64_t bb); - -inline uint64_t shiftNorthWestWest(uint64_t bb); - -inline uint64_t shiftSouthWestWest(uint64_t bb); - -inline uint64_t shiftSouthSouthWest(uint64_t bb); - -template -uint64_t shift(uint64_t bb); - -inline uint64_t whitePawnSinglePush(uint64_t bb, uint64_t empty); - -inline uint64_t whitePawnDoublePush(uint64_t bb, uint64_t empty); - -inline uint64_t whitePawnWestAttacks(uint64_t bb); - -inline uint64_t whitePawnEastAttacks(uint64_t bb); - -inline uint64_t whitePawnAttacks(uint64_t bb); - -inline uint64_t whitePushablePawns(uint64_t bb, uint64_t empty); - -inline uint64_t whiteDoublePushablePawns(uint64_t bb, uint64_t empty); - -inline uint64_t blackPawnSinglePush(uint64_t bb, uint64_t empty); - -inline uint64_t blackPawnDoublePush(uint64_t bb, uint64_t empty); - -inline uint64_t blackPawnWestAttacks(uint64_t bb); - -inline uint64_t blackPawnEastAttacks(uint64_t bb); - -inline uint64_t blackPawnAttacks(uint64_t bb); - -inline uint64_t blackPushablePawns(uint64_t bb, uint64_t empty); - -inline uint64_t blackDoublePushablePawns(uint64_t bb, uint64_t empty); - -inline uint64_t knightAttacks(uint64_t bb); +namespace Zagreus { +inline uint64_t popcnt(uint64_t bb) { +#ifdef _MSC_VER + return __popcnt64(bb); +#else + return __builtin_popcountll(bb); +#endif +} + +inline int bitscanForward(uint64_t bb) { + assert(bb != 0); +#ifdef _MSC_VER + unsigned long index; + _BitScanForward64(&index, bb); + return (int) index; +#else + return __builtin_ctzll(bb); +#endif +} + +inline int bitscanReverse(const uint64_t bb) { + assert(bb != 0); +#ifdef _MSC_VER + unsigned long index; + _BitScanReverse64(&index, bb); + return (int) index; +#else + return 63 ^ __builtin_clzll(bb); +#endif +} + +inline int popLsb(uint64_t& bb) { + const int lsb = bitscanForward(bb); + + bb &= bb - 1; + return lsb; +} } // namespace Zagreus::Bitwise diff --git a/src/board.cpp b/src/board.cpp index cb4d0222..aefb6e5e 100644 --- a/src/board.cpp +++ b/src/board.cpp @@ -23,25 +23,5 @@ #include namespace Zagreus { -template -uint64_t Board::getBitboard() const { - return bitboards[std::to_underlying(piece)]; -} - -template -uint64_t Board::getColorBitboard() const { - return colorBoards[std::to_underlying(color)]; -} - -Piece Board::getPieceOnSquare(const int square) const { - return board[square]; -} - -uint64_t Board::getOccupiedBitboard() const { - return occupied; -} - -uint64_t Board::getEmptyBitboard() const { - return ~occupied; -} + } // namespace Zagreus diff --git a/src/board.h b/src/board.h index 8c4b4fbf..84d584d2 100644 --- a/src/board.h +++ b/src/board.h @@ -21,7 +21,10 @@ #pragma once +#include <__utility/to_underlying.h> + #include +#include #include "bitboard.h" @@ -35,15 +38,26 @@ class Board { public: template - [[nodiscard]] uint64_t getBitboard() const; - - [[nodiscard]] Piece getPieceOnSquare(int square) const; + [[nodiscard]] uint64_t getBitboard() const { + return bitboards[std::to_underlying(piece)]; + } template - [[nodiscard]] uint64_t getColorBitboard() const; - - [[nodiscard]] uint64_t getOccupiedBitboard() const; - - [[nodiscard]] uint64_t getEmptyBitboard() const; + [[nodiscard]] uint64_t getColorBitboard() const { + return colorBoards[std::to_underlying(color)]; + } + + [[nodiscard]] Piece getPieceOnSquare(const int square) const { + assert(square >= 0 && square < 64); + return board[square]; + } + + [[nodiscard]] uint64_t getOccupiedBitboard() const { + return occupied; + } + + [[nodiscard]] uint64_t getEmptyBitboard() const { + return ~occupied; + } }; } // namespace Zagreus diff --git a/src/main.cpp b/src/main.cpp index 70ef64dd..67ec1172 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -18,11 +18,19 @@ along with Zagreus. If not, see . */ +#include + #include "uci.h" int main(int argc, char *argv[]) { - Zagreus::Engine engine; + try { + Zagreus::Engine engine; + engine.startUci(); + } catch (const std::exception& e) { + // Handle the exception or log the error + std::cerr << "An error occurred: " << e.what() << std::endl; + return 1; + } - engine.startUci(); return 0; } diff --git a/src/move.h b/src/move.h index c3c46c9e..caf5a4be 100644 --- a/src/move.h +++ b/src/move.h @@ -28,7 +28,7 @@ using Move = uint16_t; struct MoveList { std::array list{}; - int size = 0; + uint8_t size = 0; }; // bits 0-5: to square (0-63) diff --git a/src/move_gen.cpp b/src/move_gen.cpp index 20eafb76..f5476835 100644 --- a/src/move_gen.cpp +++ b/src/move_gen.cpp @@ -45,15 +45,15 @@ void generatePawnMoves(const Board& board, MoveList& moves) { uint64_t pawnEastAttacks; if constexpr (color == PieceColor::WHITE) { - pawnSinglePushes = Bitwise::whitePawnSinglePush(pawnBB, emptyBB); - pawnDoublePushes = Bitwise::whitePawnDoublePush(pawnBB, emptyBB); - pawnWestAttacks = Bitwise::whitePawnWestAttacks(pawnBB); - pawnEastAttacks = Bitwise::whitePawnEastAttacks(pawnBB); + pawnSinglePushes = whitePawnSinglePush(pawnBB, emptyBB); + pawnDoublePushes = whitePawnDoublePush(pawnBB, emptyBB); + pawnWestAttacks = whitePawnWestAttacks(pawnBB); + pawnEastAttacks = whitePawnEastAttacks(pawnBB); } else { - pawnSinglePushes = Bitwise::blackPawnSinglePush(pawnBB, emptyBB); - pawnDoublePushes = Bitwise::blackPawnDoublePush(pawnBB, emptyBB); - pawnWestAttacks = Bitwise::blackPawnWestAttacks(pawnBB); - pawnEastAttacks = Bitwise::blackPawnEastAttacks(pawnBB); + pawnSinglePushes = blackPawnSinglePush(pawnBB, emptyBB); + pawnDoublePushes = blackPawnDoublePush(pawnBB, emptyBB); + pawnWestAttacks = blackPawnWestAttacks(pawnBB); + pawnEastAttacks = blackPawnEastAttacks(pawnBB); } pawnWestAttacks &= opponentPieces; @@ -69,8 +69,8 @@ void generatePawnMoves(const Board& board, MoveList& moves) { : Direction::NORTH_EAST; while (pawnSinglePushes) { - const uint64_t squareTo = Bitwise::popLsb(pawnSinglePushes); - const uint64_t squareFrom = Bitwise::shift(squareTo); + const uint64_t squareTo = popLsb(pawnSinglePushes); + const uint64_t squareFrom = shift(squareTo); const Move move = encodeMove(squareFrom, squareTo); moves.list[moves.size] = move; @@ -78,8 +78,8 @@ void generatePawnMoves(const Board& board, MoveList& moves) { } while (pawnDoublePushes) { - const uint64_t squareTo = Bitwise::popLsb(pawnDoublePushes); - const uint64_t squareFrom = Bitwise::shift(squareTo); + const uint64_t squareTo = popLsb(pawnDoublePushes); + const uint64_t squareFrom = shift(squareTo); const Move move = encodeMove(squareFrom, squareTo); moves.list[moves.size] = move; @@ -87,8 +87,8 @@ void generatePawnMoves(const Board& board, MoveList& moves) { } while (pawnWestAttacks) { - const uint64_t squareTo = Bitwise::popLsb(pawnWestAttacks); - const uint64_t squareFrom = Bitwise::shift(squareTo); + const uint64_t squareTo = popLsb(pawnWestAttacks); + const uint64_t squareFrom = shift(squareTo); const Move move = encodeMove(squareFrom, squareTo); moves.list[moves.size] = move; @@ -96,8 +96,8 @@ void generatePawnMoves(const Board& board, MoveList& moves) { } while (pawnEastAttacks) { - const uint64_t squareTo = Bitwise::popLsb(pawnEastAttacks); - const uint64_t squareFrom = Bitwise::shift(squareTo); + const uint64_t squareTo = popLsb(pawnEastAttacks); + const uint64_t squareFrom = shift(squareTo); const Move move = encodeMove(squareFrom, squareTo); moves.list[moves.size] = move; @@ -113,11 +113,11 @@ void generateKnightMoves(const Board& board, MoveList& moves) { uint64_t knightBB = board.getBitboard(); while (knightBB) { - const uint64_t fromSquare = Bitwise::popLsb(knightBB); - uint64_t genBB = Bitwise::knightAttacks(fromSquare) & ~ownPieces; + const uint64_t fromSquare = popLsb(knightBB); + uint64_t genBB = knightAttacks(fromSquare) & ~ownPieces; while (genBB) { - const uint64_t toSquare = Bitwise::popLsb(genBB); + const uint64_t toSquare = popLsb(genBB); const Move move = encodeMove(fromSquare, toSquare); moves.list[moves.size] = move; diff --git a/src/move_gen.h b/src/move_gen.h index 1037f83b..be43abe7 100644 --- a/src/move_gen.h +++ b/src/move_gen.h @@ -26,7 +26,7 @@ #include "types.h" namespace Zagreus { -enum class GenerationType { +enum class GenerationType : uint8_t { All, Quiet, Capture, diff --git a/src/move_picker.cpp b/src/move_picker.cpp index cb06cb66..09e9d553 100644 --- a/src/move_picker.cpp +++ b/src/move_picker.cpp @@ -21,7 +21,5 @@ #include "move_picker.h" namespace Zagreus { -const MoveList& MovePicker::getMoveList() const { - return this->moveList; -} + } // namespace Zagreus diff --git a/src/move_picker.h b/src/move_picker.h index ff130e91..768f5d94 100644 --- a/src/move_picker.h +++ b/src/move_picker.h @@ -32,6 +32,8 @@ class MovePicker { MoveList moveList{}; public: - MoveList& getMoveList(); + [[nodiscard]] MoveList& getMoveList() { + return this->moveList; + } }; } // namespace Zagreus diff --git a/src/types.h b/src/types.h index bba0f5a6..e45d2b2e 100644 --- a/src/types.h +++ b/src/types.h @@ -33,11 +33,11 @@ enum class Direction { SOUTH_WEST }; -enum class PieceType : int8_t { PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, EMPTY = -1 }; +enum class PieceType : uint8_t { PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, EMPTY = 255 }; -enum class PieceColor : int8_t { WHITE, BLACK, EMPTY = -1 }; +enum class PieceColor : uint8_t { WHITE, BLACK, EMPTY = 255 }; -enum class Piece : int8_t { +enum class Piece : uint8_t { WHITE_PAWN, WHITE_KNIGHT, WHITE_BISHOP, @@ -50,5 +50,5 @@ enum class Piece : int8_t { BLACK_ROOK, BLACK_QUEEN, BLACK_KING, - EMPTY = -1 + EMPTY = 255 }; diff --git a/src/uci.cpp b/src/uci.cpp index 7d0f0d9a..48e53fe3 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -10,7 +10,7 @@ (at your option) any later version. Zagreus is distributed in the hope that it will be useful, - but WITHOUstd::string ANY WARRANTY; without even the implied warranty of + but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. @@ -23,6 +23,7 @@ #include #include #include +#include #include "magics.h" #include "uci.h" @@ -96,7 +97,7 @@ void Engine::handleDebugCommand(std::string_view args) { sendMessage("Debug mode is currently not implemented."); } -void Engine::handleIsReadyCommand(const std::string& args) { +void Engine::handleIsReadyCommand(std::string_view args) { if (!didSetup) { doSetup(); } @@ -117,7 +118,7 @@ void Engine::handleSetOptionCommand(const std::string& args) { while (iss >> word) { std::string lowercaseWord = word; - std::ranges::transform(lowercaseWord, lowercaseWord.begin(), tolower); + std::ranges::transform(lowercaseWord, lowercaseWord.begin(), [](const unsigned char c) { return std::tolower(c); }); if (lowercaseWord == "name") { section = word; @@ -174,31 +175,31 @@ void Engine::handleSetOptionCommand(const std::string& args) { option.setValue(value); } -void Engine::handleUciNewGameCommand(const std::string& args) { +void Engine::handleUciNewGameCommand(std::string_view args) { } -void Engine::handlePositionCommand(const std::string& args) { +void Engine::handlePositionCommand(std::string_view args) { } -void Engine::handleGoCommand(const std::string& args) { +void Engine::handleGoCommand(std::string_view args) { } -void Engine::handleStopCommand(const std::string& args) { +void Engine::handleStopCommand(std::string_view args) { } -void Engine::handlePonderHitCommand(const std::string& args) { +void Engine::handlePonderHitCommand(std::string_view args) { } -void Engine::handleQuitCommand(const std::string& args) { +void Engine::handleQuitCommand(std::string_view args) { } -void Engine::processCommand(const std::string& command, const std::string& args) { +void Engine::processCommand(const std::string_view command, const std::string& args) { if (command == "uci") { handleUciCommand(); } else if (command == "debug") { @@ -251,7 +252,7 @@ UCIOption& Engine::getOption(const std::string& name) { return this->options[name]; } -bool Engine::hasOption(const std::string& name) { +bool Engine::hasOption(const std::string& name) const { return this->options.contains(name); } @@ -263,8 +264,17 @@ void Engine::startUci() { while (std::getline(std::cin, line)) { line = removeRedundantSpaces(line); - std::string command = line.substr(0, line.find(' ')); - std::string args = line.substr(line.find(' ') + 1); + std::string command; + std::string args; + + if (size_t space_pos = line.find(' '); space_pos != std::string::npos) { + command = line.substr(0, space_pos); + args = line.substr(space_pos + 1); + } else { + command = line; + args = ""; + } + if (args == "\n" || args == " ") { args = ""; @@ -274,15 +284,15 @@ void Engine::startUci() { } } -void Engine::sendInfoMessage(const std::string& message) { +void Engine::sendInfoMessage(std::string_view message) { std::cout << "info " << message << std::endl; } -void Engine::sendMessage(const std::string& message) { +void Engine::sendMessage(std::string_view message) { std::cout << message << std::endl; } -std::string removeRedundantSpaces(const std::string& input) { +std::string removeRedundantSpaces(std::string_view input) { std::string result; bool inSpace = false; // Track if we are in a sequence of spaces/tabs @@ -332,7 +342,7 @@ std::string UCIOption::getValue() { return this->value; } -void UCIOption::setValue(std::string& value) { +void UCIOption::setValue(const std::string& value) { this->value = value; } @@ -340,7 +350,7 @@ std::string UCIOption::getDefaultValue() { return this->defaultValue; } -void UCIOption::setDefaultValue(std::string value) { +void UCIOption::setDefaultValue(const std::string& value) { this->defaultValue = value; } @@ -372,6 +382,8 @@ std::string getUciOptionTypeAsString(const UCIOptionType type) { return "button"; case String: return "string"; + default: + return "unknown"; } } @@ -391,8 +403,8 @@ std::string UCIOption::toString() { } if (!this->var.empty()) { - for (const std::string& value : this->var) { - result += " var " + value; + for (const auto& optionValue : this->var) { + result += " var " + optionValue; } } diff --git a/src/uci.h b/src/uci.h index 59ece902..295c9fda 100644 --- a/src/uci.h +++ b/src/uci.h @@ -34,24 +34,25 @@ class Engine { void handleUciCommand(); void handleDebugCommand(std::string_view args); - void handleIsReadyCommand(const std::string& args); + void handleIsReadyCommand(std::string_view args); void handleSetOptionCommand(const std::string& args); - void handleUciNewGameCommand(const std::string& args); - void handlePositionCommand(const std::string& args); - void handleGoCommand(const std::string& args); - void handleStopCommand(const std::string& args); - void handlePonderHitCommand(const std::string& args); - void handleQuitCommand(const std::string& args); - void processCommand(const std::string& string, const std::string& args); + void handleUciNewGameCommand(std::string_view args); + void handlePositionCommand(std::string_view args); + void handleGoCommand(std::string_view args); + void handleStopCommand(std::string_view args); + void handlePonderHitCommand(std::string_view args); + void handleQuitCommand(std::string_view args); + void processCommand(std::string_view command, const std::string& args); + public: void startUci(); - void sendInfoMessage(const std::string& message); - void sendMessage(const std::string& message); + void sendInfoMessage(std::string_view message); + void sendMessage(std::string_view message); void doSetup(); void printStartupMessage(); void addOption(UCIOption& option); UCIOption& getOption(const std::string& name); - bool hasOption(const std::string& name); + bool hasOption(const std::string& name) const; }; enum UCIOptionType { @@ -82,11 +83,11 @@ class UCIOption { std::string getValue(); - void setValue(std::string& value); + void setValue(const std::string& value); std::string getDefaultValue(); - void setDefaultValue(std::string value); + void setDefaultValue(const std::string& value); std::string getMinValue(); @@ -111,5 +112,5 @@ class UCIOption { std::vector getVar(); }; -std::string removeRedundantSpaces(const std::string& input); +std::string removeRedundantSpaces(std::string_view input); } // namespace Zagreus \ No newline at end of file From 25c544d5864d18c3b9d8ce78d49d7885344f5cdf Mon Sep 17 00:00:00 2001 From: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> Date: Sat, 5 Oct 2024 15:20:21 +0200 Subject: [PATCH 11/91] Fix to_underlying import --- src/board.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/board.h b/src/board.h index 84d584d2..1a892551 100644 --- a/src/board.h +++ b/src/board.h @@ -21,10 +21,9 @@ #pragma once -#include <__utility/to_underlying.h> - #include #include +#include #include "bitboard.h" From 3da939ab941435d582161218849fac83a6143d5e Mon Sep 17 00:00:00 2001 From: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> Date: Sat, 5 Oct 2024 16:40:54 +0200 Subject: [PATCH 12/91] Fixed bitscanReverse --- src/bitwise.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bitwise.h b/src/bitwise.h index 064ef0c4..9c84de6f 100644 --- a/src/bitwise.h +++ b/src/bitwise.h @@ -52,7 +52,7 @@ inline int bitscanReverse(const uint64_t bb) { _BitScanReverse64(&index, bb); return (int) index; #else - return 63 ^ __builtin_clzll(bb); + return 63 - __builtin_clzll(bb); #endif } From 5bc36db9f4635a7f417a8a9f21160a187cacdd56 Mon Sep 17 00:00:00 2001 From: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> Date: Sat, 5 Oct 2024 16:43:56 +0200 Subject: [PATCH 13/91] uci-specification.txt was not supposed to be pushed to git --- .gitignore | 2 + src/uci-specification.txt | 544 -------------------------------------- 2 files changed, 2 insertions(+), 544 deletions(-) delete mode 100644 src/uci-specification.txt diff --git a/.gitignore b/.gitignore index 47e4b33f..7ba904ed 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ /cmake-build-debug/ /cmake-build-release/ /perft.txt +/quiet-labeled.epd +/src/uci-specification.txt diff --git a/src/uci-specification.txt b/src/uci-specification.txt deleted file mode 100644 index df57f389..00000000 --- a/src/uci-specification.txt +++ /dev/null @@ -1,544 +0,0 @@ - - -Description of the universal chess interface (UCI) April 2006 -================================================================= - -* The specification is independent of the operating system. For Windows, - the engine is a normal exe file, either a console or "real" windows application. - -* all communication is done via standard input and output with text commands, - -* The engine should boot and wait for input from the GUI, - the engine should wait for the "isready" or "setoption" command to set up its internal parameters - as the boot process should be as quick as possible. - -* the engine must always be able to process input from stdin, even while thinking. - -* all command strings the engine receives will end with '\n', - also all commands the GUI receives should end with '\n', - Note: '\n' can be 0x0d or 0x0a0d or any combination depending on your OS. - If you use Engine and GUI in the same OS this should be no problem if you communicate in text mode, - but be aware of this when for example running a Linux engine in a Windows GUI. - -* arbitrary white space between tokens is allowed - Example: "debug on\n" and " debug on \n" and "\t debug \t \t\ton\t \n" - all set the debug mode of the engine on. - -* The engine will always be in forced mode which means it should never start calculating - or pondering without receiving a "go" command first. - -* Before the engine is asked to search on a position, there will always be a position command - to tell the engine about the current position. - -* by default all the opening book handling is done by the GUI, - but there is an option for the engine to use its own book ("OwnBook" option, see below) - -* if the engine or the GUI receives an unknown command or token it should just ignore it and try to - parse the rest of the string in this line. - Examples: "joho debug on\n" should switch the debug mode on given that joho is not defined, - "debug joho on\n" will be undefined however. - -* if the engine receives a command which is not supposed to come, for example "stop" when the engine is - not calculating, it should also just ignore it. - - -Move format: ------------- - -The move format is in long algebraic notation. -A nullmove from the Engine to the GUI should be sent as 0000. -Examples: e2e4, e7e5, e1g1 (white short castling), e7e8q (for promotion) - - - -GUI to engine: --------------- - -These are all the command the engine gets from the interface. - -* uci - tell engine to use the uci (universal chess interface), - this will be sent once as a first command after program boot - to tell the engine to switch to uci mode. - After receiving the uci command the engine must identify itself with the "id" command - and send the "option" commands to tell the GUI which engine settings the engine supports if any. - After that the engine should send "uciok" to acknowledge the uci mode. - If no uciok is sent within a certain time period, the engine task will be killed by the GUI. - -* debug [ on | off ] - switch the debug mode of the engine on and off. - In debug mode the engine should send additional infos to the GUI, e.g. with the "info string" command, - to help debugging, e.g. the commands that the engine has received etc. - This mode should be switched off by default and this command can be sent - any time, also when the engine is thinking. - -* isready - this is used to synchronize the engine with the GUI. When the GUI has sent a command or - multiple commands that can take some time to complete, - this command can be used to wait for the engine to be ready again or - to ping the engine to find out if it is still alive. - E.g. this should be sent after setting the path to the tablebases as this can take some time. - This command is also required once before the engine is asked to do any search - to wait for the engine to finish initializing. - This command must always be answered with "readyok" and can be sent also when the engine is calculating - in which case the engine should also immediately answer with "readyok" without stopping the search. - -* setoption name [value ] - this is sent to the engine when the user wants to change the internal parameters - of the engine. For the "button" type no value is needed. - One string will be sent for each parameter and this will only be sent when the engine is waiting. - The name and value of the option in should not be case sensitive and can inlude spaces. - The substrings "value" and "name" should be avoided in and to allow unambiguous parsing, - for example do not use = "draw value". - Here are some strings for the example below: - "setoption name Nullmove value true\n" - "setoption name Selectivity value 3\n" - "setoption name Style value Risky\n" - "setoption name Clear Hash\n" - "setoption name NalimovPath value c:\chess\tb\4;c:\chess\tb\5\n" - -* register - this is the command to try to register an engine or to tell the engine that registration - will be done later. This command should always be sent if the engine has sent "registration error" - at program startup. - The following tokens are allowed: - * later - the user doesn't want to register the engine now. - * name - the engine should be registered with the name - * code - the engine should be registered with the code - Example: - "register later" - "register name Stefan MK code 4359874324" - -* ucinewgame - this is sent to the engine when the next search (started with "position" and "go") will be from - a different game. This can be a new game the engine should play or a new game it should analyse but - also the next position from a testsuite with positions only. - If the GUI hasn't sent a "ucinewgame" before the first "position" command, the engine shouldn't - expect any further ucinewgame commands as the GUI is probably not supporting the ucinewgame command. - So the engine should not rely on this command even though all new GUIs should support it. - As the engine's reaction to "ucinewgame" can take some time the GUI should always send "isready" - after "ucinewgame" to wait for the engine to finish its operation. - -* position [fen | startpos ] moves .... - set up the position described in fenstring on the internal board and - play the moves on the internal chess board. - if the game was played from the start position the string "startpos" will be sent - Note: no "new" command is needed. However, if this position is from a different game than - the last position sent to the engine, the GUI should have sent a "ucinewgame" inbetween. - -* go - start calculating on the current position set up with the "position" command. - There are a number of commands that can follow this command, all will be sent in the same string. - If one command is not sent its value should be interpreted as it would not influence the search. - * searchmoves .... - restrict search to this moves only - Example: After "position startpos" and "go infinite searchmoves e2e4 d2d4" - the engine should only search the two moves e2e4 and d2d4 in the initial position. - * ponder - start searching in pondering mode. - Do not exit the search in ponder mode, even if it's mate! - This means that the last move sent in in the position string is the ponder move. - The engine can do what it wants to do, but after a "ponderhit" command - it should execute the suggested move to ponder on. This means that the ponder move sent by - the GUI can be interpreted as a recommendation about which move to ponder. However, if the - engine decides to ponder on a different move, it should not display any mainlines as they are - likely to be misinterpreted by the GUI because the GUI expects the engine to ponder - on the suggested move. - * wtime - white has x msec left on the clock - * btime - black has x msec left on the clock - * winc - white increment per move in mseconds if x > 0 - * binc - black increment per move in mseconds if x > 0 - * movestogo - there are x moves to the next time control, - this will only be sent if x > 0, - if you don't get this and get the wtime and btime it's sudden death - * depth - search x plies only. - * nodes - search x nodes only, - * mate - search for a mate in x moves - * movetime - search exactly x mseconds - * infinite - search until the "stop" command. Do not exit the search without being told so in this mode! - -* stop - stop calculating as soon as possible, - don't forget the "bestmove" and possibly the "ponder" token when finishing the search - -* ponderhit - the user has played the expected move. This will be sent if the engine was told to ponder on the same move - the user has played. The engine should continue searching but switch from pondering to normal search. - -* quit - quit the program as soon as possible - - -Engine to GUI: --------------- - -* id - * name - this must be sent after receiving the "uci" command to identify the engine, - e.g. "id name Shredder X.Y\n" - * author - this must be sent after receiving the "uci" command to identify the engine, - e.g. "id author Stefan MK\n" - -* uciok - Must be sent after the id and optional options to tell the GUI that the engine - has sent all infos and is ready in uci mode. - -* readyok - This must be sent when the engine has received an "isready" command and has - processed all input and is ready to accept new commands now. - It is usually sent after a command that can take some time to be able to wait for the engine, - but it can be used anytime, even when the engine is searching, - and must always be answered with "isready". - -* bestmove [ ponder ] - the engine has stopped searching and found the move best in this position. - the engine can send the move it likes to ponder on. The engine must not start pondering automatically. - this command must always be sent if the engine stops searching, also in pondering mode if there is a - "stop" command, so for every "go" command a "bestmove" command is needed! - Directly before that the engine should send a final "info" command with the final search information, - the the GUI has the complete statistics about the last search. - -* copyprotection - this is needed for copyprotected engines. After the uciok command the engine can tell the GUI, - that it will check the copy protection now. This is done by "copyprotection checking". - If the check is ok the engine should send "copyprotection ok", otherwise "copyprotection error". - If there is an error the engine should not function properly but should not quit alone. - If the engine reports "copyprotection error" the GUI should not use this engine - and display an error message instead! - The code in the engine can look like this - TellGUI("copyprotection checking\n"); - // ... check the copy protection here ... - if(ok) - TellGUI("copyprotection ok\n"); - else - TellGUI("copyprotection error\n"); - -* registration - this is needed for engines that need a username and/or a code to function with all features. - Analog to the "copyprotection" command the engine can send "registration checking" - after the uciok command followed by either "registration ok" or "registration error". - Also after every attempt to register the engine it should answer with "registration checking" - and then either "registration ok" or "registration error". - In contrast to the "copyprotection" command, the GUI can use the engine after the engine has - reported an error, but should inform the user that the engine is not properly registered - and might not use all its features. - In addition the GUI should offer to open a dialog to - enable registration of the engine. To try to register an engine the GUI can send - the "register" command. - The GUI has to always answer with the "register" command if the engine sends "registration error" - at engine startup (this can also be done with "register later") - and tell the user somehow that the engine is not registered. - This way the engine knows that the GUI can deal with the registration procedure and the user - will be informed that the engine is not properly registered. - -* info - the engine wants to send information to the GUI. This should be done whenever one of the info has changed. - The engine can send only selected infos or multiple infos with one info command, - e.g. "info currmove e2e4 currmovenumber 1" or - "info depth 12 nodes 123456 nps 100000". - Also all infos belonging to the pv should be sent together - e.g. "info depth 2 score cp 214 time 1242 nodes 2124 nps 34928 pv e2e4 e7e5 g1f3" - I suggest to start sending "currmove", "currmovenumber", "currline" and "refutation" only after one second - to avoid too much traffic. - Additional info: - * depth - search depth in plies - * seldepth - selective search depth in plies, - if the engine sends seldepth there must also be a "depth" present in the same string. - * time - the time searched in ms, this should be sent together with the pv. - * nodes - x nodes searched, the engine should send this info regularly - * pv ... - the best line found - * multipv - this for the multi pv mode. - for the best move/pv add "multipv 1" in the string when you send the pv. - in k-best mode always send all k variants in k strings together. - * score - * cp - the score from the engine's point of view in centipawns. - * mate - mate in y moves, not plies. - If the engine is getting mated use negative values for y. - * lowerbound - the score is just a lower bound. - * upperbound - the score is just an upper bound. - * currmove - currently searching this move - * currmovenumber - currently searching move number x, for the first move x should be 1 not 0. - * hashfull - the hash is x permill full, the engine should send this info regularly - * nps - x nodes per second searched, the engine should send this info regularly - * tbhits - x positions where found in the endgame table bases - * sbhits - x positions where found in the shredder endgame databases - * cpuload - the cpu usage of the engine is x permill. - * string - any string str which will be displayed be the engine, - if there is a string command the rest of the line will be interpreted as . - * refutation ... - move is refuted by the line ... , i can be any number >= 1. - Example: after move d1h5 is searched, the engine can send - "info refutation d1h5 g6h5" - if g6h5 is the best answer after d1h5 or if g6h5 refutes the move d1h5. - if there is no refutation for d1h5 found, the engine should just send - "info refutation d1h5" - The engine should only send this if the option "UCI_ShowRefutations" is set to true. - * currline ... - this is the current line the engine is calculating. is the number of the cpu if - the engine is running on more than one cpu. = 1,2,3.... - if the engine is just using one cpu, can be omitted. - If is greater than 1, always send all k lines in k strings together. - The engine should only send this if the option "UCI_ShowCurrLine" is set to true. - - -* option - This command tells the GUI which parameters can be changed in the engine. - This should be sent once at engine startup after the "uci" and the "id" commands - if any parameter can be changed in the engine. - The GUI should parse this and build a dialog for the user to change the settings. - Note that not every option needs to appear in this dialog as some options like - "Ponder", "UCI_AnalyseMode", etc. are better handled elsewhere or are set automatically. - If the user wants to change some settings, the GUI will send a "setoption" command to the engine. - Note that the GUI need not send the setoption command when starting the engine for every option if - it doesn't want to change the default value. - For all allowed combinations see the examples below, - as some combinations of this tokens don't make sense. - One string will be sent for each parameter. - * name - The option has the name id. - Certain options have a fixed value for , which means that the semantics of this option is fixed. - Usually those options should not be displayed in the normal engine options window of the GUI but - get a special treatment. "Pondering" for example should be set automatically when pondering is - enabled or disabled in the GUI options. The same for "UCI_AnalyseMode" which should also be set - automatically by the GUI. All those certain options have the prefix "UCI_" except for the - first 6 options below. If the GUI gets an unknown Option with the prefix "UCI_", it should just - ignore it and not display it in the engine's options dialog. - * = Hash, type is spin - the value in MB for memory for hash tables can be changed, - this should be answered with the first "setoptions" command at program boot - if the engine has sent the appropriate "option name Hash" command, - which should be supported by all engines! - So the engine should use a very small hash first as default. - * = NalimovPath, type string - this is the path on the hard disk to the Nalimov compressed format. - Multiple directories can be concatenated with ";" - * = NalimovCache, type spin - this is the size in MB for the cache for the nalimov table bases - These last two options should also be present in the initial options exchange dialog - when the engine is booted if the engine supports it - * = Ponder, type check - this means that the engine is able to ponder. - The GUI will send this whenever pondering is possible or not. - Note: The engine should not start pondering on its own if this is enabled, this option is only - needed because the engine might change its time management algorithm when pondering is allowed. - * = OwnBook, type check - this means that the engine has its own book which is accessed by the engine itself. - if this is set, the engine takes care of the opening book and the GUI will never - execute a move out of its book for the engine. If this is set to false by the GUI, - the engine should not access its own book. - * = MultiPV, type spin - the engine supports multi best line or k-best mode. the default value is 1 - * = UCI_ShowCurrLine, type check, should be false by default, - the engine can show the current line it is calculating. see "info currline" above. - * = UCI_ShowRefutations, type check, should be false by default, - the engine can show a move and its refutation in a line. see "info refutations" above. - * = UCI_LimitStrength, type check, should be false by default, - The engine is able to limit its strength to a specific Elo number, - This should always be implemented together with "UCI_Elo". - * = UCI_Elo, type spin - The engine can limit its strength in Elo within this interval. - If UCI_LimitStrength is set to false, this value should be ignored. - If UCI_LimitStrength is set to true, the engine should play with this specific strength. - This should always be implemented together with "UCI_LimitStrength". - * = UCI_AnalyseMode, type check - The engine wants to behave differently when analysing or playing a game. - For example when playing it can use some kind of learning. - This is set to false if the engine is playing a game, otherwise it is true. - * = UCI_Opponent, type string - With this command the GUI can send the name, title, elo and if the engine is playing a human - or computer to the engine. - The format of the string has to be [GM|IM|FM|WGM|WIM|none] [|none] [computer|human] - Examples: - "setoption name UCI_Opponent value GM 2800 human Gary Kasparov" - "setoption name UCI_Opponent value none none computer Shredder" - * = UCI_EngineAbout, type string - With this command, the engine tells the GUI information about itself, for example a license text, - usually it doesn't make sense that the GUI changes this text with the setoption command. - Example: - "option name UCI_EngineAbout type string default Shredder by Stefan Meyer-Kahlen, see www.shredderchess.com" - * = UCI_ShredderbasesPath, type string - this is either the path to the folder on the hard disk containing the Shredder endgame databases or - the path and filename of one Shredder endgame datbase. - * = UCI_SetPositionValue, type string - the GUI can send this to the engine to tell the engine to use a certain value in centipawns from white's - point of view if evaluating this specifix position. - The string can have the formats: - + | clear + | clearall - - * type - The option has type t. - There are 5 different types of options the engine can send - * check - a checkbox that can either be true or false - * spin - a spin wheel that can be an integer in a certain range - * combo - a combo box that can have different predefined strings as a value - * button - a button that can be pressed to send a command to the engine - * string - a text field that has a string as a value, - an empty string has the value "" - * default - the default value of this parameter is x - * min - the minimum value of this parameter is x - * max - the maximum value of this parameter is x - * var - a predefined value of this parameter is x - Examples: - Here are 5 strings for each of the 5 possible types of options - "option name Nullmove type check default true\n" - "option name Selectivity type spin default 2 min 0 max 4\n" - "option name Style type combo default Normal var Solid var Normal var Risky\n" - "option name NalimovPath type string default c:\\n" - "option name Clear Hash type button\n" - - - -Examples: ---------- - -This is how the communication when the engine boots can look like: - -GUI engine - -// tell the engine to switch to UCI mode -uci - -// engine identify - id name Shredder - id author Stefan MK - -// engine sends the options it can change -// the engine can change the hash size from 1 to 128 MB - option name Hash type spin default 1 min 1 max 128 - -// the engine supports Nalimov endgame tablebases - option name NalimovPath type string default - option name NalimovCache type spin default 1 min 1 max 32 - -// the engine can switch off Nullmove and set the playing style - option name Nullmove type check default true - option name Style type combo default Normal var Solid var Normal var Risky - -// the engine has sent all parameters and is ready - uciok - -// Note: here the GUI can already send a "quit" command if it just wants to find out -// details about the engine, so the engine should not initialize its internal -// parameters before here. -// now the GUI sets some values in the engine -// set hash to 32 MB -setoption name Hash value 32 - -// init tbs -setoption name NalimovCache value 1 -setoption name NalimovPath value d:\tb;c\tb - -// waiting for the engine to finish initializing -// this command and the answer is required here! -isready - -// engine has finished setting up the internal values - readyok - -// now we are ready to go - -// if the GUI is supporting it, tell the engine that is is -// searching on a game that it hasn't searched on before -ucinewgame - -// if the engine supports the "UCI_AnalyseMode" option and the next search is supposed to -// be an analysis, the GUI should set "UCI_AnalyseMode" to true if it is currently -// set to false with this engine -setoption name UCI_AnalyseMode value true - -// tell the engine to search infinite from the start position after 1.e4 e5 -position startpos moves e2e4 e7e5 -go infinite - -// the engine starts sending infos about the search to the GUI -// (only some examples are given) - - - info depth 1 seldepth 0 - info score cp 13 depth 1 nodes 13 time 15 pv f1b5 - info depth 2 seldepth 2 - info nps 15937 - info score cp 14 depth 2 nodes 255 time 15 pv f1c4 f8c5 - info depth 2 seldepth 7 nodes 255 - info depth 3 seldepth 7 - info nps 26437 - info score cp 20 depth 3 nodes 423 time 15 pv f1c4 g8f6 b1c3 - info nps 41562 - .... - - -// here the user has seen enough and asks to stop the searching -stop - -// the engine has finished searching and is sending the bestmove command -// which is needed for every "go" command sent to tell the GUI -// that the engine is ready again - bestmove g1f3 ponder d8f6 - - - -Chess960 -======== - -UCI could easily be extended to support Chess960 (also known as Fischer Random Chess). - -The engine has to tell the GUI that it is capable of playing Chess960 and the GUI has to tell -the engine that is should play according to the Chess960 rules. -This is done by the special engine option UCI_Chess960. If the engine knows about Chess960 -it should send the command 'option name UCI_Chess960 type check default false' -to the GUI at program startup. -Whenever a Chess960 game is played, the GUI should set this engine option to 'true'. - -Castling is different in Chess960 and the white king move when castling short is not always e1g1. -A king move could both be the castling king move or just a normal king move. -This is why castling moves are sent in the form king "takes" his own rook. -Example: e1h1 for the white short castle move in the normal chess start position. - -In EPD and FEN position strings specifying the castle rights with w and q is not enough as -there could be more than one rook on the right or left side of the king. -This is why the castle rights are specified with the letter of the castle rook's line. -Upper case letters for white's and lower case letters for black's castling rights. -Example: The normal chess position would be: -rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w AHah - - From 89e2e4fb1c3e5d4e85d84f72089c93d163c39cf5 Mon Sep 17 00:00:00 2001 From: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> Date: Sat, 5 Oct 2024 17:36:41 +0200 Subject: [PATCH 14/91] Implement bishop move generation, fix moves being generated that would capture the opponent king --- src/bitboard.cpp | 32 ++++++++++++++++++++++++++ src/bitboard.h | 3 +++ src/move_gen.cpp | 59 +++++++++++++++++++++++++++++++++++++----------- src/move_gen.h | 3 +++ 4 files changed, 84 insertions(+), 13 deletions(-) create mode 100644 src/bitboard.cpp diff --git a/src/bitboard.cpp b/src/bitboard.cpp new file mode 100644 index 00000000..1c0e71bb --- /dev/null +++ b/src/bitboard.cpp @@ -0,0 +1,32 @@ +/* + This file is part of Zagreus. + + Zagreus is a UCI chess engine + Copyright (C) 2023-2024 Danny Jelsma + + Zagreus is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Zagreus 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Zagreus. If not, see . + */ + +#include "bitboard.h" + +namespace Zagreus { +uint64_t bishopAttacks(const uint8_t square, const uint64_t occupied) { + uint64_t occupancy = occupied; + occupancy &= getBishopMask(square); + occupancy *= getBishopMagic(square); + occupancy >>= 64 - BBits[square]; + + return getBishopMagicAttacks(square, occupancy); +} +} // namespace Zagreus diff --git a/src/bitboard.h b/src/bitboard.h index 3dae8c6c..14b03da2 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -24,6 +24,7 @@ #include #include "constants.h" +#include "magics.h" #include "types.h" namespace Zagreus { @@ -185,4 +186,6 @@ inline uint64_t knightAttacks(const uint64_t bb) { shiftSouthSouthEast(bb) | shiftSouthSouthWest(bb) | shiftSouthWestWest(bb) | shiftNorthWestWest(bb) |shiftNorthNorthWest(bb); } + +uint64_t bishopAttacks(uint8_t square, uint64_t occupied); } // namespace Zagreus diff --git a/src/move_gen.cpp b/src/move_gen.cpp index f5476835..51935bd4 100644 --- a/src/move_gen.cpp +++ b/src/move_gen.cpp @@ -35,9 +35,13 @@ void generatePawnMoves(const Board& board, MoveList& moves) { constexpr PieceColor opponentColor = color == PieceColor::WHITE ? PieceColor::BLACK : PieceColor::WHITE; + constexpr Piece opponentKing = opponentColor == PieceColor::WHITE + ? Piece::WHITE_KING + : Piece::BLACK_KING; const uint64_t pawnBB = board.getBitboard(); const uint64_t emptyBB = board.getEmptyBitboard(); + const uint64_t opponentKingBB = board.getBitboard(); const uint64_t opponentPieces = board.getColorBitboard(); uint64_t pawnSinglePushes; uint64_t pawnDoublePushes; @@ -56,8 +60,9 @@ void generatePawnMoves(const Board& board, MoveList& moves) { pawnEastAttacks = blackPawnEastAttacks(pawnBB); } - pawnWestAttacks &= opponentPieces; - pawnEastAttacks &= opponentPieces; + pawnWestAttacks &= (opponentPieces & ~opponentKingBB); + pawnEastAttacks &= (opponentPieces & ~opponentKingBB); + constexpr Direction fromPushDirection = color == PieceColor::WHITE ? Direction::SOUTH : Direction::NORTH; @@ -69,8 +74,8 @@ void generatePawnMoves(const Board& board, MoveList& moves) { : Direction::NORTH_EAST; while (pawnSinglePushes) { - const uint64_t squareTo = popLsb(pawnSinglePushes); - const uint64_t squareFrom = shift(squareTo); + const uint8_t squareTo = popLsb(pawnSinglePushes); + const uint8_t squareFrom = shift(squareTo); const Move move = encodeMove(squareFrom, squareTo); moves.list[moves.size] = move; @@ -78,8 +83,8 @@ void generatePawnMoves(const Board& board, MoveList& moves) { } while (pawnDoublePushes) { - const uint64_t squareTo = popLsb(pawnDoublePushes); - const uint64_t squareFrom = shift(squareTo); + const uint8_t squareTo = popLsb(pawnDoublePushes); + const uint8_t squareFrom = shift(squareTo); const Move move = encodeMove(squareFrom, squareTo); moves.list[moves.size] = move; @@ -87,8 +92,8 @@ void generatePawnMoves(const Board& board, MoveList& moves) { } while (pawnWestAttacks) { - const uint64_t squareTo = popLsb(pawnWestAttacks); - const uint64_t squareFrom = shift(squareTo); + const uint8_t squareTo = popLsb(pawnWestAttacks); + const uint8_t squareFrom = shift(squareTo); const Move move = encodeMove(squareFrom, squareTo); moves.list[moves.size] = move; @@ -96,8 +101,8 @@ void generatePawnMoves(const Board& board, MoveList& moves) { } while (pawnEastAttacks) { - const uint64_t squareTo = popLsb(pawnEastAttacks); - const uint64_t squareFrom = shift(squareTo); + const uint8_t squareTo = popLsb(pawnEastAttacks); + const uint8_t squareFrom = shift(squareTo); const Move move = encodeMove(squareFrom, squareTo); moves.list[moves.size] = move; @@ -109,15 +114,43 @@ template void generateKnightMoves(const Board& board, MoveList& moves) { // TODO: Move to table calculations constexpr Piece knight = color == PieceColor::WHITE ? Piece::WHITE_KNIGHT : Piece::BLACK_KNIGHT; + constexpr Piece opponentKing = color == PieceColor::WHITE ? Piece::WHITE_KING : Piece::BLACK_KING; const uint64_t ownPieces = board.getColorBitboard(); + const uint64_t opponentKingBB = board.getBitboard(); uint64_t knightBB = board.getBitboard(); while (knightBB) { - const uint64_t fromSquare = popLsb(knightBB); - uint64_t genBB = knightAttacks(fromSquare) & ~ownPieces; + const uint8_t fromSquare = popLsb(knightBB); + uint64_t genBB = knightAttacks(fromSquare) & ~ownPieces & ~opponentKingBB; + + while (genBB) { + const uint8_t toSquare = popLsb(genBB); + const Move move = encodeMove(fromSquare, toSquare); + + moves.list[moves.size] = move; + moves.size++; + } + } +} + +template +void generateBishopMoves(const Board& board, MoveList& moves) { + using enum Piece; + constexpr Piece bishop = color == PieceColor::WHITE ? WHITE_BISHOP : BLACK_BISHOP; + constexpr Piece opponentKing = color == PieceColor::WHITE + ? WHITE_KING + : BLACK_KING; + const uint64_t ownPieces = board.getColorBitboard(); + const uint64_t opponentKingBB = board.getBitboard(); + uint64_t bishopBB = board.getBitboard(); + uint64_t occupied = board.getOccupiedBitboard(); + + while (bishopBB) { + const uint8_t fromSquare = popLsb(bishopBB); + uint64_t genBB = bishopAttacks(fromSquare, occupied) & ~ownPieces & ~opponentKingBB; while (genBB) { - const uint64_t toSquare = popLsb(genBB); + const uint8_t toSquare = popLsb(genBB); const Move move = encodeMove(fromSquare, toSquare); moves.list[moves.size] = move; diff --git a/src/move_gen.h b/src/move_gen.h index be43abe7..7deffa93 100644 --- a/src/move_gen.h +++ b/src/move_gen.h @@ -38,4 +38,7 @@ void generatePawnMoves(const Board& board, MoveList& moves); template void generateKnightMoves(const Board& board, MoveList& moves); + +template +void generateBishopMoves(const Board& board, MoveList& moves); } // namespace Zagreus From c8e48342a8f4c39449b08f81e17403f1eff19d38 Mon Sep 17 00:00:00 2001 From: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> Date: Sun, 6 Oct 2024 11:59:05 +0200 Subject: [PATCH 15/91] Implement basic move generation for the remaining pieces. --- .clang-format | 2 +- src/bitboard.cpp | 27 +++++++--- src/bitboard.h | 11 ++++ src/board.cpp | 4 +- src/board.h | 3 ++ src/move_gen.cpp | 133 +++++++++++++++++++++++++++++++++++++---------- src/move_gen.h | 18 +++++-- 7 files changed, 157 insertions(+), 41 deletions(-) diff --git a/.clang-format b/.clang-format index a998f2ec..b2faad3b 100644 --- a/.clang-format +++ b/.clang-format @@ -1,5 +1,5 @@ --- Language: Cpp BasedOnStyle: Google -ColumnLimit: 100 +ColumnLimit: 120 IndentWidth: 4 diff --git a/src/bitboard.cpp b/src/bitboard.cpp index 1c0e71bb..73ed6a07 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -1,5 +1,5 @@ /* - This file is part of Zagreus. +This file is part of Zagreus. Zagreus is a UCI chess engine Copyright (C) 2023-2024 Danny Jelsma @@ -21,12 +21,23 @@ #include "bitboard.h" namespace Zagreus { -uint64_t bishopAttacks(const uint8_t square, const uint64_t occupied) { - uint64_t occupancy = occupied; - occupancy &= getBishopMask(square); - occupancy *= getBishopMagic(square); - occupancy >>= 64 - BBits[square]; +uint64_t bishopAttacks(const uint8_t square, uint64_t occupied) { + occupied &= getBishopMask(square); + occupied *= getBishopMagic(square); + occupied >>= 64 - BBits[square]; - return getBishopMagicAttacks(square, occupancy); + return getBishopMagicAttacks(square, occupied); } -} // namespace Zagreus + +uint64_t rookAttacks(const uint8_t square, uint64_t occupied) { + occupied &= getRookMask(square); + occupied *= getRookMagic(square); + occupied >>= 64 - RBits[square]; + + return getRookMagicAttacks(square, occupied); +} + +uint64_t queenAttacks(const uint8_t square, uint64_t occupied) { + return bishopAttacks(square, occupied) | rookAttacks(square, occupied); +} +} // namespace Zagreus \ No newline at end of file diff --git a/src/bitboard.h b/src/bitboard.h index 14b03da2..cd81e51b 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -188,4 +188,15 @@ inline uint64_t knightAttacks(const uint64_t bb) { } uint64_t bishopAttacks(uint8_t square, uint64_t occupied); + +uint64_t rookAttacks(uint8_t square, uint64_t occupied); + +uint64_t queenAttacks(uint8_t square, uint64_t occupied); + +inline uint64_t kingAttacks(uint64_t bb) { + const uint64_t attacks = shiftEast(bb) | shiftWest(bb); + bb |= attacks; + + return attacks | shiftNorth(bb) | shiftSouth(bb); +} } // namespace Zagreus diff --git a/src/board.cpp b/src/board.cpp index aefb6e5e..ad788331 100644 --- a/src/board.cpp +++ b/src/board.cpp @@ -23,5 +23,7 @@ #include namespace Zagreus { - +bool Board::isMoveLegal(Move move) { + // TODO: Implement +} } // namespace Zagreus diff --git a/src/board.h b/src/board.h index 1a892551..0f6ff4db 100644 --- a/src/board.h +++ b/src/board.h @@ -26,6 +26,7 @@ #include #include "bitboard.h" +#include "move.h" namespace Zagreus { class Board { @@ -58,5 +59,7 @@ class Board { [[nodiscard]] uint64_t getEmptyBitboard() const { return ~occupied; } + + bool isMoveLegal(Move move); }; } // namespace Zagreus diff --git a/src/move_gen.cpp b/src/move_gen.cpp index 51935bd4..6b617187 100644 --- a/src/move_gen.cpp +++ b/src/move_gen.cpp @@ -26,22 +26,33 @@ namespace Zagreus { template -void generatePawnMoves(const Board& board, MoveList& moves) { +void generateMoves(const Board& board, MoveList& moves) { + // TODO: Implement GenerationType logic using a mask that is computed based on type + constexpr Piece opponentKing = color == PieceColor::WHITE ? Piece::BLACK_KING : Piece::WHITE_KING; + + uint64_t ownPieces = board.getColorBitboard(); + uint64_t opponentKingBB = board.getBitboard(); + uint64_t genMask = std::numeric_limits::max(); + genMask &= ~ownPieces & ~opponentKingBB; + + generatePawnMoves(board, moves, genMask); + generateKnightMoves(board, moves, genMask); + generateBishopMoves(board, moves, genMask); + generateRookMoves(board, moves, genMask); + generateQueenMoves(board, moves, genMask); + generateKingMoves(board, moves, genMask); +} + +template +void generatePawnMoves(const Board& board, MoveList& moves, const uint64_t genMask) { // TODO: Implement en passant // TODO: Handle promotions // TODO: Move attacks to table lookup - // TODO: Implement GenerationType logic using a mask that is computed based on type constexpr Piece pawn = color == PieceColor::WHITE ? Piece::WHITE_PAWN : Piece::BLACK_PAWN; - constexpr PieceColor opponentColor = color == PieceColor::WHITE - ? PieceColor::BLACK - : PieceColor::WHITE; - constexpr Piece opponentKing = opponentColor == PieceColor::WHITE - ? Piece::WHITE_KING - : Piece::BLACK_KING; + constexpr PieceColor opponentColor = color == PieceColor::WHITE ? PieceColor::BLACK : PieceColor::WHITE; const uint64_t pawnBB = board.getBitboard(); const uint64_t emptyBB = board.getEmptyBitboard(); - const uint64_t opponentKingBB = board.getBitboard(); const uint64_t opponentPieces = board.getColorBitboard(); uint64_t pawnSinglePushes; uint64_t pawnDoublePushes; @@ -60,12 +71,12 @@ void generatePawnMoves(const Board& board, MoveList& moves) { pawnEastAttacks = blackPawnEastAttacks(pawnBB); } - pawnWestAttacks &= (opponentPieces & ~opponentKingBB); - pawnEastAttacks &= (opponentPieces & ~opponentKingBB); + pawnSinglePushes &= genMask; + pawnDoublePushes &= genMask; + pawnWestAttacks &= opponentPieces & genMask; + pawnEastAttacks &= opponentPieces & genMask; - constexpr Direction fromPushDirection = color == PieceColor::WHITE - ? Direction::SOUTH - : Direction::NORTH; + constexpr Direction fromPushDirection = color == PieceColor::WHITE ? Direction::SOUTH : Direction::NORTH; constexpr Direction fromSqWestAttackDirection = color == PieceColor::WHITE ? Direction::SOUTH_WEST : Direction::NORTH_WEST; @@ -111,17 +122,14 @@ void generatePawnMoves(const Board& board, MoveList& moves) { } template -void generateKnightMoves(const Board& board, MoveList& moves) { - // TODO: Move to table calculations +void generateKnightMoves(const Board& board, MoveList& moves, const uint64_t genMask) { + // TODO: Implement table lookup constexpr Piece knight = color == PieceColor::WHITE ? Piece::WHITE_KNIGHT : Piece::BLACK_KNIGHT; - constexpr Piece opponentKing = color == PieceColor::WHITE ? Piece::WHITE_KING : Piece::BLACK_KING; - const uint64_t ownPieces = board.getColorBitboard(); - const uint64_t opponentKingBB = board.getBitboard(); uint64_t knightBB = board.getBitboard(); while (knightBB) { const uint8_t fromSquare = popLsb(knightBB); - uint64_t genBB = knightAttacks(fromSquare) & ~ownPieces & ~opponentKingBB; + uint64_t genBB = knightAttacks(fromSquare) & genMask; while (genBB) { const uint8_t toSquare = popLsb(genBB); @@ -134,20 +142,79 @@ void generateKnightMoves(const Board& board, MoveList& moves) { } template -void generateBishopMoves(const Board& board, MoveList& moves) { +void generateBishopMoves(const Board& board, MoveList& moves, const uint64_t genMask) { using enum Piece; constexpr Piece bishop = color == PieceColor::WHITE ? WHITE_BISHOP : BLACK_BISHOP; - constexpr Piece opponentKing = color == PieceColor::WHITE - ? WHITE_KING - : BLACK_KING; - const uint64_t ownPieces = board.getColorBitboard(); - const uint64_t opponentKingBB = board.getBitboard(); + const uint64_t occupied = board.getOccupiedBitboard(); uint64_t bishopBB = board.getBitboard(); - uint64_t occupied = board.getOccupiedBitboard(); while (bishopBB) { const uint8_t fromSquare = popLsb(bishopBB); - uint64_t genBB = bishopAttacks(fromSquare, occupied) & ~ownPieces & ~opponentKingBB; + uint64_t genBB = bishopAttacks(fromSquare, occupied) & genMask; + + while (genBB) { + const uint8_t toSquare = popLsb(genBB); + const Move move = encodeMove(fromSquare, toSquare); + + moves.list[moves.size] = move; + moves.size++; + } + } +} + +template +void generateRookMoves(const Board& board, MoveList& moves, const uint64_t genMask) { + using enum Piece; + constexpr Piece rook = color == PieceColor::WHITE ? WHITE_ROOK : BLACK_ROOK; + const uint64_t occupied = board.getOccupiedBitboard(); + uint64_t rookBB = board.getBitboard(); + + while (rookBB) { + const uint8_t fromSquare = popLsb(rookBB); + uint64_t genBB = rookAttacks(fromSquare, occupied) & genMask; + + while (genBB) { + const uint8_t toSquare = popLsb(genBB); + const Move move = encodeMove(fromSquare, toSquare); + + moves.list[moves.size] = move; + moves.size++; + } + } +} + +template +void generateQueenMoves(const Board& board, MoveList& moves, const uint64_t genMask) { + using enum Piece; + constexpr Piece queen = color == PieceColor::WHITE ? WHITE_QUEEN : BLACK_QUEEN; + const uint64_t occupied = board.getOccupiedBitboard(); + uint64_t queenBB = board.getBitboard(); + + while (queenBB) { + const uint8_t fromSquare = popLsb(queenBB); + uint64_t genBB = queenAttacks(fromSquare, occupied) & genMask; + + while (genBB) { + const uint8_t toSquare = popLsb(genBB); + const Move move = encodeMove(fromSquare, toSquare); + + moves.list[moves.size] = move; + moves.size++; + } + } +} + +template +void generateKingMoves(const Board& board, MoveList& moves, const uint64_t genMask) { + // TODO: Implement table lookup + // TODO: Implement castling + using enum Piece; + constexpr Piece king = color == PieceColor::WHITE ? WHITE_KING : BLACK_KING; + uint64_t kingBB = board.getBitboard(); + + while (kingBB) { + const uint8_t fromSquare = popLsb(kingBB); + uint64_t genBB = kingAttacks(fromSquare) & genMask; while (genBB) { const uint8_t toSquare = popLsb(genBB); @@ -158,4 +225,14 @@ void generateBishopMoves(const Board& board, MoveList& moves) { } } } + +// explicit instantiation of generateMoves +template void generateMoves(const Board& board, MoveList& moves); +template void generateMoves(const Board& board, MoveList& moves); +template void generateMoves(const Board& board, MoveList& moves); +template void generateMoves(const Board& board, MoveList& moves); +template void generateMoves(const Board& board, MoveList& moves); +template void generateMoves(const Board& board, MoveList& moves); +template void generateMoves(const Board& board, MoveList& moves); +template void generateMoves(const Board& board, MoveList& moves); } // namespace Zagreus diff --git a/src/move_gen.h b/src/move_gen.h index 7deffa93..2a5182b3 100644 --- a/src/move_gen.h +++ b/src/move_gen.h @@ -34,11 +34,23 @@ enum class GenerationType : uint8_t { }; template -void generatePawnMoves(const Board& board, MoveList& moves); +void generateMoves(const Board& board, MoveList& moves); template -void generateKnightMoves(const Board& board, MoveList& moves); +void generatePawnMoves(const Board& board, MoveList& moves, uint64_t genMask); template -void generateBishopMoves(const Board& board, MoveList& moves); +void generateKnightMoves(const Board& board, MoveList& moves, uint64_t genMask); + +template +void generateBishopMoves(const Board& board, MoveList& moves, uint64_t genMask); + +template +void generateRookMoves(const Board& board, MoveList& moves, uint64_t genMask); + +template +void generateQueenMoves(const Board& board, MoveList& moves, uint64_t genMask); + +template +void generateKingMoves(const Board& board, MoveList& moves, uint64_t genMask); } // namespace Zagreus From 06bda657866c4ca17927a28f19879c012939e939 Mon Sep 17 00:00:00 2001 From: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> Date: Sun, 6 Oct 2024 15:23:11 +0200 Subject: [PATCH 16/91] Fix imports, integrate include what you use with CMake when using the debug profile. --- .github/ISSUE_TEMPLATE/bug_report.md | 10 ++++++---- .github/workflows/release.yml | 2 +- CMakeLists.txt | 15 +++++++++++++++ README.md | 19 +++++++++++++------ src/bitboard.cpp | 2 ++ src/board.cpp | 2 -- src/board.h | 4 ++++ src/magics.cpp | 2 ++ src/main.cpp | 1 + src/move_gen.cpp | 6 +++++- src/move_gen.h | 7 +++++++ src/uci.cpp | 1 + src/uci.h | 2 ++ 13 files changed, 59 insertions(+), 14 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 88240f5b..6e67c147 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -11,15 +11,17 @@ assignees: Dannyj1 A clear and concise description of what the bug is. **FEN or PGN File (if applicable)** -If you have a specific position in which the bug consistently occurs, you can place the FEN string of said position here. If not, please provide the PGN file of a game where the bug happened. +If you have a specific position in which the bug consistently occurs, you can place the FEN string of said position +here. If not, please provide the PGN file of a game where the bug happened. **Game Settings (if applicable)** Things like time control, opening book, chess variant, opponent engine, etc. **Desktop (please complete the following information):** - - OS: [e.g. Windows, Linux] - - Version: [e.g. 10, 11, Debian Bookworm] - - Chess UI (if applicable): [e.g. Arena, CuteChess] + +- OS: [e.g. Windows, Linux] +- Version: [e.g. 10, 11, Debian Bookworm] +- Chess UI (if applicable): [e.g. Arena, CuteChess] **Additional context/comments** Add any other context about the problem or additional comments here. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 58789eeb..ba9af60b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -97,7 +97,7 @@ jobs: uses: actions/download-artifact@v4 with: path: artifacts - + - name: Create Release uses: softprops/action-gh-release@v1 with: diff --git a/CMakeLists.txt b/CMakeLists.txt index 434aa478..e30e118d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,7 @@ if (CMAKE_BUILD_TYPE STREQUAL "Debug") option(APPEND_VERSION_USE_GIT "Append version or branch to filename using git, when off it always uses just the version" ON) option(ENABLE_CLANG_TIDY "Enable the use of clang-tidy (slows down compiling a lot)" OFF) option(ENABLE_TESTS "Enable the compilation and execution of tests" ON) + option(ENABLE_IWYU "Enable the use of Include What You Use (IWYU)" ON) else () option(ENABLE_OPTIMIZATION "Enable optimization flags (-O3)" ON) option(ENABLE_OPTIMIZATION_FAST_MATH "Enable fast math optimization flags (-Ofast)" ON) @@ -68,6 +69,7 @@ else () option(APPEND_VERSION_USE_GIT "Append version or branch to filename using git, when off it always uses just the version" ON) option(ENABLE_CLANG_TIDY "Enable the use of clang-tidy (slows down compiling a lot)" OFF) option(ENABLE_TESTS "Enable the compilation and execution of tests" OFF) + option(ENABLE_IWYU "Enable the use of Include What You Use (IWYU)" OFF) endif () if (ENABLE_TESTS) @@ -103,6 +105,7 @@ message("APPEND_VERSION: ${APPEND_VERSION}") message("APPEND_VERSION_USE_GIT: ${APPEND_VERSION_USE_GIT}") message("ENABLE_CLANG_TIDY: ${ENABLE_CLANG_TIDY}") message("ENABLE_TESTS: ${ENABLE_TESTS}") +message("ENABLE_IWYU: ${ENABLE_IWYU}") if (APPEND_VERSION) execute_process(COMMAND git rev-parse --abbrev-ref HEAD @@ -206,6 +209,18 @@ add_executable(Zagreus src/main.cpp ${inc_zagreus}) target_compile_definitions(Zagreus PRIVATE ZAGREUS_VERSION_MAJOR="${ZAGREUS_VERSION_MAJOR}") target_compile_definitions(Zagreus PRIVATE ZAGREUS_VERSION_MINOR="${ZAGREUS_VERSION_MINOR}") +if (ENABLE_IWYU) + find_program(IWYU_PATH NAMES "include-what-you-use") + + if (IWYU_PATH) + set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE "${IWYU_PATH}") + set_property(TARGET Zagreus PROPERTY CXX_INCLUDE_WHAT_YOU_USE "${IWYU_PATH}") + message(STATUS "IWYU found and enabled: ${IWYU_PATH}") + else () + message(WARNING "IWYU requested but not found. Please install IWYU or disable ENABLE_IWYU.") + endif () +endif () + # TODO: re-enable #if (ENABLE_TESTS) # file(GLOB tests_folder "tests/*.h" "tests/*.cpp") diff --git a/README.md b/README.md index f303ae54..62fd34ff 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ ![License](https://img.shields.io/github/license/Dannyj1/Zagreus?style=for-the-badge) ![Downloads](https://img.shields.io/github/downloads/Dannyj1/Zagreus/total?style=for-the-badge) ![GitHub commit activity](https://img.shields.io/github/commit-activity/t/Dannyj1/Zagreus?style=for-the-badge) ![GitHub commits since latest release (by date including pre-releases)](https://img.shields.io/github/commits-since/Dannyj1/Zagreus/latest?include_prereleases&style=for-the-badge) - # Zagreus Chess Engine Zagreus is a work in progress UCI Chess Engine. You can play against Zagreus on @@ -28,13 +27,14 @@ stable version for yourself, you can use the tag. You can list all tags and sear on GitHub. # Features + - Bitboard board representation with Plain Magic Bitboards for sliding piece move generation - Tapered Evaluation with: - - Material - - Piece-Square Tables - - Several evaluation terms per piece type (e.g. passed pawn, king safety, etc.) - - Penalty for undefended minor pieces - - And more + - Material + - Piece-Square Tables + - Several evaluation terms per piece type (e.g. passed pawn, king safety, etc.) + - Penalty for undefended minor pieces + - And more - Evaluation values automatically tuned using a gradient descent tuner with the Adam optimizer. - Principal Variation Search with Alpha-Beta pruning - Quiescence Search with delta pruning and SEE move ordering @@ -44,6 +44,7 @@ on GitHub. - And more! This list is constantly growing and changing, but it is difficult to keep track of all features and changes. # UCI Options + Zagreus Engine has the following UCI options that can be changed: - `MoveOverhead` - The amount of time that will be substracted from the internal timer for each move. This helps when @@ -59,17 +60,21 @@ Zagreus uses CMake to build. On Windows you can use the [CMake-GUI](https://cmak following commands: Clone the repository: + ```bash git clone https://github.com/Dannyj1/Zagreus.git ``` If you want to use Clang/LLVM, set the compiler to clang++ (not required. If you don't run these commands, your default compiler will be used which works fine in most cases): + ```bash export CC=/usr/bin/clang- export CXX=/usr/bin/clang++- ``` + Build: + ```bash cd cmake -DCMAKE_BUILD_TYPE=Release . @@ -77,7 +82,9 @@ cmake --build . ``` # Credits + Thanks to: + - [AndyGrant](https://github.com/AndyGrant) for the easy to use and open source [OpenBench Testing Framework](https://github.com/AndyGrant/OpenBench), making testing my engine SO much easier and allowing you to distribute test over multiple devices (I use a private instance of OpenBench) diff --git a/src/bitboard.cpp b/src/bitboard.cpp index 73ed6a07..7e2c57ea 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -20,6 +20,8 @@ This file is part of Zagreus. #include "bitboard.h" +#include "magics.h" + namespace Zagreus { uint64_t bishopAttacks(const uint8_t square, uint64_t occupied) { occupied &= getBishopMask(square); diff --git a/src/board.cpp b/src/board.cpp index ad788331..139d3ad2 100644 --- a/src/board.cpp +++ b/src/board.cpp @@ -20,8 +20,6 @@ #include "board.h" -#include - namespace Zagreus { bool Board::isMoveLegal(Move move) { // TODO: Implement diff --git a/src/board.h b/src/board.h index 0f6ff4db..0e979d7f 100644 --- a/src/board.h +++ b/src/board.h @@ -21,12 +21,16 @@ #pragma once +#include #include #include #include #include "bitboard.h" #include "move.h" +#include "constants.h" + +enum class Piece : uint8_t; namespace Zagreus { class Board { diff --git a/src/magics.cpp b/src/magics.cpp index 97eb7c04..26689438 100644 --- a/src/magics.cpp +++ b/src/magics.cpp @@ -20,10 +20,12 @@ #include "magics.h" +#include #include #include #include #include +#include // Code for magic generation https://www.chessprogramming.org/Looking_for_Magics namespace Zagreus { diff --git a/src/main.cpp b/src/main.cpp index 67ec1172..bcd1366d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,6 +19,7 @@ */ #include +#include #include "uci.h" diff --git a/src/move_gen.cpp b/src/move_gen.cpp index 6b617187..c4da7626 100644 --- a/src/move_gen.cpp +++ b/src/move_gen.cpp @@ -18,11 +18,15 @@ along with Zagreus. If not, see . */ -#include "move_gen.h" +#include +#include +#include "move_gen.h" #include "bitwise.h" #include "board.h" #include "types.h" +#include "bitboard.h" +#include "move.h" namespace Zagreus { template diff --git a/src/move_gen.h b/src/move_gen.h index 2a5182b3..a95effaa 100644 --- a/src/move_gen.h +++ b/src/move_gen.h @@ -21,11 +21,18 @@ #pragma once +#include + #include "board.h" #include "move.h" #include "types.h" +enum class PieceColor : uint8_t; + namespace Zagreus { +class Board; +struct MoveList; + enum class GenerationType : uint8_t { All, Quiet, diff --git a/src/uci.cpp b/src/uci.cpp index 48e53fe3..c8c51a57 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -19,6 +19,7 @@ */ // ReSharper disable CppRedundantControlFlowJump +#include #include #include #include diff --git a/src/uci.h b/src/uci.h index 295c9fda..eedb756a 100644 --- a/src/uci.h +++ b/src/uci.h @@ -23,6 +23,8 @@ #include #include #include +#include +#include namespace Zagreus { class UCIOption; From 4a05b0abcbedcad68fd0dd973c981592b452b0ae Mon Sep 17 00:00:00 2001 From: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> Date: Sun, 6 Oct 2024 21:00:56 +0200 Subject: [PATCH 17/91] Implement lookup tables, added function that gets all attackers of a square. --- src/bitboard.cpp | 9 +++++++++ src/bitboard.h | 29 +++++++++++++++++++++++++---- src/board.cpp | 20 +++++++++++++++++++- src/board.h | 11 ++++++++--- src/uci.cpp | 4 +++- 5 files changed, 64 insertions(+), 9 deletions(-) diff --git a/src/bitboard.cpp b/src/bitboard.cpp index 7e2c57ea..47df1fd6 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -23,6 +23,15 @@ This file is part of Zagreus. #include "magics.h" namespace Zagreus { +void initializeAttackLookupTables() { + for (uint8_t square = 0; square < SQUARES; ++square) { + pawnAttacksTable[std::to_underlying(PieceColor::WHITE)][square] = calculateWhitePawnAttacks(square); + pawnAttacksTable[std::to_underlying(PieceColor::BLACK)][square] = calculateBlackPawnAttacks(square); + knightAttacksTable[square] = calculateKnightAttacks(square); + kingAttacksTable[square] = calculateKingAttacks(square); + } +} + uint64_t bishopAttacks(const uint8_t square, uint64_t occupied) { occupied &= getBishopMask(square); occupied *= getBishopMagic(square); diff --git a/src/bitboard.h b/src/bitboard.h index cd81e51b..c13e9028 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -21,6 +21,8 @@ #pragma once +#include <__utility/to_underlying.h> + #include #include "constants.h" @@ -28,6 +30,12 @@ #include "types.h" namespace Zagreus { +static std::array, COLORS> pawnAttacksTable{}; +static std::array knightAttacksTable{}; +static std::array kingAttacksTable{}; + +void initializeAttackLookupTables(); + inline uint64_t shiftNorth(const uint64_t bb) { return bb << 8; } @@ -135,10 +143,15 @@ inline uint64_t whitePawnEastAttacks(const uint64_t bb) { return shiftNorthEast(bb); } -inline uint64_t whitePawnAttacks(const uint64_t bb) { +inline uint64_t calculateWhitePawnAttacks(const uint64_t bb) { return whitePawnWestAttacks(bb) | whitePawnEastAttacks(bb); } +template +uint64_t pawnAttacks(const uint8_t square) { + return pawnAttacksTable[std::to_underlying(color)][square]; +} + inline uint64_t whitePushablePawns(const uint64_t bb, const uint64_t empty) { return shiftSouth(empty) & bb; } @@ -167,7 +180,7 @@ inline uint64_t blackPawnEastAttacks(const uint64_t bb) { return shiftSouthEast(bb); } -inline uint64_t blackPawnAttacks(const uint64_t bb) { +inline uint64_t calculateBlackPawnAttacks(const uint64_t bb) { return blackPawnWestAttacks(bb) | blackPawnEastAttacks(bb); } @@ -181,22 +194,30 @@ inline uint64_t blackDoublePushablePawns(const uint64_t bb, const uint64_t empty return blackPushablePawns(bb, emptyRank6); } -inline uint64_t knightAttacks(const uint64_t bb) { +inline uint64_t calculateKnightAttacks(const uint64_t bb) { return shiftNorthNorthEast(bb) | shiftNorthEastEast(bb) | shiftSouthEastEast(bb) | shiftSouthSouthEast(bb) | shiftSouthSouthWest(bb) | shiftSouthWestWest(bb) | shiftNorthWestWest(bb) |shiftNorthNorthWest(bb); } +inline uint64_t knightAttacks(uint8_t square) { + return knightAttacksTable[square]; +} + uint64_t bishopAttacks(uint8_t square, uint64_t occupied); uint64_t rookAttacks(uint8_t square, uint64_t occupied); uint64_t queenAttacks(uint8_t square, uint64_t occupied); -inline uint64_t kingAttacks(uint64_t bb) { +inline uint64_t calculateKingAttacks(uint64_t bb) { const uint64_t attacks = shiftEast(bb) | shiftWest(bb); bb |= attacks; return attacks | shiftNorth(bb) | shiftSouth(bb); } + +inline uint64_t kingAttacks(const uint8_t square) { + return kingAttacksTable[square]; +} } // namespace Zagreus diff --git a/src/board.cpp b/src/board.cpp index 139d3ad2..27364ee0 100644 --- a/src/board.cpp +++ b/src/board.cpp @@ -21,7 +21,25 @@ #include "board.h" namespace Zagreus { -bool Board::isMoveLegal(Move move) { +bool Board::isMoveLegal(Move move) const { // TODO: Implement } + +uint64_t Board::getSquareAttackers(const uint8_t square) const { + using enum Piece; + + const uint64_t knights = getBitboard() | getBitboard(); + const uint64_t kings = getBitboard() | getBitboard(); + uint64_t bishopsQueens = getBitboard() | getBitboard(); + uint64_t rooksQueens = getBitboard() | getBitboard(); + rooksQueens |= getBitboard() | getBitboard(); + bishopsQueens |= getBitboard() | getBitboard(); + + return (pawnAttacks(square) & getBitboard()) + | (pawnAttacks(square) & getBitboard()) + | (knightAttacks(square) & knights) + | (kingAttacks(square) & kings) + | (bishopAttacks(square, occupied) & bishopsQueens) + | (rookAttacks (square, occupied) & rooksQueens); +} } // namespace Zagreus diff --git a/src/board.h b/src/board.h index 0e979d7f..138a53b5 100644 --- a/src/board.h +++ b/src/board.h @@ -30,8 +30,6 @@ #include "move.h" #include "constants.h" -enum class Piece : uint8_t; - namespace Zagreus { class Board { private: @@ -64,6 +62,13 @@ class Board { return ~occupied; } - bool isMoveLegal(Move move); + [[nodiscard]] bool isMoveLegal(Move move) const; + + [[nodiscard]] uint64_t getSquareAttackers(uint8_t square) const; + + template + [[nodiscard]] uint64_t getSquareAttackersByColor(const uint8_t square) const { + return getSquareAttackers(square) & getColorBitboard(); + } }; } // namespace Zagreus diff --git a/src/uci.cpp b/src/uci.cpp index c8c51a57..8b0d4095 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -29,6 +29,8 @@ #include "magics.h" #include "uci.h" +#include "bitboard.h" + namespace Zagreus { void Engine::doSetup() { // According to the UCI specification, bitboard, magic bitboards and other stuff should be done only when "isready" or "setoption" is called @@ -36,8 +38,8 @@ void Engine::doSetup() { return; } - // TODO: setup here initializeMagicBitboards(); + initializeAttackLookupTables(); didSetup = true; } From dec48d5d1ef322e7d92b4c302e05cbd4a590ac94 Mon Sep 17 00:00:00 2001 From: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> Date: Sun, 6 Oct 2024 21:06:15 +0200 Subject: [PATCH 18/91] Fix wrong import that breaks CI --- src/bitboard.cpp | 5 +++-- src/bitboard.h | 12 ++++++------ src/board.cpp | 12 ++++++------ src/board.h | 5 +++-- src/macros.h | 27 +++++++++++++++++++++++++++ src/uci.cpp | 12 +++--------- 6 files changed, 48 insertions(+), 25 deletions(-) create mode 100644 src/macros.h diff --git a/src/bitboard.cpp b/src/bitboard.cpp index 47df1fd6..3b2b04e7 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -20,13 +20,14 @@ This file is part of Zagreus. #include "bitboard.h" +#include "macros.h" #include "magics.h" namespace Zagreus { void initializeAttackLookupTables() { for (uint8_t square = 0; square < SQUARES; ++square) { - pawnAttacksTable[std::to_underlying(PieceColor::WHITE)][square] = calculateWhitePawnAttacks(square); - pawnAttacksTable[std::to_underlying(PieceColor::BLACK)][square] = calculateBlackPawnAttacks(square); + pawnAttacksTable[IDX(PieceColor::WHITE)][square] = calculateWhitePawnAttacks(square); + pawnAttacksTable[IDX(PieceColor::BLACK)][square] = calculateBlackPawnAttacks(square); knightAttacksTable[square] = calculateKnightAttacks(square); kingAttacksTable[square] = calculateKingAttacks(square); } diff --git a/src/bitboard.h b/src/bitboard.h index c13e9028..41a45e3a 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -21,12 +21,12 @@ #pragma once -#include <__utility/to_underlying.h> +#include #include #include "constants.h" -#include "magics.h" +#include "macros.h" #include "types.h" namespace Zagreus { @@ -103,7 +103,7 @@ inline uint64_t shiftSouthSouthWest(const uint64_t bb) { template constexpr uint64_t shift(const uint64_t bb) { switch (direction) { - using enum Direction; + using enum Direction; case NORTH: return shiftNorth(bb); case SOUTH: @@ -149,7 +149,7 @@ inline uint64_t calculateWhitePawnAttacks(const uint64_t bb) { template uint64_t pawnAttacks(const uint8_t square) { - return pawnAttacksTable[std::to_underlying(color)][square]; + return pawnAttacksTable[IDX(color)][square]; } inline uint64_t whitePushablePawns(const uint64_t bb, const uint64_t empty) { @@ -196,8 +196,8 @@ inline uint64_t blackDoublePushablePawns(const uint64_t bb, const uint64_t empty inline uint64_t calculateKnightAttacks(const uint64_t bb) { return shiftNorthNorthEast(bb) | shiftNorthEastEast(bb) | shiftSouthEastEast(bb) | - shiftSouthSouthEast(bb) | shiftSouthSouthWest(bb) | shiftSouthWestWest(bb) | - shiftNorthWestWest(bb) |shiftNorthNorthWest(bb); + shiftSouthSouthEast(bb) | shiftSouthSouthWest(bb) | shiftSouthWestWest(bb) | + shiftNorthWestWest(bb) | shiftNorthNorthWest(bb); } inline uint64_t knightAttacks(uint8_t square) { diff --git a/src/board.cpp b/src/board.cpp index 27364ee0..c737dee4 100644 --- a/src/board.cpp +++ b/src/board.cpp @@ -32,14 +32,14 @@ uint64_t Board::getSquareAttackers(const uint8_t square) const { const uint64_t kings = getBitboard() | getBitboard(); uint64_t bishopsQueens = getBitboard() | getBitboard(); uint64_t rooksQueens = getBitboard() | getBitboard(); - rooksQueens |= getBitboard() | getBitboard(); + rooksQueens |= getBitboard() | getBitboard(); bishopsQueens |= getBitboard() | getBitboard(); return (pawnAttacks(square) & getBitboard()) - | (pawnAttacks(square) & getBitboard()) - | (knightAttacks(square) & knights) - | (kingAttacks(square) & kings) - | (bishopAttacks(square, occupied) & bishopsQueens) - | (rookAttacks (square, occupied) & rooksQueens); + | (pawnAttacks(square) & getBitboard()) + | (knightAttacks(square) & knights) + | (kingAttacks(square) & kings) + | (bishopAttacks(square, occupied) & bishopsQueens) + | (rookAttacks(square, occupied) & rooksQueens); } } // namespace Zagreus diff --git a/src/board.h b/src/board.h index 138a53b5..bdd6c86a 100644 --- a/src/board.h +++ b/src/board.h @@ -29,6 +29,7 @@ #include "bitboard.h" #include "move.h" #include "constants.h" +#include "macros.h" namespace Zagreus { class Board { @@ -41,12 +42,12 @@ class Board { public: template [[nodiscard]] uint64_t getBitboard() const { - return bitboards[std::to_underlying(piece)]; + return bitboards[IDX(piece)]; } template [[nodiscard]] uint64_t getColorBitboard() const { - return colorBoards[std::to_underlying(color)]; + return colorBoards[IDX(color)]; } [[nodiscard]] Piece getPieceOnSquare(const int square) const { diff --git a/src/macros.h b/src/macros.h new file mode 100644 index 00000000..5707dcfe --- /dev/null +++ b/src/macros.h @@ -0,0 +1,27 @@ + +/* + This file is part of Zagreus. + + Zagreus is a UCI chess engine + Copyright (C) 2023-2024 Danny Jelsma + + Zagreus is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Zagreus 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Zagreus. If not, see . + */ + +#pragma once + +#include + +// Converts an enum class to its underlying type. Useful for indexing arrays, hence the name IDX (index). +#define IDX(x) std::to_underlying(x) diff --git a/src/uci.cpp b/src/uci.cpp index 8b0d4095..83db0e2b 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -121,7 +121,8 @@ void Engine::handleSetOptionCommand(const std::string& args) { while (iss >> word) { std::string lowercaseWord = word; - std::ranges::transform(lowercaseWord, lowercaseWord.begin(), [](const unsigned char c) { return std::tolower(c); }); + std::ranges::transform(lowercaseWord, lowercaseWord.begin(), + [](const unsigned char c) { return std::tolower(c); }); if (lowercaseWord == "name") { section = word; @@ -179,27 +180,21 @@ void Engine::handleSetOptionCommand(const std::string& args) { } void Engine::handleUciNewGameCommand(std::string_view args) { - } void Engine::handlePositionCommand(std::string_view args) { - } void Engine::handleGoCommand(std::string_view args) { - } void Engine::handleStopCommand(std::string_view args) { - } void Engine::handlePonderHitCommand(std::string_view args) { - } void Engine::handleQuitCommand(std::string_view args) { - } void Engine::processCommand(const std::string_view command, const std::string& args) { @@ -278,7 +273,6 @@ void Engine::startUci() { args = ""; } - if (args == "\n" || args == " ") { args = ""; } @@ -297,7 +291,7 @@ void Engine::sendMessage(std::string_view message) { std::string removeRedundantSpaces(std::string_view input) { std::string result; - bool inSpace = false; // Track if we are in a sequence of spaces/tabs + bool inSpace = false; // Track if we are in a sequence of spaces/tabs for (size_t i = 0; i < input.length(); ++i) { char current = input[i]; From 2db260c5fc1059a5eea7e1d37d8babe122a4093d Mon Sep 17 00:00:00 2001 From: Dannyj1 <73849717+Dannyj1@users.noreply.github.com> Date: Tue, 8 Oct 2024 09:32:39 +0200 Subject: [PATCH 19/91] Implemented check to see if a position is legal/valid, added setPiece and removePiece function to the Board class --- src/bitboard.h | 8 ++++++-- src/board.cpp | 17 +++++++++++++++-- src/board.h | 35 ++++++++++++++++++++++++++++++++++- src/move_gen.cpp | 2 +- src/types.h | 22 +++++++++++++++++----- 5 files changed, 73 insertions(+), 11 deletions(-) diff --git a/src/bitboard.h b/src/bitboard.h index 41a45e3a..ffff615d 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -21,10 +21,10 @@ #pragma once -#include - #include +#include +#include "bitwise.h" #include "constants.h" #include "macros.h" #include "types.h" @@ -220,4 +220,8 @@ inline uint64_t calculateKingAttacks(uint64_t bb) { inline uint64_t kingAttacks(const uint8_t square) { return kingAttacksTable[square]; } + +inline uint64_t squareToBitboard(const uint8_t square) { return 1ULL << square; } + +inline uint8_t bitboardToSquare(const uint64_t bb) { return bitscanForward(bb); } } // namespace Zagreus diff --git a/src/board.cpp b/src/board.cpp index c737dee4..929451af 100644 --- a/src/board.cpp +++ b/src/board.cpp @@ -20,11 +20,22 @@ #include "board.h" +#include "bitwise.h" + namespace Zagreus { -bool Board::isMoveLegal(Move move) const { - // TODO: Implement +template +bool Board::isPositionLegal() const { + constexpr PieceColor opponentColor = !movedColor; + constexpr Piece king = movedColor == PieceColor::WHITE ? Piece::WHITE_KING : Piece::BLACK_KING; + const uint64_t kingBB = getBitboard(); + const uint8_t kingSquare = bitscanForward(kingBB); + + return !getSquareAttackersByColor(kingSquare); } +template bool Board::isPositionLegal() const; +template bool Board::isPositionLegal() const; + uint64_t Board::getSquareAttackers(const uint8_t square) const { using enum Piece; @@ -42,4 +53,6 @@ uint64_t Board::getSquareAttackers(const uint8_t square) const { | (bishopAttacks(square, occupied) & bishopsQueens) | (rookAttacks(square, occupied) & rooksQueens); } + +void Board::makeMove(const Move& move) {} } // namespace Zagreus diff --git a/src/board.h b/src/board.h index bdd6c86a..fec71a4b 100644 --- a/src/board.h +++ b/src/board.h @@ -63,7 +63,40 @@ class Board { return ~occupied; } - [[nodiscard]] bool isMoveLegal(Move move) const; + template + void setPiece(uint8_t square) { + const uint64_t squareBB = squareToBitboard(square); + + board[square] = piece; + bitboards[IDX(piece)] |= squareBB; + occupied |= squareBB; + colorBoards[IDX(pieceColor(piece))] |= squareBB; + } + + void removePiece(uint8_t square) { + const uint64_t squareBB = squareToBitboard(square); + const Piece piece = board[square]; + + board[square] = Piece::EMPTY; + bitboards[IDX(piece)] &= ~squareBB; + occupied &= ~squareBB; + colorBoards[IDX(pieceColor(piece))] &= ~squareBB; + } + + template + void removePiece(uint8_t square) { + const uint64_t squareBB = squareToBitboard(square); + + board[square] = Piece::EMPTY; + bitboards[IDX(piece)] &= ~squareBB; + occupied &= ~squareBB; + colorBoards[IDX(pieceColor(piece))] &= ~squareBB; + } + + void makeMove(const Move& move); + + template + [[nodiscard]] bool isPositionLegal() const; [[nodiscard]] uint64_t getSquareAttackers(uint8_t square) const; diff --git a/src/move_gen.cpp b/src/move_gen.cpp index c4da7626..8e802681 100644 --- a/src/move_gen.cpp +++ b/src/move_gen.cpp @@ -53,7 +53,7 @@ void generatePawnMoves(const Board& board, MoveList& moves, const uint64_t genMa // TODO: Handle promotions // TODO: Move attacks to table lookup constexpr Piece pawn = color == PieceColor::WHITE ? Piece::WHITE_PAWN : Piece::BLACK_PAWN; - constexpr PieceColor opponentColor = color == PieceColor::WHITE ? PieceColor::BLACK : PieceColor::WHITE; + constexpr PieceColor opponentColor = !color; const uint64_t pawnBB = board.getBitboard(); const uint64_t emptyBB = board.getEmptyBitboard(); diff --git a/src/types.h b/src/types.h index e45d2b2e..bb305b83 100644 --- a/src/types.h +++ b/src/types.h @@ -39,16 +39,28 @@ enum class PieceColor : uint8_t { WHITE, BLACK, EMPTY = 255 }; enum class Piece : uint8_t { WHITE_PAWN, - WHITE_KNIGHT, - WHITE_BISHOP, - WHITE_ROOK, - WHITE_QUEEN, - WHITE_KING, BLACK_PAWN, + WHITE_KNIGHT, BLACK_KNIGHT, + WHITE_BISHOP, BLACK_BISHOP, + WHITE_ROOK, BLACK_ROOK, + WHITE_QUEEN, BLACK_QUEEN, + WHITE_KING, BLACK_KING, EMPTY = 255 }; + +inline constexpr PieceColor operator!(const PieceColor color) { + return color == PieceColor::WHITE ? PieceColor::BLACK : PieceColor::WHITE; +} + +inline constexpr PieceColor pieceColor(const Piece piece) { + return static_cast(static_cast(piece) / 2); +} + +inline constexpr PieceType pieceType(const Piece piece) { + return static_cast(static_cast(piece) % 2); +} From 02e9609902e486c4763c7bcf3988f47be03eab4e Mon Sep 17 00:00:00 2001 From: Dannyj1 <73849717+Dannyj1@users.noreply.github.com> Date: Tue, 8 Oct 2024 13:53:03 +0200 Subject: [PATCH 20/91] Implemented makeMove and unmakeMove, started perft implementation --- src/board.cpp | 36 +++++++++++++++++++++++++- src/board.h | 31 ++++++++++++++++++++++ src/constants.h | 2 ++ src/move.h | 29 ++++++++++++++++++--- src/move_gen.cpp | 18 ++++++------- src/perft.cpp | 67 ++++++++++++++++++++++++++++++++++++++++++++++++ src/perft.h | 30 ++++++++++++++++++++++ src/uci.cpp | 12 +++++++++ src/uci.h | 8 ++++-- 9 files changed, 217 insertions(+), 16 deletions(-) create mode 100644 src/perft.cpp create mode 100644 src/perft.h diff --git a/src/board.cpp b/src/board.cpp index 929451af..152662ce 100644 --- a/src/board.cpp +++ b/src/board.cpp @@ -54,5 +54,39 @@ uint64_t Board::getSquareAttackers(const uint8_t square) const { | (rookAttacks(square, occupied) & rooksQueens); } -void Board::makeMove(const Move& move) {} +void Board::makeMove(const Move& move) { + const uint8_t fromSquare = getFromSquare(move); + const uint8_t toSquare = getToSquare(move); + const Piece movedPiece = getPieceOnSquare(fromSquare); + + if (isPieceOnSquare(toSquare)) { + removePiece(toSquare); + } + + removePiece(fromSquare); + setPiece(movedPiece, toSquare); + + sideToMove = !sideToMove; + history[ply].move = move; + history[ply].capturedPiece = getPieceOnSquare(toSquare); + ply++; +} + +void Board::unmakeMove() { + const auto [move, capturedPiece] = history[ply - 1]; + const uint8_t fromSquare = getFromSquare(move); + const uint8_t toSquare = getToSquare(move); + const Piece movedPiece = getPieceOnSquare(toSquare); + + removePiece(toSquare); + setPiece(movedPiece, fromSquare); + + if (capturedPiece != Piece::EMPTY) { + setPiece(capturedPiece, toSquare); + } + + // TODO: Restore state once more things are added to it + sideToMove = !sideToMove; + ply--; +} } // namespace Zagreus diff --git a/src/board.h b/src/board.h index fec71a4b..3cf9488f 100644 --- a/src/board.h +++ b/src/board.h @@ -32,12 +32,20 @@ #include "macros.h" namespace Zagreus { +struct BoardState { + Move move = 0; + Piece capturedPiece = Piece::EMPTY; +}; + class Board { private: std::array board{}; std::array bitboards{}; uint64_t occupied = 0; std::array colorBoards{}; + PieceColor sideToMove = PieceColor::EMPTY; + std::array history{}; + int ply = 0; public: template @@ -55,6 +63,10 @@ class Board { return board[square]; } + [[nodiscard]] bool isPieceOnSquare(const int square) const { + return board[square] != Piece::EMPTY; + } + [[nodiscard]] uint64_t getOccupiedBitboard() const { return occupied; } @@ -63,6 +75,14 @@ class Board { return ~occupied; } + [[nodiscard]] PieceColor getSideToMove() const { + return sideToMove; + } + + [[nodiscard]] int getPly() const { + return ply; + } + template void setPiece(uint8_t square) { const uint64_t squareBB = squareToBitboard(square); @@ -73,6 +93,15 @@ class Board { colorBoards[IDX(pieceColor(piece))] |= squareBB; } + void setPiece(Piece piece, uint8_t square) { + const uint64_t squareBB = squareToBitboard(square); + + board[square] = piece; + bitboards[IDX(piece)] |= squareBB; + occupied |= squareBB; + colorBoards[IDX(pieceColor(piece))] |= squareBB; + } + void removePiece(uint8_t square) { const uint64_t squareBB = squareToBitboard(square); const Piece piece = board[square]; @@ -95,6 +124,8 @@ class Board { void makeMove(const Move& move); + void unmakeMove(); + template [[nodiscard]] bool isPositionLegal() const; diff --git a/src/constants.h b/src/constants.h index ea857466..6c78b443 100644 --- a/src/constants.h +++ b/src/constants.h @@ -23,6 +23,8 @@ #include +static constexpr uint16_t MAX_PLY = 750; + constexpr uint64_t NOT_A_FILE = 0xFEFEFEFEFEFEFEFEULL; constexpr uint64_t NOT_AB_FILE = 0xFCFCFCFCFCFCFCFCULL; constexpr uint64_t NOT_GH_FILE = 0x3F3F3F3F3F3F3F3FULL; diff --git a/src/move.h b/src/move.h index caf5a4be..bb541440 100644 --- a/src/move.h +++ b/src/move.h @@ -27,13 +27,34 @@ namespace Zagreus { using Move = uint16_t; struct MoveList { - std::array list{}; + std::array moves{}; uint8_t size = 0; }; -// bits 0-5: to square (0-63) -// bits 6-11: from square (0-63) +// bits 0-5: from square (0-63) +// bits 6-11: to square (0-63) +// bits 12-13: move type (00 = normal, 01 = promotion, 10 = en passant, 11 = castling) +// bits 14-15: promotion piece (00 = queen, 01 = rook, 10 = bishop, 11 = knight) inline Move encodeMove(const uint8_t fromSquare, const uint8_t toSquare) { - return (fromSquare << 6) | toSquare; + // Assume normal move, so 12-13 are 00 + return static_cast(toSquare << 6) | static_cast(fromSquare); +} + +inline Move encodeMove(const uint8_t fromSquare, const uint8_t toSquare, const uint8_t moveType) { + return static_cast(toSquare << 6) | static_cast(fromSquare) + | static_cast(moveType << 12); +} + +inline Move encodeMove(const uint8_t fromSquare, const uint8_t toSquare, const uint8_t moveType, const uint8_t promotionPiece) { + return static_cast(toSquare << 6) | static_cast(fromSquare) + | static_cast(moveType << 12) | static_cast(promotionPiece << 14); +} + +inline uint8_t getFromSquare(const Move move) { + return (move >> 6) & 0b111111; +} + +inline uint8_t getToSquare(const Move move) { + return move & 0b111111; } } // namespace Zagreus diff --git a/src/move_gen.cpp b/src/move_gen.cpp index 8e802681..23af01aa 100644 --- a/src/move_gen.cpp +++ b/src/move_gen.cpp @@ -93,7 +93,7 @@ void generatePawnMoves(const Board& board, MoveList& moves, const uint64_t genMa const uint8_t squareFrom = shift(squareTo); const Move move = encodeMove(squareFrom, squareTo); - moves.list[moves.size] = move; + moves.moves[moves.size] = move; moves.size++; } @@ -102,7 +102,7 @@ void generatePawnMoves(const Board& board, MoveList& moves, const uint64_t genMa const uint8_t squareFrom = shift(squareTo); const Move move = encodeMove(squareFrom, squareTo); - moves.list[moves.size] = move; + moves.moves[moves.size] = move; moves.size++; } @@ -111,7 +111,7 @@ void generatePawnMoves(const Board& board, MoveList& moves, const uint64_t genMa const uint8_t squareFrom = shift(squareTo); const Move move = encodeMove(squareFrom, squareTo); - moves.list[moves.size] = move; + moves.moves[moves.size] = move; moves.size++; } @@ -120,7 +120,7 @@ void generatePawnMoves(const Board& board, MoveList& moves, const uint64_t genMa const uint8_t squareFrom = shift(squareTo); const Move move = encodeMove(squareFrom, squareTo); - moves.list[moves.size] = move; + moves.moves[moves.size] = move; moves.size++; } } @@ -139,7 +139,7 @@ void generateKnightMoves(const Board& board, MoveList& moves, const uint64_t gen const uint8_t toSquare = popLsb(genBB); const Move move = encodeMove(fromSquare, toSquare); - moves.list[moves.size] = move; + moves.moves[moves.size] = move; moves.size++; } } @@ -160,7 +160,7 @@ void generateBishopMoves(const Board& board, MoveList& moves, const uint64_t gen const uint8_t toSquare = popLsb(genBB); const Move move = encodeMove(fromSquare, toSquare); - moves.list[moves.size] = move; + moves.moves[moves.size] = move; moves.size++; } } @@ -181,7 +181,7 @@ void generateRookMoves(const Board& board, MoveList& moves, const uint64_t genMa const uint8_t toSquare = popLsb(genBB); const Move move = encodeMove(fromSquare, toSquare); - moves.list[moves.size] = move; + moves.moves[moves.size] = move; moves.size++; } } @@ -202,7 +202,7 @@ void generateQueenMoves(const Board& board, MoveList& moves, const uint64_t genM const uint8_t toSquare = popLsb(genBB); const Move move = encodeMove(fromSquare, toSquare); - moves.list[moves.size] = move; + moves.moves[moves.size] = move; moves.size++; } } @@ -224,7 +224,7 @@ void generateKingMoves(const Board& board, MoveList& moves, const uint64_t genMa const uint8_t toSquare = popLsb(genBB); const Move move = encodeMove(fromSquare, toSquare); - moves.list[moves.size] = move; + moves.moves[moves.size] = move; moves.size++; } } diff --git a/src/perft.cpp b/src/perft.cpp new file mode 100644 index 00000000..901eac1c --- /dev/null +++ b/src/perft.cpp @@ -0,0 +1,67 @@ +/* + This file is part of Zagreus. + + Zagreus is a UCI chess engine + Copyright (C) 2023-2024 Danny Jelsma + + Zagreus is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Zagreus 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Zagreus. If not, see . + */ + +#include "perft.h" + +#include "move.h" +#include "move_gen.h" + +namespace Zagreus { +uint64_t perft(Board &board, int depth) { + using enum PieceColor; + using enum GenerationType; + + if (depth == 0) { + return 1; + } + + uint64_t nodes = 0; + MoveList moveList{}; + PieceColor sideToMove = board.getSideToMove() == WHITE ? WHITE : BLACK + + if (sideToMove == WHITE) { + generateMoves(board, moveList); + } else { + generateMoves(board, moveList); + } + + for (int i = 0; i < moveList.size; i++) { + board.makeMove(moveList.moves[i]); + + if (sideToMove == WHITE) { + if (!board.isPositionLegal()) { + board.unmakeMove(); + continue; + } + } else { + if (!board.isPositionLegal()) { + board.unmakeMove(); + continue; + } + } + + nodes += perft(board, depth - 1); + board.unmakeMove(); + } + + return nodes; +} +} // namespace Zagreus + diff --git a/src/perft.h b/src/perft.h new file mode 100644 index 00000000..afb32e2f --- /dev/null +++ b/src/perft.h @@ -0,0 +1,30 @@ +/* + This file is part of Zagreus. + + Zagreus is a UCI chess engine + Copyright (C) 2023-2024 Danny Jelsma + + Zagreus is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Zagreus 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Zagreus. If not, see . + */ + +#pragma once + +#include + +#include "board.h" +#include "types.h" + +namespace Zagreus { +uint64_t perft(const Board &board, int depth); +} // namespace Zagreus \ No newline at end of file diff --git a/src/uci.cpp b/src/uci.cpp index 83db0e2b..b7bcef77 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -41,6 +41,7 @@ void Engine::doSetup() { initializeMagicBitboards(); initializeAttackLookupTables(); + board = Board(); didSetup = true; } @@ -195,6 +196,15 @@ void Engine::handlePonderHitCommand(std::string_view args) { } void Engine::handleQuitCommand(std::string_view args) { + +} + +void Engine::handlePerftCommand(std::string_view args) { + if (!didSetup) { + doSetup(); + } + + // TODO: Need to add FEN processing before we can add this } void Engine::processCommand(const std::string_view command, const std::string& args) { @@ -221,6 +231,8 @@ void Engine::processCommand(const std::string_view command, const std::string& a handlePonderHitCommand(args); } else if (command == "quit") { handleQuitCommand(args); + } else if (command == "perft") { + handlePerftCommand(args); } else { // If unknown, we must skip it and process the rest. if (args.empty() || args == " " || args == "\n") { diff --git a/src/uci.h b/src/uci.h index eedb756a..21a1c216 100644 --- a/src/uci.h +++ b/src/uci.h @@ -22,9 +22,11 @@ #include #include -#include #include #include +#include + +#include "board.h" namespace Zagreus { class UCIOption; @@ -32,7 +34,8 @@ class UCIOption; class Engine { private: bool didSetup = false; - std::map options; + std::map options{}; + Board board; void handleUciCommand(); void handleDebugCommand(std::string_view args); @@ -44,6 +47,7 @@ class Engine { void handleStopCommand(std::string_view args); void handlePonderHitCommand(std::string_view args); void handleQuitCommand(std::string_view args); + void handlePerftCommand(std::string_view args); void processCommand(std::string_view command, const std::string& args); public: From 0b98f7920fe1421c1ca10db330cbf8f056173e0e Mon Sep 17 00:00:00 2001 From: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> Date: Wed, 9 Oct 2024 16:26:27 +0200 Subject: [PATCH 21/91] Implemented setting board from FEN, implemented perft. Move generation seems to not be working correctly. --- CMakeLists.txt | 41 +++++----- src/bitboard.cpp | 2 +- src/bitboard.h | 2 +- src/board.cpp | 181 ++++++++++++++++++++++++++++++++++++++++-- src/board.h | 32 +++++++- src/magics.cpp | 10 +-- src/magics.h | 2 +- src/move.h | 4 +- src/move_gen.cpp | 24 +++--- src/move_gen.h | 10 +-- src/perft.cpp | 21 +++-- src/perft.h | 2 +- src/types.h | 34 ++++++-- src/uci.cpp | 75 +++++++++++------ src/uci.h | 3 +- tests/types_tests.cpp | 63 +++++++++++++++ 16 files changed, 406 insertions(+), 100 deletions(-) create mode 100644 tests/types_tests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e30e118d..8453bcd2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -146,9 +146,9 @@ if (ENABLE_OPTIMIZATION) set(PROFILING_FLAGS "${PROFILING_FLAGS} -O3") endif () else () - set(RELEASE_FLAGS "${RELEASE_FLAGS} -O1 -fno-inline") - set(DEBUG_FLAGS "${DEBUG_FLAGS} -O1 -fno-inline") - set(PROFILING_FLAGS "${PROFILING_FLAGS} -O1 -fno-inline") + set(RELEASE_FLAGS "${RELEASE_FLAGS} -O0 -fno-inline") + set(DEBUG_FLAGS "${DEBUG_FLAGS} -O0 -fno-inline") + set(PROFILING_FLAGS "${PROFILING_FLAGS} -O0 -fno-inline") endif () if (ENABLE_LTO) @@ -221,21 +221,20 @@ if (ENABLE_IWYU) endif () endif () -# TODO: re-enable -#if (ENABLE_TESTS) -# file(GLOB tests_folder "tests/*.h" "tests/*.cpp") -# -# # Remove main from inc_zagreus -# list(REMOVE_ITEM inc_zagreus "${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp") -# -# add_executable(zagreus-tests ${tests_folder} ${inc_zagreus}) -# target_link_libraries(zagreus-tests PRIVATE Catch2::Catch2WithMain) -# -# target_compile_definitions(zagreus-tests PRIVATE ZAGREUS_VERSION_MAJOR="${ZAGREUS_VERSION_MAJOR}") -# target_compile_definitions(zagreus-tests PRIVATE ZAGREUS_VERSION_MINOR="${ZAGREUS_VERSION_MINOR}") -# -# list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras) -# include(CTest) -# include(Catch) -# catch_discover_tests(zagreus-tests) -#endif () +if (ENABLE_TESTS) + file(GLOB tests_folder "tests/*.h" "tests/*.cpp") + + # Remove main from inc_zagreus + list(REMOVE_ITEM inc_zagreus "${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp") + + add_executable(zagreus-tests ${tests_folder} ${inc_zagreus}) + target_link_libraries(zagreus-tests PRIVATE Catch2::Catch2WithMain) + + target_compile_definitions(zagreus-tests PRIVATE ZAGREUS_VERSION_MAJOR="${ZAGREUS_VERSION_MAJOR}") + target_compile_definitions(zagreus-tests PRIVATE ZAGREUS_VERSION_MINOR="${ZAGREUS_VERSION_MINOR}") + + list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras) + include(CTest) + include(Catch) + catch_discover_tests(zagreus-tests) +endif () diff --git a/src/bitboard.cpp b/src/bitboard.cpp index 3b2b04e7..daabc398 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -49,7 +49,7 @@ uint64_t rookAttacks(const uint8_t square, uint64_t occupied) { return getRookMagicAttacks(square, occupied); } -uint64_t queenAttacks(const uint8_t square, uint64_t occupied) { +uint64_t queenAttacks(const uint8_t square, const uint64_t occupied) { return bishopAttacks(square, occupied) | rookAttacks(square, occupied); } } // namespace Zagreus \ No newline at end of file diff --git a/src/bitboard.h b/src/bitboard.h index ffff615d..bb00cbbd 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -200,7 +200,7 @@ inline uint64_t calculateKnightAttacks(const uint64_t bb) { shiftNorthWestWest(bb) | shiftNorthNorthWest(bb); } -inline uint64_t knightAttacks(uint8_t square) { +inline uint64_t knightAttacks(const uint8_t square) { return knightAttacksTable[square]; } diff --git a/src/board.cpp b/src/board.cpp index 152662ce..4c75427d 100644 --- a/src/board.cpp +++ b/src/board.cpp @@ -20,6 +20,8 @@ #include "board.h" +#include + #include "bitwise.h" namespace Zagreus { @@ -54,39 +56,202 @@ uint64_t Board::getSquareAttackers(const uint8_t square) const { | (rookAttacks(square, occupied) & rooksQueens); } +void Board::reset() { + this->board = {}; + this->bitboards = {}; + this->occupied = 0; + this->colorBoards = {}; + this->sideToMove = PieceColor::EMPTY; + this->history = {}; + this->ply = 0; + + std::ranges::fill(board, Piece::EMPTY); + std::ranges::fill(bitboards, 0); + std::ranges::fill(colorBoards, 0); + std::ranges::fill(history, BoardState{}); +} + void Board::makeMove(const Move& move) { const uint8_t fromSquare = getFromSquare(move); const uint8_t toSquare = getToSquare(move); const Piece movedPiece = getPieceOnSquare(fromSquare); + const Piece capturedPiece = getPieceOnSquare(toSquare); - if (isPieceOnSquare(toSquare)) { - removePiece(toSquare); + if (capturedPiece != Piece::EMPTY) { + removePiece(capturedPiece, toSquare); } - removePiece(fromSquare); + removePiece(movedPiece, fromSquare); setPiece(movedPiece, toSquare); sideToMove = !sideToMove; history[ply].move = move; - history[ply].capturedPiece = getPieceOnSquare(toSquare); + history[ply].capturedPiece = capturedPiece; ply++; } void Board::unmakeMove() { - const auto [move, capturedPiece] = history[ply - 1]; + ply--; + const auto [move, capturedPiece] = history[ply]; const uint8_t fromSquare = getFromSquare(move); const uint8_t toSquare = getToSquare(move); const Piece movedPiece = getPieceOnSquare(toSquare); - removePiece(toSquare); + removePiece(movedPiece, toSquare); setPiece(movedPiece, fromSquare); if (capturedPiece != Piece::EMPTY) { setPiece(capturedPiece, toSquare); } - // TODO: Restore state once more things are added to it sideToMove = !sideToMove; - ply--; +} + + +void Board::setPieceFromFENChar(const char character, const uint8_t index) { + using enum Piece; + + // Uppercase char = white, lowercase = black + switch (character) { + case 'P': + setPiece(WHITE_PAWN, index); + break; + case 'p': + setPiece(BLACK_PAWN, index); + break; + case 'N': + setPiece(WHITE_KNIGHT, index); + break; + case 'n': + setPiece(BLACK_KNIGHT, index); + break; + case 'B': + setPiece(WHITE_BISHOP, index); + break; + case 'b': + setPiece(BLACK_BISHOP, index); + break; + case 'R': + setPiece(WHITE_ROOK, index); + break; + case 'r': + setPiece(BLACK_ROOK, index); + break; + case 'Q': + setPiece(WHITE_QUEEN, index); + break; + case 'q': + setPiece(BLACK_QUEEN, index); + break; + case 'K': + setPiece(WHITE_KING, index); + break; + case 'k': + setPiece(BLACK_KING, index); + break; + default: + break; + } +} + +bool Board::setFromFEN(const std::string_view fen) { + // TODO: Update zobrist hash once that is implemented + uint8_t index = IDX(Square::A8); + int spaces = 0; + + reset(); + + for (const char character : fen) { + if (character == ' ') { + spaces++; + continue; + } + + if (character == ',') { + break; + } + + if (spaces == 0) { + if (character == '/') { + index -= 16; + continue; + } + + if (character >= '1' && character <= '8') { + index += character - '0'; + continue; + } + + if (character >= 'A' && character <= 'z') { + setPieceFromFENChar(character, index); + index++; + } else { + return false; + } + } + + if (spaces == 1) { + if (tolower(character) == 'w') { + sideToMove = PieceColor::WHITE; + } else if (tolower(character) == 'b') { + sideToMove = PieceColor::BLACK; + } else { + return false; + } + } + + // TODO: Implement and add zobrist logic once castling is implemented + /*if (spaces == 2) { + if (character == '-') { + continue; + } else if (character == 'K') { + castlingRights |= WHITE_KINGSIDE; + continue; + } else if (character == 'Q') { + castlingRights |= WHITE_QUEENSIDE; + continue; + } else if (character == 'k') { + castlingRights |= BLACK_KINGSIDE; + continue; + } else if (character == 'q') { + castlingRights |= BLACK_QUEENSIDE; + continue; + } + + return false; + }*/ + + if (spaces == 3) { + if (character == '-') { + continue; + } + + if (tolower(character) < 'a' || tolower(character) > 'h') { + continue; + } + + const int8_t file = tolower(character) - 'a'; + // const int8_t rank = (!sideToMove) == PieceColor::WHITE ? 2 : 5; + + if (file < 0 || file > 7) { + return false; + } + + // TODO: Uncomment and add zobrist once en-passant is implemented + // enPassantSquare = rank * 8 + file; + index += 2; + } + + // TODO: Implement halfmove and fullmove clock + /*if (spaces == 4) { + halfMoveClock = character - '0'; + } + + if (spaces == 5) { + fullmoveClock = character - '0'; + }*/ + } + + return true; } } // namespace Zagreus diff --git a/src/board.h b/src/board.h index 3cf9488f..fa22e709 100644 --- a/src/board.h +++ b/src/board.h @@ -22,6 +22,9 @@ #pragma once #include +#include <__fwd/string_view.h> + +#include #include #include #include @@ -48,6 +51,13 @@ class Board { int ply = 0; public: + Board() { + std::ranges::fill(board, Piece::EMPTY); + std::ranges::fill(bitboards, 0); + std::ranges::fill(colorBoards, 0); + std::ranges::fill(history, BoardState{}); + } + template [[nodiscard]] uint64_t getBitboard() const { return bitboards[IDX(piece)]; @@ -84,7 +94,7 @@ class Board { } template - void setPiece(uint8_t square) { + void setPiece(const uint8_t square) { const uint64_t squareBB = squareToBitboard(square); board[square] = piece; @@ -93,7 +103,7 @@ class Board { colorBoards[IDX(pieceColor(piece))] |= squareBB; } - void setPiece(Piece piece, uint8_t square) { + void setPiece(const Piece piece, const uint8_t square) { const uint64_t squareBB = squareToBitboard(square); board[square] = piece; @@ -102,7 +112,7 @@ class Board { colorBoards[IDX(pieceColor(piece))] |= squareBB; } - void removePiece(uint8_t square) { + void removePiece(const uint8_t square) { const uint64_t squareBB = squareToBitboard(square); const Piece piece = board[square]; @@ -113,7 +123,16 @@ class Board { } template - void removePiece(uint8_t square) { + void removePiece(const uint8_t square) { + const uint64_t squareBB = squareToBitboard(square); + + board[square] = Piece::EMPTY; + bitboards[IDX(piece)] &= ~squareBB; + occupied &= ~squareBB; + colorBoards[IDX(pieceColor(piece))] &= ~squareBB; + } + + void removePiece(const Piece piece, const uint8_t square) { const uint64_t squareBB = squareToBitboard(square); board[square] = Piece::EMPTY; @@ -125,6 +144,7 @@ class Board { void makeMove(const Move& move); void unmakeMove(); + void setPieceFromFENChar(char character, uint8_t index); template [[nodiscard]] bool isPositionLegal() const; @@ -135,5 +155,9 @@ class Board { [[nodiscard]] uint64_t getSquareAttackersByColor(const uint8_t square) const { return getSquareAttackers(square) & getColorBitboard(); } + + bool setFromFEN(std::string_view fen); + + void reset(); }; } // namespace Zagreus diff --git a/src/magics.cpp b/src/magics.cpp index 26689438..bd27417b 100644 --- a/src/magics.cpp +++ b/src/magics.cpp @@ -2,7 +2,7 @@ This file is part of Zagreus. Zagreus is a chess engine that supports the UCI protocol - Copyright (C) 2023 Danny Jelsma + Copyright (C) 2023-2024 Danny Jelsma Zagreus is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -20,12 +20,10 @@ #include "magics.h" -#include #include #include #include #include -#include // Code for magic generation https://www.chessprogramming.org/Looking_for_Magics namespace Zagreus { @@ -351,7 +349,7 @@ uint64_t rook_attacks_on_the_fly(int8_t square, uint64_t block) { return attacks; } -void init_sliders_attacks(bool is_bishop) { +void init_sliders_attacks(int is_bishop) { // loop over 64 board squares for (int8_t square = 0; square < 64; square++) { // init bishop & rook masks @@ -392,9 +390,9 @@ void initializeMagicBitboards() { generateMagics(); // init bishop attacks - init_sliders_attacks(true); + init_sliders_attacks(1); // init rook attacks - init_sliders_attacks(false); + init_sliders_attacks(0); } void generateMagics() { diff --git a/src/magics.h b/src/magics.h index 9d6dd131..13876690 100644 --- a/src/magics.h +++ b/src/magics.h @@ -2,7 +2,7 @@ This file is part of Zagreus. Zagreus is a UCI chess engine - Copyright (C) 2023 Danny Jelsma + Copyright (C) 2023-2024 Danny Jelsma Zagreus is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published diff --git a/src/move.h b/src/move.h index bb541440..895942d5 100644 --- a/src/move.h +++ b/src/move.h @@ -51,10 +51,10 @@ inline Move encodeMove(const uint8_t fromSquare, const uint8_t toSquare, const u } inline uint8_t getFromSquare(const Move move) { - return (move >> 6) & 0b111111; + return move & 0b111111; } inline uint8_t getToSquare(const Move move) { - return move & 0b111111; + return (move >> 6) & 0b111111; } } // namespace Zagreus diff --git a/src/move_gen.cpp b/src/move_gen.cpp index 23af01aa..2c5ebf53 100644 --- a/src/move_gen.cpp +++ b/src/move_gen.cpp @@ -18,7 +18,6 @@ along with Zagreus. If not, see . */ -#include #include #include "move_gen.h" @@ -34,10 +33,9 @@ void generateMoves(const Board& board, MoveList& moves) { // TODO: Implement GenerationType logic using a mask that is computed based on type constexpr Piece opponentKing = color == PieceColor::WHITE ? Piece::BLACK_KING : Piece::WHITE_KING; - uint64_t ownPieces = board.getColorBitboard(); - uint64_t opponentKingBB = board.getBitboard(); - uint64_t genMask = std::numeric_limits::max(); - genMask &= ~ownPieces & ~opponentKingBB; + const uint64_t ownPieces = board.getColorBitboard(); + const uint64_t opponentKingBB = board.getBitboard(); + const uint64_t genMask = ~(ownPieces | opponentKingBB); generatePawnMoves(board, moves, genMask); generateKnightMoves(board, moves, genMask); @@ -231,12 +229,12 @@ void generateKingMoves(const Board& board, MoveList& moves, const uint64_t genMa } // explicit instantiation of generateMoves -template void generateMoves(const Board& board, MoveList& moves); -template void generateMoves(const Board& board, MoveList& moves); -template void generateMoves(const Board& board, MoveList& moves); -template void generateMoves(const Board& board, MoveList& moves); -template void generateMoves(const Board& board, MoveList& moves); -template void generateMoves(const Board& board, MoveList& moves); -template void generateMoves(const Board& board, MoveList& moves); -template void generateMoves(const Board& board, MoveList& moves); +template void generateMoves(const Board& board, MoveList& moves); +template void generateMoves(const Board& board, MoveList& moves); +template void generateMoves(const Board& board, MoveList& moves); +template void generateMoves(const Board& board, MoveList& moves); +template void generateMoves(const Board& board, MoveList& moves); +template void generateMoves(const Board& board, MoveList& moves); +template void generateMoves(const Board& board, MoveList& moves); +template void generateMoves(const Board& board, MoveList& moves); } // namespace Zagreus diff --git a/src/move_gen.h b/src/move_gen.h index a95effaa..2c91d3b6 100644 --- a/src/move_gen.h +++ b/src/move_gen.h @@ -21,7 +21,7 @@ #pragma once -#include +#include #include "board.h" #include "move.h" @@ -34,10 +34,10 @@ class Board; struct MoveList; enum class GenerationType : uint8_t { - All, - Quiet, - Capture, - Evasions + ALL, + QUIET, + CAPTURES, + EVASIONS }; template diff --git a/src/perft.cpp b/src/perft.cpp index 901eac1c..33bcb841 100644 --- a/src/perft.cpp +++ b/src/perft.cpp @@ -20,11 +20,13 @@ #include "perft.h" +#include + #include "move.h" #include "move_gen.h" namespace Zagreus { -uint64_t perft(Board &board, int depth) { +uint64_t perft(Board &board, const int depth, bool printNodes) { using enum PieceColor; using enum GenerationType; @@ -34,12 +36,12 @@ uint64_t perft(Board &board, int depth) { uint64_t nodes = 0; MoveList moveList{}; - PieceColor sideToMove = board.getSideToMove() == WHITE ? WHITE : BLACK + const PieceColor sideToMove = board.getSideToMove(); if (sideToMove == WHITE) { - generateMoves(board, moveList); + generateMoves(board, moveList); } else { - generateMoves(board, moveList); + generateMoves(board, moveList); } for (int i = 0; i < moveList.size; i++) { @@ -57,7 +59,16 @@ uint64_t perft(Board &board, int depth) { } } - nodes += perft(board, depth - 1); + uint64_t perftNodes = perft(board, depth - 1, false); + + if (printNodes) { + // TODO: Implement proper move notation, now it's just ints + std::string fromToNotation = std::to_string(getFromSquare(moveList.moves[i])) + std::to_string(getToSquare(moveList.moves[i])); + } + + std::cout << moveList.moves[i] << ": " << perftNodes << std::endl; + + nodes += perftNodes; board.unmakeMove(); } diff --git a/src/perft.h b/src/perft.h index afb32e2f..579ea23a 100644 --- a/src/perft.h +++ b/src/perft.h @@ -26,5 +26,5 @@ #include "types.h" namespace Zagreus { -uint64_t perft(const Board &board, int depth); +uint64_t perft(Board &board, int depth, bool printNodes = true); } // namespace Zagreus \ No newline at end of file diff --git a/src/types.h b/src/types.h index bb305b83..ea7cd8b2 100644 --- a/src/types.h +++ b/src/types.h @@ -21,6 +21,10 @@ #pragma once #include +#include +#include + +#include "macros.h" enum class Direction { NORTH, @@ -33,10 +37,28 @@ enum class Direction { SOUTH_WEST }; +// clang-format off +enum class Square: uint8_t { + A1, B1, C1, D1, E1, F1, G1, H1, + A2, B2, C2, D2, E2, F2, G2, H2, + A3, B3, C3, D3, E3, F3, G3, H3, + A4, B4, C4, D4, E4, F4, G4, H4, + A5, B5, C5, D5, E5, F5, G5, H5, + A6, B6, C6, D6, E6, F6, G6, H6, + A7, B7, C7, D7, E7, F7, G7, H7, + A8, B8, C8, D8, E8, F8, G8, H8, + NONE = 255 +}; +// clang-format on + enum class PieceType : uint8_t { PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, EMPTY = 255 }; enum class PieceColor : uint8_t { WHITE, BLACK, EMPTY = 255 }; +constexpr PieceColor operator!(const PieceColor color) { + return color == PieceColor::WHITE ? PieceColor::BLACK : PieceColor::WHITE; +} + enum class Piece : uint8_t { WHITE_PAWN, BLACK_PAWN, @@ -53,14 +75,10 @@ enum class Piece : uint8_t { EMPTY = 255 }; -inline constexpr PieceColor operator!(const PieceColor color) { - return color == PieceColor::WHITE ? PieceColor::BLACK : PieceColor::WHITE; -} - -inline constexpr PieceColor pieceColor(const Piece piece) { - return static_cast(static_cast(piece) / 2); +constexpr PieceColor pieceColor(const Piece piece) { + return static_cast(IDX(piece) % 2); } -inline constexpr PieceType pieceType(const Piece piece) { - return static_cast(static_cast(piece) % 2); +constexpr PieceType pieceType(const Piece piece) { + return static_cast(IDX(piece) / 2); } diff --git a/src/uci.cpp b/src/uci.cpp index b7bcef77..e5e2da8d 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -30,8 +30,11 @@ #include "uci.h" #include "bitboard.h" +#include "perft.h" namespace Zagreus { +constexpr std::string_view startPosFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; + void Engine::doSetup() { // According to the UCI specification, bitboard, magic bitboards and other stuff should be done only when "isready" or "setoption" is called if (didSetup) { @@ -45,6 +48,18 @@ void Engine::doSetup() { didSetup = true; } +std::string Engine::getVersionString() { + const std::string majorVersion = ZAGREUS_VERSION_MAJOR; + const std::string minorVersion = ZAGREUS_VERSION_MINOR; + std::string versionString = "v" + majorVersion + "." + minorVersion; + + if (majorVersion == "dev") { + versionString = majorVersion + "-" + minorVersion; + } + + return versionString; +} + void Engine::printStartupMessage() { sendMessage(" ______ "); sendMessage(" |___ / "); @@ -64,28 +79,12 @@ void Engine::printStartupMessage() { sendMessage("along with this program. If not, see ."); sendMessage(""); - const std::string majorVersion = ZAGREUS_VERSION_MAJOR; - const std::string minorVersion = ZAGREUS_VERSION_MINOR; - std::string versionString = "v" + majorVersion + "." + minorVersion; - - if (majorVersion == "dev") { - versionString = majorVersion + "-" + minorVersion; - } - - sendMessage("Zagreus UCI chess engine " + versionString + " by Danny Jelsma (https://github.com/Dannyj1/Zagreus)"); + sendMessage("Zagreus UCI chess engine " + getVersionString() + " by Danny Jelsma (https://github.com/Dannyj1/Zagreus)"); sendMessage(""); } void Engine::handleUciCommand() { - const std::string majorVersion = ZAGREUS_VERSION_MAJOR; - const std::string minorVersion = ZAGREUS_VERSION_MINOR; - std::string versionString = "v" + majorVersion + "." + minorVersion; - - if (majorVersion == "dev") { - versionString = majorVersion + "-" + minorVersion; - } - - sendMessage("id name Zagreus " + versionString); + sendMessage("id name Zagreus " + getVersionString()); sendMessage("id author Danny Jelsma"); if (!this->options.empty()) { @@ -199,12 +198,42 @@ void Engine::handleQuitCommand(std::string_view args) { } -void Engine::handlePerftCommand(std::string_view args) { +void Engine::handlePerftCommand(const std::string& args) { if (!didSetup) { doSetup(); } - // TODO: Need to add FEN processing before we can add this + if (args.empty() || args == " " || args == "\n") { + sendMessage("ERROR: No depth provided."); + return; + } + + if (args.find(' ') != std::string::npos) { + sendMessage("ERROR: Too many arguments provided."); + return; + } + + int depth = 0; + + try { + depth = std::stoi(args); + } catch (const std::invalid_argument& e) { + sendMessage("ERROR: Depth must be an integer."); + return; + } + + if (depth <= 0) { + sendMessage("ERROR: Depth must be at least 1."); + return; + } + + if (board.getOccupiedBitboard() == 0ULL) { + board.setFromFEN(startPosFEN); + } + + const uint64_t nodes = perft(board, depth); + + sendInfoMessage("nodes " + std::to_string(nodes)); } void Engine::processCommand(const std::string_view command, const std::string& args) { @@ -293,15 +322,15 @@ void Engine::startUci() { } } -void Engine::sendInfoMessage(std::string_view message) { +void Engine::sendInfoMessage(const std::string_view message) { std::cout << "info " << message << std::endl; } -void Engine::sendMessage(std::string_view message) { +void Engine::sendMessage(const std::string_view message) { std::cout << message << std::endl; } -std::string removeRedundantSpaces(std::string_view input) { +std::string removeRedundantSpaces(const std::string_view input) { std::string result; bool inSpace = false; // Track if we are in a sequence of spaces/tabs diff --git a/src/uci.h b/src/uci.h index 21a1c216..eba33c16 100644 --- a/src/uci.h +++ b/src/uci.h @@ -47,7 +47,7 @@ class Engine { void handleStopCommand(std::string_view args); void handlePonderHitCommand(std::string_view args); void handleQuitCommand(std::string_view args); - void handlePerftCommand(std::string_view args); + void handlePerftCommand(const std::string& args); void processCommand(std::string_view command, const std::string& args); public: @@ -55,6 +55,7 @@ class Engine { void sendInfoMessage(std::string_view message); void sendMessage(std::string_view message); void doSetup(); + [[nodiscard]] static std::string getVersionString() ; void printStartupMessage(); void addOption(UCIOption& option); UCIOption& getOption(const std::string& name); diff --git a/tests/types_tests.cpp b/tests/types_tests.cpp new file mode 100644 index 00000000..7c6bb5e7 --- /dev/null +++ b/tests/types_tests.cpp @@ -0,0 +1,63 @@ +/* + This file is part of Zagreus. + + Zagreus is a UCI chess engine + Copyright (C) 2023-2024 Danny Jelsma + + Zagreus is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Zagreus 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Zagreus. If not, see . + */ + +#include "catch2/catch_test_macros.hpp" + +#include "../src/types.h" + +// Test all pieces at once for correct identification of piece type +TEST_CASE("test_pieceType", "[types]") { + REQUIRE(pieceType(Piece::WHITE_PAWN) == PieceType::PAWN); + REQUIRE(pieceType(Piece::BLACK_PAWN) == PieceType::PAWN); + REQUIRE(pieceType(Piece::WHITE_KNIGHT) == PieceType::KNIGHT); + REQUIRE(pieceType(Piece::BLACK_KNIGHT) == PieceType::KNIGHT); + REQUIRE(pieceType(Piece::WHITE_BISHOP) == PieceType::BISHOP); + REQUIRE(pieceType(Piece::BLACK_BISHOP) == PieceType::BISHOP); + REQUIRE(pieceType(Piece::WHITE_ROOK) == PieceType::ROOK); + REQUIRE(pieceType(Piece::BLACK_ROOK) == PieceType::ROOK); + REQUIRE(pieceType(Piece::WHITE_QUEEN) == PieceType::QUEEN); + REQUIRE(pieceType(Piece::BLACK_QUEEN) == PieceType::QUEEN); + REQUIRE(pieceType(Piece::WHITE_KING) == PieceType::KING); + REQUIRE(pieceType(Piece::BLACK_KING) == PieceType::KING); +} + +// Test the pieceColor function for determining the color of all pieces as white or black +TEST_CASE("test_pieceColor", "[types]") { + // Given + std::vector pieces = { + Piece::WHITE_PAWN, Piece::BLACK_PAWN, + Piece::WHITE_KNIGHT, Piece::BLACK_KNIGHT, + Piece::WHITE_BISHOP, Piece::BLACK_BISHOP, + Piece::WHITE_ROOK, Piece::BLACK_ROOK, + Piece::WHITE_QUEEN, Piece::BLACK_QUEEN, + Piece::WHITE_KING, Piece::BLACK_KING + }; + + // When & Then + for (const auto& piece : pieces) { + PieceColor color = pieceColor(piece); + if (piece == Piece::WHITE_PAWN || piece == Piece::WHITE_KNIGHT || piece == Piece::WHITE_BISHOP || + piece == Piece::WHITE_ROOK || piece == Piece::WHITE_QUEEN || piece == Piece::WHITE_KING) { + REQUIRE(color == PieceColor::WHITE); + } else { + REQUIRE(color == PieceColor::BLACK); + } + } +} From f257129abfe76b245f3f41a68a51d6a1e0c159db Mon Sep 17 00:00:00 2001 From: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> Date: Wed, 9 Oct 2024 17:35:15 +0200 Subject: [PATCH 22/91] Fixed many move generation issues, perft accurate up until depth 4 --- src/bitboard.cpp | 38 ++++++++++++++++++++++++++++++++++---- src/bitboard.h | 41 ++++++++++++++++++++++++----------------- src/bitwise.h | 1 + src/board.cpp | 33 +++++++++++++++++++-------------- src/board.h | 30 +++++++++++++++--------------- src/macros.h | 4 ++-- src/move.cpp | 35 +++++++++++++++++++++++++++++++++++ src/move.h | 22 ++++++++++++++-------- src/move_gen.cpp | 24 ++++++++++++------------ src/move_gen.h | 3 --- src/perft.cpp | 9 +++++---- src/types.h | 34 ++++++++++++++++++++++------------ src/uci.cpp | 2 +- 13 files changed, 184 insertions(+), 92 deletions(-) create mode 100644 src/move.cpp diff --git a/src/bitboard.cpp b/src/bitboard.cpp index daabc398..da04a225 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -24,16 +24,44 @@ This file is part of Zagreus. #include "magics.h" namespace Zagreus { +static std::array, COLORS> pawnAttacksTable{}; +static std::array knightAttacksTable{}; +static std::array kingAttacksTable{}; + void initializeAttackLookupTables() { for (uint8_t square = 0; square < SQUARES; ++square) { - pawnAttacksTable[IDX(PieceColor::WHITE)][square] = calculateWhitePawnAttacks(square); - pawnAttacksTable[IDX(PieceColor::BLACK)][square] = calculateBlackPawnAttacks(square); - knightAttacksTable[square] = calculateKnightAttacks(square); - kingAttacksTable[square] = calculateKingAttacks(square); + const uint64_t bb = squareToBitboard(square); + + pawnAttacksTable[TO_INT(PieceColor::WHITE)][square] = calculateWhitePawnAttacks(bb); + pawnAttacksTable[TO_INT(PieceColor::BLACK)][square] = calculateBlackPawnAttacks(bb); + knightAttacksTable[square] = calculateKnightAttacks(bb); + kingAttacksTable[square] = calculateKingAttacks(bb); } } +template +uint64_t pawnAttacks(const uint8_t square) { + assert(color != PieceColor::EMPTY); + assert(square < SQUARES); + + return pawnAttacksTable[TO_INT(color)][square]; +} + +template uint64_t pawnAttacks(uint8_t square); +template uint64_t pawnAttacks(uint8_t square); + +uint64_t knightAttacks(const uint8_t square) { + assert(square < SQUARES); + return knightAttacksTable[square]; +} + +uint64_t kingAttacks(const uint8_t square) { + assert(square < SQUARES); + return kingAttacksTable[square]; +} + uint64_t bishopAttacks(const uint8_t square, uint64_t occupied) { + assert(square < SQUARES); occupied &= getBishopMask(square); occupied *= getBishopMagic(square); occupied >>= 64 - BBits[square]; @@ -42,6 +70,7 @@ uint64_t bishopAttacks(const uint8_t square, uint64_t occupied) { } uint64_t rookAttacks(const uint8_t square, uint64_t occupied) { + assert(square < SQUARES); occupied &= getRookMask(square); occupied *= getRookMagic(square); occupied >>= 64 - RBits[square]; @@ -50,6 +79,7 @@ uint64_t rookAttacks(const uint8_t square, uint64_t occupied) { } uint64_t queenAttacks(const uint8_t square, const uint64_t occupied) { + assert(square < SQUARES); return bishopAttacks(square, occupied) | rookAttacks(square, occupied); } } // namespace Zagreus \ No newline at end of file diff --git a/src/bitboard.h b/src/bitboard.h index bb00cbbd..6e151be8 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -30,10 +30,6 @@ #include "types.h" namespace Zagreus { -static std::array, COLORS> pawnAttacksTable{}; -static std::array knightAttacksTable{}; -static std::array kingAttacksTable{}; - void initializeAttackLookupTables(); inline uint64_t shiftNorth(const uint64_t bb) { @@ -120,7 +116,20 @@ constexpr uint64_t shift(const uint64_t bb) { return shiftSouthEast(bb); case SOUTH_WEST: return shiftSouthWest(bb); + case NORTH_NORTH_EAST: + return shiftNorthNorthEast(bb); + case NORTH_EAST_EAST: + return shiftNorthEastEast(bb); + case SOUTH_EAST_EAST: + return shiftSouthEastEast(bb); + case SOUTH_SOUTH_EAST: + return shiftSouthSouthEast(bb); + case SOUTH_SOUTH_WEST: + return shiftSouthSouthWest(bb); + case SOUTH_WEST_WEST: + return shiftSouthWestWest(bb); default: + assert(false); return bb; } } @@ -147,11 +156,6 @@ inline uint64_t calculateWhitePawnAttacks(const uint64_t bb) { return whitePawnWestAttacks(bb) | whitePawnEastAttacks(bb); } -template -uint64_t pawnAttacks(const uint8_t square) { - return pawnAttacksTable[IDX(color)][square]; -} - inline uint64_t whitePushablePawns(const uint64_t bb, const uint64_t empty) { return shiftSouth(empty) & bb; } @@ -200,9 +204,12 @@ inline uint64_t calculateKnightAttacks(const uint64_t bb) { shiftNorthWestWest(bb) | shiftNorthNorthWest(bb); } -inline uint64_t knightAttacks(const uint8_t square) { - return knightAttacksTable[square]; -} +template +uint64_t pawnAttacks(uint8_t square); + +uint64_t knightAttacks(uint8_t square); + +uint64_t kingAttacks(uint8_t square); uint64_t bishopAttacks(uint8_t square, uint64_t occupied); @@ -217,11 +224,11 @@ inline uint64_t calculateKingAttacks(uint64_t bb) { return attacks | shiftNorth(bb) | shiftSouth(bb); } -inline uint64_t kingAttacks(const uint8_t square) { - return kingAttacksTable[square]; +inline uint64_t squareToBitboard(const uint8_t square) { + return 1ULL << square; } -inline uint64_t squareToBitboard(const uint8_t square) { return 1ULL << square; } - -inline uint8_t bitboardToSquare(const uint64_t bb) { return bitscanForward(bb); } +inline uint8_t bitboardToSquare(const uint64_t bb) { + return bitscanForward(bb); +} } // namespace Zagreus diff --git a/src/bitwise.h b/src/bitwise.h index 9c84de6f..2ea2898d 100644 --- a/src/bitwise.h +++ b/src/bitwise.h @@ -57,6 +57,7 @@ inline int bitscanReverse(const uint64_t bb) { } inline int popLsb(uint64_t& bb) { + assert(bb != 0); const int lsb = bitscanForward(bb); bb &= bb - 1; diff --git a/src/board.cpp b/src/board.cpp index 4c75427d..ea7a206d 100644 --- a/src/board.cpp +++ b/src/board.cpp @@ -39,6 +39,7 @@ template bool Board::isPositionLegal() const; template bool Board::isPositionLegal() const; uint64_t Board::getSquareAttackers(const uint8_t square) const { + assert(square < SQUARES); using enum Piece; const uint64_t knights = getBitboard() | getBitboard(); @@ -85,13 +86,16 @@ void Board::makeMove(const Move& move) { setPiece(movedPiece, toSquare); sideToMove = !sideToMove; + assert(ply >= 0 && ply < MAX_PLY); history[ply].move = move; history[ply].capturedPiece = capturedPiece; ply++; + assert(ply >= 0 && ply < MAX_PLY); } void Board::unmakeMove() { ply--; + assert(ply >= 0 && ply < MAX_PLY); const auto [move, capturedPiece] = history[ply]; const uint8_t fromSquare = getFromSquare(move); const uint8_t toSquare = getToSquare(move); @@ -108,46 +112,47 @@ void Board::unmakeMove() { } -void Board::setPieceFromFENChar(const char character, const uint8_t index) { +void Board::setPieceFromFENChar(const char character, const uint8_t square) { + assert(square < SQUARES); using enum Piece; // Uppercase char = white, lowercase = black switch (character) { case 'P': - setPiece(WHITE_PAWN, index); + setPiece(WHITE_PAWN, square); break; case 'p': - setPiece(BLACK_PAWN, index); + setPiece(BLACK_PAWN, square); break; case 'N': - setPiece(WHITE_KNIGHT, index); + setPiece(WHITE_KNIGHT, square); break; case 'n': - setPiece(BLACK_KNIGHT, index); + setPiece(BLACK_KNIGHT, square); break; case 'B': - setPiece(WHITE_BISHOP, index); + setPiece(WHITE_BISHOP, square); break; case 'b': - setPiece(BLACK_BISHOP, index); + setPiece(BLACK_BISHOP, square); break; case 'R': - setPiece(WHITE_ROOK, index); + setPiece(WHITE_ROOK, square); break; case 'r': - setPiece(BLACK_ROOK, index); + setPiece(BLACK_ROOK, square); break; case 'Q': - setPiece(WHITE_QUEEN, index); + setPiece(WHITE_QUEEN, square); break; case 'q': - setPiece(BLACK_QUEEN, index); + setPiece(BLACK_QUEEN, square); break; case 'K': - setPiece(WHITE_KING, index); + setPiece(WHITE_KING, square); break; case 'k': - setPiece(BLACK_KING, index); + setPiece(BLACK_KING, square); break; default: break; @@ -156,7 +161,7 @@ void Board::setPieceFromFENChar(const char character, const uint8_t index) { bool Board::setFromFEN(const std::string_view fen) { // TODO: Update zobrist hash once that is implemented - uint8_t index = IDX(Square::A8); + uint8_t index = TO_INT(Square::A8); int spaces = 0; reset(); diff --git a/src/board.h b/src/board.h index fa22e709..ebd4107f 100644 --- a/src/board.h +++ b/src/board.h @@ -21,8 +21,8 @@ #pragma once -#include -#include <__fwd/string_view.h> +#include +#include #include #include @@ -60,12 +60,12 @@ class Board { template [[nodiscard]] uint64_t getBitboard() const { - return bitboards[IDX(piece)]; + return bitboards[TO_INT(piece)]; } template [[nodiscard]] uint64_t getColorBitboard() const { - return colorBoards[IDX(color)]; + return colorBoards[TO_INT(color)]; } [[nodiscard]] Piece getPieceOnSquare(const int square) const { @@ -98,18 +98,18 @@ class Board { const uint64_t squareBB = squareToBitboard(square); board[square] = piece; - bitboards[IDX(piece)] |= squareBB; + bitboards[TO_INT(piece)] |= squareBB; occupied |= squareBB; - colorBoards[IDX(pieceColor(piece))] |= squareBB; + colorBoards[TO_INT(pieceColor(piece))] |= squareBB; } void setPiece(const Piece piece, const uint8_t square) { const uint64_t squareBB = squareToBitboard(square); board[square] = piece; - bitboards[IDX(piece)] |= squareBB; + bitboards[TO_INT(piece)] |= squareBB; occupied |= squareBB; - colorBoards[IDX(pieceColor(piece))] |= squareBB; + colorBoards[TO_INT(pieceColor(piece))] |= squareBB; } void removePiece(const uint8_t square) { @@ -117,9 +117,9 @@ class Board { const Piece piece = board[square]; board[square] = Piece::EMPTY; - bitboards[IDX(piece)] &= ~squareBB; + bitboards[TO_INT(piece)] &= ~squareBB; occupied &= ~squareBB; - colorBoards[IDX(pieceColor(piece))] &= ~squareBB; + colorBoards[TO_INT(pieceColor(piece))] &= ~squareBB; } template @@ -127,24 +127,24 @@ class Board { const uint64_t squareBB = squareToBitboard(square); board[square] = Piece::EMPTY; - bitboards[IDX(piece)] &= ~squareBB; + bitboards[TO_INT(piece)] &= ~squareBB; occupied &= ~squareBB; - colorBoards[IDX(pieceColor(piece))] &= ~squareBB; + colorBoards[TO_INT(pieceColor(piece))] &= ~squareBB; } void removePiece(const Piece piece, const uint8_t square) { const uint64_t squareBB = squareToBitboard(square); board[square] = Piece::EMPTY; - bitboards[IDX(piece)] &= ~squareBB; + bitboards[TO_INT(piece)] &= ~squareBB; occupied &= ~squareBB; - colorBoards[IDX(pieceColor(piece))] &= ~squareBB; + colorBoards[TO_INT(pieceColor(piece))] &= ~squareBB; } void makeMove(const Move& move); void unmakeMove(); - void setPieceFromFENChar(char character, uint8_t index); + void setPieceFromFENChar(char character, uint8_t square); template [[nodiscard]] bool isPositionLegal() const; diff --git a/src/macros.h b/src/macros.h index 5707dcfe..8de93ff2 100644 --- a/src/macros.h +++ b/src/macros.h @@ -23,5 +23,5 @@ #include -// Converts an enum class to its underlying type. Useful for indexing arrays, hence the name IDX (index). -#define IDX(x) std::to_underlying(x) +// Converts an enum class to its underlying type, usually an int. +#define TO_INT(x) std::to_underlying(x) diff --git a/src/move.cpp b/src/move.cpp new file mode 100644 index 00000000..7e597d63 --- /dev/null +++ b/src/move.cpp @@ -0,0 +1,35 @@ +/* + This file is part of Zagreus. + + Zagreus is a UCI chess engine + Copyright (C) 2023-2024 Danny Jelsma + + Zagreus is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Zagreus 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with Zagreus. If not, see . + */ + +#include "move.h" + +#include + +namespace Zagreus { +std::string getMoveNotation(const uint8_t square) { + assert(square < SQUARES); + std::string notation; + + notation += static_cast(square % 8 + 'a'); + notation += static_cast(square / 8 + '1'); + + return notation; +} +} // namespace Zagreus diff --git a/src/move.h b/src/move.h index 895942d5..9ed2a842 100644 --- a/src/move.h +++ b/src/move.h @@ -20,6 +20,10 @@ */ #pragma once + +#include +#include + #include "constants.h" @@ -31,30 +35,32 @@ struct MoveList { uint8_t size = 0; }; +std::string getMoveNotation(uint8_t square); + // bits 0-5: from square (0-63) // bits 6-11: to square (0-63) // bits 12-13: move type (00 = normal, 01 = promotion, 10 = en passant, 11 = castling) // bits 14-15: promotion piece (00 = queen, 01 = rook, 10 = bishop, 11 = knight) inline Move encodeMove(const uint8_t fromSquare, const uint8_t toSquare) { - // Assume normal move, so 12-13 are 00 - return static_cast(toSquare << 6) | static_cast(fromSquare); + // Assume normal move, so bits 12-13 are 00 and bits 14-15 are 00 + return static_cast(fromSquare | (toSquare << 6)); } inline Move encodeMove(const uint8_t fromSquare, const uint8_t toSquare, const uint8_t moveType) { - return static_cast(toSquare << 6) | static_cast(fromSquare) - | static_cast(moveType << 12); + return static_cast(fromSquare | (toSquare << 6) | (moveType << 12)); } inline Move encodeMove(const uint8_t fromSquare, const uint8_t toSquare, const uint8_t moveType, const uint8_t promotionPiece) { - return static_cast(toSquare << 6) | static_cast(fromSquare) - | static_cast(moveType << 12) | static_cast(promotionPiece << 14); + return static_cast(fromSquare | (toSquare << 6) | (moveType << 12) | (promotionPiece << 14)); } inline uint8_t getFromSquare(const Move move) { - return move & 0b111111; + // Extract bits 0-5 + return static_cast(move & 0x3F); } inline uint8_t getToSquare(const Move move) { - return (move >> 6) & 0b111111; + // Extract bits 6-11 + return static_cast((move >> 6) & 0x3F); } } // namespace Zagreus diff --git a/src/move_gen.cpp b/src/move_gen.cpp index 2c5ebf53..58af473e 100644 --- a/src/move_gen.cpp +++ b/src/move_gen.cpp @@ -30,6 +30,9 @@ namespace Zagreus { template void generateMoves(const Board& board, MoveList& moves) { + assert(color != PieceColor::EMPTY); + assert(moves.size == 0); + // TODO: Implement GenerationType logic using a mask that is computed based on type constexpr Piece opponentKing = color == PieceColor::WHITE ? Piece::BLACK_KING : Piece::WHITE_KING; @@ -78,17 +81,13 @@ void generatePawnMoves(const Board& board, MoveList& moves, const uint64_t genMa pawnWestAttacks &= opponentPieces & genMask; pawnEastAttacks &= opponentPieces & genMask; - constexpr Direction fromPushDirection = color == PieceColor::WHITE ? Direction::SOUTH : Direction::NORTH; - constexpr Direction fromSqWestAttackDirection = color == PieceColor::WHITE - ? Direction::SOUTH_WEST - : Direction::NORTH_WEST; - constexpr Direction fromSqEastAttackDirection = color == PieceColor::WHITE - ? Direction::SOUTH_EAST - : Direction::NORTH_EAST; + constexpr Direction fromPushDirection = color == PieceColor::WHITE ? Direction::NORTH : Direction::SOUTH; + constexpr Direction fromSqWestAttackDirection = color == PieceColor::WHITE ? Direction::NORTH_WEST : Direction::SOUTH_WEST; + constexpr Direction fromSqEastAttackDirection = color == PieceColor::WHITE ? Direction::NORTH_EAST : Direction::SOUTH_EAST; while (pawnSinglePushes) { const uint8_t squareTo = popLsb(pawnSinglePushes); - const uint8_t squareFrom = shift(squareTo); + const uint8_t squareFrom = squareTo - TO_INT(fromPushDirection); const Move move = encodeMove(squareFrom, squareTo); moves.moves[moves.size] = move; @@ -97,7 +96,7 @@ void generatePawnMoves(const Board& board, MoveList& moves, const uint64_t genMa while (pawnDoublePushes) { const uint8_t squareTo = popLsb(pawnDoublePushes); - const uint8_t squareFrom = shift(squareTo); + const uint8_t squareFrom = squareTo - TO_INT(fromPushDirection) - TO_INT(fromPushDirection); const Move move = encodeMove(squareFrom, squareTo); moves.moves[moves.size] = move; @@ -106,7 +105,7 @@ void generatePawnMoves(const Board& board, MoveList& moves, const uint64_t genMa while (pawnWestAttacks) { const uint8_t squareTo = popLsb(pawnWestAttacks); - const uint8_t squareFrom = shift(squareTo); + const uint8_t squareFrom = squareTo - TO_INT(fromSqWestAttackDirection); const Move move = encodeMove(squareFrom, squareTo); moves.moves[moves.size] = move; @@ -115,7 +114,7 @@ void generatePawnMoves(const Board& board, MoveList& moves, const uint64_t genMa while (pawnEastAttacks) { const uint8_t squareTo = popLsb(pawnEastAttacks); - const uint8_t squareFrom = shift(squareTo); + const uint8_t squareFrom = squareTo - TO_INT(fromSqEastAttackDirection); const Move move = encodeMove(squareFrom, squareTo); moves.moves[moves.size] = move; @@ -131,7 +130,8 @@ void generateKnightMoves(const Board& board, MoveList& moves, const uint64_t gen while (knightBB) { const uint8_t fromSquare = popLsb(knightBB); - uint64_t genBB = knightAttacks(fromSquare) & genMask; + uint64_t attacks = knightAttacks(fromSquare); + uint64_t genBB = attacks & genMask; while (genBB) { const uint8_t toSquare = popLsb(genBB); diff --git a/src/move_gen.h b/src/move_gen.h index 2c91d3b6..5827e7ff 100644 --- a/src/move_gen.h +++ b/src/move_gen.h @@ -30,9 +30,6 @@ enum class PieceColor : uint8_t; namespace Zagreus { -class Board; -struct MoveList; - enum class GenerationType : uint8_t { ALL, QUIET, diff --git a/src/perft.cpp b/src/perft.cpp index 33bcb841..cbc80e1c 100644 --- a/src/perft.cpp +++ b/src/perft.cpp @@ -27,6 +27,7 @@ namespace Zagreus { uint64_t perft(Board &board, const int depth, bool printNodes) { + assert(depth >= 0); using enum PieceColor; using enum GenerationType; @@ -62,11 +63,11 @@ uint64_t perft(Board &board, const int depth, bool printNodes) { uint64_t perftNodes = perft(board, depth - 1, false); if (printNodes) { - // TODO: Implement proper move notation, now it's just ints - std::string fromToNotation = std::to_string(getFromSquare(moveList.moves[i])) + std::to_string(getToSquare(moveList.moves[i])); - } + std::string fromNotation = getMoveNotation(getFromSquare(moveList.moves[i])); + std::string toNotation = getMoveNotation(getToSquare(moveList.moves[i])); - std::cout << moveList.moves[i] << ": " << perftNodes << std::endl; + std::cout << fromNotation << toNotation << ": " << perftNodes << std::endl; + } nodes += perftNodes; board.unmakeMove(); diff --git a/src/types.h b/src/types.h index ea7cd8b2..75b857be 100644 --- a/src/types.h +++ b/src/types.h @@ -21,20 +21,27 @@ #pragma once #include -#include -#include +#include #include "macros.h" enum class Direction { - NORTH, - SOUTH, - EAST, - WEST, - NORTH_EAST, - NORTH_WEST, - SOUTH_EAST, - SOUTH_WEST + NORTH = 8, + SOUTH = -8, + EAST = 1, + WEST = -1, + NORTH_EAST = 9, + NORTH_WEST = 7, + SOUTH_EAST = -7, + SOUTH_WEST = -9, + NORTH_NORTH_EAST = 17, + NORTH_EAST_EAST = 10, + SOUTH_EAST_EAST = -6, + SOUTH_SOUTH_EAST = -15, + SOUTH_SOUTH_WEST = -17, + SOUTH_WEST_WEST = -10, + NORTH_WEST_WEST = -17, + NORTH_NORTH_WEST = -15 }; // clang-format off @@ -56,6 +63,7 @@ enum class PieceType : uint8_t { PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, EMPTY enum class PieceColor : uint8_t { WHITE, BLACK, EMPTY = 255 }; constexpr PieceColor operator!(const PieceColor color) { + assert(color != PieceColor::EMPTY); return color == PieceColor::WHITE ? PieceColor::BLACK : PieceColor::WHITE; } @@ -76,9 +84,11 @@ enum class Piece : uint8_t { }; constexpr PieceColor pieceColor(const Piece piece) { - return static_cast(IDX(piece) % 2); + assert(piece != Piece::EMPTY); + return static_cast(TO_INT(piece) % 2); } constexpr PieceType pieceType(const Piece piece) { - return static_cast(IDX(piece) / 2); + assert(piece != Piece::EMPTY); + return static_cast(TO_INT(piece) / 2); } diff --git a/src/uci.cpp b/src/uci.cpp index e5e2da8d..fb1bf932 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -208,7 +208,7 @@ void Engine::handlePerftCommand(const std::string& args) { return; } - if (args.find(' ') != std::string::npos) { + if (args.contains(' ')) { sendMessage("ERROR: Too many arguments provided."); return; } From 14f567adc821d3843d03630ac8ac1d7ee911d848 Mon Sep 17 00:00:00 2001 From: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> Date: Wed, 9 Oct 2024 19:04:03 +0200 Subject: [PATCH 23/91] Implemented en passant, perft accurate up until depth 5. Depth 6 is incorrect, sot here is an issue somewhere. --- src/board.cpp | 34 ++++++++++++++++++++++++++++++++-- src/board.h | 6 ++++++ src/constants.h | 6 ++++++ src/move_gen.cpp | 15 +++++++++++---- 4 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/board.cpp b/src/board.cpp index ea7a206d..a95d5209 100644 --- a/src/board.cpp +++ b/src/board.cpp @@ -20,6 +20,8 @@ #include "board.h" +#include +#include #include #include "bitwise.h" @@ -76,6 +78,7 @@ void Board::makeMove(const Move& move) { const uint8_t fromSquare = getFromSquare(move); const uint8_t toSquare = getToSquare(move); const Piece movedPiece = getPieceOnSquare(fromSquare); + const PieceType movedPieceType = pieceType(movedPiece); const Piece capturedPiece = getPieceOnSquare(toSquare); if (capturedPiece != Piece::EMPTY) { @@ -85,10 +88,36 @@ void Board::makeMove(const Move& move) { removePiece(movedPiece, fromSquare); setPiece(movedPiece, toSquare); + if (movedPieceType == PieceType::PAWN) { + // if destination is enPassant square and the move is diagonal, remove the captured pawn + if (toSquare == enPassantSquare && (toSquare - fromSquare) % 8 != 0) { + if (sideToMove == PieceColor::WHITE) { + removePiece(Piece::BLACK_PAWN, toSquare + TO_INT(Direction::SOUTH)); + } else { + removePiece(Piece::WHITE_PAWN, toSquare + TO_INT(Direction::NORTH)); + } + } + + const uint8_t moveDistance = std::abs(toSquare - fromSquare); + + if (moveDistance == 16) { + if (sideToMove == PieceColor::WHITE) { + enPassantSquare = fromSquare + TO_INT(Direction::NORTH); + } else { + enPassantSquare = fromSquare + TO_INT(Direction::SOUTH); + } + } else { + enPassantSquare = 0; + } + } else { + enPassantSquare = 0; + } + sideToMove = !sideToMove; assert(ply >= 0 && ply < MAX_PLY); history[ply].move = move; history[ply].capturedPiece = capturedPiece; + history[ply].enPassantSquare = enPassantSquare; ply++; assert(ply >= 0 && ply < MAX_PLY); } @@ -96,7 +125,7 @@ void Board::makeMove(const Move& move) { void Board::unmakeMove() { ply--; assert(ply >= 0 && ply < MAX_PLY); - const auto [move, capturedPiece] = history[ply]; + const auto& [move, capturedPiece, enPassantSquare] = history[ply]; const uint8_t fromSquare = getFromSquare(move); const uint8_t toSquare = getToSquare(move); const Piece movedPiece = getPieceOnSquare(toSquare); @@ -108,7 +137,8 @@ void Board::unmakeMove() { setPiece(capturedPiece, toSquare); } - sideToMove = !sideToMove; + this->sideToMove = !sideToMove; + this->enPassantSquare = enPassantSquare; } diff --git a/src/board.h b/src/board.h index ebd4107f..82888b4c 100644 --- a/src/board.h +++ b/src/board.h @@ -38,6 +38,7 @@ namespace Zagreus { struct BoardState { Move move = 0; Piece capturedPiece = Piece::EMPTY; + uint8_t enPassantSquare = 0; }; class Board { @@ -49,6 +50,7 @@ class Board { PieceColor sideToMove = PieceColor::EMPTY; std::array history{}; int ply = 0; + uint8_t enPassantSquare = 0; public: Board() { @@ -156,6 +158,10 @@ class Board { return getSquareAttackers(square) & getColorBitboard(); } + [[nodiscard]] uint8_t getEnPassantSquare() const { + return enPassantSquare; + } + bool setFromFEN(std::string_view fen); void reset(); diff --git a/src/constants.h b/src/constants.h index 6c78b443..d971c0a8 100644 --- a/src/constants.h +++ b/src/constants.h @@ -30,8 +30,14 @@ constexpr uint64_t NOT_AB_FILE = 0xFCFCFCFCFCFCFCFCULL; constexpr uint64_t NOT_GH_FILE = 0x3F3F3F3F3F3F3F3FULL; constexpr uint64_t NOT_H_FILE = 0x7F7F7F7F7F7F7F7FULL; +constexpr uint64_t RANK_1 = 0x00000000000000FFULL; +constexpr uint64_t RANK_2 = 0x000000000000FF00ULL; +constexpr uint64_t RANK_3 = 0x0000000000FF0000ULL; constexpr uint64_t RANK_4 = 0x00000000FF000000ULL; constexpr uint64_t RANK_5 = 0x000000FF00000000ULL; +constexpr uint64_t RANK_6 = 0x0000FF0000000000ULL; +constexpr uint64_t RANK_7 = 0x00FF000000000000ULL; +constexpr uint64_t RANK_8 = 0xFF00000000000000ULL; constexpr uint8_t SQUARES = 64; constexpr uint8_t PIECES = 12; diff --git a/src/move_gen.cpp b/src/move_gen.cpp index 58af473e..18d32e8a 100644 --- a/src/move_gen.cpp +++ b/src/move_gen.cpp @@ -35,7 +35,6 @@ void generateMoves(const Board& board, MoveList& moves) { // TODO: Implement GenerationType logic using a mask that is computed based on type constexpr Piece opponentKing = color == PieceColor::WHITE ? Piece::BLACK_KING : Piece::WHITE_KING; - const uint64_t ownPieces = board.getColorBitboard(); const uint64_t opponentKingBB = board.getBitboard(); const uint64_t genMask = ~(ownPieces | opponentKingBB); @@ -76,10 +75,18 @@ void generatePawnMoves(const Board& board, MoveList& moves, const uint64_t genMa pawnEastAttacks = blackPawnEastAttacks(pawnBB); } + uint64_t enPassantMask = squareToBitboard(board.getEnPassantSquare()); + + if constexpr (color == PieceColor::WHITE) { + enPassantMask &= RANK_6; + } else { + enPassantMask &= RANK_3; + } + pawnSinglePushes &= genMask; pawnDoublePushes &= genMask; - pawnWestAttacks &= opponentPieces & genMask; - pawnEastAttacks &= opponentPieces & genMask; + pawnWestAttacks &= (opponentPieces | enPassantMask) & genMask; + pawnEastAttacks &= (opponentPieces | enPassantMask) & genMask; constexpr Direction fromPushDirection = color == PieceColor::WHITE ? Direction::NORTH : Direction::SOUTH; constexpr Direction fromSqWestAttackDirection = color == PieceColor::WHITE ? Direction::NORTH_WEST : Direction::SOUTH_WEST; @@ -130,7 +137,7 @@ void generateKnightMoves(const Board& board, MoveList& moves, const uint64_t gen while (knightBB) { const uint8_t fromSquare = popLsb(knightBB); - uint64_t attacks = knightAttacks(fromSquare); + const uint64_t attacks = knightAttacks(fromSquare); uint64_t genBB = attacks & genMask; while (genBB) { From 0a70888bdd793595a7228a6e69a4d60ce54b1364 Mon Sep 17 00:00:00 2001 From: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> Date: Wed, 9 Oct 2024 19:05:34 +0200 Subject: [PATCH 24/91] Remove some TODO comments --- src/move_gen.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/move_gen.cpp b/src/move_gen.cpp index 18d32e8a..db08498f 100644 --- a/src/move_gen.cpp +++ b/src/move_gen.cpp @@ -49,9 +49,7 @@ void generateMoves(const Board& board, MoveList& moves) { template void generatePawnMoves(const Board& board, MoveList& moves, const uint64_t genMask) { - // TODO: Implement en passant // TODO: Handle promotions - // TODO: Move attacks to table lookup constexpr Piece pawn = color == PieceColor::WHITE ? Piece::WHITE_PAWN : Piece::BLACK_PAWN; constexpr PieceColor opponentColor = !color; @@ -131,7 +129,6 @@ void generatePawnMoves(const Board& board, MoveList& moves, const uint64_t genMa template void generateKnightMoves(const Board& board, MoveList& moves, const uint64_t genMask) { - // TODO: Implement table lookup constexpr Piece knight = color == PieceColor::WHITE ? Piece::WHITE_KNIGHT : Piece::BLACK_KNIGHT; uint64_t knightBB = board.getBitboard(); @@ -215,7 +212,6 @@ void generateQueenMoves(const Board& board, MoveList& moves, const uint64_t genM template void generateKingMoves(const Board& board, MoveList& moves, const uint64_t genMask) { - // TODO: Implement table lookup // TODO: Implement castling using enum Piece; constexpr Piece king = color == PieceColor::WHITE ? WHITE_KING : BLACK_KING; From bf3790a0ab1fa432a43c36aad150a5109725e0b5 Mon Sep 17 00:00:00 2001 From: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> Date: Thu, 10 Oct 2024 21:05:47 +0200 Subject: [PATCH 25/91] Implemented position command, fixed some movegen bugs. Perft depth 6 is still inaccurate. --- CMakeLists.txt | 6 ++-- src/board.cpp | 45 +++++++++++++++++------ src/board.h | 11 ++++++ src/move.cpp | 94 +++++++++++++++++++++++++++++++++++++++++++++--- src/move.h | 46 ++++++++++++++++++++---- src/move_gen.cpp | 14 ++++++-- src/perft.cpp | 5 ++- src/perft.h | 1 - src/types.h | 35 ++++++++++++++++++ src/uci.cpp | 81 ++++++++++++++++++++++++++++++++++------- src/uci.h | 3 +- 11 files changed, 297 insertions(+), 44 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8453bcd2..6d5d6c41 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -146,9 +146,9 @@ if (ENABLE_OPTIMIZATION) set(PROFILING_FLAGS "${PROFILING_FLAGS} -O3") endif () else () - set(RELEASE_FLAGS "${RELEASE_FLAGS} -O0 -fno-inline") - set(DEBUG_FLAGS "${DEBUG_FLAGS} -O0 -fno-inline") - set(PROFILING_FLAGS "${PROFILING_FLAGS} -O0 -fno-inline") + set(RELEASE_FLAGS "${RELEASE_FLAGS} -O1 -fno-inline") + set(DEBUG_FLAGS "${DEBUG_FLAGS} -O1 -fno-inline") + set(PROFILING_FLAGS "${PROFILING_FLAGS} -O1 -fno-inline") endif () if (ENABLE_LTO) diff --git a/src/board.cpp b/src/board.cpp index a95d5209..83bed1c1 100644 --- a/src/board.cpp +++ b/src/board.cpp @@ -74,9 +74,25 @@ void Board::reset() { std::ranges::fill(history, BoardState{}); } +void Board::print() const { + std::cout << " ---------------------------------"; + + for (int index = 0; index < 64; index++) { + if (index % 8 == 0) { + std::cout << std::endl << index / 8 + 1 << " | "; + } + + std::cout << getCharacterForPieceType(board[index]) << " | "; + } + + std::cout << std::endl << " ---------------------------------" << std::endl; + std::cout << " a b c d e f g h " << std::endl; +} + void Board::makeMove(const Move& move) { const uint8_t fromSquare = getFromSquare(move); const uint8_t toSquare = getToSquare(move); + const MoveType moveType = getMoveType(move); const Piece movedPiece = getPieceOnSquare(fromSquare); const PieceType movedPieceType = pieceType(movedPiece); const Piece capturedPiece = getPieceOnSquare(toSquare); @@ -89,8 +105,7 @@ void Board::makeMove(const Move& move) { setPiece(movedPiece, toSquare); if (movedPieceType == PieceType::PAWN) { - // if destination is enPassant square and the move is diagonal, remove the captured pawn - if (toSquare == enPassantSquare && (toSquare - fromSquare) % 8 != 0) { + if (moveType == MoveType::EN_PASSANT) { if (sideToMove == PieceColor::WHITE) { removePiece(Piece::BLACK_PAWN, toSquare + TO_INT(Direction::SOUTH)); } else { @@ -98,13 +113,11 @@ void Board::makeMove(const Move& move) { } } - const uint8_t moveDistance = std::abs(toSquare - fromSquare); - - if (moveDistance == 16) { + if ((fromSquare ^ toSquare) == 16) { if (sideToMove == PieceColor::WHITE) { - enPassantSquare = fromSquare + TO_INT(Direction::NORTH); + enPassantSquare = toSquare + TO_INT(Direction::SOUTH); } else { - enPassantSquare = fromSquare + TO_INT(Direction::SOUTH); + enPassantSquare = toSquare + TO_INT(Direction::NORTH); } } else { enPassantSquare = 0; @@ -128,6 +141,7 @@ void Board::unmakeMove() { const auto& [move, capturedPiece, enPassantSquare] = history[ply]; const uint8_t fromSquare = getFromSquare(move); const uint8_t toSquare = getToSquare(move); + const MoveType moveType = getMoveType(move); const Piece movedPiece = getPieceOnSquare(toSquare); removePiece(movedPiece, toSquare); @@ -137,11 +151,20 @@ void Board::unmakeMove() { setPiece(capturedPiece, toSquare); } + if (moveType == MoveType::EN_PASSANT) { + const PieceColor movedPieceColor = pieceColor(movedPiece); + + if (movedPieceColor == PieceColor::WHITE) { + setPiece(Piece::BLACK_PAWN, toSquare + TO_INT(Direction::SOUTH)); + } else { + setPiece(Piece::WHITE_PAWN, toSquare + TO_INT(Direction::NORTH)); + } + } + this->sideToMove = !sideToMove; this->enPassantSquare = enPassantSquare; } - void Board::setPieceFromFENChar(const char character, const uint8_t square) { assert(square < SQUARES); using enum Piece; @@ -266,14 +289,14 @@ bool Board::setFromFEN(const std::string_view fen) { } const int8_t file = tolower(character) - 'a'; - // const int8_t rank = (!sideToMove) == PieceColor::WHITE ? 2 : 5; + const int8_t rank = (!sideToMove) == PieceColor::WHITE ? 2 : 5; if (file < 0 || file > 7) { return false; } - // TODO: Uncomment and add zobrist once en-passant is implemented - // enPassantSquare = rank * 8 + file; + // TODO: Add enPassant zobrist + enPassantSquare = rank * 8 + file; index += 2; } diff --git a/src/board.h b/src/board.h index 82888b4c..d13a1649 100644 --- a/src/board.h +++ b/src/board.h @@ -97,6 +97,8 @@ class Board { template void setPiece(const uint8_t square) { + assert(piece != Piece::EMPTY); + assert(board[square] == Piece::EMPTY); const uint64_t squareBB = squareToBitboard(square); board[square] = piece; @@ -106,6 +108,8 @@ class Board { } void setPiece(const Piece piece, const uint8_t square) { + assert(piece != Piece::EMPTY); + assert(board[square] == Piece::EMPTY); const uint64_t squareBB = squareToBitboard(square); board[square] = piece; @@ -117,6 +121,7 @@ class Board { void removePiece(const uint8_t square) { const uint64_t squareBB = squareToBitboard(square); const Piece piece = board[square]; + assert(piece != Piece::EMPTY); board[square] = Piece::EMPTY; bitboards[TO_INT(piece)] &= ~squareBB; @@ -126,6 +131,8 @@ class Board { template void removePiece(const uint8_t square) { + assert(piece != Piece::EMPTY); + assert(board[square] == piece); const uint64_t squareBB = squareToBitboard(square); board[square] = Piece::EMPTY; @@ -135,6 +142,8 @@ class Board { } void removePiece(const Piece piece, const uint8_t square) { + assert(piece != Piece::EMPTY); + assert(board[square] == piece); const uint64_t squareBB = squareToBitboard(square); board[square] = Piece::EMPTY; @@ -165,5 +174,7 @@ class Board { bool setFromFEN(std::string_view fen); void reset(); + + void print() const; }; } // namespace Zagreus diff --git a/src/move.cpp b/src/move.cpp index 7e597d63..077efd44 100644 --- a/src/move.cpp +++ b/src/move.cpp @@ -22,14 +22,100 @@ #include +#include "macros.h" + namespace Zagreus { -std::string getMoveNotation(const uint8_t square) { - assert(square < SQUARES); +std::string getMoveNotation(const uint8_t fromSquare, const uint8_t toSquare) { + // TODO: Support promotions + assert(fromSquare < SQUARES); + assert(toSquare < SQUARES); + std::string notation; + + notation += static_cast(fromSquare % 8 + 'a'); + notation += static_cast(fromSquare / 8 + '1'); + notation += static_cast(toSquare % 8 + 'a'); + notation += static_cast(toSquare / 8 + '1'); + + return notation; +} + +std::string getMoveNotation(const uint8_t fromSquare, const uint8_t toSquare, const PromotionPiece promotionPiece) { + assert(fromSquare < SQUARES); + assert(toSquare < SQUARES); + assert(TO_INT(promotionPiece) < 4); std::string notation; - notation += static_cast(square % 8 + 'a'); - notation += static_cast(square / 8 + '1'); + notation += static_cast(fromSquare % 8 + 'a'); + notation += static_cast(fromSquare / 8 + '1'); + notation += static_cast(toSquare % 8 + 'a'); + notation += static_cast(toSquare / 8 + '1'); + notation += "qrbn"[TO_INT(promotionPiece)]; return notation; } + +std::string getMoveNotation(const Move move) { + const uint8_t fromSquare = getFromSquare(move); + const uint8_t toSquare = getToSquare(move); + + if (const MoveType moveType = getMoveType(move); + moveType == MoveType::PROMOTION) { + const PromotionPiece promotionPiece = getPromotionPiece(move); + + return getMoveNotation(fromSquare, toSquare, promotionPiece); + } + + return getMoveNotation(fromSquare, toSquare); +} + +Move fromMoveNotation(const std::string_view notation) { + const Square fromSquare = fromSquareNotation(notation.substr(0, 2)); + const Square toSquare = fromSquareNotation(notation.substr(2, 2)); + + // If there is a 5th character, it is a promotion + if (notation.size() == 5) { + const char promotionPieceChar = notation[4]; + PromotionPiece promotionPiece; + + switch (promotionPieceChar) { + using enum PromotionPiece; + case 'q': + promotionPiece = QUEEN; + break; + case 'r': + promotionPiece = ROOK; + break; + case 'b': + promotionPiece = BISHOP; + break; + case 'n': + promotionPiece = KNIGHT; + break; + default: + assert(false); + return 0; + } + + return encodeMove(TO_INT(fromSquare), TO_INT(toSquare), MoveType::PROMOTION, promotionPiece); + } + + return encodeMove(TO_INT(fromSquare), TO_INT(toSquare)); +} + +std::string getSquareNotation(const Square square) { + assert(TO_INT(square) < SQUARES); + std::string notation; + + notation += static_cast(TO_INT(square) % 8 + 'a'); + notation += static_cast(TO_INT(square) / 8 + '1'); + + return notation; +} + +Square fromSquareNotation(const std::string_view notation) { + const uint8_t file = notation[0] - 'a'; + const uint8_t rank = notation[1] - '1'; + + return static_cast(file + rank * 8); +} } // namespace Zagreus diff --git a/src/move.h b/src/move.h index 9ed2a842..949b653e 100644 --- a/src/move.h +++ b/src/move.h @@ -25,6 +25,7 @@ #include #include "constants.h" +#include "types.h" namespace Zagreus { @@ -35,7 +36,31 @@ struct MoveList { uint8_t size = 0; }; -std::string getMoveNotation(uint8_t square); +enum class MoveType : uint8_t { + NORMAL = 0b00, + PROMOTION = 0b01, + EN_PASSANT = 0b10, + CASTLING = 0b11 +}; + +enum class PromotionPiece : uint8_t { + QUEEN = 0b00, + ROOK = 0b01, + BISHOP = 0b10, + KNIGHT = 0b11 +}; + +std::string getMoveNotation(uint8_t fromSquare, uint8_t toSquare); + +std::string getMoveNotation(uint8_t fromSquare, uint8_t toSquare, PromotionPiece promotionPiece); + +std::string getMoveNotation(Move move); + +Move fromMoveNotation(std::string_view notation); + +std::string getSquareNotation(Square square); + +Square fromSquareNotation(std::string_view notation); // bits 0-5: from square (0-63) // bits 6-11: to square (0-63) @@ -46,21 +71,28 @@ inline Move encodeMove(const uint8_t fromSquare, const uint8_t toSquare) { return static_cast(fromSquare | (toSquare << 6)); } -inline Move encodeMove(const uint8_t fromSquare, const uint8_t toSquare, const uint8_t moveType) { - return static_cast(fromSquare | (toSquare << 6) | (moveType << 12)); +inline Move encodeMove(const uint8_t fromSquare, const uint8_t toSquare, const MoveType moveType) { + return static_cast(fromSquare | (toSquare << 6) | (TO_INT(moveType) << 12)); } -inline Move encodeMove(const uint8_t fromSquare, const uint8_t toSquare, const uint8_t moveType, const uint8_t promotionPiece) { - return static_cast(fromSquare | (toSquare << 6) | (moveType << 12) | (promotionPiece << 14)); +inline Move encodeMove(const uint8_t fromSquare, const uint8_t toSquare, const MoveType moveType, + const PromotionPiece promotionPiece) { + return static_cast(fromSquare | (toSquare << 6) | (TO_INT(moveType) << 12) | (TO_INT(promotionPiece) << 14)); } inline uint8_t getFromSquare(const Move move) { - // Extract bits 0-5 return static_cast(move & 0x3F); } inline uint8_t getToSquare(const Move move) { - // Extract bits 6-11 return static_cast((move >> 6) & 0x3F); } + +inline MoveType getMoveType(const Move move) { + return static_cast((move >> 12) & 0x3); +} + +inline PromotionPiece getPromotionPiece(const Move move) { + return static_cast((move >> 14) & 0x3); +} } // namespace Zagreus diff --git a/src/move_gen.cpp b/src/move_gen.cpp index db08498f..12f3ef6e 100644 --- a/src/move_gen.cpp +++ b/src/move_gen.cpp @@ -111,8 +111,13 @@ void generatePawnMoves(const Board& board, MoveList& moves, const uint64_t genMa while (pawnWestAttacks) { const uint8_t squareTo = popLsb(pawnWestAttacks); const uint8_t squareFrom = squareTo - TO_INT(fromSqWestAttackDirection); - const Move move = encodeMove(squareFrom, squareTo); + MoveType moveType = MoveType::NORMAL; + + if (squareTo == board.getEnPassantSquare() && squareTo & enPassantMask) { + moveType = MoveType::EN_PASSANT; + } + const Move move = encodeMove(squareFrom, squareTo, moveType); moves.moves[moves.size] = move; moves.size++; } @@ -120,8 +125,13 @@ void generatePawnMoves(const Board& board, MoveList& moves, const uint64_t genMa while (pawnEastAttacks) { const uint8_t squareTo = popLsb(pawnEastAttacks); const uint8_t squareFrom = squareTo - TO_INT(fromSqEastAttackDirection); - const Move move = encodeMove(squareFrom, squareTo); + MoveType moveType = MoveType::NORMAL; + + if (squareTo == board.getEnPassantSquare() && squareTo & enPassantMask) { + moveType = MoveType::EN_PASSANT; + } + const Move move = encodeMove(squareFrom, squareTo, moveType); moves.moves[moves.size] = move; moves.size++; } diff --git a/src/perft.cpp b/src/perft.cpp index cbc80e1c..71b0de6d 100644 --- a/src/perft.cpp +++ b/src/perft.cpp @@ -63,10 +63,9 @@ uint64_t perft(Board &board, const int depth, bool printNodes) { uint64_t perftNodes = perft(board, depth - 1, false); if (printNodes) { - std::string fromNotation = getMoveNotation(getFromSquare(moveList.moves[i])); - std::string toNotation = getMoveNotation(getToSquare(moveList.moves[i])); + std::string moveNotation = getMoveNotation(moveList.moves[i]); - std::cout << fromNotation << toNotation << ": " << perftNodes << std::endl; + std::cout << moveNotation << ": " << perftNodes << std::endl; } nodes += perftNodes; diff --git a/src/perft.h b/src/perft.h index 579ea23a..13efcdc3 100644 --- a/src/perft.h +++ b/src/perft.h @@ -23,7 +23,6 @@ #include #include "board.h" -#include "types.h" namespace Zagreus { uint64_t perft(Board &board, int depth, bool printNodes = true); diff --git a/src/types.h b/src/types.h index 75b857be..ec3a1afe 100644 --- a/src/types.h +++ b/src/types.h @@ -92,3 +92,38 @@ constexpr PieceType pieceType(const Piece piece) { assert(piece != Piece::EMPTY); return static_cast(TO_INT(piece) / 2); } + +constexpr char getCharacterForPieceType(const Piece piece) { + using enum Piece; + + switch (piece) { + case WHITE_PAWN: + return 'P'; + case BLACK_PAWN: + return 'p'; + case WHITE_KNIGHT: + return 'N'; + case BLACK_KNIGHT: + return 'n'; + case WHITE_BISHOP: + return 'B'; + case BLACK_BISHOP: + return 'b'; + case WHITE_ROOK: + return 'R'; + case BLACK_ROOK: + return 'r'; + case WHITE_QUEEN: + return 'Q'; + case BLACK_QUEEN: + return 'q'; + case WHITE_KING: + return 'K'; + case BLACK_KING: + return 'k'; + case EMPTY: + return ' '; + } + + return ' '; +} diff --git a/src/uci.cpp b/src/uci.cpp index fb1bf932..50a4f0af 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -44,7 +44,6 @@ void Engine::doSetup() { initializeMagicBitboards(); initializeAttackLookupTables(); - board = Board(); didSetup = true; } @@ -114,39 +113,39 @@ void Engine::handleSetOptionCommand(const std::string& args) { } std::istringstream iss(args); - std::string word; - std::string section = ""; + std::string arg; + std::string section; std::string name; std::string value; - while (iss >> word) { - std::string lowercaseWord = word; + while (iss >> arg) { + std::string lowercaseWord = arg; std::ranges::transform(lowercaseWord, lowercaseWord.begin(), [](const unsigned char c) { return std::tolower(c); }); if (lowercaseWord == "name") { - section = word; + section = arg; continue; } if (lowercaseWord == "value") { - section = word; + section = arg; continue; } if (section == "name") { if (name.empty()) { - name = word; + name = arg; } else { - name += " " + word; + name += " " + arg; } } if (section == "value") { if (value.empty()) { - value = word; + value = arg; } else { - value += " " + word; + value += " " + arg; } } } @@ -182,7 +181,59 @@ void Engine::handleSetOptionCommand(const std::string& args) { void Engine::handleUciNewGameCommand(std::string_view args) { } -void Engine::handlePositionCommand(std::string_view args) { +void Engine::handlePositionCommand(const std::string_view args) { + // If the first arg is not "startpos" or "fen", report invalid usage + if (!args.starts_with("startpos") && !args.starts_with("fen")) { + sendMessage("ERROR: Invalid usage of position command."); + return; + } + + // Go through each argument one by one + std::istringstream iss((args.data())); + std::string arg; + + // If the first argument is "startpos", set the board to the starting position + iss >> arg; + board = Board(); + + if (arg == "startpos") { + board.setFromFEN(startPosFEN); + } else if (arg == "fen") { + std::string fen; + + // Append args to the fen string until we reach the "moves" keyword or the end of the string + while (iss >> arg && arg != "moves") { + if (fen.empty()) { + fen = arg; + } else { + fen += " " + arg; + } + } + + if (fen.empty()) { + sendMessage("ERROR: No FEN string provided."); + return; + } + + if (!board.setFromFEN(fen)) { + sendMessage("ERROR: Invalid FEN string provided."); + return; + } + } + + while (iss >> arg) { + if (arg == "moves") { + continue; + } + + if (arg.size() != 4 && arg.size() != 5) { + sendMessage("ERROR: Invalid move notation provided."); + return; + } + + const Move move = fromMoveNotation(arg); + board.makeMove(move); + } } void Engine::handleGoCommand(std::string_view args) { @@ -236,6 +287,10 @@ void Engine::handlePerftCommand(const std::string& args) { sendInfoMessage("nodes " + std::to_string(nodes)); } +void Engine::handlePrintCommand() { + board.print(); +} + void Engine::processCommand(const std::string_view command, const std::string& args) { if (command == "uci") { handleUciCommand(); @@ -262,6 +317,8 @@ void Engine::processCommand(const std::string_view command, const std::string& a handleQuitCommand(args); } else if (command == "perft") { handlePerftCommand(args); + } else if (command == "print") { + handlePrintCommand(); } else { // If unknown, we must skip it and process the rest. if (args.empty() || args == " " || args == "\n") { diff --git a/src/uci.h b/src/uci.h index eba33c16..a658ad3c 100644 --- a/src/uci.h +++ b/src/uci.h @@ -35,7 +35,7 @@ class Engine { private: bool didSetup = false; std::map options{}; - Board board; + Board board{}; void handleUciCommand(); void handleDebugCommand(std::string_view args); @@ -48,6 +48,7 @@ class Engine { void handlePonderHitCommand(std::string_view args); void handleQuitCommand(std::string_view args); void handlePerftCommand(const std::string& args); + void handlePrintCommand(); void processCommand(std::string_view command, const std::string& args); public: From 515cc55c8b9f7cde225cd0f0ac22ffb74d6c15a2 Mon Sep 17 00:00:00 2001 From: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> Date: Thu, 10 Oct 2024 21:19:50 +0200 Subject: [PATCH 26/91] Fixed en passant bug. Startpos perft accurate until depth 6. --- src/move.cpp | 2 +- src/move.h | 6 +++--- src/move_gen.cpp | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/move.cpp b/src/move.cpp index 077efd44..7726b074 100644 --- a/src/move.cpp +++ b/src/move.cpp @@ -96,7 +96,7 @@ Move fromMoveNotation(const std::string_view notation) { return 0; } - return encodeMove(TO_INT(fromSquare), TO_INT(toSquare), MoveType::PROMOTION, promotionPiece); + return encodeMove(TO_INT(fromSquare), TO_INT(toSquare), promotionPiece); } return encodeMove(TO_INT(fromSquare), TO_INT(toSquare)); diff --git a/src/move.h b/src/move.h index 949b653e..9719e456 100644 --- a/src/move.h +++ b/src/move.h @@ -22,6 +22,7 @@ #pragma once #include +#include #include #include "constants.h" @@ -75,9 +76,8 @@ inline Move encodeMove(const uint8_t fromSquare, const uint8_t toSquare, const M return static_cast(fromSquare | (toSquare << 6) | (TO_INT(moveType) << 12)); } -inline Move encodeMove(const uint8_t fromSquare, const uint8_t toSquare, const MoveType moveType, - const PromotionPiece promotionPiece) { - return static_cast(fromSquare | (toSquare << 6) | (TO_INT(moveType) << 12) | (TO_INT(promotionPiece) << 14)); +inline Move encodeMove(const uint8_t fromSquare, const uint8_t toSquare, const PromotionPiece promotionPiece) { + return static_cast(fromSquare | (toSquare << 6) | (0b01 << 12) | (TO_INT(promotionPiece) << 14)); } inline uint8_t getFromSquare(const Move move) { diff --git a/src/move_gen.cpp b/src/move_gen.cpp index 12f3ef6e..5994a6b8 100644 --- a/src/move_gen.cpp +++ b/src/move_gen.cpp @@ -113,7 +113,7 @@ void generatePawnMoves(const Board& board, MoveList& moves, const uint64_t genMa const uint8_t squareFrom = squareTo - TO_INT(fromSqWestAttackDirection); MoveType moveType = MoveType::NORMAL; - if (squareTo == board.getEnPassantSquare() && squareTo & enPassantMask) { + if (squareTo == board.getEnPassantSquare() && squareToBitboard(squareTo) & enPassantMask) { moveType = MoveType::EN_PASSANT; } @@ -127,7 +127,7 @@ void generatePawnMoves(const Board& board, MoveList& moves, const uint64_t genMa const uint8_t squareFrom = squareTo - TO_INT(fromSqEastAttackDirection); MoveType moveType = MoveType::NORMAL; - if (squareTo == board.getEnPassantSquare() && squareTo & enPassantMask) { + if (squareTo == board.getEnPassantSquare() && squareToBitboard(squareTo) & enPassantMask) { moveType = MoveType::EN_PASSANT; } From 41e4cdefc7aec71f6a482950ff8838ea1afb6110 Mon Sep 17 00:00:00 2001 From: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> Date: Thu, 10 Oct 2024 21:43:01 +0200 Subject: [PATCH 27/91] Performance improvements --- .github/workflows/release.yml | 6 ---- src/bitboard.cpp | 12 +++---- src/bitboard.h | 2 -- src/board.cpp | 48 +++++++++++++-------------- src/board.h | 27 ++++++++-------- src/macros.h | 27 ---------------- src/move.cpp | 17 ++++------ src/move.h | 8 ++--- src/move_gen.cpp | 61 ++++++++++++++++------------------- src/move_gen.h | 4 +-- src/perft.cpp | 2 -- src/types.h | 22 ++++++------- src/uci.cpp | 6 +++- 13 files changed, 95 insertions(+), 147 deletions(-) delete mode 100644 src/macros.h diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ba9af60b..63b97ad1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -74,12 +74,6 @@ jobs: mkdir rel cp "$executable_name" rel/ cp LICENSE README.md rel/ - mkdir "rel/source-code" - cp -r src "rel/source-code" - cp -r senjo "rel/source-code" - cp LICENSE "rel/source-code" - cp README.md "rel/source-code" - cp CMakeLists.txt "rel/source-code" cd rel ${{ matrix.archive-command }} "../$executable_name${{ matrix.artifact-extension }}" * diff --git a/src/bitboard.cpp b/src/bitboard.cpp index da04a225..36c72a4f 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -20,7 +20,6 @@ This file is part of Zagreus. #include "bitboard.h" -#include "macros.h" #include "magics.h" namespace Zagreus { @@ -32,8 +31,8 @@ void initializeAttackLookupTables() { for (uint8_t square = 0; square < SQUARES; ++square) { const uint64_t bb = squareToBitboard(square); - pawnAttacksTable[TO_INT(PieceColor::WHITE)][square] = calculateWhitePawnAttacks(bb); - pawnAttacksTable[TO_INT(PieceColor::BLACK)][square] = calculateBlackPawnAttacks(bb); + pawnAttacksTable[PieceColor::WHITE][square] = calculateWhitePawnAttacks(bb); + pawnAttacksTable[PieceColor::BLACK][square] = calculateBlackPawnAttacks(bb); knightAttacksTable[square] = calculateKnightAttacks(bb); kingAttacksTable[square] = calculateKingAttacks(bb); } @@ -41,14 +40,13 @@ void initializeAttackLookupTables() { template uint64_t pawnAttacks(const uint8_t square) { - assert(color != PieceColor::EMPTY); assert(square < SQUARES); - return pawnAttacksTable[TO_INT(color)][square]; + return pawnAttacksTable[color][square]; } -template uint64_t pawnAttacks(uint8_t square); -template uint64_t pawnAttacks(uint8_t square); +template uint64_t pawnAttacks(uint8_t square); +template uint64_t pawnAttacks(uint8_t square); uint64_t knightAttacks(const uint8_t square) { assert(square < SQUARES); diff --git a/src/bitboard.h b/src/bitboard.h index 6e151be8..41dfdf8d 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -26,7 +26,6 @@ #include "bitwise.h" #include "constants.h" -#include "macros.h" #include "types.h" namespace Zagreus { @@ -99,7 +98,6 @@ inline uint64_t shiftSouthSouthWest(const uint64_t bb) { template constexpr uint64_t shift(const uint64_t bb) { switch (direction) { - using enum Direction; case NORTH: return shiftNorth(bb); case SOUTH: diff --git a/src/board.cpp b/src/board.cpp index 83bed1c1..47ef95f6 100644 --- a/src/board.cpp +++ b/src/board.cpp @@ -30,20 +30,18 @@ namespace Zagreus { template bool Board::isPositionLegal() const { constexpr PieceColor opponentColor = !movedColor; - constexpr Piece king = movedColor == PieceColor::WHITE ? Piece::WHITE_KING : Piece::BLACK_KING; + constexpr Piece king = movedColor == WHITE ? WHITE_KING : BLACK_KING; const uint64_t kingBB = getBitboard(); const uint8_t kingSquare = bitscanForward(kingBB); return !getSquareAttackersByColor(kingSquare); } -template bool Board::isPositionLegal() const; -template bool Board::isPositionLegal() const; +template bool Board::isPositionLegal() const; +template bool Board::isPositionLegal() const; uint64_t Board::getSquareAttackers(const uint8_t square) const { assert(square < SQUARES); - using enum Piece; - const uint64_t knights = getBitboard() | getBitboard(); const uint64_t kings = getBitboard() | getBitboard(); uint64_t bishopsQueens = getBitboard() | getBitboard(); @@ -51,8 +49,8 @@ uint64_t Board::getSquareAttackers(const uint8_t square) const { rooksQueens |= getBitboard() | getBitboard(); bishopsQueens |= getBitboard() | getBitboard(); - return (pawnAttacks(square) & getBitboard()) - | (pawnAttacks(square) & getBitboard()) + return (pawnAttacks(square) & getBitboard()) + | (pawnAttacks(square) & getBitboard()) | (knightAttacks(square) & knights) | (kingAttacks(square) & kings) | (bishopAttacks(square, occupied) & bishopsQueens) @@ -64,7 +62,7 @@ void Board::reset() { this->bitboards = {}; this->occupied = 0; this->colorBoards = {}; - this->sideToMove = PieceColor::EMPTY; + this->sideToMove = WHITE; this->history = {}; this->ply = 0; @@ -104,20 +102,20 @@ void Board::makeMove(const Move& move) { removePiece(movedPiece, fromSquare); setPiece(movedPiece, toSquare); - if (movedPieceType == PieceType::PAWN) { - if (moveType == MoveType::EN_PASSANT) { - if (sideToMove == PieceColor::WHITE) { - removePiece(Piece::BLACK_PAWN, toSquare + TO_INT(Direction::SOUTH)); + if (movedPieceType == PAWN) { + if (moveType == EN_PASSANT) { + if (sideToMove == WHITE) { + removePiece(BLACK_PAWN, toSquare + SOUTH); } else { - removePiece(Piece::WHITE_PAWN, toSquare + TO_INT(Direction::NORTH)); + removePiece(WHITE_PAWN, toSquare + NORTH); } } if ((fromSquare ^ toSquare) == 16) { - if (sideToMove == PieceColor::WHITE) { - enPassantSquare = toSquare + TO_INT(Direction::SOUTH); + if (sideToMove == WHITE) { + enPassantSquare = toSquare + SOUTH; } else { - enPassantSquare = toSquare + TO_INT(Direction::NORTH); + enPassantSquare = toSquare + NORTH; } } else { enPassantSquare = 0; @@ -151,13 +149,13 @@ void Board::unmakeMove() { setPiece(capturedPiece, toSquare); } - if (moveType == MoveType::EN_PASSANT) { + if (moveType == EN_PASSANT) { const PieceColor movedPieceColor = pieceColor(movedPiece); - if (movedPieceColor == PieceColor::WHITE) { - setPiece(Piece::BLACK_PAWN, toSquare + TO_INT(Direction::SOUTH)); + if (movedPieceColor == WHITE) { + setPiece(BLACK_PAWN, toSquare + SOUTH); } else { - setPiece(Piece::WHITE_PAWN, toSquare + TO_INT(Direction::NORTH)); + setPiece(WHITE_PAWN, toSquare + NORTH); } } @@ -167,8 +165,6 @@ void Board::unmakeMove() { void Board::setPieceFromFENChar(const char character, const uint8_t square) { assert(square < SQUARES); - using enum Piece; - // Uppercase char = white, lowercase = black switch (character) { case 'P': @@ -214,7 +210,7 @@ void Board::setPieceFromFENChar(const char character, const uint8_t square) { bool Board::setFromFEN(const std::string_view fen) { // TODO: Update zobrist hash once that is implemented - uint8_t index = TO_INT(Square::A8); + uint8_t index = A8; int spaces = 0; reset(); @@ -250,9 +246,9 @@ bool Board::setFromFEN(const std::string_view fen) { if (spaces == 1) { if (tolower(character) == 'w') { - sideToMove = PieceColor::WHITE; + sideToMove = WHITE; } else if (tolower(character) == 'b') { - sideToMove = PieceColor::BLACK; + sideToMove = BLACK; } else { return false; } @@ -289,7 +285,7 @@ bool Board::setFromFEN(const std::string_view fen) { } const int8_t file = tolower(character) - 'a'; - const int8_t rank = (!sideToMove) == PieceColor::WHITE ? 2 : 5; + const int8_t rank = (!sideToMove) == WHITE ? 2 : 5; if (file < 0 || file > 7) { return false; diff --git a/src/board.h b/src/board.h index d13a1649..ed630f31 100644 --- a/src/board.h +++ b/src/board.h @@ -32,7 +32,6 @@ #include "bitboard.h" #include "move.h" #include "constants.h" -#include "macros.h" namespace Zagreus { struct BoardState { @@ -47,7 +46,7 @@ class Board { std::array bitboards{}; uint64_t occupied = 0; std::array colorBoards{}; - PieceColor sideToMove = PieceColor::EMPTY; + PieceColor sideToMove = PieceColor::WHITE; std::array history{}; int ply = 0; uint8_t enPassantSquare = 0; @@ -62,12 +61,12 @@ class Board { template [[nodiscard]] uint64_t getBitboard() const { - return bitboards[TO_INT(piece)]; + return bitboards[piece]; } template [[nodiscard]] uint64_t getColorBitboard() const { - return colorBoards[TO_INT(color)]; + return colorBoards[color]; } [[nodiscard]] Piece getPieceOnSquare(const int square) const { @@ -102,9 +101,9 @@ class Board { const uint64_t squareBB = squareToBitboard(square); board[square] = piece; - bitboards[TO_INT(piece)] |= squareBB; + bitboards[piece] |= squareBB; occupied |= squareBB; - colorBoards[TO_INT(pieceColor(piece))] |= squareBB; + colorBoards[pieceColor(piece)] |= squareBB; } void setPiece(const Piece piece, const uint8_t square) { @@ -113,9 +112,9 @@ class Board { const uint64_t squareBB = squareToBitboard(square); board[square] = piece; - bitboards[TO_INT(piece)] |= squareBB; + bitboards[piece] |= squareBB; occupied |= squareBB; - colorBoards[TO_INT(pieceColor(piece))] |= squareBB; + colorBoards[pieceColor(piece)] |= squareBB; } void removePiece(const uint8_t square) { @@ -124,9 +123,9 @@ class Board { assert(piece != Piece::EMPTY); board[square] = Piece::EMPTY; - bitboards[TO_INT(piece)] &= ~squareBB; + bitboards[piece] &= ~squareBB; occupied &= ~squareBB; - colorBoards[TO_INT(pieceColor(piece))] &= ~squareBB; + colorBoards[pieceColor(piece)] &= ~squareBB; } template @@ -136,9 +135,9 @@ class Board { const uint64_t squareBB = squareToBitboard(square); board[square] = Piece::EMPTY; - bitboards[TO_INT(piece)] &= ~squareBB; + bitboards[piece] &= ~squareBB; occupied &= ~squareBB; - colorBoards[TO_INT(pieceColor(piece))] &= ~squareBB; + colorBoards[pieceColor(piece)] &= ~squareBB; } void removePiece(const Piece piece, const uint8_t square) { @@ -147,9 +146,9 @@ class Board { const uint64_t squareBB = squareToBitboard(square); board[square] = Piece::EMPTY; - bitboards[TO_INT(piece)] &= ~squareBB; + bitboards[piece] &= ~squareBB; occupied &= ~squareBB; - colorBoards[TO_INT(pieceColor(piece))] &= ~squareBB; + colorBoards[pieceColor(piece)] &= ~squareBB; } void makeMove(const Move& move); diff --git a/src/macros.h b/src/macros.h deleted file mode 100644 index 8de93ff2..00000000 --- a/src/macros.h +++ /dev/null @@ -1,27 +0,0 @@ - -/* - This file is part of Zagreus. - - Zagreus is a UCI chess engine - Copyright (C) 2023-2024 Danny Jelsma - - Zagreus is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zagreus 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zagreus. If not, see . - */ - -#pragma once - -#include - -// Converts an enum class to its underlying type, usually an int. -#define TO_INT(x) std::to_underlying(x) diff --git a/src/move.cpp b/src/move.cpp index 7726b074..785fbb44 100644 --- a/src/move.cpp +++ b/src/move.cpp @@ -22,8 +22,6 @@ #include -#include "macros.h" - namespace Zagreus { std::string getMoveNotation(const uint8_t fromSquare, const uint8_t toSquare) { // TODO: Support promotions @@ -42,14 +40,14 @@ std::string getMoveNotation(const uint8_t fromSquare, const uint8_t toSquare) { std::string getMoveNotation(const uint8_t fromSquare, const uint8_t toSquare, const PromotionPiece promotionPiece) { assert(fromSquare < SQUARES); assert(toSquare < SQUARES); - assert(TO_INT(promotionPiece) < 4); + assert(promotionPiece < 4); std::string notation; notation += static_cast(fromSquare % 8 + 'a'); notation += static_cast(fromSquare / 8 + '1'); notation += static_cast(toSquare % 8 + 'a'); notation += static_cast(toSquare / 8 + '1'); - notation += "qrbn"[TO_INT(promotionPiece)]; + notation += "qrbn"[promotionPiece]; return notation; } @@ -78,7 +76,6 @@ Move fromMoveNotation(const std::string_view notation) { PromotionPiece promotionPiece; switch (promotionPieceChar) { - using enum PromotionPiece; case 'q': promotionPiece = QUEEN; break; @@ -96,18 +93,18 @@ Move fromMoveNotation(const std::string_view notation) { return 0; } - return encodeMove(TO_INT(fromSquare), TO_INT(toSquare), promotionPiece); + return encodeMove(fromSquare, toSquare, promotionPiece); } - return encodeMove(TO_INT(fromSquare), TO_INT(toSquare)); + return encodeMove(fromSquare, toSquare); } std::string getSquareNotation(const Square square) { - assert(TO_INT(square) < SQUARES); + assert(square < SQUARES); std::string notation; - notation += static_cast(TO_INT(square) % 8 + 'a'); - notation += static_cast(TO_INT(square) / 8 + '1'); + notation += static_cast(square % 8 + 'a'); + notation += static_cast(square / 8 + '1'); return notation; } diff --git a/src/move.h b/src/move.h index 9719e456..8da87834 100644 --- a/src/move.h +++ b/src/move.h @@ -37,14 +37,14 @@ struct MoveList { uint8_t size = 0; }; -enum class MoveType : uint8_t { +enum MoveType : uint8_t { NORMAL = 0b00, PROMOTION = 0b01, EN_PASSANT = 0b10, CASTLING = 0b11 }; -enum class PromotionPiece : uint8_t { +enum PromotionPiece : uint8_t { QUEEN = 0b00, ROOK = 0b01, BISHOP = 0b10, @@ -73,11 +73,11 @@ inline Move encodeMove(const uint8_t fromSquare, const uint8_t toSquare) { } inline Move encodeMove(const uint8_t fromSquare, const uint8_t toSquare, const MoveType moveType) { - return static_cast(fromSquare | (toSquare << 6) | (TO_INT(moveType) << 12)); + return static_cast(fromSquare | (toSquare << 6) | (moveType << 12)); } inline Move encodeMove(const uint8_t fromSquare, const uint8_t toSquare, const PromotionPiece promotionPiece) { - return static_cast(fromSquare | (toSquare << 6) | (0b01 << 12) | (TO_INT(promotionPiece) << 14)); + return static_cast(fromSquare | (toSquare << 6) | (0b01 << 12) | (promotionPiece << 14)); } inline uint8_t getFromSquare(const Move move) { diff --git a/src/move_gen.cpp b/src/move_gen.cpp index 5994a6b8..b003aa0d 100644 --- a/src/move_gen.cpp +++ b/src/move_gen.cpp @@ -30,11 +30,10 @@ namespace Zagreus { template void generateMoves(const Board& board, MoveList& moves) { - assert(color != PieceColor::EMPTY); assert(moves.size == 0); // TODO: Implement GenerationType logic using a mask that is computed based on type - constexpr Piece opponentKing = color == PieceColor::WHITE ? Piece::BLACK_KING : Piece::WHITE_KING; + constexpr Piece opponentKing = color == WHITE ? BLACK_KING : WHITE_KING; const uint64_t ownPieces = board.getColorBitboard(); const uint64_t opponentKingBB = board.getBitboard(); const uint64_t genMask = ~(ownPieces | opponentKingBB); @@ -50,7 +49,7 @@ void generateMoves(const Board& board, MoveList& moves) { template void generatePawnMoves(const Board& board, MoveList& moves, const uint64_t genMask) { // TODO: Handle promotions - constexpr Piece pawn = color == PieceColor::WHITE ? Piece::WHITE_PAWN : Piece::BLACK_PAWN; + constexpr Piece pawn = color == WHITE ? WHITE_PAWN : BLACK_PAWN; constexpr PieceColor opponentColor = !color; const uint64_t pawnBB = board.getBitboard(); @@ -61,7 +60,7 @@ void generatePawnMoves(const Board& board, MoveList& moves, const uint64_t genMa uint64_t pawnWestAttacks; uint64_t pawnEastAttacks; - if constexpr (color == PieceColor::WHITE) { + if constexpr (color == WHITE) { pawnSinglePushes = whitePawnSinglePush(pawnBB, emptyBB); pawnDoublePushes = whitePawnDoublePush(pawnBB, emptyBB); pawnWestAttacks = whitePawnWestAttacks(pawnBB); @@ -75,7 +74,7 @@ void generatePawnMoves(const Board& board, MoveList& moves, const uint64_t genMa uint64_t enPassantMask = squareToBitboard(board.getEnPassantSquare()); - if constexpr (color == PieceColor::WHITE) { + if constexpr (color == WHITE) { enPassantMask &= RANK_6; } else { enPassantMask &= RANK_3; @@ -86,13 +85,13 @@ void generatePawnMoves(const Board& board, MoveList& moves, const uint64_t genMa pawnWestAttacks &= (opponentPieces | enPassantMask) & genMask; pawnEastAttacks &= (opponentPieces | enPassantMask) & genMask; - constexpr Direction fromPushDirection = color == PieceColor::WHITE ? Direction::NORTH : Direction::SOUTH; - constexpr Direction fromSqWestAttackDirection = color == PieceColor::WHITE ? Direction::NORTH_WEST : Direction::SOUTH_WEST; - constexpr Direction fromSqEastAttackDirection = color == PieceColor::WHITE ? Direction::NORTH_EAST : Direction::SOUTH_EAST; + constexpr Direction fromPushDirection = color == WHITE ? NORTH : SOUTH; + constexpr Direction fromSqWestAttackDirection = color == WHITE ? NORTH_WEST : SOUTH_WEST; + constexpr Direction fromSqEastAttackDirection = color == WHITE ? NORTH_EAST : SOUTH_EAST; while (pawnSinglePushes) { const uint8_t squareTo = popLsb(pawnSinglePushes); - const uint8_t squareFrom = squareTo - TO_INT(fromPushDirection); + const uint8_t squareFrom = squareTo - fromPushDirection; const Move move = encodeMove(squareFrom, squareTo); moves.moves[moves.size] = move; @@ -101,7 +100,7 @@ void generatePawnMoves(const Board& board, MoveList& moves, const uint64_t genMa while (pawnDoublePushes) { const uint8_t squareTo = popLsb(pawnDoublePushes); - const uint8_t squareFrom = squareTo - TO_INT(fromPushDirection) - TO_INT(fromPushDirection); + const uint8_t squareFrom = squareTo - fromPushDirection - fromPushDirection; const Move move = encodeMove(squareFrom, squareTo); moves.moves[moves.size] = move; @@ -110,11 +109,11 @@ void generatePawnMoves(const Board& board, MoveList& moves, const uint64_t genMa while (pawnWestAttacks) { const uint8_t squareTo = popLsb(pawnWestAttacks); - const uint8_t squareFrom = squareTo - TO_INT(fromSqWestAttackDirection); - MoveType moveType = MoveType::NORMAL; + const uint8_t squareFrom = squareTo - fromSqWestAttackDirection; + MoveType moveType = NORMAL; if (squareTo == board.getEnPassantSquare() && squareToBitboard(squareTo) & enPassantMask) { - moveType = MoveType::EN_PASSANT; + moveType = EN_PASSANT; } const Move move = encodeMove(squareFrom, squareTo, moveType); @@ -124,11 +123,11 @@ void generatePawnMoves(const Board& board, MoveList& moves, const uint64_t genMa while (pawnEastAttacks) { const uint8_t squareTo = popLsb(pawnEastAttacks); - const uint8_t squareFrom = squareTo - TO_INT(fromSqEastAttackDirection); - MoveType moveType = MoveType::NORMAL; + const uint8_t squareFrom = squareTo - fromSqEastAttackDirection; + MoveType moveType = NORMAL; if (squareTo == board.getEnPassantSquare() && squareToBitboard(squareTo) & enPassantMask) { - moveType = MoveType::EN_PASSANT; + moveType = EN_PASSANT; } const Move move = encodeMove(squareFrom, squareTo, moveType); @@ -139,7 +138,7 @@ void generatePawnMoves(const Board& board, MoveList& moves, const uint64_t genMa template void generateKnightMoves(const Board& board, MoveList& moves, const uint64_t genMask) { - constexpr Piece knight = color == PieceColor::WHITE ? Piece::WHITE_KNIGHT : Piece::BLACK_KNIGHT; + constexpr Piece knight = color == WHITE ? WHITE_KNIGHT : BLACK_KNIGHT; uint64_t knightBB = board.getBitboard(); while (knightBB) { @@ -159,8 +158,7 @@ void generateKnightMoves(const Board& board, MoveList& moves, const uint64_t gen template void generateBishopMoves(const Board& board, MoveList& moves, const uint64_t genMask) { - using enum Piece; - constexpr Piece bishop = color == PieceColor::WHITE ? WHITE_BISHOP : BLACK_BISHOP; + constexpr Piece bishop = color == WHITE ? WHITE_BISHOP : BLACK_BISHOP; const uint64_t occupied = board.getOccupiedBitboard(); uint64_t bishopBB = board.getBitboard(); @@ -180,8 +178,7 @@ void generateBishopMoves(const Board& board, MoveList& moves, const uint64_t gen template void generateRookMoves(const Board& board, MoveList& moves, const uint64_t genMask) { - using enum Piece; - constexpr Piece rook = color == PieceColor::WHITE ? WHITE_ROOK : BLACK_ROOK; + constexpr Piece rook = color == WHITE ? WHITE_ROOK : BLACK_ROOK; const uint64_t occupied = board.getOccupiedBitboard(); uint64_t rookBB = board.getBitboard(); @@ -201,8 +198,7 @@ void generateRookMoves(const Board& board, MoveList& moves, const uint64_t genMa template void generateQueenMoves(const Board& board, MoveList& moves, const uint64_t genMask) { - using enum Piece; - constexpr Piece queen = color == PieceColor::WHITE ? WHITE_QUEEN : BLACK_QUEEN; + constexpr Piece queen = color == WHITE ? WHITE_QUEEN : BLACK_QUEEN; const uint64_t occupied = board.getOccupiedBitboard(); uint64_t queenBB = board.getBitboard(); @@ -223,8 +219,7 @@ void generateQueenMoves(const Board& board, MoveList& moves, const uint64_t genM template void generateKingMoves(const Board& board, MoveList& moves, const uint64_t genMask) { // TODO: Implement castling - using enum Piece; - constexpr Piece king = color == PieceColor::WHITE ? WHITE_KING : BLACK_KING; + constexpr Piece king = color == WHITE ? WHITE_KING : BLACK_KING; uint64_t kingBB = board.getBitboard(); while (kingBB) { @@ -242,12 +237,12 @@ void generateKingMoves(const Board& board, MoveList& moves, const uint64_t genMa } // explicit instantiation of generateMoves -template void generateMoves(const Board& board, MoveList& moves); -template void generateMoves(const Board& board, MoveList& moves); -template void generateMoves(const Board& board, MoveList& moves); -template void generateMoves(const Board& board, MoveList& moves); -template void generateMoves(const Board& board, MoveList& moves); -template void generateMoves(const Board& board, MoveList& moves); -template void generateMoves(const Board& board, MoveList& moves); -template void generateMoves(const Board& board, MoveList& moves); +template void generateMoves(const Board& board, MoveList& moves); +template void generateMoves(const Board& board, MoveList& moves); +template void generateMoves(const Board& board, MoveList& moves); +template void generateMoves(const Board& board, MoveList& moves); +template void generateMoves(const Board& board, MoveList& moves); +template void generateMoves(const Board& board, MoveList& moves); +template void generateMoves(const Board& board, MoveList& moves); +template void generateMoves(const Board& board, MoveList& moves); } // namespace Zagreus diff --git a/src/move_gen.h b/src/move_gen.h index 5827e7ff..2b161440 100644 --- a/src/move_gen.h +++ b/src/move_gen.h @@ -27,10 +27,10 @@ #include "move.h" #include "types.h" -enum class PieceColor : uint8_t; +enum PieceColor : uint8_t; namespace Zagreus { -enum class GenerationType : uint8_t { +enum GenerationType : uint8_t { ALL, QUIET, CAPTURES, diff --git a/src/perft.cpp b/src/perft.cpp index 71b0de6d..f11c7a91 100644 --- a/src/perft.cpp +++ b/src/perft.cpp @@ -28,8 +28,6 @@ namespace Zagreus { uint64_t perft(Board &board, const int depth, bool printNodes) { assert(depth >= 0); - using enum PieceColor; - using enum GenerationType; if (depth == 0) { return 1; diff --git a/src/types.h b/src/types.h index ec3a1afe..357d2cc2 100644 --- a/src/types.h +++ b/src/types.h @@ -23,9 +23,7 @@ #include #include -#include "macros.h" - -enum class Direction { +enum Direction { NORTH = 8, SOUTH = -8, EAST = 1, @@ -45,7 +43,7 @@ enum class Direction { }; // clang-format off -enum class Square: uint8_t { +enum Square: uint8_t { A1, B1, C1, D1, E1, F1, G1, H1, A2, B2, C2, D2, E2, F2, G2, H2, A3, B3, C3, D3, E3, F3, G3, H3, @@ -56,18 +54,18 @@ enum class Square: uint8_t { A8, B8, C8, D8, E8, F8, G8, H8, NONE = 255 }; + // clang-format on -enum class PieceType : uint8_t { PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, EMPTY = 255 }; +enum PieceType : uint8_t { PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING }; -enum class PieceColor : uint8_t { WHITE, BLACK, EMPTY = 255 }; +enum PieceColor : uint8_t { WHITE, BLACK }; constexpr PieceColor operator!(const PieceColor color) { - assert(color != PieceColor::EMPTY); - return color == PieceColor::WHITE ? PieceColor::BLACK : PieceColor::WHITE; + return color == WHITE ? BLACK : WHITE; } -enum class Piece : uint8_t { +enum Piece : uint8_t { WHITE_PAWN, BLACK_PAWN, WHITE_KNIGHT, @@ -85,17 +83,15 @@ enum class Piece : uint8_t { constexpr PieceColor pieceColor(const Piece piece) { assert(piece != Piece::EMPTY); - return static_cast(TO_INT(piece) % 2); + return static_cast(piece % 2); } constexpr PieceType pieceType(const Piece piece) { assert(piece != Piece::EMPTY); - return static_cast(TO_INT(piece) / 2); + return static_cast(piece / 2); } constexpr char getCharacterForPieceType(const Piece piece) { - using enum Piece; - switch (piece) { case WHITE_PAWN: return 'P'; diff --git a/src/uci.cpp b/src/uci.cpp index 50a4f0af..1ed41a90 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -282,9 +282,13 @@ void Engine::handlePerftCommand(const std::string& args) { board.setFromFEN(startPosFEN); } + const auto start = std::chrono::high_resolution_clock::now(); const uint64_t nodes = perft(board, depth); + const auto end = std::chrono::high_resolution_clock::now(); + const std::string tookSeconds = std::to_string(std::chrono::duration(end - start).count()); - sendInfoMessage("nodes " + std::to_string(nodes)); + sendInfoMessage( + "Depth: " + std::to_string(depth) + ", Nodes: " + std::to_string(nodes) + ", Time: " + tookSeconds + "s"); } void Engine::handlePrintCommand() { From f03dc9a3e637fdd3c2ca28f100b160083bb27b3b Mon Sep 17 00:00:00 2001 From: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> Date: Sat, 12 Oct 2024 21:28:32 +0200 Subject: [PATCH 28/91] Implemented castling, startpos perft accurate up until depth 7 (didn't test 8 yet). Seem to be either ep or castling issues on other positions that need fixing. --- src/bitboard.h | 4 +- src/board.cpp | 191 +++++++++++++++++++++++++++++++++++++----- src/board.h | 58 ++++++++----- src/constants.h | 43 +++++----- src/move.cpp | 4 +- src/move.h | 2 +- src/move_gen.cpp | 42 ++++++++-- src/types.h | 13 ++- src/uci.cpp | 36 ++++---- tests/types_tests.cpp | 24 +++--- 10 files changed, 309 insertions(+), 108 deletions(-) diff --git a/src/bitboard.h b/src/bitboard.h index 41dfdf8d..ced2aa7e 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -226,7 +226,7 @@ inline uint64_t squareToBitboard(const uint8_t square) { return 1ULL << square; } -inline uint8_t bitboardToSquare(const uint64_t bb) { - return bitscanForward(bb); +inline Square bitboardToSquare(const uint64_t bb) { + return static_cast(bitscanForward(bb)); } } // namespace Zagreus diff --git a/src/board.cpp b/src/board.cpp index 47ef95f6..f481c4f4 100644 --- a/src/board.cpp +++ b/src/board.cpp @@ -32,7 +32,41 @@ bool Board::isPositionLegal() const { constexpr PieceColor opponentColor = !movedColor; constexpr Piece king = movedColor == WHITE ? WHITE_KING : BLACK_KING; const uint64_t kingBB = getBitboard(); - const uint8_t kingSquare = bitscanForward(kingBB); + const Square kingSquare = bitboardToSquare(kingBB); + const Move lastMove = getLastMove(); + const MoveType lastMoveType = getMoveType(lastMove); + + if (lastMoveType == CASTLING) { + const uint8_t fromSquare = getFromSquare(lastMove); + const uint8_t toSquare = getToSquare(lastMove); + const uint64_t fromSquareAttacks = getSquareAttackersByColor(fromSquare); + + if (fromSquareAttacks) { + // King was in check before castling + return false; + } + + uint64_t castlingPath = 0; + + if (toSquare == G1) { + castlingPath = WHITE_KINGSIDE_CASTLE_PATH; + } else if (toSquare == C1) { + castlingPath = WHITE_QUEENSIDE_CASTLE_PATH; + } else if (toSquare == G8) { + castlingPath = BLACK_KINGSIDE_CASTLE_PATH; + } else if (toSquare == C8) { + castlingPath = BLACK_QUEENSIDE_CASTLE_PATH; + } + + while (castlingPath) { + const uint8_t square = popLsb(castlingPath); + const uint64_t attackers = getSquareAttackersByColor(square); + + if (attackers) { + return false; + } + } + } return !getSquareAttackersByColor(kingSquare); } @@ -40,6 +74,39 @@ bool Board::isPositionLegal() const { template bool Board::isPositionLegal() const; template bool Board::isPositionLegal() const; +// Checks everything except for attacks on the castling path or if the king is in check +template +bool Board::canCastle() const { + assert(side != WHITE_CASTLING && side != BLACK_CASTLING); + + if (!(castlingRights & side)) { + return false; + } + + uint64_t castlingPath = 0; + + if constexpr (side == WHITE_KINGSIDE) { + castlingPath = WHITE_KINGSIDE_CASTLE_PATH; + } else if constexpr (side == WHITE_QUEENSIDE) { + castlingPath = WHITE_QUEENSIDE_CASTLE_PATH; + } else if constexpr (side == BLACK_KINGSIDE) { + castlingPath = BLACK_KINGSIDE_CASTLE_PATH; + } else if constexpr (side == BLACK_QUEENSIDE) { + castlingPath = BLACK_QUEENSIDE_CASTLE_PATH; + } + + if (occupied & castlingPath) { + return false; + } + + return true; +} + +template bool Board::canCastle() const; +template bool Board::canCastle() const; +template bool Board::canCastle() const; +template bool Board::canCastle() const; + uint64_t Board::getSquareAttackers(const uint8_t square) const { assert(square < SQUARES); const uint64_t knights = getBitboard() | getBitboard(); @@ -65,8 +132,9 @@ void Board::reset() { this->sideToMove = WHITE; this->history = {}; this->ply = 0; + this->castlingRights = 0; - std::ranges::fill(board, Piece::EMPTY); + std::ranges::fill(board, EMPTY); std::ranges::fill(bitboards, 0); std::ranges::fill(colorBoards, 0); std::ranges::fill(history, BoardState{}); @@ -92,25 +160,82 @@ void Board::makeMove(const Move& move) { const uint8_t toSquare = getToSquare(move); const MoveType moveType = getMoveType(move); const Piece movedPiece = getPieceOnSquare(fromSquare); - const PieceType movedPieceType = pieceType(movedPiece); + const PieceType movedPieceType = getPieceType(movedPiece); const Piece capturedPiece = getPieceOnSquare(toSquare); - if (capturedPiece != Piece::EMPTY) { + history[ply].move = move; + history[ply].capturedPiece = capturedPiece; + history[ply].enPassantSquare = enPassantSquare; + history[ply].castlingRights = castlingRights; + + if (capturedPiece != EMPTY) { removePiece(capturedPiece, toSquare); + + if (capturedPiece == WHITE_ROOK) { + if (toSquare == A8) { + castlingRights &= ~BLACK_QUEENSIDE; + } else if (toSquare == H8) { + castlingRights &= ~BLACK_KINGSIDE; + } + } else if (capturedPiece == BLACK_ROOK) { + if (toSquare == A1) { + castlingRights &= ~WHITE_QUEENSIDE; + } else if (toSquare == H1) { + castlingRights &= ~WHITE_KINGSIDE; + } + } } removePiece(movedPiece, fromSquare); setPiece(movedPiece, toSquare); - if (movedPieceType == PAWN) { - if (moveType == EN_PASSANT) { - if (sideToMove == WHITE) { - removePiece(BLACK_PAWN, toSquare + SOUTH); - } else { - removePiece(WHITE_PAWN, toSquare + NORTH); - } + if (moveType == EN_PASSANT) { + if (sideToMove == WHITE) { + removePiece(BLACK_PAWN, toSquare + SOUTH); + } else { + removePiece(WHITE_PAWN, toSquare + NORTH); + } + } else if (moveType == CASTLING) { + if (toSquare == G1) { + removePiece(WHITE_ROOK, H1); + setPiece(WHITE_ROOK, F1); + } else if (toSquare == C1) { + removePiece(WHITE_ROOK, A1); + setPiece(WHITE_ROOK, D1); + } else if (toSquare == G8) { + removePiece(BLACK_ROOK, H8); + setPiece(BLACK_ROOK, F8); + } else if (toSquare == C8) { + removePiece(BLACK_ROOK, A8); + setPiece(BLACK_ROOK, D8); } + if (sideToMove == WHITE) { + castlingRights &= ~WHITE_CASTLING; + } else { + castlingRights &= ~BLACK_CASTLING; + } + } + + if (movedPiece == WHITE_KING) { + castlingRights &= ~WHITE_CASTLING; + } else if (movedPiece == BLACK_KING) { + castlingRights &= ~BLACK_CASTLING; + } else if (movedPiece == WHITE_ROOK) { + if (fromSquare == A1) { + castlingRights &= ~WHITE_QUEENSIDE; + } else if (fromSquare == H1) { + castlingRights &= ~WHITE_KINGSIDE; + } + } else if (movedPiece == BLACK_ROOK) { + if (fromSquare == A8) { + castlingRights &= ~BLACK_QUEENSIDE; + } else if (fromSquare == H8) { + castlingRights &= ~BLACK_KINGSIDE; + } + } + + if (movedPieceType == PAWN) { if ((fromSquare ^ toSquare) == 16) { if (sideToMove == WHITE) { enPassantSquare = toSquare + SOUTH; @@ -126,9 +251,6 @@ void Board::makeMove(const Move& move) { sideToMove = !sideToMove; assert(ply >= 0 && ply < MAX_PLY); - history[ply].move = move; - history[ply].capturedPiece = capturedPiece; - history[ply].enPassantSquare = enPassantSquare; ply++; assert(ply >= 0 && ply < MAX_PLY); } @@ -136,7 +258,7 @@ void Board::makeMove(const Move& move) { void Board::unmakeMove() { ply--; assert(ply >= 0 && ply < MAX_PLY); - const auto& [move, capturedPiece, enPassantSquare] = history[ply]; + const auto& [move, capturedPiece, enPassantSquare, castlingRights] = history[ply]; const uint8_t fromSquare = getFromSquare(move); const uint8_t toSquare = getToSquare(move); const MoveType moveType = getMoveType(move); @@ -145,12 +267,12 @@ void Board::unmakeMove() { removePiece(movedPiece, toSquare); setPiece(movedPiece, fromSquare); - if (capturedPiece != Piece::EMPTY) { + if (capturedPiece != EMPTY) { setPiece(capturedPiece, toSquare); } if (moveType == EN_PASSANT) { - const PieceColor movedPieceColor = pieceColor(movedPiece); + const PieceColor movedPieceColor = getPieceColor(movedPiece); if (movedPieceColor == WHITE) { setPiece(BLACK_PAWN, toSquare + SOUTH); @@ -159,8 +281,25 @@ void Board::unmakeMove() { } } + if (moveType == CASTLING) { + if (toSquare == G1) { + removePiece(WHITE_ROOK, F1); + setPiece(WHITE_ROOK, H1); + } else if (toSquare == C1) { + removePiece(WHITE_ROOK, D1); + setPiece(WHITE_ROOK, A1); + } else if (toSquare == G8) { + removePiece(BLACK_ROOK, F8); + setPiece(BLACK_ROOK, H8); + } else if (toSquare == C8) { + removePiece(BLACK_ROOK, D8); + setPiece(BLACK_ROOK, A8); + } + } + this->sideToMove = !sideToMove; this->enPassantSquare = enPassantSquare; + this->castlingRights = castlingRights; } void Board::setPieceFromFENChar(const char character, const uint8_t square) { @@ -254,26 +393,32 @@ bool Board::setFromFEN(const std::string_view fen) { } } - // TODO: Implement and add zobrist logic once castling is implemented - /*if (spaces == 2) { + // TODO: Add zobrist hashes + if (spaces == 2) { if (character == '-') { continue; } else if (character == 'K') { castlingRights |= WHITE_KINGSIDE; continue; - } else if (character == 'Q') { + } + + if (character == 'Q') { castlingRights |= WHITE_QUEENSIDE; continue; - } else if (character == 'k') { + } + + if (character == 'k') { castlingRights |= BLACK_KINGSIDE; continue; - } else if (character == 'q') { + } + + if (character == 'q') { castlingRights |= BLACK_QUEENSIDE; continue; } return false; - }*/ + } if (spaces == 3) { if (character == '-') { diff --git a/src/board.h b/src/board.h index ed630f31..12fc3bb3 100644 --- a/src/board.h +++ b/src/board.h @@ -36,64 +36,78 @@ namespace Zagreus { struct BoardState { Move move = 0; - Piece capturedPiece = Piece::EMPTY; + Piece capturedPiece = EMPTY; uint8_t enPassantSquare = 0; + uint8_t castlingRights = 0; }; class Board { private: std::array board{}; std::array bitboards{}; - uint64_t occupied = 0; std::array colorBoards{}; - PieceColor sideToMove = PieceColor::WHITE; + PieceColor sideToMove = WHITE; std::array history{}; - int ply = 0; + uint64_t occupied = 0; + uint16_t ply = 0; + uint8_t castlingRights = 0; uint8_t enPassantSquare = 0; public: Board() { - std::ranges::fill(board, Piece::EMPTY); + std::ranges::fill(board, EMPTY); std::ranges::fill(bitboards, 0); std::ranges::fill(colorBoards, 0); std::ranges::fill(history, BoardState{}); } template - [[nodiscard]] uint64_t getBitboard() const { + [[nodiscard]] constexpr uint64_t getBitboard() const { return bitboards[piece]; } template - [[nodiscard]] uint64_t getColorBitboard() const { + [[nodiscard]] constexpr uint64_t getColorBitboard() const { return colorBoards[color]; } - [[nodiscard]] Piece getPieceOnSquare(const int square) const { + [[nodiscard]] constexpr Piece getPieceOnSquare(const int square) const { assert(square >= 0 && square < 64); return board[square]; } - [[nodiscard]] bool isPieceOnSquare(const int square) const { + [[nodiscard]] constexpr bool isPieceOnSquare(const int square) const { return board[square] != Piece::EMPTY; } - [[nodiscard]] uint64_t getOccupiedBitboard() const { + [[nodiscard]] constexpr uint64_t getOccupiedBitboard() const { return occupied; } - [[nodiscard]] uint64_t getEmptyBitboard() const { + [[nodiscard]] constexpr uint64_t getEmptyBitboard() const { return ~occupied; } - [[nodiscard]] PieceColor getSideToMove() const { + [[nodiscard]] constexpr PieceColor getSideToMove() const { return sideToMove; } - [[nodiscard]] int getPly() const { + [[nodiscard]] constexpr int getPly() const { return ply; } + [[nodiscard]] constexpr uint8_t getCastlingRights() const { + return castlingRights; + } + + [[nodiscard]] constexpr uint8_t getEnPassantSquare() const { + return enPassantSquare; + } + + [[nodiscard]] constexpr Move getLastMove() const { + return history[ply - 1].move; + } + template void setPiece(const uint8_t square) { assert(piece != Piece::EMPTY); @@ -103,7 +117,7 @@ class Board { board[square] = piece; bitboards[piece] |= squareBB; occupied |= squareBB; - colorBoards[pieceColor(piece)] |= squareBB; + colorBoards[getPieceColor(piece)] |= squareBB; } void setPiece(const Piece piece, const uint8_t square) { @@ -114,7 +128,7 @@ class Board { board[square] = piece; bitboards[piece] |= squareBB; occupied |= squareBB; - colorBoards[pieceColor(piece)] |= squareBB; + colorBoards[getPieceColor(piece)] |= squareBB; } void removePiece(const uint8_t square) { @@ -122,10 +136,10 @@ class Board { const Piece piece = board[square]; assert(piece != Piece::EMPTY); - board[square] = Piece::EMPTY; + board[square] = EMPTY; bitboards[piece] &= ~squareBB; occupied &= ~squareBB; - colorBoards[pieceColor(piece)] &= ~squareBB; + colorBoards[getPieceColor(piece)] &= ~squareBB; } template @@ -137,7 +151,7 @@ class Board { board[square] = Piece::EMPTY; bitboards[piece] &= ~squareBB; occupied &= ~squareBB; - colorBoards[pieceColor(piece)] &= ~squareBB; + colorBoards[getPieceColor(piece)] &= ~squareBB; } void removePiece(const Piece piece, const uint8_t square) { @@ -148,12 +162,13 @@ class Board { board[square] = Piece::EMPTY; bitboards[piece] &= ~squareBB; occupied &= ~squareBB; - colorBoards[pieceColor(piece)] &= ~squareBB; + colorBoards[getPieceColor(piece)] &= ~squareBB; } void makeMove(const Move& move); void unmakeMove(); + void setPieceFromFENChar(char character, uint8_t square); template @@ -166,9 +181,8 @@ class Board { return getSquareAttackers(square) & getColorBitboard(); } - [[nodiscard]] uint8_t getEnPassantSquare() const { - return enPassantSquare; - } + template + bool canCastle() const; bool setFromFEN(std::string_view fen); diff --git a/src/constants.h b/src/constants.h index d971c0a8..3ab42aca 100644 --- a/src/constants.h +++ b/src/constants.h @@ -25,22 +25,27 @@ static constexpr uint16_t MAX_PLY = 750; -constexpr uint64_t NOT_A_FILE = 0xFEFEFEFEFEFEFEFEULL; -constexpr uint64_t NOT_AB_FILE = 0xFCFCFCFCFCFCFCFCULL; -constexpr uint64_t NOT_GH_FILE = 0x3F3F3F3F3F3F3F3FULL; -constexpr uint64_t NOT_H_FILE = 0x7F7F7F7F7F7F7F7FULL; - -constexpr uint64_t RANK_1 = 0x00000000000000FFULL; -constexpr uint64_t RANK_2 = 0x000000000000FF00ULL; -constexpr uint64_t RANK_3 = 0x0000000000FF0000ULL; -constexpr uint64_t RANK_4 = 0x00000000FF000000ULL; -constexpr uint64_t RANK_5 = 0x000000FF00000000ULL; -constexpr uint64_t RANK_6 = 0x0000FF0000000000ULL; -constexpr uint64_t RANK_7 = 0x00FF000000000000ULL; -constexpr uint64_t RANK_8 = 0xFF00000000000000ULL; - -constexpr uint8_t SQUARES = 64; -constexpr uint8_t PIECES = 12; -constexpr uint8_t COLORS = 2; - -constexpr uint8_t MAX_MOVES = 255; +static constexpr uint64_t NOT_A_FILE = 0xFEFEFEFEFEFEFEFEULL; +static constexpr uint64_t NOT_AB_FILE = 0xFCFCFCFCFCFCFCFCULL; +static constexpr uint64_t NOT_GH_FILE = 0x3F3F3F3F3F3F3F3FULL; +static constexpr uint64_t NOT_H_FILE = 0x7F7F7F7F7F7F7F7FULL; + +static constexpr uint64_t RANK_1 = 0x00000000000000FFULL; +static constexpr uint64_t RANK_2 = 0x000000000000FF00ULL; +static constexpr uint64_t RANK_3 = 0x0000000000FF0000ULL; +static constexpr uint64_t RANK_4 = 0x00000000FF000000ULL; +static constexpr uint64_t RANK_5 = 0x000000FF00000000ULL; +static constexpr uint64_t RANK_6 = 0x0000FF0000000000ULL; +static constexpr uint64_t RANK_7 = 0x00FF000000000000ULL; +static constexpr uint64_t RANK_8 = 0xFF00000000000000ULL; + +static constexpr uint64_t WHITE_KINGSIDE_CASTLE_PATH = 0x60ULL; +static constexpr uint64_t WHITE_QUEENSIDE_CASTLE_PATH = 0xEULL; +static constexpr uint64_t BLACK_KINGSIDE_CASTLE_PATH = 0x6000000000000000ULL; +static constexpr uint64_t BLACK_QUEENSIDE_CASTLE_PATH = 0xE00000000000000ULL; + +static constexpr uint8_t SQUARES = 64; +static constexpr uint8_t PIECES = 12; +static constexpr uint8_t COLORS = 2; + +static constexpr uint8_t MAX_MOVES = 255; diff --git a/src/move.cpp b/src/move.cpp index 785fbb44..4285ea5e 100644 --- a/src/move.cpp +++ b/src/move.cpp @@ -18,10 +18,10 @@ along with Zagreus. If not, see . */ -#include "move.h" - #include +#include "move.h" + namespace Zagreus { std::string getMoveNotation(const uint8_t fromSquare, const uint8_t toSquare) { // TODO: Support promotions diff --git a/src/move.h b/src/move.h index 8da87834..51b71d07 100644 --- a/src/move.h +++ b/src/move.h @@ -81,7 +81,7 @@ inline Move encodeMove(const uint8_t fromSquare, const uint8_t toSquare, const P } inline uint8_t getFromSquare(const Move move) { - return static_cast(move & 0x3F); + return move & 0x3F; } inline uint8_t getToSquare(const Move move) { diff --git a/src/move_gen.cpp b/src/move_gen.cpp index b003aa0d..74785166 100644 --- a/src/move_gen.cpp +++ b/src/move_gen.cpp @@ -218,17 +218,45 @@ void generateQueenMoves(const Board& board, MoveList& moves, const uint64_t genM template void generateKingMoves(const Board& board, MoveList& moves, const uint64_t genMask) { - // TODO: Implement castling constexpr Piece king = color == WHITE ? WHITE_KING : BLACK_KING; uint64_t kingBB = board.getBitboard(); + const uint8_t fromSquare = popLsb(kingBB); + uint64_t genBB = kingAttacks(fromSquare) & genMask; - while (kingBB) { - const uint8_t fromSquare = popLsb(kingBB); - uint64_t genBB = kingAttacks(fromSquare) & genMask; + while (genBB) { + const uint8_t toSquare = popLsb(genBB); + const Move move = encodeMove(fromSquare, toSquare); - while (genBB) { - const uint8_t toSquare = popLsb(genBB); - const Move move = encodeMove(fromSquare, toSquare); + moves.moves[moves.size] = move; + moves.size++; + } + + const uint8_t castlingRights = board.getCastlingRights(); + + if constexpr (color == WHITE) { + if (castlingRights & WHITE_KINGSIDE && board.canCastle()) { + const Move move = encodeMove(E1, G1, CASTLING); + + moves.moves[moves.size] = move; + moves.size++; + } + + if (castlingRights & WHITE_QUEENSIDE && board.canCastle()) { + const Move move = encodeMove(E1, C1, CASTLING); + + moves.moves[moves.size] = move; + moves.size++; + } + } else if constexpr (color == BLACK) { + if (castlingRights & BLACK_KINGSIDE && board.canCastle()) { + const Move move = encodeMove(E8, G8, CASTLING); + + moves.moves[moves.size] = move; + moves.size++; + } + + if (castlingRights & BLACK_QUEENSIDE && board.canCastle()) { + const Move move = encodeMove(E8, C8, CASTLING); moves.moves[moves.size] = move; moves.size++; diff --git a/src/types.h b/src/types.h index 357d2cc2..74c2d825 100644 --- a/src/types.h +++ b/src/types.h @@ -81,12 +81,21 @@ enum Piece : uint8_t { EMPTY = 255 }; -constexpr PieceColor pieceColor(const Piece piece) { +enum CastlingRights : uint8_t { + WHITE_KINGSIDE = 0b00000001, + WHITE_QUEENSIDE = 0b00000010, + BLACK_KINGSIDE = 0b00000100, + BLACK_QUEENSIDE = 0b00001000, + WHITE_CASTLING = 0b00000011, + BLACK_CASTLING = 0b00001100, +}; + +constexpr PieceColor getPieceColor(const Piece piece) { assert(piece != Piece::EMPTY); return static_cast(piece % 2); } -constexpr PieceType pieceType(const Piece piece) { +constexpr PieceType getPieceType(const Piece piece) { assert(piece != Piece::EMPTY); return static_cast(piece / 2); } diff --git a/src/uci.cpp b/src/uci.cpp index 1ed41a90..07a80f72 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -18,8 +18,7 @@ along with Zagreus. If not, see . */ -// ReSharper disable CppRedundantControlFlowJump -#include +#include #include #include #include @@ -60,22 +59,23 @@ std::string Engine::getVersionString() { } void Engine::printStartupMessage() { - sendMessage(" ______ "); - sendMessage(" |___ / "); - sendMessage(" / / __ _ __ _ _ __ ___ _ _ ___ "); - sendMessage(" / / / _` | / _` || '__|/ _ \\| | | |/ __|"); - sendMessage(" / /__| (_| || (_| || | | __/| |_| |\\__ \\"); - sendMessage(R"( /_____|\__,_| \__, ||_| \___| \__,_||___/)"); - sendMessage(" __/ | "); - sendMessage(" |___/ "); - sendMessage(""); - sendMessage("Zagreus Copyright (C) 2023-2024 Danny Jelsma"); - sendMessage(""); - sendMessage("This program comes with ABSOLUTELY NO WARRANTY."); - sendMessage("This is free software, and you are welcome to redistribute it"); - sendMessage("under the conditions of the GNU Affero General Public License v3.0 or later."); - sendMessage("You should have received a copy of the GNU Affero General Public License"); - sendMessage("along with this program. If not, see ."); + sendMessage(R"( + ______ + |___ / + / / __ _ __ _ _ __ ___ _ _ ___ + / / / _` | / _` || '__|/ _ \| | | |/ __| + / /__| (_| || (_| || | | __/| |_| |\__ \ + /_____|\__,_| \__, ||_| \___| \__,_||___/ + __/ | + |___/ + +Zagreus Copyright (C) 2023-2024 Danny Jelsma + +This program comes with ABSOLUTELY NO WARRANTY. +This is free software, and you are welcome to redistribute it +under the conditions of the GNU Affero General Public License v3.0 or later. +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see .)"); sendMessage(""); sendMessage("Zagreus UCI chess engine " + getVersionString() + " by Danny Jelsma (https://github.com/Dannyj1/Zagreus)"); diff --git a/tests/types_tests.cpp b/tests/types_tests.cpp index 7c6bb5e7..89e96db2 100644 --- a/tests/types_tests.cpp +++ b/tests/types_tests.cpp @@ -24,18 +24,18 @@ // Test all pieces at once for correct identification of piece type TEST_CASE("test_pieceType", "[types]") { - REQUIRE(pieceType(Piece::WHITE_PAWN) == PieceType::PAWN); - REQUIRE(pieceType(Piece::BLACK_PAWN) == PieceType::PAWN); - REQUIRE(pieceType(Piece::WHITE_KNIGHT) == PieceType::KNIGHT); - REQUIRE(pieceType(Piece::BLACK_KNIGHT) == PieceType::KNIGHT); - REQUIRE(pieceType(Piece::WHITE_BISHOP) == PieceType::BISHOP); - REQUIRE(pieceType(Piece::BLACK_BISHOP) == PieceType::BISHOP); - REQUIRE(pieceType(Piece::WHITE_ROOK) == PieceType::ROOK); - REQUIRE(pieceType(Piece::BLACK_ROOK) == PieceType::ROOK); - REQUIRE(pieceType(Piece::WHITE_QUEEN) == PieceType::QUEEN); - REQUIRE(pieceType(Piece::BLACK_QUEEN) == PieceType::QUEEN); - REQUIRE(pieceType(Piece::WHITE_KING) == PieceType::KING); - REQUIRE(pieceType(Piece::BLACK_KING) == PieceType::KING); + REQUIRE(getPieceType(Piece::WHITE_PAWN) == PieceType::PAWN); + REQUIRE(getPieceType(Piece::BLACK_PAWN) == PieceType::PAWN); + REQUIRE(getPieceType(Piece::WHITE_KNIGHT) == PieceType::KNIGHT); + REQUIRE(getPieceType(Piece::BLACK_KNIGHT) == PieceType::KNIGHT); + REQUIRE(getPieceType(Piece::WHITE_BISHOP) == PieceType::BISHOP); + REQUIRE(getPieceType(Piece::BLACK_BISHOP) == PieceType::BISHOP); + REQUIRE(getPieceType(Piece::WHITE_ROOK) == PieceType::ROOK); + REQUIRE(getPieceType(Piece::BLACK_ROOK) == PieceType::ROOK); + REQUIRE(getPieceType(Piece::WHITE_QUEEN) == PieceType::QUEEN); + REQUIRE(getPieceType(Piece::BLACK_QUEEN) == PieceType::QUEEN); + REQUIRE(getPieceType(Piece::WHITE_KING) == PieceType::KING); + REQUIRE(getPieceType(Piece::BLACK_KING) == PieceType::KING); } // Test the pieceColor function for determining the color of all pieces as white or black From 5e72925e0f77b979545fc5d30e036b4b668a92fe Mon Sep 17 00:00:00 2001 From: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> Date: Sun, 13 Oct 2024 15:32:18 +0200 Subject: [PATCH 29/91] Fixed castling bugs --- src/board.cpp | 13 +++++-------- src/constants.h | 9 +++++++-- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/board.cpp b/src/board.cpp index f481c4f4..64e4a7ce 100644 --- a/src/board.cpp +++ b/src/board.cpp @@ -86,13 +86,13 @@ bool Board::canCastle() const { uint64_t castlingPath = 0; if constexpr (side == WHITE_KINGSIDE) { - castlingPath = WHITE_KINGSIDE_CASTLE_PATH; + castlingPath = WHITE_KINGSIDE_CASTLE_UNOCCUPIED; } else if constexpr (side == WHITE_QUEENSIDE) { - castlingPath = WHITE_QUEENSIDE_CASTLE_PATH; + castlingPath = WHITE_QUEENSIDE_CASTLE_UNOCCUPIED; } else if constexpr (side == BLACK_KINGSIDE) { - castlingPath = BLACK_KINGSIDE_CASTLE_PATH; + castlingPath = BLACK_KINGSIDE_CASTLE_UNOCCUPIED; } else if constexpr (side == BLACK_QUEENSIDE) { - castlingPath = BLACK_QUEENSIDE_CASTLE_PATH; + castlingPath = BLACK_QUEENSIDE_CASTLE_UNOCCUPIED; } if (occupied & castlingPath) { @@ -166,6 +166,7 @@ void Board::makeMove(const Move& move) { history[ply].move = move; history[ply].capturedPiece = capturedPiece; history[ply].enPassantSquare = enPassantSquare; + enPassantSquare = 0; history[ply].castlingRights = castlingRights; if (capturedPiece != EMPTY) { @@ -242,11 +243,7 @@ void Board::makeMove(const Move& move) { } else { enPassantSquare = toSquare + NORTH; } - } else { - enPassantSquare = 0; } - } else { - enPassantSquare = 0; } sideToMove = !sideToMove; diff --git a/src/constants.h b/src/constants.h index 3ab42aca..f2a396ac 100644 --- a/src/constants.h +++ b/src/constants.h @@ -40,9 +40,14 @@ static constexpr uint64_t RANK_7 = 0x00FF000000000000ULL; static constexpr uint64_t RANK_8 = 0xFF00000000000000ULL; static constexpr uint64_t WHITE_KINGSIDE_CASTLE_PATH = 0x60ULL; -static constexpr uint64_t WHITE_QUEENSIDE_CASTLE_PATH = 0xEULL; +static constexpr uint64_t WHITE_QUEENSIDE_CASTLE_PATH = 0xCULL; static constexpr uint64_t BLACK_KINGSIDE_CASTLE_PATH = 0x6000000000000000ULL; -static constexpr uint64_t BLACK_QUEENSIDE_CASTLE_PATH = 0xE00000000000000ULL; +static constexpr uint64_t BLACK_QUEENSIDE_CASTLE_PATH = 0xC00000000000000ULL; + +static constexpr uint64_t WHITE_KINGSIDE_CASTLE_UNOCCUPIED = 0x60ULL; +static constexpr uint64_t WHITE_QUEENSIDE_CASTLE_UNOCCUPIED = 0xEULL; +static constexpr uint64_t BLACK_KINGSIDE_CASTLE_UNOCCUPIED = 0x6000000000000000ULL; +static constexpr uint64_t BLACK_QUEENSIDE_CASTLE_UNOCCUPIED = 0xE00000000000000ULL; static constexpr uint8_t SQUARES = 64; static constexpr uint8_t PIECES = 12; From abb88e5b9ce60790f188d824160cc5c46c0f294b Mon Sep 17 00:00:00 2001 From: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> Date: Sun, 13 Oct 2024 17:42:52 +0200 Subject: [PATCH 30/91] Implemented promotions, fix more castling bugs. Move gen seems to be correct now, tested on multiple positions --- src/board.cpp | 33 +++++++++++++++++++------- src/move.cpp | 8 +++---- src/move.h | 24 +++++++++++++++---- src/move_gen.cpp | 61 ++++++++++++++++++++++++++++++++++++------------ src/types.h | 2 ++ 5 files changed, 96 insertions(+), 32 deletions(-) diff --git a/src/board.cpp b/src/board.cpp index 64e4a7ce..30c1a6c2 100644 --- a/src/board.cpp +++ b/src/board.cpp @@ -159,7 +159,7 @@ void Board::makeMove(const Move& move) { const uint8_t fromSquare = getFromSquare(move); const uint8_t toSquare = getToSquare(move); const MoveType moveType = getMoveType(move); - const Piece movedPiece = getPieceOnSquare(fromSquare); + Piece movedPiece = getPieceOnSquare(fromSquare); const PieceType movedPieceType = getPieceType(movedPiece); const Piece capturedPiece = getPieceOnSquare(toSquare); @@ -173,22 +173,31 @@ void Board::makeMove(const Move& move) { removePiece(capturedPiece, toSquare); if (capturedPiece == WHITE_ROOK) { - if (toSquare == A8) { - castlingRights &= ~BLACK_QUEENSIDE; - } else if (toSquare == H8) { - castlingRights &= ~BLACK_KINGSIDE; - } - } else if (capturedPiece == BLACK_ROOK) { if (toSquare == A1) { castlingRights &= ~WHITE_QUEENSIDE; } else if (toSquare == H1) { castlingRights &= ~WHITE_KINGSIDE; } + } else if (capturedPiece == BLACK_ROOK) { + if (toSquare == A8) { + castlingRights &= ~BLACK_QUEENSIDE; + } else if (toSquare == H8) { + castlingRights &= ~BLACK_KINGSIDE; + } } } removePiece(movedPiece, fromSquare); - setPiece(movedPiece, toSquare); + + if (moveType == PROMOTION) { + const PieceColor color = getPieceColor(movedPiece); + const PromotionPiece promotionPieceType = getPromotionPiece(move); + const Piece promotionPiece = getPieceFromPromotionPiece(promotionPieceType, color); + + setPiece(promotionPiece, toSquare); + } else { + setPiece(movedPiece, toSquare); + } if (moveType == EN_PASSANT) { if (sideToMove == WHITE) { @@ -259,9 +268,15 @@ void Board::unmakeMove() { const uint8_t fromSquare = getFromSquare(move); const uint8_t toSquare = getToSquare(move); const MoveType moveType = getMoveType(move); - const Piece movedPiece = getPieceOnSquare(toSquare); + Piece movedPiece = getPieceOnSquare(toSquare); removePiece(movedPiece, toSquare); + + if (moveType == PROMOTION) { + const PieceColor color = getPieceColor(movedPiece); + movedPiece = color == WHITE ? WHITE_PAWN : BLACK_PAWN; + } + setPiece(movedPiece, fromSquare); if (capturedPiece != EMPTY) { diff --git a/src/move.cpp b/src/move.cpp index 4285ea5e..7d80c8a0 100644 --- a/src/move.cpp +++ b/src/move.cpp @@ -77,16 +77,16 @@ Move fromMoveNotation(const std::string_view notation) { switch (promotionPieceChar) { case 'q': - promotionPiece = QUEEN; + promotionPiece = QUEEN_PROMOTION; break; case 'r': - promotionPiece = ROOK; + promotionPiece = ROOK_PROMOTION; break; case 'b': - promotionPiece = BISHOP; + promotionPiece = BISHOP_PROMOTION; break; case 'n': - promotionPiece = KNIGHT; + promotionPiece = KNIGHT_PROMOTION; break; default: assert(false); diff --git a/src/move.h b/src/move.h index 51b71d07..8779e52a 100644 --- a/src/move.h +++ b/src/move.h @@ -45,10 +45,10 @@ enum MoveType : uint8_t { }; enum PromotionPiece : uint8_t { - QUEEN = 0b00, - ROOK = 0b01, - BISHOP = 0b10, - KNIGHT = 0b11 + QUEEN_PROMOTION = 0b00, + ROOK_PROMOTION = 0b01, + BISHOP_PROMOTION = 0b10, + KNIGHT_PROMOTION = 0b11 }; std::string getMoveNotation(uint8_t fromSquare, uint8_t toSquare); @@ -95,4 +95,20 @@ inline MoveType getMoveType(const Move move) { inline PromotionPiece getPromotionPiece(const Move move) { return static_cast((move >> 14) & 0x3); } + +inline Piece getPieceFromPromotionPiece(const PromotionPiece promotionPiece, const PieceColor color) { + switch (promotionPiece) { + case QUEEN_PROMOTION: + return color == WHITE ? WHITE_QUEEN : BLACK_QUEEN; + case ROOK_PROMOTION: + return color == WHITE ? WHITE_ROOK : BLACK_ROOK; + case BISHOP_PROMOTION: + return color == WHITE ? WHITE_BISHOP : BLACK_BISHOP; + case KNIGHT_PROMOTION: + return color == WHITE ? WHITE_KNIGHT : BLACK_KNIGHT; + default: + assert(false); + return EMPTY; + } +} } // namespace Zagreus diff --git a/src/move_gen.cpp b/src/move_gen.cpp index 74785166..69b29638 100644 --- a/src/move_gen.cpp +++ b/src/move_gen.cpp @@ -88,14 +88,25 @@ void generatePawnMoves(const Board& board, MoveList& moves, const uint64_t genMa constexpr Direction fromPushDirection = color == WHITE ? NORTH : SOUTH; constexpr Direction fromSqWestAttackDirection = color == WHITE ? NORTH_WEST : SOUTH_WEST; constexpr Direction fromSqEastAttackDirection = color == WHITE ? NORTH_EAST : SOUTH_EAST; + constexpr uint64_t promotionRank = color == WHITE ? RANK_8 : RANK_1; while (pawnSinglePushes) { const uint8_t squareTo = popLsb(pawnSinglePushes); + const uint64_t squareToBB = squareToBitboard(squareTo); const uint8_t squareFrom = squareTo - fromPushDirection; - const Move move = encodeMove(squareFrom, squareTo); - moves.moves[moves.size] = move; - moves.size++; + if (squareToBB & promotionRank) { + for (const PromotionPiece promotionPiece : {QUEEN_PROMOTION, ROOK_PROMOTION, BISHOP_PROMOTION, KNIGHT_PROMOTION}) { + const Move move = encodeMove(squareFrom, squareTo, promotionPiece); + moves.moves[moves.size] = move; + moves.size++; + } + } else { + const Move move = encodeMove(squareFrom, squareTo); + + moves.moves[moves.size] = move; + moves.size++; + } } while (pawnDoublePushes) { @@ -109,30 +120,50 @@ void generatePawnMoves(const Board& board, MoveList& moves, const uint64_t genMa while (pawnWestAttacks) { const uint8_t squareTo = popLsb(pawnWestAttacks); + const uint64_t squareToBB = squareToBitboard(squareTo); const uint8_t squareFrom = squareTo - fromSqWestAttackDirection; - MoveType moveType = NORMAL; if (squareTo == board.getEnPassantSquare() && squareToBitboard(squareTo) & enPassantMask) { - moveType = EN_PASSANT; + const Move move = encodeMove(squareFrom, squareTo, EN_PASSANT); + moves.moves[moves.size] = move; + moves.size++; + } else { + if (squareToBB & promotionRank) { + for (const PromotionPiece promotionPiece : {QUEEN_PROMOTION, ROOK_PROMOTION, BISHOP_PROMOTION, KNIGHT_PROMOTION}) { + const Move move = encodeMove(squareFrom, squareTo, promotionPiece); + moves.moves[moves.size] = move; + moves.size++; + } + } else { + const Move move = encodeMove(squareFrom, squareTo); + moves.moves[moves.size] = move; + moves.size++; + } } - - const Move move = encodeMove(squareFrom, squareTo, moveType); - moves.moves[moves.size] = move; - moves.size++; } while (pawnEastAttacks) { const uint8_t squareTo = popLsb(pawnEastAttacks); + const uint64_t squareToBB = squareToBitboard(squareTo); const uint8_t squareFrom = squareTo - fromSqEastAttackDirection; - MoveType moveType = NORMAL; if (squareTo == board.getEnPassantSquare() && squareToBitboard(squareTo) & enPassantMask) { - moveType = EN_PASSANT; + const Move move = encodeMove(squareFrom, squareTo, EN_PASSANT); + moves.moves[moves.size] = move; + moves.size++; + } else { + if (squareToBB & promotionRank) { + for (const PromotionPiece promotionPiece : {QUEEN_PROMOTION, ROOK_PROMOTION, BISHOP_PROMOTION, KNIGHT_PROMOTION}) { + const Move move = encodeMove(squareFrom, squareTo, promotionPiece); + moves.moves[moves.size] = move; + moves.size++; + } + } else { + const Move move = encodeMove(squareFrom, squareTo); + moves.moves[moves.size] = move; + moves.size++; + } } - - const Move move = encodeMove(squareFrom, squareTo, moveType); - moves.moves[moves.size] = move; - moves.size++; } } diff --git a/src/types.h b/src/types.h index 74c2d825..51d1f7e4 100644 --- a/src/types.h +++ b/src/types.h @@ -23,6 +23,7 @@ #include #include +namespace Zagreus { enum Direction { NORTH = 8, SOUTH = -8, @@ -132,3 +133,4 @@ constexpr char getCharacterForPieceType(const Piece piece) { return ' '; } +} // namespace Zagreus From cd7b627f47e7a5fb1f1156071254bc3cf8fff0a5 Mon Sep 17 00:00:00 2001 From: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> Date: Sun, 13 Oct 2024 20:13:56 +0200 Subject: [PATCH 31/91] Slight performance improvements --- src/bitboard.cpp | 16 ++++++++-------- src/bitboard.h | 10 +++++----- src/board.cpp | 14 +++++++------- src/board.h | 4 ++-- src/move.h | 10 +++++----- src/move_gen.cpp | 21 ++++++++++++--------- src/perft.cpp | 2 +- src/types.h | 8 ++++---- 8 files changed, 44 insertions(+), 41 deletions(-) diff --git a/src/bitboard.cpp b/src/bitboard.cpp index 36c72a4f..4a53e141 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -39,26 +39,26 @@ void initializeAttackLookupTables() { } template -uint64_t pawnAttacks(const uint8_t square) { +uint64_t getPawnAttacks(const uint8_t square) { assert(square < SQUARES); return pawnAttacksTable[color][square]; } -template uint64_t pawnAttacks(uint8_t square); -template uint64_t pawnAttacks(uint8_t square); +template uint64_t getPawnAttacks(uint8_t square); +template uint64_t getPawnAttacks(uint8_t square); -uint64_t knightAttacks(const uint8_t square) { +uint64_t getKnightAttacks(const uint8_t square) { assert(square < SQUARES); return knightAttacksTable[square]; } -uint64_t kingAttacks(const uint8_t square) { +uint64_t getKingAttacks(const uint8_t square) { assert(square < SQUARES); return kingAttacksTable[square]; } -uint64_t bishopAttacks(const uint8_t square, uint64_t occupied) { +uint64_t getBishopAttacks(const uint8_t square, uint64_t occupied) { assert(square < SQUARES); occupied &= getBishopMask(square); occupied *= getBishopMagic(square); @@ -67,7 +67,7 @@ uint64_t bishopAttacks(const uint8_t square, uint64_t occupied) { return getBishopMagicAttacks(square, occupied); } -uint64_t rookAttacks(const uint8_t square, uint64_t occupied) { +uint64_t getRookAttacks(const uint8_t square, uint64_t occupied) { assert(square < SQUARES); occupied &= getRookMask(square); occupied *= getRookMagic(square); @@ -78,6 +78,6 @@ uint64_t rookAttacks(const uint8_t square, uint64_t occupied) { uint64_t queenAttacks(const uint8_t square, const uint64_t occupied) { assert(square < SQUARES); - return bishopAttacks(square, occupied) | rookAttacks(square, occupied); + return getBishopAttacks(square, occupied) | getRookAttacks(square, occupied); } } // namespace Zagreus \ No newline at end of file diff --git a/src/bitboard.h b/src/bitboard.h index ced2aa7e..91d42b3c 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -203,15 +203,15 @@ inline uint64_t calculateKnightAttacks(const uint64_t bb) { } template -uint64_t pawnAttacks(uint8_t square); +uint64_t getPawnAttacks(uint8_t square); -uint64_t knightAttacks(uint8_t square); +uint64_t getKnightAttacks(uint8_t square); -uint64_t kingAttacks(uint8_t square); +uint64_t getKingAttacks(uint8_t square); -uint64_t bishopAttacks(uint8_t square, uint64_t occupied); +uint64_t getBishopAttacks(uint8_t square, uint64_t occupied); -uint64_t rookAttacks(uint8_t square, uint64_t occupied); +uint64_t getRookAttacks(uint8_t square, uint64_t occupied); uint64_t queenAttacks(uint8_t square, uint64_t occupied); diff --git a/src/board.cpp b/src/board.cpp index 30c1a6c2..efa3295f 100644 --- a/src/board.cpp +++ b/src/board.cpp @@ -116,12 +116,12 @@ uint64_t Board::getSquareAttackers(const uint8_t square) const { rooksQueens |= getBitboard() | getBitboard(); bishopsQueens |= getBitboard() | getBitboard(); - return (pawnAttacks(square) & getBitboard()) - | (pawnAttacks(square) & getBitboard()) - | (knightAttacks(square) & knights) - | (kingAttacks(square) & kings) - | (bishopAttacks(square, occupied) & bishopsQueens) - | (rookAttacks(square, occupied) & rooksQueens); + return (getPawnAttacks(square) & getBitboard()) + | (getPawnAttacks(square) & getBitboard()) + | (getKnightAttacks(square) & knights) + | (getKingAttacks(square) & kings) + | (getBishopAttacks(square, occupied) & bishopsQueens) + | (getRookAttacks(square, occupied) & rooksQueens); } void Board::reset() { @@ -274,7 +274,7 @@ void Board::unmakeMove() { if (moveType == PROMOTION) { const PieceColor color = getPieceColor(movedPiece); - movedPiece = color == WHITE ? WHITE_PAWN : BLACK_PAWN; + movedPiece = static_cast(WHITE_PAWN + color); } setPiece(movedPiece, fromSquare); diff --git a/src/board.h b/src/board.h index 12fc3bb3..865a46af 100644 --- a/src/board.h +++ b/src/board.h @@ -148,7 +148,7 @@ class Board { assert(board[square] == piece); const uint64_t squareBB = squareToBitboard(square); - board[square] = Piece::EMPTY; + board[square] = EMPTY; bitboards[piece] &= ~squareBB; occupied &= ~squareBB; colorBoards[getPieceColor(piece)] &= ~squareBB; @@ -159,7 +159,7 @@ class Board { assert(board[square] == piece); const uint64_t squareBB = squareToBitboard(square); - board[square] = Piece::EMPTY; + board[square] = EMPTY; bitboards[piece] &= ~squareBB; occupied &= ~squareBB; colorBoards[getPieceColor(piece)] &= ~squareBB; diff --git a/src/move.h b/src/move.h index 8779e52a..20aacb48 100644 --- a/src/move.h +++ b/src/move.h @@ -99,16 +99,16 @@ inline PromotionPiece getPromotionPiece(const Move move) { inline Piece getPieceFromPromotionPiece(const PromotionPiece promotionPiece, const PieceColor color) { switch (promotionPiece) { case QUEEN_PROMOTION: - return color == WHITE ? WHITE_QUEEN : BLACK_QUEEN; + return static_cast(WHITE_QUEEN + color); case ROOK_PROMOTION: - return color == WHITE ? WHITE_ROOK : BLACK_ROOK; + return static_cast(WHITE_ROOK + color); case BISHOP_PROMOTION: - return color == WHITE ? WHITE_BISHOP : BLACK_BISHOP; + return static_cast(WHITE_BISHOP + color); case KNIGHT_PROMOTION: - return color == WHITE ? WHITE_KNIGHT : BLACK_KNIGHT; + return static_cast(WHITE_KNIGHT + color); default: assert(false); - return EMPTY; + return EMPTY; } } } // namespace Zagreus diff --git a/src/move_gen.cpp b/src/move_gen.cpp index 69b29638..f67664b5 100644 --- a/src/move_gen.cpp +++ b/src/move_gen.cpp @@ -96,7 +96,8 @@ void generatePawnMoves(const Board& board, MoveList& moves, const uint64_t genMa const uint8_t squareFrom = squareTo - fromPushDirection; if (squareToBB & promotionRank) { - for (const PromotionPiece promotionPiece : {QUEEN_PROMOTION, ROOK_PROMOTION, BISHOP_PROMOTION, KNIGHT_PROMOTION}) { + for (const PromotionPiece promotionPiece : {QUEEN_PROMOTION, ROOK_PROMOTION, BISHOP_PROMOTION, + KNIGHT_PROMOTION}) { const Move move = encodeMove(squareFrom, squareTo, promotionPiece); moves.moves[moves.size] = move; moves.size++; @@ -123,13 +124,14 @@ void generatePawnMoves(const Board& board, MoveList& moves, const uint64_t genMa const uint64_t squareToBB = squareToBitboard(squareTo); const uint8_t squareFrom = squareTo - fromSqWestAttackDirection; - if (squareTo == board.getEnPassantSquare() && squareToBitboard(squareTo) & enPassantMask) { + if (squareTo == board.getEnPassantSquare() && squareToBB & enPassantMask) { const Move move = encodeMove(squareFrom, squareTo, EN_PASSANT); moves.moves[moves.size] = move; moves.size++; } else { if (squareToBB & promotionRank) { - for (const PromotionPiece promotionPiece : {QUEEN_PROMOTION, ROOK_PROMOTION, BISHOP_PROMOTION, KNIGHT_PROMOTION}) { + for (const PromotionPiece promotionPiece : {QUEEN_PROMOTION, ROOK_PROMOTION, BISHOP_PROMOTION, + KNIGHT_PROMOTION}) { const Move move = encodeMove(squareFrom, squareTo, promotionPiece); moves.moves[moves.size] = move; moves.size++; @@ -147,13 +149,14 @@ void generatePawnMoves(const Board& board, MoveList& moves, const uint64_t genMa const uint64_t squareToBB = squareToBitboard(squareTo); const uint8_t squareFrom = squareTo - fromSqEastAttackDirection; - if (squareTo == board.getEnPassantSquare() && squareToBitboard(squareTo) & enPassantMask) { + if (squareTo == board.getEnPassantSquare() && squareToBB & enPassantMask) { const Move move = encodeMove(squareFrom, squareTo, EN_PASSANT); moves.moves[moves.size] = move; moves.size++; } else { if (squareToBB & promotionRank) { - for (const PromotionPiece promotionPiece : {QUEEN_PROMOTION, ROOK_PROMOTION, BISHOP_PROMOTION, KNIGHT_PROMOTION}) { + for (const PromotionPiece promotionPiece : {QUEEN_PROMOTION, ROOK_PROMOTION, BISHOP_PROMOTION, + KNIGHT_PROMOTION}) { const Move move = encodeMove(squareFrom, squareTo, promotionPiece); moves.moves[moves.size] = move; moves.size++; @@ -174,7 +177,7 @@ void generateKnightMoves(const Board& board, MoveList& moves, const uint64_t gen while (knightBB) { const uint8_t fromSquare = popLsb(knightBB); - const uint64_t attacks = knightAttacks(fromSquare); + const uint64_t attacks = getKnightAttacks(fromSquare); uint64_t genBB = attacks & genMask; while (genBB) { @@ -195,7 +198,7 @@ void generateBishopMoves(const Board& board, MoveList& moves, const uint64_t gen while (bishopBB) { const uint8_t fromSquare = popLsb(bishopBB); - uint64_t genBB = bishopAttacks(fromSquare, occupied) & genMask; + uint64_t genBB = getBishopAttacks(fromSquare, occupied) & genMask; while (genBB) { const uint8_t toSquare = popLsb(genBB); @@ -215,7 +218,7 @@ void generateRookMoves(const Board& board, MoveList& moves, const uint64_t genMa while (rookBB) { const uint8_t fromSquare = popLsb(rookBB); - uint64_t genBB = rookAttacks(fromSquare, occupied) & genMask; + uint64_t genBB = getRookAttacks(fromSquare, occupied) & genMask; while (genBB) { const uint8_t toSquare = popLsb(genBB); @@ -252,7 +255,7 @@ void generateKingMoves(const Board& board, MoveList& moves, const uint64_t genMa constexpr Piece king = color == WHITE ? WHITE_KING : BLACK_KING; uint64_t kingBB = board.getBitboard(); const uint8_t fromSquare = popLsb(kingBB); - uint64_t genBB = kingAttacks(fromSquare) & genMask; + uint64_t genBB = getKingAttacks(fromSquare) & genMask; while (genBB) { const uint8_t toSquare = popLsb(genBB); diff --git a/src/perft.cpp b/src/perft.cpp index f11c7a91..1ca504ab 100644 --- a/src/perft.cpp +++ b/src/perft.cpp @@ -26,7 +26,7 @@ #include "move_gen.h" namespace Zagreus { -uint64_t perft(Board &board, const int depth, bool printNodes) { +uint64_t perft(Board& board, const int depth, bool printNodes) { assert(depth >= 0); if (depth == 0) { diff --git a/src/types.h b/src/types.h index 51d1f7e4..08f83bef 100644 --- a/src/types.h +++ b/src/types.h @@ -60,10 +60,10 @@ enum Square: uint8_t { enum PieceType : uint8_t { PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING }; -enum PieceColor : uint8_t { WHITE, BLACK }; +enum PieceColor : uint8_t { WHITE = 0, BLACK = 1 }; constexpr PieceColor operator!(const PieceColor color) { - return color == WHITE ? BLACK : WHITE; + return static_cast(color ^ 1); } enum Piece : uint8_t { @@ -93,12 +93,12 @@ enum CastlingRights : uint8_t { constexpr PieceColor getPieceColor(const Piece piece) { assert(piece != Piece::EMPTY); - return static_cast(piece % 2); + return static_cast(piece & 1); } constexpr PieceType getPieceType(const Piece piece) { assert(piece != Piece::EMPTY); - return static_cast(piece / 2); + return static_cast(piece >> 1); } constexpr char getCharacterForPieceType(const Piece piece) { From 6bead70e47ffab436fdce6dfaf5956108b786719 Mon Sep 17 00:00:00 2001 From: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> Date: Tue, 15 Oct 2024 12:23:49 +0200 Subject: [PATCH 32/91] Create doxygen.yml Signed-off-by: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> --- .github/workflows/doxygen.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/doxygen.yml diff --git a/.github/workflows/doxygen.yml b/.github/workflows/doxygen.yml new file mode 100644 index 00000000..d12c09ed --- /dev/null +++ b/.github/workflows/doxygen.yml @@ -0,0 +1,20 @@ +name: Doxygen GitHub Pages Deploy Action + +on: + push: + branches: + - main + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: DenverCoder1/doxygen-github-pages-action@v2.0.0 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + branch: gh-pages + folder: docs/html + config_file: Doxyfil + doxygen_version: 1.12.0 From c88813819af4a9e5b22b62f9c3a2bd6833835f62 Mon Sep 17 00:00:00 2001 From: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> Date: Tue, 15 Oct 2024 12:24:16 +0200 Subject: [PATCH 33/91] Update doxygen.yml Signed-off-by: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> --- .github/workflows/doxygen.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/doxygen.yml b/.github/workflows/doxygen.yml index d12c09ed..1eea9f40 100644 --- a/.github/workflows/doxygen.yml +++ b/.github/workflows/doxygen.yml @@ -3,7 +3,8 @@ name: Doxygen GitHub Pages Deploy Action on: push: branches: - - main + - master + - rewrite jobs: deploy: From 23062224e80879e0a6d4df7ce4faf976bafb5faf Mon Sep 17 00:00:00 2001 From: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> Date: Tue, 15 Oct 2024 12:25:48 +0200 Subject: [PATCH 34/91] Update doxygen.yml Signed-off-by: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> --- .github/workflows/doxygen.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/doxygen.yml b/.github/workflows/doxygen.yml index 1eea9f40..d862bdb4 100644 --- a/.github/workflows/doxygen.yml +++ b/.github/workflows/doxygen.yml @@ -16,6 +16,6 @@ jobs: with: github_token: ${{ secrets.GITHUB_TOKEN }} branch: gh-pages - folder: docs/html - config_file: Doxyfil + #folder: docs/html + #config_file: Doxyfile doxygen_version: 1.12.0 From dc4ed04f5f74a9f5ca37c1969027883f4c3a3a9c Mon Sep 17 00:00:00 2001 From: Danny Jelsma <73849717+Dannyj1@users.noreply.github.com> Date: Tue, 15 Oct 2024 12:34:15 +0200 Subject: [PATCH 35/91] Add Doxyfile --- Doxyfile | 2924 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2924 insertions(+) create mode 100644 Doxyfile diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 00000000..50252795 --- /dev/null +++ b/Doxyfile @@ -0,0 +1,2924 @@ +# Doxyfile 1.12.0 + +# This file describes the settings to be used by the documentation system +# Doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use Doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use Doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables or CMake type +# replacement variables: +# doxygen -x_noenv [configFile] + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = Zagreus + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# With the PROJECT_ICON tag one can specify an icon that is included in the tabs +# when the HTML document is shown. Doxygen will copy the logo to the output +# directory. + +PROJECT_ICON = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where Doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = ./docs + +# If the CREATE_SUBDIRS tag is set to YES then Doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this +# option can be useful when feeding Doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# number of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + +# If the ALLOW_UNICODE_NAMES tag is set to YES, Doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by Doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, Doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, Doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, Doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, Doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which Doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where Doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, Doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then Doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by Doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# By default Python docstrings are displayed as preformatted text and Doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# Doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as Doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then Doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:^^" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by Doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make Doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by Doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then Doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by Doxygen, so you can +# mix Doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 6. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 6 + +# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to +# generate identifiers for the Markdown headings. Note: Every identifier is +# unique. +# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a +# sequence number starting at 0 and GITHUB use the lower case version of title +# with any whitespace replaced by '-' and punctuation characters removed. +# The default value is: DOXYGEN. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +MARKDOWN_ID_STYLE = DOXYGEN + +# When enabled Doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let Doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also makes the inheritance and +# collaboration diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software) sources only. Doxygen will parse +# them like normal C++ but will assume all classes use public instead of private +# inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# Doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then Doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, Doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# Doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run Doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number of threads Doxygen is allowed to use +# during processing. When set to 0 Doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which effectively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +# If the TIMESTAMP tag is set different from NO then each generated page will +# contain the date or date and time when the page was generated. Setting this to +# NO can help when comparing the output of multiple runs. +# Possible values are: YES, NO, DATETIME and DATE. +# The default value is: NO. + +TIMESTAMP = NO + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, Doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# will also hide undocumented C++ concepts if enabled. This option has no effect +# if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES Doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and macOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# Possible values are: SYSTEM, NO and YES. +# The default value is: SYSTEM. + +CASE_SENSE_NAMES = SYSTEM + +# If the HIDE_SCOPE_NAMES tag is set to NO then Doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then Doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES then Doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then Doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then Doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then Doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then Doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and Doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING Doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# Doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by Doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by Doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents Doxygen's defaults, run Doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. +# +# Note that if you run Doxygen from a directory containing a file called +# DoxygenLayout.xml, Doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +# The EXTERNAL_TOOL_PATH tag can be used to extend the search path (PATH +# environment variable) so that external tools such as latex and gs can be +# found. +# Note: Directories specified with EXTERNAL_TOOL_PATH are added in front of the +# path already specified by the PATH variable, and are added in the order +# specified. +# Note: This option is particularly useful for macOS version 14 (Sonoma) and +# higher, when running Doxygen from Doxywizard, because in this case any user- +# defined changes to the PATH are ignored. A typical example on macOS is to set +# EXTERNAL_TOOL_PATH = /Library/TeX/texbin /usr/local/bin +# together with the standard path, the full search path used by doxygen when +# launching external tools will then become +# PATH=/Library/TeX/texbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin + +EXTERNAL_TOOL_PATH = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by Doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by Doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then Doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, Doxygen will generate warnings for +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# If WARN_IF_INCOMPLETE_DOC is set to YES, Doxygen will warn about incomplete +# function parameter documentation. If set to NO, Doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, Doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, Doxygen will warn about +# undocumented enumeration values. If set to NO, Doxygen will accept +# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: NO. + +WARN_IF_UNDOC_ENUM_VAL = NO + +# If the WARN_AS_ERROR tag is set to YES then Doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then Doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the Doxygen process Doxygen will return with a non-zero status. +# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then Doxygen behaves +# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined Doxygen will not +# write the warning messages in between other messages but write them at the end +# of a run, in case a WARN_LOGFILE is defined the warning messages will be +# besides being in the defined file also be shown at the end of a run, unless +# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case +# the behavior will remain as with the setting FAIL_ON_WARNINGS. +# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that Doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of Doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = ./src + +# This tag can be used to specify the character encoding of the source files +# that Doxygen parses. Internally Doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# See also: INPUT_FILE_ENCODING +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# This tag can be used to specify the character encoding of the source files +# that Doxygen parses The INPUT_FILE_ENCODING tag can be used to specify +# character encoding on a per file pattern basis. Doxygen will compare the file +# name with each pattern and apply the encoding instead of the default +# INPUT_ENCODING) if there is a match. The character encodings are a list of the +# form: pattern=encoding (like *.php=ISO-8859-1). +# See also: INPUT_ENCODING for further information on supported encodings. + +INPUT_FILE_ENCODING = + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by Doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, +# *.cpp, *.cppm, *.ccm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, +# *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, +# *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to +# be provided as Doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cxxm \ + *.cpp \ + *.cppm \ + *.ccm \ + *.c++ \ + *.c++m \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.ixx \ + *.l \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f18 \ + *.f \ + *.for \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.ice + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which Doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# ANamespace::AClass, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that Doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that Doxygen will use the data processed and written to standard output +# for further processing, therefore nothing else, like debug statements or used +# commands (so in case of a Windows batch file always use @echo OFF), should be +# written to standard output. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by Doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by Doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the Doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +# The Fortran standard specifies that for fixed formatted Fortran code all +# characters from position 72 are to be considered as comment. A common +# extension is to allow longer lines before the automatic comment starts. The +# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can +# be processed before the automatic comment starts. +# Minimum value: 7, maximum value: 10000, default value: 72. + +FORTRAN_COMMENT_AFTER = 72 + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# multi-line macros, enums or list initialized variables directly into the +# documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct Doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of Doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by Doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then Doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +# If the CLANG_ASSISTED_PARSING tag is set to YES then Doxygen will use the +# clang parser (see: +# http://clang.llvm.org/) for more accurate parsing at the cost of reduced +# performance. This can be particularly helpful with template rich C++ code for +# which Doxygen's built-in parser lacks the necessary type information. +# Note: The availability of this option depends on whether or not Doxygen was +# generated with the -Duse_libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS +# tag is set to YES then Doxygen will add the directory of each input to the +# include path. +# The default value is: YES. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_ADD_INC_PATHS = YES + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by Doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +# If clang assisted parsing is enabled you can provide the clang parser with the +# path to the directory containing a file called compile_commands.json. This +# file is the compilation database (see: +# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the +# options used when the source files were built. This is equivalent to +# specifying the -p option to a clang tool, such as clang-check. These options +# will then be passed to the parser. Any options specified with CLANG_OPTIONS +# will be added as well. +# Note: The availability of this option depends on whether or not Doxygen was +# generated with the -Duse_libclang=ON option for CMake. + +CLANG_DATABASE_PATH = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes) +# that should be ignored while generating the index headers. The IGNORE_PREFIX +# tag works for classes, function and member names. The entity will be placed in +# the alphabetical list under the first letter of the entity name that remains +# after removing the prefix. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, Doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank Doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that Doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that Doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of Doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank Doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that Doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank Doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that Doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by Doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# Note: Since the styling of scrollbars can currently not be overruled in +# Webkit/Chromium, the styling will be left out of the default doxygen.css if +# one or more extra stylesheets have been specified. So if scrollbar +# customization is desired it has to be added explicitly. For an example see the +# documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output +# should be rendered with a dark or light theme. +# Possible values are: LIGHT always generates light mode output, DARK always +# generates dark mode output, AUTO_LIGHT automatically sets the mode according +# to the user preference, uses light mode if no preference is set (the default), +# AUTO_DARK automatically sets the mode according to the user preference, uses +# dark mode if no preference is set and TOGGLE allows a user to switch between +# light and dark mode via a button. +# The default value is: AUTO_LIGHT. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE = AUTO_LIGHT + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a color-wheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use gray-scales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be +# dynamically folded and expanded in the generated HTML source code. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_CODE_FOLDING = YES + +# If the HTML_COPY_CLIPBOARD tag is set to YES then Doxygen will show an icon in +# the top right corner of code and text fragments that allows the user to copy +# its content to the clipboard. Note this only works if supported by the browser +# and the web page is served via a secure context (see: +# https://www.w3.org/TR/secure-contexts/), i.e. using the https: or file: +# protocol. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COPY_CLIPBOARD = YES + +# Doxygen stores a couple of settings persistently in the browser (via e.g. +# cookies). By default these settings apply to all HTML pages generated by +# Doxygen across all projects. The HTML_PROJECT_COOKIE tag can be used to store +# the settings under a project specific key, such that the user preferences will +# be stored separately. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_PROJECT_COOKIE = + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, Doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then Doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline the HTML help workshop was already many years +# in maintenance mode). You can download the HTML help workshop from the web +# archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by Doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# Doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# The SITEMAP_URL tag is used to specify the full URL of the place where the +# generated documentation will be placed on the server by the user during the +# deployment of the documentation. The generated sitemap is called sitemap.xml +# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL +# is specified no sitemap is generated. For information about the sitemap +# protocol see https://www.sitemaps.org +# This tag requires that the tag GENERATE_HTML is set to YES. + +SITEMAP_URL = + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty Doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by Doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has the same information as the tab index, you could +# consider setting DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATE_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# Doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# When the SHOW_ENUM_VALUES tag is set doxygen will show the specified +# enumeration values besides the enumeration mnemonics. +# The default value is: NO. + +SHOW_ENUM_VALUES = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, Doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the OBFUSCATE_EMAILS tag is set to YES, Doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, Doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# Doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for MathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see +# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions): +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with JavaScript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled Doxygen will generate a search box for +# the HTML output. The underlying search engine uses JavaScript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the JavaScript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /