From: Fabian Fichter Date: Sun, 18 Oct 2020 18:40:07 +0000 (+0200) Subject: Merge official-stockfish/master X-Git-Url: http://winboard.nl/cgi-bin?a=commitdiff_plain;h=26249698119fa5767cffa70a4dae472dc06c6d97;p=fairystockfish.git Merge official-stockfish/master - Updates language standard to C++17. - Includes merge of NNUE code. - NNUE embedding is disabled by default - Variant name is matched against NN file name to detect support bench: 4170106 --- 26249698119fa5767cffa70a4dae472dc06c6d97 diff --cc .travis.yml index 73bfc1c,092c7f5..499bb02 --- a/.travis.yml +++ b/.travis.yml @@@ -31,23 -29,23 +29,28 @@@ matrix - COMP=gcc - os: osx + osx_image: xcode12 compiler: clang env: - - COMPILER=clang++ V='Apple LLVM 9.4.1' # Apple LLVM version 9.1.0 (clang-902.0.39.2) + - COMPILER=clang++ - COMP=clang -branches: - only: - - master - before_script: - cd src script: - # Download net ++ # Download net and test nnue + - make net ++ - make clean && make -j2 ARCH=x86-64 nnue=yes debug=yes build && ./stockfish bench > /dev/null 2>&1 ++ + # Check perft of large-board version + - make clean && make -j2 ARCH=x86-64 largeboards=yes all=yes debug=yes build && ../tests/perft.sh largeboard + - ./stockfish bench xiangqi > /dev/null 2>&1 + - ./stockfish bench shogi > /dev/null 2>&1 + - ./stockfish bench capablanca > /dev/null 2>&1 + - ./stockfish bench sittuyin > /dev/null 2>&1 - - make clean && make -j2 ARCH=x86-32 largeboards=yes all=yes debug=yes build && ../tests/perft.sh largeboard - # ++ - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then make clean && make -j2 ARCH=x86-32 largeboards=yes all=yes debug=yes build && ../tests/perft.sh largeboard; fi + # Obtain bench reference from git log - git log HEAD | grep "\b[Bb]ench[ :]\+[0-9]\{7\}" | head -n 1 | sed "s/[^0-9]*\([0-9]*\).*/\1/g" > git_sig - export benchref=$(cat git_sig) @@@ -61,11 -83,10 +88,11 @@@ # # Check perft and reproducible search - - export CXXFLAGS="-Werror" - - make clean && make -j2 ARCH=x86-64 build - - ../tests/protocol.sh + - make clean && make -j2 ARCH=x86-64-modern build - ../tests/perft.sh - ../tests/reprosearch.sh ++ - ../tests/protocol.sh + # # Valgrind # diff --cc appveyor.yml index 5b75bcc,ab60840..9d9d540 --- a/appveyor.yml +++ b/appveyor.yml @@@ -4,11 -4,9 +4,11 @@@ clone_depth: 5 branches: only: - master + - merge + - appveyor # Operating system (build VM template) - os: Visual Studio 2017 + os: Visual Studio 2019 # Build platform, i.e. x86, x64, AnyCPU. This setting is optional. platform: diff --cc appveyor_python.yml index 7bc6d5e,0000000..e8eae81 mode 100644,000000..100644 --- a/appveyor_python.yml +++ b/appveyor_python.yml @@@ -1,50 -1,0 +1,51 @@@ ++image: ++ - Visual Studio 2019 +environment: - + matrix: + - PYTHON: "C:\\Python36-x64" + - PYTHON: "C:\\Python37-x64" + - PYTHON: "C:\\Python38-x64" + +init: + - "ECHO %PYTHON% %PYTHON_ARCH% %MSVC_VERSION%" + +install: + - ps: | + if (-not (Test-Path $env:PYTHON)) { + curl -o install_python.ps1 https://raw.githubusercontent.com/matthew-brett/multibuild/11a389d78892cf90addac8f69433d5e22bfa422a/install_python.ps1 + .\install_python.ps1 + } + - ps: if (-not (Test-Path $env:PYTHON)) { throw "No $env:PYTHON" } + - "set PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" + - python --version + + # We need wheel installed to build wheels + - "%PYTHON%\\python.exe -m pip install wheel" + +build: off + +test_script: + # Put your test command here. + # If you don't need to build C extensions on 64-bit Python 3.3 or 3.4, + # you can remove "build.cmd" from the front of the command, as it's + # only needed to support those cases. + # Note that you must use the environment variable %PYTHON% to refer to + # the interpreter you're using - Appveyor does not do anything special + # to put the Python version you want to use on PATH. + - "%PYTHON%\\python.exe setup.py test" + +after_test: + # This step builds your wheels. + # Again, you only need build.cmd if you're building C extensions for + # 64-bit Python 3.3/3.4. And you need to use %PYTHON% to get the correct + # interpreter + - "%PYTHON%\\python.exe setup.py bdist_wheel" + +artifacts: + # bdist_wheel puts your built wheel in the dist directory + - path: dist\* + +#on_success: +# You can use this step to upload your artifacts to a public website. +# See Appveyor's documentation for more details. Or you can simply +# access your wheels from the Appveyor "artifacts" tab for your build. diff --cc setup.py index 96761c3,0000000..6a25f81 mode 100644,000000..100644 --- a/setup.py +++ b/setup.py @@@ -1,51 -1,0 +1,53 @@@ +# -*- coding: utf-8 -*- + +from setuptools import setup, Extension +from glob import glob +import platform +import io +import os + + - args = ["-DLARGEBOARDS", "-DPRECOMPUTED_MAGICS", "-flto", "-std=c++11"] ++if platform.python_compiler().startswith("MSC"): ++ args = ["/std:c++17"] ++else: ++ args = ["-std=c++17", "-flto", "-Wno-date-time"] + - if not platform.python_compiler().startswith("MSC"): - args.append("-Wno-date-time") ++args.extend(["-DLARGEBOARDS", "-DPRECOMPUTED_MAGICS", "-DNNUE_EMBEDDING_OFF"]) + +if "64bit" in platform.architecture(): + args.append("-DIS_64BIT") + +CLASSIFIERS = [ + "Development Status :: 3 - Alpha", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + "Programming Language :: Python :: 3", + "Operating System :: OS Independent", +] + - with io.open("Readme.md", "r", encoding="utf8") as fh: ++with io.open("README.md", "r", encoding="utf8") as fh: + long_description = fh.read().strip() + - sources = glob("src/*.cpp") + glob("src/syzygy/*.cpp") ++sources = glob("src/*.cpp") + glob("src/syzygy/*.cpp") + glob("src/nnue/*.cpp") + glob("src/nnue/features/*.cpp") +ffish_source_file = os.path.normcase("src/ffishjs.cpp") +try: + sources.remove(ffish_source_file) +except ValueError: + print(f"ffish_source_file {ffish_source_file} was not found in sources {sources}.") + +pyffish_module = Extension( + "pyffish", + sources=sources, + extra_compile_args=args) + +setup(name="pyffish", version="0.0.51", + description="Fairy-Stockfish Python wrapper", + long_description=long_description, + long_description_content_type="text/markdown", + author="Bajusz Tamás", + author_email="gbtami@gmail.com", + license="GPL3", + classifiers=CLASSIFIERS, + url="https://github.com/gbtami/Fairy-Stockfish", + python_requires=">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*", + ext_modules=[pyffish_module] + ) diff --cc src/Makefile index bb42504,54868b3..0c0a347 --- a/src/Makefile +++ b/src/Makefile @@@ -37,8 -37,9 +37,10 @@@ PGOBENCH = ./$(EXE) benc ### Source and object files SRCS = benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp main.cpp \ - material.cpp misc.cpp movegen.cpp movepick.cpp partner.cpp parser.cpp pawns.cpp piece.cpp position.cpp psqt.cpp \ - search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp variant.cpp xboard.cpp tune.cpp syzygy/tbprobe.cpp + material.cpp misc.cpp movegen.cpp movepick.cpp pawns.cpp position.cpp psqt.cpp \ + search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \ - nnue/evaluate_nnue.cpp nnue/features/half_kp.cpp ++ nnue/evaluate_nnue.cpp nnue/features/half_kp.cpp \ ++ partner.cpp parser.cpp piece.cpp variant.cpp xboard.cpp OBJS = $(notdir $(SRCS:.cpp=.o)) @@@ -74,9 -84,23 +85,27 @@@ endi # at the end of the line for flag values. ### 2.1. General and architecture defaults +largeboards = no +all = no +precomputedmagics = yes ++nnue = no + + ifeq ($(ARCH),) + ARCH = x86-64-modern + help_skip_sanity = yes + endif + # explicitly check for the list of supported architectures (as listed with make help), + # the user can override with `make ARCH=x86-32-vnni256 SUPPORTED_ARCH=true` + ifeq ($(ARCH), $(filter $(ARCH), \ + x86-64-vnni512 x86-64-vnni256 x86-64-avx512 x86-64-bmi2 x86-64-avx2 \ + x86-64-sse41-popcnt x86-64-modern x86-64-ssse3 x86-64-sse3-popcnt \ + x86-64 x86-32-sse41-popcnt x86-32-sse2 x86-32 ppc-64 ppc-32 \ + armv7 armv7-neon armv8 apple-silicon general-64 general-32)) + SUPPORTED_ARCH=true + else + SUPPORTED_ARCH=false + endif + optimize = yes debug = no sanitize = no @@@ -153,29 -289,17 +294,43 @@@ ifeq ($(ARCH),ppc-64 prefetch = yes endif ++# Disable precomputed magics when 64-bit PEXT is available ++ifeq ($(pext),yes) ++ precomputedmagics = no ++endif ++ + endif + ### ========================================================================== ### Section 3. Low-level Configuration ### ========================================================================== ### 3.1 Selecting compiler (default = gcc) - CXXFLAGS += -Wall -Wcast-qual -fno-exceptions -std=c++11 $(EXTRACXXFLAGS) - DEPENDFLAGS += -std=c++11 + CXXFLAGS += -Wall -Wcast-qual -fno-exceptions -std=c++17 $(EXTRACXXFLAGS) + DEPENDFLAGS += -std=c++17 LDFLAGS += $(EXTRALDFLAGS) +# Compile version with support for large board variants +# Use precomputed magics by default if pext is not available +ifneq ($(largeboards),no) + CXXFLAGS += -DLARGEBOARDS + ifeq ($(precomputedmagics),yes) + CXXFLAGS += -DPRECOMPUTED_MAGICS + endif +endif + ++# Embed and enable NNUE ++ifeq ($(nnue),yes) ++ CXXFLAGS += -DUSE_NNUE ++else ++ CXXFLAGS += -DNNUE_EMBEDDING_OFF ++endif ++ +# Enable all variants, even heavyweight ones like amazons +ifneq ($(all),no) + CXXFLAGS += -DALLVARS +endif + ifeq ($(COMP),) COMP=gcc endif @@@ -183,12 -307,9 +338,12 @@@ ifeq ($(COMP),gcc) comp=gcc CXX=g++ - CXXFLAGS += -pedantic -Wextra -Wshadow + CXXFLAGS += -Wextra -Wshadow + ifeq ($(largeboards),no) + CXXFLAGS += -pedantic + endif - ifeq ($(ARCH),$(filter $(ARCH),armv7 armv8)) + ifeq ($(arch),$(filter $(arch),armv7 armv8)) ifeq ($(OS),Android) CXXFLAGS += -m$(bits) LDFLAGS += -m$(bits) @@@ -430,25 -688,26 +722,40 @@@ help @echo "" @echo "Simple examples. If you don't know what to do, you likely want to run: " @echo "" - @echo "make build ARCH=x86-64 (This is for 64-bit systems)" - @echo "make build ARCH=x86-32 (This is for 32-bit systems)" + @echo "make -j build ARCH=x86-64 (A portable, slow compile for 64-bit systems)" + @echo "make -j build ARCH=x86-32 (A portable, slow compile for 32-bit systems)" @echo "" - @echo "Advanced examples, for experienced users: " + @echo "Advanced examples, for experienced users looking for performance: " @echo "" - @echo "make build ARCH=x86-64 COMP=clang" - @echo "make profile-build ARCH=x86-64-bmi2 COMP=gcc COMPCXX=g++-4.8" + @echo "make help ARCH=x86-64-bmi2" + @echo "make -j profile-build ARCH=x86-64-bmi2 COMP=gcc COMPCXX=g++-9.0" + @echo "make -j build ARCH=x86-64-ssse3 COMP=clang" @echo "" + @echo "-------------------------------" + ifeq ($(SUPPORTED_ARCH)$(help_skip_sanity), true) + @echo "The selected architecture $(ARCH) will enable the following configuration: " + @$(MAKE) ARCH=$(ARCH) COMP=$(COMP) config-sanity + else + @echo "Specify a supported architecture with the ARCH option for more details" + @echo "" ++ @echo "-------------------------------" ++ @echo "Embed and enable NNUE by default: " ++ @echo "" ++ @echo "make build ARCH=x86-64 nnue=yes" ++ @echo "" ++ @echo "-------------------------------" + @echo "Version for large boards: " + @echo "" + @echo "make build ARCH=x86-64 COMP=gcc largeboards=yes" + @echo "" - @echo "Include all variants: " ++ @echo "Include all variants (adds Game of the Amazons): " + @echo "" + @echo "make build ARCH=x86-64 largeboards=yes all=yes" + @echo "" + endif - .PHONY: help build profile-build strip install clean objclean profileclean \ + .PHONY: help build profile-build strip install clean net objclean profileclean \ config-sanity icc-profile-use icc-profile-make gcc-profile-use gcc-profile-make \ clang-profile-use clang-profile-make diff --cc src/benchmark.cpp index 3adb935,ffb631a..99219a6 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@@ -132,6 -116,7 +131,11 @@@ vector setup_bench(const Positi string limit = (is >> token) ? token : "13"; string fenFile = (is >> token) ? token : "default"; string limitType = (is >> token) ? token : "depth"; ++#ifdef USE_NNUE + string evalType = (is >> token) ? token : "mixed"; ++#else ++ string evalType = (is >> token) ? token : "classic"; ++#endif go = limitType == "eval" ? "eval" : "go " + limitType + " " + limit; @@@ -166,17 -146,25 +170,30 @@@ list.emplace_back("setoption name Threads value " + threads); list.emplace_back("setoption name Hash value " + ttSize); + list.emplace_back("setoption name UCI_Variant value " + varname); list.emplace_back("ucinewgame"); + size_t posCounter = 0; + for (const string& fen : fens) if (fen.find("setoption") != string::npos) list.emplace_back(fen); else { + if (evalType == "classical" || (evalType == "mixed" && posCounter % 2 == 0)) + list.emplace_back("setoption name Use NNUE value false"); + else if (evalType == "NNUE" || (evalType == "mixed" && posCounter % 2 != 0)) + list.emplace_back("setoption name Use NNUE value true"); list.emplace_back("position fen " + fen); list.emplace_back(go); + ++posCounter; } ++#ifdef USE_NNUE + list.emplace_back("setoption name Use NNUE value true"); ++#else ++ list.emplace_back("setoption name Use NNUE value false"); ++#endif + return list; } diff --cc src/bitboard.cpp index 604f746,80206b5..fcdeada --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@@ -50,104 -35,20 +48,114 @@@ Magic JanggiElephantMagics[SQUARE_NB] namespace { - Bitboard RookTable[0x19000]; // To store rook attacks +#ifdef LARGEBOARDS + Bitboard RookTableH[0x11800]; // To store horizontalrook attacks + Bitboard RookTableV[0x4800]; // To store vertical rook attacks + Bitboard BishopTable[0x33C00]; // To store bishop attacks + Bitboard CannonTableH[0x11800]; // To store horizontal cannon attacks + Bitboard CannonTableV[0x4800]; // To store vertical cannon attacks + Bitboard HorseTable[0x500]; // To store horse attacks + Bitboard ElephantTable[0x400]; // To store elephant attacks + Bitboard JanggiElephantTable[0x1C000]; // To store janggi elephant attacks +#else + Bitboard RookTableH[0xA00]; // To store horizontal rook attacks + Bitboard RookTableV[0xA00]; // To store vertical rook attacks Bitboard BishopTable[0x1480]; // To store bishop attacks + Bitboard CannonTableH[0xA00]; // To store horizontal cannon attacks + Bitboard CannonTableV[0xA00]; // To store vertical cannon attacks + Bitboard HorseTable[0x240]; // To store horse attacks + Bitboard ElephantTable[0x1A0]; // To store elephant attacks + Bitboard JanggiElephantTable[0x5C00]; // To store janggi elephant attacks +#endif + + enum MovementType { RIDER, HOPPER, LAME_LEAPER }; + + template +#ifdef PRECOMPUTED_MAGICS + void init_magics(Bitboard table[], Magic magics[], std::vector directions, Bitboard magicsInit[]); +#else + void init_magics(Bitboard table[], Magic magics[], std::vector directions); +#endif + + template + Bitboard sliding_attack(std::vector directions, Square sq, Bitboard occupied, Color c = WHITE) { + assert(MT != LAME_LEAPER); + + Bitboard attack = 0; + + for (Direction d : directions) + { + bool hurdle = false; + for (Square s = sq + (c == WHITE ? d : -d); + is_ok(s) && distance(s, s - (c == WHITE ? d : -d)) == 1; + s += (c == WHITE ? d : -d)) + { + if (MT != HOPPER || hurdle) + attack |= s; + + if (occupied & s) + { + if (MT == HOPPER && !hurdle) + hurdle = true; + else + break; + } + } + } + + return attack; + } + + Bitboard lame_leaper_path(Direction d, Square s) { + Direction dr = d > 0 ? NORTH : SOUTH; + Direction df = (std::abs(d % NORTH) < NORTH / 2 ? d % NORTH : -(d % NORTH)) < 0 ? WEST : EAST; + Square to = s + d; + Bitboard b = 0; + if (!is_ok(to) || distance(s, to) >= 4) + return b; + while (s != to) + { + int diff = std::abs(file_of(to) - file_of(s)) - std::abs(rank_of(to) - rank_of(s)); + if (diff > 0) + s += df; + else if (diff < 0) + s += dr; + else + s += df + dr; + + if (s != to) + b |= s; + } + return b; + } - void init_magics(PieceType pt, Bitboard table[], Magic magics[]); + Bitboard lame_leaper_path(std::vector directions, Square s) { + Bitboard b = 0; + for (Direction d : directions) + b |= lame_leaper_path(d, s); + return b; + } + + Bitboard lame_leaper_attack(std::vector directions, Square s, Bitboard occupied) { + Bitboard b = 0; + for (Direction d : directions) + { + Square to = s + d; + if (is_ok(to) && distance(s, to) < 4 && !(lame_leaper_path(d, s) & occupied)) + b |= to; + } + return b; + } + + } + + + /// safe_destination() returns the bitboard of target square for the given step + /// from the given square. If the step is off the board, returns empty bitboard. + + inline Bitboard safe_destination(Square s, int step) { + Square to = Square(s + step); - return is_ok(to) && distance(s, to) <= 2 ? square_bb(to) : Bitboard(0); ++ return is_ok(to) && distance(s, to) <= 3 ? square_bb(to) : Bitboard(0); } diff --cc src/bitboard.h index 8167bb5,29d8f66..2a0542f --- a/src/bitboard.h +++ b/src/bitboard.h @@@ -373,34 -275,8 +371,25 @@@ template<> inline int distance(Sq template<> inline int distance(Square x, Square y) { return std::abs(rank_of(x) - rank_of(y)); } template<> inline int distance(Square x, Square y) { return SquareDistance[x][y]; } -inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); } -inline int edge_distance(Rank r) { return std::min(r, Rank(RANK_8 - r)); } +inline int edge_distance(File f, File maxFile = FILE_H) { return std::min(f, File(maxFile - f)); } +inline int edge_distance(Rank r, Rank maxRank = RANK_8) { return std::min(r, Rank(maxRank - r)); } + + - /// safe_destination() returns the bitboard of target square for the given step - /// from the given square. If the step is off the board, returns empty bitboard. - - inline Bitboard safe_destination(Square s, int step) - { - Square to = Square(s + step); - return is_ok(to) && distance(s, to) <= 3 ? square_bb(to) : Bitboard(0); - } - +template +inline Bitboard rider_attacks_bb(Square s, Bitboard occupied) { + + assert(R == RIDER_BISHOP || R == RIDER_ROOK_H || R == RIDER_ROOK_V || R == RIDER_CANNON_H || R == RIDER_CANNON_V + || R == RIDER_HORSE || R == RIDER_ELEPHANT || R == RIDER_JANGGI_ELEPHANT); + const Magic& m = R == RIDER_ROOK_H ? RookMagicsH[s] + : R == RIDER_ROOK_V ? RookMagicsV[s] + : R == RIDER_CANNON_H ? CannonMagicsH[s] + : R == RIDER_CANNON_V ? CannonMagicsV[s] + : R == RIDER_HORSE ? HorseMagics[s] + : R == RIDER_ELEPHANT ? ElephantMagics[s] + : R == RIDER_JANGGI_ELEPHANT ? JanggiElephantMagics[s] + : BishopMagics[s]; + return m.attacks[m.index(occupied)]; +} /// attacks_bb(Square) returns the pseudo attacks of the give piece type diff --cc src/evaluate.cpp index 68af824,425ba6f..1884e03 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@@ -30,6 -34,114 +34,118 @@@ #include "pawns.h" #include "thread.h" #include "uci.h" + #include "incbin/incbin.h" + + + // Macro to embed the default NNUE file data in the engine binary (using incbin.h, by Dale Weiler). + // This macro invocation will declare the following three variables + // const unsigned char gEmbeddedNNUEData[]; // a pointer to the embedded data + // const unsigned char *const gEmbeddedNNUEEnd; // a marker to the end + // const unsigned int gEmbeddedNNUESize; // the size of the embedded file + // Note that this does not work in Microsof Visual Studio. + #if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF) + INCBIN(EmbeddedNNUE, EvalFileDefaultName); + #else + const unsigned char gEmbeddedNNUEData[1] = {0x0}; ++ [[maybe_unused]] + const unsigned char *const gEmbeddedNNUEEnd = &gEmbeddedNNUEData[1]; + const unsigned int gEmbeddedNNUESize = 1; + #endif + + + using namespace std; + using namespace Eval::NNUE; + + namespace Eval { + + bool useNNUE; + string eval_file_loaded = "None"; + + /// NNUE::init() tries to load a nnue network at startup time, or when the engine + /// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" + /// The name of the nnue network is always retrieved from the EvalFile option. + /// We search the given network in three locations: internally (the default + /// network may be embedded in the binary), in the active working directory and + /// in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY + /// variable to have the engine search in a special directory in their distro. + + void NNUE::init() { + - useNNUE = Options["Use NNUE"]; ++ string eval_file = string(Options["EvalFile"]); ++ ++ useNNUE = Options["Use NNUE"] ++ && ( eval_file.find(string(Options["UCI_Variant"])) != string::npos ++ || (Options["UCI_Variant"] == "chess" && eval_file.rfind("nn-", 0) != string::npos)) // restrict NNUE usage to corresponding variant ++ && RANK_MAX == RANK_8; // TODO: fix for large boards + if (!useNNUE) + return; + - string eval_file = string(Options["EvalFile"]); - + #if defined(DEFAULT_NNUE_DIRECTORY) + #define stringify2(x) #x + #define stringify(x) stringify2(x) + vector dirs = { "" , "" , CommandLine::binaryDirectory , stringify(DEFAULT_NNUE_DIRECTORY) }; + #else + vector dirs = { "" , "" , CommandLine::binaryDirectory }; + #endif + + for (string directory : dirs) + if (eval_file_loaded != eval_file) + { + if (directory != "") + { + ifstream stream(directory + eval_file, ios::binary); + if (load_eval(eval_file, stream)) + eval_file_loaded = eval_file; + } + + if (directory == "" && eval_file == EvalFileDefaultName) + { + // C++ way to prepare a buffer for a memory stream + class MemoryBuffer : public basic_streambuf { + public: MemoryBuffer(char* p, size_t n) { setg(p, p, p + n); setp(p, p + n); } + }; + + MemoryBuffer buffer(const_cast(reinterpret_cast(gEmbeddedNNUEData)), + size_t(gEmbeddedNNUESize)); + + istream stream(&buffer); + if (load_eval(eval_file, stream)) + eval_file_loaded = eval_file; + } + } + } + + /// NNUE::verify() verifies that the last net used was loaded successfully + void NNUE::verify() { + + string eval_file = string(Options["EvalFile"]); + + if (useNNUE && eval_file_loaded != eval_file) + { + UCI::OptionsMap defaults; + UCI::init(defaults); + + string msg1 = "If the UCI option \"Use NNUE\" is set to true, network evaluation parameters compatible with the engine must be available."; + string msg2 = "The option is set to true, but the network file " + eval_file + " was not loaded successfully."; + string msg3 = "The UCI option EvalFile might need to specify the full path, including the directory name, to the network file."; + string msg4 = "The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/" + string(defaults["EvalFile"]); + string msg5 = "The engine will be terminated now."; + + sync_cout << "info string ERROR: " << msg1 << sync_endl; + sync_cout << "info string ERROR: " << msg2 << sync_endl; + sync_cout << "info string ERROR: " << msg3 << sync_endl; + sync_cout << "info string ERROR: " << msg4 << sync_endl; + sync_cout << "info string ERROR: " << msg5 << sync_endl; + + exit(EXIT_FAILURE); + } + + if (useNNUE) + sync_cout << "info string NNUE evaluation using " << eval_file << " enabled" << sync_endl; + else + sync_cout << "info string classical evaluation enabled" << sync_endl; + } + } namespace Trace { @@@ -75,40 -187,40 +191,42 @@@ using namespace Trace namespace { // Threshold for lazy and space evaluation - constexpr Value LazyThreshold1 = Value(1400); - constexpr Value LazyThreshold2 = Value(1300); + constexpr Value LazyThreshold1 = Value(1400); + constexpr Value LazyThreshold2 = Value(1300); constexpr Value SpaceThreshold = Value(12222); + constexpr Value NNUEThreshold1 = Value(550); + constexpr Value NNUEThreshold2 = Value(150); // KingAttackWeights[PieceType] contains king attack weights by piece type - constexpr int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 81, 52, 44, 10 }; + constexpr int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 81, 52, 44, 10, 40 }; // SafeCheck[PieceType][single/multiple] contains safe check bonus by piece type, // higher if multiple safe checks are possible for that piece type. constexpr int SafeCheck[][2] = { - {}, {600, 600}, {792, 1283}, {645, 967}, {1084, 1897}, {772, 1119}, {600, 900} - {}, {}, {803, 1292}, {639, 974}, {1087, 1878}, {759, 1132} ++ {}, {600, 600}, {803, 1292}, {639, 974}, {1087, 1878}, {759, 1132}, {600, 900} }; #define S(mg, eg) make_score(mg, eg) // MobilityBonus[PieceType-2][attacked] contains bonuses for middle and end game, // indexed by piece type and number of attacked squares in the mobility area. - constexpr Score MobilityBonus[][32] = { + constexpr Score MobilityBonus[][4 * RANK_NB] = { - { S(-62,-81), S(-53,-56), S(-12,-31), S( -4,-16), S( 3, 5), S( 13, 11), // Knight - S( 22, 17), S( 28, 20), S( 33, 25) }, - { S(-48,-59), S(-20,-23), S( 16, -3), S( 26, 13), S( 38, 24), S( 51, 42), // Bishop - S( 55, 54), S( 63, 57), S( 63, 65), S( 68, 73), S( 81, 78), S( 81, 86), - S( 91, 88), S( 98, 97) }, - { S(-60,-78), S(-20,-17), S( 2, 23), S( 3, 39), S( 3, 70), S( 11, 99), // Rook - S( 22,103), S( 31,121), S( 40,134), S( 40,139), S( 41,158), S( 48,164), - S( 57,168), S( 57,169), S( 62,172) }, - { S(-30,-48), S(-12,-30), S( -8, -7), S( -9, 19), S( 20, 40), S( 23, 55), // Queen - S( 23, 59), S( 35, 75), S( 38, 78), S( 53, 96), S( 64, 96), S( 65,100), - S( 65,121), S( 66,127), S( 67,131), S( 67,133), S( 72,136), S( 72,141), - S( 77,147), S( 79,150), S( 93,151), S(108,168), S(108,168), S(108,171), - S(110,182), S(114,182), S(114,192), S(116,219) } + { S(-62,-79), S(-53,-57), S(-12,-31), S( -3,-17), S( 3, 7), S( 12, 13), // Knight + S( 21, 16), S( 28, 21), S( 37, 26) }, + { S(-47,-59), S(-20,-25), S( 14, -8), S( 29, 12), S( 39, 21), S( 53, 40), // Bishop + S( 53, 56), S( 60, 58), S( 62, 65), S( 69, 72), S( 78, 78), S( 83, 87), + S( 91, 88), S( 96, 98) }, + { S(-61,-82), S(-20,-17), S( 2, 23) ,S( 3, 40), S( 4, 72), S( 11,100), // Rook + S( 22,104), S( 31,120), S( 39,134), S(40 ,138), S( 41,158), S( 47,163), + S( 59,168), S( 60,169), S( 64,173) }, + { S(-29,-49), S(-16,-29), S( -8, -8), S( -8, 17), S( 18, 39), S( 25, 54), // Queen + S( 23, 59), S( 37, 73), S( 41, 76), S( 54, 95), S( 65, 95) ,S( 68,101), + S( 69,124), S( 70,128), S( 70,132), S( 70,133) ,S( 71,136), S( 72,140), + S( 74,147), S( 76,149), S( 90,153), S(104,169), S(105,171), S(106,171), + S(112,178), S(114,185), S(114,187), S(119,221) } }; + constexpr Score MaxMobility = S(150, 200); + constexpr Score DropMobility = S(10, 10); // KingProtector[knight/bishop] contains penalty for each distance unit to own king constexpr Score KingProtector[] = { S(8, 9), S(6, 9) }; @@@ -254,34 -353,18 +370,34 @@@ // Squares occupied by those pawns, by our king or queen, by blockers to attacks on our king // or controlled by enemy pawns are excluded from the mobility area. - mobilityArea[Us] = ~(b | pos.pieces(Us, KING, QUEEN) | pos.blockers_for_king(Us) | pe->pawn_attacks(Them)); + if (pos.must_capture()) + mobilityArea[Us] = AllSquares; + else + mobilityArea[Us] = ~(b | pos.pieces(Us, KING, QUEEN) | pos.blockers_for_king(Us) | pe->pawn_attacks(Them) + | (pos.pieces(Us, SHOGI_PAWN) & shift(pos.pieces(Us))) + | shift(pos.pieces(Them, SHOGI_PAWN, SOLDIER)) + | shift(pos.promoted_soldiers(Them)) + | shift(pos.promoted_soldiers(Them))); // Initialize attackedBy[] for king and pawns - attackedBy[Us][KING] = attacks_bb(ksq); + attackedBy[Us][KING] = pos.count(Us) ? pos.attacks_from(Us, KING, ksq) : Bitboard(0); attackedBy[Us][PAWN] = pe->pawn_attacks(Us); - attackedBy[Us][ALL_PIECES] = attackedBy[Us][KING] | attackedBy[Us][PAWN]; - attackedBy2[Us] = dblAttackByPawn | (attackedBy[Us][KING] & attackedBy[Us][PAWN]); + attackedBy[Us][SHOGI_PAWN] = shift(pos.pieces(Us, SHOGI_PAWN)); + attackedBy[Us][ALL_PIECES] = attackedBy[Us][KING] | attackedBy[Us][PAWN] | attackedBy[Us][SHOGI_PAWN]; + attackedBy2[Us] = (attackedBy[Us][KING] & attackedBy[Us][PAWN]) + | (attackedBy[Us][KING] & attackedBy[Us][SHOGI_PAWN]) + | (attackedBy[Us][PAWN] & attackedBy[Us][SHOGI_PAWN]) + | dblAttackByPawn; // Init our king safety tables - Square s = make_square(std::clamp(file_of(ksq), FILE_B, FILE_G), - std::clamp(rank_of(ksq), RANK_2, RANK_7)); - kingRing[Us] = attacks_bb(s) | s; + if (!pos.count(Us)) + kingRing[Us] = Bitboard(0); + else + { - Square s = make_square(Utility::clamp(file_of(ksq), FILE_B, File(pos.max_file() - 1)), - Utility::clamp(rank_of(ksq), RANK_2, Rank(pos.max_rank() - 1))); ++ Square s = make_square(std::clamp(file_of(ksq), FILE_B, File(pos.max_file() - 1)), ++ std::clamp(rank_of(ksq), RANK_2, Rank(pos.max_rank() - 1))); + kingRing[Us] = attacks_bb(s) | s; + } kingAttackersCount[Them] = popcount(kingRing[Us] & pe->pawn_attacks(Them)); kingAttacksCount[Them] = kingAttackersWeight[Them] = 0; @@@ -455,12 -497,8 +568,8 @@@ { // Penalty if any relative pin or discovered attack against the queen Bitboard queenPinners; - if (pos.slider_blockers(pos.pieces(Them, ROOK, BISHOP), s, queenPinners)) + if (pos.slider_blockers(pos.pieces(Them, ROOK, BISHOP), s, queenPinners, Them)) score -= WeakQueen; - - // Bonus for queen on weak square in enemy camp - if (relative_rank(Us, s) > RANK_4 && (~pe->pawn_attacks_span(Them) & s)) - score += QueenInfiltration; } } if (T) @@@ -1112,15 -846,10 +1221,16 @@@ template Value Evaluation::winnable(Score score) const { - int outflanking = distance(pos.square(WHITE), pos.square(BLACK)) - - distance(pos.square(WHITE), pos.square(BLACK)); + // No initiative bonus for extinction variants + int complexity = 0; ++ bool pawnsOnBothFlanks = true; + if (pos.extinction_value() == VALUE_NONE && !pos.captures_to_hand() && !pos.connect_n()) + { + int outflanking = !pos.count(WHITE) || !pos.count(BLACK) ? 0 + : distance(pos.square(WHITE), pos.square(BLACK)) + - distance(pos.square(WHITE), pos.square(BLACK)); -- bool pawnsOnBothFlanks = (pos.pieces(PAWN) & QueenSide) ++ pawnsOnBothFlanks = (pos.pieces(PAWN) & QueenSide) && (pos.pieces(PAWN) & KingSide); bool almostUnwinnable = outflanking < 0 @@@ -1180,7 -905,9 +1290,9 @@@ sf = 37 + 3 * (pos.count(WHITE) == 1 ? pos.count(BLACK) + pos.count(BLACK) : pos.count(WHITE) + pos.count(WHITE)); else - sf = std::min(sf, 36 + 7 * (pos.count(strongSide) + pos.count(strongSide))); - sf = std::min(sf, 36 + 7 * pos.count(strongSide)) - 4 * !pawnsOnBothFlanks; ++ sf = std::min(sf, 36 + 7 * (pos.count(strongSide) + pos.count(strongSide))) - 4 * !pawnsOnBothFlanks; + + sf -= 4 * !pawnsOnBothFlanks; } // Interpolate between the middlegame and (scaled by 'sf') endgame score @@@ -1280,16 -999,8 +1392,8 @@@ make_v v = (v / 16) * 16; // Side to move point of view - v = (pos.side_to_move() == WHITE ? v : -v) + Tempo; + v = (pos.side_to_move() == WHITE ? v : -v) + Eval::tempo_value(pos); - // Damp down the evaluation linearly when shuffling - if (pos.n_move_rule()) - { - v = v * (2 * pos.n_move_rule() - pos.rule50_count()) / (2 * pos.n_move_rule()); - if (pos.material_counting()) - v += pos.material_counting_result() / (10 * std::max(2 * pos.n_move_rule() - pos.rule50_count(), 1)); - } - return v; } @@@ -1307,9 -1011,46 +1411,51 @@@ Value Eval::tempo_value(const Position /// evaluation of the position from the point of view of the side to move. Value Eval::evaluate(const Position& pos) { - return Evaluation(pos).value(); - } + Value v; + + if (!Eval::useNNUE) + v = Evaluation(pos).value(); + else + { + // Scale and shift NNUE for compatibility with search and classical evaluation + auto adjusted_NNUE = [&](){ + int mat = pos.non_pawn_material() + PieceValue[MG][PAWN] * pos.count(); + return NNUE::evaluate(pos) * (720 + mat / 32) / 1024 + Tempo; + }; + + // If there is PSQ imbalance use classical eval, with small probability if it is small + Value psq = Value(abs(eg_value(pos.psq_score()))); + int r50 = 16 + pos.rule50_count(); + bool largePsq = psq * 16 > (NNUEThreshold1 + pos.non_pawn_material() / 64) * r50; + bool classical = largePsq || (psq > PawnValueMg / 4 && !(pos.this_thread()->nodes & 0xB)); + + v = classical ? Evaluation(pos).value() : adjusted_NNUE(); + + // If the classical eval is small and imbalance large, use NNUE nevertheless. + // For the case of opposite colored bishops, switch to NNUE eval with + // small probability if the classical eval is less than the threshold. + if ( largePsq + && (abs(v) * 16 < NNUEThreshold2 * r50 + || ( pos.opposite_bishops() + && abs(v) * 16 < (NNUEThreshold1 + pos.non_pawn_material() / 64) * r50 + && !(pos.this_thread()->nodes & 0xB)))) + v = adjusted_NNUE(); + } + + // Damp down the evaluation linearly when shuffling - v = v * (100 - pos.rule50_count()) / 100; ++ if (pos.n_move_rule()) ++ { ++ v = v * (2 * pos.n_move_rule() - pos.rule50_count()) / (2 * pos.n_move_rule()); ++ if (pos.material_counting()) ++ v += pos.material_counting_result() / (10 * std::max(2 * pos.n_move_rule() - pos.rule50_count(), 1)); ++ } + + // Guarantee evaluation does not hit the tablebase range + v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); + + return v; + } /// trace() is like evaluate(), but instead of returning a value, it returns /// a string (suitable for outputting to stdout) that contains the detailed diff --cc src/evaluate.h index c289736,6a17f28..000139b --- a/src/evaluate.h +++ b/src/evaluate.h @@@ -29,10 -27,26 +27,27 @@@ class Position namespace Eval { - std::string trace(const Position& pos); ++ Value tempo_value(const Position& pos); + std::string trace(const Position& pos); + Value evaluate(const Position& pos); - Value tempo_value(const Position& pos); - Value evaluate(const Position& pos); - } + extern bool useNNUE; + extern std::string eval_file_loaded; + + // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue + // for the build process (profile-build and fishtest) to work. Do not change the + // name of the macro, as it is used in the Makefile. + #define EvalFileDefaultName "nn-04cf2b4ed1da.nnue" + + namespace NNUE { + + Value evaluate(const Position& pos); + bool load_eval(std::string name, std::istream& stream); + void init(); + void verify(); + + } // namespace NNUE + + } // namespace Eval #endif // #ifndef EVALUATE_H_INCLUDED diff --cc src/main.cpp index c3dc794,e6dff91..8ac8329 --- a/src/main.cpp +++ b/src/main.cpp @@@ -39,11 -35,10 +37,12 @@@ int main(int argc, char* argv[]) std::cout << engine_info() << std::endl; + pieceMap.init(); + variants.init(); + CommandLine::init(argc, argv); UCI::init(Options); Tune::init(); - PSQT::init(); + PSQT::init(variants.find(Options["UCI_Variant"])->second); Bitboards::init(); Position::init(); Bitbases::init(); diff --cc src/material.cpp index 097a674,870a5e1..9323408 --- a/src/material.cpp +++ b/src/material.cpp @@@ -141,23 -130,11 +139,23 @@@ Entry* probe(const Position& pos) Value npm_w = pos.non_pawn_material(WHITE); Value npm_b = pos.non_pawn_material(BLACK); - Value npm = Utility::clamp(npm_w + npm_b, EndgameLimit, MidgameLimit); + Value npm = std::clamp(npm_w + npm_b, EndgameLimit, MidgameLimit); // Map total non-pawn material into [PHASE_ENDGAME, PHASE_MIDGAME] - e->gamePhase = Phase(((npm - EndgameLimit) * PHASE_MIDGAME) / (MidgameLimit - EndgameLimit)); + if (pos.captures_to_hand() || pos.two_boards()) + { + Value npm2 = VALUE_ZERO; + for (PieceType pt : pos.piece_types()) + npm2 += (pos.count_in_hand(WHITE, pt) + pos.count_in_hand(BLACK, pt)) * PieceValue[MG][make_piece(WHITE, pt)]; + e->gamePhase = Phase(PHASE_MIDGAME * npm / std::max(int(npm + npm2), 1)); + int countAll = pos.count_with_hand(WHITE, ALL_PIECES) + pos.count_with_hand(BLACK, ALL_PIECES); + e->materialDensity = (npm + npm2 + pos.count() * PawnValueMg) * countAll / ((pos.max_file() + 1) * (pos.max_rank() + 1)); + } + else + e->gamePhase = Phase(((npm - EndgameLimit) * PHASE_MIDGAME) / (MidgameLimit - EndgameLimit)); + if (pos.endgame_eval()) + { // Let's look if we have a specialized evaluation function for this particular // material configuration. Firstly we look for a fixed configuration one, then // for a generic one if the previous search failed. diff --cc src/misc.cpp index da02150,a16a6e9..64d30af --- a/src/misc.cpp +++ b/src/misc.cpp @@@ -147,16 -152,8 +152,13 @@@ const string engine_info(bool to_uci, b ss << setw(2) << day << setw(2) << (1 + months.find(month) / 4) << year.substr(2); } - ss << (to_uci ? "\nid author ": " by ") - << "the Stockfish developers (see AUTHORS file)"; +#ifdef LARGEBOARDS + ss << " LB"; +#endif + - ss << (Is64Bit ? " 64" : "") - << (HasPext ? " BMI2" : (HasPopCnt ? " POPCNT" : "")); - + if (!to_xboard) + ss << (to_uci ? "\nid author ": " by ") + << "Fabian Fichter"; return ss.str(); } @@@ -300,25 -330,38 +335,38 @@@ void prefetch(void* addr) #endif - /// aligned_ttmem_alloc() will return suitably aligned memory, and if possible use large pages. - /// The returned pointer is the aligned one, while the mem argument is the one that needs - /// to be passed to free. With c++17 some of this functionality could be simplified. + /// std_aligned_alloc() is our wrapper for systems where the c++17 implementation + /// does not guarantee the availability of aligned_alloc(). Memory allocated with + /// std_aligned_alloc() must be freed with std_aligned_free(). - #if defined(__linux__) && !defined(__ANDROID__) + void* std_aligned_alloc(size_t alignment, size_t size) { - void* aligned_ttmem_alloc(size_t allocSize, void*& mem) { + #if defined(POSIXALIGNEDALLOC) + void *mem; + return posix_memalign(&mem, alignment, size) ? nullptr : mem; + #elif defined(_WIN32) + return _mm_malloc(size, alignment); + #else - return std::aligned_alloc(alignment, size); ++ return aligned_alloc(alignment, size); + #endif + } - constexpr size_t alignment = 2 * 1024 * 1024; // assumed 2MB page sizes - size_t size = ((allocSize + alignment - 1) / alignment) * alignment; // multiple of alignment - if (posix_memalign(&mem, alignment, size)) - mem = nullptr; - madvise(mem, allocSize, MADV_HUGEPAGE); - return mem; + void std_aligned_free(void* ptr) { + + #if defined(POSIXALIGNEDALLOC) + free(ptr); + #elif defined(_WIN32) + _mm_free(ptr); + #else + free(ptr); + #endif } - #elif defined(_WIN64) + /// aligned_large_pages_alloc() will return suitably aligned memory, if possible using large pages. + + #if defined(_WIN32) - static void* aligned_ttmem_alloc_large_pages(size_t allocSize) { + static void* aligned_large_pages_alloc_win(size_t allocSize) { HANDLE hProcessToken { }; LUID luid { }; diff --cc src/movegen.cpp index 279e84c,3340f65..7d44606 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@@ -363,67 -233,24 +361,67 @@@ namespace default: static_assert(true, "Unsupported type in generate_all()"); } + target &= pos.board_bb(); moveList = generate_pawn_moves(pos, moveList, target); - moveList = generate_moves(pos, moveList, target); - moveList = generate_moves(pos, moveList, target); - moveList = generate_moves(pos, moveList, target); - moveList = generate_moves(pos, moveList, target); - - if (Type != QUIET_CHECKS && Type != EVASIONS) + for (PieceType pt : pos.piece_types()) + if (pt != PAWN && pt != KING) + moveList = generate_moves(pos, moveList, pt, target); + // generate drops + if (pos.piece_drops() && Type != CAPTURES && pos.count_in_hand(Us, ALL_PIECES)) + for (PieceType pt : pos.piece_types()) + moveList = generate_drops(pos, moveList, pt, target & ~pos.pieces(~Us)); + + if (Type != QUIET_CHECKS && Type != EVASIONS && pos.count(Us)) { Square ksq = pos.square(Us); - Bitboard b = attacks_bb(ksq) & target; + Bitboard b = ( (pos.attacks_from(Us, KING, ksq) & pos.pieces()) + | (pos.moves_from(Us, KING, ksq) & ~pos.pieces())) & target; while (b) - *moveList++ = make_move(ksq, pop_lsb(&b)); + moveList = make_move_and_gating(pos, moveList, Us, ksq, pop_lsb(&b)); + + // Passing move by king + if (pos.pass()) + *moveList++ = make(ksq, ksq); if ((Type != CAPTURES) && pos.can_castle(Us & ANY_CASTLING)) - for(CastlingRights cr : { Us & KING_SIDE, Us & QUEEN_SIDE } ) + for (CastlingRights cr : { Us & KING_SIDE, Us & QUEEN_SIDE } ) if (!pos.castling_impeded(cr) && pos.can_castle(cr)) - *moveList++ = make(ksq, pos.castling_rook_square(cr)); + moveList = make_move_and_gating(pos, moveList, Us,ksq, pos.castling_rook_square(cr)); + } + // Workaround for passing: Execute a non-move with any piece + else if (pos.pass() && !pos.count(Us) && pos.pieces(Us)) + *moveList++ = make(lsb(pos.pieces(Us)), lsb(pos.pieces(Us))); + + // Castling with non-king piece + if (!pos.count(Us) && Type != CAPTURES && pos.can_castle(Us & ANY_CASTLING)) + { + Square from = make_square(FILE_E, pos.castling_rank(Us)); + for(CastlingRights cr : { Us & KING_SIDE, Us & QUEEN_SIDE } ) + if (!pos.castling_impeded(cr) && pos.can_castle(cr)) + moveList = make_move_and_gating(pos, moveList, Us, from, pos.castling_rook_square(cr)); + } + + // Special moves + if (pos.cambodian_moves() && pos.gates(Us)) + { + if (Type != CAPTURES && Type != EVASIONS && (pos.pieces(Us, KING) & pos.gates(Us))) + { + Square from = pos.square(Us); + Bitboard b = PseudoAttacks[WHITE][KNIGHT][from] & rank_bb(rank_of(from + (Us == WHITE ? NORTH : SOUTH))) + & target & ~pos.pieces(); + while (b) + moveList = make_move_and_gating(pos, moveList, Us, from, pop_lsb(&b)); + } + + Bitboard b = pos.pieces(Us, FERS) & pos.gates(Us); + while (b) + { + Square from = pop_lsb(&b); + Square to = from + 2 * (Us == WHITE ? NORTH : SOUTH); + if (is_ok(to) && (target & to)) + moveList = make_move_and_gating(pos, moveList, Us, from, to); + } } return moveList; diff --cc src/movepick.h index d736cff,4c0ad55..f488615 --- a/src/movepick.h +++ b/src/movepick.h @@@ -86,13 -84,13 +84,13 @@@ enum StatsType { NoCaptures, Captures } /// unsuccessful during the current search, and is used for reduction and move /// ordering decisions. It uses 2 tables (one for each color) indexed by /// the move's from and to squares, see www.chessprogramming.org/Butterfly_Boards -typedef Stats ButterflyHistory; +typedef Stats ButterflyHistory; - /// At higher depths LowPlyHistory records successful quiet moves near the root and quiet - /// moves which are/were in the PV (ttPv) - /// It is cleared with each new search and filled during iterative deepening + /// At higher depths LowPlyHistory records successful quiet moves near the root + /// and quiet moves which are/were in the PV (ttPv). It is cleared with each new + /// search and filled during iterative deepening. constexpr int MAX_LPH = 4; -typedef Stats LowPlyHistory; +typedef Stats LowPlyHistory; /// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous /// move, see www.chessprogramming.org/Countermove_Heuristic diff --cc src/pawns.cpp index 663f81e,a5102db..3972fb4 --- a/src/pawns.cpp +++ b/src/pawns.cpp @@@ -32,14 -30,14 +30,14 @@@ namespace #define S(mg, eg) make_score(mg, eg) // Pawn penalties - constexpr Score Backward = S( 9, 24); - constexpr Score Doubled = S(11, 56); - constexpr Score Isolated = S( 5, 15); - constexpr Score WeakLever = S( 0, 56); - constexpr Score WeakUnopposed = S(13, 27); + constexpr Score Backward = S( 8, 25); + constexpr Score Doubled = S(10, 55); + constexpr Score Isolated = S( 3, 15); + constexpr Score WeakLever = S( 3, 55); + constexpr Score WeakUnopposed = S(13, 25); // Bonus for blocked pawns at 5th or 6th rank - constexpr Score BlockedPawn[RANK_NB - 5] = { S(-11, -4), S(-3, 4) }; - constexpr Score BlockedPawn[2] = { S(-13, -4), S(-5, 2) }; ++ constexpr Score BlockedPawn[RANK_NB - 5] = { S(-13, -4), S(-5, 2) }; constexpr Score BlockedStorm[RANK_NB] = { S(0, 0), S(0, 0), S(76, 78), S(-10, 15), S(-7, 10), S(-4, 6), S(-1, 2) @@@ -147,12 -144,10 +145,12 @@@ e->passedPawns[Us] |= s; // Score this pawn - if (support | phalanx) + if ((support | phalanx) && (r < pos.promotion_rank() || !pos.mandatory_pawn_promotion())) { - int v = Connected[r] * (2 + bool(phalanx) - bool(opposed)) + int v = Connected[r] * (2 + bool(phalanx) - bool(opposed)) * (r == RANK_2 && pos.captures_to_hand() ? 3 : 1) - + 21 * popcount(support); + + 22 * popcount(support); + if (r >= RANK_4 && pos.count(Us) > popcount(pos.board_bb()) / 4) + v = popcount(support | phalanx) * 50 / (opposed ? 2 : 1); score += make_score(v, v * (r - 2) / 4); } @@@ -244,7 -219,7 +242,7 @@@ Score Entry::evaluate_shelter(const Pos Score bonus = make_score(5, 5); - File center = Utility::clamp(file_of(ksq), FILE_B, File(pos.max_file() - 1)); - File center = std::clamp(file_of(ksq), FILE_B, FILE_G); ++ File center = std::clamp(file_of(ksq), FILE_B, File(pos.max_file() - 1)); for (File f = File(center - 1); f <= File(center + 1); ++f) { b = ourPawns & file_bb(f); diff --cc src/position.cpp index 9ddb9ae,e6a760d..5d3b102 --- a/src/position.cpp +++ b/src/position.cpp @@@ -1206,9 -701,12 +1204,14 @@@ void Position::do_move(Move m, StateInf ++gamePly; ++st->rule50; ++st->pliesFromNull; + if (st->countingLimit) + ++st->countingPly; + // Used by NNUE + st->accumulator.computed_accumulation = false; + auto& dp = st->dirtyPiece; + dp.dirty_num = 1; + Color us = sideToMove; Color them = ~us; Square from = from_sq(m); @@@ -1267,21 -754,19 +1270,29 @@@ else st->nonPawnMaterial[them] -= PieceValue[MG][captured]; + if (Eval::useNNUE) + { + dp.dirty_num = 2; // 1 piece moved, 1 piece captured + dp.piece[1] = captured; + dp.from[1] = capsq; + dp.to[1] = SQ_NONE; + } + // Update board and piece lists + bool capturedPromoted = is_promoted(capsq); + Piece unpromotedCaptured = unpromoted_piece_on(capsq); remove_piece(capsq); - if (type_of(m) == ENPASSANT) board[capsq] = NO_PIECE; + if (captures_to_hand()) + { + Piece pieceToHand = !capturedPromoted || drop_loop() ? ~captured + : unpromotedCaptured ? ~unpromotedCaptured + : make_piece(~color_of(captured), PAWN); + add_to_hand(pieceToHand); + k ^= Zobrist::inHand[pieceToHand][pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)] - 1] + ^ Zobrist::inHand[pieceToHand][pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)]]; + } // Update material hash key and prefetch access to materialTable k ^= Zobrist::psq[captured][capsq]; @@@ -1319,73 -795,18 +1330,91 @@@ k ^= Zobrist::castling[st->castlingRights]; } + // Flip enclosed pieces + st->flippedPieces = 0; + if (flip_enclosed_pieces() && !is_pass(m)) + { + // Find end of rows to be flipped + if (flip_enclosed_pieces() == REVERSI) + { + Bitboard b = attacks_bb(us, QUEEN, to, board_bb() & ~pieces(~us)) & ~PseudoAttacks[us][KING][to] & pieces(us); + while(b) + st->flippedPieces |= between_bb(to, pop_lsb(&b)); + } + else + { + assert(flip_enclosed_pieces() == ATAXX); + st->flippedPieces = PseudoAttacks[us][KING][to] & pieces(~us); + } + + // Flip pieces + Bitboard to_flip = st->flippedPieces; + while(to_flip) + { + Square s = pop_lsb(&to_flip); + Piece flipped = piece_on(s); + Piece resulting = ~flipped; + + // remove opponent's piece + remove_piece(s); + k ^= Zobrist::psq[flipped][s]; + st->materialKey ^= Zobrist::psq[flipped][pieceCount[flipped]]; + st->nonPawnMaterial[them] -= PieceValue[MG][flipped]; + + // add our piece + put_piece(resulting, s); + k ^= Zobrist::psq[resulting][s]; + st->materialKey ^= Zobrist::psq[resulting][pieceCount[resulting]-1]; + st->nonPawnMaterial[us] += PieceValue[MG][resulting]; + } + } + // Move the piece. The tricky Chess960 castling is handled earlier - if (type_of(m) != CASTLING) + if (type_of(m) == DROP) + { ++ if (Eval::useNNUE) ++ { ++ // Add drop piece ++ dp.piece[dp.dirty_num] = pc; ++ dp.from[dp.dirty_num] = SQ_NONE; ++ dp.to[dp.dirty_num] = to; ++ dp.dirty_num++; ++ } ++ + drop_piece(make_piece(us, in_hand_piece_type(m)), pc, to); + st->materialKey ^= Zobrist::psq[pc][pieceCount[pc]-1]; + if (type_of(pc) != PAWN) + st->nonPawnMaterial[us] += PieceValue[MG][pc]; + // Set castling rights for dropped king or rook + if (castling_dropped_piece() && rank_of(to) == castling_rank(us)) + { + if (type_of(pc) == KING && file_of(to) == FILE_E) + { + Bitboard castling_rooks = pieces(us, castling_rook_piece()) + & rank_bb(castling_rank(us)) + & (file_bb(FILE_A) | file_bb(max_file())); + while (castling_rooks) + set_castling_right(us, pop_lsb(&castling_rooks)); + } + else if (type_of(pc) == castling_rook_piece()) + { + if ( (file_of(to) == FILE_A || file_of(to) == max_file()) + && piece_on(make_square(FILE_E, castling_rank(us))) == make_piece(us, KING)) + set_castling_right(us, to); + } + } + } + else if (type_of(m) != CASTLING) + { + if (Eval::useNNUE) + { + dp.piece[0] = pc; + dp.from[0] = from; + dp.to[0] = to; + } + move_piece(from, to); + } // If the moving piece is a pawn do some special extra work if (type_of(pc) == PAWN) @@@ -1399,16 -819,26 +1428,26 @@@ k ^= Zobrist::enpassant[file_of(st->epSquare)]; } - else if (type_of(m) == PROMOTION) + else if (type_of(m) == PROMOTION || type_of(m) == PIECE_PROMOTION) { - Piece promotion = make_piece(us, promotion_type(m)); + Piece promotion = make_piece(us, type_of(m) == PROMOTION ? promotion_type(m) : promoted_piece_type(PAWN)); - assert(relative_rank(us, to) == RANK_8); - assert(type_of(promotion) >= KNIGHT && type_of(promotion) <= QUEEN); + assert(relative_rank(us, to, max_rank()) >= promotion_rank() || sittuyin_promotion()); + assert(type_of(promotion) >= KNIGHT && type_of(promotion) < KING); remove_piece(to); - put_piece(promotion, to); + put_piece(promotion, to, true, type_of(m) == PIECE_PROMOTION ? pc : NO_PIECE); + if (Eval::useNNUE) + { + // Promoting pawn to SQ_NONE, promoted piece from SQ_NONE + dp.to[0] = SQ_NONE; + dp.piece[dp.dirty_num] = promotion; + dp.from[dp.dirty_num] = SQ_NONE; + dp.to[dp.dirty_num] = to; + dp.dirty_num++; + } + // Update hash keys k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[promotion][to]; st->pawnKey ^= Zobrist::psq[pc][to]; @@@ -1425,70 -855,15 +1464,101 @@@ // Reset rule 50 draw counter st->rule50 = 0; } + else if (type_of(m) == PIECE_PROMOTION) + { + Piece promotion = make_piece(us, promoted_piece_type(type_of(pc))); + + remove_piece(to); + put_piece(promotion, to, true, pc); + ++ if (Eval::useNNUE) ++ { ++ // Promoting piece to SQ_NONE, promoted piece from SQ_NONE ++ dp.to[0] = SQ_NONE; ++ dp.piece[dp.dirty_num] = promotion; ++ dp.from[dp.dirty_num] = SQ_NONE; ++ dp.to[dp.dirty_num] = to; ++ dp.dirty_num++; ++ } ++ + // Update hash keys + k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[promotion][to]; + st->materialKey ^= Zobrist::psq[promotion][pieceCount[promotion]-1] + ^ Zobrist::psq[pc][pieceCount[pc]]; + + // Update material + st->nonPawnMaterial[us] += PieceValue[MG][promotion] - PieceValue[MG][pc]; + } + else if (type_of(m) == PIECE_DEMOTION) + { + Piece demotion = unpromoted_piece_on(to); + + remove_piece(to); + put_piece(demotion, to); + ++ if (Eval::useNNUE) ++ { ++ // Demoting piece to SQ_NONE, demoted piece from SQ_NONE ++ dp.to[0] = SQ_NONE; ++ dp.piece[dp.dirty_num] = demotion; ++ dp.from[dp.dirty_num] = SQ_NONE; ++ dp.to[dp.dirty_num] = to; ++ dp.dirty_num++; ++ } ++ + // Update hash keys + k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[demotion][to]; + st->materialKey ^= Zobrist::psq[demotion][pieceCount[demotion]-1] + ^ Zobrist::psq[pc][pieceCount[pc]]; + + // Update material + st->nonPawnMaterial[us] += PieceValue[MG][demotion] - PieceValue[MG][pc]; + } // Set capture piece st->capturedPiece = captured; + // Add gating piece + if (is_gating(m)) + { + Square gate = gating_square(m); + Piece gating_piece = make_piece(us, gating_type(m)); ++ + put_piece(gating_piece, gate); + remove_from_hand(gating_piece); ++ ++ if (Eval::useNNUE) ++ { ++ // Add gating piece ++ dp.piece[dp.dirty_num] = gating_piece; ++ dp.from[dp.dirty_num] = SQ_NONE; ++ dp.to[dp.dirty_num] = gate; ++ dp.dirty_num++; ++ } ++ + st->gatesBB[us] ^= gate; + k ^= Zobrist::psq[gating_piece][gate]; + st->materialKey ^= Zobrist::psq[gating_piece][pieceCount[gating_piece]]; + st->nonPawnMaterial[us] += PieceValue[MG][gating_piece]; + } + + // Remove gates + if (gating()) + { + if (is_ok(from) && (gates(us) & from)) + st->gatesBB[us] ^= from; + if (type_of(m) == CASTLING && (gates(us) & to_sq(m))) + st->gatesBB[us] ^= to_sq(m); + if (gates(them) & to) + st->gatesBB[them] ^= to; + if (seirawan_gating() && !count_in_hand(us, ALL_PIECES) && !captures_to_hand()) + st->gatesBB[us] = 0; + } + // Update the key with the final value st->key = k; - // Calculate checkers bitboard (if move gives check) - st->checkersBB = givesCheck ? attackers_to(square(them)) & pieces(us) : 0; + st->checkersBB = givesCheck ? attackers_to(square(them), us) & pieces(us) : Bitboard(0); sideToMove = ~sideToMove; @@@ -1643,17 -964,27 +1713,30 @@@ void Position::do_castling(Color us, Sq bool kingSide = to > from; rfrom = to; // Castling is encoded as "king captures friendly rook" - rto = relative_square(us, kingSide ? SQ_F1 : SQ_D1); - to = relative_square(us, kingSide ? SQ_G1 : SQ_C1); + to = make_square(kingSide ? castling_kingside_file() : castling_queenside_file(), castling_rank(us)); + rto = to + (kingSide ? WEST : EAST); + - // Remove both pieces first since squares could overlap in Chess960 + Piece castlingKingPiece = piece_on(Do ? from : to); + Piece castlingRookPiece = piece_on(Do ? rfrom : rto); + + if (Do && Eval::useNNUE) + { + auto& dp = st->dirtyPiece; - dp.piece[0] = make_piece(us, KING); ++ dp.piece[0] = castlingKingPiece; + dp.from[0] = from; + dp.to[0] = to; - dp.piece[1] = make_piece(us, ROOK); ++ dp.piece[1] = castlingRookPiece; + dp.from[1] = rfrom; + dp.to[1] = rto; + dp.dirty_num = 2; + } + + // Remove both pieces first since squares could overlap in Chess960 remove_piece(Do ? from : to); remove_piece(Do ? rfrom : rto); - board[Do ? from : to] = board[Do ? rfrom : rto] = NO_PIECE; // Since remove_piece doesn't do this for us - put_piece(make_piece(us, KING), Do ? to : from); - put_piece(make_piece(us, ROOK), Do ? rto : rfrom); + board[Do ? from : to] = board[Do ? rfrom : rto] = NO_PIECE; // Since remove_piece doesn't do it for us + put_piece(castlingKingPiece, Do ? to : from); + put_piece(castlingRookPiece, Do ? rto : rfrom); } diff --cc src/position.h index eae140d,d6f5c9f..792daaa --- a/src/position.h +++ b/src/position.h @@@ -25,12 -23,13 +23,15 @@@ #include #include // For std::unique_ptr #include +#include #include "bitboard.h" + #include "evaluate.h" #include "types.h" +#include "variant.h" + #include "nnue/nnue_accumulator.h" + /// StateInfo struct stores information needed to restore a Position object to /// its previous state when we retract a move. Whenever a move is made on the @@@ -60,12 -54,11 +61,16 @@@ struct StateInfo Bitboard blockersForKing[COLOR_NB]; Bitboard pinners[COLOR_NB]; Bitboard checkSquares[PIECE_TYPE_NB]; + Bitboard flippedPieces; + bool capturedpromoted; + bool shak; + bool bikjang; + bool pass; int repetition; + + // Used by NNUE + Eval::NNUE::Accumulator accumulator; + DirtyPiece dirtyPiece; }; @@@ -1127,75 -439,9 +1140,82 @@@ inline void Position::do_move(Move m, S do_move(m, newSt, gives_check(m)); } + inline StateInfo* Position::state() const { + + return st; + } + ++// Variant-specific ++ +inline int Position::count_in_hand(Color c, PieceType pt) const { + return pieceCountInHand[c][pt]; +} + +inline int Position::count_with_hand(Color c, PieceType pt) const { + return pieceCount[make_piece(c, pt)] + pieceCountInHand[c][pt]; +} + +inline bool Position::bikjang() const { + return st->bikjang; +} + +inline Value Position::material_counting_result() const { + auto weigth_count = [this](PieceType pt, int v){ return v * (count(WHITE, pt) - count(BLACK, pt)); }; + int materialCount; + Value result; + switch (var->materialCounting) + { + case JANGGI_MATERIAL: + materialCount = weigth_count(ROOK, 13) + + weigth_count(JANGGI_CANNON, 7) + + weigth_count(HORSE, 5) + + weigth_count(JANGGI_ELEPHANT, 3) + + weigth_count(WAZIR, 3) + + weigth_count(SOLDIER, 2) + - 1; + result = materialCount > 0 ? VALUE_MATE : -VALUE_MATE; + break; + case UNWEIGHTED_MATERIAL: + result = count(WHITE, ALL_PIECES) > count(BLACK, ALL_PIECES) ? VALUE_MATE + : count(WHITE, ALL_PIECES) < count(BLACK, ALL_PIECES) ? -VALUE_MATE + : VALUE_DRAW; + break; + case WHITE_DRAW_ODDS: + result = VALUE_MATE; + break; + case BLACK_DRAW_ODDS: + result = -VALUE_MATE; + break; + default: + assert(false); + result = VALUE_DRAW; + } + return sideToMove == WHITE ? result : -result; +} + +inline void Position::add_to_hand(Piece pc) { + pieceCountInHand[color_of(pc)][type_of(pc)]++; + pieceCountInHand[color_of(pc)][ALL_PIECES]++; + psq += PSQT::psq[pc][SQ_NONE]; +} + +inline void Position::remove_from_hand(Piece pc) { + pieceCountInHand[color_of(pc)][type_of(pc)]--; + pieceCountInHand[color_of(pc)][ALL_PIECES]--; + psq -= PSQT::psq[pc][SQ_NONE]; +} + +inline void Position::drop_piece(Piece pc_hand, Piece pc_drop, Square s) { + assert(pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)]); + put_piece(pc_drop, s, pc_drop != pc_hand, pc_drop != pc_hand ? pc_hand : NO_PIECE); + remove_from_hand(pc_hand); +} + +inline void Position::undrop_piece(Piece pc_hand, Square s) { + remove_piece(s); + board[s] = NO_PIECE; + add_to_hand(pc_hand); + assert(pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)]); +} + #endif // #ifndef POSITION_H_INCLUDED diff --cc src/psqt.cpp index c68be31,eb36e75..e85c423 --- a/src/psqt.cpp +++ b/src/psqt.cpp @@@ -109,112 -103,19 +107,112 @@@ Score psq[PIECE_NB][SQUARE_NB + 1] // PSQT::init() initializes piece-square tables: the white halves of the tables are // copied from Bonus[] and PBonus[], adding the piece value, then the black halves of // the tables are initialized by flipping and changing the sign of the white scores. -void init() { +void init(const Variant* v) { + + PieceType strongestPiece = NO_PIECE_TYPE; + for (PieceType pt : v->pieceTypes) + if (PieceValue[MG][pt] > PieceValue[MG][strongestPiece]) + strongestPiece = pt; + + Value maxPromotion = VALUE_ZERO; + for (PieceType pt : v->promotionPieceTypes) + maxPromotion = std::max(maxPromotion, PieceValue[EG][pt]); - for (Piece pc : {W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING}) + for (PieceType pt = PAWN; pt <= KING; ++pt) { + Piece pc = make_piece(WHITE, pt); + Score score = make_score(PieceValue[MG][pc], PieceValue[EG][pc]); - for (Square s = SQ_A1; s <= SQ_H8; ++s) + // Consider promotion types in pawn score + if (pt == PAWN) + score -= make_score(0, (QueenValueEg - maxPromotion) / 100); + + // Scale slider piece values with board size + const PieceInfo* pi = pieceMap.find(pt)->second; + bool isSlider = pi->sliderQuiet.size() || pi->sliderCapture.size() || pi->hopperQuiet.size() || pi->hopperCapture.size(); + bool isPawn = !isSlider && pi->stepsQuiet.size() && !std::any_of(pi->stepsQuiet.begin(), pi->stepsQuiet.end(), [](Direction d) { return d < SOUTH / 2; }); + bool isSlowLeaper = !isSlider && !std::any_of(pi->stepsQuiet.begin(), pi->stepsQuiet.end(), [](Direction d) { return dist(d) > 1; }); + + if (isSlider) + { + constexpr int lc = 5; + constexpr int rm = 5; + constexpr int r0 = rm + RANK_8; + int r1 = rm + (v->maxRank + v->maxFile) / 2; + int leaper = pi->stepsQuiet.size() + pi->stepsCapture.size(); + int slider = pi->sliderQuiet.size() + pi->sliderCapture.size() + pi->hopperQuiet.size() + pi->hopperCapture.size(); + score = make_score(mg_value(score) * (lc * leaper + r1 * slider) / (lc * leaper + r0 * slider), + eg_value(score) * (lc * leaper + r1 * slider) / (lc * leaper + r0 * slider)); + } + + // Increase leapers' value in makpong + if (v->makpongRule) + { + if (std::any_of(pi->stepsCapture.begin(), pi->stepsCapture.end(), [](Direction d) { return dist(d) > 1; }) + && !pi->lameLeaper) + score = make_score(mg_value(score) * 4200 / (3500 + mg_value(score)), + eg_value(score) * 4700 / (3500 + mg_value(score))); + } + + // For drop variants, halve the piece values + if (v->capturesToHand) + score = make_score(mg_value(score) * 3500 / (7000 + mg_value(score)), + eg_value(score) * 3500 / (7000 + eg_value(score))); + else if (!v->checking) + score = make_score(mg_value(score) * 2000 / (3500 + mg_value(score)), + eg_value(score) * 2200 / (3500 + eg_value(score))); + else if (v->twoBoards) + score = make_score(mg_value(score) * 7000 / (7000 + mg_value(score)), + eg_value(score) * 7000 / (7000 + eg_value(score))); + else if (v->checkCounting) + score = make_score(mg_value(score) * (40000 + mg_value(score)) / 41000, + eg_value(score) * (30000 + eg_value(score)) / 31000); + else if (pt == strongestPiece) + score += make_score(std::max(QueenValueMg - PieceValue[MG][pt], VALUE_ZERO) / 20, + std::max(QueenValueEg - PieceValue[EG][pt], VALUE_ZERO) / 20); + + // For antichess variants, use negative piece values + if ( v->extinctionValue == VALUE_MATE + && v->extinctionPieceTypes.find(ALL_PIECES) != v->extinctionPieceTypes.end()) + score = -make_score(mg_value(score) / 8, eg_value(score) / 8 / (1 + !pi->sliderCapture.size())); + + // Determine pawn rank + std::istringstream ss(v->startFen); + unsigned char token; + Rank rc = v->maxRank; + Rank pawnRank = RANK_2; + while ((ss >> token) && !isspace(token)) + { + if (token == '/') + --rc; + else if (token == v->pieceToChar[PAWN] || token == v->pieceToChar[SHOGI_PAWN]) + pawnRank = rc; + } + + for (Square s = SQ_A1; s <= SQ_MAX; ++s) { - File f = File(edge_distance(file_of(s))); - psq[ pc][s] = score + (type_of(pc) == PAWN ? PBonus[rank_of(s)][file_of(s)] - : Bonus[pc][rank_of(s)][f]); - psq[~pc][flip_rank(s)] = -psq[pc][s]; + File f = std::max(File(edge_distance(file_of(s), v->maxFile)), FILE_A); + Rank r = rank_of(s); + psq[ pc][s] = score + ( pt == PAWN ? PBonus[std::min(r, RANK_8)][std::min(file_of(s), FILE_H)] - : pt == KING ? KingBonus[Utility::clamp(Rank(r - pawnRank + 1), RANK_1, RANK_8)][std::min(f, FILE_D)] * (1 + v->capturesToHand) ++ : pt == KING ? KingBonus[std::clamp(Rank(r - pawnRank + 1), RANK_1, RANK_8)][std::min(f, FILE_D)] * (1 + v->capturesToHand) + : pt <= QUEEN ? Bonus[pc][std::min(r, RANK_8)][std::min(f, FILE_D)] + : pt == HORSE ? Bonus[KNIGHT][std::min(r, RANK_8)][std::min(f, FILE_D)] + : isSlider ? make_score(5, 5) * (2 * f + std::max(std::min(r, Rank(v->maxRank - r)), RANK_1) - v->maxFile - 1) + : isPawn ? make_score(5, 5) * (2 * f - v->maxFile) + : make_score(10, 10) * (1 + isSlowLeaper) * (f + std::max(std::min(r, Rank(v->maxRank - r)), RANK_1) - v->maxFile / 2)); + if (pt == SOLDIER && r < v->soldierPromotionRank) + psq[pc][s] -= score * (v->soldierPromotionRank - r) / (4 + f); + if (v->enclosingDrop == REVERSI) + { + if (f == FILE_A && (r == RANK_1 || r == v->maxRank)) + psq[pc][s] += make_score(1000, 1000); + } + psq[~pc][rank_of(s) <= v->maxRank ? flip_rank(s, v->maxRank) : s] = -psq[pc][s]; } + // pieces in pocket + psq[ pc][SQ_NONE] = score + make_score(35, 10) * (1 + !isSlider); + psq[~pc][SQ_NONE] = -psq[pc][SQ_NONE]; } } diff --cc src/search.cpp index b12995f,eaa79fb..d0b74fa --- a/src/search.cpp +++ b/src/search.cpp @@@ -226,28 -222,16 +224,30 @@@ void MainThread::search() } Color us = rootPos.side_to_move(); - Time.init(Limits, us, rootPos.game_ply()); + Time.init(rootPos, Limits, us, rootPos.game_ply()); TT.new_search(); + Eval::NNUE::verify(); + - if (rootMoves.empty()) + if (rootMoves.empty() || (Options["Protocol"] == "xboard" && rootPos.is_optional_game_end())) { rootMoves.emplace_back(MOVE_NONE); + Value variantResult; + Value result = rootPos.is_game_end(variantResult) ? variantResult + : rootPos.checkers() ? rootPos.checkmate_value() + : rootPos.stalemate_value(); + if (Options["Protocol"] == "xboard") + { + // rotate MOVE_NONE to front (for optional game end) + std::rotate(rootMoves.rbegin(), rootMoves.rbegin() + 1, rootMoves.rend()); + sync_cout << ( result == VALUE_DRAW ? "1/2-1/2 {Draw}" + : (rootPos.side_to_move() == BLACK ? -result : result) == VALUE_MATE ? "1-0 {White wins}" + : "0-1 {Black wins}") + << sync_endl; + } + else sync_cout << "info depth 0 score " - << UCI::value(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW) + << UCI::value(result) << sync_endl; } else @@@ -433,7 -403,7 +433,7 @@@ void Thread::search() if (rootDepth >= 4) { Value prev = rootMoves[pvIdx].previousScore; - delta = Value(19 * (1 + rootPos.captures_to_hand())); - delta = Value(17); ++ delta = Value(17 * (1 + rootPos.captures_to_hand())); alpha = std::max(prev - delta,-VALUE_INFINITE); beta = std::min(prev + delta, VALUE_INFINITE); @@@ -856,15 -791,11 +853,11 @@@ namespace else { if ((ss-1)->currentMove != MOVE_NULL) - { - int bonus = -(ss-1)->statScore / 512; - - ss->staticEval = eval = evaluate(pos) + bonus; - } + ss->staticEval = eval = evaluate(pos); else - ss->staticEval = eval = -(ss-1)->staticEval + 2 * Tempo; + ss->staticEval = eval = -(ss-1)->staticEval + 2 * Eval::tempo_value(pos); - tte->save(posKey, VALUE_NONE, ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval); + tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval); } // Step 7. Razoring (~1 Elo) @@@ -876,40 -804,32 +869,41 @@@ && eval <= alpha - RazorMargin) return qsearch(pos, ss, alpha, beta); - improving = (ss-2)->staticEval == VALUE_NONE ? (ss->staticEval > (ss-4)->staticEval - || (ss-4)->staticEval == VALUE_NONE) : ss->staticEval > (ss-2)->staticEval; + improving = (ss-2)->staticEval == VALUE_NONE + ? ss->staticEval > (ss-4)->staticEval || (ss-4)->staticEval == VALUE_NONE + : ss->staticEval > (ss-2)->staticEval; + // Skip early pruning in case of mandatory capture + if (pos.must_capture() && MoveList(pos).size()) + goto moves_loop; + // Step 8. Futility pruning: child node (~50 Elo) if ( !PvNode - && depth < 6 + && depth < 8 - && eval - futility_margin(depth, improving) >= beta + && !( pos.extinction_value() == -VALUE_MATE + && pos.extinction_piece_types().find(ALL_PIECES) == pos.extinction_piece_types().end()) + && !(pos.capture_the_flag_piece() && !pos.checking_permitted()) + && eval - futility_margin(depth, improving) * (1 + pos.check_counting() + 2 * pos.must_capture()) >= beta && eval < VALUE_KNOWN_WIN) // Do not return unproven wins return eval; // Step 9. Null move search with verification search (~40 Elo) if ( !PvNode && (ss-1)->currentMove != MOVE_NULL - && (ss-1)->statScore < 23824 + && (ss-1)->statScore < 22977 && eval >= beta && eval >= ss->staticEval - && ss->staticEval >= beta - 33 * depth - 33 * improving + 112 * ttPv + 311 + && ss->staticEval >= beta - 30 * depth - 28 * improving + 84 * ss->ttPv + 182 && !excludedMove && pos.non_pawn_material(us) + && pos.count(~us) != pos.count(~us) + && !pos.flip_enclosed_pieces() && (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor)) { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and value - Depth R = (737 - 150 * !pos.checking_permitted() + 77 * depth) / 246 + std::min(int(eval - beta) / 192, 3); - Depth R = (982 + 85 * depth) / 256 + std::min(int(eval - beta) / 192, 3); ++ Depth R = (982 - 150 * !pos.checking_permitted() + 85 * depth) / 256 + std::min(int(eval - beta) / 192, 3); ss->currentMove = MOVE_NULL; ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@@ -945,7 -865,7 +939,7 @@@ } } - probcutBeta = beta + (176 + 20 * !!pos.capture_the_flag_piece()) * (1 + pos.check_counting() + (pos.extinction_value() != VALUE_NONE)) - 49 * improving; - probCutBeta = beta + 176 - 49 * improving; ++ probCutBeta = beta + (176 + 20 * !!pos.capture_the_flag_piece()) * (1 + pos.check_counting() + (pos.extinction_value() != VALUE_NONE)) - 49 * improving; // Step 10. ProbCut (~10 Elo) // If we have a good enough capture and a reduced search returns a value @@@ -1060,11 -986,15 +1060,15 @@@ moves_loop: // When in check, search st thisThread->rootMoves.begin() + thisThread->pvLast, move)) continue; + // Check for legality + if (!rootNode && !pos.legal(move)) + continue; + ss->moveCount = ++moveCount; - if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000) + if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000 && Options["Protocol"] != "xboard") sync_cout << "info depth " << depth - << " currmove " << UCI::move(move, pos.is_chess960()) + << " currmove " << UCI::move(pos, move) << " currmovenumber " << moveCount + thisThread->pvIdx << sync_endl; if (PvNode) (ss+1)->pv = nullptr; @@@ -1100,22 -1028,20 +1104,22 @@@ continue; // Futility pruning: parent node (~5 Elo) - if ( lmrDepth < 6 + if ( lmrDepth < 7 && !ss->inCheck - && ss->staticEval + 283 + 170 * lmrDepth <= alpha - && (*contHist[0])[movedPiece][to_sq(move)] - + (*contHist[1])[movedPiece][to_sq(move)] - + (*contHist[3])[movedPiece][to_sq(move)] - + (*contHist[5])[movedPiece][to_sq(move)] / 2 < 27376) + && !( pos.extinction_value() == -VALUE_MATE + && pos.extinction_piece_types().find(ALL_PIECES) == pos.extinction_piece_types().end()) - && ss->staticEval + (284 + 188 * lmrDepth) * (1 + pos.check_counting()) <= alpha ++ && ss->staticEval + (283 + 170 * lmrDepth) * (1 + pos.check_counting()) <= alpha + && (*contHist[0])[history_slot(movedPiece)][to_sq(move)] + + (*contHist[1])[history_slot(movedPiece)][to_sq(move)] + + (*contHist[3])[history_slot(movedPiece)][to_sq(move)] - + (*contHist[5])[history_slot(movedPiece)][to_sq(move)] / 2 < 28388) ++ + (*contHist[5])[history_slot(movedPiece)][to_sq(move)] / 2 < 27376) continue; // Prune moves with negative SEE (~20 Elo) - if (!pos.see_ge(move, Value(-(29 - std::min(lmrDepth, 17) + 10 * !!pos.capture_the_flag_piece()) * lmrDepth * lmrDepth))) - if (!pos.see_ge(move, Value(-(29 - std::min(lmrDepth, 18)) * lmrDepth * lmrDepth))) ++ if (!pos.see_ge(move, Value(-(29 - std::min(lmrDepth, 18) + 10 * !!pos.capture_the_flag_piece()) * lmrDepth * lmrDepth))) continue; } - else + else if (!pos.must_capture()) { // Capture history based pruning when the move doesn't give check if ( !givesCheck @@@ -1123,18 -1049,8 +1127,8 @@@ && captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] < 0) continue; - // Futility pruning for captures - if ( !givesCheck - && lmrDepth < 6 - && !(PvNode && abs(bestValue) < 2) - && PieceValue[MG][type_of(movedPiece)] >= PieceValue[MG][type_of(pos.piece_on(to_sq(move)))] - && !ss->inCheck - && ss->staticEval + 267 + 391 * lmrDepth - + PieceValue[MG][type_of(pos.piece_on(to_sq(move)))] <= alpha) - continue; - // See based pruning - if (!pos.see_ge(move, Value(-202 - 120 * pos.captures_to_hand()) * depth)) // (~25 Elo) - if (!pos.see_ge(move, Value(-221) * depth)) // (~25 Elo) ++ if (!pos.see_ge(move, Value(-221 - 120 * pos.captures_to_hand()) * depth)) // (~25 Elo) continue; } } @@@ -1205,16 -1114,12 +1192,18 @@@ && pos.non_pawn_material() <= 2 * RookValueMg) extension = 1; - // Castling extension - if (type_of(move) == CASTLING) - extension = 1; + // Late irreversible move extension + if ( move == ttMove + && pos.rule50_count() > 80 + && (captureOrPromotion || type_of(movedPiece) == PAWN)) + extension = 2; + // Losing chess capture extension + else if ( pos.must_capture() + && pos.capture(move) + && MoveList(pos).size() == 1) + extension = 1; + // Add extension to new depth newDepth += extension; @@@ -1247,8 -1144,7 +1228,8 @@@ || moveCountPruning || ss->staticEval + PieceValue[EG][pos.captured_piece()] <= alpha || cutNode - || thisThread->ttHitAverage < 415 * TtHitAverageResolution * TtHitAverageWindow / 1024) - || thisThread->ttHitAverage < 427 * TtHitAverageResolution * TtHitAverageWindow / 1024)) ++ || thisThread->ttHitAverage < 427 * TtHitAverageResolution * TtHitAverageWindow / 1024) + && !(pos.must_capture() && MoveList(pos).size())) { Depth r = reduction(improving, depth, moveCount); @@@ -1297,23 -1186,23 +1271,23 @@@ // hence break make_move(). (~2 Elo) else if ( type_of(move) == NORMAL && !pos.see_ge(reverse_move(move))) - r -= 2 + ttPv - (type_of(movedPiece) == PAWN); + r -= 2 + ss->ttPv - (type_of(movedPiece) == PAWN); ss->statScore = thisThread->mainHistory[us][from_to(move)] - + (*contHist[0])[movedPiece][to_sq(move)] - + (*contHist[1])[movedPiece][to_sq(move)] - + (*contHist[3])[movedPiece][to_sq(move)] + + (*contHist[0])[history_slot(movedPiece)][to_sq(move)] + + (*contHist[1])[history_slot(movedPiece)][to_sq(move)] + + (*contHist[3])[history_slot(movedPiece)][to_sq(move)] - - 4826; + - 5287; // Decrease/increase reduction by comparing opponent's stat score (~10 Elo) - if (ss->statScore >= -100 && (ss-1)->statScore < -112) + if (ss->statScore >= -106 && (ss-1)->statScore < -104) r--; - else if ((ss-1)->statScore >= -125 && ss->statScore < -138) + else if ((ss-1)->statScore >= -119 && ss->statScore < -140) r++; // Decrease/increase reduction for moves with a good/bad history (~30 Elo) - r -= ss->statScore / (14615 - 4434 * pos.captures_to_hand()); - r -= ss->statScore / 14884; ++ r -= ss->statScore / (14884 - 4434 * pos.captures_to_hand()); } else { @@@ -1657,9 -1555,16 +1646,16 @@@ ss->currentMove = move; ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] [captureOrPromotion] - [pos.moved_piece(move)] + [history_slot(pos.moved_piece(move))] [to_sq(move)]; + // CounterMove based pruning + if ( !captureOrPromotion + && moveCount - && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < CounterMovePruneThreshold - && (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < CounterMovePruneThreshold) ++ && (*contHist[0])[history_slot(pos.moved_piece(move))][to_sq(move)] < CounterMovePruneThreshold ++ && (*contHist[1])[history_slot(pos.moved_piece(move))][to_sq(move)] < CounterMovePruneThreshold) + continue; + // Make and search the move pos.do_move(move, st, givesCheck); value = -qsearch(pos, ss+1, -beta, -alpha, depth - 1); diff --cc src/thread.cpp index fb8285e,2fbf745..0127815 --- a/src/thread.cpp +++ b/src/thread.cpp @@@ -216,11 -201,10 +201,10 @@@ void ThreadPool::start_thinking(Positio th->nodes = th->tbHits = th->nmpMinPly = th->bestMoveChanges = 0; th->rootDepth = th->completedDepth = 0; th->rootMoves = rootMoves; - th->rootPos.set(pos.variant(), pos.fen(), pos.is_chess960(), &setupStates->back(), th); - th->rootPos.set(pos.fen(), pos.is_chess960(), &th->rootState, th); ++ th->rootPos.set(pos.variant(), pos.fen(), pos.is_chess960(), &th->rootState, th); + th->rootState = setupStates->back(); } - setupStates->back() = tmp; - main()->start_searching(); } diff --cc src/tt.cpp index 4f97fdd,dea7c71..658fd0a --- a/src/tt.cpp +++ b/src/tt.cpp @@@ -37,20 -35,21 +35,21 @@@ void TTEntry::save(Key k, Value v, boo // Preserve any existing move for the same position if (m || (uint16_t)k != key16) - move16 = (uint16_t)m; + move32 = (uint32_t)m; - // Overwrite less valuable entries - if ((uint16_t)k != key16 - || d - DEPTH_OFFSET > depth8 - 4 - || b == BOUND_EXACT) + // Overwrite less valuable entries (cheapest checks first) + if (b == BOUND_EXACT + || (uint16_t)k != key16 + || d - DEPTH_OFFSET > depth8 - 4) { - assert(d >= DEPTH_OFFSET); + assert(d > DEPTH_OFFSET); + assert(d < 256 + DEPTH_OFFSET); key16 = (uint16_t)k; + depth8 = (uint8_t)(d - DEPTH_OFFSET); + genBound8 = (uint8_t)(TT.generation8 | uint8_t(pv) << 2 | b); value16 = (int16_t)v; eval16 = (int16_t)ev; - genBound8 = (uint8_t)(TT.generation8 | uint8_t(pv) << 2 | b); - depth8 = (uint8_t)(d - DEPTH_OFFSET); } } diff --cc src/tt.h index a745f7f,6aa066c..b9fca59 --- a/src/tt.h +++ b/src/tt.h @@@ -24,16 -22,16 +22,16 @@@ #include "misc.h" #include "types.h" -/// TTEntry struct is the 10 bytes transposition table entry, defined as below: +/// TTEntry struct is the 12 bytes transposition table entry, defined as below: /// - /// move 32 bit /// key 16 bit - /// value 16 bit - /// eval value 16 bit + /// depth 8 bit /// generation 5 bit /// pv node 1 bit /// bound type 2 bit - /// depth 8 bit -/// move 16 bit ++/// move 32 bit (official SF: 16 bit) + /// value 16 bit + /// eval value 16 bit struct TTEntry { @@@ -48,12 -46,12 +46,12 @@@ private: friend class TranspositionTable; - uint32_t move32; uint16_t key16; + uint8_t depth8; + uint8_t genBound8; - uint16_t move16; ++ uint32_t move32; int16_t value16; int16_t eval16; - uint8_t genBound8; - uint8_t depth8; }; @@@ -69,13 -67,13 +67,13 @@@ class TranspositionTable struct Cluster { TTEntry entry[ClusterSize]; - char padding[2]; // Pad to 32 bytes + char padding[4]; // Pad to 64 bytes }; - static_assert(sizeof(Cluster) == 32, "Unexpected Cluster size"); + static_assert(sizeof(Cluster) == 64, "Unexpected Cluster size"); public: - ~TranspositionTable() { aligned_ttmem_free(mem); } + ~TranspositionTable() { aligned_large_pages_free(table); } void new_search() { generation8 += 8; } // Lower 3 bits are used by PV flag and Bound TTEntry* probe(const Key key, bool& found) const; int hashfull() const; diff --cc src/types.h index 55b92c6,5873c69..680f444 --- a/src/types.h +++ b/src/types.h @@@ -494,17 -229,10 +493,18 @@@ enum Square : int SQ_A6, SQ_B6, SQ_C6, SQ_D6, SQ_E6, SQ_F6, SQ_G6, SQ_H6, SQ_A7, SQ_B7, SQ_C7, SQ_D7, SQ_E7, SQ_F7, SQ_G7, SQ_H7, SQ_A8, SQ_B8, SQ_C8, SQ_D8, SQ_E8, SQ_F8, SQ_G8, SQ_H8, +#endif SQ_NONE, + SQUARE_ZERO = 0, - SQUARE_NB = 64 +#ifdef LARGEBOARDS + SQUARE_NB = 120, + SQUARE_BIT_MASK = 127, +#else + SQUARE_NB = 64, + SQUARE_BIT_MASK = 63, +#endif + SQ_MAX = SQUARE_NB - 1 }; enum Direction : int { @@@ -534,15 -252,24 +534,30 @@@ enum File : int }; enum Rank : int { - RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, RANK_NB +#ifdef LARGEBOARDS + RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, RANK_9, RANK_10, +#else + RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, +#endif + RANK_NB, + RANK_MAX = RANK_NB - 1 }; + // Keep track of what a move changes on the board (used by NNUE) + struct DirtyPiece { + + // Number of changed pieces + int dirty_num; + + // Max 3 pieces can change in one move. A promotion with capture moves + // both the pawn and the captured piece to SQ_NONE and the piece promoted + // to from SQ_NONE to the capture square. + Piece piece[3]; + + // From and to squares, which may be SQ_NONE + Square from[3]; + Square to[3]; + }; /// Score enum stores a middlegame and an endgame value in a single integer (enum). /// The least significant 16 bits are used to store the middlegame value and the @@@ -567,20 -294,11 +582,20 @@@ inline Value mg_value(Score s) return Value(mg.s); } +#define ENABLE_BIT_OPERATORS_ON(T) \ +inline T operator~ (T d) { return (T)~(int)d; } \ +inline T operator| (T d1, T d2) { return (T)((int)d1 | (int)d2); } \ +inline T operator& (T d1, T d2) { return (T)((int)d1 & (int)d2); } \ +inline T operator^ (T d1, T d2) { return (T)((int)d1 ^ (int)d2); } \ +inline T& operator|= (T& d1, T d2) { return (T&)((int&)d1 |= (int)d2); } \ +inline T& operator&= (T& d1, T d2) { return (T&)((int&)d1 &= (int)d2); } \ +inline T& operator^= (T& d1, T d2) { return (T&)((int&)d1 ^= (int)d2); } + #define ENABLE_BASE_OPERATORS_ON(T) \ - constexpr T operator+(T d1, int d2) { return T(int(d1) + d2); } \ - constexpr T operator-(T d1, int d2) { return T(int(d1) - d2); } \ + constexpr T operator+(T d1, int d2) { return T(int(d1) + d2); } \ + constexpr T operator-(T d1, int d2) { return T(int(d1) - d2); } \ constexpr T operator-(T d) { return T(-int(d)); } \ - inline T& operator+=(T& d1, int d2) { return d1 = d1 + d2; } \ + inline T& operator+=(T& d1, int d2) { return d1 = d1 + d2; } \ inline T& operator-=(T& d1, int d2) { return d1 = d1 - d2; } #define ENABLE_INCR_OPERATORS_ON(T) \ diff --cc src/uci.cpp index 2232aa2,b63e55a..1439222 --- a/src/uci.cpp +++ b/src/uci.cpp @@@ -77,6 -76,20 +75,20 @@@ namespace } } + // trace_eval() prints the evaluation for the current position, consistent with the UCI + // options set so far. + + void trace_eval(Position& pos) { + + StateListPtr states(new std::deque(1)); + Position p; - p.set(pos.fen(), Options["UCI_Chess960"], &states->back(), Threads.main()); ++ p.set(pos.variant(), pos.fen(), Options["UCI_Chess960"], &states->back(), Threads.main()); + + Eval::NNUE::verify(); + + sync_cout << "\n" << Eval::trace(p) << sync_endl; + } + // setoption() is called when engine receives the "setoption" UCI command. The // function updates the UCI option ("name") to the given value ("value"). @@@ -321,10 -273,8 +333,10 @@@ void UCI::loop(int argc, char* argv[]) else if (token == "flip") pos.flip(); else if (token == "bench") bench(pos, is, states); else if (token == "d") sync_cout << pos << sync_endl; - else if (token == "eval") sync_cout << Eval::trace(pos) << sync_endl; + else if (token == "eval") trace_eval(pos); else if (token == "compiler") sync_cout << compiler_info() << sync_endl; + else if (token == "load") { load(is); argc = 1; } // continue reading stdin + else if (token == "check") check(is); else sync_cout << "Unknown command: " << cmd << sync_endl; diff --cc src/ucioption.cpp index 597c14f,bb0b831..53da357 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@@ -22,10 -20,9 +20,11 @@@ #include #include #include +#include + #include "evaluate.h" #include "misc.h" +#include "piece.h" #include "search.h" #include "thread.h" #include "tt.h" @@@ -56,74 -41,9 +55,81 @@@ void on_hash_size(const Option& o) { TT void on_logger(const Option& o) { start_logger(o); } void on_threads(const Option& o) { Threads.set(size_t(o)); } void on_tb_path(const Option& o) { Tablebases::init(o); } ++ + void on_use_NNUE(const Option& ) { Eval::NNUE::init(); } + void on_eval_file(const Option& ) { Eval::NNUE::init(); } + +void on_variant_path(const Option& o) { variants.parse(o); Options["UCI_Variant"].set_combo(variants.get_keys()); } +void on_variant_change(const Option &o) { ++ // Re-initialize NNUE ++ Eval::NNUE::init(); ++ + const Variant* v = variants.find(o)->second; + PSQT::init(v); + // Do not send setup command for known variants + if (standard_variants.find(o) != standard_variants.end()) + return; + int pocketsize = v->pieceDrops ? (v->pocketSize ? v->pocketSize : v->pieceTypes.size()) : 0; + if (Options["Protocol"] == "xboard") + { + // Send setup command + sync_cout << "setup (" << v->pieceToCharTable << ") " + << v->maxFile + 1 << "x" << v->maxRank + 1 + << "+" << pocketsize << "_" << v->variantTemplate + << " " << v->startFen + << sync_endl; + // Send piece command with Betza notation + // https://www.gnu.org/software/xboard/Betza.html + for (PieceType pt : v->pieceTypes) + { + string suffix = pt == PAWN && v->doubleStep ? "ifmnD" + : pt == KING && v->cambodianMoves ? "ismN" + : pt == FERS && v->cambodianMoves ? "ifD" + : ""; + // Janggi palace moves + if (v->diagonalLines) + { + PieceType pt2 = pt == KING ? v->kingType : pt; + if (pt2 == WAZIR) + suffix += "F"; + else if (pt2 == SOLDIER) + suffix += "fF"; + else if (pt2 == ROOK) + suffix += "B"; + else if (pt2 == JANGGI_CANNON) + suffix += "pB"; + } + // Castling + if (pt == KING && v->castling) + suffix += "O" + std::to_string((v->castlingKingsideFile - v->castlingQueensideFile) / 2); + // Drop region + if (v->pieceDrops) + { + if (pt == PAWN && !v->firstRankPawnDrops) + suffix += "j"; + else if (pt == SHOGI_PAWN && !v->shogiDoubledPawn) + suffix += "f"; + else if (pt == BISHOP && v->dropOppositeColoredBishop) + suffix += "s"; + suffix += "@" + std::to_string(pt == PAWN && !v->promotionZonePawnDrops ? v->promotionRank : v->maxRank + 1); + } + sync_cout << "piece " << v->pieceToChar[pt] << "& " << pieceMap.find(pt == KING ? v->kingType : pt)->second->betza << suffix << sync_endl; + PieceType promType = v->promotedPieceType[pt]; + if (promType) + sync_cout << "piece +" << v->pieceToChar[pt] << "& " << pieceMap.find(promType)->second->betza << sync_endl; + } + } + else + sync_cout << "info string variant " << (std::string)o + << " files " << v->maxFile + 1 + << " ranks " << v->maxRank + 1 + << " pocket " << pocketsize + << " template " << v->variantTemplate + << " startpos " << v->startFen + << sync_endl; +} + + /// Our case insensitive less() function as required by UCI protocol bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const { @@@ -161,8 -79,8 +167,14 @@@ void init(OptionsMap& o) o["SyzygyProbeDepth"] << Option(1, 1, 100); o["Syzygy50MoveRule"] << Option(true); o["SyzygyProbeLimit"] << Option(7, 0, 7); ++#ifdef USE_NNUE + o["Use NNUE"] << Option(true, on_use_NNUE); ++#else ++ o["Use NNUE"] << Option(false, on_use_NNUE); ++#endif + o["EvalFile"] << Option(EvalFileDefaultName, on_eval_file); + o["TsumeMode"] << Option(false); + o["VariantPath"] << Option("", on_variant_path); } diff --cc tests/js/README.md index 2d370d5,0000000..6a7ed3e mode 100644,000000..100644 --- a/tests/js/README.md +++ b/tests/js/README.md @@@ -1,272 -1,0 +1,276 @@@ +

