- 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
- 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)
#
# 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
#
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:
--- /dev/null
++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.
--- /dev/null
+# -*- 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]
+ )
### 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))
# 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
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
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)
@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
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;
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;
}
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 <MovementType MT>
+#ifdef PRECOMPUTED_MAGICS
+ void init_magics(Bitboard table[], Magic magics[], std::vector<Direction> directions, Bitboard magicsInit[]);
+#else
+ void init_magics(Bitboard table[], Magic magics[], std::vector<Direction> directions);
+#endif
+
+ template <MovementType MT>
+ Bitboard sliding_attack(std::vector<Direction> 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<Direction> directions, Square s) {
+ Bitboard b = 0;
+ for (Direction d : directions)
+ b |= lame_leaper_path(d, s);
+ return b;
+ }
+
+ Bitboard lame_leaper_attack(std::vector<Direction> 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);
}
template<> inline int distance<Rank>(Square x, Square y) { return std::abs(rank_of(x) - rank_of(y)); }
template<> inline int distance<Square>(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<RiderType R>
+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
#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<string> dirs = { "<internal>" , "" , CommandLine::binaryDirectory , stringify(DEFAULT_NNUE_DIRECTORY) };
+ #else
+ vector<string> dirs = { "<internal>" , "" , CommandLine::binaryDirectory };
+ #endif
+
+ for (string directory : dirs)
+ if (eval_file_loaded != eval_file)
+ {
+ if (directory != "<internal>")
+ {
+ ifstream stream(directory + eval_file, ios::binary);
+ if (load_eval(eval_file, stream))
+ eval_file_loaded = eval_file;
+ }
+
+ if (directory == "<internal>" && eval_file == EvalFileDefaultName)
+ {
+ // C++ way to prepare a buffer for a memory stream
+ class MemoryBuffer : public basic_streambuf<char> {
+ public: MemoryBuffer(char* p, size_t n) { setg(p, p, p + n); setp(p, p + n); }
+ };
+
+ MemoryBuffer buffer(const_cast<char*>(reinterpret_cast<const char*>(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 {
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) };
// 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<Down>(pos.pieces(Us)))
+ | shift<Down>(pos.pieces(Them, SHOGI_PAWN, SOLDIER))
+ | shift<EAST>(pos.promoted_soldiers(Them))
+ | shift<WEST>(pos.promoted_soldiers(Them)));
// Initialize attackedBy[] for king and pawns
- attackedBy[Us][KING] = attacks_bb<KING>(ksq);
+ attackedBy[Us][KING] = pos.count<KING>(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<Up>(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<KING>(s) | s;
+ if (!pos.count<KING>(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<KING>(s) | s;
+ }
kingAttackersCount[Them] = popcount(kingRing[Us] & pe->pawn_attacks(Them));
kingAttacksCount[Them] = kingAttackersWeight[Them] = 0;
{
// 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)
template<Tracing T>
Value Evaluation<T>::winnable(Score score) const {
- int outflanking = distance<File>(pos.square<KING>(WHITE), pos.square<KING>(BLACK))
- - distance<Rank>(pos.square<KING>(WHITE), pos.square<KING>(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<KING>(WHITE) || !pos.count<KING>(BLACK) ? 0
+ : distance<File>(pos.square<KING>(WHITE), pos.square<KING>(BLACK))
+ - distance<Rank>(pos.square<KING>(WHITE), pos.square<KING>(BLACK));
-- bool pawnsOnBothFlanks = (pos.pieces(PAWN) & QueenSide)
++ pawnsOnBothFlanks = (pos.pieces(PAWN) & QueenSide)
&& (pos.pieces(PAWN) & KingSide);
bool almostUnwinnable = outflanking < 0
sf = 37 + 3 * (pos.count<QUEEN>(WHITE) == 1 ? pos.count<BISHOP>(BLACK) + pos.count<KNIGHT>(BLACK)
: pos.count<BISHOP>(WHITE) + pos.count<KNIGHT>(WHITE));
else
- sf = std::min(sf, 36 + 7 * (pos.count<PAWN>(strongSide) + pos.count<SOLDIER>(strongSide)));
- sf = std::min(sf, 36 + 7 * pos.count<PAWN>(strongSide)) - 4 * !pawnsOnBothFlanks;
++ sf = std::min(sf, 36 + 7 * (pos.count<PAWN>(strongSide) + pos.count<SOLDIER>(strongSide))) - 4 * !pawnsOnBothFlanks;
+
+ sf -= 4 * !pawnsOnBothFlanks;
}
// Interpolate between the middlegame and (scaled by 'sf') endgame score
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;
}
/// evaluation of the position from the point of view of the side to move.
Value Eval::evaluate(const Position& pos) {
- return Evaluation<NO_TRACE>(pos).value();
- }
+ Value v;
+
+ if (!Eval::useNNUE)
+ v = Evaluation<NO_TRACE>(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<PAWN>();
+ 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<NO_TRACE>(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
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
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();
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<PAWN>() * 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.
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();
}
#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 { };
default:
static_assert(true, "Unsupported type in generate_all()");
}
+ target &= pos.board_bb();
moveList = generate_pawn_moves<Us, Type>(pos, moveList, target);
- moveList = generate_moves<Us, KNIGHT, Checks>(pos, moveList, target);
- moveList = generate_moves<Us, BISHOP, Checks>(pos, moveList, target);
- moveList = generate_moves<Us, ROOK, Checks>(pos, moveList, target);
- moveList = generate_moves<Us, QUEEN, Checks>(pos, moveList, target);
-
- if (Type != QUIET_CHECKS && Type != EVASIONS)
+ for (PieceType pt : pos.piece_types())
+ if (pt != PAWN && pt != KING)
+ moveList = generate_moves<Us, Checks>(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<Us, Checks>(pos, moveList, pt, target & ~pos.pieces(~Us));
+
+ if (Type != QUIET_CHECKS && Type != EVASIONS && pos.count<KING>(Us))
{
Square ksq = pos.square<KING>(Us);
- Bitboard b = attacks_bb<KING>(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<NORMAL>(pos, moveList, Us, ksq, pop_lsb(&b));
+
+ // Passing move by king
+ if (pos.pass())
+ *moveList++ = make<SPECIAL>(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<CASTLING>(ksq, pos.castling_rook_square(cr));
+ moveList = make_move_and_gating<CASTLING>(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<KING>(Us) && pos.pieces(Us))
+ *moveList++ = make<SPECIAL>(lsb(pos.pieces(Us)), lsb(pos.pieces(Us)));
+
+ // Castling with non-king piece
+ if (!pos.count<KING>(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<CASTLING>(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<KING>(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<SPECIAL>(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<SPECIAL>(pos, moveList, Us, from, to);
+ }
}
return moveList;
/// 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<int16_t, 10692, COLOR_NB, int(SQUARE_NB) * int(SQUARE_NB)> ButterflyHistory;
+typedef Stats<int16_t, 10692, COLOR_NB, int(SQUARE_NB + 1) * int(1 << SQUARE_BITS)> 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<int16_t, 10692, MAX_LPH, int(SQUARE_NB) * int(SQUARE_NB)> LowPlyHistory;
+typedef Stats<int16_t, 10692, MAX_LPH, int(SQUARE_NB + 1) * int(1 << SQUARE_BITS)> LowPlyHistory;
/// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous
/// move, see www.chessprogramming.org/Countermove_Heuristic
#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)
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<PAWN>(Us) > popcount(pos.board_bb()) / 4)
+ v = popcount(support | phalanx) * 50 / (opposed ? 2 : 1);
score += make_score(v, v * (r - 2) / 4);
}
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);
++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);
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];
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)
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];
// 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<KING>(them)) & pieces(us) : 0;
+ st->checkersBB = givesCheck ? attackers_to(square<KING>(them), us) & pieces(us) : Bitboard(0);
sideToMove = ~sideToMove;
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);
}
#include <deque>
#include <memory> // For std::unique_ptr
#include <string>
+#include <functional>
#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
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;
};
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
// 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];
}
}
}
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
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);
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)
&& eval <= alpha - RazorMargin)
return qsearch<NT>(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<CAPTURES>(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<ALL_PIECES>(~us) != pos.count<PAWN>(~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];
}
}
- 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
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;
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
&& 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;
}
}
&& 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<CAPTURES>(pos).size() == 1)
+ extension = 1;
+
// Add extension to new depth
newDepth += extension;
|| 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<CAPTURES>(pos).size()))
{
Depth r = reduction(improving, depth, moveCount);
// 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
{
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<NT>(pos, ss+1, -beta, -alpha, depth - 1);
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();
}
// 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);
}
}
#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 {
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;
};
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;
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 {
};
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
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) \
}
}
+ // 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<StateInfo>(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").
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;
#include <cassert>
#include <ostream>
#include <sstream>
+#include <iostream>
+ #include "evaluate.h"
#include "misc.h"
+#include "piece.h"
#include "search.h"
#include "thread.h"
#include "tt.h"
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<false>(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 {
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("<empty>", on_variant_path);
}
--- /dev/null
+<h2 align="center">ffish.js</h2>
+
+<p align="center">
+<a href="https://img.shields.io/badge/-ffish.js-green"><img src="https://img.shields.io/badge/-ffish.js-green" alt="Package"></a>
+<a href="https://npmcharts.com/compare/ffish?minimal=true"><img src="https://img.shields.io/npm/dm/ffish.svg?sanitize=true" alt="Downloads"></a>
+<a href="https://www.npmjs.com/package/ffish"><img src="https://img.shields.io/npm/v/ffish.svg?sanitize=true" alt="Version"></a>
+</p>
+
+<p align="center">
+<a href="https://img.shields.io/badge/-ffish--es6.js-green"><img src="https://img.shields.io/badge/-ffish--es6.js-green" alt="Package-ES6"></a>
+<a href="https://npmcharts.com/compare/ffish-es6?minimal=true"><img src="https://img.shields.io/npm/dm/ffish-es6.svg?sanitize=true" alt="Downloads-ES6"></a>
+<a href="https://www.npmjs.com/package/ffish-es6"><img src="https://img.shields.io/npm/v/ffish-es6.svg?sanitize=true" alt="Version-ES6"></a>
+</p>
+
+
+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
+```