ffish.js

+ +

+Package +Downloads +Version +

+ +

+Package-ES6 +Downloads-ES6 +Version-ES6 +

+ + +The package **ffish.js** is a high performance WebAssembly chess variant library based on [_Fairy-Stockfish_](https://github.com/ianfab/Fairy-Stockfish). + +It is available as a [standard module](https://www.npmjs.com/package/ffish), as an [ES6 module](https://www.npmjs.com/package/ffish-es6) and aims to have a syntax similar to [python-chess](https://python-chess.readthedocs.io/en/latest/index.html). + +## Install instructions + +### Standard module + +```bash +npm install ffish +``` + +### ES6 module +```bash +npm install ffish-es6 +``` + +## Examples + +Load the API in JavaScript: + +### Standard module + +```javascript +const ffish = require('ffish'); +``` + +### ES6 module + +```javascript +import Module from 'ffish-es6'; +let ffish = null; + +new Module().then(loadedModule => { + ffish = loadedModule; + console.log(`initialized ${ffish} ${loadedModule}`); + } +}); +``` + +### Available variants + +Show all available variants supported by _Fairy-Stockfish_ and **ffish.js**. + +```javascript +ffish.variants() +``` +``` +>> 3check 5check ai-wok almost amazon antichess armageddon asean ataxx breakthrough bughouse cambodian\ +capablanca capahouse caparandom centaur chancellor chess chessgi chigorin clobber clobber10 codrus courier\ +crazyhouse dobutsu embassy euroshogi extinction fairy fischerandom gardner giveaway gorogoro gothic grand\ +hoppelpoppel horde janggi janggicasual janggimodern janggitraditional janus jesonmor judkins karouk kinglet\ +kingofthehill knightmate koedem kyotoshogi loop losalamos losers makpong makruk manchu micro mini minishogi\ +minixiangqi modern newzealand nocastle normal placement pocketknight racingkings seirawan shako shatar\ +shatranj shogi shouse sittuyin suicide supply threekings xiangqi +``` + +## Custom variants + +Fairy-Stockfish also allows defining custom variants by loading a configuration file. +See e.g. the confiugration for connect4, tictactoe or janggihouse in [variants.ini](https://github.com/ianfab/Fairy-Stockfish/blob/master/src/variants.ini). +```javascript +fs = require('fs'); +let configFilePath = './variants.ini'; + fs.readFile(configFilePath, 'utf8', function (err,data) { + if (err) { + return console.log(err); + } + ffish.loadVariantConfig(data) + let board = new ffish.Board("tictactoe"); + board.delete(); + }); +``` + +### Board object + +Create a new variant board from its default starting position. +The event `onRuntimeInitialized` ensures that the wasm file was properly loaded. + +```javascript +ffish['onRuntimeInitialized'] = () => { + let board = new ffish.Board("chess"); +} +``` + +Set a custom fen position: +```javascript +board.setFen("rnb1kbnr/ppp1pppp/8/3q4/8/8/PPPP1PPP/RNBQKBNR w KQkq - 0 3"); +``` + +Alternatively, you can initialize a board with a custom FEN directly: +```javascript +let board2 = new ffish.Board("crazyhouse", "rnb1kb1r/ppp2ppp/4pn2/8/3P4/2N2Q2/PPP2PPP/R1B1KB1R/QPnp b KQkq - 0 6"); +``` + +Add a new move: +```javascript +board.push("g2g4"); +``` + +Generate all legal moves in UCI and SAN notation: +```javascript +let legalMoves = board.legalMoves().split(" "); +let legalMovesSan = board.legalMovesSan().split(" "); + +for (var i = 0; i < legalMovesSan.length; i++) { + console.log(`${i}: ${legalMoves[i]}, ${legalMoves}) +} +``` + +Unfortunately, it is impossible for Emscripten to call the destructors on C++ object. +Therefore, you need to call `.delete()` to free the heap memory of an object. +```javascript +board.delete(); +``` + +## PGN parsing + +Read a string from a file and parse it as a single PGN game. + +```javascript +fs = require('fs'); +let pgnFilePath = "data/pgn/kasparov-deep-blue-1997.pgn" + +fs.readFile(pgnFilePath, 'utf8', function (err,data) { + if (err) { + return console.log(err); + } + game = ffish.readGamePGN(data); + console.log(game.headerKeys()); + console.log(game.headers("White")); + console.log(game.mainlineMoves()) + + let board = new ffish.Board(game.headers("Variant").toLowerCase()); + board.pushMoves(game.mainlineMoves()); + + let finalFen = board.fen(); + board.delete(); + game.delete(); +} +``` + +## Remaining features + +For an example of each available function see [test.js](https://github.com/ianfab/Fairy-Stockfish/blob/master/tests/js/test.js). + +## Build instuctions + +It is built using emscripten/Embind from C++ source code. + +* https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html + + +If you want to disable variants with a board greater than 8x8, + you can remove the flags `-s TOTAL_MEMORY=67108864 -s + ALLOW_MEMORY_GROWTH=1 -s WASM_MEM_MAX=2147483648 + -DLARGEBOARDS -DPRECOMPUTED_MAGICS`. + +The pre-compiled wasm binary is built with `-DLARGEBOARDS`. + +It is recommended to set `-s ASSERTIONS=1 -s SAFE_HEAP=1` before running tests. + + +### Compile as standard module + +```bash +cd Fairy-Stockfish/src +``` +```bash - emcc -O3 --bind -DLARGEBOARDS -DPRECOMPUTED_MAGICS -DNO_THREADS \ ++emcc -O3 --bind -DLARGEBOARDS -DPRECOMPUTED_MAGICS -DNNUE_EMBEDDING_OFF -DNO_THREADS \ + -s TOTAL_MEMORY=67108864 -s ALLOW_MEMORY_GROWTH=1 -s WASM_MEM_MAX=2147483648 \ + -s ASSERTIONS=0 -s SAFE_HEAP=0 \ + -DNO_THREADS -DLARGEBOARDS -DPRECOMPUTED_MAGICS \ +ffishjs.cpp \ +benchmark.cpp \ +bitbase.cpp \ +bitboard.cpp \ +endgame.cpp \ +evaluate.cpp \ +material.cpp \ +misc.cpp \ +movegen.cpp \ +movepick.cpp \ +parser.cpp \ +partner.cpp \ +pawns.cpp \ +piece.cpp \ +position.cpp \ +psqt.cpp \ +search.cpp \ +thread.cpp \ +timeman.cpp \ +tt.cpp \ +uci.cpp \ - syzygy/tbprobe.cpp \ +ucioption.cpp \ +variant.cpp \ +xboard.cpp \ ++nnue/*.cpp \ ++nnue/features/*.cpp \ ++syzygy/*.cpp \ +-o ../tests/js/ffish.js +``` + +### Compile as ES6/ES2015 module + +Some environments such as [vue-js](https://vuejs.org/) may require the library to be exported + as a ES6/ES2015 module. + +```bash +cd Fairy-Stockfish/src +``` +```bash - emcc -O3 --bind -DLARGEBOARDS -DPRECOMPUTED_MAGICS -DNO_THREADS \ ++emcc -O3 --bind -DLARGEBOARDS -DPRECOMPUTED_MAGICS -DNNUE_EMBEDDING_OFF -DNO_THREADS \ + -s TOTAL_MEMORY=67108864 -s ALLOW_MEMORY_GROWTH=1 -s WASM_MEM_MAX=2147483648 \ + -s ASSERTIONS=0 -s SAFE_HEAP=0 \ + -s ENVIRONMENT='web,worker' -s EXPORT_ES6=1 -s MODULARIZE=1 -s USE_ES6_IMPORT_META=0 \ +ffishjs.cpp \ +benchmark.cpp \ +bitbase.cpp \ +bitboard.cpp \ +endgame.cpp \ +evaluate.cpp \ +material.cpp \ +misc.cpp \ +movegen.cpp \ +movepick.cpp \ +parser.cpp \ +partner.cpp \ +pawns.cpp \ +piece.cpp \ +position.cpp \ +psqt.cpp \ +search.cpp \ +thread.cpp \ +timeman.cpp \ +tt.cpp \ +uci.cpp \ - syzygy/tbprobe.cpp \ +ucioption.cpp \ +variant.cpp \ +xboard.cpp \ ++nnue/*.cpp \ ++nnue/features/*.cpp \ ++syzygy/*.cpp \ +-o ../tests/js/ffish.js +``` + +Reference: [emscripten/#10114](https://github.com/emscripten-core/emscripten/issues/10114) + +## Instructions to run the tests +```bash +npm install +npm test +``` + +## Instructions to run the example server +```bash +npm install +``` +```bash +node index.js +```