diff --git a/Fairy-Stockfish-master/.gitattributes b/Fairy-Stockfish-master/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..b0ff81123acece91bf17d3254b245b0fad95ea29 --- /dev/null +++ b/Fairy-Stockfish-master/.gitattributes @@ -0,0 +1 @@ +README.md merge=ours diff --git a/Fairy-Stockfish-master/.github/FUNDING.yml b/Fairy-Stockfish-master/.github/FUNDING.yml new file mode 100644 index 0000000000000000000000000000000000000000..6c865b035be6d4bc9fd684b27d749a53b453a26c --- /dev/null +++ b/Fairy-Stockfish-master/.github/FUNDING.yml @@ -0,0 +1,2 @@ +# These are supported funding model platforms +github: ianfab diff --git a/Fairy-Stockfish-master/.github/dependabot.yml b/Fairy-Stockfish-master/.github/dependabot.yml new file mode 100644 index 0000000000000000000000000000000000000000..be006de9a1ae3b9628e796c74277cd6d61e0a34a --- /dev/null +++ b/Fairy-Stockfish-master/.github/dependabot.yml @@ -0,0 +1,13 @@ +# Keep GitHub Actions up to date with GitHub's Dependabot... +# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot +# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + groups: + github-actions: + patterns: + - "*" # Group all Actions updates into a single larger pull request + schedule: + interval: weekly diff --git a/Fairy-Stockfish-master/.github/workflows/fairy.yml b/Fairy-Stockfish-master/.github/workflows/fairy.yml new file mode 100644 index 0000000000000000000000000000000000000000..3dfb53ca6b894818ce2954315645520b92e7f728 --- /dev/null +++ b/Fairy-Stockfish-master/.github/workflows/fairy.yml @@ -0,0 +1,93 @@ +name: fairy +on: + push: + branches: + - master + pull_request: + branches: + - master +jobs: + fairy: + name: ${{ matrix.config.name }} + runs-on: ${{ matrix.config.os }} + env: + COMPILER: ${{ matrix.config.compiler }} + COMP: ${{ matrix.config.comp }} + CXXFLAGS: "-Werror" + strategy: + matrix: + config: + - { + name: "Ubuntu 24.04 GCC", + os: ubuntu-24.04, + compiler: g++, + comp: gcc, + run_expensive_tests: true + } + - { + name: "Ubuntu 24.04 Clang", + os: ubuntu-24.04, + compiler: clang++, + comp: clang, + run_expensive_tests: false + } + + defaults: + run: + working-directory: src + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Download required packages + run: | + sudo apt update + sudo apt install expect valgrind g++-multilib + + - name: Download the used network from the fishtest framework + run: | + make net + + - name: Test NNUE + run: | + make clean + make -j2 ARCH=x86-64 nnue=yes debug=yes build + ./stockfish bench + + - name: Test NNUE largeboards + run: | + make clean + make -j2 ARCH=x86-64 largeboards=yes nnue=yes debug=yes build + ./stockfish bench + + - name: Build all variants + run: | + make clean + make -j2 ARCH=x86-64 largeboards=yes all=yes debug=yes build + + - name: Test protocols + run: | + ../tests/protocol.sh + + - name: Test variants.ini + run: | + ! ./stockfish check variants.ini 2>&1 >/dev/null | grep -v "Parsing variant" + + - name: Test variant perft + run: | + ../tests/perft.sh all + + - name: Test variant bench + run: | + ./stockfish bench xiangqi + ./stockfish bench shogi + ./stockfish bench capablanca + ./stockfish bench sittuyin + + - name: Test 32bit largeboards + run: | + if [[ "$COMP" == "gcc" ]]; then export EXTRACXXFLAGS=-Wno-class-memaccess; fi + make clean + make -j2 ARCH=x86-32 largeboards=yes build + ../tests/perft.sh largeboard diff --git a/Fairy-Stockfish-master/.github/workflows/ffishjs.yml b/Fairy-Stockfish-master/.github/workflows/ffishjs.yml new file mode 100644 index 0000000000000000000000000000000000000000..a7644db3b1a96e872eb1fc4dd60867c4f16e8bc5 --- /dev/null +++ b/Fairy-Stockfish-master/.github/workflows/ffishjs.yml @@ -0,0 +1,90 @@ +name: ffishjs + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +env: + EM_VERSION: 1.39.16 + EM_CACHE_FOLDER: 'emsdk-cache' + +jobs: + test: + runs-on: ubuntu-24.04 + + strategy: + matrix: + node-version: [12.x] + + steps: + - uses: actions/checkout@v6 + - name: Setup cache + id: cache-system-libraries + uses: actions/cache@v5 + with: + path: ${{env.EM_CACHE_FOLDER}} + key: emsdk-${{env.EM_VERSION}}-${{ runner.os }} + - uses: mymindstorm/setup-emsdk@v16 + with: + version: ${{env.EM_VERSION}} + actions-cache-folder: ${{env.EM_CACHE_FOLDER}} + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v6 + with: + node-version: ${{ matrix.node-version }} + - name: Build ffishjs + working-directory: src + run: make -f Makefile_js build debug=yes + - name: Install dependencies + working-directory: tests/js + run: npm install + - name: Run unit tests + working-directory: tests/js + run: npm test + + build: + runs-on: ubuntu-24.04 + needs: [test] #Building process must start after successful testing process + + strategy: + matrix: + node-version: [12.x] + + steps: + - uses: actions/checkout@v6 + - name: Setup cache + id: cache-system-libraries + uses: actions/cache@v5 + with: + path: ${{env.EM_CACHE_FOLDER}} + key: emsdk-${{env.EM_VERSION}}-${{ runner.os }} + - uses: mymindstorm/setup-emsdk@v16 + with: + version: ${{env.EM_VERSION}} + actions-cache-folder: ${{env.EM_CACHE_FOLDER}} + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v6 + with: + node-version: ${{ matrix.node-version }} + - name: Build ffish.js ES6/ES2015 module + working-directory: src + run: rm -f ../tests/js/ffish.js & rm -f ../tests/js/ffish.wasm & make -f Makefile_js build es6=yes + - name: Upload ffish.js ES6/ES2015 module ZIP archive + uses: actions/upload-artifact@v7 + with: + name: ffishjs-es6 + path: tests/js/* + if-no-files-found: error + compression-level: 9 + - name: Build ffish.js standard module + working-directory: src + run: rm -f ../tests/js/ffish.js & rm -f ../tests/js/ffish.wasm & make -f Makefile_js build + - name: Upload ffish.js standard module ZIP archive + uses: actions/upload-artifact@v7 + with: + name: ffishjs-standard + path: tests/js/* + if-no-files-found: error + compression-level: 9 diff --git a/Fairy-Stockfish-master/.github/workflows/release.yml b/Fairy-Stockfish-master/.github/workflows/release.yml new file mode 100644 index 0000000000000000000000000000000000000000..7c037ac05fdb50bdd870a2b5599f52883016731d --- /dev/null +++ b/Fairy-Stockfish-master/.github/workflows/release.yml @@ -0,0 +1,116 @@ +name: Release + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + windows: + strategy: + matrix: + arch: ["x86-64-bmi2", "x86-64-modern", "x86-64"] + runs-on: windows-2022 + + steps: + - uses: al-cheb/configure-pagefile-action@v1.5 + with: + minimum-size: 16GB + maximum-size: 16GB + disk-root: "C:" + + - uses: actions/checkout@v6 + + - name: make + run: cd src && make clean && make -j build COMP=mingw ARCH=${{ matrix.arch }} EXE=fairy-stockfish_${{ matrix.arch }}.exe && strip fairy-stockfish_${{ matrix.arch }}.exe + + - name: make largeboards + run: cd src && make clean && make -j build COMP=mingw ARCH=${{ matrix.arch }} EXE=fairy-stockfish-largeboards_${{ matrix.arch }}.exe largeboards=yes && strip fairy-stockfish-largeboards_${{ matrix.arch }}.exe + + - name: make all + if: ${{ matrix.arch == 'x86-64' }} + run: cd src && make clean && make -j build COMP=mingw ARCH=${{ matrix.arch }} EXE=fairy-stockfish-all_${{ matrix.arch }}.exe largeboards=yes all=yes && strip fairy-stockfish-all_${{ matrix.arch }}.exe + + - uses: actions/upload-artifact@v7 + with: + name: fairy-stockfish-windows-${{ matrix.arch }} + path: src/fairy-stockfish*.exe + if-no-files-found: error + compression-level: 9 + + linux: + strategy: + matrix: + arch: ["x86-64-bmi2", "x86-64-modern", "x86-64"] + runs-on: ubuntu-24.04 + + steps: + - uses: actions/checkout@v6 + + - name: make + run: cd src && make clean && make -j build COMP=gcc ARCH=${{ matrix.arch }} EXE=fairy-stockfish_${{ matrix.arch }} && strip fairy-stockfish_${{ matrix.arch }} + + - name: make largeboards + run: cd src && make clean && make -j build COMP=gcc ARCH=${{ matrix.arch }} EXE=fairy-stockfish-largeboards_${{ matrix.arch }} largeboards=yes && strip fairy-stockfish-largeboards_${{ matrix.arch }} + + - name: make all + if: ${{ matrix.arch == 'x86-64' }} + run: cd src && make clean && make -j build COMP=gcc ARCH=${{ matrix.arch }} EXE=fairy-stockfish-all_${{ matrix.arch }} largeboards=yes all=yes && strip fairy-stockfish-all_${{ matrix.arch }} + + - uses: actions/upload-artifact@v7 + with: + name: fairy-stockfish-linux-${{ matrix.arch }} + path: src/fairy-stockfish* + if-no-files-found: error + compression-level: 9 + + macos: + strategy: + matrix: + arch: ["x86-64"] + runs-on: macos-15-intel + + steps: + - uses: actions/checkout@v6 + + - name: make + run: cd src && make clean && make -j build COMP=clang ARCH=${{ matrix.arch }} EXE=fairy-stockfish_${{ matrix.arch }} && strip fairy-stockfish_${{ matrix.arch }} + + - name: make largeboards + run: cd src && make clean && make -j build COMP=clang ARCH=${{ matrix.arch }} EXE=fairy-stockfish-largeboards_${{ matrix.arch }} largeboards=yes && strip fairy-stockfish-largeboards_${{ matrix.arch }} + + - name: make all + run: cd src && make clean && make -j build COMP=clang ARCH=${{ matrix.arch }} EXE=fairy-stockfish-all_${{ matrix.arch }} largeboards=yes all=yes && strip fairy-stockfish-all_${{ matrix.arch }} + + - uses: actions/upload-artifact@v7 + with: + name: fairy-stockfish-macos-${{ matrix.arch }} + path: src/fairy-stockfish* + if-no-files-found: error + compression-level: 9 + + macos-m1: + strategy: + matrix: + arch: ["apple-silicon"] + runs-on: macos-14 + + steps: + - uses: actions/checkout@v6 + + - name: make + run: cd src && make clean && make -j build COMP=clang ARCH=${{ matrix.arch }} EXE=fairy-stockfish_${{ matrix.arch }} && strip fairy-stockfish_${{ matrix.arch }} + + - name: make largeboards + run: cd src && make clean && make -j build COMP=clang ARCH=${{ matrix.arch }} EXE=fairy-stockfish-largeboards_${{ matrix.arch }} largeboards=yes && strip fairy-stockfish-largeboards_${{ matrix.arch }} + + - name: make all + run: cd src && make clean && make -j build COMP=clang ARCH=${{ matrix.arch }} EXE=fairy-stockfish-all_${{ matrix.arch }} largeboards=yes all=yes && strip fairy-stockfish-all_${{ matrix.arch }} + + - uses: actions/upload-artifact@v7 + with: + name: fairy-stockfish-macos-m1-${{ matrix.arch }} + path: src/fairy-stockfish* + if-no-files-found: error + compression-level: 9 diff --git a/Fairy-Stockfish-master/.github/workflows/stockfish.yml b/Fairy-Stockfish-master/.github/workflows/stockfish.yml new file mode 100644 index 0000000000000000000000000000000000000000..bc2f5676a2a74156e2d58e7ca0a92f082b22fa9c --- /dev/null +++ b/Fairy-Stockfish-master/.github/workflows/stockfish.yml @@ -0,0 +1,213 @@ +name: Stockfish +on: + push: + branches: + - master + - tools + - github_ci + pull_request: + branches: + - master + - tools +jobs: + Stockfish: + name: ${{ matrix.config.name }} + runs-on: ${{ matrix.config.os }} + env: + COMPILER: ${{ matrix.config.compiler }} + COMP: ${{ matrix.config.comp }} + CXXFLAGS: "-Werror" + strategy: + matrix: + config: + - { + name: "Ubuntu 24.04 GCC", + os: ubuntu-24.04, + compiler: g++, + comp: gcc, + run_expensive_tests: true + } + - { + name: "Ubuntu 24.04 Clang", + os: ubuntu-24.04, + compiler: clang++, + comp: clang, + run_expensive_tests: false + } + + defaults: + run: + working-directory: src + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Download required packages + run: | + sudo apt update + sudo apt install expect valgrind g++-multilib llvm + + - name: Download the used network from the fishtest framework + run: | + make net + + - name: Extract the bench number from the commit history + run: | + git log HEAD | grep "\b[Bb]ench[ :]\+[0-9]\{7\}" | head -n 1 | sed "s/[^0-9]*\([0-9]*\).*/\1/g" > git_sig + [ -s git_sig ] && echo "benchref=$(cat git_sig)" >> $GITHUB_ENV && echo "Reference bench:" $(cat git_sig) || echo "No bench found" + + - name: Check compiler + run: | + $COMPILER -v + + - name: Test help target + run: | + make help + + # x86-32 tests + + - name: Test debug x86-32 build + run: | + export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG" + make clean + make -j2 ARCH=x86-32 optimize=no debug=yes build + ../tests/signature.sh $benchref + + - name: Test debug x86-32 profile build (Optimization on, All features) + run: | + export CXXFLAGS="" + make clean + make -j2 ARCH=x86-32 optimize=yes all=yes largeboards=yes debug=yes profile-build + + - name: Test x86-32 build + run: | + make clean + make -j2 ARCH=x86-32 build + ../tests/signature.sh $benchref + + - name: Test x86-32-sse41-popcnt build + run: | + make clean + make -j2 ARCH=x86-32-sse41-popcnt build + ../tests/signature.sh $benchref + + - name: Test x86-32-sse2 build + run: | + make clean + make -j2 ARCH=x86-32-sse2 build + ../tests/signature.sh $benchref + + - name: Test general-32 build + run: | + make clean + make -j2 ARCH=general-32 build + ../tests/signature.sh $benchref + + # x86-64 tests + + - name: Test debug x86-64-modern build + run: | + export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG" + make clean + make -j2 ARCH=x86-64-modern optimize=no debug=yes build + ../tests/signature.sh $benchref + + - name: Test debug x86-64-modern profile build (Optimization on, All features) + run: | + export CXXFLAGS="" + make clean + make -j2 ARCH=x86-64-modern optimize=yes all=yes largeboards=yes debug=yes profile-build + + - name: Test x86-64-modern build + run: | + make clean + make -j2 ARCH=x86-64-modern build + ../tests/signature.sh $benchref + + - name: Test x86-64-ssse3 build + run: | + make clean + make -j2 ARCH=x86-64-ssse3 build + ../tests/signature.sh $benchref + + - name: Test x86-64-sse3-popcnt build + run: | + make clean + make -j2 ARCH=x86-64-sse3-popcnt build + ../tests/signature.sh $benchref + + - name: Test x86-64 build + run: | + make clean + make -j2 ARCH=x86-64 build + ../tests/signature.sh $benchref + + - name: Test general-64 build + run: | + make clean + make -j2 ARCH=general-64 build + ../tests/signature.sh $benchref + + # x86-64 with newer extensions tests + + - name: Compile x86-64-avx2 build + run: | + make clean + make -j2 ARCH=x86-64-avx2 build + + - name: Compile x86-64-bmi2 build + run: | + make clean + make -j2 ARCH=x86-64-bmi2 build + + - name: Compile x86-64-avx512 build + run: | + make clean + make -j2 ARCH=x86-64-avx512 build + + - name: Compile x86-64-vnni512 build + run: | + make clean + make -j2 ARCH=x86-64-vnni512 build + + - name: Compile x86-64-vnni256 build + run: | + make clean + make -j2 ARCH=x86-64-vnni256 build + + # Other tests + + - name: Check perft and search reproducibility + run: | + make clean + make -j2 ARCH=x86-64-modern build + ../tests/perft.sh + ../tests/reprosearch.sh + + # Sanitizers + + - name: Run under valgrind + if: ${{ matrix.config.run_expensive_tests }} + run: | + export CXXFLAGS="-O1 -fno-inline" + make clean + make -j2 ARCH=x86-64-modern debug=yes optimize=no build > /dev/null + ../tests/instrumented.sh --valgrind + ../tests/instrumented.sh --valgrind-thread + + - name: Run with UB sanitizer + if: ${{ matrix.config.run_expensive_tests }} + run: | + export CXXFLAGS="-O1 -fno-inline" + make clean + make -j2 ARCH=x86-64-modern sanitize=undefined optimize=no debug=yes build > /dev/null + ../tests/instrumented.sh --sanitizer-undefined + + - name: Run with thread sanitizer + if: ${{ matrix.config.run_expensive_tests }} + run: | + export CXXFLAGS="-O1 -fno-inline" + make clean + make -j2 ARCH=x86-64-modern sanitize=thread optimize=no debug=yes build > /dev/null + ../tests/instrumented.sh --sanitizer-thread diff --git a/Fairy-Stockfish-master/.github/workflows/wheels.yml b/Fairy-Stockfish-master/.github/workflows/wheels.yml new file mode 100644 index 0000000000000000000000000000000000000000..184f3d22e2ed7f9a14ac30f56bb6ea4c50df0ef7 --- /dev/null +++ b/Fairy-Stockfish-master/.github/workflows/wheels.yml @@ -0,0 +1,57 @@ +name: Wheels + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + build_wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-24.04, windows-2022, macos-14] + + steps: + - uses: actions/checkout@v6 + + # Used to host cibuildwheel + - uses: actions/setup-python@v6 + + - name: Install cibuildwheel + run: python -m pip install cibuildwheel==3.4.0 + + - name: Build wheels + run: python -m cibuildwheel --output-dir wheelhouse + # to supply options, put them in 'env', like: + env: + MACOSX_DEPLOYMENT_TARGET: "10.14" + CIBW_ARCHS_MACOS: "x86_64 arm64" + CIBW_SKIP: "pp* *-win32 *-manylinux_i686 *-musllinux_* cp36-* cp37-* cp38" + CIBW_TEST_COMMAND: python {project}/test.py + + - uses: actions/upload-artifact@v7 + with: + name: dist-wheel-${{ matrix.os }} + path: ./wheelhouse/*.whl + + build_sdist: + name: Build source distribution + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 + with: + python-version: '3.9' + + - name: Build sdist + run: python setup.py sdist + + - uses: actions/upload-artifact@v7 + with: + name: dist-source + path: dist/*.tar.gz diff --git a/Fairy-Stockfish-master/.gitignore b/Fairy-Stockfish-master/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..ebfe03307acf816fc005d645927f00da8f5d3704 --- /dev/null +++ b/Fairy-Stockfish-master/.gitignore @@ -0,0 +1,43 @@ +# Ignore binaries in src/ that have no extension +/src/* +!/src/*.* +!/src/*/ + +# Files from build +**/*.o +**/*.s +src/.depend + +# Built binary +src/stockfish* +src/-lstdc++.res + +# Neural network for the NNUE evaluation +**/*.nnue + + +# Build/test artifacts +*.exe +tests/syzygy + +# ffishjs +tests/js/node_modules +ffish.js +*.wasm + +# pyffish +venv +*.so +pyffish.egg-info +__pycache__ +*.pyc + +# test artifacts +tests/*.exp +src/*.log +src/*.epd +src/*.txt + +# IDEs +.vscode +.idea \ No newline at end of file diff --git a/Fairy-Stockfish-master/AGENTS.md b/Fairy-Stockfish-master/AGENTS.md new file mode 100644 index 0000000000000000000000000000000000000000..611774a0b8b2935999b269fb55867f20c11700d7 --- /dev/null +++ b/Fairy-Stockfish-master/AGENTS.md @@ -0,0 +1,158 @@ +# Fairy-Stockfish Development Guide + +## Repository Overview + +**Fairy-Stockfish** is a chess variant engine derived from Stockfish, designed to support numerous chess variants and protocols. Written primarily in C++17, it includes Python and JavaScript bindings for library use. + +**Repository Statistics:** +- **Languages:** C++17 (primary), Python, JavaScript, Shell scripts +- **Architecture:** Multi-protocol chess engine (UCI, UCCI, USI, XBoard/CECP) +- **Target Platforms:** Windows, Linux, macOS, Android, WebAssembly +- **Supported Variants:** 90+ chess variants including regional, historical, and modern variants + +## Build System & Requirements + +### Prerequisites +- **Compiler:** GCC, Clang, or MSVC with C++17 support +- **Build Tool:** GNU Make (required for C++ engine) +- **Python:** 3.7+ (for Python bindings) +- **Node.js:** (for JavaScript bindings) +- **Additional Tools:** expect utility (for testing) + +### Core Build Process + +Note: Run engine and test commands from the `src/` directory unless specified otherwise. + +#### Basic Build Commands +```bash +# Standard release build (recommended for most users) +make -j2 ARCH=x86-64 build + +# Debug build (for development) +make -j2 ARCH=x86-64 debug=yes build + +# All variants including ones with large boards (up to 12x10) and large branching factor (all) +make -j2 ARCH=x86-64 largeboards=yes all=yes build +``` + +### Python Bindings (pyffish) +```bash +# Build Python bindings (from repository root) +python3 setup.py install + +# Alternative: Install from PyPI +pip install pyffish +``` + +### JavaScript Bindings (ffish.js) +Also see the `tests/js/README.md`. +```bash +cd src/ + +# Build JavaScript bindings (requires emscripten) +make -f Makefile_js build + +# Alternative: Install from npm +npm install ffish +``` + +## Testing & Validation + +All test commands below assume the current directory is `src/`. + +### Core Engine Tests +```bash +# Basic functionality test +./stockfish bench + +# Variant-specific benchmarks +./stockfish bench xiangqi +./stockfish bench shogi +./stockfish bench capablanca + +# Validate variants configuration +./stockfish check variants.ini +``` + +### Comprehensive Test Suite +```bash +# Protocol compliance tests +../tests/protocol.sh + +# Move generation validation +../tests/perft.sh all +../tests/perft.sh chess # Chess only +../tests/perft.sh largeboard # Large board variants only + +# Regression testing +../tests/regression.sh + +# Reproducible search test +../tests/reprosearch.sh + +# Build signature verification +../tests/signature.sh +``` + + +## Project Architecture + +### Directory Structure +``` +src/ # Core C++ engine source +tests/ # Test scripts and data +.github/workflows/ # CI/CD configurations +``` + +### Configuration Files +- **`src/variants.ini`**: Defines examples for configuration of chess variants +- **`setup.py`**: Python package build configuration +- **`tests/js/package.json`**: JavaScript bindings configuration + +### Key Source Files in `src/` +- **`variant.h`**: Variant rule properties +- **`variant.cpp`**: Variant-specific game rules +- **`variant.ini`**: Variant rule configuration examples and documentation of variant properties +- **`position.h`**: Position representation +- **`position.cpp`**: Board logic +- **`movegen.cpp`**: Move generation logic +- **`parser.cpp`**: Variant rule configuration parsing +- **`piece.cpp`**: Piece type definitions and behavior +- **`pyffish.cpp`**: Python bindings +- **`ffishjs.cpp`**: JavaScript bindings + +## Continuous Integration + +### GitHub Actions Workflows +- **`fairy.yml`**: Core engine testing (perft, protocols, variants) +- **`stockfish.yml`**: Standard Stockfish compatibility tests +- **`release.yml`**: Binary releases for multiple platforms +- **`wheels.yml`**: Python package builds +- **`ffishjs.yml`**: JavaScript binding builds + +## Common Development Patterns + +### Making Engine Changes +1. **Always test basic functionality:** `./stockfish bench` after changes +2. **Validate variant compatibility:** `./stockfish check variants.ini` +3. **Run relevant tests:** `../tests/perft.sh all` for move generation changes + +### Adding New Configurable Variants +1. **Edit `src/variants.ini`**: Add variant configuration +2. **Test parsing:** `./stockfish check variants.ini` + +### Relevant websites for researching chess variant rules +- [Chess Variants on Wikipedia](https://en.wikipedia.org/wiki/List_of_chess_variants) +- [Chess Variants Wiki](https://www.chessvariants.com/) +- [Variant Chess on BoardGameGeek](https://boardgamegeek.com/boardgamefamily/4024/traditional-games-chess) +- [PyChess Variants](https://www.pychess.org/variants) +- [Ludii](https://ludii.games/library.php) +- [Lichess.org Variants](https://lichess.org/variant) +- [Greenchess](https://greenchess.net/variants.php) +- [Chess Variants on Chess.com](https://www.chess.com/variants) + +### Development Best Practices +* Make sure to only stage and commit changes that were changed as part of the task, do not simply add all changes. +* Keep changes minimal and focused on the task at hand. +* After applying changes make sure that all places related to the task have been identified. +* Stay consistent with the existing code style and conventions. diff --git a/Fairy-Stockfish-master/AUTHORS b/Fairy-Stockfish-master/AUTHORS new file mode 100644 index 0000000000000000000000000000000000000000..a0f09c8c5068bcfeb940fe150f39406fc87ef857 --- /dev/null +++ b/Fairy-Stockfish-master/AUTHORS @@ -0,0 +1,231 @@ +# Fairy-Stockfish authors + +# Main author +Fabian Fichter (ianfab) + +# Contributors in alphabetical order +alwey +Belzedar94 +cloudfish +CouchTomato87 +dpldgr +Fulmene +gbtami +gnunicode +johndoknjas +kz04px +majormink +mtaktikos +QueensGambit +RainRat +rkb97 +romstad +Snowmoondaphne +SriMethan +thearst3rd +TheYoBots +tttak +yjf2002ghty +yoav-rozin +ydirson + + + +# List of authors for Stockfish, as of June 14, 2021 + +# Founders of the Stockfish project and fishtest infrastructure +Tord Romstad (romstad) +Marco Costalba (mcostalba) +Joona Kiiski (zamar) +Gary Linscott (glinscott) + +# Authors and inventors of NNUE, training, NNUE port +Yu Nasu (ynasu87) +Motohiro Isozaki (yaneurao) +Hisayori Noda (nodchip) + +# all other authors of the code in alphabetical order +Aditya (absimaldata) +Adrian Petrescu (apetresc) +Ajith Chandy Jose (ajithcj) +Alain Savard (Rocky640) +Alayan Feh (Alayan-stk-2) +Alexander Kure +Alexander Pagel (Lolligerhans) +Alfredo Menezes (lonfom169) +Ali AlZhrani (Cooffe) +Andrew Grant (AndyGrant) +Andrey Neporada (nepal) +Andy Duplain +Antoine Champion (antoinechampion) +Aram Tumanian (atumanian) +Arjun Temurnikar +Artem Solopiy (EntityFX) +Auguste Pop +Balint Pfliegel +Ben Koshy (BKSpurgeon) +Bill Henry (VoyagerOne) +Bojun Guo (noobpwnftw, Nooby) +braich +Brian Sheppard (SapphireBrand, briansheppard-toast) +Bruno de Melo Costa (BM123499) +Bryan Cross (crossbr) +candirufish +Chess13234 +Chris Cain (ceebo) +Dale Weiler (graphitemaster) +Dan Schmidt (dfannius) +Daniel Axtens (daxtens) +Daniel Dugovic (ddugovic) +Dariusz Orzechowski (dorzechowski) +David Zar +Daylen Yang (daylen) +Deshawn Mohan-Smith (GoldenRare) +Dieter Dobbelaere (ddobbelaere) +DiscanX +Dominik Schlösser (domschl) +double-beep +Douglas Matos Gomes (dsmsgms) +Eduardo Cáceres (eduherminio) +Eelco de Groot (KingDefender) +Elvin Liu (solarlight2) +erbsenzaehler +Ernesto Gatti +Linmiao Xu (linrock) +Fabian Beuke (madnight) +Fabian Fichter (ianfab) +Fanael Linithien (Fanael) +fanon +Fauzi Akram Dabat (FauziAkram) +Felix Wittmann +gamander +Gary Heckman (gheckman) +George Sobala (gsobala) +gguliash +Gian-Carlo Pascutto (gcp) +Gontran Lemaire (gonlem) +Goodkov Vasiliy Aleksandrovich (goodkov) +Gregor Cramer +GuardianRM +Günther Demetz (pb00067, pb00068) +Guy Vreuls (gvreuls) +Henri Wiechers +Hiraoka Takuya (HiraokaTakuya) +homoSapiensSapiens +Hongzhi Cheng +Ivan Ivec (IIvec) +Jacques B. (Timshel) +Jan Ondruš (hxim) +Jared Kish (Kurtbusch) +Jarrod Torriero (DU-jdto) +Jean Gauthier (OuaisBla) +Jean-Francois Romang (jromang) +Jekaa +Jerry Donald Watson (jerrydonaldwatson) +jjoshua2 +Jonathan Calovski (Mysseno) +Jonathan Buladas Dumale (SFisGOD) +Joost VandeVondele (vondele) +Jörg Oster (joergoster) +Joseph Ellis (jhellis3) +Joseph R. Prostko +Julian Willemer (NightlyKing) +jundery +Justin Blanchard (UncombedCoconut) +Kelly Wilson +Ken Takusagawa +kinderchocolate +Kiran Panditrao (Krgp) +Kojirion +Krystian Kuzniarek (kuzkry) +Leonardo Ljubičić (ICCF World Champion) +Leonid Pechenik (lp--) +Linus Arver (listx) +loco-loco +Lub van den Berg (ElbertoOne) +Luca Brivio (lucabrivio) +Lucas Braesch (lucasart) +Lyudmil Antonov (lantonov) +Maciej Żenczykowski (zenczykowski) +Malcolm Campbell (xoto10) +Mark Tenzer (31m059) +marotear +Matt Ginsberg (mattginsberg) +Matthew Lai (matthewlai) +Matthew Sullivan (Matt14916) +Maxim Molchanov (Maxim) +Michael An (man) +Michael Byrne (MichaelB7) +Michael Chaly (Vizvezdenec) +Michael Stembera (mstembera) +Michael Whiteley (protonspring) +Michel Van den Bergh (vdbergh) +Miguel Lahoz (miguel-l) +Mikael Bäckman (mbootsector) +Mira +Miroslav Fontán (Hexik) +Moez Jellouli (MJZ1977) +Mohammed Li (tthsqe12) +Nathan Rugg (nmrugg) +Nick Pelling (nickpelling) +Nicklas Persson (NicklasPersson) +Niklas Fiekas (niklasf) +Nikolay Kostov (NikolayIT) +Nguyen Pham (nguyenpham) +Norman Schmidt (FireFather) +notruck +Ondrej Mosnáček (WOnder93) +Oskar Werkelin Ahlin +Pablo Vazquez +Panthee +Pascal Romaret +Pasquale Pigazzini (ppigazzini) +Patrick Jansen (mibere) +pellanda +Peter Zsifkovits (CoffeeOne) +Praveen Kumar Tummala (praveentml) +Rahul Dsilva (silversolver1) +Ralph Stößer (Ralph Stoesser) +Raminder Singh +renouve +Reuven Peleg +Richard Lloyd +Rodrigo Exterckötter Tjäder +Ron Britvich (Britvich) +Ronald de Man (syzygy1, syzygy) +rqs +Ryan Schmitt +Ryan Takker +Sami Kiminki (skiminki) +Sebastian Buchwald (UniQP) +Sergei Antonov (saproj) +Sergei Ivanov (svivanov72) +Sergio Vieri (sergiovieri) +sf-x +Shane Booth (shane31) +Shawn Varghese (xXH4CKST3RXx) +Siad Daboul (Topologist) +Stefan Geschwentner (locutus2) +Stefano Cardanobile (Stefano80) +Steinar Gunderson (sesse) +Stéphane Nicolet (snicolet) +Prokop Randáček (ProkopRandacek) +Thanar2 +thaspel +theo77186 +Tom Truscott +Tom Vijlbrief (tomtor) +Tomasz Sobczyk (Sopel97) +Torsten Franz (torfranz, tfranzer) +Tracey Emery (basepr1me) +tttak +Unai Corzo (unaiic) +Uri Blass (uriblass) +Vince Negri (cuddlestmonkey) +zz4032 + + +# Additionally, we acknowledge the authors and maintainers of fishtest, +# an amazing and essential framework for the development of Stockfish! +# +# https://github.com/glinscott/fishtest/blob/master/AUTHORS diff --git a/Fairy-Stockfish-master/Copying.txt b/Fairy-Stockfish-master/Copying.txt new file mode 100644 index 0000000000000000000000000000000000000000..818433ecc0e094a4db1023c68b33f24344643ad8 --- /dev/null +++ b/Fairy-Stockfish-master/Copying.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/Fairy-Stockfish-master/MANIFEST.in b/Fairy-Stockfish-master/MANIFEST.in new file mode 100644 index 0000000000000000000000000000000000000000..1265c8117db8540fd5993cd87256eb0db40896a0 --- /dev/null +++ b/Fairy-Stockfish-master/MANIFEST.in @@ -0,0 +1,2 @@ +include AUTHORS Copying.txt Readme.md test.py +recursive-include src *.h *.cpp diff --git a/Fairy-Stockfish-master/README.md b/Fairy-Stockfish-master/README.md new file mode 100644 index 0000000000000000000000000000000000000000..b8e87970a24dc8081edb624b098ad7f2d519bbf1 --- /dev/null +++ b/Fairy-Stockfish-master/README.md @@ -0,0 +1,126 @@ +# Fairy-Stockfish + +## Overview + +[![Build Status](https://github.com/fairy-stockfish/Fairy-Stockfish/actions/workflows/release.yml/badge.svg?branch=master)](https://github.com/fairy-stockfish/Fairy-Stockfish/actions?query=workflow%3ARelease) +[![Build Status](https://github.com/fairy-stockfish/Fairy-Stockfish/actions/workflows/fairy.yml/badge.svg?branch=master)](https://github.com/fairy-stockfish/Fairy-Stockfish/actions?query=workflow%3Afairy) +[![Build Status](https://ci.appveyor.com/api/projects/status/github/fairy-stockfish/Fairy-Stockfish?branch=master&svg=true)](https://ci.appveyor.com/project/ianfab/Fairy-Stockfish/branch/master) +[![PyPI version](https://badge.fury.io/py/pyffish.svg)](https://badge.fury.io/py/pyffish) +[![NPM version](https://img.shields.io/npm/v/ffish.svg?sanitize=true)](https://www.npmjs.com/package/ffish) + +Fairy-Stockfish is a chess variant engine derived from [Stockfish](https://github.com/official-stockfish/Stockfish/) designed for the support of fairy chess variants and easy extensibility with more games. It can play various regional, historical, and modern chess variants as well as [games with user-defined rules](https://github.com/fairy-stockfish/Fairy-Stockfish/wiki/Variant-configuration). For [compatibility with graphical user interfaces](https://github.com/fairy-stockfish/Fairy-Stockfish/wiki/Graphical-user-interfaces) it supports the UCI, UCCI, USI, UCI-cyclone, and CECP/XBoard protocols. + +The goal of the project is to create an engine supporting a large variety of chess-like games, equipped with the powerful search of Stockfish. Despite its generality the [playing strength](https://github.com/fairy-stockfish/Fairy-Stockfish/wiki/Playing-strength) is on a very high level in almost all supported variants. Due to its multi-protocol support Fairy-Stockfish works with almost any chess variant GUI. + +## Installation +You can download the [Windows executable](https://github.com/fairy-stockfish/Fairy-Stockfish/releases/latest/download/fairy-stockfish-largeboard_x86-64.exe) or [Linux binary](https://github.com/fairy-stockfish/Fairy-Stockfish/releases/latest/download/fairy-stockfish-largeboard_x86-64) from the [latest release](https://github.com/fairy-stockfish/Fairy-Stockfish/releases/latest) or [compile the program from source](https://github.com/fairy-stockfish/Fairy-Stockfish#compiling-stockfish-yourself-from-the-sources). The program comes without a graphical user interface, so you perhaps want to use it together with a [compatible GUI](https://github.com/fairy-stockfish/Fairy-Stockfish/wiki/Graphical-user-interfaces), or [play against it online](https://github.com/fairy-stockfish/Fairy-Stockfish/wiki/Online) at [pychess](https://www.pychess.org/), [lishogi](https://lishogi.org/@/Fairy-Stockfish), or [lichess](https://lichess.org/@/Fairy-Stockfish). Read more about [how to use](https://github.com/fairy-stockfish/Fairy-Stockfish/wiki/Usage) Fairy-Stockfish in the wiki. + +If you want to preview the functionality of Fairy-Stockfish before downloading, you can try it out on the [Fairy-Stockfish playground](https://fairyground.vercel.app/) in the browser. + +Optional NNUE evaluation parameter files to improve playing strength for many variants are in the [list of NNUE networks](https://fairy-stockfish.github.io/nnue/#current-best-nnue-networks). +For the regional variants Xiangqi, Janggi, and Makruk [dedicated releases with built-in NNUE networks](https://github.com/fairy-stockfish/Fairy-Stockfish-NNUE) are available. See the [wiki](https://github.com/fairy-stockfish/Fairy-Stockfish/wiki/NNUE) for more details on NNUE. + +## Contributing + +If you like this project, please support its development via [patreon](https://www.patreon.com/ianfab), [paypal](https://paypal.me/FairyStockfish), or [github](https://github.com/sponsors/ianfab), or by [contributing to the code](https://github.com/fairy-stockfish/Fairy-Stockfish/wiki/Contributing) or documentation. An [introduction to the code base](https://github.com/fairy-stockfish/Fairy-Stockfish/wiki/Understanding-the-code) can be found in the wiki. + +## Supported games + +The games currently supported besides chess are listed below. Fairy-Stockfish can also play user-defined variants loaded via a variant configuration file, see the file [`src/variants.ini`](https://github.com/ianfab/Fairy-Stockfish/blob/master/src/variants.ini) and the [wiki](https://github.com/fairy-stockfish/Fairy-Stockfish/wiki/Variant-configuration). + +### Regional and historical games +- [Xiangqi](https://en.wikipedia.org/wiki/Xiangqi), [Manchu](https://en.wikipedia.org/wiki/Manchu_chess), [Minixiangqi](https://www.pychess.org/variants/minixiangqi), [Supply chess](https://en.wikipedia.org/wiki/Xiangqi#Variations) +- [Shogi](https://en.wikipedia.org/wiki/Shogi), [Shogi variants](https://github.com/fairy-stockfish/Fairy-Stockfish#shogi-variants) +- [Janggi](https://en.wikipedia.org/wiki/Janggi) +- [Makruk](https://en.wikipedia.org/wiki/Makruk), [ASEAN](http://hgm.nubati.net/rules/ASEAN.html), Makpong, Ai-Wok +- [Ouk Chatrang](https://en.wikipedia.org/wiki/Makruk#Cambodian_chess), [Kar Ouk](https://en.wikipedia.org/wiki/Makruk#Cambodian_chess) +- [Sittuyin](https://en.wikipedia.org/wiki/Sittuyin) +- [Shatar](https://en.wikipedia.org/wiki/Shatar), [Jeson Mor](https://en.wikipedia.org/wiki/Jeson_Mor) +- [Shatranj](https://en.wikipedia.org/wiki/Shatranj), [Courier](https://en.wikipedia.org/wiki/Courier_chess) +- [Raazuvaa](https://www.reddit.com/r/chess/comments/sj7y3n/raazuvaa_the_chess_of_the_maldives) + +### Chess variants +- [Capablanca](https://en.wikipedia.org/wiki/Capablanca_Chess), [Janus](https://en.wikipedia.org/wiki/Janus_Chess), [Modern](https://en.wikipedia.org/wiki/Modern_Chess_(chess_variant)), [Chancellor](https://en.wikipedia.org/wiki/Chancellor_Chess), [Embassy](https://en.wikipedia.org/wiki/Embassy_Chess), [Gothic](https://www.chessvariants.com/large.dir/gothicchess.html), [Capablanca random chess](https://en.wikipedia.org/wiki/Capablanca_Random_Chess) +- [Grand](https://en.wikipedia.org/wiki/Grand_Chess), [Shako](https://www.chessvariants.com/large.dir/shako.html), [Centaur](https://www.chessvariants.com/large.dir/contest/royalcourt.html), [Tencubed](https://www.chessvariants.com/contests/10/tencubedchess.html), [Opulent](https://www.chessvariants.com/rules/opulent-chess) +- [Chess960](https://en.wikipedia.org/wiki/Chess960), [Placement/Pre-Chess](https://www.chessvariants.com/link/placement-chess) +- [Crazyhouse](https://en.wikipedia.org/wiki/Crazyhouse), [Loop](https://en.wikipedia.org/wiki/Crazyhouse#Variations), [Chessgi](https://en.wikipedia.org/wiki/Crazyhouse#Variations), [Pocket Knight](http://www.chessvariants.com/other.dir/pocket.html), Capablanca-Crazyhouse +- [Bughouse](https://en.wikipedia.org/wiki/Bughouse_chess), [Koedem](http://schachclub-oetigheim.de/wp-content/uploads/2016/04/Koedem-rules.pdf) +- [Seirawan](https://en.wikipedia.org/wiki/Seirawan_chess), Seirawan-Crazyhouse, [Dragon Chess](https://www.edami.com/dragonchess/) +- [Amazon](https://www.chessvariants.com/diffmove.dir/amazone.html), [Chigorin](https://www.chessvariants.com/diffsetup.dir/chigorin.html), [Almost chess](https://en.wikipedia.org/wiki/Almost_Chess) +- [Hoppel-Poppel](http://www.chessvariants.com/diffmove.dir/hoppel-poppel.html), New Zealand +- [Antichess](https://lichess.org/variant/antichess), [Giveaway](http://www.chessvariants.com/diffobjective.dir/giveaway.old.html), [Suicide](https://www.freechess.org/Help/HelpFiles/suicide_chess.html), [Losers](https://www.chessclub.com/help/Wild17), [Codrus](http://www.binnewirtz.com/Schlagschach1.htm) +- [Extinction](https://en.wikipedia.org/wiki/Extinction_chess), [Kinglet](https://en.wikipedia.org/wiki/V._R._Parton#Kinglet_chess), Three Kings, [Coregal](https://www.chessvariants.com/winning.dir/coregal.html) +- [King of the Hill](https://en.wikipedia.org/wiki/King_of_the_Hill_(chess)), [Racing Kings](https://en.wikipedia.org/wiki/V._R._Parton#Racing_Kings) +- [Three-check](https://en.wikipedia.org/wiki/Three-check_chess), Five-check +- [Los Alamos](https://en.wikipedia.org/wiki/Los_Alamos_chess), [Gardner's Minichess](https://en.wikipedia.org/wiki/Minichess#5%C3%975_chess) +- [Atomic](https://en.wikipedia.org/wiki/Atomic_chess) +- [Horde](https://en.wikipedia.org/wiki/Dunsany%27s_Chess#Horde_Chess), [Maharajah and the Sepoys](https://en.wikipedia.org/wiki/Maharajah_and_the_Sepoys) +- [Knightmate](https://www.chessvariants.com/diffobjective.dir/knightmate.html), [Nightrider](https://en.wikipedia.org/wiki/Nightrider_(chess)), [Grasshopper](https://en.wikipedia.org/wiki/Grasshopper_chess) +- [Duck chess](https://duckchess.com/), [Omicron](http://www.eglebbk.dds.nl/program/chess-omicron.html), [Gustav III](https://www.chessvariants.com/play/gustav-iiis-chess) +- [Berolina Chess](https://en.wikipedia.org/wiki/Berolina_pawn#Berolina_chess), [Pawn-Sideways](https://arxiv.org/abs/2009.04374), [Pawn-Back](https://arxiv.org/abs/2009.04374), [Torpedo](https://arxiv.org/abs/2009.04374), [Legan Chess](https://en.wikipedia.org/wiki/Legan_chess) +- [Spartan Chess](https://www.chessvariants.com/rules/spartan-chess) +- [Wolf Chess](https://en.wikipedia.org/wiki/Wolf_chess) +- [Troitzky Chess](https://www.chessvariants.com/play/troitzky-chess) + +### Shogi variants +- [Minishogi](https://en.wikipedia.org/wiki/Minishogi), [EuroShogi](https://en.wikipedia.org/wiki/EuroShogi), [Judkins shogi](https://en.wikipedia.org/wiki/Judkins_shogi) +- [Kyoto shogi](https://en.wikipedia.org/wiki/Kyoto_shogi), [Microshogi](https://en.wikipedia.org/wiki/Micro_shogi) +- [Dobutsu shogi](https://en.wikipedia.org/wiki/Dōbutsu_shōgi), [Goro goro shogi](https://en.wikipedia.org/wiki/D%C5%8Dbutsu_sh%C5%8Dgi#Variation) +- [Tori shogi](https://en.wikipedia.org/wiki/Tori_shogi) +- [Yari shogi](https://en.wikipedia.org/wiki/Yari_shogi) +- [Okisaki shogi](https://en.wikipedia.org/wiki/Okisaki_shogi) +- [Sho shogi](https://en.wikipedia.org/wiki/Sho_shogi) + +### Related games +- [Amazons](https://en.wikipedia.org/wiki/Game_of_the_Amazons) +- [Ataxx](https://en.wikipedia.org/wiki/Ataxx) +- [Breakthrough](https://en.wikipedia.org/wiki/Breakthrough_(board_game)) +- [Clobber](https://en.wikipedia.org/wiki/Clobber) +- [Cfour](https://en.wikipedia.org/wiki/Connect_Four), [Tic-tac-toe](https://en.wikipedia.org/wiki/Tic-tac-toe) +- [Five Field Kono](https://en.wikipedia.org/wiki/Five_Field_Kono) +- [Flipersi](https://en.wikipedia.org/wiki/Reversi), [Flipello](https://en.wikipedia.org/wiki/Reversi#Othello) +- [Fox and Hounds](https://boardgamegeek.com/boardgame/148180/fox-and-hounds) +- [Isolation](https://boardgamegeek.com/boardgame/1875/isolation) +- [Joust](https://www.chessvariants.com/programs.dir/joust.html) +- [Snailtrail](https://boardgamegeek.com/boardgame/37135/snailtrail) + + +## Help + +See the [Fairy-Stockfish Wiki](https://github.com/fairy-stockfish/Fairy-Stockfish/wiki) for more info, or if the required information is not available, open an [issue](https://github.com/fairy-stockfish/Fairy-Stockfish/issues) or join our [discord server](https://discord.gg/FYUGgmCFB4). + +## Bindings + +Besides the C++ engine, this project also includes bindings for other programming languages in order to be able to use it as a library for chess variants. They support move, SAN, and FEN generation, as well as checking of game end conditions for all variants supported by Fairy-Stockfish. Since the bindings are using the C++ code, they are very performant compared to libraries directly written in the respective target language. + +### Python + +The python binding [pyffish](https://pypi.org/project/pyffish/) contributed by [@gbtami](https://github.com/gbtami) is implemented in [pyffish.cpp](https://github.com/fairy-stockfish/Fairy-Stockfish/blob/master/src/pyffish.cpp). It is e.g. used in the backend for the [pychess server](https://github.com/gbtami/pychess-variants). + +### Javascript + +The javascript binding [ffish.js](https://www.npmjs.com/package/ffish) contributed by [@QueensGambit](https://github.com/QueensGambit) is implemented in [ffishjs.cpp](https://github.com/fairy-stockfish/Fairy-Stockfish/blob/master/src/ffishjs.cpp). The compilation/binding to javascript is done using emscripten, see the [readme](https://github.com/fairy-stockfish/Fairy-Stockfish/tree/master/tests/js). + +## Ports + +### WebAssembly + +For in-browser use a [port of Fairy-Stockfish to WebAssembly](https://github.com/fairy-stockfish/fairy-stockfish.wasm) is available at [npm](https://www.npmjs.com/package/fairy-stockfish-nnue.wasm). It is e.g. used for local analysis on [pychess.org](https://www.pychess.org/analysis/chess). Also see the [Fairy-Stockfish WASM demo](https://github.com/ianfab/fairy-stockfish-nnue-wasm-demo) available at https://fairy-stockfish-nnue-wasm.vercel.app/. + +## Terms of use + +Fairy-Stockfish is free, and distributed under the **GNU General Public License version 3** +(GPL v3). Essentially, this means you are free to do almost exactly +what you want with the program, including distributing it among your +friends, making it available for download from your website, selling +it (either by itself or as part of some bigger software package), or +using it as the starting point for a software project of your own. + +The only real limitation is that whenever you distribute Fairy-Stockfish in +some way, you MUST always include the full source code, or a pointer +to where the source code can be found, to generate the exact binary +you are distributing. If you make any changes to the source code, +these changes must also be made available under the GPL. + +For full details, read the copy of the GPL v3 found in the file named +[*Copying.txt*](https://github.com/fairy-stockfish/Fairy-Stockfish/blob/master/Copying.txt). diff --git a/Fairy-Stockfish-master/Top CPU Contributors.txt b/Fairy-Stockfish-master/Top CPU Contributors.txt new file mode 100644 index 0000000000000000000000000000000000000000..dacc5781b2ddf47131ff0e72f54922bea29d3d5e --- /dev/null +++ b/Fairy-Stockfish-master/Top CPU Contributors.txt @@ -0,0 +1,205 @@ +Contributors to Fishtest with >10,000 CPU hours, as of Jun 29, 2021. +Thank you! + +Username CPU Hours Games played +----------------------------------------------------- +noobpwnftw 27649494 1834734733 +mlang 1426107 89454622 +dew 1380910 82831648 +mibere 703840 46867607 +grandphish2 692707 41737913 +tvijlbrief 669642 42371594 +JojoM 597778 35297180 +TueRens 519226 31823562 +cw 458421 30307421 +fastgm 439667 25950040 +gvreuls 436599 28177460 +crunchy 427035 27344275 +CSU_Dynasty 374765 25106278 +Fisherman 326901 21822979 +ctoks 325477 21767943 +velislav 295343 18844324 +linrock 292789 10624427 +bcross 278584 19488961 +okrout 262818 13803272 +pemo 245982 11376085 +glinscott 217799 13780820 +leszek 212346 12959025 +nordlandia 211692 13484886 +bking_US 198894 11876016 +drabel 196463 13450602 +robal 195473 12375650 +mgrabiak 187226 12016564 +Dantist 183202 10990484 +Thanar 179852 12365359 +vdv 175274 9889046 +spams 157128 10319326 +marrco 150295 9402141 +sqrt2 147963 9724586 +mhoram 141278 8901241 +CoffeeOne 137100 5024116 +vdbergh 137041 8926915 +malala 136182 8002293 +xoto 133702 9156676 +davar 122092 7960001 +dsmith 122059 7570238 +Data 113305 8220352 +BrunoBanani 112960 7436849 +MaZePallas 102823 6633619 +sterni1971 100532 5880772 +ElbertoOne 99028 7023771 +brabos 92118 6186135 +oz 92100 6486640 +psk 89957 5984901 +amicic 89156 5392305 +sunu 88851 6028873 +Vizvezdenec 83761 5344740 +0x3C33 82614 5271253 +BRAVONE 81239 5054681 +racerschmacer 80899 5759262 +cuistot 80300 4606144 +nssy 76497 5259388 +teddybaer 75125 5407666 +Pking_cda 73776 5293873 +jromang 72192 5057715 +solarlight 70517 5028306 +dv8silencer 70287 3883992 +Bobo1239 68515 4652287 +manap 66273 4121774 +skiminki 65088 4023328 +tinker 64333 4268790 +sschnee 60767 3500800 +qurashee 57344 3168264 +robnjr 57262 4053117 +Freja 56938 3733019 +ttruscott 56010 3680085 +rkl 55132 4164467 +renouve 53811 3501516 +finfish 51360 3370515 +eva42 51272 3599691 +rap 49985 3219146 +pb00067 49727 3298270 +ronaldjerum 47654 3240695 +bigpen0r 47653 3335327 +eastorwest 47585 3221629 +biffhero 46564 3111352 +VoyagerOne 45476 3452465 +yurikvelo 44834 3034550 +speedycpu 43842 3003273 +jbwiebe 43305 2805433 +Spprtr 42279 2680153 +DesolatedDodo 42007 2447516 +Antihistamine 41788 2761312 +mhunt 41735 2691355 +homyur 39893 2850481 +gri 39871 2515779 +Fifis 38776 2529121 +oryx 38724 2966648 +SC 37290 2731014 +csnodgrass 36207 2688994 +jmdana 36157 2210661 +strelock 34716 2074055 +rpngn 33951 2057395 +Garf 33922 2751802 +EthanOConnor 33370 2090311 +slakovv 32915 2021889 +manapbk 30987 1810399 +Prcuvu 30377 2170122 +anst 30301 2190091 +jkiiski 30136 1904470 +hyperbolic.tom 29840 2017394 +Pyafue 29650 1902349 +Wolfgang 29260 1658936 +zeryl 28156 1579911 +OuaisBla 27636 1578800 +DMBK 27051 1999456 +chriswk 26902 1868317 +achambord 26582 1767323 +Patrick_G 26276 1801617 +yorkman 26193 1992080 +SFTUser 25182 1675689 +nabildanial 24942 1519409 +Sharaf_DG 24765 1786697 +ncfish1 24411 1520927 +rodneyc 24227 1409514 +agg177 23890 1395014 +JanErik 23408 1703875 +Isidor 23388 1680691 +Norabor 23164 1591830 +cisco2015 22897 1762669 +Zirie 22542 1472937 +team-oh 22272 1636708 +MazeOfGalious 21978 1629593 +sg4032 21947 1643265 +ianh2105 21725 1632562 +xor12 21628 1680365 +dex 21612 1467203 +nesoneg 21494 1463031 +sphinx 21211 1384728 +jjoshua2 21001 1423089 +horst.prack 20878 1465656 +Ente 20865 1477066 +0xB00B1ES 20590 1208666 +j3corre 20405 941444 +Adrian.Schmidt123 20316 1281436 +wei 19973 1745989 +MaxKlaxxMiner 19850 1009176 +rstoesser 19569 1293588 +gopeto 19491 1174952 +eudhan 19274 1283717 +jundery 18445 1115855 +megaman7de 18377 1067540 +iisiraider 18247 1101015 +ville 17883 1384026 +chris 17698 1487385 +purplefishies 17595 1092533 +dju 17353 978595 +DragonLord 17014 1162790 +IgorLeMasson 16064 1147232 +ako027ako 15671 1173203 +chuckstablers 15289 891576 +Nikolay.IT 15154 1068349 +Andrew Grant 15114 895539 +OssumOpossum 14857 1007129 +Karby 14808 867120 +enedene 14476 905279 +bpfliegel 14298 884523 +mpx86 14019 759568 +jpulman 13982 870599 +crocogoat 13803 1117422 +joster 13794 950160 +Nesa92 13786 1114691 +Hjax 13535 915487 +jsys14 13459 785000 +Dark_wizzie 13422 1007152 +mabichito 12903 749391 +thijsk 12886 722107 +AdrianSA 12860 804972 +Flopzee 12698 894821 +fatmurphy 12547 853210 +Rudolphous 12520 832340 +scuzzi 12511 845761 +SapphireBrand 12416 969604 +modolief 12386 896470 +Machariel 12335 810784 +pgontarz 12151 848794 +stocky 11954 699440 +mschmidt 11941 803401 +Maxim 11543 836024 +infinity 11470 727027 +torbjo 11395 729145 +Thomas A. Anderson 11372 732094 +savage84 11358 670860 +d64 11263 789184 +MooTheCow 11237 720174 +snicolet 11106 869170 +ali-al-zhrani 11086 767926 +AndreasKrug 10875 887457 +pirt 10806 836519 +basepi 10637 744851 +michaelrpg 10508 739039 +dzjp 10343 732529 +aga 10302 622975 +ols 10259 570669 +lbraesch 10252 647825 +FormazChar 10059 757283 diff --git a/Fairy-Stockfish-master/appveyor.yml b/Fairy-Stockfish-master/appveyor.yml new file mode 100644 index 0000000000000000000000000000000000000000..d6f5226e8490658f01737e36c51b3e14d7f67312 --- /dev/null +++ b/Fairy-Stockfish-master/appveyor.yml @@ -0,0 +1,91 @@ +version: 1.0.{build} +clone_depth: 50 + +branches: + only: + - master + - merge + - appveyor + +# Operating system (build VM template) +os: Visual Studio 2019 + +# Build platform, i.e. x86, x64, AnyCPU. This setting is optional. +platform: + - x86 + - x64 + +# build Configuration, i.e. Debug, Release, etc. +configuration: + - Debug + - Release + +matrix: + # The build fail immediately once one of the job fails + fast_finish: true + +# Scripts that are called at very beginning, before repo cloning +init: + - cmake --version + - msbuild /version + +before_build: + - ps: | + # Get sources + $src = get-childitem -Path *.cpp -Recurse -Exclude pyffish.cpp,ffishjs.cpp | select -ExpandProperty FullName + $src = $src -join ' ' + $src = $src.Replace("\", "/") + + # Build CMakeLists.txt + $t = 'cmake_minimum_required(VERSION 3.17)', + 'project(Stockfish)', + 'set(CMAKE_CXX_STANDARD 17)', + 'set(CMAKE_CXX_STANDARD_REQUIRED ON)', + 'set (CMAKE_CXX_EXTENSIONS OFF)', + 'set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/src)', + 'set(source_files', $src, ')', + 'add_compile_definitions(NNUE_EMBEDDING_OFF)', + 'add_executable(stockfish ${source_files})' + + # Write CMakeLists.txt without BOM + $MyPath = (Get-Item -Path "." -Verbose).FullName + '\CMakeLists.txt' + $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False + [System.IO.File]::WriteAllLines($MyPath, $t, $Utf8NoBomEncoding) + + # Obtain bench reference from git log + $b = git log HEAD | sls "\b[Bb]ench[ :]+[0-9]{7}" | select -first 1 + $bench = $b -match '\D+(\d+)' | % { $matches[1] } + Write-Host "Reference bench:" $bench + $g = "Visual Studio 16 2019" + If (${env:PLATFORM} -eq 'x64') { $a = "x64" } + If (${env:PLATFORM} -eq 'x86') { $a = "Win32" } + cmake -G "${g}" -A ${a} . + Write-Host "Generated files for: " $g $a + +build_script: + - cmake --build . --config %CONFIGURATION% -- /verbosity:minimal + - ps: | + # Download default NNUE net from fishtest + $nnuenet = Get-Content -Path src\evaluate.h | Select-String -CaseSensitive -Pattern "EvalFileDefaultName" | Select-String -CaseSensitive -Pattern "nn-[a-z0-9]{12}.nnue" + $dummy = $nnuenet -match "(?nn-[a-z0-9]{12}.nnue)" + $nnuenet = $Matches.nnuenet + Write-Host "Default net:" $nnuenet + $nnuedownloadurl = "https://github.com/official-stockfish/networks/raw/master/$nnuenet" + $nnuefilepath = "src\${env:CONFIGURATION}\$nnuenet" + if (Test-Path -Path $nnuefilepath) { + Write-Host "Already available." + } else { + Write-Host "Downloading $nnuedownloadurl to $nnuefilepath" + Invoke-WebRequest -Uri $nnuedownloadurl -OutFile $nnuefilepath + } + +before_test: + - cd src/%CONFIGURATION% + - stockfish bench 2> out.txt >NUL + - ps: | + # Verify bench number + $s = (gc "./out.txt" | out-string) + $r = ($s -match 'Nodes searched \D+(\d+)' | % { $matches[1] }) + Write-Host "Engine bench:" $r + Write-Host "Reference bench:" $bench + If ($r -ne $bench) { exit 1 } diff --git a/Fairy-Stockfish-master/pyffish.pyi b/Fairy-Stockfish-master/pyffish.pyi new file mode 100644 index 0000000000000000000000000000000000000000..c955f22b7ecbe379a317e800efa27d9adb472b48 --- /dev/null +++ b/Fairy-Stockfish-master/pyffish.pyi @@ -0,0 +1,39 @@ +from typing import Any + +FEN_OK: int +NOTATION_DEFAULT: int +NOTATION_JANGGI: int +NOTATION_LAN: int +NOTATION_SAN: int +NOTATION_SHOGI_HODGES: int +NOTATION_SHOGI_HODGES_NUMBER: int +NOTATION_SHOGI_HOSKING: int +NOTATION_THAI_LAN: int +NOTATION_THAI_SAN: int +NOTATION_XIANGQI_WXF: int +VALUE_DRAW: int +VALUE_MATE: int + +class error(Exception): ... + +def version() -> tuple[int, int, int]: ... +def info() -> str: ... +def variants() -> list[str]: ... +def set_option(name: str, value: Any) -> None: ... +def load_variant_config(config: str) -> None: ... +def start_fen(variant: str) -> str: ... +def two_boards(variant: str) -> bool: ... +def captures_to_hand(variant: str) -> bool: ... +def get_san(variant: str, fen: str, move: str, chess960: bool = False, notation: int = NOTATION_DEFAULT) -> str: ... +def get_san_moves(variant: str, fen: str, movelist: list[str], chess960: bool = False, notation: int = NOTATION_DEFAULT) -> list[str]: ... +def legal_moves(variant: str, fen: str, movelist: list[str], chess960: bool = False) -> list[str]: ... +def get_fen(variant: str, fen: str, movelist: list[str], chess960: bool = False, sfen: bool = False, show_promoted: bool = False, count_started: int = 0) -> str: ... +def gives_check(variant: str, fen: str, movelist: list[str], chess960: bool = False) -> bool: ... +def is_capture(variant: str, fen: str, movelist: list[str], move: str, chess960: bool = False) -> bool: ... +def piece_to_partner(variant: str, fen: str, movelist: list[str], chess960: bool = False) -> str: ... +def game_result(variant: str, fen: str, movelist: list[str], chess960: bool = False) -> int: ... +def is_immediate_game_end(variant: str, fen: str, movelist: list[str], chess960: bool = False) -> tuple[bool, int]: ... +def is_optional_game_end(variant: str, fen: str, movelist: list[str], chess960: bool = False, count_started: int = 0) -> tuple[bool, int]: ... +def has_insufficient_material(variant: str, fen: str, movelist: list[str], chess960: bool = False) -> tuple[bool, bool]: ... +def validate_fen(fen: str, variant: str, chess960: bool = False) -> int: ... +def get_fog_fen(fen: str, variant: str, chess960: bool = False) -> str: ... diff --git a/Fairy-Stockfish-master/setup.py b/Fairy-Stockfish-master/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..5e1f4967b8c1d5147ccf4f9edc040e7a33ffc2f1 --- /dev/null +++ b/Fairy-Stockfish-master/setup.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- + +from setuptools import setup, Extension +from glob import glob +import platform +import io +import os + + +if platform.python_compiler().startswith("MSC"): + args = ["/std:c++17"] +else: + args = ["-std=c++17", "-flto", "-Wno-date-time"] + +args.extend(["-DLARGEBOARDS", "-DALLVARS", "-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 or later (GPLv3+)", + "Programming Language :: Python :: 3", + "Operating System :: OS Independent", +] + +with io.open("README.md", "r", encoding="utf8") as fh: + long_description = fh.read().strip() + +sources = glob("src/*.cpp") + glob("src/syzygy/*.cpp") + glob("src/nnue/*.cpp") + glob("src/nnue/features/*.cpp") +headers = glob("src/*.h") + glob("src/syzygy/*.h") + glob("src/nnue/*.h") + glob("src/nnue/features/*.h") +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, + depends=headers, + extra_compile_args=args) + +setup(name="pyffish", version="0.0.89", + 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], + data_files=[("", ["pyffish.pyi"])] + ) diff --git a/Fairy-Stockfish-master/src/Makefile b/Fairy-Stockfish-master/src/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..823649fb389b778d867befd6e7b809bd5fe37f8e --- /dev/null +++ b/Fairy-Stockfish-master/src/Makefile @@ -0,0 +1,982 @@ +# Stockfish, a UCI chess playing engine derived from Glaurung 2.1 +# Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) +# +# Stockfish is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Stockfish is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +### ========================================================================== +### Section 1. General Configuration +### ========================================================================== + +### Executable name +ifeq ($(COMP),mingw) +EXE = stockfish.exe +else +EXE = stockfish +endif + +### Installation dir definitions +PREFIX = /usr/local +BINDIR = $(PREFIX)/bin + +### Built-in benchmark for pgo-builds +ifeq ($(SDE_PATH),) + PGOBENCH = ./$(EXE) bench +else + PGOBENCH = $(SDE_PATH) -- ./$(EXE) bench +endif + +### 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 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_ka_v2.cpp \ + partner.cpp parser.cpp piece.cpp variant.cpp xboard.cpp \ + nnue/features/half_ka_v2_variants.cpp + +OBJS = $(notdir $(SRCS:.cpp=.o)) + +VPATH = syzygy:nnue:nnue/features + +### Establish the operating system name +KERNEL = $(shell uname -s) +ifeq ($(KERNEL),Linux) + OS = $(shell uname -o) +endif + +### ========================================================================== +### Section 2. High-level Configuration +### ========================================================================== +# +# flag --- Comp switch --- Description +# ---------------------------------------------------------------------------- +# +# debug = yes/no --- -DNDEBUG --- Enable/Disable debug mode +# sanitize = none/ ... (-fsanitize ) +# --- ( undefined ) --- enable undefined behavior checks +# --- ( thread ) --- enable threading error checks +# --- ( address ) --- enable memory access checks +# --- ...etc... --- see compiler documentation for supported sanitizers +# optimize = yes/no --- (-O3/-fast etc.) --- Enable/Disable optimizations +# arch = (name) --- (-arch) --- Target architecture +# bits = 64/32 --- -DIS_64BIT --- 64-/32-bit operating system +# prefetch = yes/no --- -DUSE_PREFETCH --- Use prefetch asm-instruction +# popcnt = yes/no --- -DUSE_POPCNT --- Use popcnt asm-instruction +# pext = yes/no --- -DUSE_PEXT --- Use pext x86_64 asm-instruction +# sse = yes/no --- -msse --- Use Intel Streaming SIMD Extensions +# mmx = yes/no --- -mmmx --- Use Intel MMX instructions +# sse2 = yes/no --- -msse2 --- Use Intel Streaming SIMD Extensions 2 +# ssse3 = yes/no --- -mssse3 --- Use Intel Supplemental Streaming SIMD Extensions 3 +# sse41 = yes/no --- -msse4.1 --- Use Intel Streaming SIMD Extensions 4.1 +# avx2 = yes/no --- -mavx2 --- Use Intel Advanced Vector Extensions 2 +# avx512 = yes/no --- -mavx512bw --- Use Intel Advanced Vector Extensions 512 +# vnni256 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 256 +# vnni512 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 512 +# neon = yes/no --- -DUSE_NEON --- Use ARM SIMD architecture +# +# Note that Makefile is space sensitive, so when adding new architectures +# or modifying existing flags, you have to make sure there are no extra spaces +# at the end of the line for flag values. +# +# Example of use for these flags: +# make build ARCH=x86-64-avx512 debug=on sanitize="address undefined" + + +### 2.1. General and architecture defaults +largeboards = no +all = no +precomputedmagics = yes +nnue = no +load_net = $(if $(filter $(nnue),yes),net) + +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 e2k \ + armv7 armv7-neon armv8 apple-silicon general-64 general-32)) + SUPPORTED_ARCH=true +else + SUPPORTED_ARCH=false +endif + +optimize = yes +debug = no +sanitize = none +bits = 64 +prefetch = no +popcnt = no +pext = no +sse = no +mmx = no +sse2 = no +ssse3 = no +sse41 = no +avx2 = no +avx512 = no +vnni256 = no +vnni512 = no +neon = no +STRIP = strip + +### 2.2 Architecture specific + +ifeq ($(findstring x86,$(ARCH)),x86) + +# x86-32/64 + +ifeq ($(findstring x86-32,$(ARCH)),x86-32) + arch = i386 + bits = 32 + sse = yes + mmx = yes +else + arch = x86_64 + sse = yes + sse2 = yes +endif + +ifeq ($(findstring -sse,$(ARCH)),-sse) + sse = yes +endif + +ifeq ($(findstring -popcnt,$(ARCH)),-popcnt) + popcnt = yes +endif + +ifeq ($(findstring -mmx,$(ARCH)),-mmx) + mmx = yes +endif + +ifeq ($(findstring -sse2,$(ARCH)),-sse2) + sse = yes + sse2 = yes +endif + +ifeq ($(findstring -ssse3,$(ARCH)),-ssse3) + sse = yes + sse2 = yes + ssse3 = yes +endif + +ifeq ($(findstring -sse41,$(ARCH)),-sse41) + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes +endif + +ifeq ($(findstring -modern,$(ARCH)),-modern) + popcnt = yes + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes +endif + +ifeq ($(findstring -avx2,$(ARCH)),-avx2) + popcnt = yes + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes + avx2 = yes +endif + +ifeq ($(findstring -bmi2,$(ARCH)),-bmi2) + popcnt = yes + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes + avx2 = yes + pext = yes +endif + +ifeq ($(findstring -avx512,$(ARCH)),-avx512) + popcnt = yes + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes + avx2 = yes + pext = yes + avx512 = yes +endif + +ifeq ($(findstring -vnni256,$(ARCH)),-vnni256) + popcnt = yes + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes + avx2 = yes + pext = yes + vnni256 = yes +endif + +ifeq ($(findstring -vnni512,$(ARCH)),-vnni512) + popcnt = yes + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes + avx2 = yes + pext = yes + avx512 = yes + vnni512 = yes +endif + +ifeq ($(sse),yes) + prefetch = yes +endif + +# 64-bit pext is not available on x86-32 +ifeq ($(bits),32) + pext = no +endif + +else + +# all other architectures + +ifeq ($(ARCH),general-32) + arch = any + bits = 32 +endif + +ifeq ($(ARCH),general-64) + arch = any +endif + +ifeq ($(ARCH),armv7) + arch = armv7 + prefetch = yes + bits = 32 +endif + +ifeq ($(ARCH),armv7-neon) + arch = armv7 + prefetch = yes + popcnt = yes + neon = yes + bits = 32 +endif + +ifeq ($(ARCH),armv8) + arch = armv8 + prefetch = yes + popcnt = yes + neon = yes +endif + +ifeq ($(ARCH),apple-silicon) + arch = arm64 + prefetch = yes + popcnt = yes + neon = yes +endif + +ifeq ($(ARCH),ppc-32) + arch = ppc + bits = 32 +endif + +ifeq ($(ARCH),ppc-64) + arch = ppc64 + popcnt = yes + prefetch = yes +endif + +ifeq ($(findstring e2k,$(ARCH)),e2k) + arch = e2k + mmx = yes + bits = 64 + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes + popcnt = yes +endif + +endif + +# Disable precomputed magics when 64-bit PEXT is available +ifeq ($(pext),yes) + precomputedmagics = no +endif + +### ========================================================================== +### Section 3. Low-level Configuration +### ========================================================================== + +### 3.1 Selecting compiler (default = gcc) +CXXFLAGS += -Wall -Wcast-qual -fno-exceptions -std=c++17 $(EXTRACXXFLAGS) +DEPENDFLAGS += -std=c++17 +LDFLAGS += $(EXTRALDFLAGS) + +# Ignore warning due to missing profile information from unused NNUE features +CXXFLAGS += -Wno-profile-instr-out-of-date + +# 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),no) + CXXFLAGS += -DNNUE_EMBEDDING_OFF +endif + +# Enable all variants, even heavyweight ones like duck and amazons +ifneq ($(all),no) + CXXFLAGS += -DALLVARS +endif + +ifeq ($(COMP),) + COMP=gcc +endif + +ifeq ($(COMP),gcc) + comp=gcc + CXX=g++ + CXXFLAGS += -Wextra -Wshadow + ifeq ($(largeboards),no) + CXXFLAGS += -pedantic + endif + + ifeq ($(arch),$(filter $(arch),armv7 armv8)) + ifeq ($(OS),Android) + CXXFLAGS += -m$(bits) + LDFLAGS += -m$(bits) + endif + else + CXXFLAGS += -m$(bits) + LDFLAGS += -m$(bits) + endif + + ifeq ($(arch),$(filter $(arch),armv7)) + LDFLAGS += -latomic + endif + + ifneq ($(KERNEL),Darwin) + LDFLAGS += -Wl,--no-as-needed + endif +endif + +ifeq ($(COMP),mingw) + comp=mingw + + ifeq ($(KERNEL),Linux) + ifeq ($(bits),64) + ifeq ($(shell which x86_64-w64-mingw32-c++-posix),) + CXX=x86_64-w64-mingw32-c++ + else + CXX=x86_64-w64-mingw32-c++-posix + endif + else + ifeq ($(shell which i686-w64-mingw32-c++-posix),) + CXX=i686-w64-mingw32-c++ + else + CXX=i686-w64-mingw32-c++-posix + endif + endif + else + CXX=g++ + endif + + CXXFLAGS += -Wextra -Wshadow + LDFLAGS += -static +endif + +ifeq ($(COMP),icc) + comp=icc + CXX=icpc + CXXFLAGS += -diag-disable 1476,10120 -Wcheck -Wabi -Wdeprecated -strict-ansi +endif + +ifeq ($(COMP),clang) + comp=clang + CXX=clang++ + CXXFLAGS += -pedantic -Wextra -Wshadow + + ifneq ($(KERNEL),Darwin) + ifneq ($(KERNEL),OpenBSD) + ifneq ($(KERNEL),FreeBSD) + LDFLAGS += -latomic + endif + endif + endif + + ifeq ($(arch),$(filter $(arch),armv7 armv8)) + ifeq ($(OS),Android) + CXXFLAGS += -m$(bits) + LDFLAGS += -m$(bits) + endif + else + CXXFLAGS += -m$(bits) + LDFLAGS += -m$(bits) + endif +endif + +ifeq ($(KERNEL),Darwin) + CXXFLAGS += -arch $(arch) -mmacosx-version-min=10.14 + LDFLAGS += -arch $(arch) -mmacosx-version-min=10.14 + XCRUN = xcrun +endif + +# To cross-compile for Android, NDK version r21 or later is recommended. +# In earlier NDK versions, you'll need to pass -fno-addrsig if using GNU binutils. +# Currently we don't know how to make PGO builds with the NDK yet. +ifeq ($(COMP),ndk) + CXXFLAGS += -stdlib=libc++ -fPIE + comp=clang + ifeq ($(arch),armv7) + CXX=armv7a-linux-androideabi16-clang++ + CXXFLAGS += -mthumb -march=armv7-a -mfloat-abi=softfp -mfpu=neon + STRIP=arm-linux-androideabi-strip + endif + ifeq ($(arch),armv8) + CXX=aarch64-linux-android21-clang++ + STRIP=aarch64-linux-android-strip + endif + LDFLAGS += -static-libstdc++ -pie -lm -latomic +endif + +ifeq ($(comp),icc) + profile_make = icc-profile-make + profile_use = icc-profile-use +else ifeq ($(comp),clang) + profile_make = clang-profile-make + profile_use = clang-profile-use +else + profile_make = gcc-profile-make + profile_use = gcc-profile-use +endif + +### Travis CI script uses COMPILER to overwrite CXX +ifdef COMPILER + COMPCXX=$(COMPILER) +endif + +### Allow overwriting CXX from command line +ifdef COMPCXX + CXX=$(COMPCXX) +endif + +### Sometimes gcc is really clang +ifeq ($(COMP),gcc) + gccversion = $(shell $(CXX) --version) + gccisclang = $(findstring clang,$(gccversion)) + ifneq ($(gccisclang),) + profile_make = clang-profile-make + profile_use = clang-profile-use + endif +endif + +### On mingw use Windows threads, otherwise POSIX +ifneq ($(comp),mingw) + CXXFLAGS += -DUSE_PTHREADS + # On Android Bionic's C library comes with its own pthread implementation bundled in + ifneq ($(OS),Android) + # Haiku has pthreads in its libroot, so only link it in on other platforms + ifneq ($(KERNEL),Haiku) + ifneq ($(COMP),ndk) + LDFLAGS += -lpthread + endif + endif + endif +endif + +### 3.2.1 Debugging +ifeq ($(debug),no) + CXXFLAGS += -DNDEBUG +else + CXXFLAGS += -g +endif + +### 3.2.2 Debugging with undefined behavior sanitizers +ifneq ($(sanitize),none) + CXXFLAGS += -g3 $(addprefix -fsanitize=,$(sanitize)) + LDFLAGS += $(addprefix -fsanitize=,$(sanitize)) +endif + +### 3.3 Optimization +ifeq ($(optimize),yes) + + CXXFLAGS += -O3 -fno-strict-aliasing + + ifeq ($(comp),gcc) + ifeq ($(OS), Android) + CXXFLAGS += -fno-gcse -mthumb -march=armv7-a -mfloat-abi=softfp + endif + endif + + ifeq ($(comp),$(filter $(comp),gcc clang icc)) + ifeq ($(KERNEL),Darwin) + CXXFLAGS += -mdynamic-no-pic + endif + endif + + ifeq ($(comp),clang) + clangmajorversion = $(shell $(CXX) -dumpversion 2>/dev/null | cut -f1 -d.) + ifeq ($(shell expr $(clangmajorversion) \< 16),1) + CXXFLAGS += -fexperimental-new-pass-manager + endif + endif +endif + +### 3.4 Bits +ifeq ($(bits),64) + CXXFLAGS += -DIS_64BIT +endif + +### 3.5 prefetch +ifeq ($(prefetch),yes) + ifeq ($(sse),yes) + CXXFLAGS += -msse + endif +else + CXXFLAGS += -DNO_PREFETCH +endif + +### 3.6 popcnt +ifeq ($(popcnt),yes) + ifeq ($(arch),$(filter $(arch),ppc64 armv7 armv8 arm64)) + CXXFLAGS += -DUSE_POPCNT + else ifeq ($(comp),icc) + CXXFLAGS += -msse3 -DUSE_POPCNT + else + CXXFLAGS += -msse3 -mpopcnt -DUSE_POPCNT + endif +endif + +ifeq ($(avx2),yes) + CXXFLAGS += -DUSE_AVX2 + ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + CXXFLAGS += -mavx2 -mbmi + endif +endif + +ifeq ($(avx512),yes) + CXXFLAGS += -DUSE_AVX512 + ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + CXXFLAGS += -mavx512f -mavx512bw + endif +endif + +ifeq ($(vnni256),yes) + CXXFLAGS += -DUSE_VNNI + ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + CXXFLAGS += -mavx512f -mavx512bw -mavx512vnni -mavx512dq -mavx512vl -mprefer-vector-width=256 + endif +endif + +ifeq ($(vnni512),yes) + CXXFLAGS += -DUSE_VNNI + ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + CXXFLAGS += -mavx512vnni -mavx512dq -mavx512vl + endif +endif + +ifeq ($(sse41),yes) + CXXFLAGS += -DUSE_SSE41 + ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + CXXFLAGS += -msse4.1 + endif +endif + +ifeq ($(ssse3),yes) + CXXFLAGS += -DUSE_SSSE3 + ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + CXXFLAGS += -mssse3 + endif +endif + +ifeq ($(sse2),yes) + CXXFLAGS += -DUSE_SSE2 + ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + CXXFLAGS += -msse2 + endif +endif + +ifeq ($(mmx),yes) + CXXFLAGS += -DUSE_MMX + ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + CXXFLAGS += -mmmx + endif +endif + +ifeq ($(neon),yes) + CXXFLAGS += -DUSE_NEON + ifeq ($(KERNEL),Linux) + ifneq ($(COMP),ndk) + ifneq ($(arch),armv8) + CXXFLAGS += -mfpu=neon + endif + endif + endif +endif + +### 3.7 pext +ifeq ($(pext),yes) + CXXFLAGS += -DUSE_PEXT + ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + CXXFLAGS += -mbmi2 + endif +endif + +### 3.8 Link Time Optimization +### This is a mix of compile and link time options because the lto link phase +### needs access to the optimization flags. +ifeq ($(optimize),yes) +ifeq ($(debug), no) + ifeq ($(comp),clang) + CXXFLAGS += -flto + ifneq ($(findstring MINGW,$(KERNEL)),) + CXXFLAGS += -fuse-ld=lld + else ifneq ($(findstring MSYS,$(KERNEL)),) + CXXFLAGS += -fuse-ld=lld + endif + LDFLAGS += $(CXXFLAGS) + +# GCC and CLANG use different methods for parallelizing LTO and CLANG pretends to be +# GCC on some systems. + else ifeq ($(comp),gcc) + ifeq ($(gccisclang),) + CXXFLAGS += -flto + LDFLAGS += $(CXXFLAGS) -flto=jobserver + ifneq ($(findstring MINGW,$(KERNEL)),) + LDFLAGS += -save-temps + else ifneq ($(findstring MSYS,$(KERNEL)),) + LDFLAGS += -save-temps + endif + else + CXXFLAGS += -flto + LDFLAGS += $(CXXFLAGS) + endif + +# To use LTO and static linking on windows, the tool chain requires a recent gcc: +# gcc version 10.1 in msys2 or TDM-GCC version 9.2 are known to work, older might not. +# So, only enable it for a cross from Linux by default. + else ifeq ($(comp),mingw) + ifeq ($(KERNEL),Linux) + ifneq ($(arch),i386) + CXXFLAGS += -flto + LDFLAGS += $(CXXFLAGS) -flto=jobserver + endif + endif + endif +endif +endif + +### 3.9 Android 5 can only run position independent executables. Note that this +### breaks Android 4.0 and earlier. +ifeq ($(OS), Android) + CXXFLAGS += -fPIE + LDFLAGS += -fPIE -pie +endif + +### ========================================================================== +### Section 4. Public Targets +### ========================================================================== + + +help: + @echo "" + @echo "To compile stockfish, type: " + @echo "" + @echo "make target ARCH=arch [COMP=compiler] [COMPCXX=cxx]" + @echo "" + @echo "Supported targets:" + @echo "" + @echo "help > Display architecture details" + @echo "build > Standard build" + @echo "net > Download the default nnue net" + @echo "profile-build > Faster build (with profile-guided optimization)" + @echo "strip > Strip executable" + @echo "install > Install executable" + @echo "clean > Clean up" + @echo "" + @echo "Supported archs:" + @echo "" + @echo "x86-64-vnni512 > x86 64-bit with vnni support 512bit wide" + @echo "x86-64-vnni256 > x86 64-bit with vnni support 256bit wide" + @echo "x86-64-avx512 > x86 64-bit with avx512 support" + @echo "x86-64-bmi2 > x86 64-bit with bmi2 support" + @echo "x86-64-avx2 > x86 64-bit with avx2 support" + @echo "x86-64-sse41-popcnt > x86 64-bit with sse41 and popcnt support" + @echo "x86-64-modern > common modern CPU, currently x86-64-sse41-popcnt" + @echo "x86-64-ssse3 > x86 64-bit with ssse3 support" + @echo "x86-64-sse3-popcnt > x86 64-bit with sse3 and popcnt support" + @echo "x86-64 > x86 64-bit generic (with sse2 support)" + @echo "x86-32-sse41-popcnt > x86 32-bit with sse41 and popcnt support" + @echo "x86-32-sse2 > x86 32-bit with sse2 support" + @echo "x86-32 > x86 32-bit generic (with mmx and sse support)" + @echo "ppc-64 > PPC 64-bit" + @echo "ppc-32 > PPC 32-bit" + @echo "armv7 > ARMv7 32-bit" + @echo "armv7-neon > ARMv7 32-bit with popcnt and neon" + @echo "armv8 > ARMv8 64-bit with popcnt and neon" + @echo "e2k > Elbrus 2000" + @echo "apple-silicon > Apple silicon ARM64" + @echo "general-64 > unspecified 64-bit" + @echo "general-32 > unspecified 32-bit" + @echo "" + @echo "Supported compilers:" + @echo "" + @echo "gcc > Gnu compiler (default)" + @echo "mingw > Gnu compiler with MinGW under Windows" + @echo "clang > LLVM Clang compiler" + @echo "icc > Intel compiler" + @echo "ndk > Google NDK to cross-compile for Android" + @echo "" + @echo "Simple examples. If you don't know what to do, you likely want to run: " + @echo "" + @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 looking for performance: " + @echo "" + @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 (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 net objclean profileclean \ + config-sanity icc-profile-use icc-profile-make gcc-profile-use gcc-profile-make \ + clang-profile-use clang-profile-make + +build: $(load_net) config-sanity + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) all + +profile-build: $(load_net) config-sanity objclean profileclean + @echo "" + @echo "Step 1/4. Building instrumented executable ..." + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_make) + @echo "" + @echo "Step 2/4. Running benchmark for pgo-build ..." + $(PGOBENCH) > /dev/null + @echo "" + @echo "Step 3/4. Building optimized executable ..." + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) objclean + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_use) + @echo "" + @echo "Step 4/4. Deleting profile data ..." + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) profileclean + +strip: + $(STRIP) $(EXE) + +install: + -mkdir -p -m 755 $(BINDIR) + -cp $(EXE) $(BINDIR) + -strip $(BINDIR)/$(EXE) + +# clean all +clean: objclean profileclean + @rm -f .depend *~ core + +# evaluation network (nnue) +net: + $(eval nnuenet := $(shell grep EvalFileDefaultName evaluate.h | grep define | sed 's/.*\(nn-[a-z0-9]\{12\}.nnue\).*/\1/')) + @echo "Default net: $(nnuenet)" + $(eval nnuedownloadurl1 := https://tests.stockfishchess.org/api/nn/$(nnuenet)) + $(eval nnuedownloadurl2 := https://github.com/official-stockfish/networks/raw/master/$(nnuenet)) + $(eval curl_or_wget := $(shell if hash curl 2>/dev/null; then echo "curl -skL"; elif hash wget 2>/dev/null; then echo "wget -qO-"; fi)) + @if [ "x$(curl_or_wget)" = "x" ]; then \ + echo "Automatic download failed: neither curl nor wget is installed. Install one of these tools or download the net manually"; exit 1; \ + fi + $(eval shasum_command := $(shell if hash shasum 2>/dev/null; then echo "shasum -a 256 "; elif hash sha256sum 2>/dev/null; then echo "sha256sum "; fi)) + @if [ "x$(shasum_command)" = "x" ]; then \ + echo "shasum / sha256sum not found, skipping net validation"; \ + fi + @for nnuedownloadurl in "$(nnuedownloadurl1)" "$(nnuedownloadurl2)"; do \ + if test -f "$(nnuenet)"; then \ + echo "$(nnuenet) available."; \ + else \ + if [ "x$(curl_or_wget)" != "x" ]; then \ + echo "Downloading $${nnuedownloadurl}"; $(curl_or_wget) $${nnuedownloadurl} > $(nnuenet);\ + fi; \ + fi; \ + if [ "x$(shasum_command)" != "x" ]; then \ + if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \ + echo "Removing failed download"; rm -f $(nnuenet); \ + else \ + echo "Network validated"; break; \ + fi; \ + fi; \ + done + @if ! test -f "$(nnuenet)"; then \ + echo "Failed to download $(nnuenet)."; \ + fi + +# clean binaries and objects +objclean: + @rm -f $(EXE) *.o ./syzygy/*.o ./nnue/*.o ./nnue/features/*.o + +# clean auxiliary profiling files +profileclean: + @rm -rf profdir + @rm -f bench.txt *.gcda *.gcno ./syzygy/*.gcda ./nnue/*.gcda ./nnue/features/*.gcda *.s + @rm -f stockfish.profdata *.profraw + @rm -f stockfish.exe.lto_wrapper_args + @rm -f stockfish.exe.ltrans.out + @rm -f ./-lstdc++.res + +default: + help + +### ========================================================================== +### Section 5. Private Targets +### ========================================================================== + +all: $(EXE) .depend + +config-sanity: $(load_net) + @echo "" + @echo "Config:" + @echo "debug: '$(debug)'" + @echo "sanitize: '$(sanitize)'" + @echo "optimize: '$(optimize)'" + @echo "arch: '$(arch)'" + @echo "bits: '$(bits)'" + @echo "kernel: '$(KERNEL)'" + @echo "os: '$(OS)'" + @echo "prefetch: '$(prefetch)'" + @echo "popcnt: '$(popcnt)'" + @echo "pext: '$(pext)'" + @echo "sse: '$(sse)'" + @echo "mmx: '$(mmx)'" + @echo "sse2: '$(sse2)'" + @echo "ssse3: '$(ssse3)'" + @echo "sse41: '$(sse41)'" + @echo "avx2: '$(avx2)'" + @echo "avx512: '$(avx512)'" + @echo "vnni256: '$(vnni256)'" + @echo "vnni512: '$(vnni512)'" + @echo "neon: '$(neon)'" + @echo "" + @echo "Fairy-Stockfish specific:" + @echo "largeboards: '$(largeboards)'" + @echo "all: '$(all)'" + @echo "precomputedmagics: '$(precomputedmagics)'" + @echo "nnue: '$(nnue)'" + @echo "" + @echo "Flags:" + @echo "CXX: $(CXX)" + @echo "CXXFLAGS: $(CXXFLAGS)" + @echo "LDFLAGS: $(LDFLAGS)" + @echo "" + @echo "Testing config sanity. If this fails, try 'make help' ..." + @echo "" + @test "$(debug)" = "yes" || test "$(debug)" = "no" + @test "$(optimize)" = "yes" || test "$(optimize)" = "no" + @test "$(SUPPORTED_ARCH)" = "true" + @test "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \ + test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || test "$(arch)" = "e2k" || \ + test "$(arch)" = "armv7" || test "$(arch)" = "armv8" || test "$(arch)" = "arm64" + @test "$(bits)" = "32" || test "$(bits)" = "64" + @test "$(prefetch)" = "yes" || test "$(prefetch)" = "no" + @test "$(popcnt)" = "yes" || test "$(popcnt)" = "no" + @test "$(pext)" = "yes" || test "$(pext)" = "no" + @test "$(sse)" = "yes" || test "$(sse)" = "no" + @test "$(mmx)" = "yes" || test "$(mmx)" = "no" + @test "$(sse2)" = "yes" || test "$(sse2)" = "no" + @test "$(ssse3)" = "yes" || test "$(ssse3)" = "no" + @test "$(sse41)" = "yes" || test "$(sse41)" = "no" + @test "$(avx2)" = "yes" || test "$(avx2)" = "no" + @test "$(avx512)" = "yes" || test "$(avx512)" = "no" + @test "$(vnni256)" = "yes" || test "$(vnni256)" = "no" + @test "$(vnni512)" = "yes" || test "$(vnni512)" = "no" + @test "$(neon)" = "yes" || test "$(neon)" = "no" + @test "$(comp)" = "gcc" || test "$(comp)" = "icc" || test "$(comp)" = "mingw" || test "$(comp)" = "clang" \ + || test "$(comp)" = "armv7a-linux-androideabi16-clang" || test "$(comp)" = "aarch64-linux-android21-clang" + +$(EXE): $(OBJS) + +$(CXX) -o $@ $(OBJS) $(LDFLAGS) + +clang-profile-make: + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ + EXTRACXXFLAGS='-fprofile-instr-generate ' \ + EXTRALDFLAGS=' -fprofile-instr-generate' \ + all + +clang-profile-use: + $(XCRUN) llvm-profdata merge -output=stockfish.profdata *.profraw + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ + EXTRACXXFLAGS='-fprofile-instr-use=stockfish.profdata' \ + EXTRALDFLAGS='-fprofile-use ' \ + all + +gcc-profile-make: + @mkdir -p profdir + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ + EXTRACXXFLAGS='-fprofile-generate=profdir' \ + EXTRALDFLAGS='-lgcov' \ + all + +gcc-profile-use: + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ + EXTRACXXFLAGS='-fprofile-use=profdir -fno-peel-loops -fno-tracer' \ + EXTRALDFLAGS='-lgcov' \ + all + +icc-profile-make: + @mkdir -p profdir + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ + EXTRACXXFLAGS='-prof-gen=srcpos -prof_dir ./profdir' \ + all + +icc-profile-use: + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ + EXTRACXXFLAGS='-prof_use -prof_dir ./profdir' \ + all + +.depend: + -@$(CXX) $(DEPENDFLAGS) -MM $(SRCS) > $@ 2> /dev/null + +-include .depend diff --git a/Fairy-Stockfish-master/src/Makefile_js b/Fairy-Stockfish-master/src/Makefile_js new file mode 100644 index 0000000000000000000000000000000000000000..a224a71d0d872b90711d62acc738498154b35210 --- /dev/null +++ b/Fairy-Stockfish-master/src/Makefile_js @@ -0,0 +1,95 @@ +# ffish.js, a JavaScript chess variant library derived from Fairy-Stockfish +# Copyright (C) 2022 Fabian Fichter, Johannes Czech +# +# ffish.js is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# ffish.js is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +EXE = ../tests/js/ffish.js + +SRCS = ffishjs.cpp benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.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_ka_v2.cpp \ + partner.cpp parser.cpp piece.cpp variant.cpp xboard.cpp \ + nnue/features/half_ka_v2_variants.cpp + +CXX=emcc +CXXFLAGS += --bind -DNNUE_EMBEDDING_OFF -DNO_THREADS -std=c++17 -Wall + +largeboards = yes +all = yes +optimize = yes +debug = no + +### Debugging +ifeq ($(debug),no) + CXXFLAGS += -DNDEBUG -s ASSERTIONS=0 -s SAFE_HEAP=0 +else + CXXFLAGS += -g -s ASSERTIONS=1 -s SAFE_HEAP=1 +endif + +### Optimization +ifeq ($(optimize),yes) + CXXFLAGS += -O3 +endif + +# Compile version with support for large board variants +# Use precomputed magics by default +ifneq ($(largeboards),no) + CXXFLAGS += -DLARGEBOARDS -DPRECOMPUTED_MAGICS -s TOTAL_MEMORY=32MB -s ALLOW_MEMORY_GROWTH=1 -s WASM_MEM_MAX=1GB +endif + +# Enable all variants, even heavyweight ones like duck and amazons +ifneq ($(all),no) + CXXFLAGS += -DALLVARS +endif + +### Compile as ES6/ES2015 module +ifeq ($(es6),yes) + CXXFLAGS += -s ENVIRONMENT='web,worker' -s EXPORT_ES6=1 -s MODULARIZE=1 -s USE_ES6_IMPORT_META=0 +endif + +.PHONY: help objclean clean build deps test serve + +help: + @echo "" + @echo "To compile ffishjs, type: " + @echo "" + @echo "make -f Makefile_js build" + @echo "" + @echo "Supported targets:" + @echo "" + @echo "help > Display this help" + @echo "build > Standard build" + @echo "clean > Clean up" + @echo "deps > Install runtime dependencies using npm" + @echo "test > Run tests" + @echo "serve > Run example server" + @echo "" + +objclean: + @rm -f $(EXE) + +clean: objclean + +build: + $(CXX) $(CXXFLAGS) $(SRCS) -o $(EXE) + +deps: + cd ../tests/js && npm install + +test: deps + cd ../tests/js && npm test + +serve: deps + cd ../tests/js && node index.js diff --git a/Fairy-Stockfish-master/src/apiutil.h b/Fairy-Stockfish-master/src/apiutil.h new file mode 100644 index 0000000000000000000000000000000000000000..c898cf2ca529dc867d55d43a5a019bbbe96cdc41 --- /dev/null +++ b/Fairy-Stockfish-master/src/apiutil.h @@ -0,0 +1,1165 @@ +/* + Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish + Copyright (C) 2018-2022 Fabian Fichter + + Fairy-Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Fairy-Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef APIUTIL_H_INCLUDED +#define APIUTIL_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include + +#include "types.h" +#include "position.h" +#include "variant.h" + +namespace Stockfish { + +enum Notation { + NOTATION_DEFAULT, + // https://en.wikipedia.org/wiki/Algebraic_notation_(chess) + NOTATION_SAN, + NOTATION_LAN, + // https://en.wikipedia.org/wiki/Shogi_notation#Western_notation + NOTATION_SHOGI_HOSKING, // Examples: P76, S’34 + NOTATION_SHOGI_HODGES, // Examples: P-7f, S*3d + NOTATION_SHOGI_HODGES_NUMBER, // Examples: P-76, S*34 + // http://www.janggi.pl/janggi-notation/ + NOTATION_JANGGI, + // https://en.wikipedia.org/wiki/Xiangqi#Notation + NOTATION_XIANGQI_WXF, + // https://web.archive.org/web/20180817205956/http://bgsthai.com/2018/05/07/lawofthaichessc/ + NOTATION_THAI_SAN, + NOTATION_THAI_LAN, +}; + +inline Notation default_notation(const Variant* v) { + if (v->variantTemplate == "shogi") + return NOTATION_SHOGI_HODGES_NUMBER; + return NOTATION_SAN; +} + +enum Termination { + ONGOING, + CHECKMATE, + STALEMATE, + INSUFFICIENT_MATERIAL, + N_MOVE_RULE, + N_FOLD_REPETITION, + VARIANT_END, +}; + +const std::array THAI_FILES = {"ก", "ข", "ค", "ง", "จ", "ฉ", "ช", "ญ", "ต", "ถ", "ธ", "น"}; +const std::array THAI_RANKS = {"๑", "๒", "๓", "๔", "๕", "๖", "๗", "๘", "๙", "๑๐", "๑๑", "๑๒"}; + +namespace SAN { + +enum Disambiguation { + NO_DISAMBIGUATION, + FILE_DISAMBIGUATION, + RANK_DISAMBIGUATION, + SQUARE_DISAMBIGUATION, +}; + +inline bool is_shogi(Notation n) { + return n == NOTATION_SHOGI_HOSKING || n == NOTATION_SHOGI_HODGES || n == NOTATION_SHOGI_HODGES_NUMBER; +} + +inline bool is_thai(Notation n) { + return n == NOTATION_THAI_SAN || n == NOTATION_THAI_LAN; +} + +// is there more than one file with a pair of pieces? +inline bool multi_tandem(Bitboard b) { + int tandems = 0; + for (File f = FILE_A; f <= FILE_MAX; ++f) + if (more_than_one(b & file_bb(f))) + tandems++; + return tandems >= 2; +} + +inline std::string piece_to_thai_char(Piece pc, bool promoted) { + switch(type_of(pc)) { + case KING: + return "ข"; + case KHON: + return "ค"; + case FERS: + return promoted ? "ง" : "ม็"; + case KNIGHT: + return "ม"; + case ROOK: + return "ร"; + case PAWN: + return "บ"; + case AIWOK: + return "ว"; + default: + return "X"; + } +} + +inline std::string piece(const Position& pos, Move m, Notation n) { + Color us = pos.side_to_move(); + Square from = from_sq(m); + Piece pc = pos.moved_piece(m); + PieceType pt = type_of(pc); + // Quiet pawn moves + if ((n == NOTATION_SAN || n == NOTATION_LAN || n == NOTATION_THAI_SAN) && type_of(pc) == PAWN && type_of(m) != DROP) + return ""; + // Tandem pawns + else if (n == NOTATION_XIANGQI_WXF && popcount(pos.pieces(us, pt) & file_bb(from)) >= 3 - multi_tandem(pos.pieces(us, pt))) + return std::to_string(popcount(forward_file_bb(us, from) & pos.pieces(us, pt)) + 1); + // Moves of promoted pieces + else if (is_shogi(n) && type_of(m) != DROP && pos.unpromoted_piece_on(from)) + return "+" + std::string(1, toupper(pos.piece_to_char()[pos.unpromoted_piece_on(from)])); + // Promoted drops + else if (is_shogi(n) && type_of(m) == DROP && dropped_piece_type(m) != in_hand_piece_type(m)) + return "+" + std::string(1, toupper(pos.piece_to_char()[in_hand_piece_type(m)])); + else if (is_thai(n)) + return piece_to_thai_char(pc, pos.is_promoted(from)); + else if (pos.piece_to_char_synonyms()[pc] != ' ') + return std::string(1, toupper(pos.piece_to_char_synonyms()[pc])); + else + return std::string(1, toupper(pos.piece_to_char()[pc])); +} + +inline std::string file(const Position& pos, Square s, Notation n) { + switch (n) + { + case NOTATION_SHOGI_HOSKING: + case NOTATION_SHOGI_HODGES: + case NOTATION_SHOGI_HODGES_NUMBER: + return std::to_string(pos.max_file() - file_of(s) + 1); + case NOTATION_JANGGI: + return std::to_string(file_of(s) + 1); + case NOTATION_XIANGQI_WXF: + return std::to_string((pos.side_to_move() == WHITE ? pos.max_file() - file_of(s) : file_of(s)) + 1); + case NOTATION_THAI_SAN: + case NOTATION_THAI_LAN: + return THAI_FILES[file_of(s)]; + default: + return std::string(1, char('a' + file_of(s))); + } +} + +inline std::string rank(const Position& pos, Square s, Notation n) { + switch (n) + { + case NOTATION_SHOGI_HOSKING: + case NOTATION_SHOGI_HODGES_NUMBER: + return std::to_string(pos.max_rank() - rank_of(s) + 1); + case NOTATION_SHOGI_HODGES: + return std::string(1, char('a' + pos.max_rank() - rank_of(s))); + case NOTATION_JANGGI: + return std::to_string((pos.max_rank() - rank_of(s) + 1) % 10); + case NOTATION_XIANGQI_WXF: + { + if (pos.empty(s)) + // Handle piece drops + return std::to_string(relative_rank(pos.side_to_move(), s, pos.max_rank()) + 1); + else if (pos.pieces(pos.side_to_move(), type_of(pos.piece_on(s))) & forward_file_bb(pos.side_to_move(), s)) + return "-"; + else + return "+"; + } + case NOTATION_THAI_SAN: + case NOTATION_THAI_LAN: + return THAI_RANKS[rank_of(s)]; + default: + return std::to_string(rank_of(s) + 1); + } +} + +inline std::string square(const Position& pos, Square s, Notation n) { + switch (n) + { + case NOTATION_JANGGI: + return rank(pos, s, n) + file(pos, s, n); + default: + return file(pos, s, n) + rank(pos, s, n); + } +} + +inline Disambiguation disambiguation_level(const Position& pos, Move m, Notation n) { + // Drops never need disambiguation + if (type_of(m) == DROP) + return NO_DISAMBIGUATION; + + // NOTATION_LAN and Janggi always use disambiguation + if (n == NOTATION_LAN || n == NOTATION_THAI_LAN || n == NOTATION_JANGGI) + return SQUARE_DISAMBIGUATION; + + Color us = pos.side_to_move(); + Square from = from_sq(m); + Square to = to_sq(m); + Piece pc = pos.moved_piece(m); + PieceType pt = type_of(pc); + + // Xiangqi uses either file disambiguation or +/- if two pieces on file + if (n == NOTATION_XIANGQI_WXF) + { + // Disambiguate by rank (+/-) if target square of other piece is valid + if (popcount(pos.pieces(us, pt) & file_bb(from)) == 2 && !multi_tandem(pos.pieces(us, pt))) + { + Square otherFrom = lsb((pos.pieces(us, pt) & file_bb(from)) ^ from); + Square otherTo = otherFrom + Direction(to) - Direction(from); + if (is_ok(otherTo) && (pos.board_bb(us, pt) & otherTo)) + return RANK_DISAMBIGUATION; + } + return FILE_DISAMBIGUATION; + } + + // Pawn captures always use disambiguation + if ((n == NOTATION_SAN || n == NOTATION_THAI_SAN) && pt == PAWN) + { + if (pos.capture(m)) + return FILE_DISAMBIGUATION; + if (type_of(m) == PROMOTION && from != to && pos.sittuyin_promotion()) + return SQUARE_DISAMBIGUATION; + } + + // A disambiguation occurs if we have more than one piece of type 'pt' + // that can reach 'to' with a legal move. + Bitboard b = pos.pieces(us, pt) ^ from; + Bitboard others = 0; + + while (b) + { + Square s = pop_lsb(b); + // Construct a potential move with identical special move flags + // and only a different "from" square. + Move testMove = Move(m ^ make_move(from, to) ^ make_move(s, to)); + if ( pos.pseudo_legal(testMove) + && pos.legal(testMove) + && !(is_shogi(n) && pos.unpromoted_piece_on(s) != pos.unpromoted_piece_on(from))) + others |= s; + } + + if (!others) + return NO_DISAMBIGUATION; + else if (is_shogi(n)) + return SQUARE_DISAMBIGUATION; + else if (!(others & file_bb(from))) + return FILE_DISAMBIGUATION; + else if (!(others & rank_bb(from))) + return RANK_DISAMBIGUATION; + else + return SQUARE_DISAMBIGUATION; +} + +inline std::string disambiguation(const Position& pos, Square s, Notation n, Disambiguation d) { + switch (d) + { + case FILE_DISAMBIGUATION: + return file(pos, s, n); + case RANK_DISAMBIGUATION: + return rank(pos, s, n); + case SQUARE_DISAMBIGUATION: + return square(pos, s, n); + default: + assert(d == NO_DISAMBIGUATION); + return ""; + } +} + +inline const std::string move_to_san(Position& pos, Move m, Notation n) { + std::string san = ""; + Color us = pos.side_to_move(); + Square from = from_sq(m); + Square to = to_sq(m); + + if (type_of(m) == CASTLING) + { + san = to > from ? "O-O" : "O-O-O"; + + if (is_gating(m)) + { + san += std::string("/") + (char)toupper(pos.piece_to_char()[make_piece(us, gating_type(m))]); + san += square(pos, gating_square(m), n); + } + } + else + { + // Piece + san += piece(pos, m, n); + + if (n == NOTATION_THAI_LAN) + san += " "; + + // Origin square, disambiguation + Disambiguation d = disambiguation_level(pos, m, n); + san += disambiguation(pos, from, n, d); + + // Separator/Operator + if (type_of(m) == DROP) + san += n == NOTATION_SHOGI_HOSKING ? '\'' : is_shogi(n) ? '*' : '@'; + else if (n == NOTATION_XIANGQI_WXF) + { + if (rank_of(from) == rank_of(to)) + san += '='; + else if (relative_rank(us, to, pos.max_rank()) > relative_rank(us, from, pos.max_rank())) + san += '+'; + else + san += '-'; + } + else if (pos.capture(m)) + san += 'x'; + else if (n == NOTATION_LAN || n == NOTATION_THAI_LAN || (is_shogi(n) && (n != NOTATION_SHOGI_HOSKING || d == SQUARE_DISAMBIGUATION)) || n == NOTATION_JANGGI || (n == NOTATION_THAI_SAN && type_of(pos.moved_piece(m)) != PAWN)) + san += '-'; + + // Destination square + if (n == NOTATION_XIANGQI_WXF && type_of(m) != DROP) + san += file_of(to) == file_of(from) ? std::to_string(std::abs(rank_of(to) - rank_of(from))) : file(pos, to, n); + else + san += square(pos, to, n); + + // Suffix + if (type_of(m) == PROMOTION) + san += std::string("=") + (char)toupper(pos.piece_to_char()[make_piece(us, promotion_type(m))]); + else if (type_of(m) == PIECE_PROMOTION) + san += is_shogi(n) ? std::string("+") : std::string("=") + (char)toupper(pos.piece_to_char()[make_piece(us, pos.promoted_piece_type(type_of(pos.moved_piece(m))))]); + else if (type_of(m) == PIECE_DEMOTION) + san += is_shogi(n) ? std::string("-") : std::string("=") + std::string(1, toupper(pos.piece_to_char()[pos.unpromoted_piece_on(from)])); + else if (type_of(m) == NORMAL && is_shogi(n) && pos.pseudo_legal(make(from, to))) + san += std::string("="); + if (is_gating(m)) + san += std::string("/") + (char)toupper(pos.piece_to_char()[make_piece(us, gating_type(m))]); + } + + // Wall square + if (pos.walling()) + san += "," + square(pos, gating_square(m), n); + + // Check and checkmate + if (pos.gives_check(m) && !is_shogi(n) && n != NOTATION_XIANGQI_WXF) + { + StateInfo st; + pos.do_move(m, st); + san += MoveList(pos).size() ? "+" : "#"; + pos.undo_move(m); + } + + return san; +} + +} // namespace SAN + +inline bool has_insufficient_material(Color c, const Position& pos) { + + // Other win rules + if ( pos.captures_to_hand() + || pos.count_in_hand(c, ALL_PIECES) + || (pos.extinction_value() != VALUE_NONE && !pos.extinction_pseudo_royal()) + || (pos.variant()->nFoldRule && pos.variant()->nFoldValue != VALUE_DRAW) + || (pos.flag_region(c) && pos.count(c, pos.flag_piece(c)))) + return false; + + // Precalculate if any promotion pawn types have pieces + bool hasPromotingPawn = false; + for (PieceSet pawnTypes = pos.promotion_pawn_types(c); pawnTypes; ) + { + PieceType pawnType = pop_lsb(pawnTypes); + if (pos.count(c, pawnType) > 0) + { + hasPromotingPawn = true; + break; + } + } + + // Determine checkmating potential of present pieces + constexpr PieceSet MAJOR_PIECES = piece_set(ROOK) | QUEEN | ARCHBISHOP | CHANCELLOR + | SILVER | GOLD | COMMONER | CENTAUR | AMAZON | BERS; + constexpr PieceSet COLORBOUND_PIECES = piece_set(BISHOP) | FERS | FERS_ALFIL | ALFIL | ELEPHANT; + Bitboard restricted = pos.pieces(KING); + Bitboard colorbound = 0; + for (PieceSet ps = pos.piece_types(); ps;) + { + PieceType pt = pop_lsb(ps); + + // Constrained pieces + if (pt == KING || !(pos.board_bb(c, pt) & pos.board_bb(~c, KING)) || (pos.extinction_pseudo_royal() && pos.blast_on_capture() && (pos.extinction_piece_types() & pt))) + restricted |= pos.pieces(c, pt); + + // If piece is a major piece or a custom piece we consider it sufficient for mate. + // To avoid false positives, we assume any custom piece has mating potential. + else if ((MAJOR_PIECES & pt) || is_custom(pt)) + { + // Check if piece is already on the board + if (pos.count(c, pt) > 0) + return false; + + // Check if any pawn can promote to this piece type + if (hasPromotingPawn && (pos.promotion_piece_types(c) & pt)) + return false; + } + + // Collect color-bound pieces + else if (COLORBOUND_PIECES & pt) + colorbound |= pos.pieces(pt); + } + + Bitboard unbound = pos.pieces() ^ restricted ^ colorbound; + + // Color-bound pieces + if ((colorbound & pos.pieces(c)) && (((DarkSquares & colorbound) && (~DarkSquares & colorbound)) || unbound || pos.stalemate_value() != VALUE_DRAW || pos.check_counting() || pos.makpong())) + return false; + + // Unbound pieces require one helper piece of either color + if ((pos.pieces(c) & unbound) && (popcount(pos.pieces() ^ restricted) >= 2 || pos.stalemate_value() != VALUE_DRAW || pos.check_counting() || pos.makpong())) + return false; + + // Non-draw stalemate with lone custom king + if ( pos.stalemate_value() != VALUE_DRAW && pos.king_type() != KING + && pos.pieces(c, KING) && (pos.board_bb(c, KING) & pos.board_bb(~c, KING))) + return false; + + return true; +} + +inline Bitboard checked(const Position& pos) { + return (pos.checkers() ? square_bb(pos.square(pos.side_to_move())) : Bitboard(0)) + | (pos.extinction_pseudo_royal() ? pos.checked_pseudo_royals(pos.side_to_move()) : Bitboard(0)); +} + +namespace FEN { + +enum FenValidation : int { + FEN_INVALID_COUNTING_RULE = -14, + FEN_INVALID_CHECK_COUNT = -13, + FEN_INVALID_PROMOTED_PIECE = -12, + FEN_INVALID_NB_PARTS = -11, + FEN_INVALID_CHAR = -10, + FEN_TOUCHING_KINGS = -9, + FEN_INVALID_BOARD_GEOMETRY = -8, + FEN_INVALID_POCKET_INFO = -7, + FEN_INVALID_SIDE_TO_MOVE = -6, + FEN_INVALID_CASTLING_INFO = -5, + FEN_INVALID_EN_PASSANT_SQ = -4, + FEN_INVALID_NUMBER_OF_KINGS = -3, + FEN_INVALID_HALF_MOVE_COUNTER = -2, + FEN_INVALID_MOVE_COUNTER = -1, + FEN_EMPTY = 0, + FEN_OK = 1 +}; +enum Validation : int { + NOK, + OK +}; + +struct CharSquare { + int rowIdx; + int fileIdx; + CharSquare() : rowIdx(-1), fileIdx(-1) {} + CharSquare(int row, int file) : rowIdx(row), fileIdx(file) {} +}; + +inline bool operator==(const CharSquare& s1, const CharSquare& s2) { + return s1.rowIdx == s2.rowIdx && s1.fileIdx == s2.fileIdx; +} + +inline bool operator!=(const CharSquare& s1, const CharSquare& s2) { + return !(s1 == s2); +} + +inline int non_root_euclidian_distance(const CharSquare& s1, const CharSquare& s2) { + return pow(s1.rowIdx - s2.rowIdx, 2) + pow(s1.fileIdx - s2.fileIdx, 2); +} + +class CharBoard { +private: + int nbRanks; + int nbFiles; + std::vector board; // fill an array where the pieces are for later geometry checks +public: + CharBoard(int ranks, int files) : nbRanks(ranks), nbFiles(files) { + assert(nbFiles > 0 && nbRanks > 0); + board = std::vector(nbRanks * nbFiles, ' '); + } + void set_piece(int rankIdx, int fileIdx, char c) { + board[rankIdx * nbFiles + fileIdx] = c; + } + char get_piece(int rowIdx, int fileIdx) const { + return board[rowIdx * nbFiles + fileIdx]; + } + int get_nb_ranks() const { + return nbRanks; + } + int get_nb_files() const { + return nbFiles; + } + /// Returns the square of a given character + CharSquare get_square_for_piece(char piece) const { + CharSquare s; + for (int r = 0; r < nbRanks; ++r) + { + for (int c = 0; c < nbFiles; ++c) + { + if (get_piece(r, c) == piece) + { + s.rowIdx = r; + s.fileIdx = c; + return s; + } + } + } + return s; + } + /// Returns all square positions for a given piece + std::vector get_squares_for_pieces(Color color, PieceSet ps, const std::string& pieceChars) const { + std::vector squares; + size_t pcIdx; + for (int r = 0; r < nbRanks; ++r) + for (int c = 0; c < nbFiles; ++c) + if ((pcIdx = pieceChars.find(get_piece(r, c))) != std::string::npos && (ps & type_of(Piece(pcIdx))) && color_of(Piece(pcIdx)) == color) + squares.emplace_back(CharSquare(r, c)); + return squares; + } + friend std::ostream& operator<<(std::ostream& os, const CharBoard& board); +}; + +inline std::ostream& operator<<(std::ostream& os, const CharBoard& board) { + for (int r = 0; r < board.nbRanks; ++r) + { + for (int c = 0; c < board.nbFiles; ++c) + os << "[" << board.get_piece(r, c) << "] "; + os << std::endl; + } + return os; +} + +inline bool contains(const std::string& str, char c) { + return str.find(c) != std::string::npos; +} + +inline bool in_any(const std::vector& vec, char c) { + for (std::string str : vec) + if (contains(str, c)) + return true; + return false; +} + +inline Validation check_for_valid_characters(const std::string& firstFenPart, const std::string& validSpecialCharactersFirstField, const Variant* v) { + for (char c : firstFenPart) + { + if (!isdigit(c) && !in_any({v->pieceToChar, v->pieceToCharSynonyms, validSpecialCharactersFirstField}, c)) + { + std::cerr << "Invalid piece character: '" << c << "'." << std::endl; + return NOK; + } + } + return OK; +} + +inline Validation check_promoted_pieces(const std::string& firstFenPart, const Variant* v) { + // Only check promoted pieces if the variant supports shogi-style promotions + if (!v || !v->shogiStylePromotions) + return OK; + + for (size_t i = 0; i < firstFenPart.length() - 1; ++i) { + // Look for promoted pieces ('+' followed by piece character) + if (firstFenPart[i] == '+') { + char pieceChar = firstFenPart[i + 1]; + + // Skip if next character is not a piece character or is a special character + if (isdigit(pieceChar) || pieceChar == '/' || pieceChar == ' ' || pieceChar == '[') + continue; + + // Find the piece type corresponding to this character + size_t idx = v->pieceToChar.find(pieceChar); + if (idx == std::string::npos) { + // Try synonyms + idx = v->pieceToCharSynonyms.find(pieceChar); + if (idx == std::string::npos) + continue; // Character validation will catch this + } + + // Ensure idx is within valid range for piece types + if (idx >= PIECE_TYPE_NB) + continue; + + // Get the piece type directly from the index + PieceType pt = PieceType(idx); + + // Check if this piece type has a promoted form + if (pt != NO_PIECE_TYPE && pt < PIECE_TYPE_NB && v->promotedPieceType[pt] == NO_PIECE_TYPE) { + std::cerr << "Invalid promoted piece: '+' followed by '" << pieceChar + << "'. This piece cannot be promoted in variant." << std::endl; + return NOK; + } + } + } + return OK; +} + +inline std::vector get_fen_parts(const std::string& fullFen, char delim) { + std::vector fenParts; + std::string curPart; + std::stringstream ss(fullFen); + while (std::getline(ss, curPart, delim)) + fenParts.emplace_back(curPart); + return fenParts; +} + +/// fills the character board according to a given FEN string +inline Validation fill_char_board(CharBoard& board, const std::string& fenBoard, const std::string& validSpecialCharactersFirstField, const Variant* v) { + int rankIdx = 0; + int fileIdx = 0; + + char prevChar = '?'; + for (char c : fenBoard) + { + if (c == ' ' || c == '[') + break; + if (c == '*') + ++fileIdx; + else if (isdigit(c)) + { + fileIdx += c - '0'; + // if we have multiple digits attached we can add multiples of 9 to compute the resulting number (e.g. -> 21 = 2 + 2 * 9 + 1) + if (isdigit(prevChar)) + fileIdx += 9 * (prevChar - '0'); + } + else if (c == '/') + { + ++rankIdx; + if (fileIdx != board.get_nb_files()) + { + std::cerr << "curRankWidth != nbFiles: " << fileIdx << " != " << board.get_nb_files() << std::endl; + return NOK; + } + if (rankIdx == board.get_nb_ranks()) + break; + fileIdx = 0; + } + else if (!contains(validSpecialCharactersFirstField, c)) + { // normal piece + if (fileIdx == board.get_nb_files()) + { + std::cerr << "File index: " << fileIdx << " for piece '" << c << "' exceeds maximum of allowed number of files: " << board.get_nb_files() << "." << std::endl; + return NOK; + } + board.set_piece(v->maxRank-rankIdx, fileIdx, c); // we mirror the rank index because the black pieces are given first in the FEN + ++fileIdx; + } + prevChar = c; + } + + if (v->pieceDrops) + { // pockets can either be defined by [] or / + if (rankIdx+1 != board.get_nb_ranks() && rankIdx != board.get_nb_ranks()) + { + std::cerr << "Invalid number of ranks. Expected: " << board.get_nb_ranks() << " Actual: " << rankIdx+1 << std::endl; + return NOK; + } + } + else + { + if (rankIdx+1 != board.get_nb_ranks()) + { + std::cerr << "Invalid number of ranks. Expected: " << board.get_nb_ranks() << " Actual: " << rankIdx+1 << std::endl; + return NOK; + } + } + return OK; +} + +inline Validation check_touching_kings(const CharBoard& board, const std::array& kingPositions) { + if (non_root_euclidian_distance(kingPositions[WHITE], kingPositions[BLACK]) <= 2) + { + std::cerr << "King pieces are next to each other." << std::endl; + std::cerr << board << std::endl; + return NOK; + } + return OK; +} + +inline Validation fill_castling_info_splitted(const std::string& castlingInfo, std::array& castlingInfoSplitted) { + for (char c : castlingInfo) + { + if (c != '-') + { + if (!isalpha(c)) + { + std::cerr << "Invalid castling specification: '" << c << "'." << std::endl; + return NOK; + } + else if (isupper(c)) + castlingInfoSplitted[WHITE] += tolower(c); + else + castlingInfoSplitted[BLACK] += c; + } + } + return OK; +} + +inline std::string color_to_string(Color c) { + switch (c) + { + case WHITE: + return "WHITE"; + case BLACK: + return "BLACK"; + case COLOR_NB: + return "COLOR_NB"; + default: + return "INVALID_COLOR"; + } +} + +inline std::string castling_rights_to_string(CastlingRights castlingRights) { + switch (castlingRights) + { + case KING_SIDE: + return "KING_SIDE"; + case QUEEN_SIDE: + return "QUEENS_SIDE"; + case WHITE_OO: + return "WHITE_OO"; + case WHITE_OOO: + return "WHITE_OOO"; + case BLACK_OO: + return "BLACK_OO"; + case BLACK_OOO: + return "BLACK_OOO"; + case WHITE_CASTLING: + return "WHITE_CASTLING"; + case BLACK_CASTLING: + return "BLACK_CASTLING"; + case ANY_CASTLING: + return "ANY_CASTLING"; + case CASTLING_RIGHT_NB: + return "CASTLING_RIGHT_NB"; + default: + return "INVALID_CASTLING_RIGHTS"; + } +} + +inline Validation check_castling_rank(const std::array& castlingInfoSplitted, const CharBoard& board, + const std::array& kingPositions, const Variant* v) { + + for (Color c : {WHITE, BLACK}) + { + const Rank castlingRank = relative_rank(c, v->castlingRank, v->maxRank); + for (char castlingFlag : castlingInfoSplitted[c]) + { + if (tolower(castlingFlag) == 'k' || tolower(castlingFlag) == 'q') + { + if (kingPositions[c].rowIdx != castlingRank) + { + std::cerr << "The " << color_to_string(c) << " king must be on rank " << castlingRank << " if castling is enabled for " << color_to_string(c) << "." << std::endl; + return NOK; + } + bool kingside = tolower(castlingFlag) == 'k'; + bool castlingRook = false; + size_t pcIdx; + for (int f = kingside ? board.get_nb_files() - 1 : 0; f != kingPositions[c].fileIdx; kingside ? f-- : f++) + if ( (pcIdx = v->pieceToChar.find(board.get_piece(castlingRank, f))) != std::string::npos + && (v->castlingRookPieces[c] & type_of(Piece(pcIdx))) + && color_of(Piece(pcIdx)) == c) + { + castlingRook = true; + break; + } + if (!castlingRook) + { + std::cerr << "No castling rook for flag " << castlingFlag << std::endl; + return NOK; + } + } + else if (board.get_piece(castlingRank, tolower(castlingFlag) - 'a') == ' ') + { + std::cerr << "No gating piece for flag " << castlingFlag << std::endl; + return NOK; + } + } + } + return OK; +} + +inline Validation check_standard_castling(std::array& castlingInfoSplitted, const CharBoard& board, + const std::array& kingPositions, const std::array& kingPositionsStart, + const std::array, 2>& rookPositionsStart, const Variant* v) { + + for (Color c : {WHITE, BLACK}) + { + if (castlingInfoSplitted[c].size() == 0) + continue; + if (kingPositions[c] != kingPositionsStart[c]) + { + std::cerr << "The " << color_to_string(c) << " KING has moved. Castling is no longer valid for " << color_to_string(c) << "." << std::endl; + return NOK; + } + + for (CastlingRights castling: {KING_SIDE, QUEEN_SIDE}) + { + CharSquare rookStartingSquare = castling == QUEEN_SIDE ? rookPositionsStart[c][0] : rookPositionsStart[c][1]; + char targetChar = castling == QUEEN_SIDE ? 'q' : 'k'; + size_t pcIdx; + if (castlingInfoSplitted[c].find(targetChar) != std::string::npos) + { + if ( (pcIdx = v->pieceToChar.find(board.get_piece(rookStartingSquare.rowIdx, rookStartingSquare.fileIdx))) == std::string::npos + || !(v->castlingRookPieces[c] & type_of(Piece(pcIdx))) + || color_of(Piece(pcIdx)) != c) + { + std::cerr << "The " << color_to_string(c) << " ROOK on the "<< castling_rights_to_string(castling) << " has moved. " + << castling_rights_to_string(castling) << " castling is no longer valid for " << color_to_string(c) << "." << std::endl; + return NOK; + } + } + + } + } + return OK; +} + +inline Validation check_pocket_info(const std::string& fenBoard, int nbRanks, const Variant* v, std::string& pocket) { + + char stopChar; + int offset = 0; + if (std::count(fenBoard.begin(), fenBoard.end(), '/') == nbRanks) + { + // look for last '/' + stopChar = '/'; + } + else if (std::count(fenBoard.begin(), fenBoard.end(), '[') == 1) + { + // pocket is defined as [ and ] + stopChar = '['; + offset = 1; + if (*(fenBoard.end()-1) != ']') + { + std::cerr << "Pocket specification does not end with ']'." << std::endl; + return NOK; + } + } + else + // allow to skip pocket + return OK; + + // look for last '/' + for (auto it = fenBoard.rbegin()+offset; it != fenBoard.rend(); ++it) + { + const char c = *it; + if (c == stopChar) + return OK; + if (c != '-') + { + if (!in_any({v->pieceToChar, v->pieceToCharSynonyms}, c)) + { + std::cerr << "Invalid pocket piece: '" << c << "'." << std::endl; + return NOK; + } + else + pocket += c; + } + } + std::cerr << "Pocket piece closing character '" << stopChar << "' was not found." << std::endl; + return NOK; +} + +inline int piece_count(const std::string& fenBoard, Color c, PieceType pt, const Variant* v) { + return std::count(fenBoard.begin(), fenBoard.end(), v->pieceToChar[make_piece(c, pt)]); +} + +inline Validation check_number_of_kings(const std::string& fenBoard, const std::string& startFenBoard, const Variant* v) { + int nbWhiteKings = piece_count(fenBoard, WHITE, KING, v); + int nbBlackKings = piece_count(fenBoard, BLACK, KING, v); + int nbWhiteKingsStart = piece_count(startFenBoard, WHITE, KING, v); + int nbBlackKingsStart = piece_count(startFenBoard, BLACK, KING, v); + + if (nbWhiteKings > 1) + { + std::cerr << "Invalid number of white kings. Maximum: 1. Given: " << nbWhiteKings << std::endl; + return NOK; + } + if (nbBlackKings > 1) + { + std::cerr << "Invalid number of black kings. Maximum: 1. Given: " << nbBlackKings << std::endl; + return NOK; + } + if (nbWhiteKings != nbWhiteKingsStart) + { + std::cerr << "Invalid number of white kings. Expected: " << nbWhiteKingsStart << ". Given: " << nbWhiteKings << std::endl; + return NOK; + } + if (nbBlackKings != nbBlackKingsStart) + { + std::cerr << "Invalid number of black kings. Expected: " << nbBlackKingsStart << ". Given: " << nbBlackKings << std::endl; + return NOK; + } + return OK; +} + + +inline Validation check_en_passant_square(const std::string& enPassantInfo) { + if (enPassantInfo.size() != 1 || enPassantInfo[0] != '-') + { + if (enPassantInfo.size() != 2) + { + std::cerr << "Invalid en-passant square '" << enPassantInfo << "'. Expects 2 characters. Actual: " << enPassantInfo.size() << " character(s)." << std::endl; + return NOK; + } + if (!isalpha(enPassantInfo[0])) + { + std::cerr << "Invalid en-passant square '" << enPassantInfo << "'. Expects 1st character to be a letter." << std::endl; + return NOK; + } + if (!isdigit(enPassantInfo[1])) + { + std::cerr << "Invalid en-passant square '" << enPassantInfo << "'. Expects 2nd character to be a digit." << std::endl; + return NOK; + } + } + return OK; +} + + +inline Validation check_check_count(const std::string& checkCountInfo) { + if (checkCountInfo.size() != 3) + { + std::cerr << "Invalid check count '" << checkCountInfo << "'. Expects 3 characters. Actual: " << checkCountInfo.size() << " character(s)." << std::endl; + return NOK; + } + if (!isdigit(checkCountInfo[0])) + { + std::cerr << "Invalid check count '" << checkCountInfo << "'. Expects 1st character to be a digit." << std::endl; + return NOK; + } + if (!isdigit(checkCountInfo[2])) { + std::cerr << "Invalid check count '" << checkCountInfo << "'. Expects 3rd character to be a digit." << std::endl; + return NOK; + } + return OK; +} + +inline Validation check_lichess_check_count(const std::string& checkCountInfo) { + if (checkCountInfo.size() != 4) + { + std::cerr << "Invalid check count '" << checkCountInfo << "'. Expects 4 characters. Actual: " << checkCountInfo.size() << " character(s)." << std::endl; + return NOK; + } + if (!isdigit(checkCountInfo[1]) || checkCountInfo[1] - '0' > 3) + { + std::cerr << "Invalid check count '" << checkCountInfo << "'. Expects 2nd character to be a digit up to 3." << std::endl; + return NOK; + } + if (!isdigit(checkCountInfo[3]) || checkCountInfo[3] - '0' > 3) { + std::cerr << "Invalid check count '" << checkCountInfo << "'. Expects 4th character to be a digit up to 3." << std::endl; + return NOK; + } + return OK; +} + +inline Validation check_digit_field(const std::string& field) { + if (field.size() == 1 && field[0] == '-') + return OK; + for (char c : field) + if (!isdigit(c)) + return NOK; + return OK; +} + +inline std::string get_valid_special_chars(const Variant* v) { + std::string validSpecialCharactersFirstField = "/*"; + // Whether or not '-', '+', '~', '[', ']' are valid depends on the variant being played. + if (v->shogiStylePromotions) + validSpecialCharactersFirstField += '+'; + if (v->promotionPieceTypes[WHITE] || v->promotionPieceTypes[BLACK]) + validSpecialCharactersFirstField += '~'; + if (!v->freeDrops && (v->pieceDrops || v->seirawanGating)) + validSpecialCharactersFirstField += "[-]"; + return validSpecialCharactersFirstField; +} + +inline FenValidation validate_fen(const std::string& fen, const Variant* v, bool chess960 = false) { + + const std::string validSpecialCharactersFirstField = get_valid_special_chars(v); + // 0) Layout + // check for empty fen + if (fen.empty()) + { + std::cerr << "Fen is empty." << std::endl; + return FEN_EMPTY; + } + + std::vector fenParts = get_fen_parts(fen, ' '); + std::vector startFenParts = get_fen_parts(v->startFen, ' '); + + // check for number of parts + const unsigned int maxNumberFenParts = 6 + v->checkCounting; + if (fenParts.size() < 1 || fenParts.size() > maxNumberFenParts) + { + std::cerr << "Invalid number of fen parts. Expected: >= 1 and <= " << maxNumberFenParts + << " Actual: " << fenParts.size() << std::endl; + return FEN_INVALID_NB_PARTS; + } + + // 1) Part + // check for valid characters + if (check_for_valid_characters(fenParts[0], validSpecialCharactersFirstField, v) == NOK) + return FEN_INVALID_CHAR; + + // check for valid promoted pieces + if (check_promoted_pieces(fenParts[0], v) == NOK) + return FEN_INVALID_PROMOTED_PIECE; + + // check for number of ranks + const int nbRanks = v->maxRank + 1; + // check for number of files + const int nbFiles = v->maxFile + 1; + CharBoard board(nbRanks, nbFiles); // create a 2D character board for later geometry checks + + if (fill_char_board(board, fenParts[0], validSpecialCharactersFirstField, v) == NOK) + return FEN_INVALID_BOARD_GEOMETRY; + + // check for pocket + std::string pocket = ""; + if (v->pieceDrops || v->seirawanGating) + { + if (check_pocket_info(fenParts[0], nbRanks, v, pocket) == NOK) + return FEN_INVALID_POCKET_INFO; + } + + // check for number of kings + if (v->pieceTypes & KING) + { + // we have a royal king in this variant, + // ensure that each side has exactly as many kings as in the starting position + // (variants like giveaway use the COMMONER piece type instead) + if (check_number_of_kings(fenParts[0], startFenParts[0], v) == NOK) + return FEN_INVALID_NUMBER_OF_KINGS; + + // check for touching kings if there are exactly two royal kings on the board (excluding pocket) + if ( v->kingType == KING + && piece_count(fenParts[0], WHITE, KING, v) - piece_count(pocket, WHITE, KING, v) == 1 + && piece_count(fenParts[0], BLACK, KING, v) - piece_count(pocket, BLACK, KING, v) == 1) + { + std::array kingPositions; + kingPositions[WHITE] = board.get_square_for_piece(v->pieceToChar[make_piece(WHITE, KING)]); + kingPositions[BLACK] = board.get_square_for_piece(v->pieceToChar[make_piece(BLACK, KING)]); + if (check_touching_kings(board, kingPositions) == NOK) + return FEN_TOUCHING_KINGS; + } + } + + // 2) Part + // check side to move char + if (fenParts.size() >= 2 && fenParts[1][0] != 'w' && fenParts[1][0] != 'b') + { + std::cerr << "Invalid side to move specification: '" << fenParts[1][0] << "'." << std::endl; + return FEN_INVALID_SIDE_TO_MOVE; + } + + // Castling and en passant can be skipped + bool skipCastlingAndEp = fenParts.size() >= 4 && fenParts.size() <= 5 && isdigit(fenParts[2][0]); + + // 3) Part + // check castling rights + if (fenParts.size() >= 3 && !skipCastlingAndEp && v->castling) + { + std::array castlingInfoSplitted; + if (fill_castling_info_splitted(fenParts[2], castlingInfoSplitted) == NOK) + return FEN_INVALID_CASTLING_INFO; + + if (castlingInfoSplitted[WHITE].size() != 0 || castlingInfoSplitted[BLACK].size() != 0) + { + std::array kingPositions; + kingPositions[WHITE] = board.get_square_for_piece(toupper(v->pieceToChar[v->castlingKingPiece[WHITE]])); + kingPositions[BLACK] = board.get_square_for_piece(tolower(v->pieceToChar[v->castlingKingPiece[BLACK]])); + + CharBoard startBoard(board.get_nb_ranks(), board.get_nb_files()); + fill_char_board(startBoard, v->startFen, validSpecialCharactersFirstField, v); + + // Check pieces present on castling rank against castling/gating rights + if (check_castling_rank(castlingInfoSplitted, board, kingPositions, v) == NOK) + return FEN_INVALID_CASTLING_INFO; + + // only check exact squares if starting position of castling pieces is known + if (!v->chess960 && !v->castlingDroppedPiece && !chess960) + { + std::array kingPositionsStart; + kingPositionsStart[WHITE] = startBoard.get_square_for_piece(v->pieceToChar[make_piece(WHITE, v->castlingKingPiece[WHITE])]); + kingPositionsStart[BLACK] = startBoard.get_square_for_piece(v->pieceToChar[make_piece(BLACK, v->castlingKingPiece[BLACK])]); + std::array, 2> rookPositionsStart; + rookPositionsStart[WHITE] = startBoard.get_squares_for_pieces(WHITE, v->castlingRookPieces[WHITE], v->pieceToChar); + rookPositionsStart[BLACK] = startBoard.get_squares_for_pieces(BLACK, v->castlingRookPieces[BLACK], v->pieceToChar); + + if (check_standard_castling(castlingInfoSplitted, board, kingPositions, kingPositionsStart, rookPositionsStart, v) == NOK) + return FEN_INVALID_CASTLING_INFO; + } + } + } + + // 4) Part + // check en-passant square + if (fenParts.size() >= 4 && !skipCastlingAndEp) + { + if (v->doubleStep && (v->pieceTypes & PAWN)) + { + if (check_en_passant_square(fenParts[3]) == NOK) + return FEN_INVALID_EN_PASSANT_SQ; + } + else if (v->countingRule && !check_digit_field(fenParts[3])) + return FEN_INVALID_COUNTING_RULE; + } + + // 5) Part + // check check count + unsigned int optionalInbetweenFields = 2 * !skipCastlingAndEp; + unsigned int optionalTrailingFields = 0; + if (fenParts.size() >= 3 + optionalInbetweenFields && v->checkCounting && fenParts.size() % 2) + { + if (check_check_count(fenParts[2 + optionalInbetweenFields]) == NOK) + { + // allow valid lichess style check as alternative + if (fenParts.size() < 5 + optionalInbetweenFields || check_lichess_check_count(fenParts[fenParts.size() - 1]) == NOK) + return FEN_INVALID_CHECK_COUNT; + else + optionalTrailingFields++; + } + else + optionalInbetweenFields++; + } + + // 6) Part + // check half move counter + if (fenParts.size() >= 3 + optionalInbetweenFields && !check_digit_field(fenParts[fenParts.size() - 2 - optionalTrailingFields])) + { + std::cerr << "Invalid half move counter: '" << fenParts[fenParts.size()-2] << "'." << std::endl; + return FEN_INVALID_HALF_MOVE_COUNTER; + } + + // 7) Part + // check move counter + if (fenParts.size() >= 4 + optionalInbetweenFields && !check_digit_field(fenParts[fenParts.size() - 1 - optionalTrailingFields])) + { + std::cerr << "Invalid move counter: '" << fenParts[fenParts.size()-1] << "'." << std::endl; + return FEN_INVALID_MOVE_COUNTER; + } + + return FEN_OK; +} +} // namespace FEN + +} // namespace Stockfish + +#endif // #ifndef APIUTIL_H_INCLUDED diff --git a/Fairy-Stockfish-master/src/benchmark.cpp b/Fairy-Stockfish-master/src/benchmark.cpp new file mode 100644 index 0000000000000000000000000000000000000000..00b9959dd17b4ca917289637980d9f3d57a05aa1 --- /dev/null +++ b/Fairy-Stockfish-master/src/benchmark.cpp @@ -0,0 +1,195 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include + +#include "position.h" +#include "uci.h" + +using namespace std; + +namespace { + +const vector Defaults = { + "setoption name UCI_Chess960 value false", + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", + "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 10", + "8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 11", + "4rrk1/pp1n3p/3q2pQ/2p1pb2/2PP4/2P3N1/P2B2PP/4RRK1 b - - 7 19", + "rq3rk1/ppp2ppp/1bnpb3/3N2B1/3NP3/7P/PPPQ1PP1/2KR3R w - - 7 14 moves d4e6", + "r1bq1r1k/1pp1n1pp/1p1p4/4p2Q/4Pp2/1BNP4/PPP2PPP/3R1RK1 w - - 2 14 moves g2g4", + "r3r1k1/2p2ppp/p1p1bn2/8/1q2P3/2NPQN2/PPP3PP/R4RK1 b - - 2 15", + "r1bbk1nr/pp3p1p/2n5/1N4p1/2Np1B2/8/PPP2PPP/2KR1B1R w kq - 0 13", + "r1bq1rk1/ppp1nppp/4n3/3p3Q/3P4/1BP1B3/PP1N2PP/R4RK1 w - - 1 16", + "4r1k1/r1q2ppp/ppp2n2/4P3/5Rb1/1N1BQ3/PPP3PP/R5K1 w - - 1 17", + "2rqkb1r/ppp2p2/2npb1p1/1N1Nn2p/2P1PP2/8/PP2B1PP/R1BQK2R b KQ - 0 11", + "r1bq1r1k/b1p1npp1/p2p3p/1p6/3PP3/1B2NN2/PP3PPP/R2Q1RK1 w - - 1 16", + "3r1rk1/p5pp/bpp1pp2/8/q1PP1P2/b3P3/P2NQRPP/1R2B1K1 b - - 6 22", + "r1q2rk1/2p1bppp/2Pp4/p6b/Q1PNp3/4B3/PP1R1PPP/2K4R w - - 2 18", + "4k2r/1pb2ppp/1p2p3/1R1p4/3P4/2r1PN2/P4PPP/1R4K1 b - - 3 22", + "3q2k1/pb3p1p/4pbp1/2r5/PpN2N2/1P2P2P/5PP1/Q2R2K1 b - - 4 26", + "6k1/6p1/6Pp/ppp5/3pn2P/1P3K2/1PP2P2/3N4 b - - 0 1", + "3b4/5kp1/1p1p1p1p/pP1PpP1P/P1P1P3/3KN3/8/8 w - - 0 1", + "2K5/p7/7P/5pR1/8/5k2/r7/8 w - - 0 1 moves g5g6 f3e3 g6g5 e3f3", + "8/6pk/1p6/8/PP3p1p/5P2/4KP1q/3Q4 w - - 0 1", + "7k/3p2pp/4q3/8/4Q3/5Kp1/P6b/8 w - - 0 1", + "8/2p5/8/2kPKp1p/2p4P/2P5/3P4/8 w - - 0 1", + "8/1p3pp1/7p/5P1P/2k3P1/8/2K2P2/8 w - - 0 1", + "8/pp2r1k1/2p1p3/3pP2p/1P1P1P1P/P5KR/8/8 w - - 0 1", + "8/3p4/p1bk3p/Pp6/1Kp1PpPp/2P2P1P/2P5/5B2 b - - 0 1", + "5k2/7R/4P2p/5K2/p1r2P1p/8/8/8 b - - 0 1", + "6k1/6p1/P6p/r1N5/5p2/7P/1b3PP1/4R1K1 w - - 0 1", + "1r3k2/4q3/2Pp3b/3Bp3/2Q2p2/1p1P2P1/1P2KP2/3N4 w - - 0 1", + "6k1/4pp1p/3p2p1/P1pPb3/R7/1r2P1PP/3B1P2/6K1 w - - 0 1", + "8/3p3B/5p2/5P2/p7/PP5b/k7/6K1 w - - 0 1", + "5rk1/q6p/2p3bR/1pPp1rP1/1P1Pp3/P3B1Q1/1K3P2/R7 w - - 93 90", + "4rrk1/1p1nq3/p7/2p1P1pp/3P2bp/3Q1Bn1/PPPB4/1K2R1NR w - - 40 21", + "r3k2r/3nnpbp/q2pp1p1/p7/Pp1PPPP1/4BNN1/1P5P/R2Q1RK1 w kq - 0 16", + "3Qb1k1/1r2ppb1/pN1n2q1/Pp1Pp1Pr/4P2p/4BP2/4B1R1/1R5K b - - 11 40", + "4k3/3q1r2/1N2r1b1/3ppN2/2nPP3/1B1R2n1/2R1Q3/3K4 w - - 5 1", + + // 5-man positions + "8/8/8/8/5kp1/P7/8/1K1N4 w - - 0 1", // Kc2 - mate + "8/8/8/5N2/8/p7/8/2NK3k w - - 0 1", // Na2 - mate + "8/3k4/8/8/8/4B3/4KB2/2B5 w - - 0 1", // draw + + // 6-man positions + "8/8/1P6/5pr1/8/4R3/7k/2K5 w - - 0 1", // Re5 - mate + "8/2p4P/8/kr6/6R1/8/8/1K6 w - - 0 1", // Ka2 - mate + "8/8/3P3k/8/1p6/8/1P6/1K3n2 b - - 0 1", // Nd2 - draw + + // 7-man positions + "8/R7/2q5/8/6k1/8/1P5p/K6R w - - 0 124", // Draw + + // Mate and stalemate positions + "6k1/3b3r/1p1p4/p1n2p2/1PPNpP1q/P3Q1p1/1R1RB1P1/5K2 b - - 0 1", + "r2r1n2/pp2bk2/2p1p2p/3q4/3PN1QP/2P3R1/P4PP1/5RK1 w - - 0 1", + "8/8/8/8/8/6k1/6p1/6K1 w - -", + "7k/7P/6K1/8/3B4/8/8/8 b - -", + + // Chess 960 + "setoption name UCI_Chess960 value true", + "bbqnnrkr/pppppppp/8/8/8/8/PPPPPPPP/BBQNNRKR w HFhf - 0 1 moves g2g3 d7d5 d2d4 c8h3 c1g5 e8d6 g5e7 f7f6", + "setoption name UCI_Chess960 value false" +}; + +} // namespace + +namespace Stockfish { + +/// setup_bench() builds a list of UCI commands to be run by bench. There +/// are five parameters: TT size in MB, number of search threads that +/// should be used, the limit value spent for each position, a file name +/// where to look for positions in FEN format, the type of the limit: +/// depth, perft, nodes and movetime (in millisecs), and evaluation type +/// mixed (default), classical, NNUE. +/// +/// bench -> search default positions up to depth 13 +/// bench 64 1 15 -> search default positions up to depth 15 (TT = 64MB) +/// bench 64 4 5000 current movetime -> search current position with 4 threads for 5 sec +/// bench 64 1 100000 default nodes -> search default positions for 100K nodes each +/// bench 16 1 5 default perft -> run a perft 5 on default positions + +vector setup_bench(const Position& current, istream& is) { + + vector fens, list; + string go, token, varname; + + streampos args = is.tellg(); + // Check whether the next token is a variant name + if ((is >> token) && variants.find(token) != variants.end()) + { + args = is.tellg(); + varname = token; + } + else + { + is.seekg(args); + varname = string(Options["UCI_Variant"]); + } + const Variant* variant = variants.find(varname)->second; + + // Assign default values to missing arguments + string ttSize = (is >> token) ? token : "16"; + string threads = (is >> token) ? token : "1"; + string limit = (is >> token) ? token : "13"; + string fenFile = (is >> token) ? token : "default"; + string limitType = (is >> token) ? token : "depth"; + string evalType = (is >> token) ? token : "mixed"; + + go = limitType == "eval" ? "eval" : "go " + limitType + " " + limit; + + if (fenFile == "default") + { + if (varname != "chess") + fens.push_back(variant->startFen); + else + fens = Defaults; + } + + else if (fenFile == "current") + fens.push_back(current.fen()); + + else + { + string fen; + ifstream file(fenFile); + + if (!file.is_open()) + { + cerr << "Unable to open file " << fenFile << endl; + exit(EXIT_FAILURE); + } + + while (getline(file, fen)) + if (!fen.empty()) + fens.push_back(fen); + + file.close(); + } + + 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; + } + + list.emplace_back("setoption name Use NNUE value true"); + + return list; +} + +} // namespace Stockfish diff --git a/Fairy-Stockfish-master/src/bitbase.cpp b/Fairy-Stockfish-master/src/bitbase.cpp new file mode 100644 index 0000000000000000000000000000000000000000..08a3ed9409ef83b0ff5d634f75191da06e229502 --- /dev/null +++ b/Fairy-Stockfish-master/src/bitbase.cpp @@ -0,0 +1,177 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include + +#include "bitboard.h" +#include "types.h" + +namespace Stockfish { + +namespace { + + // There are 24 possible pawn squares: files A to D and ranks from 2 to 7. + // Positions with the pawn on files E to H will be mirrored before probing. + constexpr unsigned MAX_INDEX = 2*24*64*64; // stm * psq * wksq * bksq = 196608 + + std::bitset KPKBitbase; + + // A KPK bitbase index is an integer in [0, IndexMax] range + // + // Information is mapped in a way that minimizes the number of iterations: + // + // bit 0- 5: white king square (from SQ_A1 to SQ_H8) + // bit 6-11: black king square (from SQ_A1 to SQ_H8) + // bit 12: side to move (WHITE or BLACK) + // bit 13-14: white pawn file (from FILE_A to FILE_D) + // bit 15-17: white pawn RANK_7 - rank (from RANK_7 - RANK_7 to RANK_7 - RANK_2) + unsigned index(Color stm, Square bksq, Square wksq, Square psq) { + return int(wksq) | (bksq << 6) | (stm << 12) | (file_of(psq) << 13) | ((RANK_7 - rank_of(psq)) << 15); + } + + enum Result { + INVALID = 0, + UNKNOWN = 1, + DRAW = 2, + WIN = 4 + }; + + Result& operator|=(Result& r, Result v) { return r = Result(r | v); } + + struct KPKPosition { + KPKPosition() = default; + explicit KPKPosition(unsigned idx); + operator Result() const { return result; } + Result classify(const std::vector& db); + + Color stm; + Square ksq[COLOR_NB], psq; + Result result; + }; + +} // namespace + +bool Bitbases::probe(Square wksq, Square wpsq, Square bksq, Color stm) { + + assert(file_of(wpsq) <= FILE_D); + + return KPKBitbase[index(stm, bksq, wksq, wpsq)]; +} + + +void Bitbases::init() { + +#ifdef LARGEBOARDS + // Bitbases are not working for large-board version + return; +#endif + + std::vector db(MAX_INDEX); + unsigned idx, repeat = 1; + + // Initialize db with known win / draw positions + for (idx = 0; idx < MAX_INDEX; ++idx) + db[idx] = KPKPosition(idx); + + // Iterate through the positions until none of the unknown positions can be + // changed to either wins or draws (15 cycles needed). + while (repeat) + for (repeat = idx = 0; idx < MAX_INDEX; ++idx) + repeat |= (db[idx] == UNKNOWN && db[idx].classify(db) != UNKNOWN); + + // Fill the bitbase with the decisive results + for (idx = 0; idx < MAX_INDEX; ++idx) + if (db[idx] == WIN) + KPKBitbase.set(idx); +} + +namespace { + + KPKPosition::KPKPosition(unsigned idx) { + + ksq[WHITE] = Square((idx >> 0) & 0x3F); + ksq[BLACK] = Square((idx >> 6) & 0x3F); + stm = Color ((idx >> 12) & 0x01); + psq = make_square(File((idx >> 13) & 0x3), Rank(RANK_7 - ((idx >> 15) & 0x7))); + + // Invalid if two pieces are on the same square or if a king can be captured + if ( distance(ksq[WHITE], ksq[BLACK]) <= 1 + || ksq[WHITE] == psq + || ksq[BLACK] == psq + || (stm == WHITE && (pawn_attacks_bb(WHITE, psq) & ksq[BLACK]))) + result = INVALID; + + // Win if the pawn can be promoted without getting captured + else if ( stm == WHITE + && rank_of(psq) == RANK_7 + && ksq[WHITE] != psq + NORTH + && ( distance(ksq[BLACK], psq + NORTH) > 1 + || (distance(ksq[WHITE], psq + NORTH) == 1))) + result = WIN; + + // Draw if it is stalemate or the black king can capture the pawn + else if ( stm == BLACK + && ( !(attacks_bb(ksq[BLACK]) & ~(attacks_bb(ksq[WHITE]) | pawn_attacks_bb(WHITE, psq))) + || (attacks_bb(ksq[BLACK]) & ~attacks_bb(ksq[WHITE]) & psq))) + result = DRAW; + + // Position will be classified later + else + result = UNKNOWN; + } + + Result KPKPosition::classify(const std::vector& db) { + + // White to move: If one move leads to a position classified as WIN, the result + // of the current position is WIN. If all moves lead to positions classified + // as DRAW, the current position is classified as DRAW, otherwise the current + // position is classified as UNKNOWN. + // + // Black to move: If one move leads to a position classified as DRAW, the result + // of the current position is DRAW. If all moves lead to positions classified + // as WIN, the position is classified as WIN, otherwise the current position is + // classified as UNKNOWN. + const Result Good = (stm == WHITE ? WIN : DRAW); + const Result Bad = (stm == WHITE ? DRAW : WIN); + + Result r = INVALID; + Bitboard b = attacks_bb(ksq[stm]); + + while (b) + r |= stm == WHITE ? db[index(BLACK, ksq[BLACK], pop_lsb(b), psq)] + : db[index(WHITE, pop_lsb(b), ksq[WHITE], psq)]; + + if (stm == WHITE) + { + if (rank_of(psq) < RANK_7) // Single push + r |= db[index(BLACK, ksq[BLACK], ksq[WHITE], psq + NORTH)]; + + if ( rank_of(psq) == RANK_2 // Double push + && psq + NORTH != ksq[WHITE] + && psq + NORTH != ksq[BLACK]) + r |= db[index(BLACK, ksq[BLACK], ksq[WHITE], psq + NORTH + NORTH)]; + } + + return result = r & Good ? Good : r & UNKNOWN ? UNKNOWN : Bad; + } + +} // namespace + +} // namespace Stockfish diff --git a/Fairy-Stockfish-master/src/bitboard.cpp b/Fairy-Stockfish-master/src/bitboard.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4f00575970a039998f71d9f7278dc853f5705b66 --- /dev/null +++ b/Fairy-Stockfish-master/src/bitboard.cpp @@ -0,0 +1,501 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#include "bitboard.h" +#include "magic.h" +#include "misc.h" +#include "piece.h" + +namespace Stockfish { + +uint8_t PopCnt16[1 << 16]; +uint8_t SquareDistance[SQUARE_NB][SQUARE_NB]; + +Bitboard SquareBB[SQUARE_NB]; +Bitboard LineBB[SQUARE_NB][SQUARE_NB]; +Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; +Bitboard PseudoAttacks[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; +Bitboard PseudoMoves[2][COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; +Bitboard LeaperAttacks[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; +Bitboard LeaperMoves[2][COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; +Bitboard BoardSizeBB[FILE_NB][RANK_NB]; +RiderType AttackRiderTypes[PIECE_TYPE_NB]; +RiderType MoveRiderTypes[2][PIECE_TYPE_NB]; + +Magic RookMagicsH[SQUARE_NB]; +Magic RookMagicsV[SQUARE_NB]; +Magic BishopMagics[SQUARE_NB]; +Magic CannonMagicsH[SQUARE_NB]; +Magic CannonMagicsV[SQUARE_NB]; +Magic LameDabbabaMagics[SQUARE_NB]; +Magic HorseMagics[SQUARE_NB]; +Magic ElephantMagics[SQUARE_NB]; +Magic JanggiElephantMagics[SQUARE_NB]; +Magic CannonDiagMagics[SQUARE_NB]; +Magic NightriderMagics[SQUARE_NB]; +Magic GrasshopperMagicsH[SQUARE_NB]; +Magic GrasshopperMagicsV[SQUARE_NB]; +Magic GrasshopperMagicsD[SQUARE_NB]; + +Magic* magics[] = {BishopMagics, RookMagicsH, RookMagicsV, CannonMagicsH, CannonMagicsV, + LameDabbabaMagics, HorseMagics, ElephantMagics, JanggiElephantMagics, CannonDiagMagics, NightriderMagics, + GrasshopperMagicsH, GrasshopperMagicsV, GrasshopperMagicsD}; + +namespace { + +// Some magics need to be split in order to reduce memory consumption. +// Otherwise on a 12x10 board they can be >100 MB. +#ifdef LARGEBOARDS + Bitboard RookTableH[0x11800]; // To store horizontal rook 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 LameDabbabaTable[0x500]; // To store lame dabbaba attacks + Bitboard HorseTable[0x500]; // To store horse attacks + Bitboard ElephantTable[0x400]; // To store elephant attacks + Bitboard JanggiElephantTable[0x1C000]; // To store janggi elephant attacks + Bitboard CannonDiagTable[0x33C00]; // To store diagonal cannon attacks + Bitboard NightriderTable[0x70200]; // To store nightrider attacks + Bitboard GrasshopperTableH[0x11800]; // To store horizontal grasshopper attacks + Bitboard GrasshopperTableV[0x4800]; // To store vertical grasshopper attacks + Bitboard GrasshopperTableD[0x33C00]; // To store diagonal grasshopper 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 LameDabbabaTable[0x240]; // To store lame dabbaba attacks + Bitboard HorseTable[0x240]; // To store horse attacks + Bitboard ElephantTable[0x1A0]; // To store elephant attacks + Bitboard JanggiElephantTable[0x5C00]; // To store janggi elephant attacks + Bitboard CannonDiagTable[0x1480]; // To store diagonal cannon attacks + Bitboard NightriderTable[0x1840]; // To store nightrider attacks + Bitboard GrasshopperTableH[0xA00]; // To store horizontal grasshopper attacks + Bitboard GrasshopperTableV[0xA00]; // To store vertical grasshopper attacks + Bitboard GrasshopperTableD[0x1480]; // To store diagonal grasshopper attacks +#endif + + // Rider directions + const std::map RookDirectionsV { {NORTH, 0}, {SOUTH, 0}}; + const std::map RookDirectionsH { {EAST, 0}, {WEST, 0} }; + const std::map BishopDirections { {NORTH_EAST, 0}, {SOUTH_EAST, 0}, {SOUTH_WEST, 0}, {NORTH_WEST, 0} }; + const std::map LameDabbabaDirections { {2 * NORTH, 0}, {2 * EAST, 0}, {2 * SOUTH, 0}, {2 * WEST, 0} }; + const std::map HorseDirections { {2 * SOUTH + WEST, 0}, {2 * SOUTH + EAST, 0}, {SOUTH + 2 * WEST, 0}, {SOUTH + 2 * EAST, 0}, + {NORTH + 2 * WEST, 0}, {NORTH + 2 * EAST, 0}, {2 * NORTH + WEST, 0}, {2 * NORTH + EAST, 0} }; + const std::map ElephantDirections { {2 * NORTH_EAST, 0}, {2 * SOUTH_EAST, 0}, {2 * SOUTH_WEST, 0}, {2 * NORTH_WEST, 0} }; + const std::map JanggiElephantDirections { {NORTH + 2 * NORTH_EAST, 0}, {EAST + 2 * NORTH_EAST, 0}, + {EAST + 2 * SOUTH_EAST, 0}, {SOUTH + 2 * SOUTH_EAST, 0}, + {SOUTH + 2 * SOUTH_WEST, 0}, {WEST + 2 * SOUTH_WEST, 0}, + {WEST + 2 * NORTH_WEST, 0}, {NORTH + 2 * NORTH_WEST, 0} }; + const std::map GrasshopperDirectionsV { {NORTH, 1}, {SOUTH, 1}}; + const std::map GrasshopperDirectionsH { {EAST, 1}, {WEST, 1} }; + const std::map GrasshopperDirectionsD { {NORTH_EAST, 1}, {SOUTH_EAST, 1}, {SOUTH_WEST, 1}, {NORTH_WEST, 1} }; + + enum MovementType { RIDER, HOPPER, LAME_LEAPER, HOPPER_RANGE }; + + template +#ifdef PRECOMPUTED_MAGICS + void init_magics(Bitboard table[], Magic magics[], std::map directions, const Bitboard magicsInit[]); +#else + void init_magics(Bitboard table[], Magic magics[], std::map directions); +#endif + + template + Bitboard sliding_attack(std::map directions, Square sq, Bitboard occupied, Color c = WHITE) { + assert(MT != LAME_LEAPER); + + Bitboard attack = 0; + + for (auto const& [d, limit] : directions) + { + int count = 0; + bool hurdle = false; + for (Square s = sq + (c == WHITE ? d : -d); + is_ok(s) && distance(s, s - (c == WHITE ? d : -d)) <= 2; + s += (c == WHITE ? d : -d)) + { + if (MT != HOPPER || hurdle) + { + attack |= s; + // For hoppers we consider limit == 1 as a grasshopper, + // but limit > 1 as a limited distance hopper + if (limit && !(MT == HOPPER_RANGE && limit == 1) && ++count >= limit) + break; + } + + 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; + } + + Bitboard lame_leaper_path(std::map directions, Square s) { + Bitboard b = 0; + for (const auto& i : directions) + b |= lame_leaper_path(i.first, s); + return b; + } + + Bitboard lame_leaper_attack(std::map directions, Square s, Bitboard occupied) { + Bitboard b = 0; + for (const auto& i : directions) + { + Square to = s + i.first; + if (is_ok(to) && distance(s, to) < 4 && !(lame_leaper_path(i.first, 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) <= 3 ? square_bb(to) : Bitboard(0); +} + + +/// Bitboards::pretty() returns an ASCII representation of a bitboard suitable +/// to be printed to standard output. Useful for debugging. + +std::string Bitboards::pretty(Bitboard b) { + + std::string s = "+---+---+---+---+---+---+---+---+---+---+---+---+\n"; + + for (Rank r = RANK_MAX; r >= RANK_1; --r) + { + for (File f = FILE_A; f <= FILE_MAX; ++f) + s += b & make_square(f, r) ? "| X " : "| "; + + s += "| " + std::to_string(1 + r) + "\n+---+---+---+---+---+---+---+---+---+---+---+---+\n"; + } + s += " a b c d e f g h i j k l\n"; + + return s; +} + +/// Bitboards::init_pieces() initializes piece move/attack bitboards and rider types + +void Bitboards::init_pieces() { + + for (PieceType pt = PAWN; pt <= KING; ++pt) + { + const PieceInfo* pi = pieceMap.find(pt)->second; + + // Detect rider types + for (auto modality : {MODALITY_QUIET, MODALITY_CAPTURE}) + { + for (bool initial : {false, true}) + { + // We do not support initial captures + if (modality == MODALITY_CAPTURE && initial) + continue; + auto& riderTypes = modality == MODALITY_CAPTURE ? AttackRiderTypes[pt] : MoveRiderTypes[initial][pt]; + riderTypes = NO_RIDER; + for (auto const& [d, limit] : pi->steps[initial][modality]) + { + if (limit && LameDabbabaDirections.find(d) != LameDabbabaDirections.end()) + riderTypes |= RIDER_LAME_DABBABA; + if (limit && HorseDirections.find(d) != HorseDirections.end()) + riderTypes |= RIDER_HORSE; + if (limit && ElephantDirections.find(d) != ElephantDirections.end()) + riderTypes |= RIDER_ELEPHANT; + if (limit && JanggiElephantDirections.find(d) != JanggiElephantDirections.end()) + riderTypes |= RIDER_JANGGI_ELEPHANT; + } + for (auto const& [d, limit] : pi->slider[initial][modality]) + { + if (BishopDirections.find(d) != BishopDirections.end()) + riderTypes |= RIDER_BISHOP; + if (RookDirectionsH.find(d) != RookDirectionsH.end()) + riderTypes |= RIDER_ROOK_H; + if (RookDirectionsV.find(d) != RookDirectionsV.end()) + riderTypes |= RIDER_ROOK_V; + if (HorseDirections.find(d) != HorseDirections.end()) + riderTypes |= RIDER_NIGHTRIDER; + } + for (auto const& [d, limit] : pi->hopper[initial][modality]) + { + if (RookDirectionsH.find(d) != RookDirectionsH.end()) + riderTypes |= limit == 1 ? RIDER_GRASSHOPPER_H : RIDER_CANNON_H; + if (RookDirectionsV.find(d) != RookDirectionsV.end()) + riderTypes |= limit == 1 ? RIDER_GRASSHOPPER_V : RIDER_CANNON_V; + if (BishopDirections.find(d) != BishopDirections.end()) + riderTypes |= limit == 1 ? RIDER_GRASSHOPPER_D : RIDER_CANNON_DIAG; + } + } + } + + // Initialize move/attack bitboards + for (Color c : { WHITE, BLACK }) + { + for (Square s = SQ_A1; s <= SQ_MAX; ++s) + { + for (auto modality : {MODALITY_QUIET, MODALITY_CAPTURE}) + { + for (bool initial : {false, true}) + { + // We do not support initial captures + if (modality == MODALITY_CAPTURE && initial) + continue; + auto& pseudo = modality == MODALITY_CAPTURE ? PseudoAttacks[c][pt][s] : PseudoMoves[initial][c][pt][s]; + auto& leaper = modality == MODALITY_CAPTURE ? LeaperAttacks[c][pt][s] : LeaperMoves[initial][c][pt][s]; + pseudo = 0; + leaper = 0; + for (auto const& [d, limit] : pi->steps[initial][modality]) + { + pseudo |= safe_destination(s, c == WHITE ? d : -d); + if (!limit) + leaper |= safe_destination(s, c == WHITE ? d : -d); + } + pseudo |= sliding_attack(pi->slider[initial][modality], s, 0, c); + pseudo |= sliding_attack(pi->hopper[initial][modality], s, 0, c); + } + } + } + } + } +} + + +/// Bitboards::init() initializes various bitboard tables. It is called at +/// startup and relies on global objects to be already zero-initialized. + +void Bitboards::init() { + + for (unsigned i = 0; i < (1 << 16); ++i) + PopCnt16[i] = uint8_t(std::bitset<16>(i).count()); + + for (Square s = SQ_A1; s <= SQ_MAX; ++s) + SquareBB[s] = make_bitboard(s); + + for (File f = FILE_A; f <= FILE_MAX; ++f) + for (Rank r = RANK_1; r <= RANK_MAX; ++r) + BoardSizeBB[f][r] = forward_file_bb(BLACK, make_square(f, r)) | SquareBB[make_square(f, r)] | (f > FILE_A ? BoardSizeBB[f - 1][r] : Bitboard(0)); + + for (Square s1 = SQ_A1; s1 <= SQ_MAX; ++s1) + for (Square s2 = SQ_A1; s2 <= SQ_MAX; ++s2) + SquareDistance[s1][s2] = std::max(distance(s1, s2), distance(s1, s2)); + +#ifdef PRECOMPUTED_MAGICS + init_magics(RookTableH, RookMagicsH, RookDirectionsH, RookMagicHInit); + init_magics(RookTableV, RookMagicsV, RookDirectionsV, RookMagicVInit); + init_magics(BishopTable, BishopMagics, BishopDirections, BishopMagicInit); + init_magics(CannonTableH, CannonMagicsH, RookDirectionsH, CannonMagicHInit); + init_magics(CannonTableV, CannonMagicsV, RookDirectionsV, CannonMagicVInit); + init_magics(LameDabbabaTable, LameDabbabaMagics, LameDabbabaDirections, LameDabbabaMagicInit); + init_magics(HorseTable, HorseMagics, HorseDirections, HorseMagicInit); + init_magics(ElephantTable, ElephantMagics, ElephantDirections, ElephantMagicInit); + init_magics(JanggiElephantTable, JanggiElephantMagics, JanggiElephantDirections, JanggiElephantMagicInit); + init_magics(CannonDiagTable, CannonDiagMagics, BishopDirections, CannonDiagMagicInit); + init_magics(NightriderTable, NightriderMagics, HorseDirections, NightriderMagicInit); + init_magics(GrasshopperTableH, GrasshopperMagicsH, GrasshopperDirectionsH, GrasshopperMagicHInit); + init_magics(GrasshopperTableV, GrasshopperMagicsV, GrasshopperDirectionsV, GrasshopperMagicVInit); + init_magics(GrasshopperTableD, GrasshopperMagicsD, GrasshopperDirectionsD, GrasshopperMagicDInit); +#else + init_magics(RookTableH, RookMagicsH, RookDirectionsH); + init_magics(RookTableV, RookMagicsV, RookDirectionsV); + init_magics(BishopTable, BishopMagics, BishopDirections); + init_magics(CannonTableH, CannonMagicsH, RookDirectionsH); + init_magics(CannonTableV, CannonMagicsV, RookDirectionsV); + init_magics(LameDabbabaTable, LameDabbabaMagics, LameDabbabaDirections); + init_magics(HorseTable, HorseMagics, HorseDirections); + init_magics(ElephantTable, ElephantMagics, ElephantDirections); + init_magics(JanggiElephantTable, JanggiElephantMagics, JanggiElephantDirections); + init_magics(CannonDiagTable, CannonDiagMagics, BishopDirections); + init_magics(NightriderTable, NightriderMagics, HorseDirections); + init_magics(GrasshopperTableH, GrasshopperMagicsH, GrasshopperDirectionsH); + init_magics(GrasshopperTableV, GrasshopperMagicsV, GrasshopperDirectionsV); + init_magics(GrasshopperTableD, GrasshopperMagicsD, GrasshopperDirectionsD); +#endif + + init_pieces(); + + for (Square s1 = SQ_A1; s1 <= SQ_MAX; ++s1) + { + for (PieceType pt : { BISHOP, ROOK }) + for (Square s2 = SQ_A1; s2 <= SQ_MAX; ++s2) + { + if (PseudoAttacks[WHITE][pt][s1] & s2) + { + LineBB[s1][s2] = (attacks_bb(WHITE, pt, s1, 0) & attacks_bb(WHITE, pt, s2, 0)) | s1 | s2; + BetweenBB[s1][s2] = (attacks_bb(WHITE, pt, s1, square_bb(s2)) & attacks_bb(WHITE, pt, s2, square_bb(s1))); + } + BetweenBB[s1][s2] |= s2; + } + } +} + +namespace { + + // init_magics() computes all rook and bishop attacks at startup. Magic + // bitboards are used to look up attacks of sliding pieces. As a reference see + // www.chessprogramming.org/Magic_Bitboards. In particular, here we use the so + // called "fancy" approach. + + template +#ifdef PRECOMPUTED_MAGICS + void init_magics(Bitboard table[], Magic magics[], std::map directions, const Bitboard magicsInit[]) { +#else + void init_magics(Bitboard table[], Magic magics[], std::map directions) { +#endif + + // Optimal PRNG seeds to pick the correct magics in the shortest time +#ifndef PRECOMPUTED_MAGICS +#ifdef LARGEBOARDS + int seeds[][RANK_NB] = { { 734, 10316, 55013, 32803, 12281, 15100, 16645, 255, 346, 89123 }, + { 734, 10316, 55013, 32803, 12281, 15100, 16645, 255, 346, 89123 } }; +#else + int seeds[][RANK_NB] = { { 8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020 }, + { 728, 10316, 55013, 32803, 12281, 15100, 16645, 255 } }; +#endif +#endif + + Bitboard* occupancy = new Bitboard[1 << (FILE_NB + RANK_NB - 4)]; + Bitboard* reference = new Bitboard[1 << (FILE_NB + RANK_NB - 4)]; + Bitboard edges, b; + int* epoch = new int[1 << (FILE_NB + RANK_NB - 4)](); + int cnt = 0, size = 0; + + for (Square s = SQ_A1; s <= SQ_MAX; ++s) + { + // Board edges are not considered in the relevant occupancies + edges = ((Rank1BB | rank_bb(RANK_MAX)) & ~rank_bb(s)) | ((FileABB | file_bb(FILE_MAX)) & ~file_bb(s)); + + // Given a square 's', the mask is the bitboard of sliding attacks from + // 's' computed on an empty board. The index must be big enough to contain + // all the attacks for each possible subset of the mask and so is 2 power + // the number of 1s of the mask. Hence we deduce the size of the shift to + // apply to the 64 or 32 bits word to get the index. + Magic& m = magics[s]; + // The mask for hoppers is unlimited distance, even if the hopper is limited distance (e.g., grasshopper) + m.mask = (MT == LAME_LEAPER ? lame_leaper_path(directions, s) : sliding_attack(directions, s, 0)) & ~edges; +#ifdef LARGEBOARDS + m.shift = 128 - popcount(m.mask); +#else + m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask); +#endif + + // Set the offset for the attacks table of the square. We have individual + // table sizes for each square with "Fancy Magic Bitboards". + m.attacks = s == SQ_A1 ? table : magics[s - 1].attacks + size; + + // Use Carry-Rippler trick to enumerate all subsets of masks[s] and + // store the corresponding sliding attack bitboard in reference[]. + b = size = 0; + do { + occupancy[size] = b; + reference[size] = MT == LAME_LEAPER ? lame_leaper_attack(directions, s, b) : sliding_attack(directions, s, b); + + if (HasPext) + m.attacks[pext(b, m.mask)] = reference[size]; + + size++; + b = (b - m.mask) & m.mask; + } while (b); + + if (HasPext) + continue; + +#ifndef PRECOMPUTED_MAGICS + PRNG rng(seeds[Is64Bit][rank_of(s)]); +#endif + + // Find a magic for square 's' picking up an (almost) random number + // until we find the one that passes the verification test. + for (int i = 0; i < size; ) + { + for (m.magic = 0; popcount((m.magic * m.mask) >> (SQUARE_NB - FILE_NB)) < FILE_NB - 2; ) + { +#ifdef LARGEBOARDS +#ifdef PRECOMPUTED_MAGICS + m.magic = magicsInit[s]; +#else + m.magic = (rng.sparse_rand() << 64) ^ rng.sparse_rand(); +#endif +#else + m.magic = rng.sparse_rand(); +#endif + } + + // A good magic must map every possible occupancy to an index that + // looks up the correct sliding attack in the attacks[s] database. + // Note that we build up the database for square 's' as a side + // effect of verifying the magic. Keep track of the attempt count + // and save it in epoch[], little speed-up trick to avoid resetting + // m.attacks[] after every failed attempt. + for (++cnt, i = 0; i < size; ++i) + { + unsigned idx = m.index(occupancy[i]); + + if (epoch[idx] < cnt) + { + epoch[idx] = cnt; + m.attacks[idx] = reference[i]; + } + else if (m.attacks[idx] != reference[i]) + break; + } + } + } + + delete[] occupancy; + delete[] reference; + delete[] epoch; + } +} + +} // namespace Stockfish diff --git a/Fairy-Stockfish-master/src/bitboard.h b/Fairy-Stockfish-master/src/bitboard.h new file mode 100644 index 0000000000000000000000000000000000000000..ce575b5d3f34a090eab35143717c729f5b731ed7 --- /dev/null +++ b/Fairy-Stockfish-master/src/bitboard.h @@ -0,0 +1,787 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef BITBOARD_H_INCLUDED +#define BITBOARD_H_INCLUDED + +#include + +#include "types.h" + +namespace Stockfish { + +namespace Bitbases { + +void init(); +bool probe(Square wksq, Square wpsq, Square bksq, Color us); + +} // namespace Stockfish::Bitbases + +namespace Bitboards { + +void init_pieces(); +void init(); +std::string pretty(Bitboard b); + +} // namespace Stockfish::Bitboards + +#ifdef LARGEBOARDS +constexpr Bitboard AllSquares = ((~Bitboard(0)) >> 8); +#else +constexpr Bitboard AllSquares = ~Bitboard(0); +#endif +#ifdef LARGEBOARDS +constexpr Bitboard DarkSquares = (Bitboard(0xAAA555AAA555AAULL) << 64) ^ Bitboard(0xA555AAA555AAA555ULL); +#else +constexpr Bitboard DarkSquares = 0xAA55AA55AA55AA55ULL; +#endif + +#ifdef LARGEBOARDS +constexpr Bitboard FileABB = (Bitboard(0x00100100100100ULL) << 64) ^ Bitboard(0x1001001001001001ULL); +#else +constexpr Bitboard FileABB = 0x0101010101010101ULL; +#endif +constexpr Bitboard FileBBB = FileABB << 1; +constexpr Bitboard FileCBB = FileABB << 2; +constexpr Bitboard FileDBB = FileABB << 3; +constexpr Bitboard FileEBB = FileABB << 4; +constexpr Bitboard FileFBB = FileABB << 5; +constexpr Bitboard FileGBB = FileABB << 6; +constexpr Bitboard FileHBB = FileABB << 7; +#ifdef LARGEBOARDS +constexpr Bitboard FileIBB = FileABB << 8; +constexpr Bitboard FileJBB = FileABB << 9; +constexpr Bitboard FileKBB = FileABB << 10; +constexpr Bitboard FileLBB = FileABB << 11; +#endif + + +#ifdef LARGEBOARDS +constexpr Bitboard Rank1BB = 0xFFF; +#else +constexpr Bitboard Rank1BB = 0xFF; +#endif +constexpr Bitboard Rank2BB = Rank1BB << (FILE_NB * 1); +constexpr Bitboard Rank3BB = Rank1BB << (FILE_NB * 2); +constexpr Bitboard Rank4BB = Rank1BB << (FILE_NB * 3); +constexpr Bitboard Rank5BB = Rank1BB << (FILE_NB * 4); +constexpr Bitboard Rank6BB = Rank1BB << (FILE_NB * 5); +constexpr Bitboard Rank7BB = Rank1BB << (FILE_NB * 6); +constexpr Bitboard Rank8BB = Rank1BB << (FILE_NB * 7); +#ifdef LARGEBOARDS +constexpr Bitboard Rank9BB = Rank1BB << (FILE_NB * 8); +constexpr Bitboard Rank10BB = Rank1BB << (FILE_NB * 9); +#endif + +constexpr Bitboard QueenSide = FileABB | FileBBB | FileCBB | FileDBB; +constexpr Bitboard CenterFiles = FileCBB | FileDBB | FileEBB | FileFBB; +constexpr Bitboard KingSide = FileEBB | FileFBB | FileGBB | FileHBB; +constexpr Bitboard Center = (FileDBB | FileEBB) & (Rank4BB | Rank5BB); + +constexpr Bitboard KingFlank[FILE_NB] = { + QueenSide ^ FileDBB, QueenSide, QueenSide, + CenterFiles, CenterFiles, + KingSide, KingSide, KingSide ^ FileEBB +}; + +extern uint8_t PopCnt16[1 << 16]; +extern uint8_t SquareDistance[SQUARE_NB][SQUARE_NB]; + +extern Bitboard SquareBB[SQUARE_NB]; +extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; +extern Bitboard LineBB[SQUARE_NB][SQUARE_NB]; +extern Bitboard PseudoAttacks[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; +extern Bitboard PseudoMoves[2][COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; +extern Bitboard LeaperAttacks[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; +extern Bitboard LeaperMoves[2][COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; +extern Bitboard SquareBB[SQUARE_NB]; +extern Bitboard BoardSizeBB[FILE_NB][RANK_NB]; +extern RiderType AttackRiderTypes[PIECE_TYPE_NB]; +extern RiderType MoveRiderTypes[2][PIECE_TYPE_NB]; + +#ifdef LARGEBOARDS +int popcount(Bitboard b); // required for 128 bit pext +#endif + +/// Magic holds all magic bitboards relevant data for a single square +struct Magic { + Bitboard mask; + Bitboard magic; + Bitboard* attacks; + unsigned shift; + + // Compute the attack's index using the 'magic bitboards' approach + unsigned index(Bitboard occupied) const { + + if (HasPext) + return unsigned(pext(occupied, mask)); + +#ifdef LARGEBOARDS + return unsigned(((occupied & mask) * magic) >> shift); +#else + if (Is64Bit) + return unsigned(((occupied & mask) * magic) >> shift); +#endif + + unsigned lo = unsigned(occupied) & unsigned(mask); + unsigned hi = unsigned(occupied >> 32) & unsigned(mask >> 32); + return (lo * unsigned(magic) ^ hi * unsigned(magic >> 32)) >> shift; + } +}; + +extern Magic RookMagicsH[SQUARE_NB]; +extern Magic RookMagicsV[SQUARE_NB]; +extern Magic BishopMagics[SQUARE_NB]; +extern Magic CannonMagicsH[SQUARE_NB]; +extern Magic CannonMagicsV[SQUARE_NB]; +extern Magic LameDabbabaMagics[SQUARE_NB]; +extern Magic HorseMagics[SQUARE_NB]; +extern Magic ElephantMagics[SQUARE_NB]; +extern Magic JanggiElephantMagics[SQUARE_NB]; +extern Magic CannonDiagMagics[SQUARE_NB]; +extern Magic NightriderMagics[SQUARE_NB]; +extern Magic GrasshopperMagicsH[SQUARE_NB]; +extern Magic GrasshopperMagicsV[SQUARE_NB]; +extern Magic GrasshopperMagicsD[SQUARE_NB]; + +extern Magic* magics[]; + +constexpr Bitboard make_bitboard() { return 0; } + +template +constexpr Bitboard make_bitboard(Square s, Squares... squares) { + return (Bitboard(1) << s) | make_bitboard(squares...); +} + +inline Bitboard square_bb(Square s) { + assert(is_ok(s)); + return SquareBB[s]; +} + + +/// Overloads of bitwise operators between a Bitboard and a Square for testing +/// whether a given bit is set in a bitboard, and for setting and clearing bits. + +inline Bitboard operator&( Bitboard b, Square s) { return b & square_bb(s); } +inline Bitboard operator|( Bitboard b, Square s) { return b | square_bb(s); } +inline Bitboard operator^( Bitboard b, Square s) { return b ^ square_bb(s); } +inline Bitboard& operator|=(Bitboard& b, Square s) { return b |= square_bb(s); } +inline Bitboard& operator^=(Bitboard& b, Square s) { return b ^= square_bb(s); } + +inline Bitboard operator-( Bitboard b, Square s) { return b & ~square_bb(s); } +inline Bitboard& operator-=(Bitboard& b, Square s) { return b &= ~square_bb(s); } + +inline Bitboard operator&(Square s, Bitboard b) { return b & s; } +inline Bitboard operator|(Square s, Bitboard b) { return b | s; } +inline Bitboard operator^(Square s, Bitboard b) { return b ^ s; } + +inline Bitboard operator|(Square s1, Square s2) { return square_bb(s1) | s2; } + +constexpr bool more_than_one(Bitboard b) { + return b & (b - 1); +} + + +inline Bitboard undo_move_board(Bitboard b, Move m) { + return (from_sq(m) != SQ_NONE && (b & to_sq(m))) ? (b ^ to_sq(m)) | from_sq(m) : b; +} + +/// board_size_bb() returns a bitboard representing all the squares +/// on a board with given size. + +inline Bitboard board_size_bb(File f, Rank r) { + return BoardSizeBB[f][r]; +} + +constexpr bool opposite_colors(Square s1, Square s2) { + return (s1 + rank_of(s1) + s2 + rank_of(s2)) & 1; +} + + +/// rank_bb() and file_bb() return a bitboard representing all the squares on +/// the given file or rank. + +constexpr Bitboard rank_bb(Rank r) { + return Rank1BB << (FILE_NB * r); +} + +constexpr Bitboard rank_bb(Square s) { + return rank_bb(rank_of(s)); +} + +constexpr Bitboard file_bb(File f) { + return FileABB << f; +} + +constexpr Bitboard file_bb(Square s) { + return file_bb(file_of(s)); +} + + +/// shift() moves a bitboard one or two steps as specified by the direction D + +template +constexpr Bitboard shift(Bitboard b) { + return D == NORTH ? b << NORTH : D == SOUTH ? b >> NORTH + : D == NORTH+NORTH? b <<(2 * NORTH) : D == SOUTH+SOUTH? b >> (2 * NORTH) + : D == EAST ? (b & ~file_bb(FILE_MAX)) << EAST : D == WEST ? (b & ~FileABB) >> EAST + : D == NORTH_EAST ? (b & ~file_bb(FILE_MAX)) << NORTH_EAST : D == NORTH_WEST ? (b & ~FileABB) << NORTH_WEST + : D == SOUTH_EAST ? (b & ~file_bb(FILE_MAX)) >> NORTH_WEST : D == SOUTH_WEST ? (b & ~FileABB) >> NORTH_EAST + : Bitboard(0); +} + + +/// shift() moves a bitboard one step along direction D (mainly for pawns) + +constexpr Bitboard shift(Direction D, Bitboard b) { + return D == NORTH ? b << NORTH : D == SOUTH ? b >> NORTH + : D == NORTH+NORTH? b <<(2 * NORTH) : D == SOUTH+SOUTH? b >> (2 * NORTH) + : D == EAST ? (b & ~file_bb(FILE_MAX)) << EAST : D == WEST ? (b & ~FileABB) >> EAST + : D == NORTH_EAST ? (b & ~file_bb(FILE_MAX)) << NORTH_EAST : D == NORTH_WEST ? (b & ~FileABB) << NORTH_WEST + : D == SOUTH_EAST ? (b & ~file_bb(FILE_MAX)) >> NORTH_WEST : D == SOUTH_WEST ? (b & ~FileABB) >> NORTH_EAST + : Bitboard(0); +} + + +/// pawn_attacks_bb() returns the squares attacked by pawns of the given color +/// from the squares in the given bitboard. + +template +constexpr Bitboard pawn_attacks_bb(Bitboard b) { + return C == WHITE ? shift(b) | shift(b) + : shift(b) | shift(b); +} + +inline Bitboard pawn_attacks_bb(Color c, Square s) { + + assert(is_ok(s)); + return PseudoAttacks[c][PAWN][s]; +} + + +/// pawn_double_attacks_bb() returns the squares doubly attacked by pawns of the +/// given color from the squares in the given bitboard. + +template +constexpr Bitboard pawn_double_attacks_bb(Bitboard b) { + return C == WHITE ? shift(b) & shift(b) + : shift(b) & shift(b); +} + + +/// adjacent_files_bb() returns a bitboard representing all the squares on the +/// adjacent files of a given square. + +constexpr Bitboard adjacent_files_bb(Square s) { + return shift(file_bb(s)) | shift(file_bb(s)); +} + + +/// line_bb() returns a bitboard representing an entire line (from board edge +/// to board edge) that intersects the two given squares. If the given squares +/// are not on a same file/rank/diagonal, the function returns 0. For instance, +/// line_bb(SQ_C4, SQ_F7) will return a bitboard with the A2-G8 diagonal. + +inline Bitboard line_bb(Square s1, Square s2) { + + assert(is_ok(s1) && is_ok(s2)); + + return LineBB[s1][s2]; +} + + +/// between_bb(s1, s2) returns a bitboard representing the squares in the semi-open +/// segment between the squares s1 and s2 (excluding s1 but including s2). If the +/// given squares are not on a same file/rank/diagonal, it returns s2. For instance, +/// between_bb(SQ_C4, SQ_F7) will return a bitboard with squares D5, E6 and F7, but +/// between_bb(SQ_E6, SQ_F8) will return a bitboard with the square F8. This trick +/// allows to generate non-king evasion moves faster: the defending piece must either +/// interpose itself to cover the check or capture the checking piece. + +inline Bitboard between_bb(Square s1, Square s2) { + + assert(is_ok(s1) && is_ok(s2)); + + return BetweenBB[s1][s2]; +} + +inline Bitboard between_bb(Square s1, Square s2, PieceType pt) { + if (pt == HORSE) + return PseudoAttacks[WHITE][WAZIR][s2] & PseudoAttacks[WHITE][FERS][s1]; + else if (pt == JANGGI_ELEPHANT) + return (PseudoAttacks[WHITE][WAZIR][s2] & PseudoAttacks[WHITE][ALFIL][s1]) + | (PseudoAttacks[WHITE][KNIGHT][s2] & PseudoAttacks[WHITE][FERS][s1]); + else + return between_bb(s1, s2); +} + + +/// forward_ranks_bb() returns a bitboard representing the squares on the ranks in +/// front of the given one, from the point of view of the given color. For instance, +/// forward_ranks_bb(BLACK, SQ_D3) will return the 16 squares on ranks 1 and 2. + +constexpr Bitboard forward_ranks_bb(Color c, Square s) { + return c == WHITE ? (AllSquares ^ Rank1BB) << FILE_NB * relative_rank(WHITE, s, RANK_MAX) + : (AllSquares ^ rank_bb(RANK_MAX)) >> FILE_NB * relative_rank(BLACK, s, RANK_MAX); +} + +constexpr Bitboard forward_ranks_bb(Color c, Rank r) { + return c == WHITE ? (AllSquares ^ Rank1BB) << FILE_NB * (r - RANK_1) + : (AllSquares ^ rank_bb(RANK_MAX)) >> FILE_NB * (RANK_MAX - r); +} + + +/// zone_bb() returns a bitboard representing the squares on all the ranks +/// in front of and on the given relative rank, from the point of view of the given color. +/// For instance, zone_bb(BLACK, RANK_7) will return the 16 squares on ranks 1 and 2. + +inline Bitboard zone_bb(Color c, Rank r, Rank maxRank) { + return forward_ranks_bb(c, relative_rank(c, r, maxRank)) | rank_bb(relative_rank(c, r, maxRank)); +} + + +/// forward_file_bb() returns a bitboard representing all the squares along the +/// line in front of the given one, from the point of view of the given color. + +constexpr Bitboard forward_file_bb(Color c, Square s) { + return forward_ranks_bb(c, s) & file_bb(s); +} + + +/// pawn_attack_span() returns a bitboard representing all the squares that can +/// be attacked by a pawn of the given color when it moves along its file, starting +/// from the given square. + +constexpr Bitboard pawn_attack_span(Color c, Square s) { + return forward_ranks_bb(c, s) & adjacent_files_bb(s); +} + + +/// passed_pawn_span() returns a bitboard which can be used to test if a pawn of +/// the given color and on the given square is a passed pawn. + +constexpr Bitboard passed_pawn_span(Color c, Square s) { + return pawn_attack_span(c, s) | forward_file_bb(c, s); +} + + +/// aligned() returns true if the squares s1, s2 and s3 are aligned either on a +/// straight or on a diagonal line. + +inline bool aligned(Square s1, Square s2, Square s3) { + return line_bb(s1, s2) & s3; +} + + +/// distance() functions return the distance between x and y, defined as the +/// number of steps for a king in x to reach y. + +template inline int distance(Square x, Square y); +template<> inline int distance(Square x, Square y) { return std::abs(file_of(x) - file_of(y)); } +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, 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)); } + + +template +inline Bitboard rider_attacks_bb(Square s, Bitboard occupied) { + + static_assert(R != NO_RIDER && !(R & (R - 1))); // exactly one bit + 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_LAME_DABBABA ? LameDabbabaMagics[s] + : R == RIDER_HORSE ? HorseMagics[s] + : R == RIDER_ELEPHANT ? ElephantMagics[s] + : R == RIDER_JANGGI_ELEPHANT ? JanggiElephantMagics[s] + : R == RIDER_CANNON_DIAG ? CannonDiagMagics[s] + : R == RIDER_NIGHTRIDER ? NightriderMagics[s] + : R == RIDER_GRASSHOPPER_H ? GrasshopperMagicsH[s] + : R == RIDER_GRASSHOPPER_V ? GrasshopperMagicsV[s] + : R == RIDER_GRASSHOPPER_D ? GrasshopperMagicsD[s] + : BishopMagics[s]; + return m.attacks[m.index(occupied)]; +} + +inline Square lsb(Bitboard b); + +inline Bitboard rider_attacks_bb(RiderType R, Square s, Bitboard occupied) { + + assert(R != NO_RIDER && !(R & (R - 1))); // exactly one bit + const Magic& m = magics[lsb(R)][s]; // re-use Bitboard lsb for riders + return m.attacks[m.index(occupied)]; +} + + +/// attacks_bb(Square) returns the pseudo attacks of the give piece type +/// assuming an empty board. + +template +inline Bitboard attacks_bb(Square s) { + + assert((Pt != PAWN) && (is_ok(s))); + + return PseudoAttacks[WHITE][Pt][s]; +} + + +/// attacks_bb(Square, Bitboard) returns the attacks by the given piece +/// assuming the board is occupied according to the passed Bitboard. +/// Sliding piece attacks do not continue passed an occupied square. + +template +inline Bitboard attacks_bb(Square s, Bitboard occupied) { + + assert((Pt != PAWN) && (is_ok(s))); + + switch (Pt) + { + case BISHOP: return rider_attacks_bb(s, occupied); + case ROOK : return rider_attacks_bb(s, occupied) | rider_attacks_bb(s, occupied); + case QUEEN : return attacks_bb(s, occupied) | attacks_bb(s, occupied); + default : return PseudoAttacks[WHITE][Pt][s]; + } +} + +/// pop_rider() finds and clears a rider in a (hybrid) rider type + +inline RiderType pop_rider(RiderType* r) { + assert(*r); + const RiderType r2 = *r & ~(*r - 1); + *r &= *r - 1; + return r2; +} + +inline Bitboard attacks_bb(Color c, PieceType pt, Square s, Bitboard occupied) { + Bitboard b = LeaperAttacks[c][pt][s]; + RiderType r = AttackRiderTypes[pt]; + while (r) + b |= rider_attacks_bb(pop_rider(&r), s, occupied); + return b & PseudoAttacks[c][pt][s]; +} + + +template +inline Bitboard moves_bb(Color c, PieceType pt, Square s, Bitboard occupied) { + Bitboard b = LeaperMoves[Initial][c][pt][s]; + RiderType r = MoveRiderTypes[Initial][pt]; + while (r) + b |= rider_attacks_bb(pop_rider(&r), s, occupied); + return b & PseudoMoves[Initial][c][pt][s]; +} + + +/// popcount() counts the number of non-zero bits in a bitboard + +inline int popcount(Bitboard b) { + +#ifndef USE_POPCNT + +#ifdef LARGEBOARDS + union { Bitboard bb; uint16_t u[8]; } v = { b }; + return PopCnt16[v.u[0]] + PopCnt16[v.u[1]] + PopCnt16[v.u[2]] + PopCnt16[v.u[3]] + + PopCnt16[v.u[4]] + PopCnt16[v.u[5]] + PopCnt16[v.u[6]] + PopCnt16[v.u[7]]; +#else + union { Bitboard bb; uint16_t u[4]; } v = { b }; + return PopCnt16[v.u[0]] + PopCnt16[v.u[1]] + PopCnt16[v.u[2]] + PopCnt16[v.u[3]]; +#endif + +#elif defined(_MSC_VER) || defined(__INTEL_COMPILER) + +#ifdef LARGEBOARDS + return (int)_mm_popcnt_u64(uint64_t(b >> 64)) + (int)_mm_popcnt_u64(uint64_t(b)); +#else + return (int)_mm_popcnt_u64(b); +#endif + +#else // Assumed gcc or compatible compiler + +#ifdef LARGEBOARDS + return __builtin_popcountll(b >> 64) + __builtin_popcountll(b); +#else + return __builtin_popcountll(b); +#endif + +#endif +} + + +/// lsb() and msb() return the least/most significant bit in a non-zero bitboard + +#if defined(__GNUC__) // GCC, Clang, ICC + +inline Square lsb(Bitboard b) { + assert(b); +#ifdef LARGEBOARDS + if (!(b << 64)) + return Square(__builtin_ctzll(b >> 64) + 64); +#endif + return Square(__builtin_ctzll(b)); +} + +inline Square msb(Bitboard b) { + assert(b); +#ifdef LARGEBOARDS + if (b >> 64) + return Square(int(SQUARE_BIT_MASK) ^ __builtin_clzll(b >> 64)); + return Square(int(SQUARE_BIT_MASK) ^ (__builtin_clzll(b) + 64)); +#else + return Square(int(SQUARE_BIT_MASK) ^ __builtin_clzll(b)); +#endif +} + +#elif defined(_MSC_VER) // MSVC + +#ifdef _WIN64 // MSVC, WIN64 + +inline Square lsb(Bitboard b) { + assert(b); + unsigned long idx; +#ifdef LARGEBOARDS + if (uint64_t(b)) + { + _BitScanForward64(&idx, uint64_t(b)); + return Square(idx); + } + else + { + _BitScanForward64(&idx, uint64_t(b >> 64)); + return Square(idx + 64); + } +#else + _BitScanForward64(&idx, b); + return (Square) idx; +#endif +} + +inline Square msb(Bitboard b) { + assert(b); + unsigned long idx; +#ifdef LARGEBOARDS + if (b >> 64) + { + _BitScanReverse64(&idx, uint64_t(b >> 64)); + return Square(idx + 64); + } + else + { + _BitScanReverse64(&idx, uint64_t(b)); + return Square(idx); + } +#else + _BitScanReverse64(&idx, b); + return (Square) idx; +#endif +} + +#else // MSVC, WIN32 + +inline Square lsb(Bitboard b) { + assert(b); + unsigned long idx; + +#ifdef LARGEBOARDS + if (b << 96) { + _BitScanForward(&idx, uint32_t(b)); + return Square(idx); + } else if (b << 64) { + _BitScanForward(&idx, uint32_t(b >> 32)); + return Square(idx + 32); + } else if (b << 32) { + _BitScanForward(&idx, uint32_t(b >> 64)); + return Square(idx + 64); + } else { + _BitScanForward(&idx, uint32_t(b >> 96)); + return Square(idx + 96); + } +#else + if (b & 0xffffffff) { + _BitScanForward(&idx, uint32_t(b)); + return Square(idx); + } else { + _BitScanForward(&idx, uint32_t(b >> 32)); + return Square(idx + 32); + } +#endif +} + +inline Square msb(Bitboard b) { + assert(b); + unsigned long idx; + +#ifdef LARGEBOARDS + if (b >> 96) { + _BitScanReverse(&idx, uint32_t(b >> 96)); + return Square(idx + 96); + } else if (b >> 64) { + _BitScanReverse(&idx, uint32_t(b >> 64)); + return Square(idx + 64); + } else +#endif + if (b >> 32) { + _BitScanReverse(&idx, uint32_t(b >> 32)); + return Square(idx + 32); + } else { + _BitScanReverse(&idx, uint32_t(b)); + return Square(idx); + } +} + +#endif + +#else // Compiler is neither GCC nor MSVC compatible + +#error "Compiler not supported." + +#endif + +/// least_significant_square_bb() returns the bitboard of the least significant +/// square of a non-zero bitboard. It is equivalent to square_bb(lsb(bb)). + +inline Bitboard least_significant_square_bb(Bitboard b) { + assert(b); + return b & -b; +} + +/// pop_lsb() finds and clears the least significant bit in a non-zero bitboard + +inline Square pop_lsb(Bitboard& b) { + assert(b); + const Square s = lsb(b); + b &= b - 1; + return s; +} + + +/// frontmost_sq() returns the most advanced square for the given color, +/// requires a non-zero bitboard. +inline Square frontmost_sq(Color c, Bitboard b) { + assert(b); + return c == WHITE ? msb(b) : lsb(b); +} + + +/// popcount() counts the number of non-zero bits in a piece set + +inline int popcount(PieceSet ps) { + +#ifndef USE_POPCNT + + union { uint64_t bb; uint16_t u[4]; } v = { (uint64_t)ps }; + return PopCnt16[v.u[0]] + PopCnt16[v.u[1]] + PopCnt16[v.u[2]] + PopCnt16[v.u[3]]; + +#elif defined(_MSC_VER) || defined(__INTEL_COMPILER) + + return (int)_mm_popcnt_u64(ps); + +#else // Assumed gcc or compatible compiler + + return __builtin_popcountll(ps); + +#endif +} + +/// lsb() and msb() return the least/most significant bit in a non-zero piece set + +#if defined(__GNUC__) // GCC, Clang, ICC + +inline PieceType lsb(PieceSet ps) { + assert(ps); + return PieceType(__builtin_ctzll(ps)); +} + +inline PieceType msb(PieceSet ps) { + assert(ps); + return PieceType((PIECE_TYPE_NB - 1) ^ __builtin_clzll(ps)); +} + +#elif defined(_MSC_VER) // MSVC + +#ifdef _WIN64 // MSVC, WIN64 + +inline PieceType lsb(PieceSet ps) { + assert(ps); + unsigned long idx; + _BitScanForward64(&idx, ps); + return (PieceType) idx; +} + +inline PieceType msb(PieceSet ps) { + assert(ps); + unsigned long idx; + _BitScanReverse64(&idx, ps); + return (PieceType) idx; +} + +#else // MSVC, WIN32 + +inline PieceType lsb(PieceSet ps) { + assert(ps); + unsigned long idx; + + if (ps & 0xffffffff) { + _BitScanForward(&idx, uint32_t(ps)); + return PieceType(idx); + } else { + _BitScanForward(&idx, uint32_t(ps >> 32)); + return PieceType(idx + 32); + } +} + +inline PieceType msb(PieceSet ps) { + assert(ps); + unsigned long idx; + if (ps >> 32) { + _BitScanReverse(&idx, uint32_t(ps >> 32)); + return PieceType(idx + 32); + } else { + _BitScanReverse(&idx, uint32_t(ps)); + return PieceType(idx); + } +} + +#endif + +#else // Compiler is neither GCC nor MSVC compatible + +#error "Compiler not supported." + +#endif + +/// pop_lsb() and pop_msb() find and clear the least/most significant bit in a non-zero piece set + +inline PieceType pop_lsb(PieceSet& ps) { + assert(ps); + const PieceType pt = lsb(ps); + ps &= PieceSet(ps - 1); + return pt; +} + +inline PieceType pop_msb(PieceSet& ps) { + assert(ps); + const PieceType pt = msb(ps); + ps &= ~piece_set(pt); + return pt; +} + +} // namespace Stockfish + +#endif // #ifndef BITBOARD_H_INCLUDED diff --git a/Fairy-Stockfish-master/src/endgame.cpp b/Fairy-Stockfish-master/src/endgame.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6a8247d903ad3d2d2899a205b69871dc605ed859 --- /dev/null +++ b/Fairy-Stockfish-master/src/endgame.cpp @@ -0,0 +1,1321 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bitboard.h" +#include "endgame.h" +#include "movegen.h" + +namespace Stockfish { + +namespace { + + // Used to drive the king towards the edge of the board + // in KX vs K and KQ vs KR endgames. + // Values range from 27 (center squares) to 90 (in the corners) + inline int push_to_edge(Square s, const Position& pos) { + int rd = edge_distance(rank_of(s), pos.max_rank()), fd = edge_distance(file_of(s), pos.max_file()); + return 90 - (7 * fd * fd / 2 + 7 * rd * rd / 2); + } + + // Used to drive the king towards A1H8 corners in KBN vs K endgames. + // Values range from 0 on A8H1 diagonal to 7 in A1H8 corners. + inline int push_to_corner(Square s, const Position& pos) { + return abs((pos.max_file() + pos.max_rank()) / 2 - rank_of(s) - file_of(s)); + } + + // Used to drive the king towards the edge of the board in KSF vs K. + inline int push_to_opposing_edge(Square s, const Position& pos) { + int rd = rank_of(s), fd = edge_distance(file_of(s), pos.max_file()); + return 20 - (7 * fd * fd / 2 - 7 * rd * rd / 4); + } + + // Drive a piece close to or away from another piece + inline int push_close(Square s1, Square s2) { return 140 - 20 * distance(s1, s2); } + inline int push_away(Square s1, Square s2) { return 120 - push_close(s1, s2); } + +#ifndef NDEBUG + bool verify_material(const Position& pos, Color c, Value npm, int pawnsCnt) { + return pos.non_pawn_material(c) == npm && pos.count(c) == pawnsCnt; + } +#endif + + // Map the square as if strongSide is white and strongSide's only pawn + // is on the left half of the board. + Square normalize(const Position& pos, Color strongSide, Square sq) { + + assert(pos.count(strongSide) == 1); + + if (file_of(pos.square(strongSide)) > pos.max_file() / 2) + sq = flip_file(sq, pos.max_file()); + + return strongSide == WHITE ? sq : flip_rank(sq, pos.max_rank()); + } + +} // namespace + + +namespace Endgames { + + std::pair, Map> maps; + + void init() { + + add("KPK"); + add("KNNK"); + add("KBNK"); + add("KRKP"); + add("KRKB"); + add("KRKN"); + add("KQKP"); + add("KQKR"); + add("KNNKP"); + + // Fairy piece endgames + add("KNSK"); + add("KNFK"); + add("KNSFKR"); + add("KSFK"); + add("KSFKF"); + add("KRKS"); + add("KCKR"); + add("KAKR"); + + // Anti + add("RvM"); + add("MvN"); + add("NvN"); + + // Atomic + add("MPvM"); + add("MNvM"); + add("MBvM"); + add("MRvM"); + add("MQvM"); + add("MNNvM"); + + // Duck + add("MBvM"); + add("MNvM"); + add("MPvM"); + + // Racing kings + add("KQK"); + add("KRK"); + add("KK"); + + add("KRPKB"); + add("KBPKB"); + add("KBPKN"); + add("KBPPKB"); + add("KRPPKRP"); + } +} + + +/// Mate with KX vs K. This function is used to evaluate positions with +/// king and plenty of material vs a lone king. It simply gives the +/// attacking side a bonus for driving the defending king towards the edge +/// of the board, and for keeping the distance between the two kings small. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); + assert(!pos.checkers()); // Eval is never called when in check + + // Stalemate detection with lone king + if (pos.side_to_move() == weakSide && !MoveList(pos).size()) + return VALUE_DRAW; + + Square strongKing = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + + Value result = pos.non_pawn_material(strongSide) + + pos.count(strongSide) * PawnValueEg + + push_to_edge(weakKing, pos) + + push_close(strongKing, weakKing); + + if ( pos.count(strongSide) + || pos.count(strongSide) + ||(pos.count(strongSide) && pos.count(strongSide)) + || ( (pos.pieces(strongSide, BISHOP) & ~DarkSquares) + && (pos.pieces(strongSide, BISHOP) & DarkSquares)) + || pos.count(strongSide) >= 2 + ||(pos.count(strongSide) && pos.count(strongSide)) + ||(pos.count(strongSide) && pos.count(strongSide)) + ||(pos.count(strongSide) && pos.count(strongSide) >= 2) + ||(pos.count(strongSide) >= 3 + && ( DarkSquares & pos.pieces(strongSide, FERS)) + && (~DarkSquares & pos.pieces(strongSide, FERS)))) + result = std::min(result + VALUE_KNOWN_WIN, VALUE_TB_WIN_IN_MAX_PLY - 1); + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// Mate with KBN vs K. This is similar to KX vs K, but we have to drive the +/// defending king towards a corner square that our bishop attacks. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, KnightValueMg + BishopValueMg, 0)); + assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); + + Square strongKing = pos.square(strongSide); + Square strongBishop = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + + // If our bishop does not attack A1/H8, we flip the enemy king square + // to drive to opposite corners (A8/H1). + + Value result = (VALUE_KNOWN_WIN + 3520) + + push_close(strongKing, weakKing) + + 420 * push_to_corner(opposite_colors(strongBishop, SQ_A1) ? flip_file(weakKing, pos.max_file()) : weakKing, pos); + + assert(abs(result) < VALUE_TB_WIN_IN_MAX_PLY); + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// KP vs K. This endgame is evaluated with the help of a bitbase +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, VALUE_ZERO, 1)); + assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); + + // Assume strongSide is white and the pawn is on files A-D + Square strongKing = normalize(pos, strongSide, pos.square(strongSide)); + Square strongPawn = normalize(pos, strongSide, pos.square(strongSide)); + Square weakKing = normalize(pos, strongSide, pos.square(weakSide)); + + Color us = strongSide == pos.side_to_move() ? WHITE : BLACK; + + // Non-standard promotion, evaluation unclear + if ( pos.promotion_zone(us) != rank_bb(relative_rank(us, RANK_8, pos.max_rank())) + || RANK_MAX != RANK_8 + || !(pos.promotion_piece_types(us) & QUEEN)) + { + Value result = PawnValueEg + Value(rank_of(strongPawn)); + return strongSide == pos.side_to_move() ? result : -result; + } + + if (!Bitbases::probe(strongKing, strongPawn, weakKing, us)) + return VALUE_DRAW; + + Value result = VALUE_KNOWN_WIN + PawnValueEg + Value(rank_of(strongPawn)); + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// KR vs KP. This is a somewhat tricky endgame to evaluate precisely without +/// a bitbase. The function below returns drawish scores when the pawn is +/// far advanced with support of the king, while the attacking king is far +/// away. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, RookValueMg, 0)); + assert(verify_material(pos, weakSide, VALUE_ZERO, 1)); + + Square strongKing = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + Square strongRook = pos.square(strongSide); + Square weakPawn = pos.square(weakSide); + Square queeningSquare = make_square(file_of(weakPawn), relative_rank(weakSide, RANK_8)); + Value result; + + // If the stronger side's king is in front of the pawn, it's a win + if (forward_file_bb(strongSide, strongKing) & weakPawn) + result = RookValueEg - distance(strongKing, weakPawn); + + // If the weaker side's king is too far from the pawn and the rook, + // it's a win. + else if ( distance(weakKing, weakPawn) >= 3 + (pos.side_to_move() == weakSide) + && distance(weakKing, strongRook) >= 3) + result = RookValueEg - distance(strongKing, weakPawn); + + // If the pawn is far advanced and supported by the defending king, + // the position is drawish + else if ( relative_rank(strongSide, weakKing) <= RANK_3 + && distance(weakKing, weakPawn) == 1 + && relative_rank(strongSide, strongKing) >= RANK_4 + && distance(strongKing, weakPawn) > 2 + (pos.side_to_move() == strongSide)) + result = Value(80) - 8 * distance(strongKing, weakPawn); + + else + result = Value(200) - 8 * ( distance(strongKing, weakPawn + pawn_push(weakSide)) + - distance(weakKing, weakPawn + pawn_push(weakSide)) + - distance(weakPawn, queeningSquare)); + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// KR vs KB. This is very simple, and always returns drawish scores. The +/// score is slightly bigger when the defending king is close to the edge. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, RookValueMg, 0)); + assert(verify_material(pos, weakSide, BishopValueMg, 0)); + + Value result = Value(push_to_edge(pos.square(weakSide), pos)); + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// KR vs KN. The attacking side has slightly better winning chances than +/// in KR vs KB, particularly if the king and the knight are far apart. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, RookValueMg, 0)); + assert(verify_material(pos, weakSide, KnightValueMg, 0)); + + Square weakKing = pos.square(weakSide); + Square weakKnight = pos.square(weakSide); + Value result = Value(push_to_edge(weakKing, pos) + push_away(weakKing, weakKnight)); + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// KQ vs KP. In general, this is a win for the stronger side, but there are a +/// few important exceptions. A pawn on 7th rank and on the A,C,F or H files +/// with a king positioned next to it can be a draw, so in that case, we only +/// use the distance between the kings. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, QueenValueMg, 0)); + assert(verify_material(pos, weakSide, VALUE_ZERO, 1)); + + Square strongKing = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + Square weakPawn = pos.square(weakSide); + + Value result = Value(push_close(strongKing, weakKing)); + + if ( relative_rank(weakSide, weakPawn) != RANK_7 + || distance(weakKing, weakPawn) != 1 + || ((FileBBB | FileDBB | FileEBB | FileGBB) & weakPawn)) + result += QueenValueEg - PawnValueEg; + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// KQ vs KR. This is almost identical to KX vs K: we give the attacking +/// king a bonus for having the kings close together, and for forcing the +/// defending king towards the edge. If we also take care to avoid null move for +/// the defending side in the search, this is usually sufficient to win KQ vs KR. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, QueenValueMg, 0)); + assert(verify_material(pos, weakSide, RookValueMg, 0)); + + Square strongKing = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + + Value result = QueenValueEg + - RookValueEg + + push_to_edge(weakKing, pos) + + push_close(strongKing, weakKing); + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// KC vs KR. Drawish, but good winning chances if king and rook are close. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, ChancellorValueMg, 0)); + assert(verify_material(pos, weakSide, RookValueMg, 0)); + + Square strongKing = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + Square weakRook = pos.square(weakSide); + + Value result = Value(push_to_edge(weakKing, pos)) + + push_close(strongKing, weakKing) + + push_close(weakRook, weakKing); + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// KA vs KR. Very drawish. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, ArchbishopValueMg, 0)); + assert(verify_material(pos, weakSide, RookValueMg, 0)); + + Square strongKing = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + + Value result = Value(push_to_edge(weakKing, pos)) + + push_close(strongKing, weakKing); + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// KNN vs KP. Very drawish, but there are some mate opportunities if we can +/// press the weakSide King to a corner before the pawn advances too much. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, 2 * KnightValueMg, 0)); + assert(verify_material(pos, weakSide, VALUE_ZERO, 1)); + + Square weakKing = pos.square(weakSide); + Square weakPawn = pos.square(weakSide); + + Value result = PawnValueEg + + 2 * push_to_edge(weakKing, pos) + - 10 * relative_rank(weakSide, weakPawn); + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// Some cases of trivial draws +template<> Value Endgame::operator()(const Position&) const { return VALUE_DRAW; } + + +/// KFsPs vs K. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); + + Square winnerKSq = pos.square(strongSide); + Square loserKSq = pos.square(weakSide); + + Value result = pos.non_pawn_material(strongSide) + + pos.count(strongSide) * PawnValueEg + + push_to_edge(loserKSq, pos) + + push_close(winnerKSq, loserKSq); + + if ( pos.count(strongSide) >= 3 + && ( DarkSquares & pos.pieces(strongSide, FERS)) + && (~DarkSquares & pos.pieces(strongSide, FERS))) + result = std::min(result + VALUE_KNOWN_WIN, VALUE_TB_WIN_IN_MAX_PLY - 1); + else if (pos.count(strongSide) + pos.count(strongSide) < 3) + return VALUE_DRAW; + else + { + bool dark = DarkSquares & pos.pieces(strongSide, FERS); + bool light = ~DarkSquares & pos.pieces(strongSide, FERS); + + // Determine the color of ferzes from promoting pawns + Bitboard b = pos.pieces(strongSide, PAWN); + while (b && (!dark || !light)) + { + Square s = pos.promotion_square(strongSide, pop_lsb(b)); + if (s != SQ_NONE) + { + if (DarkSquares & s) + dark = true; + else + light = true; + } + } + if (!dark || !light) + return VALUE_DRAW; // we can not checkmate with same colored ferzes + } + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// Mate with KNS vs K. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, KnightValueMg + SilverValueMg, 0)); + assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); + + Square winnerKSq = pos.square(strongSide); + Square loserKSq = pos.square(weakSide); + + Value result = VALUE_KNOWN_WIN + + push_close(winnerKSq, loserKSq) + + push_to_opposing_edge(relative_square(strongSide, loserKSq, pos.max_rank()), pos); + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// KNF vs K. Can only be won if the weaker side's king +/// is close to a corner of the same color as the fers. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, KnightValueMg + FersValueMg, 0)); + assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); + + Square winnerKSq = pos.square(strongSide); + Square loserKSq = pos.square(weakSide); + Square fersSq = pos.square(strongSide); + + // tries to drive toward corners A1 or H8. If we have a + // fers that cannot reach the above squares, we flip the kings in order + // to drive the enemy toward corners A8 or H1. + if (opposite_colors(fersSq, SQ_A1)) + { + winnerKSq = relative_square(BLACK, winnerKSq, pos.max_rank()); + loserKSq = relative_square(BLACK, loserKSq, pos.max_rank()); + } + + Value result = Value(push_close(winnerKSq, loserKSq)) + + 50 * push_to_corner(loserKSq, pos); + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// KNSFKR vs K. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, KnightValueMg + SilverValueMg + FersValueMg, 0)); + assert(verify_material(pos, weakSide, RookValueMg, 0)); + + Square winnerKSq = pos.square(strongSide); + Square loserKSq = pos.square(weakSide); + + Value result = KnightValueEg + SilverValueEg + FersValueEg - RookValueEg + + push_close(winnerKSq, loserKSq) + + push_to_opposing_edge(relative_square(strongSide, loserKSq, pos.max_rank()), pos); + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// Mate with KSF vs K. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, SilverValueMg + FersValueMg, 0)); + assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); + + Square winnerKSq = pos.square(strongSide); + Square loserKSq = pos.square(weakSide); + + Value result = VALUE_KNOWN_WIN + + push_close(winnerKSq, loserKSq) + + push_to_opposing_edge(relative_square(strongSide, loserKSq, pos.max_rank()), pos); + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// Mate with KSF vs KF. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, SilverValueMg + FersValueMg, 0)); + assert(verify_material(pos, weakSide, FersValueMg, 0)); + + Square winnerKSq = pos.square(strongSide); + Square loserKSq = pos.square(weakSide); + Square fersSq = pos.square(weakSide); + + Value result = SilverValueEg + + push_close(winnerKSq, loserKSq) + + push_away(fersSq, loserKSq) + + push_to_opposing_edge(relative_square(strongSide, loserKSq, pos.max_rank()), pos); + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// KR vs KS +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, RookValueMg, 0)); + assert(verify_material(pos, weakSide, SilverValueMg, 0)); + + Square winnerKSq = pos.square(strongSide); + Square loserKSq = pos.square(weakSide); + + Value result = RookValueEg + - SilverValueEg + + push_to_edge(loserKSq, pos) + + push_close(winnerKSq, loserKSq); + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// KB and one or more pawns vs K. It checks for draws with rook pawns and +/// a bishop of the wrong color. If such a draw is detected, SCALE_FACTOR_DRAW +/// is returned. If not, the return value is SCALE_FACTOR_NONE, i.e. no scaling +/// will be used. +template<> +ScaleFactor Endgame::operator()(const Position& pos) const { + + assert(pos.non_pawn_material(strongSide) == BishopValueMg); + assert(pos.count(strongSide) >= 1); + + // No assertions about the material of weakSide, because we want draws to + // be detected even when the weaker side has some pawns. + + Bitboard strongPawns = pos.pieces(strongSide, PAWN); + Bitboard allPawns = pos.pieces(PAWN); + + Square strongBishop = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + Square strongKing = pos.square(strongSide); + + // All strongSide pawns are on a single rook file? + if (!(strongPawns & ~FileABB) || !(strongPawns & ~FileHBB)) + { + Square queeningSquare = relative_square(strongSide, make_square(file_of(lsb(strongPawns)), RANK_8)); + + if ( opposite_colors(queeningSquare, strongBishop) + && distance(queeningSquare, weakKing) <= 1) + return SCALE_FACTOR_DRAW; + } + + // If all the pawns are on the same B or G file, then it's potentially a draw + if ((!(allPawns & ~FileBBB) || !(allPawns & ~FileGBB)) + && pos.non_pawn_material(weakSide) == 0 + && pos.count(weakSide) >= 1) + { + // Get the least advanced weakSide pawn + Square weakPawn = frontmost_sq(strongSide, pos.pieces(weakSide, PAWN)); + + // There's potential for a draw if our pawn is blocked on the 7th rank, + // the bishop cannot attack it or they only have one pawn left. + if ( relative_rank(strongSide, weakPawn) == RANK_7 + && (strongPawns & (weakPawn + pawn_push(weakSide))) + && (opposite_colors(strongBishop, weakPawn) || !more_than_one(strongPawns))) + { + int strongKingDist = distance(weakPawn, strongKing); + int weakKingDist = distance(weakPawn, weakKing); + + // It's a draw if the weak king is on its back two ranks, within 2 + // squares of the blocking pawn and the strong king is not + // closer. (I think this rule only fails in practically + // unreachable positions such as 5k1K/6p1/6P1/8/8/3B4/8/8 w + // and positions where qsearch will immediately correct the + // problem such as 8/4k1p1/6P1/1K6/3B4/8/8/8 w). + if ( relative_rank(strongSide, weakKing) >= RANK_7 + && weakKingDist <= 2 + && weakKingDist <= strongKingDist) + return SCALE_FACTOR_DRAW; + } + } + + return SCALE_FACTOR_NONE; +} + + +/// KQ vs KR and one or more pawns. It tests for fortress draws with a rook on +/// the third rank defended by a pawn. +template<> +ScaleFactor Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, QueenValueMg, 0)); + assert(pos.count(weakSide) == 1); + assert(pos.count(weakSide) >= 1); + + Square strongKing = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + Square weakRook = pos.square(weakSide); + + if ( relative_rank(weakSide, weakKing) <= RANK_2 + && relative_rank(weakSide, strongKing) >= RANK_4 + && relative_rank(weakSide, weakRook) == RANK_3 + && ( pos.pieces(weakSide, PAWN) + & attacks_bb(weakKing) + & pawn_attacks_bb(strongSide, weakRook))) + return SCALE_FACTOR_DRAW; + + return SCALE_FACTOR_NONE; +} + + +/// KRP vs KR. This function knows a handful of the most important classes of +/// drawn positions, but is far from perfect. It would probably be a good idea +/// to add more knowledge in the future. +/// +/// It would also be nice to rewrite the actual code for this function, +/// which is mostly copied from Glaurung 1.x, and isn't very pretty. +template<> +ScaleFactor Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, RookValueMg, 1)); + assert(verify_material(pos, weakSide, RookValueMg, 0)); + + // Assume strongSide is white and the pawn is on files A-D + Square strongKing = normalize(pos, strongSide, pos.square(strongSide)); + Square strongRook = normalize(pos, strongSide, pos.square(strongSide)); + Square strongPawn = normalize(pos, strongSide, pos.square(strongSide)); + Square weakKing = normalize(pos, strongSide, pos.square(weakSide)); + Square weakRook = normalize(pos, strongSide, pos.square(weakSide)); + + File pawnFile = file_of(strongPawn); + Rank pawnRank = rank_of(strongPawn); + Square queeningSquare = make_square(pawnFile, RANK_8); + int tempo = (pos.side_to_move() == strongSide); + + // If the pawn is not too far advanced and the defending king defends the + // queening square, use the third-rank defence. + if ( pawnRank <= RANK_5 + && distance(weakKing, queeningSquare) <= 1 + && strongKing <= SQ_H5 + && (rank_of(weakRook) == RANK_6 || (pawnRank <= RANK_3 && rank_of(strongRook) != RANK_6))) + return SCALE_FACTOR_DRAW; + + // The defending side saves a draw by checking from behind in case the pawn + // has advanced to the 6th rank with the king behind. + if ( pawnRank == RANK_6 + && distance(weakKing, queeningSquare) <= 1 + && rank_of(strongKing) + tempo <= RANK_6 + && (rank_of(weakRook) == RANK_1 || (!tempo && distance(weakRook, strongPawn) >= 3))) + return SCALE_FACTOR_DRAW; + + if ( pawnRank >= RANK_6 + && weakKing == queeningSquare + && rank_of(weakRook) == RANK_1 + && (!tempo || distance(strongKing, strongPawn) >= 2)) + return SCALE_FACTOR_DRAW; + + // White pawn on a7 and rook on a8 is a draw if black's king is on g7 or h7 + // and the black rook is behind the pawn. + if ( strongPawn == SQ_A7 + && strongRook == SQ_A8 + && (weakKing == SQ_H7 || weakKing == SQ_G7) + && file_of(weakRook) == FILE_A + && (rank_of(weakRook) <= RANK_3 || file_of(strongKing) >= FILE_D || rank_of(strongKing) <= RANK_5)) + return SCALE_FACTOR_DRAW; + + // If the defending king blocks the pawn and the attacking king is too far + // away, it's a draw. + if ( pawnRank <= RANK_5 + && weakKing == strongPawn + NORTH + && distance(strongKing, strongPawn) - tempo >= 2 + && distance(strongKing, weakRook) - tempo >= 2) + return SCALE_FACTOR_DRAW; + + // Pawn on the 7th rank supported by the rook from behind usually wins if the + // attacking king is closer to the queening square than the defending king, + // and the defending king cannot gain tempi by threatening the attacking rook. + if ( pawnRank == RANK_7 + && pawnFile != FILE_A + && file_of(strongRook) == pawnFile + && strongRook != queeningSquare + && (distance(strongKing, queeningSquare) < distance(weakKing, queeningSquare) - 2 + tempo) + && (distance(strongKing, queeningSquare) < distance(weakKing, strongRook) + tempo)) + return ScaleFactor(SCALE_FACTOR_MAX - 2 * distance(strongKing, queeningSquare)); + + // Similar to the above, but with the pawn further back + if ( pawnFile != FILE_A + && file_of(strongRook) == pawnFile + && strongRook < strongPawn + && (distance(strongKing, queeningSquare) < distance(weakKing, queeningSquare) - 2 + tempo) + && (distance(strongKing, strongPawn + NORTH) < distance(weakKing, strongPawn + NORTH) - 2 + tempo) + && ( distance(weakKing, strongRook) + tempo >= 3 + || ( distance(strongKing, queeningSquare) < distance(weakKing, strongRook) + tempo + && (distance(strongKing, strongPawn + NORTH) < distance(weakKing, strongPawn) + tempo)))) + return ScaleFactor( SCALE_FACTOR_MAX + - 8 * distance(strongPawn, queeningSquare) + - 2 * distance(strongKing, queeningSquare)); + + // If the pawn is not far advanced and the defending king is somewhere in + // the pawn's path, it's probably a draw. + if (pawnRank <= RANK_4 && weakKing > strongPawn) + { + if (file_of(weakKing) == file_of(strongPawn)) + return ScaleFactor(10); + if ( distance(weakKing, strongPawn) == 1 + && distance(strongKing, weakKing) > 2) + return ScaleFactor(24 - 2 * distance(strongKing, weakKing)); + } + return SCALE_FACTOR_NONE; +} + +template<> +ScaleFactor Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, RookValueMg, 1)); + assert(verify_material(pos, weakSide, BishopValueMg, 0)); + + // Test for a rook pawn + if (pos.pieces(PAWN) & (FileABB | FileHBB)) + { + Square weakKing = pos.square(weakSide); + Square weakBishop = pos.square(weakSide); + Square strongKing = pos.square(strongSide); + Square strongPawn = pos.square(strongSide); + Rank pawnRank = relative_rank(strongSide, strongPawn); + Direction push = pawn_push(strongSide); + + // If the pawn is on the 5th rank and the pawn (currently) is on + // the same color square as the bishop then there is a chance of + // a fortress. Depending on the king position give a moderate + // reduction or a stronger one if the defending king is near the + // corner but not trapped there. + if (pawnRank == RANK_5 && !opposite_colors(weakBishop, strongPawn)) + { + int d = distance(strongPawn + 3 * push, weakKing); + + if (d <= 2 && !(d == 0 && weakKing == strongKing + 2 * push)) + return ScaleFactor(24); + else + return ScaleFactor(48); + } + + // When the pawn has moved to the 6th rank we can be fairly sure + // it's drawn if the bishop attacks the square in front of the + // pawn from a reasonable distance and the defending king is near + // the corner + if ( pawnRank == RANK_6 + && distance(strongPawn + 2 * push, weakKing) <= 1 + && (attacks_bb(weakBishop) & (strongPawn + push)) + && distance(weakBishop, strongPawn) >= 2) + return ScaleFactor(8); + } + + return SCALE_FACTOR_NONE; +} + +/// KRPP vs KRP. There is just a single rule: if the stronger side has no passed +/// pawns and the defending king is actively placed, the position is drawish. +template<> +ScaleFactor Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, RookValueMg, 2)); + assert(verify_material(pos, weakSide, RookValueMg, 1)); + + Square strongPawn1 = lsb(pos.pieces(strongSide, PAWN)); + Square strongPawn2 = msb(pos.pieces(strongSide, PAWN)); + Square weakKing = pos.square(weakSide); + + // Does the stronger side have a passed pawn? + if (pos.pawn_passed(strongSide, strongPawn1) || pos.pawn_passed(strongSide, strongPawn2)) + return SCALE_FACTOR_NONE; + + Rank pawnRank = std::max(relative_rank(strongSide, strongPawn1), relative_rank(strongSide, strongPawn2)); + + if ( distance(weakKing, strongPawn1) <= 1 + && distance(weakKing, strongPawn2) <= 1 + && relative_rank(strongSide, weakKing) > pawnRank) + { + assert(pawnRank > RANK_1 && pawnRank < RANK_7); + return ScaleFactor(7 * pawnRank); + } + return SCALE_FACTOR_NONE; +} + + +/// K and two or more pawns vs K. There is just a single rule here: if all pawns +/// are on the same rook file and are blocked by the defending king, it's a draw. +template<> +ScaleFactor Endgame::operator()(const Position& pos) const { + + assert(pos.non_pawn_material(strongSide) == VALUE_ZERO); + assert(pos.count(strongSide) >= 2); + assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); + + Square weakKing = pos.square(weakSide); + Bitboard strongPawns = pos.pieces(strongSide, PAWN); + + // If all pawns are ahead of the king on a single rook file, it's a draw. + if ( !(strongPawns & ~(FileABB | FileHBB)) + && !(strongPawns & ~passed_pawn_span(weakSide, weakKing))) + return SCALE_FACTOR_DRAW; + + return SCALE_FACTOR_NONE; +} + + +/// KBP vs KB. There are two rules: if the defending king is somewhere along the +/// path of the pawn, and the square of the king is not of the same color as the +/// stronger side's bishop, it's a draw. If the two bishops have opposite color, +/// it's almost always a draw. +template<> +ScaleFactor Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, BishopValueMg, 1)); + assert(verify_material(pos, weakSide, BishopValueMg, 0)); + + Square strongPawn = pos.square(strongSide); + Square strongBishop = pos.square(strongSide); + Square weakBishop = pos.square(weakSide); + Square weakKing = pos.square(weakSide); + + // Case 1: Defending king blocks the pawn, and cannot be driven away + if ( (forward_file_bb(strongSide, strongPawn) & weakKing) + && ( opposite_colors(weakKing, strongBishop) + || relative_rank(strongSide, weakKing) <= RANK_6)) + return SCALE_FACTOR_DRAW; + + // Case 2: Opposite colored bishops + if (opposite_colors(strongBishop, weakBishop)) + return SCALE_FACTOR_DRAW; + + return SCALE_FACTOR_NONE; +} + + +/// KBPP vs KB. It detects a few basic draws with opposite-colored bishops +template<> +ScaleFactor Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, BishopValueMg, 2)); + assert(verify_material(pos, weakSide, BishopValueMg, 0)); + + Square strongBishop = pos.square(strongSide); + Square weakBishop = pos.square(weakSide); + + if (!opposite_colors(strongBishop, weakBishop)) + return SCALE_FACTOR_NONE; + + Square weakKing = pos.square(weakSide); + Square strongPawn1 = lsb(pos.pieces(strongSide, PAWN)); + Square strongPawn2 = msb(pos.pieces(strongSide, PAWN)); + Square blockSq1, blockSq2; + + if (relative_rank(strongSide, strongPawn1) > relative_rank(strongSide, strongPawn2)) + { + blockSq1 = strongPawn1 + pawn_push(strongSide); + blockSq2 = make_square(file_of(strongPawn2), rank_of(strongPawn1)); + } + else + { + blockSq1 = strongPawn2 + pawn_push(strongSide); + blockSq2 = make_square(file_of(strongPawn1), rank_of(strongPawn2)); + } + + switch (distance(strongPawn1, strongPawn2)) + { + case 0: + // Both pawns are on the same file. It's an easy draw if the defender firmly + // controls some square in the frontmost pawn's path. + if ( file_of(weakKing) == file_of(blockSq1) + && relative_rank(strongSide, weakKing) >= relative_rank(strongSide, blockSq1) + && opposite_colors(weakKing, strongBishop)) + return SCALE_FACTOR_DRAW; + else + return SCALE_FACTOR_NONE; + + case 1: + // Pawns on adjacent files. It's a draw if the defender firmly controls the + // square in front of the frontmost pawn's path, and the square diagonally + // behind this square on the file of the other pawn. + if ( weakKing == blockSq1 + && opposite_colors(weakKing, strongBishop) + && ( weakBishop == blockSq2 + || (attacks_bb(blockSq2, pos.pieces()) & pos.pieces(weakSide, BISHOP)) + || distance(strongPawn1, strongPawn2) >= 2)) + return SCALE_FACTOR_DRAW; + + else if ( weakKing == blockSq2 + && opposite_colors(weakKing, strongBishop) + && ( weakBishop == blockSq1 + || (attacks_bb(blockSq1, pos.pieces()) & pos.pieces(weakSide, BISHOP)))) + return SCALE_FACTOR_DRAW; + else + return SCALE_FACTOR_NONE; + + default: + // The pawns are not on the same file or adjacent files. No scaling. + return SCALE_FACTOR_NONE; + } +} + + +/// KBP vs KN. There is a single rule: if the defending king is somewhere along +/// the path of the pawn, and the square of the king is not of the same color as +/// the stronger side's bishop, it's a draw. +template<> +ScaleFactor Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, BishopValueMg, 1)); + assert(verify_material(pos, weakSide, KnightValueMg, 0)); + + Square strongPawn = pos.square(strongSide); + Square strongBishop = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + + if ( file_of(weakKing) == file_of(strongPawn) + && relative_rank(strongSide, strongPawn) < relative_rank(strongSide, weakKing) + && ( opposite_colors(weakKing, strongBishop) + || relative_rank(strongSide, weakKing) <= RANK_6)) + return SCALE_FACTOR_DRAW; + + return SCALE_FACTOR_NONE; +} + + +/// KP vs KP. This is done by removing the weakest side's pawn and probing the +/// KP vs K bitbase: if the weakest side has a draw without the pawn, it probably +/// has at least a draw with the pawn as well. The exception is when the stronger +/// side's pawn is far advanced and not on a rook file; in this case it is often +/// possible to win (e.g. 8/4k3/3p4/3P4/6K1/8/8/8 w - - 0 1). +template<> +ScaleFactor Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, VALUE_ZERO, 1)); + assert(verify_material(pos, weakSide, VALUE_ZERO, 1)); + + // Assume strongSide is white and the pawn is on files A-D + Square strongKing = normalize(pos, strongSide, pos.square(strongSide)); + Square weakKing = normalize(pos, strongSide, pos.square(weakSide)); + Square strongPawn = normalize(pos, strongSide, pos.square(strongSide)); + + Color us = strongSide == pos.side_to_move() ? WHITE : BLACK; + + // If the pawn has advanced to the fifth rank or further, and is not a + // rook pawn, it's too dangerous to assume that it's at least a draw. + if (rank_of(strongPawn) >= RANK_5 && file_of(strongPawn) != FILE_A) + return SCALE_FACTOR_NONE; + + // Probe the KPK bitbase with the weakest side's pawn removed. If it's a draw, + // it's probably at least a draw even with the pawn. + if ( pos.promotion_zone(us) != rank_bb(relative_rank(us, RANK_8, pos.max_rank())) + || RANK_MAX != RANK_8 + || !(pos.promotion_piece_types(us) & QUEEN)) + return SCALE_FACTOR_NONE; + + return Bitbases::probe(strongKing, strongPawn, weakKing, us) ? SCALE_FACTOR_NONE : SCALE_FACTOR_DRAW; +} + + +/// Endgame evals for special variants +/// R vs K. The rook side always wins if there is no immediate forced capture. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(pos.endgame_eval() == EG_EVAL_ANTI); + + Square RSq = pos.square(strongSide); + Square KSq = pos.square(weakSide); + + Value result = Value(push_to_edge(KSq, pos)) + push_close(RSq, KSq); + + int dist_min = std::min(distance(RSq, KSq), distance(RSq, KSq)); + int dist_max = std::max(distance(RSq, KSq), distance(RSq, KSq)); + + if (dist_min == 0) + result += strongSide == pos.side_to_move() || dist_max > 1 ? -VALUE_KNOWN_WIN : VALUE_KNOWN_WIN; + else if (dist_min == 1) + result += weakSide == pos.side_to_move() && dist_max > 1 ? -VALUE_KNOWN_WIN : VALUE_KNOWN_WIN; + else + result += VALUE_KNOWN_WIN; + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// K vs N. The king usually wins, but there are a few exceptions. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(pos.endgame_eval() == EG_EVAL_ANTI); + + Square KSq = pos.square(strongSide); + Square NSq = pos.square(weakSide); + Bitboard kingAttacks = attacks_bb(KSq) & pos.board_bb(); + Bitboard knightAttacks = attacks_bb(NSq) & pos.board_bb(); + bool strongSideToMove = pos.side_to_move() == strongSide; + + // Loss in 1 play + if (strongSideToMove ? kingAttacks & NSq : knightAttacks & KSq) + return VALUE_TB_LOSS_IN_MAX_PLY + 1; + // Win in 2 ply + if (kingAttacks & knightAttacks) + return VALUE_TB_WIN_IN_MAX_PLY - 2; + // Loss in 3 ply + if (strongSideToMove ? knightAttacks & KSq : kingAttacks & NSq) + return VALUE_TB_LOSS_IN_MAX_PLY + 3; + + Value result = Value(push_to_edge(NSq, pos)) - push_to_edge(KSq, pos); + // The king usually wins, but scenarios with king on the edge are more complicated + if (!(KSq & (FileABB | FileHBB | Rank1BB | Rank8BB))) + result += VALUE_KNOWN_WIN; + + return strongSideToMove ? result : -result; +} + +/// N vs N. The side to move always wins/loses if the knights are on +/// same/opposite colored squares. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(pos.endgame_eval() == EG_EVAL_ANTI); + + Square N1Sq = pos.square(pos.side_to_move()); + Square N2Sq = pos.square(~pos.side_to_move()); + + Value result = VALUE_KNOWN_WIN + push_close(N1Sq, N2Sq); + + return !opposite_colors(N1Sq, N2Sq) ? result : -result; +} + + +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(pos.endgame_eval() == EG_EVAL_ATOMIC); + + // Stalemate/checkmate detection with lone king + if (pos.side_to_move() == weakSide && !MoveList(pos).size()) + return std::min(pos.stalemate_value(), VALUE_MATE_IN_MAX_PLY - 1); + + Square winnerKSq = pos.square(strongSide); + Square loserKSq = pos.square(weakSide); + + Value result = pos.non_pawn_material(strongSide) + + pos.count(strongSide) * PawnValueEg + + push_to_edge(loserKSq, pos) + + push_away(winnerKSq, loserKSq); + + // We need at least a major and a minor, or three minors to force checkmate + if ( ((pos.count(strongSide) || pos.count(strongSide)) && pos.count(strongSide) >= 3) + || (pos.count(strongSide) + pos.count(strongSide) >= 3 + && (pos.count(strongSide) >= 2 || ((pos.pieces(strongSide, BISHOP) & DarkSquares) + && (pos.pieces(strongSide, BISHOP) & ~DarkSquares))))) + result = std::min(result + VALUE_KNOWN_WIN, VALUE_MATE_IN_MAX_PLY - 1); + + return strongSide == pos.side_to_move() ? result : -result; +} + +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(pos.endgame_eval() == EG_EVAL_ATOMIC); + + Square winnerKSq = pos.square(strongSide); + Square loserKSq = pos.square(weakSide); + + int dist = distance(winnerKSq, loserKSq); + // Draw in case of adjacent kings + if (dist <= (strongSide == pos.side_to_move() ? 1 : 2)) + return VALUE_DRAW; + + Value result = PawnValueEg + + 20 * relative_rank(strongSide, pos.square(strongSide), pos.max_rank()) - 20 + + push_away(winnerKSq, loserKSq); + + return strongSide == pos.side_to_move() ? result : -result; +} + +template<> Value Endgame::operator()(const Position&) const { return VALUE_DRAW; } + +template<> Value Endgame::operator()(const Position&) const { return VALUE_DRAW; } + +template<> Value Endgame::operator()(const Position&) const { return VALUE_DRAW; } + +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(pos.endgame_eval() == EG_EVAL_ATOMIC); + + // Stalemate/checkmate detection with lone king + if (pos.side_to_move() == weakSide && !MoveList(pos).size()) + return std::min(pos.stalemate_value(), VALUE_MATE_IN_MAX_PLY - 1); + + Square winnerKSq = pos.square(strongSide); + Square loserKSq = pos.square(weakSide); + + int dist = distance(winnerKSq, loserKSq); + // Draw in case of adjacent kings + if (dist == 1) + return VALUE_DRAW; + // If the weaker side is to move and there is a way to connect kings, it is a draw + if ( pos.side_to_move() == weakSide + && (attacks_bb(winnerKSq) & attacks_bb(loserKSq) & ~pos.pieces())) + return VALUE_DRAW; + + Value result = Value(push_to_edge(loserKSq, pos)) + + push_away(winnerKSq, loserKSq); + + if (dist >= (strongSide == pos.side_to_move() ? 3 : 4)) + result += VALUE_KNOWN_WIN; + + return strongSide == pos.side_to_move() ? result : -result; +} + +template<> Value Endgame::operator()(const Position&) const { return VALUE_DRAW; } + + +/// Self-mate with KX vs KX. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(!pos.checkers()); // Eval is never called when in check + + // Stalemate detection with lone king + if (pos.side_to_move() == weakSide && !MoveList(pos).size()) + return VALUE_DRAW; + + Square strongKing = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + + Value result = pos.non_pawn_material(strongSide) * int(VALUE_KNOWN_WIN) / int(VALUE_KNOWN_WIN + pos.non_pawn_material(strongSide)) + - pos.non_pawn_material(weakSide) + + pos.count(weakSide) * PawnValueEg + + push_to_opposing_edge(relative_square(weakSide, strongKing, pos.max_rank()), pos) * 2 + + push_close(strongKing, weakKing) * 2; + + for (Bitboard b = pos.pieces(PAWN); b;) + { + Square s = pop_lsb(b); + result += (push_close(strongKing, s) + push_close(weakKing, s)) / 2; + } + + if (!pos.count(weakSide)) + result = VALUE_DRAW; + else if (pos.count(weakSide) == 1) + result = result / 2; + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// Mate with KX vs K. This function is used to evaluate positions with +/// king and plenty of material vs a lone king. It simply gives the +/// attacking side a bonus for driving the defending king towards the edge +/// of the board, and for keeping the distance between the two kings small. +template<> +Value Endgame::operator()(const Position& pos) const { + + Square strongKing = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + + Value result = pos.non_pawn_material(strongSide) + + pos.count(strongSide) * PawnValueEg + + push_to_edge(weakKing, pos) + + push_close(strongKing, weakKing); + + result = std::min(result + VALUE_KNOWN_WIN, VALUE_TB_WIN_IN_MAX_PLY - 1); + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// Drawish, but king should stay away from the edge +template<> +Value Endgame::operator()(const Position& pos) const { + + Square strongKing = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + + Value result = Value(push_to_edge(weakKing, pos)) + + push_close(strongKing, weakKing); + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// Drawish, but king should stay away from the edge +template<> +Value Endgame::operator()(const Position& pos) const { + + Square strongKing = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + + Value result = Value(push_to_edge(weakKing, pos)) + + push_close(strongKing, weakKing); + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// Winning as long as pawn is safe +template<> +Value Endgame::operator()(const Position& pos) const { + + Square strongKing = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + Square strongPawn = pos.square(strongSide); + + Value result = PawnValueEg + 50 * relative_rank(strongSide, strongPawn, pos.max_rank()) + + push_to_edge(weakKing, pos) + + push_close(strongKing, weakKing) + + push_close(strongKing, strongPawn) / 2 + + push_away(weakKing, strongPawn) / 2; + + return strongSide == pos.side_to_move() ? result : -result; +} + +/// Winning as long as last rank can be blocked +template<> +Value Endgame::operator()(const Position& pos) const { + + Square strongKing = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + Square strongQueen = pos.square(strongSide); + + Value result; + + if ( rank_of(weakKing) < rank_of(strongQueen) + || rank_of(weakKing) + (weakSide == pos.side_to_move()) < RANK_7 + || (Rank8BB & attacks_bb(strongQueen, pos.pieces()) & ~(attacks_bb(weakKing) | attacks_bb(weakKing)))) + result = VALUE_KNOWN_WIN + 100 * rank_of(strongKing); + else + result = -VALUE_KNOWN_WIN; + + return strongSide == pos.side_to_move() ? result : -result; +} + +/// Winning as long as last rank can be blocked +template<> +Value Endgame::operator()(const Position& pos) const { + + Square strongKing = pos.square(strongSide); + Square weakKing = pos.square(weakSide); + Square strongRook = pos.square(strongSide); + + Value result; + + if ( rank_of(weakKing) < rank_of(strongRook) + || rank_of(weakKing) + (weakSide == pos.side_to_move()) < RANK_7 + || (Rank8BB & attacks_bb(strongRook, pos.pieces()) & ~(attacks_bb(weakKing) | attacks_bb(weakKing)))) + result = VALUE_KNOWN_WIN + 100 * rank_of(strongKing); + else + result = -VALUE_KNOWN_WIN; + + return strongSide == pos.side_to_move() ? result : -result; +} + +/// KvK. Pure race +template<> +Value Endgame::operator()(const Position& pos) const { + + Square whiteKing = pos.square(WHITE); + Square blackKing = pos.square(BLACK); + + Value result = (VALUE_KNOWN_WIN + 100 * std::max(rank_of(whiteKing), rank_of(blackKing))) + * std::clamp(rank_of(whiteKing) - rank_of(blackKing) - (pos.side_to_move() == BLACK), -1, 1); + + return pos.side_to_move() == WHITE ? result : -result; +} + +} // namespace Stockfish diff --git a/Fairy-Stockfish-master/src/endgame.h b/Fairy-Stockfish-master/src/endgame.h new file mode 100644 index 0000000000000000000000000000000000000000..aec3dcdbabc595fba30749e955c8e8db8bd0879e --- /dev/null +++ b/Fairy-Stockfish-master/src/endgame.h @@ -0,0 +1,148 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef ENDGAME_H_INCLUDED +#define ENDGAME_H_INCLUDED + +#include +#include +#include +#include +#include + +#include "position.h" +#include "types.h" + +namespace Stockfish { + +/// EndgameCode lists all supported endgame functions by corresponding codes + +enum EndgameCode { + + EVALUATION_FUNCTIONS, + KNNK, // KNN vs K + KNNKP, // KNN vs KP + KXK, // Generic "mate lone king" eval + KBNK, // KBN vs K + KPK, // KP vs K + KRKP, // KR vs KP + KRKB, // KR vs KB + KRKN, // KR vs KN + KQKP, // KQ vs KP + KQKR, // KQ vs KR + + // Fairy piece endgames + KFsPsK, // KFsPsK vs K + KNSK, // KNS vs K + KNFK, // KNF vs K + KNSFKR, // KNSFKR vs K + KSFK, // KSF vs K + KSFKF, // KSF vs KF + KRKS, // KR vs KS + KCKR, // KC vs KR + KAKR, // KA vs KR + + // Special + KXKX, + RK, + KN, + NN, + KQK, + KRK, + KBK, + KNK, + KK, + + SCALING_FUNCTIONS, + KBPsK, // KB and pawns vs K + KQKRPs, // KQ vs KR and pawns + KRPKR, // KRP vs KR + KRPKB, // KRP vs KB + KRPPKRP, // KRPP vs KRP + KPsK, // K and pawns vs K + KBPKB, // KBP vs KB + KBPPKB, // KBPP vs KB + KBPKN, // KBP vs KN + KPKP // KP vs KP +}; + + +/// Endgame functions can be of two types depending on whether they return a +/// Value or a ScaleFactor. + +template using +eg_type = typename std::conditional<(E < SCALING_FUNCTIONS), Value, ScaleFactor>::type; + + +/// Base and derived functors for endgame evaluation and scaling functions + +template +struct EndgameBase { + + explicit EndgameBase(Color c) : strongSide(c), weakSide(~c) {} + virtual ~EndgameBase() = default; + virtual T operator()(const Position&) const = 0; + + const Color strongSide, weakSide; +}; + + +template> +struct Endgame : public EndgameBase { + + explicit Endgame(Color c) : EndgameBase(c) {} + T operator()(const Position&) const override; +}; + + +/// The Endgames namespace handles the pointers to endgame evaluation and scaling +/// base objects in two std::map. We use polymorphism to invoke the actual +/// endgame function by calling its virtual operator(). + +namespace Endgames { + + template using Ptr = std::unique_ptr>; + template using Map = std::unordered_map>; + + extern std::pair, Map> maps; + + void init(); + + template + Map& map() { + return std::get::value>(maps); + } + + template> + void add(const std::string& code) { + + StateInfo st; + map()[Position().set(code, WHITE, &st).material_key(V)] = Ptr(new Endgame(WHITE)); + map()[Position().set(code, BLACK, &st).material_key(V)] = Ptr(new Endgame(BLACK)); + } + + template + const EndgameBase* probe(Key key) { + auto it = map().find(key); + return it != map().end() ? it->second.get() : nullptr; + } +} + +} // namespace Stockfish + +#endif // #ifndef ENDGAME_H_INCLUDED diff --git a/Fairy-Stockfish-master/src/evaluate.cpp b/Fairy-Stockfish-master/src/evaluate.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ce30aa0453b05428b0ce12f42901139d7cc49869 --- /dev/null +++ b/Fairy-Stockfish-master/src/evaluate.cpp @@ -0,0 +1,1736 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include // For std::memset +#include +#include +#include +#include +#include +#include + +#include "bitboard.h" +#include "evaluate.h" +#include "material.h" +#include "misc.h" +#include "pawns.h" +#include "thread.h" +#include "timeman.h" +#include "uci.h" +#include "incbin/incbin.h" + + +// Macro to embed the default efficiently updatable neural network (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 Microsoft 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; + +namespace Stockfish { + +const Variant* currentNnueVariant; + +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"]; + if (!useNNUE) + return; + + string eval_file = string(Options["EvalFile"]); + + // Restrict NNUE usage to corresponding variant + // Support multiple variant networks separated by semicolon(Windows)/colon(Unix) + stringstream ss(eval_file); + string variant = string(Options["UCI_Variant"]); + useNNUE = false; + while (getline(ss, eval_file, UCI::SepChar)) + { + string basename = eval_file.substr(eval_file.find_last_of("\\/") + 1); + string nnueAlias = variants.find(variant)->second->nnueAlias; + if (basename.rfind(variant, 0) != string::npos || (!nnueAlias.empty() && basename.rfind(nnueAlias, 0) != string::npos)) + { + useNNUE = true; + break; + } + } + if (!useNNUE) + return; + + currentNnueVariant = variants.find(variant)->second; + + #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.find(eval_file_loaded) == string::npos) + { + 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 (CurrentProtocol != XBOARD) + { + if (useNNUE) + sync_cout << "info string NNUE evaluation using " << eval_file_loaded << " enabled" << sync_endl; + else + sync_cout << "info string classical evaluation enabled" << sync_endl; + } + } +} + +namespace Trace { + + enum Tracing { NO_TRACE, TRACE }; + + enum Term { // The first PIECE_TYPE_NB entries are reserved for PieceType + MATERIAL = PIECE_TYPE_NB, IMBALANCE, MOBILITY, THREAT, PASSED, SPACE, VARIANT, WINNABLE, TOTAL, TERM_NB + }; + + Score scores[TERM_NB][COLOR_NB]; + + double to_cp(Value v) { return double(v) / PawnValueEg; } + + void add(int idx, Color c, Score s) { + scores[idx][c] = s; + } + + void add(int idx, Score w, Score b = SCORE_ZERO) { + scores[idx][WHITE] = w; + scores[idx][BLACK] = b; + } + + std::ostream& operator<<(std::ostream& os, Score s) { + os << std::setw(5) << to_cp(mg_value(s)) << " " + << std::setw(5) << to_cp(eg_value(s)); + return os; + } + + std::ostream& operator<<(std::ostream& os, Term t) { + + if (t == MATERIAL || t == IMBALANCE || t == WINNABLE || t == TOTAL) + os << " ---- ----" << " | " << " ---- ----"; + else + os << scores[t][WHITE] << " | " << scores[t][BLACK]; + + os << " | " << scores[t][WHITE] - scores[t][BLACK] << " |\n"; + return os; + } +} + +using namespace Trace; + +namespace { + + // Threshold for lazy and space evaluation + constexpr Value LazyThreshold1 = Value(1565); + constexpr Value LazyThreshold2 = Value(1102); + constexpr Value SpaceThreshold = Value(11551); + + // KingAttackWeights[PieceType] contains king attack weights by piece type + 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}, {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[][4 * RANK_NB] = { + { 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(-60,-82), S(-24,-15), S( 0, 17) ,S( 3, 43), S( 4, 72), S( 14,100), // Rook + S( 20,102), S( 30,122), S( 41,133), S(41 ,139), S( 41,153), S( 45,160), + S( 57,165), S( 58,170), S( 67,175) }, + { 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); + + // BishopPawns[distance from edge] contains a file-dependent penalty for pawns on + // squares of the same color as our bishop. + constexpr Score BishopPawns[int(FILE_NB) / 2] = { + S(3, 8), S(3, 9), S(2, 8), S(3, 8) + }; + + // KingProtector[knight/bishop] contains penalty for each distance unit to own king + constexpr Score KingProtector[] = { S(8, 9), S(6, 9) }; + + // Outpost[knight/bishop] contains bonuses for each knight or bishop occupying a + // pawn protected square on rank 4 to 6 which is also safe from a pawn attack. + constexpr Score Outpost[] = { S(57, 38), S(31, 24) }; + + // PassedRank[Rank] contains a bonus according to the rank of a passed pawn + constexpr Score PassedRank[RANK_NB] = { + S(0, 0), S(7, 27), S(16, 32), S(17, 40), S(64, 71), S(170, 174), S(278, 262) + }; + + constexpr Score RookOnClosedFile = S(10, 5); + constexpr Score RookOnOpenFile[] = { S(19, 6), S(47, 26) }; + + // ThreatByMinor/ByRook[attacked PieceType] contains bonuses according to + // which piece type attacks which one. Attacks on lesser pieces which are + // pawn-defended are not considered. + constexpr Score ThreatByMinor[PIECE_TYPE_NB] = { + S(0, 0), S(5, 32), S(55, 41), S(77, 56), S(89, 119), S(79, 162) + }; + + constexpr Score ThreatByRook[PIECE_TYPE_NB] = { + S(0, 0), S(3, 44), S(37, 68), S(42, 60), S(0, 39), S(58, 43) + }; + + constexpr Value CorneredBishop = Value(50); + + // Assorted bonuses and penalties + constexpr Score UncontestedOutpost = S( 1, 10); + constexpr Score BishopOnKingRing = S( 24, 0); + constexpr Score BishopXRayPawns = S( 4, 5); + constexpr Score FlankAttacks = S( 8, 0); + constexpr Score Hanging = S( 69, 36); + constexpr Score KnightOnQueen = S( 16, 11); + constexpr Score LongDiagonalBishop = S( 45, 0); + constexpr Score MinorBehindPawn = S( 18, 3); + constexpr Score PassedFile = S( 11, 8); + constexpr Score PawnlessFlank = S( 17, 95); + constexpr Score ReachableOutpost = S( 31, 22); + constexpr Score RestrictedPiece = S( 7, 7); + constexpr Score RookOnKingRing = S( 16, 0); + constexpr Score SliderOnQueen = S( 60, 18); + constexpr Score ThreatByKing = S( 24, 89); + constexpr Score ThreatByPawnPush = S( 48, 39); + constexpr Score ThreatBySafePawn = S(173, 94); + constexpr Score TrappedRook = S( 55, 13); + constexpr Score WeakQueenProtection = S( 14, 0); + constexpr Score WeakQueen = S( 56, 15); + + + // Variant and fairy piece bonuses + constexpr Score KingProximity = S(2, 6); + constexpr Score EndgameKingProximity = S(0, 10); + constexpr Score ConnectedSoldier = S(20, 20); + + constexpr int VirtualCheck = 600; + +#undef S + + // Evaluation class computes and stores attacks tables and other working data + template + class Evaluation { + + public: + Evaluation() = delete; + explicit Evaluation(const Position& p) : pos(p) {} + Evaluation& operator=(const Evaluation&) = delete; + Value value(); + + private: + template void initialize(); + template Score pieces(PieceType Pt); + template Score hand(PieceType pt); + template Score king() const; + template Score threats() const; + template Score passed() const; + template Score space() const; + template Score variant() const; + Value winnable(Score score) const; + + const Position& pos; + Material::Entry* me; + Pawns::Entry* pe; + Bitboard mobilityArea[COLOR_NB]; + Score mobility[COLOR_NB] = { SCORE_ZERO, SCORE_ZERO }; + + // attackedBy[color][piece type] is a bitboard representing all squares + // attacked by a given color and piece type. Special "piece types" which + // is also calculated is ALL_PIECES. + Bitboard attackedBy[COLOR_NB][PIECE_TYPE_NB]; + + // attackedBy2[color] are the squares attacked by at least 2 units of a given + // color, including x-rays. But diagonal x-rays through pawns are not computed. + Bitboard attackedBy2[COLOR_NB]; + + // kingRing[color] are the squares adjacent to the king plus some other + // very near squares, depending on king position. + Bitboard kingRing[COLOR_NB]; + + // kingAttackersCount[color] is the number of pieces of the given color + // which attack a square in the kingRing of the enemy king. + int kingAttackersCount[COLOR_NB]; + int kingAttackersCountInHand[COLOR_NB]; + + // kingAttackersWeight[color] is the sum of the "weights" of the pieces of + // the given color which attack a square in the kingRing of the enemy king. + // The weights of the individual piece types are given by the elements in + // the KingAttackWeights array. + int kingAttackersWeight[COLOR_NB]; + int kingAttackersWeightInHand[COLOR_NB]; + + // kingAttacksCount[color] is the number of attacks by the given color to + // squares directly adjacent to the enemy king. Pieces which attack more + // than one square are counted multiple times. For instance, if there is + // a white knight on g5 and black's king is on g8, this white knight adds 2 + // to kingAttacksCount[WHITE]. + int kingAttacksCount[COLOR_NB]; + }; + + + // Evaluation::initialize() computes king and pawn attacks, and the king ring + // bitboard for a given color. This is done at the beginning of the evaluation. + + template template + void Evaluation::initialize() { + + constexpr Color Them = ~Us; + constexpr Direction Up = pawn_push(Us); + constexpr Direction Down = -Up; + Bitboard LowRanks = rank_bb(relative_rank(Us, RANK_2, pos.max_rank())) | rank_bb(relative_rank(Us, RANK_3, pos.max_rank())); + + const Square ksq = pos.count(Us) ? pos.square(Us) : SQ_NONE; + + Bitboard dblAttackByPawn = pawn_double_attacks_bb(pos.pieces(Us, PAWN)); + + // Find our pawns that are blocked or on the first two ranks + Bitboard b = pos.pieces(Us, PAWN) & (shift(pos.pieces()) | LowRanks); + + // 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. + 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] = pos.count(Us) ? pos.attacks_from(Us, KING, ksq) : Bitboard(0); + attackedBy[Us][PAWN] = pe->pawn_attacks(Us); + 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 + if (!pos.count(Us)) + kingRing[Us] = Bitboard(0); + else + { + 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) | shift(pos.pieces(Them, SHOGI_PAWN)))); + kingAttacksCount[Them] = kingAttackersWeight[Them] = 0; + kingAttackersCountInHand[Them] = kingAttackersWeightInHand[Them] = 0; + + // Remove from kingRing[] the squares defended by two pawns + kingRing[Us] &= ~dblAttackByPawn; + + kingRing[Us] &= pos.board_bb(); + } + + + // Evaluation::pieces() scores pieces of a given color and type + + template template + Score Evaluation::pieces(PieceType Pt) { + + constexpr Color Them = ~Us; + constexpr Direction Down = -pawn_push(Us); + constexpr Bitboard OutpostRanks = (Us == WHITE ? Rank4BB | Rank5BB | Rank6BB + : Rank5BB | Rank4BB | Rank3BB); + Bitboard b1 = pos.pieces(Us, Pt); + Bitboard b, bb; + Score score = SCORE_ZERO; + + attackedBy[Us][Pt] = 0; + + while (b1) + { + Square s = pop_lsb(b1); + + // Find attacked squares, including x-ray attacks for bishops and rooks + b = Pt == BISHOP ? attacks_bb(s, pos.pieces() ^ pos.pieces(QUEEN)) + : Pt == ROOK && !pos.diagonal_lines() ? attacks_bb< ROOK>(s, pos.pieces() ^ pos.pieces(QUEEN) ^ pos.pieces(Us, ROOK)) + : pos.attacks_from(Us, Pt, s); + + // Restrict mobility to actual squares of board + b &= pos.board_bb(Us, Pt); + + if (pos.blockers_for_king(Us) & s) + b &= line_bb(pos.square(Us), s); + + attackedBy2[Us] |= attackedBy[Us][ALL_PIECES] & b; + attackedBy[Us][Pt] |= b; + attackedBy[Us][ALL_PIECES] |= b; + + if (b & kingRing[Them]) + { + kingAttackersCount[Us]++; + kingAttackersWeight[Us] += KingAttackWeights[std::min(Pt, FAIRY_PIECES)]; + kingAttacksCount[Us] += popcount(b & attackedBy[Them][KING]); + } + + else if (Pt == ROOK && (file_bb(s) & kingRing[Them])) + score += RookOnKingRing; + + else if (Pt == BISHOP && (attacks_bb(s, pos.pieces(PAWN)) & kingRing[Them])) + score += BishopOnKingRing; + + if (Pt > QUEEN) + b = (b & pos.pieces()) | (pos.moves_from(Us, Pt, s) & ~pos.pieces() & pos.board_bb()); + + int mob = popcount(b & mobilityArea[Us]); + if (Pt <= QUEEN) + mobility[Us] += MobilityBonus[Pt - 2][mob]; + else + mobility[Us] += MaxMobility * (mob - 2) / (8 + mob); + + // Piece promotion bonus + if (pos.promoted_piece_type(Pt) != NO_PIECE_TYPE) + { + Bitboard zone = pos.promotion_zone(Us); + if (zone & (b | s)) + score += make_score(PieceValue[MG][pos.promoted_piece_type(Pt)] - PieceValue[MG][Pt], + PieceValue[EG][pos.promoted_piece_type(Pt)] - PieceValue[EG][Pt]) / (zone & s && b ? 6 : 12); + } + else if (pos.piece_demotion() && pos.unpromoted_piece_on(s)) + score -= make_score(PieceValue[MG][Pt] - PieceValue[MG][pos.unpromoted_piece_on(s)], + PieceValue[EG][Pt] - PieceValue[EG][pos.unpromoted_piece_on(s)]) / 4; + else if (pos.captures_to_hand() && pos.unpromoted_piece_on(s)) + score += make_score(PieceValue[MG][Pt] - PieceValue[MG][pos.unpromoted_piece_on(s)], + PieceValue[EG][Pt] - PieceValue[EG][pos.unpromoted_piece_on(s)]) / 8; + + // Penalty if the piece is far from the kings in drop variants + if ((pos.captures_to_hand() || pos.two_boards()) && pos.count(Them) && pos.count(Us)) + { + if (!(b & (kingRing[Us] | kingRing[Them]))) + score -= KingProximity * distance(s, pos.square(Us)) * distance(s, pos.square(Them)); + } + + else if (pos.count(Us) && (Pt == FERS || Pt == SILVER)) + score -= EndgameKingProximity * (distance(s, pos.square(Us)) - 2); + + if (Pt == SOLDIER && (pos.pieces(Us, SOLDIER) & rank_bb(s) & adjacent_files_bb(s))) + score += ConnectedSoldier; + + if (Pt == BISHOP || Pt == KNIGHT) + { + // Bonus if the piece is on an outpost square or can reach one + // Bonus for knights (UncontestedOutpost) if few relevant targets + bb = OutpostRanks & (attackedBy[Us][PAWN] | shift(pos.pieces(PAWN))) + & ~pe->pawn_attacks_span(Them); + Bitboard targets = pos.pieces(Them) & ~pos.pieces(PAWN); + + if ( Pt == KNIGHT + && bb & s & ~CenterFiles // on a side outpost + && !(b & targets) // no relevant attacks + && (!more_than_one(targets & (s & QueenSide ? QueenSide : KingSide)))) + score += UncontestedOutpost * popcount(pos.pieces(PAWN) & (s & QueenSide ? QueenSide : KingSide)); + else if (bb & s) + score += Outpost[Pt == BISHOP]; + else if (Pt == KNIGHT && bb & b & ~pos.pieces(Us)) + score += ReachableOutpost; + + // Bonus for a knight or bishop shielded by pawn + if (shift(pos.pieces(PAWN)) & s) + score += MinorBehindPawn; + + // Penalty if the piece is far from the king + if (pos.count(Us)) + score -= KingProtector[Pt == BISHOP] * distance(pos.square(Us), s); + + if (Pt == BISHOP) + { + // Penalty according to the number of our pawns on the same color square as the + // bishop, bigger when the center files are blocked with pawns and smaller + // when the bishop is outside the pawn chain. + Bitboard blocked = pos.pieces(Us, PAWN) & shift(pos.pieces()); + + score -= BishopPawns[edge_distance(file_of(s), pos.max_file())] * pos.pawns_on_same_color_squares(Us, s) + * (!(attackedBy[Us][PAWN] & s) + popcount(blocked & CenterFiles)); + + // Penalty for all enemy pawns x-rayed + score -= BishopXRayPawns * popcount(attacks_bb(s) & pos.pieces(Them, PAWN)); + + // Bonus for bishop on a long diagonal which can "see" both center squares + if (more_than_one(attacks_bb(s, pos.pieces(PAWN)) & Center)) + score += LongDiagonalBishop; + + // An important Chess960 pattern: a cornered bishop blocked by a friendly + // pawn diagonally in front of it is a very serious problem, especially + // when that pawn is also blocked. + if ( pos.is_chess960() + && (s == relative_square(Us, SQ_A1) || s == relative_square(Us, SQ_H1))) + { + Direction d = pawn_push(Us) + (file_of(s) == FILE_A ? EAST : WEST); + if (pos.piece_on(s + d) == make_piece(Us, PAWN)) + score -= !pos.empty(s + d + pawn_push(Us)) ? 4 * make_score(CorneredBishop, CorneredBishop) + : 3 * make_score(CorneredBishop, CorneredBishop); + } + } + } + + if (Pt == ROOK) + { + // Bonuses for rook on a (semi-)open or closed file + if (pos.is_on_semiopen_file(Us, s)) + { + score += RookOnOpenFile[pos.is_on_semiopen_file(Them, s)]; + } + else + { + // If our pawn on this file is blocked, increase penalty + if ( pos.pieces(Us, PAWN) + & shift(pos.pieces()) + & file_bb(s)) + { + score -= RookOnClosedFile; + } + + // Penalty when trapped by the king, even more if the king cannot castle + if (mob <= 3 && pos.count(Us)) + { + File kf = file_of(pos.square(Us)); + if ((kf < FILE_E) == (file_of(s) < kf)) + score -= TrappedRook * (1 + !pos.castling_rights(Us)); + } + } + } + + if (Pt == QUEEN) + { + // Penalty if any relative pin or discovered attack against the queen + Bitboard queenPinners; + if (pos.slider_blockers(pos.pieces(Them, ROOK, BISHOP), s, queenPinners, Them)) + score -= WeakQueen; + } + } + if constexpr (T) + Trace::add(Pt, Us, score); + + return score; + } + + // Evaluation::hand() scores pieces of a given color and type in hand + template template + Score Evaluation::hand(PieceType pt) { + + constexpr Color Them = ~Us; + + Score score = SCORE_ZERO; + + if (pos.count_in_hand(Us, pt) > 0 && pt != KING) + { + Bitboard b = pos.drop_region(Us, pt) & ~pos.pieces() & (~attackedBy2[Them] | attackedBy[Us][ALL_PIECES]); + if ((b & kingRing[Them]) && pt != SHOGI_PAWN) + { + kingAttackersCountInHand[Us] += pos.count_in_hand(Us, pt); + kingAttackersWeightInHand[Us] += KingAttackWeights[std::min(pt, FAIRY_PIECES)] * pos.count_in_hand(Us, pt); + kingAttacksCount[Us] += popcount(b & attackedBy[Them][KING]); + } + Bitboard theirHalf = pos.board_bb() & ~forward_ranks_bb(Them, relative_rank(Them, Rank((pos.max_rank() - 1) / 2), pos.max_rank())); + mobility[Us] += DropMobility * popcount(b & theirHalf & ~attackedBy[Them][ALL_PIECES]); + + // Bonus for Kyoto shogi style drops of promoted pieces + if (pos.promoted_piece_type(pt) != NO_PIECE_TYPE && pos.drop_promoted()) + score += make_score(std::max(PieceValue[MG][pos.promoted_piece_type(pt)] - PieceValue[MG][pt], VALUE_ZERO), + std::max(PieceValue[EG][pos.promoted_piece_type(pt)] - PieceValue[EG][pt], VALUE_ZERO)) / 4 * pos.count_in_hand(Us, pt); + + // Mobility bonus for reversi variants + if (pos.enclosing_drop()) + mobility[Us] += make_score(500, 500) * popcount(b); + + // Reduce score if there is a deficit of gates + if (pos.seirawan_gating() && !pos.piece_drops() && pos.count_in_hand(Us, ALL_PIECES) > popcount(pos.gates(Us))) + score -= make_score(200, 900) / pos.count_in_hand(Us, ALL_PIECES) * (pos.count_in_hand(Us, ALL_PIECES) - popcount(pos.gates(Us))); + + // Redundant pieces that can not be doubled per file (e.g., shogi pawns) + if (pt == pos.drop_no_doubled()) + score -= make_score(50, 20) * std::max(pos.count_with_hand(Us, pt) - pos.max_file() - 1, 0); + } + + return score; + } + + // Evaluation::king() assigns bonuses and penalties to a king of a given color + + template template + Score Evaluation::king() const { + + constexpr Color Them = ~Us; + Rank r = relative_rank(Us, std::min(Rank((pos.max_rank() - 1) / 2 + 1), pos.max_rank()), pos.max_rank()); + Bitboard Camp = pos.board_bb() & ~forward_ranks_bb(Us, r); + + if (!pos.count(Us) || !pos.checking_permitted() || pos.checkmate_value() != -VALUE_MATE) + return SCORE_ZERO; + + Bitboard weak, b1, b2, b3, safe, unsafeChecks = 0; + Bitboard queenChecks, knightChecks, pawnChecks, otherChecks; + int kingDanger = 0; + const Square ksq = pos.square(Us); + + // Init the score with king shelter and enemy pawns storm + Score score = pe->king_safety(pos); + + // Attacked squares defended at most once by our queen or king + weak = attackedBy[Them][ALL_PIECES] + & ~attackedBy2[Us] + & (~attackedBy[Us][ALL_PIECES] | attackedBy[Us][KING] | attackedBy[Us][QUEEN]); + + // Analyse the safe enemy's checks which are possible on next move + safe = ~pos.pieces(Them); + if (!pos.check_counting() || pos.checks_remaining(Them) > 1) + safe &= ~attackedBy[Us][ALL_PIECES] | (weak & attackedBy2[Them]); + + b1 = attacks_bb(ksq, pos.pieces() ^ pos.pieces(Us, QUEEN)); + b2 = attacks_bb(ksq, pos.pieces() ^ pos.pieces(Us, QUEEN)); + + std::function get_attacks = [this](Color c, PieceType pt) { + return attackedBy[c][pt] | (pos.piece_drops() && pos.count_in_hand(c, pt) > 0 ? pos.drop_region(c, pt) & ~pos.pieces() : Bitboard(0)); + }; + for (PieceSet ps = pos.piece_types(); ps;) + { + PieceType pt = pop_lsb(ps); + switch (pt) + { + case QUEEN: + // Enemy queen safe checks: we count them only if they are from squares from + // which we can't give a rook check, because rook checks are more valuable. + queenChecks = (b1 | b2) + & get_attacks(Them, QUEEN) + & pos.board_bb() + & safe + & ~attackedBy[Us][QUEEN] + & ~(b1 & attackedBy[Them][ROOK]); + + if (queenChecks) + kingDanger += SafeCheck[QUEEN][more_than_one(queenChecks)]; + break; + case ROOK: + case BISHOP: + case KNIGHT: + knightChecks = attacks_bb(Us, pt, ksq, pos.pieces() ^ pos.pieces(Us, QUEEN)) & get_attacks(Them, pt) & pos.board_bb(); + if (knightChecks & safe) + kingDanger += SafeCheck[pt][more_than_one(knightChecks & safe)]; + else + unsafeChecks |= knightChecks; + break; + case PAWN: + if (pos.piece_drops() && pos.count_in_hand(Them, pt) > 0) + { + pawnChecks = attacks_bb(Us, pt, ksq, pos.pieces()) & ~pos.pieces() & pos.board_bb(); + if (pawnChecks & safe) + kingDanger += SafeCheck[PAWN][more_than_one(pawnChecks & safe)]; + else + unsafeChecks |= pawnChecks; + } + break; + case SHOGI_PAWN: + if (pos.promoted_piece_type(pt)) + { + otherChecks = attacks_bb(Us, pos.promoted_piece_type(pt), ksq, pos.pieces()) & attackedBy[Them][pt] + & pos.promotion_zone(Them) & pos.board_bb(); + if (otherChecks & safe) + kingDanger += SafeCheck[FAIRY_PIECES][more_than_one(otherChecks & safe)]; + else + unsafeChecks |= otherChecks; + } + break; + case KING: + break; + default: + otherChecks = attacks_bb(Us, pt, ksq, pos.pieces()) & get_attacks(Them, pt) & pos.board_bb(); + if (otherChecks & safe) + kingDanger += SafeCheck[FAIRY_PIECES][more_than_one(otherChecks & safe)]; + else + unsafeChecks |= otherChecks; + } + } + + // Virtual piece drops + if (pos.two_boards() && pos.piece_drops()) + { + for (PieceSet ps = pos.piece_types(); ps;) + { + PieceType pt = pop_lsb(ps); + if (pos.count_in_hand(Them, pt) <= 0 && (attacks_bb(Us, pt, ksq, pos.pieces()) & safe & pos.drop_region(Them, pt) & ~pos.pieces())) + { + kingDanger += VirtualCheck * 500 / (500 + PieceValue[MG][pt]); + // Presumably a mate threat + if (!(attackedBy[Us][KING] & ~(attackedBy[Them][ALL_PIECES] | pos.pieces(Us)))) + kingDanger += 2000; + } + } + } + + if (pos.check_counting()) + kingDanger += kingDanger * 7 / (3 + pos.checks_remaining(Them)); + + Square s = file_of(ksq) == FILE_A ? ksq + EAST : file_of(ksq) == pos.max_file() ? ksq + WEST : ksq; + Bitboard kingFlank = pos.max_file() == FILE_H ? KingFlank[file_of(ksq)] : file_bb(s) | adjacent_files_bb(s); + + // Find the squares that opponent attacks in our king flank, the squares + // which they attack twice in that flank, and the squares that we defend. + b1 = attackedBy[Them][ALL_PIECES] & kingFlank & Camp; + b2 = b1 & attackedBy2[Them]; + b3 = attackedBy[Us][ALL_PIECES] & kingFlank & Camp; + + int kingFlankAttack = popcount(b1) + popcount(b2); + int kingFlankDefense = popcount(b3); + + kingDanger += kingAttackersCount[Them] * kingAttackersWeight[Them] + + kingAttackersCountInHand[Them] * kingAttackersWeight[Them] + + kingAttackersCount[Them] * kingAttackersWeightInHand[Them] + + 183 * popcount(kingRing[Us] & (weak | ~pos.board_bb(Us, KING))) * (1 + pos.captures_to_hand() + pos.check_counting()) + + 148 * popcount(unsafeChecks) * (1 + pos.check_counting()) + + 98 * popcount(pos.blockers_for_king(Us)) + + 69 * kingAttacksCount[Them] * (2 + 8 * pos.check_counting() + pos.captures_to_hand()) / 2 + + 3 * kingFlankAttack * kingFlankAttack / 8 + + mg_value(mobility[Them] - mobility[Us]) * int(!pos.captures_to_hand()) + - 873 * !(pos.major_pieces(Them) || pos.captures_to_hand()) + * 2 / (2 + 2 * pos.check_counting() + 2 * pos.two_boards() + 2 * pos.makpong() + + (pos.king_type() != KING) * (pos.diagonal_lines() ? 1 : 2)) + - 100 * bool(attackedBy[Us][KNIGHT] & attackedBy[Us][KING]) + - 6 * mg_value(score) / 8 + - 4 * kingFlankDefense + + 37; + + // Transform the kingDanger units into a Score, and subtract it from the evaluation + if (kingDanger > 100) + score -= make_score(std::min(kingDanger, 3500) * kingDanger / 4096, kingDanger / 16); + + // Penalty when our king is on a pawnless flank + if (!(pos.pieces(PAWN) & kingFlank)) + score -= PawnlessFlank; + + // Penalty if king flank is under attack, potentially moving toward the king + score -= FlankAttacks * kingFlankAttack * (1 + 5 * pos.captures_to_hand() + pos.check_counting()); + + if (pos.check_counting()) + score += make_score(0, mg_value(score) * 2 / (2 + pos.checks_remaining(Them))); + + if (pos.king_type() == WAZIR) + score += make_score(0, mg_value(score) / 2); + + // For drop games, king danger is independent of game phase, but dependent on material density + if (pos.captures_to_hand() || pos.two_boards()) + score = make_score(mg_value(score) * me->material_density() / 11000, + mg_value(score) * me->material_density() / 11000); + + if constexpr (T) + Trace::add(KING, Us, score); + + return score; + } + + + // Evaluation::threats() assigns bonuses according to the types of the + // attacking and the attacked pieces. + + template template + Score Evaluation::threats() const { + + constexpr Color Them = ~Us; + constexpr Direction Up = pawn_push(Us); + constexpr Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB); + + Bitboard b, weak, defended, nonPawnEnemies, stronglyProtected, safe; + Score score = SCORE_ZERO; + + // Bonuses for variants with mandatory captures + if (pos.must_capture()) + { + // Penalties for possible captures + Bitboard captures = attackedBy[Us][ALL_PIECES] & pos.pieces(Them); + if (captures) + score -= make_score(2000, 2000) / (1 + popcount(captures & attackedBy[Them][ALL_PIECES] & ~attackedBy2[Us])); + + // Bonus if we threaten to force captures + Bitboard moves = 0, piecebb = pos.pieces(Us); + while (piecebb) + { + Square s = pop_lsb(piecebb); + if (type_of(pos.piece_on(s)) != KING) + moves |= pos.moves_from(Us, type_of(pos.piece_on(s)), s); + } + score += make_score(200, 200) * popcount(attackedBy[Them][ALL_PIECES] & moves & ~pos.pieces()); + score += make_score(200, 220) * popcount(attackedBy[Them][ALL_PIECES] & moves & ~pos.pieces() & ~attackedBy2[Us]); + } + + // Extinction threats + if (pos.extinction_value() == -VALUE_MATE) + { + Bitboard bExt = attackedBy[Us][ALL_PIECES] & pos.pieces(Them); + for (PieceSet ps = pos.extinction_piece_types(); ps;) + { + PieceType pt = pop_lsb(ps); + if (pt == ALL_PIECES) + continue; + int denom = std::max(pos.count_with_hand(Them, pt) - pos.extinction_piece_count(), 1); + // Explosion threats + if (pos.blast_on_capture()) + { + int evasions = popcount(((attackedBy[Them][pt] & ~pos.pieces(Them)) | pos.pieces(Them, pt)) & ~attackedBy[Us][ALL_PIECES]) * denom; + int attacks = popcount((attackedBy[Them][pt] | pos.pieces(Them, pt)) & attackedBy[Us][ALL_PIECES]); + int explosions = 0; + + Bitboard bExtBlast = bExt & (attackedBy2[Us] | ~attackedBy[Us][pt]); + while (bExtBlast) + { + Square s = pop_lsb(bExtBlast); + if (((attacks_bb(s) | s) & pos.pieces(Them, pt)) && !(attacks_bb(s) & pos.pieces(Us, pt))) + explosions++; + } + int danger = 20 * attacks / (evasions + 1) + 40 * explosions; + score += make_score(danger * (100 + danger), 0); + } + else + // Direct extinction threats + score += make_score(1000, 1000) / (denom * denom) * popcount(bExt & pos.pieces(Them, pt)); + } + } + + // Non-pawn enemies + nonPawnEnemies = pos.pieces(Them) & ~pos.pieces(PAWN, SHOGI_PAWN) & ~pos.pieces(SOLDIER); + + // Squares strongly protected by the enemy, either because they defend the + // square with a pawn, or because they defend the square twice and we don't. + stronglyProtected = (attackedBy[Them][PAWN] | attackedBy[Them][SHOGI_PAWN] | attackedBy[Them][SOLDIER]) + | (attackedBy2[Them] & ~attackedBy2[Us]); + + // Non-pawn enemies, strongly protected + defended = nonPawnEnemies & stronglyProtected; + + // Enemies not strongly protected and under our attack + weak = pos.pieces(Them) & ~stronglyProtected & attackedBy[Us][ALL_PIECES]; + + // Bonus according to the kind of attacking pieces + if (defended | weak) + { + b = (defended | weak) & (attackedBy[Us][KNIGHT] | attackedBy[Us][BISHOP]); + while (b) + score += ThreatByMinor[type_of(pos.piece_on(pop_lsb(b)))]; + + b = weak & attackedBy[Us][ROOK]; + while (b) + score += ThreatByRook[type_of(pos.piece_on(pop_lsb(b)))]; + + if (weak & attackedBy[Us][KING]) + score += ThreatByKing; + + b = ~attackedBy[Them][ALL_PIECES] + | (nonPawnEnemies & attackedBy2[Us]); + score += Hanging * popcount(weak & b); + + // Additional bonus if weak piece is only protected by a queen + score += WeakQueenProtection * popcount(weak & attackedBy[Them][QUEEN]); + } + + // Bonus for restricting their piece moves + b = attackedBy[Them][ALL_PIECES] + & ~stronglyProtected + & attackedBy[Us][ALL_PIECES]; + score += RestrictedPiece * popcount(b); + + // Protected or unattacked squares + safe = ~attackedBy[Them][ALL_PIECES] | attackedBy[Us][ALL_PIECES]; + + // Bonus for attacking enemy pieces with our relatively safe pawns + b = pos.pieces(Us, PAWN) & safe; + b = pawn_attacks_bb(b) & nonPawnEnemies; + score += ThreatBySafePawn * popcount(b); + + // Find squares where our pawns can push on the next move + b = shift(pos.pieces(Us, PAWN)) & ~pos.pieces(); + b |= shift(b & TRank3BB) & ~pos.pieces(); + + // Keep only the squares which are relatively safe + b &= ~attackedBy[Them][PAWN] & safe; + + // Bonus for safe pawn threats on the next move + b = (pawn_attacks_bb(b) | shift(shift(pos.pieces(Us, SHOGI_PAWN, SOLDIER)))) & nonPawnEnemies; + score += ThreatByPawnPush * popcount(b); + + // Bonus for threats on the next moves against enemy queen + if (pos.count(Them) == 1) + { + bool queenImbalance = pos.count() == 1; + + Square s = pos.square(Them); + safe = mobilityArea[Us] + & ~pos.pieces(Us, PAWN) + & ~stronglyProtected; + + b = attackedBy[Us][KNIGHT] & attacks_bb(s); + + score += KnightOnQueen * popcount(b & safe) * (1 + queenImbalance); + + b = (attackedBy[Us][BISHOP] & attacks_bb(s, pos.pieces())) + | (attackedBy[Us][ROOK ] & attacks_bb(s, pos.pieces())); + + score += SliderOnQueen * popcount(b & safe & attackedBy2[Us]) * (1 + queenImbalance); + } + + if constexpr (T) + Trace::add(THREAT, Us, score); + + return score; + } + + // Evaluation::passed() evaluates the passed pawns and candidate passed + // pawns of the given color. + + template template + Score Evaluation::passed() const { + + constexpr Color Them = ~Us; + constexpr Direction Up = pawn_push(Us); + constexpr Direction Down = -Up; + + auto king_proximity = [&](Color c, Square s) { + return pos.extinction_value() == VALUE_MATE ? 0 : pos.count(c) ? std::min(distance(pos.square(c), s), 5) : 5; + }; + + Bitboard b, bb, squaresToQueen, unsafeSquares, blockedPassers, helpers; + Score score = SCORE_ZERO; + + b = pe->passed_pawns(Us); + + blockedPassers = b & shift(pos.pieces(Them, PAWN)); + if (blockedPassers) + { + helpers = shift(pos.pieces(Us, PAWN)) + & ~pos.pieces(Them) + & (~attackedBy2[Them] | attackedBy[Us][ALL_PIECES]); + + // Remove blocked candidate passers that don't have help to pass + b &= ~blockedPassers + | shift(helpers) + | shift(helpers); + } + + while (b) + { + Square s = pop_lsb(b); + + assert(!(pos.pieces(Them, PAWN) & forward_file_bb(Us, s + Up))); + + int r = std::max(RANK_8 - std::max(relative_rank(Us, pos.promotion_square(Us, s), pos.max_rank()) - relative_rank(Us, s, pos.max_rank()), 0), 0); + + Score bonus = PassedRank[r]; + + if (r > RANK_3) + { + int w = 5 * r - 13; + Square blockSq = s + Up; + + // Adjust bonus based on the king's proximity + bonus += make_score(0, ( king_proximity(Them, blockSq) * 19 / 4 + - king_proximity(Us, blockSq) * 2) * w); + + // If blockSq is not the queening square then consider also a second push + if (r != RANK_7) + bonus -= make_score(0, king_proximity(Us, blockSq + Up) * w); + + // If the pawn is free to advance, then increase the bonus + if (pos.empty(blockSq)) + { + squaresToQueen = forward_file_bb(Us, s); + unsafeSquares = passed_pawn_span(Us, s); + + bb = forward_file_bb(Them, s) & pos.pieces(ROOK, QUEEN); + + if (!(pos.pieces(Them) & bb)) + unsafeSquares &= attackedBy[Them][ALL_PIECES] | pos.pieces(Them); + + // If there are no enemy pieces or attacks on passed pawn span, assign a big bonus. + // Or if there is some, but they are all attacked by our pawns, assign a bit smaller bonus. + // Otherwise assign a smaller bonus if the path to queen is not attacked + // and even smaller bonus if it is attacked but block square is not. + int k = !unsafeSquares ? 36 : + !(unsafeSquares & ~attackedBy[Us][PAWN]) ? 30 : + !(unsafeSquares & squaresToQueen) ? 17 : + !(unsafeSquares & blockSq) ? 7 : + 0 ; + + // Assign a larger bonus if the block square is defended + if ((pos.pieces(Us) & bb) || (attackedBy[Us][ALL_PIECES] & blockSq)) + k += 5; + + bonus += make_score(k * w, k * w); + } + } // r > RANK_3 + + score += bonus - PassedFile * edge_distance(file_of(s), pos.max_file()); + } + + // Passed custom pawns + for (PieceSet ps = (pos.promotion_pawn_types(Us) & ~piece_set(PAWN)); ps;) + { + PieceType pt = pop_lsb(ps); + Bitboard b2 = pos.pieces(Us, pt); + while (b2) + { + Square s = pop_lsb(b2); + if (pos.promotion_square(Us, s) == SQ_NONE || (pos.pieces(Them, pt) & forward_file_bb(Us, s))) + continue; + int r = std::max(RANK_8 - std::max(relative_rank(Us, pos.promotion_square(Us, s), pos.max_rank()) - relative_rank(Us, s, pos.max_rank()), 0), 0); + score += PassedRank[r]; + } + } + + // Scale by maximum promotion piece value + Value maxMg = VALUE_ZERO, maxEg = VALUE_ZERO; + for (PieceSet ps = pos.promotion_piece_types(Us); ps;) + { + PieceType pt = pop_lsb(ps); + maxMg = std::max(maxMg, PieceValue[MG][pt]); + maxEg = std::max(maxEg, PieceValue[EG][pt]); + } + score = make_score(mg_value(score) * int(maxMg - PawnValueMg) / (QueenValueMg - PawnValueMg), + eg_value(score) * int(maxEg - PawnValueEg) / (QueenValueEg - PawnValueEg)); + + // Score passed shogi pawns + PieceType pt = pos.promoted_piece_type(SHOGI_PAWN); + if (pt != NO_PIECE_TYPE) + { + b = pos.pieces(Us, SHOGI_PAWN); + while (b) + { + Square s = pop_lsb(b); + if ((pos.pieces(Them, SHOGI_PAWN) & forward_file_bb(Us, s)) || pos.promotion_square(Us, s) == SQ_NONE) + continue; + + Square blockSq = s + Up; + int d = 2 * std::max(relative_rank(Us, pos.promotion_square(Us, s), pos.max_rank()) - relative_rank(Us, s, pos.max_rank()), 1); + d += !!(attackedBy[Them][ALL_PIECES] & ~attackedBy2[Us] & blockSq); + score += make_score(PieceValue[MG][pt], PieceValue[EG][pt]) / (d * d); + } + } + + if constexpr (T) + Trace::add(PASSED, Us, score); + + return score; + } + + + // Evaluation::space() computes a space evaluation for a given side, aiming to improve game + // play in the opening. It is based on the number of safe squares on the four central files + // on ranks 2 to 4. Completely safe squares behind a friendly pawn are counted twice. + // Finally, the space bonus is multiplied by a weight which decreases according to occupancy. + + template template + Score Evaluation::space() const { + + bool pawnsOnly = !(pos.pieces(Us) ^ pos.pieces(Us, PAWN)); + + // Early exit if, for example, both queens or 6 minor pieces have been exchanged + if (pos.non_pawn_material() < SpaceThreshold && !pawnsOnly && pos.double_step_region(Us)) + return SCORE_ZERO; + + constexpr Color Them = ~Us; + constexpr Direction Down = -pawn_push(Us); + constexpr Bitboard SpaceMask = + Us == WHITE ? CenterFiles & (Rank2BB | Rank3BB | Rank4BB) + : CenterFiles & (Rank7BB | Rank6BB | Rank5BB); + + // Find the available squares for our pieces inside the area defined by SpaceMask + Bitboard safe = SpaceMask + & ~pos.pieces(Us, PAWN) + & ~attackedBy[Them][PAWN]; + + // Find all squares which are at most three squares behind some friendly pawn + Bitboard behind = pos.pieces(Us, PAWN); + behind |= shift(behind); + behind |= shift(behind); + + if (pawnsOnly) + { + safe = pos.board_bb() & ((attackedBy2[Us] & ~attackedBy2[Them]) | (attackedBy[Us][PAWN] & ~pos.pieces(Us, PAWN))); + behind = 0; + } + + // Compute space score based on the number of safe squares and number of our pieces + // increased with number of total blocked pawns in position. + int bonus = popcount(safe) + popcount(behind & safe & ~attackedBy[Them][ALL_PIECES]); + int weight = pos.count(Us) - 3 + std::min(pe->blocked_count(), 9); + Score score = make_score(bonus * weight * weight / 16, 0); + + if (pos.flag_region(Us)) + score += make_score(200, 200) * popcount(behind & safe & pos.flag_region(Us)); + + if constexpr (T) + Trace::add(SPACE, Us, score); + + return score; + } + + + // Evaluation::variant() computes variant-specific evaluation bonuses for a given side. + + template template + Score Evaluation::variant() const { + + constexpr Color Them = ~Us; + constexpr Direction Down = pawn_push(Them); + + Score score = SCORE_ZERO; + + // Capture the flag + if (pos.flag_region(Us)) + { + Bitboard ctfPieces = pos.pieces(Us, pos.flag_piece(Us)); + Bitboard ctfTargets = pos.flag_region(Us) & pos.board_bb(); + Bitboard onHold = 0; + Bitboard onHold2 = 0; + Bitboard processed = 0; + Bitboard blocked = pos.pieces(Us, PAWN) | attackedBy[Them][ALL_PIECES]; + Bitboard doubleBlocked = attackedBy2[Them] + | (pos.pieces(Us, PAWN) & (shift(pos.pieces()) | attackedBy[Them][ALL_PIECES])) + | (pos.pieces(Them) & pe->pawn_attacks(Them)) + | (pawn_attacks_bb(pos.pieces(Them, PAWN) & pe->pawn_attacks(Them))); + Bitboard inaccessible = pos.pieces(Us, PAWN) & shift(pos.pieces(Them, PAWN)); + // Traverse all paths of the CTF pieces to the CTF targets. + // Put squares that are attacked or occupied on hold for one iteration. + // This reflects that likely a move will be needed to block or capture the attack. + // If all piece types are eligible, use the king path as a proxy for distance. + PieceType ptCtf = pos.flag_piece(Us) == ALL_PIECES ? KING : pos.flag_piece(Us); + for (int dist = 0; (ctfPieces || onHold || onHold2) && (ctfTargets & ~processed); dist++) + { + int wins = popcount(ctfTargets & ctfPieces); + if (wins) + score += make_score(4000, 4000) * wins / (wins + dist * dist); + Bitboard current = ctfPieces & ~ctfTargets; + processed |= ctfPieces; + ctfPieces = onHold & ~processed; + onHold = onHold2 & ~processed; + onHold2 = 0; + while (current) + { + Square s = pop_lsb(current); + Bitboard attacks = ( (PseudoAttacks[Us][ptCtf][s] & pos.pieces()) + | (PseudoMoves[0][Us][ptCtf][s] & ~pos.pieces())) & ~processed & pos.board_bb(); + ctfPieces |= attacks & ~blocked; + onHold |= attacks & ~doubleBlocked; + onHold2 |= attacks & ~inaccessible; + } + } + } + + // nCheck + if (pos.check_counting()) + { + int remainingChecks = pos.checks_remaining(Us); + assert(remainingChecks > 0); + score += make_score(3600, 1000) / (remainingChecks * remainingChecks); + } + + // Extinction + if (pos.extinction_value() != VALUE_NONE) + { + for (PieceSet ps = pos.extinction_piece_types(); ps;) + { + PieceType pt = pop_lsb(ps); + if (pt != ALL_PIECES) + { + // Single piece type extinction bonus + int denom = std::max(pos.count(Us, pt) - pos.extinction_piece_count(), 1); + if (pos.count(Them, pt) >= pos.extinction_opponent_piece_count() || pos.two_boards()) + score += make_score(1000000 / (500 + PieceValue[MG][pt]), + 1000000 / (500 + PieceValue[EG][pt])) / (denom * denom) + * (pos.extinction_value() / VALUE_MATE); + } + else if (pos.extinction_value() == VALUE_MATE) + { + // Losing chess variant bonus + score += make_score(pos.non_pawn_material(Us), pos.non_pawn_material(Us)) / std::max(pos.count(Us), 1); + } + else if (pos.count(Us) == pos.count(Us)) + { + // Pawns easy to stop/capture + int l = 0, m = 0, r = popcount(pos.pieces(Us, PAWN) & file_bb(FILE_A)); + for (File f = FILE_A; f <= pos.max_file(); ++f) + { + l = m; m = r; r = popcount(pos.pieces(Us, PAWN) & shift(file_bb(f))); + score -= make_score(80 - 10 * (edge_distance(f, pos.max_file()) % 2), + 80 - 15 * (edge_distance(f, pos.max_file()) % 2)) * m / (1 + l * r); + } + } + else if (pos.count(Them) == pos.count(Them)) + { + // Add a bonus according to how close we are to breaking through the pawn wall + int dist = 8; + Bitboard breakthroughs = attackedBy[Us][ALL_PIECES] & rank_bb(relative_rank(Us, pos.max_rank(), pos.max_rank())); + if (breakthroughs) + dist = attackedBy[Us][QUEEN] & breakthroughs ? 0 : 1; + else for (File f = FILE_A; f <= pos.max_file(); ++f) + dist = std::min(dist, popcount(pos.pieces(PAWN) & file_bb(f))); + score += make_score(70, 70) * pos.count(Them) / (1 + dist * dist) / (pos.pieces(Us, QUEEN) ? 2 : 4); + } + } + } + + // Connect-n + if (pos.connect_n() > 0) + { + //Calculate eligible pieces for connection once. + //Still consider all opponent pieces as blocking. + Bitboard connectPiecesUs = 0; + for (PieceSet ps = pos.connect_piece_types(); ps;){ + PieceType pt = pop_lsb(ps); + connectPiecesUs |= pos.pieces(pt); + }; + connectPiecesUs &= pos.pieces(Us); + + for (const Direction& d : pos.getConnectDirections()) + + { + // Find sufficiently large gaps + Bitboard b = pos.board_bb() & ~pos.pieces(Them); + for (int i = 1; i < pos.connect_n(); i++) + b &= shift(d, b); + // Count number of pieces per gap + while (b) + { + Square s = pop_lsb(b); + int c = 0; + for (int j = 0; j < pos.connect_n(); j++) + if (connectPiecesUs & (s - j * d)) + c++; + score += make_score(200, 200) * c / (pos.connect_n() - c) / (pos.connect_n() - c); + } + } + } + + // Potential piece flips (Reversi) + if (pos.flip_enclosed_pieces()) + { + // Stable pieces + if (pos.flip_enclosed_pieces() == REVERSI) + { + Bitboard edges = (FileABB | file_bb(pos.max_file()) | Rank1BB | rank_bb(pos.max_rank())) & pos.board_bb(); + Bitboard edgePieces = pos.pieces(Us) & edges; + while (edgePieces) + { + Bitboard connectedEdge = attacks_bb(Us, ROOK, pop_lsb(edgePieces), ~(pos.pieces(Us) & edges)) & edges; + if (!more_than_one(connectedEdge & ~pos.pieces(Us))) + score += make_score(300, 300); + else if (!(connectedEdge & ~pos.pieces())) + score += make_score(200, 200); + } + } + + // Unstable + Bitboard unstable = 0; + Bitboard drops = pos.drop_region(Them, IMMOBILE_PIECE); + while (drops) + { + Square s = pop_lsb(drops); + if (pos.flip_enclosed_pieces() == REVERSI) + { + Bitboard b = attacks_bb(Them, QUEEN, s, ~pos.pieces(Us)) & ~PseudoAttacks[Them][KING][s] & pos.pieces(Them); + while(b) + unstable |= between_bb(s, pop_lsb(b)); + } + else + unstable |= PseudoAttacks[Them][KING][s] & pos.pieces(Us); + } + score -= make_score(200, 200) * popcount(unstable); + } + + if (T) + Trace::add(VARIANT, Us, score); + + return score; + } + + + // Evaluation::winnable() adjusts the midgame and endgame score components, based on + // the known attacking/defending status of the players. The final value is derived + // by interpolation from the midgame and endgame values. + + template + Value Evaluation::winnable(Score score) const { + + // No initiative bonus for variants that do not require sufficient mating material, e.g., extinction variants. + // This protects them from misidentification as drawish. + int complexity = 0; + bool pawnsOnBothFlanks = true; + if ( pos.extinction_value() == VALUE_NONE + && !pos.captures_to_hand() + && !pos.connect_n() + && !pos.material_counting() + && !(pos.flag_region(WHITE) || pos.flag_region(BLACK))) + { + int outflanking = !pos.count(WHITE) || !pos.count(BLACK) ? 0 + : distance(pos.square(WHITE), pos.square(BLACK)) + + int(rank_of(pos.square(WHITE)) - rank_of(pos.square(BLACK))); + + pawnsOnBothFlanks = (pos.pieces(PAWN) & QueenSide) + && (pos.pieces(PAWN) & KingSide); + + bool almostUnwinnable = outflanking < 0 + && pos.stalemate_value() == VALUE_DRAW + && !pawnsOnBothFlanks; + + bool infiltration = (pos.count(WHITE) && rank_of(pos.square(WHITE)) > RANK_4) + || (pos.count(BLACK) && rank_of(pos.square(BLACK)) < RANK_5); + + // Compute the initiative bonus for the attacking side + complexity = 9 * pe->passed_count() + + 12 * pos.count(WHITE, pos.main_promotion_pawn_type(WHITE)) * bool(pos.main_promotion_pawn_type(WHITE)) + + 12 * pos.count(BLACK, pos.main_promotion_pawn_type(BLACK)) * bool(pos.main_promotion_pawn_type(BLACK)) + + 15 * pos.count() + + 9 * outflanking + + 21 * pawnsOnBothFlanks + + 24 * infiltration + + 51 * !pos.non_pawn_material() + - 43 * almostUnwinnable + -110 ; + } + + Value mg = mg_value(score); + Value eg = eg_value(score); + + // Now apply the bonus: note that we find the attacking side by extracting the + // sign of the midgame or endgame values, and that we carefully cap the bonus + // so that the midgame and endgame scores do not change sign after the bonus. + int u = ((mg > 0) - (mg < 0)) * std::clamp(complexity + 50, -abs(mg), 0); + int v = ((eg > 0) - (eg < 0)) * std::max(complexity, -abs(eg)); + + mg += u; + eg += v; + + // Compute the scale factor for the winning side + Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK; + int sf = me->scale_factor(pos, strongSide); + + // If scale factor is not already specific, scale up/down via general heuristics + if (sf == SCALE_FACTOR_NORMAL && !pos.captures_to_hand() && !pos.material_counting()) + { + if (pos.opposite_bishops()) + { + // For pure opposite colored bishops endgames use scale factor + // based on the number of passed pawns of the strong side. + if ( pos.non_pawn_material(WHITE) == BishopValueMg + && pos.non_pawn_material(BLACK) == BishopValueMg) + sf = 18 + 4 * popcount(pe->passed_pawns(strongSide)); + // For every other opposite colored bishops endgames use scale factor + // based on the number of all pieces of the strong side. + else + sf = 22 + 3 * pos.count(strongSide); + } + // For rook endgames with strong side not having overwhelming pawn number advantage + // and its pawns being on one flank and weak side protecting its pieces with a king + // use lower scale factor. + else if ( pos.non_pawn_material(WHITE) == RookValueMg + && pos.non_pawn_material(BLACK) == RookValueMg + && pos.count(strongSide) - pos.count(~strongSide) <= 1 + && bool(KingSide & pos.pieces(strongSide, PAWN)) != bool(QueenSide & pos.pieces(strongSide, PAWN)) + && pos.count(~strongSide) + && (attacks_bb(pos.square(~strongSide)) & pos.pieces(~strongSide, PAWN))) + sf = 36; + // For queen vs no queen endgames use scale factor + // based on number of minors of side that doesn't have queen. + else if (pos.count() == 1) + sf = 37 + 3 * (pos.count(WHITE) == 1 ? pos.count(BLACK) + pos.count(BLACK) + : pos.count(WHITE) + pos.count(WHITE)); + // In every other case use scale factor based on + // the number of pawns of the strong side reduced if pawns are on a single flank. + else + sf = std::min(sf, 36 + 7 * (pos.count(strongSide) + pos.count(strongSide))) - 4 * !pawnsOnBothFlanks; + + // Reduce scale factor in case of pawns being on a single flank + sf -= 4 * !pawnsOnBothFlanks; + } + + // Interpolate between the middlegame and (scaled by 'sf') endgame score + v = mg * int(me->game_phase()) + + eg * int(PHASE_MIDGAME - me->game_phase()) * ScaleFactor(sf) / SCALE_FACTOR_NORMAL; + v /= PHASE_MIDGAME; + + if constexpr (T) + { + Trace::add(WINNABLE, make_score(u, eg * ScaleFactor(sf) / SCALE_FACTOR_NORMAL - eg_value(score))); + Trace::add(TOTAL, make_score(mg, eg * ScaleFactor(sf) / SCALE_FACTOR_NORMAL)); + } + + return Value(v); + } + + + // Evaluation::value() is the main function of the class. It computes the various + // parts of the evaluation and returns the value of the position from the point + // of view of the side to move. + + template + Value Evaluation::value() { + + assert(!pos.checkers()); + assert(!pos.is_immediate_game_end()); + + // Probe the material hash table + me = Material::probe(pos); + + // If we have a specialized evaluation function for the current material + // configuration, call it and return. + if (me->specialized_eval_exists()) + return me->evaluate(pos); + + // Initialize score by reading the incrementally updated scores included in + // the position object (material + piece square tables) and the material + // imbalance. Score is computed internally from the white point of view. + Score score = pos.psq_score(); + if (T) + Trace::add(MATERIAL, score); + score += me->imbalance() + pos.this_thread()->trend; + + // Probe the pawn hash table + pe = Pawns::probe(pos); + score += pe->pawn_score(WHITE) - pe->pawn_score(BLACK); + + // Early exit if score is high + auto lazy_skip = [&](Value lazyThreshold) { + return abs(mg_value(score) + eg_value(score)) / 2 > lazyThreshold + pos.non_pawn_material() / 64; + }; + + if (lazy_skip(LazyThreshold1) && Options["UCI_Variant"] == "chess") + goto make_v; + + // Main evaluation begins here + std::memset(attackedBy, 0, sizeof(attackedBy)); + initialize(); + initialize(); + + // Pieces evaluated first (also populates attackedBy, attackedBy2). + // For unused piece types, we still need to set attack bitboard to zero. + for (PieceSet ps = pos.piece_types(); ps;) + { + PieceType pt = pop_lsb(ps); + if (pt != SHOGI_PAWN && pt != PAWN && pt != KING) + score += pieces(pt) - pieces(pt); + } + + // Evaluate pieces in hand once attack tables are complete + if (pos.piece_drops() || pos.seirawan_gating()) + for (PieceSet ps = pos.piece_types(); ps;) + { + PieceType pt = pop_lsb(ps); + score += hand(pt) - hand(pt); + } + + score += (mobility[WHITE] - mobility[BLACK]) * (1 + pos.captures_to_hand() + pos.must_capture() + pos.check_counting()); + + // More complex interactions that require fully populated attack bitboards + score += king< WHITE>() - king< BLACK>() + + passed< WHITE>() - passed< BLACK>() + + variant() - variant(); + + if (lazy_skip(LazyThreshold2) && Options["UCI_Variant"] == "chess") + goto make_v; + + score += threats() - threats() + + space< WHITE>() - space< BLACK>(); + +make_v: + // Derive single value from mg and eg parts of score + Value v = winnable(score); + + // In case of tracing add all remaining individual evaluation terms + if constexpr (T) + { + Trace::add(IMBALANCE, me->imbalance()); + Trace::add(PAWN, pe->pawn_score(WHITE), pe->pawn_score(BLACK)); + Trace::add(MOBILITY, mobility[WHITE], mobility[BLACK]); + } + + // Evaluation grain + v = (v / 16) * 16; + + // Side to move point of view + v = (pos.side_to_move() == WHITE ? v : -v) + 80 * pos.captures_to_hand(); + + return v; + } + + + /// Fisher Random Chess: correction for cornered bishops, to fix chess960 play with NNUE + + Value fix_FRC(const Position& pos) { + + constexpr Bitboard Corners = Bitboard(1ULL) << SQ_A1 | Bitboard(1ULL) << SQ_H1 | Bitboard(1ULL) << SQ_A8 | Bitboard(1ULL) << SQ_H8; + + if (!(pos.pieces(BISHOP) & Corners)) + return VALUE_ZERO; + + int correction = 0; + + if ( pos.piece_on(SQ_A1) == W_BISHOP + && pos.piece_on(SQ_B2) == W_PAWN) + correction += !pos.empty(SQ_B3) ? -CorneredBishop * 4 + : -CorneredBishop * 3; + + if ( pos.piece_on(SQ_H1) == W_BISHOP + && pos.piece_on(SQ_G2) == W_PAWN) + correction += !pos.empty(SQ_G3) ? -CorneredBishop * 4 + : -CorneredBishop * 3; + + if ( pos.piece_on(SQ_A8) == B_BISHOP + && pos.piece_on(SQ_B7) == B_PAWN) + correction += !pos.empty(SQ_B6) ? CorneredBishop * 4 + : CorneredBishop * 3; + + if ( pos.piece_on(SQ_H8) == B_BISHOP + && pos.piece_on(SQ_G7) == B_PAWN) + correction += !pos.empty(SQ_G6) ? CorneredBishop * 4 + : CorneredBishop * 3; + + return pos.side_to_move() == WHITE ? Value(correction) + : -Value(correction); + } + +} // namespace Eval + + +/// evaluate() is the evaluator for the outer world. It returns a static +/// evaluation of the position from the point of view of the side to move. + +Value Eval::evaluate(const Position& pos) { + + Value v; + + if (!Eval::useNNUE || !pos.nnue_applicable()) + v = Evaluation(pos).value(); + else + { + // Scale and shift NNUE for compatibility with search and classical evaluation + auto adjusted_NNUE = [&]() + { + int scale = 903 + + 32 * pos.count() + + 32 * pos.non_pawn_material() / 1024; + + Value nnue = NNUE::evaluate(pos, true) * scale / 1024; + + if (pos.is_chess960()) + nnue += fix_FRC(pos); + + if (pos.check_counting()) + { + Color us = pos.side_to_move(); + nnue += 6 * scale / (5 * pos.checks_remaining( us)) + - 6 * scale / (5 * pos.checks_remaining(~us)); + } + + return nnue; + }; + + // If there is PSQ imbalance we use the classical eval, but we switch to + // NNUE eval faster when shuffling or if the material on the board is high. + int r50 = pos.rule50_count(); + Value psq = Value(abs(eg_value(pos.psq_score()))); + bool pure = !pos.check_counting(); + bool classical = psq * 5 > (750 + pos.non_pawn_material() / 64) * (5 + r50) && !pure; + + v = classical ? Evaluation(pos).value() // classical + : adjusted_NNUE(); // NNUE + } + + // 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)); + } + + // Guarantee evaluation does not hit the virtual win/loss range + if (pos.two_boards() && std::abs(v) >= VALUE_VIRTUAL_MATE_IN_MAX_PLY) + v += v > VALUE_ZERO ? MAX_PLY + 1 : -MAX_PLY - 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 +/// descriptions and values of each evaluation term. Useful for debugging. +/// Trace scores are from white's point of view + +std::string Eval::trace(Position& pos) { + + if (pos.checkers()) + return "Final evaluation: none (in check)"; + + std::stringstream ss; + ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); + + Value v; + + std::memset(scores, 0, sizeof(scores)); + + pos.this_thread()->trend = SCORE_ZERO; // Reset any dynamic contempt + + v = Evaluation(pos).value(); + + ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2) + << " Contributing terms for the classical eval:\n" + << "+------------+-------------+-------------+-------------+\n" + << "| Term | White | Black | Total |\n" + << "| | MG EG | MG EG | MG EG |\n" + << "+------------+-------------+-------------+-------------+\n" + << "| Material | " << Term(MATERIAL) + << "| Imbalance | " << Term(IMBALANCE) + << "| Pawns | " << Term(PAWN) + << "| Knights | " << Term(KNIGHT) + << "| Bishops | " << Term(BISHOP) + << "| Rooks | " << Term(ROOK) + << "| Queens | " << Term(QUEEN) + << "| Mobility | " << Term(MOBILITY) + << "|King safety | " << Term(KING) + << "| Threats | " << Term(THREAT) + << "| Passed | " << Term(PASSED) + << "| Space | " << Term(SPACE) + << "| Variant | " << Term(VARIANT) + << "| Winnable | " << Term(WINNABLE) + << "+------------+-------------+-------------+-------------+\n" + << "| Total | " << Term(TOTAL) + << "+------------+-------------+-------------+-------------+\n"; + + if (Eval::useNNUE && pos.nnue_applicable()) + ss << '\n' << NNUE::trace(pos) << '\n'; + + ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); + + v = pos.side_to_move() == WHITE ? v : -v; + ss << "\nClassical evaluation " << to_cp(v) << " (white side)\n"; + if (Eval::useNNUE && pos.nnue_applicable()) + { + v = NNUE::evaluate(pos, false); + v = pos.side_to_move() == WHITE ? v : -v; + ss << "NNUE evaluation " << to_cp(v) << " (white side)\n"; + } + + v = evaluate(pos); + v = pos.side_to_move() == WHITE ? v : -v; + ss << "Final evaluation " << to_cp(v) << " (white side)"; + if (Eval::useNNUE && pos.nnue_applicable()) + ss << " [with scaled NNUE, hybrid, ...]"; + ss << "\n"; + + return ss.str(); +} + +} // namespace Stockfish diff --git a/Fairy-Stockfish-master/src/evaluate.h b/Fairy-Stockfish-master/src/evaluate.h new file mode 100644 index 0000000000000000000000000000000000000000..b7b7e2345492c7f4b426ee19966bd6ed19afe701 --- /dev/null +++ b/Fairy-Stockfish-master/src/evaluate.h @@ -0,0 +1,66 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef EVALUATE_H_INCLUDED +#define EVALUATE_H_INCLUDED + +#include +#include + +#include "types.h" + +#include "variant.h" + +namespace Stockfish { + +class Position; + +namespace Eval { + + std::string trace(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-3475407dc199.nnue" + + namespace NNUE { + + std::string trace(Position& pos); + Value evaluate(const Position& pos, bool adjusted = false); + + void init(); + void verify(); + + bool load_eval(std::string name, std::istream& stream); + bool save_eval(std::ostream& stream); + bool save_eval(const std::optional& filename); + + } // namespace NNUE + +} // namespace Eval + +extern const Variant* currentNnueVariant; + +} // namespace Stockfish + +#endif // #ifndef EVALUATE_H_INCLUDED diff --git a/Fairy-Stockfish-master/src/ffishjs.cpp b/Fairy-Stockfish-master/src/ffishjs.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4c0b0f7763d24b8af2e16f8232b8fec51de16f10 --- /dev/null +++ b/Fairy-Stockfish-master/src/ffishjs.cpp @@ -0,0 +1,767 @@ +/* + ffish.js, a JavaScript chess variant library derived from Fairy-Stockfish + Copyright (C) 2022 Fabian Fichter, Johannes Czech + + ffish.js is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ffish.js is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include +#include +#include + +#include "misc.h" +#include "types.h" +#include "bitboard.h" +#include "evaluate.h" +#include "position.h" +#include "search.h" +#include "syzygy/tbprobe.h" +#include "thread.h" +#include "tt.h" +#include "uci.h" +#include "piece.h" +#include "variant.h" +#include "movegen.h" +#include "apiutil.h" + +using namespace emscripten; + +using namespace Stockfish; + +void initialize_stockfish() { + pieceMap.init(); + variants.init(); + UCI::init(Options); + Bitboards::init(); + Position::init(); + Bitbases::init(); +} + +#define DELIM " " + +inline void save_pop_back(std::string& s) { + if (s.size() != 0) { + s.pop_back(); + } +} + +const Variant* get_variant(const std::string& uciVariant) { + if (uciVariant.size() == 0 || uciVariant == "Standard" || uciVariant == "standard") + return variants.find("chess")->second; + return variants.find(uciVariant)->second; +} + +template +inline bool is_move_none(Move move, const std::string& strMove, const Position& pos) { + if (move == MOVE_NONE) { + std::cerr << "The given "; + isUCI ? std::cerr << "uciMove" : std::cerr << "sanMove"; + std::cerr << " '" << strMove << "' for position '" << pos.fen() << "' is invalid." << std::endl; + return true; + } + return false; +} + +class Board { + // note: we can't use references for strings here due to conversion to JavaScript +private: + const Variant* v; + StateListPtr states; + Position pos; + Thread* thread; + std::vector moveStack; + bool is960; + +public: + static bool sfInitialized; + + Board(): + Board("chess", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" , false) { + } + + Board(std::string uciVariant): + Board(uciVariant, "", false) { + } + + Board(std::string uciVariant, std::string fen): + Board(uciVariant, fen, false) { + } + + Board(std::string uciVariant, std::string fen, bool is960) { + init(uciVariant, fen, is960); + } + + std::string legal_moves() { + std::string moves; + for (const ExtMove& move : MoveList(this->pos)) { + moves += UCI::move(this->pos, move); + moves += DELIM; + } + save_pop_back(moves); + return moves; + } + + std::string legal_moves_san() { + std::string movesSan; + for (const ExtMove& move : MoveList(this->pos)) { + movesSan += SAN::move_to_san(this->pos, move, NOTATION_SAN); + movesSan += DELIM; + } + save_pop_back(movesSan); + return movesSan; + } + + int number_legal_moves() const { + return MoveList(pos).size(); + } + + bool push(std::string uciMove) { + const Move move = UCI::to_move(this->pos, uciMove); + if (is_move_none(move, uciMove, pos)) + return false; + do_move(move); + return true; + } + + bool push_san(std::string sanMove) { + return push_san(sanMove, NOTATION_SAN); + } + + // TODO: This is a naive implementation which compares all legal SAN moves with the requested string. + // If the SAN move wasn't found the position remains unchanged. Alternatively, implement a direct conversion. + bool push_san(std::string sanMove, Notation notation) { + Move foundMove = MOVE_NONE; + for (const ExtMove& move : MoveList(pos)) { + if (sanMove == SAN::move_to_san(this->pos, move, notation)) { + foundMove = move; + break; + } + } + if (is_move_none(foundMove, sanMove, pos)) + return false; + do_move(foundMove); + return true; + } + + void pop() { + pos.undo_move(this->moveStack.back()); + moveStack.pop_back(); + states->pop_back(); + } + + void reset() { + set_fen(v->startFen); + } + + bool is_960() const { + return is960; + } + + std::string fen() const { + return this->pos.fen(); + } + + std::string fen(bool showPromoted) const { + return this->pos.fen(false, showPromoted); + } + + std::string fen(bool showPromoted, int countStarted) const { + return this->pos.fen(false, showPromoted, countStarted); + } + + void set_fen(std::string fen) { + resetStates(); + moveStack.clear(); + pos.set(v, fen, is960, &states->back(), thread); + } + + // note: const identifier for pos not possible due to SAN::move_to_san() + std::string san_move(std::string uciMove) { + return san_move(uciMove, NOTATION_SAN); + } + + std::string san_move(std::string uciMove, Notation notation) { + const Move move = UCI::to_move(this->pos, uciMove); + if (is_move_none(move, uciMove, pos)) + return ""; + return SAN::move_to_san(this->pos, UCI::to_move(this->pos, uciMove), notation); + } + + std::string variation_san(std::string uciMoves) { + return variation_san(uciMoves, NOTATION_SAN, true); + } + + std::string variation_san(std::string uciMoves, Notation notation) { + return variation_san(uciMoves, notation, true); + } + + std::string variation_san(std::string uciMoves, Notation notation, bool moveNumbers) { + std::stringstream ss(uciMoves); + StateListPtr tempStates; + std::vector moves; + std::string variationSan = ""; + std::string uciMove; + bool first = true; + + while (std::getline(ss, uciMove, ' ')) { + const Move move = UCI::to_move(this->pos, uciMove); + if (is_move_none(move, uciMove, pos)) + return ""; + moves.emplace_back(UCI::to_move(this->pos, uciMove)); + if (first) { + first = false; + if (moveNumbers) { + variationSan = std::to_string(fullmove_number()); + if (pos.side_to_move() == WHITE) + variationSan += ". "; + else + variationSan += "..."; + } + variationSan += SAN::move_to_san(this->pos, moves.back(), Notation(notation)); + } + else { + if (moveNumbers && pos.side_to_move() == WHITE) { + variationSan += DELIM; + variationSan += std::to_string(fullmove_number()); + variationSan += "."; + } + variationSan += DELIM; + variationSan += SAN::move_to_san(this->pos, moves.back(), Notation(notation)); + } + states->emplace_back(); + pos.do_move(moves.back(), states->back()); + } + + // recover initial state + for(auto rIt = std::rbegin(moves); rIt != std::rend(moves); ++rIt) { + pos.undo_move(*rIt); + } + + return variationSan; + } + + // returns true for WHITE and false for BLACK + bool turn() const { + return !pos.side_to_move(); + } + + int fullmove_number() const { + return pos.game_ply() / 2 + 1; + } + + int halfmove_clock() const { + return pos.rule50_count(); + } + + int game_ply() const { + return pos.game_ply(); + } + + bool has_insufficient_material(bool turn) const { + return Stockfish::has_insufficient_material(turn ? WHITE : BLACK, pos); + } + + bool is_insufficient_material() const { + return Stockfish::has_insufficient_material(WHITE, pos) && Stockfish::has_insufficient_material(BLACK, pos); + } + + bool is_game_over() const { + return is_game_over(false); + } + + bool is_game_over(bool claim_draw) const { + if (is_insufficient_material()) + return true; + if (claim_draw && pos.is_optional_game_end()) + return true; + return MoveList(pos).size() == 0; + } + + std::string result() const { + return result(false); + } + + std::string result(bool claim_draw) const { + Value result; + bool gameEnd = pos.is_immediate_game_end(result); + if (!gameEnd) { + if (is_insufficient_material()) { + gameEnd = true; + result = VALUE_DRAW; + } + } + if (!gameEnd && MoveList(pos).size() == 0) { + gameEnd = true; + result = pos.checkers() ? pos.checkmate_value() : pos.stalemate_value(); + } + if (!gameEnd && claim_draw) + gameEnd = pos.is_optional_game_end(result); + + if (!gameEnd) + return "*"; + if (result == 0) + return "1/2-1/2"; + if (pos.side_to_move() == BLACK) + result = -result; + if (result > 0) + return "1-0"; + else + return "0-1"; + } + + std::string checked_pieces() const { + Bitboard checked = Stockfish::checked(pos); + std::string squares; + while (checked) { + Square sr = pop_lsb(checked); + squares += UCI::square(pos, sr); + squares += DELIM; + } + save_pop_back(squares); + return squares; + } + + bool is_check() const { + return Stockfish::checked(pos); + } + + bool is_bikjang() const { + return pos.bikjang(); + } + + bool is_capture(std::string uciMove) const { + return pos.capture(UCI::to_move(pos, uciMove)); + } + + std::string move_stack() const { + std::string moves; + for(auto it = std::begin(moveStack); it != std::end(moveStack); ++it) { + moves += UCI::move(pos, *it); + moves += DELIM; + } + save_pop_back(moves); + return moves; + } + + void push_moves(std::string uciMoves) { + std::stringstream ss(uciMoves); + std::string uciMove; + while (std::getline(ss, uciMove, ' ')) { + push(uciMove); + } + } + + void push_san_moves(std::string sanMoves) { + return push_san_moves(sanMoves, NOTATION_SAN); + } + + void push_san_moves(std::string sanMoves, Notation notation) { + std::stringstream ss(sanMoves); + std::string sanMove; + while (std::getline(ss, sanMove, ' ')) + push_san(sanMove, notation); + } + + std::string pocket(bool color) { + const Color c = Color(!color); + std::string pocket; + for (PieceType pt = KING; pt >= PAWN; --pt) { + for (int i = 0; i < pos.count_in_hand(c, pt); ++i) { + // only create BLACK pieces in order to convert to lower case + pocket += std::string(1, pos.piece_to_char()[make_piece(BLACK, pt)]); + } + } + return pocket; + } + + std::string to_string() { + std::string stringBoard; + for (Rank r = pos.max_rank(); r >= RANK_1; --r) { + for (File f = FILE_A; f <= pos.max_file(); ++f) { + if (f != FILE_A) + stringBoard += " "; + const Piece p = pos.piece_on(make_square(f, r)); + switch(p) { + case NO_PIECE: + stringBoard += '.'; + break; + default: + stringBoard += pos.piece_to_char()[p]; + } + } + if (r != RANK_1) + stringBoard += "\n"; + } + return stringBoard; + } + + std::string to_verbose_string() { + std::stringstream ss; + operator<<(ss, pos); + return ss.str(); + } + + std::string variant() { + // Iterate through the variants map + for (auto it = variants.begin(); it != variants.end(); ++it) + if (it->second == v) + return it->first; + + std::cerr << "Current variant is not registered." << std::endl; + return "unknown"; + } + +private: + void resetStates() { + this->states = StateListPtr(new std::deque(1)); + } + + void do_move(Move move) { + states->emplace_back(); + this->pos.do_move(move, states->back()); + this->moveStack.emplace_back(move); + } + + void init(std::string uciVariant, std::string fen, bool is960) { + if (!Board::sfInitialized) { + initialize_stockfish(); + Board::sfInitialized = true; + } + v = get_variant(uciVariant); + UCI::init_variant(v); + this->resetStates(); + if (fen == "") + fen = v->startFen; + this->pos.set(this->v, fen, is960, &this->states->back(), this->thread); + this->is960 = is960; + } +}; + +bool Board::sfInitialized = false; + +namespace ffish { + // returns the version of the Fairy-Stockfish binary + std::string info() { + return engine_info(); + } + + template + void set_option(std::string name, T value) { + Options[name] = value; + Board::sfInitialized = false; + } + + std::string available_variants() { + std::string availableVariants; + for (std::string variant : variants.get_keys()) { + availableVariants += variant; + availableVariants += DELIM; + } + save_pop_back(availableVariants); + return availableVariants; + } + + void load_variant_config(std::string variantInitContent) { + std::stringstream ss(variantInitContent); + if (!Board::sfInitialized) + initialize_stockfish(); + variants.parse_istream(ss); + Options["UCI_Variant"].set_combo(variants.get_keys()); + Board::sfInitialized = true; + } + + bool captures_to_hand(std::string uciVariant) { + const Variant* v = get_variant(uciVariant); + return v->capturesToHand; + } + + std::string starting_fen(std::string uciVariant) { + const Variant* v = get_variant(uciVariant); + return v->startFen; + } + + int validate_fen(std::string fen, std::string uciVariant, bool chess960) { + const Variant* v = get_variant(uciVariant); + return FEN::validate_fen(fen, v, chess960); + } + + int validate_fen(std::string fen, std::string uciVariant) { + return validate_fen(fen, uciVariant, false); + } + + int validate_fen(std::string fen) { + return validate_fen(fen, "chess"); + } +} + +class Game { +private: + std::unordered_map header; + std::unique_ptr board; + std::string variant = "chess"; + std::string fen = ""; // start pos + bool is960 = false; + bool parsedGame = false; +public: + std::string header_keys() { + std::string keys; + for (auto it = header.begin(); it != header.end(); ++it) { + keys += it->first; + keys += DELIM; + } + save_pop_back(keys); + return keys; + } + + std::string headers(std::string item) { + auto it = header.find(item); + if (it == header.end()) + return ""; + return it->second; + } + + std::string mainline_moves() { + if (!parsedGame) + return ""; + return board->move_stack(); + } + + friend Game read_game_pgn(std::string); +}; + + +bool skip_comment(const std::string& pgn, size_t& curIdx, size_t& lineEnd) { + curIdx = pgn.find('}', curIdx); + if (curIdx == std::string::npos) { + std::cerr << "Missing '}' for move comment while reading pgn." << std::endl; + return false; + } + if (curIdx > lineEnd) + lineEnd = pgn.find('\n', curIdx); + return true; +} + +Game read_game_pgn(std::string pgn) { + Game game; + size_t lineStart = 0; + bool headersParsed = false; + + while(true) { + size_t lineEnd = pgn.find('\n', lineStart); + + if (lineEnd == std::string::npos) + lineEnd = pgn.size(); + + if (!headersParsed && pgn[lineStart] == '[') { + // parse header + // look for item + size_t headerKeyStart = lineStart+1; + size_t headerKeyEnd = pgn.find(' ', lineStart); + size_t headerItemStart = pgn.find('"', headerKeyEnd)+1; + size_t headerItemEnd = pgn.find('"', headerItemStart); + + // put item into list + game.header[pgn.substr(headerKeyStart, headerKeyEnd-headerKeyStart)] = pgn.substr(headerItemStart, headerItemEnd-headerItemStart); + } + else { + if (!headersParsed) { + headersParsed = true; + auto it = game.header.find("Variant"); + if (it != game.header.end()) { + game.is960 = it->second.find("960", it->second.size() - 3) != std::string::npos; + if (game.is960) { + game.variant = it->second.substr(0, it->second.size() - 3); + } else { + game.variant = it->second; + } + std::transform(game.variant.begin(), game.variant.end(), game.variant.begin(), + [](unsigned char c){ return std::tolower(c); }); + } + + it = game.header.find("FEN"); + if (it != game.header.end()) + game.fen = it->second; + + game.board = std::make_unique(game.variant, game.fen, game.is960); + game.parsedGame = true; + } + + // game line + size_t curIdx = lineStart; + while (curIdx <= lineEnd) { + if (pgn[curIdx] == '*') + return game; + + if (pgn[curIdx] == '{') { + if (!skip_comment(pgn, curIdx, lineEnd)) + return game; + ++curIdx; + } + + // Movetext RAV (Recursive Annotation Variation) + size_t openedRAV = 0; + if (pgn[curIdx] == '(') { + openedRAV = 1; + ++curIdx; + } + while (openedRAV != 0) { + switch (pgn[curIdx]) { + case '(': + ++openedRAV; + break; + case ')': + --openedRAV; + break; + case '{': + if (!skip_comment(pgn, curIdx, lineEnd)) + return game; + default: ; // pass + } + ++curIdx; + if (curIdx > lineEnd) + lineEnd = pgn.find('\n', curIdx); + } + + if (pgn[curIdx] == '$') { + // we are at a glyph + curIdx = pgn.find(' ', curIdx); + } + + if (pgn[curIdx] >= '0' && pgn[curIdx] <= '9') { + // we are at a move number -> look for next point + curIdx = pgn.find('.', curIdx); + if (curIdx == std::string::npos) + break; + ++curIdx; + // increment if we're at a space + while (curIdx < pgn.size() && pgn[curIdx] == ' ') + ++curIdx; + // increment if we're at a point + while (curIdx < pgn.size() && pgn[curIdx] == '.') + ++curIdx; + } + // extract sanMove + size_t sanMoveEnd = std::min(pgn.find(' ', curIdx), lineEnd); + if (sanMoveEnd > curIdx) { + std::string sanMove = pgn.substr(curIdx, sanMoveEnd-curIdx); + // clean possible ? and ! from string + size_t annotationChar1 = sanMove.find('?'); + size_t annotationChar2 = sanMove.find('!'); + if (annotationChar1 != std::string::npos || annotationChar2 != std::string::npos) + sanMove = sanMove.substr(0, std::min(annotationChar1, annotationChar2)); + std::cout << sanMove << " "; + game.board->push_san(sanMove); + } + curIdx = sanMoveEnd+1; + } + } + lineStart = lineEnd+1; + + if (lineStart >= pgn.size()) + return game; + } + return game; +} + + +// binding code +EMSCRIPTEN_BINDINGS(ffish_js) { + class_("Board") + .constructor<>() + .constructor() + .constructor() + .constructor() + .function("legalMoves", &Board::legal_moves) + .function("legalMovesSan", &Board::legal_moves_san) + .function("numberLegalMoves", &Board::number_legal_moves) + .function("push", &Board::push) + .function("pushSan", select_overload(&Board::push_san)) + .function("pushSan", select_overload(&Board::push_san)) + .function("pop", &Board::pop) + .function("reset", &Board::reset) + .function("is960", &Board::is_960) + .function("fen", select_overload(&Board::fen)) + .function("fen", select_overload(&Board::fen)) + .function("fen", select_overload(&Board::fen)) + .function("setFen", &Board::set_fen) + .function("sanMove", select_overload(&Board::san_move)) + .function("sanMove", select_overload(&Board::san_move)) + .function("variationSan", select_overload(&Board::variation_san)) + .function("variationSan", select_overload(&Board::variation_san)) + .function("variationSan", select_overload(&Board::variation_san)) + .function("turn", &Board::turn) + .function("fullmoveNumber", &Board::fullmove_number) + .function("halfmoveClock", &Board::halfmove_clock) + .function("gamePly", &Board::game_ply) + .function("hasInsufficientMaterial", &Board::has_insufficient_material) + .function("isInsufficientMaterial", &Board::is_insufficient_material) + .function("isGameOver", select_overload(&Board::is_game_over)) + .function("isGameOver", select_overload(&Board::is_game_over)) + .function("result", select_overload(&Board::result)) + .function("result", select_overload(&Board::result)) + .function("checkedPieces", &Board::checked_pieces) + .function("isCheck", &Board::is_check) + .function("isBikjang", &Board::is_bikjang) + .function("isCapture", &Board::is_capture) + .function("moveStack", &Board::move_stack) + .function("pushMoves", &Board::push_moves) + .function("pushSanMoves", select_overload(&Board::push_san_moves)) + .function("pushSanMoves", select_overload(&Board::push_san_moves)) + .function("pocket", &Board::pocket) + .function("toString", &Board::to_string) + .function("toVerboseString", &Board::to_verbose_string) + .function("variant", &Board::variant); + class_("Game") + .function("headerKeys", &Game::header_keys) + .function("headers", &Game::headers) + .function("mainlineMoves", &Game::mainline_moves); + // usage: e.g. ffish.Notation.DEFAULT + enum_("Notation") + .value("DEFAULT", NOTATION_DEFAULT) + .value("SAN", NOTATION_SAN) + .value("LAN", NOTATION_LAN) + .value("SHOGI_HOSKING", NOTATION_SHOGI_HOSKING) + .value("SHOGI_HODGES", NOTATION_SHOGI_HODGES) + .value("SHOGI_HODGES_NUMBER", NOTATION_SHOGI_HODGES_NUMBER) + .value("JANGGI", NOTATION_JANGGI) + .value("XIANGQI_WXF", NOTATION_XIANGQI_WXF) + .value("THAI_SAN", NOTATION_THAI_SAN) + .value("THAI_LAN", NOTATION_THAI_LAN); + // usage: e.g. ffish.Termination.CHECKMATE + enum_("Termination") + .value("ONGOING", ONGOING) + .value("CHECKMATE", CHECKMATE) + .value("STALEMATE", STALEMATE) + .value("INSUFFICIENT_MATERIAL", INSUFFICIENT_MATERIAL) + .value("N_MOVE_RULE", N_MOVE_RULE) + .value("N_FOLD_REPETITION", N_FOLD_REPETITION) + .value("VARIANT_END", VARIANT_END); + function("info", &ffish::info); + function("setOption", &ffish::set_option); + function("setOptionInt", &ffish::set_option); + function("setOptionBool", &ffish::set_option); + function("readGamePGN", &read_game_pgn); + function("variants", &ffish::available_variants); + function("loadVariantConfig", &ffish::load_variant_config); + function("capturesToHand", &ffish::captures_to_hand); + function("startingFen", &ffish::starting_fen); + function("validateFen", select_overload(&ffish::validate_fen)); + function("validateFen", select_overload(&ffish::validate_fen)); + function("validateFen", select_overload(&ffish::validate_fen)); + // TODO: enable to string conversion method + // .class_function("getStringFromInstance", &Board::get_string_from_instance); +} diff --git a/Fairy-Stockfish-master/src/incbin/UNLICENCE b/Fairy-Stockfish-master/src/incbin/UNLICENCE new file mode 100644 index 0000000000000000000000000000000000000000..32484ab5e7026f9a1f15c2f8c08b1418802e02a8 --- /dev/null +++ b/Fairy-Stockfish-master/src/incbin/UNLICENCE @@ -0,0 +1,26 @@ +The file "incbin.h" is free and unencumbered software released into +the public domain by Dale Weiler, see: + + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/Fairy-Stockfish-master/src/incbin/incbin.h b/Fairy-Stockfish-master/src/incbin/incbin.h new file mode 100644 index 0000000000000000000000000000000000000000..c19684d72420ed0b326dd857fbe39d100713e746 --- /dev/null +++ b/Fairy-Stockfish-master/src/incbin/incbin.h @@ -0,0 +1,368 @@ +/** + * @file incbin.h + * @author Dale Weiler + * @brief Utility for including binary files + * + * Facilities for including binary files into the current translation unit and + * making use from them externally in other translation units. + */ +#ifndef INCBIN_HDR +#define INCBIN_HDR +#include +#if defined(__AVX512BW__) || \ + defined(__AVX512CD__) || \ + defined(__AVX512DQ__) || \ + defined(__AVX512ER__) || \ + defined(__AVX512PF__) || \ + defined(__AVX512VL__) || \ + defined(__AVX512F__) +# define INCBIN_ALIGNMENT_INDEX 6 +#elif defined(__AVX__) || \ + defined(__AVX2__) +# define INCBIN_ALIGNMENT_INDEX 5 +#elif defined(__SSE__) || \ + defined(__SSE2__) || \ + defined(__SSE3__) || \ + defined(__SSSE3__) || \ + defined(__SSE4_1__) || \ + defined(__SSE4_2__) || \ + defined(__neon__) +# define INCBIN_ALIGNMENT_INDEX 4 +#elif ULONG_MAX != 0xffffffffu +# define INCBIN_ALIGNMENT_INDEX 3 +# else +# define INCBIN_ALIGNMENT_INDEX 2 +#endif + +/* Lookup table of (1 << n) where `n' is `INCBIN_ALIGNMENT_INDEX' */ +#define INCBIN_ALIGN_SHIFT_0 1 +#define INCBIN_ALIGN_SHIFT_1 2 +#define INCBIN_ALIGN_SHIFT_2 4 +#define INCBIN_ALIGN_SHIFT_3 8 +#define INCBIN_ALIGN_SHIFT_4 16 +#define INCBIN_ALIGN_SHIFT_5 32 +#define INCBIN_ALIGN_SHIFT_6 64 + +/* Actual alignment value */ +#define INCBIN_ALIGNMENT \ + INCBIN_CONCATENATE( \ + INCBIN_CONCATENATE(INCBIN_ALIGN_SHIFT, _), \ + INCBIN_ALIGNMENT_INDEX) + +/* Stringize */ +#define INCBIN_STR(X) \ + #X +#define INCBIN_STRINGIZE(X) \ + INCBIN_STR(X) +/* Concatenate */ +#define INCBIN_CAT(X, Y) \ + X ## Y +#define INCBIN_CONCATENATE(X, Y) \ + INCBIN_CAT(X, Y) +/* Deferred macro expansion */ +#define INCBIN_EVAL(X) \ + X +#define INCBIN_INVOKE(N, ...) \ + INCBIN_EVAL(N(__VA_ARGS__)) + +/* Green Hills uses a different directive for including binary data */ +#if defined(__ghs__) +# if (__ghs_asm == 2) +# define INCBIN_MACRO ".file" +/* Or consider the ".myrawdata" entry in the ld file */ +# else +# define INCBIN_MACRO "\tINCBIN" +# endif +#else +# define INCBIN_MACRO ".incbin" +#endif + +#ifndef _MSC_VER +# define INCBIN_ALIGN \ + __attribute__((aligned(INCBIN_ALIGNMENT))) +#else +# define INCBIN_ALIGN __declspec(align(INCBIN_ALIGNMENT)) +#endif + +#if defined(__arm__) || /* GNU C and RealView */ \ + defined(__arm) || /* Diab */ \ + defined(_ARM) /* ImageCraft */ +# define INCBIN_ARM +#endif + +#ifdef __GNUC__ +/* Utilize .balign where supported */ +# define INCBIN_ALIGN_HOST ".balign " INCBIN_STRINGIZE(INCBIN_ALIGNMENT) "\n" +# define INCBIN_ALIGN_BYTE ".balign 1\n" +#elif defined(INCBIN_ARM) +/* + * On arm assemblers, the alignment value is calculated as (1 << n) where `n' is + * the shift count. This is the value passed to `.align' + */ +# define INCBIN_ALIGN_HOST ".align " INCBIN_STRINGIZE(INCBIN_ALIGNMENT_INDEX) "\n" +# define INCBIN_ALIGN_BYTE ".align 0\n" +#else +/* We assume other inline assembler's treat `.align' as `.balign' */ +# define INCBIN_ALIGN_HOST ".align " INCBIN_STRINGIZE(INCBIN_ALIGNMENT) "\n" +# define INCBIN_ALIGN_BYTE ".align 1\n" +#endif + +/* INCBIN_CONST is used by incbin.c generated files */ +#if defined(__cplusplus) +# define INCBIN_EXTERNAL extern "C" +# define INCBIN_CONST extern const +#else +# define INCBIN_EXTERNAL extern +# define INCBIN_CONST const +#endif + +/** + * @brief Optionally override the linker section into which data is emitted. + * + * @warning If you use this facility, you'll have to deal with platform-specific linker output + * section naming on your own + * + * Overriding the default linker output section, e.g for esp8266/Arduino: + * @code + * #define INCBIN_OUTPUT_SECTION ".irom.text" + * #include "incbin.h" + * INCBIN(Foo, "foo.txt"); + * // Data is emitted into program memory that never gets copied to RAM + * @endcode + */ +#if !defined(INCBIN_OUTPUT_SECTION) +# if defined(__APPLE__) +# define INCBIN_OUTPUT_SECTION ".const_data" +# else +# define INCBIN_OUTPUT_SECTION ".rodata" +# endif +#endif + +#if defined(__APPLE__) +/* The directives are different for Apple branded compilers */ +# define INCBIN_SECTION INCBIN_OUTPUT_SECTION "\n" +# define INCBIN_GLOBAL(NAME) ".globl " INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME "\n" +# define INCBIN_INT ".long " +# define INCBIN_MANGLE "_" +# define INCBIN_BYTE ".byte " +# define INCBIN_TYPE(...) +#else +# define INCBIN_SECTION ".section " INCBIN_OUTPUT_SECTION "\n" +# define INCBIN_GLOBAL(NAME) ".global " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME "\n" +# if defined(__ghs__) +# define INCBIN_INT ".word " +# else +# define INCBIN_INT ".int " +# endif +# if defined(__USER_LABEL_PREFIX__) +# define INCBIN_MANGLE INCBIN_STRINGIZE(__USER_LABEL_PREFIX__) +# else +# define INCBIN_MANGLE "" +# endif +# if defined(INCBIN_ARM) +/* On arm assemblers, `@' is used as a line comment token */ +# define INCBIN_TYPE(NAME) ".type " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME ", %object\n" +# elif defined(__MINGW32__) || defined(__MINGW64__) +/* Mingw doesn't support this directive either */ +# define INCBIN_TYPE(NAME) +# else +/* It's safe to use `@' on other architectures */ +# define INCBIN_TYPE(NAME) ".type " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME ", @object\n" +# endif +# define INCBIN_BYTE ".byte " +#endif + +/* List of style types used for symbol names */ +#define INCBIN_STYLE_CAMEL 0 +#define INCBIN_STYLE_SNAKE 1 + +/** + * @brief Specify the prefix to use for symbol names. + * + * By default this is `g', producing symbols of the form: + * @code + * #include "incbin.h" + * INCBIN(Foo, "foo.txt"); + * + * // Now you have the following symbols: + * // const unsigned char gFooData[]; + * // const unsigned char *const gFooEnd; + * // const unsigned int gFooSize; + * @endcode + * + * If however you specify a prefix before including: e.g: + * @code + * #define INCBIN_PREFIX incbin + * #include "incbin.h" + * INCBIN(Foo, "foo.txt"); + * + * // Now you have the following symbols instead: + * // const unsigned char incbinFooData[]; + * // const unsigned char *const incbinFooEnd; + * // const unsigned int incbinFooSize; + * @endcode + */ +#if !defined(INCBIN_PREFIX) +# define INCBIN_PREFIX g +#endif + +/** + * @brief Specify the style used for symbol names. + * + * Possible options are + * - INCBIN_STYLE_CAMEL "CamelCase" + * - INCBIN_STYLE_SNAKE "snake_case" + * + * Default option is *INCBIN_STYLE_CAMEL* producing symbols of the form: + * @code + * #include "incbin.h" + * INCBIN(Foo, "foo.txt"); + * + * // Now you have the following symbols: + * // const unsigned char FooData[]; + * // const unsigned char *const FooEnd; + * // const unsigned int FooSize; + * @endcode + * + * If however you specify a style before including: e.g: + * @code + * #define INCBIN_STYLE INCBIN_STYLE_SNAKE + * #include "incbin.h" + * INCBIN(foo, "foo.txt"); + * + * // Now you have the following symbols: + * // const unsigned char foo_data[]; + * // const unsigned char *const foo_end; + * // const unsigned int foo_size; + * @endcode + */ +#if !defined(INCBIN_STYLE) +# define INCBIN_STYLE INCBIN_STYLE_CAMEL +#endif + +/* Style lookup tables */ +#define INCBIN_STYLE_0_DATA Data +#define INCBIN_STYLE_0_END End +#define INCBIN_STYLE_0_SIZE Size +#define INCBIN_STYLE_1_DATA _data +#define INCBIN_STYLE_1_END _end +#define INCBIN_STYLE_1_SIZE _size + +/* Style lookup: returning identifier */ +#define INCBIN_STYLE_IDENT(TYPE) \ + INCBIN_CONCATENATE( \ + INCBIN_STYLE_, \ + INCBIN_CONCATENATE( \ + INCBIN_EVAL(INCBIN_STYLE), \ + INCBIN_CONCATENATE(_, TYPE))) + +/* Style lookup: returning string literal */ +#define INCBIN_STYLE_STRING(TYPE) \ + INCBIN_STRINGIZE( \ + INCBIN_STYLE_IDENT(TYPE)) \ + +/* Generate the global labels by indirectly invoking the macro with our style + * type and concatenating the name against them. */ +#define INCBIN_GLOBAL_LABELS(NAME, TYPE) \ + INCBIN_INVOKE( \ + INCBIN_GLOBAL, \ + INCBIN_CONCATENATE( \ + NAME, \ + INCBIN_INVOKE( \ + INCBIN_STYLE_IDENT, \ + TYPE))) \ + INCBIN_INVOKE( \ + INCBIN_TYPE, \ + INCBIN_CONCATENATE( \ + NAME, \ + INCBIN_INVOKE( \ + INCBIN_STYLE_IDENT, \ + TYPE))) + +/** + * @brief Externally reference binary data included in another translation unit. + * + * Produces three external symbols that reference the binary data included in + * another translation unit. + * + * The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with + * "Data", as well as "End" and "Size" after. An example is provided below. + * + * @param NAME The name given for the binary data + * + * @code + * INCBIN_EXTERN(Foo); + * + * // Now you have the following symbols: + * // extern const unsigned char FooData[]; + * // extern const unsigned char *const FooEnd; + * // extern const unsigned int FooSize; + * @endcode + */ +#define INCBIN_EXTERN(NAME) \ + INCBIN_EXTERNAL const INCBIN_ALIGN unsigned char \ + INCBIN_CONCATENATE( \ + INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \ + INCBIN_STYLE_IDENT(DATA))[]; \ + INCBIN_EXTERNAL const INCBIN_ALIGN unsigned char *const \ + INCBIN_CONCATENATE( \ + INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \ + INCBIN_STYLE_IDENT(END)); \ + INCBIN_EXTERNAL const unsigned int \ + INCBIN_CONCATENATE( \ + INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \ + INCBIN_STYLE_IDENT(SIZE)) + +/** + * @brief Include a binary file into the current translation unit. + * + * Includes a binary file into the current translation unit, producing three symbols + * for objects that encode the data and size respectively. + * + * The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with + * "Data", as well as "End" and "Size" after. An example is provided below. + * + * @param NAME The name to associate with this binary data (as an identifier.) + * @param FILENAME The file to include (as a string literal.) + * + * @code + * INCBIN(Icon, "icon.png"); + * + * // Now you have the following symbols: + * // const unsigned char IconData[]; + * // const unsigned char *const IconEnd; + * // const unsigned int IconSize; + * @endcode + * + * @warning This must be used in global scope + * @warning The identifiers may be different if INCBIN_STYLE is not default + * + * To externally reference the data included by this in another translation unit + * please @see INCBIN_EXTERN. + */ +#ifdef _MSC_VER +#define INCBIN(NAME, FILENAME) \ + INCBIN_EXTERN(NAME) +#else +#define INCBIN(NAME, FILENAME) \ + __asm__(INCBIN_SECTION \ + INCBIN_GLOBAL_LABELS(NAME, DATA) \ + INCBIN_ALIGN_HOST \ + INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(DATA) ":\n" \ + INCBIN_MACRO " \"" FILENAME "\"\n" \ + INCBIN_GLOBAL_LABELS(NAME, END) \ + INCBIN_ALIGN_BYTE \ + INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(END) ":\n" \ + INCBIN_BYTE "1\n" \ + INCBIN_GLOBAL_LABELS(NAME, SIZE) \ + INCBIN_ALIGN_HOST \ + INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(SIZE) ":\n" \ + INCBIN_INT INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(END) " - " \ + INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(DATA) "\n" \ + INCBIN_ALIGN_HOST \ + ".text\n" \ + ); \ + INCBIN_EXTERN(NAME) + +#endif +#endif diff --git a/Fairy-Stockfish-master/src/magic.h b/Fairy-Stockfish-master/src/magic.h new file mode 100644 index 0000000000000000000000000000000000000000..ff1453d860e00076212b9d4941543e184bcfbb07 --- /dev/null +++ b/Fairy-Stockfish-master/src/magic.h @@ -0,0 +1,1741 @@ +/* + Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish + Copyright (C) 2018-2022 Fabian Fichter + + Fairy-Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Fairy-Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef MAGIC_H_INCLUDED +#define MAGIC_H_INCLUDED + +namespace Stockfish { + +#ifdef PRECOMPUTED_MAGICS +#define B(a, b) (Bitboard(a) << 64) ^ Bitboard(b) + // Use precomputed magics if pext is not available, + // since the magics generation is very slow. + constexpr Bitboard RookMagicHInit[SQUARE_NB] = { + B(0x120000880110000, 0x1008000000020020), + B(0x24200C080840A052, 0x2004004000010008), + B(0xC030024000228800, 0x4000010400000020), + B(0x1A0020802008802, 0x206010208000), + B(0x12002000D001024, 0x80100800090138), + B(0x4220010000241010, 0x3098000602001500), + B(0x401010004801040, 0x8000280480100000), + B(0x820082024921836, 0x220028000), + B(0x100400502411400, 0x220402120240D14), + B(0x880202020010404, 0xA80202510000), + B(0x140002801000018, 0x1000346490040), + B(0x120000880110000, 0x1008000000020020), + B(0xD01004008030400, 0x104000408104420), + B(0x8420060100020000, 0x800280400000120), + B(0x4010020018010, 0x40A00001100000), + B(0x40006A0004000200, 0x40000000110), + B(0xD01004008030400, 0x104000408104420), + B(0x8908A20028110011, 0x800080000001A114), + B(0x200042000080F009, 0x20001000004000), + B(0x2820008820100, 0x10002400058000B9), + B(0x6083100420008050, 0x4040012600280080), + B(0x216020000000446, 0x4080204000000211), + B(0x340140003002089, 0x2402008000000911), + B(0xD01004008030400, 0x104000408104420), + B(0x1404040B20001000, 0x8000824010800011), + B(0x8C0488120024214, 0x8414880202291), + B(0x1010000060050000, 0x4000004050002602), + B(0x4022983A0060000, 0x80000040010400), + B(0x1404040B20001000, 0x8000824010800011), + B(0x6020101802002840, 0x31000003000004), + B(0x9000420008840, 0x4881300000000210), + B(0xA200808865, 0x41C0048023000128), + B(0x31801100400000, 0x8802DC001221240), + B(0x884000080200920, 0x1004002410401001), + B(0x2400040000884, 0x421006208040C0), + B(0x1404040B20001000, 0x8000824010800011), + B(0x24100400060009, 0x112008025042410), + B(0x1800040009040200, 0x180000A1004E408A), + B(0x24100400060009, 0x112008025042410), + B(0x4060402008080, 0xC240080000110000), + B(0x20080100920020, 0x2002248010242052), + B(0x10001010802050, 0x880000001C98420), + B(0x4000800100420022, 0x502022010A00D0), + B(0x4C18104500200885, 0x400880800), + B(0x8080810081020090, 0x8000000000000), + B(0x8000062812080201, 0x8004C8300800), + B(0xC010220920198, 0x85000A08000), + B(0x24100400060009, 0x112008025042410), + B(0x80102204040, 0x1000000900000000), + B(0x2080000004202804, 0x120880003461), + B(0x102004090A4030, 0x801020589240), + B(0x20001100814000A0, 0x420202000820004), + B(0x100800000A000120, 0x208000800010000), + B(0x1008205000040802, 0x80002000400040), + B(0x1480000098008401, 0xA0010000581010), + B(0x30C0008200100820, 0x102800080904834), + B(0x4810821884000500, 0x4400000200000212), + B(0x1811D00128A0180, 0x2500848803000000), + B(0x41618A0300040040, 0x21200200A421801), + B(0x80102204040, 0x1000000900000000), + B(0xA1808E0100108000, 0x2008000505000002), + B(0x8C890020410000A0, 0xA010000048000400), + B(0x40006002210044, 0x600008000408000), + B(0x1200447220090042, 0x80001000160012), + B(0x48410010AB000000, 0x9200600000000100), + B(0x2040000000240003, 0x8020080288000600), + B(0x9080000088848088, 0x4010210500000041), + B(0xA1808E0100108000, 0x2008000505000002), + B(0x480100400024, 0x1004800018200000), + B(0x808403080080200, 0x802601000000500), + B(0x8C890020410000A0, 0xA010000048000400), + B(0xA1808E0100108000, 0x2008000505000002), + B(0x100A40000004008, 0x2800200400200480), + B(0x100A40000004008, 0x2800200400200480), + B(0x400014006000000, 0x10006000810001F5), + B(0xC410062001414, 0x820080041B01044), + B(0x20000800310, 0x430040000201000), + B(0xA40010008000008, 0x4002200028000040), + B(0xC00102000008021C, 0x10C2000A010E024), + B(0x80004200104008, 0x50A00800C400020), + B(0x20200080012542, 0x910F0040000402C0), + B(0xB040100504000300, 0x24802002000040), + B(0x800001000014008, 0x400031004000), + B(0x100A40000004008, 0x2800200400200480), + B(0x84008002041081C0, 0x8080500200000000), + B(0x440090001012001, 0x4020004010), + B(0x100A0028088020, 0x80040E00010020), + B(0x2180808000810, 0xB018040A00040000), + B(0x40C80920304C4001, 0x42800B200800000), + B(0x85000425001000, 0x4810048020001100), + B(0x600C000801000004, 0x8015084010200020), + B(0x20020050000240C0, 0x100202008600800), + B(0x38000050001220, 0x9200010200145900), + B(0x1042108040005, 0x1402A0802201001), + B(0x824240000C20400, 0x1000000400080010), + B(0x84008002041081C0, 0x8080500200000000), + B(0x400804A1000008, 0x1024104A0200010), + B(0x8000402308483, 0x20006020100100), + B(0x80880120000080, 0x8000240100084), + B(0x5840020004882001, 0x1004528000A00010), + B(0x8001018800300002, 0x84010040804), + B(0x180D10004000A008, 0xA001080008020004), + B(0x400080B, 0x10A0000004010000), + B(0x8080000200000, 0x2001000082004E0), + B(0x40040001000C2000, 0x2024800001004008), + B(0x400804A1000008, 0x1024104A0200010), + B(0x8000402308483, 0x20006020100100), + B(0x400804A1000008, 0x1024104A0200010), + B(0x2000200000, 0x1201011000802), + B(0x100100000000C4, 0x208004084048201), + B(0x400084000044, 0x100810140300), + B(0x29040C0C01010, 0x300204010820080), + B(0x1A808000020200, 0x1000000005210040), + B(0x20000400150000, 0x85008020), + B(0x40C040008184014, 0x8002AA00024010), + B(0x202000081B00804, 0x10001002008), + B(0x40011000210060, 0x6080C40000021004), + B(0x2000200000, 0x1201011000802), + B(0x4100480203840, 0x300080100804), + B(0x2000200000, 0x1201011000802), + }; + constexpr Bitboard RookMagicVInit[SQUARE_NB] = { + B(0x202000812104400, 0x24800B01C0000303), + B(0x340020400010D, 0x88060150C00400), + B(0x400802040609, 0x49010200501A0002), + B(0x8002680301000208, 0x628006C0C020200), + B(0x20400209001C0804, 0xA044000800143110), + B(0xC400082060010202, 0x4000480401014000), + B(0x22500200144040, 0x8204820084704C00), + B(0x8C1204009030020, 0x328400805000000), + B(0x84800800D0001640, 0x200080040060108), + B(0x804810208020040, 0x140010108020000), + B(0x1102010B008004, 0x300208006220020), + B(0x140080404A0A2428, 0x6308010100080), + B(0x20444002120408, 0xA080010508010001), + B(0x82011044000D02, 0x4112028620110809), + B(0x81010831000C02, 0x408802085000000), + B(0x81010831000C02, 0x408802085000000), + B(0x920008920600040, 0x8053801004000028), + B(0x81140283208300, 0x10040C004200420), + B(0x103080022201, 0xC01000081312620), + B(0x2200221100008, 0x1000408104000A4), + B(0x4402088080042008, 0x210401501040340), + B(0x898400202170001, 0x80040404208000), + B(0x20080004051012, 0x5100048200081800), + B(0x2320020000401018, 0x108501021040210), + B(0x21080410A422021, 0x83040180008800), + B(0x44E8100000408224, 0x20010008040400), + B(0x1800240002810405, 0x23004820000020), + B(0x80A0100400110, 0x80104020100C4028), + B(0x1002050001222C0, 0x5100818004024020), + B(0x104000200040, 0xC010A09800102000), + B(0x1020003A058120, 0x450900809000302), + B(0x40040045008B1, 0x202800400383010), + B(0x4640200220034, 0x8800485420304000), + B(0x5001042100084288, 0x110820001240080A), + B(0x2002C04004010120, 0xA15008020880001), + B(0x2800004080C4190, 0x890808280020080), + B(0x40C0401000104000, 0x2020880008002580), + B(0x40020C002400802, 0x801104010000000), + B(0x44842000040080, 0x2050011084000400), + B(0x4110040800000401, 0x2023810029008000), + B(0x20884000840, 0x8017102004008000), + B(0x10411104000480, 0x1414042000201001), + B(0x220040000008, 0x800306021000000), + B(0x41400A0008080, 0x501000298ACAD10), + B(0x800240012831810, 0x80120004468050E), + B(0x800005020801008, 0x20102400240000), + B(0x20C00040C114C010, 0x88080820200C00), + B(0x1044010100820081, 0x20080841004000), + B(0x8041048400022, 0x8020836040005002), + B(0x2001004010205, 0x8001002884042009), + B(0x128088400087, 0x20008002201002), + B(0x8084108040402000, 0x80809000A080400), + B(0x408081840880, 0x201002088000040), + B(0xA40180010280, 0x241004006000010), + B(0x4204100080048140, 0x2002C4F104202020), + B(0x100140A10204, 0x980200800840060), + B(0x1005140010202048, 0x1442280800202815), + B(0x2000082025008600, 0x1108400040600003), + B(0x1005050648000, 0x200020240008002), + B(0x202010208044000, 0x8210404060008), + B(0x8011040402000210, 0xC840180408016004), + B(0x404098801028, 0x80020A0001000400), + B(0x404098801028, 0x80020A0001000400), + B(0x80101002180140, 0x40C2080820000C0), + B(0x208202081260800, 0x14090E4C04000050), + B(0x4221201084004C2, 0x110480A011060), + B(0x8000008421090204, 0x1C01010800024), + B(0x8000008421090204, 0x1C01010800024), + B(0x200180C840088A0, 0x401100400820000), + B(0x10084043A021070, 0x202041600080200), + B(0x210E6202001040C, 0x10100800080B0), + B(0x848008021204002, 0x801004308100BAD), + B(0xC082C0390A000601, 0x4040080189008), + B(0x431200240210402D, 0x58102820000), + B(0x202020100A0019B0, 0x4010C0D018000000), + B(0x800800908402203, 0x102948C84C184), + B(0x26801100080845, 0x4009702022A00820), + B(0x8880520010401040, 0x1060084832052000), + B(0x100100022042081, 0x10000600008C121), + B(0x46020384100040, 0x800200320882021), + B(0xC0002010148, 0x4200800800040003), + B(0x2002208020090040, 0x40820210021410), + B(0x9000A41160002004, 0x2A09000100080043), + B(0x800004010008001, 0x1108002020104600), + B(0x800540C000A4E041, 0x18021180000401), + B(0x808200900A900202, 0x8364202140012005), + B(0x1DBA52000081010, 0x4008000023000010), + B(0x4100110204401481, 0x800040091020001C), + B(0x4100110204401481, 0x800040091020001C), + B(0x4101100020400482, 0x2000402302100120), + B(0x100408000A020212, 0xA000400111000020), + B(0x2000010488080104, 0x3000404410208100), + B(0x2684220180008DD0, 0x422040200004000A), + B(0x2021200C0424, 0x1010100000080200), + B(0x8908020020801006, 0x3010800020C2000), + B(0x4000030008062044, 0x244010202688000), + B(0x242101200408009, 0x8150040000200015), + B(0x42004C02180204, 0x210208014241040), + B(0x4E1A01C208410804, 0x8890041000012004), + B(0x2080200401000080, 0x8001098429008004), + B(0xA01400121804104, 0x280200C400000500), + B(0xD0080408040420, 0x1006040100224000), + B(0x28400205000800C9, 0x6021101401040075), + B(0x4000900040020104, 0x88129801100D0C), + B(0x8000004002180410, 0x400380200400204), + B(0x4002A430043008, 0x400200340100020), + B(0x401960004140A42, 0x100880710000464), + B(0x58014090102, 0xB8D30004010080), + B(0xA004C08000244000, 0x11280100E0000040), + B(0x2102008089208804, 0x110001004080040), + B(0x700010084E003004, 0x8080864112000D40), + B(0x4080881000200C20, 0x30324040880E0600), + B(0x2024A40401810820, 0x3000888002000000), + B(0x8200100400014, 0x4400340800252844), + B(0x24A00804288281, 0x410103002201140), + B(0x4080005022A08, 0x1000402200100264), + B(0x200080032244040, 0x200502189010001), + B(0x28108110404001, 0x400600120008412), + B(0xA00002102810020, 0xB1080240015408), + B(0x810080200806, 0x410440804080046) + }; + constexpr Bitboard BishopMagicInit[SQUARE_NB] = { + B(0x2001040305000010, 0x830200040400082), + B(0x1042400080E01200, 0x2004904010811400), + B(0x400010120200, 0x880080D080018000), + B(0x240190C00100040, 0x100A020140044404), + B(0x1018010404010004, 0x1001010018081E0), + B(0x41200A804C0904, 0x40000322000008), + B(0x4001180A004, 0x8000001106000000), + B(0x6006020020030600, 0x1840002100004841), + B(0x4200200100, 0x4001041808002000), + B(0x4100020050124600, 0x1001802902400CA0), + B(0x448C0081440161, 0x200206010008000), + B(0x400008008008408, 0x1000080210100080), + B(0x200280C01008200, 0x210200813000080), + B(0x1A000204400, 0x222200401023000), + B(0x10081040640A00, 0x8410021881400000), + B(0x1840400318080008, 0x800800840080000), + B(0x4204050C040, 0x6500600200140000), + B(0x1012100040204, 0x402404444400000), + B(0x6000012680008240, 0x410140000004220), + B(0x1000020810040008, 0x2D0011000060000), + B(0x1020020400, 0x400108059001001), + B(0x400020001100808, 0x480204800200000B), + B(0x10000010030084, 0x2042000848900022), + B(0x10000010030084, 0x2042000848900022), + B(0x100D801402400, 0x1512404009000400), + B(0x8000208005112400, 0xA02040401000000), + B(0x1000420002800200, 0x4CA000183020000), + B(0x800811480020, 0x408801010224001), + B(0xC805200810900100, 0x9000084204004020), + B(0x8200160204100004, 0x8040004004002022), + B(0x104514013080080, 0x146410040001000), + B(0x140844000080002, 0x1008102020040001), + B(0x4040400041A2002, 0x8040000A8802510), + B(0x801014041008002, 0x80068008025200), + B(0xA00540A414040, 0x4101040010A0000), + B(0x6484008010810002, 0x1100506884024000), + B(0x2800401008006000, 0x1005420884029020), + B(0x6822091010004421, 0x2000458080480), + B(0x40101000200101, 0x10020100001C4E0), + B(0x100400008C42, 0x4000100009008000), + B(0x851220018800400, 0x1681800040080080), + B(0x64200002010, 0x900020200040002), + B(0x20800080000022, 0x80040810002010), + B(0xA88408000802080, 0x20808001000000), + B(0x200000400C005040, 0x100140020290108), + B(0x224100000800408, 0x4204802004400020), + B(0x80080620010210, 0x91080088804040), + B(0x4008002100010, 0x80AC201001000001), + B(0x10008200902C046, 0x8080D03004000010), + B(0x3002100081000180, 0x2210002121528408), + B(0x8C101800804420, 0x1019880200043008), + B(0x200022000920D0, 0x8000800081300020), + B(0x1D40800880000, 0x400040001400050), + B(0x2020004100040, 0x200008040008008), + B(0x4840800040100001, 0x100100040203040), + B(0x40084001105, 0x8800080088000089), + B(0x4000128008020008, 0x4004200200440020), + B(0x210040008520000, 0x820219001080022), + B(0x1494040018002116, 0x400101047020008), + B(0x510008001910C224, 0x80200148118000), + B(0xC0301002301000, 0x4211A08004801), + B(0x50008E0C01001080, 0x100C004102845100), + B(0x400600020060400, 0x88024100250050), + B(0x8202920002002040, 0x810012000003), + B(0x800004208800200, 0x18AA00201000048), + B(0x402100800100002, 0x411000081000400), + B(0x101000022004044, 0x9000100040000), + B(0x41068001001, 0xC00400010001), + B(0x310210001040, 0x1A1200020010000), + B(0xA082409200004048, 0x490040800124101), + B(0x18844820E0040212, 0x1000404420D10000), + B(0x802908A40003348, 0x20200040104140), + B(0x1800404028205003, 0xC020010401089020), + B(0x802100044D01000, 0x8C41888000800040), + B(0x1D0161011410081, 0x10008000100200), + B(0x401000480040100, 0x286800404002212), + B(0x821030000100009, 0x2000090200A00000), + B(0x200020800200800, 0x2000480900841012), + B(0x80A000048030080, 0x200000120200008), + B(0x40B1400008020020, 0x148000200008004), + B(0xA021700002002010, 0x3040E400040100), + B(0x400242C200200640, 0x20440210200281), + B(0x80AC140040206240, 0x120000102801401), + B(0x2020340040832040, 0x10402100A44000), + B(0x420100400040220, 0x80014C8004000106), + B(0x504300822421120, 0x8004004008400100), + B(0x2001100008040, 0x2020104302000000), + B(0xA500802000A, 0x2008008000114100), + B(0x8A0020000200, 0x9C00101001002408), + B(0x104000001001008, 0x9001000204040060), + B(0x1000820080108200, 0xA401000008100001), + B(0x2008600009000480, 0x9008020001400000), + B(0x4000800200040200, 0xA00030400308082), + B(0x4004300202004709, 0x1000100180010020), + B(0xC014800100440010, 0x402020280002C010), + B(0x220208010884680, 0x1040280000042110), + B(0x40B0018019202801, 0x1008408000100040), + B(0x8269010206080044, 0x8001810000000040), + B(0x4000020880081040, 0x208A44000028000), + B(0x4004004E9004220A, 0x2104004001400024), + B(0x8035006008C0904, 0x402002001080120), + B(0x1800884002, 0x404400820000000), + B(0x8088000004008910, 0x8024100401000000), + B(0x142200086000100, 0x28021040020002E), + B(0x1000409141004018, 0x100410820080040A), + B(0x1800801800140, 0x810801060C0801), + B(0x1000C00100402220, 0x808023420000000), + B(0x8A0A202414305008, 0x100040200000021), + B(0xC0208024050, 0x8003088008020401), + B(0x8044004201440101, 0x400820080C024022), + B(0x406018884120099, 0xB00088018002000), + B(0x2000800010403010, 0xC5A002002010010), + B(0x800020040840, 0x201800202800200), + B(0x201280120020008D, 0x258809001000040), + B(0x9100002020181, 0x80400082204000), + B(0x104010080201001, 0x40080080181080), + B(0x8440248092000430, 0xA200804900100000), + B(0x2031010C01000C20, 0x200310A560082008), + B(0x400202081811400, 0x40081802050000C), + B(0x1011002100821300, 0x2400825040804100) + }; + constexpr Bitboard CannonMagicHInit[SQUARE_NB] = { + B(0x120000880110000, 0x1008000000020020), + B(0x24200C080840A052, 0x2004004000010008), + B(0xC030024000228800, 0x4000010400000020), + B(0x1A0020802008802, 0x206010208000), + B(0x12002000D001024, 0x80100800090138), + B(0x4220010000241010, 0x3098000602001500), + B(0x401010004801040, 0x8000280480100000), + B(0x820082024921836, 0x220028000), + B(0x100400502411400, 0x220402120240D14), + B(0x880202020010404, 0xA80202510000), + B(0x140002801000018, 0x1000346490040), + B(0x120000880110000, 0x1008000000020020), + B(0xD01004008030400, 0x104000408104420), + B(0x8420060100020000, 0x800280400000120), + B(0x4010020018010, 0x40A00001100000), + B(0x40006A0004000200, 0x40000000110), + B(0xD01004008030400, 0x104000408104420), + B(0x8908A20028110011, 0x800080000001A114), + B(0x200042000080F009, 0x20001000004000), + B(0x2820008820100, 0x10002400058000B9), + B(0x6083100420008050, 0x4040012600280080), + B(0x216020000000446, 0x4080204000000211), + B(0x340140003002089, 0x2402008000000911), + B(0xD01004008030400, 0x104000408104420), + B(0x1404040B20001000, 0x8000824010800011), + B(0x8C0488120024214, 0x8414880202291), + B(0x1010000060050000, 0x4000004050002602), + B(0x4022983A0060000, 0x80000040010400), + B(0x1404040B20001000, 0x8000824010800011), + B(0x6020101802002840, 0x31000003000004), + B(0x9000420008840, 0x4881300000000210), + B(0xA200808865, 0x41C0048023000128), + B(0x31801100400000, 0x8802DC001221240), + B(0x884000080200920, 0x1004002410401001), + B(0x2400040000884, 0x421006208040C0), + B(0x1404040B20001000, 0x8000824010800011), + B(0x24100400060009, 0x112008025042410), + B(0x1800040009040200, 0x180000A1004E408A), + B(0x24100400060009, 0x112008025042410), + B(0x4060402008080, 0xC240080000110000), + B(0x20080100920020, 0x2002248010242052), + B(0x10001010802050, 0x880000001C98420), + B(0x4000800100420022, 0x502022010A00D0), + B(0x4C18104500200885, 0x400880800), + B(0x8080810081020090, 0x8000000000000), + B(0x8000062812080201, 0x8004C8300800), + B(0x1800040009040200, 0x180000A1004E408A), + B(0x24100400060009, 0x112008025042410), + B(0x80102204040, 0x1000000900000000), + B(0x2080000004202804, 0x120880003461), + B(0x102004090A4030, 0x801020589240), + B(0x20001100814000A0, 0x420202000820004), + B(0x100800000A000120, 0x208000800010000), + B(0x1008205000040802, 0x80002000400040), + B(0x1480000098008401, 0xA0010000581010), + B(0x30C0008200100820, 0x102800080904834), + B(0x4810821884000500, 0x4400000200000212), + B(0x1811D00128A0180, 0x2500848803000000), + B(0x41618A0300040040, 0x21200200A421801), + B(0x80102204040, 0x1000000900000000), + B(0xA1808E0100108000, 0x2008000505000002), + B(0x8C890020410000A0, 0xA010000048000400), + B(0x40006002210044, 0x600008000408000), + B(0x1200447220090042, 0x80001000160012), + B(0x48410010AB000000, 0x9200600000000100), + B(0x2040000000240003, 0x8020080288000600), + B(0x9080000088848088, 0x4010210500000041), + B(0xA1808E0100108000, 0x2008000505000002), + B(0x480100400024, 0x1004800018200000), + B(0x808403080080200, 0x802601000000500), + B(0x8C890020410000A0, 0xA010000048000400), + B(0xA1808E0100108000, 0x2008000505000002), + B(0x100A40000004008, 0x2800200400200480), + B(0x100A40000004008, 0x2800200400200480), + B(0x400014006000000, 0x10006000810001F5), + B(0xC410062001414, 0x820080041B01044), + B(0x20000800310, 0x430040000201000), + B(0xA40010008000008, 0x4002200028000040), + B(0xC00102000008021C, 0x10C2000A010E024), + B(0x80004200104008, 0x50A00800C400020), + B(0x20200080012542, 0x910F0040000402C0), + B(0xB040100504000300, 0x24802002000040), + B(0x800001000014008, 0x400031004000), + B(0x100A40000004008, 0x2800200400200480), + B(0x84008002041081C0, 0x8080500200000000), + B(0x440090001012001, 0x4020004010), + B(0x100A0028088020, 0x80040E00010020), + B(0x2180808000810, 0xB018040A00040000), + B(0x40C80920304C4001, 0x42800B200800000), + B(0x85000425001000, 0x4810048020001100), + B(0x600C000801000004, 0x8015084010200020), + B(0x20020050000240C0, 0x100202008600800), + B(0x38000050001220, 0x9200010200145900), + B(0x1042108040005, 0x1402A0802201001), + B(0x824240000C20400, 0x1000000400080010), + B(0x84008002041081C0, 0x8080500200000000), + B(0x400804A1000008, 0x1024104A0200010), + B(0x8000402308483, 0x20006020100100), + B(0x80880120000080, 0x8000240100084), + B(0x5840020004882001, 0x1004528000A00010), + B(0x8001018800300002, 0x84010040804), + B(0x180D10004000A008, 0xA001080008020004), + B(0x400080B, 0x10A0000004010000), + B(0x8080000200000, 0x2001000082004E0), + B(0x40040001000C2000, 0x2024800001004008), + B(0x400804A1000008, 0x1024104A0200010), + B(0x8000402308483, 0x20006020100100), + B(0x400804A1000008, 0x1024104A0200010), + B(0x2000200000, 0x1201011000802), + B(0x100100000000C4, 0x208004084048201), + B(0x400084000044, 0x100810140300), + B(0x29040C0C01010, 0x300204010820080), + B(0x1A808000020200, 0x1000000005210040), + B(0x20000400150000, 0x85008020), + B(0x40C040008184014, 0x8002AA00024010), + B(0x202000081B00804, 0x10001002008), + B(0x40011000210060, 0x6080C40000021004), + B(0x2000200000, 0x1201011000802), + B(0x4100480203840, 0x300080100804), + B(0x2000200000, 0x1201011000802), + }; + constexpr Bitboard CannonMagicVInit[SQUARE_NB] = { + B(0x202000812104400, 0x24800B01C0000303), + B(0x340020400010D, 0x88060150C00400), + B(0x400802040609, 0x49010200501A0002), + B(0x8002680301000208, 0x628006C0C020200), + B(0x20400209001C0804, 0xA044000800143110), + B(0xC400082060010202, 0x4000480401014000), + B(0x22500200144040, 0x8204820084704C00), + B(0x8C1204009030020, 0x328400805000000), + B(0x84800800D0001640, 0x200080040060108), + B(0x804810208020040, 0x140010108020000), + B(0x1102010B008004, 0x300208006220020), + B(0x140080404A0A2428, 0x6308010100080), + B(0x20444002120408, 0xA080010508010001), + B(0x82011044000D02, 0x4112028620110809), + B(0x81010831000C02, 0x408802085000000), + B(0x81010831000C02, 0x408802085000000), + B(0x920008920600040, 0x8053801004000028), + B(0x81140283208300, 0x10040C004200420), + B(0x103080022201, 0xC01000081312620), + B(0x2200221100008, 0x1000408104000A4), + B(0x4402088080042008, 0x210401501040340), + B(0x898400202170001, 0x80040404208000), + B(0x20080004051012, 0x5100048200081800), + B(0x2320020000401018, 0x108501021040210), + B(0x21080410A422021, 0x83040180008800), + B(0x44E8100000408224, 0x20010008040400), + B(0x1800240002810405, 0x23004820000020), + B(0x80A0100400110, 0x80104020100C4028), + B(0x1002050001222C0, 0x5100818004024020), + B(0x104000200040, 0xC010A09800102000), + B(0x1020003A058120, 0x450900809000302), + B(0x40040045008B1, 0x202800400383010), + B(0x4640200220034, 0x8800485420304000), + B(0x5001042100084288, 0x110820001240080A), + B(0x2002C04004010120, 0xA15008020880001), + B(0x2800004080C4190, 0x890808280020080), + B(0x40C0401000104000, 0x2020880008002580), + B(0x40020C002400802, 0x801104010000000), + B(0x44842000040080, 0x2050011084000400), + B(0x4110040800000401, 0x2023810029008000), + B(0x20884000840, 0x8017102004008000), + B(0x10411104000480, 0x1414042000201001), + B(0x220040000008, 0x800306021000000), + B(0x41400A0008080, 0x501000298ACAD10), + B(0x800240012831810, 0x80120004468050E), + B(0x800005020801008, 0x20102400240000), + B(0x20C00040C114C010, 0x88080820200C00), + B(0x1044010100820081, 0x20080841004000), + B(0x8041048400022, 0x8020836040005002), + B(0x2001004010205, 0x8001002884042009), + B(0x128088400087, 0x20008002201002), + B(0x8084108040402000, 0x80809000A080400), + B(0x408081840880, 0x201002088000040), + B(0xA40180010280, 0x241004006000010), + B(0x4204100080048140, 0x2002C4F104202020), + B(0x100140A10204, 0x980200800840060), + B(0x1005140010202048, 0x1442280800202815), + B(0x2000082025008600, 0x1108400040600003), + B(0x1005050648000, 0x200020240008002), + B(0x202010208044000, 0x8210404060008), + B(0x8011040402000210, 0xC840180408016004), + B(0x404098801028, 0x80020A0001000400), + B(0x404098801028, 0x80020A0001000400), + B(0x80101002180140, 0x40C2080820000C0), + B(0x208202081260800, 0x14090E4C04000050), + B(0x4221201084004C2, 0x110480A011060), + B(0x8000008421090204, 0x1C01010800024), + B(0x8000008421090204, 0x1C01010800024), + B(0x200180C840088A0, 0x401100400820000), + B(0x10084043A021070, 0x202041600080200), + B(0x210E6202001040C, 0x10100800080B0), + B(0x848008021204002, 0x801004308100BAD), + B(0xC082C0390A000601, 0x4040080189008), + B(0x431200240210402D, 0x58102820000), + B(0x202020100A0019B0, 0x4010C0D018000000), + B(0x800800908402203, 0x102948C84C184), + B(0x26801100080845, 0x4009702022A00820), + B(0x8880520010401040, 0x1060084832052000), + B(0x100100022042081, 0x10000600008C121), + B(0x46020384100040, 0x800200320882021), + B(0xC0002010148, 0x4200800800040003), + B(0x2002208020090040, 0x40820210021410), + B(0x9000A41160002004, 0x2A09000100080043), + B(0x800004010008001, 0x1108002020104600), + B(0x800540C000A4E041, 0x18021180000401), + B(0x808200900A900202, 0x8364202140012005), + B(0x1DBA52000081010, 0x4008000023000010), + B(0x4100110204401481, 0x800040091020001C), + B(0x4100110204401481, 0x800040091020001C), + B(0x4101100020400482, 0x2000402302100120), + B(0x100408000A020212, 0xA000400111000020), + B(0x2000010488080104, 0x3000404410208100), + B(0x2684220180008DD0, 0x422040200004000A), + B(0x2021200C0424, 0x1010100000080200), + B(0x8908020020801006, 0x3010800020C2000), + B(0x4000030008062044, 0x244010202688000), + B(0x242101200408009, 0x8150040000200015), + B(0x42004C02180204, 0x210208014241040), + B(0x4E1A01C208410804, 0x8890041000012004), + B(0x2080200401000080, 0x8001098429008004), + B(0xA01400121804104, 0x280200C400000500), + B(0xD0080408040420, 0x1006040100224000), + B(0x28400205000800C9, 0x6021101401040075), + B(0x4000900040020104, 0x88129801100D0C), + B(0x8000004002180410, 0x400380200400204), + B(0x4002A430043008, 0x400200340100020), + B(0x401960004140A42, 0x100880710000464), + B(0x58014090102, 0xB8D30004010080), + B(0xA004C08000244000, 0x11280100E0000040), + B(0x2102008089208804, 0x110001004080040), + B(0x700010084E003004, 0x8080864112000D40), + B(0x4080881000200C20, 0x30324040880E0600), + B(0x2024A40401810820, 0x3000888002000000), + B(0x8200100400014, 0x4400340800252844), + B(0x24A00804288281, 0x410103002201140), + B(0x4080005022A08, 0x1000402200100264), + B(0x200080032244040, 0x200502189010001), + B(0x28108110404001, 0x400600120008412), + B(0xA00002102810020, 0xB1080240015408), + B(0x810080200806, 0x410440804080046), + }; + constexpr Bitboard LameDabbabaMagicInit[SQUARE_NB] = { + B(0x3C080482A592000C, 0x540104000020000), + B(0x2802C40008000420, 0x4A00000001818009), + B(0x1083040280804000, 0x120004C20100880), + B(0x6840940880000892, 0x2014A01080800C2), + B(0x8401489004000180, 0x2000800000400000), + B(0x820161C800000110, 0x8000100000204020), + B(0x610011A122000109, 0x1000004020008004), + B(0x83282004023000, 0xE000020004848446), + B(0x6840940880000892, 0x2014A01080800C2), + B(0x4020120800800002, 0x88008000010020), + B(0x30025B140A1000, 0x3141801401000040), + B(0x41104D1810100050, 0x8141002010910), + B(0x4200828A298400, 0x400340001040C000), + B(0x8016A4900110040, 0x844812001068020), + B(0x2250035820400A2, 0x8012010080900), + B(0x820080083A009000, 0x880404091080110), + B(0x80401500AF0020, 0x240000082201A04), + B(0x668020020C081005, 0x4008001004100021), + B(0x240100910000000, 0x82000A0030454000), + B(0xA24091400008, 0x200014880004A921), + B(0x840110042200410, 0x100080000A400000), + B(0x40024024102000, 0x1000000002180404), + B(0x92828423000530, 0x118800020110), + B(0x1122404A1C90A8, 0x822040280020D00), + B(0x41201A40900A000, 0x80C0480040605100), + B(0x2504A85005488280, 0x3028112120022800), + B(0x210180080626B048, 0x8000401000014000), + B(0x1000410401040200, 0x41014000050C0106), + B(0x1040650210802200, 0x80C0041000000), + B(0x4020C10110900002, 0x2140C2001050009), + B(0x191180092200022, 0x6010008400400800), + B(0x8010821088080202, 0xCA240011008208), + B(0x8C0488120024214, 0x8414880202291), + B(0x8C0488120024214, 0x8414880202291), + B(0x22080C8A0161401, 0x200C10004C002002), + B(0x8430818023034080, 0x210090800000801), + B(0x4845087008200, 0x40661480000), + B(0x1202804428812050, 0x100022038020000), + B(0x400016001201080, 0x24002200402060), + B(0x680E041300800800, 0xE00130080004000), + B(0x3409080200, 0x282840210000000), + B(0x803310108400, 0x85200000080100A0), + B(0xE180008A04162104, 0x9088240412404), + B(0x20080100920020, 0x2002248010242052), + B(0x8A000400C2410, 0x1000024086014300), + B(0x1821040024663, 0x100000100010009), + B(0x4000822310611, 0x120280406014008), + B(0x1004008010818D08, 0x800000141892000), + B(0x8010800004024042, 0x44B106008800896), + B(0xA0063423444, 0x41002C15811008), + B(0x2040012381001282, 0x4804080104A4000), + B(0x10840101820880, 0xA800008000020020), + B(0x10840101820880, 0xA800008000020020), + B(0x60201D8300408190, 0x2010020920200000), + B(0x4048100200090090, 0x2008090100000900), + B(0x24200000280210, 0xD440050008004000), + B(0x1280001000580020, 0x2200040089000A4), + B(0x10208018C1020A20, 0x84C0432240610014), + B(0x10208018C1020A20, 0x84C0432240610014), + B(0x4108000010209089, 0x913000000024840), + B(0x410C208008008E02, 0xE8000000000001), + B(0x802208004005, 0x94206000022080), + B(0xC00290018902002, 0x4204100000000000), + B(0x2102801400093816, 0x9810004001000202), + B(0x8008304000015800, 0x4A5C000000020000), + B(0x1020108380800514, 0x1144210000000080), + B(0xC0001000008090, 0x2812060000204000), + B(0x1001100200003100, 0x246240060A004004), + B(0xA00020A008002030, 0x2440C40000110B00), + B(0x80502104000C008, 0x8222200042100010), + B(0xC020200088014, 0x422094000000480), + B(0x1029002000001030, 0x8105841120000210), + B(0x49040D, 0x2310808A14042C0), + B(0x200040200080A02C, 0xB890290400080000), + B(0x2240180C0800002, 0x4151050280000100), + B(0x2240180C0800002, 0x4151050280000100), + B(0x8220224180420006, 0x4024501212011000), + B(0x1806810A0881000, 0x802002048400080), + B(0x400400A080842, 0x9305000401180000), + B(0x10008001444110, 0x4420401040041833), + B(0x2000002C02010E00, 0x400408D08009804), + B(0x69D008200020100, 0x100842240049021), + B(0x42C24450020000, 0xD38400880090884), + B(0x485800800100001, 0x2484086522018840), + B(0x900200020820042, 0x22302421400040C0), + B(0x50B0413001818000, 0x452014040800C40), + B(0x8004040021008, 0x20088A08000290), + B(0x600C000801000004, 0x8015084010200020), + B(0x208000C00, 0xE004804021100100), + B(0x20001000040204, 0x948110C0B2081), + B(0x268502400100021, 0x80A201840802080), + B(0x408C000008, 0x8822102408014), + B(0x1182080410100000, 0x608002046A0100), + B(0x100820A083C00002, 0x3100100410A00), + B(0x8401040000400124, 0x2000081288202200), + B(0xB014040003000800, 0x11960D1101210), + B(0x10040001900C000, 0x85603C1001280), + B(0x2000844000000100, 0x2000024C60800800), + B(0x120004234800900, 0x210010841040), + B(0x8010300040000002, 0x4200008222104100), + B(0x1000120402200100, 0x209080CC040108B4), + B(0x110049A00000800, 0x80000420022180A8), + B(0x80001C00080384, 0x1400101111081001), + B(0x8011200008100428, 0x2020000880800922), + B(0x10001000000204C8, 0x280C11104240), + B(0x50100C82C000500, 0x28000280618DD1), + B(0x8800498020000, 0x20500A0200320128), + B(0x20010104000860, 0x8021720186008), + B(0x4000000000100080, 0x35040084270C04), + B(0x4500080000800, 0x280100002482C842), + B(0x10400000000000, 0x20080051100130C2), + B(0x10400000000000, 0x20080051100130C2), + B(0x2000002110202014, 0x121004004004681), + B(0x400202001006D40, 0x82240082202424), + B(0x4500080000800, 0x280100002482C842), + B(0xC6000000D00804, 0x1050020C0081090C), + B(0x200080000000042, 0x10800661), + B(0x2000001011200200, 0x2A420000802A0222), + B(0x802020001202412, 0x2400404148426), + B(0x8000440801040002, 0x444002800010052A), + }; + constexpr Bitboard HorseMagicInit[SQUARE_NB] = { + B(0x3C080482A592000C, 0x540104000020000), + B(0x2802C40008000420, 0x4A00000001818009), + B(0x1083040280804000, 0x120004C20100880), + B(0x6840940880000892, 0x2014A01080800C2), + B(0x8401489004000180, 0x2000800000400000), + B(0x820161C800000110, 0x8000100000204020), + B(0x610011A122000109, 0x1000004020008004), + B(0x83282004023000, 0xE000020004848446), + B(0x6840940880000892, 0x2014A01080800C2), + B(0x4020120800800002, 0x88008000010020), + B(0x30025B140A1000, 0x3141801401000040), + B(0x41104D1810100050, 0x8141002010910), + B(0x4200828A298400, 0x400340001040C000), + B(0x8016A4900110040, 0x844812001068020), + B(0x2250035820400A2, 0x8012010080900), + B(0x820080083A009000, 0x880404091080110), + B(0x80401500AF0020, 0x240000082201A04), + B(0x668020020C081005, 0x4008001004100021), + B(0x240100910000000, 0x82000A0030454000), + B(0xA24091400008, 0x200014880004A921), + B(0x840110042200410, 0x100080000A400000), + B(0x40024024102000, 0x1000000002180404), + B(0x92828423000530, 0x118800020110), + B(0x1122404A1C90A8, 0x822040280020D00), + B(0x41201A40900A000, 0x80C0480040605100), + B(0x2504A85005488280, 0x3028112120022800), + B(0x210180080626B048, 0x8000401000014000), + B(0x1000410401040200, 0x41014000050C0106), + B(0x1040650210802200, 0x80C0041000000), + B(0x4020C10110900002, 0x2140C2001050009), + B(0x191180092200022, 0x6010008400400800), + B(0x8010821088080202, 0xCA240011008208), + B(0x8C0488120024214, 0x8414880202291), + B(0x8C0488120024214, 0x8414880202291), + B(0x22080C8A0161401, 0x200C10004C002002), + B(0x8430818023034080, 0x210090800000801), + B(0x4845087008200, 0x40661480000), + B(0x1202804428812050, 0x100022038020000), + B(0x400016001201080, 0x24002200402060), + B(0x680E041300800800, 0xE00130080004000), + B(0x3409080200, 0x282840210000000), + B(0x803310108400, 0x85200000080100A0), + B(0xE180008A04162104, 0x9088240412404), + B(0x20080100920020, 0x2002248010242052), + B(0x8A000400C2410, 0x1000024086014300), + B(0x1821040024663, 0x100000100010009), + B(0x4000822310611, 0x120280406014008), + B(0x1004008010818D08, 0x800000141892000), + B(0x8010800004024042, 0x44B106008800896), + B(0xA0063423444, 0x41002C15811008), + B(0x2040012381001282, 0x4804080104A4000), + B(0x10840101820880, 0xA800008000020020), + B(0x10840101820880, 0xA800008000020020), + B(0x60201D8300408190, 0x2010020920200000), + B(0x4048100200090090, 0x2008090100000900), + B(0x24200000280210, 0xD440050008004000), + B(0x1280001000580020, 0x2200040089000A4), + B(0x10208018C1020A20, 0x84C0432240610014), + B(0x10208018C1020A20, 0x84C0432240610014), + B(0x4108000010209089, 0x913000000024840), + B(0x410C208008008E02, 0xE8000000000001), + B(0x802208004005, 0x94206000022080), + B(0xC00290018902002, 0x4204100000000000), + B(0x2102801400093816, 0x9810004001000202), + B(0x8008304000015800, 0x4A5C000000020000), + B(0x1020108380800514, 0x1144210000000080), + B(0xC0001000008090, 0x2812060000204000), + B(0x1001100200003100, 0x246240060A004004), + B(0xA00020A008002030, 0x2440C40000110B00), + B(0x80502104000C008, 0x8222200042100010), + B(0xC020200088014, 0x422094000000480), + B(0x1029002000001030, 0x8105841120000210), + B(0x49040D, 0x2310808A14042C0), + B(0x200040200080A02C, 0xB890290400080000), + B(0x2240180C0800002, 0x4151050280000100), + B(0x2240180C0800002, 0x4151050280000100), + B(0x8220224180420006, 0x4024501212011000), + B(0x1806810A0881000, 0x802002048400080), + B(0x400400A080842, 0x9305000401180000), + B(0x10008001444110, 0x4420401040041833), + B(0x2000002C02010E00, 0x400408D08009804), + B(0x69D008200020100, 0x100842240049021), + B(0x42C24450020000, 0xD38400880090884), + B(0x485800800100001, 0x2484086522018840), + B(0x900200020820042, 0x22302421400040C0), + B(0x50B0413001818000, 0x452014040800C40), + B(0x8004040021008, 0x20088A08000290), + B(0x600C000801000004, 0x8015084010200020), + B(0x208000C00, 0xE004804021100100), + B(0x20001000040204, 0x948110C0B2081), + B(0x268502400100021, 0x80A201840802080), + B(0x408C000008, 0x8822102408014), + B(0x1182080410100000, 0x608002046A0100), + B(0x100820A083C00002, 0x3100100410A00), + B(0x8401040000400124, 0x2000081288202200), + B(0xB014040003000800, 0x11960D1101210), + B(0x10040001900C000, 0x85603C1001280), + B(0x2000844000000100, 0x2000024C60800800), + B(0x120004234800900, 0x210010841040), + B(0x8010300040000002, 0x4200008222104100), + B(0x1000120402200100, 0x209080CC040108B4), + B(0x110049A00000800, 0x80000420022180A8), + B(0x80001C00080384, 0x1400101111081001), + B(0x8011200008100428, 0x2020000880800922), + B(0x10001000000204C8, 0x280C11104240), + B(0x50100C82C000500, 0x28000280618DD1), + B(0x8800498020000, 0x20500A0200320128), + B(0x20010104000860, 0x8021720186008), + B(0x4000000000100080, 0x35040084270C04), + B(0x4500080000800, 0x280100002482C842), + B(0x10400000000000, 0x20080051100130C2), + B(0x10400000000000, 0x20080051100130C2), + B(0x2000002110202014, 0x121004004004681), + B(0x400202001006D40, 0x82240082202424), + B(0x4500080000800, 0x280100002482C842), + B(0xC6000000D00804, 0x1050020C0081090C), + B(0x200080000000042, 0x10800661), + B(0x2000001011200200, 0x2A420000802A0222), + B(0x802020001202412, 0x2400404148426), + B(0x8000440801040002, 0x444002800010052A), + }; + constexpr Bitboard ElephantMagicInit[SQUARE_NB] = { + B(0x64D2990200008, 0x4401880001C000), + B(0x29BAA00010020, 0x200000400800600), + B(0x3024240000000, 0x4100400010080), + B(0xA490A00480020, 0x20084001041010A4), + B(0x328C021008042, 0x100000000C10204), + B(0x1964090001018, 0x7002040148001205), + B(0x800302098404080, 0x4983020000000001), + B(0x8812244630A02080, 0x8200006204003C08), + B(0x41120231008000, 0x240441401020), + B(0x840091030C00040, 0x1400008200023400), + B(0x8001040E77030200, 0x100040090022000), + B(0x602022139D835040, 0x101002010025900), + B(0x405707C48400, 0x40010000008001), + B(0x982003456A82050, 0x60800820040030), + B(0x204184849200088, 0x101800004006), + B(0x300222470949200, 0x2A0800200200800), + B(0x400001211914000, 0x8200001407001), + B(0x2000008614831020, 0x4000020001404000), + B(0x84000024A2048048, 0x1200102000042), + B(0x424010A58422008, 0x88440242212A0110), + B(0x20020812C0C4408, 0x4121400000080010), + B(0x680200062042420, 0x2001100000800000), + B(0x200010060AEC855, 0x8083002040200000), + B(0x4000008BAA85810, 0x82000805C0200A90), + B(0x81450B200A025400, 0x4400101050000040), + B(0x820A2241409010, 0x888420030000), + B(0x909203000028, 0xC000004C00200041), + B(0x8021400A84880240, 0x100180002010020), + B(0x8001A20061410000, 0x14008499A000000), + B(0x8201444800A00080, 0x402010040588120), + B(0x100C06280020, 0x60010104840130), + B(0x520040800080044, 0x8220000080001402), + B(0x102021410040202, 0x2004400410006000), + B(0x5401832090020400, 0x300010020001), + B(0x180003105A84C108, 0x1012008800081000), + B(0x480C10210026904, 0xA006000004200418), + B(0x48050820210843A6, 0x108001004000C00), + B(0x1030101182206324, 0x4401008921502002), + B(0x40281060800800, 0x406000201260022), + B(0xC29002440040C820, 0x400001002008020), + B(0x40000400800241, 0xC220000000400280), + B(0x40880126014208, 0x2A8004C008940000), + B(0x121028100114080, 0x5010280481100082), + B(0x4000088280442, 0x908420140008041), + B(0x808C42400C0020, 0x3028100840801000), + B(0x4000000410078488, 0x501000000620000), + B(0x90080001421020A4, 0x4118400101060406), + B(0x280420004855, 0xD200100400820000), + B(0xA0063423444, 0x41002C15811008), + B(0x200061201808102, 0x4286969000200002), + B(0x10208018C1020A20, 0x84C0432240610014), + B(0x4001A04880402000, 0x8100824080000001), + B(0x60201D8300408190, 0x2010020920200000), + B(0x20018C04908019, 0x2010884002002040), + B(0x800000000C40810, 0x680100081150000D), + B(0x2002002000040040, 0x8810049000010600), + B(0x41618A0300040040, 0x21200200A421801), + B(0x10208018C1020A20, 0x84C0432240610014), + B(0x10208018C1020A20, 0x84C0432240610014), + B(0x5A04001400412854, 0x8A44006000010002), + B(0x13000C0810072432, 0x50049001021104), + B(0x400048801142130, 0x4C1204100226010C), + B(0x80001048, 0x408800104000080), + B(0x8104868204040412, 0x22244202000081), + B(0x8104868204040412, 0x22244202000081), + B(0x4140001000240440, 0x80209004410004E), + B(0x800800000100, 0xB111820100000002), + B(0x404240004220, 0x2110402802050080), + B(0x284010400004040, 0x100245002502020), + B(0x14880A100114010, 0x400208080010024), + B(0x4100004040440648, 0x10030D838041A80), + B(0x32004000210, 0x4010225C88014000), + B(0x2240180C0800002, 0x4151050280000100), + B(0x2010A12002000042, 0x189051442010000), + B(0x4060050080121883, 0x8250C10001000141), + B(0x10000000044100, 0x8401084010261009), + B(0xA00028040000, 0x2003224000002000), + B(0x2060009001000020, 0x1000432022020228), + B(0x404200000883080, 0x1080800848245000), + B(0x240000402080, 0xCA0820814210502), + B(0x200040200080A02C, 0xB890290400080000), + B(0x800000000300482, 0x9203008100100013), + B(0x8000210202042000, 0x22642104004C2400), + B(0x1040400805000401, 0x2A0300102C80010), + B(0x8010A01088020000, 0x122106105A06A030), + B(0x8000C00001010494, 0x130A1A20404120), + B(0x4B084010844290, 0x10A08008900840), + B(0x1180001802460000, 0xB08000034C82004), + B(0x4001880060028029, 0x204040002401000), + B(0x8021A0001308002A, 0x97001822040040), + B(0xC00000009A020AC1, 0x1000080900400), + B(0x60010110001990, 0x4000880900400000), + B(0x10290402401200, 0x230080402C08), + B(0x4220000219012000, 0x140204804100008), + B(0x1400200100002, 0x8E62200414128), + B(0x402808502004403, 0x20049100C0284520), + B(0xB30041004280280, 0x10020464DB200308), + B(0x440010800808, 0xA0102E295812100), + B(0x10008000B000, 0x2000058583220200), + B(0x2000844000000100, 0x2000024C60800800), + B(0x110000400100028, 0x24052304508004), + B(0x8458000000840004, 0x118006463400001), + B(0x804008000040050, 0x41044890228000), + B(0x20000050000400, 0x80A101824A00086), + B(0x600080404000020, 0x100007322480005), + B(0xD082200020020008, 0x642000630120001), + B(0x10000100040230, 0x8048114733320002), + B(0x20200442002A880A, 0x8200002CB4B8052), + B(0x290080000000, 0xA41297838F40D), + B(0x800205000080, 0xF221232039874400), + B(0x1444002004880C20, 0xC4100049144200), + B(0x4500080000800, 0x280100002482C842), + B(0x281240881008, 0x204084004C101900), + B(0x1444002004880C20, 0xC4100049144200), + B(0x4500080000800, 0x280100002482C842), + B(0xC0010928430540, 0x92041902180), + B(0x1051001208A, 0x4900064800C20640), + B(0x882020418C00000, 0x30004040092A821), + B(0x224404002004268C, 0x202500204C7D254), + B(0x290080000000, 0xA41297838F40D), + }; + constexpr Bitboard JanggiElephantMagicInit[SQUARE_NB] = { + B(0xC502282200061400, 0x2D07081241D90200), + B(0xC502282200061400, 0x2D07081241D90200), + B(0x8084810022440C2, 0x81402202004), + B(0x80204010A800500, 0x5000021001740218), + B(0x8048100401208000, 0x2001000390000044), + B(0x202080020000000, 0x4010800010090424), + B(0x4081A0480073200, 0x100000A010406000), + B(0x4081A0480073200, 0x100000A010406000), + B(0x2040450004000C40, 0x8400000006302), + B(0x84010410018201, 0xA00A00000100000), + B(0x840091030C00040, 0x1400008200023400), + B(0x801058C0A0022, 0xC1920480010034), + B(0x80B4004800840800, 0x4080210A42040010), + B(0x400402221000445, 0x80321200408040), + B(0x4028142401012A00, 0x4005009000104448), + B(0x1440102040800220, 0x82800010A082000), + B(0x4100040300C00200, 0x800805100120000), + B(0x8200080061100, 0x2000101400000), + B(0x2000100410070001, 0x40818200B0900410), + B(0x400088020080000, 0x4A000402000CA0), + B(0x1402040410004000, 0x9840044504040), + B(0x20800088A00A0400, 0x1000020100180), + B(0x2001820520308201, 0x2008003404349000), + B(0x4004808022100, 0x8001000008081080), + B(0x102041041100425, 0x840400180B100104), + B(0x8806446000800214, 0x404402100010000), + B(0x8200141409C04101, 0x209030004A00D00), + B(0x8806004800880080, 0x1560004201000A01), + B(0x4200050600200090, 0x1CD0000000000421), + B(0x4820100022408100, 0x101404080320), + B(0x2A000A0A08080080, 0x1C02808000C2C0), + B(0x8808425040040014, 0x2021000100020), + B(0x5282104044A0020, 0x6B402104200008), + B(0x4001091040068120, 0x202000004003031), + B(0x4001091040068120, 0x202000004003031), + B(0x98040200A0214344, 0xA00300840010), + B(0x82508040A40808A, 0x40010000110042), + B(0x4400100101023, 0x450C8480040022), + B(0x210588880010800, 0x800A000108018102), + B(0x9400010144400, 0xC00010100018000), + B(0x20A0400100040004, 0x1242000101002040), + B(0x8022900040001001, 0x100000014000260), + B(0x51004124000A080, 0x40098400000002), + B(0x2158040001080022, 0x80009238401222), + B(0xA0103A0000802220, 0x20000200400010), + B(0x1101001208240, 0x100000800001064), + B(0x821020002090081, 0x5840D0010290280), + B(0x821020002090081, 0x5840D0010290280), + B(0x10400C1042000400, 0x4005000000440200), + B(0x844022008804820, 0x1000800100118000), + B(0x10802A9800800139, 0x4802840100842200), + B(0x4000A008200081, 0x4001100200402000), + B(0x200000008108400, 0x1000C00008080020), + B(0x120C11500100081, 0x440300308041100), + B(0x8080040080060100, 0xC00101B0040028), + B(0x901420A00110000, 0x8200010044700280), + B(0x140080080410000, 0x808040000C001001), + B(0x80210C0200A0008, 0x88088004600201), + B(0x8000004202020301, 0x2100142104002000), + B(0x1101011210004880, 0x8500840400000000), + B(0x40208802004800, 0x8080806009011240), + B(0x800000140408880, 0xC001018004060040), + B(0xC008080420500, 0x8024A10000000000), + B(0x2800000000400010, 0x44001C00400408), + B(0xA804008001200408, 0x202000020001000), + B(0xC08288805004080, 0x200042000800004), + B(0xA40A01000080012, 0x8800080042408), + B(0x2200100000100810, 0x800200010000100), + B(0x9881800004040001, 0x8058100100884004), + B(0x820000044020014, 0x4AA00010245012), + B(0x820000044020014, 0x4AA00010245012), + B(0x4000080240000808, 0x10100022054000), + B(0x5002000840101, 0x202020004000A00), + B(0x1188008200008402, 0x8088100020A2204), + B(0x304012004044080, 0x8028108818006010), + B(0x102210000008400, 0x1008000200380002), + B(0x51410E114200, 0x100C00084000000), + B(0x5001242320218, 0x800025000040040), + B(0x4008000200008190, 0x400020021000000), + B(0x10910022F0040, 0x450084400040001), + B(0x180010810000040, 0x4004100040040), + B(0x1088801424062010, 0x400084010030401), + B(0x3000120408000040, 0x10802001080A4051), + B(0x200008420, 0x40C0100020008804), + B(0x1048C000004000, 0x4220120804004000), + B(0x404A180000000E, 0x4C30412008110102), + B(0x400000404202005, 0x800808550EC40044), + B(0x282000200212010, 0x8001C0C102000210), + B(0x9012240000008100, 0x280CA04010040000), + B(0x2000C04001020C00, 0x2002010101042000), + B(0x1010000204408408, 0x8008004800E0C4A), + B(0x800286801000025, 0x8402401040050088), + B(0x40002000A0880000, 0x8400300108082086), + B(0x2080004404011, 0x20C080400100001), + B(0xB0010218100800, 0x8040200482C14103), + B(0x8011035000000C20, 0x4200044043200040), + B(0x804008000040050, 0x41044890228000), + B(0x80000400A0020020, 0x5308022021000000), + B(0x2118200000008004, 0x4141014004423D00), + B(0x90C0000200008040, 0x41041062000082), + B(0x1D000100941204, 0x12402001200420), + B(0x8C0040400400065, 0x22300B408100000), + B(0x8C0040400400065, 0x22300B408100000), + B(0x802802044600000, 0x1210100401030082), + B(0x9400488010000000, 0x8005404902040000), + B(0x2214020200001, 0x40102100820200), + B(0x2022000000800000, 0x6400440108480), + B(0x110000400100028, 0x24052304508004), + B(0x848820140010000, 0x201012500A000), + B(0x848820140010000, 0x201012500A000), + B(0x100100000000C4, 0x208004084048201), + B(0x100500000000290, 0x10102818208000), + B(0x2800414000C000, 0x20004005001301), + B(0x698180005101241, 0x10002014800210), + B(0x20000080000009, 0x440340C040), + B(0x1C0220200290020, 0x42100004004011C0), + B(0x200E620018320208, 0x440410402), + B(0xD04101010004024, 0x20000121104010A4), + B(0x220400000A80040, 0x806080020810010C), + B(0xA000200000000080, 0x1040801A0081208), + }; + constexpr Bitboard CannonDiagMagicInit[SQUARE_NB] = { + B(0x811801000400, 0x312260280280202), + B(0x44A000402022680, 0x1020224880420005), + B(0x8000C80800200880, 0x2000810060080C0), + B(0x2010300240428040, 0x40240002C004E30), + B(0x1018010404010004, 0x1001010018081E0), + B(0x2042040010080090, 0x100000008410300), + B(0x400080020102000, 0x4500005300000000), + B(0x2D00C80420010200, 0x804003280020008), + B(0x8038820024420, 0x6010010080012040), + B(0x1202028004200088, 0x50018100004000C6), + B(0xA02010F0410081, 0x20013001000009A), + B(0x4013002041030588, 0x4802004110000004), + B(0x110020802000081, 0x202001800908002), + B(0x22010404103, 0x2020882080491200), + B(0x60000220400580, 0x85902800100100), + B(0x100080800050100, 0x200010220021088), + B(0x8088840404200080, 0x140011040104000), + B(0x4008508080082015, 0x8010100200580048), + B(0x4010400420201001, 0x260002080A80808), + B(0xC2002004A0008008, 0x8020082000110840), + B(0xA000A0820042400, 0x810408082100420), + B(0x80231808100004, 0x204002000800400), + B(0x8296144044004900, 0x4A1003008001840), + B(0x80A0020A0011008, 0x800104846080810), + B(0x803800801041000, 0x1030500102000404), + B(0x240C00900800850, 0x1804000108810000), + B(0x800400000088800, 0x800021801020000), + B(0x84800409300082, 0x1002D40680044000), + B(0xA110C0000200010, 0x401010001200260), + B(0x8200160204100004, 0x8040004004002022), + B(0x10001000000100C0, 0x84002811000200), + B(0x2000080020014001, 0x42002020000102), + B(0x109040044020018, 0x2020400202001000), + B(0x620000CD0108, 0x40040201008000), + B(0xA1402200A0020, 0x81400400300912), + B(0x20020CF100018020, 0x801A14086404000), + B(0x800801844001, 0x11621488425000), + B(0x10201004A8080, 0x100A000801000010), + B(0x2800411001000800, 0x80224084900020), + B(0x40400024028100, 0x501000400230060), + B(0x404808010080, 0x1201000400100004), + B(0x80802005200, 0x2000200008A0000), + B(0x20800080000022, 0x80040810002010), + B(0x40016004808240, 0x400114000801100), + B(0x8410004204240, 0x20011000604050), + B(0x8000C1009008268, 0x201004000209000), + B(0x10240C000920, 0xE000A5C14003002), + B(0x10184024280008, 0x90240802000000), + B(0x40889081081000, 0x8010050008800000), + B(0x100008C089000019, 0x802032014020010), + B(0x401C1804C00, 0x402501002002020), + B(0x200022000920D0, 0x8000800081300020), + B(0x801000400011, 0x400100044010226), + B(0x4A04010100100000, 0x500400080400000), + B(0xA000050200080000, 0x8500090001010000), + B(0x40400040001812, 0x4403000400100A0), + B(0x20C2250203020004, 0x210001C000080000), + B(0x21000408C6020001, 0x4200830012D1001), + B(0x840082016080A210, 0x2400080801081008), + B(0x40001020000, 0x4041240200083120), + B(0x2C04030010C0818, 0xA670002000818100), + B(0x4704A07085000510, 0x914001000040), + B(0x900210304100100, 0x1010004000281840), + B(0x8202920002002040, 0x810012000003), + B(0x4001400100050, 0x1144000408002000), + B(0x5900200020008100, 0x40200020002004), + B(0x301020002000480, 0x202000C0004), + B(0x20D000201104040, 0x34840100020010), + B(0x800004200080408, 0x40184200100240), + B(0x8430080100404020, 0x90042100244500), + B(0x3800100010220062, 0x50404030200218), + B(0x42E20008002020, 0x2000008200200300), + B(0xE488008280A004, 0x200001010CC80000), + B(0x6018010041109810, 0x800002000242041A), + B(0x40A8002438980, 0x8000810008208009), + B(0x401000480040100, 0x286800404002212), + B(0x821030000100009, 0x2000090200A00000), + B(0x20000100C0008028, 0x5000000100400082), + B(0x80A000048030080, 0x200000120200008), + B(0x6300280800204003, 0x48000105C0040100), + B(0x83008802420C0200, 0x2008020200080100), + B(0x1050C3102200042, 0x20103900010008), + B(0x8040902021408180, 0x12000021806200A4), + B(0x3008204008C10004, 0x680110100010401), + B(0x204321100421000, 0x400E204820494000), + B(0x8000044022404048, 0x4024010090024021), + B(0x140201424050, 0x280A000130008000), + B(0x900340808004002, 0x21026008000380), + B(0x82808000300444, 0x20002000A2001141), + B(0x140180100406002, 0x4004480001000004), + B(0x4808420800841900, 0x14008C0041000000), + B(0x2008600009000480, 0x9008020001400000), + B(0x2000100800100002, 0x2004100820210020), + B(0x2062010401A8100, 0x12200108420090), + B(0x1403188200032, 0x40048166105000), + B(0x410020020140041, 0x4400348102940040), + B(0x414040209208041, 0x4402400028B004), + B(0x8008010100421202, 0x401418002008800), + B(0x4000020010062200, 0xA02009148048000), + B(0x4443080082008B, 0x104014022801010), + B(0x42B440A0C000800, 0x9001009016111020), + B(0x400000214002, 0x8008080209020009), + B(0x480C414A001900, 0x3400100400210200), + B(0x1006008800604, 0x20240004030A050), + B(0x4C022401002A8300, 0x405008400000600), + B(0x3104000800A1042, 0x2004800204406200), + B(0xA09010280008200C, 0x4004000208C4168), + B(0x2800401120C20120, 0x4A00450200022030), + B(0x88001800304C0200, 0x204288102080000), + B(0x8044004201440101, 0x400820080C024022), + B(0xA000100C080, 0x4B40341004008081), + B(0x94802001300810, 0x140206008000800), + B(0x40002020202820, 0x280680404000040), + B(0xA820800004200, 0x80E1401012000491), + B(0x804000010020C000, 0x9403020200802000), + B(0x8C0001284201400, 0xC000100C01620800), + B(0x4010004002200414, 0x403080080200000), + B(0x140400A100800101, 0x10054C031080400), + B(0x20012C2400880082, 0x7000880020C03200), + B(0x204040300004, 0x840800041101002), + }; + constexpr Bitboard NightriderMagicInit[SQUARE_NB] = { + B(0x8008100800020052, 0x8001440000000000), + B(0x24028400210090, 0x4200000000021), + B(0x22002020A0200800, 0x100820120000082), + B(0x424009020002200, 0x84810D4100002A), + B(0x404008020000600, 0x1000202000000081), + B(0x21020001000680, 0x2140200000000000), + B(0x2060021100000608, 0x8080020040000C8), + B(0x910C20408001200, 0x80A4030800000500), + B(0x2C40100010400009, 0x40400000200000C), + B(0x4090210034400004, 0x2104008000000), + B(0x1082008040840180, 0x140082080000000F), + B(0x8041001002101002, 0x8C002000000), + B(0x102205086800080, 0x84400030020D600), + B(0x2120080A32044E0, 0x400A02000016000), + B(0x6040003021040A0, 0x8000200206040040), + B(0x8001000008104082, 0x4002104202000114), + B(0x100400081080000, 0x201020020000020), + B(0x203000090010040, 0x140040010000000C), + B(0x9140000108100040, 0x4004004C08450010), + B(0x4402A12120020000, 0x450C002C000040C), + B(0x2028000200120800, 0x100100401001000), + B(0x800890081000, 0x10700A080180048), + B(0x41040C4008400, 0x8080001000080020), + B(0x2404000001060401, 0x10800116C0009110), + B(0x2001810020400110, 0xA0100800000480E2), + B(0x4081400120200218, 0x8800410800000000), + B(0x4C020020300E00, 0x810800000000808), + B(0xC8002200100108, 0x4860040380000100), + B(0x8000E0000804080, 0x4C800000000000), + B(0x211880010934041, 0x31000040000188), + B(0x4800801000003140, 0x80000000028), + B(0x61240410840004, 0x230100050000002), + B(0x61240410840004, 0x230100050000002), + B(0x4020008000C04C42, 0x3000021000000804), + B(0x829808040040840, 0x288010000002200), + B(0xA0420002040084, 0x1008604002000048), + B(0x10200004980, 0x2010410028000000), + B(0x4900200420100010, 0x1000A00401001898), + B(0x20000801600134, 0xC0002400000021C), + B(0x200040021010001, 0x8800000400000088), + B(0x88000824, 0x8800000040010000), + B(0x40000000200000D0, 0x8200004000AA84), + B(0x1002000000812002, 0x4000001C1008480), + B(0xC00010420003, 0x400002C00000004), + B(0x208400008008040, 0x111004280000C0), + B(0x60004000E800241, 0x202001000010040), + B(0x200004800061, 0x1109100480045800), + B(0x52880000200001, 0x608204100012DA0), + B(0x1000429008100800, 0x2008001000006018), + B(0x8490024200123001, 0x1041400080020), + B(0x810001020200014, 0x2C50500220000020), + B(0x20234B8000A0080, 0x4091200200000020), + B(0x400401010012600, 0xE00200000000020), + B(0x8040C00804004000, 0x8208014000000083), + B(0x102000410001044, 0x8200000000000), + B(0x102000410001044, 0x8200000000000), + B(0x242E00508040001, 0x80028000000000), + B(0x40810368002080, 0x4008080000224), + B(0x48200010208800, 0x1020800C000022), + B(0x220C4018008000, 0x282100130000211), + B(0x8008004400100, 0xC128208200020400), + B(0x44C004100020, 0x4008004000011000), + B(0x1008000000600008, 0x41C00180480183), + B(0x10080000, 0x404090018800020A), + B(0x4500000000004200, 0x45200200A0140), + B(0x2040000000240003, 0x8020080288000600), + B(0x2040000000240003, 0x8020080288000600), + B(0x2040000000240003, 0x8020080288000600), + B(0x48000000020A0080, 0x1000020200088), + B(0xA00000008404200, 0x4204010101001004), + B(0x10008030082, 0x2002800200100104), + B(0x44080002460010, 0x8007804100082D00), + B(0x98400040001802, 0x913002002020301), + B(0xA0400501040028, 0x8040481824002024), + B(0x4C0A0600100080A0, 0x8005000080008200), + B(0x802124000860008, 0x1009081800100080), + B(0x4000110040011000, 0x4080422200001081), + B(0x422008008000080, 0x4001020C00000202), + B(0x8000202008002101, 0xA008010000040), + B(0x804200020000020, 0xE0B0008010000001), + B(0x14040200002, 0x2000000006000004), + B(0x45000002010020, 0xA4040008400000), + B(0x800040020204184A, 0x10000402000006), + B(0x908000080400004, 0x5040100038400050), + B(0x3100002000040014, 0x804008008120000), + B(0x6002200000020008, 0xC011409081008881), + B(0xC02004060280001, 0x2003004010220254), + B(0x16410101, 0x100040108600000), + B(0x100000000200000, 0x201200010800200), + B(0xC400000004090002, 0x808808010224000A), + B(0xC400000004090002, 0x808808010224000A), + B(0x1000000000890001, 0x4050002001040), + B(0x618000000008484, 0x40000A0150000D4), + B(0x500000400009004, 0x1100200204100A1), + B(0x6006000008080480, 0x100254800C00904), + B(0x6006000008080480, 0x100254800C00904), + B(0x404808040044040, 0x4202020040000200), + B(0x2000080440810, 0x9044008844920000), + B(0x40204418040, 0x412000204080010), + B(0x88400820040248, 0x408040010C040800), + B(0x8110084000803, 0x102000000000), + B(0x4000040140001100, 0x80808102002), + B(0x100480044020008, 0x4601000100084488), + B(0x6200204420840, 0x41802043B0000), + B(0x540000010091010, 0x409004800A01208), + B(0x300040008000008, 0x2030080800010000), + B(0x408804010400090, 0x4008100800004000), + B(0x401600A0000080, 0x8400200201002200), + B(0x44000000100000, 0x1140040210000001), + B(0xC6000000D00804, 0x1050020C0081090C), + B(0x4098002, 0x900000C045008300), + B(0x245190008020, 0x2000C00100041000), + B(0x1000000A100046, 0x88000084800004), + B(0x1000000220240002, 0x400000401001088), + B(0x1000000220240002, 0x400000401001088), + B(0x820000118808000, 0x8000048161004A), + B(0x1100000000071A02, 0x500146102000048), + B(0x610000, 0x10010080040641), + B(0x2000010441A0044, 0x500800502020188), + B(0xA80000000180000, 0x234402012110080), + }; + constexpr Bitboard GrasshopperMagicHInit[SQUARE_NB] = { + B(0x120000880110000, 0x1008000000020020), + B(0x24200C080840A052, 0x2004004000010008), + B(0xC030024000228800, 0x4000010400000020), + B(0x1A0020802008802, 0x206010208000), + B(0x12002000D001024, 0x80100800090138), + B(0x4220010000241010, 0x3098000602001500), + B(0x401010004801040, 0x8000280480100000), + B(0x820082024921836, 0x220028000), + B(0x100400502411400, 0x220402120240D14), + B(0x880202020010404, 0xA80202510000), + B(0x140002801000018, 0x1000346490040), + B(0x120000880110000, 0x1008000000020020), + B(0xD01004008030400, 0x104000408104420), + B(0x8420060100020000, 0x800280400000120), + B(0x4010020018010, 0x40A00001100000), + B(0x40006A0004000200, 0x40000000110), + B(0xD01004008030400, 0x104000408104420), + B(0x8908A20028110011, 0x800080000001A114), + B(0x200042000080F009, 0x20001000004000), + B(0x2820008820100, 0x10002400058000B9), + B(0x6083100420008050, 0x4040012600280080), + B(0x216020000000446, 0x4080204000000211), + B(0x340140003002089, 0x2402008000000911), + B(0xD01004008030400, 0x104000408104420), + B(0x1404040B20001000, 0x8000824010800011), + B(0x8C0488120024214, 0x8414880202291), + B(0x1010000060050000, 0x4000004050002602), + B(0x4022983A0060000, 0x80000040010400), + B(0x1404040B20001000, 0x8000824010800011), + B(0x6020101802002840, 0x31000003000004), + B(0x9000420008840, 0x4881300000000210), + B(0xA200808865, 0x41C0048023000128), + B(0x31801100400000, 0x8802DC001221240), + B(0x884000080200920, 0x1004002410401001), + B(0x2400040000884, 0x421006208040C0), + B(0x1404040B20001000, 0x8000824010800011), + B(0x24100400060009, 0x112008025042410), + B(0x1800040009040200, 0x180000A1004E408A), + B(0x24100400060009, 0x112008025042410), + B(0x4060402008080, 0xC240080000110000), + B(0x20080100920020, 0x2002248010242052), + B(0x10001010802050, 0x880000001C98420), + B(0x4000800100420022, 0x502022010A00D0), + B(0x4C18104500200885, 0x400880800), + B(0x8080810081020090, 0x8000000000000), + B(0x8000062812080201, 0x8004C8300800), + B(0xC010220920198, 0x85000A08000), + B(0x24100400060009, 0x112008025042410), + B(0x80102204040, 0x1000000900000000), + B(0x2080000004202804, 0x120880003461), + B(0x102004090A4030, 0x801020589240), + B(0x20001100814000A0, 0x420202000820004), + B(0x100800000A000120, 0x208000800010000), + B(0x1008205000040802, 0x80002000400040), + B(0x1480000098008401, 0xA0010000581010), + B(0x30C0008200100820, 0x102800080904834), + B(0x4810821884000500, 0x4400000200000212), + B(0x1811D00128A0180, 0x2500848803000000), + B(0x41618A0300040040, 0x21200200A421801), + B(0x80102204040, 0x1000000900000000), + B(0xA1808E0100108000, 0x2008000505000002), + B(0x8C890020410000A0, 0xA010000048000400), + B(0x40006002210044, 0x600008000408000), + B(0x1200447220090042, 0x80001000160012), + B(0x48410010AB000000, 0x9200600000000100), + B(0x2040000000240003, 0x8020080288000600), + B(0x9080000088848088, 0x4010210500000041), + B(0xA1808E0100108000, 0x2008000505000002), + B(0x480100400024, 0x1004800018200000), + B(0x808403080080200, 0x802601000000500), + B(0x8C890020410000A0, 0xA010000048000400), + B(0xA1808E0100108000, 0x2008000505000002), + B(0x100A40000004008, 0x2800200400200480), + B(0x100A40000004008, 0x2800200400200480), + B(0x400014006000000, 0x10006000810001F5), + B(0xC410062001414, 0x820080041B01044), + B(0x20000800310, 0x430040000201000), + B(0xA40010008000008, 0x4002200028000040), + B(0xC00102000008021C, 0x10C2000A010E024), + B(0x80004200104008, 0x50A00800C400020), + B(0x20200080012542, 0x910F0040000402C0), + B(0xB040100504000300, 0x24802002000040), + B(0x800001000014008, 0x400031004000), + B(0x100A40000004008, 0x2800200400200480), + B(0x84008002041081C0, 0x8080500200000000), + B(0x440090001012001, 0x4020004010), + B(0x100A0028088020, 0x80040E00010020), + B(0x2180808000810, 0xB018040A00040000), + B(0x40C80920304C4001, 0x42800B200800000), + B(0x85000425001000, 0x4810048020001100), + B(0x600C000801000004, 0x8015084010200020), + B(0x20020050000240C0, 0x100202008600800), + B(0x38000050001220, 0x9200010200145900), + B(0x1042108040005, 0x1402A0802201001), + B(0x824240000C20400, 0x1000000400080010), + B(0x84008002041081C0, 0x8080500200000000), + B(0x400804A1000008, 0x1024104A0200010), + B(0x8000402308483, 0x20006020100100), + B(0x80880120000080, 0x8000240100084), + B(0x5840020004882001, 0x1004528000A00010), + B(0x8001018800300002, 0x84010040804), + B(0x180D10004000A008, 0xA001080008020004), + B(0x400080B, 0x10A0000004010000), + B(0x8080000200000, 0x2001000082004E0), + B(0x40040001000C2000, 0x2024800001004008), + B(0x400804A1000008, 0x1024104A0200010), + B(0x8000402308483, 0x20006020100100), + B(0x400804A1000008, 0x1024104A0200010), + B(0x2000200000, 0x1201011000802), + B(0x100100000000C4, 0x208004084048201), + B(0x400084000044, 0x100810140300), + B(0x29040C0C01010, 0x300204010820080), + B(0x1A808000020200, 0x1000000005210040), + B(0x20000400150000, 0x85008020), + B(0x40C040008184014, 0x8002AA00024010), + B(0x202000081B00804, 0x10001002008), + B(0x40011000210060, 0x6080C40000021004), + B(0x2000200000, 0x1201011000802), + B(0x4100480203840, 0x300080100804), + B(0x2000200000, 0x1201011000802), + }; + constexpr Bitboard GrasshopperMagicVInit[SQUARE_NB] = { + B(0x202000812104400, 0x24800B01C0000303), + B(0x340020400010D, 0x88060150C00400), + B(0x400802040609, 0x49010200501A0002), + B(0x8002680301000208, 0x628006C0C020200), + B(0x20400209001C0804, 0xA044000800143110), + B(0xC400082060010202, 0x4000480401014000), + B(0x22500200144040, 0x8204820084704C00), + B(0x8C1204009030020, 0x328400805000000), + B(0x84800800D0001640, 0x200080040060108), + B(0x804810208020040, 0x140010108020000), + B(0x1102010B008004, 0x300208006220020), + B(0x140080404A0A2428, 0x6308010100080), + B(0x20444002120408, 0xA080010508010001), + B(0x82011044000D02, 0x4112028620110809), + B(0x81010831000C02, 0x408802085000000), + B(0x81010831000C02, 0x408802085000000), + B(0x920008920600040, 0x8053801004000028), + B(0x81140283208300, 0x10040C004200420), + B(0x103080022201, 0xC01000081312620), + B(0x2200221100008, 0x1000408104000A4), + B(0x4402088080042008, 0x210401501040340), + B(0x898400202170001, 0x80040404208000), + B(0x20080004051012, 0x5100048200081800), + B(0x2320020000401018, 0x108501021040210), + B(0x21080410A422021, 0x83040180008800), + B(0x44E8100000408224, 0x20010008040400), + B(0x1800240002810405, 0x23004820000020), + B(0x80A0100400110, 0x80104020100C4028), + B(0x1002050001222C0, 0x5100818004024020), + B(0x104000200040, 0xC010A09800102000), + B(0x1020003A058120, 0x450900809000302), + B(0x40040045008B1, 0x202800400383010), + B(0x4640200220034, 0x8800485420304000), + B(0x5001042100084288, 0x110820001240080A), + B(0x2002C04004010120, 0xA15008020880001), + B(0x2800004080C4190, 0x890808280020080), + B(0x40C0401000104000, 0x2020880008002580), + B(0x40020C002400802, 0x801104010000000), + B(0x44842000040080, 0x2050011084000400), + B(0x4110040800000401, 0x2023810029008000), + B(0x20884000840, 0x8017102004008000), + B(0x10411104000480, 0x1414042000201001), + B(0x220040000008, 0x800306021000000), + B(0x41400A0008080, 0x501000298ACAD10), + B(0x800240012831810, 0x80120004468050E), + B(0x800005020801008, 0x20102400240000), + B(0x20C00040C114C010, 0x88080820200C00), + B(0x1044010100820081, 0x20080841004000), + B(0x8041048400022, 0x8020836040005002), + B(0x2001004010205, 0x8001002884042009), + B(0x128088400087, 0x20008002201002), + B(0x8084108040402000, 0x80809000A080400), + B(0x408081840880, 0x201002088000040), + B(0xA40180010280, 0x241004006000010), + B(0x4204100080048140, 0x2002C4F104202020), + B(0x100140A10204, 0x980200800840060), + B(0x1005140010202048, 0x1442280800202815), + B(0x2000082025008600, 0x1108400040600003), + B(0x1005050648000, 0x200020240008002), + B(0x202010208044000, 0x8210404060008), + B(0x8011040402000210, 0xC840180408016004), + B(0x404098801028, 0x80020A0001000400), + B(0x404098801028, 0x80020A0001000400), + B(0x80101002180140, 0x40C2080820000C0), + B(0x208202081260800, 0x14090E4C04000050), + B(0x4221201084004C2, 0x110480A011060), + B(0x8000008421090204, 0x1C01010800024), + B(0x8000008421090204, 0x1C01010800024), + B(0x200180C840088A0, 0x401100400820000), + B(0x10084043A021070, 0x202041600080200), + B(0x210E6202001040C, 0x10100800080B0), + B(0x848008021204002, 0x801004308100BAD), + B(0xC082C0390A000601, 0x4040080189008), + B(0x431200240210402D, 0x58102820000), + B(0x202020100A0019B0, 0x4010C0D018000000), + B(0x800800908402203, 0x102948C84C184), + B(0x26801100080845, 0x4009702022A00820), + B(0x8880520010401040, 0x1060084832052000), + B(0x100100022042081, 0x10000600008C121), + B(0x46020384100040, 0x800200320882021), + B(0xC0002010148, 0x4200800800040003), + B(0x2002208020090040, 0x40820210021410), + B(0x9000A41160002004, 0x2A09000100080043), + B(0x800004010008001, 0x1108002020104600), + B(0x800540C000A4E041, 0x18021180000401), + B(0x808200900A900202, 0x8364202140012005), + B(0x1DBA52000081010, 0x4008000023000010), + B(0x4100110204401481, 0x800040091020001C), + B(0x4100110204401481, 0x800040091020001C), + B(0x4101100020400482, 0x2000402302100120), + B(0x100408000A020212, 0xA000400111000020), + B(0x2000010488080104, 0x3000404410208100), + B(0x2684220180008DD0, 0x422040200004000A), + B(0x2021200C0424, 0x1010100000080200), + B(0x8908020020801006, 0x3010800020C2000), + B(0x4000030008062044, 0x244010202688000), + B(0x242101200408009, 0x8150040000200015), + B(0x42004C02180204, 0x210208014241040), + B(0x4E1A01C208410804, 0x8890041000012004), + B(0x2080200401000080, 0x8001098429008004), + B(0xA01400121804104, 0x280200C400000500), + B(0xD0080408040420, 0x1006040100224000), + B(0x28400205000800C9, 0x6021101401040075), + B(0x4000900040020104, 0x88129801100D0C), + B(0x8000004002180410, 0x400380200400204), + B(0x4002A430043008, 0x400200340100020), + B(0x401960004140A42, 0x100880710000464), + B(0x58014090102, 0xB8D30004010080), + B(0xA004C08000244000, 0x11280100E0000040), + B(0x2102008089208804, 0x110001004080040), + B(0x700010084E003004, 0x8080864112000D40), + B(0x4080881000200C20, 0x30324040880E0600), + B(0x2024A40401810820, 0x3000888002000000), + B(0x8200100400014, 0x4400340800252844), + B(0x24A00804288281, 0x410103002201140), + B(0x4080005022A08, 0x1000402200100264), + B(0x200080032244040, 0x200502189010001), + B(0x28108110404001, 0x400600120008412), + B(0xA00002102810020, 0xB1080240015408), + B(0x810080200806, 0x410440804080046), + }; + constexpr Bitboard GrasshopperMagicDInit[SQUARE_NB] = { + B(0x811801000400, 0x312260280280202), + B(0x44A000402022680, 0x1020224880420005), + B(0x8000C80800200880, 0x2000810060080C0), + B(0x2010300240428040, 0x40240002C004E30), + B(0x1018010404010004, 0x1001010018081E0), + B(0x2042040010080090, 0x100000008410300), + B(0x400080020102000, 0x4500005300000000), + B(0x2D00C80420010200, 0x804003280020008), + B(0x8038820024420, 0x6010010080012040), + B(0x1202028004200088, 0x50018100004000C6), + B(0xA02010F0410081, 0x20013001000009A), + B(0x4013002041030588, 0x4802004110000004), + B(0x110020802000081, 0x202001800908002), + B(0x22010404103, 0x2020882080491200), + B(0x60000220400580, 0x85902800100100), + B(0x100080800050100, 0x200010220021088), + B(0x8088840404200080, 0x140011040104000), + B(0x4008508080082015, 0x8010100200580048), + B(0x4010400420201001, 0x260002080A80808), + B(0xC2002004A0008008, 0x8020082000110840), + B(0xA000A0820042400, 0x810408082100420), + B(0x80231808100004, 0x204002000800400), + B(0x8296144044004900, 0x4A1003008001840), + B(0x80A0020A0011008, 0x800104846080810), + B(0x803800801041000, 0x1030500102000404), + B(0x240C00900800850, 0x1804000108810000), + B(0x800400000088800, 0x800021801020000), + B(0x84800409300082, 0x1002D40680044000), + B(0xA110C0000200010, 0x401010001200260), + B(0x8200160204100004, 0x8040004004002022), + B(0x10001000000100C0, 0x84002811000200), + B(0x2000080020014001, 0x42002020000102), + B(0x109040044020018, 0x2020400202001000), + B(0x620000CD0108, 0x40040201008000), + B(0xA1402200A0020, 0x81400400300912), + B(0x20020CF100018020, 0x801A14086404000), + B(0x800801844001, 0x11621488425000), + B(0x10201004A8080, 0x100A000801000010), + B(0x2800411001000800, 0x80224084900020), + B(0x40400024028100, 0x501000400230060), + B(0x404808010080, 0x1201000400100004), + B(0x80802005200, 0x2000200008A0000), + B(0x20800080000022, 0x80040810002010), + B(0x40016004808240, 0x400114000801100), + B(0x8410004204240, 0x20011000604050), + B(0x8000C1009008268, 0x201004000209000), + B(0x10240C000920, 0xE000A5C14003002), + B(0x10184024280008, 0x90240802000000), + B(0x40889081081000, 0x8010050008800000), + B(0x100008C089000019, 0x802032014020010), + B(0x401C1804C00, 0x402501002002020), + B(0x200022000920D0, 0x8000800081300020), + B(0x801000400011, 0x400100044010226), + B(0x4A04010100100000, 0x500400080400000), + B(0xA000050200080000, 0x8500090001010000), + B(0x40400040001812, 0x4403000400100A0), + B(0x20C2250203020004, 0x210001C000080000), + B(0x21000408C6020001, 0x4200830012D1001), + B(0x840082016080A210, 0x2400080801081008), + B(0x40001020000, 0x4041240200083120), + B(0x2C04030010C0818, 0xA670002000818100), + B(0x4704A07085000510, 0x914001000040), + B(0x900210304100100, 0x1010004000281840), + B(0x8202920002002040, 0x810012000003), + B(0x4001400100050, 0x1144000408002000), + B(0x5900200020008100, 0x40200020002004), + B(0x301020002000480, 0x202000C0004), + B(0x20D000201104040, 0x34840100020010), + B(0x800004200080408, 0x40184200100240), + B(0x8430080100404020, 0x90042100244500), + B(0x3800100010220062, 0x50404030200218), + B(0x42E20008002020, 0x2000008200200300), + B(0xE488008280A004, 0x200001010CC80000), + B(0x6018010041109810, 0x800002000242041A), + B(0x40A8002438980, 0x8000810008208009), + B(0x401000480040100, 0x286800404002212), + B(0x821030000100009, 0x2000090200A00000), + B(0x20000100C0008028, 0x5000000100400082), + B(0x80A000048030080, 0x200000120200008), + B(0x6300280800204003, 0x48000105C0040100), + B(0x83008802420C0200, 0x2008020200080100), + B(0x1050C3102200042, 0x20103900010008), + B(0x8040902021408180, 0x12000021806200A4), + B(0x3008204008C10004, 0x680110100010401), + B(0x204321100421000, 0x400E204820494000), + B(0x8000044022404048, 0x4024010090024021), + B(0x140201424050, 0x280A000130008000), + B(0x900340808004002, 0x21026008000380), + B(0x82808000300444, 0x20002000A2001141), + B(0x140180100406002, 0x4004480001000004), + B(0x4808420800841900, 0x14008C0041000000), + B(0x2008600009000480, 0x9008020001400000), + B(0x2000100800100002, 0x2004100820210020), + B(0x2062010401A8100, 0x12200108420090), + B(0x1403188200032, 0x40048166105000), + B(0x410020020140041, 0x4400348102940040), + B(0x414040209208041, 0x4402400028B004), + B(0x8008010100421202, 0x401418002008800), + B(0x4000020010062200, 0xA02009148048000), + B(0x4443080082008B, 0x104014022801010), + B(0x42B440A0C000800, 0x9001009016111020), + B(0x400000214002, 0x8008080209020009), + B(0x480C414A001900, 0x3400100400210200), + B(0x1006008800604, 0x20240004030A050), + B(0x4C022401002A8300, 0x405008400000600), + B(0x3104000800A1042, 0x2004800204406200), + B(0xA09010280008200C, 0x4004000208C4168), + B(0x2800401120C20120, 0x4A00450200022030), + B(0x88001800304C0200, 0x204288102080000), + B(0x8044004201440101, 0x400820080C024022), + B(0xA000100C080, 0x4B40341004008081), + B(0x94802001300810, 0x140206008000800), + B(0x40002020202820, 0x280680404000040), + B(0xA820800004200, 0x80E1401012000491), + B(0x804000010020C000, 0x9403020200802000), + B(0x8C0001284201400, 0xC000100C01620800), + B(0x4010004002200414, 0x403080080200000), + B(0x140400A100800101, 0x10054C031080400), + B(0x20012C2400880082, 0x7000880020C03200), + B(0x204040300004, 0x840800041101002), + }; +#undef B +#endif + +} // namespace Stockfish + +#endif // #ifndef MAGIC_H_INCLUDED \ No newline at end of file diff --git a/Fairy-Stockfish-master/src/main.cpp b/Fairy-Stockfish-master/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..afad7f13daea47719b3e3dbb78feaa7d1ca54975 --- /dev/null +++ b/Fairy-Stockfish-master/src/main.cpp @@ -0,0 +1,63 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bitboard.h" +#include "endgame.h" +#include "position.h" +#include "psqt.h" +#include "search.h" +#include "syzygy/tbprobe.h" +#include "thread.h" +#include "tt.h" +#include "uci.h" + +#include "piece.h" +#include "variant.h" +#include "xboard.h" + + +using namespace Stockfish; + +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(variants.find(Options["UCI_Variant"])->second); + Bitboards::init(); + Position::init(); + Bitbases::init(); + Endgames::init(); + Threads.set(size_t(Options["Threads"])); + Search::clear(); // After threads are up + Eval::NNUE::init(); + + UCI::loop(argc, argv); + + Threads.set(0); + variants.clear_all(); + pieceMap.clear_all(); + delete XBoard::stateMachine; + return 0; +} diff --git a/Fairy-Stockfish-master/src/material.cpp b/Fairy-Stockfish-master/src/material.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bea9d4676e5f40d1f785e87801fd28dff4d74506 --- /dev/null +++ b/Fairy-Stockfish-master/src/material.cpp @@ -0,0 +1,321 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include // For std::memset + +#include "material.h" +#include "thread.h" + +using namespace std; + +namespace Stockfish { + +namespace { + #define S(mg, eg) make_score(mg, eg) + + // Polynomial material imbalance parameters + + // One Score parameter for each pair (our piece, another of our pieces) + constexpr Score QuadraticOurs[][PIECE_TYPE_NB] = { + // OUR PIECE 2 + // bishop pair pawn knight bishop rook queen + {S(1419, 1455) }, // Bishop pair + {S( 101, 28), S( 37, 39) }, // Pawn + {S( 57, 64), S(249, 187), S(-49, -62) }, // Knight OUR PIECE 1 + {S( 0, 0), S(118, 137), S( 10, 27), S( 0, 0) }, // Bishop + {S( -63, -68), S( -5, 3), S(100, 81), S(132, 118), S(-246, -244) }, // Rook + {S(-210, -211), S( 37, 14), S(147, 141), S(161, 105), S(-158, -174), S(-9,-31) } // Queen + }; + + // One Score parameter for each pair (our piece, their piece) + constexpr Score QuadraticTheirs[][PIECE_TYPE_NB] = { + // THEIR PIECE + // bishop pair pawn knight bishop rook queen + { }, // Bishop pair + {S( 33, 30) }, // Pawn + {S( 46, 18), S(106, 84) }, // Knight OUR PIECE + {S( 75, 35), S( 59, 44), S( 60, 15) }, // Bishop + {S( 26, 35), S( 6, 22), S( 38, 39), S(-12, -2) }, // Rook + {S( 97, 93), S(100, 163), S(-58, -91), S(112, 192), S(276, 225) } // Queen + }; + + #undef S + + // Endgame evaluation and scaling functions are accessed directly and not through + // the function maps because they correspond to more than one material hash key. + Endgame EvaluateKFsPsK[] = { Endgame(WHITE), Endgame(BLACK) }; + Endgame EvaluateKXK[] = { Endgame(WHITE), Endgame(BLACK) }; + Endgame EvaluateKXKAtomic[] = { Endgame(WHITE), Endgame(BLACK) }; + Endgame EvaluateKXKDuck[] = { Endgame(WHITE), Endgame(BLACK) }; + Endgame EvaluateKXKXMisere[] = { Endgame(WHITE), Endgame(BLACK) }; + + Endgame ScaleKBPsK[] = { Endgame(WHITE), Endgame(BLACK) }; + Endgame ScaleKQKRPs[] = { Endgame(WHITE), Endgame(BLACK) }; + Endgame ScaleKPsK[] = { Endgame(WHITE), Endgame(BLACK) }; + Endgame ScaleKPKP[] = { Endgame(WHITE), Endgame(BLACK) }; + + // Helper used to detect a given material distribution + bool is_KFsPsK(const Position& pos, Color us) { + return (pos.promotion_piece_types(us) == piece_set(FERS)) + && !more_than_one(pos.pieces(~us)) + && (pos.count(us) || pos.count(us)) + && !(pos.count(us) - pos.count(us) - pos.count(us) - pos.count(us)); + } + + bool is_KXK(const Position& pos, Color us) { + return !more_than_one(pos.pieces(~us)) + && pos.non_pawn_material(us) >= std::min(RookValueMg, 2 * SilverValueMg); + } + + bool is_KXK_atomic(const Position& pos, Color us) { + return !more_than_one(pos.pieces(~us)) + && pos.non_pawn_material(us) >= RookValueMg + KnightValueMg; + } + + bool is_KXKX(const Position& pos, Color us) { + return pos.non_pawn_material(us) - pos.non_pawn_material(~us) > QueenValueMg; + } + + bool is_KBPsK(const Position& pos, Color us) { + return pos.non_pawn_material(us) == BishopValueMg + && pos.count(us) >= 1; + } + + bool is_KQKRPs(const Position& pos, Color us) { + return !pos.count(us) + && pos.non_pawn_material(us) == QueenValueMg + && pos.count(~us) == 1 + && pos.count(~us) >= 1; + } + + + /// imbalance() calculates the imbalance by comparing the piece count of each + /// piece type for both colors. + + template + Score imbalance(const Position& pos, const int pieceCount[][PIECE_TYPE_NB]) { + + constexpr Color Them = ~Us; + + Score bonus = SCORE_ZERO; + + // Second-degree polynomial material imbalance, by Tord Romstad + for (int pt1 = NO_PIECE_TYPE; pt1 <= QUEEN; ++pt1) + { + if (!pieceCount[Us][pt1]) + continue; + + int v = QuadraticOurs[pt1][pt1] * pieceCount[Us][pt1]; + + for (int pt2 = NO_PIECE_TYPE; pt2 < pt1; ++pt2) + v += QuadraticOurs[pt1][pt2] * pieceCount[Us][pt2] + + QuadraticTheirs[pt1][pt2] * pieceCount[Them][pt2]; + + bonus += pieceCount[Us][pt1] * v; + } + + if (pos.must_capture()) + bonus += (make_score(mg_value(QuadraticOurs[PAWN][PAWN]), 0) * pieceCount[Us][PAWN] + + 3 * QuadraticOurs[KNIGHT][PAWN] * pieceCount[Us][KNIGHT] + + QuadraticOurs[QUEEN][PAWN] * pieceCount[Us][QUEEN]) * pieceCount[Us][PAWN] + + make_score( mg_value(QuadraticOurs[KNIGHT][KNIGHT]), + -eg_value(QuadraticOurs[KNIGHT][KNIGHT])) * pieceCount[Us][KNIGHT] * pieceCount[Us][KNIGHT]; + else if (pos.blast_on_capture()) + bonus -= make_score(mg_value(QuadraticOurs[KNIGHT][PAWN]) * pieceCount[Us][KNIGHT] * pieceCount[Us][PAWN] / 2, 0); + else if (pos.check_counting()) + bonus -= 2 * QuadraticOurs[PAWN][PAWN] * pieceCount[Us][PAWN] * pieceCount[Us][PAWN]; + else if (pos.captures_to_hand()) + bonus -= bonus / 10; + + return bonus; + } + +} // namespace + +namespace Material { + + +/// Material::probe() looks up the current position's material configuration in +/// the material hash table. It returns a pointer to the Entry if the position +/// is found. Otherwise a new Entry is computed and stored there, so we don't +/// have to recompute all when the same material configuration occurs again. + +Entry* probe(const Position& pos) { + + Key key = pos.material_key(pos.endgame_eval()); + Entry* e = pos.this_thread()->materialTable[key]; + + if (e->key == key) + return e; + + std::memset(e, 0, sizeof(Entry)); + e->key = key; + e->factor[WHITE] = e->factor[BLACK] = (uint8_t)SCALE_FACTOR_NORMAL; + + Value npm_w = pos.non_pawn_material(WHITE); + Value npm_b = pos.non_pawn_material(BLACK); + Value npm = std::clamp(npm_w + npm_b, EndgameLimit, MidgameLimit); + + // Map total non-pawn material into [PHASE_ENDGAME, PHASE_MIDGAME] + if (pos.captures_to_hand() || pos.two_boards()) + { + Value npm2 = VALUE_ZERO; + for (PieceSet ps = pos.piece_types(); ps;) + { + PieceType pt = pop_lsb(ps); + npm2 += pos.count_in_hand(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.files() * pos.ranks()); + } + else + e->gamePhase = Phase(((npm - EndgameLimit) * PHASE_MIDGAME) / (MidgameLimit - EndgameLimit)); + + // 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. + if (pos.endgame_eval() && (e->evaluationFunction = Endgames::probe(key)) != nullptr) + return e; + + switch (pos.endgame_eval()) + { + case EG_EVAL_CHESS: + for (Color c : { WHITE, BLACK }) + if (is_KFsPsK(pos, c)) + { + e->evaluationFunction = &EvaluateKFsPsK[c]; + return e; + } + + for (Color c : { WHITE, BLACK }) + if (is_KXK(pos, c)) + { + e->evaluationFunction = &EvaluateKXK[c]; + return e; + } + + // OK, we didn't find any special evaluation function for the current material + // configuration. Is there a suitable specialized scaling function? + { + const auto* sf = Endgames::probe(key); + + if (sf) + { + e->scalingFunction[sf->strongSide] = sf; // Only strong color assigned + return e; + } + } + + // We didn't find any specialized scaling function, so fall back on generic + // ones that refer to more than one material distribution. Note that in this + // case we don't return after setting the function. + for (Color c : { WHITE, BLACK }) + { + if (is_KBPsK(pos, c)) + e->scalingFunction[c] = &ScaleKBPsK[c]; + + else if (is_KQKRPs(pos, c)) + e->scalingFunction[c] = &ScaleKQKRPs[c]; + } + + if (npm_w + npm_b == VALUE_ZERO && pos.pieces(PAWN)) // Only pawns on the board + { + if (!pos.count(BLACK)) + { + assert(pos.count(WHITE) >= 2); + + e->scalingFunction[WHITE] = &ScaleKPsK[WHITE]; + } + else if (!pos.count(WHITE)) + { + assert(pos.count(BLACK) >= 2); + + e->scalingFunction[BLACK] = &ScaleKPsK[BLACK]; + } + else if (pos.count(WHITE) == 1 && pos.count(BLACK) == 1) + { + // This is a special case because we set scaling functions + // for both colors instead of only one. + e->scalingFunction[WHITE] = &ScaleKPKP[WHITE]; + e->scalingFunction[BLACK] = &ScaleKPKP[BLACK]; + } + } + + // Zero or just one pawn makes it difficult to win, even with a small material + // advantage. This catches some trivial draws like KK, KBK and KNK and gives a + // drawish scale factor for cases such as KRKBP and KmmKm (except for KBBKN). + if (!pos.count(WHITE) && npm_w - npm_b <= BishopValueMg) + e->factor[WHITE] = uint8_t(npm_w < RookValueMg && pos.count(WHITE) <= 2 ? SCALE_FACTOR_DRAW : + npm_b <= BishopValueMg && pos.count(WHITE) <= 3 ? 4 : 14); + + if (!pos.count(BLACK) && npm_b - npm_w <= BishopValueMg) + e->factor[BLACK] = uint8_t(npm_b < RookValueMg && pos.count(BLACK) <= 2 ? SCALE_FACTOR_DRAW : + npm_w <= BishopValueMg && pos.count(BLACK) <= 3 ? 4 : 14); + break; + case EG_EVAL_ANTI: + break; + case EG_EVAL_ATOMIC: + for (Color c : { WHITE, BLACK }) + if (is_KXK_atomic(pos, c)) + { + e->evaluationFunction = &EvaluateKXKAtomic[c]; + return e; + } + break; + case EG_EVAL_DUCK: + for (Color c : { WHITE, BLACK }) + if (is_KXK(pos, c)) + { + e->evaluationFunction = &EvaluateKXKDuck[c]; + return e; + } + break; + case EG_EVAL_MISERE: + for (Color c : { WHITE, BLACK }) + if (is_KXKX(pos, c)) + { + e->evaluationFunction = &EvaluateKXKXMisere[c]; + return e; + } + break; + case EG_EVAL_RK: + break; + case NO_EG_EVAL: + break; + default: + assert(false); + } + + // Evaluate the material imbalance. We use PIECE_TYPE_NONE as a place holder + // for the bishop pair "extended piece", which allows us to be more flexible + // in defining bishop pair bonuses. + const int pieceCount[COLOR_NB][PIECE_TYPE_NB] = { + { pos.count(WHITE) > 1, pos.count(WHITE), pos.count(WHITE), + pos.count(WHITE) , pos.count(WHITE), pos.count(WHITE) }, + { pos.count(BLACK) > 1, pos.count(BLACK), pos.count(BLACK), + pos.count(BLACK) , pos.count(BLACK), pos.count(BLACK) } }; + + e->score = (imbalance(pos, pieceCount) - imbalance(pos, pieceCount)) / 16; + return e; +} + +} // namespace Material + +} // namespace Stockfish diff --git a/Fairy-Stockfish-master/src/material.h b/Fairy-Stockfish-master/src/material.h new file mode 100644 index 0000000000000000000000000000000000000000..1406c936b5fd17f1a7ae9ae039fc5f4fd5660003 --- /dev/null +++ b/Fairy-Stockfish-master/src/material.h @@ -0,0 +1,75 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef MATERIAL_H_INCLUDED +#define MATERIAL_H_INCLUDED + +#include "endgame.h" +#include "misc.h" +#include "position.h" +#include "types.h" + +namespace Stockfish::Material { + +/// Material::Entry contains various information about a material configuration. +/// It contains a material imbalance evaluation, a function pointer to a special +/// endgame evaluation function (which in most cases is NULL, meaning that the +/// standard evaluation function will be used), and scale factors. +/// +/// The scale factors are used to scale the evaluation score up or down. For +/// instance, in KRB vs KR endgames, the score is scaled down by a factor of 4, +/// which will result in scores of absolute value less than one pawn. + +struct Entry { + + Score imbalance() const { return score; } + Phase game_phase() const { return (Phase)gamePhase; } + bool specialized_eval_exists() const { return evaluationFunction != nullptr; } + Value evaluate(const Position& pos) const { return (*evaluationFunction)(pos); } + + int material_density() const { return materialDensity; } + + // scale_factor() takes a position and a color as input and returns a scale factor + // for the given color. We have to provide the position in addition to the color + // because the scale factor may also be a function which should be applied to + // the position. For instance, in KBP vs K endgames, the scaling function looks + // for rook pawns and wrong-colored bishops. + ScaleFactor scale_factor(const Position& pos, Color c) const { + ScaleFactor sf = scalingFunction[c] ? (*scalingFunction[c])(pos) + : SCALE_FACTOR_NONE; + return sf != SCALE_FACTOR_NONE ? sf : ScaleFactor(factor[c]); + } + + Key key; + const EndgameBase* evaluationFunction; + const EndgameBase* scalingFunction[COLOR_NB]; // Could be one for each + // side (e.g. KPKP, KBPsK) + Score score; + int16_t gamePhase; + uint8_t factor[COLOR_NB]; + + int materialDensity; +}; + +typedef HashTable Table; + +Entry* probe(const Position& pos); + +} // namespace Stockfish::Material + +#endif // #ifndef MATERIAL_H_INCLUDED diff --git a/Fairy-Stockfish-master/src/misc.cpp b/Fairy-Stockfish-master/src/misc.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3dad8000576735609cc8b0279d8e811dff74519e --- /dev/null +++ b/Fairy-Stockfish-master/src/misc.cpp @@ -0,0 +1,656 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifdef _WIN32 +#if _WIN32_WINNT < 0x0601 +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0601 // Force to include needed API prototypes +#endif + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include +// The needed Windows API for processor groups could be missed from old Windows +// versions, so instead of calling them directly (forcing the linker to resolve +// the calls at compile time), try to load them at runtime. To do this we need +// first to define the corresponding function pointers. +extern "C" { +typedef bool(*fun1_t)(LOGICAL_PROCESSOR_RELATIONSHIP, + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, PDWORD); +typedef bool(*fun2_t)(USHORT, PGROUP_AFFINITY); +typedef bool(*fun3_t)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY); +} +#endif + +#include +#include +#include +#include +#include +#include + +#if defined(__linux__) && !defined(__ANDROID__) +#include +#include +#endif + +#if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__) || (defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC) && !defined(_WIN32)) || defined(__e2k__) +#define POSIXALIGNEDALLOC +#include +#endif + +#include "misc.h" +#include "thread.h" + +using namespace std; + +namespace Stockfish { + +namespace { + +/// Version number. If Version is left empty, then compile date in the format +/// DD-MM-YY and show in engine_info. +const string Version = ""; + +/// Our fancy logging facility. The trick here is to replace cin.rdbuf() and +/// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We +/// can toggle the logging of std::cout and std:cin at runtime whilst preserving +/// usual I/O functionality, all without changing a single line of code! +/// Idea from http://groups.google.com/group/comp.lang.c++/msg/1d941c0f26ea0d81 + +struct Tie: public streambuf { // MSVC requires split streambuf for cin and cout + + Tie(streambuf* b, streambuf* l) : buf(b), logBuf(l) {} + + int sync() override { return logBuf->pubsync(), buf->pubsync(); } + int overflow(int c) override { return log(buf->sputc((char)c), "<< "); } + int underflow() override { return buf->sgetc(); } + int uflow() override { return log(buf->sbumpc(), ">> "); } + + streambuf *buf, *logBuf; + + int log(int c, const char* prefix) { + + static int last = '\n'; // Single log file + + if (last == '\n') + logBuf->sputn(prefix, 3); + + return last = logBuf->sputc((char)c); + } +}; + +class Logger { + + Logger() : in(cin.rdbuf(), file.rdbuf()), out(cout.rdbuf(), file.rdbuf()) {} + ~Logger() { start(""); } + + ofstream file; + Tie in, out; + +public: + static void start(const std::string& fname) { + + static Logger l; + + if (!fname.empty() && !l.file.is_open()) + { + l.file.open(fname, ifstream::out); + + if (!l.file.is_open()) + { + cerr << "Unable to open debug log file " << fname << endl; + exit(EXIT_FAILURE); + } + + cin.rdbuf(&l.in); + cout.rdbuf(&l.out); + } + else if (fname.empty() && l.file.is_open()) + { + cout.rdbuf(l.out.buf); + cin.rdbuf(l.in.buf); + l.file.close(); + } + } +}; + +} // namespace + + +/// engine_info() returns the full name of the current Stockfish version. This +/// will be either "Stockfish DD-MM-YY" (where DD-MM-YY is the date when +/// the program was compiled) or "Stockfish ", depending on whether +/// Version is empty. + +string engine_info(bool to_uci, bool to_xboard) { + + const string months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"); + string month, day, year; + stringstream ss, date(__DATE__); // From compiler, format is "Sep 21 2008" + + ss << "Fairy-Stockfish " << Version << setfill('0'); + + if (Version.empty()) + { + date >> month >> day >> year; + ss << setw(2) << day << setw(2) << (1 + months.find(month) / 4) << year.substr(2); + } + +#ifdef LARGEBOARDS + ss << " LB"; +#endif + + if (!to_xboard) + ss << (to_uci ? "\nid author ": " by ") + << "Fabian Fichter"; + + return ss.str(); +} + + +/// compiler_info() returns a string trying to describe the compiler we use + +std::string compiler_info() { + + #define stringify2(x) #x + #define stringify(x) stringify2(x) + #define make_version_string(major, minor, patch) stringify(major) "." stringify(minor) "." stringify(patch) + +/// Predefined macros hell: +/// +/// __GNUC__ Compiler is gcc, Clang or Intel on Linux +/// __INTEL_COMPILER Compiler is Intel +/// _MSC_VER Compiler is MSVC or Intel on Windows +/// _WIN32 Building on Windows (any) +/// _WIN64 Building on Windows 64 bit + + std::string compiler = "\nCompiled by "; + + #ifdef __clang__ + compiler += "clang++ "; + compiler += make_version_string(__clang_major__, __clang_minor__, __clang_patchlevel__); + #elif __INTEL_COMPILER + compiler += "Intel compiler "; + compiler += "(version "; + compiler += stringify(__INTEL_COMPILER) " update " stringify(__INTEL_COMPILER_UPDATE); + compiler += ")"; + #elif _MSC_VER + compiler += "MSVC "; + compiler += "(version "; + compiler += stringify(_MSC_FULL_VER) "." stringify(_MSC_BUILD); + compiler += ")"; + #elif defined(__e2k__) && defined(__LCC__) + #define dot_ver2(n) \ + compiler += (char)'.'; \ + compiler += (char)('0' + (n) / 10); \ + compiler += (char)('0' + (n) % 10); + + compiler += "MCST LCC "; + compiler += "(version "; + compiler += std::to_string(__LCC__ / 100); + dot_ver2(__LCC__ % 100) + dot_ver2(__LCC_MINOR__) + compiler += ")"; + #elif __GNUC__ + compiler += "g++ (GNUC) "; + compiler += make_version_string(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__); + #else + compiler += "Unknown compiler "; + compiler += "(unknown version)"; + #endif + + #if defined(__APPLE__) + compiler += " on Apple"; + #elif defined(__CYGWIN__) + compiler += " on Cygwin"; + #elif defined(__MINGW64__) + compiler += " on MinGW64"; + #elif defined(__MINGW32__) + compiler += " on MinGW32"; + #elif defined(__ANDROID__) + compiler += " on Android"; + #elif defined(__linux__) + compiler += " on Linux"; + #elif defined(_WIN64) + compiler += " on Microsoft Windows 64-bit"; + #elif defined(_WIN32) + compiler += " on Microsoft Windows 32-bit"; + #else + compiler += " on unknown system"; + #endif + + compiler += "\nCompilation settings include: "; + compiler += (Is64Bit ? " 64bit" : " 32bit"); + #if defined(USE_VNNI) + compiler += " VNNI"; + #endif + #if defined(USE_AVX512) + compiler += " AVX512"; + #endif + compiler += (HasPext ? " BMI2" : ""); + #if defined(USE_AVX2) + compiler += " AVX2"; + #endif + #if defined(USE_SSE41) + compiler += " SSE41"; + #endif + #if defined(USE_SSSE3) + compiler += " SSSE3"; + #endif + #if defined(USE_SSE2) + compiler += " SSE2"; + #endif + compiler += (HasPopCnt ? " POPCNT" : ""); + #if defined(USE_MMX) + compiler += " MMX"; + #endif + #if defined(USE_NEON) + compiler += " NEON"; + #endif + + #if !defined(NDEBUG) + compiler += " DEBUG"; + #endif + + compiler += "\n__VERSION__ macro expands to: "; + #ifdef __VERSION__ + compiler += __VERSION__; + #else + compiler += "(undefined macro)"; + #endif + compiler += "\n"; + + return compiler; +} + + +/// Debug functions used mainly to collect run-time statistics +static std::atomic hits[2], means[2]; + +void dbg_hit_on(bool b) { ++hits[0]; if (b) ++hits[1]; } +void dbg_hit_on(bool c, bool b) { if (c) dbg_hit_on(b); } +void dbg_mean_of(int v) { ++means[0]; means[1] += v; } + +void dbg_print() { + + if (hits[0]) + cerr << "Total " << hits[0] << " Hits " << hits[1] + << " hit rate (%) " << 100 * hits[1] / hits[0] << endl; + + if (means[0]) + cerr << "Total " << means[0] << " Mean " + << (double)means[1] / means[0] << endl; +} + + +/// Used to serialize access to std::cout to avoid multiple threads writing at +/// the same time. + +std::ostream& operator<<(std::ostream& os, SyncCout sc) { + + static std::mutex m; + + if (sc == IO_LOCK) + m.lock(); + + if (sc == IO_UNLOCK) + m.unlock(); + + return os; +} + + +/// Trampoline helper to avoid moving Logger to misc.h +void start_logger(const std::string& fname) { Logger::start(fname); } + + +/// prefetch() preloads the given address in L1/L2 cache. This is a non-blocking +/// function that doesn't stall the CPU waiting for data to be loaded from memory, +/// which can be quite slow. +#ifdef NO_PREFETCH + +void prefetch(void*) {} + +#else + +void prefetch(void* addr) { + +# if defined(__INTEL_COMPILER) + // This hack prevents prefetches from being optimized away by + // Intel compiler. Both MSVC and gcc seem not be affected by this. + __asm__ (""); +# endif + +# if defined(__INTEL_COMPILER) || defined(_MSC_VER) + _mm_prefetch((char*)addr, _MM_HINT_T0); +# else + __builtin_prefetch(addr); +# endif +} + +#endif + + +/// 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(). + +void* std_aligned_alloc(size_t alignment, size_t size) { + +#if defined(POSIXALIGNEDALLOC) + void *mem; + return posix_memalign(&mem, alignment, size) ? nullptr : mem; +#elif defined(_WIN32) + return _mm_malloc(size, alignment); +#else + return aligned_alloc(alignment, size); +#endif +} + +void std_aligned_free(void* ptr) { + +#if defined(POSIXALIGNEDALLOC) + free(ptr); +#elif defined(_WIN32) + _mm_free(ptr); +#else + free(ptr); +#endif +} + +/// aligned_large_pages_alloc() will return suitably aligned memory, if possible using large pages. + +#if defined(_WIN32) + +static void* aligned_large_pages_alloc_windows(size_t allocSize) { + + #if !defined(_WIN64) + return nullptr; + #else + + HANDLE hProcessToken { }; + LUID luid { }; + void* mem = nullptr; + + const size_t largePageSize = GetLargePageMinimum(); + if (!largePageSize) + return nullptr; + + // We need SeLockMemoryPrivilege, so try to enable it for the process + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken)) + return nullptr; + + if (LookupPrivilegeValue(NULL, SE_LOCK_MEMORY_NAME, &luid)) + { + TOKEN_PRIVILEGES tp { }; + TOKEN_PRIVILEGES prevTp { }; + DWORD prevTpLen = 0; + + tp.PrivilegeCount = 1; + tp.Privileges[0].Luid = luid; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + // Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() succeeds, + // we still need to query GetLastError() to ensure that the privileges were actually obtained. + if (AdjustTokenPrivileges( + hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, &prevTpLen) && + GetLastError() == ERROR_SUCCESS) + { + // Round up size to full pages and allocate + allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1); + mem = VirtualAlloc( + NULL, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE); + + // Privilege no longer needed, restore previous state + AdjustTokenPrivileges(hProcessToken, FALSE, &prevTp, 0, NULL, NULL); + } + } + + CloseHandle(hProcessToken); + + return mem; + + #endif +} + +void* aligned_large_pages_alloc(size_t allocSize) { + + // Try to allocate large pages + void* mem = aligned_large_pages_alloc_windows(allocSize); + + // Fall back to regular, page aligned, allocation if necessary + if (!mem) + mem = VirtualAlloc(NULL, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + + return mem; +} + +#else + +void* aligned_large_pages_alloc(size_t allocSize) { + +#if defined(__linux__) + constexpr size_t alignment = 2 * 1024 * 1024; // assumed 2MB page size +#else + constexpr size_t alignment = 4096; // assumed small page size +#endif + + // round up to multiples of alignment + size_t size = ((allocSize + alignment - 1) / alignment) * alignment; + void *mem = std_aligned_alloc(alignment, size); +#if defined(MADV_HUGEPAGE) + madvise(mem, size, MADV_HUGEPAGE); +#endif + return mem; +} + +#endif + + +/// aligned_large_pages_free() will free the previously allocated ttmem + +#if defined(_WIN32) + +void aligned_large_pages_free(void* mem) { + + if (mem && !VirtualFree(mem, 0, MEM_RELEASE)) + { + DWORD err = GetLastError(); + std::cerr << "Failed to free large page memory. Error code: 0x" + << std::hex << err + << std::dec << std::endl; + exit(EXIT_FAILURE); + } +} + +#else + +void aligned_large_pages_free(void *mem) { + std_aligned_free(mem); +} + +#endif + + +namespace WinProcGroup { + +#ifndef _WIN32 + +void bindThisThread(size_t) {} + +#else + +/// best_group() retrieves logical processor information using Windows specific +/// API and returns the best group id for the thread with index idx. Original +/// code from Texel by Peter Österlund. + +int best_group(size_t idx) { + + int threads = 0; + int nodes = 0; + int cores = 0; + DWORD returnLength = 0; + DWORD byteOffset = 0; + + // Early exit if the needed API is not available at runtime + HMODULE k32 = GetModuleHandle("Kernel32.dll"); + auto fun1 = (fun1_t)(void(*)())GetProcAddress(k32, "GetLogicalProcessorInformationEx"); + if (!fun1) + return -1; + + // First call to get returnLength. We expect it to fail due to null buffer + if (fun1(RelationAll, nullptr, &returnLength)) + return -1; + + // Once we know returnLength, allocate the buffer + SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *buffer, *ptr; + ptr = buffer = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)malloc(returnLength); + + // Second call, now we expect to succeed + if (!fun1(RelationAll, buffer, &returnLength)) + { + free(buffer); + return -1; + } + + while (byteOffset < returnLength) + { + if (ptr->Relationship == RelationNumaNode) + nodes++; + + else if (ptr->Relationship == RelationProcessorCore) + { + cores++; + threads += (ptr->Processor.Flags == LTP_PC_SMT) ? 2 : 1; + } + + assert(ptr->Size); + byteOffset += ptr->Size; + ptr = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)(((char*)ptr) + ptr->Size); + } + + free(buffer); + + std::vector groups; + + // Run as many threads as possible on the same node until core limit is + // reached, then move on filling the next node. + for (int n = 0; n < nodes; n++) + for (int i = 0; i < cores / nodes; i++) + groups.push_back(n); + + // In case a core has more than one logical processor (we assume 2) and we + // have still threads to allocate, then spread them evenly across available + // nodes. + for (int t = 0; t < threads - cores; t++) + groups.push_back(t % nodes); + + // If we still have more threads than the total number of logical processors + // then return -1 and let the OS to decide what to do. + return idx < groups.size() ? groups[idx] : -1; +} + + +/// bindThisThread() set the group affinity of the current thread + +void bindThisThread(size_t idx) { + + // Use only local variables to be thread-safe + int group = best_group(idx); + + if (group == -1) + return; + + // Early exit if the needed API are not available at runtime + HMODULE k32 = GetModuleHandle("Kernel32.dll"); + auto fun2 = (fun2_t)(void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMaskEx"); + auto fun3 = (fun3_t)(void(*)())GetProcAddress(k32, "SetThreadGroupAffinity"); + + if (!fun2 || !fun3) + return; + + GROUP_AFFINITY affinity; + if (fun2(group, &affinity)) + fun3(GetCurrentThread(), &affinity, nullptr); +} + +#endif + +} // namespace WinProcGroup + +#ifdef _WIN32 +#include +#define GETCWD _getcwd +#else +#include +#define GETCWD getcwd +#endif + +namespace CommandLine { + +string argv0; // path+name of the executable binary, as given by argv[0] +string binaryDirectory; // path of the executable directory +string workingDirectory; // path of the working directory + +void init(int argc, char* argv[]) { + (void)argc; + string pathSeparator; + + // extract the path+name of the executable binary + argv0 = argv[0]; + +#ifdef _WIN32 + pathSeparator = "\\"; + #ifdef _MSC_VER + // Under windows argv[0] may not have the extension. Also _get_pgmptr() had + // issues in some windows 10 versions, so check returned values carefully. + char* pgmptr = nullptr; + if (!_get_pgmptr(&pgmptr) && pgmptr != nullptr && *pgmptr) + argv0 = pgmptr; + #endif +#else + pathSeparator = "/"; +#endif + + // extract the working directory + workingDirectory = ""; + char buff[40000]; + char* cwd = GETCWD(buff, 40000); + if (cwd) + workingDirectory = cwd; + + // extract the binary directory path from argv0 + binaryDirectory = argv0; + size_t pos = binaryDirectory.find_last_of("\\/"); + if (pos == std::string::npos) + binaryDirectory = "." + pathSeparator; + else + binaryDirectory.resize(pos + 1); + + // pattern replacement: "./" at the start of path is replaced by the working directory + if (binaryDirectory.find("." + pathSeparator) == 0) + binaryDirectory.replace(0, 1, workingDirectory); +} + + +} // namespace CommandLine + +} // namespace Stockfish diff --git a/Fairy-Stockfish-master/src/misc.h b/Fairy-Stockfish-master/src/misc.h new file mode 100644 index 0000000000000000000000000000000000000000..03ab60936ee7590d12480ea9458fe50c81b5e829 --- /dev/null +++ b/Fairy-Stockfish-master/src/misc.h @@ -0,0 +1,200 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef MISC_H_INCLUDED +#define MISC_H_INCLUDED + +#include +#include +#include +#include +#include +#include + +#include "types.h" + +namespace Stockfish { + +std::string engine_info(bool to_uci = false, bool to_xboard = false); +std::string compiler_info(); +void prefetch(void* addr); +void start_logger(const std::string& fname); +void* std_aligned_alloc(size_t alignment, size_t size); +void std_aligned_free(void* ptr); +void* aligned_large_pages_alloc(size_t size); // memory aligned by page size, min alignment: 4096 bytes +void aligned_large_pages_free(void* mem); // nop if mem == nullptr + +void dbg_hit_on(bool b); +void dbg_hit_on(bool c, bool b); +void dbg_mean_of(int v); +void dbg_print(); + +typedef std::chrono::milliseconds::rep TimePoint; // A value in milliseconds +static_assert(sizeof(TimePoint) == sizeof(int64_t), "TimePoint should be 64 bits"); +inline TimePoint now() { + return std::chrono::duration_cast + (std::chrono::steady_clock::now().time_since_epoch()).count(); +} + +template +struct HashTable { + Entry* operator[](Key key) { return &table[(uint32_t)key & (Size - 1)]; } + +private: + std::vector table = std::vector(Size); // Allocate on the heap +}; + + +enum SyncCout { IO_LOCK, IO_UNLOCK }; +std::ostream& operator<<(std::ostream&, SyncCout); + +#define sync_cout std::cout << IO_LOCK +#define sync_endl std::endl << IO_UNLOCK + + +// align_ptr_up() : get the first aligned element of an array. +// ptr must point to an array of size at least `sizeof(T) * N + alignment` bytes, +// where N is the number of elements in the array. +template +T* align_ptr_up(T* ptr) +{ + static_assert(alignof(T) < Alignment); + + const uintptr_t ptrint = reinterpret_cast(reinterpret_cast(ptr)); + return reinterpret_cast(reinterpret_cast((ptrint + (Alignment - 1)) / Alignment * Alignment)); +} + + +// IsLittleEndian : true if and only if the binary is compiled on a little endian machine +static inline const union { uint32_t i; char c[4]; } Le = { 0x01020304 }; +static inline const bool IsLittleEndian = (Le.c[0] == 4); + + +template +class ValueListInserter { +public: + ValueListInserter(T* v, std::size_t& s) : + values(v), + size(&s) + { + } + + void push_back(const T& value) { values[(*size)++] = value; } +private: + T* values; + std::size_t* size; +}; + +template +class ValueList { + +public: + std::size_t size() const { return size_; } + void resize(std::size_t newSize) { size_ = newSize; } + void push_back(const T& value) { values_[size_++] = value; } + T& operator[](std::size_t index) { return values_[index]; } + T* begin() { return values_; } + T* end() { return values_ + size_; } + const T& operator[](std::size_t index) const { return values_[index]; } + const T* begin() const { return values_; } + const T* end() const { return values_ + size_; } + operator ValueListInserter() { return ValueListInserter(values_, size_); } + + void swap(ValueList& other) { + const std::size_t maxSize = std::max(size_, other.size_); + for (std::size_t i = 0; i < maxSize; ++i) { + std::swap(values_[i], other.values_[i]); + } + std::swap(size_, other.size_); + } + +private: + T values_[MaxSize]; + std::size_t size_ = 0; +}; + +/// xorshift64star Pseudo-Random Number Generator +/// This class is based on original code written and dedicated +/// to the public domain by Sebastiano Vigna (2014). +/// It has the following characteristics: +/// +/// - Outputs 64-bit numbers +/// - Passes Dieharder and SmallCrush test batteries +/// - Does not require warm-up, no zeroland to escape +/// - Internal state is a single 64-bit integer +/// - Period is 2^64 - 1 +/// - Speed: 1.60 ns/call (Core i7 @3.40GHz) +/// +/// For further analysis see +/// + +class PRNG { + + uint64_t s; + + uint64_t rand64() { + + s ^= s >> 12, s ^= s << 25, s ^= s >> 27; + return s * 2685821657736338717LL; + } + +public: + PRNG(uint64_t seed) : s(seed) { assert(seed); } + + template T rand() { return T(rand64()); } + + /// Special generator used to fast init magic numbers. + /// Output values only have 1/8th of their bits set on average. + template T sparse_rand() + { return T(rand64() & rand64() & rand64()); } +}; + +inline uint64_t mul_hi64(uint64_t a, uint64_t b) { +#if defined(__GNUC__) && defined(IS_64BIT) + __extension__ typedef unsigned __int128 uint128; + return ((uint128)a * (uint128)b) >> 64; +#else + uint64_t aL = (uint32_t)a, aH = a >> 32; + uint64_t bL = (uint32_t)b, bH = b >> 32; + uint64_t c1 = (aL * bL) >> 32; + uint64_t c2 = aH * bL + c1; + uint64_t c3 = aL * bH + (uint32_t)c2; + return aH * bH + (c2 >> 32) + (c3 >> 32); +#endif +} + +/// Under Windows it is not possible for a process to run on more than one +/// logical processor group. This usually means to be limited to use max 64 +/// cores. To overcome this, some special platform specific API should be +/// called to set group affinity for each thread. Original code from Texel by +/// Peter Österlund. + +namespace WinProcGroup { + void bindThisThread(size_t idx); +} + +namespace CommandLine { + void init(int argc, char* argv[]); + + extern std::string binaryDirectory; // path of the executable directory + extern std::string workingDirectory; // path of the working directory +} + +} // namespace Stockfish + +#endif // #ifndef MISC_H_INCLUDED diff --git a/Fairy-Stockfish-master/src/movegen.cpp b/Fairy-Stockfish-master/src/movegen.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a7b6a9f9f4fda8ab27edb9170538a4bef8844cf2 --- /dev/null +++ b/Fairy-Stockfish-master/src/movegen.cpp @@ -0,0 +1,528 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "movegen.h" +#include "position.h" + +namespace Stockfish { + +namespace { + + template + ExtMove* make_move_and_gating(const Position& pos, ExtMove* moveList, Color us, Square from, Square to, PieceType pt = NO_PIECE_TYPE) { + + // Wall placing moves + //if it's "wall or move", and they chose non-null move, skip even generating wall move + if (pos.walling() && !(pos.wall_or_move() && (from!=to))) + { + Bitboard b = pos.board_bb() & ~((pos.pieces() ^ from) | to); + if (T == CASTLING) + { + Square kto = make_square(to > from ? pos.castling_kingside_file() : pos.castling_queenside_file(), pos.castling_rank(us)); + Direction step = kto > from ? EAST : WEST; + Square rto = kto - step; + b ^= square_bb(to) ^ kto ^ rto; + } + if (T == EN_PASSANT) + b ^= pos.capture_square(to); + + if (pos.walling_rule() == ARROW) + b &= moves_bb(us, type_of(pos.piece_on(from)), to, pos.pieces() ^ from); + + //Any current or future wall variant must follow the walling region rule if set: + b &= pos.walling_region(us); + + if (pos.walling_rule() == PAST) + b &= square_bb(from); + if (pos.walling_rule() == EDGE) + { + Bitboard wallsquares = pos.state()->wallSquares; + + b &= (FileABB | file_bb(pos.max_file()) | Rank1BB | rank_bb(pos.max_rank())) | + ( shift(wallsquares) | shift(wallsquares) + | shift(wallsquares) | shift(wallsquares)); + } + while (b) + *moveList++ = make_gating(from, to, pt, pop_lsb(b)); + return moveList; + } + + *moveList++ = make(from, to, pt); + + // Gating moves + if (pos.seirawan_gating() && (pos.gates(us) & from)) + for (PieceSet ps = pos.piece_types(); ps;) + { + PieceType pt_gating = pop_lsb(ps); + if (pos.can_drop(us, pt_gating) && (pos.drop_region(us, pt_gating) & from)) + *moveList++ = make_gating(from, to, pt_gating, from); + } + if (pos.seirawan_gating() && T == CASTLING && (pos.gates(us) & to)) + for (PieceSet ps = pos.piece_types(); ps;) + { + PieceType pt_gating = pop_lsb(ps); + if (pos.can_drop(us, pt_gating) && (pos.drop_region(us, pt_gating) & to)) + *moveList++ = make_gating(from, to, pt_gating, to); + } + + return moveList; + } + + template + ExtMove* make_promotions(const Position& pos, ExtMove* moveList, Square to) { + + if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) + { + for (PieceSet promotions = pos.promotion_piece_types(c); promotions;) + { + PieceType pt = pop_msb(promotions); + if (!pos.promotion_limit(pt) || pos.promotion_limit(pt) > pos.count(c, pt)) + moveList = make_move_and_gating(pos, moveList, pos.side_to_move(), to - D, to, pt); + } + PieceType pt = pos.promoted_piece_type(PAWN); + if (pt && !(pos.piece_promotion_on_capture() && pos.empty(to))) + moveList = make_move_and_gating(pos, moveList, pos.side_to_move(), to - D, to); + } + + return moveList; + } + + template + ExtMove* generate_drops(const Position& pos, ExtMove* moveList, PieceType pt, Bitboard b) { + assert(Type != CAPTURES); + // Do not generate virtual drops for perft and at root + if (pos.can_drop(Us, pt) || (Type != NON_EVASIONS && pos.two_boards() && pos.allow_virtual_drop(Us, pt))) + { + // Restrict to valid target + b &= pos.drop_region(Us, pt); + + // Add to move list + if (pos.drop_promoted() && pos.promoted_piece_type(pt)) + { + Bitboard b2 = b; + if (Type == QUIET_CHECKS) + b2 &= pos.check_squares(pos.promoted_piece_type(pt)); + while (b2) + *moveList++ = make_drop(pop_lsb(b2), pt, pos.promoted_piece_type(pt)); + } + if (Type == QUIET_CHECKS || !pos.can_drop(Us, pt)) + b &= pos.check_squares(pt); + while (b) + *moveList++ = make_drop(pop_lsb(b), pt, pt); + } + + return moveList; + } + + template + ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard target) { + + if (!pos.pieces(Us, PAWN)) + return moveList; + + constexpr Color Them = ~Us; + constexpr Direction Up = pawn_push(Us); + constexpr Direction UpRight = (Us == WHITE ? NORTH_EAST : SOUTH_WEST); + constexpr Direction UpLeft = (Us == WHITE ? NORTH_WEST : SOUTH_EAST); + + const Bitboard promotionZone = pos.promotion_zone(Us); + const Bitboard standardPromotionZone = pos.sittuyin_promotion() ? Bitboard(0) : promotionZone; + const Bitboard doubleStepRegion = pos.double_step_region(Us); + const Bitboard tripleStepRegion = pos.triple_step_region(Us); + + const Bitboard pawns = pos.pieces(Us, PAWN); + const Bitboard movable = pos.board_bb(Us, PAWN) & ~pos.pieces(); + const Bitboard capturable = pos.board_bb(Us, PAWN) & pos.pieces(Them); + + target = Type == EVASIONS ? target : AllSquares; + + // Define single and double push, left and right capture, as well as respective promotion moves + Bitboard b1 = shift(pawns) & movable & target; + Bitboard b2 = shift(shift(pawns & doubleStepRegion) & movable) & movable & target; + Bitboard b3 = shift(shift(shift(pawns & tripleStepRegion) & movable) & movable) & movable & target; + Bitboard brc = shift(pawns) & capturable & target; + Bitboard blc = shift(pawns) & capturable & target; + + Bitboard b1p = b1 & standardPromotionZone; + Bitboard b2p = b2 & standardPromotionZone; + Bitboard b3p = b3 & standardPromotionZone; + Bitboard brcp = brc & standardPromotionZone; + Bitboard blcp = blc & standardPromotionZone; + + // Restrict regions based on rules and move generation type + if (pos.mandatory_pawn_promotion()) + { + b1 &= ~standardPromotionZone; + b2 &= ~standardPromotionZone; + b3 &= ~standardPromotionZone; + brc &= ~standardPromotionZone; + blc &= ~standardPromotionZone; + } + + if (Type == QUIET_CHECKS && pos.count(Them)) + { + // To make a quiet check, you either make a direct check by pushing a pawn + // or push a blocker pawn that is not on the same file as the enemy king. + // Discovered check promotion has been already generated amongst the captures. + Square ksq = pos.square(Them); + Bitboard dcCandidatePawns = pos.blockers_for_king(Them) & ~file_bb(ksq); + b1 &= pawn_attacks_bb(Them, ksq) | shift< Up>(dcCandidatePawns); + b2 &= pawn_attacks_bb(Them, ksq) | shift(dcCandidatePawns); + } + + // Single and double pawn pushes, no promotions + if (Type != CAPTURES) + { + while (b1) + { + Square to = pop_lsb(b1); + moveList = make_move_and_gating(pos, moveList, Us, to - Up, to); + } + + while (b2) + { + Square to = pop_lsb(b2); + moveList = make_move_and_gating(pos, moveList, Us, to - Up - Up, to); + } + + while (b3) + { + Square to = pop_lsb(b3); + moveList = make_move_and_gating(pos, moveList, Us, to - Up - Up - Up, to); + } + } + + // Promotions and underpromotions + while (brcp) + moveList = make_promotions(pos, moveList, pop_lsb(brcp)); + + while (blcp) + moveList = make_promotions(pos, moveList, pop_lsb(blcp)); + + while (b1p) + moveList = make_promotions(pos, moveList, pop_lsb(b1p)); + + while (b2p) + moveList = make_promotions(pos, moveList, pop_lsb(b2p)); + + while (b3p) + moveList = make_promotions(pos, moveList, pop_lsb(b3p)); + + // Sittuyin promotions + if (pos.sittuyin_promotion() && (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)) + { + // Pawns need to be in promotion zone if there is more than one pawn + Bitboard promotionPawns = pos.count(Us) > 1 ? pawns & promotionZone : pawns; + while (promotionPawns) + { + Square from = pop_lsb(promotionPawns); + for (PieceSet ps = pos.promotion_piece_types(Us); ps;) + { + PieceType pt = pop_msb(ps); + if (pos.promotion_limit(pt) && pos.promotion_limit(pt) <= pos.count(Us, pt)) + continue; + Bitboard b = ((pos.attacks_from(Us, pt, from) & ~pos.pieces()) | from) & target; + while (b) + { + Square to = pop_lsb(b); + if (!(attacks_bb(Us, pt, to, pos.pieces() ^ from) & pos.pieces(Them))) + *moveList++ = make(from, to, pt); + } + } + } + } + + // Standard and en passant captures + if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) + { + while (brc) + { + Square to = pop_lsb(brc); + moveList = make_move_and_gating(pos, moveList, Us, to - UpRight, to); + } + + while (blc) + { + Square to = pop_lsb(blc); + moveList = make_move_and_gating(pos, moveList, Us, to - UpLeft, to); + } + + for (Bitboard epSquares = pos.ep_squares() & ~pos.pieces(); epSquares; ) + { + Square epSquare = pop_lsb(epSquares); + + // An en passant capture cannot resolve a discovered check (unless there non-sliding riders) + if (Type == EVASIONS && (target & (epSquare + Up)) && !pos.non_sliding_riders()) + return moveList; + + Bitboard b = pawns & pawn_attacks_bb(Them, epSquare); + + // En passant square is already disabled for non-fairy variants if there is no attacker + assert(b || !pos.fast_attacks()); + + while (b) + moveList = make_move_and_gating(pos, moveList, Us, pop_lsb(b), epSquare); + } + } + + return moveList; + } + + + template + ExtMove* generate_moves(const Position& pos, ExtMove* moveList, PieceType Pt, Bitboard target) { + + assert(Pt != KING && Pt != PAWN); + + Bitboard bb = pos.pieces(Us, Pt); + + while (bb) + { + Square from = pop_lsb(bb); + + Bitboard attacks = pos.attacks_from(Us, Pt, from); + Bitboard quiets = pos.moves_from(Us, Pt, from); + Bitboard b = ( (attacks & pos.pieces()) + | (quiets & ~pos.pieces())); + Bitboard b1 = b & target; + Bitboard promotion_zone = pos.promotion_zone(Us); + PieceType promPt = pos.promoted_piece_type(Pt); + Bitboard b2 = promPt && (!pos.promotion_limit(promPt) || pos.promotion_limit(promPt) > pos.count(Us, promPt)) ? b1 : Bitboard(0); + Bitboard b3 = pos.piece_demotion() && pos.is_promoted(from) ? b1 : Bitboard(0); + Bitboard pawnPromotions = (pos.promotion_pawn_types(Us) & Pt) ? (b & (Type == EVASIONS ? target : ~pos.pieces(Us)) & promotion_zone) : Bitboard(0); + Bitboard epSquares = (pos.en_passant_types(Us) & Pt) ? (attacks & ~quiets & pos.ep_squares() & ~pos.pieces()) : Bitboard(0); + + // target squares considering pawn promotions + if (pawnPromotions && pos.mandatory_pawn_promotion()) + b1 &= ~pawnPromotions; + + // Restrict target squares considering promotion zone + if (b2 | b3) + { + if (pos.mandatory_piece_promotion()) + b1 &= (promotion_zone & from ? Bitboard(0) : ~promotion_zone) | (pos.piece_promotion_on_capture() ? ~pos.pieces() : Bitboard(0)); + // Exclude quiet promotions/demotions + if (pos.piece_promotion_on_capture()) + { + b2 &= pos.pieces(); + b3 &= pos.pieces(); + } + // Consider promotions/demotions into promotion zone + if (!(promotion_zone & from)) + { + b2 &= promotion_zone; + b3 &= promotion_zone; + } + } + + if (Type == QUIET_CHECKS) + { + b1 &= pos.check_squares(Pt); + if (b2) + b2 &= pos.check_squares(pos.promoted_piece_type(Pt)); + if (b3) + b3 &= pos.check_squares(type_of(pos.unpromoted_piece_on(from))); + } + + while (b1) + moveList = make_move_and_gating(pos, moveList, Us, from, pop_lsb(b1)); + + // Shogi-style piece promotions + while (b2) + *moveList++ = make(from, pop_lsb(b2)); + + // Piece demotions + while (b3) + *moveList++ = make(from, pop_lsb(b3)); + + // Pawn-style promotions + if ((Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) && pawnPromotions) + for (PieceSet ps = pos.promotion_piece_types(Us); ps;) + { + PieceType ptP = pop_msb(ps); + if (!pos.promotion_limit(ptP) || pos.promotion_limit(ptP) > pos.count(Us, ptP)) + for (Bitboard promotions = pawnPromotions; promotions; ) + moveList = make_move_and_gating(pos, moveList, pos.side_to_move(), from, pop_lsb(promotions), ptP); + } + + // En passant captures + if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) + while (epSquares) + moveList = make_move_and_gating(pos, moveList, Us, from, pop_lsb(epSquares)); + } + + return moveList; + } + + + template + ExtMove* generate_all(const Position& pos, ExtMove* moveList) { + + static_assert(Type != LEGAL, "Unsupported type in generate_all()"); + + constexpr bool Checks = Type == QUIET_CHECKS; // Reduce template instantiations + const Square ksq = pos.count(Us) ? pos.square(Us) : SQ_NONE; + Bitboard target; + + // Skip generating non-king moves when in double check + if (Type != EVASIONS || !more_than_one(pos.checkers() & ~pos.non_sliding_riders())) + { + target = Type == EVASIONS ? between_bb(ksq, lsb(pos.checkers())) + : Type == NON_EVASIONS ? ~pos.pieces( Us) + : Type == CAPTURES ? pos.pieces(~Us) + : ~pos.pieces( ); // QUIETS || QUIET_CHECKS + + if (Type == EVASIONS) + { + if (pos.checkers() & pos.non_sliding_riders()) + target = ~pos.pieces(Us); + // Leaper attacks can not be blocked + Square checksq = lsb(pos.checkers()); + if (LeaperAttacks[~Us][type_of(pos.piece_on(checksq))][checksq] & pos.square(Us)) + target = pos.checkers(); + } + + // Remove inaccessible squares (outside board + wall squares) + target &= pos.board_bb(); + + moveList = generate_pawn_moves(pos, moveList, target); + for (PieceSet ps = pos.piece_types() & ~(piece_set(PAWN) | KING); ps;) + moveList = generate_moves(pos, moveList, pop_lsb(ps), target); + // generate drops + if (pos.piece_drops() && Type != CAPTURES && (pos.can_drop(Us, ALL_PIECES) || pos.two_boards())) + for (PieceSet ps = pos.piece_types(); ps;) + moveList = generate_drops(pos, moveList, pop_lsb(ps), target & ~pos.pieces(~Us)); + + // Castling with non-king piece + if (!pos.count(Us) && Type != CAPTURES && pos.can_castle(Us & ANY_CASTLING)) + { + Square from = pos.castling_king_square(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) && Type != CAPTURES) + { + if (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 & ~pos.pieces())) + moveList = make_move_and_gating(pos, moveList, Us, from, to); + } + } + + // Workaround for passing: Execute a non-move with any piece + if (pos.pass(Us) && !pos.count(Us) && pos.pieces(Us)) + *moveList++ = make(lsb(pos.pieces(Us)), lsb(pos.pieces(Us))); + + //if "wall or move", generate walling action with null move + if (pos.wall_or_move()) + { + moveList = make_move_and_gating(pos, moveList, Us, lsb(pos.pieces(Us)), lsb(pos.pieces(Us))); + } + } + + // King moves + if (pos.count(Us) && (!Checks || pos.blockers_for_king(~Us) & ksq)) + { + Bitboard b = ( (pos.attacks_from(Us, KING, ksq) & pos.pieces()) + | (pos.moves_from(Us, KING, ksq) & ~pos.pieces())) & (Type == EVASIONS ? ~pos.pieces(Us) : target); + while (b) + moveList = make_move_and_gating(pos, moveList, Us, ksq, pop_lsb(b)); + + // Passing move by king + if (pos.pass(Us)) + *moveList++ = make(ksq, ksq); + + if ((Type == QUIETS || Type == NON_EVASIONS) && pos.can_castle(Us & ANY_CASTLING)) + 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,ksq, pos.castling_rook_square(cr)); + } + + return moveList; + } + +} // namespace + + +/// Generates all pseudo-legal captures plus queen promotions +/// Generates all pseudo-legal non-captures and underpromotions +/// Generates all pseudo-legal check evasions when the side to move is in check +/// Generates all pseudo-legal non-captures giving check, except castling and promotions +/// Generates all pseudo-legal captures and non-captures +/// +/// Returns a pointer to the end of the move list. + +template +ExtMove* generate(const Position& pos, ExtMove* moveList) { + + static_assert(Type != LEGAL, "Unsupported type in generate()"); + assert((Type == EVASIONS) == (bool)pos.checkers()); + + Color us = pos.side_to_move(); + + return us == WHITE ? generate_all(pos, moveList) + : generate_all(pos, moveList); +} + +// Explicit template instantiations +template ExtMove* generate(const Position&, ExtMove*); +template ExtMove* generate(const Position&, ExtMove*); +template ExtMove* generate(const Position&, ExtMove*); +template ExtMove* generate(const Position&, ExtMove*); +template ExtMove* generate(const Position&, ExtMove*); + + +/// generate generates all the legal moves in the given position + +template<> +ExtMove* generate(const Position& pos, ExtMove* moveList) { + + if (pos.is_immediate_game_end()) + return moveList; + + ExtMove* cur = moveList; + + moveList = pos.checkers() ? generate(pos, moveList) + : generate(pos, moveList); + while (cur != moveList) + if (!pos.legal(*cur) || pos.virtual_drop(*cur)) + *cur = (--moveList)->move; + else + ++cur; + + return moveList; +} + +} // namespace Stockfish diff --git a/Fairy-Stockfish-master/src/movegen.h b/Fairy-Stockfish-master/src/movegen.h new file mode 100644 index 0000000000000000000000000000000000000000..3047bf6cee6ab4d4f432f3f510491fb0764551a7 --- /dev/null +++ b/Fairy-Stockfish-master/src/movegen.h @@ -0,0 +1,107 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef MOVEGEN_H_INCLUDED +#define MOVEGEN_H_INCLUDED + +#include + +#include "types.h" + +namespace Stockfish { + +class Position; + +enum GenType { + CAPTURES, + QUIETS, + QUIET_CHECKS, + EVASIONS, + NON_EVASIONS, + LEGAL +}; + +struct ExtMove { + Move move; + int value; + + operator Move() const { return move; } + void operator=(Move m) { move = m; } + + // Inhibit unwanted implicit conversions to Move + // with an ambiguity that yields to a compile error. + operator float() const = delete; +}; + +inline bool operator<(const ExtMove& f, const ExtMove& s) { + return f.value < s.value; +} + +template +ExtMove* generate(const Position& pos, ExtMove* moveList); + +constexpr size_t moveListSize = sizeof(ExtMove) * MAX_MOVES; + +/// The MoveList struct is a simple wrapper around generate(). It sometimes comes +/// in handy to use this class instead of the low level generate() function. +template +struct MoveList { + + +#ifdef USE_HEAP_INSTEAD_OF_STACK_FOR_MOVE_LIST + explicit MoveList(const Position& pos) + { + this->moveList = (ExtMove*)malloc(moveListSize); + if (this->moveList == 0) + { + printf("Error: Failed to allocate memory in heap."); + exit(1); + } + this->last = generate(pos, this->moveList); + } + + ~MoveList() + { + free(this->moveList); + } +#else + explicit MoveList(const Position& pos) : last(generate(pos, moveList)) + { + ; + } +#endif + + const ExtMove* begin() const { return moveList; } + const ExtMove* end() const { return last; } + size_t size() const { return last - moveList; } + bool contains(Move move) const { + return std::find(begin(), end(), move) != end(); + } + +private: + ExtMove* last; +#ifdef USE_HEAP_INSTEAD_OF_STACK_FOR_MOVE_LIST + ExtMove* moveList = 0; +#else + ExtMove moveList[MAX_MOVES]; +#endif +}; + +} // namespace Stockfish + +#endif // #ifndef MOVEGEN_H_INCLUDED diff --git a/Fairy-Stockfish-master/src/movepick.cpp b/Fairy-Stockfish-master/src/movepick.cpp new file mode 100644 index 0000000000000000000000000000000000000000..eac77e8aa17a2b04c1b7217095625b18c3b9bd07 --- /dev/null +++ b/Fairy-Stockfish-master/src/movepick.cpp @@ -0,0 +1,279 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "movepick.h" + +namespace Stockfish { + +// Since continuation history grows quadratically with the number of piece types, +// we need to reserve a limited number of slots and map piece types to these slots +// in order to reduce memory consumption to a reasonable level. +int history_slot(Piece pc) { + return pc == NO_PIECE ? 0 : (type_of(pc) == KING ? PIECE_SLOTS - 1 : type_of(pc) % (PIECE_SLOTS - 1)) + color_of(pc) * PIECE_SLOTS; +} + +namespace { + + enum Stages { + MAIN_TT, CAPTURE_INIT, GOOD_CAPTURE, REFUTATION, QUIET_INIT, QUIET, BAD_CAPTURE, + EVASION_TT, EVASION_INIT, EVASION, + PROBCUT_TT, PROBCUT_INIT, PROBCUT, + QSEARCH_TT, QCAPTURE_INIT, QCAPTURE, QCHECK_INIT, QCHECK + }; + + // partial_insertion_sort() sorts moves in descending order up to and including + // a given limit. The order of moves smaller than the limit is left unspecified. + void partial_insertion_sort(ExtMove* begin, ExtMove* end, int limit) { + + for (ExtMove *sortedEnd = begin, *p = begin + 1; p < end; ++p) + if (p->value >= limit) + { + ExtMove tmp = *p, *q; + *p = *++sortedEnd; + for (q = sortedEnd; q != begin && *(q - 1) < tmp; --q) + *q = *(q - 1); + *q = tmp; + } + } + +} // namespace + + +/// Constructors of the MovePicker class. As arguments we pass information +/// to help it to return the (presumably) good moves first, to decide which +/// moves to return (in the quiescence search, for instance, we only want to +/// search captures, promotions, and some checks) and how important good move +/// ordering is at the current node. + +/// MovePicker constructor for the main search +MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, const GateHistory* dh, const LowPlyHistory* lp, + const CapturePieceToHistory* cph, const PieceToHistory** ch, Move cm, const Move* killers, int pl) + : pos(p), mainHistory(mh), gateHistory(dh), lowPlyHistory(lp), captureHistory(cph), continuationHistory(ch), + ttMove(ttm), refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}}, depth(d), ply(pl) { + + assert(d > 0); + + stage = (pos.checkers() ? EVASION_TT : MAIN_TT) + + !(ttm && pos.pseudo_legal(ttm)); +} + +/// MovePicker constructor for quiescence search +MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, const GateHistory* dh, + const CapturePieceToHistory* cph, const PieceToHistory** ch, Square rs) + : pos(p), mainHistory(mh), gateHistory(dh), captureHistory(cph), continuationHistory(ch), ttMove(ttm), recaptureSquare(rs), depth(d) { + + assert(d <= 0); + + stage = (pos.checkers() ? EVASION_TT : QSEARCH_TT) + + !( ttm + && (pos.checkers() || depth > DEPTH_QS_RECAPTURES || to_sq(ttm) == recaptureSquare) + && pos.pseudo_legal(ttm)); +} + +/// MovePicker constructor for ProbCut: we generate captures with SEE greater +/// than or equal to the given threshold. +MovePicker::MovePicker(const Position& p, Move ttm, Value th, const GateHistory* dh, const CapturePieceToHistory* cph) + : pos(p), gateHistory(dh), captureHistory(cph), ttMove(ttm), threshold(th) { + + assert(!pos.checkers()); + + stage = PROBCUT_TT + !(ttm && pos.capture(ttm) + && pos.pseudo_legal(ttm) + && pos.see_ge(ttm, threshold)); +} + +/// MovePicker::score() assigns a numerical value to each move in a list, used +/// for sorting. Captures are ordered by Most Valuable Victim (MVV), preferring +/// captures with a good history. Quiets moves are ordered using the histories. +template +void MovePicker::score() { + + static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type"); + + for (auto& m : *this) + if constexpr (Type == CAPTURES) + m.value = int(PieceValue[MG][pos.piece_on(to_sq(m))]) * 6 + + (*gateHistory)[pos.side_to_move()][gating_square(m)] + + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))]; + + else if constexpr (Type == QUIETS) + m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] + + (*gateHistory)[pos.side_to_move()][gating_square(m)] + + 2 * (*continuationHistory[0])[history_slot(pos.moved_piece(m))][to_sq(m)] + + (*continuationHistory[1])[history_slot(pos.moved_piece(m))][to_sq(m)] + + (*continuationHistory[3])[history_slot(pos.moved_piece(m))][to_sq(m)] + + (*continuationHistory[5])[history_slot(pos.moved_piece(m))][to_sq(m)] + + (ply < MAX_LPH ? std::min(4, depth / 3) * (*lowPlyHistory)[ply][from_to(m)] : 0); + + else // Type == EVASIONS + { + if (pos.capture(m)) + m.value = PieceValue[MG][pos.piece_on(to_sq(m))] + - Value(type_of(pos.moved_piece(m))); + else + m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] + + 2 * (*continuationHistory[0])[history_slot(pos.moved_piece(m))][to_sq(m)] + - (1 << 28); + } +} + +/// MovePicker::select() returns the next move satisfying a predicate function. +/// It never returns the TT move. +template +Move MovePicker::select(Pred filter) { + + while (cur < endMoves) + { + if (T == Best) + std::swap(*cur, *std::max_element(cur, endMoves)); + + if (*cur != ttMove && filter()) + return *cur++; + + cur++; + } + return MOVE_NONE; +} + +/// MovePicker::next_move() is the most important method of the MovePicker class. It +/// returns a new pseudo-legal move every time it is called until there are no more +/// moves left, picking the move with the highest score from a list of generated moves. +Move MovePicker::next_move(bool skipQuiets) { + +top: + switch (stage) { + + case MAIN_TT: + case EVASION_TT: + case QSEARCH_TT: + case PROBCUT_TT: + ++stage; + assert(pos.legal(ttMove) == MoveList(pos).contains(ttMove) || pos.virtual_drop(ttMove)); + return ttMove; + + case CAPTURE_INIT: + case PROBCUT_INIT: + case QCAPTURE_INIT: + cur = endBadCaptures = moves; + endMoves = generate(pos, cur); + + score(); + ++stage; + goto top; + + case GOOD_CAPTURE: + if (select([&](){ + return pos.see_ge(*cur, Value(-69 * cur->value / 1024 - 500 * (pos.captures_to_hand() && pos.gives_check(*cur))))? + // Move losing capture to endBadCaptures to be tried later + true : (*endBadCaptures++ = *cur, false); })) + return *(cur - 1); + + // Prepare the pointers to loop over the refutations array + cur = std::begin(refutations); + endMoves = std::end(refutations); + + // If the countermove is the same as a killer, skip it + if ( refutations[0].move == refutations[2].move + || refutations[1].move == refutations[2].move) + --endMoves; + + ++stage; + [[fallthrough]]; + + case REFUTATION: + if (select([&](){ return *cur != MOVE_NONE + && !pos.capture(*cur) + && pos.pseudo_legal(*cur); })) + return *(cur - 1); + ++stage; + [[fallthrough]]; + + case QUIET_INIT: + if (!skipQuiets && !(pos.must_capture() && pos.has_capture())) + { + cur = endBadCaptures; + endMoves = generate(pos, cur); + + score(); + partial_insertion_sort(cur, endMoves, -3000 * depth); + } + + ++stage; + [[fallthrough]]; + + case QUIET: + if ( !skipQuiets + && select([&](){return *cur != refutations[0].move + && *cur != refutations[1].move + && *cur != refutations[2].move;})) + return *(cur - 1); + + // Prepare the pointers to loop over the bad captures + cur = moves; + endMoves = endBadCaptures; + + ++stage; + [[fallthrough]]; + + case BAD_CAPTURE: + return select([](){ return true; }); + + case EVASION_INIT: + cur = moves; + endMoves = generate(pos, cur); + + score(); + ++stage; + [[fallthrough]]; + + case EVASION: + return select([](){ return true; }); + + case PROBCUT: + return select([&](){ return pos.see_ge(*cur, threshold); }); + + case QCAPTURE: + if (select([&](){ return depth > DEPTH_QS_RECAPTURES + || to_sq(*cur) == recaptureSquare; })) + return *(cur - 1); + + // If we did not find any move and we do not try checks, we have finished + if (depth != DEPTH_QS_CHECKS) + return MOVE_NONE; + + ++stage; + [[fallthrough]]; + + case QCHECK_INIT: + cur = moves; + endMoves = generate(pos, cur); + + ++stage; + [[fallthrough]]; + + case QCHECK: + return select([](){ return true; }); + } + + assert(false); + return MOVE_NONE; // Silence warning +} + +} // namespace Stockfish diff --git a/Fairy-Stockfish-master/src/movepick.h b/Fairy-Stockfish-master/src/movepick.h new file mode 100644 index 0000000000000000000000000000000000000000..9047ee742210ac34a3a2b42e08f15b0e91e8d3bc --- /dev/null +++ b/Fairy-Stockfish-master/src/movepick.h @@ -0,0 +1,169 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef MOVEPICK_H_INCLUDED +#define MOVEPICK_H_INCLUDED + +#include +#include +#include + +#include "movegen.h" +#include "position.h" +#include "types.h" + +namespace Stockfish { + +/// StatsEntry stores the stat table value. It is usually a number but could +/// be a move or even a nested history. We use a class instead of naked value +/// to directly call history update operator<<() on the entry so to use stats +/// tables at caller sites as simple multi-dim arrays. +template +class StatsEntry { + + T entry; + +public: + void operator=(const T& v) { entry = v; } + T* operator&() { return &entry; } + T* operator->() { return &entry; } + operator const T&() const { return entry; } + + void operator<<(int bonus) { + assert(abs(bonus) <= D); // Ensure range is [-D, D] + static_assert(D <= std::numeric_limits::max(), "D overflows T"); + + entry += bonus - entry * abs(bonus) / D; + + assert(abs(entry) <= D); + } +}; + +/// Stats is a generic N-dimensional array used to store various statistics. +/// The first template parameter T is the base type of the array, the second +/// template parameter D limits the range of updates in [-D, D] when we update +/// values with the << operator, while the last parameters (Size and Sizes) +/// encode the dimensions of the array. +template +struct Stats : public std::array, Size> +{ + typedef Stats stats; + + void fill(const T& v) { + + // For standard-layout 'this' points to first struct member + assert(std::is_standard_layout::value); + + typedef StatsEntry entry; + entry* p = reinterpret_cast(this); + std::fill(p, p + sizeof(*this) / sizeof(entry), v); + } +}; + +template +struct Stats : public std::array, Size> {}; + +/// In stats table, D=0 means that the template parameter is not used +enum StatsParams { NOT_USED = 0, PIECE_SLOTS = 8 }; +enum StatsType { NoCaptures, Captures }; + +/// ButterflyHistory records how often quiet moves have been successful or +/// 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 GateHistory; + +/// 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; + +/// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous +/// move, see www.chessprogramming.org/Countermove_Heuristic +typedef Stats CounterMoveHistory; + +/// CapturePieceToHistory is addressed by a move's [piece][to][captured piece type] +typedef Stats CapturePieceToHistory; + +/// PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to] +typedef Stats PieceToHistory; + +/// ContinuationHistory is the combined history of a given pair of moves, usually +/// the current one given a previous one. The nested history table is based on +/// PieceToHistory instead of ButterflyBoards. +typedef Stats ContinuationHistory; + +int history_slot(Piece pc); + +/// MovePicker class is used to pick one pseudo-legal move at a time from the +/// current position. The most important method is next_move(), which returns a +/// new pseudo-legal move each time it is called, until there are no moves left, +/// when MOVE_NONE is returned. In order to improve the efficiency of the +/// alpha-beta algorithm, MovePicker attempts to return the moves which are most +/// likely to get a cut-off first. +class MovePicker { + + enum PickType { Next, Best }; + +public: + MovePicker(const MovePicker&) = delete; + MovePicker& operator=(const MovePicker&) = delete; + MovePicker(const Position&, Move, Value, const GateHistory*, const CapturePieceToHistory*); + MovePicker(const Position&, Move, Depth, const ButterflyHistory*, + const GateHistory*, + const CapturePieceToHistory*, + const PieceToHistory**, + Square); + MovePicker(const Position&, Move, Depth, const ButterflyHistory*, + const GateHistory*, + const LowPlyHistory*, + const CapturePieceToHistory*, + const PieceToHistory**, + Move, + const Move*, + int); + Move next_move(bool skipQuiets = false); + +private: + template Move select(Pred); + template void score(); + ExtMove* begin() { return cur; } + ExtMove* end() { return endMoves; } + + const Position& pos; + const ButterflyHistory* mainHistory; + const GateHistory* gateHistory; + const LowPlyHistory* lowPlyHistory; + const CapturePieceToHistory* captureHistory; + const PieceToHistory** continuationHistory; + Move ttMove; + ExtMove refutations[3], *cur, *endMoves, *endBadCaptures; + int stage; + Square recaptureSquare; + Value threshold; + Depth depth; + int ply; + ExtMove moves[MAX_MOVES]; +}; + +} // namespace Stockfish + +#endif // #ifndef MOVEPICK_H_INCLUDED diff --git a/Fairy-Stockfish-master/src/nnue/evaluate_nnue.cpp b/Fairy-Stockfish-master/src/nnue/evaluate_nnue.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c13fe535f228ce7145db520d019525f9597bdeaf --- /dev/null +++ b/Fairy-Stockfish-master/src/nnue/evaluate_nnue.cpp @@ -0,0 +1,443 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Code for calculating NNUE evaluation function + +#include +#include +#include +#include +#include + +#include "../evaluate.h" +#include "../position.h" +#include "../misc.h" +#include "../uci.h" +#include "../types.h" + +#include "evaluate_nnue.h" + +namespace Stockfish::Eval::NNUE { + + // Input feature converter + LargePagePtr featureTransformer; + + // Evaluation function + AlignedPtr network[LayerStacks]; + + // Evaluation function file name + std::string fileName; + std::string netDescription; + + namespace Detail { + + // Initialize the evaluation function parameters + template + void initialize(AlignedPtr& pointer) { + + pointer.reset(reinterpret_cast(std_aligned_alloc(alignof(T), sizeof(T)))); + std::memset(pointer.get(), 0, sizeof(T)); + } + + template + void initialize(LargePagePtr& pointer) { + + static_assert(alignof(T) <= 4096, "aligned_large_pages_alloc() may fail for such a big alignment requirement of T"); + pointer.reset(reinterpret_cast(aligned_large_pages_alloc(sizeof(T)))); + std::memset(pointer.get(), 0, sizeof(T)); + } + + // Read evaluation function parameters + template + bool read_parameters(std::istream& stream, T& reference) { + + std::uint32_t header; + header = read_little_endian(stream); + if (!stream || header != T::get_hash_value()) return false; + return reference.read_parameters(stream); + } + + // Write evaluation function parameters + template + bool write_parameters(std::ostream& stream, const T& reference) { + + write_little_endian(stream, T::get_hash_value()); + return reference.write_parameters(stream); + } + + } // namespace Detail + + // Initialize the evaluation function parameters + void initialize() { + + Detail::initialize(featureTransformer); + for (std::size_t i = 0; i < LayerStacks; ++i) + Detail::initialize(network[i]); + } + + // Read network header + bool read_header(std::istream& stream, std::uint32_t* hashValue, std::string* desc) + { + std::uint32_t version, size; + + version = read_little_endian(stream); + *hashValue = read_little_endian(stream); + size = read_little_endian(stream); + if (!stream || version != Version) return false; + desc->resize(size); + stream.read(&(*desc)[0], size); + return !stream.fail(); + } + + // Write network header + bool write_header(std::ostream& stream, std::uint32_t hashValue, const std::string& desc) + { + write_little_endian(stream, Version); + write_little_endian(stream, hashValue); + write_little_endian(stream, desc.size()); + stream.write(&desc[0], desc.size()); + return !stream.fail(); + } + + // Read network parameters + bool read_parameters(std::istream& stream) { + + std::uint32_t hashValue; + if (!read_header(stream, &hashValue, &netDescription)) return false; + if (hashValue != HashValue) return false; + if (!Detail::read_parameters(stream, *featureTransformer)) return false; + for (std::size_t i = 0; i < LayerStacks; ++i) + if (!Detail::read_parameters(stream, *(network[i]))) return false; + return stream && stream.peek() == std::ios::traits_type::eof(); + } + + // Write network parameters + bool write_parameters(std::ostream& stream) { + + if (!write_header(stream, HashValue, netDescription)) return false; + if (!Detail::write_parameters(stream, *featureTransformer)) return false; + for (std::size_t i = 0; i < LayerStacks; ++i) + if (!Detail::write_parameters(stream, *(network[i]))) return false; + return (bool)stream; + } + + // Evaluation function. Perform differential calculation. + Value evaluate(const Position& pos, bool adjusted) { + + // We manually align the arrays on the stack because with gcc < 9.3 + // overaligning stack variables with alignas() doesn't work correctly. + + constexpr uint64_t alignment = CacheLineSize; + +#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) + TransformedFeatureType transformedFeaturesUnaligned[ + FeatureTransformer::BufferSize + alignment / sizeof(TransformedFeatureType)]; + char bufferUnaligned[Network::BufferSize + alignment]; + + auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); + auto* buffer = align_ptr_up(&bufferUnaligned[0]); +#else + alignas(alignment) + TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize]; + alignas(alignment) char buffer[Network::BufferSize]; +#endif + + ASSERT_ALIGNED(transformedFeatures, alignment); + ASSERT_ALIGNED(buffer, alignment); + + const std::size_t bucket = std::min((pos.count() - 1) * 8 / currentNnueVariant->nnueMaxPieces, 7); + const auto psqt = featureTransformer->transform(pos, transformedFeatures, bucket); + const auto output = network[bucket]->propagate(transformedFeatures, buffer); + + int materialist = psqt; + int positional = output[0]; + + int delta_npm = abs(pos.non_pawn_material(WHITE) - pos.non_pawn_material(BLACK)); + int entertainment = (adjusted && delta_npm <= BishopValueMg - KnightValueMg ? 7 : 0); + + int A = 128 - entertainment; + int B = 128 + entertainment; + + int sum = (A * materialist + B * positional) / 128; + + return static_cast( sum / OutputScale ); + } + + struct NnueEvalTrace { + static_assert(LayerStacks == PSQTBuckets); + + Value psqt[LayerStacks]; + Value positional[LayerStacks]; + std::size_t correctBucket; + }; + + static NnueEvalTrace trace_evaluate(const Position& pos) { + + // We manually align the arrays on the stack because with gcc < 9.3 + // overaligning stack variables with alignas() doesn't work correctly. + + constexpr uint64_t alignment = CacheLineSize; + +#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) + TransformedFeatureType transformedFeaturesUnaligned[ + FeatureTransformer::BufferSize + alignment / sizeof(TransformedFeatureType)]; + char bufferUnaligned[Network::BufferSize + alignment]; + + auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); + auto* buffer = align_ptr_up(&bufferUnaligned[0]); +#else + alignas(alignment) + TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize]; + alignas(alignment) char buffer[Network::BufferSize]; +#endif + + ASSERT_ALIGNED(transformedFeatures, alignment); + ASSERT_ALIGNED(buffer, alignment); + + NnueEvalTrace t{}; + t.correctBucket = std::min((pos.count() - 1) * 8 / currentNnueVariant->nnueMaxPieces, 7); + for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket) { + const auto psqt = featureTransformer->transform(pos, transformedFeatures, bucket); + const auto output = network[bucket]->propagate(transformedFeatures, buffer); + + int materialist = psqt; + int positional = output[0]; + + t.psqt[bucket] = static_cast( materialist / OutputScale ); + t.positional[bucket] = static_cast( positional / OutputScale ); + } + + return t; + } + + // Requires the buffer to have capacity for at least 5 values + static void format_cp_compact(Value v, char* buffer) { + + buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' '); + + int cp = std::abs(100 * v / PawnValueEg); + + if (cp >= 10000) + { + buffer[1] = '0' + cp / 10000; cp %= 10000; + buffer[2] = '0' + cp / 1000; cp %= 1000; + buffer[3] = '0' + cp / 100; cp %= 100; + buffer[4] = ' '; + } + else if (cp >= 1000) + { + buffer[1] = '0' + cp / 1000; cp %= 1000; + buffer[2] = '0' + cp / 100; cp %= 100; + buffer[3] = '.'; + buffer[4] = '0' + cp / 10; + } + else + { + buffer[1] = '0' + cp / 100; cp %= 100; + buffer[2] = '.'; + buffer[3] = '0' + cp / 10; cp %= 10; + buffer[4] = '0' + cp / 1; + } + } + + // Requires the buffer to have capacity for at least 7 values + static void format_cp_aligned_dot(Value v, char* buffer) { + buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' '); + + int cp = std::abs(100 * v / PawnValueEg); + + if (cp >= 10000) + { + buffer[1] = '0' + cp / 10000; cp %= 10000; + buffer[2] = '0' + cp / 1000; cp %= 1000; + buffer[3] = '0' + cp / 100; cp %= 100; + buffer[4] = '.'; + buffer[5] = '0' + cp / 10; cp %= 10; + buffer[6] = '0' + cp; + } + else if (cp >= 1000) + { + buffer[1] = ' '; + buffer[2] = '0' + cp / 1000; cp %= 1000; + buffer[3] = '0' + cp / 100; cp %= 100; + buffer[4] = '.'; + buffer[5] = '0' + cp / 10; cp %= 10; + buffer[6] = '0' + cp; + } + else + { + buffer[1] = ' '; + buffer[2] = ' '; + buffer[3] = '0' + cp / 100; cp %= 100; + buffer[4] = '.'; + buffer[5] = '0' + cp / 10; cp %= 10; + buffer[6] = '0' + cp / 1; + } + } + + + // trace() returns a string with the value of each piece on a board, + // and a table for (PSQT, Layers) values bucket by bucket. + + std::string trace(Position& pos) { + + std::stringstream ss; + + char board[3*RANK_NB+1][8*FILE_NB+2]; + std::memset(board, ' ', sizeof(board)); + for (int row = 0; row < 3*pos.ranks()+1; ++row) + board[row][8*FILE_NB+1] = '\0'; + + // A lambda to output one box of the board + auto writeSquare = [&board, &pos](File file, Rank rank, Piece pc, Value value) { + + const int x = ((int)file) * 8; + const int y = (pos.max_rank() - (int)rank) * 3; + for (int i = 1; i < 8; ++i) + board[y][x+i] = board[y+3][x+i] = '-'; + for (int i = 1; i < 3; ++i) + board[y+i][x] = board[y+i][x+8] = '|'; + board[y][x] = board[y][x+8] = board[y+3][x+8] = board[y+3][x] = '+'; + if (pc != NO_PIECE) + board[y+1][x+4] = pos.piece_to_char()[pc]; + if (value != VALUE_NONE) + format_cp_compact(value, &board[y+2][x+2]); + }; + + // We estimate the value of each piece by doing a differential evaluation from + // the current base eval, simulating the removal of the piece from its square. + Value base = evaluate(pos); + base = pos.side_to_move() == WHITE ? base : -base; + + for (File f = FILE_A; f <= pos.max_file(); ++f) + for (Rank r = RANK_1; r <= pos.max_rank(); ++r) + { + Square sq = make_square(f, r); + Piece pc = pos.piece_on(sq); + Piece unpromotedPc = pos.unpromoted_piece_on(sq); + bool isPromoted = pos.is_promoted(sq); + Value v = VALUE_NONE; + + if (pc != NO_PIECE && type_of(pc) != pos.nnue_king()) + { + auto st = pos.state(); + + pos.remove_piece(sq); + st->accumulator.computed[WHITE] = false; + st->accumulator.computed[BLACK] = false; + + Value eval = evaluate(pos); + eval = pos.side_to_move() == WHITE ? eval : -eval; + v = base - eval; + + pos.put_piece(pc, sq, isPromoted, unpromotedPc); + st->accumulator.computed[WHITE] = false; + st->accumulator.computed[BLACK] = false; + } + + writeSquare(f, r, pc, v); + } + + ss << " NNUE derived piece values:\n"; + for (int row = 0; row < 3*pos.ranks()+1; ++row) + ss << board[row] << '\n'; + ss << '\n'; + + auto t = trace_evaluate(pos); + + ss << " NNUE network contributions " + << (pos.side_to_move() == WHITE ? "(White to move)" : "(Black to move)") << std::endl + << "+------------+------------+------------+------------+\n" + << "| Bucket | Material | Positional | Total |\n" + << "| | (PSQT) | (Layers) | |\n" + << "+------------+------------+------------+------------+\n"; + + for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket) + { + char buffer[3][8]; + std::memset(buffer, '\0', sizeof(buffer)); + + format_cp_aligned_dot(t.psqt[bucket], buffer[0]); + format_cp_aligned_dot(t.positional[bucket], buffer[1]); + format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], buffer[2]); + + ss << "| " << bucket << " " + << " | " << buffer[0] << " " + << " | " << buffer[1] << " " + << " | " << buffer[2] << " " + << " |"; + if (bucket == t.correctBucket) + ss << " <-- this bucket is used"; + ss << '\n'; + } + + ss << "+------------+------------+------------+------------+\n"; + + return ss.str(); + } + + + // Load eval, from a file stream or a memory stream + bool load_eval(std::string name, std::istream& stream) { + + initialize(); + fileName = name; + return read_parameters(stream); + } + + // Save eval, to a file stream or a memory stream + bool save_eval(std::ostream& stream) { + + if (fileName.empty()) + return false; + + return write_parameters(stream); + } + + /// Save eval, to a file given by its name + bool save_eval(const std::optional& filename) { + + std::string actualFilename; + std::string msg; + + if (filename.has_value()) + actualFilename = filename.value(); + else + { + if (eval_file_loaded != EvalFileDefaultName) + { + msg = "Failed to export a net. A non-embedded net can only be saved if the filename is specified"; + + sync_cout << msg << sync_endl; + return false; + } + actualFilename = EvalFileDefaultName; + } + + std::ofstream stream(actualFilename, std::ios_base::binary); + bool saved = save_eval(stream); + + msg = saved ? "Network saved successfully to " + actualFilename + : "Failed to export a net"; + + sync_cout << msg << sync_endl; + return saved; + } + + +} // namespace Stockfish::Eval::NNUE diff --git a/Fairy-Stockfish-master/src/nnue/evaluate_nnue.h b/Fairy-Stockfish-master/src/nnue/evaluate_nnue.h new file mode 100644 index 0000000000000000000000000000000000000000..2e4f1f5098db640edfcfc680dc75ff8afeac83ad --- /dev/null +++ b/Fairy-Stockfish-master/src/nnue/evaluate_nnue.h @@ -0,0 +1,59 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// header used in NNUE evaluation function + +#ifndef NNUE_EVALUATE_NNUE_H_INCLUDED +#define NNUE_EVALUATE_NNUE_H_INCLUDED + +#include "nnue_feature_transformer.h" + +#include + +namespace Stockfish::Eval::NNUE { + + // Hash value of evaluation function structure + constexpr std::uint32_t HashValue = + FeatureTransformer::get_hash_value() ^ Network::get_hash_value(); + + // Deleter for automating release of memory area + template + struct AlignedDeleter { + void operator()(T* ptr) const { + ptr->~T(); + std_aligned_free(ptr); + } + }; + + template + struct LargePageDeleter { + void operator()(T* ptr) const { + ptr->~T(); + aligned_large_pages_free(ptr); + } + }; + + template + using AlignedPtr = std::unique_ptr>; + + template + using LargePagePtr = std::unique_ptr>; + +} // namespace Stockfish::Eval::NNUE + +#endif // #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED diff --git a/Fairy-Stockfish-master/src/nnue/features/half_ka_v2.cpp b/Fairy-Stockfish-master/src/nnue/features/half_ka_v2.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6850c0c1728e6b8df7225d70a60d3e8ac472f1ba --- /dev/null +++ b/Fairy-Stockfish-master/src/nnue/features/half_ka_v2.cpp @@ -0,0 +1,90 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +//Definition of input features HalfKAv2 of NNUE evaluation function + +#include "half_ka_v2.h" + +#include "../../position.h" + +namespace Stockfish::Eval::NNUE::Features { + + // Map square to numbering on 8x8 board + constexpr Square to_chess_square(Square s) { + return Square(s - rank_of(s) * (FILE_MAX - FILE_H)); + } + + // Orient a square according to perspective (rotates by 180 for black) + inline Square HalfKAv2::orient(Color perspective, Square s) { + return Square(int(to_chess_square(s)) ^ (bool(perspective) * 56)); + } + + // Index of a feature for a given king position and another piece on some square + inline IndexType HalfKAv2::make_index(Color perspective, Square s, Piece pc, Square ksq) { + return IndexType(orient(perspective, s) + PieceSquareIndex[perspective][pc] + PS_NB * ksq); + } + + // Get a list of indices for active features + void HalfKAv2::append_active_indices( + const Position& pos, + Color perspective, + ValueListInserter active + ) { + Square ksq = orient(perspective, pos.square(perspective)); + Bitboard bb = pos.pieces(); + while (bb) + { + Square s = pop_lsb(bb); + active.push_back(make_index(perspective, s, pos.piece_on(s), ksq)); + } + } + + + // append_changed_indices() : get a list of indices for recently changed features + + void HalfKAv2::append_changed_indices( + Square ksq, + StateInfo* st, + Color perspective, + ValueListInserter removed, + ValueListInserter added + ) { + const auto& dp = st->dirtyPiece; + Square oriented_ksq = orient(perspective, ksq); + for (int i = 0; i < dp.dirty_num; ++i) { + Piece pc = dp.piece[i]; + if (dp.from[i] != SQ_NONE) + removed.push_back(make_index(perspective, dp.from[i], pc, oriented_ksq)); + if (dp.to[i] != SQ_NONE) + added.push_back(make_index(perspective, dp.to[i], pc, oriented_ksq)); + } + } + + int HalfKAv2::update_cost(StateInfo* st) { + return st->dirtyPiece.dirty_num; + } + + int HalfKAv2::refresh_cost(const Position& pos) { + return pos.count(); + } + + bool HalfKAv2::requires_refresh(StateInfo* st, Color perspective) { + return st->dirtyPiece.piece[0] == make_piece(perspective, KING); + } + +} // namespace Stockfish::Eval::NNUE::Features diff --git a/Fairy-Stockfish-master/src/nnue/features/half_ka_v2.h b/Fairy-Stockfish-master/src/nnue/features/half_ka_v2.h new file mode 100644 index 0000000000000000000000000000000000000000..1ca8129fb989df690432c9df3305bfc8a80b1840 --- /dev/null +++ b/Fairy-Stockfish-master/src/nnue/features/half_ka_v2.h @@ -0,0 +1,152 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +//Definition of input features HalfKAv2 of NNUE evaluation function + +#ifndef NNUE_FEATURES_HALF_KA_V2_H_INCLUDED +#define NNUE_FEATURES_HALF_KA_V2_H_INCLUDED + +#include "../nnue_common.h" + +#include "../../evaluate.h" +#include "../../misc.h" + +namespace Stockfish { + struct StateInfo; +} + +namespace Stockfish::Eval::NNUE::Features { + + // Feature HalfKAv2: Combination of the position of own king + // and the position of pieces + class HalfKAv2 { + + // unique number for each piece type on each square + enum { + PS_NONE = 0, + PS_W_PAWN = 0, + PS_B_PAWN = 1 * SQUARE_NB_CHESS, + PS_W_KNIGHT = 2 * SQUARE_NB_CHESS, + PS_B_KNIGHT = 3 * SQUARE_NB_CHESS, + PS_W_BISHOP = 4 * SQUARE_NB_CHESS, + PS_B_BISHOP = 5 * SQUARE_NB_CHESS, + PS_W_ROOK = 6 * SQUARE_NB_CHESS, + PS_B_ROOK = 7 * SQUARE_NB_CHESS, + PS_W_QUEEN = 8 * SQUARE_NB_CHESS, + PS_B_QUEEN = 9 * SQUARE_NB_CHESS, + PS_KING = 10 * SQUARE_NB_CHESS, + PS_NB = 11 * SQUARE_NB_CHESS + }; + + static constexpr uint32_t PieceSquareIndex[COLOR_NB][PIECE_NB] = { + // convention: W - us, B - them + // viewed from other side, W and B are reversed + { + PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_NONE, PS_NONE, + PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, + PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, + PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, + PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, + PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, + PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, + PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_KING, + + PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_NONE, PS_NONE, + PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, + PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, + PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, + PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, + PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, + PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, + PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_KING, + }, + + { + PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_NONE, PS_NONE, + PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, + PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, + PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, + PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, + PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, + PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, + PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_KING, + + PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_NONE, PS_NONE, + PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, + PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, + PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, + PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, + PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, + PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, + PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_KING, + } + }; + // Check that the fragile array definition is correct + static_assert(PieceSquareIndex[WHITE][make_piece(WHITE, PAWN)] == PS_W_PAWN); + static_assert(PieceSquareIndex[WHITE][make_piece(WHITE, KING)] == PS_KING); + static_assert(PieceSquareIndex[WHITE][make_piece(BLACK, PAWN)] == PS_B_PAWN); + static_assert(PieceSquareIndex[WHITE][make_piece(BLACK, KING)] == PS_KING); + + + // Orient a square according to perspective (rotates by 180 for black) + static Square orient(Color perspective, Square s); + + // Index of a feature for a given king position and another piece on some square + static IndexType make_index(Color perspective, Square s, Piece pc, Square ksq); + + public: + // Feature name + static constexpr const char* Name = "HalfKAv2(Friend)"; + + // Hash value embedded in the evaluation file + static constexpr std::uint32_t HashValue = 0x5f234cb8u; + + // Number of feature dimensions + static constexpr IndexType Dimensions = + static_cast(SQUARE_NB_CHESS) * static_cast(PS_NB); + + // Maximum number of simultaneously active features. + static constexpr IndexType MaxActiveDimensions = 32; + + // Get a list of indices for active features + static void append_active_indices( + const Position& pos, + Color perspective, + ValueListInserter active); + + // Get a list of indices for recently changed features + static void append_changed_indices( + Square ksq, + StateInfo* st, + Color perspective, + ValueListInserter removed, + ValueListInserter added); + + // Returns the cost of updating one perspective, the most costly one. + // Assumes no refresh needed. + static int update_cost(StateInfo* st); + static int refresh_cost(const Position& pos); + + // Returns whether the change stored in this StateInfo means that + // a full accumulator refresh is required. + static bool requires_refresh(StateInfo* st, Color perspective); + }; + +} // namespace Stockfish::Eval::NNUE::Features + +#endif // #ifndef NNUE_FEATURES_HALF_KA_V2_H_INCLUDED diff --git a/Fairy-Stockfish-master/src/nnue/features/half_ka_v2_variants.cpp b/Fairy-Stockfish-master/src/nnue/features/half_ka_v2_variants.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bf4360a11c9d2be0a284d996d13d52695cd96933 --- /dev/null +++ b/Fairy-Stockfish-master/src/nnue/features/half_ka_v2_variants.cpp @@ -0,0 +1,112 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +//Definition of input features HalfKAv2 of NNUE evaluation function + +#include "half_ka_v2_variants.h" + +#include "../../position.h" + +namespace Stockfish::Eval::NNUE::Features { + + // Map square to numbering on variant board + inline Square to_variant_square(Square s, const Position& pos) { + return Square(s - rank_of(s) * (FILE_MAX - pos.max_file())); + } + + // Orient a square according to perspective (rotates by 180 for black) + // Missing kings map to index 0 (SQ_A1) + inline Square HalfKAv2Variants::orient(Color perspective, Square s, const Position& pos) { + return s != SQ_NONE ? to_variant_square( perspective == WHITE || (pos.flag_region(BLACK) & Rank8BB) ? s + : flip_rank(s, pos.max_rank()), pos) : SQ_A1; + } + + // Index of a feature for a given king position and another piece on some square + inline IndexType HalfKAv2Variants::make_index(Color perspective, Square s, Piece pc, Square ksq, const Position& pos) { + return IndexType(orient(perspective, s, pos) + pos.nnue_piece_square_index(perspective, pc) + pos.nnue_king_square_index(ksq)); + } + + // Index of a feature for a given king position and another piece on some square + inline IndexType HalfKAv2Variants::make_index(Color perspective, int handCount, Piece pc, Square ksq, const Position& pos) { + return IndexType(handCount + pos.nnue_piece_hand_index(perspective, pc) + pos.nnue_king_square_index(ksq)); + } + + // Get a list of indices for active features + void HalfKAv2Variants::append_active_indices( + const Position& pos, + Color perspective, + ValueListInserter active + ) { + Square oriented_ksq = orient(perspective, pos.nnue_king_square(perspective), pos); + Bitboard bb = pos.pieces(WHITE) | pos.pieces(BLACK); + while (bb) + { + Square s = pop_lsb(bb); + active.push_back(make_index(perspective, s, pos.piece_on(s), oriented_ksq, pos)); + } + + // Indices for pieces in hand + if (pos.nnue_use_pockets()) + for (Color c : {WHITE, BLACK}) + for (PieceSet ps = pos.piece_types(); ps;) + { + PieceType pt = pop_lsb(ps); + for (int i = 0; i < pos.count_in_hand(c, pt); i++) + active.push_back(make_index(perspective, i, make_piece(c, pt), oriented_ksq, pos)); + } + + } + + // append_changed_indices() : get a list of indices for recently changed features + + void HalfKAv2Variants::append_changed_indices( + Square ksq, + StateInfo* st, + Color perspective, + ValueListInserter removed, + ValueListInserter added, + const Position& pos + ) { + const auto& dp = st->dirtyPiece; + Square oriented_ksq = orient(perspective, ksq, pos); + for (int i = 0; i < dp.dirty_num; ++i) { + Piece pc = dp.piece[i]; + if (dp.from[i] != SQ_NONE) + removed.push_back(make_index(perspective, dp.from[i], pc, oriented_ksq, pos)); + else if (dp.handPiece[i] != NO_PIECE) + removed.push_back(make_index(perspective, dp.handCount[i] - 1, dp.handPiece[i], oriented_ksq, pos)); + if (dp.to[i] != SQ_NONE) + added.push_back(make_index(perspective, dp.to[i], pc, oriented_ksq, pos)); + else if (dp.handPiece[i] != NO_PIECE) + added.push_back(make_index(perspective, dp.handCount[i] - 1, dp.handPiece[i], oriented_ksq, pos)); + } + } + + int HalfKAv2Variants::update_cost(StateInfo* st) { + return st->dirtyPiece.dirty_num; + } + + int HalfKAv2Variants::refresh_cost(const Position& pos) { + return pos.count(); + } + + bool HalfKAv2Variants::requires_refresh(StateInfo* st, Color perspective, const Position& pos) { + return st->dirtyPiece.piece[0] == make_piece(perspective, pos.nnue_king()) || pos.flip_enclosed_pieces(); + } + +} // namespace Stockfish::Eval::NNUE::Features diff --git a/Fairy-Stockfish-master/src/nnue/features/half_ka_v2_variants.h b/Fairy-Stockfish-master/src/nnue/features/half_ka_v2_variants.h new file mode 100644 index 0000000000000000000000000000000000000000..34cb8d69e98d1047e9607c0e64b427deaf4b7a3d --- /dev/null +++ b/Fairy-Stockfish-master/src/nnue/features/half_ka_v2_variants.h @@ -0,0 +1,94 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +//Definition of input features HalfKAv2 of NNUE evaluation function + +#ifndef NNUE_FEATURES_HALF_KA_V2_VARIANTS_H_INCLUDED +#define NNUE_FEATURES_HALF_KA_V2_VARIANTS_H_INCLUDED + +#include "../nnue_common.h" + +#include "../../evaluate.h" +#include "../../misc.h" + +#include "half_ka_v2.h" + +namespace Stockfish { + struct StateInfo; +} + +namespace Stockfish::Eval::NNUE::Features { + + // Feature HalfKAv2: Combination of the position of own king + // and the position of pieces + class HalfKAv2Variants { + + // Orient a square according to perspective (rotates by 180 for black) + static Square orient(Color perspective, Square s, const Position& pos); + + // Index of a feature for a given king position and another piece on some square + static IndexType make_index(Color perspective, Square s, Piece pc, Square ksq, const Position& pos); + + // Index of a feature for a given king position and another piece in hand + static IndexType make_index(Color perspective, int handCount, Piece pc, Square ksq, const Position& pos); + + public: + // Feature name + static constexpr const char* Name = "HalfKAv2(Friend)"; + + // Hash value embedded in the evaluation file + static constexpr std::uint32_t HashValue = 0x5f234cb8u; + + // Number of feature dimensions + static constexpr IndexType Dimensions = static_cast(SQUARE_NB) * static_cast(SQUARE_NB) * 19; + + static IndexType get_dimensions() { + return currentNnueVariant->nnueDimensions; + } + + // Maximum number of simultaneously active features. + static constexpr IndexType MaxActiveDimensions = 128; + + // Get a list of indices for active features + static void append_active_indices( + const Position& pos, + Color perspective, + ValueListInserter active); + + // Get a list of indices for recently changed features + static void append_changed_indices( + Square ksq, + StateInfo* st, + Color perspective, + ValueListInserter removed, + ValueListInserter added, + const Position& pos); + + // Returns the cost of updating one perspective, the most costly one. + // Assumes no refresh needed. + static int update_cost(StateInfo* st); + static int refresh_cost(const Position& pos); + + // Returns whether the change stored in this StateInfo means that + // a full accumulator refresh is required. + static bool requires_refresh(StateInfo* st, Color perspective, const Position& pos); + }; + +} // namespace Stockfish::Eval::NNUE::Features + +#endif // #ifndef NNUE_FEATURES_HALF_KA_V2_VARIANTS_H_INCLUDED diff --git a/Fairy-Stockfish-master/src/nnue/layers/affine_transform.h b/Fairy-Stockfish-master/src/nnue/layers/affine_transform.h new file mode 100644 index 0000000000000000000000000000000000000000..33c7b9dc27e5280fadeae2ccb496269ceaf9b31a --- /dev/null +++ b/Fairy-Stockfish-master/src/nnue/layers/affine_transform.h @@ -0,0 +1,435 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Definition of layer AffineTransform of NNUE evaluation function + +#ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED +#define NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED + +#include +#include "../nnue_common.h" + +namespace Stockfish::Eval::NNUE::Layers { + + // Affine transformation layer + template + class AffineTransform { + public: + // Input/output type + using InputType = typename PreviousLayer::OutputType; + using OutputType = std::int32_t; + static_assert(std::is_same::value, ""); + + // Number of input/output dimensions + static constexpr IndexType InputDimensions = + PreviousLayer::OutputDimensions; + static constexpr IndexType OutputDimensions = OutDims; + static constexpr IndexType PaddedInputDimensions = + ceil_to_multiple(InputDimensions, MaxSimdWidth); +#if defined (USE_AVX512) + static constexpr const IndexType OutputSimdWidth = SimdWidth / 2; +#elif defined (USE_SSSE3) + static constexpr const IndexType OutputSimdWidth = SimdWidth / 4; +#endif + + // Size of forward propagation buffer used in this layer + static constexpr std::size_t SelfBufferSize = + ceil_to_multiple(OutputDimensions * sizeof(OutputType), CacheLineSize); + + // Size of the forward propagation buffer used from the input layer to this layer + static constexpr std::size_t BufferSize = + PreviousLayer::BufferSize + SelfBufferSize; + + // Hash value embedded in the evaluation file + static constexpr std::uint32_t get_hash_value() { + std::uint32_t hashValue = 0xCC03DAE4u; + hashValue += OutputDimensions; + hashValue ^= PreviousLayer::get_hash_value() >> 1; + hashValue ^= PreviousLayer::get_hash_value() << 31; + return hashValue; + } + + // Read network parameters + bool read_parameters(std::istream& stream) { + if (!previousLayer.read_parameters(stream)) return false; + for (std::size_t i = 0; i < OutputDimensions; ++i) + biases[i] = read_little_endian(stream); + for (std::size_t i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) +#if !defined (USE_SSSE3) + weights[i] = read_little_endian(stream); +#else + weights[ + (i / 4) % (PaddedInputDimensions / 4) * OutputDimensions * 4 + + i / PaddedInputDimensions * 4 + + i % 4 + ] = read_little_endian(stream); +#endif + + return !stream.fail(); + } + + // Write network parameters + bool write_parameters(std::ostream& stream) const { + if (!previousLayer.write_parameters(stream)) return false; + for (std::size_t i = 0; i < OutputDimensions; ++i) + write_little_endian(stream, biases[i]); +#if !defined (USE_SSSE3) + for (std::size_t i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) + write_little_endian(stream, weights[i]); +#else + std::unique_ptr unscrambledWeights = std::make_unique(OutputDimensions * PaddedInputDimensions); + for (std::size_t i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) { + unscrambledWeights[i] = + weights[ + (i / 4) % (PaddedInputDimensions / 4) * OutputDimensions * 4 + + i / PaddedInputDimensions * 4 + + i % 4 + ]; + } + + for (std::size_t i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) + write_little_endian(stream, unscrambledWeights[i]); +#endif + + return !stream.fail(); + } + + // Forward propagation + const OutputType* propagate( + const TransformedFeatureType* transformedFeatures, char* buffer) const { + const auto input = previousLayer.propagate( + transformedFeatures, buffer + SelfBufferSize); + +#if defined (USE_AVX512) + + [[maybe_unused]] const __m512i Ones512 = _mm512_set1_epi16(1); + + [[maybe_unused]] auto m512_hadd = [](__m512i sum, int bias) -> int { + return _mm512_reduce_add_epi32(sum) + bias; + }; + + [[maybe_unused]] auto m512_add_dpbusd_epi32 = [=](__m512i& acc, __m512i a, __m512i b) { +#if defined (USE_VNNI) + acc = _mm512_dpbusd_epi32(acc, a, b); +#else + __m512i product0 = _mm512_maddubs_epi16(a, b); + product0 = _mm512_madd_epi16(product0, Ones512); + acc = _mm512_add_epi32(acc, product0); +#endif + }; + + [[maybe_unused]] auto m512_add_dpbusd_epi32x4 = [=](__m512i& acc, __m512i a0, __m512i b0, __m512i a1, __m512i b1, + __m512i a2, __m512i b2, __m512i a3, __m512i b3) { +#if defined (USE_VNNI) + acc = _mm512_dpbusd_epi32(acc, a0, b0); + acc = _mm512_dpbusd_epi32(acc, a1, b1); + acc = _mm512_dpbusd_epi32(acc, a2, b2); + acc = _mm512_dpbusd_epi32(acc, a3, b3); +#else + __m512i product0 = _mm512_maddubs_epi16(a0, b0); + __m512i product1 = _mm512_maddubs_epi16(a1, b1); + __m512i product2 = _mm512_maddubs_epi16(a2, b2); + __m512i product3 = _mm512_maddubs_epi16(a3, b3); + product0 = _mm512_adds_epi16(product0, product1); + product0 = _mm512_madd_epi16(product0, Ones512); + product2 = _mm512_adds_epi16(product2, product3); + product2 = _mm512_madd_epi16(product2, Ones512); + acc = _mm512_add_epi32(acc, _mm512_add_epi32(product0, product2)); +#endif + }; + +#endif +#if defined (USE_AVX2) + + [[maybe_unused]] const __m256i Ones256 = _mm256_set1_epi16(1); + + [[maybe_unused]] auto m256_hadd = [](__m256i sum, int bias) -> int { + __m128i sum128 = _mm_add_epi32(_mm256_castsi256_si128(sum), _mm256_extracti128_si256(sum, 1)); + sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_BADC)); + sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_CDAB)); + return _mm_cvtsi128_si32(sum128) + bias; + }; + + [[maybe_unused]] auto m256_add_dpbusd_epi32 = [=](__m256i& acc, __m256i a, __m256i b) { +#if defined (USE_VNNI) + acc = _mm256_dpbusd_epi32(acc, a, b); +#else + __m256i product0 = _mm256_maddubs_epi16(a, b); + product0 = _mm256_madd_epi16(product0, Ones256); + acc = _mm256_add_epi32(acc, product0); +#endif + }; + + [[maybe_unused]] auto m256_add_dpbusd_epi32x4 = [=](__m256i& acc, __m256i a0, __m256i b0, __m256i a1, __m256i b1, + __m256i a2, __m256i b2, __m256i a3, __m256i b3) { +#if defined (USE_VNNI) + acc = _mm256_dpbusd_epi32(acc, a0, b0); + acc = _mm256_dpbusd_epi32(acc, a1, b1); + acc = _mm256_dpbusd_epi32(acc, a2, b2); + acc = _mm256_dpbusd_epi32(acc, a3, b3); +#else + __m256i product0 = _mm256_maddubs_epi16(a0, b0); + __m256i product1 = _mm256_maddubs_epi16(a1, b1); + __m256i product2 = _mm256_maddubs_epi16(a2, b2); + __m256i product3 = _mm256_maddubs_epi16(a3, b3); + product0 = _mm256_adds_epi16(product0, product1); + product0 = _mm256_madd_epi16(product0, Ones256); + product2 = _mm256_adds_epi16(product2, product3); + product2 = _mm256_madd_epi16(product2, Ones256); + acc = _mm256_add_epi32(acc, _mm256_add_epi32(product0, product2)); +#endif + }; + +#endif +#if defined (USE_SSSE3) + + [[maybe_unused]] const __m128i Ones128 = _mm_set1_epi16(1); + + [[maybe_unused]] auto m128_hadd = [](__m128i sum, int bias) -> int { + sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0x4E)); //_MM_PERM_BADC + sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0xB1)); //_MM_PERM_CDAB + return _mm_cvtsi128_si32(sum) + bias; + }; + + [[maybe_unused]] auto m128_add_dpbusd_epi32 = [=](__m128i& acc, __m128i a, __m128i b) { + __m128i product0 = _mm_maddubs_epi16(a, b); + product0 = _mm_madd_epi16(product0, Ones128); + acc = _mm_add_epi32(acc, product0); + }; + + [[maybe_unused]] auto m128_add_dpbusd_epi32x4 = [=](__m128i& acc, __m128i a0, __m128i b0, __m128i a1, __m128i b1, + __m128i a2, __m128i b2, __m128i a3, __m128i b3) { + __m128i product0 = _mm_maddubs_epi16(a0, b0); + __m128i product1 = _mm_maddubs_epi16(a1, b1); + __m128i product2 = _mm_maddubs_epi16(a2, b2); + __m128i product3 = _mm_maddubs_epi16(a3, b3); + product0 = _mm_adds_epi16(product0, product1); + product0 = _mm_madd_epi16(product0, Ones128); + product2 = _mm_adds_epi16(product2, product3); + product2 = _mm_madd_epi16(product2, Ones128); + acc = _mm_add_epi32(acc, _mm_add_epi32(product0, product2)); + }; + +#endif + +#if defined (USE_AVX512) + using vec_t = __m512i; + #define vec_setzero _mm512_setzero_si512 + #define vec_set_32 _mm512_set1_epi32 + auto& vec_add_dpbusd_32 = m512_add_dpbusd_epi32; + auto& vec_add_dpbusd_32x4 = m512_add_dpbusd_epi32x4; + auto& vec_hadd = m512_hadd; +#elif defined (USE_AVX2) + using vec_t = __m256i; + #define vec_setzero _mm256_setzero_si256 + #define vec_set_32 _mm256_set1_epi32 + auto& vec_add_dpbusd_32 = m256_add_dpbusd_epi32; + auto& vec_add_dpbusd_32x4 = m256_add_dpbusd_epi32x4; + auto& vec_hadd = m256_hadd; +#elif defined (USE_SSSE3) + using vec_t = __m128i; + #define vec_setzero _mm_setzero_si128 + #define vec_set_32 _mm_set1_epi32 + auto& vec_add_dpbusd_32 = m128_add_dpbusd_epi32; + auto& vec_add_dpbusd_32x4 = m128_add_dpbusd_epi32x4; + auto& vec_hadd = m128_hadd; +#endif + +#if defined (USE_SSSE3) + // Different layout, we process 4 inputs at a time, always. + static_assert(InputDimensions % 4 == 0); + + const auto output = reinterpret_cast(buffer); + const auto inputVector = reinterpret_cast(input); + + static_assert(OutputDimensions % OutputSimdWidth == 0 || OutputDimensions == 1); + + // OutputDimensions is either 1 or a multiple of SimdWidth + // because then it is also an input dimension. + if constexpr (OutputDimensions % OutputSimdWidth == 0) + { + constexpr IndexType NumChunks = InputDimensions / 4; + + const auto input32 = reinterpret_cast(input); + vec_t* outptr = reinterpret_cast(output); + std::memcpy(output, biases, OutputDimensions * sizeof(OutputType)); + + for (int i = 0; i < (int)NumChunks - 3; i += 4) + { + const vec_t in0 = vec_set_32(input32[i + 0]); + const vec_t in1 = vec_set_32(input32[i + 1]); + const vec_t in2 = vec_set_32(input32[i + 2]); + const vec_t in3 = vec_set_32(input32[i + 3]); + const auto col0 = reinterpret_cast(&weights[(i + 0) * OutputDimensions * 4]); + const auto col1 = reinterpret_cast(&weights[(i + 1) * OutputDimensions * 4]); + const auto col2 = reinterpret_cast(&weights[(i + 2) * OutputDimensions * 4]); + const auto col3 = reinterpret_cast(&weights[(i + 3) * OutputDimensions * 4]); + for (int j = 0; j * OutputSimdWidth < OutputDimensions; ++j) + vec_add_dpbusd_32x4(outptr[j], in0, col0[j], in1, col1[j], in2, col2[j], in3, col3[j]); + } + } + else if constexpr (OutputDimensions == 1) + { +#if defined (USE_AVX512) + if constexpr (PaddedInputDimensions % (SimdWidth * 2) != 0) + { + constexpr IndexType NumChunks = PaddedInputDimensions / SimdWidth; + const auto inputVector256 = reinterpret_cast(input); + + __m256i sum0 = _mm256_setzero_si256(); + const auto row0 = reinterpret_cast(&weights[0]); + + for (int j = 0; j < (int)NumChunks; ++j) + { + const __m256i in = inputVector256[j]; + m256_add_dpbusd_epi32(sum0, in, row0[j]); + } + output[0] = m256_hadd(sum0, biases[0]); + } + else +#endif + { +#if defined (USE_AVX512) + constexpr IndexType NumChunks = PaddedInputDimensions / (SimdWidth * 2); +#else + constexpr IndexType NumChunks = PaddedInputDimensions / SimdWidth; +#endif + vec_t sum0 = vec_setzero(); + const auto row0 = reinterpret_cast(&weights[0]); + + for (int j = 0; j < (int)NumChunks; ++j) + { + const vec_t in = inputVector[j]; + vec_add_dpbusd_32(sum0, in, row0[j]); + } + output[0] = vec_hadd(sum0, biases[0]); + } + } + +#else + +// Use old implementation for the other architectures. + + auto output = reinterpret_cast(buffer); + +#if defined(USE_SSE2) + // At least a multiple of 16, with SSE2. + static_assert(InputDimensions % SimdWidth == 0); + constexpr IndexType NumChunks = InputDimensions / SimdWidth; + const __m128i Zeros = _mm_setzero_si128(); + const auto inputVector = reinterpret_cast(input); + +#elif defined(USE_MMX) + static_assert(InputDimensions % SimdWidth == 0); + constexpr IndexType NumChunks = InputDimensions / SimdWidth; + const __m64 Zeros = _mm_setzero_si64(); + const auto inputVector = reinterpret_cast(input); + +#elif defined(USE_NEON) + static_assert(InputDimensions % SimdWidth == 0); + constexpr IndexType NumChunks = InputDimensions / SimdWidth; + const auto inputVector = reinterpret_cast(input); +#endif + + for (IndexType i = 0; i < OutputDimensions; ++i) { + const IndexType offset = i * PaddedInputDimensions; + +#if defined(USE_SSE2) + __m128i sumLo = _mm_cvtsi32_si128(biases[i]); + __m128i sumHi = Zeros; + const auto row = reinterpret_cast(&weights[offset]); + for (IndexType j = 0; j < NumChunks; ++j) { + __m128i row_j = _mm_load_si128(&row[j]); + __m128i input_j = _mm_load_si128(&inputVector[j]); + __m128i extendedRowLo = _mm_srai_epi16(_mm_unpacklo_epi8(row_j, row_j), 8); + __m128i extendedRowHi = _mm_srai_epi16(_mm_unpackhi_epi8(row_j, row_j), 8); + __m128i extendedInputLo = _mm_unpacklo_epi8(input_j, Zeros); + __m128i extendedInputHi = _mm_unpackhi_epi8(input_j, Zeros); + __m128i productLo = _mm_madd_epi16(extendedRowLo, extendedInputLo); + __m128i productHi = _mm_madd_epi16(extendedRowHi, extendedInputHi); + sumLo = _mm_add_epi32(sumLo, productLo); + sumHi = _mm_add_epi32(sumHi, productHi); + } + __m128i sum = _mm_add_epi32(sumLo, sumHi); + __m128i sumHigh_64 = _mm_shuffle_epi32(sum, _MM_SHUFFLE(1, 0, 3, 2)); + sum = _mm_add_epi32(sum, sumHigh_64); + __m128i sum_second_32 = _mm_shufflelo_epi16(sum, _MM_SHUFFLE(1, 0, 3, 2)); + sum = _mm_add_epi32(sum, sum_second_32); + output[i] = _mm_cvtsi128_si32(sum); + +#elif defined(USE_MMX) + __m64 sumLo = _mm_cvtsi32_si64(biases[i]); + __m64 sumHi = Zeros; + const auto row = reinterpret_cast(&weights[offset]); + for (IndexType j = 0; j < NumChunks; ++j) { + __m64 row_j = row[j]; + __m64 input_j = inputVector[j]; + __m64 extendedRowLo = _mm_srai_pi16(_mm_unpacklo_pi8(row_j, row_j), 8); + __m64 extendedRowHi = _mm_srai_pi16(_mm_unpackhi_pi8(row_j, row_j), 8); + __m64 extendedInputLo = _mm_unpacklo_pi8(input_j, Zeros); + __m64 extendedInputHi = _mm_unpackhi_pi8(input_j, Zeros); + __m64 productLo = _mm_madd_pi16(extendedRowLo, extendedInputLo); + __m64 productHi = _mm_madd_pi16(extendedRowHi, extendedInputHi); + sumLo = _mm_add_pi32(sumLo, productLo); + sumHi = _mm_add_pi32(sumHi, productHi); + } + __m64 sum = _mm_add_pi32(sumLo, sumHi); + sum = _mm_add_pi32(sum, _mm_unpackhi_pi32(sum, sum)); + output[i] = _mm_cvtsi64_si32(sum); + +#elif defined(USE_NEON) + int32x4_t sum = {biases[i]}; + const auto row = reinterpret_cast(&weights[offset]); + for (IndexType j = 0; j < NumChunks; ++j) { + int16x8_t product = vmull_s8(inputVector[j * 2], row[j * 2]); + product = vmlal_s8(product, inputVector[j * 2 + 1], row[j * 2 + 1]); + sum = vpadalq_s16(sum, product); + } + output[i] = sum[0] + sum[1] + sum[2] + sum[3]; + +#else + OutputType sum = biases[i]; + for (IndexType j = 0; j < InputDimensions; ++j) { + sum += weights[offset + j] * input[j]; + } + output[i] = sum; +#endif + + } +#if defined(USE_MMX) + _mm_empty(); +#endif + +#endif + + return output; + } + + private: + using BiasType = OutputType; + using WeightType = std::int8_t; + + PreviousLayer previousLayer; + + alignas(CacheLineSize) BiasType biases[OutputDimensions]; + alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions]; + }; + +} // namespace Stockfish::Eval::NNUE::Layers + +#endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED diff --git a/Fairy-Stockfish-master/src/nnue/layers/clipped_relu.h b/Fairy-Stockfish-master/src/nnue/layers/clipped_relu.h new file mode 100644 index 0000000000000000000000000000000000000000..f5a0b2924e93e30876d1727ca19148c7bfa7bf79 --- /dev/null +++ b/Fairy-Stockfish-master/src/nnue/layers/clipped_relu.h @@ -0,0 +1,191 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Definition of layer ClippedReLU of NNUE evaluation function + +#ifndef NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED +#define NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED + +#include "../nnue_common.h" + +namespace Stockfish::Eval::NNUE::Layers { + + // Clipped ReLU + template + class ClippedReLU { + public: + // Input/output type + using InputType = typename PreviousLayer::OutputType; + using OutputType = std::uint8_t; + static_assert(std::is_same::value, ""); + + // Number of input/output dimensions + static constexpr IndexType InputDimensions = + PreviousLayer::OutputDimensions; + static constexpr IndexType OutputDimensions = InputDimensions; + + // Size of forward propagation buffer used in this layer + static constexpr std::size_t SelfBufferSize = + ceil_to_multiple(OutputDimensions * sizeof(OutputType), CacheLineSize); + + // Size of the forward propagation buffer used from the input layer to this layer + static constexpr std::size_t BufferSize = + PreviousLayer::BufferSize + SelfBufferSize; + + // Hash value embedded in the evaluation file + static constexpr std::uint32_t get_hash_value() { + std::uint32_t hashValue = 0x538D24C7u; + hashValue += PreviousLayer::get_hash_value(); + return hashValue; + } + + // Read network parameters + bool read_parameters(std::istream& stream) { + return previousLayer.read_parameters(stream); + } + + // Write network parameters + bool write_parameters(std::ostream& stream) const { + return previousLayer.write_parameters(stream); + } + + // Forward propagation + const OutputType* propagate( + const TransformedFeatureType* transformedFeatures, char* buffer) const { + const auto input = previousLayer.propagate( + transformedFeatures, buffer + SelfBufferSize); + const auto output = reinterpret_cast(buffer); + + #if defined(USE_AVX2) + if constexpr (InputDimensions % SimdWidth == 0) { + constexpr IndexType NumChunks = InputDimensions / SimdWidth; + const __m256i Zero = _mm256_setzero_si256(); + const __m256i Offsets = _mm256_set_epi32(7, 3, 6, 2, 5, 1, 4, 0); + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast<__m256i*>(output); + for (IndexType i = 0; i < NumChunks; ++i) { + const __m256i words0 = _mm256_srai_epi16(_mm256_packs_epi32( + _mm256_load_si256(&in[i * 4 + 0]), + _mm256_load_si256(&in[i * 4 + 1])), WeightScaleBits); + const __m256i words1 = _mm256_srai_epi16(_mm256_packs_epi32( + _mm256_load_si256(&in[i * 4 + 2]), + _mm256_load_si256(&in[i * 4 + 3])), WeightScaleBits); + _mm256_store_si256(&out[i], _mm256_permutevar8x32_epi32(_mm256_max_epi8( + _mm256_packs_epi16(words0, words1), Zero), Offsets)); + } + } else { + constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2); + const __m128i Zero = _mm_setzero_si128(); + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast<__m128i*>(output); + for (IndexType i = 0; i < NumChunks; ++i) { + const __m128i words0 = _mm_srai_epi16(_mm_packs_epi32( + _mm_load_si128(&in[i * 4 + 0]), + _mm_load_si128(&in[i * 4 + 1])), WeightScaleBits); + const __m128i words1 = _mm_srai_epi16(_mm_packs_epi32( + _mm_load_si128(&in[i * 4 + 2]), + _mm_load_si128(&in[i * 4 + 3])), WeightScaleBits); + const __m128i packedbytes = _mm_packs_epi16(words0, words1); + _mm_store_si128(&out[i], _mm_max_epi8(packedbytes, Zero)); + } + } + constexpr IndexType Start = + InputDimensions % SimdWidth == 0 + ? InputDimensions / SimdWidth * SimdWidth + : InputDimensions / (SimdWidth / 2) * (SimdWidth / 2); + + #elif defined(USE_SSE2) + constexpr IndexType NumChunks = InputDimensions / SimdWidth; + + #ifdef USE_SSE41 + const __m128i Zero = _mm_setzero_si128(); + #else + const __m128i k0x80s = _mm_set1_epi8(-128); + #endif + + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast<__m128i*>(output); + for (IndexType i = 0; i < NumChunks; ++i) { + const __m128i words0 = _mm_srai_epi16(_mm_packs_epi32( + _mm_load_si128(&in[i * 4 + 0]), + _mm_load_si128(&in[i * 4 + 1])), WeightScaleBits); + const __m128i words1 = _mm_srai_epi16(_mm_packs_epi32( + _mm_load_si128(&in[i * 4 + 2]), + _mm_load_si128(&in[i * 4 + 3])), WeightScaleBits); + const __m128i packedbytes = _mm_packs_epi16(words0, words1); + _mm_store_si128(&out[i], + + #ifdef USE_SSE41 + _mm_max_epi8(packedbytes, Zero) + #else + _mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s) + #endif + + ); + } + constexpr IndexType Start = NumChunks * SimdWidth; + + #elif defined(USE_MMX) + constexpr IndexType NumChunks = InputDimensions / SimdWidth; + const __m64 k0x80s = _mm_set1_pi8(-128); + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast<__m64*>(output); + for (IndexType i = 0; i < NumChunks; ++i) { + const __m64 words0 = _mm_srai_pi16( + _mm_packs_pi32(in[i * 4 + 0], in[i * 4 + 1]), + WeightScaleBits); + const __m64 words1 = _mm_srai_pi16( + _mm_packs_pi32(in[i * 4 + 2], in[i * 4 + 3]), + WeightScaleBits); + const __m64 packedbytes = _mm_packs_pi16(words0, words1); + out[i] = _mm_subs_pi8(_mm_adds_pi8(packedbytes, k0x80s), k0x80s); + } + _mm_empty(); + constexpr IndexType Start = NumChunks * SimdWidth; + + #elif defined(USE_NEON) + constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2); + const int8x8_t Zero = {0}; + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast(output); + for (IndexType i = 0; i < NumChunks; ++i) { + int16x8_t shifted; + const auto pack = reinterpret_cast(&shifted); + pack[0] = vqshrn_n_s32(in[i * 2 + 0], WeightScaleBits); + pack[1] = vqshrn_n_s32(in[i * 2 + 1], WeightScaleBits); + out[i] = vmax_s8(vqmovn_s16(shifted), Zero); + } + constexpr IndexType Start = NumChunks * (SimdWidth / 2); + #else + constexpr IndexType Start = 0; + #endif + + for (IndexType i = Start; i < InputDimensions; ++i) { + output[i] = static_cast( + std::max(0, std::min(127, input[i] >> WeightScaleBits))); + } + return output; + } + + private: + PreviousLayer previousLayer; + }; + +} // namespace Stockfish::Eval::NNUE::Layers + +#endif // NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED diff --git a/Fairy-Stockfish-master/src/nnue/layers/input_slice.h b/Fairy-Stockfish-master/src/nnue/layers/input_slice.h new file mode 100644 index 0000000000000000000000000000000000000000..8f526b745f724f435a35f1496f5f43f10bf58d45 --- /dev/null +++ b/Fairy-Stockfish-master/src/nnue/layers/input_slice.h @@ -0,0 +1,73 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// NNUE evaluation function layer InputSlice definition + +#ifndef NNUE_LAYERS_INPUT_SLICE_H_INCLUDED +#define NNUE_LAYERS_INPUT_SLICE_H_INCLUDED + +#include "../nnue_common.h" + +namespace Stockfish::Eval::NNUE::Layers { + +// Input layer +template +class InputSlice { + public: + // Need to maintain alignment + static_assert(Offset % MaxSimdWidth == 0, ""); + + // Output type + using OutputType = TransformedFeatureType; + + // Output dimensionality + static constexpr IndexType OutputDimensions = OutDims; + + // Size of forward propagation buffer used from the input layer to this layer + static constexpr std::size_t BufferSize = 0; + + // Hash value embedded in the evaluation file + static constexpr std::uint32_t get_hash_value() { + std::uint32_t hashValue = 0xEC42E90Du; + hashValue ^= OutputDimensions ^ (Offset << 10); + return hashValue; + } + + // Read network parameters + bool read_parameters(std::istream& /*stream*/) { + return true; + } + + // Write network parameters + bool write_parameters(std::ostream& /*stream*/) const { + return true; + } + + // Forward propagation + const OutputType* propagate( + const TransformedFeatureType* transformedFeatures, + char* /*buffer*/) const { + return transformedFeatures + Offset; + } + + private: +}; + +} // namespace Stockfish::Eval::NNUE::Layers + +#endif // #ifndef NNUE_LAYERS_INPUT_SLICE_H_INCLUDED diff --git a/Fairy-Stockfish-master/src/nnue/nnue_accumulator.h b/Fairy-Stockfish-master/src/nnue/nnue_accumulator.h new file mode 100644 index 0000000000000000000000000000000000000000..600483b5cfdc2c0875280d4db9df706562077e69 --- /dev/null +++ b/Fairy-Stockfish-master/src/nnue/nnue_accumulator.h @@ -0,0 +1,37 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Class for difference calculation of NNUE evaluation function + +#ifndef NNUE_ACCUMULATOR_H_INCLUDED +#define NNUE_ACCUMULATOR_H_INCLUDED + +#include "nnue_architecture.h" + +namespace Stockfish::Eval::NNUE { + + // Class that holds the result of affine transformation of input features + struct alignas(CacheLineSize) Accumulator { + std::int16_t accumulation[2][TransformedFeatureDimensions]; + std::int32_t psqtAccumulation[2][PSQTBuckets]; + bool computed[2]; + }; + +} // namespace Stockfish::Eval::NNUE + +#endif // NNUE_ACCUMULATOR_H_INCLUDED diff --git a/Fairy-Stockfish-master/src/nnue/nnue_architecture.h b/Fairy-Stockfish-master/src/nnue/nnue_architecture.h new file mode 100644 index 0000000000000000000000000000000000000000..fdbfb3ceb05136724ae0ebfd6c1b308b8e8ed062 --- /dev/null +++ b/Fairy-Stockfish-master/src/nnue/nnue_architecture.h @@ -0,0 +1,60 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Input features and network structure used in NNUE evaluation function + +#ifndef NNUE_ARCHITECTURE_H_INCLUDED +#define NNUE_ARCHITECTURE_H_INCLUDED + +#include "nnue_common.h" + +#include "features/half_ka_v2_variants.h" + +#include "layers/input_slice.h" +#include "layers/affine_transform.h" +#include "layers/clipped_relu.h" + +namespace Stockfish::Eval::NNUE { + + // Input features used in evaluation function + using FeatureSet = Features::HalfKAv2Variants; + + // Number of input feature dimensions after conversion + constexpr IndexType TransformedFeatureDimensions = 512; + constexpr IndexType PSQTBuckets = 8; + constexpr IndexType LayerStacks = 8; + + namespace Layers { + + // Define network structure + using InputLayer = InputSlice; + using HiddenLayer1 = ClippedReLU>; + using HiddenLayer2 = ClippedReLU>; + using OutputLayer = AffineTransform; + + } // namespace Layers + + using Network = Layers::OutputLayer; + + static_assert(TransformedFeatureDimensions % MaxSimdWidth == 0, ""); + static_assert(Network::OutputDimensions == 1, ""); + static_assert(std::is_same::value, ""); + +} // namespace Stockfish::Eval::NNUE + +#endif // #ifndef NNUE_ARCHITECTURE_H_INCLUDED diff --git a/Fairy-Stockfish-master/src/nnue/nnue_common.h b/Fairy-Stockfish-master/src/nnue/nnue_common.h new file mode 100644 index 0000000000000000000000000000000000000000..3d238935deaece0a50788c773bdc349aca6178c9 --- /dev/null +++ b/Fairy-Stockfish-master/src/nnue/nnue_common.h @@ -0,0 +1,164 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Constants used in NNUE evaluation function + +#ifndef NNUE_COMMON_H_INCLUDED +#define NNUE_COMMON_H_INCLUDED + +#include +#include + +#include "../misc.h" // for IsLittleEndian + +#if defined(USE_AVX2) +#include + +#elif defined(USE_SSE41) +#include + +#elif defined(USE_SSSE3) +#include + +#elif defined(USE_SSE2) +#include + +#elif defined(USE_MMX) +#include + +#elif defined(USE_NEON) +#include +#endif + +namespace Stockfish::Eval::NNUE { + + // Version of the evaluation file + constexpr std::uint32_t Version = 0x7AF32F20u; + + // Constant used in evaluation value calculation + constexpr int OutputScale = 16; + constexpr int WeightScaleBits = 6; + + // Size of cache line (in bytes) + constexpr std::size_t CacheLineSize = 64; + + // SIMD width (in bytes) + #if defined(USE_AVX2) + constexpr std::size_t SimdWidth = 32; + + #elif defined(USE_SSE2) + constexpr std::size_t SimdWidth = 16; + + #elif defined(USE_MMX) + constexpr std::size_t SimdWidth = 8; + + #elif defined(USE_NEON) + constexpr std::size_t SimdWidth = 16; + #endif + + constexpr std::size_t MaxSimdWidth = 32; + + // Type of input feature after conversion + using TransformedFeatureType = std::uint8_t; + using IndexType = std::uint32_t; + + // Round n up to be a multiple of base + template + constexpr IntType ceil_to_multiple(IntType n, IntType base) { + return (n + base - 1) / base * base; + } + + // read_little_endian() is our utility to read an integer (signed or unsigned, any size) + // from a stream in little-endian order. We swap the byte order after the read if + // necessary to return a result with the byte ordering of the compiling machine. + template + inline IntType read_little_endian(std::istream& stream) { + IntType result; + + if (IsLittleEndian) + stream.read(reinterpret_cast(&result), sizeof(IntType)); + else + { + std::uint8_t u[sizeof(IntType)]; + typename std::make_unsigned::type v = 0; + + stream.read(reinterpret_cast(u), sizeof(IntType)); + for (std::size_t i = 0; i < sizeof(IntType); ++i) + v = (v << 8) | u[sizeof(IntType) - i - 1]; + + std::memcpy(&result, &v, sizeof(IntType)); + } + + return result; + } + + // write_little_endian() is our utility to write an integer (signed or unsigned, any size) + // to a stream in little-endian order. We swap the byte order before the write if + // necessary to always write in little endian order, independently of the byte + // ordering of the compiling machine. + template + inline void write_little_endian(std::ostream& stream, IntType value) { + + if (IsLittleEndian) + stream.write(reinterpret_cast(&value), sizeof(IntType)); + else + { + std::uint8_t u[sizeof(IntType)]; + typename std::make_unsigned::type v = value; + + std::size_t i = 0; + // if constexpr to silence the warning about shift by 8 + if constexpr (sizeof(IntType) > 1) + { + for (; i + 1 < sizeof(IntType); ++i) + { + u[i] = v; + v >>= 8; + } + } + u[i] = v; + + stream.write(reinterpret_cast(u), sizeof(IntType)); + } + } + + // read_little_endian(s, out, N) : read integers in bulk from a little endian stream. + // This reads N integers from stream s and put them in array out. + template + inline void read_little_endian(std::istream& stream, IntType* out, std::size_t count) { + if (IsLittleEndian) + stream.read(reinterpret_cast(out), sizeof(IntType) * count); + else + for (std::size_t i = 0; i < count; ++i) + out[i] = read_little_endian(stream); + } + + // write_little_endian(s, values, N) : write integers in bulk to a little endian stream. + // This takes N integers from array values and writes them on stream s. + template + inline void write_little_endian(std::ostream& stream, const IntType* values, std::size_t count) { + if (IsLittleEndian) + stream.write(reinterpret_cast(values), sizeof(IntType) * count); + else + for (std::size_t i = 0; i < count; ++i) + write_little_endian(stream, values[i]); + } + +} // namespace Stockfish::Eval::NNUE + +#endif // #ifndef NNUE_COMMON_H_INCLUDED diff --git a/Fairy-Stockfish-master/src/nnue/nnue_feature_transformer.h b/Fairy-Stockfish-master/src/nnue/nnue_feature_transformer.h new file mode 100644 index 0000000000000000000000000000000000000000..fbb0c4558c7d891c98b65cf666e023fe575320ea --- /dev/null +++ b/Fairy-Stockfish-master/src/nnue/nnue_feature_transformer.h @@ -0,0 +1,615 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// A class that converts the input features of the NNUE evaluation function + +#ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED +#define NNUE_FEATURE_TRANSFORMER_H_INCLUDED + +#include "nnue_common.h" +#include "nnue_architecture.h" + +#include // std::memset() + +namespace Stockfish::Eval::NNUE { + + using BiasType = std::int16_t; + using WeightType = std::int16_t; + using PSQTWeightType = std::int32_t; + + // If vector instructions are enabled, we update and refresh the + // accumulator tile by tile such that each tile fits in the CPU's + // vector registers. + #define VECTOR + + static_assert(PSQTBuckets % 8 == 0, + "Per feature PSQT values cannot be processed at granularity lower than 8 at a time."); + + #ifdef USE_AVX512 + typedef __m512i vec_t; + typedef __m256i psqt_vec_t; + #define vec_load(a) _mm512_load_si512(a) + #define vec_store(a,b) _mm512_store_si512(a,b) + #define vec_add_16(a,b) _mm512_add_epi16(a,b) + #define vec_sub_16(a,b) _mm512_sub_epi16(a,b) + #define vec_load_psqt(a) _mm256_load_si256(a) + #define vec_store_psqt(a,b) _mm256_store_si256(a,b) + #define vec_add_psqt_32(a,b) _mm256_add_epi32(a,b) + #define vec_sub_psqt_32(a,b) _mm256_sub_epi32(a,b) + #define vec_zero_psqt() _mm256_setzero_si256() + #define NumRegistersSIMD 32 + + #elif USE_AVX2 + typedef __m256i vec_t; + typedef __m256i psqt_vec_t; + #define vec_load(a) _mm256_load_si256(a) + #define vec_store(a,b) _mm256_store_si256(a,b) + #define vec_add_16(a,b) _mm256_add_epi16(a,b) + #define vec_sub_16(a,b) _mm256_sub_epi16(a,b) + #define vec_load_psqt(a) _mm256_load_si256(a) + #define vec_store_psqt(a,b) _mm256_store_si256(a,b) + #define vec_add_psqt_32(a,b) _mm256_add_epi32(a,b) + #define vec_sub_psqt_32(a,b) _mm256_sub_epi32(a,b) + #define vec_zero_psqt() _mm256_setzero_si256() + #define NumRegistersSIMD 16 + + #elif USE_SSE2 + typedef __m128i vec_t; + typedef __m128i psqt_vec_t; + #define vec_load(a) (*(a)) + #define vec_store(a,b) *(a)=(b) + #define vec_add_16(a,b) _mm_add_epi16(a,b) + #define vec_sub_16(a,b) _mm_sub_epi16(a,b) + #define vec_load_psqt(a) (*(a)) + #define vec_store_psqt(a,b) *(a)=(b) + #define vec_add_psqt_32(a,b) _mm_add_epi32(a,b) + #define vec_sub_psqt_32(a,b) _mm_sub_epi32(a,b) + #define vec_zero_psqt() _mm_setzero_si128() + #define NumRegistersSIMD (Is64Bit ? 16 : 8) + + #elif USE_MMX + typedef __m64 vec_t; + typedef __m64 psqt_vec_t; + #define vec_load(a) (*(a)) + #define vec_store(a,b) *(a)=(b) + #define vec_add_16(a,b) _mm_add_pi16(a,b) + #define vec_sub_16(a,b) _mm_sub_pi16(a,b) + #define vec_load_psqt(a) (*(a)) + #define vec_store_psqt(a,b) *(a)=(b) + #define vec_add_psqt_32(a,b) _mm_add_pi32(a,b) + #define vec_sub_psqt_32(a,b) _mm_sub_pi32(a,b) + #define vec_zero_psqt() _mm_setzero_si64() + #define NumRegistersSIMD 8 + + #elif USE_NEON + typedef int16x8_t vec_t; + typedef int32x4_t psqt_vec_t; + #define vec_load(a) (*(a)) + #define vec_store(a,b) *(a)=(b) + #define vec_add_16(a,b) vaddq_s16(a,b) + #define vec_sub_16(a,b) vsubq_s16(a,b) + #define vec_load_psqt(a) (*(a)) + #define vec_store_psqt(a,b) *(a)=(b) + #define vec_add_psqt_32(a,b) vaddq_s32(a,b) + #define vec_sub_psqt_32(a,b) vsubq_s32(a,b) + #define vec_zero_psqt() psqt_vec_t{0} + #define NumRegistersSIMD 16 + + #else + #undef VECTOR + + #endif + + + #ifdef VECTOR + + // Compute optimal SIMD register count for feature transformer accumulation. + + // We use __m* types as template arguments, which causes GCC to emit warnings + // about losing some attribute information. This is irrelevant to us as we + // only take their size, so the following pragma are harmless. + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wignored-attributes" + + template + static constexpr int BestRegisterCount() + { + #define RegisterSize sizeof(SIMDRegisterType) + #define LaneSize sizeof(LaneType) + + static_assert(RegisterSize >= LaneSize); + static_assert(MaxRegisters <= NumRegistersSIMD); + static_assert(MaxRegisters > 0); + static_assert(NumRegistersSIMD > 0); + static_assert(RegisterSize % LaneSize == 0); + static_assert((NumLanes * LaneSize) % RegisterSize == 0); + + const int ideal = (NumLanes * LaneSize) / RegisterSize; + if (ideal <= MaxRegisters) + return ideal; + + // Look for the largest divisor of the ideal register count that is smaller than MaxRegisters + for (int divisor = MaxRegisters; divisor > 1; --divisor) + if (ideal % divisor == 0) + return divisor; + + return 1; + } + + static constexpr int NumRegs = BestRegisterCount(); + static constexpr int NumPsqtRegs = BestRegisterCount(); + + #pragma GCC diagnostic pop + + #endif + + + + // Input feature converter + class FeatureTransformer { + + private: + // Number of output dimensions for one side + static constexpr IndexType HalfDimensions = TransformedFeatureDimensions; + + #ifdef VECTOR + static constexpr IndexType TileHeight = NumRegs * sizeof(vec_t) / 2; + static constexpr IndexType PsqtTileHeight = NumPsqtRegs * sizeof(psqt_vec_t) / 4; + static_assert(HalfDimensions % TileHeight == 0, "TileHeight must divide HalfDimensions"); + static_assert(PSQTBuckets % PsqtTileHeight == 0, "PsqtTileHeight must divide PSQTBuckets"); + #endif + + public: + // Output type + using OutputType = TransformedFeatureType; + + // Number of input/output dimensions + static constexpr IndexType InputDimensions = FeatureSet::Dimensions; + static constexpr IndexType OutputDimensions = HalfDimensions * 2; + + // Size of forward propagation buffer + static constexpr std::size_t BufferSize = + OutputDimensions * sizeof(OutputType); + + // Hash value embedded in the evaluation file + static constexpr std::uint32_t get_hash_value() { + return FeatureSet::HashValue ^ OutputDimensions; + } + + // Read network parameters + bool read_parameters(std::istream& stream) { + + read_little_endian(stream, biases , HalfDimensions ); + read_little_endian(stream, weights , HalfDimensions * FeatureSet::get_dimensions()); + read_little_endian(stream, psqtWeights, PSQTBuckets * FeatureSet::get_dimensions()); + + return !stream.fail(); + } + + // Write network parameters + bool write_parameters(std::ostream& stream) const { + + write_little_endian(stream, biases , HalfDimensions ); + write_little_endian(stream, weights , HalfDimensions * FeatureSet::get_dimensions()); + write_little_endian(stream, psqtWeights, PSQTBuckets * FeatureSet::get_dimensions()); + + return !stream.fail(); + } + + // Convert input features + std::int32_t transform(const Position& pos, OutputType* output, int bucket) const { + update_accumulator(pos, WHITE); + update_accumulator(pos, BLACK); + + const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()}; + const auto& accumulation = pos.state()->accumulator.accumulation; + const auto& psqtAccumulation = pos.state()->accumulator.psqtAccumulation; + + const auto psqt = ( + psqtAccumulation[perspectives[0]][bucket] + - psqtAccumulation[perspectives[1]][bucket] + ) / 2; + + + #if defined(USE_AVX512) + + constexpr IndexType NumChunks = HalfDimensions / (SimdWidth * 2); + static_assert(HalfDimensions % (SimdWidth * 2) == 0); + const __m512i Control = _mm512_setr_epi64(0, 2, 4, 6, 1, 3, 5, 7); + const __m512i Zero = _mm512_setzero_si512(); + + for (IndexType p = 0; p < 2; ++p) + { + const IndexType offset = HalfDimensions * p; + auto out = reinterpret_cast<__m512i*>(&output[offset]); + for (IndexType j = 0; j < NumChunks; ++j) + { + __m512i sum0 = _mm512_load_si512(&reinterpret_cast + (accumulation[perspectives[p]])[j * 2 + 0]); + __m512i sum1 = _mm512_load_si512(&reinterpret_cast + (accumulation[perspectives[p]])[j * 2 + 1]); + + _mm512_store_si512(&out[j], _mm512_permutexvar_epi64(Control, + _mm512_max_epi8(_mm512_packs_epi16(sum0, sum1), Zero))); + } + } + return psqt; + + #elif defined(USE_AVX2) + + constexpr IndexType NumChunks = HalfDimensions / SimdWidth; + constexpr int Control = 0b11011000; + const __m256i Zero = _mm256_setzero_si256(); + + for (IndexType p = 0; p < 2; ++p) + { + const IndexType offset = HalfDimensions * p; + auto out = reinterpret_cast<__m256i*>(&output[offset]); + for (IndexType j = 0; j < NumChunks; ++j) + { + __m256i sum0 = _mm256_load_si256(&reinterpret_cast + (accumulation[perspectives[p]])[j * 2 + 0]); + __m256i sum1 = _mm256_load_si256(&reinterpret_cast + (accumulation[perspectives[p]])[j * 2 + 1]); + + _mm256_store_si256(&out[j], _mm256_permute4x64_epi64( + _mm256_max_epi8(_mm256_packs_epi16(sum0, sum1), Zero), Control)); + } + } + return psqt; + + #elif defined(USE_SSE2) + + #ifdef USE_SSE41 + constexpr IndexType NumChunks = HalfDimensions / SimdWidth; + const __m128i Zero = _mm_setzero_si128(); + #else + constexpr IndexType NumChunks = HalfDimensions / SimdWidth; + const __m128i k0x80s = _mm_set1_epi8(-128); + #endif + + for (IndexType p = 0; p < 2; ++p) + { + const IndexType offset = HalfDimensions * p; + auto out = reinterpret_cast<__m128i*>(&output[offset]); + for (IndexType j = 0; j < NumChunks; ++j) + { + __m128i sum0 = _mm_load_si128(&reinterpret_cast + (accumulation[perspectives[p]])[j * 2 + 0]); + __m128i sum1 = _mm_load_si128(&reinterpret_cast + (accumulation[perspectives[p]])[j * 2 + 1]); + const __m128i packedbytes = _mm_packs_epi16(sum0, sum1); + + #ifdef USE_SSE41 + _mm_store_si128(&out[j], _mm_max_epi8(packedbytes, Zero)); + #else + _mm_store_si128(&out[j], _mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s)); + #endif + } + } + return psqt; + + #elif defined(USE_MMX) + + constexpr IndexType NumChunks = HalfDimensions / SimdWidth; + const __m64 k0x80s = _mm_set1_pi8(-128); + + for (IndexType p = 0; p < 2; ++p) + { + const IndexType offset = HalfDimensions * p; + auto out = reinterpret_cast<__m64*>(&output[offset]); + for (IndexType j = 0; j < NumChunks; ++j) + { + __m64 sum0 = *(&reinterpret_cast(accumulation[perspectives[p]])[j * 2 + 0]); + __m64 sum1 = *(&reinterpret_cast(accumulation[perspectives[p]])[j * 2 + 1]); + const __m64 packedbytes = _mm_packs_pi16(sum0, sum1); + out[j] = _mm_subs_pi8(_mm_adds_pi8(packedbytes, k0x80s), k0x80s); + } + } + _mm_empty(); + return psqt; + + #elif defined(USE_NEON) + + constexpr IndexType NumChunks = HalfDimensions / (SimdWidth / 2); + const int8x8_t Zero = {0}; + + for (IndexType p = 0; p < 2; ++p) + { + const IndexType offset = HalfDimensions * p; + const auto out = reinterpret_cast(&output[offset]); + for (IndexType j = 0; j < NumChunks; ++j) + { + int16x8_t sum = reinterpret_cast(accumulation[perspectives[p]])[j]; + out[j] = vmax_s8(vqmovn_s16(sum), Zero); + } + } + return psqt; + + #else + + for (IndexType p = 0; p < 2; ++p) + { + const IndexType offset = HalfDimensions * p; + for (IndexType j = 0; j < HalfDimensions; ++j) + { + BiasType sum = accumulation[perspectives[p]][j]; + output[offset + j] = static_cast(std::max(0, std::min(127, sum))); + } + } + return psqt; + + #endif + + } // end of function transform() + + + + private: + void update_accumulator(const Position& pos, const Color perspective) const { + + // The size must be enough to contain the largest possible update. + // That might depend on the feature set and generally relies on the + // feature set's update cost calculation to be correct and never + // allow updates with more added/removed features than MaxActiveDimensions. + using IndexList = ValueList; + + #ifdef VECTOR + // Gcc-10.2 unnecessarily spills AVX2 registers if this array + // is defined in the VECTOR code below, once in each branch + vec_t acc[NumRegs]; + psqt_vec_t psqt[NumPsqtRegs]; + #endif + + // Look for a usable accumulator of an earlier position. We keep track + // of the estimated gain in terms of features to be added/subtracted. + StateInfo *st = pos.state(), *next = nullptr; + int gain = FeatureSet::refresh_cost(pos); + while (st->previous && !st->accumulator.computed[perspective]) + { + // This governs when a full feature refresh is needed and how many + // updates are better than just one full refresh. + if ( FeatureSet::requires_refresh(st, perspective, pos) + || (gain -= FeatureSet::update_cost(st) + 1) < 0) + break; + next = st; + st = st->previous; + } + + if (st->accumulator.computed[perspective]) + { + if (next == nullptr) + return; + + // Update incrementally in two steps. First, we update the "next" + // accumulator. Then, we update the current accumulator (pos.state()). + + // Gather all features to be updated. + const Square ksq = pos.nnue_king_square(perspective); + IndexList removed[2], added[2]; + FeatureSet::append_changed_indices( + ksq, next, perspective, removed[0], added[0], pos); + for (StateInfo *st2 = pos.state(); st2 != next; st2 = st2->previous) + FeatureSet::append_changed_indices( + ksq, st2, perspective, removed[1], added[1], pos); + + // Mark the accumulators as computed. + next->accumulator.computed[perspective] = true; + pos.state()->accumulator.computed[perspective] = true; + + // Now update the accumulators listed in states_to_update[], where the last element is a sentinel. + StateInfo *states_to_update[3] = + { next, next == pos.state() ? nullptr : pos.state(), nullptr }; + #ifdef VECTOR + for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) + { + // Load accumulator + auto accTile = reinterpret_cast( + &st->accumulator.accumulation[perspective][j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_load(&accTile[k]); + + for (IndexType i = 0; states_to_update[i]; ++i) + { + // Difference calculation for the deactivated features + for (const auto index : removed[i]) + { + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_sub_16(acc[k], column[k]); + } + + // Difference calculation for the activated features + for (const auto index : added[i]) + { + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } + + // Store accumulator + accTile = reinterpret_cast( + &states_to_update[i]->accumulator.accumulation[perspective][j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + vec_store(&accTile[k], acc[k]); + } + } + + for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) + { + // Load accumulator + auto accTilePsqt = reinterpret_cast( + &st->accumulator.psqtAccumulation[perspective][j * PsqtTileHeight]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_load_psqt(&accTilePsqt[k]); + + for (IndexType i = 0; states_to_update[i]; ++i) + { + // Difference calculation for the deactivated features + for (const auto index : removed[i]) + { + const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; + auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); + } + + // Difference calculation for the activated features + for (const auto index : added[i]) + { + const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; + auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); + } + + // Store accumulator + accTilePsqt = reinterpret_cast( + &states_to_update[i]->accumulator.psqtAccumulation[perspective][j * PsqtTileHeight]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + vec_store_psqt(&accTilePsqt[k], psqt[k]); + } + } + + #else + for (IndexType i = 0; states_to_update[i]; ++i) + { + std::memcpy(states_to_update[i]->accumulator.accumulation[perspective], + st->accumulator.accumulation[perspective], + HalfDimensions * sizeof(BiasType)); + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + states_to_update[i]->accumulator.psqtAccumulation[perspective][k] = st->accumulator.psqtAccumulation[perspective][k]; + + st = states_to_update[i]; + + // Difference calculation for the deactivated features + for (const auto index : removed[i]) + { + const IndexType offset = HalfDimensions * index; + + for (IndexType j = 0; j < HalfDimensions; ++j) + st->accumulator.accumulation[perspective][j] -= weights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + st->accumulator.psqtAccumulation[perspective][k] -= psqtWeights[index * PSQTBuckets + k]; + } + + // Difference calculation for the activated features + for (const auto index : added[i]) + { + const IndexType offset = HalfDimensions * index; + + for (IndexType j = 0; j < HalfDimensions; ++j) + st->accumulator.accumulation[perspective][j] += weights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + st->accumulator.psqtAccumulation[perspective][k] += psqtWeights[index * PSQTBuckets + k]; + } + } + #endif + } + else + { + // Refresh the accumulator + auto& accumulator = pos.state()->accumulator; + accumulator.computed[perspective] = true; + IndexList active; + FeatureSet::append_active_indices(pos, perspective, active); + + #ifdef VECTOR + for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) + { + auto biasesTile = reinterpret_cast( + &biases[j * TileHeight]); + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = biasesTile[k]; + + for (const auto index : active) + { + const IndexType offset = HalfDimensions * index + j * TileHeight; + auto column = reinterpret_cast(&weights[offset]); + + for (unsigned k = 0; k < NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } + + auto accTile = reinterpret_cast( + &accumulator.accumulation[perspective][j * TileHeight]); + for (unsigned k = 0; k < NumRegs; k++) + vec_store(&accTile[k], acc[k]); + } + + for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) + { + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_zero_psqt(); + + for (const auto index : active) + { + const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; + auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); + + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); + } + + auto accTilePsqt = reinterpret_cast( + &accumulator.psqtAccumulation[perspective][j * PsqtTileHeight]); + for (std::size_t k = 0; k < NumPsqtRegs; ++k) + vec_store_psqt(&accTilePsqt[k], psqt[k]); + } + + #else + std::memcpy(accumulator.accumulation[perspective], biases, + HalfDimensions * sizeof(BiasType)); + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + accumulator.psqtAccumulation[perspective][k] = 0; + + for (const auto index : active) + { + const IndexType offset = HalfDimensions * index; + + for (IndexType j = 0; j < HalfDimensions; ++j) + accumulator.accumulation[perspective][j] += weights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + accumulator.psqtAccumulation[perspective][k] += psqtWeights[index * PSQTBuckets + k]; + } + #endif + } + + #if defined(USE_MMX) + _mm_empty(); + #endif + } + + alignas(CacheLineSize) BiasType biases[HalfDimensions]; + alignas(CacheLineSize) WeightType weights[HalfDimensions * InputDimensions]; + alignas(CacheLineSize) PSQTWeightType psqtWeights[InputDimensions * PSQTBuckets]; + }; + +} // namespace Stockfish::Eval::NNUE + +#endif // #ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED diff --git a/Fairy-Stockfish-master/src/parser.cpp b/Fairy-Stockfish-master/src/parser.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cbd24046ee7cc221dc892a3a0ee79c3ce020efc3 --- /dev/null +++ b/Fairy-Stockfish-master/src/parser.cpp @@ -0,0 +1,656 @@ +/* + Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish + Copyright (C) 2018-2022 Fabian Fichter + + Fairy-Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Fairy-Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#include "apiutil.h" +#include "parser.h" +#include "piece.h" +#include "types.h" + +namespace Stockfish { + +namespace { + + template bool set(const std::string& value, T& target) + { + std::stringstream ss(value); + ss >> target; + return !ss.fail(); + } + + template <> bool set(const std::string& value, Rank& target) { + std::stringstream ss(value); + int i; + ss >> i; + target = Rank(i - 1); + return !ss.fail() && target >= RANK_1 && target <= RANK_MAX; + } + + template <> bool set(const std::string& value, File& target) { + std::stringstream ss(value); + if (isdigit(ss.peek())) + { + int i; + ss >> i; + target = File(i - 1); + } + else + { + char c; + ss >> c; + target = File(c - 'a'); + } + return !ss.fail() && target >= FILE_A && target <= FILE_MAX; + } + + template <> bool set(const std::string& value, std::string& target) { + target = value; + return true; + } + + template <> bool set(const std::string& value, bool& target) { + target = value == "true"; + return value == "true" || value == "false"; + } + + template <> bool set(const std::string& value, Value& target) { + target = value == "win" ? VALUE_MATE + : value == "loss" ? -VALUE_MATE + : value == "draw" ? VALUE_DRAW + : VALUE_NONE; + return value == "win" || value == "loss" || value == "draw" || value == "none"; + } + + template <> bool set(const std::string& value, MaterialCounting& target) { + target = value == "janggi" ? JANGGI_MATERIAL + : value == "unweighted" ? UNWEIGHTED_MATERIAL + : value == "whitedrawodds" ? WHITE_DRAW_ODDS + : value == "blackdrawodds" ? BLACK_DRAW_ODDS + : NO_MATERIAL_COUNTING; + return value == "janggi" || value == "unweighted" + || value == "whitedrawodds" || value == "blackdrawodds" || value == "none"; + } + + template <> bool set(const std::string& value, CountingRule& target) { + target = value == "makruk" ? MAKRUK_COUNTING + : value == "cambodian" ? CAMBODIAN_COUNTING + : value == "asean" ? ASEAN_COUNTING + : NO_COUNTING; + return value == "makruk" || value == "asean" || value == "none"; + } + + template <> bool set(const std::string& value, ChasingRule& target) { + target = value == "axf" ? AXF_CHASING + : NO_CHASING; + return value == "axf" || value == "none"; + } + + template <> bool set(const std::string& value, EnclosingRule& target) { + target = value == "reversi" ? REVERSI + : value == "ataxx" ? ATAXX + : value == "quadwrangle" ? QUADWRANGLE + : value == "snort" ? SNORT + : value == "anyside" ? ANYSIDE + : value == "top" ? TOP + : NO_ENCLOSING; + return value == "reversi" || value == "ataxx" || value == "quadwrangle" || value =="snort" || value =="anyside" || value =="top" || value == "none"; + } + + template <> bool set(const std::string& value, WallingRule& target) { + target = value == "arrow" ? ARROW + : value == "duck" ? DUCK + : value == "edge" ? EDGE + : value == "past" ? PAST + : value == "static" ? STATIC + : NO_WALLING; + return value == "arrow" || value == "duck" || value == "edge" || value =="past" || value == "static" || value == "none"; + } + + template <> bool set(const std::string& value, Bitboard& target) { + std::string symbol; + std::stringstream ss(value); + target = 0; + while (!ss.eof() && ss >> symbol && symbol != "-") + { + if (symbol.back() == '*') { + if (isalpha(symbol[0]) && symbol.length() == 2) { + char file = tolower(symbol[0]); + if (File(file - 'a') > FILE_MAX) return false; + target |= file_bb(File(file - 'a')); + } else { + return false; + } + } else if (symbol[0] == '*') { + int rank = std::stoi(symbol.substr(1)); + if (Rank(rank - 1) > RANK_MAX) return false; + target |= rank_bb(Rank(rank - 1)); + } else if (isalpha(symbol[0]) && symbol.length() > 1) { + char file = tolower(symbol[0]); + int rank = std::stoi(symbol.substr(1)); + if (Rank(rank - 1) > RANK_MAX || File(file - 'a') > FILE_MAX) return false; + target |= square_bb(make_square(File(file - 'a'), Rank(rank - 1))); + } else { + return false; + } + } + return !ss.fail(); + } + + + template <> bool set(const std::string& value, CastlingRights& target) { + char c; + CastlingRights castlingRight; + std::stringstream ss(value); + target = NO_CASTLING; + bool valid = true; + while (ss >> c && c != '-') + { + castlingRight = c == 'K' ? WHITE_OO + : c == 'Q' ? WHITE_OOO + : c == 'k' ? BLACK_OO + : c == 'q' ? BLACK_OOO + : NO_CASTLING; + if (castlingRight) + target = CastlingRights(target | castlingRight); + else + valid = false; + } + return valid; + } + + template void set(PieceType pt, T& target) { + target.insert(pt); + } + + template <> void set(PieceType pt, PieceType& target) { + target = pt; + } + + template <> void set(PieceType pt, PieceSet& target) { + target |= pt; + } + +} // namespace + +template +template bool VariantParser::parse_attribute(const std::string& key, T& target) { + const auto& it = config.find(key); + if (it != config.end()) + { + bool valid = set(it->second, target); + if (DoCheck && !Current) + std::cerr << key << " - Deprecated option might be removed in future version." << std::endl; + if (DoCheck && !valid) + { + std::string typeName = std::is_same() ? "int" + : std::is_same() ? "Rank" + : std::is_same() ? "File" + : std::is_same() ? "bool" + : std::is_same() ? "Value" + : std::is_same() ? "MaterialCounting" + : std::is_same() ? "CountingRule" + : std::is_same() ? "ChasingRule" + : std::is_same() ? "EnclosingRule" + : std::is_same() ? "Bitboard" + : std::is_same() ? "CastlingRights" + : std::is_same() ? "WallingRule" + : typeid(T).name(); + std::cerr << key << " - Invalid value " << it->second << " for type " << typeName << std::endl; + } + return valid; + } + return false; +} + +template +template bool VariantParser::parse_attribute(const std::string& key, T& target, std::string pieceToChar) { + const auto& it = config.find(key); + if (it != config.end()) + { + target = T(); + char token; + size_t idx = std::string::npos; + std::stringstream ss(it->second); + while (ss >> token && (idx = token == '*' ? size_t(ALL_PIECES) : pieceToChar.find(toupper(token))) != std::string::npos) + set(PieceType(idx), target); + if (DoCheck && idx == std::string::npos && token != '-') + std::cerr << key << " - Invalid piece type: " << token << std::endl; + return idx != std::string::npos || token == '-'; + } + return false; +} + +template +Variant* VariantParser::parse() { + Variant* v = new Variant(); + v->reset_pieces(); + return parse(v); +} + +template +Variant* VariantParser::parse(Variant* v) { + parse_attribute("maxRank", v->maxRank); + parse_attribute("maxFile", v->maxFile); + // piece types + for (PieceType pt = PAWN; pt <= KING; ++pt) + { + if (pt == CUSTOM_PIECES_ROYAL) + // reserved custom royal/king slot + continue; + + // piece char + std::string name = piece_name(pt); + + const auto& keyValue = config.find(name); + if (keyValue != config.end() && !keyValue->second.empty()) + { + if (isalpha(keyValue->second.at(0))) + v->add_piece(pt, keyValue->second.at(0)); + else + { + if (DoCheck && keyValue->second.at(0) != '-') + std::cerr << name << " - Invalid letter: " << keyValue->second.at(0) << std::endl; + v->remove_piece(pt); + } + // betza + if (is_custom(pt)) + { + if (keyValue->second.size() > 1) + { + v->customPiece[pt - CUSTOM_PIECES] = keyValue->second.substr(2); + // Is there an en passant flag in the Betza notation? + if (v->customPiece[pt - CUSTOM_PIECES].find('e') != std::string::npos) + { + v->enPassantTypes[WHITE] |= piece_set(pt); + v->enPassantTypes[BLACK] |= piece_set(pt); + } + } + else if (DoCheck) + std::cerr << name << " - Missing Betza move notation" << std::endl; + } + else if (pt == KING) + { + if (keyValue->second.size() > 1) + { + // custom royal piece + v->customPiece[CUSTOM_PIECES_ROYAL - CUSTOM_PIECES] = keyValue->second.substr(2); + v->kingType = CUSTOM_PIECES_ROYAL; + } + else + v->kingType = KING; + } + } + // mobility region + std::string capitalizedPiece = name; + capitalizedPiece[0] = toupper(capitalizedPiece[0]); + for (Color c : {WHITE, BLACK}) + { + std::string color = c == WHITE ? "White" : "Black"; + parse_attribute("mobilityRegion" + color + capitalizedPiece, v->mobilityRegion[c][pt]); + } + } + // piece values + for (Phase phase : {MG, EG}) + { + const std::string optionName = phase == MG ? "pieceValueMg" : "pieceValueEg"; + const auto& pv = config.find(optionName); + if (pv != config.end()) + { + char token; + size_t idx = 0; + std::stringstream ss(pv->second); + while (!ss.eof() && ss >> token && (idx = v->pieceToChar.find(toupper(token))) != std::string::npos + && ss >> token && ss >> v->pieceValue[phase][idx]) {} + if (DoCheck && idx == std::string::npos) + std::cerr << optionName << " - Invalid piece type: " << token << std::endl; + else if (DoCheck && !ss.eof()) + std::cerr << optionName << " - Invalid piece value for type: " << v->pieceToChar[idx] << std::endl; + } + } + + // Parse deprecate values for backwards compatibility + Rank promotionRank = RANK_8; + if (parse_attribute("promotionRank", promotionRank)) + { + for (Color c : {WHITE, BLACK}) + v->promotionRegion[c] = zone_bb(c, promotionRank, v->maxRank); + } + Rank doubleStepRank = RANK_2; + Rank doubleStepRankMin = RANK_2; + if ( parse_attribute("doubleStepRank", doubleStepRank) + || parse_attribute("doubleStepRankMin", doubleStepRankMin)) + { + for (Color c : {WHITE, BLACK}) + v->doubleStepRegion[c] = zone_bb(c, doubleStepRankMin, v->maxRank) + & ~forward_ranks_bb(c, relative_rank(c, doubleStepRank, v->maxRank)); + } + parse_attribute("whiteFlag", v->flagRegion[WHITE]); + parse_attribute("blackFlag", v->flagRegion[BLACK]); + parse_attribute("castlingRookPiece", v->castlingRookPieces[WHITE], v->pieceToChar); + parse_attribute("castlingRookPiece", v->castlingRookPieces[BLACK], v->pieceToChar); + parse_attribute("whiteDropRegion", v->dropRegion[WHITE]); + parse_attribute("blackDropRegion", v->dropRegion[BLACK]); + + bool dropOnTop = false; + parse_attribute("dropOnTop", dropOnTop); + if (dropOnTop) v->enclosingDrop=TOP; + + // Parse aliases + parse_attribute("pawnTypes", v->mainPromotionPawnType[WHITE], v->pieceToChar); + parse_attribute("pawnTypes", v->mainPromotionPawnType[BLACK], v->pieceToChar); + parse_attribute("pawnTypes", v->promotionPawnTypes[WHITE], v->pieceToChar); + parse_attribute("pawnTypes", v->promotionPawnTypes[BLACK], v->pieceToChar); + parse_attribute("pawnTypes", v->enPassantTypes[WHITE], v->pieceToChar); + parse_attribute("pawnTypes", v->enPassantTypes[BLACK], v->pieceToChar); + parse_attribute("pawnTypes", v->nMoveRuleTypes[WHITE], v->pieceToChar); + parse_attribute("pawnTypes", v->nMoveRuleTypes[BLACK], v->pieceToChar); + + // Parse the official config options + parse_attribute("variantTemplate", v->variantTemplate); + parse_attribute("pieceToCharTable", v->pieceToCharTable); + parse_attribute("pocketSize", v->pocketSize); + parse_attribute("chess960", v->chess960); + parse_attribute("twoBoards", v->twoBoards); + parse_attribute("startFen", v->startFen); + parse_attribute("promotionRegionWhite", v->promotionRegion[WHITE]); + parse_attribute("promotionRegionBlack", v->promotionRegion[BLACK]); + // Take the first promotionPawnTypes as the main promotionPawnType + parse_attribute("promotionPawnTypes", v->mainPromotionPawnType[WHITE], v->pieceToChar); + parse_attribute("promotionPawnTypes", v->mainPromotionPawnType[BLACK], v->pieceToChar); + parse_attribute("promotionPawnTypes", v->promotionPawnTypes[WHITE], v->pieceToChar); + parse_attribute("promotionPawnTypes", v->promotionPawnTypes[BLACK], v->pieceToChar); + parse_attribute("promotionPawnTypesWhite", v->mainPromotionPawnType[WHITE], v->pieceToChar); + parse_attribute("promotionPawnTypesBlack", v->mainPromotionPawnType[BLACK], v->pieceToChar); + parse_attribute("promotionPawnTypesWhite", v->promotionPawnTypes[WHITE], v->pieceToChar); + parse_attribute("promotionPawnTypesBlack", v->promotionPawnTypes[BLACK], v->pieceToChar); + parse_attribute("promotionPieceTypes", v->promotionPieceTypes[WHITE], v->pieceToChar); + parse_attribute("promotionPieceTypes", v->promotionPieceTypes[BLACK], v->pieceToChar); + parse_attribute("promotionPieceTypesWhite", v->promotionPieceTypes[WHITE], v->pieceToChar); + parse_attribute("promotionPieceTypesBlack", v->promotionPieceTypes[BLACK], v->pieceToChar); + parse_attribute("sittuyinPromotion", v->sittuyinPromotion); + // promotion limit + const auto& it_prom_limit = config.find("promotionLimit"); + if (it_prom_limit != config.end()) + { + char token; + size_t idx = 0; + std::stringstream ss(it_prom_limit->second); + while (!ss.eof() && ss >> token && (idx = v->pieceToChar.find(toupper(token))) != std::string::npos + && ss >> token && ss >> v->promotionLimit[idx]) {} + if (DoCheck && idx == std::string::npos) + std::cerr << "promotionLimit - Invalid piece type: " << token << std::endl; + else if (DoCheck && !ss.eof()) + std::cerr << "promotionLimit - Invalid piece count for type: " << v->pieceToChar[idx] << std::endl; + } + // promoted piece types + const auto& it_prom_pt = config.find("promotedPieceType"); + if (it_prom_pt != config.end()) + { + char token; + size_t idx = 0, idx2 = 0; + std::stringstream ss(it_prom_pt->second); + while ( ss >> token && (idx = v->pieceToChar.find(toupper(token))) != std::string::npos && ss >> token + && ss >> token && (idx2 = (token == '-' ? 0 : v->pieceToChar.find(toupper(token)))) != std::string::npos) + v->promotedPieceType[idx] = PieceType(idx2); + if (DoCheck && (idx == std::string::npos || idx2 == std::string::npos)) + std::cerr << "promotedPieceType - Invalid piece type: " << token << std::endl; + } + parse_attribute("piecePromotionOnCapture", v->piecePromotionOnCapture); + parse_attribute("mandatoryPawnPromotion", v->mandatoryPawnPromotion); + parse_attribute("mandatoryPiecePromotion", v->mandatoryPiecePromotion); + parse_attribute("pieceDemotion", v->pieceDemotion); + parse_attribute("blastOnCapture", v->blastOnCapture); + parse_attribute("blastImmuneTypes", v->blastImmuneTypes, v->pieceToChar); + parse_attribute("mutuallyImmuneTypes", v->mutuallyImmuneTypes, v->pieceToChar); + parse_attribute("petrifyOnCaptureTypes", v->petrifyOnCaptureTypes, v->pieceToChar); + parse_attribute("petrifyBlastPieces", v->petrifyBlastPieces); + parse_attribute("doubleStep", v->doubleStep); + parse_attribute("doubleStepRegionWhite", v->doubleStepRegion[WHITE]); + parse_attribute("doubleStepRegionBlack", v->doubleStepRegion[BLACK]); + parse_attribute("tripleStepRegionWhite", v->tripleStepRegion[WHITE]); + parse_attribute("tripleStepRegionBlack", v->tripleStepRegion[BLACK]); + parse_attribute("enPassantRegion", v->enPassantRegion[WHITE]); + parse_attribute("enPassantRegion", v->enPassantRegion[BLACK]); + parse_attribute("enPassantRegionWhite", v->enPassantRegion[WHITE]); + parse_attribute("enPassantRegionBlack", v->enPassantRegion[BLACK]); + parse_attribute("enPassantTypes", v->enPassantTypes[WHITE], v->pieceToChar); + parse_attribute("enPassantTypes", v->enPassantTypes[BLACK], v->pieceToChar); + parse_attribute("enPassantTypesWhite", v->enPassantTypes[WHITE], v->pieceToChar); + parse_attribute("enPassantTypesBlack", v->enPassantTypes[BLACK], v->pieceToChar); + parse_attribute("castling", v->castling); + parse_attribute("castlingDroppedPiece", v->castlingDroppedPiece); + parse_attribute("castlingKingsideFile", v->castlingKingsideFile); + parse_attribute("castlingQueensideFile", v->castlingQueensideFile); + parse_attribute("castlingRank", v->castlingRank); + parse_attribute("castlingKingFile", v->castlingKingFile); + parse_attribute("castlingKingPiece", v->castlingKingPiece[WHITE], v->pieceToChar); + parse_attribute("castlingKingPiece", v->castlingKingPiece[BLACK], v->pieceToChar); + parse_attribute("castlingKingPieceWhite", v->castlingKingPiece[WHITE], v->pieceToChar); + parse_attribute("castlingKingPieceBlack", v->castlingKingPiece[BLACK], v->pieceToChar); + parse_attribute("castlingRookKingsideFile", v->castlingRookKingsideFile); + parse_attribute("castlingRookQueensideFile", v->castlingRookQueensideFile); + parse_attribute("castlingRookPieces", v->castlingRookPieces[WHITE], v->pieceToChar); + parse_attribute("castlingRookPieces", v->castlingRookPieces[BLACK], v->pieceToChar); + parse_attribute("castlingRookPiecesWhite", v->castlingRookPieces[WHITE], v->pieceToChar); + parse_attribute("castlingRookPiecesBlack", v->castlingRookPieces[BLACK], v->pieceToChar); + parse_attribute("oppositeCastling", v->oppositeCastling); + parse_attribute("checking", v->checking); + parse_attribute("dropChecks", v->dropChecks); + parse_attribute("mustCapture", v->mustCapture); + parse_attribute("mustDrop", v->mustDrop); + parse_attribute("mustDropType", v->mustDropType, v->pieceToChar); + parse_attribute("pieceDrops", v->pieceDrops); + parse_attribute("dropLoop", v->dropLoop); + parse_attribute("capturesToHand", v->capturesToHand); + parse_attribute("firstRankPawnDrops", v->firstRankPawnDrops); + parse_attribute("promotionZonePawnDrops", v->promotionZonePawnDrops); + parse_attribute("enclosingDrop", v->enclosingDrop); + parse_attribute("enclosingDropStart", v->enclosingDropStart); + parse_attribute("dropRegionWhite", v->dropRegion[WHITE]); + parse_attribute("dropRegionBlack", v->dropRegion[BLACK]); + parse_attribute("sittuyinRookDrop", v->sittuyinRookDrop); + parse_attribute("dropOppositeColoredBishop", v->dropOppositeColoredBishop); + parse_attribute("dropPromoted", v->dropPromoted); + parse_attribute("dropNoDoubled", v->dropNoDoubled, v->pieceToChar); + parse_attribute("dropNoDoubledCount", v->dropNoDoubledCount); + parse_attribute("immobilityIllegal", v->immobilityIllegal); + parse_attribute("gating", v->gating); + parse_attribute("wallingRule", v->wallingRule); + parse_attribute("wallingRegionWhite", v->wallingRegion[WHITE]); + parse_attribute("wallingRegionBlack", v->wallingRegion[BLACK]); + parse_attribute("wallingRegion", v->wallingRegion[WHITE]); + parse_attribute("wallingRegion", v->wallingRegion[BLACK]); + parse_attribute("wallOrMove", v->wallOrMove); + parse_attribute("seirawanGating", v->seirawanGating); + parse_attribute("cambodianMoves", v->cambodianMoves); + parse_attribute("diagonalLines", v->diagonalLines); + parse_attribute("pass", v->pass[WHITE]); + parse_attribute("pass", v->pass[BLACK]); + parse_attribute("passWhite", v->pass[WHITE]); + parse_attribute("passBlack", v->pass[BLACK]); + parse_attribute("passOnStalemate", v->passOnStalemate[WHITE]); + parse_attribute("passOnStalemate", v->passOnStalemate[BLACK]); + parse_attribute("passOnStalemateWhite", v->passOnStalemate[WHITE]); + parse_attribute("passOnStalemateBlack", v->passOnStalemate[BLACK]); + parse_attribute("makpongRule", v->makpongRule); + parse_attribute("flyingGeneral", v->flyingGeneral); + parse_attribute("soldierPromotionRank", v->soldierPromotionRank); + parse_attribute("flipEnclosedPieces", v->flipEnclosedPieces); + // game end + parse_attribute("nMoveRuleTypes", v->nMoveRuleTypes[WHITE], v->pieceToChar); + parse_attribute("nMoveRuleTypes", v->nMoveRuleTypes[BLACK], v->pieceToChar); + parse_attribute("nMoveRuleTypesWhite", v->nMoveRuleTypes[WHITE], v->pieceToChar); + parse_attribute("nMoveRuleTypesBlack", v->nMoveRuleTypes[BLACK], v->pieceToChar); + parse_attribute("nMoveRule", v->nMoveRule); + parse_attribute("nFoldRule", v->nFoldRule); + parse_attribute("nFoldValue", v->nFoldValue); + parse_attribute("nFoldValueAbsolute", v->nFoldValueAbsolute); + parse_attribute("perpetualCheckIllegal", v->perpetualCheckIllegal); + parse_attribute("moveRepetitionIllegal", v->moveRepetitionIllegal); + parse_attribute("chasingRule", v->chasingRule); + parse_attribute("stalemateValue", v->stalemateValue); + parse_attribute("stalematePieceCount", v->stalematePieceCount); + parse_attribute("checkmateValue", v->checkmateValue); + parse_attribute("shogiPawnDropMateIllegal", v->shogiPawnDropMateIllegal); + parse_attribute("shatarMateRule", v->shatarMateRule); + parse_attribute("bikjangRule", v->bikjangRule); + parse_attribute("extinctionValue", v->extinctionValue); + parse_attribute("extinctionClaim", v->extinctionClaim); + parse_attribute("extinctionPseudoRoyal", v->extinctionPseudoRoyal); + parse_attribute("dupleCheck", v->dupleCheck); + // extinction piece types + parse_attribute("extinctionPieceTypes", v->extinctionPieceTypes, v->pieceToChar); + parse_attribute("extinctionPieceCount", v->extinctionPieceCount); + parse_attribute("extinctionOpponentPieceCount", v->extinctionOpponentPieceCount); + parse_attribute("flagPiece", v->flagPiece[WHITE], v->pieceToChar); + parse_attribute("flagPiece", v->flagPiece[BLACK], v->pieceToChar); + parse_attribute("flagPieceWhite", v->flagPiece[WHITE], v->pieceToChar); + parse_attribute("flagPieceBlack", v->flagPiece[BLACK], v->pieceToChar); + parse_attribute("flagRegion", v->flagRegion[WHITE]); + parse_attribute("flagRegion", v->flagRegion[BLACK]); + parse_attribute("flagRegionWhite", v->flagRegion[WHITE]); + parse_attribute("flagRegionBlack", v->flagRegion[BLACK]); + parse_attribute("flagPieceCount", v->flagPieceCount); + parse_attribute("flagPieceBlockedWin", v->flagPieceBlockedWin); + parse_attribute("flagMove", v->flagMove); + parse_attribute("flagPieceSafe", v->flagPieceSafe); + parse_attribute("checkCounting", v->checkCounting); + parse_attribute("connectN", v->connectN); + parse_attribute("connectPieceTypes", v->connectPieceTypes, v->pieceToChar); + parse_attribute("connectHorizontal", v->connectHorizontal); + parse_attribute("connectVertical", v->connectVertical); + parse_attribute("connectDiagonal", v->connectDiagonal); + parse_attribute("connectRegion1White", v->connectRegion1[WHITE]); + parse_attribute("connectRegion2White", v->connectRegion2[WHITE]); + parse_attribute("connectRegion1Black", v->connectRegion1[BLACK]); + parse_attribute("connectRegion2Black", v->connectRegion2[BLACK]); + parse_attribute("connectNxN", v->connectNxN); + parse_attribute("collinearN", v->collinearN); + parse_attribute("connectValue", v->connectValue); + parse_attribute("materialCounting", v->materialCounting); + parse_attribute("adjudicateFullBoard", v->adjudicateFullBoard); + parse_attribute("countingRule", v->countingRule); + parse_attribute("castlingWins", v->castlingWins); + + // Report invalid options + if (DoCheck) + { + const std::set& parsedKeys = config.get_consumed_keys(); + for (const auto& it : config) + if (parsedKeys.find(it.first) == parsedKeys.end()) + std::cerr << "Invalid option: " << it.first << std::endl; + } + // Check consistency + if (DoCheck) + { + // pieces + for (PieceSet ps = v->pieceTypes; ps;) + { + PieceType pt = pop_lsb(ps); + for (Color c : {WHITE, BLACK}) + if (std::count(v->pieceToChar.begin(), v->pieceToChar.end(), v->pieceToChar[make_piece(c, pt)]) != 1) + std::cerr << piece_name(pt) << " - Ambiguous piece character: " << v->pieceToChar[make_piece(c, pt)] << std::endl; + } + + v->conclude(); // In preparation for the consistency checks below + + // startFen + if (FEN::validate_fen(v->startFen, v, v->chess960) != FEN::FEN_OK) + std::cerr << "startFen - Invalid starting position: " << v->startFen << std::endl; + + // pieceToCharTable + if (v->pieceToCharTable != "-") + { + const std::string fenBoard = v->startFen.substr(0, v->startFen.find(' ')); + std::stringstream ss(v->pieceToCharTable); + char token; + while (ss >> token) + if (isalpha(token) && v->pieceToChar.find(toupper(token)) == std::string::npos) + std::cerr << "pieceToCharTable - Invalid piece type: " << token << std::endl; + for (PieceSet ps = v->pieceTypes; ps;) + { + PieceType pt = pop_lsb(ps); + char ptl = tolower(v->pieceToChar[pt]); + if (v->pieceToCharTable.find(ptl) == std::string::npos && fenBoard.find(ptl) != std::string::npos) + std::cerr << "pieceToCharTable - Missing piece type: " << ptl << std::endl; + char ptu = toupper(v->pieceToChar[pt]); + if (v->pieceToCharTable.find(ptu) == std::string::npos && fenBoard.find(ptu) != std::string::npos) + std::cerr << "pieceToCharTable - Missing piece type: " << ptu << std::endl; + } + } + + // Contradictory options + if (!v->checking && v->checkCounting) + std::cerr << "checkCounting=true requires checking=true." << std::endl; + if (v->castling && v->castlingRank > v->maxRank) + std::cerr << "Inconsistent settings: castlingRank > maxRank." << std::endl; + if (v->castling && v->castlingQueensideFile > v->castlingKingsideFile) + std::cerr << "Inconsistent settings: castlingQueensideFile > castlingKingsideFile." << std::endl; + + // Check for limitations + if (v->pieceDrops && v->wallingRule) + std::cerr << "pieceDrops and any walling are incompatible." << std::endl; + + // Options incompatible with royal kings + if (v->pieceTypes & KING) + { + if (v->blastOnCapture) + std::cerr << "Can not use kings with blastOnCapture." << std::endl; + if (v->flipEnclosedPieces) + std::cerr << "Can not use kings with flipEnclosedPieces." << std::endl; + if (v->wallingRule==DUCK) + std::cerr << "Can not use kings with wallingRule = duck." << std::endl; + // We can not fully check support for custom king movements at this point, + // since custom pieces are only initialized on loading of the variant. + // We will assume this is valid, but it might cause problems later if it's not. + if (!is_custom(v->kingType)) + { + const PieceInfo* pi = pieceMap.find(v->kingType)->second; + if ( pi->hopper[0][MODALITY_QUIET].size() + || pi->hopper[0][MODALITY_CAPTURE].size() + || std::any_of(pi->steps[0][MODALITY_CAPTURE].begin(), + pi->steps[0][MODALITY_CAPTURE].end(), + [](const std::pair& d) { return d.second; })) + std::cerr << piece_name(v->kingType) << " is not supported as kingType." << std::endl; + } + } + // Options incompatible with royal kings OR pseudo-royal kings. Possible in theory though: + // 1. In blast variants, moving a (pseudo-)royal blastImmuneType into another piece is legal. + // 2. In blast variants, capturing a piece next to a (pseudo-)royal blastImmuneType is legal. + // 3. Moving a (pseudo-)royal mutuallyImmuneType into a square threatened by the same type is legal. + if ((v->extinctionPseudoRoyal) || (v->pieceTypes & KING)) + { + if (v->blastImmuneTypes) + std::cerr << "Can not use kings or pseudo-royal with blastImmuneTypes." << std::endl; + if (v->mutuallyImmuneTypes) + std::cerr << "Can not use kings or pseudo-royal with mutuallyImmuneTypes." << std::endl; + } + if (v->flagPieceSafe && v->blastOnCapture) + std::cerr << "Can not use flagPieceSafe with blastOnCapture (flagPieceSafe uses simple assessment that does not see blast)." << std::endl; + } + return v; +} + +template Variant* VariantParser::parse(); +template Variant* VariantParser::parse(); +template Variant* VariantParser::parse(Variant* v); +template Variant* VariantParser::parse(Variant* v); + +} // namespace Stockfish diff --git a/Fairy-Stockfish-master/src/parser.h b/Fairy-Stockfish-master/src/parser.h new file mode 100644 index 0000000000000000000000000000000000000000..04610b2ffefa05087dce48ce2f874d5744c985ce --- /dev/null +++ b/Fairy-Stockfish-master/src/parser.h @@ -0,0 +1,59 @@ +/* + Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish + Copyright (C) 2018-2022 Fabian Fichter + + Fairy-Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Fairy-Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef PARSER_H_INCLUDED +#define PARSER_H_INCLUDED + +#include + +#include "variant.h" + +namespace Stockfish { + +class Config : public std::map { +public: + Config::iterator find (const std::string& s) { + constexpr bool PrintOptions = false; // print config options? + if (PrintOptions) + std::cout << s << std::endl; + consumedKeys.insert(s); + return std::map::find(s); + } + const std::set& get_consumed_keys() { + return consumedKeys; + } +private: + std::set consumedKeys = {}; +}; + +template +class VariantParser { +public: + VariantParser(const Config& c) : config (c) {}; + Variant* parse(); + Variant* parse(Variant* v); + +private: + Config config; + template bool parse_attribute(const std::string& key, T& target); + template bool parse_attribute(const std::string& key, T& target, std::string pieceToChar); +}; + +} // namespace Stockfish + +#endif // #ifndef PARSER_H_INCLUDED \ No newline at end of file diff --git a/Fairy-Stockfish-master/src/partner.cpp b/Fairy-Stockfish-master/src/partner.cpp new file mode 100644 index 0000000000000000000000000000000000000000..785394fc3f7c48d3b286656b1f449a50f536c26b --- /dev/null +++ b/Fairy-Stockfish-master/src/partner.cpp @@ -0,0 +1,158 @@ +/* + Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish + Copyright (C) 2018-2022 Fabian Fichter + + Fairy-Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Fairy-Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include + +#include "partner.h" +#include "thread.h" +#include "uci.h" + +namespace Stockfish { + +PartnerHandler Partner; // Global object + +void PartnerHandler::reset() { + fast = sitRequested = partnerDead = weDead = weWin = weVirtualWin = weVirtualLoss = false; + time = opptime = 0; +} + +template +void PartnerHandler::ptell(const std::string& message) { + if (p == ALL_PARTNERS || (p == FAIRY && isFairy) || (p == HUMAN && !isFairy)) + sync_cout << "tellics ptell " << message << sync_endl; +} + +void PartnerHandler::parse_partner(std::istringstream& is) { + std::string token; + if (is >> token) + // handshake to identify Fairy-Stockfish + ptell("partner Fairy-Stockfish is an engine. Ask it 'help' for supported commands."); + else + isFairy = false; +} + +void PartnerHandler::parse_ptell(std::istringstream& is, const Position& pos) { + std::string token; + is >> token; + if (token == "partner") + { + // handshake to identify Fairy-Stockfish + if (is >> token && token == "Fairy-Stockfish") + isFairy = true; + } + else if (token == "help") + { + if (!(is >> token)) + { + ptell("I listen to the commands help, sit, go, move, fast, slow, dead, x, time, and otim."); + ptell("Tell 'help sit', etc. for details."); + } + else if (token == "sit") + ptell("After receiving 'sit', I stop moving. Also see 'go'."); + else if (token == "go") + ptell("After receiving 'go', I will no longer sit."); + else if (token == "move") + { + ptell("After receiving 'move', I will move immediately." ); + ptell("If you specify a valid move, e.g., 'move e2e4', I will play it."); + } + else if (token == "fast") + ptell("After receiving 'fast', I will play fast."); + else if (token == "slow") + ptell("After receiving 'slow', I will play at normal speed."); + else if (token == "dead") + ptell("After receiving 'dead', I assume you are dead and I play fast."); + else if (token == "x") + ptell("After receiving 'x', I assume I can play normally again."); + else if (token == "time") + { + ptell("'time' together with your time in centiseconds allows me to consider your time."); + ptell("E.g., 'time 1000' for 10 seconds."); + } + else if (token == "otim") + ptell("'otim' together with your opponent's time in centiseconds allows me to consider his time."); + } + else if (!pos.two_boards()) + return; + else if (token == "sit") + { + // Avoid deadlocking sit + if (!isFairy || !weWin) + sitRequested = true; + ptell("I sit, tell me 'go' to continue"); + } + else if (token == "go") + { + sitRequested = false; + Threads.stop = true; + } + else if (token == "move") + { + if (is >> token) + { + // if the given move is valid and we can still abort the search, play it + Move move = UCI::to_move(pos, token); + if (move && !Threads.abort.exchange(true)) + moveRequested = move; + else + ptell("sorry, not possible"); + } + else + // Move immediately on request + Threads.stop = true; + } + else if (token == "fast") + { + fast = true; + ptell("I play fast, tell me 'slow' to play normally again"); + } + else if (token == "slow") + { + fast = false; + ptell("I play at normal speed again."); + } + else if (token == "dead") + { + partnerDead = true; + ptell("I play fast, tell me 'x' if you are no longer dead."); + } + else if (token == "x") + { + partnerDead = false; + sitRequested = false; + ptell("I play normally again"); + } + else if (token == "time") + { + int value; + time = (is >> value) ? value * 10 : 0; + } + else if (token == "otim") + { + int value; + opptime = (is >> value) ? value * 10 : 0; + } +} + +template void PartnerHandler::ptell(const std::string&); +template void PartnerHandler::ptell(const std::string&); +template void PartnerHandler::ptell(const std::string&); + +} // namespace Stockfish diff --git a/Fairy-Stockfish-master/src/partner.h b/Fairy-Stockfish-master/src/partner.h new file mode 100644 index 0000000000000000000000000000000000000000..64c5f654cba87a3d84401206065b4bbc33a7c151 --- /dev/null +++ b/Fairy-Stockfish-master/src/partner.h @@ -0,0 +1,56 @@ +/* + Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish + Copyright (C) 2018-2022 Fabian Fichter + + Fairy-Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Fairy-Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef PARTNER_H_INCLUDED +#define PARTNER_H_INCLUDED + +#include +#include + +#include "misc.h" +#include "position.h" + +namespace Stockfish { + +/// PartnerHandler manages the communication with the partner +/// in games played on two boards, such as bughouse. + +enum PartnerType { + HUMAN, + FAIRY, + ALL_PARTNERS +}; + +struct PartnerHandler { + void reset(); + template + void ptell(const std::string& message); + void parse_partner(std::istringstream& is); + void parse_ptell(std::istringstream& is, const Position& pos); + + std::atomic isFairy; + std::atomic fast, sitRequested, partnerDead, weDead, weWin, weVirtualWin, weVirtualLoss; + std::atomic time, opptime; + Move moveRequested; +}; + +extern PartnerHandler Partner; + +} // namespace Stockfish + +#endif // #ifndef PARTNER_H_INCLUDED \ No newline at end of file diff --git a/Fairy-Stockfish-master/src/pawns.cpp b/Fairy-Stockfish-master/src/pawns.cpp new file mode 100644 index 0000000000000000000000000000000000000000..00a533ea8c7aeee9122c557efc5079f530b588fa --- /dev/null +++ b/Fairy-Stockfish-master/src/pawns.cpp @@ -0,0 +1,318 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#include "bitboard.h" +#include "pawns.h" +#include "position.h" +#include "thread.h" + +namespace Stockfish { + +namespace { + + #define V Value + #define S(mg, eg) make_score(mg, eg) + + // Pawn penalties + constexpr Score Backward = S( 9, 22); + constexpr Score Doubled = S(13, 51); + constexpr Score DoubledEarly = S(20, 7); + constexpr Score Isolated = S( 3, 15); + constexpr Score WeakLever = S( 4, 58); + constexpr Score WeakUnopposed = S(13, 24); + + // Bonus for blocked pawns at 5th or 6th rank + constexpr Score BlockedPawn[RANK_NB - 5] = { S(-17, -6), S(-9, 2) }; + + constexpr Score BlockedStorm[RANK_NB] = { + S(0, 0), S(0, 0), S(75, 78), S(-8, 16), S(-6, 10), S(-6, 6), S(0, 2) + }; + + // Connected pawn bonus + constexpr int Connected[RANK_NB] = { 0, 5, 7, 11, 23, 48, 87 }; + + // Strength of pawn shelter for our king by [distance from edge][rank]. + // RANK_1 = 0 is used for files where we have no pawn, or pawn is behind our king. + constexpr Value ShelterStrength[int(FILE_NB) / 2][RANK_NB] = { + { V( -5), V( 82), V( 92), V( 54), V( 36), V( 22), V( 28) }, + { V(-44), V( 63), V( 33), V(-50), V(-30), V(-12), V( -62) }, + { V(-11), V( 77), V( 22), V( -6), V( 31), V( 8), V( -45) }, + { V(-39), V(-12), V(-29), V(-50), V(-43), V(-68), V(-164) } + }; + + // Danger of enemy pawns moving toward our king by [distance from edge][rank]. + // RANK_1 = 0 is used for files where the enemy has no pawn, or their pawn + // is behind our king. Note that UnblockedStorm[0][1-2] accommodate opponent pawn + // on edge, likely blocked by our king. + constexpr Value UnblockedStorm[int(FILE_NB) / 2][RANK_NB] = { + { V( 87), V(-288), V(-168), V( 96), V( 47), V( 44), V( 46) }, + { V( 42), V( -25), V( 120), V( 45), V( 34), V( -9), V( 24) }, + { V( -8), V( 51), V( 167), V( 35), V( -4), V(-16), V(-12) }, + { V(-17), V( -13), V( 100), V( 4), V( 9), V(-16), V(-31) } + }; + + + // KingOnFile[semi-open Us][semi-open Them] contains bonuses/penalties + // for king when the king is on a semi-open or open file. + constexpr Score KingOnFile[2][2] = {{ S(-21,10), S(-7, 1) }, + { S( 0,-3), S( 9,-4) }}; + + + // Variant bonuses + constexpr int HordeConnected[2][RANK_NB] = {{ 5, 10, 20, 55, 55, 100, 80 }, + { -10, 5, -10, 5, 25, 40, 30 }}; + + #undef S + #undef V + + + /// evaluate() calculates a score for the static pawn structure of the given position. + /// We cannot use the location of pieces or king in this function, as the evaluation + /// of the pawn structure will be stored in a small cache for speed reasons, and will + /// be re-used even when the pieces have moved. + + template + Score evaluate(const Position& pos, Pawns::Entry* e) { + + constexpr Color Them = ~Us; + constexpr Direction Up = pawn_push(Us); + constexpr Direction Down = -Up; + + Bitboard neighbours, stoppers, support, phalanx, opposed; + Bitboard lever, leverPush, blocked; + Square s; + bool backward, passed, doubled; + Score score = SCORE_ZERO; + Bitboard b = pos.pieces(Us, PAWN); + + Bitboard ourPawns = pos.pieces( Us, PAWN); + Bitboard theirPawns = pos.pieces(Them, PAWN); + + Bitboard doubleAttackThem = pawn_double_attacks_bb(theirPawns); + + e->passedPawns[Us] = 0; + e->kingSquares[Us] = SQ_NONE; + e->pawnAttacks[Us] = e->pawnAttacksSpan[Us] = pawn_attacks_bb(ourPawns); + e->blockedCount += popcount(shift(ourPawns) & (theirPawns | doubleAttackThem)); + + // Loop through all pawns of the current color and score each pawn + while (b) + { + s = pop_lsb(b); + + assert(pos.piece_on(s) == make_piece(Us, PAWN)); + + Rank r = relative_rank(Us, s, pos.max_rank()); + + // Flag the pawn + opposed = theirPawns & forward_file_bb(Us, s); + blocked = is_ok(s + Up) ? theirPawns & (s + Up) : Bitboard(0); + stoppers = theirPawns & passed_pawn_span(Us, s); + lever = theirPawns & pawn_attacks_bb(Us, s); + leverPush = relative_rank(Them, s, pos.max_rank()) > RANK_1 ? theirPawns & pawn_attacks_bb(Us, s + Up) : Bitboard(0); + doubled = r > RANK_1 ? ourPawns & (s - Up) : Bitboard(0); + neighbours = ourPawns & adjacent_files_bb(s); + phalanx = neighbours & rank_bb(s); + support = r > RANK_1 ? neighbours & rank_bb(s - Up) : Bitboard(0); + + if (doubled) + { + // Additional doubled penalty if none of their pawns is fixed + if (!(ourPawns & shift(theirPawns | pawn_attacks_bb(theirPawns)))) + score -= DoubledEarly; + } + + // A pawn is backward when it is behind all pawns of the same color on + // the adjacent files and cannot safely advance. + backward = is_ok(s + Up) + && !(neighbours & forward_ranks_bb(Them, s + Up)) + && (stoppers & blocked); + + // Compute additional span if pawn is not backward nor blocked + if (!backward && !blocked) + e->pawnAttacksSpan[Us] |= pawn_attack_span(Us, s); + + // A pawn is passed if one of the three following conditions is true: + // (a) there is no stoppers except some levers + // (b) the only stoppers are the leverPush, but we outnumber them + // (c) there is only one front stopper which can be levered. + // (Refined in Evaluation::passed) + passed = !(stoppers ^ lever) + || ( !(stoppers ^ leverPush) + && popcount(phalanx) >= popcount(leverPush)) + || ( stoppers == blocked && r >= RANK_5 + && (shift(support) & ~(theirPawns | doubleAttackThem))); + + passed &= !(forward_file_bb(Us, s) & ourPawns); + + // Passed pawns will be properly scored later in evaluation when we have + // full attack info. + if (passed && pos.promotion_square(Us, s) != SQ_NONE && !pos.sittuyin_promotion()) + e->passedPawns[Us] |= s; + + // Score this pawn + if ((support | phalanx) && !pos.sittuyin_promotion()) + { + int v = Connected[r] * (2 + bool(phalanx) - bool(opposed)) * (r == RANK_2 && pos.captures_to_hand() ? 3 : 1) + + 22 * popcount(support); + if (pos.count(Us) > popcount(pos.board_bb()) / 4) + v = popcount(support | phalanx) * HordeConnected[bool(opposed)][r]; + + score += make_score(v, v * (r - 2) / 4); + } + + else if (!neighbours) + { + if ( opposed + && (ourPawns & forward_file_bb(Them, s)) + && !(theirPawns & adjacent_files_bb(s))) + score -= Doubled * (1 + 2 * pos.must_capture()); + else + score -= Isolated * (1 + 2 * pos.must_capture()) + + WeakUnopposed * !opposed; + } + + else if (backward) + score -= Backward + + WeakUnopposed * !opposed * bool(~(FileABB | FileHBB) & s); + + if (!support) + score -= Doubled * doubled + + WeakLever * more_than_one(lever); + + if (blocked && r >= RANK_5) + score += BlockedPawn[r - RANK_5]; + } + + // Double pawn evaluation if there are no non-pawn pieces + if (pos.count(Us) == pos.count(Us)) + score = score * 2; + + return score; + } + +} // namespace + +namespace Pawns { + + +/// Pawns::probe() looks up the current position's pawns configuration in +/// the pawns hash table. It returns a pointer to the Entry if the position +/// is found. Otherwise a new Entry is computed and stored there, so we don't +/// have to recompute all when the same pawns configuration occurs again. + +Entry* probe(const Position& pos) { + + Key key = pos.pawn_key(); + Entry* e = pos.this_thread()->pawnsTable[key]; + + if (e->key == key && !pos.pieces(SHOGI_PAWN)) + return e; + + e->key = key; + e->blockedCount = 0; + e->scores[WHITE] = evaluate(pos, e); + e->scores[BLACK] = evaluate(pos, e); + + return e; +} + + +/// Entry::evaluate_shelter() calculates the shelter bonus and the storm +/// penalty for a king, looking at the king file and the two closest files. + +template +Score Entry::evaluate_shelter(const Position& pos, Square ksq) const { + + constexpr Color Them = ~Us; + + Bitboard b = pos.pieces(PAWN, SHOGI_PAWN) & ~forward_ranks_bb(Them, ksq); + Bitboard ourPawns = b & pos.pieces(Us) & ~pawnAttacks[Them]; + Bitboard theirPawns = b & pos.pieces(Them); + + Score bonus = make_score(5, 5); + + 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); + int ourRank = b ? relative_rank(Us, frontmost_sq(Them, b), pos.max_rank()) : 0; + + b = theirPawns & file_bb(f); + int theirRank = b ? relative_rank(Us, frontmost_sq(Them, b), pos.max_rank()) : 0; + + int d = std::min(File(edge_distance(f, pos.max_file())), FILE_D); + bonus += make_score(ShelterStrength[d][ourRank], 0) * (1 + (pos.captures_to_hand() && ourRank <= RANK_2) + + (pos.check_counting() && d == 0 && ourRank == RANK_2)); + + if (ourRank && (ourRank == theirRank - 1)) + bonus -= BlockedStorm[theirRank]; + else + bonus -= make_score(UnblockedStorm[d][theirRank], 0); + } + + // King On File + bonus -= KingOnFile[pos.is_on_semiopen_file(Us, ksq)][pos.is_on_semiopen_file(Them, ksq)]; + + return bonus; +} + + +/// Entry::do_king_safety() calculates a bonus for king safety. It is called only +/// when king square changes, which is about 20% of total king_safety() calls. + +template +Score Entry::do_king_safety(const Position& pos) { + + Square ksq = pos.square(Us); + kingSquares[Us] = ksq; + castlingRights[Us] = pos.castling_rights(Us); + auto compare = [](Score a, Score b) { return mg_value(a) < mg_value(b); }; + + Score shelter = evaluate_shelter(pos, ksq); + + // If we can castle use the bonus after castling if it is bigger + + if (pos.can_castle(Us & KING_SIDE)) + shelter = std::max(shelter, evaluate_shelter(pos, make_square(pos.castling_kingside_file(), pos.castling_rank(Us))), compare); + + if (pos.can_castle(Us & QUEEN_SIDE)) + shelter = std::max(shelter, evaluate_shelter(pos, make_square(pos.castling_queenside_file(), pos.castling_rank(Us))), compare); + + // In endgame we like to bring our king near our closest pawn + Bitboard pawns = pos.pieces(Us, PAWN); + int minPawnDist = 6; + + if (pawns & attacks_bb(ksq)) + minPawnDist = 1; + else while (pawns) + minPawnDist = std::min(minPawnDist, distance(ksq, pop_lsb(pawns))); + + return shelter - make_score(0, 16 * minPawnDist); +} + +// Explicit template instantiation +template Score Entry::do_king_safety(const Position& pos); +template Score Entry::do_king_safety(const Position& pos); + +} // namespace Pawns + +} // namespace Stockfish diff --git a/Fairy-Stockfish-master/src/pawns.h b/Fairy-Stockfish-master/src/pawns.h new file mode 100644 index 0000000000000000000000000000000000000000..af0370fc41aa76abd70ab2889a6741ca3aed0f7f --- /dev/null +++ b/Fairy-Stockfish-master/src/pawns.h @@ -0,0 +1,70 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef PAWNS_H_INCLUDED +#define PAWNS_H_INCLUDED + +#include "misc.h" +#include "position.h" +#include "types.h" + +namespace Stockfish::Pawns { + +/// Pawns::Entry contains various information about a pawn structure. A lookup +/// to the pawn hash table (performed by calling the probe function) returns a +/// pointer to an Entry object. + +struct Entry { + + Score pawn_score(Color c) const { return scores[c]; } + Bitboard pawn_attacks(Color c) const { return pawnAttacks[c]; } + Bitboard passed_pawns(Color c) const { return passedPawns[c]; } + Bitboard pawn_attacks_span(Color c) const { return pawnAttacksSpan[c]; } + int passed_count() const { return popcount(passedPawns[WHITE] | passedPawns[BLACK]); } + int blocked_count() const { return blockedCount; } + + template + Score king_safety(const Position& pos) { + return kingSquares[Us] == pos.square(Us) && castlingRights[Us] == pos.castling_rights(Us) + ? kingSafety[Us] : (kingSafety[Us] = do_king_safety(pos)); + } + + template + Score do_king_safety(const Position& pos); + + template + Score evaluate_shelter(const Position& pos, Square ksq) const; + + Key key; + Score scores[COLOR_NB]; + Bitboard passedPawns[COLOR_NB]; + Bitboard pawnAttacks[COLOR_NB]; + Bitboard pawnAttacksSpan[COLOR_NB]; + Square kingSquares[COLOR_NB]; + Score kingSafety[COLOR_NB]; + int castlingRights[COLOR_NB]; + int blockedCount; +}; + +typedef HashTable Table; + +Entry* probe(const Position& pos); + +} // namespace Stockfish::Pawns + +#endif // #ifndef PAWNS_H_INCLUDED diff --git a/Fairy-Stockfish-master/src/piece.cpp b/Fairy-Stockfish-master/src/piece.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1a48f8028f7abc67bf45a625fa4d6c02d81da005 --- /dev/null +++ b/Fairy-Stockfish-master/src/piece.cpp @@ -0,0 +1,243 @@ +/* + Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish + Copyright (C) 2018-2022 Fabian Fichter + + Fairy-Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Fairy-Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include + +#include "types.h" +#include "piece.h" + +namespace Stockfish { + +PieceMap pieceMap; // Global object + + +namespace { + const std::map>> leaperAtoms = { + {'W', {std::make_pair(1, 0)}}, + {'F', {std::make_pair(1, 1)}}, + {'D', {std::make_pair(2, 0)}}, + {'N', {std::make_pair(2, 1)}}, + {'A', {std::make_pair(2, 2)}}, + {'H', {std::make_pair(3, 0)}}, + {'L', {std::make_pair(3, 1)}}, + {'C', {std::make_pair(3, 1)}}, + {'J', {std::make_pair(3, 2)}}, + {'Z', {std::make_pair(3, 2)}}, + {'G', {std::make_pair(3, 3)}}, + {'K', {std::make_pair(1, 0), std::make_pair(1, 1)}}, + }; + const std::map>> riderAtoms = { + {'R', {std::make_pair(1, 0)}}, + {'B', {std::make_pair(1, 1)}}, + {'Q', {std::make_pair(1, 0), std::make_pair(1, 1)}}, + }; + const std::string verticals = "fbvh"; + const std::string horizontals = "rlsh"; + // from_betza creates a piece by parsing Betza notation + // https://en.wikipedia.org/wiki/Betza%27s_funny_notation + PieceInfo* from_betza(const std::string& betza, const std::string& name) { + PieceInfo* p = new PieceInfo(); + p->name = name; + p->betza = betza; + std::vector moveModalities = {}; + bool hopper = false; + bool rider = false; + bool lame = false; + bool initial = false; + int distance = 0; + std::vector prelimDirections = {}; + for (std::string::size_type i = 0; i < betza.size(); i++) + { + char c = betza[i]; + // Modality + if (c == 'm' || c == 'c') + moveModalities.push_back(c == 'c' ? MODALITY_CAPTURE : MODALITY_QUIET); + // Hopper + else if (c == 'p' || c == 'g') + { + hopper = true; + // Grasshopper + if (c == 'g') + distance = 1; + } + // Lame leaper + else if (c == 'n') + lame = true; + // Initial move + else if (c == 'i') + initial = true; + // Directional modifiers + else if (verticals.find(c) != std::string::npos || horizontals.find(c) != std::string::npos) + { + if (i + 1 < betza.size()) + { + char c2 = betza[i+1]; + // Can modifiers be combined? + if ( c2 == c + || (verticals.find(c) != std::string::npos && horizontals.find(c2) != std::string::npos) + || (horizontals.find(c) != std::string::npos && verticals.find(c2) != std::string::npos)) + { + prelimDirections.push_back(std::string(1, c) + c2); + i++; + continue; + } + } + prelimDirections.push_back(std::string(2, c)); + } + // Move atom + else if (leaperAtoms.find(c) != leaperAtoms.end() || riderAtoms.find(c) != riderAtoms.end()) + { + const auto& atoms = riderAtoms.find(c) != riderAtoms.end() ? riderAtoms.find(c)->second + : leaperAtoms.find(c)->second; + // Check for rider + if (riderAtoms.find(c) != riderAtoms.end()) + rider = true; + if (i + 1 < betza.size() && (isdigit(betza[i+1]) || betza[i+1] == c)) + { + rider = true; + // limited distance riders + if (isdigit(betza[i+1])) + distance = betza[i+1] - '0'; + i++; + } + if (!rider && lame) + distance = -1; + // No modality qualifier means m+c + if (moveModalities.size() == 0) + { + moveModalities.push_back(MODALITY_QUIET); + moveModalities.push_back(MODALITY_CAPTURE); + } + // Define moves + for (const auto& atom : atoms) + { + std::vector directions = {}; + // Split directions for orthogonal pieces + // This is required e.g. to correctly interpret fsW for soldiers + for (auto s : prelimDirections) + if (atoms.size() == 1 && atom.second == 0 && s[0] != s[1]) + { + directions.push_back(std::string(2, s[0])); + directions.push_back(std::string(2, s[1])); + } + else + directions.push_back(s); + // Add moves + for (auto modality : moveModalities) + { + auto& v = hopper ? p->hopper[initial][modality] + : rider ? p->slider[initial][modality] + : p->steps[initial][modality]; + auto has_dir = [&](std::string s) { + return std::find(directions.begin(), directions.end(), s) != directions.end(); + }; + if (directions.size() == 0 || has_dir("ff") || has_dir("vv") || has_dir("rf") || has_dir("rv") || has_dir("fh") || has_dir("rh") || has_dir("hr")) + v[Direction(atom.first * FILE_NB + atom.second)] = distance; + if (directions.size() == 0 || has_dir("bb") || has_dir("vv") || has_dir("lb") || has_dir("lv") || has_dir("bh") || has_dir("lh") || has_dir("hr")) + v[Direction(-atom.first * FILE_NB - atom.second)] = distance; + if (directions.size() == 0 || has_dir("rr") || has_dir("ss") || has_dir("br") || has_dir("bs") || has_dir("bh") || has_dir("rh") || has_dir("hr")) + v[Direction(-atom.second * FILE_NB + atom.first)] = distance; + if (directions.size() == 0 || has_dir("ll") || has_dir("ss") || has_dir("fl") || has_dir("fs") || has_dir("fh") || has_dir("lh") || has_dir("hr")) + v[Direction(atom.second * FILE_NB - atom.first)] = distance; + if (directions.size() == 0 || has_dir("rr") || has_dir("ss") || has_dir("fr") || has_dir("fs") || has_dir("fh") || has_dir("rh") || has_dir("hl")) + v[Direction(atom.second * FILE_NB + atom.first)] = distance; + if (directions.size() == 0 || has_dir("ll") || has_dir("ss") || has_dir("bl") || has_dir("bs") || has_dir("bh") || has_dir("lh") || has_dir("hl")) + v[Direction(-atom.second * FILE_NB - atom.first)] = distance; + if (directions.size() == 0 || has_dir("bb") || has_dir("vv") || has_dir("rb") || has_dir("rv") || has_dir("bh") || has_dir("rh") || has_dir("hl")) + v[Direction(-atom.first * FILE_NB + atom.second)] = distance; + if (directions.size() == 0 || has_dir("ff") || has_dir("vv") || has_dir("lf") || has_dir("lv") || has_dir("fh") || has_dir("lh") || has_dir("hl")) + v[Direction(atom.first * FILE_NB - atom.second)] = distance; + } + } + // Reset state + moveModalities.clear(); + prelimDirections.clear(); + hopper = false; + rider = false; + lame = false; + initial = false; + distance = 0; + } + } + return p; + } + // Special multi-leg betza description for Janggi elephant + PieceInfo* janggi_elephant_piece() { + PieceInfo* p = from_betza("nZ", "janggiElephant"); + p->betza = "mafsmafW"; // for compatibility with XBoard/Winboard + return p; + } +} + +void PieceMap::init(const Variant* v) { + clear_all(); + add(PAWN, from_betza("fmWfceF", "pawn")); + add(KNIGHT, from_betza("N", "knight")); + add(BISHOP, from_betza("B", "bishop")); + add(ROOK, from_betza("R", "rook")); + add(QUEEN, from_betza("Q", "queen")); + add(FERS, from_betza("F", "fers")); + add(ALFIL, from_betza("A", "alfil")); + add(FERS_ALFIL, from_betza("FA", "fersAlfil")); + add(SILVER, from_betza("FfW", "silver")); + add(AIWOK, from_betza("RNF", "aiwok")); + add(BERS, from_betza("RF", "bers")); + add(ARCHBISHOP, from_betza("BN", "archbishop")); + add(CHANCELLOR, from_betza("RN", "chancellor")); + add(AMAZON, from_betza("QN", "amazon")); + add(KNIBIS, from_betza("mNcB", "knibis")); + add(BISKNI, from_betza("mBcN", "biskni")); + add(KNIROO, from_betza("mNcR", "kniroo")); + add(ROOKNI, from_betza("mRcN", "rookni")); + add(SHOGI_PAWN, from_betza("fW", "shogiPawn")); + add(LANCE, from_betza("fR", "lance")); + add(SHOGI_KNIGHT, from_betza("fN", "shogiKnight")); + add(GOLD, from_betza("WfF", "gold")); + add(DRAGON_HORSE, from_betza("BW", "dragonHorse")); + add(CLOBBER_PIECE, from_betza("cW", "clobber")); + add(BREAKTHROUGH_PIECE, from_betza("fmWfF", "breakthrough")); + add(IMMOBILE_PIECE, from_betza("", "immobile")); + add(CANNON, from_betza("mRcpR", "cannon")); + add(JANGGI_CANNON, from_betza("pR", "janggiCannon")); + add(SOLDIER, from_betza("fsW", "soldier")); + add(HORSE, from_betza("nN", "horse")); + add(ELEPHANT, from_betza("nA", "elephant")); + add(JANGGI_ELEPHANT, janggi_elephant_piece()); + add(BANNER, from_betza("RcpRnN", "banner")); + add(WAZIR, from_betza("W", "wazir")); + add(COMMONER, from_betza("K", "commoner")); + add(CENTAUR, from_betza("KN", "centaur")); + add(KING, from_betza("K", "king")); + // Add custom pieces + for (PieceType pt = CUSTOM_PIECES; pt <= CUSTOM_PIECES_END; ++pt) + add(pt, from_betza(v != nullptr ? v->customPiece[pt - CUSTOM_PIECES] : "", "")); +} + +void PieceMap::add(PieceType pt, const PieceInfo* p) { + insert(std::pair(pt, p)); +} + +void PieceMap::clear_all() { + for (auto const& element : *this) + delete element.second; + clear(); +} + +} // namespace Stockfish diff --git a/Fairy-Stockfish-master/src/piece.h b/Fairy-Stockfish-master/src/piece.h new file mode 100644 index 0000000000000000000000000000000000000000..8d14dfd1898dfc8dbd9326d63e4af3d9719965e7 --- /dev/null +++ b/Fairy-Stockfish-master/src/piece.h @@ -0,0 +1,57 @@ +/* + Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish + Copyright (C) 2018-2022 Fabian Fichter + + Fairy-Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Fairy-Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef PIECE_H_INCLUDED +#define PIECE_H_INCLUDED + +#include +#include + +#include "types.h" +#include "variant.h" + +namespace Stockfish { + +enum MoveModality {MODALITY_QUIET, MODALITY_CAPTURE, MOVE_MODALITY_NB}; + +/// PieceInfo struct stores information about the piece movements. + +struct PieceInfo { + std::string name = ""; + std::string betza = ""; + std::map steps[2][MOVE_MODALITY_NB] = {}; + std::map slider[2][MOVE_MODALITY_NB] = {}; + std::map hopper[2][MOVE_MODALITY_NB] = {}; +}; + +struct PieceMap : public std::map { + void init(const Variant* v = nullptr); + void add(PieceType pt, const PieceInfo* v); + void clear_all(); +}; + +extern PieceMap pieceMap; + +inline std::string piece_name(PieceType pt) { + return is_custom(pt) ? "customPiece" + std::to_string(pt - CUSTOM_PIECES + 1) + : pieceMap.find(pt)->second->name; +} + +} // namespace Stockfish + +#endif // #ifndef PIECE_H_INCLUDED diff --git a/Fairy-Stockfish-master/src/position.cpp b/Fairy-Stockfish-master/src/position.cpp new file mode 100644 index 0000000000000000000000000000000000000000..352b6d58e1a1946b1f67a8a5da95337a62864f4c --- /dev/null +++ b/Fairy-Stockfish-master/src/position.cpp @@ -0,0 +1,3341 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include // For offsetof() +#include // For std::memset, std::memcmp +#include +#include + +#include "bitboard.h" +#include "misc.h" +#include "movegen.h" +#include "position.h" +#include "thread.h" +#include "tt.h" +#include "uci.h" +#include "syzygy/tbprobe.h" + +using std::string; + +namespace Stockfish { + +namespace Zobrist { + + Key psq[PIECE_NB][SQUARE_NB]; + Key enpassant[FILE_NB]; + Key castling[CASTLING_RIGHT_NB]; + Key side, noPawns; + Key inHand[PIECE_NB][SQUARE_NB]; + Key checks[COLOR_NB][CHECKS_NB]; + Key wall[SQUARE_NB]; + Key endgame[EG_EVAL_NB]; +} + + +/// operator<<(Position) returns an ASCII representation of the position + +std::ostream& operator<<(std::ostream& os, const Position& pos) { + + os << "\n "; + for (File f = FILE_A; f <= pos.max_file(); ++f) + os << "+---"; + os << "+\n"; + + for (Rank r = pos.max_rank(); r >= RANK_1; --r) + { + for (File f = FILE_A; f <= pos.max_file(); ++f) + if (pos.state()->wallSquares & make_square(f, r)) + os << " | *"; + else if (pos.unpromoted_piece_on(make_square(f, r))) + os << " |+" << pos.piece_to_char()[pos.unpromoted_piece_on(make_square(f, r))]; + else if (((pos.captures_to_hand() && !pos.drop_loop()) || pos.two_boards()) && pos.is_promoted(make_square(f, r))) + os << " |~" << pos.piece_to_char()[pos.piece_on(make_square(f, r))]; + else + os << " | " << pos.piece_to_char()[pos.piece_on(make_square(f, r))]; + +#ifdef LARGEBOARDS + os << " |" << (pos.max_rank() == RANK_10 && CurrentProtocol != UCI_GENERAL ? r : 1 + r); +#else + os << " |" << (1 + r); +#endif + if (r == pos.max_rank() || r == RANK_1) + { + Color c = r == RANK_1 ? WHITE : BLACK; + if (c == pos.side_to_move()) + os << " *"; + else + os << " "; + if (!pos.free_drops() && (pos.piece_drops() || pos.seirawan_gating())) + { + os << " ["; + for (PieceType pt = KING; pt >= PAWN; --pt) + os << std::string(pos.count_in_hand(c, pt), pos.piece_to_char()[make_piece(c, pt)]); + os << "]"; + } + } + os << "\n "; + for (File f = FILE_A; f <= pos.max_file(); ++f) + os << "+---"; + os << "+\n"; + } + + for (File f = FILE_A; f <= pos.max_file(); ++f) + os << " " << char('a' + f); + os << "\n"; + os << "\nFen: " << pos.fen() << "\nSfen: " << pos.fen(true) << "\nKey: " << std::hex << std::uppercase + << std::setfill('0') << std::setw(16) << pos.key() + << std::setfill(' ') << std::dec << "\nCheckers: "; + + for (Bitboard b = pos.checkers(); b; ) + os << UCI::square(pos, pop_lsb(b)) << " "; + + os << "\nChased: "; + for (Bitboard b = pos.state()->chased; b; ) + os << UCI::square(pos, pop_lsb(b)) << " "; + + if ( int(Tablebases::MaxCardinality) >= popcount(pos.pieces()) + && Options["UCI_Variant"] == "chess" + && !pos.can_castle(ANY_CASTLING)) + { + StateInfo st; + ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); + + Position p; + p.set(pos.variant(), pos.fen(), pos.is_chess960(), &st, pos.this_thread()); + Tablebases::ProbeState s1, s2; + Tablebases::WDLScore wdl = Tablebases::probe_wdl(p, &s1); + int dtz = Tablebases::probe_dtz(p, &s2); + os << "\nTablebases WDL: " << std::setw(4) << wdl << " (" << s1 << ")" + << "\nTablebases DTZ: " << std::setw(4) << dtz << " (" << s2 << ")"; + } + + return os; +} + + +// Marcel van Kervinck's cuckoo algorithm for fast detection of "upcoming repetition" +// situations. Description of the algorithm in the following paper: +// https://marcelk.net/2013-04-06/paper/upcoming-rep-v2.pdf + +// First and second hash functions for indexing the cuckoo tables +#ifdef LARGEBOARDS +inline int H1(Key h) { return h & 0x7fff; } +inline int H2(Key h) { return (h >> 16) & 0x7fff; } +#else +inline int H1(Key h) { return h & 0x1fff; } +inline int H2(Key h) { return (h >> 16) & 0x1fff; } +#endif + +// Cuckoo tables with Zobrist hashes of valid reversible moves, and the moves themselves +#ifdef LARGEBOARDS +Key cuckoo[65536]; +Move cuckooMove[65536]; +#else +Key cuckoo[8192]; +Move cuckooMove[8192]; +#endif + + +/// Position::init() initializes at startup the various arrays used to compute hash keys + +void Position::init() { + + PRNG rng(1070372); + + for (Color c : {WHITE, BLACK}) + for (PieceType pt = PAWN; pt <= KING; ++pt) + for (Square s = SQ_A1; s <= SQ_MAX; ++s) + Zobrist::psq[make_piece(c, pt)][s] = rng.rand(); + + for (File f = FILE_A; f <= FILE_MAX; ++f) + Zobrist::enpassant[f] = rng.rand(); + + for (int cr = NO_CASTLING; cr <= ANY_CASTLING; ++cr) + Zobrist::castling[cr] = rng.rand(); + + Zobrist::side = rng.rand(); + Zobrist::noPawns = rng.rand(); + + for (Color c : {WHITE, BLACK}) + for (int n = 0; n < CHECKS_NB; ++n) + Zobrist::checks[c][n] = rng.rand(); + + for (Color c : {WHITE, BLACK}) + for (PieceType pt = PAWN; pt <= KING; ++pt) + for (int n = 0; n < SQUARE_NB; ++n) + Zobrist::inHand[make_piece(c, pt)][n] = rng.rand(); + + for (Square s = SQ_A1; s <= SQ_MAX; ++s) + Zobrist::wall[s] = rng.rand(); + + for (int i = NO_EG_EVAL; i < EG_EVAL_NB; ++i) + Zobrist::endgame[i] = rng.rand(); + + // Prepare the cuckoo tables + std::memset(cuckoo, 0, sizeof(cuckoo)); + std::memset(cuckooMove, 0, sizeof(cuckooMove)); + [[maybe_unused]] int count = 0; + for (Color c : {WHITE, BLACK}) + for (PieceSet ps = CHESS_PIECES & ~piece_set(PAWN); ps;) + { + Piece pc = make_piece(c, pop_lsb(ps)); + for (Square s1 = SQ_A1; s1 <= SQ_MAX; ++s1) + for (Square s2 = Square(s1 + 1); s2 <= SQ_MAX; ++s2) + if ((type_of(pc) != PAWN) && (attacks_bb(c, type_of(pc), s1, 0) & s2)) + { + Move move = make_move(s1, s2); + Key key = Zobrist::psq[pc][s1] ^ Zobrist::psq[pc][s2] ^ Zobrist::side; + int i = H1(key); + while (true) + { + std::swap(cuckoo[i], key); + std::swap(cuckooMove[i], move); + if (move == MOVE_NONE) // Arrived at empty slot? + break; + i = (i == H1(key)) ? H2(key) : H1(key); // Push victim to alternative slot + } + count++; + } + } +#ifdef LARGEBOARDS + assert(count == 9344); +#else + assert(count == 3668); +#endif +} + +Key Position::material_key(EndgameEval e) const { + return st->materialKey ^ Zobrist::endgame[e]; +} + + +/// Position::set() initializes the position object with the given FEN string. +/// This function is not very robust - make sure that input FENs are correct, +/// this is assumed to be the responsibility of the GUI. + +Position& Position::set(const Variant* v, const string& fenStr, bool isChess960, StateInfo* si, Thread* th, bool sfen) { +/* + A FEN string defines a particular position using only the ASCII character set. + + A FEN string contains six fields separated by a space. The fields are: + + 1) Piece placement (from white's perspective). Each rank is described, starting + with rank 8 and ending with rank 1. Within each rank, the contents of each + square are described from file A through file H. Following the Standard + Algebraic Notation (SAN), each piece is identified by a single letter taken + from the standard English names. White pieces are designated using upper-case + letters ("PNBRQK") whilst Black uses lowercase ("pnbrqk"). Blank squares are + noted using digits 1 through 8 (the number of blank squares), and "/" + separates ranks. + + 2) Active color. "w" means white moves next, "b" means black. + + 3) Castling availability. If neither side can castle, this is "-". Otherwise, + this has one or more letters: "K" (White can castle kingside), "Q" (White + can castle queenside), "k" (Black can castle kingside), and/or "q" (Black + can castle queenside). + + 4) En passant target square (in algebraic notation). If there's no en passant + target square, this is "-". If a pawn has just made a 2-square move, this + is the position "behind" the pawn. Following X-FEN standard, this is recorded only + if there is a pawn in position to make an en passant capture, and if there really + is a pawn that might have advanced two squares. + + 5) Halfmove clock. This is the number of halfmoves since the last pawn advance + or capture. This is used to determine if a draw can be claimed under the + fifty-move rule. + + 6) Fullmove number. The number of the full move. It starts at 1, and is + incremented after Black's move. +*/ + + unsigned char col, row, token; + size_t idx; + std::istringstream ss(fenStr); + + std::memset(this, 0, sizeof(Position)); + std::memset(si, 0, sizeof(StateInfo)); + st = si; + + var = v; + + ss >> std::noskipws; + + Rank r = max_rank(); + Square sq = SQ_A1 + r * NORTH; + + // 1. Piece placement + while ((ss >> token) && !isspace(token)) + { + if (isdigit(token)) + { +#ifdef LARGEBOARDS + if (isdigit(ss.peek())) + { + sq += 10 * (token - '0') * EAST; + ss >> token; + } +#endif + sq += (token - '0') * EAST; // Advance the given number of files + } + + else if (token == '/') + { + sq = SQ_A1 + --r * NORTH; + if (!is_ok(sq)) + break; + } + + // Stop before pieces in hand + else if (token == '[') + break; + + // Ignore pieces outside the board and wait for next / or [ to return to a valid state + else if (!is_ok(sq) || file_of(sq) > max_file() || rank_of(sq) > r) + continue; + + // Wall square + else if (token == '*') + { + st->wallSquares |= sq; + byTypeBB[ALL_PIECES] |= sq; + ++sq; + } + + else if ((idx = piece_to_char().find(token)) != string::npos || (idx = piece_to_char_synonyms().find(token)) != string::npos) + { + if (ss.peek() == '~') + ss >> token; + put_piece(Piece(idx), sq, token == '~'); + ++sq; + } + + // Promoted shogi pieces + else if (token == '+' && (idx = piece_to_char().find(ss.peek())) != string::npos && promoted_piece_type(type_of(Piece(idx)))) + { + ss >> token; + put_piece(make_piece(color_of(Piece(idx)), promoted_piece_type(type_of(Piece(idx)))), sq, true, Piece(idx)); + ++sq; + } + } + // Pieces in hand + if (!isspace(token)) + while ((ss >> token) && !isspace(token)) + { + if (token == ']') + continue; + else if ((idx = piece_to_char().find(token)) != string::npos) + add_to_hand(Piece(idx)); + } + + // 2. Active color + ss >> token; + sideToMove = (token != (sfen ? 'w' : 'b') ? WHITE : BLACK); // Invert colors for SFEN + ss >> token; + + // 3-4. Skip parsing castling and en passant flags if not present + st->epSquares = 0; + st->castlingKingSquare[WHITE] = st->castlingKingSquare[BLACK] = SQ_NONE; + if (!isdigit(ss.peek()) && !sfen) + { + // 3. Castling availability. Compatible with 3 standards: Normal FEN standard, + // Shredder-FEN that uses the letters of the columns on which the rooks began + // the game instead of KQkq and also X-FEN standard that, in case of Chess960, + // if an inner rook is associated with the castling right, the castling tag is + // replaced by the file letter of the involved rook, as for the Shredder-FEN. + while ((ss >> token) && !isspace(token)) + { + Square rsq; + Color c = islower(token) ? BLACK : WHITE; + + token = char(toupper(token)); + + if (castling_enabled() && token == 'K') + for (rsq = make_square(var->castlingRookKingsideFile, castling_rank(c)); (!(castling_rook_pieces(c) & type_of(piece_on(rsq))) || color_of(piece_on(rsq)) != c) && file_of(rsq) > FILE_A; --rsq) {} + + else if (castling_enabled() && token == 'Q') + for (rsq = make_square(var->castlingRookQueensideFile, castling_rank(c)); (!(castling_rook_pieces(c) & type_of(piece_on(rsq))) || color_of(piece_on(rsq)) != c) && file_of(rsq) < max_file(); ++rsq) {} + + else if (token >= 'A' && token <= 'A' + max_file()) + rsq = make_square(File(token - 'A'), castling_rank(c)); + + else + continue; + + // Determine castling "king" position + if (castling_enabled() && st->castlingKingSquare[c] == SQ_NONE) + { + Bitboard castlingKings = pieces(c, castling_king_piece(c)) & rank_bb(castling_rank(c)); + // Ambiguity resolution for 960 variants with more than one "king" + // e.g., EAH means that an e-file king can castle with a- and h-file rooks + st->castlingKingSquare[c] = isChess960 && piece_on(rsq) == make_piece(c, castling_king_piece(c)) ? rsq + : castlingKings && (!more_than_one(castlingKings) || isChess960) ? lsb(castlingKings) + : make_square(castling_king_file(), castling_rank(c)); + // Skip invalid castling rights + if (!(castlingKings & st->castlingKingSquare[c])) + st->castlingKingSquare[c] = SQ_NONE; + } + + // Set gates (and skip castling rights) + if (gating()) + { + // Only add gates for occupied squares + if (pieces(c) & rsq) + st->gatesBB[c] |= rsq; + if ((token == 'K' || token == 'Q') && st->castlingKingSquare[c] != SQ_NONE) + st->gatesBB[c] |= st->castlingKingSquare[c]; + // Do not set castling rights for gates unless there are no pieces in hand, + // which means that the file is referring to a chess960 castling right. + else if (!seirawan_gating() || count_in_hand(c, ALL_PIECES) > 0 || captures_to_hand()) + continue; + } + + // Only add castling right if both king and rook are on expected squares + if ( castling_enabled() + && st->castlingKingSquare[c] != SQ_NONE + && (castling_rook_pieces(c) & type_of(piece_on(rsq))) && color_of(piece_on(rsq)) == c) + set_castling_right(c, rsq); + } + + // Set castling rights for 960 gating variants + if (gating() && castling_enabled()) + for (Color c : {WHITE, BLACK}) + if ((gates(c) & pieces(castling_king_piece(c))) && !castling_rights(c) && (!seirawan_gating() || count_in_hand(c, ALL_PIECES) > 0 || captures_to_hand())) + { + Bitboard castling_rooks = gates(c) & pieces(c); + while (castling_rooks) + { + Square s = pop_lsb(castling_rooks); + if (castling_rook_pieces(c) & type_of(piece_on(s))) + set_castling_right(c, s); + } + } + + // counting limit + if (counting_rule() && isdigit(ss.peek())) + ss >> st->countingLimit; + + // 4. En passant square. + // Ignore if square is invalid or not on side to move relative rank 6. + else + while ( ((ss >> col) && (col >= 'a' && col <= 'a' + max_file())) + && ((ss >> row) && (row >= '1' && row <= '1' + max_rank()))) + { + Square epSquare = make_square(File(col - 'a'), Rank(row - '1')); +#ifdef LARGEBOARDS + // Consider different rank numbering in CECP + if (max_rank() == RANK_10 && CurrentProtocol == XBOARD) + epSquare += NORTH; +#endif + + // En passant square will be considered only if + // epSquare is within enPassantRegion and + // 1) variant has non-standard rules + // or + // 2) + // a) side to move have a pawn threatening epSquare + // b) there is an enemy pawn one or two (for triple steps) squares in front of epSquare + // c) there is no (non-wall) piece on epSquare or behind epSquare + if ( (var->enPassantRegion[sideToMove] & epSquare) + && ( !var->fastAttacks + || (var->enPassantTypes[sideToMove] & ~piece_set(PAWN)) + || ( pawn_attacks_bb(~sideToMove, epSquare) & pieces(sideToMove, PAWN) + && ( (pieces(~sideToMove, PAWN) & (epSquare + pawn_push(~sideToMove))) + || (pieces(~sideToMove, PAWN) & (epSquare + 2 * pawn_push(~sideToMove)))) + && !((pieces(WHITE) | pieces(BLACK)) & (epSquare | (epSquare + pawn_push(sideToMove))))))) + st->epSquares |= epSquare; + } + } + + // Check counter for nCheck + ss >> std::skipws >> token >> std::noskipws; + + if (check_counting()) + { + if (ss.peek() == '+') + { + st->checksRemaining[WHITE] = CheckCount(std::max(token - '0', 0)); + ss >> token >> token; + st->checksRemaining[BLACK] = CheckCount(std::max(token - '0', 0)); + } + else + { + // If check count is not provided, assume that the next check wins + st->checksRemaining[WHITE] = CheckCount(1); + st->checksRemaining[BLACK] = CheckCount(1); + ss.putback(token); + } + } + else + ss.putback(token); + + // 5-6. Halfmove clock and fullmove number + if (sfen) + { + // Pieces in hand for SFEN + int handCount = 1; + while ((ss >> token) && !isspace(token)) + { + if (token == '-') + continue; + else if (isdigit(token)) + { + handCount = token - '0'; + while (isdigit(ss.peek()) && ss >> token) + handCount = 10 * handCount + (token - '0'); + } + else if ((idx = piece_to_char().find(token)) != string::npos) + { + for (int i = 0; i < handCount; i++) + add_to_hand(Piece(idx)); + handCount = 1; + } + } + // Move count is in ply for SFEN + ss >> std::skipws >> gamePly; + gamePly = std::max(gamePly - 1, 0); + } + else + { + ss >> std::skipws >> st->rule50 >> gamePly; + + // Convert from fullmove starting from 1 to gamePly starting from 0, + // handle also common incorrect FEN with fullmove = 0. + gamePly = std::max(2 * (gamePly - 1), 0) + (sideToMove == BLACK); + } + + // counting rules + if (st->countingLimit && st->rule50) + { + st->countingPly = st->rule50; + st->rule50 = 0; + } + + // Lichess-style counter for 3check + if (check_counting()) + { + if (ss >> token && token == '+') + { + ss >> token; + st->checksRemaining[WHITE] = CheckCount(std::max(3 - (token - '0'), 0)); + ss >> token >> token; + st->checksRemaining[BLACK] = CheckCount(std::max(3 - (token - '0'), 0)); + } + } + + chess960 = isChess960 || v->chess960; + tsumeMode = Options["TsumeMode"]; + thisThread = th; + set_state(st); + + assert(pos_is_ok()); + + return *this; +} + + +/// Position::set_castling_right() is a helper function used to set castling +/// rights given the corresponding color and the rook starting square. + +void Position::set_castling_right(Color c, Square rfrom) { + + assert(st->castlingKingSquare[c] != SQ_NONE); + Square kfrom = st->castlingKingSquare[c]; + CastlingRights cr = c & (kfrom < rfrom ? KING_SIDE: QUEEN_SIDE); + + st->castlingRights |= cr; + castlingRightsMask[kfrom] |= cr; + castlingRightsMask[rfrom] |= cr; + castlingRookSquare[cr] = rfrom; + + Square kto = make_square(cr & KING_SIDE ? castling_kingside_file() : castling_queenside_file(), castling_rank(c)); + Square rto = kto + (cr & KING_SIDE ? WEST : EAST); + + castlingPath[cr] = (between_bb(rfrom, rto) | between_bb(kfrom, kto)) + & ~(kfrom | rfrom); +} + + +/// Position::set_check_info() sets king attacks to detect if a move gives check + +void Position::set_check_info(StateInfo* si) const { + + si->blockersForKing[WHITE] = slider_blockers(pieces(BLACK), count(WHITE) ? square(WHITE) : SQ_NONE, si->pinners[BLACK], BLACK); + si->blockersForKing[BLACK] = slider_blockers(pieces(WHITE), count(BLACK) ? square(BLACK) : SQ_NONE, si->pinners[WHITE], WHITE); + + Square ksq = count(~sideToMove) ? square(~sideToMove) : SQ_NONE; + + // For unused piece types, the check squares are left uninitialized + si->nonSlidingRiders = 0; + for (PieceSet ps = piece_types(); ps;) + { + PieceType pt = pop_lsb(ps); + PieceType movePt = pt == KING ? king_type() : pt; + si->checkSquares[pt] = ksq != SQ_NONE ? attacks_bb(~sideToMove, movePt, ksq, pieces()) : Bitboard(0); + // Collect special piece types that require slower check and evasion detection + if (AttackRiderTypes[movePt] & NON_SLIDING_RIDERS) + si->nonSlidingRiders |= pieces(pt); + } + si->shak = si->checkersBB & (byTypeBB[KNIGHT] | byTypeBB[ROOK] | byTypeBB[BERS]); + si->bikjang = var->bikjangRule && ksq != SQ_NONE ? bool(attacks_bb(sideToMove, ROOK, ksq, pieces()) & pieces(sideToMove, KING)) : false; + si->chased = var->chasingRule ? chased() : Bitboard(0); + si->legalCapture = NO_VALUE; + if (var->extinctionPseudoRoyal) + { + si->pseudoRoyalCandidates = 0; + si->pseudoRoyals = 0; + for (PieceSet ps = extinction_piece_types(); ps;) + { + PieceType pt = pop_lsb(ps); + si->pseudoRoyalCandidates |= pieces(pt); + if (count(sideToMove, pt) <= var->extinctionPieceCount + 1) + si->pseudoRoyals |= pieces(sideToMove, pt); + if (count(~sideToMove, pt) <= var->extinctionPieceCount + 1) + si->pseudoRoyals |= pieces(~sideToMove, pt); + } + } +} + + +/// Position::set_state() computes the hash keys of the position, and other +/// data that once computed is updated incrementally as moves are made. +/// The function is only used when a new position is set up, and to verify +/// the correctness of the StateInfo data when running in debug mode. + +void Position::set_state(StateInfo* si) const { + + si->key = si->materialKey = 0; + si->pawnKey = Zobrist::noPawns; + si->nonPawnMaterial[WHITE] = si->nonPawnMaterial[BLACK] = VALUE_ZERO; + si->checkersBB = count(sideToMove) ? attackers_to(square(sideToMove), ~sideToMove) : Bitboard(0); + si->move = MOVE_NONE; + + set_check_info(si); + + for (Bitboard b = pieces(); b; ) + { + Square s = pop_lsb(b); + Piece pc = piece_on(s); + si->key ^= Zobrist::psq[pc][s]; + + if (!pc) + si->key ^= Zobrist::wall[s]; + + else if (type_of(pc) == PAWN) + si->pawnKey ^= Zobrist::psq[pc][s]; + + else if (type_of(pc) != KING) + si->nonPawnMaterial[color_of(pc)] += PieceValue[MG][pc]; + } + + for (Bitboard b = si->epSquares; b; ) + si->key ^= Zobrist::enpassant[file_of(pop_lsb(b))]; + + if (sideToMove == BLACK) + si->key ^= Zobrist::side; + + si->key ^= Zobrist::castling[si->castlingRights]; + + for (Color c : {WHITE, BLACK}) + for (PieceType pt = PAWN; pt <= KING; ++pt) + { + Piece pc = make_piece(c, pt); + + for (int cnt = 0; cnt < pieceCount[pc]; ++cnt) + si->materialKey ^= Zobrist::psq[pc][cnt]; + + if (piece_drops() || seirawan_gating()) + si->key ^= Zobrist::inHand[pc][pieceCountInHand[c][pt]]; + } + + if (check_counting()) + for (Color c : {WHITE, BLACK}) + si->key ^= Zobrist::checks[c][si->checksRemaining[c]]; +} + + +/// Position::set() is an overload to initialize the position object with +/// the given endgame code string like "KBPKN". It is mainly a helper to +/// get the material key out of an endgame code. + +Position& Position::set(const string& code, Color c, StateInfo* si) { + + string sides[] = { code.substr(code.find('v') != string::npos ? code.find('v') + 1 : code.find('K', 1)), // Weak + code.substr(0, std::min(code.find('v'), code.find('K', 1))) }; // Strong + + assert(sides[0].length() > 0 && sides[0].length() < 8); + assert(sides[1].length() > 0 && sides[1].length() < 8); + + std::transform(sides[c].begin(), sides[c].end(), sides[c].begin(), tolower); + + string n = std::to_string(8); + string fenStr = sides[0] + "///////" + sides[1] + " w - - 0 10"; + + return set(variants.find("fairy")->second, fenStr, false, si, nullptr); +} + + +/// Position::fen() returns a FEN representation of the position. In case of +/// Chess960 the Shredder-FEN notation is used. This is mainly a debugging function. + +string Position::fen(bool sfen, bool showPromoted, int countStarted, std::string holdings, Bitboard fogArea) const { + + int emptyCnt; + std::ostringstream ss; + + for (Rank r = max_rank(); r >= RANK_1; --r) + { + for (File f = FILE_A; f <= max_file(); ++f) + { + for (emptyCnt = 0; f <= max_file() && !(pieces() & make_square(f, r)) && !(fogArea & make_square(f, r)); ++f) + ++emptyCnt; + + if (emptyCnt) + ss << emptyCnt; + + if (f <= max_file()) + { + if (empty(make_square(f, r)) || fogArea & make_square(f, r)) + // Wall square + ss << "*"; + else if (unpromoted_piece_on(make_square(f, r))) + // Promoted shogi pieces, e.g., +r for dragon + ss << "+" << piece_to_char()[unpromoted_piece_on(make_square(f, r))]; + else + { + ss << piece_to_char()[piece_on(make_square(f, r))]; + + // Set promoted pieces + if (((captures_to_hand() && !drop_loop()) || two_boards() || showPromoted) && is_promoted(make_square(f, r))) + ss << "~"; + } + } + } + + if (r > RANK_1) + ss << '/'; + } + + // SFEN + if (sfen) + { + ss << (sideToMove == WHITE ? " b " : " w "); + for (Color c : {WHITE, BLACK}) + for (PieceType pt = KING; pt >= PAWN; --pt) + if (pieceCountInHand[c][pt] > 0) + { + if (pieceCountInHand[c][pt] > 1) + ss << pieceCountInHand[c][pt]; + ss << piece_to_char()[make_piece(c, pt)]; + } + if (count_in_hand(ALL_PIECES) == 0) + ss << '-'; + ss << " " << gamePly + 1; + return ss.str(); + } + + // pieces in hand + if (!free_drops() && (piece_drops() || seirawan_gating())) + { + ss << '['; + if (holdings != "-") + ss << holdings; + else + for (Color c : {WHITE, BLACK}) + for (PieceType pt = KING; pt >= PAWN; --pt) + { + assert(pieceCountInHand[c][pt] >= 0); + ss << std::string(pieceCountInHand[c][pt], piece_to_char()[make_piece(c, pt)]); + } + ss << ']'; + } + + ss << (sideToMove == WHITE ? " w " : " b "); + + // Disambiguation for chess960 "king" square + if (chess960 && can_castle(WHITE_CASTLING) && popcount(pieces(WHITE, castling_king_piece(WHITE)) & rank_bb(castling_rank(WHITE))) > 1) + ss << char('A' + castling_king_square(WHITE)); + + if (can_castle(WHITE_OO)) + ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OO ))) : 'K'); + + if (can_castle(WHITE_OOO)) + ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OOO))) : 'Q'); + + if (gating() && gates(WHITE) && (!seirawan_gating() || count_in_hand(WHITE, ALL_PIECES) > 0 || captures_to_hand())) + for (File f = FILE_A; f <= max_file(); ++f) + if ( (gates(WHITE) & file_bb(f)) + // skip gating flags redundant with castling flags + && !(!chess960 && can_castle(WHITE_CASTLING) && f == file_of(castling_king_square(WHITE))) + && !(can_castle(WHITE_OO ) && f == file_of(castling_rook_square(WHITE_OO ))) + && !(can_castle(WHITE_OOO) && f == file_of(castling_rook_square(WHITE_OOO)))) + ss << char('A' + f); + + // Disambiguation for chess960 "king" square + if (chess960 && can_castle(BLACK_CASTLING) && popcount(pieces(BLACK, castling_king_piece(BLACK)) & rank_bb(castling_rank(BLACK))) > 1) + ss << char('a' + castling_king_square(BLACK)); + + if (can_castle(BLACK_OO)) + ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OO ))) : 'k'); + + if (can_castle(BLACK_OOO)) + ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OOO))) : 'q'); + + if (gating() && gates(BLACK) && (!seirawan_gating() || count_in_hand(BLACK, ALL_PIECES) > 0 || captures_to_hand())) + for (File f = FILE_A; f <= max_file(); ++f) + if ( (gates(BLACK) & file_bb(f)) + // skip gating flags redundant with castling flags + && !(!chess960 && can_castle(BLACK_CASTLING) && f == file_of(castling_king_square(BLACK))) + && !(can_castle(BLACK_OO ) && f == file_of(castling_rook_square(BLACK_OO ))) + && !(can_castle(BLACK_OOO) && f == file_of(castling_rook_square(BLACK_OOO)))) + ss << char('a' + f); + + if (!can_castle(ANY_CASTLING) && !(gating() && (gates(WHITE) | gates(BLACK)))) + ss << '-'; + + // Counting limit or ep-square + if (st->countingLimit) + ss << " " << counting_limit(countStarted) << " "; + else if (!ep_squares()) + ss << " - "; + else + { + ss << " "; + for (Bitboard b = ep_squares(); b; ) + ss << UCI::square(*this, pop_lsb(b)); + ss << " "; + } + + // Check count + if (check_counting()) + ss << st->checksRemaining[WHITE] << "+" << st->checksRemaining[BLACK] << " "; + + // Counting ply or 50-move rule counter + if (st->countingLimit) + ss << counting_ply(countStarted); + else + ss << st->rule50; + + ss << " " << 1 + (gamePly - (sideToMove == BLACK)) / 2; + + return ss.str(); +} + + +/// Position::slider_blockers() returns a bitboard of all the pieces (both colors) +/// that are blocking attacks on the square 's' from 'sliders'. A piece blocks a +/// slider if removing that piece from the board would result in a position where +/// square 's' is attacked. For example, a king-attack blocking piece can be either +/// a pinned or a discovered check piece, according if its color is the opposite +/// or the same of the color of the slider. + +Bitboard Position::slider_blockers(Bitboard sliders, Square s, Bitboard& pinners, Color c) const { + + Bitboard blockers = 0; + pinners = 0; + + if (s == SQ_NONE || !sliders) + return blockers; + + // Snipers are sliders that attack 's' when a piece and other snipers are removed + Bitboard snipers = 0; + Bitboard slidingSnipers = 0; + + if (var->fastAttacks) + { + snipers = ( (attacks_bb< ROOK>(s) & pieces(c, QUEEN, ROOK, CHANCELLOR)) + | (attacks_bb(s) & pieces(c, QUEEN, BISHOP, ARCHBISHOP))) & sliders; + slidingSnipers = snipers; + } + else + { + for (PieceSet ps = piece_types(); ps;) + { + PieceType pt = pop_lsb(ps); + Bitboard b = sliders & (PseudoAttacks[~c][pt][s] ^ LeaperAttacks[~c][pt][s]) & pieces(c, pt); + if (b) + { + // Consider asymmetrical moves (e.g., horse) + if (AttackRiderTypes[pt] & ASYMMETRICAL_RIDERS) + { + Bitboard asymmetricals = PseudoAttacks[~c][pt][s] & pieces(c, pt); + while (asymmetricals) + { + Square s2 = pop_lsb(asymmetricals); + if (!(attacks_from(c, pt, s2) & s)) + snipers |= s2; + } + } + else + snipers |= b & ~attacks_bb(~c, pt, s, pieces()); + if (AttackRiderTypes[pt] & ~HOPPING_RIDERS) + slidingSnipers |= snipers & pieces(pt); + } + } + // Diagonal rook pins in Janggi palace + if (diagonal_lines() & s) + { + Bitboard diags = diagonal_lines() & PseudoAttacks[~c][BISHOP][s] & sliders & pieces(c, ROOK); + while (diags) + { + Square s2 = pop_lsb(diags); + if (!(attacks_from(c, ROOK, s2) & s)) + { + snipers |= s2; + slidingSnipers |= s2; + } + } + } + } + Bitboard occupancy = pieces() ^ slidingSnipers; + + while (snipers) + { + Square sniperSq = pop_lsb(snipers); + bool isHopper = AttackRiderTypes[type_of(piece_on(sniperSq))] & HOPPING_RIDERS; + Bitboard b = between_bb(s, sniperSq, type_of(piece_on(sniperSq))) & (isHopper ? (pieces() ^ sniperSq) : occupancy); + + if (b && (!more_than_one(b) || (isHopper && popcount(b) == 2))) + { + // Janggi cannons block each other + if ((pieces(JANGGI_CANNON) & sniperSq) && (pieces(JANGGI_CANNON) & b)) + b &= pieces(JANGGI_CANNON); + blockers |= b; + if (b & pieces(color_of(piece_on(s)))) + pinners |= sniperSq; + } + } + return blockers; +} + + +/// Position::attackers_to() computes a bitboard of all pieces which attack a +/// given square. Slider attacks use the occupied bitboard to indicate occupancy. + +Bitboard Position::attackers_to(Square s, Bitboard occupied, Color c, Bitboard janggiCannons) const { + + // Use a faster version for variants with moderate rule variations + if (var->fastAttacks) + { + return (pawn_attacks_bb(~c, s) & pieces(c, PAWN)) + | (attacks_bb(s) & pieces(c, KNIGHT, ARCHBISHOP, CHANCELLOR)) + | (attacks_bb< ROOK>(s, occupied) & pieces(c, ROOK, QUEEN, CHANCELLOR)) + | (attacks_bb(s, occupied) & pieces(c, BISHOP, QUEEN, ARCHBISHOP)) + | (attacks_bb(s) & pieces(c, KING, COMMONER)); + } + + // Use a faster version for selected fairy pieces + if (var->fastAttacks2) + { + return (pawn_attacks_bb(~c, s) & pieces(c, PAWN, BREAKTHROUGH_PIECE, GOLD)) + | (attacks_bb(s) & pieces(c, KNIGHT)) + | (attacks_bb< ROOK>(s, occupied) & ( pieces(c, ROOK, QUEEN, DRAGON) + | (pieces(c, LANCE) & PseudoAttacks[~c][LANCE][s]))) + | (attacks_bb(s, occupied) & pieces(c, BISHOP, QUEEN, DRAGON_HORSE)) + | (attacks_bb(s) & pieces(c, KING, COMMONER)) + | (attacks_bb(s) & pieces(c, FERS, DRAGON, SILVER)) + | (attacks_bb(s) & pieces(c, WAZIR, DRAGON_HORSE, GOLD)) + | (LeaperAttacks[~c][SHOGI_KNIGHT][s] & pieces(c, SHOGI_KNIGHT)) + | (LeaperAttacks[~c][SHOGI_PAWN][s] & pieces(c, SHOGI_PAWN, SILVER)); + } + + Bitboard b = 0; + for (PieceSet ps = piece_types(); ps;) + { + PieceType pt = pop_lsb(ps); + if (board_bb(c, pt) & s) + { + PieceType move_pt = pt == KING ? king_type() : pt; + // Consider asymmetrical moves (e.g., horse) + if (AttackRiderTypes[move_pt] & ASYMMETRICAL_RIDERS) + { + Bitboard asymmetricals = PseudoAttacks[~c][move_pt][s] & pieces(c, pt); + while (asymmetricals) + { + Square s2 = pop_lsb(asymmetricals); + if (attacks_bb(c, move_pt, s2, occupied) & s) + b |= s2; + } + } + else if (pt == JANGGI_CANNON) + b |= attacks_bb(~c, move_pt, s, occupied) & attacks_bb(~c, move_pt, s, occupied & ~janggiCannons) & pieces(c, JANGGI_CANNON); + else + b |= attacks_bb(~c, move_pt, s, occupied) & pieces(c, pt); + } + } + + // Janggi palace moves + if (diagonal_lines() & s) + { + Bitboard diags = 0; + if (king_type() == WAZIR) + diags |= attacks_bb(~c, FERS, s, occupied) & pieces(c, KING); + diags |= attacks_bb(~c, FERS, s, occupied) & pieces(c, WAZIR); + diags |= attacks_bb(~c, PAWN, s, occupied) & pieces(c, SOLDIER); + diags |= rider_attacks_bb(s, occupied) & pieces(c, ROOK); + diags |= rider_attacks_bb(s, occupied) + & rider_attacks_bb(s, occupied & ~janggiCannons) + & pieces(c, JANGGI_CANNON); + b |= diags & diagonal_lines(); + } + + // Unpromoted soldiers + if (b & pieces(SOLDIER) && relative_rank(c, s, max_rank()) < var->soldierPromotionRank) + b ^= b & pieces(SOLDIER) & ~PseudoAttacks[~c][SHOGI_PAWN][s]; + + return b; +} + + +Bitboard Position::attackers_to(Square s, Bitboard occupied) const { + return attackers_to(s, occupied, WHITE) | attackers_to(s, occupied, BLACK); +} + +/// Position::checked_pseudo_royals computes a bitboard of +/// all pseudo-royal pieces of a particular color that are in check +Bitboard Position::checked_pseudo_royals(Color c) const { + assert(extinction_pseudo_royal()); + Bitboard checked = 0; + Bitboard pseudoRoyals = st->pseudoRoyals & pieces(c); + Bitboard pseudoRoyalsTheirs = st->pseudoRoyals & pieces(~c); + while (pseudoRoyals) + { + Square sr = pop_lsb(pseudoRoyals); + // skip if capturing this piece would blast any of the attacker's pseudo-royal pieces + if (!(blast_on_capture() && (pseudoRoyalsTheirs & attacks_bb(sr))) + && attackers_to(sr, ~c)) + checked |= sr; + } + // Look for duple check + if (var->dupleCheck) + { + Bitboard allAttacked = 0; + Bitboard pseudoRoyalCandidates = st->pseudoRoyalCandidates & pieces(c); + while (pseudoRoyalCandidates) + { + Square sr = pop_lsb(pseudoRoyalCandidates); + if (!(blast_on_capture() && (pseudoRoyalsTheirs & attacks_bb(sr))) + && attackers_to(sr, ~c)) + allAttacked |= sr; + else + // If at least one isn't attacked, it is not a duple check + return checked; + } + checked |= allAttacked; + } + return checked; +} + + +/// Position::legal() tests whether a pseudo-legal move is legal + +bool Position::legal(Move m) const { + + assert(is_ok(m)); + assert(type_of(m) != DROP || piece_drops()); + + Color us = sideToMove; + Square from = from_sq(m); + Square to = to_sq(m); + + assert(color_of(moved_piece(m)) == us); + assert(!count(us) || piece_on(square(us)) == make_piece(us, KING)); + assert(board_bb() & to); + + // Illegal checks + if ((!checking_permitted() || (sittuyin_promotion() && type_of(m) == PROMOTION) || (!drop_checks() && type_of(m) == DROP)) && gives_check(m)) + return false; + + // Illegal quiet moves + if (must_capture() && !capture(m) && has_capture()) + return false; + + // Illegal non-drop moves + if (must_drop() && count_in_hand(us, var->mustDropType) > 0) + { + if (type_of(m) == DROP) + { + if (var->mustDropType != ALL_PIECES && var->mustDropType != in_hand_piece_type(m)) + return false; + } + else if (checkers()) + { + for (const auto& mevasion : MoveList(*this)) + if (type_of(mevasion) == DROP && legal(mevasion)) + return false; + } + else + { + for (const auto& mquiet : MoveList(*this)) + if (type_of(mquiet) == DROP && legal(mquiet)) + return false; + } + } + + // Illegal drop move + if (drop_opposite_colored_bishop() && type_of(m) == DROP) + { + if (type_of(moved_piece(m)) != BISHOP) + { + Bitboard remaining = drop_region(us, BISHOP) & ~pieces() & ~square_bb(to); + // Are enough squares available to drop bishops on opposite colors? + if ( popcount( DarkSquares & (pieces(us, BISHOP) | remaining)) < count_with_hand(us, BISHOP) / 2 + || popcount(~DarkSquares & (pieces(us, BISHOP) | remaining)) < count_with_hand(us, BISHOP) / 2) + return false; + } + else + // Drop resulting in same-colored bishops + if (popcount((DarkSquares & to ? DarkSquares : ~DarkSquares) & pieces(us, BISHOP)) + 1 > (count_with_hand(us, BISHOP) + 1) / 2) + return false; + } + + // No legal moves from target square + if (immobility_illegal() && (type_of(m) == DROP || type_of(m) == NORMAL) && !(PseudoMoves[0][us][type_of(moved_piece(m))][to] & board_bb())) + return false; + + // Illegal king passing move + if (pass_on_stalemate(us) && is_pass(m) && !checkers()) + { + for (const auto& move : MoveList(*this)) + if (!is_pass(move) && legal(move)) + return false; + } + + // Check for attacks to pseudo-royal pieces + if (var->extinctionPseudoRoyal) + { + Square kto = to; + Bitboard occupied = (type_of(m) != DROP ? pieces() ^ from : pieces()); + if (walling_rule() == DUCK) + occupied ^= st->wallSquares; + if (walling() || is_gating(m)) + occupied |= gating_square(m); + if (type_of(m) == CASTLING) + { + // After castling, the rook and king final positions are the same in + // Chess960 as they would be in standard chess. + kto = make_square(to > from ? castling_kingside_file() : castling_queenside_file(), castling_rank(us)); + Direction step = kto > from ? EAST : WEST; + Square rto = kto - (to > from ? EAST : WEST); + // Pseudo-royal king + if (st->pseudoRoyals & from) + // Loop over squares between the king and its final position + // Ensure to include the initial square if from == kto + for (Square s = from; from != kto ? s != kto : s == from; s += step) + if ( !(blast_on_capture() && (attacks_bb(s) & st->pseudoRoyals & pieces(~sideToMove))) + && attackers_to(s, occupied, ~us)) + return false; + // Move the rook + occupied ^= to | rto; + } + occupied |= kto; + if (type_of(m) == EN_PASSANT) + occupied &= ~square_bb(capture_square(kto)); + if (capture(m) && blast_on_capture()) + occupied &= ~((attacks_bb(kto) & ((pieces(WHITE) | pieces(BLACK)) ^ pieces(PAWN))) | kto); + // Petrifying a pseudo-royal piece is illegal + if (capture(m) && (var->petrifyOnCaptureTypes & type_of(moved_piece(m))) && (st->pseudoRoyals & from)) + return false; + Bitboard pseudoRoyals = st->pseudoRoyals & pieces(sideToMove); + // Add dropped pseudo-royal + if (type_of(m) == DROP && (extinction_piece_types() & type_of(moved_piece(m)))) + pseudoRoyals |= square_bb(to); + Bitboard pseudoRoyalsTheirs = st->pseudoRoyals & pieces(~sideToMove); + if (is_ok(from) && (pseudoRoyals & from)) + pseudoRoyals ^= square_bb(from) ^ kto; + if (type_of(m) == PROMOTION && (extinction_piece_types() & promotion_type(m))) + { + if (count(sideToMove, promotion_type(m)) > extinction_piece_count()) + // increase in count leads to loss of pseudo-royalty + pseudoRoyals &= ~pieces(sideToMove, promotion_type(m)); + else + // promoted piece is pseudo-royal + pseudoRoyals |= kto; + } + // Self-explosions are illegal + if (pseudoRoyals & ~occupied) + return false; + // Petrifiable pseudo-royals can't capture + Bitboard attackerCandidatesTheirs = occupied & ~square_bb(kto); + for (PieceSet ps = var->petrifyOnCaptureTypes & extinction_piece_types(); ps;) + attackerCandidatesTheirs &= ~pieces(~us, pop_lsb(ps)); + // Check for legality unless we capture a pseudo-royal piece + if (!(pseudoRoyalsTheirs & ~occupied)) + while (pseudoRoyals) + { + Square sr = pop_lsb(pseudoRoyals); + // Touching pseudo-royal pieces are immune + if ( !(blast_on_capture() && (pseudoRoyalsTheirs & attacks_bb(sr))) + && (attackers_to(sr, occupied, ~us) & attackerCandidatesTheirs)) + return false; + } + // Look for duple check + if (var->dupleCheck) + { + Bitboard pseudoRoyalCandidates = st->pseudoRoyalCandidates & pieces(sideToMove); + if (is_ok(from) && (pseudoRoyalCandidates & from)) + pseudoRoyalCandidates ^= square_bb(from) ^ kto; + if (type_of(m) == PROMOTION && (extinction_piece_types() & promotion_type(m))) + pseudoRoyalCandidates |= kto; + bool allCheck = bool(pseudoRoyalCandidates); + while (allCheck && pseudoRoyalCandidates) + { + Square sr = pop_lsb(pseudoRoyalCandidates); + // Touching pseudo-royal pieces are immune + if (!( !(blast_on_capture() && (pseudoRoyalsTheirs & attacks_bb(sr))) + && (attackers_to(sr, occupied, ~us) & attackerCandidatesTheirs))) + allCheck = false; + } + if (allCheck) + return false; + } + } + + // mutuallyImmuneTypes (diplomacy in Atomar)-- In no-check Atomic, kings can be beside each other, but in Atomar, this prevents them from actually taking. + // Generalized to allow a custom set of pieces that can't capture a piece of the same type. + if (capture(m) && + (mutually_immune_types() & type_of(moved_piece(m))) && + (type_of(moved_piece(m)) == type_of(piece_on(to))) + ) + return false; + + // En passant captures are a tricky special case. Because they are rather + // uncommon, we do it simply by testing whether the king is attacked after + // the move is made. + if (type_of(m) == EN_PASSANT && count(us)) + { + Square ksq = square(us); + Square capsq = capture_square(to); + Bitboard occupied = (pieces() ^ from ^ capsq) | to; + + assert(ep_squares() & to); + assert(piece_on(to) == NO_PIECE); + + return !(attackers_to(ksq, occupied, ~us) & occupied); + } + + // Castling moves generation does not check if the castling path is clear of + // enemy attacks, it is delayed at a later time: now! + if (type_of(m) == CASTLING) + { + // After castling, the rook and king final positions are the same in + // Chess960 as they would be in standard chess. + to = make_square(to > from ? castling_kingside_file() : castling_queenside_file(), castling_rank(us)); + Direction step = to > from ? WEST : EAST; + + // Will the gate be blocked by king or rook? + Square rto = to + (to_sq(m) > from_sq(m) ? WEST : EAST); + if (is_gating(m) && (gating_square(m) == to || gating_square(m) == rto)) + return false; + + // Non-royal pieces can not be impeded from castling + if (type_of(piece_on(from)) != KING) + return true; + + for (Square s = to; s != from; s += step) + if (attackers_to(s, ~us) + || (var->flyingGeneral && (attacks_bb(~us, ROOK, s, pieces() ^ from) & pieces(~us, KING)))) + return false; + + // In case of Chess960, verify if the Rook blocks some checks + // For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1. + return !attackers_to(to, pieces() ^ to_sq(m), ~us); + } + + Bitboard occupied = (type_of(m) != DROP ? pieces() ^ from : pieces()) | to; + + // Flying general rule and bikjang + // In case of bikjang passing is always allowed, even when in check + if (st->bikjang && is_pass(m)) + return true; + if ((var->flyingGeneral && count(us)) || st->bikjang) + { + Square s = type_of(moved_piece(m)) == KING ? to : square(us); + if (attacks_bb(~us, ROOK, s, occupied) & pieces(~us, KING) & ~square_bb(to)) + return false; + } + + // Makpong rule + if (var->makpongRule && checkers() && type_of(moved_piece(m)) == KING && (checkers() ^ to)) + return false; + + // If the moving piece is a king, check whether the destination square is + // attacked by the opponent. + if (type_of(moved_piece(m)) == KING) + return !attackers_to(to, occupied, ~us); + + // Return early when without king + if (!count(us)) + return true; + + Bitboard janggiCannons = pieces(JANGGI_CANNON); + if (type_of(moved_piece(m)) == JANGGI_CANNON) + janggiCannons = (type_of(m) == DROP ? janggiCannons : janggiCannons ^ from) | to; + else if (janggiCannons & to) + janggiCannons ^= to; + + // A non-king move is legal if the king is not under attack after the move. + return !(attackers_to(square(us), occupied, ~us, janggiCannons) & ~SquareBB[to]); +} + + +/// Position::pseudo_legal() takes a random move and tests whether the move is +/// pseudo legal. It is used to validate moves from TT that can be corrupted +/// due to SMP concurrent access or hash position key aliasing. + +bool Position::pseudo_legal(const Move m) const { + + Color us = sideToMove; + Square from = from_sq(m); + Square to = to_sq(m); + Piece pc = moved_piece(m); + + // Illegal moves to squares outside of board or to wall squares + if (!(board_bb() & to)) + return false; + + // Use a fast check for piece drops + if (type_of(m) == DROP) + return piece_drops() + && pc != NO_PIECE + && color_of(pc) == us + && (can_drop(us, in_hand_piece_type(m)) || (two_boards() && allow_virtual_drop(us, type_of(pc)))) + && (drop_region(us, type_of(pc)) & ~pieces() & to) + && ( type_of(pc) == in_hand_piece_type(m) + || (drop_promoted() && type_of(pc) == promoted_piece_type(in_hand_piece_type(m)))); + + // Use a slower but simpler function for uncommon cases + // yet we skip the legality check of MoveList(). + if (type_of(m) != NORMAL || is_gating(m)) + return checkers() ? MoveList< EVASIONS>(*this).contains(m) + : MoveList(*this).contains(m); + + //if walling, and walling is not optional, or they didn't move, do the checks. + if (walling() && (!var->wallOrMove || (from==to))) + { + Bitboard wallsquares = st->wallSquares; + + // Illegal wall square placement + if (!((board_bb() & ~((pieces() ^ from) | to)) & gating_square(m))) + return false; + if (!(var->wallingRegion[us] & gating_square(m)) || //putting a wall on disallowed square + wallsquares & gating_square(m)) //or square already with a wall + return false; + if (walling_rule() == ARROW && !(moves_bb(us, type_of(pc), to, pieces() ^ from) & gating_square(m))) + return false; + if (walling_rule() == PAST && (from != gating_square(m))) + return false; + if (walling_rule() == EDGE) + { + Bitboard validsquares = board_bb() & + ((FileABB | file_bb(max_file()) | Rank1BB | rank_bb(max_rank())) | + ( shift(wallsquares) | shift(wallsquares) + | shift(wallsquares) | shift(wallsquares))); + if (!(validsquares & gating_square(m))) return false; + }; + } + + // Handle the case where a mandatory piece promotion/demotion is not taken + if ( mandatory_piece_promotion() + && (is_promoted(from) ? piece_demotion() : promoted_piece_type(type_of(pc)) != NO_PIECE_TYPE) + && (promotion_zone(us) & (SquareBB[from] | to)) + && (!piece_promotion_on_capture() || capture(m))) + return false; + + // Is not a promotion, so promotion piece must be empty + if (promotion_type(m) != NO_PIECE_TYPE) + return false; + + // If the 'from' square is not occupied by a piece belonging to the side to + // move, the move is obviously not legal. + if (pc == NO_PIECE || color_of(pc) != us) + return false; + + // The destination square cannot be occupied by a friendly piece + if (pieces(us) & to) + return false; + + // Handle the special case of a pawn move + if (type_of(pc) == PAWN) + { + // We have already handled promotion moves, so destination + // cannot be on the 8th/1st rank. + if (mandatory_pawn_promotion() && (promotion_zone(us) & to) && !sittuyin_promotion()) + return false; + + if ( !(pawn_attacks_bb(us, from) & pieces(~us) & to) // Not a capture + && !((from + pawn_push(us) == to) && !(pieces() & to)) // Not a single push + && !( (from + 2 * pawn_push(us) == to) // Not a double push + && (double_step_region(us) & from) + && !(pieces() & (to | (to - pawn_push(us))))) + && !( (from + 3 * pawn_push(us) == to) // Not a triple push + && (triple_step_region(us) & from) + && !(pieces() & (to | (to - pawn_push(us)) | (to - 2 * pawn_push(us)))))) + return false; + } + else if (!((capture(m) ? attacks_from(us, type_of(pc), from) : moves_from(us, type_of(pc), from)) & to)) + return false; + + // Janggi cannon + if (type_of(pc) == JANGGI_CANNON && (pieces(JANGGI_CANNON) & (between_bb(from, to) | to))) + return false; + + // Evasions generator already takes care to avoid some kind of illegal moves + // and legal() relies on this. We therefore have to take care that the same + // kind of moves are filtered out here. + if (checkers() && !(checkers() & non_sliding_riders())) + { + if (type_of(pc) != KING) + { + // Double check? In this case a king move is required + if (more_than_one(checkers())) + return false; + + // Our move must be a blocking evasion or a capture of the checking piece + Square checksq = lsb(checkers()); + if ( !(between_bb(square(us), lsb(checkers())) & to) + || ((LeaperAttacks[~us][type_of(piece_on(checksq))][checksq] & square(us)) && !(checkers() & to))) + return false; + } + // In case of king moves under check we have to remove king so as to catch + // invalid moves like b1a1 when opposite queen is on c1. + else if (attackers_to(to, pieces() ^ from, ~us)) + return false; + } + + return true; +} + + +/// Position::gives_check() tests whether a pseudo-legal move gives a check + +bool Position::gives_check(Move m) const { + + assert(is_ok(m)); + assert(color_of(moved_piece(m)) == sideToMove); + + Square from = from_sq(m); + Square to = to_sq(m); + + // No check possible without king + if (!count(~sideToMove)) + return false; + + Bitboard occupied = (type_of(m) != DROP ? pieces() ^ from : pieces()) | to; + Bitboard janggiCannons = pieces(JANGGI_CANNON); + if (type_of(moved_piece(m)) == JANGGI_CANNON) + janggiCannons = (type_of(m) == DROP ? janggiCannons : janggiCannons ^ from) | to; + else if (janggiCannons & to) + janggiCannons ^= to; + + // Is there a direct check? + if (type_of(m) != PROMOTION && type_of(m) != PIECE_PROMOTION && type_of(m) != PIECE_DEMOTION && type_of(m) != CASTLING + && !((var->petrifyOnCaptureTypes & type_of(moved_piece(m))) && capture(m))) + { + PieceType pt = type_of(moved_piece(m)); + if (pt == JANGGI_CANNON) + { + if (attacks_bb(sideToMove, pt, to, occupied) & attacks_bb(sideToMove, pt, to, occupied & ~janggiCannons) & square(~sideToMove)) + return true; + } + else if (AttackRiderTypes[pt] & (HOPPING_RIDERS | ASYMMETRICAL_RIDERS)) + { + if (attacks_bb(sideToMove, pt, to, occupied) & square(~sideToMove)) + return true; + } + else if (check_squares(pt) & to) + return true; + } + + // Is there a discovered check? + if ( ((type_of(m) != DROP && (blockers_for_king(~sideToMove) & from)) || (non_sliding_riders() & pieces(sideToMove))) + && attackers_to(square(~sideToMove), occupied, sideToMove, janggiCannons) & occupied) + return true; + + // Is there a check by gated pieces? + if ( is_gating(m) + && attacks_bb(sideToMove, gating_type(m), gating_square(m), (pieces() ^ from) | to) & square(~sideToMove)) + return true; + + // Petrified piece can't give check + if ((var->petrifyOnCaptureTypes & type_of(moved_piece(m))) && capture(m)) + return false; + + // Is there a check by special diagonal moves? + if (more_than_one(diagonal_lines() & (to | square(~sideToMove)))) + { + PieceType pt = type_of(moved_piece(m)); + PieceType diagType = pt == WAZIR ? FERS : pt == SOLDIER ? PAWN : pt == ROOK ? BISHOP : NO_PIECE_TYPE; + if (diagType && (attacks_bb(sideToMove, diagType, to, occupied) & square(~sideToMove))) + return true; + else if (pt == JANGGI_CANNON && ( rider_attacks_bb(to, occupied) + & rider_attacks_bb(to, occupied & ~janggiCannons) + & square(~sideToMove))) + return true; + } + + switch (type_of(m)) + { + case NORMAL: + case DROP: + case SPECIAL: + return false; + + case PROMOTION: + return attacks_bb(sideToMove, promotion_type(m), to, pieces() ^ from) & square(~sideToMove); + + case PIECE_PROMOTION: + return attacks_bb(sideToMove, promoted_piece_type(type_of(moved_piece(m))), to, pieces() ^ from) & square(~sideToMove); + + case PIECE_DEMOTION: + return attacks_bb(sideToMove, type_of(unpromoted_piece_on(from)), to, pieces() ^ from) & square(~sideToMove); + + // En passant capture with check? We have already handled the case + // of direct checks and ordinary discovered check, so the only case we + // need to handle is the unusual case of a discovered check through + // the captured pawn. + case EN_PASSANT: + { + Square capsq = capture_square(to); + Bitboard b = (pieces() ^ from ^ capsq) | to; + + return attackers_to(square(~sideToMove), b) & pieces(sideToMove) & b; + } + default: //CASTLING + { + // Castling is encoded as 'king captures the rook' + Square kfrom = from; + Square rfrom = to; + Square kto = make_square(rfrom > kfrom ? castling_kingside_file() : castling_queenside_file(), castling_rank(sideToMove)); + Square rto = kto + (rfrom > kfrom ? WEST : EAST); + + // Is there a discovered check? + if ( castling_rank(WHITE) > RANK_1 + && ((blockers_for_king(~sideToMove) & rfrom) || (non_sliding_riders() & pieces(sideToMove))) + && attackers_to(square(~sideToMove), (pieces() ^ kfrom ^ rfrom) | rto | kto, sideToMove)) + return true; + + return (PseudoAttacks[sideToMove][type_of(piece_on(rfrom))][rto] & square(~sideToMove)) + && (attacks_bb(sideToMove, type_of(piece_on(rfrom)), rto, (pieces() ^ kfrom ^ rfrom) | rto | kto) & square(~sideToMove)); + } + } +} + +/// Position::do_move() makes a move, and saves all information necessary +/// to a StateInfo object. The move is assumed to be legal. Pseudo-legal +/// moves should be filtered out before this function is called. + +void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { + + assert(is_ok(m)); + assert(&newSt != st); + +#ifndef NO_THREADS + thisThread->nodes.fetch_add(1, std::memory_order_relaxed); +#endif + Key k = st->key ^ Zobrist::side; + + // Copy some fields of the old state to our new StateInfo object except the + // ones which are going to be recalculated from scratch anyway and then switch + // our state pointer to point to the new (ready to be updated) state. + std::memcpy(static_cast(&newSt), static_cast(st), offsetof(StateInfo, key)); + newSt.previous = st; + st = &newSt; + st->move = m; + + // Increment ply counters. In particular, rule50 will be reset to zero later on + // in case of a capture or a pawn move. + ++gamePly; + ++st->rule50; + ++st->pliesFromNull; + if (st->countingLimit) + ++st->countingPly; + + // Used by NNUE + st->accumulator.computed[WHITE] = false; + st->accumulator.computed[BLACK] = false; + auto& dp = st->dirtyPiece; + dp.dirty_num = 1; + + Color us = sideToMove; + Color them = ~us; + Square from = from_sq(m); + Square to = to_sq(m); + Piece pc = moved_piece(m); + Piece captured = piece_on(type_of(m) == EN_PASSANT ? capture_square(to) : to); + if (to == from) + { + assert((type_of(m) == PROMOTION && sittuyin_promotion()) || (is_pass(m) && (pass(us) || var->wallOrMove ))); + captured = NO_PIECE; + } + st->capturedpromoted = is_promoted(to); + st->unpromotedCapturedPiece = captured ? unpromoted_piece_on(to) : NO_PIECE; + st->pass = is_pass(m); + + assert(color_of(pc) == us); + assert(captured == NO_PIECE || color_of(captured) == (type_of(m) != CASTLING ? them : us)); + assert(type_of(captured) != KING); + + if (check_counting() && givesCheck) + k ^= Zobrist::checks[us][st->checksRemaining[us]] ^ Zobrist::checks[us][--(st->checksRemaining[us])]; + + if (type_of(m) == CASTLING) + { + assert(type_of(pc) != NO_PIECE_TYPE); + assert(castling_rook_pieces(us) & type_of(captured)); + + Square rfrom, rto; + do_castling(us, from, to, rfrom, rto); + + k ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto]; + captured = NO_PIECE; + } + + if (captured) + { + Square capsq = to; + + if (type_of(m) == EN_PASSANT) + { + capsq = capture_square(to); + st->captureSquare = capsq; + + assert(st->epSquares & to); + assert(var->enPassantRegion[us] & to); + assert(piece_on(to) == NO_PIECE); + } + + // If the captured piece is a pawn, update pawn hash key, otherwise + // update non-pawn material. + if (type_of(captured) == PAWN) + st->pawnKey ^= Zobrist::psq[captured][capsq]; + 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) == EN_PASSANT) + board[capsq] = NO_PIECE; + if (captures_to_hand()) + { + Piece pieceToHand = !capturedPromoted || drop_loop() ? ~captured + : unpromotedCaptured ? ~unpromotedCaptured + : make_piece(~color_of(captured), main_promotion_pawn_type(color_of(captured))); + 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)]]; + + if (Eval::useNNUE) + { + dp.handPiece[1] = pieceToHand; + dp.handCount[1] = pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)]; + } + } + else if (Eval::useNNUE) + dp.handPiece[1] = NO_PIECE; + + // Update material hash key and prefetch access to materialTable + k ^= Zobrist::psq[captured][capsq]; + st->materialKey ^= Zobrist::psq[captured][pieceCount[captured]]; +#ifndef NO_THREADS + prefetch(thisThread->materialTable[material_key(var->endgameEval)]); +#endif + // Reset rule 50 counter + st->rule50 = 0; + } + + // Update hash key + if (type_of(m) == DROP) + { + Piece pc_hand = make_piece(us, in_hand_piece_type(m)); + k ^= Zobrist::psq[pc][to] + ^ Zobrist::inHand[pc_hand][pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)] - 1] + ^ Zobrist::inHand[pc_hand][pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)]]; + + // Reset rule 50 counter for irreversible drops + st->rule50 = 0; + } + else + { + k ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; + + // Reset rule 50 draw counter for irreversible moves + // - irreversible pawn/piece promotions + // - irreversible pawn moves + if ( type_of(m) == PROMOTION + || (type_of(m) == PIECE_PROMOTION && !piece_demotion()) + || ( (var->nMoveRuleTypes[us] & type_of(pc)) + && !(PseudoMoves[0][us][type_of(pc)][to] & from))) + st->rule50 = 0; + } + + // Reset en passant squares + while (st->epSquares) + k ^= Zobrist::enpassant[file_of(pop_lsb(st->epSquares))]; + + // Update castling rights if needed + if (type_of(m) != DROP && !is_pass(m) && st->castlingRights && (castlingRightsMask[from] | castlingRightsMask[to])) + { + k ^= Zobrist::castling[st->castlingRights]; + st->castlingRights &= ~(castlingRightsMask[from] | castlingRightsMask[to]); + + // Remove castling rights from opponent on the same side if oppositeCastling + if ((var->oppositeCastling) && (type_of(m) == CASTLING)) + { + bool kingSide = to > from; + st->castlingRights &= ~(~us & (kingSide ? KING_SIDE : QUEEN_SIDE)); + } + 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, ~pieces(~us)) & ~PseudoAttacks[us][KING][to] & pieces(us); + while(b) + st->flippedPieces |= between_bb(pop_lsb(b), to) ^ to; + } + else + { + assert((flip_enclosed_pieces() == ATAXX) || (flip_enclosed_pieces() == QUADWRANGLE)); + if ((flip_enclosed_pieces() == ATAXX) || (flip_enclosed_pieces() == QUADWRANGLE && (PseudoAttacks[us][KING][to] & pieces(us) || type_of(m) == NORMAL))) + { + 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) == DROP) + { + if (Eval::useNNUE) + { + // Add drop piece + dp.piece[0] = pc; + dp.handPiece[0] = make_piece(us, in_hand_piece_type(m)); + dp.handCount[0] = pieceCountInHand[us][in_hand_piece_type(m)]; + dp.from[0] = SQ_NONE; + dp.to[0] = to; + } + + 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) == castling_king_piece(us) && file_of(to) == castling_king_file()) + { + st->castlingKingSquare[us] = to; + Bitboard castling_rooks = pieces(us) + & rank_bb(castling_rank(us)) + & (file_bb(FILE_A) | file_bb(max_file())); + while (castling_rooks) + { + Square s = pop_lsb(castling_rooks); + if (castling_rook_pieces(us) & type_of(piece_on(s))) + set_castling_right(us, s); + } + } + else if (castling_rook_pieces(us) & type_of(pc)) + { + if ( (file_of(to) == FILE_A || file_of(to) == max_file()) + && piece_on(make_square(castling_king_file(), castling_rank(us))) == make_piece(us, castling_king_piece(us))) + { + st->castlingKingSquare[us] = make_square(castling_king_file(), castling_rank(us)); + 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) + { + if (type_of(m) == PROMOTION || type_of(m) == PIECE_PROMOTION) + { + Piece promotion = make_piece(us, type_of(m) == PROMOTION ? promotion_type(m) : promoted_piece_type(PAWN)); + + assert((promotion_zone(us) & to) || sittuyin_promotion()); + assert(type_of(promotion) >= KNIGHT && type_of(promotion) < KING); + + st->promotionPawn = piece_on(to); + remove_piece(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.handPiece[0] = NO_PIECE; + dp.piece[dp.dirty_num] = promotion; + dp.handPiece[dp.dirty_num] = NO_PIECE; + 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]; + st->materialKey ^= Zobrist::psq[promotion][pieceCount[promotion]-1] + ^ Zobrist::psq[pc][pieceCount[pc]]; + + // Update material + st->nonPawnMaterial[us] += PieceValue[MG][promotion]; + } + + // Set en passant square(s) if the moved pawn can be captured + else if ( type_of(m) != DROP + && ( std::abs(int(to) - int(from)) == 2 * NORTH + || std::abs(int(to) - int(from)) == 3 * NORTH)) + { + if ( (var->enPassantRegion[them] & (to - pawn_push(us))) + && ((pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN)) || var->enPassantTypes[them] & ~piece_set(PAWN)) + && !(walling() && gating_square(m) == to - pawn_push(us))) + { + st->epSquares |= to - pawn_push(us); + k ^= Zobrist::enpassant[file_of(to)]; + } + if ( std::abs(int(to) - int(from)) == 3 * NORTH + && (var->enPassantRegion[them] & (to - 2 * pawn_push(us))) + && ((pawn_attacks_bb(us, to - 2 * pawn_push(us)) & pieces(them, PAWN)) || var->enPassantTypes[them] & ~piece_set(PAWN)) + && !(walling() && gating_square(m) == to - 2 * pawn_push(us))) + { + st->epSquares |= to - 2 * pawn_push(us); + k ^= Zobrist::enpassant[file_of(to)]; + } + } + + // Update pawn hash key + st->pawnKey ^= (type_of(m) != DROP ? Zobrist::psq[pc][from] : 0) ^ Zobrist::psq[pc][to]; + } + else if (type_of(m) == PROMOTION || type_of(m) == PIECE_PROMOTION) + { + Piece promotion = make_piece(us, type_of(m) == PROMOTION ? promotion_type(m) : promoted_piece_type(type_of(pc))); + + st->promotionPawn = piece_on(to); + remove_piece(to); + put_piece(promotion, to, true, type_of(m) == PIECE_PROMOTION ? pc : NO_PIECE); + + if (Eval::useNNUE) + { + // Promoting piece to SQ_NONE, promoted piece from SQ_NONE + dp.to[0] = SQ_NONE; + dp.handPiece[0] = NO_PIECE; + dp.piece[dp.dirty_num] = promotion; + dp.handPiece[dp.dirty_num] = NO_PIECE; + 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.handPiece[0] = NO_PIECE; + dp.piece[dp.dirty_num] = demotion; + dp.handPiece[dp.dirty_num] = NO_PIECE; + 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 en passant square(s) if the moved piece can be captured + else if ( type_of(m) != DROP + && ((PseudoMoves[1][us][type_of(pc)][from] & ~PseudoMoves[0][us][type_of(pc)][from]) & to)) + { + assert(type_of(pc) != PAWN); + st->epSquares = between_bb(from, to) & var->enPassantRegion[them]; + for (Bitboard b = st->epSquares; b; ) + k ^= Zobrist::enpassant[file_of(pop_lsb(b))]; + } + + // 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)); + + if (Eval::useNNUE) + { + // Add gating piece + dp.piece[dp.dirty_num] = gating_piece; + dp.handPiece[dp.dirty_num] = gating_piece; + dp.handCount[dp.dirty_num] = pieceCountInHand[us][gating_type(m)]; + dp.from[dp.dirty_num] = SQ_NONE; + dp.to[dp.dirty_num] = gate; + dp.dirty_num++; + } + + put_piece(gating_piece, gate); + remove_from_hand(gating_piece); + + 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) == 0 && !captures_to_hand()) + st->gatesBB[us] = 0; + } + + // Remove king leaping right when aimed by a rook + if (cambodian_moves() && type_of(pc) == ROOK && (square(them) & gates(them) & attacks_bb(to))) + st->gatesBB[them] ^= square(them); + + + // Remove the blast pieces + if (captured && (blast_on_capture() || var->petrifyOnCaptureTypes)) + { + std::memset(st->unpromotedBycatch, 0, sizeof(st->unpromotedBycatch)); + st->demotedBycatch = st->promotedBycatch = 0; + Bitboard blastImmune = 0; + for (PieceSet ps = blast_immune_types(); ps;){ + PieceType pt = pop_lsb(ps); + blastImmune |= pieces(pt); + }; + Bitboard blast = blast_on_capture() ? ((attacks_bb(to) & ((pieces(WHITE) | pieces(BLACK)) ^ pieces(PAWN))) | to) + & (pieces() ^ blastImmune) : var->petrifyOnCaptureTypes & type_of(pc) ? square_bb(to) : Bitboard(0); + while (blast) + { + Square bsq = pop_lsb(blast); + Piece bpc = piece_on(bsq); + Color bc = color_of(bpc); + if (type_of(bpc) != PAWN) + st->nonPawnMaterial[bc] -= PieceValue[MG][bpc]; + + if (Eval::useNNUE) + { + dp.piece[dp.dirty_num] = bpc; + dp.handPiece[dp.dirty_num] = NO_PIECE; + dp.from[dp.dirty_num] = bsq; + dp.to[dp.dirty_num] = SQ_NONE; + dp.dirty_num++; + } + + // Update board and piece lists + // In order to not have to store the values of both board and unpromotedBoard, + // demote promoted pieces, but keep promoted pawns as promoted, + // and store demotion/promotion bitboards to disambiguate the piece state + bool capturedPromoted = is_promoted(bsq); + Piece unpromotedCaptured = unpromoted_piece_on(bsq); + st->unpromotedBycatch[bsq] = unpromotedCaptured ? unpromotedCaptured : bpc; + if (unpromotedCaptured) + st->demotedBycatch |= bsq; + else if (capturedPromoted) + st->promotedBycatch |= bsq; + remove_piece(bsq); + board[bsq] = NO_PIECE; + if (captures_to_hand()) + { + Piece pieceToHand = !capturedPromoted || drop_loop() ? ~bpc + : unpromotedCaptured ? ~unpromotedCaptured + : make_piece(~color_of(bpc), 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)]]; + + if (Eval::useNNUE) + { + dp.handPiece[dp.dirty_num - 1] = pieceToHand; + dp.handCount[dp.dirty_num - 1] = pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)]; + } + } + + // Update material hash key + k ^= Zobrist::psq[bpc][bsq]; + st->materialKey ^= Zobrist::psq[bpc][pieceCount[bpc]]; + if (type_of(bpc) == PAWN) + st->pawnKey ^= Zobrist::psq[bpc][bsq]; + + // Update castling rights if needed + if (st->castlingRights && castlingRightsMask[bsq]) + { + k ^= Zobrist::castling[st->castlingRights]; + st->castlingRights &= ~castlingRightsMask[bsq]; + k ^= Zobrist::castling[st->castlingRights]; + } + + // Make a wall square where the piece was + if (bsq == to ? bool(var->petrifyOnCaptureTypes & type_of(bpc)) : var->petrifyBlastPieces) + { + st->wallSquares |= bsq; + byTypeBB[ALL_PIECES] |= bsq; + k ^= Zobrist::wall[bsq]; + } + } + } + + // Add gated wall square + // if wallOrMove, only actually place the wall if they gave up their move + if (walling() && (!var->wallOrMove || (from==to))) + { + // Reset wall squares for duck walling + if (walling_rule() == DUCK) + { + Bitboard b = st->previous->wallSquares; + byTypeBB[ALL_PIECES] ^= b; + while (b) + k ^= Zobrist::wall[pop_lsb(b)]; + st->wallSquares = 0; + } + st->wallSquares |= gating_square(m); + byTypeBB[ALL_PIECES] |= gating_square(m); + k ^= Zobrist::wall[gating_square(m)]; + } + + // Update the key with the final value + st->key = k; + // Calculate checkers bitboard (if move gives check) + st->checkersBB = givesCheck ? attackers_to(square(them), us) & pieces(us) : Bitboard(0); + assert(givesCheck == bool(st->checkersBB)); + + sideToMove = ~sideToMove; + + if (counting_rule()) + { + if (counting_rule() != ASEAN_COUNTING && type_of(captured) == PAWN && count(~sideToMove) == 1 && !count() && count_limit(~sideToMove)) + { + st->countingLimit = 2 * count_limit(~sideToMove); + st->countingPly = 2 * count() - 1; + } + + if ((!st->countingLimit || ((captured || type_of(m) == PROMOTION) && count(sideToMove) == 1)) && count_limit(sideToMove)) + { + st->countingLimit = 2 * count_limit(sideToMove); + st->countingPly = counting_rule() == ASEAN_COUNTING || count(sideToMove) > 1 ? 0 : 2 * count(); + } + } + + // Update king attacks used for fast check detection + set_check_info(st); + + // Calculate the repetition info. It is the ply distance from the previous + // occurrence of the same position, negative in the 3-fold case, or zero + // if the position was not repeated. + st->repetition = 0; + int end = captures_to_hand() ? st->pliesFromNull : std::min(st->rule50, st->pliesFromNull); + if (end >= 4) + { + StateInfo* stp = st->previous->previous; + for (int i = 4; i <= end; i += 2) + { + stp = stp->previous->previous; + if (stp->key == st->key) + { + st->repetition = stp->repetition ? -i : i; + break; + } + } + } + + assert(pos_is_ok()); +} + + +/// Position::undo_move() unmakes a move. When it returns, the position should +/// be restored to exactly the same state as before the move was made. + +void Position::undo_move(Move m) { + + assert(is_ok(m)); + + sideToMove = ~sideToMove; + + Color us = sideToMove; + Square from = from_sq(m); + Square to = to_sq(m); + Piece pc = piece_on(to); + + assert(type_of(m) == DROP || empty(from) || type_of(m) == CASTLING || is_gating(m) + || (type_of(m) == PROMOTION && sittuyin_promotion()) + || (is_pass(m) && (pass(us) || var->wallOrMove))); + assert(type_of(st->capturedPiece) != KING); + + // Reset wall squares + byTypeBB[ALL_PIECES] ^= st->wallSquares ^ st->previous->wallSquares; + + // Add the blast pieces + if (st->capturedPiece && (blast_on_capture() || var->petrifyOnCaptureTypes)) + { + Bitboard blast = attacks_bb(to) | to; + while (blast) + { + Square bsq = pop_lsb(blast); + Piece unpromotedBpc = st->unpromotedBycatch[bsq]; + Piece bpc = st->demotedBycatch & bsq ? make_piece(color_of(unpromotedBpc), promoted_piece_type(type_of(unpromotedBpc))) + : unpromotedBpc; + bool isPromoted = (st->promotedBycatch | st->demotedBycatch) & bsq; + + // Update board and piece lists + if (bpc) + { + put_piece(bpc, bsq, isPromoted, st->demotedBycatch & bsq ? unpromotedBpc : NO_PIECE); + if (captures_to_hand()) + remove_from_hand(!drop_loop() && (st->promotedBycatch & bsq) ? make_piece(~color_of(unpromotedBpc), PAWN) + : ~unpromotedBpc); + } + } + // Reset piece since it exploded itself + pc = piece_on(to); + } + + // Remove gated piece + if (is_gating(m)) + { + Piece gating_piece = make_piece(us, gating_type(m)); + remove_piece(gating_square(m)); + board[gating_square(m)] = NO_PIECE; + add_to_hand(gating_piece); + st->gatesBB[us] |= gating_square(m); + } + + if (type_of(m) == PROMOTION) + { + assert((promotion_zone(us) & to) || sittuyin_promotion()); + assert(type_of(pc) == promotion_type(m)); + assert(type_of(pc) >= KNIGHT && type_of(pc) < KING); + assert(type_of(st->promotionPawn) == main_promotion_pawn_type(us) || !captures_to_hand()); + + remove_piece(to); + pc = st->promotionPawn; + put_piece(pc, to); + } + else if (type_of(m) == PIECE_PROMOTION) + { + Piece unpromotedPiece = unpromoted_piece_on(to); + remove_piece(to); + pc = unpromotedPiece; + put_piece(pc, to); + } + else if (type_of(m) == PIECE_DEMOTION) + { + remove_piece(to); + Piece unpromotedPc = pc; + pc = make_piece(us, promoted_piece_type(type_of(pc))); + put_piece(pc, to, true, unpromotedPc); + } + + if (type_of(m) == CASTLING) + { + Square rfrom, rto; + do_castling(us, from, to, rfrom, rto); + } + else + { + if (type_of(m) == DROP) + undrop_piece(make_piece(us, in_hand_piece_type(m)), to); // Remove the dropped piece + else + move_piece(to, from); // Put the piece back at the source square + + if (st->capturedPiece) + { + Square capsq = to; + + if (type_of(m) == EN_PASSANT) + { + capsq = st->captureSquare; + + assert(st->previous->epSquares & to); + assert(var->enPassantRegion[sideToMove] & to); + assert(piece_on(capsq) == NO_PIECE); + } + + put_piece(st->capturedPiece, capsq, st->capturedpromoted, st->unpromotedCapturedPiece); // Restore the captured piece + if (captures_to_hand()) + remove_from_hand(!drop_loop() && st->capturedpromoted ? (st->unpromotedCapturedPiece ? ~st->unpromotedCapturedPiece + : make_piece(~color_of(st->capturedPiece), main_promotion_pawn_type(us))) + : ~st->capturedPiece); + } + } + + if (flip_enclosed_pieces()) + { + // Flip pieces + Bitboard to_flip = st->flippedPieces; + while(to_flip) + { + Square s = pop_lsb(to_flip); + Piece resulting = ~piece_on(s); + remove_piece(s); + put_piece(resulting, s); + } + } + + // Finally point our state pointer back to the previous state + st = st->previous; + --gamePly; + + assert(pos_is_ok()); +} + + +/// Position::do_castling() is a helper used to do/undo a castling move. This +/// is a bit tricky in Chess960 where from/to squares can overlap. +template +void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto) { + + bool kingSide = to > from; + rfrom = to; // Castling is encoded as "king captures friendly rook" + to = make_square(kingSide ? castling_kingside_file() : castling_queenside_file(), castling_rank(us)); + rto = to + (kingSide ? WEST : EAST); + + 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] = castlingKingPiece; + dp.from[0] = from; + dp.to[0] = to; + 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 it for us + put_piece(castlingKingPiece, Do ? to : from); + put_piece(castlingRookPiece, Do ? rto : rfrom); +} + + +/// Position::do_null_move() is used to do a "null move": it flips +/// the side to move without executing any move on the board. + +void Position::do_null_move(StateInfo& newSt) { + + assert(!checkers()); + assert(&newSt != st); + + std::memcpy(&newSt, st, offsetof(StateInfo, accumulator)); + + newSt.previous = st; + st = &newSt; + + st->dirtyPiece.dirty_num = 0; + st->dirtyPiece.piece[0] = NO_PIECE; // Avoid checks in UpdateAccumulator() + st->accumulator.computed[WHITE] = false; + st->accumulator.computed[BLACK] = false; + + while (st->epSquares) + st->key ^= Zobrist::enpassant[file_of(pop_lsb(st->epSquares))]; + + st->key ^= Zobrist::side; + prefetch(TT.first_entry(key())); + + ++st->rule50; + st->pliesFromNull = 0; + + sideToMove = ~sideToMove; + + set_check_info(st); + + st->repetition = 0; + + assert(pos_is_ok()); +} + + +/// Position::undo_null_move() must be used to undo a "null move" + +void Position::undo_null_move() { + + assert(!checkers()); + + st = st->previous; + sideToMove = ~sideToMove; +} + + +/// Position::key_after() computes the new hash key after the given move. Needed +/// for speculative prefetch. It doesn't recognize special moves like castling, +/// en passant and promotions. + +Key Position::key_after(Move m) const { + + Square from = from_sq(m); + Square to = to_sq(m); + Piece pc = moved_piece(m); + Piece captured = piece_on(to); + Key k = st->key ^ Zobrist::side; + + if (captured) + { + k ^= Zobrist::psq[captured][to]; + if (captures_to_hand()) + { + Piece removeFromHand = !drop_loop() && is_promoted(to) ? make_piece(~color_of(captured), main_promotion_pawn_type(color_of(captured))) : ~captured; + k ^= Zobrist::inHand[removeFromHand][pieceCountInHand[color_of(removeFromHand)][type_of(removeFromHand)] + 1] + ^ Zobrist::inHand[removeFromHand][pieceCountInHand[color_of(removeFromHand)][type_of(removeFromHand)]]; + } + } + if (type_of(m) == DROP) + { + Piece pc_hand = make_piece(sideToMove, in_hand_piece_type(m)); + return k ^ Zobrist::psq[pc][to] ^ Zobrist::inHand[pc_hand][pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)]] + ^ Zobrist::inHand[pc_hand][pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)] - 1]; + } + + return k ^ Zobrist::psq[pc][to] ^ Zobrist::psq[pc][from]; +} + + +Value Position::blast_see(Move m) const { + assert(is_ok(m)); + + Square from = from_sq(m); + Square to = to_sq(m); + Color us = color_of(moved_piece(m)); + Bitboard fromto = type_of(m) == DROP ? square_bb(to) : from | to; + Bitboard blast = ((attacks_bb(to) & ~pieces(PAWN)) | fromto) & (pieces(WHITE) | pieces(BLACK)); + + Value result = VALUE_ZERO; + + // Add the least valuable attacker for quiet moves + if (!capture(m)) + { + Bitboard attackers = attackers_to(to, pieces() ^ fromto, ~us); + Value minAttacker = VALUE_INFINITE; + + while (attackers) + { + Square s = pop_lsb(attackers); + if (!(extinction_piece_types() & type_of(piece_on(s)))) + minAttacker = std::min(minAttacker, blast & s ? VALUE_ZERO : CapturePieceValue[MG][piece_on(s)]); + } + + if (minAttacker == VALUE_INFINITE) + return VALUE_ZERO; + + result += minAttacker; + if (type_of(m) == DROP) + result -= CapturePieceValue[MG][dropped_piece_type(m)]; + } + + // Sum up blast piece values + bool extinctsUs = false; + bool extinctsThem = false; + while (blast) + { + Piece bpc = piece_on(pop_lsb(blast)); + if (extinction_piece_types() & type_of(bpc)) + { + if (color_of(bpc) == us) + extinctsUs = true; + else + extinctsThem = true; + } + result += color_of(bpc) == us ? -CapturePieceValue[MG][bpc] : CapturePieceValue[MG][bpc]; + } + + // Evaluate extinctions + if (!capture(m)) + { + // For quiet moves, the opponent can decide whether to capture or not + // so they can pick the better of the two + if (extinctsThem && extinctsUs) + return VALUE_ZERO; + if (extinctsThem) + return std::min(-extinction_value(), VALUE_ZERO); + if (extinctsUs) + return std::min(extinction_value(), VALUE_ZERO); + } + else + { + if (extinctsUs) + return extinction_value(); + if (extinctsThem) + return -extinction_value(); + } + + return capture(m) || must_capture() ? result - 1 : std::min(result, VALUE_ZERO); +} + + +/// Position::see_ge (Static Exchange Evaluation Greater or Equal) tests if the +/// SEE value of move is greater or equal to the given threshold. We'll use an +/// algorithm similar to alpha-beta pruning with a null window. + +bool Position::see_ge(Move m, Value threshold) const { + + assert(is_ok(m)); + + // Only deal with normal moves, assume others pass a simple SEE + if (type_of(m) != NORMAL && type_of(m) != DROP && type_of(m) != PIECE_PROMOTION) + return VALUE_ZERO >= threshold; + + Square from = from_sq(m), to = to_sq(m); + + // nCheck + if (check_counting() && color_of(moved_piece(m)) == sideToMove && gives_check(m)) + return true; + + // Atomic explosion SEE + if (blast_on_capture()) + return blast_see(m) >= threshold; + + // Extinction + if ( extinction_value() != VALUE_NONE + && piece_on(to) + && ( ( (extinction_piece_types() & type_of(piece_on(to))) + && pieceCount[piece_on(to)] == extinction_piece_count() + 1) + || ( (extinction_piece_types() & ALL_PIECES) + && count(~sideToMove) == extinction_piece_count() + 1))) + return extinction_value() < VALUE_ZERO; + + // Do not evaluate SEE if value would be unreliable + if (must_capture() || !checking_permitted() || is_gating(m) || count() == count()) + return VALUE_ZERO >= threshold; + + int swap = PieceValue[MG][piece_on(to)] - threshold; + if (swap < 0) + return false; + + swap = PieceValue[MG][moved_piece(m)] - swap; + if (swap <= 0) + return true; + + // Petrification ends SEE + if (var->petrifyOnCaptureTypes & type_of(moved_piece(m)) && capture(m)) + return false; + + Bitboard occupied = (type_of(m) != DROP ? pieces() ^ from : pieces()) ^ to; + Color stm = color_of(moved_piece(m)); + Bitboard attackers = attackers_to(to, occupied); + Bitboard stmAttackers, bb; + int res = 1; + + // Flying general rule + if (var->flyingGeneral) + { + if (attackers & pieces(stm, KING)) + attackers |= attacks_bb(stm, ROOK, to, occupied & ~pieces(ROOK)) & pieces(~stm, KING); + if (attackers & pieces(~stm, KING)) + attackers |= attacks_bb(~stm, ROOK, to, occupied & ~pieces(ROOK)) & pieces(stm, KING); + } + + // Janggi cannons can not capture each other + if (type_of(moved_piece(m)) == JANGGI_CANNON && !(attackers & pieces(~stm) & ~pieces(JANGGI_CANNON))) + attackers &= ~pieces(~stm, JANGGI_CANNON); + + while (true) + { + stm = ~stm; + attackers &= occupied; + + // If stm has no more attackers then give up: stm loses + if (!(stmAttackers = attackers & pieces(stm))) + break; + + // Don't allow pinned pieces to attack as long as there are + // pinners on their original square. + if (pinners(~stm) & occupied) + stmAttackers &= ~blockers_for_king(stm); + + // Ignore distant sliders + if (walling_rule() == DUCK) + stmAttackers &= attacks_bb(to) | ~(pieces(BISHOP, ROOK) | pieces(QUEEN)); + + if (!stmAttackers) + break; + + res ^= 1; + + // Locate and remove the next least valuable attacker, and add to + // the bitboard 'attackers' any X-ray attackers behind it. + if ((bb = stmAttackers & pieces(PAWN))) + { + if ((swap = PawnValueMg - swap) < res) + break; + + occupied ^= least_significant_square_bb(bb); + attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); + } + + else if ((bb = stmAttackers & pieces(KNIGHT))) + { + if ((swap = KnightValueMg - swap) < res) + break; + + occupied ^= least_significant_square_bb(bb); + } + + else if ((bb = stmAttackers & pieces(BISHOP))) + { + if ((swap = BishopValueMg - swap) < res) + break; + + occupied ^= least_significant_square_bb(bb); + attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); + } + + else if ((bb = stmAttackers & pieces(ROOK))) + { + if ((swap = RookValueMg - swap) < res) + break; + + occupied ^= least_significant_square_bb(bb); + attackers |= attacks_bb(to, occupied) & pieces(ROOK, QUEEN); + } + + else if ((bb = stmAttackers & pieces(QUEEN))) + { + if ((swap = QueenValueMg - swap) < res) + break; + + occupied ^= least_significant_square_bb(bb); + attackers |= (attacks_bb(to, occupied) & pieces(BISHOP, QUEEN)) + | (attacks_bb(to, occupied) & pieces(ROOK , QUEEN)); + } + + // fairy pieces + // pick next piece without considering value + else if ((bb = stmAttackers & ~pieces(KING))) + { + if ((swap = PieceValue[MG][piece_on(lsb(bb))] - swap) < res) + break; + + occupied ^= lsb(bb); + } + + else // KING + // If we "capture" with the king but opponent still has attackers, + // reverse the result. + return (attackers & ~pieces(stm)) ? res ^ 1 : res; + } + + return bool(res); +} + +/// Position::is_optional_game_end() tests whether the position may end the game by +/// 50-move rule, by repetition, or a variant rule that allows a player to claim a game result. + +bool Position::is_optional_game_end(Value& result, int ply, int countStarted) const { + + // n-move rule + if (n_move_rule() && st->rule50 > (2 * n_move_rule() - 1) && (!checkers() || MoveList(*this).size())) + { + int offset = 0; + if (var->chasingRule == AXF_CHASING && st->pliesFromNull >= 20) + { + int end = std::min(st->rule50, st->pliesFromNull); + StateInfo* stp = st; + int checkThem = bool(stp->checkersBB); + int checkUs = bool(stp->previous->checkersBB); + for (int i = 2; i < end; i += 2) + { + stp = stp->previous->previous; + checkThem += bool(stp->checkersBB); + checkUs += bool(stp->previous->checkersBB); + } + offset = 2 * std::max(std::max(checkThem, checkUs) - 10, 0) + 20 * (CurrentProtocol == UCCI || CurrentProtocol == UCI_CYCLONE); + } + if (st->rule50 - offset > (2 * n_move_rule() - 1)) + { + result = var->materialCounting ? convert_mate_value(material_counting_result(), ply) : VALUE_DRAW; + return true; + } + } + + // n-fold repetition + if (n_fold_rule()) + { + int end = captures_to_hand() ? st->pliesFromNull : std::min(st->rule50, st->pliesFromNull); + + if (end >= 4) + { + StateInfo* stp = st->previous->previous; + int cnt = 0; + bool perpetualThem = var->perpetualCheckIllegal && st->checkersBB && stp->checkersBB; + bool perpetualUs = var->perpetualCheckIllegal && st->previous->checkersBB && stp->previous->checkersBB; + Bitboard chaseThem = undo_move_board(st->chased, st->previous->move) & stp->chased; + Bitboard chaseUs = undo_move_board(st->previous->chased, stp->move) & stp->previous->chased; + int moveRepetition = var->moveRepetitionIllegal + && type_of(st->move) == NORMAL + && !st->previous->checkersBB && !stp->previous->checkersBB + && (board_bb(~side_to_move(), type_of(piece_on(to_sq(st->move)))) & board_bb(side_to_move(), KING)) + ? (stp->move == reverse_move(st->move) ? 2 : is_pass(stp->move) ? 1 : 0) : 0; + + for (int i = 4; i <= end; i += 2) + { + // Janggi repetition rule + if (moveRepetition > 0) + { + if (i + 1 <= end && stp->previous->previous->previous->checkersBB) + moveRepetition = 0; + else if (moveRepetition < 4) + { + if (stp->previous->previous->move == reverse_move((moveRepetition == 1 ? st : stp)->move)) + moveRepetition++; + else + moveRepetition = 0; + } + else + { + assert(moveRepetition == 4); + if (!stp->previous->previous->capturedPiece && from_sq(stp->move) == to_sq(stp->previous->previous->move)) + { + result = VALUE_MATE; + return true; + } + else + moveRepetition = 0; + } + } + // Chased pieces are empty when there is no previous move + if (i != st->pliesFromNull) + chaseThem = undo_move_board(chaseThem, stp->previous->move) & stp->previous->previous->chased; + stp = stp->previous->previous; + perpetualThem &= bool(stp->checkersBB); + + // Return a draw score if a position repeats once earlier but strictly + // after the root, or repeats twice before or at the root. + if ( stp->key == st->key + && ++cnt + 1 >= (ply > i && !moveRepetition && !chaseUs && !chaseThem && !perpetualUs && !perpetualThem ? 2 : n_fold_rule())) + { + result = convert_mate_value( (perpetualThem || perpetualUs) ? (!perpetualUs ? VALUE_MATE : !perpetualThem ? -VALUE_MATE : VALUE_DRAW) + : (chaseThem || chaseUs) ? (!chaseUs ? VALUE_MATE : !chaseThem ? -VALUE_MATE : VALUE_DRAW) + : var->nFoldValueAbsolute && sideToMove == BLACK ? -var->nFoldValue + : var->nFoldValue, ply); + if (result == VALUE_DRAW && var->materialCounting) + result = convert_mate_value(material_counting_result(), ply); + return true; + } + + if (i + 1 <= end) + { + perpetualUs &= bool(stp->previous->checkersBB); + chaseUs = undo_move_board(chaseUs, stp->move) & stp->previous->chased; + } + } + } + } + + // counting rules + if ( counting_rule() + && st->countingLimit + && counting_ply(countStarted) > counting_limit(countStarted) + && (!checkers() || MoveList(*this).size())) + { + result = VALUE_DRAW; + return true; + } + + // sittuyin stalemate due to optional promotion (3.9 c.7) + if ( sittuyin_promotion() + && count(sideToMove) == 2 + && count(sideToMove) == 1 + && !checkers()) + { + bool promotionsOnly = true; + for (const auto& m : MoveList(*this)) + if (type_of(m) != PROMOTION) + { + promotionsOnly = false; + break; + } + if (promotionsOnly) + { + result = VALUE_DRAW; + return true; + } + } + + return false; +} + +/// Position::is_immediate_game_end() tests whether the position ends the game +/// immediately by a variant rule, i.e., there are no more legal moves. +/// It does not detect stalemates. + +bool Position::is_immediate_game_end(Value& result, int ply) const { + + // Extinction + // Extinction does not apply for pseudo-royal pieces, because they can not be captured + if (extinction_value() != VALUE_NONE && (!var->extinctionPseudoRoyal || blast_on_capture())) + { + for (Color c : { ~sideToMove, sideToMove }) + for (PieceSet ps = extinction_piece_types(); ps;) + { + PieceType pt = pop_lsb(ps); + if ( count_with_hand( c, pt) <= var->extinctionPieceCount + && count_with_hand(~c, pt) >= var->extinctionOpponentPieceCount + (extinction_claim() && c == sideToMove)) + { + result = c == sideToMove ? extinction_value(ply) : -extinction_value(ply); + return true; + } + } + } + // capture the flag + // A flag win by the side to move is only possible if flagMove or flagPieceSafe are enabled + // and they already reached the flag region the move before. + // In the case both colors reached it, it is a draw for flagPieceSafe or if white's king was first (special case for racing kings). + if ((flag_move() || var->flagPieceSafe) && flag_reached(sideToMove)) + { + result = ((flag_move() && sideToMove == WHITE && flag_piece(~sideToMove) == KING) + || (var->flagPieceSafe && !flag_move())) && flag_reached(~sideToMove) ? VALUE_DRAW : mate_in(ply); + return true; + } + // A direct flag win is possible if the opponent does not get an extra flag move + // or we can detect early for kings that they won't be able to reach the flag region + // Note: This condition has to be after the above, since both might be true e.g. in racing kings. + if ( (!flag_move() || var->flagPieceSafe || flag_piece(sideToMove) == KING) // we can do early win detection only for the king + && flag_reached(~sideToMove)) + { + bool gameEnd = true; + // Check whether king can move to CTF zone (racing kings) to draw + if ( flag_move() && flag_piece(sideToMove) == KING && sideToMove == BLACK && !checkers() && count(sideToMove) + && (flag_region(sideToMove) & attacks_from(sideToMove, KING, square(sideToMove)))) + { + assert(flag_piece(sideToMove) == KING); + for (const auto& m : MoveList(*this)) + if (type_of(moved_piece(m)) == KING && (flag_region(sideToMove) & to_sq(m)) && legal(m)) + { + gameEnd = false; + break; + } + } + if (gameEnd) + { + result = mated_in(ply); + return true; + } + } + + // Castle chess + if (var->castlingWins) + { + if (st->pliesFromNull > 0 && type_of(st->move) == CASTLING) + { + // check for victory first, because castling also removes castling rights. + CastlingRights justCastled = ~sideToMove & ((from_sq(st->move) < to_sq(st->move)) ? KING_SIDE : QUEEN_SIDE); + if (var->castlingWins & justCastled) + { + result = mated_in(ply); + return true; + } + } + // We check the opponent side first, because a rook capturing a rook could remove both sides castling rights, + // which should likely be seen as losing, analogous to extinction rules. + for (Color c : { ~sideToMove, sideToMove }) + if ((c & var->castlingWins) && !(c & var->castlingWins & st->castlingRights)) + { + // player permanently losing castling rights. either through moving a castling piece, + // or having their rook captured. + result = c == sideToMove ? mated_in(ply) : mate_in(ply); + return true; + } + } + + // nCheck + if (check_counting() && checks_remaining(~sideToMove) == 0) + { + result = mated_in(ply); + return true; + } + + //Calculate eligible pieces for connection once. + Bitboard connectPieces = 0; + for (PieceSet ps = connect_piece_types(); ps;){ + PieceType pt = pop_lsb(ps); + connectPieces |= pieces(pt); + }; + connectPieces &= pieces(~sideToMove); + + // Connect-n + if (connect_n() > 0) + { + Bitboard b; + + for (Direction d : var->connectDirections) + { + b = connectPieces; + for (int i = 1; i < connect_n() && b; i++) + b &= shift(d, b); + if (b) + { + result = convert_mate_value(-var->connectValue, ply); + return true; + } + } + } + + if ((var->connectRegion1[~sideToMove] & connectPieces) && (var->connectRegion2[~sideToMove] & connectPieces)) + { + Bitboard target = var->connectRegion2[~sideToMove]; + Bitboard current = var->connectRegion1[~sideToMove] & connectPieces; + + while (true) { + Bitboard newBitboard = 0; + for (Direction d : var->connectDirections) { + newBitboard |= shift(d, current | newBitboard) & connectPieces; // the "| newBitboard" here probably saves a few loops + } + + if (newBitboard & target) { + // A connection has been made + result = convert_mate_value(-var->connectValue, ply); + return true; + } + + if (!(newBitboard & ~current)) { + // The expansion got stuck; no further squares to explore + break; + } + + current |= newBitboard; + } + } + + if (connect_nxn()) + { + Bitboard connectors = connectPieces; + for (int i = 1; i < connect_nxn() && connectors; i++) + connectors &= shift(connectors) & shift(connectors) & shift(connectors); + if (connectors) + { + result = convert_mate_value(-var->connectValue, ply); + return true; + } + } + + // Collinear-n + if (collinear_n() > 0) { + Bitboard allPieces = connectPieces; + for (Direction d : var->connectDirections) { + Bitboard b = allPieces; + while (b) { + Square s = pop_lsb(b); + + int total_count = 1; // Start with the current piece + + // Check in both directions + for (int sign : {-1, 1}) { + Bitboard shifted = shift(sign * d, square_bb(s)); + while (shifted) { + if (shifted & b) { + total_count++; + b &= ~shifted; // Remove this piece from further consideration + } + shifted = shift(sign * d, shifted); + } + } + + if (total_count >= collinear_n()) { + result = convert_mate_value(-var->connectValue, ply); + return true; + } + } + } + } + + // Check for bikjang rule (Janggi), double passing, or board running full + if ( (st->pliesFromNull > 0 && ((st->bikjang && st->previous->bikjang) || ((st->pass && st->previous->pass)&&!var->wallOrMove))) + || (var->adjudicateFullBoard && !(~pieces() & board_bb()))) + { + result = var->materialCounting ? convert_mate_value(material_counting_result(), ply) : VALUE_DRAW; + return true; + } + + // Tsume mode: Assume that side with king wins when not in check + if (tsumeMode && !count(~sideToMove) && count(sideToMove) && !checkers()) + { + result = mate_in(ply); + return true; + } + + // Failing to checkmate with virtual pieces is a loss + if (two_boards() && !checkers()) + { + int virtualCount = 0; + for (PieceSet ps = piece_types(); ps;) + virtualCount += std::max(-count_in_hand(~sideToMove, pop_lsb(ps)), 0); + + if (virtualCount > 0) + { + result = mate_in(ply); + return true; + } + } + + return false; +} + +// Position::chased() tests whether the last move was a chase. + +Bitboard Position::chased() const { + Bitboard b = 0; + if (st->move == MOVE_NONE) + return b; + + Bitboard pins = blockers_for_king(sideToMove); + if (var->flyingGeneral) + { + Bitboard kingFilePieces = file_bb(file_of(square(~sideToMove))) & pieces(sideToMove); + if ((kingFilePieces & pieces(sideToMove, KING)) && !more_than_one(kingFilePieces & ~pieces(KING))) + pins |= kingFilePieces & ~pieces(KING); + } + auto addChased = [&](Square attackerSq, PieceType attackerType, Bitboard attacks) { + if (attacks & ~b) + { + // Exclude attacks on unpromoted soldiers and checks + attacks &= ~(pieces(sideToMove, KING, SOLDIER) ^ promoted_soldiers(sideToMove)); + // Attacks against stronger pieces + if (attackerType == HORSE || attackerType == CANNON) + b |= attacks & pieces(sideToMove, ROOK); + if (attackerType == ELEPHANT || attackerType == FERS) + b |= attacks & pieces(sideToMove, ROOK, CANNON, HORSE); + // Exclude mutual/symmetric attacks + // Exceptions: + // - asymmetric pieces ("impaired horse") + // - pins + if (attackerType == HORSE && (PseudoAttacks[WHITE][FERS][attackerSq] & pieces())) + { + Bitboard horses = attacks & pieces(sideToMove, attackerType); + while (horses) + { + Square s = pop_lsb(horses); + if (attacks_bb(sideToMove, attackerType, s, pieces()) & attackerSq) + attacks ^= s; + } + } + else + attacks &= ~pieces(sideToMove, attackerType) | pins; + // Attacks against potentially unprotected pieces + while (attacks) + { + Square s = pop_lsb(attacks); + Bitboard roots = attackers_to(s, pieces() ^ attackerSq, sideToMove) & ~pins; + if (!roots || (var->flyingGeneral && roots == pieces(sideToMove, KING) && (attacks_bb(sideToMove, ROOK, square(~sideToMove), pieces() ^ attackerSq) & s))) + b |= s; + } + } + }; + + // Direct attacks + Square from = from_sq(st->move); + Square to = to_sq(st->move); + PieceType movedPiece = type_of(piece_on(to)); + if (movedPiece != KING && movedPiece != SOLDIER) + { + Bitboard directAttacks = attacks_from(~sideToMove, movedPiece, to) & pieces(sideToMove); + // Only new attacks count. This avoids expensive comparison of previous and new attacks. + if (movedPiece == ROOK || movedPiece == CANNON) + directAttacks &= ~line_bb(from, to); + addChased(to, movedPiece, directAttacks); + } + + // Discovered attacks + Bitboard discoveryCandidates = (PseudoAttacks[WHITE][WAZIR][from] & pieces(~sideToMove, HORSE)) + | (PseudoAttacks[WHITE][FERS][from] & pieces(~sideToMove, ELEPHANT)) + | (PseudoAttacks[WHITE][ROOK][from] & pieces(~sideToMove, CANNON, ROOK)) + | (PseudoAttacks[WHITE][ROOK][to] & pieces(~sideToMove, CANNON)); + while (discoveryCandidates) + { + Square s = pop_lsb(discoveryCandidates); + PieceType discoveryPiece = type_of(piece_on(s)); + Bitboard discoveries = pieces(sideToMove) + & attacks_bb(~sideToMove, discoveryPiece, s, pieces()) + & ~attacks_bb(~sideToMove, discoveryPiece, s, (captured_piece() ? pieces() : pieces() ^ to) ^ from); + addChased(s, discoveryPiece, discoveries); + } + + // Changes in real roots and discovered checks + if (st->pliesFromNull > 0) + { + // Fake roots + Bitboard newPins = st->blockersForKing[sideToMove] & ~st->previous->blockersForKing[sideToMove] & pieces(sideToMove); + while (newPins) + { + Square s = pop_lsb(newPins); + PieceType pinnedPiece = type_of(piece_on(s)); + Bitboard fakeRooted = pieces(sideToMove) + & ~(pieces(sideToMove, KING, SOLDIER) ^ promoted_soldiers(sideToMove)) + & attacks_bb(sideToMove, pinnedPiece, s, pieces()); + while (fakeRooted) + { + Square s2 = pop_lsb(fakeRooted); + if (attackers_to(s2, ~sideToMove) & ~blockers_for_king(~sideToMove)) + b |= s2; + } + } + // Discovered checks + Bitboard newDiscoverers = st->blockersForKing[sideToMove] & ~st->previous->blockersForKing[sideToMove] & pieces(~sideToMove); + while (newDiscoverers) + { + Square s = pop_lsb(newDiscoverers); + PieceType discoveryPiece = type_of(piece_on(s)); + Bitboard discoveryAttacks = attacks_from(~sideToMove, discoveryPiece, s) & pieces(sideToMove); + // Include all captures except where the king can pseudo-legally recapture + b |= discoveryAttacks & ~attacks_from(sideToMove, KING, square(sideToMove)); + // Include captures where king can not legally recapture + discoveryAttacks &= attacks_from(sideToMove, KING, square(sideToMove)); + while (discoveryAttacks) + { + Square s2 = pop_lsb(discoveryAttacks); + if (attackers_to(s2, pieces() ^ s ^ square(sideToMove), ~sideToMove) & ~square_bb(s)) + b |= s2; + } + } + } + + return b; +} + +// Position::has_repeated() tests whether there has been at least one repetition +// of positions since the last capture or pawn move. + +bool Position::has_repeated() const { + + StateInfo* stc = st; + int end = captures_to_hand() ? st->pliesFromNull : std::min(st->rule50, st->pliesFromNull); + while (end-- >= 4) + { + if (stc->repetition) + return true; + + stc = stc->previous; + } + return false; +} + + +/// Position::has_game_cycle() tests if the position has a move which draws by repetition, +/// or an earlier position has a move that directly reaches the current position. + +bool Position::has_game_cycle(int ply) const { + + int j; + + int end = captures_to_hand() ? st->pliesFromNull : std::min(st->rule50, st->pliesFromNull); + + if (end < 3 || var->nFoldValue != VALUE_DRAW || var->perpetualCheckIllegal || var->materialCounting || var->moveRepetitionIllegal || walling_rule() == DUCK) + return false; + + Key originalKey = st->key; + StateInfo* stp = st->previous; + + for (int i = 3; i <= end; i += 2) + { + stp = stp->previous->previous; + + Key moveKey = originalKey ^ stp->key; + if ( (j = H1(moveKey), cuckoo[j] == moveKey) + || (j = H2(moveKey), cuckoo[j] == moveKey)) + { + Move move = cuckooMove[j]; + Square s1 = from_sq(move); + Square s2 = to_sq(move); + + if (!((between_bb(s1, s2) ^ s2) & pieces())) + { + if (ply > i) + return true; + + // For nodes before or at the root, check that the move is a + // repetition rather than a move to the current position. + // In the cuckoo table, both moves Rc1c5 and Rc5c1 are stored in + // the same location, so we have to select which square to check. + if (color_of(piece_on(empty(s1) ? s2 : s1)) != side_to_move()) + continue; + + // For repetitions before or at the root, require one more + if (stp->repetition) + return true; + } + } + } + return false; +} + + +/// Position::count_limit() returns the counting limit in full moves. + +int Position::count_limit(Color sideToCount) const { + + assert(counting_rule()); + + switch (counting_rule()) + { + case MAKRUK_COUNTING: + // No counting for side to move + if (count() || count(~sideToCount) == 1) + return 0; + // Board's honor rule + if (count(sideToCount) > 1) + return 64; + // Pieces' honor rule + if (count(~sideToCount) > 1) + return 8; + if (count(~sideToCount) == 1) + return 16; + if (count(~sideToCount) > 1) + return 22; + if (count(~sideToCount) > 1) + return 32; + if (count(~sideToCount) == 1) + return 44; + + return 64; + + case CAMBODIAN_COUNTING: + // No counting for side to move + if (count(sideToCount) > 3 || count(~sideToCount) == 1) + return 0; + // Board's honor rule + if (count(sideToCount) > 1) + return 63; + // Pieces' honor rule + if (count()) + return 0; + if (count(~sideToCount) > 1) + return 7; + if (count(~sideToCount) == 1) + return 15; + if (count(~sideToCount) > 1) + return 21; + if (count(~sideToCount) > 1) + return 31; + if (count(~sideToCount) == 1) + return 43; + + return 63; + + case ASEAN_COUNTING: + if (count() || count(sideToCount) > 1) + return 0; + if (count(~sideToCount)) + return 16; + if (count(~sideToCount)) + return 44; + if (count(~sideToCount)) + return 64; + + return 0; + + default: + assert(false); + return 0; + } + +} + + +/// Position::flip() flips position with the white and black sides reversed. This +/// is only useful for debugging e.g. for finding evaluation symmetry bugs. + +void Position::flip() { + + string f, token; + std::stringstream ss(fen()); + + for (Rank r = max_rank(); r >= RANK_1; --r) // Piece placement + { + std::getline(ss, token, r > RANK_1 ? '/' : ' '); + size_t bracketPos = token.find('['); + if (bracketPos != string::npos) + { + f.insert(0, token.substr(0, bracketPos) + (f.empty() ? "" : "/")); + f += token.substr(bracketPos); + } + else + f.insert(0, token + (f.empty() ? "" : "/")); + } + f += " "; + + ss >> token; // Active color + f += (token == "w" ? "B " : "W "); // Will be lowercased later + + ss >> token; // Castling availability + f += token + " "; + + std::transform(f.begin(), f.end(), f.begin(), + [](char c) { return char(islower(c) ? toupper(c) : tolower(c)); }); + + ss >> token; // En passant square + f += (token == "-" ? token : token.replace(1, 1, token[1] == '3' ? "6" : "3")); + + std::getline(ss, token); // Half and full moves + f += token; + + set(variant(), f, is_chess960(), st, this_thread()); + + assert(pos_is_ok()); +} + + +/// Position::pos_is_ok() performs some consistency checks for the +/// position object and raises an asserts if something wrong is detected. +/// This is meant to be helpful when debugging. + +bool Position::pos_is_ok() const { + + constexpr bool Fast = true; // Quick (default) or full check? + + if ( (sideToMove != WHITE && sideToMove != BLACK) + || (count(WHITE) && piece_on(square(WHITE)) != make_piece(WHITE, KING)) + || (count(BLACK) && piece_on(square(BLACK)) != make_piece(BLACK, KING)) + || (ep_squares() & ~(var->enPassantRegion[WHITE] | var->enPassantRegion[BLACK]))) + assert(0 && "pos_is_ok: Default"); + + if (Fast) + return true; + + if ( pieceCount[make_piece(~sideToMove, KING)] + && (attackers_to(square(~sideToMove)) & pieces(sideToMove))) + assert(0 && "pos_is_ok: Kings"); + + if ( pieceCount[make_piece(WHITE, PAWN)] > 64 + || pieceCount[make_piece(BLACK, PAWN)] > 64) + assert(0 && "pos_is_ok: Pawns"); + + if ( (pieces(WHITE) & pieces(BLACK)) + || (pieces(WHITE) | pieces(BLACK)) != pieces() + || popcount(pieces(WHITE)) > 64 + || popcount(pieces(BLACK)) > 64) + assert(0 && "pos_is_ok: Bitboards"); + + for (PieceType p1 = PAWN; p1 <= KING; ++p1) + for (PieceType p2 = PAWN; p2 <= KING; ++p2) + if (p1 != p2 && (pieces(p1) & pieces(p2))) + assert(0 && "pos_is_ok: Bitboards"); + + StateInfo si = *st; + ASSERT_ALIGNED(&si, Eval::NNUE::CacheLineSize); + + set_state(&si); + if (std::memcmp(&si, st, sizeof(StateInfo))) + assert(0 && "pos_is_ok: State"); + + for (Color c : {WHITE, BLACK}) + for (PieceType pt = PAWN; pt <= KING; ++pt) + { + Piece pc = make_piece(c, pt); + if ( pieceCount[pc] != popcount(pieces(c, pt)) + || pieceCount[pc] != std::count(board, board + SQUARE_NB, pc)) + assert(0 && "pos_is_ok: Pieces"); + } + + for (Color c : { WHITE, BLACK }) + for (CastlingRights cr : {c & KING_SIDE, c & QUEEN_SIDE}) + { + if (!can_castle(cr)) + continue; + + if ( !(castling_rook_pieces(c) & type_of(piece_on(castlingRookSquare[cr]))) + || castlingRightsMask[castlingRookSquare[cr]] != cr + || (count(c) && (castlingRightsMask[square(c)] & cr) != cr)) + assert(0 && "pos_is_ok: Castling"); + } + + return true; +} + +} // namespace Stockfish diff --git a/Fairy-Stockfish-master/src/position.h b/Fairy-Stockfish-master/src/position.h new file mode 100644 index 0000000000000000000000000000000000000000..685c07992a0e8f6f1538d03fb72423812c4fdfff --- /dev/null +++ b/Fairy-Stockfish-master/src/position.h @@ -0,0 +1,1650 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef POSITION_H_INCLUDED +#define POSITION_H_INCLUDED + +#include +#include +#include // For std::unique_ptr +#include +#include + +#include "bitboard.h" +#include "evaluate.h" +#include "psqt.h" +#include "types.h" +#include "variant.h" +#include "movegen.h" + +#include "nnue/nnue_accumulator.h" + +namespace Stockfish { + +/// 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 +/// board (by calling Position::do_move), a StateInfo object must be passed. + +struct StateInfo { + + // Copied when making a move + Key pawnKey; + Key materialKey; + Value nonPawnMaterial[COLOR_NB]; + int castlingRights; + int rule50; + int pliesFromNull; + int countingPly; + int countingLimit; + CheckCount checksRemaining[COLOR_NB]; + Bitboard epSquares; + Square castlingKingSquare[COLOR_NB]; + Bitboard wallSquares; + Bitboard gatesBB[COLOR_NB]; + + // Not copied when making a move (will be recomputed anyhow) + Key key; + Bitboard checkersBB; + Piece unpromotedCapturedPiece; + Piece unpromotedBycatch[SQUARE_NB]; + Bitboard promotedBycatch; + Bitboard demotedBycatch; + StateInfo* previous; + Bitboard blockersForKing[COLOR_NB]; + Bitboard pinners[COLOR_NB]; + Bitboard checkSquares[PIECE_TYPE_NB]; + Piece capturedPiece; + Square captureSquare; // when != to_sq, e.g., en passant + Piece promotionPawn; + Bitboard nonSlidingRiders; + Bitboard flippedPieces; + Bitboard pseudoRoyalCandidates; + Bitboard pseudoRoyals; + OptBool legalCapture; + bool capturedpromoted; + bool shak; + bool bikjang; + Bitboard chased; + bool pass; + Move move; + int repetition; + + // Used by NNUE + Eval::NNUE::Accumulator accumulator; + DirtyPiece dirtyPiece; +}; + + +/// A list to keep track of the position states along the setup moves (from the +/// start position to the position just before the search starts). Needed by +/// 'draw by repetition' detection. Use a std::deque because pointers to +/// elements are not invalidated upon list resizing. +typedef std::unique_ptr> StateListPtr; + + +/// Position class stores information regarding the board representation as +/// pieces, side to move, hash keys, castling info, etc. Important methods are +/// do_move() and undo_move(), used by the search to update node info when +/// traversing the search tree. +class Thread; + +class Position { +public: + static void init(); + + Position() = default; + Position(const Position&) = delete; + Position& operator=(const Position&) = delete; + + // FEN string input/output + Position& set(const Variant* v, const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th, bool sfen = false); + Position& set(const std::string& code, Color c, StateInfo* si); + std::string fen(bool sfen = false, bool showPromoted = false, int countStarted = 0, std::string holdings = "-", Bitboard fogArea = 0) const; + + // Variant rule properties + const Variant* variant() const; + Rank max_rank() const; + File max_file() const; + int ranks() const; + int files() const; + bool two_boards() const; + Bitboard board_bb() const; + Bitboard board_bb(Color c, PieceType pt) const; + PieceSet piece_types() const; + const std::string& piece_to_char() const; + const std::string& piece_to_char_synonyms() const; + Bitboard promotion_zone(Color c) const; + Square promotion_square(Color c, Square s) const; + PieceType main_promotion_pawn_type(Color c) const; + PieceSet promotion_piece_types(Color c) const; + bool sittuyin_promotion() const; + int promotion_limit(PieceType pt) const; + PieceType promoted_piece_type(PieceType pt) const; + bool piece_promotion_on_capture() const; + bool mandatory_pawn_promotion() const; + bool mandatory_piece_promotion() const; + bool piece_demotion() const; + bool blast_on_capture() const; + PieceSet blast_immune_types() const; + PieceSet mutually_immune_types() const; + EndgameEval endgame_eval() const; + Bitboard double_step_region(Color c) const; + Bitboard triple_step_region(Color c) const; + bool castling_enabled() const; + bool castling_dropped_piece() const; + File castling_kingside_file() const; + File castling_queenside_file() const; + Rank castling_rank(Color c) const; + File castling_king_file() const; + PieceType castling_king_piece(Color c) const; + PieceSet castling_rook_pieces(Color c) const; + PieceType king_type() const; + PieceType nnue_king() const; + Square nnue_king_square(Color c) const; + bool nnue_use_pockets() const; + bool nnue_applicable() const; + int nnue_piece_square_index(Color perspective, Piece pc) const; + int nnue_piece_hand_index(Color perspective, Piece pc) const; + int nnue_king_square_index(Square ksq) const; + bool free_drops() const; + bool fast_attacks() const; + bool fast_attacks2() const; + bool checking_permitted() const; + bool drop_checks() const; + bool must_capture() const; + bool has_capture() const; + bool must_drop() const; + bool piece_drops() const; + bool drop_loop() const; + bool captures_to_hand() const; + bool first_rank_pawn_drops() const; + bool can_drop(Color c, PieceType pt) const; + EnclosingRule enclosing_drop() const; + Bitboard drop_region(Color c) const; + Bitboard drop_region(Color c, PieceType pt) const; + bool sittuyin_rook_drop() const; + bool drop_opposite_colored_bishop() const; + bool drop_promoted() const; + PieceType drop_no_doubled() const; + PieceSet promotion_pawn_types(Color c) const; + PieceSet en_passant_types(Color c) const; + bool immobility_illegal() const; + bool gating() const; + bool walling() const; + WallingRule walling_rule() const; + bool wall_or_move() const; + Bitboard walling_region(Color c) const; + bool seirawan_gating() const; + bool cambodian_moves() const; + Bitboard diagonal_lines() const; + bool pass(Color c) const; + bool pass_on_stalemate(Color c) const; + Bitboard promoted_soldiers(Color c) const; + bool makpong() const; + EnclosingRule flip_enclosed_pieces() const; + // winning conditions + int n_move_rule() const; + int n_fold_rule() const; + Value stalemate_value(int ply = 0) const; + Value checkmate_value(int ply = 0) const; + Value extinction_value(int ply = 0) const; + bool extinction_claim() const; + PieceSet extinction_piece_types() const; + bool extinction_single_piece() const; + int extinction_piece_count() const; + int extinction_opponent_piece_count() const; + bool extinction_pseudo_royal() const; + PieceType flag_piece(Color c) const; + Bitboard flag_region(Color c) const; + bool flag_move() const; + bool flag_reached(Color c) const; + bool check_counting() const; + int connect_n() const; + PieceSet connect_piece_types() const; + bool connect_horizontal() const; + bool connect_vertical() const; + bool connect_diagonal() const; + const std::vector& getConnectDirections() const; + int connect_nxn() const; + int collinear_n() const; + + CheckCount checks_remaining(Color c) const; + MaterialCounting material_counting() const; + CountingRule counting_rule() const; + + // Variant-specific properties + int count_in_hand(PieceType pt) const; + int count_in_hand(Color c, PieceType pt) const; + int count_with_hand(Color c, PieceType pt) const; + bool bikjang() const; + bool allow_virtual_drop(Color c, PieceType pt) const; + + // Position representation + Bitboard pieces(PieceType pt = ALL_PIECES) const; + Bitboard pieces(PieceType pt1, PieceType pt2) const; + Bitboard pieces(Color c) const; + Bitboard pieces(Color c, PieceType pt) const; + Bitboard pieces(Color c, PieceType pt1, PieceType pt2) const; + Bitboard pieces(Color c, PieceType pt1, PieceType pt2, PieceType pt3) const; + Bitboard major_pieces(Color c) const; + Bitboard non_sliding_riders() const; + Piece piece_on(Square s) const; + Piece unpromoted_piece_on(Square s) const; + Bitboard ep_squares() const; + Square castling_king_square(Color c) const; + Bitboard gates(Color c) const; + bool empty(Square s) const; + int count(Color c, PieceType pt) const; + template int count(Color c) const; + template int count() const; + template Square square(Color c) const; + Square square(Color c, PieceType pt) const; + bool is_on_semiopen_file(Color c, Square s) const; + + // Castling + CastlingRights castling_rights(Color c) const; + bool can_castle(CastlingRights cr) const; + bool castling_impeded(CastlingRights cr) const; + Square castling_rook_square(CastlingRights cr) const; + + // Checking + Bitboard checkers() const; + Bitboard blockers_for_king(Color c) const; + Bitboard check_squares(PieceType pt) const; + Bitboard pinners(Color c) const; + Bitboard checked_pseudo_royals(Color c) const; + + // Attacks to/from a given square + Bitboard attackers_to(Square s) const; + Bitboard attackers_to(Square s, Color c) const; + Bitboard attackers_to(Square s, Bitboard occupied) const; + Bitboard attackers_to(Square s, Bitboard occupied, Color c) const; + Bitboard attackers_to(Square s, Bitboard occupied, Color c, Bitboard janggiCannons) const; + Bitboard attacks_from(Color c, PieceType pt, Square s) const; + Bitboard moves_from(Color c, PieceType pt, Square s) const; + Bitboard slider_blockers(Bitboard sliders, Square s, Bitboard& pinners, Color c) const; + + // Properties of moves + bool legal(Move m) const; + bool pseudo_legal(const Move m) const; + bool virtual_drop(Move m) const; + bool capture(Move m) const; + bool capture_or_promotion(Move m) const; + Square capture_square(Square to) const; + bool gives_check(Move m) const; + Piece moved_piece(Move m) const; + Piece captured_piece() const; + const std::string piece_to_partner() const; + + // Piece specific + bool pawn_passed(Color c, Square s) const; + bool opposite_bishops() const; + bool is_promoted(Square s) const; + int pawns_on_same_color_squares(Color c, Square s) const; + + // Doing and undoing moves + void do_move(Move m, StateInfo& newSt); + void do_move(Move m, StateInfo& newSt, bool givesCheck); + void undo_move(Move m); + void do_null_move(StateInfo& newSt); + void undo_null_move(); + + // Static Exchange Evaluation + Value blast_see(Move m) const; + bool see_ge(Move m, Value threshold = VALUE_ZERO) const; + + // Accessing hash keys + Key key() const; + Key key_after(Move m) const; + Key material_key(EndgameEval e = EG_EVAL_CHESS) const; + Key pawn_key() const; + + // Other properties of the position + Color side_to_move() const; + int game_ply() const; + bool is_chess960() const; + Thread* this_thread() const; + bool is_immediate_game_end() const; + bool is_immediate_game_end(Value& result, int ply = 0) const; + bool is_optional_game_end() const; + bool is_optional_game_end(Value& result, int ply = 0, int countStarted = 0) const; + bool is_game_end(Value& result, int ply = 0) const; + Value material_counting_result() const; + bool is_draw(int ply) const; + bool has_game_cycle(int ply) const; + bool has_repeated() const; + Bitboard chased() const; + int count_limit(Color sideToCount) const; + int board_honor_counting_ply(int countStarted) const; + bool board_honor_counting_shorter(int countStarted) const; + int counting_limit(int countStarted) const; + int counting_ply(int countStarted) const; + int rule50_count() const; + Score psq_score() const; + Value non_pawn_material(Color c) const; + Value non_pawn_material() const; + Bitboard fog_area() const; + + // Position consistency check, for debugging + bool pos_is_ok() const; + void flip(); + + // Used by NNUE + StateInfo* state() const; + + void put_piece(Piece pc, Square s, bool isPromoted = false, Piece unpromotedPc = NO_PIECE); + void remove_piece(Square s); + +private: + // Initialization helpers (used while setting up a position) + void set_castling_right(Color c, Square rfrom); + void set_state(StateInfo* si) const; + void set_check_info(StateInfo* si) const; + + // Other helpers + void move_piece(Square from, Square to); + template + void do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto); + + // Data members + Piece board[SQUARE_NB]; + Piece unpromotedBoard[SQUARE_NB]; + Bitboard byTypeBB[PIECE_TYPE_NB]; + Bitboard byColorBB[COLOR_NB]; + int pieceCount[PIECE_NB]; + int castlingRightsMask[SQUARE_NB]; + Square castlingRookSquare[CASTLING_RIGHT_NB]; + Bitboard castlingPath[CASTLING_RIGHT_NB]; + Thread* thisThread; + StateInfo* st; + int gamePly; + Color sideToMove; + Score psq; + + // variant-specific + const Variant* var; + bool tsumeMode; + bool chess960; + int pieceCountInHand[COLOR_NB][PIECE_TYPE_NB]; + int virtualPieces; + Bitboard promotedPieces; + void add_to_hand(Piece pc); + void remove_from_hand(Piece pc); + void drop_piece(Piece pc_hand, Piece pc_drop, Square s); + void undrop_piece(Piece pc_hand, Square s); + Bitboard find_drop_region(Direction dir, Square s, Bitboard occupied) const; +}; + +extern std::ostream& operator<<(std::ostream& os, const Position& pos); + +inline const Variant* Position::variant() const { + assert(var != nullptr); + return var; +} + +inline Rank Position::max_rank() const { + assert(var != nullptr); + return var->maxRank; +} + +inline File Position::max_file() const { + assert(var != nullptr); + return var->maxFile; +} + +inline int Position::ranks() const { + assert(var != nullptr); + return var->maxRank + 1; +} + +inline int Position::files() const { + assert(var != nullptr); + return var->maxFile + 1; +} + +inline bool Position::two_boards() const { + assert(var != nullptr); + return var->twoBoards; +} + +inline Bitboard Position::board_bb() const { + assert(var != nullptr); + return board_size_bb(var->maxFile, var->maxRank) & ~st->wallSquares; +} + +inline Bitboard Position::board_bb(Color c, PieceType pt) const { + assert(var != nullptr); + return var->mobilityRegion[c][pt] ? var->mobilityRegion[c][pt] & board_bb() : board_bb(); +} + +inline PieceSet Position::piece_types() const { + assert(var != nullptr); + return var->pieceTypes; +} + +inline const std::string& Position::piece_to_char() const { + assert(var != nullptr); + return var->pieceToChar; +} + +inline const std::string& Position::piece_to_char_synonyms() const { + assert(var != nullptr); + return var->pieceToCharSynonyms; +} + +inline Bitboard Position::promotion_zone(Color c) const { + assert(var != nullptr); + return var->promotionRegion[c]; +} + +inline Square Position::promotion_square(Color c, Square s) const { + assert(var != nullptr); + Bitboard b = promotion_zone(c) & forward_file_bb(c, s) & board_bb(); + return !b ? SQ_NONE : c == WHITE ? lsb(b) : msb(b); +} + +inline PieceType Position::main_promotion_pawn_type(Color c) const { + assert(var != nullptr); + return var->mainPromotionPawnType[c]; +} + +inline PieceSet Position::promotion_piece_types(Color c) const { + assert(var != nullptr); + return var->promotionPieceTypes[c]; +} + +inline bool Position::sittuyin_promotion() const { + assert(var != nullptr); + return var->sittuyinPromotion; +} + +inline int Position::promotion_limit(PieceType pt) const { + assert(var != nullptr); + return var->promotionLimit[pt]; +} + +inline PieceType Position::promoted_piece_type(PieceType pt) const { + assert(var != nullptr); + return var->promotedPieceType[pt]; +} + +inline bool Position::piece_promotion_on_capture() const { + assert(var != nullptr); + return var->piecePromotionOnCapture; +} + +inline bool Position::mandatory_pawn_promotion() const { + assert(var != nullptr); + return var->mandatoryPawnPromotion; +} + +inline bool Position::mandatory_piece_promotion() const { + assert(var != nullptr); + return var->mandatoryPiecePromotion; +} + +inline bool Position::piece_demotion() const { + assert(var != nullptr); + return var->pieceDemotion; +} + +inline bool Position::blast_on_capture() const { + assert(var != nullptr); + return var->blastOnCapture; +} + +inline PieceSet Position::blast_immune_types() const { + assert(var != nullptr); + return var->blastImmuneTypes; +} + +inline PieceSet Position::mutually_immune_types() const { + assert(var != nullptr); + return var->mutuallyImmuneTypes; +} + +inline EndgameEval Position::endgame_eval() const { + assert(var != nullptr); + return !count_in_hand(ALL_PIECES) && (var->endgameEval != EG_EVAL_CHESS || count() == 2) ? var->endgameEval : NO_EG_EVAL; +} + +inline Bitboard Position::double_step_region(Color c) const { + assert(var != nullptr); + return var->doubleStepRegion[c]; +} + +inline Bitboard Position::triple_step_region(Color c) const { + assert(var != nullptr); + return var->tripleStepRegion[c]; +} + +inline bool Position::castling_enabled() const { + assert(var != nullptr); + return var->castling; +} + +inline bool Position::castling_dropped_piece() const { + assert(var != nullptr); + return var->castlingDroppedPiece; +} + +inline File Position::castling_kingside_file() const { + assert(var != nullptr); + return var->castlingKingsideFile; +} + +inline File Position::castling_queenside_file() const { + assert(var != nullptr); + return var->castlingQueensideFile; +} + +inline Rank Position::castling_rank(Color c) const { + assert(var != nullptr); + return relative_rank(c, var->castlingRank, max_rank()); +} + +inline File Position::castling_king_file() const { + assert(var != nullptr); + return var->castlingKingFile; +} + +inline PieceType Position::castling_king_piece(Color c) const { + assert(var != nullptr); + return var->castlingKingPiece[c]; +} + +inline PieceSet Position::castling_rook_pieces(Color c) const { + assert(var != nullptr); + return var->castlingRookPieces[c]; +} + +inline PieceType Position::king_type() const { + assert(var != nullptr); + return var->kingType; +} + +inline PieceType Position::nnue_king() const { + assert(var != nullptr); + return var->nnueKing; +} + +inline Square Position::nnue_king_square(Color c) const { + return nnue_king() ? square(c, nnue_king()) : SQ_NONE; +} + +inline bool Position::nnue_use_pockets() const { + assert(var != nullptr); + return var->nnueUsePockets; +} + +inline bool Position::nnue_applicable() const { + // Do not use NNUE during setup phases (placement, sittuyin) + return (!count_in_hand(ALL_PIECES) || nnue_use_pockets() || !must_drop()) + && !virtualPieces + && (!nnue_king() || (count(WHITE, nnue_king()) == 1 && count(BLACK, nnue_king()) == 1)); +} + +inline int Position::nnue_piece_square_index(Color perspective, Piece pc) const { + assert(var != nullptr); + return var->pieceSquareIndex[perspective][pc]; +} + +inline int Position::nnue_piece_hand_index(Color perspective, Piece pc) const { + assert(var != nullptr); + return var->pieceHandIndex[perspective][pc]; +} + +inline int Position::nnue_king_square_index(Square ksq) const { + assert(var != nullptr); + return var->kingSquareIndex[ksq]; +} + +inline bool Position::checking_permitted() const { + assert(var != nullptr); + return var->checking; +} + +inline bool Position::free_drops() const { + assert(var != nullptr); + return var->freeDrops; +} + +inline bool Position::fast_attacks() const { + assert(var != nullptr); + return var->fastAttacks; +} + +inline bool Position::fast_attacks2() const { + assert(var != nullptr); + return var->fastAttacks2; +} + +inline bool Position::drop_checks() const { + assert(var != nullptr); + return var->dropChecks; +} + +inline bool Position::must_capture() const { + assert(var != nullptr); + return var->mustCapture; +} + +inline bool Position::has_capture() const { + // Check for cached value + if (st->legalCapture != NO_VALUE) + return st->legalCapture == VALUE_TRUE; + if (checkers()) + { + for (const auto& mevasion : MoveList(*this)) + if (capture(mevasion) && legal(mevasion)) + { + st->legalCapture = VALUE_TRUE; + return true; + } + } + else + { + for (const auto& mcap : MoveList(*this)) + if (capture(mcap) && legal(mcap)) + { + st->legalCapture = VALUE_TRUE; + return true; + } + } + st->legalCapture = VALUE_FALSE; + return false; +} + +inline bool Position::must_drop() const { + assert(var != nullptr); + return var->mustDrop; +} + +inline bool Position::piece_drops() const { + assert(var != nullptr); + return var->pieceDrops; +} + +inline bool Position::drop_loop() const { + assert(var != nullptr); + return var->dropLoop; +} + +inline bool Position::captures_to_hand() const { + assert(var != nullptr); + return var->capturesToHand; +} + +inline bool Position::first_rank_pawn_drops() const { + assert(var != nullptr); + return var->firstRankPawnDrops; +} + +inline EnclosingRule Position::enclosing_drop() const { + assert(var != nullptr); + return var->enclosingDrop; +} + +inline Bitboard Position::drop_region(Color c) const { + assert(var != nullptr); + return var->dropRegion[c]; +} + +inline Bitboard Position::drop_region(Color c, PieceType pt) const { + Bitboard b = drop_region(c) & board_bb(c, pt); + + // Pawns on back ranks + if (pt == PAWN) + { + if (!var->promotionZonePawnDrops) + b &= ~promotion_zone(c); + if (!first_rank_pawn_drops()) + b &= ~rank_bb(relative_rank(c, RANK_1, max_rank())); + } + // Doubled shogi pawns + if (pt == drop_no_doubled()) + for (File f = FILE_A; f <= max_file(); ++f) + if (popcount(file_bb(f) & pieces(c, pt)) >= var->dropNoDoubledCount) + b &= ~file_bb(f); + // Sittuyin rook drops + if (pt == ROOK && sittuyin_rook_drop()) + b &= rank_bb(relative_rank(c, RANK_1, max_rank())); + + if (enclosing_drop()) + { + // Reversi start + if (var->enclosingDropStart & ~pieces()) + b &= var->enclosingDropStart; + else + { + // Filter out squares where the drop does not enclose at least one opponent's piece + if (enclosing_drop() == REVERSI) + { + Bitboard theirs = pieces(~c); + b &= shift(theirs) | shift(theirs) + | shift(theirs) | shift(theirs) + | shift(theirs) | shift(theirs) + | shift(theirs) | shift(theirs); + Bitboard b2 = b; + while (b2) + { + Square s = pop_lsb(b2); + if (!(attacks_bb(c, QUEEN, s, board_bb() & ~pieces(~c)) & ~PseudoAttacks[c][KING][s] & pieces(c))) + b ^= s; + } + } + else if (enclosing_drop() == SNORT) + { + Bitboard theirs = pieces(~c); + b &= ~(shift(theirs) | shift(theirs) + | shift(theirs) | shift(theirs)); + } + else if (enclosing_drop() == ANYSIDE) + { + Bitboard occupied = pieces(); + b = 0ULL; + Bitboard candidates = (shift(occupied) | file_bb(max_file())) & ~occupied; + + for (Rank r = RANK_1; r <= max_rank(); ++r) { + if (!(occupied & make_square(FILE_A, r))) { + b |= lsb(candidates & rank_bb(r)); + } + } + candidates = (shift(occupied) | rank_bb(max_rank())) & ~occupied; + for (File f = FILE_A; f <= max_file(); ++f) { + if (!(occupied & make_square(f, RANK_1))) { + b |= lsb(candidates & file_bb(f)); + } + } + candidates = (shift(occupied) | rank_bb(RANK_1)) & ~occupied; + for (File f = FILE_A; f <= max_file(); ++f) { + if (!(occupied & make_square(f, max_rank()))) { + b |= lsb(candidates & file_bb(f)); + } + } + candidates = (shift(occupied) | file_bb(FILE_A)) & ~occupied; + for (Rank r = RANK_1; r <= max_rank(); ++r) { + if (!(occupied & make_square(max_file(), r))) { + b |= lsb(candidates & rank_bb(r)); + } + } + } + else if (enclosing_drop() == TOP) + { + b &= shift(pieces()) | Rank1BB; + } + else + { + assert(enclosing_drop() == ATAXX); + Bitboard ours = pieces(c); + b &= shift(ours) | shift(ours) + | shift(ours) | shift(ours) + | shift(ours) | shift(ours) + | shift(ours) | shift(ours); + } + } + } + + return b; +} + +inline bool Position::sittuyin_rook_drop() const { + assert(var != nullptr); + return var->sittuyinRookDrop; +} + +inline bool Position::drop_opposite_colored_bishop() const { + assert(var != nullptr); + return var->dropOppositeColoredBishop; +} + +inline bool Position::drop_promoted() const { + assert(var != nullptr); + return var->dropPromoted; +} + +inline PieceType Position::drop_no_doubled() const { + assert(var != nullptr); + return var->dropNoDoubled; +} + +inline PieceSet Position::promotion_pawn_types(Color c) const { + assert(var != nullptr); + return var->promotionPawnTypes[c]; +} + +inline PieceSet Position::en_passant_types(Color c) const { + assert(var != nullptr); + return var->enPassantTypes[c]; +} + +inline bool Position::immobility_illegal() const { + assert(var != nullptr); + return var->immobilityIllegal; +} + +inline bool Position::gating() const { + assert(var != nullptr); + return var->gating; +} + +inline bool Position::walling() const { + assert(var != nullptr); + return var->wallingRule != NO_WALLING; +} + +inline WallingRule Position::walling_rule() const { + assert(var != nullptr); + return var->wallingRule; +} + +inline bool Position::wall_or_move() const { + assert(var != nullptr); + return var->wallOrMove; +} + +inline Bitboard Position::walling_region(Color c) const { + assert(var != nullptr); + return var->wallingRegion[c]; +} + +inline bool Position::seirawan_gating() const { + assert(var != nullptr); + return var->seirawanGating; +} + +inline bool Position::cambodian_moves() const { + assert(var != nullptr); + return var->cambodianMoves; +} + +inline Bitboard Position::diagonal_lines() const { + assert(var != nullptr); + return var->diagonalLines; +} + +inline bool Position::pass(Color c) const { + assert(var != nullptr); + return var->pass[c] || var->passOnStalemate[c]; +} + +inline bool Position::pass_on_stalemate(Color c) const { + assert(var != nullptr); + return var->passOnStalemate[c]; +} + +inline Bitboard Position::promoted_soldiers(Color c) const { + assert(var != nullptr); + return pieces(c, SOLDIER) & zone_bb(c, var->soldierPromotionRank, max_rank()); +} + +inline bool Position::makpong() const { + assert(var != nullptr); + return var->makpongRule; +} + +inline int Position::n_move_rule() const { + assert(var != nullptr); + return var->nMoveRule; +} + +inline int Position::n_fold_rule() const { + assert(var != nullptr); + return var->nFoldRule; +} + +inline EnclosingRule Position::flip_enclosed_pieces() const { + assert(var != nullptr); + return var->flipEnclosedPieces; +} + +inline Value Position::stalemate_value(int ply) const { + assert(var != nullptr); + // Check for checkmate of pseudo-royal pieces + if (var->extinctionPseudoRoyal) + { + Bitboard pseudoRoyals = st->pseudoRoyals & pieces(sideToMove); + Bitboard pseudoRoyalsTheirs = st->pseudoRoyals & pieces(~sideToMove); + while (pseudoRoyals) + { + Square sr = pop_lsb(pseudoRoyals); + if ( !(blast_on_capture() && (pseudoRoyalsTheirs & attacks_bb(sr))) + && attackers_to(sr, ~sideToMove)) + return convert_mate_value(var->checkmateValue, ply); + } + // Look for duple check + if (var->dupleCheck) + { + Bitboard pseudoRoyalCandidates = st->pseudoRoyalCandidates & pieces(sideToMove); + bool allCheck = bool(pseudoRoyalCandidates); + while (allCheck && pseudoRoyalCandidates) + { + Square sr = pop_lsb(pseudoRoyalCandidates); + // Touching pseudo-royal pieces are immune + if (!( !(blast_on_capture() && (pseudoRoyalsTheirs & attacks_bb(sr))) + && attackers_to(sr, ~sideToMove))) + allCheck = false; + } + if (allCheck) + return convert_mate_value(var->checkmateValue, ply); + } + } + Value result = var->stalemateValue; + // Is piece count used to determine stalemate result? + if (var->stalematePieceCount) + { + int c = count(sideToMove) - count(~sideToMove); + result = c == 0 ? VALUE_DRAW : c < 0 ? var->stalemateValue : -var->stalemateValue; + } + // Apply material counting + if (result == VALUE_DRAW && var->materialCounting) + result = material_counting_result(); + return convert_mate_value(result, ply); +} + +inline Value Position::checkmate_value(int ply) const { + assert(var != nullptr); + // Check for illegal mate by shogi pawn drop + if ( var->shogiPawnDropMateIllegal + && !(checkers() & ~pieces(SHOGI_PAWN)) + && !st->capturedPiece + && st->pliesFromNull > 0 + && (st->materialKey != st->previous->materialKey)) + { + return mate_in(ply); + } + // Check for shatar mate rule + if (var->shatarMateRule) + { + // Mate by knight is illegal + if (!(checkers() & ~pieces(KNIGHT))) + return mate_in(ply); + + StateInfo* stp = st; + while (stp->checkersBB) + { + // Return mate score if there is at least one shak in series of checks + if (stp->shak) + return convert_mate_value(var->checkmateValue, ply); + + if (stp->pliesFromNull < 2) + break; + + stp = stp->previous->previous; + } + // Niol + return VALUE_DRAW; + } + // Checkmate using virtual pieces + if (two_boards() && var->checkmateValue < VALUE_ZERO) + { + Value virtualMaterial = VALUE_ZERO; + for (PieceSet ps = piece_types(); ps;) + { + PieceType pt = pop_lsb(ps); + virtualMaterial += std::max(-count_in_hand(~sideToMove, pt), 0) * PieceValue[MG][pt]; + } + + if (virtualMaterial > 0) + return -VALUE_VIRTUAL_MATE + virtualMaterial / 20 + ply; + } + // Return mate value + return convert_mate_value(var->checkmateValue, ply); +} + +inline Value Position::extinction_value(int ply) const { + assert(var != nullptr); + return convert_mate_value(var->extinctionValue, ply); +} + +inline bool Position::extinction_claim() const { + assert(var != nullptr); + return var->extinctionClaim; +} + +inline PieceSet Position::extinction_piece_types() const { + assert(var != nullptr); + return var->extinctionPieceTypes; +} + +inline bool Position::extinction_single_piece() const { + assert(var != nullptr); + return var->extinctionValue == -VALUE_MATE + && (var->extinctionPieceTypes & ~piece_set(ALL_PIECES)); +} + +inline int Position::extinction_piece_count() const { + assert(var != nullptr); + return var->extinctionPieceCount; +} + +inline int Position::extinction_opponent_piece_count() const { + assert(var != nullptr); + return var->extinctionOpponentPieceCount; +} + +inline bool Position::extinction_pseudo_royal() const { + assert(var != nullptr); + return var->extinctionPseudoRoyal; +} + +inline PieceType Position::flag_piece(Color c) const { + assert(var != nullptr); + return var->flagPiece[c]; +} + +inline Bitboard Position::flag_region(Color c) const { + assert(var != nullptr); + return var->flagRegion[c]; +} + +inline bool Position::flag_move() const { + assert(var != nullptr); + return var->flagMove; +} + +inline bool Position::flag_reached(Color c) const { + assert(var != nullptr); + bool simpleResult = + (flag_region(c) & pieces(c, flag_piece(c))) + && ( popcount(flag_region(c) & pieces(c, flag_piece(c))) >= var->flagPieceCount + || (var->flagPieceBlockedWin && !(flag_region(c) & ~pieces()))); + + // When flagPieceSafe and flagMove are combined, it means that only unsafe pieces cause an extra move + if (simpleResult && var->flagPieceSafe && (!flag_move() || c == ~sideToMove)) + { + Bitboard piecesInFlagZone = flag_region(c) & pieces(c, flag_piece(c)); + int potentialPieces = popcount(piecesInFlagZone); + // If we are exactly at the required piece count, all pieces in the flag zone need to be safe + while (piecesInFlagZone && potentialPieces == var->flagPieceCount) + { + Square sr = pop_lsb(piecesInFlagZone); + Bitboard flagAttackers = attackers_to(sr, ~c); + if (flagAttackers) + return false; + } + } + return simpleResult; +} + +inline bool Position::check_counting() const { + assert(var != nullptr); + return var->checkCounting; +} + +inline int Position::connect_n() const { + assert(var != nullptr); + return var->connectN; +} + +inline PieceSet Position::connect_piece_types() const { + assert(var != nullptr); + return var->connectPieceTypesTrimmed; +} + +inline bool Position::connect_horizontal() const { + assert(var != nullptr); + return var->connectHorizontal; +} +inline bool Position::connect_vertical() const { + assert(var != nullptr); + return var->connectVertical; +} +inline bool Position::connect_diagonal() const { + assert(var != nullptr); + return var->connectDiagonal; +} + +inline const std::vector& Position::getConnectDirections() const { + assert(var != nullptr); + return var->connectDirections; +} + +inline int Position::connect_nxn() const { + assert(var != nullptr); + return var->connectNxN; +} + +inline int Position::collinear_n() const { + assert(var != nullptr); + return var->collinearN; +} + +inline CheckCount Position::checks_remaining(Color c) const { + return st->checksRemaining[c]; +} + +inline MaterialCounting Position::material_counting() const { + assert(var != nullptr); + return var->materialCounting; +} + +inline CountingRule Position::counting_rule() const { + assert(var != nullptr); + return var->countingRule; +} + +inline bool Position::is_immediate_game_end() const { + Value result; + return is_immediate_game_end(result); +} + +inline bool Position::is_optional_game_end() const { + Value result; + return is_optional_game_end(result); +} + +inline bool Position::is_draw(int ply) const { + Value result; + return is_optional_game_end(result, ply); +} + +inline bool Position::is_game_end(Value& result, int ply) const { + return is_immediate_game_end(result, ply) || is_optional_game_end(result, ply); +} + +inline Color Position::side_to_move() const { + return sideToMove; +} + +inline Piece Position::piece_on(Square s) const { + assert(is_ok(s)); + return board[s]; +} + +inline bool Position::empty(Square s) const { + return piece_on(s) == NO_PIECE; +} + +inline Piece Position::unpromoted_piece_on(Square s) const { + return unpromotedBoard[s]; +} + +inline Piece Position::moved_piece(Move m) const { + if (type_of(m) == DROP) + return make_piece(sideToMove, dropped_piece_type(m)); + return piece_on(from_sq(m)); +} + +inline Bitboard Position::pieces(PieceType pt) const { + return byTypeBB[pt]; +} + +inline Bitboard Position::pieces(PieceType pt1, PieceType pt2) const { + return pieces(pt1) | pieces(pt2); +} + +inline Bitboard Position::pieces(Color c) const { + return byColorBB[c]; +} + +inline Bitboard Position::pieces(Color c, PieceType pt) const { + return pieces(c) & pieces(pt); +} + +inline Bitboard Position::pieces(Color c, PieceType pt1, PieceType pt2) const { + return pieces(c) & (pieces(pt1) | pieces(pt2)); +} + +inline Bitboard Position::pieces(Color c, PieceType pt1, PieceType pt2, PieceType pt3) const { + return pieces(c) & (pieces(pt1) | pieces(pt2) | pieces(pt3)); +} + +inline Bitboard Position::major_pieces(Color c) const { + return pieces(c) & (pieces(QUEEN) | pieces(AIWOK) | pieces(ARCHBISHOP) | pieces(CHANCELLOR) | pieces(AMAZON)); +} + +inline Bitboard Position::non_sliding_riders() const { + return st->nonSlidingRiders; +} + +inline int Position::count(Color c, PieceType pt) const { + return pieceCount[make_piece(c, pt)]; +} + +template inline int Position::count(Color c) const { + return pieceCount[make_piece(c, Pt)]; +} + +template inline int Position::count() const { + return count(WHITE) + count(BLACK); +} + +template inline Square Position::square(Color c) const { + assert(count(c) == 1); + return lsb(pieces(c, Pt)); +} + +inline Square Position::square(Color c, PieceType pt) const { + assert(count(c, pt) == 1); + return lsb(pieces(c, pt)); +} + +inline Bitboard Position::ep_squares() const { + return st->epSquares; +} + +inline Square Position::castling_king_square(Color c) const { + return st->castlingKingSquare[c]; +} + +inline Bitboard Position::gates(Color c) const { + assert(var != nullptr); + return st->gatesBB[c]; +} + +inline bool Position::is_on_semiopen_file(Color c, Square s) const { + return !((pieces(c, PAWN) | pieces(c, SHOGI_PAWN, SOLDIER)) & file_bb(s)); +} + +inline bool Position::can_castle(CastlingRights cr) const { + return st->castlingRights & cr; +} + +inline CastlingRights Position::castling_rights(Color c) const { + return c & CastlingRights(st->castlingRights); +} + +inline bool Position::castling_impeded(CastlingRights cr) const { + assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO); + + return pieces() & castlingPath[cr]; +} + +inline Square Position::castling_rook_square(CastlingRights cr) const { + assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO); + + return castlingRookSquare[cr]; +} + +inline Bitboard Position::attacks_from(Color c, PieceType pt, Square s) const { + if (var->fastAttacks || var->fastAttacks2) + return attacks_bb(c, pt, s, byTypeBB[ALL_PIECES]) & board_bb(); + + PieceType movePt = pt == KING ? king_type() : pt; + Bitboard b = attacks_bb(c, movePt, s, byTypeBB[ALL_PIECES]); + // Xiangqi soldier + if (pt == SOLDIER && !(promoted_soldiers(c) & s)) + b &= file_bb(file_of(s)); + // Janggi cannon restrictions + if (pt == JANGGI_CANNON) + { + b &= ~pieces(pt); + b &= attacks_bb(c, pt, s, pieces() ^ pieces(pt)); + } + // Janggi palace moves + if (diagonal_lines() & s) + { + PieceType diagType = movePt == WAZIR ? FERS : movePt == SOLDIER ? PAWN : movePt == ROOK ? BISHOP : NO_PIECE_TYPE; + if (diagType) + b |= attacks_bb(c, diagType, s, pieces()) & diagonal_lines(); + else if (movePt == JANGGI_CANNON) + b |= rider_attacks_bb(s, pieces()) + & rider_attacks_bb(s, pieces() ^ pieces(pt)) + & ~pieces(pt) + & diagonal_lines(); + } + return b & board_bb(c, pt); +} + +inline Bitboard Position::moves_from(Color c, PieceType pt, Square s) const { + if (var->fastAttacks || var->fastAttacks2) + return moves_bb(c, pt, s, byTypeBB[ALL_PIECES]) & board_bb(); + + PieceType movePt = pt == KING ? king_type() : pt; + Bitboard b = moves_bb(c, movePt, s, byTypeBB[ALL_PIECES]); + // Add initial moves + if (double_step_region(c) & s) + b |= moves_bb(c, movePt, s, byTypeBB[ALL_PIECES]); + // Xiangqi soldier + if (pt == SOLDIER && !(promoted_soldiers(c) & s)) + b &= file_bb(file_of(s)); + // Janggi cannon restrictions + if (pt == JANGGI_CANNON) + { + b &= ~pieces(pt); + b &= attacks_bb(c, pt, s, pieces() ^ pieces(pt)); + } + // Janggi palace moves + if (diagonal_lines() & s) + { + PieceType diagType = movePt == WAZIR ? FERS : movePt == SOLDIER ? PAWN : movePt == ROOK ? BISHOP : NO_PIECE_TYPE; + if (diagType) + b |= attacks_bb(c, diagType, s, pieces()) & diagonal_lines(); + else if (movePt == JANGGI_CANNON) + b |= rider_attacks_bb(s, pieces()) + & rider_attacks_bb(s, pieces() ^ pieces(pt)) + & ~pieces(pt) + & diagonal_lines(); + } + return b & board_bb(c, pt); +} + +inline Bitboard Position::attackers_to(Square s) const { + return attackers_to(s, pieces()); +} + +inline Bitboard Position::attackers_to(Square s, Color c) const { + return attackers_to(s, byTypeBB[ALL_PIECES], c); +} + +inline Bitboard Position::attackers_to(Square s, Bitboard occupied, Color c) const { + return attackers_to(s, occupied, c, byTypeBB[JANGGI_CANNON]); +} + +inline Bitboard Position::checkers() const { + return st->checkersBB; +} + +inline Bitboard Position::blockers_for_king(Color c) const { + return st->blockersForKing[c]; +} + +inline Bitboard Position::pinners(Color c) const { + return st->pinners[c]; +} + +inline Bitboard Position::check_squares(PieceType pt) const { + return st->checkSquares[pt]; +} + +inline bool Position::pawn_passed(Color c, Square s) const { + return !(pieces(~c, PAWN) & passed_pawn_span(c, s)); +} + +inline int Position::pawns_on_same_color_squares(Color c, Square s) const { + return popcount(pieces(c, PAWN) & ((DarkSquares & s) ? DarkSquares : ~DarkSquares)); +} + +inline Key Position::key() const { + return st->rule50 < 14 ? st->key + : st->key ^ make_key((st->rule50 - 14) / 8); +} + +inline Key Position::pawn_key() const { + return st->pawnKey; +} + +inline Score Position::psq_score() const { + return psq; +} + +inline Value Position::non_pawn_material(Color c) const { + return st->nonPawnMaterial[c]; +} + +inline Value Position::non_pawn_material() const { + return non_pawn_material(WHITE) + non_pawn_material(BLACK); +} + +inline int Position::game_ply() const { + return gamePly; +} + +inline int Position::board_honor_counting_ply(int countStarted) const { + return countStarted == 0 ? + st->countingPly : + countStarted < 0 ? 0 : std::max(1 + gamePly - countStarted, 0); +} + +inline bool Position::board_honor_counting_shorter(int countStarted) const { + return counting_rule() == CAMBODIAN_COUNTING && 126 - board_honor_counting_ply(countStarted) < st->countingLimit - st->countingPly; +} + +inline int Position::counting_limit(int countStarted) const { + return board_honor_counting_shorter(countStarted) ? 126 : st->countingLimit; +} + +inline int Position::counting_ply(int countStarted) const { + return !count() && (count(WHITE) <= 1 || count(BLACK) <= 1) && !board_honor_counting_shorter(countStarted) ? + st->countingPly : + board_honor_counting_ply(countStarted); +} + +inline int Position::rule50_count() const { + return st->rule50; +} + +inline bool Position::opposite_bishops() const { + return count(WHITE) == 1 + && count(BLACK) == 1 + && opposite_colors(square(WHITE), square(BLACK)); +} + +inline bool Position::is_promoted(Square s) const { + return promotedPieces & s; +} + +inline bool Position::is_chess960() const { + return chess960; +} + +inline bool Position::capture_or_promotion(Move m) const { + assert(is_ok(m)); + return type_of(m) == PROMOTION || type_of(m) == EN_PASSANT || (type_of(m) != CASTLING && !empty(to_sq(m))); +} + +inline bool Position::capture(Move m) const { + assert(is_ok(m)); + // Castling is encoded as "king captures rook" + return (!empty(to_sq(m)) && type_of(m) != CASTLING && from_sq(m) != to_sq(m)) || type_of(m) == EN_PASSANT; +} + +inline Square Position::capture_square(Square to) const { + assert(is_ok(to)); + // The capture square of en passant is either the marked ep piece or the closest piece behind the target square + Bitboard customEp = ep_squares() & pieces(); + if (customEp) + { + // For longer custom en passant paths, we take the frontmost piece + return sideToMove == WHITE ? lsb(customEp) : msb(customEp); + } + else + { + // The capture square of normal en passant is the closest piece behind the target square + Bitboard epCandidates = pieces(~sideToMove) & forward_file_bb(~sideToMove, to); + return sideToMove == WHITE ? msb(epCandidates) : lsb(epCandidates); + } +} + +inline bool Position::virtual_drop(Move m) const { + assert(is_ok(m)); + return type_of(m) == DROP && !can_drop(side_to_move(), in_hand_piece_type(m)); +} + +inline Piece Position::captured_piece() const { + return st->capturedPiece; +} + +inline Bitboard Position::fog_area() const { + Bitboard b = board_bb(); + // Our own pieces are visible + Bitboard visible = pieces(sideToMove); + // Squares where we can move to are visible as well + for (const auto& m : MoveList(*this)) + { + Square to = to_sq(m); + visible |= to; + } + // Everything else is invisible + return ~visible & b; +} + +inline const std::string Position::piece_to_partner() const { + if (!st->capturedPiece) return std::string(); + Color color = color_of(st->capturedPiece); + Piece piece = st->capturedpromoted ? + (st->unpromotedCapturedPiece ? st->unpromotedCapturedPiece : make_piece(color, main_promotion_pawn_type(color))) : + st->capturedPiece; + return std::string(1, piece_to_char()[piece]); +} + +inline Thread* Position::this_thread() const { + return thisThread; +} + +inline void Position::put_piece(Piece pc, Square s, bool isPromoted, Piece unpromotedPc) { + + board[s] = pc; + byTypeBB[ALL_PIECES] |= byTypeBB[type_of(pc)] |= s; + byColorBB[color_of(pc)] |= s; + pieceCount[pc]++; + pieceCount[make_piece(color_of(pc), ALL_PIECES)]++; + psq += PSQT::psq[pc][s]; + if (isPromoted) + promotedPieces |= s; + unpromotedBoard[s] = unpromotedPc; +} + +inline void Position::remove_piece(Square s) { + + Piece pc = board[s]; + byTypeBB[ALL_PIECES] ^= s; + byTypeBB[type_of(pc)] ^= s; + byColorBB[color_of(pc)] ^= s; + board[s] = NO_PIECE; + pieceCount[pc]--; + pieceCount[make_piece(color_of(pc), ALL_PIECES)]--; + psq -= PSQT::psq[pc][s]; + promotedPieces -= s; + unpromotedBoard[s] = NO_PIECE; +} + +inline void Position::move_piece(Square from, Square to) { + + Piece pc = board[from]; + Bitboard fromTo = square_bb(from) ^ to; // from == to needs to cancel out + byTypeBB[ALL_PIECES] ^= fromTo; + byTypeBB[type_of(pc)] ^= fromTo; + byColorBB[color_of(pc)] ^= fromTo; + board[from] = NO_PIECE; + board[to] = pc; + psq += PSQT::psq[pc][to] - PSQT::psq[pc][from]; + if (is_promoted(from)) + promotedPieces ^= fromTo; + unpromotedBoard[to] = unpromotedBoard[from]; + unpromotedBoard[from] = NO_PIECE; +} + +inline void Position::do_move(Move m, StateInfo& newSt) { + do_move(m, newSt, gives_check(m)); +} + +inline StateInfo* Position::state() const { + + return st; +} + +// Variant-specific + +inline int Position::count_in_hand(PieceType pt) const { + return pieceCountInHand[WHITE][pt] + pieceCountInHand[BLACK][pt]; +} + +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 bool Position::allow_virtual_drop(Color c, PieceType pt) const { + assert(two_boards()); + // Do we allow a virtual drop? + return pt != KING && ( count_in_hand(c, PAWN) >= -(pt == PAWN) + && count_in_hand(c, KNIGHT) >= -(pt == PAWN) + && count_in_hand(c, BISHOP) >= -(pt == PAWN) + && count_in_hand(c, ROOK) >= 0 + && count_in_hand(c, QUEEN) >= 0); +} + +inline Value Position::material_counting_result() const { + auto weight_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 = weight_count(ROOK, 13) + + weight_count(JANGGI_CANNON, 7) + + weight_count(HORSE, 5) + + weight_count(JANGGI_ELEPHANT, 3) + + weight_count(WAZIR, 3) + + weight_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) { + if (variant()->freeDrops) return; + 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) { + if (variant()->freeDrops) return; + 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(can_drop(color_of(pc_hand), type_of(pc_hand)) || var->twoBoards); + put_piece(pc_drop, s, pc_drop != pc_hand, pc_drop != pc_hand ? pc_hand : NO_PIECE); + remove_from_hand(pc_hand); + virtualPieces += (pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)] < 0); +} + +inline void Position::undrop_piece(Piece pc_hand, Square s) { + virtualPieces -= (pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)] < 0); + remove_piece(s); + board[s] = NO_PIECE; + add_to_hand(pc_hand); + assert(can_drop(color_of(pc_hand), type_of(pc_hand)) || var->twoBoards); +} + +inline bool Position::can_drop(Color c, PieceType pt) const { + return variant()->freeDrops || count_in_hand(c, pt) > 0; +} + +} // namespace Stockfish + +#endif // #ifndef POSITION_H_INCLUDED diff --git a/Fairy-Stockfish-master/src/psqt.cpp b/Fairy-Stockfish-master/src/psqt.cpp new file mode 100644 index 0000000000000000000000000000000000000000..25b5471c1c5a647d038e5aa44a5fb515d735dbed --- /dev/null +++ b/Fairy-Stockfish-master/src/psqt.cpp @@ -0,0 +1,355 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + + +#include "psqt.h" + +#include +#include +#include + +#include "bitboard.h" +#include "types.h" + +#include "piece.h" +#include "variant.h" +#include "misc.h" + + +namespace Stockfish { + +Value EvalPieceValue[PHASE_NB][PIECE_NB]; +Value CapturePieceValue[PHASE_NB][PIECE_NB]; + +Value PieceValue[PHASE_NB][PIECE_NB] = { + { + VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg, FersValueMg, AlfilValueMg, + FersAlfilValueMg, SilverValueMg, AiwokValueMg, BersValueMg, ArchbishopValueMg, ChancellorValueMg, AmazonValueMg, KnibisValueMg, + BiskniValueMg, KnirooValueMg, RookniValueMg, ShogiPawnValueMg, LanceValueMg, ShogiKnightValueMg, GoldValueMg, DragonHorseValueMg, + ClobberPieceValueMg, BreakthroughPieceValueMg, ImmobilePieceValueMg, CannonPieceValueMg, JanggiCannonPieceValueMg, SoldierValueMg, HorseValueMg, ElephantValueMg, + JanggiElephantValueMg, BannerValueMg, WazirValueMg, CommonerValueMg, CentaurValueMg, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, + VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, + VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, + VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, + + VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg, FersValueMg, AlfilValueMg, + FersAlfilValueMg, SilverValueMg, AiwokValueMg, BersValueMg, ArchbishopValueMg, ChancellorValueMg, AmazonValueMg, KnibisValueMg, + BiskniValueMg, KnirooValueMg, RookniValueMg, ShogiPawnValueMg, LanceValueMg, ShogiKnightValueMg, GoldValueMg, DragonHorseValueMg, + ClobberPieceValueMg, BreakthroughPieceValueMg, ImmobilePieceValueMg, CannonPieceValueMg, JanggiCannonPieceValueMg, SoldierValueMg, HorseValueMg, ElephantValueMg, + JanggiElephantValueMg, BannerValueMg, WazirValueMg, CommonerValueMg, CentaurValueMg, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, + VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, + VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, + VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, + }, + { + VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg, FersValueEg, AlfilValueEg, + FersAlfilValueEg, SilverValueEg, AiwokValueEg, BersValueEg, ArchbishopValueEg, ChancellorValueEg, AmazonValueEg, KnibisValueEg, + BiskniValueEg, KnirooValueEg, RookniValueEg, ShogiPawnValueEg, LanceValueEg, ShogiKnightValueEg, GoldValueEg, DragonHorseValueEg, + ClobberPieceValueEg, BreakthroughPieceValueEg, ImmobilePieceValueEg, CannonPieceValueEg, JanggiCannonPieceValueEg, SoldierValueEg, HorseValueEg, ElephantValueEg, + JanggiElephantValueEg, BannerValueEg, WazirValueEg, CommonerValueEg, CentaurValueEg, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, + VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, + VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, + VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, + + VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg, FersValueEg, AlfilValueEg, + FersAlfilValueEg, SilverValueEg, AiwokValueEg, BersValueEg, ArchbishopValueEg, ChancellorValueEg, AmazonValueEg, KnibisValueEg, + BiskniValueEg, KnirooValueEg, RookniValueEg, ShogiPawnValueEg, LanceValueEg, ShogiKnightValueEg, GoldValueEg, DragonHorseValueEg, + ClobberPieceValueEg, BreakthroughPieceValueEg, ImmobilePieceValueEg, CannonPieceValueEg, JanggiCannonPieceValueEg, SoldierValueEg, HorseValueEg, ElephantValueEg, + JanggiElephantValueEg, BannerValueEg, WazirValueEg, CommonerValueEg, CentaurValueEg, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, + VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, + VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, + VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, + }, +}; + + +namespace +{ + +auto constexpr S = make_score; + +// 'Bonus' contains Piece-Square parameters. +// Scores are explicit for files A to D, implicitly mirrored for E to H. +constexpr Score Bonus[PIECE_TYPE_NB][RANK_NB][int(FILE_NB) / 2] = { + { }, + { }, + { // Knight + { S(-175, -96), S(-92,-65), S(-74,-49), S(-73,-21) }, + { S( -77, -67), S(-41,-54), S(-27,-18), S(-15, 8) }, + { S( -61, -40), S(-17,-27), S( 6, -8), S( 12, 29) }, + { S( -35, -35), S( 8, -2), S( 40, 13), S( 49, 28) }, + { S( -34, -45), S( 13,-16), S( 44, 9), S( 51, 39) }, + { S( -9, -51), S( 22,-44), S( 58,-16), S( 53, 17) }, + { S( -67, -69), S(-27,-50), S( 4,-51), S( 37, 12) }, + { S(-201,-100), S(-83,-88), S(-56,-56), S(-26,-17) } + }, + { // Bishop + { S(-37,-40), S(-4 ,-21), S( -6,-26), S(-16, -8) }, + { S(-11,-26), S( 6, -9), S( 13,-12), S( 3, 1) }, + { S(-5 ,-11), S( 15, -1), S( -4, -1), S( 12, 7) }, + { S(-4 ,-14), S( 8, -4), S( 18, 0), S( 27, 12) }, + { S(-8 ,-12), S( 20, -1), S( 15,-10), S( 22, 11) }, + { S(-11,-21), S( 4, 4), S( 1, 3), S( 8, 4) }, + { S(-12,-22), S(-10,-14), S( 4, -1), S( 0, 1) }, + { S(-34,-32), S( 1,-29), S(-10,-26), S(-16,-17) } + }, + { // Rook + { S(-31, -9), S(-20,-13), S(-14,-10), S(-5, -9) }, + { S(-21,-12), S(-13, -9), S( -8, -1), S( 6, -2) }, + { S(-25, 6), S(-11, -8), S( -1, -2), S( 3, -6) }, + { S(-13, -6), S( -5, 1), S( -4, -9), S(-6, 7) }, + { S(-27, -5), S(-15, 8), S( -4, 7), S( 3, -6) }, + { S(-22, 6), S( -2, 1), S( 6, -7), S(12, 10) }, + { S( -2, 4), S( 12, 5), S( 16, 20), S(18, -5) }, + { S(-17, 18), S(-19, 0), S( -1, 19), S( 9, 13) } + }, + { // Queen + { S( 3,-69), S(-5,-57), S(-5,-47), S( 4,-26) }, + { S(-3,-54), S( 5,-31), S( 8,-22), S(12, -4) }, + { S(-3,-39), S( 6,-18), S(13, -9), S( 7, 3) }, + { S( 4,-23), S( 5, -3), S( 9, 13), S( 8, 24) }, + { S( 0,-29), S(14, -6), S(12, 9), S( 5, 21) }, + { S(-4,-38), S(10,-18), S( 6,-11), S( 8, 1) }, + { S(-5,-50), S( 6,-27), S(10,-24), S( 8, -8) }, + { S(-2,-74), S(-2,-52), S( 1,-43), S(-2,-34) } + } +}; + +constexpr Score KingBonus[RANK_NB][int(FILE_NB) / 2] = { + { S(271, 1), S(327, 45), S(271, 85), S(198, 76) }, + { S(278, 53), S(303,100), S(234,133), S(179,135) }, + { S(195, 88), S(258,130), S(169,169), S(120,175) }, + { S(164,103), S(190,156), S(138,172), S( 98,172) }, + { S(154, 96), S(179,166), S(105,199), S( 70,199) }, + { S(123, 92), S(145,172), S( 81,184), S( 31,191) }, + { S( 88, 47), S(120,121), S( 65,116), S( 33,131) }, + { S( 59, 11), S( 89, 59), S( 45, 73), S( -1, 78) } +}; + +constexpr Score PBonus[RANK_NB][FILE_NB] = + { // Pawn (asymmetric distribution) + { }, + { S( 2, -8), S( 4, -6), S( 11, 9), S( 18, 5), S( 16, 16), S( 21, 6), S( 9, -6), S( -3,-18) }, + { S( -9, -9), S(-15, -7), S( 11,-10), S( 15, 5), S( 31, 2), S( 23, 3), S( 6, -8), S(-20, -5) }, + { S( -3, 7), S(-20, 1), S( 8, -8), S( 19, -2), S( 39,-14), S( 17,-13), S( 2,-11), S( -5, -6) }, + { S( 11, 12), S( -4, 6), S(-11, 2), S( 2, -6), S( 11, -5), S( 0, -4), S(-12, 14), S( 5, 9) }, + { S( 3, 27), S(-11, 18), S( -6, 19), S( 22, 29), S( -8, 30), S( -5, 9), S(-14, 8), S(-11, 14) }, + { S( -7, -1), S( 6,-14), S( -2, 13), S(-11, 22), S( 4, 24), S(-14, 17), S( 10, 7), S( -9, 7) } + }; + + +// Scale down slider value based on distance +int slider_fraction(std::map slider) { + int s = 0; + for (auto const& [_, limit] : slider) { + s += limit == 0 ? 100 : 200 * std::min(limit + 1, 8) / 16; + } + return s; +} + + +// Estimate piece value +Value piece_value(Phase phase, PieceType pt) +{ + const PieceInfo* pi = pieceMap.find(pt)->second; + int v0 = (phase == MG ? 60 : 60) * pi->steps[0][MODALITY_CAPTURE].size() + + (phase == MG ? 30 : 40) * pi->steps[0][MODALITY_QUIET].size() + + (phase == MG ? 185 : 185) * slider_fraction(pi->slider[0][MODALITY_CAPTURE]) / 100 + + (phase == MG ? 55 : 45) * slider_fraction(pi->slider[0][MODALITY_QUIET]) / 100 + // Hoppers are more useful with more pieces on the board + + (phase == MG ? 100 : 80) * pi->hopper[0][MODALITY_CAPTURE].size() + + (phase == MG ? 85 : 60) * pi->hopper[0][MODALITY_QUIET].size() + // Rook sliding directions are more valuable, especially in endgame + + (phase == MG ? 15 : 15) * std::count_if(pi->slider[0][MODALITY_CAPTURE].begin(), pi->slider[0][MODALITY_CAPTURE].end(), [](const std::pair& d) { return std::abs(d.first) == NORTH || std::abs(d.first) == 1; }) + + (phase == MG ? 30 : 50) * std::count_if(pi->slider[0][MODALITY_QUIET].begin(), pi->slider[0][MODALITY_QUIET].end(), [](const std::pair& d) { return std::abs(d.first) == NORTH || std::abs(d.first) == 1; }); + return Value(v0 * exp(double(v0) / 10000)); +} + +} // namespace + + +namespace PSQT +{ + +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(const Variant* v) { + + PieceType strongestPiece = NO_PIECE_TYPE; + for (PieceSet ps = v->pieceTypes; ps;) + { + PieceType pt = pop_lsb(ps); + if (is_custom(pt)) + { + PieceValue[MG][make_piece(WHITE, pt)] = PieceValue[MG][make_piece(BLACK, pt)] = piece_value(MG, pt); + PieceValue[EG][make_piece(WHITE, pt)] = PieceValue[EG][make_piece(BLACK, pt)] = piece_value(EG, pt); + } + + if (PieceValue[MG][pt] > PieceValue[MG][strongestPiece]) + strongestPiece = pt; + } + + Value maxPromotion = VALUE_ZERO; + for (PieceSet ps = v->promotionPieceTypes[WHITE]; ps;) + maxPromotion = std::max(maxPromotion, PieceValue[EG][pop_lsb(ps)]); + + for (PieceType pt = PAWN; pt <= KING; ++pt) + { + Piece pc = make_piece(WHITE, pt); + + Score score = make_score(PieceValue[MG][pc], PieceValue[EG][pc]); + + // Consider promotion types in pawn score + if (pt == v->mainPromotionPawnType[WHITE]) + { + score -= make_score(0, (QueenValueEg - maxPromotion) / 100); + if (v->blastOnCapture) + score += make_score(mg_value(score) * 3 / 2, eg_value(score)); + } + + const PieceInfo* pi = pieceMap.find(pt)->second; + bool isSlider = pi->slider[0][MODALITY_QUIET].size() || pi->slider[0][MODALITY_CAPTURE].size() || pi->hopper[0][MODALITY_QUIET].size() || pi->hopper[0][MODALITY_CAPTURE].size(); + bool isPawn = !isSlider && pi->steps[0][MODALITY_QUIET].size() && !std::any_of(pi->steps[0][MODALITY_QUIET].begin(), pi->steps[0][MODALITY_QUIET].end(), [](const std::pair& d) { return d.first < SOUTH / 2; }); + bool isSlowLeaper = !isSlider && !std::any_of(pi->steps[0][MODALITY_QUIET].begin(), pi->steps[0][MODALITY_QUIET].end(), [](const std::pair& d) { return dist(d.first) > 1; }); + + // Scale slider piece values with board size + if (isSlider) + { + constexpr int lc = 5; + constexpr int rm = 5; + constexpr int r0 = rm + RANK_8; + int r1 = rm + (v->maxRank + v->maxFile - 2 * v->capturesToHand) / 2; + int leaper = pi->steps[0][MODALITY_QUIET].size() + pi->steps[0][MODALITY_CAPTURE].size(); + int slider = pi->slider[0][MODALITY_QUIET].size() + pi->slider[0][MODALITY_CAPTURE].size() + pi->hopper[0][MODALITY_QUIET].size() + pi->hopper[0][MODALITY_CAPTURE].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)); + } + + // Piece values saturate earlier in drop variants + if (v->capturesToHand || v->twoBoards) + score = make_score(mg_value(score) * 7000 / (7000 + mg_value(score)), + eg_value(score) * 7000 / (7000 + eg_value(score))); + + // In variants where checks are prohibited, strong pieces are less mobile, so limit their value + if (!v->checking) + score = make_score(std::min(mg_value(score), Value(1800)) / 2, + std::min(eg_value(score), Value(1800)) * 3 / 5); + + // With check counting, strong pieces are even more dangerous + else if (v->checkCounting) + score = make_score(mg_value(score) * (20000 + mg_value(score)) / 22000, + eg_value(score) * (20000 + eg_value(score)) / 21000); + + // Increase leapers' value in makpong + else if (v->makpongRule) + { + if (std::any_of(pi->steps[0][MODALITY_CAPTURE].begin(), pi->steps[0][MODALITY_CAPTURE].end(), [](const std::pair& d) { return dist(d.first) > 1 && !d.second; })) + score = make_score(mg_value(score) * 4200 / (3500 + mg_value(score)), + eg_value(score) * 4700 / (3500 + mg_value(score))); + } + + // Adjust piece values for atomic captures + if (v->blastOnCapture) + score = make_score(mg_value(score) * 7000 / (7000 + mg_value(score)), eg_value(score)); + + // In variants such as horde where all pieces need to be captured, weak pieces such as pawns are more useful + if ( v->extinctionValue == -VALUE_MATE + && v->extinctionPieceCount == 0 + && (v->extinctionPieceTypes & ALL_PIECES)) + score += make_score(0, std::max(KnightValueEg - PieceValue[EG][pt], VALUE_ZERO) / 20); + + // The strongest piece of a variant usually has some dominance, such as rooks in Makruk and Xiangqi. + // This does not apply to drop variants. + if (pt == strongestPiece && !v->capturesToHand) + 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) + score = -make_score(mg_value(score) / 8, eg_value(score) / 8 / (1 + !pi->slider[0][MODALITY_CAPTURE].size())); + + // Override variant piece value + if (v->pieceValue[MG][pt]) + score = make_score(v->pieceValue[MG][pt], eg_value(score)); + if (v->pieceValue[EG][pt]) + score = make_score(mg_value(score), v->pieceValue[EG][pt]); + + CapturePieceValue[MG][pc] = CapturePieceValue[MG][~pc] = mg_value(score); + CapturePieceValue[EG][pc] = CapturePieceValue[EG][~pc] = eg_value(score); + + // For drop variants, halve the piece values to compensate for double changes by captures + if (v->capturesToHand) + score = score / 2; + + EvalPieceValue[MG][pc] = EvalPieceValue[MG][~pc] = mg_value(score); + EvalPieceValue[EG][pc] = EvalPieceValue[EG][~pc] = eg_value(score); + + // 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 = 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[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)] * (1 + v->blastOnCapture) + : pt == HORSE ? Bonus[KNIGHT][std::min(r, RANK_8)][std::min(f, FILE_D)] + : pt == COMMONER && v->extinctionValue == -VALUE_MATE && (v->extinctionPieceTypes & COMMONER) ? KingBonus[std::clamp(Rank(r - pawnRank + 1), RANK_1, 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)); + // Add a penalty for unpromoted soldiers + if (pt == SOLDIER && r < v->soldierPromotionRank) + psq[pc][s] -= score * (v->soldierPromotionRank - r) / (4 + f); + // Corners are valuable in reversi + if (v->enclosingDrop == REVERSI) + { + if (f == FILE_A && (r == RANK_1 || r == v->maxRank)) + psq[pc][s] += make_score(1000, 1000); + } + // In atomic variants pieces are "self-defending" and should therefore be pushed forward + if (v->blastOnCapture) + psq[pc][s] += make_score(40, 0) * (r - v->maxRank / 2); + // Safe king squares + if (r == RANK_1 && f <= FILE_B && ((pt == KING && v->checkCounting) || (pt == COMMONER && v->blastOnCapture))) + psq[pc][s] += make_score(100, 0); + psq[~pc][rank_of(s) <= v->maxRank ? flip_rank(s, v->maxRank) : s] = -psq[pc][s]; + } + // Pieces in hand + psq[ pc][SQ_NONE] = score + make_score(35, 10) * (1 + !isSlider); + psq[~pc][SQ_NONE] = -psq[pc][SQ_NONE]; + } +} + +} // namespace PSQT + +} // namespace Stockfish diff --git a/Fairy-Stockfish-master/src/psqt.h b/Fairy-Stockfish-master/src/psqt.h new file mode 100644 index 0000000000000000000000000000000000000000..f8caed4798bca2dcb1e3e5673769405aacdb7959 --- /dev/null +++ b/Fairy-Stockfish-master/src/psqt.h @@ -0,0 +1,39 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + + +#ifndef PSQT_H_INCLUDED +#define PSQT_H_INCLUDED + + +#include "types.h" + +#include "variant.h" + +namespace Stockfish::PSQT +{ + +extern Score psq[PIECE_NB][SQUARE_NB + 1]; + +// Fill psqt array from a set of internally linked parameters +extern void init(const Variant*); + +} // namespace Stockfish::PSQT + + +#endif // PSQT_H_INCLUDED diff --git a/Fairy-Stockfish-master/src/pyffish.cpp b/Fairy-Stockfish-master/src/pyffish.cpp new file mode 100644 index 0000000000000000000000000000000000000000..31ee13d7ce7adfef4f84a99de7ad90ef0ff16a5f --- /dev/null +++ b/Fairy-Stockfish-master/src/pyffish.cpp @@ -0,0 +1,480 @@ +/* + Based on Jean-Francois Romang work + https://github.com/jromang/Stockfish/blob/pyfish/src/pyfish.cpp +*/ + +#include +#include + +#include "misc.h" +#include "types.h" +#include "bitboard.h" +#include "evaluate.h" +#include "position.h" +#include "search.h" +#include "syzygy/tbprobe.h" +#include "thread.h" +#include "tt.h" +#include "uci.h" +#include "piece.h" +#include "variant.h" +#include "apiutil.h" + +using namespace Stockfish; + +static PyObject* PyFFishError; + +void buildPosition(Position& pos, StateListPtr& states, const char *variant, const char *fen, PyObject *moveList, const bool chess960) { + states = StateListPtr(new std::deque(1)); // Drop old and create a new one + + const Variant* v = variants.find(std::string(variant))->second; + UCI::init_variant(v); + if (strcmp(fen, "startpos") == 0) + fen = v->startFen.c_str(); + pos.set(v, std::string(fen), chess960, &states->back(), Threads.main()); + + // parse move list + int numMoves = PyList_Size(moveList); + for (int i = 0; i < numMoves ; i++) + { + PyObject *MoveStr = PyUnicode_AsEncodedString( PyList_GetItem(moveList, i), "UTF-8", "strict"); + std::string moveStr(PyBytes_AS_STRING(MoveStr)); + Py_XDECREF(MoveStr); + Move m; + if ((m = UCI::to_move(pos, moveStr)) != MOVE_NONE) + { + // do the move + states->emplace_back(); + pos.do_move(m, states->back()); + } + else + PyErr_SetString(PyExc_ValueError, (std::string("Invalid move '") + moveStr + "'").c_str()); + } + return; +} + +extern "C" PyObject* pyffish_version(PyObject* self) { + return Py_BuildValue("(iii)", 0, 0, 89); +} + +extern "C" PyObject* pyffish_info(PyObject* self) { + return Py_BuildValue("s", engine_info().c_str()); +} + +extern "C" PyObject* pyffish_variants(PyObject* self, PyObject *args) { + PyObject* varList = PyList_New(0); + + for (std::string v : variants.get_keys()) + { + PyObject* variant = Py_BuildValue("s", v.c_str()); + PyList_Append(varList, variant); + Py_XDECREF(variant); + } + + PyObject* Result = Py_BuildValue("O", varList); + Py_XDECREF(varList); + return Result; +} + +// INPUT option name, option value +extern "C" PyObject* pyffish_setOption(PyObject* self, PyObject *args) { + const char *name; + PyObject *valueObj; + if (!PyArg_ParseTuple(args, "sO", &name, &valueObj)) return NULL; + + if (Options.count(name)) + { + PyObject *Value = PyUnicode_AsEncodedString( PyObject_Str(valueObj), "UTF-8", "strict"); + Options[name] = std::string(PyBytes_AS_STRING(Value)); + Py_XDECREF(Value); + } + else + { + PyErr_SetString(PyExc_ValueError, (std::string("No such option ") + name + "'").c_str()); + return NULL; + } + Py_RETURN_NONE; +} + +// INPUT variant config +extern "C" PyObject* pyffish_loadVariantConfig(PyObject* self, PyObject *args) { + const char *config; + if (!PyArg_ParseTuple(args, "s", &config)) + return NULL; + std::stringstream ss(config); + variants.parse_istream(ss); + Options["UCI_Variant"].set_combo(variants.get_keys()); + Py_RETURN_NONE; +} + +// INPUT variant +extern "C" PyObject* pyffish_startFen(PyObject* self, PyObject *args) { + const char *variant; + + if (!PyArg_ParseTuple(args, "s", &variant)) { + return NULL; + } + + return Py_BuildValue("s", variants.find(std::string(variant))->second->startFen.c_str()); +} + +// INPUT variant +extern "C" PyObject* pyffish_twoBoards(PyObject* self, PyObject *args) { + const char *variant; + + if (!PyArg_ParseTuple(args, "s", &variant)) { + return NULL; + } + + return Py_BuildValue("O", variants.find(std::string(variant))->second->twoBoards ? Py_True : Py_False); +} + +// INPUT variant +extern "C" PyObject* pyffish_capturesToHand(PyObject* self, PyObject *args) { + const char *variant; + + if (!PyArg_ParseTuple(args, "s", &variant)) { + return NULL; + } + + return Py_BuildValue("O", variants.find(std::string(variant))->second->capturesToHand ? Py_True : Py_False); +} + +// INPUT variant, fen, move +extern "C" PyObject* pyffish_getSAN(PyObject* self, PyObject *args) { + PyObject* moveList = PyList_New(0); + Position pos; + const char *fen, *variant, *move; + + int chess960 = false; + Notation notation = NOTATION_DEFAULT; + if (!PyArg_ParseTuple(args, "sss|pi", &variant, &fen, &move, &chess960, ¬ation)) { + return NULL; + } + if (notation == NOTATION_DEFAULT) + notation = default_notation(variants.find(std::string(variant))->second); + StateListPtr states(new std::deque(1)); + buildPosition(pos, states, variant, fen, moveList, chess960); + std::string moveStr = move; + + Py_XDECREF(moveList); + return Py_BuildValue("s", SAN::move_to_san(pos, UCI::to_move(pos, moveStr), notation).c_str()); +} + +// INPUT variant, fen, movelist +extern "C" PyObject* pyffish_getSANmoves(PyObject* self, PyObject *args) { + PyObject* sanMoves = PyList_New(0), *moveList; + Position pos; + const char *fen, *variant; + + int chess960 = false; + Notation notation = NOTATION_DEFAULT; + if (!PyArg_ParseTuple(args, "ssO!|pi", &variant, &fen, &PyList_Type, &moveList, &chess960, ¬ation)) { + return NULL; + } + if (notation == NOTATION_DEFAULT) + notation = default_notation(variants.find(std::string(variant))->second); + StateListPtr states(new std::deque(1)); + buildPosition(pos, states, variant, fen, sanMoves, chess960); + + int numMoves = PyList_Size(moveList); + for (int i=0; iemplace_back(); + pos.do_move(m, states->back()); + } + else + { + PyErr_SetString(PyExc_ValueError, (std::string("Invalid move '") + moveStr + "'").c_str()); + return NULL; + } + } + PyObject *Result = Py_BuildValue("O", sanMoves); + Py_XDECREF(sanMoves); + return Result; +} + +// INPUT variant, fen, move list +extern "C" PyObject* pyffish_legalMoves(PyObject* self, PyObject *args) { + PyObject* legalMoves = PyList_New(0), *moveList; + Position pos; + const char *fen, *variant; + + int chess960 = false; + if (!PyArg_ParseTuple(args, "ssO!|p", &variant, &fen, &PyList_Type, &moveList, &chess960)) { + return NULL; + } + + StateListPtr states(new std::deque(1)); + buildPosition(pos, states, variant, fen, moveList, chess960); + for (const auto& m : MoveList(pos)) + { + PyObject *moveStr; + moveStr = Py_BuildValue("s", UCI::move(pos, m).c_str()); + PyList_Append(legalMoves, moveStr); + Py_XDECREF(moveStr); + } + + PyObject *Result = Py_BuildValue("O", legalMoves); + Py_XDECREF(legalMoves); + return Result; +} + +// INPUT variant, fen, move list +extern "C" PyObject* pyffish_getFEN(PyObject* self, PyObject *args) { + PyObject *moveList; + Position pos; + const char *fen, *variant; + + int chess960 = false, sfen = false, showPromoted = false, countStarted = 0; + if (!PyArg_ParseTuple(args, "ssO!|pppi", &variant, &fen, &PyList_Type, &moveList, &chess960, &sfen, &showPromoted, &countStarted)) { + return NULL; + } + + StateListPtr states(new std::deque(1)); + buildPosition(pos, states, variant, fen, moveList, chess960); + return Py_BuildValue("s", pos.fen(sfen, showPromoted, countStarted).c_str()); +} + +// INPUT variant, fen, move list +extern "C" PyObject* pyffish_givesCheck(PyObject* self, PyObject *args) { + PyObject *moveList; + Position pos; + const char *fen, *variant; + int chess960 = false; + if (!PyArg_ParseTuple(args, "ssO!|p", &variant, &fen, &PyList_Type, &moveList, &chess960)) { + return NULL; + } + + StateListPtr states(new std::deque(1)); + buildPosition(pos, states, variant, fen, moveList, chess960); + return Py_BuildValue("O", Stockfish::checked(pos) ? Py_True : Py_False); +} + +// INPUT variant, fen, move list, move +extern "C" PyObject* pyffish_isCapture(PyObject* self, PyObject *args) { + Position pos; + PyObject *moveList; + const char *variant, *fen, *move; + int chess960 = false; + if (!PyArg_ParseTuple(args, "ssO!s|p", &variant, &fen, &PyList_Type, &moveList, &move, &chess960)) { + return NULL; + } + + StateListPtr states(new std::deque(1)); + buildPosition(pos, states, variant, fen, moveList, chess960); + std::string moveStr = move; + + return Py_BuildValue("O", pos.capture(UCI::to_move(pos, moveStr)) ? Py_True : Py_False); +} + +// INPUT variant, fen, move list +extern "C" PyObject* pyffish_pieceToPartner(PyObject* self, PyObject *args) { + PyObject *moveList; + Position pos; + const char *fen, *variant; + int chess960 = false; + if (!PyArg_ParseTuple(args, "ssO!|p", &variant, &fen, &PyList_Type, &moveList, &chess960)) { + return NULL; + } + + StateListPtr states(new std::deque(1)); + buildPosition(pos, states, variant, fen, moveList, chess960); + return Py_BuildValue("s", pos.piece_to_partner().c_str()); +} + +// INPUT variant, fen, move list +// should only be called when the move list is empty +extern "C" PyObject* pyffish_gameResult(PyObject* self, PyObject *args) { + PyObject *moveList; + Position pos; + const char *fen, *variant; + bool gameEnd; + Value result; + int chess960 = false; + if (!PyArg_ParseTuple(args, "ssO!|p", &variant, &fen, &PyList_Type, &moveList, &chess960)) { + return NULL; + } + + StateListPtr states(new std::deque(1)); + buildPosition(pos, states, variant, fen, moveList, chess960); + assert(!MoveList(pos).size()); + gameEnd = pos.is_immediate_game_end(result); + if (!gameEnd) + result = pos.checkers() ? pos.checkmate_value() : pos.stalemate_value(); + + return Py_BuildValue("i", result); +} + +// INPUT variant, fen, move list +extern "C" PyObject* pyffish_isImmediateGameEnd(PyObject* self, PyObject *args) { + PyObject *moveList; + Position pos; + const char *fen, *variant; + bool gameEnd; + Value result; + int chess960 = false; + if (!PyArg_ParseTuple(args, "ssO!|p", &variant, &fen, &PyList_Type, &moveList, &chess960)) { + return NULL; + } + + StateListPtr states(new std::deque(1)); + buildPosition(pos, states, variant, fen, moveList, chess960); + gameEnd = pos.is_immediate_game_end(result); + return Py_BuildValue("(Oi)", gameEnd ? Py_True : Py_False, result); +} + +// INPUT variant, fen, move list +extern "C" PyObject* pyffish_isOptionalGameEnd(PyObject* self, PyObject *args) { + PyObject *moveList; + Position pos; + const char *fen, *variant; + bool gameEnd; + Value result; + int chess960 = false, countStarted = 0; + if (!PyArg_ParseTuple(args, "ssO!|pi", &variant, &fen, &PyList_Type, &moveList, &chess960, &countStarted)) { + return NULL; + } + + StateListPtr states(new std::deque(1)); + buildPosition(pos, states, variant, fen, moveList, chess960); + gameEnd = pos.is_optional_game_end(result, 0, countStarted); + return Py_BuildValue("(Oi)", gameEnd ? Py_True : Py_False, result); +} + +// INPUT variant, fen, move list +extern "C" PyObject* pyffish_hasInsufficientMaterial(PyObject* self, PyObject *args) { + PyObject *moveList; + Position pos; + const char *fen, *variant; + int chess960 = false; + if (!PyArg_ParseTuple(args, "ssO!|p", &variant, &fen, &PyList_Type, &moveList, &chess960)) { + return NULL; + } + + StateListPtr states(new std::deque(1)); + buildPosition(pos, states, variant, fen, moveList, chess960); + + bool wInsufficient = has_insufficient_material(WHITE, pos); + bool bInsufficient = has_insufficient_material(BLACK, pos); + + return Py_BuildValue("(OO)", wInsufficient ? Py_True : Py_False, bInsufficient ? Py_True : Py_False); +} + +// INPUT variant, fen +extern "C" PyObject* pyffish_validateFen(PyObject* self, PyObject *args) { + const char *fen, *variant; + int chess960 = false; + if (!PyArg_ParseTuple(args, "ss|p", &fen, &variant, &chess960)) { + return NULL; + } + + return Py_BuildValue("i", FEN::validate_fen(std::string(fen), variants.find(std::string(variant))->second, chess960)); +} + +// INPUT variant, fen +extern "C" PyObject* pyffish_getFogFEN(PyObject* self, PyObject *args) { + PyObject* moveList = PyList_New(0); + Position pos; + const char *fen, *variant; + + int chess960 = false, sfen = false, showPromoted = false, countStarted = 0; + if (!PyArg_ParseTuple(args, "ss|p", &fen, &variant, &chess960)) { + return NULL; + } + StateListPtr states(new std::deque(1)); + buildPosition(pos, states, variant, fen, moveList, chess960); + + Py_XDECREF(moveList); + return Py_BuildValue("s", pos.fen(sfen, showPromoted, countStarted, "-", pos.fog_area()).c_str()); +} + +static PyMethodDef PyFFishMethods[] = { + {"version", (PyCFunction)pyffish_version, METH_NOARGS, "Get package version."}, + {"info", (PyCFunction)pyffish_info, METH_NOARGS, "Get Stockfish version info."}, + {"variants", (PyCFunction)pyffish_variants, METH_NOARGS, "Get supported variants."}, + {"set_option", (PyCFunction)pyffish_setOption, METH_VARARGS, "Set UCI option."}, + {"load_variant_config", (PyCFunction)pyffish_loadVariantConfig, METH_VARARGS, "Load variant configuration."}, + {"start_fen", (PyCFunction)pyffish_startFen, METH_VARARGS, "Get starting position FEN."}, + {"two_boards", (PyCFunction)pyffish_twoBoards, METH_VARARGS, "Checks whether the variant is played on two boards."}, + {"captures_to_hand", (PyCFunction)pyffish_capturesToHand, METH_VARARGS, "Checks whether the variant rules contains capturesToHand."}, + {"get_san", (PyCFunction)pyffish_getSAN, METH_VARARGS, "Get SAN move from given FEN and UCI move."}, + {"get_san_moves", (PyCFunction)pyffish_getSANmoves, METH_VARARGS, "Get SAN movelist from given FEN and UCI movelist."}, + {"legal_moves", (PyCFunction)pyffish_legalMoves, METH_VARARGS, "Get legal moves from given FEN and movelist."}, + {"get_fen", (PyCFunction)pyffish_getFEN, METH_VARARGS, "Get resulting FEN from given FEN and movelist."}, + {"gives_check", (PyCFunction)pyffish_givesCheck, METH_VARARGS, "Get check status from given FEN and movelist."}, + {"is_capture", (PyCFunction)pyffish_isCapture, METH_VARARGS, "Get whether given move is a capture from given FEN and movelist."}, + {"piece_to_partner", (PyCFunction)pyffish_pieceToPartner, METH_VARARGS, "Get unpromoted captured piece from given FEN and movelist."}, + {"game_result", (PyCFunction)pyffish_gameResult, METH_VARARGS, "Get result from given FEN, considering variant end, checkmate, and stalemate."}, + {"is_immediate_game_end", (PyCFunction)pyffish_isImmediateGameEnd, METH_VARARGS, "Get result from given FEN if variant rules ends the game."}, + {"is_optional_game_end", (PyCFunction)pyffish_isOptionalGameEnd, METH_VARARGS, "Get result from given FEN it rules enable game end by player."}, + {"has_insufficient_material", (PyCFunction)pyffish_hasInsufficientMaterial, METH_VARARGS, "Checks for insufficient material."}, + {"validate_fen", (PyCFunction)pyffish_validateFen, METH_VARARGS, "Validate an input FEN."}, + {"get_fog_fen", (PyCFunction)pyffish_getFogFEN, METH_VARARGS, "Get Fog of War FEN from given FEN."}, + {NULL, NULL, 0, NULL}, // sentinel +}; + +static PyModuleDef pyffishmodule = { + PyModuleDef_HEAD_INIT, + "pyffish", + "Fairy-Stockfish extension module.", + -1, + PyFFishMethods, +}; + +PyMODINIT_FUNC PyInit_pyffish() { + PyObject* module; + + module = PyModule_Create(&pyffishmodule); + if (module == NULL) { + return NULL; + } + PyFFishError = PyErr_NewException("pyffish.error", NULL, NULL); + Py_INCREF(PyFFishError); + PyModule_AddObject(module, "error", PyFFishError); + + // values + PyModule_AddObject(module, "VALUE_MATE", PyLong_FromLong(VALUE_MATE)); + PyModule_AddObject(module, "VALUE_DRAW", PyLong_FromLong(VALUE_DRAW)); + + // notations + PyModule_AddObject(module, "NOTATION_DEFAULT", PyLong_FromLong(NOTATION_DEFAULT)); + PyModule_AddObject(module, "NOTATION_SAN", PyLong_FromLong(NOTATION_SAN)); + PyModule_AddObject(module, "NOTATION_LAN", PyLong_FromLong(NOTATION_LAN)); + PyModule_AddObject(module, "NOTATION_SHOGI_HOSKING", PyLong_FromLong(NOTATION_SHOGI_HOSKING)); + PyModule_AddObject(module, "NOTATION_SHOGI_HODGES", PyLong_FromLong(NOTATION_SHOGI_HODGES)); + PyModule_AddObject(module, "NOTATION_SHOGI_HODGES_NUMBER", PyLong_FromLong(NOTATION_SHOGI_HODGES_NUMBER)); + PyModule_AddObject(module, "NOTATION_JANGGI", PyLong_FromLong(NOTATION_JANGGI)); + PyModule_AddObject(module, "NOTATION_XIANGQI_WXF", PyLong_FromLong(NOTATION_XIANGQI_WXF)); + PyModule_AddObject(module, "NOTATION_THAI_SAN", PyLong_FromLong(NOTATION_THAI_SAN)); + PyModule_AddObject(module, "NOTATION_THAI_LAN", PyLong_FromLong(NOTATION_THAI_LAN)); + + // validation + PyModule_AddObject(module, "FEN_OK", PyLong_FromLong(FEN::FEN_OK)); + PyModule_AddObject(module, "FEN_INVALID_PROMOTED_PIECE", PyLong_FromLong(FEN::FEN_INVALID_PROMOTED_PIECE)); + + // initialize stockfish + pieceMap.init(); + variants.init(); + UCI::init(Options); + PSQT::init(variants.find(Options["UCI_Variant"])->second); + Bitboards::init(); + Position::init(); + Bitbases::init(); + Search::init(); + Threads.set(Options["Threads"]); + Search::clear(); // After threads are up + + return module; +}; diff --git a/Fairy-Stockfish-master/src/search.cpp b/Fairy-Stockfish-master/src/search.cpp new file mode 100644 index 0000000000000000000000000000000000000000..651f6bd9ef094bff36f246fee12568250f055492 --- /dev/null +++ b/Fairy-Stockfish-master/src/search.cpp @@ -0,0 +1,2148 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include // For std::memset +#include +#include + +#include "evaluate.h" +#include "misc.h" +#include "movegen.h" +#include "movepick.h" +#include "partner.h" +#include "position.h" +#include "search.h" +#include "thread.h" +#include "timeman.h" +#include "tt.h" +#include "uci.h" +#include "xboard.h" +#include "syzygy/tbprobe.h" + +namespace Stockfish { + +namespace Search { + + LimitsType Limits; +} + +namespace Tablebases { + + int Cardinality; + bool RootInTB; + bool UseRule50; + Depth ProbeDepth; +} + +namespace TB = Tablebases; + +using std::string; +using Eval::evaluate; +using namespace Search; + +namespace { + + // Different node types, used as a template parameter + enum NodeType { NonPV, PV, Root }; + + constexpr uint64_t TtHitAverageWindow = 4096; + constexpr uint64_t TtHitAverageResolution = 1024; + + // Futility margin + Value futility_margin(Depth d, bool improving) { + return Value(214 * (d - improving)); + } + + // Reductions lookup table, initialized at startup + int Reductions[MAX_MOVES]; // [depth or moveNumber] + + Depth reduction(bool i, Depth d, int mn) { + int r = Reductions[d] * Reductions[mn]; + return (r + 534) / 1024 + (!i && r > 904); + } + + int futility_move_count(bool improving, Depth depth, const Position& pos) { + return (3 + depth * depth * (1 + pos.walling()) + 2 * pos.blast_on_capture()) / (2 - improving + pos.blast_on_capture()); + } + + // History and stats update bonus, based on depth + int stat_bonus(Depth d) { + return d > 14 ? 73 : 6 * d * d + 229 * d - 215; + } + + // Add a small random component to draw evaluations to avoid 3-fold blindness + Value value_draw(Thread* thisThread) { + return VALUE_DRAW + Value(2 * (thisThread->nodes & 1) - 1); + } + + // Skill structure is used to implement strength limit + struct Skill { + explicit Skill(int l) : level(l) {} + bool enabled() const { return level < 20; } + bool time_to_pick(Depth depth) const { return depth == 1 + std::max(level, 0); } + Move pick_best(size_t multiPV); + + int level; + Move best = MOVE_NONE; + }; + + template + Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode); + + template + Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = 0); + + Value value_to_tt(Value v, int ply); + Value value_from_tt(Value v, int ply, int r50c); + void update_pv(Move* pv, Move move, Move* childPv); + void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus); + void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus, int depth); + void update_all_stats(const Position& pos, Stack* ss, Move bestMove, Value bestValue, Value beta, Square prevSq, + Move* quietsSearched, int quietCount, Move* capturesSearched, int captureCount, Depth depth); + + // perft() is our utility to verify move generation. All the leaf nodes up + // to the given depth are generated and counted, and the sum is returned. + template + uint64_t perft(Position& pos, Depth depth) { + + StateInfo st; + ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); + + uint64_t cnt, nodes = 0; + const bool leaf = (depth == 2); + + for (const auto& m : MoveList(pos)) + { + assert(pos.pseudo_legal(m)); + if (Root && depth <= 1) + cnt = 1, nodes++; + else + { + pos.do_move(m, st); + cnt = leaf ? MoveList(pos).size() : perft(pos, depth - 1); + nodes += cnt; + pos.undo_move(m); + } + if (Root) + sync_cout << UCI::move(pos, m) << ": " << cnt << sync_endl; + } + return nodes; + } + +} // namespace + + +/// Search::init() is called at startup to initialize various lookup tables + +void Search::init() { + + for (int i = 1; i < MAX_MOVES; ++i) + Reductions[i] = int(21.9 * std::log(i)); +} + + +/// Search::clear() resets search state to its initial value + +void Search::clear() { + + Threads.main()->wait_for_search_finished(); + + Time.availableNodes = 0; + TT.clear(); + Threads.clear(); + Tablebases::init(Options["SyzygyPath"]); // Free mapped files +} + + +/// MainThread::search() is started when the program receives the UCI 'go' +/// command. It searches from the root position and outputs the "bestmove". + +void MainThread::search() { + + if (Limits.perft) + { + nodes = perft(rootPos, Limits.perft); + sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl; + return; + } + + Color us = rootPos.side_to_move(); + Time.init(rootPos, Limits, us, rootPos.game_ply()); + TT.new_search(); + + Eval::NNUE::verify(); + + if (rootMoves.empty() || (CurrentProtocol == 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 (CurrentProtocol == XBOARD) + { + // rotate MOVE_NONE to front (for optional game end) + std::rotate(rootMoves.rbegin(), rootMoves.rbegin() + 1, rootMoves.rend()); + // do not claim when pondering + if (!ponder) + 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(result) + << sync_endl; + } + else + { + Threads.start_searching(); // start non-main threads + Thread::search(); // main thread start searching + } + + // Sit in bughouse variants if partner requested it or we are dead + if (rootPos.two_boards() && !Threads.abort && CurrentProtocol == XBOARD) + { + while (!Threads.stop && (Partner.sitRequested || (Partner.weDead && !Partner.partnerDead)) && Time.elapsed() < Limits.time[us] - 1000) + {} + } + + // When we reach the maximum depth, we can arrive here without a raise of + // Threads.stop. However, if we are pondering or in an infinite search, + // the UCI protocol states that we shouldn't print the best move before the + // GUI sends a "stop" or "ponderhit" command. We therefore simply wait here + // until the GUI sends one of those commands. + + while (!Threads.stop && (ponder || Limits.infinite)) + {} // Busy wait for a stop or a ponder reset + + // Stop the threads if not already stopped (also raise the stop if + // "ponderhit" just reset Threads.ponder). + Threads.stop = true; + + // Wait until all threads have finished + Threads.wait_for_search_finished(); + + // When playing in 'nodes as time' mode, subtract the searched nodes from + // the available ones before exiting. + if (Limits.npmsec) + Time.availableNodes += Limits.inc[us] - Threads.nodes_searched(); + + bestThread = this; + + if ( int(Options["MultiPV"]) == 1 + && !Limits.depth + && !(Skill(Options["Skill Level"]).enabled() || int(Options["UCI_LimitStrength"])) + && rootMoves[0].pv[0] != MOVE_NONE) + bestThread = Threads.get_best_thread(); + + bestPreviousScore = bestThread->rootMoves[0].score; + + // Send again PV info if we have a new best thread + if (bestThread != this) + sync_cout << UCI::pv(bestThread->rootPos, bestThread->completedDepth, -VALUE_INFINITE, VALUE_INFINITE) << sync_endl; + + if (CurrentProtocol == XBOARD) + { + Move bestMove = bestThread->rootMoves[0].pv[0]; + // Wait for virtual drop to become real + if (rootPos.two_boards() && rootPos.virtual_drop(bestMove)) + { + Partner.ptell("fast"); + while (!Threads.abort && !Partner.partnerDead && !Partner.fast && Limits.time[us] - Time.elapsed() > Partner.opptime) + {} + Partner.ptell("x"); + // Find best real move + for (const auto& m : this->rootMoves) + if (!rootPos.virtual_drop(m.pv[0])) + { + bestMove = m.pv[0]; + break; + } + } + // Send move only when not in analyze mode and not at game end + if (!Limits.infinite && !ponder && rootMoves[0].pv[0] != MOVE_NONE && !Threads.abort.exchange(true)) + { + std::string move = UCI::move(rootPos, bestMove); + if (rootPos.walling()) + { + sync_cout << "move " << move.substr(0, move.find(",")) << "," << sync_endl; + sync_cout << "move " << move.substr(move.find(",") + 1) << sync_endl; + } + else + sync_cout << "move " << UCI::move(rootPos, bestMove) << sync_endl; + if (XBoard::stateMachine->moveAfterSearch) + { + XBoard::stateMachine->do_move(bestMove); + XBoard::stateMachine->moveAfterSearch = false; + if (Options["Ponder"] && ( bestThread->rootMoves[0].pv.size() > 1 + || bestThread->rootMoves[0].extract_ponder_from_tt(rootPos))) + XBoard::stateMachine->ponderMove = bestThread->rootMoves[0].pv[1]; + } + } + return; + } + + sync_cout << "bestmove " << UCI::move(rootPos, bestThread->rootMoves[0].pv[0]); + + if (bestThread->rootMoves[0].pv.size() > 1 || bestThread->rootMoves[0].extract_ponder_from_tt(rootPos)) + std::cout << " ponder " << UCI::move(rootPos, bestThread->rootMoves[0].pv[1]); + + std::cout << sync_endl; +} + + +/// Thread::search() is the main iterative deepening loop. It calls search() +/// repeatedly with increasing depth until the allocated thinking time has been +/// consumed, the user stops the search, or the maximum search depth is reached. + +void Thread::search() { + + // To allow access to (ss-7) up to (ss+2), the stack must be oversized. + // The former is needed to allow update_continuation_histories(ss-1, ...), + // which accesses its argument at ss-6, also near the root. + // The latter is needed for statScore and killer initialization. + Stack stack[MAX_PLY+10], *ss = stack+7; + Move pv[MAX_PLY+1]; + Value bestValue, alpha, beta, delta; + Move lastBestMove = MOVE_NONE; + Depth lastBestMoveDepth = 0; + MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr); + double timeReduction = 1, totBestMoveChanges = 0; + Color us = rootPos.side_to_move(); + int iterIdx = 0; + + std::memset(ss-7, 0, 10 * sizeof(Stack)); + for (int i = 7; i > 0; i--) + (ss-i)->continuationHistory = &this->continuationHistory[0][0][NO_PIECE][0]; // Use as a sentinel + + for (int i = 0; i <= MAX_PLY + 2; ++i) + (ss+i)->ply = i; + + ss->pv = pv; + + bestValue = delta = alpha = -VALUE_INFINITE; + beta = VALUE_INFINITE; + + if (mainThread) + { + if (mainThread->bestPreviousScore == VALUE_INFINITE) + for (int i = 0; i < 4; ++i) + mainThread->iterValue[i] = VALUE_ZERO; + else + for (int i = 0; i < 4; ++i) + mainThread->iterValue[i] = mainThread->bestPreviousScore; + } + + std::copy(&lowPlyHistory[2][0], &lowPlyHistory.back().back() + 1, &lowPlyHistory[0][0]); + std::fill(&lowPlyHistory[MAX_LPH - 2][0], &lowPlyHistory.back().back() + 1, 0); + + size_t multiPV = size_t(Options["MultiPV"]); + + // Pick integer skill levels, but non-deterministically round up or down + // such that the average integer skill corresponds to the input floating point one. + // UCI_Elo is converted to a suitable fractional skill level, using anchoring + // to CCRL Elo (goldfish 1.13 = 2000) and a fit through Ordo derived Elo + // for match (TC 60+0.6) results spanning a wide range of k values. + PRNG rng(now()); + double shiftedElo = Options["UCI_Elo"] - 1346.6; + double floatLevel = Options["UCI_LimitStrength"] ? + std::clamp(shiftedElo > 0 ? std::pow(shiftedElo / 143.4, 1 / 0.806) + : shiftedElo / 143.4 + std::pow(shiftedElo / 500, 5), + -20.0, 20.0) : + double(Options["Skill Level"]); + int intLevel = int(floatLevel) + + ((floatLevel - int(floatLevel)) * 1024 > rng.rand() % 1024 ? 1 : 0); + Skill skill(intLevel); + + // When playing with strength handicap enable MultiPV search that we will + // use behind the scenes to retrieve a set of possible moves. + if (skill.enabled()) + multiPV = std::max(multiPV, (size_t)4); + + multiPV = std::min(multiPV, rootMoves.size()); + ttHitAverage = TtHitAverageWindow * TtHitAverageResolution / 2; + + trend = SCORE_ZERO; + + int searchAgainCounter = 0; + + // Iterative deepening loop until requested to stop or the target depth is reached + while ( ++rootDepth < MAX_PLY + && !Threads.stop + && !(Limits.depth && mainThread && rootDepth > Limits.depth)) + { + // Age out PV variability metric + if (mainThread) + totBestMoveChanges /= 2; + + // Save the last iteration's scores before first PV line is searched and + // all the move scores except the (new) PV are set to -VALUE_INFINITE. + for (RootMove& rm : rootMoves) + rm.previousScore = rm.score; + + size_t pvFirst = 0; + pvLast = 0; + + if (!Threads.increaseDepth) + searchAgainCounter++; + + // MultiPV loop. We perform a full root search for each PV line + for (pvIdx = 0; pvIdx < multiPV && !Threads.stop; ++pvIdx) + { + if (pvIdx == pvLast) + { + pvFirst = pvLast; + for (pvLast++; pvLast < rootMoves.size(); pvLast++) + if (rootMoves[pvLast].tbRank != rootMoves[pvFirst].tbRank) + break; + } + + // Reset UCI info selDepth for each depth and each PV line + selDepth = 0; + + // Reset aspiration window starting size + if (rootDepth >= 4) + { + Value prev = rootMoves[pvIdx].previousScore; + delta = Value(17 * (1 + rootPos.captures_to_hand())); + alpha = std::max(prev - delta,-VALUE_INFINITE); + beta = std::min(prev + delta, VALUE_INFINITE); + + // Adjust trend based on root move's previousScore (dynamic contempt) + int tr = 113 * prev / (abs(prev) + 147); + + trend = (us == WHITE ? make_score(tr, tr / 2) + : -make_score(tr, tr / 2)); + } + + // Start with a small aspiration window and, in the case of a fail + // high/low, re-search with a bigger window until we don't fail + // high/low anymore. + int failedHighCnt = 0; + while (true) + { + Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt - searchAgainCounter); + bestValue = Stockfish::search(rootPos, ss, alpha, beta, adjustedDepth, false); + + // Bring the best move to the front. It is critical that sorting + // is done with a stable algorithm because all the values but the + // first and eventually the new best one are set to -VALUE_INFINITE + // and we want to keep the same order for all the moves except the + // new PV that goes to the front. Note that in case of MultiPV + // search the already searched PV lines are preserved. + std::stable_sort(rootMoves.begin() + pvIdx, rootMoves.begin() + pvLast); + + // If search has been stopped, we break immediately. Sorting is + // safe because RootMoves is still valid, although it refers to + // the previous iteration. + if (Threads.stop) + break; + + // When failing high/low give some update (without cluttering + // the UI) before a re-search. + if ( mainThread + && multiPV == 1 + && (bestValue <= alpha || bestValue >= beta) + && Time.elapsed() > 3000) + sync_cout << UCI::pv(rootPos, rootDepth, alpha, beta) << sync_endl; + + // In case of failing low/high increase aspiration window and + // re-search, otherwise exit the loop. + if (bestValue <= alpha) + { + beta = (alpha + beta) / 2; + alpha = std::max(bestValue - delta, -VALUE_INFINITE); + + failedHighCnt = 0; + if (mainThread) + mainThread->stopOnPonderhit = false; + } + else if (bestValue >= beta) + { + beta = std::min(bestValue + delta, VALUE_INFINITE); + ++failedHighCnt; + } + else + break; + + delta += delta / 4 + 5; + + assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE); + } + + // Sort the PV lines searched so far and update the GUI + std::stable_sort(rootMoves.begin() + pvFirst, rootMoves.begin() + pvIdx + 1); + + if ( mainThread + && (Threads.stop || pvIdx + 1 == multiPV || Time.elapsed() > 3000)) + sync_cout << UCI::pv(rootPos, rootDepth, alpha, beta) << sync_endl; + } + + if (!Threads.stop) + completedDepth = rootDepth; + + if (rootMoves[0].pv[0] != lastBestMove) { + lastBestMove = rootMoves[0].pv[0]; + lastBestMoveDepth = rootDepth; + } + + // Have we found a "mate in x"? + if ( Limits.mate + && bestValue >= VALUE_MATE_IN_MAX_PLY + && VALUE_MATE - bestValue <= 2 * Limits.mate) + Threads.stop = true; + + if (!mainThread) + continue; + + // If skill level is enabled and time is up, pick a sub-optimal best move + if (skill.enabled() && skill.time_to_pick(rootDepth)) + skill.pick_best(multiPV); + + // Do we have time for the next iteration? Can we stop searching now? + if ( Limits.use_time_management() + && !Threads.stop + && !mainThread->stopOnPonderhit) + { + double fallingEval = (318 + 6 * (mainThread->bestPreviousScore - bestValue) + + 6 * (mainThread->iterValue[iterIdx] - bestValue)) / 825.0; + fallingEval = std::clamp(fallingEval, 0.5, 1.5); + + // If the bestMove is stable over several iterations, reduce time accordingly + timeReduction = lastBestMoveDepth + 9 < completedDepth ? 1.92 : 0.95; + double reduction = (1.47 + mainThread->previousTimeReduction) / (2.32 * timeReduction); + + // Use part of the gained time from a previous stable move for the current move + for (Thread* th : Threads) + { + totBestMoveChanges += th->bestMoveChanges; + th->bestMoveChanges = 0; + } + double bestMoveInstability = 1.073 + std::max(1.0, 2.25 - 9.9 / rootDepth) + * totBestMoveChanges / Threads.size(); + double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability; + + // Cap used time in case of a single legal move for a better viewer experience in tournaments + // yielding correct scores and sufficiently fast moves. + if (rootMoves.size() == 1) + totalTime = std::min(500.0, totalTime); + + // Update partner in bughouse variants + if (completedDepth >= 8 && rootPos.two_boards() && CurrentProtocol == XBOARD) + { + // Communicate clock times relevant for sitting decisions + if (Limits.time[us]) + Partner.ptell("time " + std::to_string((Limits.time[us] - Time.elapsed()) / 10)); + if (Limits.time[~us]) + Partner.ptell("otim " + std::to_string(Limits.time[~us] / 10)); + // We are dead and need to sit + if (!Partner.weDead && bestValue <= VALUE_MATED_IN_MAX_PLY) + { + Partner.ptell("dead"); + Partner.weDead = true; + } + // We were dead but are fine again + else if (Partner.weDead && bestValue > VALUE_MATED_IN_MAX_PLY) + { + Partner.ptell("x"); + Partner.weDead = false; + } + // We win by force, so partner should sit + else if (!Partner.weWin && bestValue >= VALUE_MATE_IN_MAX_PLY && Limits.time[~us] < Partner.time) + { + Partner.ptell("sit"); + Partner.weWin = true; + } + // We are no longer winning + else if (Partner.weWin && (bestValue < VALUE_MATE_IN_MAX_PLY || Limits.time[~us] > Partner.time)) + { + Partner.ptell("x"); + Partner.weWin = false; + } + // We can win if partner delivers required material quickly + else if ( !Partner.weVirtualWin + && bestValue >= VALUE_VIRTUAL_MATE_IN_MAX_PLY + && bestValue <= VALUE_VIRTUAL_MATE + && Limits.time[us] - Time.elapsed() > Partner.opptime) + { + Partner.ptell("fast"); + Partner.weVirtualWin = true; + } + // Virtual mate is gone + else if ( Partner.weVirtualWin + && (bestValue < VALUE_VIRTUAL_MATE_IN_MAX_PLY || bestValue > VALUE_VIRTUAL_MATE || Limits.time[us] - Time.elapsed() < Partner.opptime)) + { + Partner.ptell("slow"); + Partner.weVirtualWin = false; + } + // We need to survive a virtual mate and play fast + else if ( !Partner.weVirtualLoss + && (bestValue <= -VALUE_VIRTUAL_MATE_IN_MAX_PLY && bestValue >= -VALUE_VIRTUAL_MATE) + && Limits.time[~us] > Partner.time) + { + Partner.ptell("sit"); + Partner.weVirtualLoss = true; + Partner.fast = true; + } + // Virtual mate threat is over + else if ( Partner.weVirtualLoss + && (bestValue > -VALUE_VIRTUAL_MATE_IN_MAX_PLY || bestValue < -VALUE_VIRTUAL_MATE || Limits.time[~us] < Partner.time)) + { + Partner.ptell("x"); + Partner.weVirtualLoss = false; + Partner.fast = false; + } + } + + // Stop the search if we have exceeded the totalTime + if (Time.elapsed() > totalTime) + { + // If we are allowed to ponder do not stop the search now but + // keep pondering until the GUI sends "ponderhit" or "stop". + if (mainThread->ponder) + mainThread->stopOnPonderhit = true; + else if (!(rootPos.two_boards() && (Partner.sitRequested || Partner.weDead))) + Threads.stop = true; + } + else if ( Threads.increaseDepth + && !mainThread->ponder + && Time.elapsed() > totalTime * 0.58) + Threads.increaseDepth = false; + else + Threads.increaseDepth = true; + } + + mainThread->iterValue[iterIdx] = bestValue; + iterIdx = (iterIdx + 1) & 3; + } + + if (!mainThread) + return; + + mainThread->previousTimeReduction = timeReduction; + + // If skill level is enabled, swap best PV line with the sub-optimal one + if (skill.enabled()) + std::swap(rootMoves[0], *std::find(rootMoves.begin(), rootMoves.end(), + skill.best ? skill.best : skill.pick_best(multiPV))); +} + + +namespace { + + // search<>() is the main search function for both PV and non-PV nodes + + template + Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) { + + constexpr bool PvNode = nodeType != NonPV; + constexpr bool rootNode = nodeType == Root; + const Depth maxNextDepth = rootNode ? depth : depth + 1; + + // Check if we have an upcoming move which draws by repetition, or + // if the opponent had an alternative move earlier to this position. + if ( !rootNode + && pos.rule50_count() >= 3 + && alpha < VALUE_DRAW + && pos.has_game_cycle(ss->ply)) + { + alpha = value_draw(pos.this_thread()); + if (alpha >= beta) + return alpha; + } + + // Dive into quiescence search when the depth reaches zero + if (depth <= 0) + return qsearch(pos, ss, alpha, beta); + + assert(-VALUE_INFINITE <= alpha && alpha < beta && beta <= VALUE_INFINITE); + assert(PvNode || (alpha == beta - 1)); + assert(0 < depth && depth < MAX_PLY); + assert(!(PvNode && cutNode)); + + Move pv[MAX_PLY+1], capturesSearched[32], quietsSearched[64]; + StateInfo st; + ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); + + TTEntry* tte; + Key posKey; + Move ttMove, move, excludedMove, bestMove; + Depth extension, newDepth; + Value bestValue, value, ttValue, eval, maxValue, probCutBeta; + bool givesCheck, improving, didLMR, priorCapture; + bool captureOrPromotion, doFullDepthSearch, moveCountPruning, + ttCapture, singularQuietLMR; + Piece movedPiece; + int moveCount, captureCount, quietCount; + + // Step 1. Initialize node + Thread* thisThread = pos.this_thread(); + ss->inCheck = pos.checkers(); + priorCapture = pos.captured_piece(); + Color us = pos.side_to_move(); + moveCount = captureCount = quietCount = ss->moveCount = 0; + bestValue = -VALUE_INFINITE; + maxValue = VALUE_INFINITE; + + // Check for the available remaining time + if (thisThread == Threads.main()) + static_cast(thisThread)->check_time(); + + // Used to send selDepth info to GUI (selDepth counts from 1, ply from 0) + if (PvNode && thisThread->selDepth < ss->ply + 1) + thisThread->selDepth = ss->ply + 1; + + if (!rootNode) + { + Value variantResult; + if (pos.is_game_end(variantResult, ss->ply)) + return variantResult; + + // Step 2. Check for aborted search and immediate draw + if ( Threads.stop.load(std::memory_order_relaxed) + || ss->ply >= MAX_PLY) + return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) + : value_draw(pos.this_thread()); + + // Step 3. Mate distance pruning. Even if we mate at the next move our score + // would be at best mate_in(ss->ply+1), but if alpha is already bigger because + // a shorter mate was found upward in the tree then there is no need to search + // because we will never beat the current alpha. Same logic but with reversed + // signs applies also in the opposite condition of being mated instead of giving + // mate. In this case return a fail-high score. + alpha = std::max(mated_in(ss->ply), alpha); + beta = std::min(mate_in(ss->ply+1), beta); + if (alpha >= beta) + return alpha; + } + + assert(0 <= ss->ply && ss->ply < MAX_PLY); + + (ss+1)->ttPv = false; + (ss+1)->excludedMove = bestMove = MOVE_NONE; + (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE; + ss->doubleExtensions = (ss-1)->doubleExtensions; + Square prevSq = to_sq((ss-1)->currentMove); + + // Initialize statScore to zero for the grandchildren of the current position. + // So statScore is shared between all grandchildren and only the first grandchild + // starts with statScore = 0. Later grandchildren start with the last calculated + // statScore of the previous grandchild. This influences the reduction rules in + // LMR which are based on the statScore of parent position. + if (!rootNode) + (ss+2)->statScore = 0; + + // Step 4. Transposition table lookup. We don't want the score of a partial + // search to overwrite a previous full search TT value, so we use a different + // position key in case of an excluded move. + excludedMove = ss->excludedMove; + posKey = excludedMove == MOVE_NONE ? pos.key() : pos.key() ^ make_key(excludedMove); + tte = TT.probe(posKey, ss->ttHit); + ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; + ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0] + : ss->ttHit ? tte->move() : MOVE_NONE; + if (!excludedMove) + ss->ttPv = PvNode || (ss->ttHit && tte->is_pv()); + + // Update low ply history for previous move if we are near root and position is or has been in PV + if ( ss->ttPv + && depth > 12 + && ss->ply - 1 < MAX_LPH + && !priorCapture + && is_ok((ss-1)->currentMove)) + thisThread->lowPlyHistory[ss->ply - 1][from_to((ss-1)->currentMove)] << stat_bonus(depth - 5); + + // thisThread->ttHitAverage can be used to approximate the running average of ttHit + thisThread->ttHitAverage = (TtHitAverageWindow - 1) * thisThread->ttHitAverage / TtHitAverageWindow + + TtHitAverageResolution * ss->ttHit; + + // At non-PV nodes we check for an early TT cutoff + if ( !PvNode + && ss->ttHit + && tte->depth() >= depth + && ttValue != VALUE_NONE // Possible in case of TT access race + && (ttValue >= beta ? (tte->bound() & BOUND_LOWER) + : (tte->bound() & BOUND_UPPER))) + { + // If ttMove is quiet, update move sorting heuristics on TT hit + if (ttMove) + { + if (ttValue >= beta) + { + // Bonus for a quiet ttMove that fails high + if (!pos.capture_or_promotion(ttMove)) + update_quiet_stats(pos, ss, ttMove, stat_bonus(depth), depth); + + // Extra penalty for early quiet moves of the previous ply + if ((ss-1)->moveCount <= 2 && !priorCapture) + update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + 1)); + } + // Penalty for a quiet ttMove that fails low + else if (!pos.capture_or_promotion(ttMove)) + { + int penalty = -stat_bonus(depth); + thisThread->mainHistory[us][from_to(ttMove)] << penalty; + if (pos.walling()) + thisThread->gateHistory[us][gating_square(ttMove)] << penalty; + update_continuation_histories(ss, pos.moved_piece(ttMove), to_sq(ttMove), penalty); + } + } + + // Partial workaround for the graph history interaction problem + // For high rule50 counts don't produce transposition table cutoffs. + if (pos.rule50_count() < 90) + return ttValue; + } + + // Step 5. Tablebases probe + if (!rootNode && TB::Cardinality) + { + int piecesCount = pos.count(); + + if ( piecesCount <= TB::Cardinality + && (piecesCount < TB::Cardinality || depth >= TB::ProbeDepth) + && pos.rule50_count() == 0 + && Options["UCI_Variant"] == "chess" + && !pos.can_castle(ANY_CASTLING)) + { + TB::ProbeState err; + TB::WDLScore wdl = Tablebases::probe_wdl(pos, &err); + + // Force check of time on the next occasion + if (thisThread == Threads.main()) + static_cast(thisThread)->callsCnt = 0; + + if (err != TB::ProbeState::FAIL) + { + thisThread->tbHits.fetch_add(1, std::memory_order_relaxed); + + int drawScore = TB::UseRule50 ? 1 : 0; + + // use the range VALUE_MATE_IN_MAX_PLY to VALUE_TB_WIN_IN_MAX_PLY to score + value = wdl < -drawScore ? VALUE_MATED_IN_MAX_PLY + ss->ply + 1 + : wdl > drawScore ? VALUE_MATE_IN_MAX_PLY - ss->ply - 1 + : VALUE_DRAW + 2 * wdl * drawScore; + + Bound b = wdl < -drawScore ? BOUND_UPPER + : wdl > drawScore ? BOUND_LOWER : BOUND_EXACT; + + if ( b == BOUND_EXACT + || (b == BOUND_LOWER ? value >= beta : value <= alpha)) + { + tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, b, + std::min(MAX_PLY - 1, depth + 6), + MOVE_NONE, VALUE_NONE); + + return value; + } + + if (PvNode) + { + if (b == BOUND_LOWER) + bestValue = value, alpha = std::max(alpha, bestValue); + else + maxValue = value; + } + } + } + } + + CapturePieceToHistory& captureHistory = thisThread->captureHistory; + + // Step 6. Static evaluation of the position + if (ss->inCheck) + { + // Skip early pruning when in check + ss->staticEval = eval = VALUE_NONE; + improving = false; + goto moves_loop; + } + else if (ss->ttHit) + { + // Never assume anything about values stored in TT + ss->staticEval = eval = tte->eval(); + if (eval == VALUE_NONE) + ss->staticEval = eval = evaluate(pos); + + // Randomize draw evaluation + if (eval == VALUE_DRAW) + eval = value_draw(thisThread); + + // Can ttValue be used as a better position evaluation? + if ( ttValue != VALUE_NONE + && (tte->bound() & (ttValue > eval ? BOUND_LOWER : BOUND_UPPER))) + eval = ttValue; + } + else + { + // In case of null move search use previous static eval with a different sign + // and addition of two tempos + if ((ss-1)->currentMove != MOVE_NULL) + ss->staticEval = eval = evaluate(pos); + else + ss->staticEval = eval = -(ss-1)->staticEval; + + // Save static evaluation into transposition table + tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval); + } + + // Use static evaluation difference to improve quiet move ordering + if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture) + { + int bonus = std::clamp(-depth * 4 * int((ss-1)->staticEval + ss->staticEval), -1000, 1000); + thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << bonus; + } + + // Set up improving flag that is used in various pruning heuristics + // We define position as improving if static evaluation of position is better + // Than the previous static evaluation at our turn + // In case of us being in check at our previous move we look at move prior to it + 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() && pos.has_capture()) + goto moves_loop; + + // Step 7. Futility pruning: child node (~50 Elo) + if ( !PvNode + && depth < 9 - 3 * pos.blast_on_capture() + && eval - futility_margin(depth, improving) * (1 + pos.check_counting() + 2 * pos.must_capture() + pos.extinction_single_piece() + !pos.checking_permitted()) >= beta + && eval < VALUE_KNOWN_WIN) // Do not return unproven wins + return eval; + + // Step 8. Null move search with verification search (~40 Elo) + if ( !PvNode + && (ss-1)->currentMove != MOVE_NULL + && (ss-1)->statScore < 23767 + && eval >= beta + && eval >= ss->staticEval + && ss->staticEval >= beta - 20 * depth - 22 * improving + 168 * ss->ttPv + 159 + 200 * (!pos.double_step_region(pos.side_to_move()) && (pos.piece_types() & PAWN)) + && !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 = (1090 - 300 * pos.must_capture() - 250 * !pos.checking_permitted() + 81 * depth) / 256 + std::min(int(eval - beta) / 205, pos.must_capture() || pos.blast_on_capture() ? 0 : 3); + + ss->currentMove = MOVE_NULL; + ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; + + pos.do_null_move(st); + + Value nullValue = -search(pos, ss+1, -beta, -beta+1, depth-R, !cutNode); + + pos.undo_null_move(); + + if (nullValue >= beta) + { + // Do not return unproven mate or TB scores + if (nullValue >= VALUE_TB_WIN_IN_MAX_PLY) + nullValue = beta; + + if (thisThread->nmpMinPly || (abs(beta) < VALUE_KNOWN_WIN && depth < 14)) + return nullValue; + + assert(!thisThread->nmpMinPly); // Recursive verification is not allowed + + // Do verification search at high depths, with null move pruning disabled + // for us, until ply exceeds nmpMinPly. + thisThread->nmpMinPly = ss->ply + 3 * (depth-R) / 4; + thisThread->nmpColor = us; + + Value v = search(pos, ss, beta-1, beta, depth-R, false); + + thisThread->nmpMinPly = 0; + + if (v >= beta) + return nullValue; + } + } + + probCutBeta = beta + (209 + 20 * !!pos.flag_region(~pos.side_to_move()) + 50 * pos.captures_to_hand()) * (1 + pos.check_counting() + pos.extinction_single_piece()) - 44 * improving; + + // Step 9. ProbCut (~4 Elo) + // If we have a good enough capture and a reduced search returns a value + // much above beta, we can (almost) safely prune the previous move. + if ( !PvNode + && depth > 4 + && abs(beta) < VALUE_TB_WIN_IN_MAX_PLY + // if value from transposition table is lower than probCutBeta, don't attempt probCut + // there and in further interactions with transposition table cutoff depth is set to depth - 3 + // because probCut search has depth set to depth - 4 but we also do a move before it + // so effective depth is equal to depth - 3 + && !( ss->ttHit + && tte->depth() >= depth - 3 + && ttValue != VALUE_NONE + && ttValue < probCutBeta)) + { + assert(probCutBeta < VALUE_INFINITE); + + MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &thisThread->gateHistory, &captureHistory); + int probCutCount = 0; + bool ttPv = ss->ttPv; + ss->ttPv = false; + + while ( (move = mp.next_move()) != MOVE_NONE + && probCutCount < 2 + 2 * cutNode) + if (move != excludedMove && pos.legal(move)) + { + assert(pos.capture_or_promotion(move)); + assert(depth >= 5); + + captureOrPromotion = true; + probCutCount++; + + ss->currentMove = move; + ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] + [captureOrPromotion] + [history_slot(pos.moved_piece(move))] + [to_sq(move)]; + + pos.do_move(move, st); + + // Perform a preliminary qsearch to verify that the move holds + value = -qsearch(pos, ss+1, -probCutBeta, -probCutBeta+1); + + // If the qsearch held, perform the regular search + if (value >= probCutBeta) + value = -search(pos, ss+1, -probCutBeta, -probCutBeta+1, depth - 4, !cutNode); + + pos.undo_move(move); + + if (value >= probCutBeta) + { + // if transposition table doesn't have equal or more deep info write probCut data into it + if ( !(ss->ttHit + && tte->depth() >= depth - 3 + && ttValue != VALUE_NONE)) + tte->save(posKey, value_to_tt(value, ss->ply), ttPv, + BOUND_LOWER, + depth - 3, move, ss->staticEval); + return value; + } + } + ss->ttPv = ttPv; + } + + // Step 10. If the position is not in TT, decrease depth by 2 + if ( PvNode + && depth >= 6 + && !ttMove) + depth -= 2; + +moves_loop: // When in check, search starts from here + + ttCapture = ttMove && pos.capture_or_promotion(ttMove); + + // Step 11. A small Probcut idea, when we are in check + probCutBeta = beta + 409; + if ( ss->inCheck + && !PvNode + && depth >= 4 + && ttCapture + && (tte->bound() & BOUND_LOWER) + && tte->depth() >= depth - 3 + && ttValue >= probCutBeta + && abs(ttValue) <= VALUE_KNOWN_WIN + && abs(beta) <= VALUE_KNOWN_WIN + ) + return probCutBeta; + + + const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, + nullptr , (ss-4)->continuationHistory, + nullptr , (ss-6)->continuationHistory }; + + Move countermove = thisThread->counterMoves[pos.piece_on(prevSq)][prevSq]; + + MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, + &thisThread->gateHistory, + &thisThread->lowPlyHistory, + &captureHistory, + contHist, + countermove, + ss->killers, + ss->ply); + + value = bestValue; + singularQuietLMR = moveCountPruning = false; + bool doubleExtension = false; + + // Indicate PvNodes that will probably fail low if the node was searched + // at a depth equal or greater than the current depth, and the result of this search was a fail low. + bool likelyFailLow = PvNode + && ttMove + && (tte->bound() & BOUND_UPPER) + && tte->depth() >= depth; + + // Step 12. Loop through all pseudo-legal moves until no moves remain + // or a beta cutoff occurs. + while ((move = mp.next_move(moveCountPruning)) != MOVE_NONE) + { + assert(is_ok(move)); + + if (move == excludedMove) + continue; + + // At root obey the "searchmoves" option and skip moves not listed in Root + // Move List. As a consequence any illegal move is also skipped. In MultiPV + // mode we also skip PV moves which have been already searched and those + // of lower "TB rank" if we are in a TB root position. + if (rootNode && !std::count(thisThread->rootMoves.begin() + thisThread->pvIdx, + 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 && is_uci_dialect(CurrentProtocol)) + sync_cout << "info depth " << depth + << " currmove " << UCI::move(pos, move) + << " currmovenumber " << moveCount + thisThread->pvIdx << sync_endl; + if (PvNode) + (ss+1)->pv = nullptr; + + extension = 0; + captureOrPromotion = pos.capture_or_promotion(move); + movedPiece = pos.moved_piece(move); + givesCheck = pos.gives_check(move); + + // Calculate new depth for this move + newDepth = depth - 1; + + // Step 13. Pruning at shallow depth (~200 Elo) + if ( !rootNode + && (pos.non_pawn_material(us) || pos.count(us) == pos.count(us)) + && bestValue > VALUE_TB_LOSS_IN_MAX_PLY) + { + // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold + moveCountPruning = moveCount >= futility_move_count(improving, depth, pos); + + // Reduced depth of the next LMR search + int lmrDepth = std::max(newDepth - reduction(improving, depth, moveCount), 0); + + if (pos.must_capture() && pos.attackers_to(to_sq(move), ~us)) + {} + else + + if ( captureOrPromotion + || givesCheck) + { + // Capture history based pruning when the move doesn't give check + if ( !givesCheck + && lmrDepth < 1 + && captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] < 0) + continue; + + // SEE based pruning + if (!pos.see_ge(move, Value(-218 - 120 * pos.captures_to_hand()) * depth)) // (~25 Elo) + continue; + } + else + { + // Continuation history based pruning (~20 Elo) + if ( lmrDepth < 5 + && (*contHist[0])[history_slot(movedPiece)][to_sq(move)] < CounterMovePruneThreshold + && (*contHist[1])[history_slot(movedPiece)][to_sq(move)] < CounterMovePruneThreshold) + continue; + + // Futility pruning: parent node (~5 Elo) + if ( lmrDepth < 7 + && !ss->inCheck + && !pos.extinction_single_piece() + && ss->staticEval + (174 + 157 * 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)] / 3 < 28255) + continue; + + // Prune moves with negative SEE (~20 Elo) + if (!(pos.walling_rule() == DUCK) && !pos.see_ge(move, Value(-(30 - std::min(lmrDepth, 18) + 10 * !!pos.flag_region(pos.side_to_move())) * lmrDepth * lmrDepth))) + continue; + } + } + + // Step 14. Extensions (~75 Elo) + + // Singular extension search (~70 Elo). If all moves but one fail low on a + // search of (alpha-s, beta-s), and just one fails high on (alpha, beta), + // then that move is singular and should be extended. To verify this we do + // a reduced search on all the other moves but the ttMove and if the + // result is lower than ttValue minus a margin, then we will extend the ttMove. + if ( !rootNode + && depth >= 7 - 2 * (pos.count() == 1) + && move == ttMove + && !excludedMove // Avoid recursive singular search + /* && ttValue != VALUE_NONE Already implicit in the next condition */ + && abs(ttValue) < VALUE_KNOWN_WIN + && (tte->bound() & BOUND_LOWER) + && tte->depth() >= depth - 3) + { + Value singularBeta = ttValue - 2 * depth; + Depth singularDepth = (depth - 1) / 2; + + ss->excludedMove = move; + value = search(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode); + ss->excludedMove = MOVE_NONE; + + if (value < singularBeta) + { + extension = 1; + singularQuietLMR = !ttCapture; + + // Avoid search explosion by limiting the number of double extensions to at most 3 + if ( !PvNode + && value < singularBeta - 93 + && ss->doubleExtensions < 3) + { + extension = 2; + doubleExtension = true; + } + } + + // Multi-cut pruning + // Our ttMove is assumed to fail high, and now we failed high also on a reduced + // search without the ttMove. So we assume this expected Cut-node is not singular, + // that multiple moves fail high, and we can prune the whole subtree by returning + // a soft bound. + else if (singularBeta >= beta) + return singularBeta; + + // If the eval of ttMove is greater than beta we try also if there is another + // move that pushes it over beta, if so also produce a cutoff. + else if (ttValue >= beta) + { + ss->excludedMove = move; + value = search(pos, ss, beta - 1, beta, (depth + 3) / 2, cutNode); + ss->excludedMove = MOVE_NONE; + + if (value >= beta) + return beta; + } + } + else if ( givesCheck + && depth > 6 + && abs(ss->staticEval) > Value(100)) + extension = 1; + + // Losing chess capture extension + else if ( pos.must_capture() + && pos.capture(move) + && (ss->inCheck || MoveList(pos).size() == 1)) + extension = 1; + + // Add extension to new depth + newDepth += extension; + ss->doubleExtensions = (ss-1)->doubleExtensions + (extension == 2); + + // Speculative prefetch as early as possible + // Update the current move (this must be done after singular extension search) + ss->currentMove = move; + ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] + [captureOrPromotion] + [history_slot(movedPiece)] + [to_sq(move)]; + + // Step 15. Make the move + pos.do_move(move, st, givesCheck); + + // Step 16. Late moves reduction / extension (LMR, ~200 Elo) + // We use various heuristics for the sons of a node after the first son has + // been searched. In general we would like to reduce them, but there are many + // cases where we extend a son if it has good chances to be "interesting". + if ( depth >= 3 + && moveCount > 1 + 2 * rootNode + && !(pos.must_capture() && pos.has_capture()) + && ( !captureOrPromotion + || (cutNode && (ss-1)->moveCount > 1) + || !ss->ttPv) + && (!PvNode || ss->ply > 1 || thisThread->id() % 4 != 3)) + { + Depth r = reduction(improving, depth, moveCount); + + if (PvNode) + r--; + + // Decrease reduction if the ttHit running average is large (~0 Elo) + if (thisThread->ttHitAverage > 537 * TtHitAverageResolution * TtHitAverageWindow / 1024) + r--; + + // Decrease reduction if position is or has been on the PV + // and node is not likely to fail low. (~3 Elo) + if ( ss->ttPv + && !likelyFailLow) + r -= 2; + + // Increase reduction at root and non-PV nodes when the best move does not change frequently + if ( (rootNode || !PvNode) + && thisThread->bestMoveChanges <= 2) + r++; + + // Decrease reduction if opponent's move count is high (~1 Elo) + if ((ss-1)->moveCount > 13) + r--; + + // Decrease reduction if ttMove has been singularly extended (~1 Elo) + if (singularQuietLMR) + r--; + + // Increase reduction for cut nodes (~3 Elo) + if (cutNode) + r += 1 + !captureOrPromotion; + + if (!captureOrPromotion) + { + // Increase reduction if ttMove is a capture (~3 Elo) + if (ttCapture) + r++; + + ss->statScore = thisThread->mainHistory[us][from_to(move)] + + thisThread->gateHistory[us][gating_square(move)] * 2 + + (*contHist[0])[history_slot(movedPiece)][to_sq(move)] + + (*contHist[1])[history_slot(movedPiece)][to_sq(move)] + + (*contHist[3])[history_slot(movedPiece)][to_sq(move)] + - 4923; + + // Decrease/increase reduction for moves with a good/bad history (~30 Elo) + if (!ss->inCheck) + r -= ss->statScore / (14721 - 4434 * pos.captures_to_hand()); + } + + // In general we want to cap the LMR depth search at newDepth. But if + // reductions are really negative and movecount is low, we allow this move + // to be searched deeper than the first move, unless ttMove was extended by 2. + Depth d = std::clamp(newDepth - r, 1, newDepth + (r < -1 && moveCount <= 5 && !doubleExtension)); + + value = -search(pos, ss+1, -(alpha+1), -alpha, d, true); + + // If the son is reduced and fails high it will be re-searched at full depth + doFullDepthSearch = value > alpha && d < newDepth; + didLMR = true; + } + else + { + doFullDepthSearch = !PvNode || moveCount > 1; + didLMR = false; + } + + // Step 17. Full depth search when LMR is skipped or fails high + if (doFullDepthSearch) + { + value = -search(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode); + + // If the move passed LMR update its stats + if (didLMR && !captureOrPromotion) + { + int bonus = value > alpha ? stat_bonus(newDepth) + : -stat_bonus(newDepth); + + update_continuation_histories(ss, movedPiece, to_sq(move), bonus); + } + } + + // For PV nodes only, do a full PV search on the first move or after a fail + // high (in the latter case search only if value < beta), otherwise let the + // parent node fail low with value <= alpha and try another move. + if (PvNode && (moveCount == 1 || (value > alpha && (rootNode || value < beta)))) + { + (ss+1)->pv = pv; + (ss+1)->pv[0] = MOVE_NONE; + + value = -search(pos, ss+1, -beta, -alpha, + std::min(maxNextDepth, newDepth), false); + } + + // Step 18. Undo move + pos.undo_move(move); + + assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); + + // Step 19. Check for a new best move + // Finished searching the move. If a stop occurred, the return value of + // the search cannot be trusted, and we return immediately without + // updating best move, PV and TT. + if (Threads.stop.load(std::memory_order_relaxed)) + return VALUE_ZERO; + + if (rootNode) + { + RootMove& rm = *std::find(thisThread->rootMoves.begin(), + thisThread->rootMoves.end(), move); + + // PV move or new best move? + if (moveCount == 1 || value > alpha) + { + rm.score = value; + rm.selDepth = thisThread->selDepth; + rm.pv.resize(1); + + assert((ss+1)->pv); + + for (Move* m = (ss+1)->pv; *m != MOVE_NONE; ++m) + rm.pv.push_back(*m); + + // We record how often the best move has been changed in each + // iteration. This information is used for time management and LMR + if (moveCount > 1) + ++thisThread->bestMoveChanges; + } + else + // All other moves but the PV are set to the lowest value: this + // is not a problem when sorting because the sort is stable and the + // move position in the list is preserved - just the PV is pushed up. + rm.score = -VALUE_INFINITE; + } + + if (value > bestValue) + { + bestValue = value; + + if (value > alpha) + { + bestMove = move; + + if (PvNode && !rootNode) // Update pv even in fail-high case + update_pv(ss->pv, move, (ss+1)->pv); + + if (PvNode && value < beta) // Update alpha! Always alpha < beta + alpha = value; + else + { + assert(value >= beta); // Fail high + break; + } + } + } + + // If the move is worse than some previously searched move, remember it to update its stats later + if (move != bestMove) + { + if (captureOrPromotion && captureCount < 32) + capturesSearched[captureCount++] = move; + + else if (!captureOrPromotion && quietCount < 64) + quietsSearched[quietCount++] = move; + } + } + + // The following condition would detect a stop only after move loop has been + // completed. But in this case bestValue is valid because we have fully + // searched our subtree, and we can anyhow save the result in TT. + /* + if (Threads.stop) + return VALUE_DRAW; + */ + + // Step 20. Check for mate and stalemate + // All legal moves have been searched and if there are no legal moves, it + // must be a mate or a stalemate. If we are in a singular extension search then + // return a fail low score. + + assert(moveCount || !ss->inCheck || excludedMove || !MoveList(pos).size()); + + if (!moveCount) + bestValue = excludedMove ? alpha : + ss->inCheck ? pos.checkmate_value(ss->ply) + : pos.stalemate_value(ss->ply); + + // If there is a move which produces search value greater than alpha we update stats of searched moves + else if (bestMove) + update_all_stats(pos, ss, bestMove, bestValue, beta, prevSq, + quietsSearched, quietCount, capturesSearched, captureCount, depth); + + // Bonus for prior countermove that caused the fail low + else if ( (depth >= 3 || PvNode) + && !priorCapture) + update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth)); + + if (PvNode) + bestValue = std::min(bestValue, maxValue); + + // If no good move is found and the previous position was ttPv, then the previous + // opponent move is probably good and the new position is added to the search tree. + if (bestValue <= alpha) + ss->ttPv = ss->ttPv || ((ss-1)->ttPv && depth > 3); + // Otherwise, a counter move has been found and if the position is the last leaf + // in the search tree, remove the position from the search tree. + else if (depth > 3) + ss->ttPv = ss->ttPv && (ss+1)->ttPv; + + // Write gathered information in transposition table + if (!excludedMove && !(rootNode && thisThread->pvIdx)) + tte->save(posKey, value_to_tt(bestValue, ss->ply), ss->ttPv, + bestValue >= beta ? BOUND_LOWER : + PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER, + depth, bestMove, ss->staticEval); + + assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); + + return bestValue; + } + + + // qsearch() is the quiescence search function, which is called by the main search + // function with zero depth, or recursively with further decreasing depth per call. + template + Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { + + static_assert(nodeType != Root); + constexpr bool PvNode = nodeType == PV; + + assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE); + assert(PvNode || (alpha == beta - 1)); + assert(depth <= 0); + + Move pv[MAX_PLY+1]; + StateInfo st; + ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); + + TTEntry* tte; + Key posKey; + Move ttMove, move, bestMove; + Depth ttDepth; + Value bestValue, value, ttValue, futilityValue, futilityBase, oldAlpha; + bool pvHit, givesCheck, captureOrPromotion; + int moveCount; + + if (PvNode) + { + oldAlpha = alpha; // To flag BOUND_EXACT when eval above alpha and no available moves + (ss+1)->pv = pv; + ss->pv[0] = MOVE_NONE; + } + + Thread* thisThread = pos.this_thread(); + bestMove = MOVE_NONE; + ss->inCheck = pos.checkers(); + moveCount = 0; + + Value gameResult; + if (pos.is_game_end(gameResult, ss->ply)) + return gameResult; + + // Check for maximum ply reached + if (ss->ply >= MAX_PLY) + return !ss->inCheck ? evaluate(pos) : VALUE_DRAW; + + // Safeguard against too deep recursions in quiescence search + if (depth < DEPTH_QS_MAX && !ss->inCheck) + return evaluate(pos); + + assert(0 <= ss->ply && ss->ply < MAX_PLY); + + // Decide whether or not to include checks: this fixes also the type of + // TT entry depth that we are going to use. Note that in qsearch we use + // only two types of depth in TT: DEPTH_QS_CHECKS or DEPTH_QS_NO_CHECKS. + ttDepth = ss->inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS + : DEPTH_QS_NO_CHECKS; + // Transposition table lookup + posKey = pos.key(); + tte = TT.probe(posKey, ss->ttHit); + ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; + ttMove = ss->ttHit ? tte->move() : MOVE_NONE; + pvHit = ss->ttHit && tte->is_pv(); + + if ( !PvNode + && ss->ttHit + && tte->depth() >= ttDepth + && ttValue != VALUE_NONE // Only in case of TT access race + && (ttValue >= beta ? (tte->bound() & BOUND_LOWER) + : (tte->bound() & BOUND_UPPER))) + return ttValue; + + // Evaluate the position statically + if (ss->inCheck) + { + ss->staticEval = VALUE_NONE; + bestValue = futilityBase = -VALUE_INFINITE; + } + else + { + if (ss->ttHit) + { + // Never assume anything about values stored in TT + if ((ss->staticEval = bestValue = tte->eval()) == VALUE_NONE) + ss->staticEval = bestValue = evaluate(pos); + + // Can ttValue be used as a better position evaluation? + if ( ttValue != VALUE_NONE + && (tte->bound() & (ttValue > bestValue ? BOUND_LOWER : BOUND_UPPER))) + bestValue = ttValue; + } + else + // In case of null move search use previous static eval with a different sign + // and addition of two tempos + ss->staticEval = bestValue = + (ss-1)->currentMove != MOVE_NULL ? evaluate(pos) + : -(ss-1)->staticEval; + + // Stand pat. Return immediately if static value is at least beta + if (bestValue >= beta) + { + // Save gathered info in transposition table + if (!ss->ttHit) + tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, + DEPTH_NONE, MOVE_NONE, ss->staticEval); + + return bestValue; + } + + if (PvNode && bestValue > alpha) + alpha = bestValue; + + futilityBase = bestValue + 155; + } + + const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory, + nullptr , (ss-4)->continuationHistory, + nullptr , (ss-6)->continuationHistory }; + + // Initialize a MovePicker object for the current position, and prepare + // to search the moves. Because the depth is <= 0 here, only captures, + // queen promotions, and other checks (only if depth >= DEPTH_QS_CHECKS) + // will be generated. + MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, + &thisThread->gateHistory, + &thisThread->captureHistory, + contHist, + to_sq((ss-1)->currentMove)); + + // Loop through the moves until no moves remain or a beta cutoff occurs + while ((move = mp.next_move()) != MOVE_NONE) + { + assert(is_ok(move)); + + givesCheck = pos.gives_check(move); + captureOrPromotion = pos.capture_or_promotion(move); + + moveCount++; + + // Futility pruning and moveCount pruning + if ( bestValue > VALUE_TB_LOSS_IN_MAX_PLY + && !givesCheck + && !( pos.extinction_value() == -VALUE_MATE + && pos.piece_on(to_sq(move)) + && (pos.extinction_piece_types() & type_of(pos.piece_on(to_sq(move))))) + && futilityBase > -VALUE_KNOWN_WIN + && type_of(move) != PROMOTION) + { + + if (moveCount > 2) + continue; + + futilityValue = futilityBase + PieceValue[EG][pos.piece_on(to_sq(move))]; + + if (futilityValue <= alpha) + { + bestValue = std::max(bestValue, futilityValue); + continue; + } + + if (futilityBase <= alpha && !pos.see_ge(move, VALUE_ZERO + 1)) + { + bestValue = std::max(bestValue, futilityBase); + continue; + } + } + + // Do not search moves with negative SEE values + if ( bestValue > VALUE_TB_LOSS_IN_MAX_PLY + && !pos.see_ge(move)) + continue; + + // Speculative prefetch as early as possible + prefetch(TT.first_entry(pos.key_after(move))); + + // Check for legality just before making the move + if (!pos.legal(move)) + { + moveCount--; + continue; + } + + ss->currentMove = move; + ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] + [captureOrPromotion] + [history_slot(pos.moved_piece(move))] + [to_sq(move)]; + + // Continuation history based pruning + if ( !captureOrPromotion + && bestValue > VALUE_TB_LOSS_IN_MAX_PLY + && (*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); + pos.undo_move(move); + + assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); + + // Check for a new best move + if (value > bestValue) + { + bestValue = value; + + if (value > alpha) + { + bestMove = move; + + if (PvNode) // Update pv even in fail-high case + update_pv(ss->pv, move, (ss+1)->pv); + + if (PvNode && value < beta) // Update alpha here! + alpha = value; + else + break; // Fail high + } + } + } + + // All legal moves have been searched. A special case: if we're in check + // and no legal moves were found, it is checkmate. + if (ss->inCheck && bestValue == -VALUE_INFINITE) + { + assert(!MoveList(pos).size()); + + return pos.checkmate_value(ss->ply); // Plies to mate from the root + } + + // Save gathered info in transposition table + tte->save(posKey, value_to_tt(bestValue, ss->ply), pvHit, + bestValue >= beta ? BOUND_LOWER : + PvNode && bestValue > oldAlpha ? BOUND_EXACT : BOUND_UPPER, + ttDepth, bestMove, ss->staticEval); + + assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); + + return bestValue; + } + + + // value_to_tt() adjusts a mate or TB score from "plies to mate from the root" to + // "plies to mate from the current position". Standard scores are unchanged. + // The function is called before storing a value in the transposition table. + + Value value_to_tt(Value v, int ply) { + + assert(v != VALUE_NONE); + + return v >= VALUE_TB_WIN_IN_MAX_PLY ? v + ply + : v <= VALUE_TB_LOSS_IN_MAX_PLY ? v - ply : v; + } + + + // value_from_tt() is the inverse of value_to_tt(): it adjusts a mate or TB score + // from the transposition table (which refers to the plies to mate/be mated from + // current position) to "plies to mate/be mated (TB win/loss) from the root". However, + // for mate scores, to avoid potentially false mate scores related to the 50 moves rule + // and the graph history interaction, we return an optimal TB score instead. + + Value value_from_tt(Value v, int ply, int r50c) { + + if (v == VALUE_NONE) + return VALUE_NONE; + + if (v >= VALUE_TB_WIN_IN_MAX_PLY) // TB win or better + { + if (v >= VALUE_MATE_IN_MAX_PLY && VALUE_MATE - v > 99 - r50c) + return VALUE_MATE_IN_MAX_PLY - 1; // do not return a potentially false mate score + + return v - ply; + } + + if (v <= VALUE_TB_LOSS_IN_MAX_PLY) // TB loss or worse + { + if (v <= VALUE_MATED_IN_MAX_PLY && VALUE_MATE + v > 99 - r50c) + return VALUE_MATED_IN_MAX_PLY + 1; // do not return a potentially false mate score + + return v + ply; + } + + return v; + } + + + // update_pv() adds current move and appends child pv[] + + void update_pv(Move* pv, Move move, Move* childPv) { + + for (*pv++ = move; childPv && *childPv != MOVE_NONE; ) + *pv++ = *childPv++; + *pv = MOVE_NONE; + } + + + // update_all_stats() updates stats at the end of search() when a bestMove is found + + void update_all_stats(const Position& pos, Stack* ss, Move bestMove, Value bestValue, Value beta, Square prevSq, + Move* quietsSearched, int quietCount, Move* capturesSearched, int captureCount, Depth depth) { + + int bonus1, bonus2; + Color us = pos.side_to_move(); + Thread* thisThread = pos.this_thread(); + CapturePieceToHistory& captureHistory = thisThread->captureHistory; + Piece moved_piece = pos.moved_piece(bestMove); + PieceType captured = type_of(pos.piece_on(to_sq(bestMove))); + + bonus1 = stat_bonus(depth + 1); + bonus2 = bestValue > beta + PawnValueMg ? bonus1 // larger bonus + : std::min(bonus1, stat_bonus(depth)); // smaller bonus + + if (!pos.capture_or_promotion(bestMove)) + { + // Increase stats for the best move in case it was a quiet move + update_quiet_stats(pos, ss, bestMove, bonus2, depth); + + // Decrease stats for all non-best quiet moves + for (int i = 0; i < quietCount; ++i) + { + if (!(pos.walling() && from_to(quietsSearched[i]) == from_to(bestMove))) + thisThread->mainHistory[us][from_to(quietsSearched[i])] << -bonus2; + if (pos.walling()) + thisThread->gateHistory[us][gating_square(quietsSearched[i])] << -bonus2; + update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), to_sq(quietsSearched[i]), -bonus2); + } + } + else + { + // Increase stats for the best move in case it was a capture move + captureHistory[moved_piece][to_sq(bestMove)][captured] << bonus1; + if (pos.walling()) + thisThread->gateHistory[us][gating_square(bestMove)] << bonus1; + } + + // Extra penalty for a quiet early move that was not a TT move or + // main killer move in previous ply when it gets refuted. + if ( ((ss-1)->moveCount == 1 + (ss-1)->ttHit || ((ss-1)->currentMove == (ss-1)->killers[0])) + && !pos.captured_piece()) + update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -bonus1); + + // Decrease stats for all non-best capture moves + for (int i = 0; i < captureCount; ++i) + { + moved_piece = pos.moved_piece(capturesSearched[i]); + captured = type_of(pos.piece_on(to_sq(capturesSearched[i]))); + if (!(pos.walling() && from_to(capturesSearched[i]) == from_to(bestMove))) + captureHistory[moved_piece][to_sq(capturesSearched[i])][captured] << -bonus1; + if (pos.walling()) + thisThread->gateHistory[us][gating_square(capturesSearched[i])] << -bonus1; + } + } + + + // update_continuation_histories() updates histories of the move pairs formed + // by moves at ply -1, -2, -4, and -6 with current move. + + void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { + + for (int i : {1, 2, 4, 6}) + { + // Only update first 2 continuation histories if we are in check + if (ss->inCheck && i > 2) + break; + if (is_ok((ss-i)->currentMove)) + (*(ss-i)->continuationHistory)[history_slot(pc)][to] << bonus; + } + } + + + // update_quiet_stats() updates move sorting heuristics + + void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus, int depth) { + + // Update killers + if (ss->killers[0] != move) + { + ss->killers[1] = ss->killers[0]; + ss->killers[0] = move; + } + + Color us = pos.side_to_move(); + Thread* thisThread = pos.this_thread(); + thisThread->mainHistory[us][from_to(move)] << bonus; + if (pos.walling()) + thisThread->gateHistory[us][gating_square(move)] << bonus; + update_continuation_histories(ss, pos.moved_piece(move), to_sq(move), bonus); + + // Penalty for reversed move in case of moved piece not being a pawn + if (type_of(pos.moved_piece(move)) != PAWN && type_of(move) != DROP) + thisThread->mainHistory[us][from_to(reverse_move(move))] << -bonus; + + // Update countermove history + if (is_ok((ss-1)->currentMove)) + { + Square prevSq = to_sq((ss-1)->currentMove); + thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] = move; + } + + // Update low ply history + if (depth > 11 && ss->ply < MAX_LPH) + thisThread->lowPlyHistory[ss->ply][from_to(move)] << stat_bonus(depth - 7); + } + + // When playing with strength handicap, choose best move among a set of RootMoves + // using a statistical rule dependent on 'level'. Idea by Heinz van Saanen. + + Move Skill::pick_best(size_t multiPV) { + + const RootMoves& rootMoves = Threads.main()->rootMoves; + static PRNG rng(now()); // PRNG sequence should be non-deterministic + + // RootMoves are already sorted by score in descending order + Value topScore = rootMoves[0].score; + int delta = std::min(topScore - rootMoves[multiPV - 1].score, PawnValueMg); + int weakness = 120 - 2 * level; + int maxScore = -VALUE_INFINITE; + + // Choose best move. For each move score we add two terms, both dependent on + // weakness. One is deterministic and bigger for weaker levels, and one is + // random. Then we choose the move with the resulting highest score. + for (size_t i = 0; i < multiPV; ++i) + { + // This is our magic formula + int push = ( weakness * int(topScore - rootMoves[i].score) + + delta * (rng.rand() % weakness)) / 128; + + if (rootMoves[i].score + push >= maxScore) + { + maxScore = rootMoves[i].score + push; + best = rootMoves[i].pv[0]; + } + } + + return best; + } + +} // namespace + + +/// MainThread::check_time() is used to print debug info and, more importantly, +/// to detect when we are out of available time and thus stop the search. + +void MainThread::check_time() { + + if (--callsCnt > 0) + return; + + // When using nodes, ensure checking rate is not lower than 0.1% of nodes + callsCnt = Limits.nodes ? std::min(1024, int(Limits.nodes / 1024)) : 1024; + + static TimePoint lastInfoTime = now(); + + TimePoint elapsed = Time.elapsed(); + TimePoint tick = Limits.startTime + elapsed; + + if (tick - lastInfoTime >= 1000) + { + lastInfoTime = tick; + dbg_print(); + } + + // We should not stop pondering until told so by the GUI + if (ponder) + return; + + if ( rootPos.two_boards() + && Time.elapsed() < Limits.time[rootPos.side_to_move()] - 1000 + && (Partner.sitRequested || (Partner.weDead && !Partner.partnerDead) || Partner.weVirtualWin)) + return; + + if ( (Limits.use_time_management() && (elapsed > Time.maximum() - 10 || stopOnPonderhit)) + || (Limits.movetime && elapsed >= Limits.movetime) + || (Limits.nodes && Threads.nodes_searched() >= (uint64_t)Limits.nodes)) + Threads.stop = true; +} + + +/// UCI::pv() formats PV information according to the UCI protocol. UCI requires +/// that all (if any) unsearched PV lines are sent using a previous search score. + +string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) { + + std::stringstream ss; + TimePoint elapsed = Time.elapsed() + 1; + const RootMoves& rootMoves = pos.this_thread()->rootMoves; + size_t pvIdx = pos.this_thread()->pvIdx; + size_t multiPV = std::min((size_t)Options["MultiPV"], rootMoves.size()); + uint64_t nodesSearched = Threads.nodes_searched(); + uint64_t tbHits = Threads.tb_hits() + (TB::RootInTB ? rootMoves.size() : 0); + + for (size_t i = 0; i < multiPV; ++i) + { + bool updated = rootMoves[i].score != -VALUE_INFINITE; + + if (depth == 1 && !updated && i > 0) + continue; + + Depth d = updated ? depth : std::max(1, depth - 1); + Value v = updated ? rootMoves[i].score : rootMoves[i].previousScore; + + if (v == -VALUE_INFINITE) + v = VALUE_ZERO; + + bool tb = TB::RootInTB && abs(v) < VALUE_MATE_IN_MAX_PLY; + v = tb ? rootMoves[i].tbScore : v; + + if (ss.rdbuf()->in_avail()) // Not at first line + ss << "\n"; + + if (CurrentProtocol == XBOARD) + { + ss << d << " " + << UCI::value(v) << " " + << elapsed / 10 << " " + << nodesSearched << " " + << rootMoves[i].selDepth << " " + << nodesSearched * 1000 / elapsed << " " + << tbHits << "\t"; + + // Do not print PVs with virtual drops in bughouse variants + if (!pos.two_boards()) + for (Move m : rootMoves[i].pv) + ss << " " << UCI::move(pos, m); + } + else + { + ss << "info" + << " depth " << d + << " seldepth " << rootMoves[i].selDepth + << " multipv " << i + 1 + << " score " << UCI::value(v); + + if (Options["UCI_ShowWDL"]) + ss << UCI::wdl(v, pos.game_ply()); + + if (!tb && i == pvIdx) + ss << (v >= beta ? " lowerbound" : v <= alpha ? " upperbound" : ""); + + ss << " nodes " << nodesSearched + << " nps " << nodesSearched * 1000 / elapsed; + + if (elapsed > 1000) // Earlier makes little sense + ss << " hashfull " << TT.hashfull(); + + ss << " tbhits " << tbHits + << " time " << elapsed + << " pv"; + + for (Move m : rootMoves[i].pv) + ss << " " << UCI::move(pos, m); + } + } + + return ss.str(); +} + + +/// RootMove::extract_ponder_from_tt() is called in case we have no ponder move +/// before exiting the search, for instance, in case we stop the search during a +/// fail high at root. We try hard to have a ponder move to return to the GUI, +/// otherwise in case of 'ponder on' we have nothing to think on. + +bool RootMove::extract_ponder_from_tt(Position& pos) { + + StateInfo st; + ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); + + bool ttHit; + + assert(pv.size() == 1); + + if (pv[0] == MOVE_NONE) + return false; + + pos.do_move(pv[0], st); + TTEntry* tte = TT.probe(pos.key(), ttHit); + + if (ttHit) + { + Move m = tte->move(); // Local copy to be SMP safe + if (MoveList(pos).contains(m)) + pv.push_back(m); + } + + pos.undo_move(pv[0]); + return pv.size() > 1; +} + +void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) { + + RootInTB = false; + UseRule50 = bool(Options["Syzygy50MoveRule"]); + ProbeDepth = int(Options["SyzygyProbeDepth"]); + Cardinality = int(Options["SyzygyProbeLimit"]); + bool dtz_available = true; + + // Tables with fewer pieces than SyzygyProbeLimit are searched with + // ProbeDepth == DEPTH_ZERO + if (Cardinality > MaxCardinality) + { + Cardinality = MaxCardinality; + ProbeDepth = 0; + } + + if (Cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) + { + // Rank moves using DTZ tables + RootInTB = root_probe(pos, rootMoves); + + if (!RootInTB) + { + // DTZ tables are missing; try to rank moves using WDL tables + dtz_available = false; + RootInTB = root_probe_wdl(pos, rootMoves); + } + } + + if (RootInTB) + { + // Sort moves according to TB rank + std::stable_sort(rootMoves.begin(), rootMoves.end(), + [](const RootMove &a, const RootMove &b) { return a.tbRank > b.tbRank; } ); + + // Probe during search only if DTZ is not available and we are winning + if (dtz_available || rootMoves[0].tbScore <= VALUE_DRAW) + Cardinality = 0; + } + else + { + // Clean up if root_probe() and root_probe_wdl() have failed + for (auto& m : rootMoves) + m.tbRank = 0; + } +} + +} // namespace Stockfish diff --git a/Fairy-Stockfish-master/src/search.h b/Fairy-Stockfish-master/src/search.h new file mode 100644 index 0000000000000000000000000000000000000000..1af5a5e06c76a99192eab4cbb33a98a6ba3d344c --- /dev/null +++ b/Fairy-Stockfish-master/src/search.h @@ -0,0 +1,114 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef SEARCH_H_INCLUDED +#define SEARCH_H_INCLUDED + +#include + +#include "misc.h" +#include "movepick.h" +#include "types.h" + +namespace Stockfish { + +class Position; + +namespace Search { + +/// Threshold used for countermoves based pruning +constexpr int CounterMovePruneThreshold = 0; + + +/// Stack struct keeps track of the information we need to remember from nodes +/// shallower and deeper in the tree during the search. Each search thread has +/// its own array of Stack objects, indexed by the current ply. + +struct Stack { + Move* pv; + PieceToHistory* continuationHistory; + int ply; + Move currentMove; + Move excludedMove; + Move killers[2]; + Value staticEval; + int statScore; + int moveCount; + bool inCheck; + bool ttPv; + bool ttHit; + int doubleExtensions; +}; + + +/// RootMove struct is used for moves at the root of the tree. For each root move +/// we store a score and a PV (really a refutation in the case of moves which +/// fail low). Score is normally set at -VALUE_INFINITE for all non-pv moves. + +struct RootMove { + + explicit RootMove(Move m) : pv(1, m) {} + bool extract_ponder_from_tt(Position& pos); + bool operator==(const Move& m) const { return pv[0] == m; } + bool operator<(const RootMove& m) const { // Sort in descending order + return m.score != score ? m.score < score + : m.previousScore < previousScore; + } + + Value score = -VALUE_INFINITE; + Value previousScore = -VALUE_INFINITE; + int selDepth = 0; + int tbRank = 0; + Value tbScore; + std::vector pv; +}; + +typedef std::vector RootMoves; + + +/// LimitsType struct stores information sent by GUI about available time to +/// search the current move, maximum depth/time, or if we are in analysis mode. + +struct LimitsType { + + LimitsType() { // Init explicitly due to broken value-initialization of non POD in MSVC + time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = npmsec = movetime = TimePoint(0); + movestogo = depth = mate = perft = infinite = 0; + nodes = 0; + } + + bool use_time_management() const { + return time[WHITE] || time[BLACK]; + } + + std::vector searchmoves, banmoves; + TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime; + int movestogo, depth, mate, perft, infinite; + int64_t nodes; +}; + +extern LimitsType Limits; + +void init(); +void clear(); + +} // namespace Search + +} // namespace Stockfish + +#endif // #ifndef SEARCH_H_INCLUDED diff --git a/Fairy-Stockfish-master/src/syzygy/tbprobe.cpp b/Fairy-Stockfish-master/src/syzygy/tbprobe.cpp new file mode 100644 index 0000000000000000000000000000000000000000..36b2fd0aed3a7a9da6d32b4d3d322c66d66ce7ef --- /dev/null +++ b/Fairy-Stockfish-master/src/syzygy/tbprobe.cpp @@ -0,0 +1,1636 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include // For std::memset and std::memcpy +#include +#include +#include +#include +#include +#include +#include + +#include "../bitboard.h" +#include "../movegen.h" +#include "../position.h" +#include "../search.h" +#include "../types.h" +#include "../uci.h" + +#include "tbprobe.h" + +#ifndef _WIN32 +#include +#include +#include +#include +#else +#define WIN32_LEAN_AND_MEAN +#ifndef NOMINMAX +# define NOMINMAX // Disable macros min() and max() +#endif +#include +#endif + +using namespace Stockfish::Tablebases; + +int Stockfish::Tablebases::MaxCardinality; + +namespace Stockfish { + +namespace { + +constexpr int TBPIECES = 7; // Max number of supported pieces + +enum { BigEndian, LittleEndian }; +enum TBType { WDL, DTZ }; // Used as template parameter + +// Each table has a set of flags: all of them refer to DTZ tables, the last one to WDL tables +enum TBFlag { STM = 1, Mapped = 2, WinPlies = 4, LossPlies = 8, Wide = 16, SingleValue = 128 }; + +inline WDLScore operator-(WDLScore d) { return WDLScore(-int(d)); } +inline Square operator^(Square s, int i) { return Square(int(s) ^ i); } + +const std::string PieceToChar( " PNBRQ" + std::string(KING - QUEEN - 1, ' ') + "K" + std::string(PIECE_TYPE_NB - KING - 1, ' ') + + " pnbrq" + std::string(KING - QUEEN - 1, ' ') + "k" + std::string(PIECE_TYPE_NB - KING - 1, ' ')); + +int MapPawns[SQUARE_NB]; +int MapB1H1H7[SQUARE_NB]; +int MapA1D1D4[SQUARE_NB]; +int MapKK[10][SQUARE_NB]; // [MapA1D1D4][SQUARE_NB] + +int Binomial[6][SQUARE_NB]; // [k][n] k elements from a set of n elements +int LeadPawnIdx[6][SQUARE_NB]; // [leadPawnsCnt][SQUARE_NB] +int LeadPawnsSize[6][4]; // [leadPawnsCnt][FILE_A..FILE_D] + +// Comparison function to sort leading pawns in ascending MapPawns[] order +bool pawns_comp(Square i, Square j) { return MapPawns[i] < MapPawns[j]; } +int off_A1H8(Square sq) { return int(rank_of(sq)) - file_of(sq); } + +constexpr Value WDL_to_value[] = { + -VALUE_MATE + MAX_PLY + 1, + VALUE_DRAW - 2, + VALUE_DRAW, + VALUE_DRAW + 2, + VALUE_MATE - MAX_PLY - 1 +}; + +template +inline void swap_endian(T& x) +{ + static_assert(std::is_unsigned::value, "Argument of swap_endian not unsigned"); + + uint8_t tmp, *c = (uint8_t*)&x; + for (int i = 0; i < Half; ++i) + tmp = c[i], c[i] = c[End - i], c[End - i] = tmp; +} +template<> inline void swap_endian(uint8_t&) {} + +template T number(void* addr) +{ + T v; + + if ((uintptr_t)addr & (alignof(T) - 1)) // Unaligned pointer (very rare) + std::memcpy(&v, addr, sizeof(T)); + else + v = *((T*)addr); + + if (LE != IsLittleEndian) + swap_endian(v); + return v; +} + +// DTZ tables don't store valid scores for moves that reset the rule50 counter +// like captures and pawn moves but we can easily recover the correct dtz of the +// previous move if we know the position's WDL score. +int dtz_before_zeroing(WDLScore wdl) { + return wdl == WDLWin ? 1 : + wdl == WDLCursedWin ? 101 : + wdl == WDLBlessedLoss ? -101 : + wdl == WDLLoss ? -1 : 0; +} + +// Return the sign of a number (-1, 0, 1) +template int sign_of(T val) { + return (T(0) < val) - (val < T(0)); +} + +// Numbers in little endian used by sparseIndex[] to point into blockLength[] +struct SparseEntry { + char block[4]; // Number of block + char offset[2]; // Offset within the block +}; + +static_assert(sizeof(SparseEntry) == 6, "SparseEntry must be 6 bytes"); + +typedef uint16_t Sym; // Huffman symbol + +struct LR { + enum Side { Left, Right }; + + uint8_t lr[3]; // The first 12 bits is the left-hand symbol, the second 12 + // bits is the right-hand symbol. If symbol has length 1, + // then the left-hand symbol is the stored value. + template + Sym get() { + return S == Left ? ((lr[1] & 0xF) << 8) | lr[0] : + S == Right ? (lr[2] << 4) | (lr[1] >> 4) : (assert(false), Sym(-1)); + } +}; + +static_assert(sizeof(LR) == 3, "LR tree entry must be 3 bytes"); + +// Tablebases data layout is structured as following: +// +// TBFile: memory maps/unmaps the physical .rtbw and .rtbz files +// TBTable: one object for each file with corresponding indexing information +// TBTables: has ownership of TBTable objects, keeping a list and a hash + +// class TBFile memory maps/unmaps the single .rtbw and .rtbz files. Files are +// memory mapped for best performance. Files are mapped at first access: at init +// time only existence of the file is checked. +class TBFile : public std::ifstream { + + std::string fname; + +public: + // Look for and open the file among the Paths directories where the .rtbw + // and .rtbz files can be found. Multiple directories are separated by ";" + // on Windows and by ":" on Unix-based operating systems. + // + // Example: + // C:\tb\wdl345;C:\tb\wdl6;D:\tb\dtz345;D:\tb\dtz6 + static std::string Paths; + + TBFile(const std::string& f) { + +#ifndef _WIN32 + constexpr char SepChar = ':'; +#else + constexpr char SepChar = ';'; +#endif + std::stringstream ss(Paths); + std::string path; + + while (std::getline(ss, path, SepChar)) + { + fname = path + "/" + f; + std::ifstream::open(fname); + if (is_open()) + return; + } + } + + // Memory map the file and check it. File should be already open and will be + // closed after mapping. + uint8_t* map(void** baseAddress, uint64_t* mapping, TBType type) { + + assert(is_open()); + + close(); // Need to re-open to get native file descriptor + +#ifndef _WIN32 + struct stat statbuf; + int fd = ::open(fname.c_str(), O_RDONLY); + + if (fd == -1) + return *baseAddress = nullptr, nullptr; + + fstat(fd, &statbuf); + + if (statbuf.st_size % 64 != 16) + { + std::cerr << "Corrupt tablebase file " << fname << std::endl; + exit(EXIT_FAILURE); + } + + *mapping = statbuf.st_size; + *baseAddress = mmap(nullptr, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0); +#if defined(MADV_RANDOM) + madvise(*baseAddress, statbuf.st_size, MADV_RANDOM); +#endif + ::close(fd); + + if (*baseAddress == MAP_FAILED) + { + std::cerr << "Could not mmap() " << fname << std::endl; + exit(EXIT_FAILURE); + } +#else + // Note FILE_FLAG_RANDOM_ACCESS is only a hint to Windows and as such may get ignored. + HANDLE fd = CreateFile(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, + OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, nullptr); + + if (fd == INVALID_HANDLE_VALUE) + return *baseAddress = nullptr, nullptr; + + DWORD size_high; + DWORD size_low = GetFileSize(fd, &size_high); + + if (size_low % 64 != 16) + { + std::cerr << "Corrupt tablebase file " << fname << std::endl; + exit(EXIT_FAILURE); + } + + HANDLE mmap = CreateFileMapping(fd, nullptr, PAGE_READONLY, size_high, size_low, nullptr); + CloseHandle(fd); + + if (!mmap) + { + std::cerr << "CreateFileMapping() failed" << std::endl; + exit(EXIT_FAILURE); + } + + *mapping = (uint64_t)mmap; + *baseAddress = MapViewOfFile(mmap, FILE_MAP_READ, 0, 0, 0); + + if (!*baseAddress) + { + std::cerr << "MapViewOfFile() failed, name = " << fname + << ", error = " << GetLastError() << std::endl; + exit(EXIT_FAILURE); + } +#endif + uint8_t* data = (uint8_t*)*baseAddress; + + constexpr uint8_t Magics[][4] = { { 0xD7, 0x66, 0x0C, 0xA5 }, + { 0x71, 0xE8, 0x23, 0x5D } }; + + if (memcmp(data, Magics[type == WDL], 4)) + { + std::cerr << "Corrupted table in file " << fname << std::endl; + unmap(*baseAddress, *mapping); + return *baseAddress = nullptr, nullptr; + } + + return data + 4; // Skip Magics's header + } + + static void unmap(void* baseAddress, uint64_t mapping) { + +#ifndef _WIN32 + munmap(baseAddress, mapping); +#else + UnmapViewOfFile(baseAddress); + CloseHandle((HANDLE)mapping); +#endif + } +}; + +std::string TBFile::Paths; + +// struct PairsData contains low level indexing information to access TB data. +// There are 8, 4 or 2 PairsData records for each TBTable, according to type of +// table and if positions have pawns or not. It is populated at first access. +struct PairsData { + uint8_t flags; // Table flags, see enum TBFlag + uint8_t maxSymLen; // Maximum length in bits of the Huffman symbols + uint8_t minSymLen; // Minimum length in bits of the Huffman symbols + uint32_t blocksNum; // Number of blocks in the TB file + size_t sizeofBlock; // Block size in bytes + size_t span; // About every span values there is a SparseIndex[] entry + Sym* lowestSym; // lowestSym[l] is the symbol of length l with the lowest value + LR* btree; // btree[sym] stores the left and right symbols that expand sym + uint16_t* blockLength; // Number of stored positions (minus one) for each block: 1..65536 + uint32_t blockLengthSize; // Size of blockLength[] table: padded so it's bigger than blocksNum + SparseEntry* sparseIndex; // Partial indices into blockLength[] + size_t sparseIndexSize; // Size of SparseIndex[] table + uint8_t* data; // Start of Huffman compressed data + std::vector base64; // base64[l - min_sym_len] is the 64bit-padded lowest symbol of length l + std::vector symlen; // Number of values (-1) represented by a given Huffman symbol: 1..256 + Piece pieces[TBPIECES]; // Position pieces: the order of pieces defines the groups + uint64_t groupIdx[TBPIECES+1]; // Start index used for the encoding of the group's pieces + int groupLen[TBPIECES+1]; // Number of pieces in a given group: KRKN -> (3, 1) + uint16_t map_idx[4]; // WDLWin, WDLLoss, WDLCursedWin, WDLBlessedLoss (used in DTZ) +}; + +// struct TBTable contains indexing information to access the corresponding TBFile. +// There are 2 types of TBTable, corresponding to a WDL or a DTZ file. TBTable +// is populated at init time but the nested PairsData records are populated at +// first access, when the corresponding file is memory mapped. +template +struct TBTable { + typedef typename std::conditional::type Ret; + + static constexpr int Sides = Type == WDL ? 2 : 1; + + std::atomic_bool ready; + void* baseAddress; + uint8_t* map; + uint64_t mapping; + Key key; + Key key2; + int pieceCount; + bool hasPawns; + bool hasUniquePieces; + uint8_t pawnCount[2]; // [Lead color / other color] + PairsData items[Sides][4]; // [wtm / btm][FILE_A..FILE_D or 0] + + PairsData* get(int stm, int f) { + return &items[stm % Sides][hasPawns ? f : 0]; + } + + TBTable() : ready(false), baseAddress(nullptr) {} + explicit TBTable(const std::string& code); + explicit TBTable(const TBTable& wdl); + + ~TBTable() { + if (baseAddress) + TBFile::unmap(baseAddress, mapping); + } +}; + +template<> +TBTable::TBTable(const std::string& code) : TBTable() { + + StateInfo st; + Position pos; + + key = pos.set(code, WHITE, &st).material_key(); + pieceCount = pos.count(); + hasPawns = pos.pieces(PAWN); + + hasUniquePieces = false; + for (Color c : { WHITE, BLACK }) + for (PieceType pt = PAWN; pt < KING; ++pt) + if (popcount(pos.pieces(c, pt)) == 1) + hasUniquePieces = true; + + // Set the leading color. In case both sides have pawns the leading color + // is the side with less pawns because this leads to better compression. + bool c = !pos.count(BLACK) + || ( pos.count(WHITE) + && pos.count(BLACK) >= pos.count(WHITE)); + + pawnCount[0] = pos.count(c ? WHITE : BLACK); + pawnCount[1] = pos.count(c ? BLACK : WHITE); + + key2 = pos.set(code, BLACK, &st).material_key(); +} + +template<> +TBTable::TBTable(const TBTable& wdl) : TBTable() { + + // Use the corresponding WDL table to avoid recalculating all from scratch + key = wdl.key; + key2 = wdl.key2; + pieceCount = wdl.pieceCount; + hasPawns = wdl.hasPawns; + hasUniquePieces = wdl.hasUniquePieces; + pawnCount[0] = wdl.pawnCount[0]; + pawnCount[1] = wdl.pawnCount[1]; +} + +// class TBTables creates and keeps ownership of the TBTable objects, one for +// each TB file found. It supports a fast, hash based, table lookup. Populated +// at init time, accessed at probe time. +class TBTables { + + struct Entry + { + Key key; + TBTable* wdl; + TBTable* dtz; + + template + TBTable* get() const { + return (TBTable*)(Type == WDL ? (void*)wdl : (void*)dtz); + } + }; + + static constexpr int Size = 1 << 12; // 4K table, indexed by key's 12 lsb + static constexpr int Overflow = 1; // Number of elements allowed to map to the last bucket + + Entry hashTable[Size + Overflow]; + + std::deque> wdlTable; + std::deque> dtzTable; + + void insert(Key key, TBTable* wdl, TBTable* dtz) { + uint32_t homeBucket = (uint32_t)key & (Size - 1); + Entry entry{ key, wdl, dtz }; + + // Ensure last element is empty to avoid overflow when looking up + for (uint32_t bucket = homeBucket; bucket < Size + Overflow - 1; ++bucket) { + Key otherKey = hashTable[bucket].key; + if (otherKey == key || !hashTable[bucket].get()) { + hashTable[bucket] = entry; + return; + } + + // Robin Hood hashing: If we've probed for longer than this element, + // insert here and search for a new spot for the other element instead. + uint32_t otherHomeBucket = (uint32_t)otherKey & (Size - 1); + if (otherHomeBucket > homeBucket) { + std::swap(entry, hashTable[bucket]); + key = otherKey; + homeBucket = otherHomeBucket; + } + } + std::cerr << "TB hash table size too low!" << std::endl; + exit(EXIT_FAILURE); + } + +public: + template + TBTable* get(Key key) { + for (const Entry* entry = &hashTable[(uint32_t)key & (Size - 1)]; ; ++entry) { + if (entry->key == key || !entry->get()) + return entry->get(); + } + } + + void clear() { + memset(hashTable, 0, sizeof(hashTable)); + wdlTable.clear(); + dtzTable.clear(); + } + size_t size() const { return wdlTable.size(); } + void add(const std::vector& pieces); +}; + +TBTables TBTables; + +// If the corresponding file exists two new objects TBTable and TBTable +// are created and added to the lists and hash table. Called at init time. +void TBTables::add(const std::vector& pieces) { + + std::string code; + + for (PieceType pt : pieces) + code += PieceToChar[pt]; + + TBFile file(code.insert(code.find('K', 1), "v") + ".rtbw"); // KRK -> KRvK + + if (!file.is_open()) // Only WDL file is checked + return; + + file.close(); + + MaxCardinality = std::max((int)pieces.size(), MaxCardinality); + + wdlTable.emplace_back(code); + dtzTable.emplace_back(wdlTable.back()); + + // Insert into the hash keys for both colors: KRvK with KR white and black + insert(wdlTable.back().key , &wdlTable.back(), &dtzTable.back()); + insert(wdlTable.back().key2, &wdlTable.back(), &dtzTable.back()); +} + +// TB tables are compressed with canonical Huffman code. The compressed data is divided into +// blocks of size d->sizeofBlock, and each block stores a variable number of symbols. +// Each symbol represents either a WDL or a (remapped) DTZ value, or a pair of other symbols +// (recursively). If you keep expanding the symbols in a block, you end up with up to 65536 +// WDL or DTZ values. Each symbol represents up to 256 values and will correspond after +// Huffman coding to at least 1 bit. So a block of 32 bytes corresponds to at most +// 32 x 8 x 256 = 65536 values. This maximum is only reached for tables that consist mostly +// of draws or mostly of wins, but such tables are actually quite common. In principle, the +// blocks in WDL tables are 64 bytes long (and will be aligned on cache lines). But for +// mostly-draw or mostly-win tables this can leave many 64-byte blocks only half-filled, so +// in such cases blocks are 32 bytes long. The blocks of DTZ tables are up to 1024 bytes long. +// The generator picks the size that leads to the smallest table. The "book" of symbols and +// Huffman codes is the same for all blocks in the table. A non-symmetric pawnless TB file +// will have one table for wtm and one for btm, a TB file with pawns will have tables per +// file a,b,c,d also in this case one set for wtm and one for btm. +int decompress_pairs(PairsData* d, uint64_t idx) { + + // Special case where all table positions store the same value + if (d->flags & TBFlag::SingleValue) + return d->minSymLen; + + // First we need to locate the right block that stores the value at index "idx". + // Because each block n stores blockLength[n] + 1 values, the index i of the block + // that contains the value at position idx is: + // + // for (i = -1, sum = 0; sum <= idx; i++) + // sum += blockLength[i + 1] + 1; + // + // This can be slow, so we use SparseIndex[] populated with a set of SparseEntry that + // point to known indices into blockLength[]. Namely SparseIndex[k] is a SparseEntry + // that stores the blockLength[] index and the offset within that block of the value + // with index I(k), where: + // + // I(k) = k * d->span + d->span / 2 (1) + + // First step is to get the 'k' of the I(k) nearest to our idx, using definition (1) + uint32_t k = uint32_t(idx / d->span); + + // Then we read the corresponding SparseIndex[] entry + uint32_t block = number(&d->sparseIndex[k].block); + int offset = number(&d->sparseIndex[k].offset); + + // Now compute the difference idx - I(k). From definition of k we know that + // + // idx = k * d->span + idx % d->span (2) + // + // So from (1) and (2) we can compute idx - I(K): + int diff = idx % d->span - d->span / 2; + + // Sum the above to offset to find the offset corresponding to our idx + offset += diff; + + // Move to previous/next block, until we reach the correct block that contains idx, + // that is when 0 <= offset <= d->blockLength[block] + while (offset < 0) + offset += d->blockLength[--block] + 1; + + while (offset > d->blockLength[block]) + offset -= d->blockLength[block++] + 1; + + // Finally, we find the start address of our block of canonical Huffman symbols + uint32_t* ptr = (uint32_t*)(d->data + ((uint64_t)block * d->sizeofBlock)); + + // Read the first 64 bits in our block, this is a (truncated) sequence of + // unknown number of symbols of unknown length but we know the first one + // is at the beginning of this 64 bits sequence. + uint64_t buf64 = number(ptr); ptr += 2; + int buf64Size = 64; + Sym sym; + + while (true) + { + int len = 0; // This is the symbol length - d->min_sym_len + + // Now get the symbol length. For any symbol s64 of length l right-padded + // to 64 bits we know that d->base64[l-1] >= s64 >= d->base64[l] so we + // can find the symbol length iterating through base64[]. + while (buf64 < d->base64[len]) + ++len; + + // All the symbols of a given length are consecutive integers (numerical + // sequence property), so we can compute the offset of our symbol of + // length len, stored at the beginning of buf64. + sym = Sym((buf64 - d->base64[len]) >> (64 - len - d->minSymLen)); + + // Now add the value of the lowest symbol of length len to get our symbol + sym += number(&d->lowestSym[len]); + + // If our offset is within the number of values represented by symbol sym + // we are done... + if (offset < d->symlen[sym] + 1) + break; + + // ...otherwise update the offset and continue to iterate + offset -= d->symlen[sym] + 1; + len += d->minSymLen; // Get the real length + buf64 <<= len; // Consume the just processed symbol + buf64Size -= len; + + if (buf64Size <= 32) { // Refill the buffer + buf64Size += 32; + buf64 |= (uint64_t)number(ptr++) << (64 - buf64Size); + } + } + + // Ok, now we have our symbol that expands into d->symlen[sym] + 1 symbols. + // We binary-search for our value recursively expanding into the left and + // right child symbols until we reach a leaf node where symlen[sym] + 1 == 1 + // that will store the value we need. + while (d->symlen[sym]) + { + Sym left = d->btree[sym].get(); + + // If a symbol contains 36 sub-symbols (d->symlen[sym] + 1 = 36) and + // expands in a pair (d->symlen[left] = 23, d->symlen[right] = 11), then + // we know that, for instance the ten-th value (offset = 10) will be on + // the left side because in Recursive Pairing child symbols are adjacent. + if (offset < d->symlen[left] + 1) + sym = left; + else { + offset -= d->symlen[left] + 1; + sym = d->btree[sym].get(); + } + } + + return d->btree[sym].get(); +} + +bool check_dtz_stm(TBTable*, int, File) { return true; } + +bool check_dtz_stm(TBTable* entry, int stm, File f) { + + auto flags = entry->get(stm, f)->flags; + return (flags & TBFlag::STM) == stm + || ((entry->key == entry->key2) && !entry->hasPawns); +} + +// DTZ scores are sorted by frequency of occurrence and then assigned the +// values 0, 1, 2, ... in order of decreasing frequency. This is done for each +// of the four WDLScore values. The mapping information necessary to reconstruct +// the original values is stored in the TB file and read during map[] init. +WDLScore map_score(TBTable*, File, int value, WDLScore) { return WDLScore(value - 2); } + +int map_score(TBTable* entry, File f, int value, WDLScore wdl) { + + constexpr int WDLMap[] = { 1, 3, 0, 2, 0 }; + + auto flags = entry->get(0, f)->flags; + + uint8_t* map = entry->map; + uint16_t* idx = entry->get(0, f)->map_idx; + if (flags & TBFlag::Mapped) { + if (flags & TBFlag::Wide) + value = ((uint16_t *)map)[idx[WDLMap[wdl + 2]] + value]; + else + value = map[idx[WDLMap[wdl + 2]] + value]; + } + + // DTZ tables store distance to zero in number of moves or plies. We + // want to return plies, so we have convert to plies when needed. + if ( (wdl == WDLWin && !(flags & TBFlag::WinPlies)) + || (wdl == WDLLoss && !(flags & TBFlag::LossPlies)) + || wdl == WDLCursedWin + || wdl == WDLBlessedLoss) + value *= 2; + + return value + 1; +} + +// Compute a unique index out of a position and use it to probe the TB file. To +// encode k pieces of same type and color, first sort the pieces by square in +// ascending order s1 <= s2 <= ... <= sk then compute the unique index as: +// +// idx = Binomial[1][s1] + Binomial[2][s2] + ... + Binomial[k][sk] +// +template +Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result) { + + Square squares[TBPIECES]; + Piece pieces[TBPIECES]; + uint64_t idx; + int next = 0, size = 0, leadPawnsCnt = 0; + PairsData* d; + Bitboard b, leadPawns = 0; + File tbFile = FILE_A; + + // A given TB entry like KRK has associated two material keys: KRvk and Kvkr. + // If both sides have the same pieces keys are equal. In this case TB tables + // only store the 'white to move' case, so if the position to lookup has black + // to move, we need to switch the color and flip the squares before to lookup. + bool symmetricBlackToMove = (entry->key == entry->key2 && pos.side_to_move()); + + // TB files are calculated for white as stronger side. For instance we have + // KRvK, not KvKR. A position where stronger side is white will have its + // material key == entry->key, otherwise we have to switch the color and + // flip the squares before to lookup. + bool blackStronger = (pos.material_key() != entry->key); + + int flipColor = (symmetricBlackToMove || blackStronger) * PIECE_TYPE_NB; + int flipSquares = (symmetricBlackToMove || blackStronger) * 56; + int stm = (symmetricBlackToMove || blackStronger) ^ pos.side_to_move(); + + // For pawns, TB files store 4 separate tables according if leading pawn is on + // file a, b, c or d after reordering. The leading pawn is the one with maximum + // MapPawns[] value, that is the one most toward the edges and with lowest rank. + if (entry->hasPawns) { + + // In all the 4 tables, pawns are at the beginning of the piece sequence and + // their color is the reference one. So we just pick the first one. + Piece pc = Piece(entry->get(0, 0)->pieces[0] ^ flipColor); + + assert(type_of(pc) == PAWN); + + leadPawns = b = pos.pieces(color_of(pc), PAWN); + do + squares[size++] = pop_lsb(b) ^ flipSquares; + while (b); + + leadPawnsCnt = size; + + std::swap(squares[0], *std::max_element(squares, squares + leadPawnsCnt, pawns_comp)); + + tbFile = File(edge_distance(file_of(squares[0]))); + } + + // DTZ tables are one-sided, i.e. they store positions only for white to + // move or only for black to move, so check for side to move to be stm, + // early exit otherwise. + if (!check_dtz_stm(entry, stm, tbFile)) + return *result = CHANGE_STM, Ret(); + + // Now we are ready to get all the position pieces (but the lead pawns) and + // directly map them to the correct color and square. + b = pos.pieces() ^ leadPawns; + do { + Square s = pop_lsb(b); + squares[size] = s ^ flipSquares; + pieces[size++] = Piece(pos.piece_on(s) ^ flipColor); + } while (b); + + assert(size >= 2); + + d = entry->get(stm, tbFile); + + // Then we reorder the pieces to have the same sequence as the one stored + // in pieces[i]: the sequence that ensures the best compression. + for (int i = leadPawnsCnt; i < size - 1; ++i) + for (int j = i + 1; j < size; ++j) + if (d->pieces[i] == pieces[j]) + { + std::swap(pieces[i], pieces[j]); + std::swap(squares[i], squares[j]); + break; + } + + // Now we map again the squares so that the square of the lead piece is in + // the triangle A1-D1-D4. + if (file_of(squares[0]) > FILE_D) + for (int i = 0; i < size; ++i) + squares[i] = flip_file(squares[i]); + + // Encode leading pawns starting with the one with minimum MapPawns[] and + // proceeding in ascending order. + if (entry->hasPawns) { + idx = LeadPawnIdx[leadPawnsCnt][squares[0]]; + + std::stable_sort(squares + 1, squares + leadPawnsCnt, pawns_comp); + + for (int i = 1; i < leadPawnsCnt; ++i) + idx += Binomial[i][MapPawns[squares[i]]]; + + goto encode_remaining; // With pawns we have finished special treatments + } + + // In positions without pawns, we further flip the squares to ensure leading + // piece is below RANK_5. + if (rank_of(squares[0]) > RANK_4) + for (int i = 0; i < size; ++i) + squares[i] = flip_rank(squares[i]); + + // Look for the first piece of the leading group not on the A1-D4 diagonal + // and ensure it is mapped below the diagonal. + for (int i = 0; i < d->groupLen[0]; ++i) { + if (!off_A1H8(squares[i])) + continue; + + if (off_A1H8(squares[i]) > 0) // A1-H8 diagonal flip: SQ_A3 -> SQ_C1 + for (int j = i; j < size; ++j) + squares[j] = Square(((squares[j] >> 3) | (squares[j] << 3)) & 63); + break; + } + + // Encode the leading group. + // + // Suppose we have KRvK. Let's say the pieces are on square numbers wK, wR + // and bK (each 0...63). The simplest way to map this position to an index + // is like this: + // + // index = wK * 64 * 64 + wR * 64 + bK; + // + // But this way the TB is going to have 64*64*64 = 262144 positions, with + // lots of positions being equivalent (because they are mirrors of each + // other) and lots of positions being invalid (two pieces on one square, + // adjacent kings, etc.). + // Usually the first step is to take the wK and bK together. There are just + // 462 ways legal and not-mirrored ways to place the wK and bK on the board. + // Once we have placed the wK and bK, there are 62 squares left for the wR + // Mapping its square from 0..63 to available squares 0..61 can be done like: + // + // wR -= (wR > wK) + (wR > bK); + // + // In words: if wR "comes later" than wK, we deduct 1, and the same if wR + // "comes later" than bK. In case of two same pieces like KRRvK we want to + // place the two Rs "together". If we have 62 squares left, we can place two + // Rs "together" in 62 * 61 / 2 ways (we divide by 2 because rooks can be + // swapped and still get the same position.) + // + // In case we have at least 3 unique pieces (included kings) we encode them + // together. + if (entry->hasUniquePieces) { + + int adjust1 = squares[1] > squares[0]; + int adjust2 = (squares[2] > squares[0]) + (squares[2] > squares[1]); + + // First piece is below a1-h8 diagonal. MapA1D1D4[] maps the b1-d1-d3 + // triangle to 0...5. There are 63 squares for second piece and 62 + // (mapped to 0...61) for the third. + if (off_A1H8(squares[0])) + idx = ( MapA1D1D4[squares[0]] * 63 + + (squares[1] - adjust1)) * 62 + + squares[2] - adjust2; + + // First piece is on a1-h8 diagonal, second below: map this occurrence to + // 6 to differentiate from the above case, rank_of() maps a1-d4 diagonal + // to 0...3 and finally MapB1H1H7[] maps the b1-h1-h7 triangle to 0..27. + else if (off_A1H8(squares[1])) + idx = ( 6 * 63 + rank_of(squares[0]) * 28 + + MapB1H1H7[squares[1]]) * 62 + + squares[2] - adjust2; + + // First two pieces are on a1-h8 diagonal, third below + else if (off_A1H8(squares[2])) + idx = 6 * 63 * 62 + 4 * 28 * 62 + + rank_of(squares[0]) * 7 * 28 + + (rank_of(squares[1]) - adjust1) * 28 + + MapB1H1H7[squares[2]]; + + // All 3 pieces on the diagonal a1-h8 + else + idx = 6 * 63 * 62 + 4 * 28 * 62 + 4 * 7 * 28 + + rank_of(squares[0]) * 7 * 6 + + (rank_of(squares[1]) - adjust1) * 6 + + (rank_of(squares[2]) - adjust2); + } else + // We don't have at least 3 unique pieces, like in KRRvKBB, just map + // the kings. + idx = MapKK[MapA1D1D4[squares[0]]][squares[1]]; + +encode_remaining: + idx *= d->groupIdx[0]; + Square* groupSq = squares + d->groupLen[0]; + + // Encode remaining pawns then pieces according to square, in ascending order + bool remainingPawns = entry->hasPawns && entry->pawnCount[1]; + + while (d->groupLen[++next]) + { + std::stable_sort(groupSq, groupSq + d->groupLen[next]); + uint64_t n = 0; + + // Map down a square if "comes later" than a square in the previous + // groups (similar to what done earlier for leading group pieces). + for (int i = 0; i < d->groupLen[next]; ++i) + { + auto f = [&](Square s) { return groupSq[i] > s; }; + auto adjust = std::count_if(squares, groupSq, f); + n += Binomial[i + 1][groupSq[i] - adjust - 8 * remainingPawns]; + } + + remainingPawns = false; + idx += n * d->groupIdx[next]; + groupSq += d->groupLen[next]; + } + + // Now that we have the index, decompress the pair and get the score + return map_score(entry, tbFile, decompress_pairs(d, idx), wdl); +} + +// Group together pieces that will be encoded together. The general rule is that +// a group contains pieces of same type and color. The exception is the leading +// group that, in case of positions without pawns, can be formed by 3 different +// pieces (default) or by the king pair when there is not a unique piece apart +// from the kings. When there are pawns, pawns are always first in pieces[]. +// +// As example KRKN -> KRK + N, KNNK -> KK + NN, KPPKP -> P + PP + K + K +// +// The actual grouping depends on the TB generator and can be inferred from the +// sequence of pieces in piece[] array. +template +void set_groups(T& e, PairsData* d, int order[], File f) { + + int n = 0, firstLen = e.hasPawns ? 0 : e.hasUniquePieces ? 3 : 2; + d->groupLen[n] = 1; + + // Number of pieces per group is stored in groupLen[], for instance in KRKN + // the encoder will default on '111', so groupLen[] will be (3, 1). + for (int i = 1; i < e.pieceCount; ++i) + if (--firstLen > 0 || d->pieces[i] == d->pieces[i - 1]) + d->groupLen[n]++; + else + d->groupLen[++n] = 1; + + d->groupLen[++n] = 0; // Zero-terminated + + // The sequence in pieces[] defines the groups, but not the order in which + // they are encoded. If the pieces in a group g can be combined on the board + // in N(g) different ways, then the position encoding will be of the form: + // + // g1 * N(g2) * N(g3) + g2 * N(g3) + g3 + // + // This ensures unique encoding for the whole position. The order of the + // groups is a per-table parameter and could not follow the canonical leading + // pawns/pieces -> remaining pawns -> remaining pieces. In particular the + // first group is at order[0] position and the remaining pawns, when present, + // are at order[1] position. + bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides + int next = pp ? 2 : 1; + int freeSquares = 64 - d->groupLen[0] - (pp ? d->groupLen[1] : 0); + uint64_t idx = 1; + + for (int k = 0; next < n || k == order[0] || k == order[1]; ++k) + if (k == order[0]) // Leading pawns or pieces + { + d->groupIdx[0] = idx; + idx *= e.hasPawns ? LeadPawnsSize[d->groupLen[0]][f] + : e.hasUniquePieces ? 31332 : 462; + } + else if (k == order[1]) // Remaining pawns + { + d->groupIdx[1] = idx; + idx *= Binomial[d->groupLen[1]][48 - d->groupLen[0]]; + } + else // Remaining pieces + { + d->groupIdx[next] = idx; + idx *= Binomial[d->groupLen[next]][freeSquares]; + freeSquares -= d->groupLen[next++]; + } + + d->groupIdx[n] = idx; +} + +// In Recursive Pairing each symbol represents a pair of children symbols. So +// read d->btree[] symbols data and expand each one in his left and right child +// symbol until reaching the leafs that represent the symbol value. +uint8_t set_symlen(PairsData* d, Sym s, std::vector& visited) { + + visited[s] = true; // We can set it now because tree is acyclic + Sym sr = d->btree[s].get(); + + if (sr == 0xFFF) + return 0; + + Sym sl = d->btree[s].get(); + + if (!visited[sl]) + d->symlen[sl] = set_symlen(d, sl, visited); + + if (!visited[sr]) + d->symlen[sr] = set_symlen(d, sr, visited); + + return d->symlen[sl] + d->symlen[sr] + 1; +} + +uint8_t* set_sizes(PairsData* d, uint8_t* data) { + + d->flags = *data++; + + if (d->flags & TBFlag::SingleValue) { + d->blocksNum = d->blockLengthSize = 0; + d->span = d->sparseIndexSize = 0; // Broken MSVC zero-init + d->minSymLen = *data++; // Here we store the single value + return data; + } + + // groupLen[] is a zero-terminated list of group lengths, the last groupIdx[] + // element stores the biggest index that is the tb size. + uint64_t tbSize = d->groupIdx[std::find(d->groupLen, d->groupLen + 7, 0) - d->groupLen]; + + d->sizeofBlock = 1ULL << *data++; + d->span = 1ULL << *data++; + d->sparseIndexSize = size_t((tbSize + d->span - 1) / d->span); // Round up + auto padding = number(data++); + d->blocksNum = number(data); data += sizeof(uint32_t); + d->blockLengthSize = d->blocksNum + padding; // Padded to ensure SparseIndex[] + // does not point out of range. + d->maxSymLen = *data++; + d->minSymLen = *data++; + d->lowestSym = (Sym*)data; + d->base64.resize(d->maxSymLen - d->minSymLen + 1); + + // The canonical code is ordered such that longer symbols (in terms of + // the number of bits of their Huffman code) have lower numeric value, + // so that d->lowestSym[i] >= d->lowestSym[i+1] (when read as LittleEndian). + // Starting from this we compute a base64[] table indexed by symbol length + // and containing 64 bit values so that d->base64[i] >= d->base64[i+1]. + // See https://en.wikipedia.org/wiki/Huffman_coding + for (int i = d->base64.size() - 2; i >= 0; --i) { + d->base64[i] = (d->base64[i + 1] + number(&d->lowestSym[i]) + - number(&d->lowestSym[i + 1])) / 2; + + assert(d->base64[i] * 2 >= d->base64[i+1]); + } + + // Now left-shift by an amount so that d->base64[i] gets shifted 1 bit more + // than d->base64[i+1] and given the above assert condition, we ensure that + // d->base64[i] >= d->base64[i+1]. Moreover for any symbol s64 of length i + // and right-padded to 64 bits holds d->base64[i-1] >= s64 >= d->base64[i]. + for (size_t i = 0; i < d->base64.size(); ++i) + d->base64[i] <<= 64 - i - d->minSymLen; // Right-padding to 64 bits + + data += d->base64.size() * sizeof(Sym); + d->symlen.resize(number(data)); data += sizeof(uint16_t); + d->btree = (LR*)data; + + // The compression scheme used is "Recursive Pairing", that replaces the most + // frequent adjacent pair of symbols in the source message by a new symbol, + // reevaluating the frequencies of all of the symbol pairs with respect to + // the extended alphabet, and then repeating the process. + // See http://www.larsson.dogma.net/dcc99.pdf + std::vector visited(d->symlen.size()); + + for (Sym sym = 0; sym < d->symlen.size(); ++sym) + if (!visited[sym]) + d->symlen[sym] = set_symlen(d, sym, visited); + + return data + d->symlen.size() * sizeof(LR) + (d->symlen.size() & 1); +} + +uint8_t* set_dtz_map(TBTable&, uint8_t* data, File) { return data; } + +uint8_t* set_dtz_map(TBTable& e, uint8_t* data, File maxFile) { + + e.map = data; + + for (File f = FILE_A; f <= maxFile; ++f) { + auto flags = e.get(0, f)->flags; + if (flags & TBFlag::Mapped) { + if (flags & TBFlag::Wide) { + data += (uintptr_t)data & 1; // Word alignment, we may have a mixed table + for (int i = 0; i < 4; ++i) { // Sequence like 3,x,x,x,1,x,0,2,x,x + e.get(0, f)->map_idx[i] = (uint16_t)((uint16_t *)data - (uint16_t *)e.map + 1); + data += 2 * number(data) + 2; + } + } + else { + for (int i = 0; i < 4; ++i) { + e.get(0, f)->map_idx[i] = (uint16_t)(data - e.map + 1); + data += *data + 1; + } + } + } + } + + return data += (uintptr_t)data & 1; // Word alignment +} + +// Populate entry's PairsData records with data from the just memory mapped file. +// Called at first access. +template +void set(T& e, uint8_t* data) { + + PairsData* d; + + enum { Split = 1, HasPawns = 2 }; + + assert(e.hasPawns == bool(*data & HasPawns)); + assert((e.key != e.key2) == bool(*data & Split)); + + data++; // First byte stores flags + + const int sides = T::Sides == 2 && (e.key != e.key2) ? 2 : 1; + const File maxFile = e.hasPawns ? FILE_D : FILE_A; + + bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides + + assert(!pp || e.pawnCount[0]); + + for (File f = FILE_A; f <= maxFile; ++f) { + + for (int i = 0; i < sides; i++) + *e.get(i, f) = PairsData(); + + int order[][2] = { { *data & 0xF, pp ? *(data + 1) & 0xF : 0xF }, + { *data >> 4, pp ? *(data + 1) >> 4 : 0xF } }; + data += 1 + pp; + + for (int k = 0; k < e.pieceCount; ++k, ++data) + for (int i = 0; i < sides; i++) + { + int p = i ? *data >> 4 : *data & 0xF; + e.get(i, f)->pieces[k] = make_piece(Color(p >> 3), PieceType(p & 7)); + } + + for (int i = 0; i < sides; ++i) + set_groups(e, e.get(i, f), order[i], f); + } + + data += (uintptr_t)data & 1; // Word alignment + + for (File f = FILE_A; f <= maxFile; ++f) + for (int i = 0; i < sides; i++) + data = set_sizes(e.get(i, f), data); + + data = set_dtz_map(e, data, maxFile); + + for (File f = FILE_A; f <= maxFile; ++f) + for (int i = 0; i < sides; i++) { + (d = e.get(i, f))->sparseIndex = (SparseEntry*)data; + data += d->sparseIndexSize * sizeof(SparseEntry); + } + + for (File f = FILE_A; f <= maxFile; ++f) + for (int i = 0; i < sides; i++) { + (d = e.get(i, f))->blockLength = (uint16_t*)data; + data += d->blockLengthSize * sizeof(uint16_t); + } + + for (File f = FILE_A; f <= maxFile; ++f) + for (int i = 0; i < sides; i++) { + data = (uint8_t*)(((uintptr_t)data + 0x3F) & ~0x3F); // 64 byte alignment + (d = e.get(i, f))->data = data; + data += d->blocksNum * d->sizeofBlock; + } +} + +// If the TB file corresponding to the given position is already memory mapped +// then return its base address, otherwise try to memory map and init it. Called +// at every probe, memory map and init only at first access. Function is thread +// safe and can be called concurrently. +template +void* mapped(TBTable& e, const Position& pos) { + + static std::mutex mutex; + + // Use 'acquire' to avoid a thread reading 'ready' == true while + // another is still working. (compiler reordering may cause this). + if (e.ready.load(std::memory_order_acquire)) + return e.baseAddress; // Could be nullptr if file does not exist + + std::scoped_lock lk(mutex); + + if (e.ready.load(std::memory_order_relaxed)) // Recheck under lock + return e.baseAddress; + + // Pieces strings in decreasing order for each color, like ("KPP","KR") + std::string fname, w, b; + for (PieceType pt = KING; pt >= PAWN; --pt) { + w += std::string(popcount(pos.pieces(WHITE, pt)), PieceToChar[pt]); + b += std::string(popcount(pos.pieces(BLACK, pt)), PieceToChar[pt]); + } + + fname = (e.key == pos.material_key() ? w + 'v' + b : b + 'v' + w) + + (Type == WDL ? ".rtbw" : ".rtbz"); + + uint8_t* data = TBFile(fname).map(&e.baseAddress, &e.mapping, Type); + + if (data) + set(e, data); + + e.ready.store(true, std::memory_order_release); + return e.baseAddress; +} + +template::Ret> +Ret probe_table(const Position& pos, ProbeState* result, WDLScore wdl = WDLDraw) { + + if (pos.count() == 2) // KvK + return Ret(WDLDraw); + + TBTable* entry = TBTables.get(pos.material_key()); + + if (!entry || !mapped(*entry, pos)) + return *result = FAIL, Ret(); + + return do_probe_table(pos, entry, wdl, result); +} + +// For a position where the side to move has a winning capture it is not necessary +// to store a winning value so the generator treats such positions as "don't cares" +// and tries to assign to it a value that improves the compression ratio. Similarly, +// if the side to move has a drawing capture, then the position is at least drawn. +// If the position is won, then the TB needs to store a win value. But if the +// position is drawn, the TB may store a loss value if that is better for compression. +// All of this means that during probing, the engine must look at captures and probe +// their results and must probe the position itself. The "best" result of these +// probes is the correct result for the position. +// DTZ tables do not store values when a following move is a zeroing winning move +// (winning capture or winning pawn move). Also DTZ store wrong values for positions +// where the best move is an ep-move (even if losing). So in all these cases set +// the state to ZEROING_BEST_MOVE. +template +WDLScore search(Position& pos, ProbeState* result) { + + WDLScore value, bestValue = WDLLoss; + StateInfo st; + + auto moveList = MoveList(pos); + size_t totalCount = moveList.size(), moveCount = 0; + + for (const Move move : moveList) + { + if ( !pos.capture(move) + && (!CheckZeroingMoves || type_of(pos.moved_piece(move)) != PAWN)) + continue; + + moveCount++; + + pos.do_move(move, st); + value = -search(pos, result); + pos.undo_move(move); + + if (*result == FAIL) + return WDLDraw; + + if (value > bestValue) + { + bestValue = value; + + if (value >= WDLWin) + { + *result = ZEROING_BEST_MOVE; // Winning DTZ-zeroing move + return value; + } + } + } + + // In case we have already searched all the legal moves we don't have to probe + // the TB because the stored score could be wrong. For instance TB tables + // do not contain information on position with ep rights, so in this case + // the result of probe_wdl_table is wrong. Also in case of only capture + // moves, for instance here 4K3/4q3/6p1/2k5/6p1/8/8/8 w - - 0 7, we have to + // return with ZEROING_BEST_MOVE set. + bool noMoreMoves = (moveCount && moveCount == totalCount); + + if (noMoreMoves) + value = bestValue; + else + { + value = probe_table(pos, result); + + if (*result == FAIL) + return WDLDraw; + } + + // DTZ stores a "don't care" value if bestValue is a win + if (bestValue >= value) + return *result = ( bestValue > WDLDraw + || noMoreMoves ? ZEROING_BEST_MOVE : OK), bestValue; + + return *result = OK, value; +} + +} // namespace + + +/// Tablebases::init() is called at startup and after every change to +/// "SyzygyPath" UCI option to (re)create the various tables. It is not thread +/// safe, nor it needs to be. +void Tablebases::init(const std::string& paths) { + + TBTables.clear(); + MaxCardinality = 0; + TBFile::Paths = paths; + +#ifdef LARGEBOARDS + // Tablebases are not working for large-board version + return; +#endif + + if (paths.empty() || paths == "") + return; + + // MapB1H1H7[] encodes a square below a1-h8 diagonal to 0..27 + int code = 0; + for (Square s = SQ_A1; s <= SQ_H8; ++s) + if (off_A1H8(s) < 0) + MapB1H1H7[s] = code++; + + // MapA1D1D4[] encodes a square in the a1-d1-d4 triangle to 0..9 + std::vector diagonal; + code = 0; + for (Square s = SQ_A1; s <= SQ_D4; ++s) + if (off_A1H8(s) < 0 && file_of(s) <= FILE_D) + MapA1D1D4[s] = code++; + + else if (!off_A1H8(s) && file_of(s) <= FILE_D) + diagonal.push_back(s); + + // Diagonal squares are encoded as last ones + for (auto s : diagonal) + MapA1D1D4[s] = code++; + + // MapKK[] encodes all the 461 possible legal positions of two kings where + // the first is in the a1-d1-d4 triangle. If the first king is on the a1-d4 + // diagonal, the other one shall not to be above the a1-h8 diagonal. + std::vector> bothOnDiagonal; + code = 0; + for (int idx = 0; idx < 10; idx++) + for (Square s1 = SQ_A1; s1 <= SQ_D4; ++s1) + if (MapA1D1D4[s1] == idx && (idx || s1 == SQ_B1)) // SQ_B1 is mapped to 0 + { + for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) + if ((PseudoAttacks[WHITE][KING][s1] | s1) & s2) + continue; // Illegal position + + else if (!off_A1H8(s1) && off_A1H8(s2) > 0) + continue; // First on diagonal, second above + + else if (!off_A1H8(s1) && !off_A1H8(s2)) + bothOnDiagonal.emplace_back(idx, s2); + + else + MapKK[idx][s2] = code++; + } + + // Legal positions with both kings on diagonal are encoded as last ones + for (auto p : bothOnDiagonal) + MapKK[p.first][p.second] = code++; + + // Binomial[] stores the Binomial Coefficients using Pascal rule. There + // are Binomial[k][n] ways to choose k elements from a set of n elements. + Binomial[0][0] = 1; + + for (int n = 1; n < 64; n++) // Squares + for (int k = 0; k < 6 && k <= n; ++k) // Pieces + Binomial[k][n] = (k > 0 ? Binomial[k - 1][n - 1] : 0) + + (k < n ? Binomial[k ][n - 1] : 0); + + // MapPawns[s] encodes squares a2-h7 to 0..47. This is the number of possible + // available squares when the leading one is in 's'. Moreover the pawn with + // highest MapPawns[] is the leading pawn, the one nearest the edge and, + // among pawns with same file, the one with lowest rank. + int availableSquares = 47; // Available squares when lead pawn is in a2 + + // Init the tables for the encoding of leading pawns group: with 7-men TB we + // can have up to 5 leading pawns (KPPPPPK). + for (int leadPawnsCnt = 1; leadPawnsCnt <= 5; ++leadPawnsCnt) + for (File f = FILE_A; f <= FILE_D; ++f) + { + // Restart the index at every file because TB table is splitted + // by file, so we can reuse the same index for different files. + int idx = 0; + + // Sum all possible combinations for a given file, starting with + // the leading pawn on rank 2 and increasing the rank. + for (Rank r = RANK_2; r <= RANK_7; ++r) + { + Square sq = make_square(f, r); + + // Compute MapPawns[] at first pass. + // If sq is the leading pawn square, any other pawn cannot be + // below or more toward the edge of sq. There are 47 available + // squares when sq = a2 and reduced by 2 for any rank increase + // due to mirroring: sq == a3 -> no a2, h2, so MapPawns[a3] = 45 + if (leadPawnsCnt == 1) + { + MapPawns[sq] = availableSquares--; + MapPawns[flip_file(sq)] = availableSquares--; + } + LeadPawnIdx[leadPawnsCnt][sq] = idx; + idx += Binomial[leadPawnsCnt - 1][MapPawns[sq]]; + } + // After a file is traversed, store the cumulated per-file index + LeadPawnsSize[leadPawnsCnt][f] = idx; + } + + // Add entries in TB tables if the corresponding ".rtbw" file exists + for (PieceType p1 = PAWN; p1 <= QUEEN; ++p1) { + TBTables.add({KING, p1, KING}); + + for (PieceType p2 = PAWN; p2 <= p1; ++p2) { + TBTables.add({KING, p1, p2, KING}); + TBTables.add({KING, p1, KING, p2}); + + for (PieceType p3 = PAWN; p3 <= QUEEN; ++p3) + TBTables.add({KING, p1, p2, KING, p3}); + + for (PieceType p3 = PAWN; p3 <= p2; ++p3) { + TBTables.add({KING, p1, p2, p3, KING}); + + for (PieceType p4 = PAWN; p4 <= p3; ++p4) { + TBTables.add({KING, p1, p2, p3, p4, KING}); + + for (PieceType p5 = PAWN; p5 <= p4; ++p5) + TBTables.add({KING, p1, p2, p3, p4, p5, KING}); + + for (PieceType p5 = PAWN; p5 <= QUEEN; ++p5) + TBTables.add({KING, p1, p2, p3, p4, KING, p5}); + } + + for (PieceType p4 = PAWN; p4 <= QUEEN; ++p4) { + TBTables.add({KING, p1, p2, p3, KING, p4}); + + for (PieceType p5 = PAWN; p5 <= p4; ++p5) + TBTables.add({KING, p1, p2, p3, KING, p4, p5}); + } + } + + for (PieceType p3 = PAWN; p3 <= p1; ++p3) + for (PieceType p4 = PAWN; p4 <= (p1 == p3 ? p2 : p3); ++p4) + TBTables.add({KING, p1, p2, KING, p3, p4}); + } + } + + sync_cout << "info string Found " << TBTables.size() << " tablebases" << sync_endl; +} + +// Probe the WDL table for a particular position. +// If *result != FAIL, the probe was successful. +// The return value is from the point of view of the side to move: +// -2 : loss +// -1 : loss, but draw under 50-move rule +// 0 : draw +// 1 : win, but draw under 50-move rule +// 2 : win +WDLScore Tablebases::probe_wdl(Position& pos, ProbeState* result) { + + *result = OK; + return search(pos, result); +} + +// Probe the DTZ table for a particular position. +// If *result != FAIL, the probe was successful. +// The return value is from the point of view of the side to move: +// n < -100 : loss, but draw under 50-move rule +// -100 <= n < -1 : loss in n ply (assuming 50-move counter == 0) +// -1 : loss, the side to move is mated +// 0 : draw +// 1 < n <= 100 : win in n ply (assuming 50-move counter == 0) +// 100 < n : win, but draw under 50-move rule +// +// The return value n can be off by 1: a return value -n can mean a loss +// in n+1 ply and a return value +n can mean a win in n+1 ply. This +// cannot happen for tables with positions exactly on the "edge" of +// the 50-move rule. +// +// This implies that if dtz > 0 is returned, the position is certainly +// a win if dtz + 50-move-counter <= 99. Care must be taken that the engine +// picks moves that preserve dtz + 50-move-counter <= 99. +// +// If n = 100 immediately after a capture or pawn move, then the position +// is also certainly a win, and during the whole phase until the next +// capture or pawn move, the inequality to be preserved is +// dtz + 50-move-counter <= 100. +// +// In short, if a move is available resulting in dtz + 50-move-counter <= 99, +// then do not accept moves leading to dtz + 50-move-counter == 100. +int Tablebases::probe_dtz(Position& pos, ProbeState* result) { + + *result = OK; + WDLScore wdl = search(pos, result); + + if (*result == FAIL || wdl == WDLDraw) // DTZ tables don't store draws + return 0; + + // DTZ stores a 'don't care' value in this case, or even a plain wrong + // one as in case the best move is a losing ep, so it cannot be probed. + if (*result == ZEROING_BEST_MOVE) + return dtz_before_zeroing(wdl); + + int dtz = probe_table(pos, result, wdl); + + if (*result == FAIL) + return 0; + + if (*result != CHANGE_STM) + return (dtz + 100 * (wdl == WDLBlessedLoss || wdl == WDLCursedWin)) * sign_of(wdl); + + // DTZ stores results for the other side, so we need to do a 1-ply search and + // find the winning move that minimizes DTZ. + StateInfo st; + int minDTZ = 0xFFFF; + + for (const Move move : MoveList(pos)) + { + bool zeroing = pos.capture(move) || type_of(pos.moved_piece(move)) == PAWN; + + pos.do_move(move, st); + + // For zeroing moves we want the dtz of the move _before_ doing it, + // otherwise we will get the dtz of the next move sequence. Search the + // position after the move to get the score sign (because even in a + // winning position we could make a losing capture or going for a draw). + dtz = zeroing ? -dtz_before_zeroing(search(pos, result)) + : -probe_dtz(pos, result); + + // If the move mates, force minDTZ to 1 + if (dtz == 1 && pos.checkers() && MoveList(pos).size() == 0) + minDTZ = 1; + + // Convert result from 1-ply search. Zeroing moves are already accounted + // by dtz_before_zeroing() that returns the DTZ of the previous move. + if (!zeroing) + dtz += sign_of(dtz); + + // Skip the draws and if we are winning only pick positive dtz + if (dtz < minDTZ && sign_of(dtz) == sign_of(wdl)) + minDTZ = dtz; + + pos.undo_move(move); + + if (*result == FAIL) + return 0; + } + + // When there are no legal moves, the position is mate: we return -1 + return minDTZ == 0xFFFF ? -1 : minDTZ; +} + + +// Use the DTZ tables to rank root moves. +// +// A return value false indicates that not all probes were successful. +bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { + + ProbeState result; + StateInfo st; + + // Obtain 50-move counter for the root position + int cnt50 = pos.rule50_count(); + + // Check whether a position was repeated since the last zeroing move. + bool rep = pos.has_repeated(); + + int dtz, bound = Options["Syzygy50MoveRule"] ? 900 : 1; + + // Probe and rank each move + for (auto& m : rootMoves) + { + pos.do_move(m.pv[0], st); + + // Calculate dtz for the current move counting from the root position + if (pos.rule50_count() == 0) + { + // In case of a zeroing move, dtz is one of -101/-1/0/1/101 + WDLScore wdl = -probe_wdl(pos, &result); + dtz = dtz_before_zeroing(wdl); + } + else if (pos.is_draw(1)) + { + // In case a root move leads to a draw by repetition or + // 50-move rule, we set dtz to zero. Note: since we are + // only 1 ply from the root, this must be a true 3-fold + // repetition inside the game history. + dtz = 0; + } + else + { + // Otherwise, take dtz for the new position and correct by 1 ply + dtz = -probe_dtz(pos, &result); + dtz = dtz > 0 ? dtz + 1 + : dtz < 0 ? dtz - 1 : dtz; + } + + // Make sure that a mating move is assigned a dtz value of 1 + if ( pos.checkers() + && dtz == 2 + && MoveList(pos).size() == 0) + dtz = 1; + + pos.undo_move(m.pv[0]); + + if (result == FAIL) + return false; + + // Better moves are ranked higher. Certain wins are ranked equally. + // Losing moves are ranked equally unless a 50-move draw is in sight. + int r = dtz > 0 ? (dtz + cnt50 <= 99 && !rep ? 1000 : 1000 - (dtz + cnt50)) + : dtz < 0 ? (-dtz * 2 + cnt50 < 100 ? -1000 : -1000 + (-dtz + cnt50)) + : 0; + m.tbRank = r; + + // Determine the score to be displayed for this move. Assign at least + // 1 cp to cursed wins and let it grow to 49 cp as the positions gets + // closer to a real win. + m.tbScore = r >= bound ? VALUE_MATE - MAX_PLY - 1 + : r > 0 ? Value((std::max( 3, r - 800) * int(PawnValueEg)) / 200) + : r == 0 ? VALUE_DRAW + : r > -bound ? Value((std::min(-3, r + 800) * int(PawnValueEg)) / 200) + : -VALUE_MATE + MAX_PLY + 1; + } + + return true; +} + + +// Use the WDL tables to rank root moves. +// This is a fallback for the case that some or all DTZ tables are missing. +// +// A return value false indicates that not all probes were successful. +bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) { + + static const int WDL_to_rank[] = { -1000, -899, 0, 899, 1000 }; + + ProbeState result; + StateInfo st; + WDLScore wdl; + + bool rule50 = Options["Syzygy50MoveRule"]; + + // Probe and rank each move + for (auto& m : rootMoves) + { + pos.do_move(m.pv[0], st); + + if (pos.is_draw(1)) + wdl = WDLDraw; + else + wdl = -probe_wdl(pos, &result); + + pos.undo_move(m.pv[0]); + + if (result == FAIL) + return false; + + m.tbRank = WDL_to_rank[wdl + 2]; + + if (!rule50) + wdl = wdl > WDLDraw ? WDLWin + : wdl < WDLDraw ? WDLLoss : WDLDraw; + m.tbScore = WDL_to_value[wdl + 2]; + } + + return true; +} + +} // namespace Stockfish diff --git a/Fairy-Stockfish-master/src/syzygy/tbprobe.h b/Fairy-Stockfish-master/src/syzygy/tbprobe.h new file mode 100644 index 0000000000000000000000000000000000000000..c2917fef636d53f0c8ed00e4ab709e5ddfe099de --- /dev/null +++ b/Fairy-Stockfish-master/src/syzygy/tbprobe.h @@ -0,0 +1,78 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef TBPROBE_H +#define TBPROBE_H + +#include + +#include "../search.h" + +namespace Stockfish::Tablebases { + +enum WDLScore { + WDLLoss = -2, // Loss + WDLBlessedLoss = -1, // Loss, but draw under 50-move rule + WDLDraw = 0, // Draw + WDLCursedWin = 1, // Win, but draw under 50-move rule + WDLWin = 2, // Win + + WDLScoreNone = -1000 +}; + +// Possible states after a probing operation +enum ProbeState { + FAIL = 0, // Probe failed (missing file table) + OK = 1, // Probe successful + CHANGE_STM = -1, // DTZ should check the other side + ZEROING_BEST_MOVE = 2 // Best move zeroes DTZ (capture or pawn move) +}; + +extern int MaxCardinality; + +void init(const std::string& paths); +WDLScore probe_wdl(Position& pos, ProbeState* result); +int probe_dtz(Position& pos, ProbeState* result); +bool root_probe(Position& pos, Search::RootMoves& rootMoves); +bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves); +void rank_root_moves(Position& pos, Search::RootMoves& rootMoves); + +inline std::ostream& operator<<(std::ostream& os, const WDLScore v) { + + os << (v == WDLLoss ? "Loss" : + v == WDLBlessedLoss ? "Blessed loss" : + v == WDLDraw ? "Draw" : + v == WDLCursedWin ? "Cursed win" : + v == WDLWin ? "Win" : "None"); + + return os; +} + +inline std::ostream& operator<<(std::ostream& os, const ProbeState v) { + + os << (v == FAIL ? "Failed" : + v == OK ? "Success" : + v == CHANGE_STM ? "Probed opponent side" : + v == ZEROING_BEST_MOVE ? "Best move zeroes DTZ" : "None"); + + return os; +} + +} // namespace Stockfish::Tablebases + +#endif diff --git a/Fairy-Stockfish-master/src/thread.cpp b/Fairy-Stockfish-master/src/thread.cpp new file mode 100644 index 0000000000000000000000000000000000000000..32aaf58ea554fc018887df1e734e845ae47ff902 --- /dev/null +++ b/Fairy-Stockfish-master/src/thread.cpp @@ -0,0 +1,292 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include // For std::count +#include "movegen.h" +#include "partner.h" +#include "search.h" +#include "thread.h" +#include "uci.h" +#include "syzygy/tbprobe.h" +#include "tt.h" +#include "xboard.h" + +namespace Stockfish { + +ThreadPool Threads; // Global object + + +/// Thread constructor launches the thread and waits until it goes to sleep +/// in idle_loop(). Note that 'searching' and 'exit' should be already set. + +Thread::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this) { + + wait_for_search_finished(); +} + + +/// Thread destructor wakes up the thread in idle_loop() and waits +/// for its termination. Thread should be already waiting. + +Thread::~Thread() { + + assert(!searching); + + exit = true; + start_searching(); + stdThread.join(); +} + + +/// Thread::clear() reset histories, usually before a new game + +void Thread::clear() { + + counterMoves.fill(MOVE_NONE); + mainHistory.fill(0); + gateHistory.fill(0); + lowPlyHistory.fill(0); + captureHistory.fill(0); + + for (bool inCheck : { false, true }) + for (StatsType c : { NoCaptures, Captures }) + { + for (auto& to : continuationHistory[inCheck][c]) + for (auto& h : to) + h->fill(0); + continuationHistory[inCheck][c][NO_PIECE][0]->fill(Search::CounterMovePruneThreshold - 1); + } +} + + +/// Thread::start_searching() wakes up the thread that will start the search + +void Thread::start_searching() { + + std::lock_guard lk(mutex); + searching = true; + cv.notify_one(); // Wake up the thread in idle_loop() +} + + +/// Thread::wait_for_search_finished() blocks on the condition variable +/// until the thread has finished searching. + +void Thread::wait_for_search_finished() { + + std::unique_lock lk(mutex); + cv.wait(lk, [&]{ return !searching; }); +} + + +/// Thread::idle_loop() is where the thread is parked, blocked on the +/// condition variable, when it has no work to do. + +void Thread::idle_loop() { + + // If OS already scheduled us on a different group than 0 then don't overwrite + // the choice, eventually we are one of many one-threaded processes running on + // some Windows NUMA hardware, for instance in fishtest. To make it simple, + // just check if running threads are below a threshold, in this case all this + // NUMA machinery is not needed. + if (Options["Threads"] > 8) + WinProcGroup::bindThisThread(idx); + + while (true) + { + std::unique_lock lk(mutex); + searching = false; + cv.notify_one(); // Wake up anyone waiting for search finished + // Start ponder search from separate thread to prevent deadlock + if (Threads.size() && this == Threads.main() && XBoard::stateMachine && XBoard::stateMachine->ponderMove) + { + NativeThread t(&XBoard::StateMachine::ponder, XBoard::stateMachine); + t.detach(); + } + cv.wait(lk, [&]{ return searching; }); + + if (exit) + return; + + lk.unlock(); + + search(); + } +} + +/// ThreadPool::set() creates/destroys threads to match the requested number. +/// Created and launched threads will immediately go to sleep in idle_loop. +/// Upon resizing, threads are recreated to allow for binding if necessary. + +void ThreadPool::set(size_t requested) { + + if (size() > 0) // destroy any existing thread(s) + { + main()->wait_for_search_finished(); + + while (size() > 0) + delete back(), pop_back(); + } + + if (requested > 0) // create new thread(s) + { + push_back(new MainThread(0)); + + while (size() < requested) + push_back(new Thread(size())); + clear(); + + // Reallocate the hash with the new threadpool size + TT.resize(size_t(Options["Hash"])); + + // Init thread number dependent search params. + Search::init(); + } +} + + +/// ThreadPool::clear() sets threadPool data to initial values + +void ThreadPool::clear() { + + for (Thread* th : *this) + th->clear(); + + main()->callsCnt = 0; + main()->bestPreviousScore = VALUE_INFINITE; + main()->previousTimeReduction = 1.0; +} + + +/// ThreadPool::start_thinking() wakes up main thread waiting in idle_loop() and +/// returns immediately. Main thread will wake up other threads and start the search. + +void ThreadPool::start_thinking(Position& pos, StateListPtr& states, + const Search::LimitsType& limits, bool ponderMode) { + + main()->wait_for_search_finished(); + + main()->stopOnPonderhit = stop = abort = false; + increaseDepth = true; + main()->ponder = ponderMode; + Search::Limits = limits; + Search::RootMoves rootMoves; + + for (const auto& m : MoveList(pos)) + if ( (limits.searchmoves.empty() || std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m)) + && (limits.banmoves.empty() || !std::count(limits.banmoves.begin(), limits.banmoves.end(), m))) + rootMoves.emplace_back(m); + + // Add virtual drops + if (pos.two_boards() && Partner.opptime && limits.time[pos.side_to_move()] > Partner.opptime + 1000) + { + if (pos.checkers()) + { + for (const auto& m : MoveList(pos)) + if (pos.virtual_drop(m) && pos.legal(m)) + rootMoves.emplace_back(m); + } + else + { + for (const auto& m : MoveList(pos)) + if (pos.virtual_drop(m) && pos.legal(m)) + rootMoves.emplace_back(m); + } + } + + if (!rootMoves.empty()) + Tablebases::rank_root_moves(pos, rootMoves); + + // After ownership transfer 'states' becomes empty, so if we stop the search + // and call 'go' again without setting a new position states.get() == NULL. + assert(states.get() || setupStates.get()); + + if (states.get()) + setupStates = std::move(states); // Ownership transfer, states is now empty + + // We use Position::set() to set root position across threads. But there are + // some StateInfo fields (previous, pliesFromNull, capturedPiece) that cannot + // be deduced from a fen string, so set() clears them and they are set from + // setupStates->back() later. The rootState is per thread, earlier states are shared + // since they are read-only. + for (Thread* th : *this) + { + 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(), &th->rootState, th); + th->rootState = setupStates->back(); + } + + main()->start_searching(); +} + +Thread* ThreadPool::get_best_thread() const { + + Thread* bestThread = front(); + std::map votes; + Value minScore = VALUE_NONE; + + // Find minimum score of all threads + for (Thread* th: *this) + minScore = std::min(minScore, th->rootMoves[0].score); + + // Vote according to score and depth, and select the best thread + for (Thread* th : *this) + { + votes[th->rootMoves[0].pv[0]] += + (th->rootMoves[0].score - minScore + 14) * int(th->completedDepth); + + if (abs(bestThread->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY) + { + // Make sure we pick the shortest mate / TB conversion or stave off mate the longest + if (th->rootMoves[0].score > bestThread->rootMoves[0].score) + bestThread = th; + } + else if ( th->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY + || ( th->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY + && votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]])) + bestThread = th; + } + + return bestThread; +} + + +/// Start non-main threads + +void ThreadPool::start_searching() { + + for (Thread* th : *this) + if (th != front()) + th->start_searching(); +} + + +/// Wait for non-main threads + +void ThreadPool::wait_for_search_finished() const { + + for (Thread* th : *this) + if (th != front()) + th->wait_for_search_finished(); +} + +} // namespace Stockfish diff --git a/Fairy-Stockfish-master/src/thread.h b/Fairy-Stockfish-master/src/thread.h new file mode 100644 index 0000000000000000000000000000000000000000..464be79ed8e6c6948c56f3ad5e98610f5e1b7d0b --- /dev/null +++ b/Fairy-Stockfish-master/src/thread.h @@ -0,0 +1,137 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef THREAD_H_INCLUDED +#define THREAD_H_INCLUDED + +#include +#include +#include +#include +#include + +#include "material.h" +#include "movepick.h" +#include "pawns.h" +#include "position.h" +#include "search.h" +#include "thread_win32_osx.h" + +namespace Stockfish { + +/// Thread class keeps together all the thread-related stuff. We use +/// per-thread pawn and material hash tables so that once we get a +/// pointer to an entry its life time is unlimited and we don't have +/// to care about someone changing the entry under our feet. + +class Thread { + + std::mutex mutex; + std::condition_variable cv; + size_t idx; + bool exit = false, searching = true; // Set before starting std::thread + NativeThread stdThread; + +public: + explicit Thread(size_t); + virtual ~Thread(); + virtual void search(); + void clear(); + void idle_loop(); + void start_searching(); + void wait_for_search_finished(); + size_t id() const { return idx; } + + Pawns::Table pawnsTable; + Material::Table materialTable; + size_t pvIdx, pvLast; + uint64_t ttHitAverage; + int selDepth, nmpMinPly; + Color nmpColor; + std::atomic nodes, tbHits, bestMoveChanges; + + Position rootPos; + StateInfo rootState; + Search::RootMoves rootMoves; + Depth rootDepth, completedDepth; + CounterMoveHistory counterMoves; + ButterflyHistory mainHistory; + GateHistory gateHistory; + LowPlyHistory lowPlyHistory; + CapturePieceToHistory captureHistory; + ContinuationHistory continuationHistory[2][2]; + Score trend; +}; + + +/// MainThread is a derived class specific for main thread + +struct MainThread : public Thread { + + using Thread::Thread; + + void search() override; + void check_time(); + + double previousTimeReduction; + Value bestPreviousScore; + Value iterValue[4]; + int callsCnt; + bool stopOnPonderhit; + std::atomic_bool ponder; + Thread* bestThread; // to fetch best move when in XBoard mode +}; + + +/// ThreadPool struct handles all the threads-related stuff like init, starting, +/// parking and, most importantly, launching a thread. All the access to threads +/// is done through this class. + +struct ThreadPool : public std::vector { + + void start_thinking(Position&, StateListPtr&, const Search::LimitsType&, bool = false); + void clear(); + void set(size_t); + + MainThread* main() const { return static_cast(front()); } + uint64_t nodes_searched() const { return accumulate(&Thread::nodes); } + uint64_t tb_hits() const { return accumulate(&Thread::tbHits); } + Thread* get_best_thread() const; + void start_searching(); + void wait_for_search_finished() const; + + std::atomic_bool stop, increaseDepth; + std::atomic_bool abort, sit; + + StateListPtr setupStates; + +private: + uint64_t accumulate(std::atomic Thread::* member) const { + + uint64_t sum = 0; + for (Thread* th : *this) + sum += (th->*member).load(std::memory_order_relaxed); + return sum; + } +}; + +extern ThreadPool Threads; + +} // namespace Stockfish + +#endif // #ifndef THREAD_H_INCLUDED diff --git a/Fairy-Stockfish-master/src/thread_win32_osx.h b/Fairy-Stockfish-master/src/thread_win32_osx.h new file mode 100644 index 0000000000000000000000000000000000000000..e2adc76d5815eae41e84bb750752ca604e369616 --- /dev/null +++ b/Fairy-Stockfish-master/src/thread_win32_osx.h @@ -0,0 +1,75 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef THREAD_WIN32_OSX_H_INCLUDED +#define THREAD_WIN32_OSX_H_INCLUDED + +#include + +/// On OSX threads other than the main thread are created with a reduced stack +/// size of 512KB by default, this is too low for deep searches, which require +/// somewhat more than 1MB stack, so adjust it to TH_STACK_SIZE. +/// The implementation calls pthread_create() with the stack size parameter +/// equal to the linux 8MB default, on platforms that support it. + +#if defined(__APPLE__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(USE_PTHREADS) + +#include + +namespace Stockfish { + +static const size_t TH_STACK_SIZE = 8 * 1024 * 1024; + +template > +void* start_routine(void* ptr) +{ + P* p = reinterpret_cast(ptr); + (p->first->*(p->second))(); // Call member function pointer + delete p; + return NULL; +} + +class NativeThread { + + pthread_t thread; + +public: + template> + explicit NativeThread(void(T::*fun)(), T* obj) { + pthread_attr_t attr_storage, *attr = &attr_storage; + pthread_attr_init(attr); + pthread_attr_setstacksize(attr, TH_STACK_SIZE); + pthread_create(&thread, attr, start_routine, new P(obj, fun)); + } + void join() { pthread_join(thread, NULL); } + void detach() { pthread_detach(thread); } +}; + +} // namespace Stockfish + +#else // Default case: use STL classes + +namespace Stockfish { + +typedef std::thread NativeThread; + +} // namespace Stockfish + +#endif + +#endif // #ifndef THREAD_WIN32_OSX_H_INCLUDED diff --git a/Fairy-Stockfish-master/src/timeman.cpp b/Fairy-Stockfish-master/src/timeman.cpp new file mode 100644 index 0000000000000000000000000000000000000000..02d9c0a7032684db8b3fc6c6863b730acd9f3aee --- /dev/null +++ b/Fairy-Stockfish-master/src/timeman.cpp @@ -0,0 +1,115 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include + +#include "partner.h" +#include "search.h" +#include "timeman.h" +#include "uci.h" + +namespace Stockfish { + +TimeManagement Time; // Our global time management object + + +/// TimeManagement::init() is called at the beginning of the search and calculates +/// the bounds of time allowed for the current game ply. We currently support: +// 1) x basetime (+ z increment) +// 2) x moves in y seconds (+ z increment) + +void TimeManagement::init(const Position& pos, Search::LimitsType& limits, Color us, int ply) { + + TimePoint moveOverhead = TimePoint(Options["Move Overhead"]); + TimePoint slowMover = TimePoint(Options["Slow Mover"]); + TimePoint npmsec = TimePoint(Options["nodestime"]); + + // optScale is a percentage of available time to use for the current move. + // maxScale is a multiplier applied to optimumTime. + double optScale, maxScale; + + // If we have to play in 'nodes as time' mode, then convert from time + // to nodes, and use resulting values in time management formulas. + // WARNING: to avoid time losses, the given npmsec (nodes per millisecond) + // must be much lower than the real engine speed. + if (npmsec) + { + if (!availableNodes) // Only once at game start + availableNodes = npmsec * limits.time[us]; // Time is in msec + + // Convert from milliseconds to nodes + limits.time[us] = TimePoint(availableNodes); + limits.inc[us] *= npmsec; + limits.npmsec = npmsec; + } + + startTime = limits.startTime; + + // Maximum move horizon of 50 moves + int mtg = limits.movestogo ? std::min(limits.movestogo, 50) : 50; + + // Make sure timeLeft is > 0 since we may use it as a divisor + TimePoint timeLeft = std::max(TimePoint(1), + limits.time[us] + limits.inc[us] * (mtg - 1) - moveOverhead * (2 + mtg)); + + // Adjust time management for four-player variants + if (pos.two_boards()) + { + if (Partner.partnerDead && Partner.opptime) + timeLeft -= Partner.opptime; + else + { + timeLeft = std::min(timeLeft, 5000 + std::min(std::abs(limits.time[us] - Partner.opptime), TimePoint(Partner.opptime))); + if (Partner.fast || Partner.partnerDead) + timeLeft /= 4; + } + } + + // A user may scale time usage by setting UCI option "Slow Mover" + // Default is 100 and changing this value will probably lose elo. + timeLeft = slowMover * timeLeft / 100; + + // x basetime (+ z increment) + // If there is a healthy increment, timeLeft can exceed actual available + // game time for the current move, so also cap to 20% of available game time. + if (limits.movestogo == 0) + { + optScale = std::min(0.0084 + std::pow(ply + 3.0, 0.5) * 0.0042, + 0.2 * limits.time[us] / double(timeLeft)); + maxScale = std::min(7.0, 4.0 + ply / 12.0); + } + + // x moves in y seconds (+ z increment) + else + { + optScale = std::min((0.8 + ply / 128.0) / mtg, + 0.8 * limits.time[us] / double(timeLeft)); + maxScale = std::min(6.3, 1.5 + 0.11 * mtg); + } + + // Never use more than 80% of the available time for this move + optimumTime = TimePoint(optScale * timeLeft); + maximumTime = TimePoint(std::min(0.8 * limits.time[us] - moveOverhead, maxScale * optimumTime)); + + if (Options["Ponder"]) + optimumTime += optimumTime / 4; +} + +} // namespace Stockfish diff --git a/Fairy-Stockfish-master/src/timeman.h b/Fairy-Stockfish-master/src/timeman.h new file mode 100644 index 0000000000000000000000000000000000000000..9b6201d07d1b1c63a6d74633faeb4f15e2f699b0 --- /dev/null +++ b/Fairy-Stockfish-master/src/timeman.h @@ -0,0 +1,51 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef TIMEMAN_H_INCLUDED +#define TIMEMAN_H_INCLUDED + +#include "misc.h" +#include "search.h" +#include "thread.h" + +namespace Stockfish { + +/// The TimeManagement class computes the optimal time to think depending on +/// the maximum available time, the game move number and other parameters. + +class TimeManagement { +public: + void init(const Position& pos, Search::LimitsType& limits, Color us, int ply); + TimePoint optimum() const { return optimumTime; } + TimePoint maximum() const { return maximumTime; } + TimePoint elapsed() const { return Search::Limits.npmsec ? + TimePoint(Threads.nodes_searched()) : now() - startTime; } + + int64_t availableNodes; // When in 'nodes as time' mode + +private: + TimePoint startTime; + TimePoint optimumTime; + TimePoint maximumTime; +}; + +extern TimeManagement Time; + +} // namespace Stockfish + +#endif // #ifndef TIMEMAN_H_INCLUDED diff --git a/Fairy-Stockfish-master/src/tt.cpp b/Fairy-Stockfish-master/src/tt.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5982d937deb8de70f84cdf7b203c48fa6b04cad4 --- /dev/null +++ b/Fairy-Stockfish-master/src/tt.cpp @@ -0,0 +1,162 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include // For std::memset +#include +#include + +#include "bitboard.h" +#include "misc.h" +#include "thread.h" +#include "tt.h" +#include "uci.h" + +namespace Stockfish { + +TranspositionTable TT; // Our global transposition table + +/// TTEntry::save() populates the TTEntry with a new node's data, possibly +/// overwriting an old position. Update is not atomic and can be racy. + +void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) { + + // Preserve any existing move for the same position + if (m || (uint16_t)k != key16) + move32 = (uint32_t)m; + + // 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 < 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; + } +} + + +/// TranspositionTable::resize() sets the size of the transposition table, +/// measured in megabytes. Transposition table consists of a power of 2 number +/// of clusters and each cluster consists of ClusterSize number of TTEntry. + +void TranspositionTable::resize(size_t mbSize) { + + Threads.main()->wait_for_search_finished(); + + aligned_large_pages_free(table); + + clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster); + + table = static_cast(aligned_large_pages_alloc(clusterCount * sizeof(Cluster))); + if (!table) + { + std::cerr << "Failed to allocate " << mbSize + << "MB for transposition table." << std::endl; + exit(EXIT_FAILURE); + } + + clear(); +} + + +/// TranspositionTable::clear() initializes the entire transposition table to zero, +// in a multi-threaded way. + +void TranspositionTable::clear() { + + std::vector threads; + + for (size_t idx = 0; idx < Options["Threads"]; ++idx) + { + threads.emplace_back([this, idx]() { + + // Thread binding gives faster search on systems with a first-touch policy + if (Options["Threads"] > 8) + WinProcGroup::bindThisThread(idx); + + // Each thread will zero its part of the hash table + const size_t stride = size_t(clusterCount / Options["Threads"]), + start = size_t(stride * idx), + len = idx != Options["Threads"] - 1 ? + stride : clusterCount - start; + + std::memset(&table[start], 0, len * sizeof(Cluster)); + }); + } + + for (std::thread& th : threads) + th.join(); +} + + +/// TranspositionTable::probe() looks up the current position in the transposition +/// table. It returns true and a pointer to the TTEntry if the position is found. +/// Otherwise, it returns false and a pointer to an empty or least valuable TTEntry +/// to be replaced later. The replace value of an entry is calculated as its depth +/// minus 8 times its relative age. TTEntry t1 is considered more valuable than +/// TTEntry t2 if its replace value is greater than that of t2. + +TTEntry* TranspositionTable::probe(const Key key, bool& found) const { + + TTEntry* const tte = first_entry(key); + const uint16_t key16 = (uint16_t)key; // Use the low 16 bits as key inside the cluster + + for (int i = 0; i < ClusterSize; ++i) + if (tte[i].key16 == key16 || !tte[i].depth8) + { + tte[i].genBound8 = uint8_t(generation8 | (tte[i].genBound8 & (GENERATION_DELTA - 1))); // Refresh + + return found = (bool)tte[i].depth8, &tte[i]; + } + + // Find an entry to be replaced according to the replacement strategy + TTEntry* replace = tte; + for (int i = 1; i < ClusterSize; ++i) + // Due to our packed storage format for generation and its cyclic + // nature we add GENERATION_CYCLE (256 is the modulus, plus what + // is needed to keep the unrelated lowest n bits from affecting + // the result) to calculate the entry age correctly even after + // generation8 overflows into the next cycle. + if ( replace->depth8 - ((GENERATION_CYCLE + generation8 - replace->genBound8) & GENERATION_MASK) + > tte[i].depth8 - ((GENERATION_CYCLE + generation8 - tte[i].genBound8) & GENERATION_MASK)) + replace = &tte[i]; + + return found = false, replace; +} + + +/// TranspositionTable::hashfull() returns an approximation of the hashtable +/// occupation during a search. The hash is x permill full, as per UCI protocol. + +int TranspositionTable::hashfull() const { + + int cnt = 0; + for (int i = 0; i < 1000; ++i) + for (int j = 0; j < ClusterSize; ++j) + cnt += table[i].entry[j].depth8 && (table[i].entry[j].genBound8 & GENERATION_MASK) == generation8; + + return cnt / ClusterSize; +} + +} // namespace Stockfish diff --git a/Fairy-Stockfish-master/src/tt.h b/Fairy-Stockfish-master/src/tt.h new file mode 100644 index 0000000000000000000000000000000000000000..290ccb1c14097a1b048c5633048c271824742e46 --- /dev/null +++ b/Fairy-Stockfish-master/src/tt.h @@ -0,0 +1,107 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef TT_H_INCLUDED +#define TT_H_INCLUDED + +#include "misc.h" +#include "types.h" + +namespace Stockfish { + +/// TTEntry struct is the 12 bytes transposition table entry, defined as below: +/// +/// key 16 bit +/// depth 8 bit +/// generation 5 bit +/// pv node 1 bit +/// bound type 2 bit +/// move 32 bit (official SF: 16 bit) +/// value 16 bit +/// eval value 16 bit + +struct TTEntry { + + Move move() const { return (Move )move32; } + Value value() const { return (Value)value16; } + Value eval() const { return (Value)eval16; } + Depth depth() const { return (Depth)depth8 + DEPTH_OFFSET; } + bool is_pv() const { return (bool)(genBound8 & 0x4); } + Bound bound() const { return (Bound)(genBound8 & 0x3); } + void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev); + +private: + friend class TranspositionTable; + + uint16_t key16; + uint8_t depth8; + uint8_t genBound8; + uint32_t move32; + int16_t value16; + int16_t eval16; +}; + + +/// A TranspositionTable is an array of Cluster, of size clusterCount. Each +/// cluster consists of ClusterSize number of TTEntry. Each non-empty TTEntry +/// contains information on exactly one position. The size of a Cluster should +/// divide the size of a cache line for best performance, as the cacheline is +/// prefetched when possible. + +class TranspositionTable { + + static constexpr int ClusterSize = 5; + + struct Cluster { + TTEntry entry[ClusterSize]; + char padding[4]; // Pad to 64 bytes + }; + + static_assert(sizeof(Cluster) == 64, "Unexpected Cluster size"); + + // Constants used to refresh the hash table periodically + static constexpr unsigned GENERATION_BITS = 3; // nb of bits reserved for other things + static constexpr int GENERATION_DELTA = (1 << GENERATION_BITS); // increment for generation field + static constexpr int GENERATION_CYCLE = 255 + (1 << GENERATION_BITS); // cycle length + static constexpr int GENERATION_MASK = (0xFF << GENERATION_BITS) & 0xFF; // mask to pull out generation number + +public: + ~TranspositionTable() { aligned_large_pages_free(table); } + void new_search() { generation8 += GENERATION_DELTA; } // Lower bits are used for other things + TTEntry* probe(const Key key, bool& found) const; + int hashfull() const; + void resize(size_t mbSize); + void clear(); + + TTEntry* first_entry(const Key key) const { + return &table[mul_hi64(key, clusterCount)].entry[0]; + } + +private: + friend struct TTEntry; + + size_t clusterCount; + Cluster* table; + uint8_t generation8; // Size must be not bigger than TTEntry::genBound8 +}; + +extern TranspositionTable TT; + +} // namespace Stockfish + +#endif // #ifndef TT_H_INCLUDED diff --git a/Fairy-Stockfish-master/src/tune.cpp b/Fairy-Stockfish-master/src/tune.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a885845f75086823c03a1b6439dcefdce39c5638 --- /dev/null +++ b/Fairy-Stockfish-master/src/tune.cpp @@ -0,0 +1,133 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include + +#include "types.h" +#include "misc.h" +#include "uci.h" + +using std::string; + +namespace Stockfish { + +bool Tune::update_on_last; +const UCI::Option* LastOption = nullptr; +static std::map TuneResults; + +string Tune::next(string& names, bool pop) { + + string name; + + do { + string token = names.substr(0, names.find(',')); + + if (pop) + names.erase(0, token.size() + 1); + + std::stringstream ws(token); + name += (ws >> token, token); // Remove trailing whitespace + + } while ( std::count(name.begin(), name.end(), '(') + - std::count(name.begin(), name.end(), ')')); + + return name; +} + +static void on_tune(const UCI::Option& o) { + + if (!Tune::update_on_last || LastOption == &o) + Tune::read_options(); +} + +static void make_option(const string& n, int v, const SetRange& r) { + + // Do not generate option when there is nothing to tune (ie. min = max) + if (r(v).first == r(v).second) + return; + + if (TuneResults.count(n)) + v = TuneResults[n]; + + Options[n] << UCI::Option(v, r(v).first, r(v).second, on_tune); + LastOption = &Options[n]; + + // Print formatted parameters, ready to be copy-pasted in Fishtest + std::cout << n << "," + << v << "," + << r(v).first << "," << r(v).second << "," + << (r(v).second - r(v).first) / 20.0 << "," + << "0.0020" + << std::endl; +} + +template<> void Tune::Entry::init_option() { make_option(name, value, range); } + +template<> void Tune::Entry::read_option() { + if (Options.count(name)) + value = int(Options[name]); +} + +template<> void Tune::Entry::init_option() { make_option(name, value, range); } + +template<> void Tune::Entry::read_option() { + if (Options.count(name)) + value = Value(int(Options[name])); +} + +template<> void Tune::Entry::init_option() { + make_option("m" + name, mg_value(value), range); + make_option("e" + name, eg_value(value), range); +} + +template<> void Tune::Entry::read_option() { + if (Options.count("m" + name)) + value = make_score(int(Options["m" + name]), eg_value(value)); + + if (Options.count("e" + name)) + value = make_score(mg_value(value), int(Options["e" + name])); +} + +// Instead of a variable here we have a PostUpdate function: just call it +template<> void Tune::Entry::init_option() {} +template<> void Tune::Entry::read_option() { value(); } + +} // namespace Stockfish + + +// Init options with tuning session results instead of default values. Useful to +// get correct bench signature after a tuning session or to test tuned values. +// Just copy fishtest tuning results in a result.txt file and extract the +// values with: +// +// cat results.txt | sed 's/^param: \([^,]*\), best: \([^,]*\).*/ TuneResults["\1"] = int(round(\2));/' +// +// Then paste the output below, as the function body + +#include + +namespace Stockfish { + +void Tune::read_results() { + + /* ...insert your values here... */ +} + +} // namespace Stockfish diff --git a/Fairy-Stockfish-master/src/tune.h b/Fairy-Stockfish-master/src/tune.h new file mode 100644 index 0000000000000000000000000000000000000000..75ab484acf066bff951e7d40b1e79d4e28e302b2 --- /dev/null +++ b/Fairy-Stockfish-master/src/tune.h @@ -0,0 +1,163 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef TUNE_H_INCLUDED +#define TUNE_H_INCLUDED + +#include +#include +#include +#include + +namespace Stockfish { + +typedef std::pair Range; // Option's min-max values +typedef Range (RangeFun) (int); + +// Default Range function, to calculate Option's min-max values +inline Range default_range(int v) { + return v > 0 ? Range(0, 2 * v) : Range(2 * v, 0); +} + +struct SetRange { + explicit SetRange(RangeFun f) : fun(f) {} + SetRange(int min, int max) : fun(nullptr), range(min, max) {} + Range operator()(int v) const { return fun ? fun(v) : range; } + + RangeFun* fun; + Range range; +}; + +#define SetDefaultRange SetRange(default_range) + + +/// Tune class implements the 'magic' code that makes the setup of a fishtest +/// tuning session as easy as it can be. Mainly you have just to remove const +/// qualifiers from the variables you want to tune and flag them for tuning, so +/// if you have: +/// +/// const Score myScore = S(10, 15); +/// const Value myValue[][2] = { { V(100), V(20) }, { V(7), V(78) } }; +/// +/// If you have a my_post_update() function to run after values have been updated, +/// and a my_range() function to set custom Option's min-max values, then you just +/// remove the 'const' qualifiers and write somewhere below in the file: +/// +/// TUNE(SetRange(my_range), myScore, myValue, my_post_update); +/// +/// You can also set the range directly, and restore the default at the end +/// +/// TUNE(SetRange(-100, 100), myScore, SetDefaultRange); +/// +/// In case update function is slow and you have many parameters, you can add: +/// +/// UPDATE_ON_LAST(); +/// +/// And the values update, including post update function call, will be done only +/// once, after the engine receives the last UCI option, that is the one defined +/// and created as the last one, so the GUI should send the options in the same +/// order in which have been defined. + +class Tune { + + typedef void (PostUpdate) (); // Post-update function + + Tune() { read_results(); } + Tune(const Tune&) = delete; + void operator=(const Tune&) = delete; + void read_results(); + + static Tune& instance() { static Tune t; return t; } // Singleton + + // Use polymorphism to accommodate Entry of different types in the same vector + struct EntryBase { + virtual ~EntryBase() = default; + virtual void init_option() = 0; + virtual void read_option() = 0; + }; + + template + struct Entry : public EntryBase { + + static_assert(!std::is_const::value, "Parameter cannot be const!"); + + static_assert( std::is_same::value + || std::is_same::value + || std::is_same::value + || std::is_same::value, "Parameter type not supported!"); + + Entry(const std::string& n, T& v, const SetRange& r) : name(n), value(v), range(r) {} + void operator=(const Entry&) = delete; // Because 'value' is a reference + void init_option() override; + void read_option() override; + + std::string name; + T& value; + SetRange range; + }; + + // Our facility to fill the container, each Entry corresponds to a parameter + // to tune. We use variadic templates to deal with an unspecified number of + // entries, each one of a possible different type. + static std::string next(std::string& names, bool pop = true); + + int add(const SetRange&, std::string&&) { return 0; } + + template + int add(const SetRange& range, std::string&& names, T& value, Args&&... args) { + list.push_back(std::unique_ptr(new Entry(next(names), value, range))); + return add(range, std::move(names), args...); + } + + // Template specialization for arrays: recursively handle multi-dimensional arrays + template + int add(const SetRange& range, std::string&& names, T (&value)[N], Args&&... args) { + for (size_t i = 0; i < N; i++) + add(range, next(names, i == N - 1) + "[" + std::to_string(i) + "]", value[i]); + return add(range, std::move(names), args...); + } + + // Template specialization for SetRange + template + int add(const SetRange&, std::string&& names, SetRange& value, Args&&... args) { + return add(value, (next(names), std::move(names)), args...); + } + + std::vector> list; + +public: + template + static int add(const std::string& names, Args&&... args) { + return instance().add(SetDefaultRange, names.substr(1, names.size() - 2), args...); // Remove trailing parenthesis + } + static void init() { for (auto& e : instance().list) e->init_option(); read_options(); } // Deferred, due to UCI::Options access + static void read_options() { for (auto& e : instance().list) e->read_option(); } + static bool update_on_last; +}; + +// Some macro magic :-) we define a dummy int variable that compiler initializes calling Tune::add() +#define STRINGIFY(x) #x +#define UNIQUE2(x, y) x ## y +#define UNIQUE(x, y) UNIQUE2(x, y) // Two indirection levels to expand __LINE__ +#define TUNE(...) int UNIQUE(p, __LINE__) = Tune::add(STRINGIFY((__VA_ARGS__)), __VA_ARGS__) + +#define UPDATE_ON_LAST() bool UNIQUE(p, __LINE__) = Tune::update_on_last = true + +} // namespace Stockfish + +#endif // #ifndef TUNE_H_INCLUDED diff --git a/Fairy-Stockfish-master/src/types.h b/Fairy-Stockfish-master/src/types.h new file mode 100644 index 0000000000000000000000000000000000000000..844d1955a26835d89a9c4b5e9bd1afbdb32b4ff2 --- /dev/null +++ b/Fairy-Stockfish-master/src/types.h @@ -0,0 +1,878 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef TYPES_H_INCLUDED +#define TYPES_H_INCLUDED + +/// When compiling with provided Makefile (e.g. for Linux and OSX), configuration +/// is done automatically. To get started type 'make help'. +/// +/// When Makefile is not used (e.g. with Microsoft Visual Studio) some switches +/// need to be set manually: +/// +/// -DNDEBUG | Disable debugging mode. Always use this for release. +/// +/// -DNO_PREFETCH | Disable use of prefetch asm-instruction. You may need this to +/// | run on some very old machines. +/// +/// -DUSE_POPCNT | Add runtime support for use of popcnt asm-instruction. Works +/// | only in 64-bit mode and requires hardware with popcnt support. +/// +/// -DUSE_PEXT | Add runtime support for use of pext asm-instruction. Works +/// | only in 64-bit mode and requires hardware with pext support. + +#include +#include +#include +#include +#include + +#if defined(_MSC_VER) +// Disable some silly and noisy warning from MSVC compiler +#pragma warning(disable: 4127) // Conditional expression is constant +#pragma warning(disable: 4146) // Unary minus operator applied to unsigned type +#pragma warning(disable: 4800) // Forcing value to bool 'true' or 'false' +#pragma comment(linker, "/STACK:8000000") // Use 8 MB stack size for MSVC +#pragma comment(lib, "advapi32.lib") // Fix linker error +#endif + +/// Predefined macros hell: +/// +/// __GNUC__ Compiler is gcc, Clang or Intel on Linux +/// __INTEL_COMPILER Compiler is Intel +/// _MSC_VER Compiler is MSVC or Intel on Windows +/// _WIN32 Building on Windows (any) +/// _WIN64 Building on Windows 64 bit + +#if defined(__GNUC__ ) && (__GNUC__ < 9 || (__GNUC__ == 9 && __GNUC_MINOR__ <= 2)) && defined(_WIN32) && !defined(__clang__) +#define ALIGNAS_ON_STACK_VARIABLES_BROKEN +#endif + +#define ASSERT_ALIGNED(ptr, alignment) assert(reinterpret_cast(ptr) % alignment == 0) + +#if defined(_WIN64) && defined(_MSC_VER) // No Makefile used +# include // Microsoft header for _BitScanForward64() +# define IS_64BIT +#endif + +#if defined(USE_POPCNT) && (defined(__INTEL_COMPILER) || defined(_MSC_VER)) +# include // Intel and Microsoft header for _mm_popcnt_u64() +#endif + +#if !defined(NO_PREFETCH) && (defined(__INTEL_COMPILER) || defined(_MSC_VER)) +# include // Intel and Microsoft header for _mm_prefetch() +#endif + +#if defined(USE_PEXT) +# include // Header for _pext_u64() intrinsic +# ifdef LARGEBOARDS +# define pext(b, m) (_pext_u64(b, m) ^ (_pext_u64(b >> 64, m >> 64) << popcount((m << 64) >> 64))) +# else +# define pext(b, m) _pext_u64(b, m) +# endif +#else +# define pext(b, m) 0 +#endif + +namespace Stockfish { + +#ifdef USE_POPCNT +constexpr bool HasPopCnt = true; +#else +constexpr bool HasPopCnt = false; +#endif + +#ifdef USE_PEXT +constexpr bool HasPext = true; +#else +constexpr bool HasPext = false; +#endif + +#ifdef IS_64BIT +constexpr bool Is64Bit = true; +#else +constexpr bool Is64Bit = false; +#endif + +typedef uint64_t Key; +#ifdef LARGEBOARDS +#if defined(__GNUC__) && defined(IS_64BIT) +typedef unsigned __int128 Bitboard; +#else +struct Bitboard { + uint64_t b64[2]; + + constexpr Bitboard() : b64 {0, 0} {} + constexpr Bitboard(uint64_t i) : b64 {0, i} {} + constexpr Bitboard(uint64_t hi, uint64_t lo) : b64 {hi, lo} {}; + + constexpr operator bool() const { + return b64[0] || b64[1]; + } + + constexpr operator long long unsigned () const { + return b64[1]; + } + + constexpr operator unsigned() const { + return b64[1]; + } + + constexpr Bitboard operator << (const unsigned int bits) const { + return Bitboard( bits >= 64 ? b64[1] << (bits - 64) + : bits == 0 ? b64[0] + : ((b64[0] << bits) | (b64[1] >> (64 - bits))), + bits >= 64 ? 0 : b64[1] << bits); + } + + constexpr Bitboard operator >> (const unsigned int bits) const { + return Bitboard(bits >= 64 ? 0 : b64[0] >> bits, + bits >= 64 ? b64[0] >> (bits - 64) + : bits == 0 ? b64[1] + : ((b64[1] >> bits) | (b64[0] << (64 - bits)))); + } + + constexpr Bitboard operator << (const int bits) const { + return *this << unsigned(bits); + } + + constexpr Bitboard operator >> (const int bits) const { + return *this >> unsigned(bits); + } + + constexpr bool operator == (const Bitboard y) const { + return (b64[0] == y.b64[0]) && (b64[1] == y.b64[1]); + } + + constexpr bool operator != (const Bitboard y) const { + return !(*this == y); + } + + inline Bitboard& operator |=(const Bitboard x) { + b64[0] |= x.b64[0]; + b64[1] |= x.b64[1]; + return *this; + } + inline Bitboard& operator &=(const Bitboard x) { + b64[0] &= x.b64[0]; + b64[1] &= x.b64[1]; + return *this; + } + inline Bitboard& operator ^=(const Bitboard x) { + b64[0] ^= x.b64[0]; + b64[1] ^= x.b64[1]; + return *this; + } + + constexpr Bitboard operator ~ () const { + return Bitboard(~b64[0], ~b64[1]); + } + + constexpr Bitboard operator - () const { + return Bitboard(-b64[0] - (b64[1] > 0), -b64[1]); + } + + constexpr Bitboard operator | (const Bitboard x) const { + return Bitboard(b64[0] | x.b64[0], b64[1] | x.b64[1]); + } + + constexpr Bitboard operator & (const Bitboard x) const { + return Bitboard(b64[0] & x.b64[0], b64[1] & x.b64[1]); + } + + constexpr Bitboard operator ^ (const Bitboard x) const { + return Bitboard(b64[0] ^ x.b64[0], b64[1] ^ x.b64[1]); + } + + constexpr Bitboard operator - (const Bitboard x) const { + return Bitboard(b64[0] - x.b64[0] - (b64[1] < x.b64[1]), b64[1] - x.b64[1]); + } + + constexpr Bitboard operator - (const int x) const { + return *this - Bitboard(x); + } + + inline Bitboard operator * (const Bitboard x) const { + uint64_t a_lo = (uint32_t)b64[1]; + uint64_t a_hi = b64[1] >> 32; + uint64_t b_lo = (uint32_t)x.b64[1]; + uint64_t b_hi = x.b64[1] >> 32; + + uint64_t t1 = (a_hi * b_lo) + ((a_lo * b_lo) >> 32); + uint64_t t2 = (a_lo * b_hi) + (t1 & 0xFFFFFFFF); + + return Bitboard(b64[0] * x.b64[1] + b64[1] * x.b64[0] + (a_hi * b_hi) + (t1 >> 32) + (t2 >> 32), + (t2 << 32) + (a_lo * b_lo & 0xFFFFFFFF)); + } +}; +#endif +constexpr int SQUARE_BITS = 7; +#else +typedef uint64_t Bitboard; +constexpr int SQUARE_BITS = 6; +#endif + +//When defined, move list will be stored in heap. Delete this if you want to use stack to store move list. Using stack can cause overflow (Segmentation Fault) when the search is too deep. +#define USE_HEAP_INSTEAD_OF_STACK_FOR_MOVE_LIST + +#ifdef ALLVARS +constexpr int MAX_MOVES = 8192; +#ifdef USE_HEAP_INSTEAD_OF_STACK_FOR_MOVE_LIST +constexpr int MAX_PLY = 246; +#else +constexpr int MAX_PLY = 60; +#endif +/// endif USE_HEAP_INSTEAD_OF_STACK_FOR_MOVE_LIST +#else +constexpr int MAX_MOVES = 1024; +constexpr int MAX_PLY = 246; +#endif +/// endif ALLVARS + +/// A move needs 16 bits to be stored +/// +/// bit 0- 5: destination square (from 0 to 63) +/// bit 6-11: origin square (from 0 to 63) +/// bit 12-13: promotion piece type - 2 (from KNIGHT-2 to QUEEN-2) +/// bit 14-15: special move flag: promotion (1), en passant (2), castling (3) +/// NOTE: en passant bit is set only when a pawn can be captured +/// +/// Special cases are MOVE_NONE and MOVE_NULL. We can sneak these in because in +/// any normal move destination square is always different from origin square +/// while MOVE_NONE and MOVE_NULL have the same origin and destination square. + +enum Move : int { + MOVE_NONE, + MOVE_NULL = 1 + (1 << SQUARE_BITS) +}; + +enum MoveType : int { + NORMAL, + EN_PASSANT = 1 << (2 * SQUARE_BITS), + CASTLING = 2 << (2 * SQUARE_BITS), + PROMOTION = 3 << (2 * SQUARE_BITS), + DROP = 4 << (2 * SQUARE_BITS), + PIECE_PROMOTION = 5 << (2 * SQUARE_BITS), + PIECE_DEMOTION = 6 << (2 * SQUARE_BITS), + SPECIAL = 7 << (2 * SQUARE_BITS), +}; + +constexpr int MOVE_TYPE_BITS = 4; + +enum Color { + WHITE, BLACK, COLOR_NB = 2 +}; + +enum CastlingRights { + NO_CASTLING, + WHITE_OO, + WHITE_OOO = WHITE_OO << 1, + BLACK_OO = WHITE_OO << 2, + BLACK_OOO = WHITE_OO << 3, + + KING_SIDE = WHITE_OO | BLACK_OO, + QUEEN_SIDE = WHITE_OOO | BLACK_OOO, + WHITE_CASTLING = WHITE_OO | WHITE_OOO, + BLACK_CASTLING = BLACK_OO | BLACK_OOO, + ANY_CASTLING = WHITE_CASTLING | BLACK_CASTLING, + + CASTLING_RIGHT_NB = 16 +}; + +enum CheckCount : int { + CHECKS_0 = 0, CHECKS_NB = 11 +}; + +enum MaterialCounting { + NO_MATERIAL_COUNTING, JANGGI_MATERIAL, UNWEIGHTED_MATERIAL, WHITE_DRAW_ODDS, BLACK_DRAW_ODDS +}; + +enum CountingRule { + NO_COUNTING, MAKRUK_COUNTING, CAMBODIAN_COUNTING, ASEAN_COUNTING +}; + +enum ChasingRule { + NO_CHASING, AXF_CHASING +}; + +enum EnclosingRule { + NO_ENCLOSING, REVERSI, ATAXX, QUADWRANGLE, SNORT, ANYSIDE, TOP +}; + +enum WallingRule { + NO_WALLING, ARROW, DUCK, EDGE, PAST, STATIC +}; + +enum EndgameEval { + NO_EG_EVAL, EG_EVAL_CHESS, EG_EVAL_ANTI, EG_EVAL_ATOMIC, EG_EVAL_DUCK, EG_EVAL_MISERE, EG_EVAL_RK, EG_EVAL_NB +}; + +enum OptBool { + NO_VALUE, VALUE_FALSE, VALUE_TRUE +}; + +enum Phase { + PHASE_ENDGAME, + PHASE_MIDGAME = 128, + MG = 0, EG = 1, PHASE_NB = 2 +}; + +enum ScaleFactor { + SCALE_FACTOR_DRAW = 0, + SCALE_FACTOR_NORMAL = 64, + SCALE_FACTOR_MAX = 128, + SCALE_FACTOR_NONE = 255 +}; + +enum Bound { + BOUND_NONE, + BOUND_UPPER, + BOUND_LOWER, + BOUND_EXACT = BOUND_UPPER | BOUND_LOWER +}; + +enum Value : int { + VALUE_ZERO = 0, + VALUE_DRAW = 0, + VALUE_KNOWN_WIN = 10000, + VALUE_MATE = 32000, + XBOARD_VALUE_MATE = 200000, + VALUE_VIRTUAL_MATE = 3000, + VALUE_VIRTUAL_MATE_IN_MAX_PLY = VALUE_VIRTUAL_MATE - MAX_PLY, + VALUE_INFINITE = 32001, + VALUE_NONE = 32002, + + VALUE_TB_WIN_IN_MAX_PLY = VALUE_MATE - 2 * MAX_PLY, + VALUE_TB_LOSS_IN_MAX_PLY = -VALUE_TB_WIN_IN_MAX_PLY, + VALUE_MATE_IN_MAX_PLY = VALUE_MATE - MAX_PLY, + VALUE_MATED_IN_MAX_PLY = -VALUE_MATE_IN_MAX_PLY, + + PawnValueMg = 126, PawnValueEg = 208, + KnightValueMg = 781, KnightValueEg = 854, + BishopValueMg = 825, BishopValueEg = 915, + RookValueMg = 1276, RookValueEg = 1380, + QueenValueMg = 2538, QueenValueEg = 2682, + FersValueMg = 420, FersValueEg = 450, + AlfilValueMg = 350, AlfilValueEg = 330, + FersAlfilValueMg = 700, FersAlfilValueEg = 650, + SilverValueMg = 660, SilverValueEg = 640, + AiwokValueMg = 2300, AiwokValueEg = 2700, + BersValueMg = 1800, BersValueEg = 1900, + ArchbishopValueMg = 2200, ArchbishopValueEg = 2200, + ChancellorValueMg = 2300, ChancellorValueEg = 2600, + AmazonValueMg = 2700, AmazonValueEg = 2850, + KnibisValueMg = 1100, KnibisValueEg = 1200, + BiskniValueMg = 750, BiskniValueEg = 700, + KnirooValueMg = 1050, KnirooValueEg = 1250, + RookniValueMg = 800, RookniValueEg = 950, + ShogiPawnValueMg = 90, ShogiPawnValueEg = 100, + LanceValueMg = 400, LanceValueEg = 240, + ShogiKnightValueMg = 420, ShogiKnightValueEg = 290, + GoldValueMg = 720, GoldValueEg = 700, + DragonHorseValueMg = 1550, DragonHorseValueEg = 1550, + ClobberPieceValueMg = 300, ClobberPieceValueEg = 300, + BreakthroughPieceValueMg = 300, BreakthroughPieceValueEg = 300, + ImmobilePieceValueMg = 50, ImmobilePieceValueEg = 50, + CannonPieceValueMg = 800, CannonPieceValueEg = 700, + JanggiCannonPieceValueMg = 800, JanggiCannonPieceValueEg = 600, + SoldierValueMg = 200, SoldierValueEg = 270, + HorseValueMg = 520, HorseValueEg = 800, + ElephantValueMg = 300, ElephantValueEg = 300, + JanggiElephantValueMg = 340, JanggiElephantValueEg = 350, + BannerValueMg = 3400, BannerValueEg = 3500, + WazirValueMg = 400, WazirValueEg = 350, + CommonerValueMg = 700, CommonerValueEg = 900, + CentaurValueMg = 1800, CentaurValueEg = 1900, + + MidgameLimit = 15258, EndgameLimit = 3915 +}; + +constexpr int PIECE_TYPE_BITS = 6; // PIECE_TYPE_NB = pow(2, PIECE_TYPE_BITS) + +enum PieceType { + NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, + FERS, MET = FERS, ALFIL, FERS_ALFIL, SILVER, KHON = SILVER, AIWOK, BERS, DRAGON = BERS, + ARCHBISHOP, CHANCELLOR, AMAZON, KNIBIS, BISKNI, KNIROO, ROOKNI, + SHOGI_PAWN, LANCE, SHOGI_KNIGHT, GOLD, DRAGON_HORSE, + CLOBBER_PIECE, BREAKTHROUGH_PIECE, IMMOBILE_PIECE, CANNON, JANGGI_CANNON, + SOLDIER, HORSE, ELEPHANT, JANGGI_ELEPHANT, BANNER, + WAZIR, COMMONER, CENTAUR, + + CUSTOM_PIECE_1, CUSTOM_PIECE_2, CUSTOM_PIECE_3, CUSTOM_PIECE_4, + CUSTOM_PIECE_5, CUSTOM_PIECE_6, CUSTOM_PIECE_7, CUSTOM_PIECE_8, + + PIECE_TYPE_NB = 1 << PIECE_TYPE_BITS, + KING = PIECE_TYPE_NB - 1, + + // Aliases + CUSTOM_PIECES = CUSTOM_PIECE_1, + CUSTOM_PIECES_END = KING - 1, + CUSTOM_PIECES_ROYAL = CUSTOM_PIECES_END, + CUSTOM_PIECES_NB = CUSTOM_PIECES_END - CUSTOM_PIECES + 1, + FAIRY_PIECES = QUEEN + 1, + FAIRY_PIECES_END = CUSTOM_PIECES - 1, + ALL_PIECES = 0, +}; +static_assert(KING < PIECE_TYPE_NB, "KING exceeds PIECE_TYPE_NB."); +static_assert(PIECE_TYPE_BITS <= 6, "PIECE_TYPE uses more than 6 bit"); +static_assert(!(PIECE_TYPE_NB & (PIECE_TYPE_NB - 1)), "PIECE_TYPE_NB is not a power of 2"); + +static_assert(2 * SQUARE_BITS + MOVE_TYPE_BITS + 2 * PIECE_TYPE_BITS <= 32, "Move encoding uses more than 32 bits"); + +enum Piece { + NO_PIECE, + W_PAWN = PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING = KING, + B_PAWN = PAWN + PIECE_TYPE_NB, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING = KING + PIECE_TYPE_NB, + PIECE_NB = 2 * PIECE_TYPE_NB +}; + +enum PieceSet : uint64_t { + NO_PIECE_SET = 0, + CHESS_PIECES = (1ULL << PAWN) | (1ULL << KNIGHT) | (1ULL << BISHOP) | (1ULL << ROOK) | (1ULL << QUEEN) | (1ULL << KING), + COMMON_FAIRY_PIECES = (1ULL << IMMOBILE_PIECE) | (1ULL << COMMONER) | (1ULL << ARCHBISHOP) | (1ULL << CHANCELLOR), + SHOGI_PIECES = (1ULL << SHOGI_PAWN) | (1ULL << GOLD) | (1ULL << SILVER) | (1ULL << SHOGI_KNIGHT) | (1ULL << LANCE) + | (1ULL << DRAGON)| (1ULL << DRAGON_HORSE) | (1ULL << KING), + COMMON_STEP_PIECES = (1ULL << COMMONER) | (1ULL << FERS) | (1ULL << WAZIR) | (1ULL << BREAKTHROUGH_PIECE), +}; + +enum RiderType : int { + NO_RIDER = 0, + RIDER_BISHOP = 1 << 0, + RIDER_ROOK_H = 1 << 1, + RIDER_ROOK_V = 1 << 2, + RIDER_CANNON_H = 1 << 3, + RIDER_CANNON_V = 1 << 4, + RIDER_LAME_DABBABA = 1 << 5, + RIDER_HORSE = 1 << 6, + RIDER_ELEPHANT = 1 << 7, + RIDER_JANGGI_ELEPHANT = 1 << 8, + RIDER_CANNON_DIAG = 1 << 9, + RIDER_NIGHTRIDER = 1 << 10, + RIDER_GRASSHOPPER_H = 1 << 11, + RIDER_GRASSHOPPER_V = 1 << 12, + RIDER_GRASSHOPPER_D = 1 << 13, + HOPPING_RIDERS = RIDER_CANNON_H | RIDER_CANNON_V | RIDER_CANNON_DIAG + | RIDER_GRASSHOPPER_H | RIDER_GRASSHOPPER_V | RIDER_GRASSHOPPER_D, + LAME_LEAPERS = RIDER_LAME_DABBABA | RIDER_HORSE | RIDER_ELEPHANT | RIDER_JANGGI_ELEPHANT, + ASYMMETRICAL_RIDERS = RIDER_HORSE | RIDER_JANGGI_ELEPHANT + | RIDER_GRASSHOPPER_H | RIDER_GRASSHOPPER_V | RIDER_GRASSHOPPER_D, + NON_SLIDING_RIDERS = HOPPING_RIDERS | LAME_LEAPERS | RIDER_NIGHTRIDER, +}; + +extern Value PieceValue[PHASE_NB][PIECE_NB]; +extern Value EvalPieceValue[PHASE_NB][PIECE_NB]; // variant piece values for evaluation +extern Value CapturePieceValue[PHASE_NB][PIECE_NB]; // variant piece values for captures/search + +typedef int Depth; + +enum : int { + DEPTH_QS_CHECKS = 0, + DEPTH_QS_NO_CHECKS = -1, + DEPTH_QS_RECAPTURES = -5, + DEPTH_QS_MAX = -32, + + DEPTH_NONE = -6, + + DEPTH_OFFSET = -7 // value used only for TT entry occupancy check +}; + +enum Square : int { +#ifdef LARGEBOARDS + SQ_A1, SQ_B1, SQ_C1, SQ_D1, SQ_E1, SQ_F1, SQ_G1, SQ_H1, SQ_I1, SQ_J1, SQ_K1, SQ_L1, + SQ_A2, SQ_B2, SQ_C2, SQ_D2, SQ_E2, SQ_F2, SQ_G2, SQ_H2, SQ_I2, SQ_J2, SQ_K2, SQ_L2, + SQ_A3, SQ_B3, SQ_C3, SQ_D3, SQ_E3, SQ_F3, SQ_G3, SQ_H3, SQ_I3, SQ_J3, SQ_K3, SQ_L3, + SQ_A4, SQ_B4, SQ_C4, SQ_D4, SQ_E4, SQ_F4, SQ_G4, SQ_H4, SQ_I4, SQ_J4, SQ_K4, SQ_L4, + SQ_A5, SQ_B5, SQ_C5, SQ_D5, SQ_E5, SQ_F5, SQ_G5, SQ_H5, SQ_I5, SQ_J5, SQ_K5, SQ_L5, + SQ_A6, SQ_B6, SQ_C6, SQ_D6, SQ_E6, SQ_F6, SQ_G6, SQ_H6, SQ_I6, SQ_J6, SQ_K6, SQ_L6, + SQ_A7, SQ_B7, SQ_C7, SQ_D7, SQ_E7, SQ_F7, SQ_G7, SQ_H7, SQ_I7, SQ_J7, SQ_K7, SQ_L7, + SQ_A8, SQ_B8, SQ_C8, SQ_D8, SQ_E8, SQ_F8, SQ_G8, SQ_H8, SQ_I8, SQ_J8, SQ_K8, SQ_L8, + SQ_A9, SQ_B9, SQ_C9, SQ_D9, SQ_E9, SQ_F9, SQ_G9, SQ_H9, SQ_I9, SQ_J9, SQ_K9, SQ_L9, + SQ_A10, SQ_B10, SQ_C10, SQ_D10, SQ_E10, SQ_F10, SQ_G10, SQ_H10, SQ_I10, SQ_J10, SQ_K10, SQ_L10, +#else + SQ_A1, SQ_B1, SQ_C1, SQ_D1, SQ_E1, SQ_F1, SQ_G1, SQ_H1, + SQ_A2, SQ_B2, SQ_C2, SQ_D2, SQ_E2, SQ_F2, SQ_G2, SQ_H2, + SQ_A3, SQ_B3, SQ_C3, SQ_D3, SQ_E3, SQ_F3, SQ_G3, SQ_H3, + SQ_A4, SQ_B4, SQ_C4, SQ_D4, SQ_E4, SQ_F4, SQ_G4, SQ_H4, + SQ_A5, SQ_B5, SQ_C5, SQ_D5, SQ_E5, SQ_F5, SQ_G5, SQ_H5, + SQ_A6, SQ_B6, SQ_C6, SQ_D6, SQ_E6, SQ_F6, SQ_G6, SQ_H6, + SQ_A7, SQ_B7, SQ_C7, SQ_D7, SQ_E7, SQ_F7, SQ_G7, SQ_H7, + SQ_A8, SQ_B8, SQ_C8, SQ_D8, SQ_E8, SQ_F8, SQ_G8, SQ_H8, +#endif + SQ_NONE, + + SQUARE_ZERO = 0, +#ifdef LARGEBOARDS + SQUARE_NB = 120, + SQUARE_BIT_MASK = 127, +#else + SQUARE_NB = 64, + SQUARE_BIT_MASK = 63, +#endif + SQ_MAX = SQUARE_NB - 1, + SQUARE_NB_CHESS = 64, + SQUARE_NB_SHOGI = 81, +}; + +enum Direction : int { +#ifdef LARGEBOARDS + NORTH = 12, +#else + NORTH = 8, +#endif + EAST = 1, + SOUTH = -NORTH, + WEST = -EAST, + + NORTH_EAST = NORTH + EAST, + SOUTH_EAST = SOUTH + EAST, + SOUTH_WEST = SOUTH + WEST, + NORTH_WEST = NORTH + WEST +}; + +enum File : int { +#ifdef LARGEBOARDS + FILE_A, FILE_B, FILE_C, FILE_D, FILE_E, FILE_F, FILE_G, FILE_H, FILE_I, FILE_J, FILE_K, FILE_L, +#else + FILE_A, FILE_B, FILE_C, FILE_D, FILE_E, FILE_F, FILE_G, FILE_H, +#endif + FILE_NB, + FILE_MAX = FILE_NB - 1 +}; + +enum Rank : int { +#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[12]; + Piece handPiece[12]; + int handCount[12]; + + // From and to squares, which may be SQ_NONE + Square from[12]; + Square to[12]; +}; + +/// 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 +/// upper 16 bits are used to store the endgame value. We have to take care to +/// avoid left-shifting a signed int to avoid undefined behavior. +enum Score : int { SCORE_ZERO }; + +constexpr Score make_score(int mg, int eg) { + return Score((int)((unsigned int)eg << 16) + mg); +} + +/// Extracting the signed lower and upper 16 bits is not so trivial because +/// according to the standard a simple cast to short is implementation defined +/// and so is a right shift of a signed integer. +inline Value eg_value(Score s) { + union { uint16_t u; int16_t s; } eg = { uint16_t(unsigned(s + 0x8000) >> 16) }; + return Value(eg.s); +} + +inline Value mg_value(Score s) { + union { uint16_t u; int16_t s; } mg = { uint16_t(unsigned(s)) }; + return Value(mg.s); +} + +#define ENABLE_BIT_OPERATORS_ON(T) \ +constexpr T operator~ (T d) { return (T)~(int)d; } \ +constexpr T operator| (T d1, T d2) { return (T)((int)d1 | (int)d2); } \ +constexpr T operator& (T d1, T d2) { return (T)((int)d1 & (int)d2); } \ +constexpr 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 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; } + +#define ENABLE_INCR_OPERATORS_ON(T) \ +inline T& operator++(T& d) { return d = T(int(d) + 1); } \ +inline T& operator--(T& d) { return d = T(int(d) - 1); } + +#define ENABLE_FULL_OPERATORS_ON(T) \ +ENABLE_BASE_OPERATORS_ON(T) \ +constexpr T operator*(int i, T d) { return T(i * int(d)); } \ +constexpr T operator*(T d, int i) { return T(int(d) * i); } \ +constexpr T operator/(T d, int i) { return T(int(d) / i); } \ +constexpr int operator/(T d1, T d2) { return int(d1) / int(d2); } \ +inline T& operator*=(T& d, int i) { return d = T(int(d) * i); } \ +inline T& operator/=(T& d, int i) { return d = T(int(d) / i); } + +ENABLE_FULL_OPERATORS_ON(Value) +ENABLE_FULL_OPERATORS_ON(Direction) + +ENABLE_INCR_OPERATORS_ON(Piece) +ENABLE_INCR_OPERATORS_ON(PieceType) +ENABLE_INCR_OPERATORS_ON(Square) +ENABLE_INCR_OPERATORS_ON(File) +ENABLE_INCR_OPERATORS_ON(Rank) +ENABLE_INCR_OPERATORS_ON(CheckCount) + +ENABLE_BASE_OPERATORS_ON(Score) + +ENABLE_BASE_OPERATORS_ON(PieceType) +ENABLE_BIT_OPERATORS_ON(RiderType) +ENABLE_BASE_OPERATORS_ON(RiderType) + +#undef ENABLE_FULL_OPERATORS_ON +#undef ENABLE_INCR_OPERATORS_ON +#undef ENABLE_BASE_OPERATORS_ON +#undef ENABLE_BIT_OPERATORS_ON + +constexpr PieceSet piece_set(PieceType pt) { + return PieceSet(1ULL << pt); +} + +constexpr PieceSet operator~ (PieceSet ps) { return (PieceSet)~(uint64_t)ps; } +constexpr PieceSet operator| (PieceSet ps1, PieceSet ps2) { return (PieceSet)((uint64_t)ps1 | (uint64_t)ps2); } +constexpr PieceSet operator| (PieceSet ps, PieceType pt) { return ps | piece_set(pt); } +constexpr PieceSet operator& (PieceSet ps1, PieceSet ps2) { return (PieceSet)((uint64_t)ps1 & (uint64_t)ps2); } +constexpr PieceSet operator& (PieceSet ps, PieceType pt) { return ps & piece_set(pt); } +constexpr PieceSet operator^ (PieceSet ps1, PieceSet ps2) { return (PieceSet)((uint64_t)ps1 ^ (uint64_t)ps2); } +constexpr PieceSet operator^ (PieceSet ps, PieceType pt) { return ps ^ piece_set(pt); } +inline PieceSet& operator|= (PieceSet& ps1, PieceSet ps2) { return (PieceSet&)((uint64_t&)ps1 |= (uint64_t)ps2); } +inline PieceSet& operator|= (PieceSet& ps, PieceType pt) { return ps |= piece_set(pt); } +inline PieceSet& operator&= (PieceSet& ps1, PieceSet ps2) { return (PieceSet&)((uint64_t&)ps1 &= (uint64_t)ps2); } +//inline PieceSet& operator&= (PieceSet& ps, PieceType pt) does not make sense +inline PieceSet& operator^= (PieceSet& ps1, PieceSet ps2) { return (PieceSet&)((uint64_t&)ps1 ^= (uint64_t)ps2); } +inline PieceSet& operator^= (PieceSet& ps, PieceType pt) { return ps ^= piece_set(pt); } + +static_assert(piece_set(PAWN) & PAWN); +static_assert(piece_set(KING) & KING); + +/// Additional operators to add a Direction to a Square +constexpr Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); } +constexpr Square operator-(Square s, Direction d) { return Square(int(s) - int(d)); } +inline Square& operator+=(Square& s, Direction d) { return s = s + d; } +inline Square& operator-=(Square& s, Direction d) { return s = s - d; } + +/// Only declared but not defined. We don't want to multiply two scores due to +/// a very high risk of overflow. So user should explicitly convert to integer. +Score operator*(Score, Score) = delete; + +/// Division of a Score must be handled separately for each term +inline Score operator/(Score s, int i) { + return make_score(mg_value(s) / i, eg_value(s) / i); +} + +/// Multiplication of a Score by an integer. We check for overflow in debug mode. +inline Score operator*(Score s, int i) { + + Score result = Score(int(s) * i); + + assert(eg_value(result) == (i * eg_value(s))); + assert(mg_value(result) == (i * mg_value(s))); + assert((i == 0) || (result / i) == s); + + return result; +} + +/// Multiplication of a Score by a boolean +inline Score operator*(Score s, bool b) { + return b ? s : SCORE_ZERO; +} + +constexpr Color operator~(Color c) { + return Color(c ^ BLACK); // Toggle color +} + +constexpr Square flip_rank(Square s, Rank maxRank = RANK_8) { // Swap A1 <-> A8 + return Square(s + NORTH * (maxRank - 2 * (s / NORTH))); +} + +constexpr Square flip_file(Square s, File maxFile = FILE_H) { // Swap A1 <-> H1 + return Square(s + maxFile - 2 * (s % NORTH)); +} + +constexpr Piece operator~(Piece pc) { + return Piece(pc ^ PIECE_TYPE_NB); // Swap color of piece B_KNIGHT <-> W_KNIGHT +} + +constexpr CastlingRights operator&(Color c, CastlingRights cr) { + return CastlingRights((c == WHITE ? WHITE_CASTLING : BLACK_CASTLING) & cr); +} + +constexpr Value mate_in(int ply) { + return VALUE_MATE - ply; +} + +constexpr Value mated_in(int ply) { + return -VALUE_MATE + ply; +} + +constexpr Value convert_mate_value(Value v, int ply) { + return v == VALUE_MATE ? mate_in(ply) + : v == -VALUE_MATE ? mated_in(ply) + : v; +} + +constexpr Square make_square(File f, Rank r) { + return Square(r * FILE_NB + f); +} + +constexpr Piece make_piece(Color c, PieceType pt) { + return Piece((c << PIECE_TYPE_BITS) + pt); +} + +constexpr PieceType type_of(Piece pc) { + return PieceType(pc & (PIECE_TYPE_NB - 1)); +} + +inline Color color_of(Piece pc) { + assert(pc != NO_PIECE); + return Color(pc >> PIECE_TYPE_BITS); +} + +constexpr bool is_ok(Square s) { + return s >= SQ_A1 && s <= SQ_MAX; +} + +constexpr File file_of(Square s) { + return File(s % FILE_NB); +} + +constexpr Rank rank_of(Square s) { + return Rank(s / FILE_NB); +} + +constexpr Rank relative_rank(Color c, Rank r, Rank maxRank = RANK_8) { + return Rank(c == WHITE ? r : maxRank - r); +} + +constexpr Rank relative_rank(Color c, Square s, Rank maxRank = RANK_8) { + return relative_rank(c, rank_of(s), maxRank); +} + +constexpr Square relative_square(Color c, Square s, Rank maxRank = RANK_8) { + return make_square(file_of(s), relative_rank(c, s, maxRank)); +} + +constexpr Direction pawn_push(Color c) { + return c == WHITE ? NORTH : SOUTH; +} + +constexpr MoveType type_of(Move m) { + return MoveType(m & (15 << (2 * SQUARE_BITS))); +} + +constexpr Square to_sq(Move m) { + return Square(m & SQUARE_BIT_MASK); +} + +constexpr Square from_sq(Move m) { + return type_of(m) == DROP ? SQ_NONE : Square((m >> SQUARE_BITS) & SQUARE_BIT_MASK); +} + +inline int from_to(Move m) { + return to_sq(m) + (from_sq(m) << SQUARE_BITS); +} + +inline PieceType promotion_type(Move m) { + return type_of(m) == PROMOTION ? PieceType((m >> (2 * SQUARE_BITS + MOVE_TYPE_BITS)) & (PIECE_TYPE_NB - 1)) : NO_PIECE_TYPE; +} + +inline PieceType gating_type(Move m) { + return PieceType((m >> (2 * SQUARE_BITS + MOVE_TYPE_BITS)) & (PIECE_TYPE_NB - 1)); +} + +inline Square gating_square(Move m) { + return Square((m >> (2 * SQUARE_BITS + MOVE_TYPE_BITS + PIECE_TYPE_BITS)) & SQUARE_BIT_MASK); +} + +inline bool is_gating(Move m) { + return gating_type(m) && (type_of(m) == NORMAL || type_of(m) == CASTLING); +} + +inline bool is_pass(Move m) { + return type_of(m) == SPECIAL && from_sq(m) == to_sq(m); +} + +constexpr Move make_move(Square from, Square to) { + return Move((from << SQUARE_BITS) + to); +} + +template +inline Move make(Square from, Square to, PieceType pt = NO_PIECE_TYPE) { + return Move((pt << (2 * SQUARE_BITS + MOVE_TYPE_BITS)) + T + (from << SQUARE_BITS) + to); +} + +constexpr Move make_drop(Square to, PieceType pt_in_hand, PieceType pt_dropped) { + return Move((pt_in_hand << (2 * SQUARE_BITS + MOVE_TYPE_BITS + PIECE_TYPE_BITS)) + (pt_dropped << (2 * SQUARE_BITS + MOVE_TYPE_BITS)) + DROP + to); +} + +constexpr Move reverse_move(Move m) { + return make_move(to_sq(m), from_sq(m)); +} + +template +constexpr Move make_gating(Square from, Square to, PieceType pt, Square gate) { + return Move((gate << (2 * SQUARE_BITS + MOVE_TYPE_BITS + PIECE_TYPE_BITS)) + (pt << (2 * SQUARE_BITS + MOVE_TYPE_BITS)) + T + (from << SQUARE_BITS) + to); +} + +constexpr PieceType dropped_piece_type(Move m) { + return PieceType((m >> (2 * SQUARE_BITS + MOVE_TYPE_BITS)) & (PIECE_TYPE_NB - 1)); +} + +constexpr PieceType in_hand_piece_type(Move m) { + return PieceType((m >> (2 * SQUARE_BITS + MOVE_TYPE_BITS + PIECE_TYPE_BITS)) & (PIECE_TYPE_NB - 1)); +} + +inline bool is_custom(PieceType pt) { + return pt >= CUSTOM_PIECES && pt <= CUSTOM_PIECES_END; +} + +inline bool is_ok(Move m) { + return from_sq(m) != to_sq(m) || type_of(m) == PROMOTION || type_of(m) == SPECIAL; // Catch MOVE_NULL and MOVE_NONE +} + +inline int dist(Direction d) { + return std::abs(d % NORTH) < NORTH / 2 ? std::max(std::abs(d / NORTH), int(std::abs(d % NORTH))) + : std::max(std::abs(d / NORTH) + 1, int(NORTH - std::abs(d % NORTH))); +} + +/// Based on a congruential pseudo random number generator +constexpr Key make_key(uint64_t seed) { + return seed * 6364136223846793005ULL + 1442695040888963407ULL; +} + +} // namespace Stockfish + +#endif // #ifndef TYPES_H_INCLUDED + +#include "tune.h" // Global visibility to tuning setup diff --git a/Fairy-Stockfish-master/src/uci.cpp b/Fairy-Stockfish-master/src/uci.cpp new file mode 100644 index 0000000000000000000000000000000000000000..584977aadeb467e9f47d0d1d27f8ac1b296dfa28 --- /dev/null +++ b/Fairy-Stockfish-master/src/uci.cpp @@ -0,0 +1,614 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include +#include +#include + +#include "evaluate.h" +#include "movegen.h" +#include "position.h" +#include "search.h" +#include "thread.h" +#include "timeman.h" +#include "tt.h" +#include "uci.h" +#include "xboard.h" +#include "syzygy/tbprobe.h" + +using namespace std; + +namespace Stockfish { + +extern vector setup_bench(const Position&, istream&); + +namespace { + + // position() is called when engine receives the "position" UCI command. + // The function sets up the position described in the given FEN string ("fen") + // or the starting position ("startpos") and then makes the moves given in the + // following move list ("moves"). + + void position(Position& pos, istringstream& is, StateListPtr& states) { + + Move m; + string token, fen; + + is >> token; + // Parse as SFEN if specified + bool sfen = token == "sfen"; + + if (token == "startpos") + { + fen = variants.find(Options["UCI_Variant"])->second->startFen; + is >> token; // Consume "moves" token if any + } + else if (token == "fen" || token == "sfen") + while (is >> token && token != "moves") + fen += token + " "; + else + return; + + states = StateListPtr(new std::deque(1)); // Drop old and create a new one + pos.set(variants.find(Options["UCI_Variant"])->second, fen, Options["UCI_Chess960"], &states->back(), Threads.main(), sfen); + + // Parse move list (if any) + while (is >> token && (m = UCI::to_move(pos, token)) != MOVE_NONE) + { + states->emplace_back(); + pos.do_move(m, states->back()); + } + } + + // 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.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"). + + void setoption(istringstream& is) { + + string token, name, value; + + is >> token; // Consume "name" token + + if (CurrentProtocol == UCCI) + name = token; + else + // Read option name (can contain spaces) + while (is >> token && token != "value") + name += (name.empty() ? "" : " ") + token; + + // Read option value (can contain spaces) + while (is >> token) + value += (value.empty() ? "" : " ") + token; + + if (Options.count(name)) + Options[name] = value; + // Deal with option name aliases in UCI dialects + else if (is_valid_option(Options, name)) + Options[name] = value; + else + sync_cout << "No such option: " << name << sync_endl; + } + + + // go() is called when engine receives the "go" UCI command. The function sets + // the thinking time and other parameters from the input string, then starts + // the search. + + void go(Position& pos, istringstream& is, StateListPtr& states, const std::vector& banmoves = {}) { + + Search::LimitsType limits; + string token; + bool ponderMode = false; + + limits.startTime = now(); // As early as possible! + + limits.banmoves = banmoves; + bool isUsi = CurrentProtocol == USI; + int secResolution = Options["usemillisec"] ? 1 : 1000; + + while (is >> token) + if (token == "searchmoves") // Needs to be the last command on the line + while (is >> token) + limits.searchmoves.push_back(UCI::to_move(pos, token)); + + else if (token == "wtime") is >> limits.time[isUsi ? BLACK : WHITE]; + else if (token == "btime") is >> limits.time[isUsi ? WHITE : BLACK]; + else if (token == "winc") is >> limits.inc[isUsi ? BLACK : WHITE]; + else if (token == "binc") is >> limits.inc[isUsi ? WHITE : BLACK]; + else if (token == "movestogo") is >> limits.movestogo; + else if (token == "depth") is >> limits.depth; + else if (token == "nodes") is >> limits.nodes; + else if (token == "movetime") is >> limits.movetime; + else if (token == "mate") is >> limits.mate; + else if (token == "perft") is >> limits.perft; + else if (token == "infinite") limits.infinite = 1; + else if (token == "ponder") ponderMode = true; + // UCCI commands + else if (token == "time") is >> limits.time[pos.side_to_move()], limits.time[pos.side_to_move()] *= secResolution; + else if (token == "opptime") is >> limits.time[~pos.side_to_move()], limits.time[~pos.side_to_move()] *= secResolution; + else if (token == "increment") is >> limits.inc[pos.side_to_move()], limits.inc[pos.side_to_move()] *= secResolution; + else if (token == "oppincrement") is >> limits.inc[~pos.side_to_move()], limits.inc[~pos.side_to_move()] *= secResolution; + // USI commands + else if (token == "byoyomi") + { + int byoyomi = 0; + is >> byoyomi; + limits.inc[WHITE] = limits.inc[BLACK] = byoyomi; + limits.time[WHITE] += byoyomi; + limits.time[BLACK] += byoyomi; + } + + Threads.start_thinking(pos, states, limits, ponderMode); + } + + // bench() is called when engine receives the "bench" command. Firstly + // a list of UCI commands is setup according to bench parameters, then + // it is run one by one printing a summary at the end. + + void bench(Position& pos, istream& args, StateListPtr& states) { + + string token; + uint64_t num, nodes = 0, cnt = 1; + + vector list = setup_bench(pos, args); + num = count_if(list.begin(), list.end(), [](string s) { return s.find("go ") == 0 || s.find("eval") == 0; }); + + TimePoint elapsed = now(); + + for (const auto& cmd : list) + { + istringstream is(cmd); + is >> skipws >> token; + + if (token == "go" || token == "eval") + { + cerr << "\nPosition: " << cnt++ << '/' << num << " (" << pos.fen() << ")" << endl; + if (token == "go") + { + go(pos, is, states); + Threads.main()->wait_for_search_finished(); + nodes += Threads.nodes_searched(); + } + else + trace_eval(pos); + } + else if (token == "setoption") setoption(is); + else if (token == "position") position(pos, is, states); + else if (token == "ucinewgame") { Search::clear(); elapsed = now(); } // Search::clear() may take some while + } + + elapsed = now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero' + + dbg_print(); // Just before exiting + + cerr << "\n===========================" + << "\nTotal time (ms) : " << elapsed + << "\nNodes searched : " << nodes + << "\nNodes/second : " << 1000 * nodes / elapsed << endl; + } + + // The win rate model returns the probability (per mille) of winning given an eval + // and a game-ply. The model fits rather accurately the LTC fishtest statistics. + int win_rate_model(Value v, int ply) { + + // The model captures only up to 240 plies, so limit input (and rescale) + double m = std::min(240, ply) / 64.0; + + // Coefficients of a 3rd order polynomial fit based on fishtest data + // for two parameters needed to transform eval to the argument of a + // logistic function. + double as[] = {-3.68389304, 30.07065921, -60.52878723, 149.53378557}; + double bs[] = {-2.0181857, 15.85685038, -29.83452023, 47.59078827}; + double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; + double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; + + // Transform eval to centipawns with limited range + double x = std::clamp(double(100 * v) / PawnValueEg, -2000.0, 2000.0); + + // Return win rate in per mille (rounded to nearest) + return int(0.5 + 1000 / (1 + std::exp((a - x) / b))); + } + + // load() is called when engine receives the "load" or "check" command. + // The function reads variant configuration files. + + void load(istringstream& is, bool check = false) { + + string token; + std::getline(is >> std::ws, token); + + // The argument to load either is a here-doc or a file path + if (token.rfind("<<", 0) == 0) + { + // Trim the EOF marker + if (!(stringstream(token.substr(2)) >> token)) + token = ""; + + // Parse variant config till EOF marker + stringstream ss; + std::string line; + while (std::getline(cin, line) && line != token) + ss << line << std::endl; + if (check) + variants.parse_istream(ss); + else + { + variants.parse_istream(ss); + Options["UCI_Variant"].set_combo(variants.get_keys()); + } + } + else + { + // store path if non-empty after trimming + std::size_t end = token.find_last_not_of(' '); + if (end != std::string::npos) + { + if (check) + variants.parse(token.erase(end + 1)); + else + Options["VariantPath"] = token.erase(end + 1); + } + } + } + +} // namespace + + +/// UCI::loop() waits for a command from stdin, parses it and calls the appropriate +/// function. Also intercepts EOF from stdin to ensure gracefully exiting if the +/// GUI dies unexpectedly. When called with some command line arguments, e.g. to +/// run 'bench', once the command is executed the function returns immediately. +/// In addition to the UCI ones, also some additional debug commands are supported. + +void UCI::loop(int argc, char* argv[]) { + + Position pos; + string token, cmd; + StateListPtr states(new std::deque(1)); + + assert(variants.find(Options["UCI_Variant"])->second != nullptr); + pos.set(variants.find(Options["UCI_Variant"])->second, variants.find(Options["UCI_Variant"])->second->startFen, false, &states->back(), Threads.main()); + + for (int i = 1; i < argc; ++i) + cmd += std::string(argv[i]) + " "; + + // XBoard state machine + XBoard::stateMachine = new XBoard::StateMachine(pos, states); + // UCCI banmoves state + std::vector banmoves = {}; + + if (argc > 1 && (std::strcmp(argv[1], "noautoload") == 0)) + { + cmd = ""; + argc = 1; + } + else if (argc == 1 || !(std::strcmp(argv[1], "load") == 0)) + { + // Check environment for variants.ini file + char *envVariantPath = std::getenv("FAIRY_STOCKFISH_VARIANT_PATH"); + if (envVariantPath != NULL) + Options["VariantPath"] = std::string(envVariantPath); + } + + do { + if (argc == 1 && !getline(cin, cmd)) // Block here waiting for input or EOF + cmd = "quit"; + + istringstream is(cmd); + + token.clear(); // Avoid a stale if getline() returns empty or blank line + is >> skipws >> token; + + if ( token == "quit" + || token == "stop") + Threads.stop = true; + + // The GUI sends 'ponderhit' to tell us the user has played the expected move. + // So 'ponderhit' will be sent if we were told to ponder on the same move the + // user has played. We should continue searching but switch from pondering to + // normal search. + else if (token == "ponderhit") + Threads.main()->ponder = false; // Switch to normal search + + else if (token == "uci" || token == "usi" || token == "ucci" || token == "xboard" || token == "ucicyclone") + { + CurrentProtocol = token == "uci" ? (CurrentProtocol == UCI_CYCLONE ? UCI_CYCLONE : UCI_GENERAL) + : token == "ucicyclone" ? UCI_CYCLONE + : token == "usi" ? USI + : token == "ucci" ? UCCI + : XBOARD; + string defaultVariant = string( +#ifdef LARGEBOARDS + CurrentProtocol == USI ? "shogi" + : CurrentProtocol == UCCI || CurrentProtocol == UCI_CYCLONE ? "xiangqi" +#else + CurrentProtocol == USI ? "minishogi" + : CurrentProtocol == UCCI || CurrentProtocol == UCI_CYCLONE ? "minixiangqi" +#endif + : "chess"); + Options["UCI_Variant"].set_default(defaultVariant); + std::istringstream ss("startpos"); + position(pos, ss, states); + if (is_uci_dialect(CurrentProtocol) && token != "ucicyclone") + sync_cout << "id name " << engine_info(true) + << "\n" << Options + << "\n" << token << "ok" << sync_endl; + // Allow to enforce protocol at startup + argc = 1; + } + + else if (CurrentProtocol == XBOARD) + XBoard::stateMachine->process_command(token, is); + + else if (token == "setoption") setoption(is); + // UCCI-specific banmoves command + else if (token == "banmoves") + while (is >> token) + banmoves.push_back(UCI::to_move(pos, token)); + else if (token == "go") go(pos, is, states, banmoves); + else if (token == "position") position(pos, is, states), banmoves.clear(); + else if (token == "ucinewgame" || token == "usinewgame" || token == "uccinewgame") Search::clear(); + else if (token == "isready") sync_cout << "readyok" << sync_endl; + + // Additional custom non-UCI commands, mainly for debugging. + // Do not use these commands during a search! + 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") trace_eval(pos); + else if (token == "compiler") sync_cout << compiler_info() << sync_endl; + else if (token == "export_net") + { + std::optional filename; + std::string f; + if (is >> skipws >> f) + filename = f; + Eval::NNUE::save_eval(filename); + } + else if (token == "load") { load(is); argc = 1; } // continue reading stdin + else if (token == "check") load(is, true); + // UCI-Cyclone omits the "position" keyword + else if (token == "fen" || token == "startpos") + { +#ifdef LARGEBOARDS + if (CurrentProtocol == UCI_GENERAL && Options["UCI_Variant"] == "chess") + { + CurrentProtocol = UCI_CYCLONE; + Options["UCI_Variant"].set_default("xiangqi"); + } +#endif + is.seekg(0); + position(pos, is, states); + } + else if (!token.empty() && token[0] != '#') + sync_cout << "Unknown command: " << cmd << sync_endl; + + } while (token != "quit" && argc == 1); // Command line args are one-shot +} + + +/// UCI::value() converts a Value to a string suitable for use with the UCI +/// protocol specification: +/// +/// cp The score from the engine's point of view in centipawns. +/// mate Mate in y moves, not plies. If the engine is getting mated +/// use negative values for y. + +string UCI::value(Value v) { + + assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); + + stringstream ss; + + if (CurrentProtocol == XBOARD) + { + if (abs(v) < VALUE_MATE_IN_MAX_PLY) + ss << v * 100 / PawnValueEg; + else + ss << (v > 0 ? XBOARD_VALUE_MATE + VALUE_MATE - v + 1 : -XBOARD_VALUE_MATE - VALUE_MATE - v - 1) / 2; + } else + + if (abs(v) < VALUE_MATE_IN_MAX_PLY) + ss << (CurrentProtocol == UCCI ? "" : "cp ") << v * 100 / PawnValueEg; + else if (CurrentProtocol == USI) + // In USI, mate distance is given in ply + ss << "mate " << (v > 0 ? VALUE_MATE - v : -VALUE_MATE - v); + else + ss << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v - 1) / 2; + + return ss.str(); +} + + +/// UCI::wdl() report WDL statistics given an evaluation and a game ply, based on +/// data gathered for fishtest LTC games. + +string UCI::wdl(Value v, int ply) { + + stringstream ss; + + int wdl_w = win_rate_model( v, ply); + int wdl_l = win_rate_model(-v, ply); + int wdl_d = 1000 - wdl_w - wdl_l; + ss << " wdl " << wdl_w << " " << wdl_d << " " << wdl_l; + + return ss.str(); +} + + +/// UCI::square() converts a Square to a string in algebraic notation (g1, a7, etc.) + +std::string UCI::square(const Position& pos, Square s) { +#ifdef LARGEBOARDS + if (CurrentProtocol == USI) + return rank_of(s) < RANK_10 ? std::string{ char('1' + pos.max_file() - file_of(s)), char('a' + pos.max_rank() - rank_of(s)) } + : std::string{ char('0' + (pos.max_file() - file_of(s) + 1) / 10), + char('0' + (pos.max_file() - file_of(s) + 1) % 10), + char('a' + pos.max_rank() - rank_of(s)) }; + else if (pos.max_rank() == RANK_10 && CurrentProtocol != UCI_GENERAL) + return std::string{ char('a' + file_of(s)), char('0' + rank_of(s)) }; + else + return rank_of(s) < RANK_10 ? std::string{ char('a' + file_of(s)), char('1' + (rank_of(s) % 10)) } + : std::string{ char('a' + file_of(s)), char('0' + ((rank_of(s) + 1) / 10)), + char('0' + ((rank_of(s) + 1) % 10)) }; +#else + return CurrentProtocol == USI ? std::string{ char('1' + pos.max_file() - file_of(s)), char('a' + pos.max_rank() - rank_of(s)) } + : std::string{ char('a' + file_of(s)), char('1' + rank_of(s)) }; +#endif +} + +/// UCI::dropped_piece() generates a piece label string from a Move. + +string UCI::dropped_piece(const Position& pos, Move m) { + assert(type_of(m) == DROP); + if (dropped_piece_type(m) == pos.promoted_piece_type(in_hand_piece_type(m))) + // Dropping as promoted piece + return std::string{'+', pos.piece_to_char()[in_hand_piece_type(m)]}; + else + return std::string{pos.piece_to_char()[dropped_piece_type(m)]}; +} + + +/// UCI::move() converts a Move to a string in coordinate notation (g1f3, a7a8q). +/// The only special case is castling, where we print in the e1g1 notation in +/// normal chess mode, and in e1h1 notation in chess960 mode. Internally all +/// castling moves are always encoded as 'king captures rook'. + +string UCI::move(const Position& pos, Move m) { + + Square from = from_sq(m); + Square to = to_sq(m); + + if (m == MOVE_NONE) + return CurrentProtocol == USI ? "resign" : "(none)"; + + if (m == MOVE_NULL) + return "0000"; + + if (is_pass(m) && CurrentProtocol == XBOARD) + return "@@@@"; + + if (is_gating(m) && gating_square(m) == to) + from = to_sq(m), to = from_sq(m); + else if (type_of(m) == CASTLING && !pos.is_chess960()) + { + to = make_square(to > from ? pos.castling_kingside_file() : pos.castling_queenside_file(), rank_of(from)); + // If the castling move is ambiguous with a normal king move, switch to 960 notation + if (pos.pseudo_legal(make_move(from, to))) + to = to_sq(m); + } + + string move = (type_of(m) == DROP ? UCI::dropped_piece(pos, m) + (CurrentProtocol == USI ? '*' : '@') + : UCI::square(pos, from)) + UCI::square(pos, to); + + // Wall square + if (pos.walling() && CurrentProtocol == XBOARD) + move += "," + UCI::square(pos, to) + UCI::square(pos, gating_square(m)); + + if (type_of(m) == PROMOTION) + move += pos.piece_to_char()[make_piece(BLACK, promotion_type(m))]; + else if (type_of(m) == PIECE_PROMOTION) + move += '+'; + else if (type_of(m) == PIECE_DEMOTION) + move += '-'; + else if (is_gating(m)) + { + move += pos.piece_to_char()[make_piece(BLACK, gating_type(m))]; + if (gating_square(m) != from) + move += UCI::square(pos, gating_square(m)); + } + + // Wall square + if (pos.walling() && CurrentProtocol != XBOARD) + move += "," + UCI::square(pos, to) + UCI::square(pos, gating_square(m)); + + return move; +} + + +/// UCI::to_move() converts a string representing a move in coordinate notation +/// (g1f3, a7a8q) to the corresponding legal Move, if any. + +Move UCI::to_move(const Position& pos, string& str) { + + if (str.length() == 5) + { + if (str[4] == '=') + // shogi moves refraining from promotion might use equals sign + str.pop_back(); + else + // Junior could send promotion piece in uppercase + str[4] = char(tolower(str[4])); + } + + for (const auto& m : MoveList(pos)) + if (str == UCI::move(pos, m) || (is_pass(m) && str == UCI::square(pos, from_sq(m)) + UCI::square(pos, to_sq(m)))) + return m; + + return MOVE_NONE; +} + +std::string UCI::option_name(std::string name) { + if (CurrentProtocol == UCCI && name == "Hash") + return "hashsize"; + if (CurrentProtocol == USI) + { + if (name == "Hash" || name == "Ponder" || name == "MultiPV") + return "USI_" + name; + if (name.substr(0, 4) == "UCI_") + name = "USI_" + name.substr(4); + } + if (CurrentProtocol == UCCI || CurrentProtocol == USI) + std::replace(name.begin(), name.end(), ' ', '_'); + return name; +} + +bool UCI::is_valid_option(UCI::OptionsMap& options, std::string& name) { + for (const auto& it : options) + { + std::string optionName = option_name(it.first); + if (!options.key_comp()(optionName, name) && !options.key_comp()(name, optionName)) + { + name = it.first; + return true; + } + } + return false; +} + +Protocol CurrentProtocol = UCI_GENERAL; // Global object + +} // namespace Stockfish diff --git a/Fairy-Stockfish-master/src/uci.h b/Fairy-Stockfish-master/src/uci.h new file mode 100644 index 0000000000000000000000000000000000000000..ef22a5356b4fc50bc7cfce1786bfa18cb8826fe2 --- /dev/null +++ b/Fairy-Stockfish-master/src/uci.h @@ -0,0 +1,119 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef UCI_H_INCLUDED +#define UCI_H_INCLUDED + +#include +#include +#include + +#include "types.h" + +#include "variant.h" + +namespace Stockfish { + +class Position; + +namespace UCI { + +#ifndef _WIN32 + constexpr char SepChar = ':'; +#else + constexpr char SepChar = ';'; +#endif + +void init_variant(const Variant* v); + +class Option; + +/// Custom comparator because UCI options should be case insensitive +struct CaseInsensitiveLess { + bool operator() (const std::string&, const std::string&) const; +}; + +/// Our options container is actually a std::map +typedef std::map OptionsMap; + +/// Option class implements an option as defined by UCI protocol +class Option { + + typedef void (*OnChange)(const Option&); + +public: + Option(OnChange = nullptr); + Option(bool v, OnChange = nullptr); + Option(const char* v, OnChange = nullptr); + Option(const char* v, const std::vector& variants, OnChange = nullptr); + Option(double v, int minv, int maxv, OnChange = nullptr); + + Option& operator=(const std::string&); + void operator<<(const Option&); + operator double() const; + operator std::string() const; + bool operator==(const char*) const; + bool operator!=(const char*) const; + void set_combo(std::vector newComboValues); + void set_default(std::string newDefault); + const std::string get_type() const; + +private: + friend std::ostream& operator<<(std::ostream&, const OptionsMap&); + + std::string defaultValue, currentValue, type; + int min, max; + std::vector comboValues; + size_t idx; + OnChange on_change; +}; + +void init(OptionsMap&); +void loop(int argc, char* argv[]); +std::string value(Value v); +std::string square(const Position& pos, Square s); +std::string dropped_piece(const Position& pos, Move m); +std::string move(const Position& pos, Move m); +std::string pv(const Position& pos, Depth depth, Value alpha, Value beta); +std::string wdl(Value v, int ply); +Move to_move(const Position& pos, std::string& str); + +std::string option_name(std::string name); +bool is_valid_option(UCI::OptionsMap& options, std::string& name); + +} // namespace UCI + +extern UCI::OptionsMap Options; + +enum Protocol { + UCI_GENERAL, + USI, + UCCI, + UCI_CYCLONE, + XBOARD, +}; + +constexpr bool is_uci_dialect(Protocol p) { + return p != XBOARD; +} + +extern Protocol CurrentProtocol; + +} // namespace Stockfish + +#endif // #ifndef UCI_H_INCLUDED diff --git a/Fairy-Stockfish-master/src/ucioption.cpp b/Fairy-Stockfish-master/src/ucioption.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0326e047f8a8b65cebc33328c0e2bfb39480804d --- /dev/null +++ b/Fairy-Stockfish-master/src/ucioption.cpp @@ -0,0 +1,386 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include +#include + +#include "evaluate.h" +#include "misc.h" +#include "piece.h" +#include "search.h" +#include "thread.h" +#include "tt.h" +#include "uci.h" +#include "variant.h" +#include "syzygy/tbprobe.h" + +using std::string; + +namespace Stockfish { + +UCI::OptionsMap Options; // Global object + +namespace PSQT { + void init(const Variant* v); +} + +namespace UCI { + +// standard variants of XBoard/WinBoard +std::set standard_variants = { + "normal", "nocastle", "fischerandom", "knightmate", "3check", "makruk", "shatranj", + "asean", "seirawan", "crazyhouse", "bughouse", "suicide", "giveaway", "losers", "atomic", + "capablanca", "gothic", "janus", "caparandom", "grand", "shogi", "xiangqi", "duck", + "berolina", "spartan" +}; + +void init_variant(const Variant* v) { + pieceMap.init(v); + Bitboards::init_pieces(); +} + +/// 'On change' actions, triggered by an option's value change +void on_clear_hash(const Option&) { Search::clear(); } +void on_hash_size(const Option& o) { TT.resize(size_t(o)); } +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) { + std::stringstream ss((std::string)o); + std::string path; + + while (std::getline(ss, path, SepChar)) + variants.parse(path); + + Options["UCI_Variant"].set_combo(variants.get_keys()); +} +void on_variant_set(const Option &o) { + // Re-initialize NNUE + Eval::NNUE::init(); + + const Variant* v = variants.find(o)->second; + init_variant(v); + PSQT::init(v); +} +void on_variant_change(const Option &o) { + // Variant initialization + on_variant_set(o); + + const Variant* v = variants.find(o)->second; + // 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 : popcount(v->pieceTypes)) : 0; + if (CurrentProtocol == XBOARD) + { + // Overwrite setup command for Janggi variants + auto itJanggi = variants.find("janggi"); + if ( itJanggi != variants.end() + && v->variantTemplate == itJanggi->second->variantTemplate + && v->startFen == itJanggi->second->startFen + && v->pieceToCharTable == itJanggi->second->pieceToCharTable) + { + sync_cout << "setup (PH.R.AE..K.C.ph.r.ae..k.c.) 9x10+0_janggi " + << "rhea1aehr/4k4/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/4K4/RHEA1AEHR w - - 0 1" + << sync_endl; + return; + } + // 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 (PieceSet ps = v->pieceTypes; ps;) + { + PieceType pt = pop_lsb(ps); + 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 == v->dropNoDoubled) + suffix += std::string(v->dropNoDoubledCount, 'f'); + else if (pt == BISHOP && v->dropOppositeColoredBishop) + suffix += "s"; + suffix += "@" + std::to_string(pt == PAWN && !v->promotionZonePawnDrops && v->promotionRegion[WHITE] ? rank_of(lsb(v->promotionRegion[WHITE])) : 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 { + + return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), + [](char c1, char c2) { return tolower(c1) < tolower(c2); }); +} + + +/// UCI::init() initializes the UCI options to their hard-coded default values + +void init(OptionsMap& o) { + + constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; + + o["Debug Log File"] << Option("", on_logger); + o["Threads"] << Option(1, 1, 512, on_threads); + o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size); + o["Clear Hash"] << Option(on_clear_hash); + o["Ponder"] << Option(false); + o["MultiPV"] << Option(1, 1, 500); + o["Skill Level"] << Option(20, -20, 20); + o["Move Overhead"] << Option(10, 0, 5000); + o["Slow Mover"] << Option(100, 10, 1000); + o["nodestime"] << Option(0, 0, 10000); + o["UCI_Chess960"] << Option(false); + o["UCI_Variant"] << Option("chess", variants.get_keys(), on_variant_change); + o["UCI_AnalyseMode"] << Option(false); + o["UCI_LimitStrength"] << Option(false); + o["UCI_Elo"] << Option(1350, 500, 2850); + o["UCI_ShowWDL"] << Option(false); + o["SyzygyPath"] << Option("", on_tb_path); + o["SyzygyProbeDepth"] << Option(1, 1, 100); + o["Syzygy50MoveRule"] << Option(true); + o["SyzygyProbeLimit"] << Option(7, 0, 7); + o["Use NNUE"] << Option(true, on_use_NNUE); +#ifndef NNUE_EMBEDDING_OFF + o["EvalFile"] << Option(EvalFileDefaultName, on_eval_file); +#else + o["EvalFile"] << Option("", on_eval_file); +#endif + o["TsumeMode"] << Option(false); + o["VariantPath"] << Option("", on_variant_path); + o["usemillisec"] << Option(true); // time unit for UCCI +} + + +/// operator<<() is used to print all the options default values in chronological +/// insertion order (the idx field) and in the format defined by the UCI protocol. + +std::ostream& operator<<(std::ostream& os, const OptionsMap& om) { + + if (CurrentProtocol == XBOARD) + { + for (size_t idx = 0; idx < om.size(); ++idx) + for (const auto& it : om) + if (it.second.idx == idx && it.first != "UCI_Variant" && it.first != "Threads" && it.first != "Hash") + { + const Option& o = it.second; + os << "\nfeature option=\"" << it.first << " -" << o.type; + + if (o.type == "string" || o.type == "combo") + os << " " << o.defaultValue; + else if (o.type == "check") + os << " " << int(o.defaultValue == "true"); + + if (o.type == "combo") + for (string value : o.comboValues) + if (value != o.defaultValue) + os << " /// " << value; + + if (o.type == "spin") + os << " " << int(stof(o.defaultValue)) + << " " << o.min + << " " << o.max; + + os << "\""; + + break; + } + } + else + + for (size_t idx = 0; idx < om.size(); ++idx) + for (const auto& it : om) + if (it.second.idx == idx) + { + const Option& o = it.second; + // UCI dialects do not allow spaces + if (CurrentProtocol == UCCI || CurrentProtocol == USI) + { + string name = option_name(it.first); + // UCCI skips "name" + os << "\noption " << (CurrentProtocol == UCCI ? "" : "name ") << name << " type " << o.type; + } + else + os << "\noption name " << it.first << " type " << o.type; + + if (o.type == "string" || o.type == "check" || o.type == "combo") + os << " default " << o.defaultValue; + + if (o.type == "combo") + for (string value : o.comboValues) + os << " var " << value; + + if (o.type == "spin") + os << " default " << int(stof(o.defaultValue)) + << " min " << o.min + << " max " << o.max; + + break; + } + + return os; +} + + +/// Option class constructors and conversion operators + +Option::Option(const char* v, OnChange f) : type("string"), min(0), max(0), on_change(f) +{ defaultValue = currentValue = v; } + +Option::Option(const char* v, const std::vector& values, OnChange f) : type("combo"), min(0), max(0), comboValues(values), on_change(f) +{ defaultValue = currentValue = v; } + +Option::Option(bool v, OnChange f) : type("check"), min(0), max(0), on_change(f) +{ defaultValue = currentValue = (v ? "true" : "false"); } + +Option::Option(OnChange f) : type("button"), min(0), max(0), on_change(f) +{} + +Option::Option(double v, int minv, int maxv, OnChange f) : type("spin"), min(minv), max(maxv), on_change(f) +{ defaultValue = currentValue = std::to_string(v); } + +Option::operator double() const { + assert(type == "check" || type == "spin"); + return (type == "spin" ? stof(currentValue) : currentValue == "true"); +} + +Option::operator std::string() const { + assert(type == "string" || type == "combo"); + return currentValue; +} + +bool Option::operator==(const char* s) const { + assert(type == "combo"); + return !CaseInsensitiveLess()(currentValue, s) + && !CaseInsensitiveLess()(s, currentValue); +} + +bool Option::operator!=(const char* s) const { + assert(type == "combo"); + return !(*this == s); +} + + +/// operator<<() inits options and assigns idx in the correct printing order + +void Option::operator<<(const Option& o) { + + static size_t insert_order = 0; + + *this = o; + idx = insert_order++; +} + + +/// operator=() updates currentValue and triggers on_change() action. It's up to +/// the GUI to check for option's limits, but we could receive the new value +/// from the user by console window, so let's check the bounds anyway. + +Option& Option::operator=(const string& v) { + + assert(!type.empty()); + + if ( (type != "button" && v.empty()) + || (type == "check" && v != "true" && v != "false") + || (type == "combo" && (std::find(comboValues.begin(), comboValues.end(), v) == comboValues.end())) + || (type == "spin" && (stof(v) < min || stof(v) > max))) + return *this; + + if (type == "combo") + { + OptionsMap comboMap; // To have case insensitive compare + for (string token : comboValues) + comboMap[token] << Option(); + if (!comboMap.count(v) || v == "var") + return *this; + } + + if (type != "button") + currentValue = v; + + if (on_change) + on_change(*this); + + return *this; +} + +void Option::set_combo(std::vector newComboValues) { + comboValues = newComboValues; +} + +void Option::set_default(std::string newDefault) { + defaultValue = currentValue = newDefault; + + // When changing the variant default, suppress variant definition output, + // but still do the essential re-initialization of the variant + if (on_change) + (on_change == on_variant_change ? on_variant_set : on_change)(*this); +} + +const std::string Option::get_type() const { + return type; +} + +} // namespace UCI + +} // namespace Stockfish diff --git a/Fairy-Stockfish-master/src/variant.cpp b/Fairy-Stockfish-master/src/variant.cpp new file mode 100644 index 0000000000000000000000000000000000000000..57c187d52089513491ac80aaf86afd12650d7d8c --- /dev/null +++ b/Fairy-Stockfish-master/src/variant.cpp @@ -0,0 +1,2229 @@ +/* + Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish + Copyright (C) 2018-2022 Fabian Fichter + + Fairy-Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Fairy-Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include + +#include "parser.h" +#include "piece.h" +#include "variant.h" + +using std::string; + +namespace Stockfish { + +VariantMap variants; // Global object + +namespace { + // Base variant + Variant* variant_base() { + Variant* v = new Variant(); + return v; + } + // Base for all fairy variants + Variant* chess_variant_base() { + Variant* v = variant_base()->init(); + v->pieceToCharTable = "PNBRQ................Kpnbrq................k"; + return v; + } + // Standard chess + // https://en.wikipedia.org/wiki/Chess + Variant* chess_variant() { + Variant* v = chess_variant_base()->init(); + v->nnueAlias = "nn-"; + return v; + } + // Chess960 aka Fischer random chess + // https://en.wikipedia.org/wiki/Fischer_random_chess + Variant* chess960_variant() { + Variant* v = chess_variant()->init(); + v->chess960 = true; + v->nnueAlias = "nn-"; + return v; + } + // Standard chess without castling + Variant* nocastle_variant() { + Variant* v = chess_variant()->init(); + v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - - 0 1"; + v->castling = false; + return v; + } + // Armageddon Chess + // https://en.wikipedia.org/wiki/Fast_chess#Armageddon + Variant* armageddon_variant() { + Variant* v = chess_variant()->init(); + v->materialCounting = BLACK_DRAW_ODDS; + return v; + } + // Torpedo Chess + // https://arxiv.org/abs/2009.04374 + Variant* torpedo_variant() { + Variant* v = chess_variant_base()->init(); + v->doubleStepRegion[WHITE] = AllSquares; + v->doubleStepRegion[BLACK] = AllSquares; + return v; + } + // Berolina Chess + // https://www.chessvariants.com/dpieces.dir/berlin.html + Variant* berolina_variant() { + Variant* v = chess_variant_base()->init(); + v->remove_piece(PAWN); + v->add_piece(CUSTOM_PIECE_1, 'p', "mfFcfeWimfnA"); + v->mainPromotionPawnType[WHITE] = v->mainPromotionPawnType[BLACK] = CUSTOM_PIECE_1; + v->promotionPawnTypes[WHITE] = v->promotionPawnTypes[BLACK] = piece_set(CUSTOM_PIECE_1); + v->enPassantTypes[WHITE] = v->enPassantTypes[BLACK] = piece_set(CUSTOM_PIECE_1); + v->nMoveRuleTypes[WHITE] = v->nMoveRuleTypes[BLACK] = piece_set(CUSTOM_PIECE_1); + return v; + } + // Pawnsideways + // https://arxiv.org/abs/2009.04374 + Variant* pawnsideways_variant() { + Variant* v = chess_variant_base()->init(); + v->remove_piece(PAWN); + v->add_piece(CUSTOM_PIECE_1, 'p', "fsmWfceFifmnD"); + v->mainPromotionPawnType[WHITE] = v->mainPromotionPawnType[BLACK] = CUSTOM_PIECE_1; + v->promotionPawnTypes[WHITE] = v->promotionPawnTypes[BLACK] = piece_set(CUSTOM_PIECE_1); + v->enPassantTypes[WHITE] = v->enPassantTypes[BLACK] = piece_set(CUSTOM_PIECE_1); + v->nMoveRuleTypes[WHITE] = v->nMoveRuleTypes[BLACK] = piece_set(CUSTOM_PIECE_1); + return v; + } + // Pawnback + // https://arxiv.org/abs/2009.04374 + Variant* pawnback_variant() { + Variant* v = chess_variant_base()->init(); + v->remove_piece(PAWN); + v->add_piece(CUSTOM_PIECE_1, 'p', "fbmWfceFifmnD"); + v->mobilityRegion[WHITE][CUSTOM_PIECE_1] = (Rank2BB | Rank3BB | Rank4BB | Rank5BB | Rank6BB | Rank7BB | Rank8BB); + v->mobilityRegion[BLACK][CUSTOM_PIECE_1] = (Rank7BB | Rank6BB | Rank5BB | Rank4BB | Rank3BB | Rank2BB | Rank1BB); + v->mainPromotionPawnType[WHITE] = v->mainPromotionPawnType[BLACK] = CUSTOM_PIECE_1; + v->promotionPawnTypes[WHITE] = v->promotionPawnTypes[BLACK] = piece_set(CUSTOM_PIECE_1); + v->enPassantTypes[WHITE] = v->enPassantTypes[BLACK] = piece_set(CUSTOM_PIECE_1); + v->nMoveRuleTypes[WHITE] = v->nMoveRuleTypes[BLACK] = NO_PIECE_SET; // backwards pawn moves are reversible + return v; + } + // Legan Chess + // https://en.wikipedia.org/wiki/Legan_chess + Variant* legan_variant() { + Variant* v = chess_variant_base()->init(); + v->remove_piece(PAWN); + v->add_piece(CUSTOM_PIECE_1, 'p', "mflFcflW"); + v->promotionRegion[WHITE] = make_bitboard(SQ_A8, SQ_B8, SQ_C8, SQ_D8, SQ_A7, SQ_A6, SQ_A5); + v->promotionRegion[BLACK] = make_bitboard(SQ_E1, SQ_F1, SQ_G1, SQ_H1, SQ_H2, SQ_H3, SQ_H4); + v->mainPromotionPawnType[WHITE] = v->mainPromotionPawnType[BLACK] = CUSTOM_PIECE_1; + v->promotionPawnTypes[WHITE] = v->promotionPawnTypes[BLACK] = piece_set(CUSTOM_PIECE_1); + v->nMoveRuleTypes[WHITE] = v->nMoveRuleTypes[BLACK] = piece_set(CUSTOM_PIECE_1); + v->startFen = "knbrp3/bqpp4/npp5/rp1p3P/p3P1PR/5PPN/4PPQB/3PRBNK w - - 0 1"; + v->doubleStep = false; + return v; + } + // Pseudo-variant only used for endgame initialization + Variant* fairy_variant() { + Variant* v = chess_variant_base()->init(); + v->add_piece(SILVER, 's'); + v->add_piece(FERS, 'f'); + v->add_piece(ARCHBISHOP, 'a'); + v->add_piece(CHANCELLOR, 'c'); + v->add_piece(COMMONER, 'm'); + return v; + } + // Raazuva (Maldivian Chess) + Variant* raazuvaa_variant() { + Variant* v = chess_variant_base()->init(); + v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - - 0 1"; + v->castling = false; + v->doubleStep = false; + return v; + } + // Makruk (Thai Chess) + // https://en.wikipedia.org/wiki/Makruk + Variant* makruk_variant() { + Variant* v = chess_variant_base()->init(); + v->variantTemplate = "makruk"; + v->pieceToCharTable = "PN.R.M....SKpn.r.m....sk"; + v->remove_piece(BISHOP); + v->remove_piece(QUEEN); + v->add_piece(KHON, 's'); + v->add_piece(MET, 'm'); + v->startFen = "rnsmksnr/8/pppppppp/8/8/PPPPPPPP/8/RNSKMSNR w - - 0 1"; + v->promotionRegion[WHITE] = Rank6BB | Rank7BB | Rank8BB; + v->promotionRegion[BLACK] = Rank3BB | Rank2BB | Rank1BB; + v->promotionPieceTypes[WHITE] = piece_set(MET); + v->promotionPieceTypes[BLACK] = piece_set(MET); + v->doubleStep = false; + v->castling = false; + v->nMoveRule = 0; + v->countingRule = MAKRUK_COUNTING; + return v; + } + // Makpong (Defensive Chess) + // A Makruk variant used for tie-breaks + // https://www.mayhematics.com/v/vol8/vc64b.pdf, p. 177 + Variant* makpong_variant() { + Variant* v = makruk_variant()->init(); + v->makpongRule = true; + return v; + } + // Ouk Chatrang, Cambodian chess + // https://en.wikipedia.org/wiki/Makruk#Cambodian_chess + Variant* cambodian_variant() { + Variant* v = makruk_variant()->init(); + v->startFen = "rnsmksnr/8/pppppppp/8/8/PPPPPPPP/8/RNSKMSNR w DEde - 0 1"; + v->gating = true; + v->cambodianMoves = true; + v->countingRule = CAMBODIAN_COUNTING; + v->nnueAlias = "makruk"; + return v; + } + // Kar Ouk + // A variant of Cambodian chess where the first check wins + // https://en.wikipedia.org/wiki/Makruk#Ka_Ouk + Variant* karouk_variant() { + Variant* v = cambodian_variant()->init(); + v->checkCounting = true; + return v; + } + // ASEAN chess + // A simplified version of south-east asian variants + // https://aseanchess.org/laws-of-asean-chess/ + Variant* asean_variant() { + Variant* v = chess_variant_base()->init(); + v->remove_piece(BISHOP); + v->remove_piece(QUEEN); + v->add_piece(KHON, 'b'); + v->add_piece(MET, 'q'); + v->startFen = "rnbqkbnr/8/pppppppp/8/8/PPPPPPPP/8/RNBQKBNR w - - 0 1"; + v->promotionPieceTypes[WHITE] = piece_set(ROOK) | KNIGHT | KHON | MET; + v->promotionPieceTypes[BLACK] = piece_set(ROOK) | KNIGHT | KHON | MET; + v->doubleStep = false; + v->castling = false; + v->countingRule = ASEAN_COUNTING; + return v; + } + // Ai-wok + // A makruk variant where the met is replaced by a super-piece moving as rook, knight, or met + Variant* aiwok_variant() { + Variant* v = makruk_variant()->init(); + v->pieceToCharTable = "PN.R...A..SKpn.r...a..sk"; + v->remove_piece(MET); + v->add_piece(AIWOK, 'a'); + v->startFen = "rnsaksnr/8/pppppppp/8/8/PPPPPPPP/8/RNSKASNR w - - 0 1"; + v->promotionPieceTypes[WHITE] = piece_set(AIWOK); + v->promotionPieceTypes[BLACK] = piece_set(AIWOK); + return v; + } + // Shatranj + // The medieval form of chess, originating from chaturanga + // https://en.wikipedia.org/wiki/Shatranj + Variant* shatranj_variant() { + Variant* v = chess_variant_base()->init(); + v->variantTemplate = "shatranj"; + v->pieceToCharTable = "PN.R.QB....Kpn.r.qb....k"; + v->remove_piece(BISHOP); + v->remove_piece(QUEEN); + v->add_piece(ALFIL, 'b'); + v->add_piece(FERS, 'q'); + v->startFen = "rnbkqbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBKQBNR w - - 0 1"; + v->promotionPieceTypes[WHITE] = piece_set(FERS); + v->promotionPieceTypes[BLACK] = piece_set(FERS); + v->doubleStep = false; + v->castling = false; + v->extinctionValue = -VALUE_MATE; + v->extinctionClaim = true; + v->extinctionPieceTypes = piece_set(ALL_PIECES); + v->extinctionPieceCount = 1; + v->extinctionOpponentPieceCount = 2; + v->stalemateValue = -VALUE_MATE; + v->nMoveRule = 70; + return v; + } + // Chaturanga + // The actual rules of the game are not known. This reflects the rules as used on chess.com. + // https://en.wikipedia.org/wiki/Chaturanga + Variant* chaturanga_variant() { + Variant* v = shatranj_variant()->init(); + v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - - 0 1"; + v->extinctionValue = VALUE_NONE; + v->nnueAlias = "shatranj"; + return v; + } + // Amazon chess + // The queen has the additional power of moving like a knight. + // https://www.chessvariants.com/diffmove.dir/amazone.html + Variant* amazon_variant() { + Variant* v = chess_variant_base()->init(); + v->pieceToCharTable = "PNBR..............AKpnbr..............ak"; + v->remove_piece(QUEEN); + v->add_piece(AMAZON, 'a'); + v->startFen = "rnbakbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBAKBNR w KQkq - 0 1"; + v->promotionPieceTypes[WHITE] = piece_set(AMAZON) | ROOK | BISHOP | KNIGHT; + v->promotionPieceTypes[BLACK] = piece_set(AMAZON) | ROOK | BISHOP | KNIGHT; + return v; + } + // Nightrider chess + // Knights are replaced by nightriders. + // https://en.wikipedia.org/wiki/Nightrider_(chess) + Variant* nightrider_variant() { + Variant* v = chess_variant_base()->init(); + v->remove_piece(KNIGHT); + v->add_piece(CUSTOM_PIECE_1, 'n', "NN"); + v->promotionPieceTypes[WHITE] = piece_set(QUEEN) | ROOK | BISHOP | CUSTOM_PIECE_1; + v->promotionPieceTypes[BLACK] = piece_set(QUEEN) | ROOK | BISHOP | CUSTOM_PIECE_1; + return v; + } + // Grasshopper chess + // https://en.wikipedia.org/wiki/Grasshopper_chess + Variant* grasshopper_variant() { + Variant* v = chess_variant_base()->init(); + v->add_piece(CUSTOM_PIECE_1, 'g', "gQ"); + v->promotionPieceTypes[WHITE] |= CUSTOM_PIECE_1; + v->promotionPieceTypes[BLACK] |= CUSTOM_PIECE_1; + v->startFen = "rnbqkbnr/gggggggg/pppppppp/8/8/PPPPPPPP/GGGGGGGG/RNBQKBNR w KQkq - 0 1"; + v->doubleStep = false; + return v; + } + // Hoppel-Poppel + // A variant from Germany where knights capture like bishops and vice versa + // https://www.chessvariants.com/diffmove.dir/hoppel-poppel.html + Variant* hoppelpoppel_variant() { + Variant* v = chess_variant_base()->init(); + v->remove_piece(KNIGHT); + v->remove_piece(BISHOP); + v->add_piece(KNIBIS, 'n'); + v->add_piece(BISKNI, 'b'); + v->promotionPieceTypes[WHITE] = piece_set(QUEEN) | ROOK | BISKNI | KNIBIS; + v->promotionPieceTypes[BLACK] = piece_set(QUEEN) | ROOK | BISKNI | KNIBIS; + return v; + } + // New Zealand + // Knights capture like rooks and vice versa. + Variant* newzealand_variant() { + Variant* v = chess_variant_base()->init(); + v->remove_piece(ROOK); + v->remove_piece(KNIGHT); + v->add_piece(ROOKNI, 'r'); + v->add_piece(KNIROO, 'n'); + v->castlingRookPieces[WHITE] = v->castlingRookPieces[BLACK] = piece_set(ROOKNI); + v->promotionPieceTypes[WHITE] = piece_set(QUEEN) | ROOKNI | BISHOP | KNIROO; + v->promotionPieceTypes[BLACK] = piece_set(QUEEN) | ROOKNI | BISHOP | KNIROO; + return v; + } + // King of the Hill + // https://lichess.org/variant/kingOfTheHill + Variant* kingofthehill_variant() { + Variant* v = chess_variant_base()->init(); + v->flagPiece[WHITE] = v->flagPiece[BLACK] = KING; + v->flagRegion[WHITE] = (Rank4BB | Rank5BB) & (FileDBB | FileEBB); + v->flagRegion[BLACK] = (Rank4BB | Rank5BB) & (FileDBB | FileEBB); + v->flagMove = false; + return v; + } + // Racing Kings + // https://lichess.org/variant/racingKings + Variant* racingkings_variant() { + Variant* v = chess_variant_base()->init(); + v->startFen = "8/8/8/8/8/8/krbnNBRK/qrbnNBRQ w - - 0 1"; + v->flagPiece[WHITE] = v->flagPiece[BLACK] = KING; + v->flagRegion[WHITE] = Rank8BB; + v->flagRegion[BLACK] = Rank8BB; + v->flagMove = true; + v->castling = false; + v->checking = false; + v->endgameEval = EG_EVAL_RK; + return v; + } + // Knightmate + // https://www.chessvariants.com/diffobjective.dir/knightmate.html + Variant* knightmate_variant() { + Variant* v = chess_variant_base()->init(); + v->add_piece(COMMONER, 'm'); + v->remove_piece(KNIGHT); + v->startFen = "rmbqkbmr/pppppppp/8/8/8/8/PPPPPPPP/RMBQKBMR w KQkq - 0 1"; + v->kingType = KNIGHT; + v->castlingKingPiece[WHITE] = v->castlingKingPiece[BLACK] = KING; + v->promotionPieceTypes[WHITE] = piece_set(COMMONER) | QUEEN | ROOK | BISHOP; + v->promotionPieceTypes[BLACK] = piece_set(COMMONER) | QUEEN | ROOK | BISHOP; + return v; + } + // Misere chess + // Get checkmated to win. + // Variant used to run some selfmate analysis http://www.kotesovec.cz/gustav/gustav_alybadix.htm + Variant* misere_variant() { + Variant* v = chess_variant_base()->init(); + v->checkmateValue = VALUE_MATE; + v->endgameEval = EG_EVAL_MISERE; + return v; + } + // Losers chess + // https://www.chessclub.com/help/Wild17 + Variant* losers_variant() { + Variant* v = chess_variant_base()->init(); + v->checkmateValue = VALUE_MATE; + v->stalemateValue = VALUE_MATE; + v->extinctionValue = VALUE_MATE; + v->extinctionPieceTypes = piece_set(ALL_PIECES); + v->extinctionPieceCount = 1; + v->mustCapture = true; + return v; + } + // Giveaway chess + // Antichess with castling. + // https://www.chessvariants.com/diffobjective.dir/giveaway.old.html + Variant* giveaway_variant() { + Variant* v = chess_variant_base()->init(); + v->variantTemplate = "giveaway"; + v->remove_piece(KING); + v->add_piece(COMMONER, 'k'); + v->castlingKingPiece[WHITE] = v->castlingKingPiece[BLACK] = COMMONER; + v->promotionPieceTypes[WHITE] = piece_set(COMMONER) | QUEEN | ROOK | BISHOP | KNIGHT; + v->promotionPieceTypes[BLACK] = piece_set(COMMONER) | QUEEN | ROOK | BISHOP | KNIGHT; + v->stalemateValue = VALUE_MATE; + v->extinctionValue = VALUE_MATE; + v->extinctionPieceTypes = piece_set(ALL_PIECES); + v->mustCapture = true; + v->nnueAlias = "antichess"; + v->endgameEval = EG_EVAL_ANTI; + return v; + } + // Antichess + // https://lichess.org/variant/antichess + Variant* antichess_variant() { + Variant* v = giveaway_variant()->init(); + v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - - 0 1"; + v->castling = false; + v->endgameEval = EG_EVAL_ANTI; + return v; + } + // Suicide chess + // Antichess with modified stalemate adjudication. + // https://www.freechess.org/Help/HelpFiles/suicide_chess.html + Variant* suicide_variant() { + Variant* v = antichess_variant()->init(); + v->stalematePieceCount = true; + v->nnueAlias = "antichess"; + v->endgameEval = EG_EVAL_ANTI; + return v; + } + // Codrus + // Lose the king to win. Captures are mandatory. + // http://www.binnewirtz.com/Schlagschach1.htm + Variant* codrus_variant() { + Variant* v = giveaway_variant()->init(); + v->promotionPieceTypes[WHITE] = piece_set(QUEEN) | ROOK | BISHOP | KNIGHT; + v->promotionPieceTypes[BLACK] = piece_set(QUEEN) | ROOK | BISHOP | KNIGHT; + v->extinctionPieceTypes = piece_set(COMMONER); + return v; + } + // Extinction chess + // https://en.wikipedia.org/wiki/Extinction_chess + Variant* extinction_variant() { + Variant* v = chess_variant_base()->init(); + v->remove_piece(KING); + v->add_piece(COMMONER, 'k'); + v->castlingKingPiece[WHITE] = v->castlingKingPiece[BLACK] = COMMONER; + v->promotionPieceTypes[WHITE] = piece_set(COMMONER) | QUEEN | ROOK | BISHOP | KNIGHT; + v->promotionPieceTypes[BLACK] = piece_set(COMMONER) | QUEEN | ROOK | BISHOP | KNIGHT; + v->extinctionValue = -VALUE_MATE; + v->extinctionPieceTypes = piece_set(COMMONER) | QUEEN | ROOK | BISHOP | KNIGHT | PAWN; + return v; + } + // Kinglet + // https://en.wikipedia.org/wiki/V._R._Parton#Kinglet_chess + Variant* kinglet_variant() { + Variant* v = extinction_variant()->init(); + v->promotionPieceTypes[WHITE] = piece_set(COMMONER); + v->promotionPieceTypes[BLACK] = piece_set(COMMONER); + v->extinctionPieceTypes = piece_set(PAWN); + return v; + } + // Three Kings Chess + // https://github.com/cutechess/cutechess/blob/master/projects/lib/src/board/threekingsboard.h + Variant* threekings_variant() { + Variant* v = chess_variant_base()->init(); + v->remove_piece(KING); + v->add_piece(COMMONER, 'k'); + v->castlingKingPiece[WHITE] = v->castlingKingPiece[BLACK] = COMMONER; + v->startFen = "knbqkbnk/pppppppp/8/8/8/8/PPPPPPPP/KNBQKBNK w - - 0 1"; + v->extinctionValue = -VALUE_MATE; + v->extinctionPieceTypes = piece_set(COMMONER); + v->extinctionPieceCount = 2; + return v; + } + // Horde chess + // https://en.wikipedia.org/wiki/Dunsany%27s_chess#Horde_chess + Variant* horde_variant() { + Variant* v = chess_variant_base()->init(); + v->startFen = "rnbqkbnr/pppppppp/8/1PP2PP1/PPPPPPPP/PPPPPPPP/PPPPPPPP/PPPPPPPP w kq - 0 1"; + v->doubleStepRegion[WHITE] |= Rank1BB; + v->enPassantRegion[WHITE] = Rank6BB; // exclude en passant on second rank + v->enPassantRegion[BLACK] = Rank3BB; // exclude en passant on second rank + v->extinctionValue = -VALUE_MATE; + v->extinctionPieceTypes = piece_set(ALL_PIECES); + return v; + } + // Petrified + // Sideways pawns + petrification on capture + // https://www.chess.com/variants/petrified + Variant* petrified_variant() { + Variant* v = pawnsideways_variant()->init(); + v->remove_piece(KING); + v->add_piece(COMMONER, 'k'); + v->castlingKingPiece[WHITE] = v->castlingKingPiece[BLACK] = COMMONER; + v->extinctionValue = -VALUE_MATE; + v->extinctionPieceTypes = piece_set(COMMONER); + v->extinctionPseudoRoyal = true; + v->petrifyOnCaptureTypes = piece_set(COMMONER) | QUEEN | ROOK | BISHOP | KNIGHT; + return v; + } + // Atomic chess without checks (ICC rules) + // https://www.chessclub.com/help/atomic + Variant* nocheckatomic_variant() { + Variant* v = chess_variant_base()->init(); + v->variantTemplate = "atomic"; + v->remove_piece(KING); + v->add_piece(COMMONER, 'k'); + v->castlingKingPiece[WHITE] = v->castlingKingPiece[BLACK] = COMMONER; + v->extinctionValue = -VALUE_MATE; + v->extinctionPieceTypes = piece_set(COMMONER); + v->blastOnCapture = true; + v->nnueAlias = "atomic"; + return v; + } + // Atomic chess + // https://en.wikipedia.org/wiki/Atomic_chess + Variant* atomic_variant() { + Variant* v = nocheckatomic_variant()->init(); + v->extinctionPseudoRoyal = true; + v->endgameEval = EG_EVAL_ATOMIC; + return v; + } + + // Atomar chess + // https://web.archive.org/web/20230519082613/https://chronatog.com/wp-content/uploads/2021/09/atomar-chess-rules.pdf + Variant* atomar_variant() { + Variant* v = nocheckatomic_variant()->init(); + v->blastImmuneTypes = piece_set(COMMONER); + v->mutuallyImmuneTypes = piece_set(COMMONER); + return v; + } + +#ifdef ALLVARS + // Duck chess + // https://duckchess.com/ + Variant* duck_variant() { + Variant* v = chess_variant_base()->init(); + v->remove_piece(KING); + v->add_piece(COMMONER, 'k'); + v->castlingKingPiece[WHITE] = v->castlingKingPiece[BLACK] = COMMONER; + v->extinctionValue = -VALUE_MATE; + v->extinctionPieceTypes = piece_set(COMMONER); + v->wallingRule = DUCK; + v->stalemateValue = VALUE_MATE; + v->endgameEval = EG_EVAL_DUCK; + return v; + } +#endif + + Variant* isolation_variant() { //https://boardgamegeek.com/boardgame/1875/isolation + Variant* v = chess_variant_base()->init(); + v->maxRank = RANK_8; + v->maxFile = FILE_F; + v->reset_pieces(); + v->add_piece(CUSTOM_PIECE_1, 'p', "mK"); //move as a King, but can't capture + v->startFen = "3p2/6/6/6/6/6/6/2P3 w - - 0 1"; + v->stalemateValue = -VALUE_MATE; + v->wallingRule = STATIC; + v->wallingRegion[WHITE] = v->wallingRegion[BLACK] = AllSquares ^ make_bitboard(SQ_C1, SQ_D8); + return v; + } + + Variant* isolation7x7_variant() { + Variant* v = isolation_variant()->init(); + v->maxRank = RANK_7; + v->maxFile = FILE_G; + v->startFen = "3p3/7/7/7/7/7/3P3 w - - 0 1"; + v->wallingRegion[WHITE] = v->wallingRegion[BLACK] = AllSquares ^ make_bitboard(SQ_D1, SQ_D7); + return v; + } + + Variant* snailtrail_variant() { //https://boardgamegeek.com/boardgame/37135/snailtrail + Variant* v = chess_variant_base()->init(); + v->maxRank = RANK_7; + v->maxFile = FILE_G; + v->reset_pieces(); + v->add_piece(CUSTOM_PIECE_1, 'p', "mK"); //move as a King, but can't capture + v->startFen = "6p/7/7/7/7/7/P6 w - - 0 1"; + v->stalemateValue = -VALUE_MATE; + v->wallingRule = PAST; + return v; + } + + Variant* joust_variant() { //https://www.chessvariants.com/programs.dir/joust.html + //This page mainly describes a variant where position on home row is randomized, but also a variant where they start in the centre(implemented here) + Variant* v = chess_variant_base()->init(); + v->reset_pieces(); + v->add_piece(CUSTOM_PIECE_1, 'n', "mN"); //move as a Knight, but can't capture + v->startFen = "8/8/8/4n3/3N4/8/8/8 w - - 0 1"; + v->stalemateValue = -VALUE_MATE; + v->wallingRule = PAST; + return v; + } + + Variant* fox_and_hounds_variant() { //https://boardgamegeek.com/boardgame/148180/fox-and-hounds + Variant* v = chess_variant_base()->init(); + v->reset_pieces(); + v->add_piece(CUSTOM_PIECE_1, 'h', "mfF"); //Hound + v->add_piece(CUSTOM_PIECE_2, 'f', "mF"); //Fox + v->startFen = "1h1h1h1h/8/8/8/8/8/8/4F3 w - - 0 1"; + v->stalemateValue = -VALUE_MATE; + v->flagPiece[WHITE] = CUSTOM_PIECE_2; + v->flagRegion[WHITE] = Rank8BB; + return v; + } + + // Three-check chess + // Check the king three times to win + // https://lichess.org/variant/threeCheck + Variant* threecheck_variant() { + Variant* v = chess_variant_base()->init(); + v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 3+3 0 1"; + v->checkCounting = true; + return v; + } + // Five-check chess + // Check the king five times to win + Variant* fivecheck_variant() { + Variant* v = threecheck_variant()->init(); + v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 5+5 0 1"; + v->nnueAlias = "3check"; + return v; + } + // Crazyhouse + // Chess with piece drops + // https://en.wikipedia.org/wiki/Crazyhouse + Variant* crazyhouse_variant() { + Variant* v = chess_variant_base()->init(); + v->variantTemplate = "crazyhouse"; + v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 0 1"; + v->pieceDrops = true; + v->capturesToHand = true; + return v; + } + // Loop chess + // Variant of crazyhouse where promoted pawns are not demoted when captured + // https://en.wikipedia.org/wiki/Crazyhouse#Variations + Variant* loop_variant() { + Variant* v = crazyhouse_variant()->init(); + v->dropLoop = true; + v->nnueAlias = "crazyhouse"; + return v; + } + // Chessgi + // Variant of loop chess where pawns can be dropped to the first rank + // https://en.wikipedia.org/wiki/Crazyhouse#Variations + Variant* chessgi_variant() { + Variant* v = loop_variant()->init(); + v->firstRankPawnDrops = true; + v->nnueAlias = "crazyhouse"; + return v; + } + // Bughouse + // A four player variant where captured pieces are introduced on the other board + // https://en.wikipedia.org/wiki/Bughouse_chess + Variant* bughouse_variant() { + Variant* v = crazyhouse_variant()->init(); + v->variantTemplate = "bughouse"; + v->twoBoards = true; + v->capturesToHand = false; + v->stalemateValue = -VALUE_MATE; + return v; + } + // Koedem (Bughouse variant) + // http://schachclub-oetigheim.de/wp-content/uploads/2016/04/Koedem-rules.pdf + Variant* koedem_variant() { + Variant* v = bughouse_variant()->init(); + v->remove_piece(KING); + v->add_piece(COMMONER, 'k'); + v->castlingKingPiece[WHITE] = v->castlingKingPiece[BLACK] = COMMONER; + v->mustDrop = true; + v->mustDropType = COMMONER; + v->extinctionValue = -VALUE_MATE; + v->extinctionPieceTypes = piece_set(COMMONER); + v->extinctionOpponentPieceCount = 2; // own all kings/commoners + return v; + } + // Pocket Knight chess + // Each player has an additional knight in hand which can be dropped at any move + // https://www.chessvariants.com/other.dir/pocket.html + Variant* pocketknight_variant() { + Variant* v = chess_variant_base()->init(); + v->variantTemplate = "bughouse"; + v->pocketSize = 2; + v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[Nn] w KQkq - 0 1"; + v->pieceDrops = true; + v->capturesToHand = false; + return v; + } + // Placement/Pre-chess + // A shuffle variant where the players determine the placing of the back rank pieces + // https://www.chessvariants.com/link/placement-chess + Variant* placement_variant() { + Variant* v = chess_variant_base()->init(); + v->variantTemplate = "bughouse"; + v->startFen = "8/pppppppp/8/8/8/8/PPPPPPPP/8[KQRRBBNNkqrrbbnn] w - - 0 1"; + v->mustDrop = true; + v->pieceDrops = true; + v->capturesToHand = false; + v->dropRegion[WHITE] = Rank1BB; + v->dropRegion[BLACK] = Rank8BB; + v->dropOppositeColoredBishop = true; + v->castlingDroppedPiece = true; + v->nnueAlias = "nn-"; + return v; + } + // Sittuyin (Burmese chess) + // Regional chess variant from Myanmar, similar to Makruk but with a setup phase. + // https://en.wikipedia.org/wiki/Sittuyin + Variant* sittuyin_variant() { + Variant* v = makruk_variant()->init(); + v->variantTemplate = "bughouse"; + v->pieceToCharTable = "PN.R.F....SKpn.r.f....sk"; + v->startFen = "8/8/4pppp/pppp4/4PPPP/PPPP4/8/8[KFRRSSNNkfrrssnn] w - - 0 1"; + v->add_piece(MET, 'f'); + v->mustDrop = true; + v->pieceDrops = true; + v->capturesToHand = false; + v->dropRegion[WHITE] = Rank1BB | Rank2BB | Rank3BB; + v->dropRegion[BLACK] = Rank8BB | Rank7BB | Rank6BB; + v->sittuyinRookDrop = true; + v->sittuyinPromotion = true; + v->promotionRegion[WHITE] = make_bitboard(SQ_A8, SQ_B7, SQ_C6, SQ_D5, SQ_E5, SQ_F6, SQ_G7, SQ_H8); + v->promotionRegion[BLACK] = make_bitboard(SQ_A1, SQ_B2, SQ_C3, SQ_D4, SQ_E4, SQ_F3, SQ_G2, SQ_H1); + v->promotionLimit[FERS] = 1; + v->immobilityIllegal = false; + v->countingRule = ASEAN_COUNTING; + v->nMoveRule = 50; + return v; + } + // S-Chess (aka Seirawan-, or SHarper chess) + // 8x8 variant introducing the knighted pieces from capablanca chess + // via gating when a piece first moves from its initial square. + Variant* seirawan_variant() { + Variant* v = chess_variant_base()->init(); + v->variantTemplate = "seirawan"; + v->pieceToCharTable = "PNBRQ.E..........H...Kpnbrq.e..........h...k"; + v->add_piece(ARCHBISHOP, 'h'); + v->add_piece(CHANCELLOR, 'e'); + v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[HEhe] w KQBCDFGkqbcdfg - 0 1"; + v->gating = true; + v->seirawanGating = true; + v->promotionPieceTypes[WHITE] = piece_set(ARCHBISHOP) | CHANCELLOR | QUEEN | ROOK | BISHOP | KNIGHT; + v->promotionPieceTypes[BLACK] = piece_set(ARCHBISHOP) | CHANCELLOR | QUEEN | ROOK | BISHOP | KNIGHT; + return v; + } + // S-House + // A hybrid variant of S-Chess and Crazyhouse. + // Pieces in the pocket can either be gated or dropped. + Variant* shouse_variant() { + Variant* v = seirawan_variant()->init(); + v->variantTemplate = "crazyhouse"; + v->pieceDrops = true; + v->capturesToHand = true; + return v; + } + // Dragon Chess + // 8x8 variant invented by Miguel Illescas: + // https://www.edami.com/dragonchess/ + // Like regular chess, but with an extra piece, the dragon, which moves like + // an archbishop (i.e. bishop+knight). The dragon can be dropped at an empty + // square on the back rank instead of making a normal move. + Variant* dragon_variant() { + Variant *v = chess_variant_base()->init(); + v->variantTemplate = "bughouse"; + v->pieceToCharTable = "PNBRQ............D...Kpnbrq............d...k"; + v->add_piece(ARCHBISHOP, 'd'); + v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[Dd] w KQkq - 0 1"; + v->pieceDrops = true; + v->capturesToHand = false; + v->dropRegion[WHITE] = Rank1BB; + v->dropRegion[BLACK] = Rank8BB; + v->promotionPieceTypes[WHITE] = piece_set(ARCHBISHOP) | QUEEN | ROOK | BISHOP | KNIGHT; + v->promotionPieceTypes[BLACK] = piece_set(ARCHBISHOP) | QUEEN | ROOK | BISHOP | KNIGHT; + return v; + } + // Paradigm chess30 + // 8x8 variant with a bishop+horse hybrid piece replacing bishops + // https://www.chessvariants.com/rules/paradigm-chess30 + Variant* paradigm_variant() { + Variant *v = chess_variant_base()->init(); + v->remove_piece(BISHOP); + v->add_piece(CUSTOM_PIECE_1, 'b', "BnN"); + v->promotionPieceTypes[WHITE] = piece_set(QUEEN) | CUSTOM_PIECE_1 | ROOK | KNIGHT; + v->promotionPieceTypes[BLACK] = piece_set(QUEEN) | CUSTOM_PIECE_1 | ROOK | KNIGHT; + return v; + } + // Base used for most shogi variants + Variant* minishogi_variant_base() { + Variant* v = variant_base()->init(); + v->variantTemplate = "shogi"; + v->maxRank = RANK_5; + v->maxFile = FILE_E; + v->reset_pieces(); + v->add_piece(SHOGI_PAWN, 'p'); + v->add_piece(SILVER, 's'); + v->add_piece(GOLD, 'g'); + v->add_piece(BISHOP, 'b'); + v->add_piece(DRAGON_HORSE, 'h'); + v->add_piece(ROOK, 'r'); + v->add_piece(DRAGON, 'd'); + v->add_piece(KING, 'k'); + v->startFen = "rbsgk/4p/5/P4/KGSBR[-] w 0 1"; + v->pieceDrops = true; + v->capturesToHand = true; + v->promotionRegion[WHITE] = Rank5BB; + v->promotionRegion[BLACK] = Rank1BB; + v->doubleStep = false; + v->castling = false; + v->promotedPieceType[SHOGI_PAWN] = GOLD; + v->promotedPieceType[SILVER] = GOLD; + v->promotedPieceType[BISHOP] = DRAGON_HORSE; + v->promotedPieceType[ROOK] = DRAGON; + v->dropNoDoubled = SHOGI_PAWN; + v->immobilityIllegal = true; + v->shogiPawnDropMateIllegal = true; + v->stalemateValue = -VALUE_MATE; + v->nFoldRule = 4; + v->nMoveRule = 0; + v->perpetualCheckIllegal = true; + return v; + } + // Minishogi + // 5x5 variant of shogi + // https://en.wikipedia.org/wiki/Minishogi + Variant* minishogi_variant() { + Variant* v = minishogi_variant_base()->init(); + v->pieceToCharTable = "P.BR.S...G.+.++.+Kp.br.s...g.+.++.+k"; + v->pocketSize = 5; + v->nFoldValue = -VALUE_MATE; + v->nFoldValueAbsolute = true; + v->nnueAlias = "minishogi"; + return v; + } + // Kyoto shogi + // 5x5 variant of shogi with pieces alternating between promotion and demotion + // https://en.wikipedia.org/wiki/Kyoto_shogi + Variant* kyotoshogi_variant() { + Variant* v = minishogi_variant_base()->init(); + v->add_piece(LANCE, 'l'); + v->add_piece(SHOGI_KNIGHT, 'n'); + v->startFen = "p+nks+l/5/5/5/+LSK+NP[-] w 0 1"; + v->promotionRegion[WHITE] = AllSquares; + v->promotionRegion[BLACK] = AllSquares; + v->mandatoryPiecePromotion = true; + v->pieceDemotion = true; + v->dropPromoted = true; + v->promotedPieceType[LANCE] = GOLD; + v->promotedPieceType[SILVER] = BISHOP; + v->promotedPieceType[SHOGI_KNIGHT] = GOLD; + v->promotedPieceType[SHOGI_PAWN] = ROOK; + v->promotedPieceType[GOLD] = NO_PIECE_TYPE; + v->promotedPieceType[BISHOP] = NO_PIECE_TYPE; + v->promotedPieceType[ROOK] = NO_PIECE_TYPE; + v->immobilityIllegal = false; + v->shogiPawnDropMateIllegal = false; + v->dropNoDoubled = NO_PIECE_TYPE; + return v; + } + // Micro shogi + // 4x5 shogi variant where pieces promoted and demote when capturing + // https://en.wikipedia.org/wiki/Micro_shogi + Variant* microshogi_variant() { + Variant* v = kyotoshogi_variant()->init(); + v->maxFile = FILE_D; + v->startFen = "kb+r+l/p3/4/3P/+L+RBK[-] w 0 1"; + v->promotionRegion[WHITE] = AllSquares; + v->promotionRegion[BLACK] = AllSquares; + v->piecePromotionOnCapture = true; + v->promotedPieceType[LANCE] = SILVER; + v->promotedPieceType[BISHOP] = GOLD; + v->promotedPieceType[ROOK] = GOLD; + v->promotedPieceType[SHOGI_PAWN] = SHOGI_KNIGHT; + v->promotedPieceType[SILVER] = NO_PIECE_TYPE; + v->promotedPieceType[GOLD] = NO_PIECE_TYPE; + v->promotedPieceType[SHOGI_KNIGHT] = NO_PIECE_TYPE; + return v; + } + // Dobutsu + // Educational shogi variant on a 3x4 board + // https://en.wikipedia.org/wiki/D%C5%8Dbutsu_sh%C5%8Dgi + Variant* dobutsu_variant() { + Variant* v = minishogi_variant_base()->init(); + v->pieceToCharTable = "C....E...G.+.....Lc....e...g.+.....l"; + v->pocketSize = 3; + v->maxRank = RANK_4; + v->maxFile = FILE_C; + v->reset_pieces(); + v->add_piece(SHOGI_PAWN, 'c'); + v->add_piece(GOLD, 'h'); + v->add_piece(FERS, 'e'); + v->add_piece(WAZIR, 'g'); + v->add_piece(COMMONER, 'l'); + v->startFen = "gle/1c1/1C1/ELG[-] w 0 1"; + v->promotionRegion[WHITE] = Rank4BB; + v->promotionRegion[BLACK] = Rank1BB; + v->mandatoryPiecePromotion = true; + v->immobilityIllegal = false; + v->shogiPawnDropMateIllegal = false; + v->extinctionValue = -VALUE_MATE; + v->extinctionPieceTypes = piece_set(COMMONER); + v->flagPiece[WHITE] = v->flagPiece[BLACK] = COMMONER; + v->flagRegion[WHITE] = Rank4BB; + v->flagRegion[BLACK] = Rank1BB; + v->flagPieceSafe = true; + v->dropNoDoubled = NO_PIECE_TYPE; + v->nFoldValue = VALUE_DRAW; + v->perpetualCheckIllegal = false; + return v; + } + // Goro goro shogi + // https://en.wikipedia.org/wiki/D%C5%8Dbutsu_sh%C5%8Dgi#Variation + Variant* gorogoroshogi_variant() { + Variant* v = minishogi_variant_base()->init(); + v->pieceToCharTable = "P....S...G.+....+Kp....s...g.+....+k"; + v->pocketSize = 3; + v->maxRank = RANK_6; + v->maxFile = FILE_E; + v->startFen = "sgkgs/5/1ppp1/1PPP1/5/SGKGS[-] w 0 1"; + v->promotionRegion[WHITE] = Rank5BB | Rank6BB; + v->promotionRegion[BLACK] = Rank2BB | Rank1BB; + return v; + } + // Judkins shogi + // https://en.wikipedia.org/wiki/Judkins_shogi + Variant* judkinsshogi_variant() { + Variant* v = minishogi_variant_base()->init(); + v->pieceToCharTable = "PNBR.S...G.++++.+Kpnbr.s...g.++++.+k"; + v->maxRank = RANK_6; + v->maxFile = FILE_F; + v->add_piece(SHOGI_KNIGHT, 'n'); + v->startFen = "rbnsgk/5p/6/6/P5/KGSNBR[-] w 0 1"; + v->promotionRegion[WHITE] = Rank5BB | Rank6BB; + v->promotionRegion[BLACK] = Rank2BB | Rank1BB; + v->promotedPieceType[SHOGI_KNIGHT] = GOLD; + return v; + } + // Tori shogi + // https://en.wikipedia.org/wiki/Tori_shogi + Variant* torishogi_variant() { + Variant* v = variant_base()->init(); + v->variantTemplate = "shogi"; + v->pieceToCharTable = "S.....FLR.C+.....+.PKs.....flr.c+.....+.pk"; + v->maxRank = RANK_7; + v->maxFile = FILE_G; + v->reset_pieces(); + v->add_piece(SHOGI_PAWN, 's'); + v->add_piece(KING, 'k'); + v->add_piece(CUSTOM_PIECE_1, 'f', "FsfW"); // falcon + v->add_piece(CUSTOM_PIECE_2, 'c', "FvW"); // crane + v->add_piece(CUSTOM_PIECE_3, 'l', "fRrbBlbF"); // left quail + v->add_piece(CUSTOM_PIECE_4, 'r', "fRlbBrbF"); // right quail + v->add_piece(CUSTOM_PIECE_5, 'p', "bFfD"); // pheasant + v->add_piece(CUSTOM_PIECE_6, 'g', "fAbD"); // goose + v->add_piece(CUSTOM_PIECE_7, 'e', "KbRfBbF2"); // eagle + v->startFen = "rpckcpl/3f3/sssssss/2s1S2/SSSSSSS/3F3/LPCKCPR[-] w 0 1"; + v->pieceDrops = true; + v->capturesToHand = true; + v->promotionRegion[WHITE] = Rank6BB | Rank7BB; + v->promotionRegion[BLACK] = Rank2BB | Rank1BB; + v->doubleStep = false; + v->castling = false; + v->promotedPieceType[SHOGI_PAWN] = CUSTOM_PIECE_6; // swallow promotes to goose + v->promotedPieceType[CUSTOM_PIECE_1] = CUSTOM_PIECE_7; // falcon promotes to eagle + v->mandatoryPiecePromotion = true; + v->dropNoDoubled = SHOGI_PAWN; + v->dropNoDoubledCount = 2; + v->immobilityIllegal = true; + v->shogiPawnDropMateIllegal = true; + v->stalemateValue = -VALUE_MATE; + v->nFoldValue = VALUE_MATE; + v->nFoldRule = 4; + v->nMoveRule = 0; + v->perpetualCheckIllegal = true; + return v; + } + // EuroShogi + // https://en.wikipedia.org/wiki/EuroShogi + Variant* euroshogi_variant() { + Variant* v = minishogi_variant_base()->init(); + v->pieceToCharTable = "PNBR.....G.++++Kpnbr.....g.++++k"; + v->maxRank = RANK_8; + v->maxFile = FILE_H; + v->add_piece(CUSTOM_PIECE_1, 'n', "fNsW"); + v->startFen = "1nbgkgn1/1r4b1/pppppppp/8/8/PPPPPPPP/1B4R1/1NGKGBN1[-] w 0 1"; + v->promotionRegion[WHITE] = Rank6BB | Rank7BB | Rank8BB; + v->promotionRegion[BLACK] = Rank3BB | Rank2BB | Rank1BB; + v->promotedPieceType[CUSTOM_PIECE_1] = GOLD; + v->mandatoryPiecePromotion = true; + return v; + } + // Los Alamos chess + // https://en.wikipedia.org/wiki/Los_Alamos_chess + Variant* losalamos_variant() { + Variant* v = chess_variant_base()->init(); + v->pieceToCharTable = "PN.RQ................Kpn.rq................k"; + v->maxRank = RANK_6; + v->maxFile = FILE_F; + v->remove_piece(BISHOP); + v->startFen = "rnqknr/pppppp/6/6/PPPPPP/RNQKNR w - - 0 1"; + v->promotionRegion[WHITE] = Rank6BB; + v->promotionRegion[BLACK] = Rank1BB; + v->promotionPieceTypes[WHITE] = piece_set(QUEEN) | ROOK | KNIGHT; + v->promotionPieceTypes[BLACK] = piece_set(QUEEN) | ROOK | KNIGHT; + v->doubleStep = false; + v->castling = false; + return v; + } + // Gardner's minichess + // https://en.wikipedia.org/wiki/Minichess#5%C3%975_chess + Variant* gardner_variant() { + Variant* v = chess_variant_base()->init(); + v->maxRank = RANK_5; + v->maxFile = FILE_E; + v->startFen = "rnbqk/ppppp/5/PPPPP/RNBQK w - - 0 1"; + v->promotionRegion[WHITE] = Rank5BB; + v->promotionRegion[BLACK] = Rank1BB; + v->doubleStep = false; + v->castling = false; + return v; + } + // Almost chess + // Queens are replaced by chancellors + // https://en.wikipedia.org/wiki/Almost_chess + Variant* almost_variant() { + Variant* v = chess_variant_base()->init(); + v->pieceToCharTable = "PNBR............CKpnbr............ck"; + v->remove_piece(QUEEN); + v->add_piece(CHANCELLOR, 'c'); + v->startFen = "rnbckbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBCKBNR w KQkq - 0 1"; + v->promotionPieceTypes[WHITE] = piece_set(CHANCELLOR) | ROOK | BISHOP | KNIGHT; + v->promotionPieceTypes[BLACK] = piece_set(CHANCELLOR) | ROOK | BISHOP | KNIGHT; + return v; + } + // Sort of almost chess + // One queen is replaced by a chancellor + // https://en.wikipedia.org/wiki/Almost_chess#Sort_of_almost_chess + Variant* sortofalmost_variant() { + Variant* v = chess_variant(); + v->pieceToCharTable = "PNBRQ...........CKpnbrq...........ck"; + v->add_piece(CHANCELLOR, 'c'); + v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBCKBNR w KQkq - 0 1"; + v->promotionPieceTypes[WHITE] = piece_set(CHANCELLOR) | ROOK | BISHOP | KNIGHT; + v->promotionPieceTypes[BLACK] = piece_set(QUEEN) | ROOK | BISHOP | KNIGHT; + return v; + } + // Chigorin chess + // Asymmetric variant with knight vs. bishop movements + // https://www.chessvariants.com/diffsetup.dir/chigorin.html + Variant* chigorin_variant() { + Variant* v = chess_variant_base()->init(); + v->pieceToCharTable = "PNBR............CKpnbrq............k"; + v->add_piece(CHANCELLOR, 'c'); + v->startFen = "rbbqkbbr/pppppppp/8/8/8/8/PPPPPPPP/RNNCKNNR w KQkq - 0 1"; + v->promotionPieceTypes[WHITE] = piece_set(CHANCELLOR) | ROOK | KNIGHT; + v->promotionPieceTypes[BLACK] = piece_set(QUEEN) | ROOK | BISHOP; + return v; + } + // Perfect chess + // https://www.chessvariants.com/diffmove.dir/perfectchess.html + Variant* perfect_variant() { + Variant* v = chess_variant_base()->init(); + v->add_piece(CHANCELLOR, 'c'); + v->add_piece(ARCHBISHOP, 'm'); + v->add_piece(AMAZON, 'g'); + v->startFen = "cmqgkbnr/pppppppp/8/8/8/8/PPPPPPPP/CMQGKBNR w KQkq - 0 1"; + v->promotionPieceTypes[WHITE] = piece_set(AMAZON) | CHANCELLOR | ARCHBISHOP | QUEEN | ROOK | BISHOP | KNIGHT; + v->promotionPieceTypes[BLACK] = piece_set(AMAZON) | CHANCELLOR | ARCHBISHOP | QUEEN | ROOK | BISHOP | KNIGHT; + v->castlingRookPieces[WHITE] = v->castlingRookPieces[BLACK] |= piece_set(CHANCELLOR); + return v; + } + // Spartan chess + // https://www.chessvariants.com/rules/spartan-chess + Variant* spartan_variant() { + Variant* v = threekings_variant()->init(); + v->add_piece(DRAGON, 'g'); + v->add_piece(ARCHBISHOP, 'w'); + v->add_piece(CUSTOM_PIECE_1, 'h', "fmFfcWimA"); + v->add_piece(CUSTOM_PIECE_2, 'l', "FAsmW"); + v->add_piece(CUSTOM_PIECE_3, 'c', "WD"); + v->startFen = "lgkcckwl/hhhhhhhh/8/8/8/8/PPPPPPPP/RNBQKBNR w KQ - 0 1"; + v->mainPromotionPawnType[BLACK] = CUSTOM_PIECE_1; + v->promotionPawnTypes[BLACK] = piece_set(CUSTOM_PIECE_1); + v->nMoveRuleTypes[BLACK] = piece_set(CUSTOM_PIECE_1); + v->promotionPieceTypes[BLACK] = piece_set(COMMONER) | DRAGON | ARCHBISHOP | CUSTOM_PIECE_2 | CUSTOM_PIECE_3; + v->promotionLimit[COMMONER] = 2; + v->enPassantRegion[WHITE] = 0; + v->enPassantRegion[BLACK] = 0; + v->extinctionPieceCount = 0; + v->extinctionPseudoRoyal = true; + v->dupleCheck = true; + return v; + } + // Shatar (Mongolian chess) + // https://en.wikipedia.org/wiki/Shatar + Variant* shatar_variant() { + Variant* v = chess_variant_base()->init(); + v->pieceToCharTable = "PNBR..........J......Kpnbr..........j......k"; + v->remove_piece(QUEEN); + v->add_piece(BERS, 'j'); + v->startFen = "rnbjkbnr/ppp1pppp/8/3p4/3P4/8/PPP1PPPP/RNBJKBNR w - - 0 1"; + v->promotionPieceTypes[WHITE] = piece_set(BERS); + v->promotionPieceTypes[BLACK] = piece_set(BERS); + v->doubleStep = false; + v->castling = false; + v->extinctionValue = VALUE_DRAW; // Robado + v->extinctionPieceTypes = piece_set(ALL_PIECES); + v->extinctionPieceCount = 1; + v->shatarMateRule = true; + return v; + } + // Coregal chess + // Queens are also subject to check and checkmate + // https://www.chessvariants.com/winning.dir/coregal.html + Variant* coregal_variant() { + Variant* v = chess_variant_base()->init(); + v->extinctionValue = -VALUE_MATE; + v->extinctionPieceTypes = piece_set(QUEEN); + v->extinctionPseudoRoyal = true; + v->extinctionPieceCount = 64; // no matter how many queens, all are royal + return v; + } + // Clobber + // https://en.wikipedia.org/wiki/Clobber + Variant* clobber_variant() { + Variant* v = chess_variant_base()->init(); + v->pieceToCharTable = "P.................p................."; + v->maxRank = RANK_6; + v->maxFile = FILE_E; + v->reset_pieces(); + v->add_piece(CLOBBER_PIECE, 'p'); + v->startFen = "PpPpP/pPpPp/PpPpP/pPpPp/PpPpP/pPpPp w 0 1"; + v->doubleStep = false; + v->castling = false; + v->stalemateValue = -VALUE_MATE; + v->immobilityIllegal = false; + return v; + } + // Breakthrough + // https://en.wikipedia.org/wiki/Breakthrough_(board_game) + Variant* breakthrough_variant() { + Variant* v = chess_variant_base()->init(); + v->pieceToCharTable = "P.................p................."; + v->reset_pieces(); + v->add_piece(BREAKTHROUGH_PIECE, 'p'); + v->startFen = "pppppppp/pppppppp/8/8/8/8/PPPPPPPP/PPPPPPPP w 0 1"; + v->doubleStep = false; + v->castling = false; + v->stalemateValue = -VALUE_MATE; + v->flagPiece[WHITE] = v->flagPiece[BLACK] = BREAKTHROUGH_PIECE; + v->flagRegion[WHITE] = Rank8BB; + v->flagRegion[BLACK] = Rank1BB; + return v; + } + // Ataxx + // https://en.wikipedia.org/wiki/Ataxx + Variant* ataxx_variant() { + Variant* v = chess_variant_base()->init(); + v->pieceToCharTable = "P.................p................."; + v->maxRank = RANK_7; + v->maxFile = FILE_G; + v->reset_pieces(); + v->add_piece(CUSTOM_PIECE_1, 'p', "mDmNmA"); + v->startFen = "P5p/7/7/7/7/7/p5P w 0 1"; + v->pieceDrops = true; + v->doubleStep = false; + v->castling = false; + v->immobilityIllegal = false; + v->stalemateValue = -VALUE_MATE; + v->stalematePieceCount = true; + v->passOnStalemate[WHITE] = true; + v->passOnStalemate[BLACK] = true; + v->enclosingDrop = ATAXX; + v->flipEnclosedPieces = ATAXX; + v->materialCounting = UNWEIGHTED_MATERIAL; + v->adjudicateFullBoard = true; + v->nMoveRule = 0; + v->freeDrops = true; + return v; + } + // Flipersi + // https://en.wikipedia.org/wiki/Reversi + Variant* flipersi_variant() { + Variant* v = chess_variant_base()->init(); + v->pieceToCharTable = "P.................p................."; + v->maxRank = RANK_8; + v->maxFile = FILE_H; + v->reset_pieces(); + v->add_piece(IMMOBILE_PIECE, 'p'); + v->startFen = "8/8/8/8/8/8/8/8[PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPpppppppppppppppppppppppppppppppp] w 0 1"; + v->pieceDrops = true; + v->doubleStep = false; + v->castling = false; + v->immobilityIllegal = false; + v->stalemateValue = -VALUE_MATE; + v->stalematePieceCount = true; + v->passOnStalemate[WHITE] = false; + v->passOnStalemate[BLACK] = false; + v->enclosingDrop = REVERSI; + v->enclosingDropStart = make_bitboard(SQ_D4, SQ_E4, SQ_D5, SQ_E5); + v->flipEnclosedPieces = REVERSI; + v->materialCounting = UNWEIGHTED_MATERIAL; + v->adjudicateFullBoard = true; + return v; + } + // Flipello + // https://en.wikipedia.org/wiki/Reversi#Othello + Variant* flipello_variant() { + Variant* v = flipersi_variant()->init(); + v->startFen = "8/8/8/3pP3/3Pp3/8/8/8[PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPpppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp] w 0 1"; + v->passOnStalemate[WHITE] = true; + v->passOnStalemate[BLACK] = true; + return v; + } + // Minixiangqi + // http://mlwi.magix.net/bg/minixiangqi.htm + Variant* minixiangqi_variant() { + Variant* v = chess_variant_base()->init(); + v->variantTemplate = "xiangqi"; + v->pieceToCharTable = "PN.R.....K.C.pn.r.....k.c."; + v->maxRank = RANK_7; + v->maxFile = FILE_G; + v->reset_pieces(); + v->add_piece(ROOK, 'r'); + v->add_piece(HORSE, 'n', 'h'); + v->add_piece(KING, 'k'); + v->add_piece(CANNON, 'c'); + v->add_piece(SOLDIER, 'p'); + v->startFen = "rcnkncr/p1ppp1p/7/7/7/P1PPP1P/RCNKNCR w - - 0 1"; + v->mobilityRegion[WHITE][KING] = (Rank1BB | Rank2BB | Rank3BB) & (FileCBB | FileDBB | FileEBB); + v->mobilityRegion[BLACK][KING] = (Rank5BB | Rank6BB | Rank7BB) & (FileCBB | FileDBB | FileEBB); + v->kingType = WAZIR; + v->doubleStep = false; + v->castling = false; + v->stalemateValue = -VALUE_MATE; + //v->nFoldValue = VALUE_MATE; + v->perpetualCheckIllegal = true; + v->flyingGeneral = true; + return v; + } +#ifdef LARGEBOARDS + // Shogi (Japanese chess) + // https://en.wikipedia.org/wiki/Shogi + Variant* shogi_variant() { + Variant* v = minishogi_variant_base()->init(); + v->maxRank = RANK_9; + v->maxFile = FILE_I; + v->add_piece(LANCE, 'l'); + v->add_piece(SHOGI_KNIGHT, 'n'); + v->startFen = "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL[-] w 0 1"; + v->promotionRegion[WHITE] = Rank7BB | Rank8BB | Rank9BB; + v->promotionRegion[BLACK] = Rank3BB | Rank2BB | Rank1BB; + v->promotedPieceType[LANCE] = GOLD; + v->promotedPieceType[SHOGI_KNIGHT] = GOLD; + return v; + } + // Check-Shogi + // Shogi variant with check counting enabled + Variant* checkshogi_variant() { + Variant* v = shogi_variant()->init(); + v->checkCounting = true; + return v; + } + // Sho-Shogi + // 16-th century shogi variant with one additional piece and no drops + // https://en.wikipedia.org/wiki/Sho_shogi + Variant* shoshogi_variant() { + Variant* v = shogi_variant()->init(); + v->pieceToCharTable = "PNBRLSE..G.+.++.++Kpnbrlse..g.+.++.++k"; + v->remove_piece(KING); + v->add_piece(COMMONER, 'k'); + v->add_piece(CUSTOM_PIECE_1, 'e', "FsfW"); // drunk elephant + v->startFen = "lnsgkgsnl/1r2e2b1/ppppppppp/9/9/9/PPPPPPPPP/1B2E2R1/LNSGKGSNL w 0 1"; + v->capturesToHand = false; + v->pieceDrops = false; + v->promotedPieceType[CUSTOM_PIECE_1] = COMMONER; + v->castlingKingPiece[WHITE] = v->castlingKingPiece[BLACK] = COMMONER; + v->extinctionValue = -VALUE_MATE; + v->extinctionPieceTypes = piece_set(COMMONER); + v->extinctionPseudoRoyal = true; + v->extinctionPieceCount = 0; + return v; + } + // Yari shogi + // https://en.wikipedia.org/wiki/Yari_shogi + Variant* yarishogi_variant() { + Variant* v = variant_base()->init(); + v->variantTemplate = "shogi"; + v->pieceToCharTable = "PNBR.......++++Kpnbr.......++++k"; + v->maxRank = RANK_9; + v->maxFile = FILE_G; + v->reset_pieces(); + v->add_piece(KING, 'k'); + v->add_piece(SHOGI_PAWN, 'p'); + v->add_piece(ROOK, 'l'); + v->add_piece(CUSTOM_PIECE_1, 'n', "fRffN"); // Yari knight + v->add_piece(CUSTOM_PIECE_2, 'b', "fFfR"); // Yari bishop + v->add_piece(CUSTOM_PIECE_3, 'r', "frlR"); // Yari rook + v->add_piece(CUSTOM_PIECE_4, 'g', "WfFbR"); // Yari gold + v->add_piece(CUSTOM_PIECE_5, 's', "fKbR"); // Yari silver + v->startFen = "rnnkbbr/7/ppppppp/7/7/7/PPPPPPP/7/RBBKNNR[-] w 0 1"; + v->promotionRegion[WHITE] = Rank7BB | Rank8BB | Rank9BB; + v->promotionRegion[BLACK] = Rank3BB | Rank2BB | Rank1BB; + v->promotedPieceType[SHOGI_PAWN] = CUSTOM_PIECE_5; + v->promotedPieceType[CUSTOM_PIECE_1] = CUSTOM_PIECE_4; + v->promotedPieceType[CUSTOM_PIECE_2] = CUSTOM_PIECE_4; + v->promotedPieceType[CUSTOM_PIECE_3] = ROOK; + v->pieceDrops = true; + v->capturesToHand = true; + v->doubleStep = false; + v->castling = false; + v->dropNoDoubled = SHOGI_PAWN; + v->immobilityIllegal = true; + v->shogiPawnDropMateIllegal = false; + v->stalemateValue = -VALUE_MATE; + v->nFoldRule = 3; + v->nMoveRule = 0; + v->perpetualCheckIllegal = true; + return v; + } + // Okisaki shogi + // https://en.wikipedia.org/wiki/Okisaki_shogi + Variant* okisakishogi_variant() { + Variant* v = minishogi_variant_base()->init(); + v->maxRank = RANK_10; + v->maxFile = FILE_J; + v->add_piece(CUSTOM_PIECE_1, 'l', "vR"); // Vertical slider + v->add_piece(KNIGHT, 'n'); + v->add_piece(QUEEN, 'q'); + v->startFen = "lnsgkqgsnl/1r6b1/pppppppppp/10/10/10/10/PPPPPPPPPP/1B6R1/LNSGQKGSNL[-] w 0 1"; + v->promotionRegion[WHITE] = Rank8BB | Rank9BB | Rank10BB; + v->promotionRegion[BLACK] = Rank3BB | Rank2BB | Rank1BB; + v->promotedPieceType[CUSTOM_PIECE_1] = GOLD; + v->promotedPieceType[KNIGHT] = GOLD; + return v; + } + // Capablanca chess + // https://en.wikipedia.org/wiki/Capablanca_chess + Variant* capablanca_variant() { + Variant* v = chess_variant_base()->init(); + v->pieceToCharTable = "PNBRQ..AC............Kpnbrq..ac............k"; + v->maxRank = RANK_8; + v->maxFile = FILE_J; + v->castlingKingsideFile = FILE_I; + v->castlingQueensideFile = FILE_C; + v->add_piece(ARCHBISHOP, 'a'); + v->add_piece(CHANCELLOR, 'c'); + v->startFen = "rnabqkbcnr/pppppppppp/10/10/10/10/PPPPPPPPPP/RNABQKBCNR w KQkq - 0 1"; + v->promotionPieceTypes[WHITE] = piece_set(ARCHBISHOP) | CHANCELLOR | QUEEN | ROOK | BISHOP | KNIGHT; + v->promotionPieceTypes[BLACK] = piece_set(ARCHBISHOP) | CHANCELLOR | QUEEN | ROOK | BISHOP | KNIGHT; + return v; + } + // Capahouse + // Capablanca chess with crazyhouse-style piece drops + // https://www.pychess.org/variant/capahouse + Variant* capahouse_variant() { + Variant* v = capablanca_variant()->init(); + v->startFen = "rnabqkbcnr/pppppppppp/10/10/10/10/PPPPPPPPPP/RNABQKBCNR[] w KQkq - 0 1"; + v->pieceDrops = true; + v->capturesToHand = true; + return v; + } + // Capablanca random chess (CRC) + // Shuffle variant of capablanca chess + // https://en.wikipedia.org/wiki/Capablanca_random_chess + Variant* caparandom_variant() { + Variant* v = capablanca_variant()->init(); + v->chess960 = true; + v->nnueAlias = "capablanca"; + return v; + } + // Gothic chess + // Capablanca chess with changed starting position + // https://www.chessvariants.com/large.dir/gothicchess.html + Variant* gothic_variant() { + Variant* v = capablanca_variant()->init(); + v->startFen = "rnbqckabnr/pppppppppp/10/10/10/10/PPPPPPPPPP/RNBQCKABNR w KQkq - 0 1"; + v->nnueAlias = "capablanca"; + return v; + } + // Janus chess + // 10x8 variant with two archbishops per side + // https://en.wikipedia.org/wiki/Janus_Chess + Variant* janus_variant() { + Variant* v = chess_variant_base()->init(); + v->pieceToCharTable = "PNBRQ............J...Kpnbrq............j...k"; + v->maxRank = RANK_8; + v->maxFile = FILE_J; + v->castlingKingsideFile = FILE_I; + v->castlingQueensideFile = FILE_B; + v->add_piece(ARCHBISHOP, 'j'); + v->startFen = "rjnbkqbnjr/pppppppppp/10/10/10/10/PPPPPPPPPP/RJNBKQBNJR w KQkq - 0 1"; + v->promotionPieceTypes[WHITE] = piece_set(ARCHBISHOP) | QUEEN | ROOK | BISHOP | KNIGHT; + v->promotionPieceTypes[BLACK] = piece_set(ARCHBISHOP) | QUEEN | ROOK | BISHOP | KNIGHT; + return v; + } + // Modern chess + // 9x9 variant with archbishops + // https://en.wikipedia.org/wiki/Modern_chess + Variant* modern_variant() { + Variant* v = chess_variant_base()->init(); + v->pieceToCharTable = "PNBRQ..M.............Kpnbrq..m.............k"; + v->maxRank = RANK_9; + v->maxFile = FILE_I; + v->promotionRegion[WHITE] = Rank9BB; + v->promotionRegion[BLACK] = Rank1BB; + v->doubleStepRegion[WHITE] = Rank2BB; + v->doubleStepRegion[BLACK] = Rank8BB; + v->castlingKingsideFile = FILE_G; + v->castlingQueensideFile = FILE_C; + v->add_piece(ARCHBISHOP, 'm'); + v->startFen = "rnbqkmbnr/ppppppppp/9/9/9/9/9/PPPPPPPPP/RNBMKQBNR w KQkq - 0 1"; + v->promotionPieceTypes[WHITE] = piece_set(ARCHBISHOP) | QUEEN | ROOK | BISHOP | KNIGHT; + v->promotionPieceTypes[BLACK] = piece_set(ARCHBISHOP) | QUEEN | ROOK | BISHOP | KNIGHT; + return v; + } + // Chancellor chess + // 9x9 variant with chancellors + // https://en.wikipedia.org/wiki/Chancellor_chess + Variant* chancellor_variant() { + Variant* v = chess_variant_base()->init(); + v->pieceToCharTable = "PNBRQ...........CKpnbrq...........ck"; + v->maxRank = RANK_9; + v->maxFile = FILE_I; + v->promotionRegion[WHITE] = Rank9BB; + v->promotionRegion[BLACK] = Rank1BB; + v->doubleStepRegion[WHITE] = Rank2BB; + v->doubleStepRegion[BLACK] = Rank8BB; + v->castlingKingsideFile = FILE_G; + v->castlingQueensideFile = FILE_C; + v->add_piece(CHANCELLOR, 'c'); + v->startFen = "rnbqkcnbr/ppppppppp/9/9/9/9/9/PPPPPPPPP/RNBQKCNBR w KQkq - 0 1"; + v->promotionPieceTypes[WHITE] = piece_set(CHANCELLOR) | QUEEN | ROOK | BISHOP | KNIGHT; + v->promotionPieceTypes[BLACK] = piece_set(CHANCELLOR) | QUEEN | ROOK | BISHOP | KNIGHT; + return v; + } + // Embassy chess + // Capablanca chess with different starting position + // https://en.wikipedia.org/wiki/Embassy_chess + Variant* embassy_variant() { + Variant* v = capablanca_variant()->init(); + v->castlingKingsideFile = FILE_H; + v->castlingQueensideFile = FILE_B; + v->startFen = "rnbqkcabnr/pppppppppp/10/10/10/10/PPPPPPPPPP/RNBQKCABNR w KQkq - 0 1"; + v->nnueAlias = "capablanca"; + return v; + } + // Centaur chess (aka Royal Court) + // 10x8 variant with a knight+commoner compound + // https://www.chessvariants.com/large.dir/contest/royalcourt.html + Variant* centaur_variant() { + Variant* v = chess_variant_base()->init(); + v->pieceToCharTable = "PNBRQ...............CKpnbrq...............ck"; + v->maxRank = RANK_8; + v->maxFile = FILE_J; + v->castlingKingsideFile = FILE_I; + v->castlingQueensideFile = FILE_C; + v->add_piece(CENTAUR, 'c'); + v->startFen = "rcnbqkbncr/pppppppppp/10/10/10/10/PPPPPPPPPP/RCNBQKBNCR w KQkq - 0 1"; + v->promotionPieceTypes[WHITE] = piece_set(CENTAUR) | QUEEN | ROOK | BISHOP | KNIGHT; + v->promotionPieceTypes[BLACK] = piece_set(CENTAUR) | QUEEN | ROOK | BISHOP | KNIGHT; + return v; + } + // Gustav III chess + // 10x8 variant with an amazon piece and wall squares + // https://www.chessvariants.com/play/gustav-iiis-chess + Variant* gustav3_variant() { + Variant* v = chess_variant_base()->init(); + v->pieceToCharTable = "PNBRQ.............AKpnbrq.............ak"; + v->maxRank = RANK_8; + v->maxFile = FILE_J; + v->castlingKingsideFile = FILE_H; + v->castlingQueensideFile = FILE_D; + v->add_piece(AMAZON, 'a'); + v->startFen = "arnbqkbnra/*pppppppp*/*8*/*8*/*8*/*8*/*PPPPPPPP*/ARNBQKBNRA w KQkq - 0 1"; + v->promotionPieceTypes[WHITE] = piece_set(AMAZON) | QUEEN | ROOK | BISHOP | KNIGHT; + v->promotionPieceTypes[BLACK] = piece_set(AMAZON) | QUEEN | ROOK | BISHOP | KNIGHT; + return v; + } + // Jeson mor + // Mongolian chess variant with knights only and a king of the hill like goal + // https://en.wikipedia.org/wiki/Jeson_Mor + Variant* jesonmor_variant() { + Variant* v = chess_variant_base()->init(); + v->maxRank = RANK_9; + v->maxFile = FILE_I; + v->reset_pieces(); + v->add_piece(KNIGHT, 'n'); + v->startFen = "nnnnnnnnn/9/9/9/9/9/9/9/NNNNNNNNN w - - 0 1"; + v->doubleStep = false; + v->castling = false; + v->stalemateValue = -VALUE_MATE; + v->flagPiece[WHITE] = v->flagPiece[BLACK] = KNIGHT; + v->flagRegion[WHITE] = make_bitboard(SQ_E5); + v->flagRegion[BLACK] = make_bitboard(SQ_E5); + v->flagMove = true; + // we could remove the useless extra move when the flag piece can not be captured + // v->flagPieceSafe = true; + return v; + } + // Courier chess + // Medieval variant of Shatranj on a 12x8 board + // https://en.wikipedia.org/wiki/Courier_chess + Variant* courier_variant() { + Variant* v = chess_variant_base()->init(); + v->maxRank = RANK_8; + v->maxFile = FILE_L; + v->remove_piece(QUEEN); + v->add_piece(ALFIL, 'e'); + v->add_piece(FERS, 'f'); + v->add_piece(COMMONER, 'm'); + v->add_piece(WAZIR, 'w'); + v->startFen = "rnebmk1wbenr/1ppppp1pppp1/6f5/p5p4p/P5P4P/6F5/1PPPPP1PPPP1/RNEBMK1WBENR w - - 0 1"; + v->promotionPieceTypes[WHITE] = piece_set(FERS); + v->promotionPieceTypes[BLACK] = piece_set(FERS); + v->doubleStep = false; + v->castling = false; + v->extinctionValue = -VALUE_MATE; + v->extinctionClaim = true; + v->extinctionPieceTypes = piece_set(ALL_PIECES); + v->extinctionPieceCount = 1; + v->extinctionOpponentPieceCount = 2; + v->stalemateValue = -VALUE_MATE; + return v; + } + // Grand chess + // 10x10 variant with chancellors and archbishops + // https://en.wikipedia.org/wiki/Grand_chess + Variant* grand_variant() { + Variant* v = chess_variant_base()->init(); + v->variantTemplate = "grand"; + v->pieceToCharTable = "PNBRQ..AC............Kpnbrq..ac............k"; + v->maxRank = RANK_10; + v->maxFile = FILE_J; + v->add_piece(ARCHBISHOP, 'a'); + v->add_piece(CHANCELLOR, 'c'); + v->startFen = "r8r/1nbqkcabn1/pppppppppp/10/10/10/10/PPPPPPPPPP/1NBQKCABN1/R8R w - - 0 1"; + v->promotionPieceTypes[WHITE] = piece_set(ARCHBISHOP) | CHANCELLOR | QUEEN | ROOK | BISHOP | KNIGHT; + v->promotionPieceTypes[BLACK] = piece_set(ARCHBISHOP) | CHANCELLOR | QUEEN | ROOK | BISHOP | KNIGHT; + v->promotionRegion[WHITE] = Rank8BB | Rank9BB | Rank10BB; + v->promotionRegion[BLACK] = Rank3BB | Rank2BB | Rank1BB; + v->promotionLimit[ARCHBISHOP] = 1; + v->promotionLimit[CHANCELLOR] = 1; + v->promotionLimit[QUEEN] = 1; + v->promotionLimit[ROOK] = 2; + v->promotionLimit[BISHOP] = 2; + v->promotionLimit[KNIGHT] = 2; + v->mandatoryPawnPromotion = false; + v->immobilityIllegal = true; + v->doubleStepRegion[WHITE] = Rank3BB; + v->doubleStepRegion[BLACK] = Rank8BB; + v->castling = false; + return v; + } + // Opulent chess + // Variant of Grand chess with two extra pieces + // https://www.chessvariants.com/rules/opulent-chess + Variant* opulent_variant() { + Variant* v = grand_variant()->init(); + v->pieceToCharTable = "PNBRQ..AC....W.......LKpnbrq..ac....w.......lk"; + v->remove_piece(KNIGHT); + v->add_piece(CUSTOM_PIECE_1, 'n', "NW"); + v->add_piece(CUSTOM_PIECE_2, 'w', "CF"); + v->add_piece(CUSTOM_PIECE_3, 'l', "FDH"); + v->startFen = "rw6wr/clbnqknbla/pppppppppp/10/10/10/10/PPPPPPPPPP/CLBNQKNBLA/RW6WR w - - 0 1"; + v->promotionPieceTypes[WHITE] &= ~piece_set(KNIGHT); + v->promotionPieceTypes[WHITE] |= CUSTOM_PIECE_1; + v->promotionPieceTypes[WHITE] |= CUSTOM_PIECE_2; + v->promotionPieceTypes[WHITE] |= CUSTOM_PIECE_3; + v->promotionPieceTypes[BLACK] = v->promotionPieceTypes[WHITE]; + v->promotionLimit[CUSTOM_PIECE_1] = 2; + v->promotionLimit[CUSTOM_PIECE_2] = 2; + v->promotionLimit[CUSTOM_PIECE_3] = 2; + return v; + } + // Tencubed + // https://www.chessvariants.com/contests/10/tencubedchess.html + Variant* tencubed_variant() { + Variant* v = chess_variant_base()->init(); + v->pieceToCharTable = "PNBRQ.CAM...........WKpnbrq.cam...........wk"; + v->maxRank = RANK_10; + v->maxFile = FILE_J; + v->startFen = "2cwamwc2/1rnbqkbnr1/pppppppppp/10/10/10/10/PPPPPPPPPP/1RNBQKBNR1/2CWAMWC2 w - - 0 1"; + v->add_piece(ARCHBISHOP, 'a'); + v->add_piece(CHANCELLOR, 'm'); + v->add_piece(CUSTOM_PIECE_1, 'c', "DAW"); // Champion + v->add_piece(CUSTOM_PIECE_2, 'w', "CF"); // Wizard + v->promotionPieceTypes[WHITE] = piece_set(ARCHBISHOP) | CHANCELLOR | QUEEN; + v->promotionPieceTypes[BLACK] = piece_set(ARCHBISHOP) | CHANCELLOR | QUEEN; + v->promotionRegion[WHITE] = Rank10BB; + v->promotionRegion[BLACK] = Rank1BB; + v->doubleStepRegion[WHITE] = Rank3BB; + v->doubleStepRegion[BLACK] = Rank8BB; + v->castling = false; + return v; + } + // Omicron chess + // Omega chess on a 12x10 board + // http://www.eglebbk.dds.nl/program/chess-omicron.html + Variant* omicron_variant() { + Variant* v = chess_variant_base()->init(); + v->pieceToCharTable = "PNBRQ..C.W...........Kpnbrq..c.w...........k"; + v->maxRank = RANK_10; + v->maxFile = FILE_L; + v->startFen = "w**********w/*crnbqkbnrc*/*pppppppppp*/*10*/*10*/*10*/*10*/*PPPPPPPPPP*/*CRNBQKBNRC*/W**********W w KQkq - 0 1"; + v->add_piece(CUSTOM_PIECE_1, 'c', "DAW"); // Champion + v->add_piece(CUSTOM_PIECE_2, 'w', "CF"); // Wizard + v->castlingKingsideFile = FILE_I; + v->castlingQueensideFile = FILE_E; + v->castlingRank = RANK_2; + v->promotionRegion[WHITE] = Rank9BB | Rank10BB; + v->promotionRegion[BLACK] = Rank2BB | Rank1BB; + v->promotionPieceTypes[WHITE] = piece_set(CUSTOM_PIECE_2) | CUSTOM_PIECE_1 | QUEEN | ROOK | BISHOP | KNIGHT; + v->promotionPieceTypes[BLACK] = piece_set(CUSTOM_PIECE_2) | CUSTOM_PIECE_1 | QUEEN | ROOK | BISHOP | KNIGHT; + v->doubleStepRegion[WHITE] = Rank3BB; + v->doubleStepRegion[BLACK] = Rank8BB; + return v; + } + // Troitzky Chess + // https://www.chessvariants.com/play/troitzky-chess + Variant* troitzky_variant() { + Variant* v = chess_variant_base()->init(); + v->maxRank = RANK_10; + v->maxFile = FILE_J; + v->startFen = "****qk****/**rnbbnr**/*pppppppp*/*8*/10/10/*8*/*PPPPPPPP*/**RNBBNR**/****QK**** w - - 0 1"; + v->promotionRegion[WHITE] = make_bitboard(SQ_A6, SQ_B8, SQ_C9, SQ_D9, SQ_E10, SQ_F10, SQ_G9, SQ_H9, SQ_I8, SQ_J6); + v->promotionRegion[BLACK] = make_bitboard(SQ_A5, SQ_B3, SQ_C2, SQ_D2, SQ_E1, SQ_F1, SQ_G2, SQ_H2, SQ_I3, SQ_J5); + v->doubleStepRegion[WHITE] = Rank3BB; + v->doubleStepRegion[BLACK] = Rank8BB; + v->castling = false; + return v; + } + // Wolf chess + // https://en.wikipedia.org/wiki/Wolf_chess + Variant* wolf_variant() { + Variant* v = chess_variant_base()->init(); + v->maxRank = RANK_10; + v->remove_piece(KNIGHT); + v->add_piece(CHANCELLOR, 'w'); // wolf + v->add_piece(ARCHBISHOP, 'f'); // fox + v->add_piece(CUSTOM_PIECE_1, 's', "fKifmnD"); // sergeant + v->add_piece(CUSTOM_PIECE_2, 'n', "NN"); // nightrider + v->add_piece(CUSTOM_PIECE_3, 'e', "NNQ"); // elephant + v->startFen = "qwfrbbnk/pssppssp/1pp2pp1/8/8/8/8/1PP2PP1/PSSPPSSP/KNBBRFWQ w - - 0 1"; + v->mainPromotionPawnType[WHITE] = v->mainPromotionPawnType[BLACK] = PAWN; + v->promotionPawnTypes[WHITE] = v->promotionPawnTypes[BLACK] = piece_set(PAWN) | piece_set(CUSTOM_PIECE_1); + v->promotionPieceTypes[WHITE] = piece_set(QUEEN) | CHANCELLOR | ARCHBISHOP | ROOK | BISHOP; + v->promotionPieceTypes[BLACK] = piece_set(QUEEN) | CHANCELLOR | ARCHBISHOP | ROOK | BISHOP; + v->promotedPieceType[PAWN] = CUSTOM_PIECE_3; + v->promotionRegion[WHITE] = Rank10BB; + v->promotionRegion[BLACK] = Rank1BB; + v->doubleStepRegion[WHITE] = Rank2BB | make_bitboard(SQ_B3, SQ_C3, SQ_F3, SQ_G3); + v->doubleStepRegion[BLACK] = Rank9BB | make_bitboard(SQ_B8, SQ_C8, SQ_F8, SQ_G8); + v->enPassantTypes[WHITE] = v->enPassantTypes[BLACK] = piece_set(PAWN); + v->nMoveRuleTypes[WHITE] = v->nMoveRuleTypes[BLACK] = piece_set(PAWN) | piece_set(CUSTOM_PIECE_1); + v->castling = false; + return v; + } + // Shako + // 10x10 variant with cannons by Jean-Louis Cazaux + // https://www.chessvariants.com/large.dir/shako.html + Variant* shako_variant() { + Variant* v = chess_variant_base()->init(); + v->pieceToCharTable = "PNBRQ.E....C.........Kpnbrq.e....c.........k"; + v->maxRank = RANK_10; + v->maxFile = FILE_J; + v->add_piece(FERS_ALFIL, 'e'); + v->add_piece(CANNON, 'c'); + v->startFen = "c8c/ernbqkbnre/pppppppppp/10/10/10/10/PPPPPPPPPP/ERNBQKBNRE/C8C w KQkq - 0 1"; + v->promotionPieceTypes[WHITE] = piece_set(QUEEN) | ROOK | BISHOP | KNIGHT | CANNON | FERS_ALFIL ; + v->promotionPieceTypes[BLACK] = piece_set(QUEEN) | ROOK | BISHOP | KNIGHT | CANNON | FERS_ALFIL ; + v->promotionRegion[WHITE] = Rank10BB; + v->promotionRegion[BLACK] = Rank1BB; + v->castlingKingsideFile = FILE_H; + v->castlingQueensideFile = FILE_D; + v->castlingRookKingsideFile = FILE_I; + v->castlingRookQueensideFile = FILE_B; + v->castlingRank = RANK_2; + v->doubleStepRegion[WHITE] = Rank3BB; + v->doubleStepRegion[BLACK] = Rank8BB; + return v; + } + // Clobber 10x10 + // Clobber on a 10x10 board, mainly played by computers + // https://en.wikipedia.org/wiki/Clobber + Variant* clobber10_variant() { + Variant* v = clobber_variant()->init(); + v->maxRank = RANK_10; + v->maxFile = FILE_J; + v->startFen = "PpPpPpPpPp/pPpPpPpPpP/PpPpPpPpPp/pPpPpPpPpP/PpPpPpPpPp/" + "pPpPpPpPpP/PpPpPpPpPp/pPpPpPpPpP/PpPpPpPpPp/pPpPpPpPpP w 0 1"; + return v; + } + // Flipello 10x10 + // Othello on a 10x10 board, mainly played by computers + // https://en.wikipedia.org/wiki/Reversi + Variant* flipello10_variant() { + Variant* v = flipello_variant()->init(); + v->maxRank = RANK_10; + v->maxFile = FILE_J; + v->startFen = "10/10/10/10/4pP4/4Pp4/10/10/10/10[PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPpppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp] w - - 0 1"; + v->enclosingDropStart = make_bitboard(SQ_E5, SQ_F5, SQ_E6, SQ_F6); + return v; + } +#ifdef ALLVARS + // Game of the Amazons + // https://en.wikipedia.org/wiki/Game_of_the_Amazons + Variant* amazons_variant() { + Variant* v = chess_variant_base()->init(); + v->pieceToCharTable = "....Q.....................q................."; + v->maxRank = RANK_10; + v->maxFile = FILE_J; + v->reset_pieces(); + v->add_piece(CUSTOM_PIECE_1, 'q', "mQ"); + v->startFen = "3q2q3/10/10/q8q/10/10/Q8Q/10/10/3Q2Q3 w - - 0 1"; + v->stalemateValue = -VALUE_MATE; + v->wallingRule = ARROW; + return v; + } +#endif + // Xiangqi (Chinese chess) + // https://en.wikipedia.org/wiki/Xiangqi + // Xiangqi base variant for inheriting rules without chasing rules + Variant* xiangqi_variant_base() { + Variant* v = minixiangqi_variant()->init(); + v->pieceToCharTable = "PN.R.AB..K.C..........pn.r.ab..k.c.........."; + v->maxRank = RANK_10; + v->maxFile = FILE_I; + v->add_piece(ELEPHANT, 'b', 'e'); + v->add_piece(FERS, 'a'); + v->startFen = "rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1"; + v->mobilityRegion[WHITE][KING] = (Rank1BB | Rank2BB | Rank3BB) & (FileDBB | FileEBB | FileFBB); + v->mobilityRegion[BLACK][KING] = (Rank8BB | Rank9BB | Rank10BB) & (FileDBB | FileEBB | FileFBB); + v->mobilityRegion[WHITE][FERS] = v->mobilityRegion[WHITE][KING]; + v->mobilityRegion[BLACK][FERS] = v->mobilityRegion[BLACK][KING]; + v->mobilityRegion[WHITE][ELEPHANT] = Rank1BB | Rank2BB | Rank3BB | Rank4BB | Rank5BB; + v->mobilityRegion[BLACK][ELEPHANT] = Rank6BB | Rank7BB | Rank8BB | Rank9BB | Rank10BB; + v->soldierPromotionRank = RANK_6; + return v; + } + Variant* xiangqi_variant() { + Variant* v = xiangqi_variant_base()->init(); + v->chasingRule = AXF_CHASING; + return v; + } + // Manchu/Yitong chess + // Asymmetric Xiangqi variant with a super-piece + // https://en.wikipedia.org/wiki/Manchu_chess + Variant* manchu_variant() { + Variant* v = xiangqi_variant_base()->init(); + v->pieceToCharTable = "PN.R.AB..K.C....M.....pn.r.ab..k.c.........."; + v->add_piece(BANNER, 'm'); + v->startFen = "rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/9/9/M1BAKAB2 w - - 0 1"; + return v; + } + // Supply chess + // https://en.wikipedia.org/wiki/Xiangqi#Variations + Variant* supply_variant() { + Variant* v = xiangqi_variant_base()->init(); + v->variantTemplate = "bughouse"; + v->startFen = "rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR[] w - - 0 1"; + v->twoBoards = true; + v->pieceDrops = true; + v->dropChecks = false; + v->dropRegion[WHITE] = v->mobilityRegion[WHITE][ELEPHANT]; + v->dropRegion[BLACK] = v->mobilityRegion[BLACK][ELEPHANT]; + v->mobilityRegion[WHITE][FERS] = make_bitboard(SQ_D1, SQ_F1, SQ_E2, SQ_D3, SQ_F3); + v->mobilityRegion[BLACK][FERS] = make_bitboard(SQ_D8, SQ_F8, SQ_E9, SQ_D10, SQ_F10); + v->mobilityRegion[WHITE][ELEPHANT] = make_bitboard(SQ_C1, SQ_G1, SQ_A3, SQ_E3, SQ_I3, SQ_C5, SQ_G5); + v->mobilityRegion[BLACK][ELEPHANT] = make_bitboard(SQ_C6, SQ_G6, SQ_A8, SQ_E8, SQ_I8, SQ_C10, SQ_G10); + v->mobilityRegion[WHITE][SOLDIER] = Rank6BB | Rank7BB | Rank8BB | Rank9BB | Rank10BB | + make_bitboard(SQ_A4, SQ_A5, SQ_C4, SQ_C5, SQ_E4, SQ_E5, SQ_G4, SQ_G5, SQ_I4, SQ_I5); + v->mobilityRegion[BLACK][SOLDIER] = Rank1BB | Rank2BB | Rank3BB | Rank4BB | Rank5BB | + make_bitboard(SQ_A6, SQ_A7, SQ_C6, SQ_C7, SQ_E6, SQ_E7, SQ_G6, SQ_G7, SQ_I6, SQ_I7); + return v; + } + // Janggi (Korean chess) + // https://en.wikipedia.org/wiki/Janggi + // Official tournament rules with bikjang and material counting. + Variant* janggi_variant() { + Variant* v = xiangqi_variant_base()->init(); + v->variantTemplate = "janggi"; + v->pieceToCharTable = ".N.R.AB.P..C.........K.n.r.ab.p..c.........k"; + v->remove_piece(FERS); + v->remove_piece(CANNON); + v->remove_piece(ELEPHANT); + v->add_piece(WAZIR, 'a'); + v->add_piece(JANGGI_CANNON, 'c'); + v->add_piece(JANGGI_ELEPHANT, 'b', 'e'); + v->startFen = "rnba1abnr/4k4/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/4K4/RNBA1ABNR w - - 0 1"; + v->mobilityRegion[WHITE][WAZIR] = v->mobilityRegion[WHITE][KING]; + v->mobilityRegion[BLACK][WAZIR] = v->mobilityRegion[BLACK][KING]; + v->soldierPromotionRank = RANK_1; + v->flyingGeneral = false; + v->bikjangRule = true; + v->materialCounting = JANGGI_MATERIAL; + v->diagonalLines = make_bitboard(SQ_D1, SQ_F1, SQ_E2, SQ_D3, SQ_F3, + SQ_D8, SQ_F8, SQ_E9, SQ_D10, SQ_F10); + v->pass[WHITE] = true; + v->pass[BLACK] = true; + v->nFoldValue = VALUE_DRAW; + v->perpetualCheckIllegal = true; + return v; + } + // Traditional rules of Janggi, where bikjang is a draw + Variant* janggi_traditional_variant() { + Variant* v = janggi_variant()->init(); + v->bikjangRule = true; + v->materialCounting = NO_MATERIAL_COUNTING; + v->nnueAlias = "janggi"; + return v; + } + // Modern rules of Janggi, where bikjang is not considered, but material counting is. + // The repetition rules are also adjusted for better compatibility with Kakao Janggi. + Variant* janggi_modern_variant() { + Variant* v = janggi_variant()->init(); + v->bikjangRule = false; + v->materialCounting = JANGGI_MATERIAL; + v->moveRepetitionIllegal = true; + v->nFoldRule = 4; // avoid nFold being triggered before move repetition + v->nMoveRule = 100; // avoid adjudication before reaching 200 half-moves + v->nnueAlias = "janggi"; + return v; + } + // Casual rules of Janggi, where bikjang and material counting are not considered + Variant* janggi_casual_variant() { + Variant* v = janggi_variant()->init(); + v->bikjangRule = false; + v->materialCounting = NO_MATERIAL_COUNTING; + v->nnueAlias = "janggi"; + return v; + } +#endif + +} // namespace + + +/// VariantMap::init() is called at startup to initialize all predefined variants + +void VariantMap::init() { + // Add to UCI_Variant option + add("chess", chess_variant()); + add("normal", chess_variant()); + add("fischerandom", chess960_variant()); + add("nocastle", nocastle_variant()); + add("armageddon", armageddon_variant()); + add("torpedo", torpedo_variant()); + add("berolina", berolina_variant()); + add("pawnsideways", pawnsideways_variant()); + add("pawnback", pawnback_variant()); + add("legan", legan_variant()); + add("fairy", fairy_variant()); // fairy variant used for endgame code initialization + add("makruk", makruk_variant()); + add("makpong", makpong_variant()); + add("cambodian", cambodian_variant()); + add("karouk", karouk_variant()); + add("asean", asean_variant()); + add("ai-wok", aiwok_variant()); + add("shatranj", shatranj_variant()); + add("chaturanga", chaturanga_variant()); + add("amazon", amazon_variant()); + add("nightrider", nightrider_variant()); + add("grasshopper", grasshopper_variant()); + add("hoppelpoppel", hoppelpoppel_variant()); + add("newzealand", newzealand_variant()); + add("kingofthehill", kingofthehill_variant()); + add("racingkings", racingkings_variant()); + add("knightmate", knightmate_variant()); + add("misere", misere_variant()); + add("losers", losers_variant()); + add("giveaway", giveaway_variant()); + add("antichess", antichess_variant()); + add("suicide", suicide_variant()); + add("codrus", codrus_variant()); + add("extinction", extinction_variant()); + add("kinglet", kinglet_variant()); + add("threekings", threekings_variant()); + add("horde", horde_variant()); + add("petrified", petrified_variant()); + add("nocheckatomic", nocheckatomic_variant()); + add("atomic", atomic_variant()); + add("atomar", atomar_variant()); + add("isolation", isolation_variant()); + add("isolation7x7", isolation7x7_variant()); + add("snailtrail", snailtrail_variant()); + add("fox-and-hounds", fox_and_hounds_variant()); +#ifdef ALLVARS + add("duck", duck_variant()); +#endif + add("joust", joust_variant()); + add("3check", threecheck_variant()); + add("5check", fivecheck_variant()); + add("crazyhouse", crazyhouse_variant()); + add("loop", loop_variant()); + add("chessgi", chessgi_variant()); + add("bughouse", bughouse_variant()); + add("koedem", koedem_variant()); + add("pocketknight", pocketknight_variant()); + add("placement", placement_variant()); + add("sittuyin", sittuyin_variant()); + add("seirawan", seirawan_variant()); + add("shouse", shouse_variant()); + add("dragon", dragon_variant()); + add("paradigm", paradigm_variant()); + add("minishogi", minishogi_variant()); + add("mini", minishogi_variant()); + add("kyotoshogi", kyotoshogi_variant()); + add("micro", microshogi_variant()); + add("dobutsu", dobutsu_variant()); + add("gorogoro", gorogoroshogi_variant()); + add("judkins", judkinsshogi_variant()); + add("torishogi", torishogi_variant()); + add("euroshogi", euroshogi_variant()); + add("losalamos", losalamos_variant()); + add("gardner", gardner_variant()); + add("almost", almost_variant()); + add("sortofalmost", sortofalmost_variant()); + add("chigorin", chigorin_variant()); + add("perfect", perfect_variant()); + add("spartan", spartan_variant()); + add("shatar", shatar_variant()); + add("coregal", coregal_variant()); + add("clobber", clobber_variant()); + add("breakthrough", breakthrough_variant()); + add("ataxx", ataxx_variant()); + add("flipersi", flipersi_variant()); + add("flipello", flipello_variant()); + add("minixiangqi", minixiangqi_variant()); + add("raazuvaa", raazuvaa_variant()); +#ifdef LARGEBOARDS + add("shogi", shogi_variant()); + add("checkshogi", checkshogi_variant()); + add("shoshogi", shoshogi_variant()); + add("yarishogi", yarishogi_variant()); + add("okisakishogi", okisakishogi_variant()); + add("capablanca", capablanca_variant()); + add("capahouse", capahouse_variant()); + add("caparandom", caparandom_variant()); + add("gothic", gothic_variant()); + add("janus", janus_variant()); + add("modern", modern_variant()); + add("chancellor", chancellor_variant()); + add("embassy", embassy_variant()); + add("centaur", centaur_variant()); + add("gustav3", gustav3_variant()); + add("jesonmor", jesonmor_variant()); + add("courier", courier_variant()); + add("grand", grand_variant()); + add("opulent", opulent_variant()); + add("tencubed", tencubed_variant()); + add("omicron", omicron_variant()); + add("troitzky", troitzky_variant()); + add("wolf", wolf_variant()); + add("shako", shako_variant()); + add("clobber10", clobber10_variant()); + add("flipello10", flipello10_variant()); +#ifdef ALLVARS + add("amazons", amazons_variant()); +#endif + add("xiangqi", xiangqi_variant()); + add("manchu", manchu_variant()); + add("supply", supply_variant()); + add("janggi", janggi_variant()); + add("janggitraditional", janggi_traditional_variant()); + add("janggimodern", janggi_modern_variant()); + add("janggicasual", janggi_casual_variant()); +#endif +} + + +// Pre-calculate derived properties +Variant* Variant::conclude() { + // Enforce consistency to allow runtime optimizations + if (!doubleStep) + doubleStepRegion[WHITE] = doubleStepRegion[BLACK] = 0; + if (!doubleStepRegion[WHITE] && !doubleStepRegion[BLACK]) + doubleStep = false; + + // Determine optimizations + bool restrictedMobility = false; + for (PieceSet ps = pieceTypes; !restrictedMobility && ps;) + { + PieceType pt = pop_lsb(ps); + if (mobilityRegion[WHITE][pt] || mobilityRegion[BLACK][pt]) + restrictedMobility = true; + } + fastAttacks = !(pieceTypes & ~(CHESS_PIECES | COMMON_FAIRY_PIECES)) + && kingType == KING + && !restrictedMobility + && !cambodianMoves + && !diagonalLines; + fastAttacks2 = !(pieceTypes & ~(SHOGI_PIECES | COMMON_STEP_PIECES)) + && kingType == KING + && !restrictedMobility + && !cambodianMoves + && !diagonalLines; + + // Initialize calculated NNUE properties + nnueKing = pieceTypes & KING ? KING + : extinctionPieceCount == 0 && (extinctionPieceTypes & COMMONER) ? COMMONER + : NO_PIECE_TYPE; + // The nnueKing has to present exactly once and must not change in count + if (nnueKing != NO_PIECE_TYPE) + { + // If the nnueKing is involved in promotion, count might change + if ( ((promotionPawnTypes[WHITE] | promotionPawnTypes[BLACK]) & nnueKing) + || ((promotionPieceTypes[WHITE] | promotionPieceTypes[BLACK]) & nnueKing) + || std::find(std::begin(promotedPieceType), std::end(promotedPieceType), nnueKing) != std::end(promotedPieceType)) + nnueKing = NO_PIECE_TYPE; + } + if (nnueKing != NO_PIECE_TYPE) + { + std::string fenBoard = startFen.substr(0, startFen.find(' ')); + // Switch NNUE from KA to A if there is no unique piece + if ( std::count(fenBoard.begin(), fenBoard.end(), pieceToChar[make_piece(WHITE, nnueKing)]) != 1 + || std::count(fenBoard.begin(), fenBoard.end(), pieceToChar[make_piece(BLACK, nnueKing)]) != 1) + nnueKing = NO_PIECE_TYPE; + } + // We can not use popcount here yet, as the lookup tables are initialized after the variants + int nnueSquares = (maxRank + 1) * (maxFile + 1); + nnueUsePockets = (pieceDrops && (capturesToHand || (!mustDrop && std::bitset<64>(pieceTypes).count() != 1))) || seirawanGating; + int nnuePockets = nnueUsePockets ? 2 * int(maxFile + 1) : 0; + int nnueNonDropPieceIndices = (2 * std::bitset<64>(pieceTypes).count() - (nnueKing != NO_PIECE_TYPE)) * nnueSquares; + int nnuePieceIndices = nnueNonDropPieceIndices + 2 * (std::bitset<64>(pieceTypes).count() - (nnueKing != NO_PIECE_TYPE)) * nnuePockets; + int i = 0; + for (PieceSet ps = pieceTypes; ps;) + { + // Make sure that the nnueKing type gets the last index, since the NNUE architecture relies on that + PieceType pt = lsb(ps != piece_set(nnueKing) ? ps & ~piece_set(nnueKing) : ps); + ps ^= pt; + assert(pt != nnueKing || !ps); + + for (Color c : { WHITE, BLACK}) + { + pieceSquareIndex[c][make_piece(c, pt)] = 2 * i * nnueSquares; + pieceSquareIndex[c][make_piece(~c, pt)] = (2 * i + (pt != nnueKing)) * nnueSquares; + pieceHandIndex[c][make_piece(c, pt)] = 2 * i * nnuePockets + nnueNonDropPieceIndices; + pieceHandIndex[c][make_piece(~c, pt)] = (2 * i + 1) * nnuePockets + nnueNonDropPieceIndices; + } + i++; + } + + // Map king squares to enumeration of actually available squares. + // E.g., for xiangqi map from 0-89 to 0-8. + // Variants might be initialized before bitboards, so do not rely on precomputed bitboards (like SquareBB). + // Furthermore conclude() might be called on invalid configuration during validation, + // therefore skip proper initialization in case of invalid board size. + int nnueKingSquare = 0; + if (nnueKing && nnueSquares <= SQUARE_NB) + for (Square s = SQ_A1; s < nnueSquares; ++s) + { + Square bitboardSquare = Square(s + s / (maxFile + 1) * (FILE_MAX - maxFile)); + if ( !mobilityRegion[WHITE][nnueKing] || !mobilityRegion[BLACK][nnueKing] + || (mobilityRegion[WHITE][nnueKing] & make_bitboard(bitboardSquare)) + || (mobilityRegion[BLACK][nnueKing] & make_bitboard(relative_square(BLACK, bitboardSquare, maxRank)))) + { + kingSquareIndex[s] = nnueKingSquare++ * nnuePieceIndices; + } + } + else + kingSquareIndex[SQ_A1] = nnueKingSquare++ * nnuePieceIndices; + nnueDimensions = nnueKingSquare * nnuePieceIndices; + + // Determine maximum piece count + std::istringstream ss(startFen); + ss >> std::noskipws; + unsigned char token; + nnueMaxPieces = 0; + while ((ss >> token) && !isspace(token)) + { + if (pieceToChar.find(token) != std::string::npos || pieceToCharSynonyms.find(token) != std::string::npos) + nnueMaxPieces++; + } + if (twoBoards) + nnueMaxPieces *= 2; + + // For endgame evaluation to be applicable, no special win rules must apply. + // Furthermore, rules significantly changing game mechanics also invalidate it. + endgameEval = endgameEval != EG_EVAL_CHESS + || + ( endgameEval == EG_EVAL_CHESS + && extinctionValue == VALUE_NONE + && checkmateValue == -VALUE_MATE + && stalemateValue == VALUE_DRAW + && !materialCounting + && !(flagRegion[WHITE] || flagRegion[BLACK]) + && !mustCapture + && !checkCounting + && !makpongRule + && !connectN + && !blastOnCapture + && !petrifyOnCaptureTypes + && !capturesToHand + && !twoBoards + && !restrictedMobility + && kingType == KING + ) + ? endgameEval : NO_EG_EVAL; + + shogiStylePromotions = false; + for (PieceType current: promotedPieceType) + if (current != NO_PIECE_TYPE) + { + shogiStylePromotions = true; + break; + } + + connectDirections.clear(); + if (connectHorizontal) + { + connectDirections.push_back(EAST); + } + if (connectVertical) + { + connectDirections.push_back(NORTH); + } + if (connectDiagonal) + { + connectDirections.push_back(NORTH_EAST); + connectDirections.push_back(SOUTH_EAST); + } + + // If not a connect variant, set connectPieceTypesTrimmed to no pieces. + // connectPieceTypesTrimmed is separated so that connectPieceTypes is left unchanged for inheritance. + if ( !(connectRegion1[WHITE] || connectRegion1[BLACK] || connectN || connectNxN || collinearN) ) + { + connectPieceTypesTrimmed = NO_PIECE_SET; + } + //Otherwise optimize to pieces actually in the game. + else + { + connectPieceTypesTrimmed = connectPieceTypes & pieceTypes; + }; + + return this; +} + + +/// VariantMap::parse_istream reads variants from an INI-style configuration input stream. + +template +void VariantMap::parse_istream(std::istream& file) { + std::string variant, variant_template, key, value, input; + while (file.peek() != '[' && std::getline(file, input)) {} + + std::vector varsToErase = {}; + while (file.get() && std::getline(std::getline(file, variant, ']'), input)) + { + // Extract variant template, if specified + if (!std::getline(std::getline(std::stringstream(variant), variant, ':'), variant_template)) + variant_template = ""; + + // Read variant rules + Config attribs = {}; + while (file.peek() != '[' && std::getline(file, input)) + { + if (!input.empty() && input.back() == '\r') + input.pop_back(); + std::stringstream ss(input); + if (ss.peek() != ';' && ss.peek() != '#') + { + if (DoCheck && !input.empty() && input.find('=') == std::string::npos) + std::cerr << "Invalid syntax: '" << input << "'." << std::endl; + if (std::getline(std::getline(ss, key, '=') >> std::ws, value) && !key.empty()) + attribs[key.erase(key.find_last_not_of(" ") + 1)] = value; + } + } + + // Create variant + if (variants.find(variant) != variants.end()) + std::cerr << "Variant '" << variant << "' already exists." << std::endl; + else if (!variant_template.empty() && variants.find(variant_template) == variants.end()) + std::cerr << "Variant template '" << variant_template << "' does not exist." << std::endl; + else + { + if (DoCheck) + std::cerr << "Parsing variant: " << variant << std::endl; + Variant* v = !variant_template.empty() ? VariantParser(attribs).parse((new Variant(*variants.find(variant_template)->second))->init()) + : VariantParser(attribs).parse(); + if (v->maxFile <= FILE_MAX && v->maxRank <= RANK_MAX) + { + add(variant, v); + // In order to allow inheritance, we need to temporarily add configured variants + // even when only checking them, but we remove them later after parsing is finished. + if (DoCheck) + varsToErase.push_back(variant); + } + else + delete v; + } + } + // Clean up temporary variants + for (std::string tempVar : varsToErase) + { + delete variants[tempVar]; + variants.erase(tempVar); + } +} + +/// VariantMap::parse reads variants from an INI-style configuration file. + +template +void VariantMap::parse(std::string path) { + if (path.empty() || path == "") + return; + std::ifstream file(path); + if (!file.is_open()) + { + std::cerr << "Unable to open file " << path << std::endl; + return; + } + parse_istream(file); + file.close(); +} + +template void VariantMap::parse(std::string path); +template void VariantMap::parse(std::string path); + +void VariantMap::add(std::string s, Variant* v) { + insert(std::pair(s, v->conclude())); +} + +void VariantMap::clear_all() { + for (auto const& element : *this) + delete element.second; + clear(); +} + +std::vector VariantMap::get_keys() { + std::vector keys; + for (auto const& element : *this) + keys.push_back(element.first); + return keys; +} + +} // namespace Stockfish diff --git a/Fairy-Stockfish-master/src/variant.h b/Fairy-Stockfish-master/src/variant.h new file mode 100644 index 0000000000000000000000000000000000000000..0e6d44c34f266cca5dabbe20b190a899557553e2 --- /dev/null +++ b/Fairy-Stockfish-master/src/variant.h @@ -0,0 +1,247 @@ +/* + Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish + Copyright (C) 2018-2022 Fabian Fichter + + Fairy-Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Fairy-Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef VARIANT_H_INCLUDED +#define VARIANT_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "types.h" +#include "bitboard.h" + +namespace Stockfish { + +/// Variant struct stores information needed to determine the rules of a variant. + +struct Variant { + std::string variantTemplate = "fairy"; + std::string pieceToCharTable = "-"; + int pocketSize = 0; + Rank maxRank = RANK_8; + File maxFile = FILE_H; + bool chess960 = false; + bool twoBoards = false; + int pieceValue[PHASE_NB][PIECE_TYPE_NB] = {}; + std::string customPiece[CUSTOM_PIECES_NB] = {}; + PieceSet pieceTypes = CHESS_PIECES; + std::string pieceToChar = " PNBRQ" + std::string(KING - QUEEN - 1, ' ') + "K" + std::string(PIECE_TYPE_NB - KING - 1, ' ') + + " pnbrq" + std::string(KING - QUEEN - 1, ' ') + "k" + std::string(PIECE_TYPE_NB - KING - 1, ' '); + std::string pieceToCharSynonyms = std::string(PIECE_NB, ' '); + std::string startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; + Bitboard mobilityRegion[COLOR_NB][PIECE_TYPE_NB] = {}; + Bitboard promotionRegion[COLOR_NB] = {Rank8BB, Rank1BB}; + PieceType mainPromotionPawnType[COLOR_NB] = {PAWN, PAWN}; + PieceSet promotionPawnTypes[COLOR_NB] = {piece_set(PAWN), piece_set(PAWN)}; + PieceSet promotionPieceTypes[COLOR_NB] = {piece_set(QUEEN) | ROOK | BISHOP | KNIGHT, + piece_set(QUEEN) | ROOK | BISHOP | KNIGHT}; + bool sittuyinPromotion = false; + int promotionLimit[PIECE_TYPE_NB] = {}; // 0 means unlimited + PieceType promotedPieceType[PIECE_TYPE_NB] = {}; + bool piecePromotionOnCapture = false; + bool mandatoryPawnPromotion = true; + bool mandatoryPiecePromotion = false; + bool pieceDemotion = false; + bool blastOnCapture = false; + PieceSet blastImmuneTypes = NO_PIECE_SET; + PieceSet mutuallyImmuneTypes = NO_PIECE_SET; + PieceSet petrifyOnCaptureTypes = NO_PIECE_SET; + bool petrifyBlastPieces = false; + bool doubleStep = true; + Bitboard doubleStepRegion[COLOR_NB] = {Rank2BB, Rank7BB}; + Bitboard tripleStepRegion[COLOR_NB] = {}; + Bitboard enPassantRegion[COLOR_NB] = {AllSquares, AllSquares}; + PieceSet enPassantTypes[COLOR_NB] = {piece_set(PAWN), piece_set(PAWN)}; + bool castling = true; + bool castlingDroppedPiece = false; + File castlingKingsideFile = FILE_G; + File castlingQueensideFile = FILE_C; + Rank castlingRank = RANK_1; + File castlingKingFile = FILE_E; + PieceType castlingKingPiece[COLOR_NB] = {KING, KING}; + File castlingRookKingsideFile = FILE_MAX; // only has to match if rook is not in corner in non-960 variants + File castlingRookQueensideFile = FILE_A; // only has to match if rook is not in corner in non-960 variants + PieceSet castlingRookPieces[COLOR_NB] = {piece_set(ROOK), piece_set(ROOK)}; + bool oppositeCastling = false; + PieceType kingType = KING; + bool checking = true; + bool dropChecks = true; + bool mustCapture = false; + bool mustDrop = false; + PieceType mustDropType = ALL_PIECES; + bool pieceDrops = false; + bool dropLoop = false; + bool capturesToHand = false; + bool firstRankPawnDrops = false; + bool promotionZonePawnDrops = false; + EnclosingRule enclosingDrop = NO_ENCLOSING; + Bitboard enclosingDropStart = 0; + Bitboard dropRegion[COLOR_NB] = {AllSquares, AllSquares}; + bool sittuyinRookDrop = false; + bool dropOppositeColoredBishop = false; + bool dropPromoted = false; + PieceType dropNoDoubled = NO_PIECE_TYPE; + int dropNoDoubledCount = 1; + bool immobilityIllegal = false; + bool gating = false; + WallingRule wallingRule = NO_WALLING; + Bitboard wallingRegion[COLOR_NB] = {AllSquares, AllSquares}; + bool wallOrMove = false; + bool seirawanGating = false; + bool cambodianMoves = false; + Bitboard diagonalLines = 0; + bool pass[COLOR_NB] = {false, false}; + bool passOnStalemate[COLOR_NB] = {false, false}; + bool makpongRule = false; + bool flyingGeneral = false; + Rank soldierPromotionRank = RANK_1; + EnclosingRule flipEnclosedPieces = NO_ENCLOSING; + bool freeDrops = false; + + // game end + PieceSet nMoveRuleTypes[COLOR_NB] = {piece_set(PAWN), piece_set(PAWN)}; + int nMoveRule = 50; + int nFoldRule = 3; + Value nFoldValue = VALUE_DRAW; + bool nFoldValueAbsolute = false; + bool perpetualCheckIllegal = false; + bool moveRepetitionIllegal = false; + ChasingRule chasingRule = NO_CHASING; + Value stalemateValue = VALUE_DRAW; + bool stalematePieceCount = false; // multiply stalemate value by sign(count(~stm) - count(stm)) + Value checkmateValue = -VALUE_MATE; + bool shogiPawnDropMateIllegal = false; + bool shatarMateRule = false; + bool bikjangRule = false; + Value extinctionValue = VALUE_NONE; + bool extinctionClaim = false; + bool extinctionPseudoRoyal = false; + bool dupleCheck = false; + PieceSet extinctionPieceTypes = NO_PIECE_SET; + int extinctionPieceCount = 0; + int extinctionOpponentPieceCount = 0; + PieceType flagPiece[COLOR_NB] = {ALL_PIECES, ALL_PIECES}; + Bitboard flagRegion[COLOR_NB] = {}; + int flagPieceCount = 1; + bool flagPieceBlockedWin = false; + bool flagMove = false; + bool flagPieceSafe = false; + bool checkCounting = false; + int connectN = 0; + PieceSet connectPieceTypes = ~NO_PIECE_SET; + bool connectHorizontal = true; + bool connectVertical = true; + bool connectDiagonal = true; + Bitboard connectRegion1[COLOR_NB] = {}; + Bitboard connectRegion2[COLOR_NB] = {}; + int connectNxN = 0; + int collinearN = 0; + Value connectValue = VALUE_MATE; + MaterialCounting materialCounting = NO_MATERIAL_COUNTING; + bool adjudicateFullBoard = false; + CountingRule countingRule = NO_COUNTING; + CastlingRights castlingWins = NO_CASTLING; + + // Derived properties + bool fastAttacks = true; + bool fastAttacks2 = true; + std::string nnueAlias = ""; + PieceType nnueKing = KING; + int nnueDimensions; + bool nnueUsePockets; + int pieceSquareIndex[COLOR_NB][PIECE_NB]; + int pieceHandIndex[COLOR_NB][PIECE_NB]; + int kingSquareIndex[SQUARE_NB]; + int nnueMaxPieces; + EndgameEval endgameEval = EG_EVAL_CHESS; + bool shogiStylePromotions = false; + std::vector connectDirections; + PieceSet connectPieceTypesTrimmed = ~NO_PIECE_SET; + void add_piece(PieceType pt, char c, std::string betza = "", char c2 = ' ') { + // Avoid ambiguous definition by removing existing piece with same letter + size_t idx; + if ((idx = pieceToChar.find(toupper(c))) != std::string::npos) + remove_piece(PieceType(idx)); + // Now add new piece + pieceToChar[make_piece(WHITE, pt)] = toupper(c); + pieceToChar[make_piece(BLACK, pt)] = tolower(c); + pieceToCharSynonyms[make_piece(WHITE, pt)] = toupper(c2); + pieceToCharSynonyms[make_piece(BLACK, pt)] = tolower(c2); + pieceTypes |= pt; + // Add betza notation for custom piece + if (is_custom(pt)) + customPiece[pt - CUSTOM_PIECES] = betza; + } + + void add_piece(PieceType pt, char c, char c2) { + add_piece(pt, c, "", c2); + } + + void remove_piece(PieceType pt) { + pieceToChar[make_piece(WHITE, pt)] = ' '; + pieceToChar[make_piece(BLACK, pt)] = ' '; + pieceToCharSynonyms[make_piece(WHITE, pt)] = ' '; + pieceToCharSynonyms[make_piece(BLACK, pt)] = ' '; + pieceTypes &= ~piece_set(pt); + // erase from promotion types to ensure consistency + promotionPieceTypes[WHITE] &= ~piece_set(pt); + promotionPieceTypes[BLACK] &= ~piece_set(pt); + } + + void reset_pieces() { + pieceToChar = std::string(PIECE_NB, ' '); + pieceToCharSynonyms = std::string(PIECE_NB, ' '); + pieceTypes = NO_PIECE_SET; + // clear promotion types to ensure consistency + promotionPieceTypes[WHITE] = NO_PIECE_SET; + promotionPieceTypes[BLACK] = NO_PIECE_SET; + } + + // Reset values that always need to be redefined + Variant* init() { + nnueAlias = ""; + endgameEval = EG_EVAL_CHESS; + return this; + } + + Variant* conclude(); +}; + +class VariantMap : public std::map { +public: + void init(); + template void parse(std::string path); + template void parse_istream(std::istream& file); + void clear_all(); + std::vector get_keys(); + +private: + void add(std::string s, Variant* v); +}; + +extern VariantMap variants; + +} // namespace Stockfish + +#endif // #ifndef VARIANT_H_INCLUDED diff --git a/Fairy-Stockfish-master/src/variants.ini b/Fairy-Stockfish-master/src/variants.ini new file mode 100644 index 0000000000000000000000000000000000000000..5d330f7a608ed45d4e2816c01bee9015b2336710 --- /dev/null +++ b/Fairy-Stockfish-master/src/variants.ini @@ -0,0 +1,2126 @@ +# Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish +# Copyright (C) 2018-2022 Fabian Fichter +# +# Fairy-Stockfish is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Fairy-Stockfish is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# This is a configuration file to add user-defined variants to Fairy-Stockfish. + +################################################ +### Usage: +# Add "load" and the file path to the SF call (e.g., "stockfish.exe load variants.ini") +# or set the UCI option "VariantPath" to the path of this file in order to load it. +# In order to validate the configuration without actually loading the variants +# run "stockfish.exe check variants.ini", which reports potential config errors. + +################################################ +### Variant configuration: +# The variant name needs to be specified as a section in square brackets, +# followed by its rule configurations as key-value pairs as described below. +# If you encounter problems configuring variants, please report them at: +# https://github.com/ianfab/Fairy-Stockfish/issues + +### Inheritance +# If a variant is similar to a previously defined variant, +# inheritance can be used to simplify the definition. To inherit from the +# configuration of an existing variant, specify the parent variant after the child +# variant name separated by a colon, e.g., [gothic:capablanca]. +# When inheritance is used, only the differences to the parent variant need to be defined, +# see the examples in this file, e.g., 3check-crazyhouse. +# When no inheritance is used, the default template applies, +# which is basically standard chess but without any predefined pieces. + +### Piece types +# Firstly, the piece types for a variant need to be defined. +# For that, specify the letter used for each piece type, e.g.: +# pawn = p +# +# See the list below for all available predefined piece types (and their Betza notation): +# pawn (fmWfceF) +# knight (N) +# bishop (B) +# rook (R) +# queen (Q) +# fers (F) +# alfil (A) +# fersAlfil (FA) +# silver (FfW) +# aiwok (RNF) +# bers (RF) +# archbishop (BN) +# chancellor (RN) +# amazon (QN) +# knibis (mNcB) +# biskni (mBcN) +# kniroo (mNcR) +# rookni (mRcN) +# shogiPawn (fW) +# lance (fR) +# shogiKnight (fN) +# gold (WfF) +# dragonHorse (BW) +# clobber (cW) +# breakthrough (fmWfF) +# immobile () +# cannon (mRcpR) +# janggiCannon (pR) +# soldier (fsW) +# horse (nN) +# elephant (nA) +# janggiElephant (nZ) +# banner (RcpRnN) +# wazir (W) +# commoner (K) +# centaur (KN) +# king (K) + +### Custom pieces +# Custom pieces can be defined by using one of the available slots: +# customPiece1, customPiece2, ..., customPiece25 +# E.g., pawns without double steps could be described as: +# customPiece1 = p:mfWcfF +# +# You can define custom king movements in the same way you can define another custom piece. +# E.g., to make the king move like a centaur: +# king = k:KN +# In contrast to other custom pieces the Betza notation for the king is optional though +# and defaults to a standard chess king (betza: K) when skipped, e.g.: +# king = k +# +# The movements of custom pieces can be defined using the Betza notation. +# https://www.gnu.org/software/xboard/Betza.html +# In Fairy-Stockfish only a subset of Betza notation can be used. The supported features are: +# - all base moves/atoms (W, F, etc.) +# - all directional modifiers (f, b, etc.) +# - limited and unlimited distance sliders/riders for W/R, F/B, and N directions +# - hoppers and grasshoppers for W/R and F/B directions, i.e., pR, pB, gR, and gB +# - lame leapers (n) for N, A, Z, and D directions, i.e., nN, nA, nZ, and nD + +### Piece values +# The predefined and precalculated piece values can be overridden +# by specifying the pieceValueMg and pieceValueEg options, e.g., +# pieceValueMg = p:150 n:800 +# pieceValueEg = p:200 n:900 +# +# For orientation, the internal predefined piece values can be found in types.h. +# A suitable piece for gauging the piece values is the rook, which internally has: +# pieceValueMg = r:1276 +# pieceValueEg = r:1380 + +### Option types +# [bool]: boolean flag to enable/disable a feature [true, false] +# [Rank]: denotes a rank of the board [1-10] +# [File]: denotes a file of the board [1-12, a-i] +# [int]: any natural number [0, 1, ...] +# [PieceType]: a piece type [letters defined for pieces, e.g., p] +# [PieceSet]: multiple piece types [letters defined for pieces, e.g., nbrq] +# [CastlingRights]: set of castling rights [letters for castling rights as in FEN, e.g., KQkq] +# [Bitboard]: list of squares [e.g., d4 e4 d5 e5]. * can be used as wildcard for files (e.g., *1 is the first rank) +# [Value]: game result for the side to move [win, loss, draw] +# [MaterialCounting]: material counting rules for adjudication [janggi, unweighted, whitedrawodds, blackdrawodds, none] +# [CountingRule]: makruk, cambodian, or ASEAN counting rules [makruk, cambodian, asean, none] +# [ChasingRule]: xiangqi chasing rules [axf, none] +# [EnclosingRule]: reversi, ataxx, etc. enclosing rules [reversi, ataxx, quadwrangle, snort, anyside, top, none] +# - in enclosingDrop: +# - reversi: must enclose opponent's pieces between yours by Queen move +# - ataxx: must be adjacent to own piece by King move +# - snort: most *not* be adjacent to opponent's piece by Wazir move +# - anyside: must be reached by inserting from an edge and sliding to opposite edge +# - top: must be reached by inserting from top and sliding to bottom (ie. Connect 4) +# - in flipEnclosedPieces: +# - reversi: flip opponent's pieces enclosed between yours by Queen move +# - quadwrangle: if a normal move *or* a drop with a friendly piece adjacent by King move, then flip opponent's pieces adjacent by King move +# - ataxx: flip opponent's pieces adjacent by King move +# [WallingRule]: wall-placing rule [arrow, duck, edge, past, static, none] +# - arrow: copies piece movement (ie. Game of the Amazons) +# - duck: mobile square (ie. Duck chess) +# - edge: edges of board, opening up new edges (ie. Atlantis) +# - past: previous square (ie. Snailtrail) +# - static: unchanging mask (ie. Isolation) + +### Additional options relevant for usage in Winboard/XBoard +# A few options only have the purpose of improving compatibility with Winboard/Xboard. +# These do not need to be specified when using other GUIs, but can be essential for Winboard/Xboard. +# +# variantTemplate: base variant to inherit GUI logic from [values: fairy, shogi, bughouse] (default: fairy) +# pieceToCharTable: mapping of piece characters to images, +# see https://www.gnu.org/software/xboard/whats_new/4.9.0/index.html#tag-B1 (default: -) +# pocketSize: number of pockets shown by XBoard/WinBoard for drop variants [int] (default: 0) + +### Rule definition options +# maxRank: maximum rank [Rank] (default: 8) +# maxFile: maximum file [File] (default: 8) +# chess960: allow chess960 castling [bool] (default: false) +# twoBoards: pocket pieces are added from an external source, usually from a second board (e.g., bughouse) [bool] (default: false) +# startFen: FEN of starting position (default: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1) +# mobilityRegion: the mobility area can be defined via options specific to color and piece, +# .e.g., mobilityRegionWhiteRook, mobilityRegionBlackJanggiElephant, etc. [Bitboard] +# pawnTypes: define pieces considered as "pawns" for promotion, en passant, and n move rule [PieceSet] (default: p) +# see promotionPawnTypes, enPassantTypes, and nMoveRuleTypes for more specific overrides. +# promotionRegionWhite: region where promotions are allowed for white [Bitboard] (default: *8) +# promotionRegionBlack: region where promotions are allowed for black [Bitboard] (default: *1) +# promotionPawnTypes: promotion pawn types for both colors [PieceSet] (default: p) +# promotionPawnTypesWhite: white promotion pawn types [PieceSet] (default: p) +# promotionPawnTypesBlack: black promotion pawn types [PieceSet] (default: p) +# promotionPieceTypes: pawn promotion options [PieceSet] (default: nbrq) +# promotionPieceTypesWhite: white pawn promotion options [PieceSet] (default: nbrq) +# promotionPieceTypesBlack: black pawn promotion options [PieceSet] (default: nbrq) +# sittuyinPromotion: enable Sittuyin-style pawn promotion [bool] (default: false) +# promotionLimit: maximum number of pieces of a type, e.g., q:1 r:2 (default: ) +# promotedPieceType: mapping between unpromoted and promoted non-pawn piece types, e.g., p:g s:g (default: ) +# piecePromotionOnCapture: piece promotion only allowed on captures (e.g., micro shogi) [bool] (default: false) +# mandatoryPawnPromotion: pawn promotion is mandatory [bool] (default: true) +# mandatoryPiecePromotion: piece promotion (and demotion if enabled) is mandatory [bool] (default: false) +# pieceDemotion: enable demotion of pieces (e.g., Kyoto shogi) [bool] (default: false) +# blastOnCapture: captures explode all adjacent non-pawn pieces (e.g., atomic chess) [bool] (default: false) +# blastImmuneTypes: pieces completely immune to explosions (even at ground zero) [PieceSet] (default: none) +# mutuallyImmuneTypes: pieces that can't capture another piece of same types (e.g., kings (commoners) in atomar) [PieceSet] (default: none) +# petrifyOnCaptureTypes: defined pieces are turned into wall squares when capturing [PieceSet] (default: -) +# petrifyBlastPieces: if petrify and blast combined, should pieces destroyed in the blast be petrified? [bool] (default: false) +# doubleStep: enable pawn double step [bool] (default: true) +# doubleStepRegionWhite: region where pawn double steps are allowed for white [Bitboard] (default: *2) +# doubleStepRegionBlack: region where pawn double steps are allowed for black [Bitboard] (default: *7) +# tripleStepRegionWhite: region where pawn triple steps are allowed for white [Bitboard] (default: -) +# tripleStepRegionBlack: region where pawn triple steps are allowed for black [Bitboard] (default: -) +# enPassantRegion: define region (target squares) where en passant is allowed after double steps [Bitboard] (default: AllSquares) +# enPassantRegionWhite: define region (target squares) where en passant is allowed for white [Bitboard] (default: AllSquares) +# enPassantRegionBlack: define region (target squares) where en passant is allowed for black [Bitboard] (default: AllSquares) +# enPassantTypes: define pieces able to capture en passant [PieceSet] (default: p) +# enPassantTypesWhite: define white pieces able to capture en passant [PieceSet] (default: p) +# enPassantTypesBlack: define black pieces able to capture en passant [PieceSet] (default: p) +# castling: enable castling [bool] (default: true) +# castlingDroppedPiece: enable castling with dropped rooks/kings [bool] (default: false) +# castlingKingsideFile: destination file of king after kingside castling [File] (default: g) +# castlingQueensideFile: destination file of king after queenside castling [File] (default: c) +# castlingRank: relative rank of castling [Rank] (default: 1) +# castlingKingFile: starting file of the castlingKingPiece if there can be more than one of that type [File] (default: e) +# castlingKingPiece: first piece type that participates in castling [PieceType] (default: k) +# castlingRookKingsideFile: starting file of castlingRookPieces on kingside (if not in corner) [File] (default: l) +# castlingRookQueensideFile: starting file of castlingRookPieces on queenside (if not in corner) [File] (default: a) +# castlingRookPieces: second piece type that participates in castling [PieceSet] (default: r) +# oppositeCastling: can't castle same side as opponent [bool] (default: false) +# checking: allow checks [bool] (default: true) +# dropChecks: allow checks by piece drops [bool] (default: true) +# mustCapture: captures are mandatory (check evasion still takes precedence) [bool] (default: false) +# mustDrop: drops are mandatory (e.g., for Sittuyin setup phase) [bool] (default: false) +# mustDropType: piece type for which piece drops are mandatory [PieceType] (default: *) +# pieceDrops: enable piece drops [bool] (default: false) +# dropLoop: captures promoted pieces are not demoted [bool] (default: false) +# capturesToHand: captured pieces go to opponent's hand [bool] (default: false) +# firstRankPawnDrops: allow pawn drops to first rank [bool] (default: false) +# promotionZonePawnDrops: allow pawn drops in promotion zone [bool] (default: false) +# enclosingDrop: require piece drop to enclose pieces [EnclosingRule] (default: none) +# enclosingDropStart: drop region for starting phase disregarding enclosingDrop (e.g., for reversi) [Bitboard] +# dropRegionWhite: restrict region for piece drops of all white pieces [Bitboard] +# dropRegionBlack: restrict region for piece drops of all black pieces [Bitboard] +# sittuyinRookDrop: restrict region of rook drops to first rank [bool] (default: false) +# dropOppositeColoredBishop: dropped bishops have to be on opposite-colored squares [bool] (default: false) +# dropPromoted: pieces may be dropped in promoted state [bool] (default: false) +# dropNoDoubled: specified piece type can not be dropped to the same file (e.g. shogi pawn) [PieceType] (default: -) +# dropNoDoubledCount: specifies the count of already existing pieces for dropNoDoubled [int] (default: 1) +# immobilityIllegal: pieces may not move to squares where they can never move from [bool] (default: false) +# gating: maintain squares on backrank with extra rights in castling field of FEN [bool] (default: false) +# wallingRule: rule on where wall can be placed [WallingRule] (default: none) +# wallingRegionWhite: mask where wall squares (including duck) can be placed by white [Bitboard] (default: all squares) +# wallingRegionBlack: mask where wall squares (including duck) can be placed by black [Bitboard] (default: all squares) +# wallOrMove: can wall or move, but not both [bool] (default: false) +# seirawanGating: allow gating of pieces in hand like in S-Chess, requires "gating = true" [bool] (default: false) +# cambodianMoves: enable special moves of cambodian chess, requires "gating = true" [bool] (default: false) +# diagonalLines: enable special moves along diagonal for specific squares (Janggi) [Bitboard] +# pass: allow passing [bool] (default: false) +# passWhite: allow passing for white [bool] (default: false) +# passBlack: allow passing for black [bool] (default: false) +# passOnStalemate: allow passing in case of stalemate [bool] (default: false) +# passOnStalemateWhite: allow passing in case of stalemate for white [bool] (default: false) +# passOnStalemateBlack: allow passing in case of stalemate for black [bool] (default: false) +# makpongRule: the king may not move away from check [bool] (default: false) +# flyingGeneral: disallow general face-off like in xiangqi [bool] (default: false) +# soldierPromotionRank: restrict soldier to shogi pawn movements until reaching n-th rank [Rank] (default: 1) +# flipEnclosedPieces: change color of pieces that are enclosed by a drop [EnclosingRule] (default: none) +# nMoveRuleTypes: define pieces resetting n move rule on irreversible moves [PieceSet] (default: p) +# nMoveRuleTypesWhite: define white pieces resetting n move rule on irreversible moves [PieceSet] (default: p) +# nMoveRuleTypesBlack: define black pieces resetting n move rule on irreversible moves [PieceSet] (default: p) +# nMoveRule: move count for 50/n-move rule [int] (default: 50) +# nFoldRule: move count for 3/n-fold repetition rule [int] (default: 3) +# nFoldValue: result in case of 3/n-fold repetition [Value] (default: draw) +# nFoldValueAbsolute: result in case of 3/n-fold repetition is from white's point of view [bool] (default: false) +# perpetualCheckIllegal: prohibit perpetual checks [bool] (default: false) +# moveRepetitionIllegal: prohibit moving back and forth with the same piece nFoldRule-1 times [bool] (default: false) +# chasingRule: enable chasing rules [ChasingRule] (default: none) +# stalemateValue: result in case of stalemate [Value] (default: draw) +# stalematePieceCount: count material in case of stalemate [bool] (default: false) +# checkmateValue: result in case of checkmate [Value] (default: loss) +# shogiPawnDropMateIllegal: prohibit checkmate via shogi pawn drops [bool] (default: false) +# shatarMateRule: enable shatar mating rules [bool] (default: false) +# bikjangRule: consider Janggi bikjang (facing kings) rule [bool] (default: false) +# extinctionValue: result when one of extinctionPieceTypes is extinct [Value] (default: none) +# extinctionClaim: extinction of opponent pieces can only be claimed as side to move [bool] (default: false) +# extinctionPseudoRoyal: treat the last extinction piece like a royal piece [bool] (default: false) +# dupleCheck: when all pseudo-royal pieces are attacked, it counts as a check [bool] (default: false) +# extinctionPieceTypes: list of piece types for extinction rules, e.g., pnbrq (* means all) (default: ) +# extinctionPieceCount: piece count at which the game is decided by extinction rule (default: 0) +# extinctionOpponentPieceCount: opponent piece count required to adjudicate by extinction rule (default: 0) +# flagPiece: piece type for capture the flag win rule [PieceType] (default: *) +# flagPieceWhite: piece type for capture the flag win rule [PieceType] (default: *) +# flagPieceBlack: piece type for capture the flag win rule [PieceType] (default: *) +# flagRegion: target region for capture the flag win rule [Bitboard] (default: ) +# flagRegionWhite: white's target region for capture the flag win rule [Bitboard] (default: ) +# flagRegionBlack: black's target region for capture the flag win rule [Bitboard] (default: ) +# flagPieceCount: number of flag pieces that have to be in the flag zone [int] (default: 1) +# flagPieceBlockedWin: for flagPieceCount > 1, win if at least one flag piece in flag zone and all others occupied by pieces [bool] (default: false) +# flagMove: the other side gets one more move after one reaches the flag zone [bool] (default: false) +# flagPieceSafe: the flag piece must be safe to win [bool] (default: false) +# when flagPieceSafe and flagMove are combined, it means that only unsafe flag pieces cause an extra move. +# checkCounting: enable check count win rule (check count is communicated via FEN, see 3check) [bool] (default: false) +# connectN: number of aligned pieces for win [int] (default: 0) +# connectPieceTypes: pieces evaluated for connection rule [PieceSet] (default: *) +# connectVertical: connectN looks at Vertical rows [bool] (default: true) +# connectHorizontal: connectN looks at Horizontal rows [bool] (default: true) +# connectDiagonal: connectN looks at Diagonal rows [bool] (default: true) +# connectRegion1White: connect Region 1 to Region 2 for win. obeys connectVertical, connectHorizontal, connectDiagonal [Bitboard] (default: -) +# connectRegion2White: " +# connectRegion1Black: " +# connectRegion2Black: " +# connectNxN: connect a tight NxN square for win [int] (default: 0) +# collinearN: arrange N pieces collinearly (other squares can be between pieces) [int] (default: 0) +# connectValue: result in case of connect [Value] (default: win) +# materialCounting: enable material counting rules [MaterialCounting] (default: none) +# adjudicateFullBoard: apply material counting immediately when board is full [bool] (default: false) +# countingRule: enable counting rules [CountingRule] (default: none) +# castlingWins: Specified castling moves are win conditions. Losing these rights is losing. [CastlingRights] (default: -) + +################################################ +### Example for minishogi configuration that would be equivalent to the built-in variant: + +# [minishogi] +# variantTemplate = shogi +# maxRank = 5 +# maxFile = 5 +# shogiPawn = p +# silver = s +# gold = g +# bishop = b +# dragonHorse = h +# rook = r +# bers = d +# king = k +# startFen = rbsgk/4p/5/P4/KGSBR[-] w 0 1 +# pieceDrops = true +# capturesToHand = true +# promotionRegionWhite = *5 +# promotionRegionBlack = *1 +# doubleStep = false +# castling = false +# promotedPieceType = p:g s:g b:h r:d +# dropNoDoubled = p +# immobilityIllegal = true +# shogiPawnDropMateIllegal = true +# stalemateValue = loss +# nFoldRule = 4 +# nMoveRule = 0 +# perpetualCheckIllegal = true +# pocketSize = 5 +# nFoldValue = loss +# nFoldValueAbsolute = true + +# pawns with extra sideways and backwards movement +# example for defining a custom pawn type +# resetting the original "p" piece with "pawn = -" is optional as already done implicitly +[allwayspawns:chess] +customPiece1 = p:mWfceFifmnD +pawnTypes = p + +# Hybrid variant of three-check chess and crazyhouse, using crazyhouse as a template +[3check-crazyhouse:crazyhouse] +startFen = rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 3+3 0 1 +checkCounting = true + +# Hybrid variant of atomic and giveaway +[atomic-giveaway:giveaway] +blastOnCapture = true + +# Hybrid variant of atomic, giveaway, and king of the hill +[atomic-giveaway-hill:giveaway] +blastOnCapture = true +flagPiece = k +flagRegion = d4 e4 d5 e5 + +# Crazyhouse with mandatory captures, using crazyhouse as a template +[coffeehouse:crazyhouse] +mustCapture = true + +# Hybrid variant of makruk and crazyhouse +[makhouse:makruk] +startFen = rnsmksnr/8/pppppppp/8/8/PPPPPPPP/8/RNSKMSNR[] w - - 0 1 +pieceDrops = true +capturesToHand = true +firstRankPawnDrops = true +promotionZonePawnDrops = true +immobilityIllegal = true + +# Hybrid variant of xiangqi and crazyhouse +[xiangqihouse:xiangqi] +startFen = rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR[] w - - 0 1 +pieceDrops = true +capturesToHand = true +dropChecks = false +dropRegionWhite = *1 *2 *3 *4 *5 +dropRegionBlack = *6 *7 *8 *9 *10 +mobilityRegionWhiteFers = d1 f1 e2 d3 f3 +mobilityRegionBlackFers = d8 f8 e9 d10 f10 +mobilityRegionWhiteElephant = c1 g1 a3 e3 i3 c5 g5 +mobilityRegionBlackElephant = c6 g6 a8 e8 i8 c10 g10 +mobilityRegionWhiteSoldier = a4 a5 c4 c5 e4 e5 g4 g5 i4 i5 *6 *7 *8 *9 *10 +mobilityRegionBlackSoldier = *1 *2 *3 *4 *5 a6 a7 c6 c7 e6 e7 g6 g7 i6 i7 + +# Hybrid variant of janggi and crazyhouse +[janggihouse:janggi] +startFen = rnba1abnr/4k4/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/4K4/RNBA1ABNR[] w - - 0 1 +pieceDrops = true +capturesToHand = true + +# Hybrid variant of antichess and losalamos +[anti-losalamos:losalamos] +king = - +commoner = k +promotionPieceTypes = nrqk +mustCapture = true +stalemateValue = win +extinctionValue = win +extinctionPieceTypes = * + +# Indian great chess +# https://www.chessvariants.com/historic.dir/indiangr1.html +[indiangreat] +pieceToCharTable = PNBRQ..VW.........G..Kpnbrq..vw.........g..k +pawn = p +knight = n +bishop = b +rook = r +queen = q +king = k +archbishop = v +chancellor = w +amazon = g +maxRank = 10 +maxFile = 10 +startFen = rnbqkgvbnr/ppppwwpppp/4pp4/10/10/10/10/4PP4/PPPPWWPPPP/RNBVGKQBNR w - - 0 1 +promotionRegionWhite = *10 +promotionRegionBlack = *1 +promotionPieceTypes = q +doubleStep = false +castling = false + +# Centaurking +# A variant demonstrating how to define a custom royal piece movement +[centaurking:chess] +king = k:KN + +# Mahajarah and the Sepoys +# https://en.wikipedia.org/wiki/Maharajah_and_the_Sepoys +[maharajah] +pawn = p +knight = n +bishop = b +rook = r +queen = q +king = k +amazon = m +pieceToCharTable = PNBRQ.............MKpnbrq.............mk +startFen = rnbqkbnr/pppppppp/8/8/8/8/8/4M3 w kq - 0 1 +extinctionValue = loss +extinctionPieceTypes = m +extinctionPseudoRoyal = true + +# Maharajah +# https://vchess.club/#/variants/Maharajah (Balanced version of Maharajah and the Sepoys) +[maharajah2:maharajah] +amazon = - +customPiece1 = m:QNAD +pieceToCharTable = PNBRQ.............MKpnbrq.............mk +startFen = 3mm3/8/8/8/8/8/PPPPPPPP/RNBQKBNR w KQ - 0 1 +extinctionPieceTypes = m + +# Upside-down +[upsidedown:chess] +startFen = RNBKQBNR/PPPPPPPP/8/8/8/8/pppppppp/rnbkqbnr w - - 0 1 + +# Peasant revolt +# https://www.chessvariants.com/large.dir/peasantrevolt.html +[peasant:chess] +startFen = 1nn1k1n1/4p3/8/8/8/8/PPPPPPPP/4K3 w - - 0 1 + +# https://www.chessvariants.com/unequal.dir/weak.html +[weak:chess] +startFen = nnnnknnn/pppppppp/2p2p2/1pppppp1/8/8/PPPPPPPP/RNBQKBNR w KQ - 0 1 + +# Semi-torpedo chess +[semitorpedo:chess] +doubleStepRegionWhite = *2 *3 +doubleStepRegionBlack = *7 *6 + +# This variant is similar to Capablanca Chess, but with two archbishops and no chancellor piece. +[gemini:janus] +startFen = rnbaqkabnr/pppppppppp/10/10/10/10/PPPPPPPPPP/RNBAQKABNR w KQkq - 0 1 +archbishop = a +pieceToCharTable = PNBRQ............A...Kpnbrq............a...k +castlingKingsideFile = i +castlingQueensideFile = c + +# https://www.chessvariants.com/diffsetup.dir/pawnsonly.html +[pawnsonly] +pawn = p +queen = q +startFen = 8/pppppppp/8/8/8/8/PPPPPPPP/8 w - - 0 1 +promotionPieceTypes = q +castling = false +stalemateValue = loss +flagPiece = q +flagRegionWhite = *8 +flagRegionBlack = *1 + +#https://github.com/yagu0/vchess/blob/master/client/src/translations/rules/Pawnsking/en.pug +[pawnsking:pawnsonly] +commoner = k +flagPiece = * +startFen = 4k3/pppppppp/8/8/8/8/PPPPPPPP/4K3 w - - 0 1 +extinctionValue = loss +extinctionPieceTypes = k +stalemateValue = draw + +[tictactoe] +maxRank = 3 +maxFile = 3 +immobile = p +startFen = 3/3/3[PPPPPpppp] w - - 0 1 +pieceDrops = true +doubleStep = false +castling = false +stalemateValue = draw +immobilityIllegal = false +connectN = 3 + +[cfour] +maxRank = 6 +maxFile = 7 +immobile = p +startFen = 7/7/7/7/7/7[PPPPPPPPPPPPPPPPPPPPPppppppppppppppppppppp] w - - 0 1 +pieceDrops = true +enclosingDrop = top +doubleStep = false +castling = false +stalemateValue = draw +immobilityIllegal = false +connectN = 4 +nMoveRule = 0 + +[flipello6:flipello] +maxRank = 6 +maxFile = 6 +startFen = 6/6/2pP2/2Pp2/6/6[PPPPPPPPPPPPPPPPPPPPPPpppppppppppppppppppppp] w 0 1 +enclosingDropStart = c3 d3 c4 d4 + +[grandhouse:grand] +startFen = r8r/1nbqkcabn1/pppppppppp/10/10/10/10/PPPPPPPPPP/1NBQKCABN1/R8R[] w - - 0 1 +pieceDrops = true +capturesToHand = true + +[shogun:crazyhouse] +variantTemplate = shogi +pieceToCharTable = PNBR.F.....++++.+Kpnbr.f.....++++.+k +pocketSize = 8 +startFen = rnb+fkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNB+FKBNR[] w KQkq - 0 1 +commoner = c +centaur = g +archbishop = a +chancellor = m +fers = f +promotionRegionWhite = *6 *7 *8 +promotionRegionBlack = *3 *2 *1 +promotionLimit = g:1 a:1 m:1 q:1 +promotionPieceTypes = - +promotedPieceType = p:c n:g b:a r:m f:q +mandatoryPawnPromotion = false +firstRankPawnDrops = true +promotionZonePawnDrops = true +dropRegionWhite = *1 *2 *3 *4 *5 +dropRegionBlack = *4 *5 *6 *7 *8 +immobilityIllegal = true + +#https://www.chess.com/variants/caught-in-a-snag +[caught-in-a-snag:chess] +pieceToCharTable = P..........GUFW......Mp..........gufw......m +maxRank = 6 +pawnTypes = p +fers = f +horse = u +wazir = w +centaur = m +king = g:gBgR +customPiece1 = p:fmWfcF +startFen = fuwmgwuf/pppppppp/8/8/PPPPPPPP/FUWGMWUF w - - 0 1 +stalemateValue = win +promotionPawnTypes = - + +#https://www.chess.com/variants/minihouse +[minihouse:crazyhouse] +maxRank = 6 +maxFile = 6 +startFen = 2bnrk/5p/6/6/P5/KRNB2 +promotionPieceTypes = nbr +promotionRegionWhite = *6 +doubleStep = false + +#https://www.chess.com/variants/rookmate +[rookmate:3check] +#in this variant, one rook is royal, one isn't +#to define this, the royal rook will be a king(K) with custom moves, and the non-royal king a commoner(C) +pieceToCharTable = PNBRQC...............Kpnbrqc...............k +startFen = rnbqcbnk/pppppppp/8/8/8/8/PPPPPPPP/RNBQCBNK w 7+7 0 1 +king = k:R +commoner = c +extinctionPieceCount = 1 +extinctionValue = loss +extinctionPieceTypes = * + +# Asymmetric variant with one army using pieces that move like knights but attack like other pieces (kniroo and knibis) +[orda:chess] +pieceToCharTable = PNBRQ..AH...........LKp...q..ah.y.........lk +centaur = h +knibis = a +kniroo = l +silver = y +promotionPieceTypes = qh +startFen = lhaykahl/8/pppppppp/8/8/8/PPPPPPPP/RNBQKBNR w KQ - 0 1 +flagPiece = k +flagRegionWhite = *8 +flagRegionBlack = *1 + +# Ordamirror +# https://vchess.club/#/variants/Ordamirror +[ordamirror:chess] +pieceToCharTable = P...Q..AH.F.........LKp...q..ah.f.........lk +centaur = h +knibis = a +kniroo = l +customPiece1 = f:mQcN +promotionPieceTypes = lhaf +startFen = lhafkahl/8/pppppppp/8/8/PPPPPPPP/8/LHAFKAHL w - - 0 1 +flagPiece = k +flagRegionWhite = *8 +flagRegionBlack = *1 + +# Hybrid variant of Gothic-chess and crazyhouse, using Capablanca as a template +[gothhouse:capablanca] +startFen = rnbqckabnr/pppppppppp/10/10/10/10/PPPPPPPPPP/RNBQCKABNR[] w KQkq - 0 1 +pieceDrops = true +capturesToHand = true + +# Synochess +# https://www.pychess.org/variant/synochess +[synochess:pocketknight] +pieceToCharTable = PNBRQAE...SCH........Kpnbrqae...sch........k +pocketSize = 8 +janggiCannon = c +soldier = s +horse = h +fersAlfil = e +commoner = a +stalemateValue = loss +perpetualCheckIllegal = true +startFen = rneakenr/8/1c4c1/1ss2ss1/8/8/PPPPPPPP/RNBQKBNR[ss] w KQ - 0 1 +flyingGeneral = true +capturesToHand = false +dropRegionBlack = *5 +flagPiece = k +flagRegionWhite = *8 +flagRegionBlack = *1 + +# Capture chess +# https://vchess.club/#/variants/Capture +[capture:chess] +mustCapture = true + +# Double Army chess +# https://vchess.club/#/variants/Doublearmy +[doublearmy:chess] +pieceToCharTable = PNBRQ.....C...........Kpnbrq.....c...........k +commoner = c +startFen = rnbqkbnr/pppppppp/rnbqcbnr/pppppppp/PPPPPPPP/RNBQCBNR/PPPPPPPP/RNBQKBNR w KQkq - 0 1 + +# Pawn Massacre chess +# https://vchess.club/#/variants/Pawnmassacre +[pawnsmassacre:chess] +startFen = RNBKQBNR/pppppppp/8/8/8/8/PPPPPPPP/rnbkqbnr w - - 0 1 + +# Screen chess (Below version assumes 1 drop per turn instead of the whole blind setup as in vchess) +# https://vchess.club/#/variants/Screen +[screen:placement] +dropNoDoubled = p +startFen = 8/8/8/8/8/8/8/8[KQRRBBNNPPPPPPPPkqrrbbnnpppppppp] w - - 0 1 +dropRegionWhite = *1 *2 *3 *4 +dropRegionBlack = *8 *7 *6 *5 + +# Crossing chess +# https://vchess.club/#/variants/Crossing +[crossing:kingofthehill] +flagRegionWhite = *5 +flagRegionBlack = *4 + +# 4x5 Chess +# https://greenchess.net/rules.php?v=4x5-chess --> Solved draw +[4x5chess:gardner] +maxRank = 5 +maxFile = d +startFen = rnbk/pppp/4/PPPP/RNBK w - - 0 1 + +# 4x6 Chess +# https://greenchess.net/rules.php?v=4x6-chess --> Solved draw +[4x6chess:gardner] +maxRank = 6 +maxFile = d +promotionRegionWhite = *6 +promotionRegionBlack = *1 +startFen = rnbk/pppp/4/4/PPPP/RNBK w - - 0 1 + +# 5x6 chess +# https://greenchess.net/rules.php?v=5x6-chess +[5x6chess:gardner] +maxRank = 6 +maxFile = e +promotionRegionWhite = *6 +promotionRegionBlack = *1 +startFen = rnbqk/ppppp/5/5/PPPPP/RNBQK w - - 0 1 + +# Active chess +# https://greenchess.net/rules.php?v=active +[active:chess] +maxFile = i +startFen = rnbkqbnrq/ppppppppp/9/9/9/9/PPPPPPPPP/RNBKQBNRQ w KQkq - 0 1 + +# Checkless 6x6 Atomic +[6x6atom:nocheckatomic] +extinctionPseudoRoyal = true +maxRank = 6 +maxFile = f +promotionRegionWhite = *6 +promotionRegionBlack = *1 +doubleStep = false +startFen = rbqkbr/pppppp/6/6/PPPPPP/RBQKBR w - - 0 1 + +# Advanced Pawn chess +# https://greenchess.net/rules.php?v=advanced-pawn +[advancedpawn:chess] +doubleStep = false +startFen = rnbqkbnr/8/pppppppp/8/8/PPPPPPPP/8/RNBQKBNR w KQkq - 0 1 + +# Capture-all Chess +# https://greenchess.net/rules.php?v=capture-all +[captureall:extinction] +extinctionPieceTypes = * + +# Corner Rook Chess +# https://greenchess.net/rules.php?v=corner-rook +[cornerrook:chess] +doubleStep = false +castling = false +startFen = r6r/1nbqkbn1/pppppppp/8/8/PPPPPPPP/1NBQKBN1/R6R w - - 0 1 + +# Diana Chess +# https://greenchess.net/rules.php?v=diana +[diana:losalamos] +pieceToCharTable = PNBRQ................Kpnbrq................k +bishop = b +promotionPieceTypes = rbn +castling = true +castlingKingsideFile = e +castlingQueensideFile = b +startFen = rbnkbr/pppppp/6/6/PPPPPP/RBNKBR w KQkq - 0 1 + +# Microchess +# https://greenchess.net/rules.php?v=microchess +[microchess:gardner] +maxRank = 5 +maxFile = d +startFen = rbnk/p3/4/3P/RBNK w - - 0 1 + +# Empire Chess +# https://vchess.club/#/variants/Empire +[empire:chess] +pieceToCharTable = PNBRQ.....ST.C.D.E...Kpnbrq.....st.c.d.e...k +customPiece1 = e:mQcN +customPiece2 = c:mQcB +customPiece3 = t:mQcR +customPiece4 = d:mQcK +soldier = s +promotionPieceTypes = q +startFen = rnbqkbnr/pppppppp/8/8/8/PPPSSPPP/8/TECDKCET w kq - 0 1 +stalemateValue = loss +nFoldValue = loss +flagPiece = k +flagRegionWhite = *8 +flagRegionBlack = *1 +flyingGeneral = true + +# Shinobi Chess +# https://vchess.club/#/variants/Shinobi +[shinobi:chess] +variantTemplate = shogi +pieceToCharTable = PNBRQ.DJMLH.....CKpnbrq.djmlh.....ck +pocketSize = 8 +commoner = c +bers = d +archbishop = j +fers = m +shogiKnight = h +lance = l +promotionRegionWhite = *7 *8 +promotionRegionBlack = *2 *1 +promotionPieceTypes = - +promotedPieceType = p:c m:b h:n l:r +mandatoryPiecePromotion = true +stalemateValue = loss +perpetualCheckIllegal = true +startFen = rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/LH1CK1HL[LHMMDJ] w kq - 0 1 +pieceDrops = true +dropRegionWhite = *1 *2 *3 *4 +dropRegionBlack = *5 *6 *7 *8 +immobilityIllegal = true +flagPiece = k +flagRegionWhite = *8 +flagRegionBlack = *1 + +# Wildebeest +# https://vchess.club/#/variants/Wildebeest +# Limitations: No flexible castling, no pawn triple steps +[wildebeest:chess] +maxRank = 10 +maxFile = k +customPiece1 = c:C +customPiece2 = w:NC +doubleStepRegionWhite = *2 *3 +doubleStepRegionBlack = *9 *8 +tripleStepRegionWhite = *2 +tripleStepRegionBlack = *9 +pieceToCharTable = PNBRQ.......C....WKpnbrq.......c....wk +startFen = rnccwkqbbnr/ppppppppppp/11/11/11/11/11/11/PPPPPPPPPPP/RNBBQKWCCNR w KQkq - 0 1 +promotionPieceTypes = qw +promotionRegionWhite = *9 *10 +promotionRegionBlack = *2 *1 +mandatoryPawnPromotion = false +castling = false + +# Pandemonium +# A variant that combines drops and powerful pieces, and there is no draw +# https://www.chessvariants.com/rules/pandemonium +[pandemonium] +variantTemplate = shogi +pieceToCharTable = PNBRFSA.UV.+++++++.++Kpnbrfsa.uv.+++++++.++k +maxFile = 9 +maxRank = 9 +pocketSize = 9 +startFen = rnbsksbnr/2a1f1u2/p1p1p1p1p/4v4/9/4V4/P1P1P1P1P/2U1F1A2/RNBSKSBNR[] w - - 0 1 +customPiece1 = o:NA +customPiece2 = s:WF +customPiece3 = u:D +customPiece4 = w:DWF +castling = false +pieceDrops = true +capturesToHand = true +immobilityIllegal = true +soldier = p +knight = n +bishop = b +rook = r +king = k +queen = q +commoner = g +dragonHorse = h +bers = d +alfil = a +archbishop = c +chancellor = m +fers = f +wazir = v +centaur = t +promotionRegionWhite = *7 *8 *9 +promotionRegionBlack = *3 *2 *1 +promotedPieceType = p:g n:o b:h r:d a:c v:m f:q s:w u:t +doubleStep = false +perpetualCheckIllegal = true +nMoveRule = 0 +nFoldValue = loss +stalemateValue = loss +flagPiece = k +flagRegionWhite = *9 +flagRegionBlack = *1 + +# 5x5 breakthrough +[breakthrough5:breakthrough] +maxFile = 5 +maxRank = 5 +startFen = ppppp/ppppp/5/PPPPP/PPPPP w 0 1 +flagRegionWhite = *5 +flagRegionBlack = *1 + +# 6x6 breakthrough +[breakthrough6:breakthrough] +maxFile = 6 +maxRank = 6 +startFen = pppppp/pppppp/6/6/PPPPPP/PPPPPP w 0 1 +flagRegionWhite = *6 +flagRegionBlack = *1 + +# 7x7 breakthrough +[breakthrough7:breakthrough] +maxFile = 7 +maxRank = 7 +startFen = ppppppp/ppppppp/7/7/7/PPPPPPP/PPPPPPP w 0 1 +flagRegionWhite = *7 +flagRegionBlack = *1 + +# Mansindam (Pantheon tale) +# A variant that combines drop rule and powerful pieces, and there is no draw +[mansindam] +variantTemplate = shogi +pieceToCharTable = PNBR.Q.CMA.++++...++Kpnbr.q.cma.++++...++k +maxFile = 9 +maxRank = 9 +pocketSize = 8 +startFen = rnbakqcnm/9/ppppppppp/9/9/9/PPPPPPPPP/9/MNCQKABNR[] w - - 0 1 +pieceDrops = true +capturesToHand = true +shogiPawn = p +knight = n +bishop = b +rook = r +queen = q +archbishop = c +chancellor = m +amazon = a +king = k +commoner = g +centaur = e +dragonHorse = h +bers = t +customPiece1 = i:BNW +customPiece2 = s:RNF +promotionRegionWhite = *7 *8 *9 +promotionRegionBlack = *3 *2 *1 +doubleStep = false +castling = false +promotedPieceType = p:g n:e b:h r:t c:i m:s +dropNoDoubled = p +stalemateValue = loss +nMoveRule = 0 +nFoldValue = loss +flagPiece = k +flagRegionWhite = *9 +flagRegionBlack = *1 +immobilityIllegal = true +mandatoryPiecePromotion = true + +# Cross derby +# Pawns promote to a knight on the 8th rank. Checkmate or stalemate your opponent to win the game. +# https://www.chess.com/variants/cross-derby +[crossderby:chess] +pieceToCharTable = PN...................Kpn...................k +queen = - +rook = - +bishop = - +startFen = n*nnnn*k/**pppp**/pp4pp/8/8/PP4PP/**PPPP**/K*NNNN*N w - - 0 1 +stalemateValue = loss + +# Elimination chess +# Eliminate all the opponents' pieces but one to win. No checkmates, capturing is compulsory +# https://www.chess.com/variants/elimination-chess +[elimination:giveaway] +extinctionValue = loss +stalemateValue = loss +extinctionPieceCount = 1 + +# Hybrid variant of atomic and giveaway +[antiatomic:giveaway] +blastOnCapture = true +castling = false +extinctionOpponentPieceCount = 1 + +# Hybrid of atomic and Crazyhouse. --> SOLVED +[atomiczh:atomic] +dropChecks = false +pieceDrops = true +capturesToHand = true +pocketSize = 6 +castling = false + +# Chak +# Variant invented by Couch Tomato and inspired in the Mayan civilization +# https://www.pychess.org/variants/chak +[chak] +maxRank = 9 +maxFile = 9 +rook = r +knight = v +centaur = j +immobile = o +customPiece1 = s:FvW +customPiece2 = q:pQ +customPiece3 = d:mQ2cQ2 +customPiece4 = p:fsmWfceF +customPiece5 = k:WF +customPiece6 = w:FvW +startFen = rvsqkjsvr/4o4/p1p1p1p1p/9/9/9/P1P1P1P1P/4O4/RVSJKQSVR w - - 0 1 +mobilityRegionWhiteCustomPiece6 = *5 *6 *7 *8 *9 +mobilityRegionWhiteCustomPiece3 = *5 *6 *7 *8 *9 +mobilityRegionBlackCustomPiece6 = *1 *2 *3 *4 *5 +mobilityRegionBlackCustomPiece3 = *1 *2 *3 *4 *5 +promotionRegionWhite = *5 *6 *7 *8 *9 +promotionRegionBlack = *5 *4 *3 *2 *1 +promotionPieceTypes = - +mandatoryPiecePromotion = true +promotedPieceType = p:w k:d +extinctionValue = loss +extinctionPieceTypes = kd +extinctionPseudoRoyal = true +flagPiece = d +flagRegionWhite = e8 +flagRegionBlack = e2 +nMoveRule = 50 +nFoldRule = 3 +nFoldValue = draw +stalemateValue = loss + +# Chennis +# Variant invented by Couch Tomato, tennis-themed and inspired in Kyoto Shogi +# https://www.pychess.org/variants/chennis +[chennis] +maxRank = 7 +maxFile = 7 +mobilityRegionWhiteKing = b1 c1 d1 e1 f1 b2 c2 d2 e2 f2 b3 c3 d3 e3 f3 b4 c4 d4 e4 f4 +mobilityRegionBlackKing = b4 c4 d4 e4 f4 b5 c5 d5 e5 f5 b6 c6 d6 e6 f6 b7 c7 d7 e7 f7 +customPiece1 = p:fmWfceF +cannon = c +commoner = m +fers = f +soldier = s +king = k +bishop = b +knight = n +rook = r +promotionPieceTypes = - +promotedPieceType = p:r f:c s:b m:n +promotionRegionWhite = *1 *2 *3 *4 *5 *6 *7 +promotionRegionBlack = *7 *6 *5 *4 *3 *2 *1 +startFen = 1fkm3/1p1s3/7/7/7/3S1P1/3MKF1[] w - 0 1 +pieceDrops = true +capturesToHand = true +pieceDemotion = true +mandatoryPiecePromotion = true +dropPromoted = true +castling = false +stalemateValue = loss + +# Gorogoro+ +# 5x6 Shogi +# https://www.pychess.org/variants/gorogoroplus +[gorogoroplus:gorogoro] +startFen = sgkgs/5/1ppp1/1PPP1/5/SGKGS[LNln] w 0 1 +lance = l +shogiKnight = n +promotedPieceType = l:g n:g +pieceToCharTable = P....SLN.G.+....+Kp....sln.g.+....+k + +# Dragonfly +# Simplified Crazyhouse. Two rules have been removed from the original definition (Removing captured pawns and special pawn promotion). Credits to Procyon for the definition. +# https://en.wikipedia.org/wiki/Dragonfly_(chess_variant) +[dragonfly:chess] +maxFile = 7 +maxRank = 7 +pocketSize = 7 +castlingKingsideFile = f +castlingQueensideFile = b +pieceDrops = true +capturesToHand = true +promotionRegionWhite = *7 +promotionRegionBlack = *1 +promotionPieceTypes = nbr +doubleStep = false +dropNoDoubled = p +dropNoDoubledCount = 0 +startFen = rbbknnr/ppppppp/7/7/7/PPPPPPP/RBBKNNR[] w - - 0 1 + +# Kamikaze Rooks +# The objective of the game is to lose both rooks. Credits to Procyon for the definition. +# Variant defined in Pychess discord and previously playable with Chessboi bot. +[kamikazerooks:chess] +extinctionValue = win +extinctionPieceTypes = r + +# Mounted +# Variant invented by Procyon. Credits to him for the definition. +# Variant defined in Pychess discord and previously playable with Chessboi bot. +[mounted] +maxRank = 7 +maxFile = 7 +pieceDrops = true +capturesToHand = true +stalemateValue = loss +nFoldValue = loss +extinctionValue = loss +extinctionPseudoRoyal = true +extinctionPieceTypes = k +flagPiece = k +flagRegionWhite = *7 +flagRegionBlack = *1 +centaur = k +bishop = b +rook = r +cannon = c +customPiece1 = a:mBcpB +customPiece2 = s:FsfW +startFen = 1cbkra1/2s1s2/7/7/7/2S1S2/1CBKRA1 w - - 0 1 + +# Racing Chess +# Win by campmate. No checks allowed. +# Variant defined in Pychess discord and previously playable with Chessboi bot. +[racingchess:chess] +flagPiece = k +flagRegionWhite = *8 +flagRegionBlack = *1 +flagMove = true +checking = false + +# Shinobi Mirror +# Symmetric version of Shinobi. +# Variant defined in Pychess discord and previously playable with Chessboi bot. +[shinobimirror:crazyhouse] +commoner = c +bers = d +archbishop = j +fers = m +shogiKnight = h +lance = l +pieceToCharTable = P.....DJMLH.....CKp.....djmlh.....ck +promotionRegionWhite = *7 *8 +promotionRegionBlack = *2 *1 +promotionPieceTypes = - +promotedPieceType = p:c m:b h:n l:r +mandatoryPiecePromotion = true +stalemateValue = loss +perpetualCheckIllegal = true +startFen = lh1ck1hl/pppppppp/8/8/8/8/PPPPPPPP/LH1CK1HL[LHMMDJlhmmdj] w - - 0 1 +capturesToHand = false +dropRegionWhite = *1 *2 *3 *4 +dropRegionBlack = *5 *6 *7 *8 +immobilityIllegal = true +flagPiece = k +flagRegionWhite = *8 +flagRegionBlack = *1 +castling = false + +# Two Kings v2 +# Slightly different implementation of Two Kings chess. +# Variant defined in Pychess discord and previously playable with Chessboi bot. +[twokings2:chess] +king = - +commoner = k +extinctionValue = loss +extinctionPseudoRoyal = true +extinctionPieceTypes = k +extinctionPieceCount = 1 +startFen = rnbkkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBKKBNR w - - 0 1 + +# Anti-Antichess +# Lose at anti-chess to win at anti-antichess. +# Variant defined in Liantichess website. Credits to SriMethan for the definition. +[antiantichess:giveaway] +extinctionValue = loss +stalemateValue = loss +castling = false + +# Anti-Minishogi +# Hybrid of antichess and minishogi. This might look like a coffee variant, but it isn't. +# Variant defined in Liantichess website. Credits to SriMethan for the definition. +[antiminishogi:minishogi] +king = - +commoner = k +mustCapture = true +stalemateValue = win +extinctionValue = win +extinctionPieceTypes = * +extinctionPseudoRoyal = false + +# Anti-House +# Hybrid of antichess and zh. Antichess is the base variant. +# Variant defined in Liantichess website. Credits to SriMethan for the definition. +[antihouse:giveaway] +pieceDrops = true +capturesToHand = true +pocketSize = 6 +castling = false + +# Coffee-Race +# Hybrid of rk and antichess --> SOLVED +# Variant defined in Liantichess website. Credits to SriMethan for the definition. +[coffeerace:racingkings] +mustCapture = true + +# Epic Atomic +# Atomic with bigger board size and more pieces. +# Variant defined in Liatomic website. Credits to RaviharaV for the definition. +[epicatomic:atomic] +queen = q +bishop = b +knight = n +rook = r +pawn = p +promotionPieceTypes = b +blastOnCapture = true +castling = false +startFen = rnnbqkbnnr/pppppppppp/10/10/10/10/PPPPPPPPPP/RNNBQKBNNR w KQkq - 0 1 +maxRank = 8 +maxFile = 10 +chess960 = false +promotionRegionWhite = *6 *7 *8 *9 *10 +promotionRegionBlack = *5 *4 *3 *2 *1 +doubleStep = true +castling = false + +# Checkmateless chess +# Checks and checkmates can't be played. You have to get your enemy into a position where they are forced to make an illegal move (moving your king into check, checking, and checkmating your enemy are included as illegal moves) to win the variant. +# https://lichess.org/forum/team-variant-testers/checkmateless-chess +[checkmateless:chess] +checking = false +stalemateValue = loss + +# Sixth Rank chess +# Reach the sixth rank to win. +# lichess.org/forum/team-variant-testers/sixth-rank-chess +[sixthrank:chess] +flagPiece = p +flagRegionWhite = *6 +flagRegionBlack = *3 + +# Fullmoon chess +# Variant created by moonbay and inspired in Capablanca and Seirawan +# https://lichess.org/forum/team-variant-testers/fullmoon-chess-variant +[fullmoon:chess] +pieceToCharTable = PNBRQ..AC............Kpnbrq..ac............k +archbishop = a +chancellor = c +startFen = rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[CAca] w - - 0 1 +promotionPieceTypes = qcarbn +pieceDrops = true +dropRegionWhite = *1 +dropRegionBlack = *8 +castling = false +doubleStep = false +stalemateValue = loss + +# Crowded Xiangqi +# Chinese Chess on half the board (5 × 9 intersections). Pawns (soldiers) are set to display as archbishops (via pieceToCharTable), so that WinBoard/XBoard won't disappear them on the last rank. +# https://github.com/fairy-stockfish/Fairy-Stockfish/issues/180 +[crowdedxiangqi:xiangqi] +pieceToCharTable = .N.R.ABP.K.C...........n.r.abp.k.c.......... +maxRank = 5 +startFen = rnbakabnr/pcp1p1pcp/9/PCP1P1PCP/RNBAKABNR w - - 0 1 +mobilityRegionBlackKing = d3 e3 f3 d4 e4 f4 d5 e5 f5 +mobilityRegionBlackFers = d3 e3 f3 d4 e4 f4 d5 e5 f5 +mobilityRegionBlackElephant = *1 *2 *3 *4 *5 +soldierPromotionRank = 4 + +# Massacre +# Variant created by Andy Lewicki. Giveaway style but the board is full of pieces. Credits to Mtaktikos for the definition. +# https://brainking.com/en/GameRules?tp=128 +[massacre:giveaway] +commoner = - +queen = - +rook = - +bishop = - +knight = - +pawn = - +customPiece1 = q:cQ +customPiece2 = b:cB +customPiece3 = n:cN +customPiece4 = r:cR +startFen = rBbnbqNn/bQrnQRRr/rrqbNqQb/bBNqbNBN/bNRrrrBQ/QRQqRnNB/qBRnnqNQ/BBQnqnRR w - - 0 1 +stalemateValue = loss +extinctionValue = loss +extinctionPieceTypes = * +pieceToCharTable = .NBRQ..................nbrq................. + +# Backrank +# The objective of the game is to reach the last rank with any of your pieces. Credits to Procyon for the definition +# Variant defined in Pychess discord and previously playable with Chessboi bot. +[backrank:chess] +king = - +commoner = k +castlingKingPiece = k +extinctionValue = loss +extinctionPieceTypes = k +extinctionPseudoRoyal = true +flagPiece = k +promotionPieceTypes = - +promotedPieceType = q:k r:k b:k n:k p:k +mandatoryPiecePromotion = true +flagRegionWhite = *8 +flagRegionBlack = *1 + +# Stardust +# Variant invented by Couch Tomato, spaced-themed and inspired in Star Wars. +# Variant defined in Pychess discord and previously playable with Chessboi bot. +[stardust] +maxRank = 10 +maxFile = 9 +fers = t +immobile = p +gold = c +customPiece1 = x:mRcW +customPiece2 = d:FvW +customPiece3 = r:mQ2cW +customPiece4 = y:mRcD +customPiece5 = o:WcF +startFen = 3tot3/1p5p1/3XRX3/1d5d1/9/1p1t1t1p1/4d4/9/9/4C4[XXXXYYYYtttttttttd] w - - 0 1 +pieceDrops = true +dropRegionWhite = *1 *2 *3 d4 e4 f4 d5 e5 f5 d6 e6 f6 *7 d8 e8 f8 d9 e9 f9 d10 e10 f10 +dropRegionBlack = a4 b4 c4 g4 h4 i4 a5 b5 c5 g5 h5 i5 a6 b6 c6 g6 h6 i6 a8 b8 c8 g8 h8 i8 a9 b9 c9 g9 h9 i9 a10 b10 c10 g10 h10 i10 +castling = false +mobilityRegionWhiteCustomPiece3 = *1 *2 *3 *4 a5 c5 d5 e5 f5 g5 i5 *6 *7 *8 a9 c9 d9 e9 f9 g9 i9 *10 +# mobilityRegionBlackCustomPiece3 = b5 b9 h5 h9 +extinctionValue = loss +extinctionPieceTypes = rp +extinctionPseudoRoyal = true +flagPiece = r +flagRegionWhite = e2 +nMoveRule = 50 +nFoldRule = 3 +nFoldValue = draw +stalemateValue = loss + +# Cetus +# Variant invented by Couch Tomato, ocean-themed and inspired in whales and other marine mammals. +# Variant defined in Pychess discord and previously playable with Chessboi bot. +[cetus] +maxRank = 9 +maxFile = 9 +wazir = o +fers = k +customPiece1 = b:mQ3 +customPiece2 = n:mF +customPiece3 = p:mW +customPiece4 = h:mB3 +customPiece5 = s:mR3 +customPiece6 = d:mN +startFen = 9/9/9/1b7/9/9/9/2B6/9[PPNHSDOppnnhhssdo] +pieceDrops = true +extinctionValue = loss +extinctionPieceTypes = b +piecePromotionOnCapture = true +mandatoryPiecePromotion = true +pieceDemotion = true +promotedPieceType = o:k +castling = false +connectN = 5 +promotionRegionWhite = *1 *2 *3 *4 *5 *6 *7 *8 *9 +promotionRegionBlack = *9 *8 *7 *6 *5 *4 *3 *2 *1 + +# Chess vs Hoppel-Poppel +# Asymmetrical game, Chess army vs Hoppel Poppel army. Credits to Procyon for the definition. +# Variant defined in Pychess discord and previously playable with Chessboi bot. +[chessvshp:chess] +soldier = s +king = k +bishop = b +knight = n +rook = r +promotionPieceTypes = - +knibis = i +biskni = j +promotionPieceTypes = nbrqij +pieceToCharTable = PNBRQ.......IJ.......Kpnbrq.......ij.......k +startFen = rijqkjir/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 + +# Orda vs Empire +# Asymmetrical game, Empire army vs Orda army. Empire (as Black again), no Soldiers, 8 x Imperial Pawns but on the 3rd rank. Empire rules in place. Yurt is a mNcK. Credits to Procyon for the definition. +# Variant defined in Pychess discord and previously playable with Chessboi bot. +[ordavsempire:chess] +centaur = h +knibis = a +kniroo = l +customPiece1 = e:mQcN +customPiece2 = c:mQcB +customPiece3 = t:mQcR +customPiece4 = d:mQcK +customPiece5 = y:mNcK +promotionPieceTypes = qh +startFen = tecdkcet/8/pppppppp/8/8/PPPPPPPP/8/LHAYKAHL w - - 0 1 +stalemateValue = loss +nFoldValue = loss +flagPiece = k +flagRegionWhite = *8 +flagRegionBlack = *1 +flyingGeneral = true +pieceToCharTable = P...Q..AH..ECTDY....LKp...q..ah..ectdy....lk + +# Atomic duck chess +# Atomic + duck chess hybrid. +# Playable as a custom variant in chess.com +[atomicduck:atomic] +wallingRule = duck +stalemateValue = win + +#https://www.chessvariants.com/diffmove.dir/checkers.html +[forward:chess] +pieceToCharTable = PNBRQGE......S.F..LKpnbrqge......s.f..lk +#pieces move only forward. Not even sideways, so no castling. +castling = false +#I tried to pick letters that would remind people of the full piece. +startFen = lesfgsel/pppppppp/8/8/8/8/PPPPPPPP/LESFGSEL w KQkq - 0 1 +lance = l +customPiece1 = g:fFfW +customPiece2 = e:ffNfsN +customPiece3 = s:fB +customPiece4 = f:fBfR +promotedPieceType = l:r e:n s:b f:q g:k +#since the king changes forms, should be an extinction variant +extinctionValue = loss +extinctionPieceTypes = kg +extinctionPseudoRoyal = true + +[kono] +maxRank = 5 +maxFile = e +customPiece1 = p:mF +startFen = ppppp/p3p/5/P3P/PPPPP w - - 0 1 +flagPiece = p +flagRegionWhite = a5 b5 c5 d5 e5 a4 e4 +flagRegionBlack = a1 b1 c1 d1 e1 a2 e2 +flagPieceCount = 7 +flagPieceBlockedWin = true +nFoldRule = 0 +nMoveRule = 0 + +#https://www.chessvariants.com/large.dir/xhess.html +[xhess:chess] +pieceToCharTable = HNBRQ......CI........Khnbrq......ci........k +maxRank = 10 +maxFile = j +promotionRegionWhite = *10 +promotionPieceTypes = nbrqci +flagPiece = k +flagRegionWhite = *10 +flagRegionBlack = *1 +pawnTypes = h +cannon = c +customPiece1 = i:NN +customPiece2 = h:mfWcfFfhmnN +startFen = r8r/3nkqn3/hcb1ii1bch/1hhhhhhhh1/10/10/1HHHHHHHH1/HCB1II1BCH/3NKQN3/R8R +#technically not needed because of initial setup +##enPassantRegion = - +##castling = false + +#https://boardgamegeek.com/boardgame/32/buffalo-chess +[buffalo] +pieceToCharTable = B...D................Cb...d................c +maxRank = 7 +maxFile = k +customPiece1 = b:mfW +customPiece2 = c:K +customPiece3 = d:mQ +startFen = bbbbbbbbbbb/11/11/11/11/3DDCDD3/11 w - - 0 1 +flagPiece = b +flagRegionBlack = *1 +mobilityRegionWhiteCustomPiece2 = *2 *3 *4 *5 *6 +mobilityRegionWhiteCustomPiece3 = *2 *3 *4 *5 *6 +stalemateValue = loss + +#https://www.zillions-of-games.com/cgi-bin/zilligames/submissions.cgi?do=show;id=1596 +[knights-halma] +maxRank = 8 +maxFile = h +customPiece1 = n:mN +startFen = 5nnn/5nnn/5nnn/8/8/NNN5/NNN5/NNN5 w +flagPiece = n +flagRegionWhite = f8 g8 h8 f7 g7 h7 g6 g6 h8 +flagRegionBlack = a3 b3 c3 a2 b2 c2 a1 b1 c1 +flagPieceCount = 9 +stalemateValue = loss +nFoldRule = 2 +nFoldValue = loss +nMoveRule = 0 + +#https://www.zillions-of-games.com/cgi-bin/zilligames/submissions.cgi/76202?do=show;id=83 +#pawns can be caught in the blast in this one +[allexplodeatomic:nocheckatomic] +pawn = - +customPiece1 = p:fmWfceFifmnD +pawnTypes = p + +#https://en.wikipedia.org/wiki/Hexapawn +#black wins +[hexapawn] +maxRank = 3 +maxFile = c +startFen = ppp/3/PPP w +flagPiece = p +flagRegionWhite = *3 +flagRegionBlack = *1 +customPiece1 = p:mfWcfF +stalemateValue = loss + +#https://ludii.games/details.php?keyword=1D%20Chess +#white wins +[1d-chess:chess] +maxRank = 1 +maxFile = h +customPiece1 = n:D +startFen = KNR2rnk w + +#https://ludii.games/details.php?keyword=T%27uk%20T%27uk +#The implementation on Ludii doesn't match the description. Ludii implements a Wazir, while the translated description is of a forwards and backwards (non-capturing) Rook. +#black wins +[tuktuk] +maxRank = 6 +maxFile = f +customPiece1 = w:mvR +startFen = wwwwww/6/6/6/6/WWWWWW w +stalemateValue = loss +nFoldRule=0 +nMoveRule=0 + +#https://ludii.games/details.php?keyword=Aralzaa +#possibly missing rules +#draw +[aralzaa] +maxRank = 3 +maxFile = c +customPiece1 = n:mN +startFen = nnn/3/NNN w +flagPiece = n +flagRegionWhite = *3 +flagRegionBlack = *1 +flagPieceCount = 3 +nFoldRule=0 +nMoveRule=0 + +#https://ludii.games/variantDetails.php?keyword=Bajr&variant=1036 +#Some ludii players speculate that it might be missing rules: +#https://ludii.games/forums/showthread.php?tid=762&highlight=bajr +#https://ludii.games/forums/showthread.php?tid=364&highlight=bajr +#but after looking at the translated description, the answer is, the pieces can't move backwards (they have the 3 moves that advance toward the goal). +#white wins +[bajr] +maxRank = 6 +maxFile = f +customPiece1 = p:mfrWmrfF +startFen = 4pp/5p/6/6/P5/PP4 w +flagPiece = p +flagRegionWhite = e6 f6 f5 +flagRegionBlack = a1 a2 b1 +flagPieceCount = 3 +nMoveRule=0 + +#https://ludii.games/details.php?keyword=Lewthwaite%27s%20Game +#black wins +[lewthwaite] +maxRank = 5 +maxFile = e +customPiece1 = w:mW +startFen = wWwWw/WwWwW/wW1Ww/WwWwW/wWwWw w +stalemateValue = loss +nMoveRule = 0 + +[picaria:tictactoe] +#Known under multiple names but using the name from Wikipedia. +#https://ludii.games/details.php?keyword=Picaria https://en.wikipedia.org/wiki/Picaria +#https://ludii.games/details.php?keyword=Les%20Pendus +#https://ludii.games/details.php?keyword=Wure%20Dune +#https://ludii.games/details.php?keyword=Djara-Badakh +#https://ludii.games/details.php?keyword=Tuk%20Tak +customPiece1 = p:mKmNmAmD +#moves anywhere on the board, KNAD is a list of all possible moves on a 3x3 +startFen = 3/3/3[PPPppp] w - - 0 1 +mustDrop = true +nMoveRule = 0 +nFoldRule = 0 + +[nineholes:picaria] +#https://ludii.games/details.php?keyword=Nine%20Holes https://en.wikipedia.org/wiki/Nine_Holes +#https://ludii.games/details.php?keyword=San-Noku-Narabe +#https://ludii.games/details.php?keyword=Driesticken +#https://ludii.games/details.php?keyword=Akidada +#https://ludii.games/details.php?keyword=Dris%20at-Talata +connectDiagonal = false + +#https://ludii.games/details.php?keyword=Tic-Tac-Chess +[tictacchess:picaria] +startFen = 3/3/3[KQRkqr] w - - 0 1 +customPiece1 = k:mKmgQ +customPiece2 = q:mQmgQ +customPiece3 = r:mRmgQ + +[asimplegame] +#https://ludii.games/details.php?keyword=A%20Simple%20Game +maxRank = 4 +maxFile = d +customPiece1 = p:mW +connectN = 3 +startFen = pPpP/4/4/PpPp w - - 0 1 +nMoveRule = 0 +#according to Jan's own Zillions file, any repetition is a draw +nFoldRule = 2 + +[alapo:chess] +#https://www.chessvariants.org/small.dir/alapo.html +pieceToCharTable = ..BRQ.........FW.....K..brq.........fw.....k +maxRank = 6 +maxFile = f +wazir = w +fers = f +king = - +commoner = k +startFen = rbqqbr/wfkkfw/6/6/WFKKFW/RBQQBR +flagRegionWhite = *6 +flagRegionBlack = *1 +flagPieceSafe = true +flagMove = true +stalemateValue = loss +nMoveRule = 0 +nFoldRule = 0 + +[rooksquare:chess] +#https://www.chessvariants.com/diffobjective.dir/rooksquare.html +flagRegionWhite = a8 h8 +flagRegionBlack = a1 h1 + +[allqueenschess] +#https://boardgamegeek.com/boardgame/34948/all-queens-chess +maxRank = 5 +maxFile = e +startFen = qQqQq/5/Q3q/5/QqQqQ +customPiece1 = q:mQ +connectN = 4 +nMoveRule = 0 +nFoldRule = 0 + +#https://www.ludii.games/details.php?keyword=Capture%20the%20Queen +[capturethequeen] +startFen = 3q4/8/8/8/8/8/8/1QQ1QQ2 +queen = q +extinctionPieceTypes = q +nFoldRule = 0 +#default #nMoveRule = 50 +materialCounting = blackdrawodds + +#https://www.zillions-of-games.com/cgi-bin/zilligames/submissions.cgi?do=show;id=732 +[river:chess] +king = - +commoner = k +castlingKingPiece = k +flagPiece = * +flagRegionWhite = *8 +flagRegionBlack = *1 + +#https://www.zillions-of-games.com/cgi-bin/zilligames/submissions.cgi?do=show;id=237 +[cowboys:amazons] +maxRank = 7 +maxFile = g +customPiece1 = n:mN +pieceToCharTable = .N.....................n.................... +startFen = 2n1n2/7/n5n/7/N5N/7/2N1N2 w - - 0 1 + +#https://www.zillions-of-games.com/cgi-bin/zilligames/submissions.cgi?do=show;id=1813 +[dragon-chess:chess] +customPiece1 = b:BK +customPiece2 = n:NK +customPiece3 = p:KifmnD +customPiece4 = r:RK +castlingRookPieces = r +pawnTypes = p + +[annexation:flipello] +maxRank = 10 +maxFile = 10 +startFen = ***4***/***4***/***4***/10/4pP4/4Pp4/10/***4***/***4***/***4***[PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPpppppppppppppppppppppppppppppp] w 0 1 + +#https://www.chessvariants.com/shogivariants.dir/cannonshogi.html +[cannonshogi:shogi] +dropNoDoubled = - +shogiPawnDropMateIllegal = false +soldier = p +cannon = u +customPiece1 = a:pR +customPiece2 = c:mBcpB +customPiece3 = i:pB +customPiece4 = w:mRpRmFpB2 +customPiece5 = f:mBpBmWpR2 +promotedPieceType = u:w a:w c:f i:f p:g +startFen = lnsgkgsnl/1rci1uab1/p1p1p1p1p/9/9/9/P1P1P1P1P/1BAU1ICR1/LNSGKGSNL[-] w 0 1 + +#https://www.chessvariants.com/difftaking.dir/deadsquare.html +[nuclear:atomic] +#define a piece that looks exactly like a pawn, but is not one. Takes care of major differences: +#1. Pawns can be petrified. +#2. Pawns can be destroyed by explosions. +pawn = - +customPiece1 = p:fmWfceFifmnD +pawnTypes = p +petrifyOnCaptureTypes = pnbrq +enPassantRegion = - + +#https://en.wikipedia.org/wiki/Nim +#FSF can be used to analyse Nim. The number of empty squares between the pieces is the number of items in the stack, +#the number in the custom piece is the number of items you can nim. This is the popular 1-3-5-7 stacks layout. +[nim] +maxRank = 9 +maxFile = d +#if the Nim variant has special rules, ie.nimming<=3 pieces then: +#customPiece1 = p:mfR3 +customPiece1 = p:mfR +startFen = 3p/4/2p1/4/1p2/4/p3/4/PPPP +stalemateValue = loss + +#https://www.ludii.games/details.php?keyword=Roll-Ing%20to%20Four +[roll-ing-to-four] +maxRank = 10 +maxFile = d +customPiece1 = p:mfFmfW +startFen = 1ppp/4/4/4/1PPP/ppp1/4/4/4/PPP1 +connectN = 4 + +#https://www.ludii.games/details.php?keyword=Quad%20Wrangle +[quadwrangle:ataxx] +#different sources give different info on whether it is a 7x7 or 8x8 board. +#7x7 is below +maxRank = 8 +maxFile = 8 +startFen = 1PPPPPP1/p6P/p6P/p6P/p6P/p6P/p6P/1pppppp1 +customPiece1 = p:mQ +flipEnclosedPieces = quadwrangle +#override rule from ataxx since drops can be done freely +enclosingDrop = none + +[quadwrangle7x7:quadwrangle] +maxRank = 7 +maxFile = 7 +startFen = 1PPPPP1/p5P/p5P/p5P/p5P/p5P/1ppppp1 + +#https://www.ludii.games/details.php?keyword=Crusade +[crusade:quadwrangle] +startFen = PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP +customPiece1 = p:cK +flipEnclosedPieces = ataxx +pieceDrops = false +passOnStalemate = false + +#https://www.ludii.games/details.php?keyword=Snort +#also known as Cats & Dogs +[snort:ataxx] +immobile = p +startFen = 8/8/8/8/8/8/8/8 +enclosingDrop = snort +flipEnclosedPieces = none +maxRank = 8 +maxFile = 8 +passOnStalemate = false +stalematePieceCount = false + +#https://www.chess.com/variants/blackletter-chess +[blackletter:chess] +pieceToCharTable = PNBRQ.......E...M...HKpnbrq.......e...m...hk +maxRank = 10 +maxFile = 9 +promotionRegionWhite = *10 +promotionRegionBlack = *1 +archbishop = e +chancellor = m +centaur = h +startFen = 1ebq1meb1/rn2k2nr/ppppppppp/9/9/9/9/PPPPPPPPP/RN2K2NR/1EBQ1MEB1[HHhh] w KQkq - 0 1 +promotionPieceTypes = behmnqr +doubleStepRegionWhite = *3 +doubleStepRegionBlack = *8 +pieceDrops = true +castlingRank = 2 + +#https://www.chessvariants.com/winning.dir/castle.html +[castle:chess] +castlingWins = q + +#https://github.com/yagu0/vchess/blob/master/client/src/translations/rules/Squatter1/en.pug +[squatter:chess] +flagRegionWhite = *8 +flagRegionBlack = *1 +flagPieceSafe = true + +[opposite-castling:chess] +oppositeCastling = true + +# A Kyoto Shogi variant with a left/right theme. +[gethenian] +maxRank = 7 +maxFile = 7 +king = - +customPiece1 = k:K +customPiece2 = q:mW +customPiece3 = b:lfrbB +customPiece4 = i:rflbB +customPiece5 = r:lrR +customPiece6 = n:hlN +customPiece7 = t:hrN +customPiece8 = m:WfF +customPiece9 = s:FfW +startFen = 2ikb2/2mnm2/7/7/7/2MNM2/2B+KI2[] w - - 0 1 +promotionPieceTypes = - +promotedPieceType = k:q b:r i:r n:t m:s +promotionRegionWhite = *1 *2 *3 *4 *5 *6 *7 +promotionRegionBlack = *7 *6 *5 *4 *3 *2 *1 +mandatoryPiecePromotion = true +pieceDemotion = true +pieceDrops = true +capturesToHand = true +dropPromoted = true +immobilityIllegal = false +extinctionValue = loss +extinctionPieceTypes = kq +extinctionPseudoRoyal = true +stalemateValue = loss + +#https://www.ludii.games/details.php?keyword=Gale +[gale:snort] +maxRank = 9 +maxFile = 9 +startFen = 1p1p1p1p1/P1P1P1P1P/1p1p1p1p1/P1P1P1P1P/1p1p1p1p1/P1P1P1P1P/1p1p1p1p1/P1P1P1P1P/1p1p1p1p1 +enclosingDrop = none +connectRegion1White = a* +connectRegion2White = i* +connectRegion1Black = *1 +connectRegion2Black = *9 +#should be impossible anyway +connectDiagonal = false + +#https://www.chessvariants.com/boardrules.dir/atlantis.html +[atlantis:chess] +wallingRule = edge +wallOrMove = true + +#https://www.chessvariants.com/rules/ajax-orthodox-chess +[ajax-orthodox:chess] +pieceToCharTable = PNBRQ.............MKpnbrq.............mk +customPiece1 = r:RmF +customPiece2 = n:NmK +customPiece3 = b:BmW +customPiece4 = m:KAD +promotionPieceTypes = mqnbr +castling = true +castlingRookPieces = r +startFen = rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[MMmm] w KQkq - 0 1 +pieceDrops = true +dropRegionWhite = *1 +dropRegionBlack = *8 + +#https://www.chessvariants.com/small.dir/petty.html +[petty:chess] +maxRank = 6 +maxFile = 5 +startFen = qkbnr/ppppp/5/5/PPPPP/QKBNR w - 0 1 +castling = false +doubleStep = false +promotionRegionWhite = *6 + +#https://www.zillions-of-games.com/cgi-bin/zilligames/submissions.cgi?do=show;id=655 +[teeko:picaria] +maxRank = 5 +maxFile = 5 +connectN = 4 +connectNxN = 2 +customPiece1 = p:mK +startFen = 5/5/5/5/5[PPPPpppp] w - - 0 1 + +#https://www.chessvariants.com/small.dir/haynie.html +[haynie:chess] +maxRank = 6 +maxFile = 6 +startFen = rbqkbr/pppppp/6/6/PPPPPP/RBQKBR w KQkq - 0 1 +doubleStep = false +promotionPieceTypes = rbq +castlingQueensideFile = c +castlingKingsideFile = e +promotionRegionWhite = *6 + +#https://www.zillions-of-games.com/cgi-bin/zilligames/submissions.cgi?do=show;id=1367 +[la-mancha-squeez:snailtrail] +pieceToCharTable = P.....................p..................... +maxRank = 9 +maxFile = 9 +startFen = p7P/9/9/9/3p*P3/9/9/9/p7P w 0 1 + +[la-mancha-duel:la-mancha-squeez] +customPiece1 = p:K + +#https://www.chessvariants.com/diffsetup.dir/argess.html +[argess:chess] +pawnTypes = p +customPiece1 = p:mWcF +#yes, black moves first +startFen = rppppnbk/6qb/7n/7p/PPPP3p/RNPP3p/BQNP3p/KBRP3r b 0 1 +castling = false +promotionRegionWhite = g8 h8 h7 +promotionRegionBlack = a1 a2 b1 + +#https://www.chessvariants.com/rules/4-kings-quasi-shatranj +[quasi-shatranj:twokings2] +pieceToCharTable = PN....E...G..FZ....IAKpn....e...g..fz....iak +maxRank = 10 +maxFile = 10 +customPiece1 = a:AD +customPiece2 = e:AF +customPiece3 = f:AW +customPiece4 = i:DF +customPiece5 = z:DW +customPiece6 = g:K +extinctionPieceCount = 3 +startFen = kifkaakfik/znegaagenz/pppppppppp/10/10/10/10/PPPPPPPPPP/ZNEGAAGENZ/KIFKAAKFIK w 0 1 +promotionRegionWhite = *10 +promotionPieceTypes = zangief +doubleStepRegionWhite = *3 +doubleStepRegionBlack = *8 + +#https://www.zillions-of-games.com/cgi-bin/zilligames/submissions.cgi?do=show;id=1723 +[symphony:tictactoe] +maxRank = 8 +maxFile = 8 +connectN = 5 +customPiece1 = p:mfsW +nFoldRule = 3 +startFen = 8/8/8/8/8/8/8/8[PPPPPPpppppp] w - - 0 + +#https://www.zillions-of-games.com/cgi-bin/zilligames/submissions.cgi?do=show;id=734 +#am calling it cfour-anyside so it's less confusable with roll-ing-to-four +[cfour-anyside:cfour] +maxRank = 7 +startFen = 7/7/7/7/7/7/7[PPPPPPPPPPPPPPPPPPPPPPPPPpppppppppppppppppppppppp] w - - 0 1 +enclosingDrop = anyside + +#http://gamescrafters.berkeley.edu/games.php?game=connect4 +[cfour-misere:cfour] +connectValue = loss + +#https://www.ludii.games/details.php?keyword=Three%20Musketeers +[three-musketeers] +pieceToCharTable = P......M..............p......m.............. +startFen = ppppM/ppppp/ppMpp/ppppp/Mpppp +maxRank = 5 +maxFile = 5 +collinearN = 3 +connectDiagonal = false +customPiece1 = m:cW +customPiece2 = p:mW +connectValue = loss +stalemateValue = win +connectPieceTypes = m + +#https://www.pychess.org/variants/shinobiplus +[shinobiplus:crazyhouse] +pieceToCharTable = - +commoner = c +bers = d +dragonHorse = f +archbishop = j +fers = m +shogiKnight = h +lance = l +promotionRegionWhite = *7 *8 +promotionRegionBlack = *1 *2 *3 +promotionPieceTypes = - +promotedPieceType = p:c m:b h:n l:r +mandatoryPiecePromotion = true +stalemateValue = loss +nFoldRule = 4 +perpetualCheckIllegal = true +startFen = rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/4K3[JDFCLHM] w kq - 0 1 +capturesToHand = false +dropRegionWhite = *1 *2 *3 *4 +immobilityIllegal = true +flagPiece = k +flagRegionWhite = *8 +flagRegionBlack = *1 + +#https://www.pychess.org/variants/khans +[khans:chess] +pieceToCharTable = - +centaur = h +knibis = a +kniroo = l +customPiece1 = t:mNcK +customPiece2 = s:mfhNcfW +promotionPawnTypesBlack = s +promotionPieceTypesBlack = t +stalemateValue = loss +nMoveRuleTypesBlack = s +flagPiece = k +flagRegionWhite = *8 +flagRegionBlack = *1 +startFen = lhatkahl/ssssssss/8/8/8/8/PPPPPPPP/RNBQKBNR w KQ - 0 1 + +[muje] +variantTemplate = shogi +pieceToCharTable = PNBR.F.IS..+Kpnbr.f.is..+k +maxFile = 9 +maxRank = 9 +pocketSize = 7 +startFen = rnbfkfbnr/1si3is1/p1p1p1p1p/9/9/9/P1P1P1P1P/1SI3IS1/RNBFKFBNR[] w - - 0 1 +pieceDrops = true +capturesToHand = true +soldier = p +knight = n +bishop = b +rook = r +fers = f +wazir = w +king = k +customPiece1 = i:pB +customPiece2 = s:pR +promotionRegionWhite = *7 *8 *9 +promotionRegionBlack = *3 *2 *1 +promotedPieceType = p:w +doubleStep = false +castling = false +stalemateValue = loss +nMoveRule = 0 +nFoldValue = loss +perpetualCheckIllegal = true +mobilityRegionWhiteKing = *3 *2 *1 +mobilityRegionBlackKing = *7 *8 *9 +mandatoryPiecePromotion = true + +#https://www.chessvariants.com/diffobjective.dir/giveaway.html +#When stalemate, the stalemated player does not move but the opponent can if he wish to play for win go on moving and do as many moves he wants to do. +#If the stalemate then disappear, both players move again as usual. So, if white for example has a pawn on h2 and nothing more and black a pawn on h3, pawn on a7 and rooks on a8 and h8 +# black can win by moving : 1.-,Rh4 2.-, a5. 3.-, a4 4.- Ra5 5.-,a3 6.-,a2 7.-,a1=R 8.-,Rg1 9.-,Rg3 10.hxg3,h2 11.gxh4,Rg5 12.hxg5,h1=Q 13.g6,Qh7 14.gxh7 and black has won. +[andersgiveaway:giveaway] +passOnStalemate = true + +[andersanti:antichess] +passOnStalemate = true + +# A 10x10 drop variant consisting of only Leapers, Shogi pawns, and Kings. +[leaperhouse] +variantTemplate = shogi +pieceToCharTable = PN...OU.CR.+Kpn...ou.cr.+k +maxFile = 10 +maxRank = 10 +pocketSize = 6 +startFen = oncurkucno/10/pppppppppp/10/10/10/10/PPPPPPPPPP/10/ONCUKRUCNO[] w - - 0 1 +pieceDrops = true +capturesToHand = true +shogiPawn = p +knight = n +customPiece1 = u:Z +customPiece2 = c:C +customPiece3 = r:GH +customPiece4 = o:AD +commoner = g +king = k +promotionRegionWhite = *8 *9 *10 +promotionRegionBlack = *3 *2 *1 +promotedPieceType = p:g +doubleStep = false +castling = false +perpetualCheckIllegal = true +dropNoDoubled = p +stalemateValue = loss +nMoveRule = 0 +nFoldValue = loss +flagPiece = k +flagRegionWhite = *10 +flagRegionBlack = *1 +immobilityIllegal = true +mandatoryPiecePromotion = true +flyingGeneral = true + +# Twin Leaper Kings, Leapers that can move 16 squares, and Shogi Pawns whose Promotion is Campmate. +[mandala] +maxRank = 10 +maxFile = 10 +pocketSize = 6 +pieceDrops = true +capturesToHand = true +shogiPawn = r +customPiece1 = l:NAD +customPiece2 = f:ZAD +customPiece3 = g:NC +customPiece4 = b:ZC +customPiece5 = j:FWGH +customPiece6 = e:vW +immobile = c +startFen = flgbjjbglf/10/rrrrrrrrrr/10/10/10/10/RRRRRRRRRR/10/FLGBJJBGLF[EE] w - - 0 1 +doubleStep = false +castling = false +dropNoDoubled = r +immobilityIllegal = true +promotionRegionWhite = *10 +promotionRegionBlack = *1 +promotionPieceTypes = - +promotedPieceType = r:c +mandatoryPiecePromotion = true +extinctionValue = loss +extinctionPieceTypes = j +extinctionPseudoRoyal = true +extinctionPieceCount = 1 +flagPiece = c +flagRegionWhite = *10 +flagRegionBlack = *1 +nMoveRule = 0 +nFoldValue = loss +stalemateValue = loss + +# Janggi with the Drop rule and other elements added. +[elgam] +variantTemplate = fairy +maxRank = 10 +maxFile = 11 +startFen = rheoa1aoehr/5k5/1c7c1/s1s1s1s1s1s/5v5/5V5/S1S1S1S1S1S/1C7C1/5K5/RHEOA1AOEHR[] w - - 0 1 +rook = r +horse = h +janggiElephant = e +wazir = a +king = k +janggiCannon = c +soldier = s +customPiece1 = o:nC +customPiece2 = v:mR +customPiece3 = w:W +diagonalLines = c5 d4 e1 e3 f2 g1 g3 h4 i5 c6 d7 e8 e10 f9 g8 g10 h7 i6 +mobilityRegionWhiteKing = e1 f1 g1 e2 f2 g2 e3 f3 g3 +mobilityRegionBlackKing = e8 f8 g8 e9 f9 g9 e10 f10 g10 +pieceDrops = true +capturesToHand = true +pawnTypes = s +dropNoDoubled = s +shogiPawnDropMateIllegal = true +promotionRegionWhite = *10 +promotionRegionBlack = *1 +promotionPawnTypes = s +promotionPieceTypes = w +promotedPieceType = s:w +mandatoryPawnPromotion = true +stalemateValue = loss +nMoveRule = 0 +nFoldRule = 3 +nFoldValue = loss +perpetualCheckIllegal = true +castling = false +doubleStep = false +pass = false +bikjangRule = false +materialCounting = none diff --git a/Fairy-Stockfish-master/src/xboard.cpp b/Fairy-Stockfish-master/src/xboard.cpp new file mode 100644 index 0000000000000000000000000000000000000000..09228edb38d991bc2718332cda2fb9bc48e6da5b --- /dev/null +++ b/Fairy-Stockfish-master/src/xboard.cpp @@ -0,0 +1,496 @@ +/* + Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish + Copyright (C) 2018-2022 Fabian Fichter + + Fairy-Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Fairy-Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#include "evaluate.h" +#include "misc.h" +#include "partner.h" +#include "search.h" +#include "thread.h" +#include "types.h" +#include "uci.h" +#include "xboard.h" + +namespace Stockfish { + +namespace { + + const Search::LimitsType analysisLimits = []{ + Search::LimitsType limits; + limits.infinite = 1; + return limits; + }(); + +} // namespace + +namespace XBoard { + + StateMachine* stateMachine = nullptr; + + // go() starts the search for game play, analysis, or perft. + + void StateMachine::go(Search::LimitsType searchLimits, bool ponder) { + + searchLimits.startTime = now(); // As early as possible! + + Threads.start_thinking(pos, states, searchLimits, ponder); + } + + // ponder() starts a ponder search + + void StateMachine::ponder() { + + sync_cout << "Hint: " << UCI::move(pos, ponderMove) << sync_endl; + ponderHighlight = highlight(UCI::square(pos, from_sq(ponderMove))); + do_move(ponderMove); + ponderMove = MOVE_NONE; + go(limits, true); + } + + // stop() stops an ongoing search (if any) + // and does not print/apply a move if aborted + + void StateMachine::stop(bool abort) { + + if (abort) + Threads.abort = true; + Threads.stop = true; + Threads.main()->wait_for_search_finished(); + // Ensure that current position does not get out of sync with GUI + if (Threads.main()->ponder) + { + assert(moveList.size()); + undo_move(); + Threads.main()->ponder = false; + } + } + + // setboard() is called when engine receives the "setboard" XBoard command. + + void StateMachine::setboard(std::string fen) { + + if (fen.empty()) + fen = variants.find(Options["UCI_Variant"])->second->startFen; + + states = StateListPtr(new std::deque(1)); // Drop old and create a new one + moveList.clear(); + pos.set(variants.find(Options["UCI_Variant"])->second, fen, Options["UCI_Chess960"], &states->back(), Threads.main()); + } + + // do_move() is called when engine needs to apply a move when using XBoard protocol. + + void StateMachine::do_move(Move m) { + + // transfer states back + if (Threads.setupStates.get()) + states = std::move(Threads.setupStates); + + if (m == MOVE_NONE) + return; + moveList.push_back(m); + states->emplace_back(); + pos.do_move(m, states->back()); + } + + // undo_move() is called when the engine receives the undo command in XBoard protocol. + + void StateMachine::undo_move() { + + // transfer states back + if (Threads.setupStates.get()) + states = std::move(Threads.setupStates); + + pos.undo_move(moveList.back()); + states->pop_back(); + moveList.pop_back(); + } + + std::string StateMachine::highlight(std::string square) { + Bitboard promotions = 0, captures = 0, quiets = 0; + // Collect targets + for (const auto& m : MoveList(pos)) + { + Square from = from_sq(m), to = to_sq(m); + if (is_ok(from) && UCI::square(pos, from) == square && !is_pass(m)) + { + if (type_of(m) == PROMOTION) + promotions |= to; + else if (pos.capture(m)) + captures |= to; + else + { + if (type_of(m) == CASTLING && !pos.is_chess960()) + to = make_square(to > from ? pos.castling_kingside_file() + : pos.castling_queenside_file(), rank_of(from)); + quiets |= to; + } + } + } + // Generate color FEN + int emptyCnt; + std::ostringstream ss; + for (Rank r = pos.max_rank(); r >= RANK_1; --r) + { + for (File f = FILE_A; f <= pos.max_file(); ++f) + { + for (emptyCnt = 0; f <= pos.max_file() && !((promotions | captures | quiets) & make_square(f, r)); ++f) + ++emptyCnt; + + if (emptyCnt) + ss << emptyCnt; + + if (f <= pos.max_file()) + ss << (promotions & make_square(f, r) ? "M" : captures & make_square(f, r) ? "R" : "Y"); + } + + if (r > RANK_1) + ss << '/'; + } + return ss.str(); + } + +/// StateMachine::process_command() processes commands of the XBoard protocol. + +void StateMachine::process_command(std::string token, std::istringstream& is) { + if (token == "protover") + { + std::string vars = "chess"; + for (std::string v : variants.get_keys()) + if (v != "chess") + vars += "," + v; + sync_cout << "feature setboard=1 usermove=1 time=1 memory=1 smp=1 colors=0 draw=0 " + << "highlight=1 name=0 sigint=0 ping=1 myname=\"" + << engine_info(false, true) << "\" " << "variants=\"" << vars << "\"" + << Options << sync_endl; + sync_cout << "feature done=1" << sync_endl; + } + else if (token == "accepted" || token == "rejected") {} + else if (token == "hover" || token == "put") {} + else if (token == "lift") + { + if (is >> token) + { + if (Threads.main()->ponder) + { + if (token == UCI::square(pos, from_sq(moveList.back()))) + sync_cout << "highlight " << ponderHighlight << sync_endl; + else + { + Move currentPonderMove = moveList.back(); + stop(); + sync_cout << "highlight " << highlight(token) << sync_endl; + // Restart ponder search with random guess + auto moves = MoveList(pos); + std::vector filteredMoves; + copy_if(moves.begin(), moves.end(), back_inserter(filteredMoves), [&](const Move m) { + return is_ok(from_sq(m)) && UCI::square(pos, from_sq(m)) == token; + }); + if (filteredMoves.size()) + { + static PRNG rng(now()); + ponderMove = filteredMoves.at(rng.rand() % filteredMoves.size()); + } + else + ponderMove = currentPonderMove; + ponder(); + } + } + else + sync_cout << "highlight " << highlight(token) << sync_endl; + } + } + else if (token == "ping") + { + if (!(is >> token)) + token = ""; + sync_cout << "pong " << token << sync_endl; + } + else if (token == "new") + { + stop(); + Search::clear(); + setboard(); + // play second by default + playColor = ~pos.side_to_move(); + Threads.sit = false; + Partner.reset(); + } + else if (token == "variant") + { + stop(); + if (is >> token) + Options["UCI_Variant"] = token; + setboard(); + } + else if (token == "force" || token == "result") + { + stop(); + playColor = COLOR_NB; + } + else if (token == "?") + { + if (!Threads.main()->ponder) + stop(false); + } + else if (token == "go") + { + stop(); + playColor = pos.side_to_move(); + go(limits); + moveAfterSearch = true; + } + else if (token == "level" || token == "st" || token == "sd" || token == "time" || token == "otim") + { + int num; + if (token == "level") + { + // moves to go + is >> limits.movestogo; + // base time + is >> token; + size_t idx = token.find(":"); + if (idx != std::string::npos) + num = std::stoi(token.substr(0, idx)) * 60 + std::stoi(token.substr(idx + 1)); + else + num = std::stoi(token) * 60; + limits.time[WHITE] = num * 1000; + limits.time[BLACK] = num * 1000; + // increment + is >> num; + limits.inc[WHITE] = num * 1000; + limits.inc[BLACK] = num * 1000; + } + else if (token == "sd") + is >> limits.depth; + else if (token == "st") + { + is >> num; + limits.movetime = num * 1000; + limits.time[WHITE] = limits.time[BLACK] = 0; + } + // Note: time/otim are in centi-, not milliseconds + else if (token == "time") + { + is >> num; + Color us = playColor != COLOR_NB ? playColor : pos.side_to_move(); + if (limits.time[us]) + limits.time[us] = num * 10; + } + else if (token == "otim") + { + is >> num; + Color them = playColor != COLOR_NB ? ~playColor : ~pos.side_to_move(); + if (limits.time[them]) + limits.time[them] = num * 10; + } + } + else if (token == "setboard") + { + stop(); + std::string fen; + std::getline(is >> std::ws, fen); + // Check if setboard actually indicates a passing move + // to avoid unnecessarily clearing the move history + if (pos.pass(~pos.side_to_move())) + { + StateInfo st; + Position p; + p.set(pos.variant(), fen, pos.is_chess960(), &st, pos.this_thread()); + Move m; + std::string passMove = "@@@@"; + if ((m = UCI::to_move(pos, passMove)) != MOVE_NONE) + do_move(m); + // apply setboard if passing does not lead to a match + if (pos.key() != p.key()) + setboard(fen); + } + else + setboard(fen); + // Winboard sends setboard after passing moves + if (Options["UCI_AnalyseMode"]) + go(analysisLimits); + else if (pos.side_to_move() == playColor) + { + go(limits); + moveAfterSearch = true; + } + } + else if (token == "cores") + { + stop(); + if (is >> token) + Options["Threads"] = token; + } + else if (token == "memory") + { + stop(); + if (is >> token) + Options["Hash"] = token; + } + else if (token == "hard" || token == "easy") + Options["Ponder"] = token == "hard"; + else if (token == "option") + { + std::string name, value; + is.get(); + std::getline(is, name, '='); + std::getline(is, value); + if (Options.count(name)) + { + if (Options[name].get_type() == "check") + value = value == "1" ? "true" : "false"; + Options[name] = value; + } + } + else if (token == "analyze") + { + stop(); + Options["UCI_AnalyseMode"] = std::string("true"); + go(analysisLimits); + } + else if (token == "exit") + { + stop(); + Options["UCI_AnalyseMode"] = std::string("false"); + } + else if (token == "undo") + { + stop(); + if (moveList.size()) + { + undo_move(); + if (Options["UCI_AnalyseMode"]) + go(analysisLimits); + } + } + else if (token == "remove") + { + stop(); + if (moveList.size()) + { + undo_move(); + undo_move(); + if (Options["UCI_AnalyseMode"]) + go(analysisLimits); + } + } + // Bughouse commands + else if (token == "partner") + Partner.parse_partner(is); + else if (token == "ptell") + { + Partner.parse_ptell(is, pos); + // play move requested by partner + // Partner.moveRequested can only be set if search was successfully aborted + if (moveAfterSearch && Partner.moveRequested) + { + assert(Threads.abort); + stop(); + sync_cout << "move " << UCI::move(pos, Partner.moveRequested) << sync_endl; + do_move(Partner.moveRequested); + moveAfterSearch = false; + Partner.moveRequested = MOVE_NONE; + } + } + else if (token == "holding") + { + stop(); + // holding [] [] + std::string white_holdings, black_holdings; + if ( std::getline(is, token, '[') && std::getline(is, white_holdings, ']') + && std::getline(is, token, '[') && std::getline(is, black_holdings, ']')) + { + std::string fen; + char color, pieceType; + // Use the obtained holding if available to avoid race conditions + if (is >> color && is >> pieceType) + { + fen = pos.fen(); + fen.insert(fen.find(']'), 1, toupper(color) == 'B' ? tolower(pieceType) : toupper(pieceType)); + } + else + { + std::transform(black_holdings.begin(), black_holdings.end(), black_holdings.begin(), ::tolower); + fen = pos.fen(false, false, 0, white_holdings + black_holdings); + } + setboard(fen); + } + // restart search + if (moveAfterSearch) + go(limits); + } + // Additional custom non-XBoard commands + else if (token == "perft") + { + stop(); + Search::LimitsType perft_limits; + is >> perft_limits.perft; + go(perft_limits); + } + else if (token == "d") + sync_cout << pos << sync_endl; + else if (token == "eval") + sync_cout << Eval::trace(pos) << sync_endl; + // Move strings and unknown commands + else + { + bool isMove = false; + + if (token == "usermove") + { + is >> token; + isMove = true; + } + + // Handle pondering + if (Threads.main()->ponder) + { + assert(moveList.size()); + if (token == UCI::move(pos, moveList.back())) + { + // ponderhit + moveAfterSearch = true; + Threads.main()->ponder = false; + return; + } + } + stop(false); + + // Apply move + Move m; + if ((m = UCI::to_move(pos, token)) != MOVE_NONE) + do_move(m); + else + sync_cout << (isMove ? "Illegal move: " : "Error (unknown command): ") << token << sync_endl; + + // Restart search if applicable + if (Options["UCI_AnalyseMode"]) + go(analysisLimits); + else if (pos.side_to_move() == playColor) + { + moveAfterSearch = true; + go(limits); + } + } +} + +} // namespace XBoard + +} // namespace Stockfish diff --git a/Fairy-Stockfish-master/src/xboard.h b/Fairy-Stockfish-master/src/xboard.h new file mode 100644 index 0000000000000000000000000000000000000000..a76cda5c0a555ff58ee393b2247f8c491ee2db39 --- /dev/null +++ b/Fairy-Stockfish-master/src/xboard.h @@ -0,0 +1,71 @@ +/* + Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish + Copyright (C) 2018-2022 Fabian Fichter + + Fairy-Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Fairy-Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef XBOARD_H_INCLUDED +#define XBOARD_H_INCLUDED + +#include +#include +#include + +#include "types.h" + +namespace Stockfish { + +class Position; + +namespace XBoard { + +/// StateMachine class maintains the states required by XBoard protocol + +class StateMachine { +public: + StateMachine(Position& uciPos, StateListPtr& uciPosStates) : pos(uciPos), states(uciPosStates) { + moveList = std::deque(); + moveAfterSearch = false; + playColor = COLOR_NB; + ponderMove = MOVE_NONE; + ponderHighlight = ""; + } + void go(Search::LimitsType searchLimits, bool ponder = false); + void ponder(); + void stop(bool abort = true); + void setboard(std::string fen = ""); + void do_move(Move m); + void undo_move(); + std::string highlight(std::string square); + void process_command(std::string token, std::istringstream& is); + bool moveAfterSearch; + Move ponderMove; + +private: + Position& pos; + StateListPtr& states; + std::deque moveList; + Search::LimitsType limits; + Color playColor; + std::string ponderHighlight; +}; + +extern StateMachine* stateMachine; + +} // namespace XBoard + +} // namespace Stockfish + +#endif // #ifndef XBOARD_H_INCLUDED diff --git a/Fairy-Stockfish-master/test.py b/Fairy-Stockfish-master/test.py new file mode 100644 index 0000000000000000000000000000000000000000..b71d674e1dbe9e7ae3f2a49dc4a61a87232fe02e --- /dev/null +++ b/Fairy-Stockfish-master/test.py @@ -0,0 +1,1311 @@ +# -*- coding: utf-8 -*- + +import faulthandler +import unittest +import pyffish as sf + +faulthandler.enable() + +CHESS = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" +CHESS960 = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w HAha - 0 1" +CAPA = "rnabqkbcnr/pppppppppp/10/10/10/10/PPPPPPPPPP/RNABQKBCNR w KQkq - 0 1" +CAPAHOUSE = "rnabqkbcnr/pppppppppp/10/10/10/10/PPPPPPPPPP/RNABQKBCNR[] w KQkq - 0 1" +SITTUYIN = "8/8/4pppp/pppp4/4PPPP/PPPP4/8/8[KFRRSSNNkfrrssnn] w - - 0 1" +MAKRUK = "rnsmksnr/8/pppppppp/8/8/PPPPPPPP/8/RNSKMSNR w - - 0 1" +CAMBODIAN = "rnsmksnr/8/pppppppp/8/8/PPPPPPPP/8/RNSKMSNR w DEde - 0 1" +SHOGI = "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL[-] w - - 0 1" +SHOGI_SFEN = "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1" +SEIRAWAN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[EHeh] w KQBCDFGkqbcdfg - 0 1" +GRAND = "r8r/1nbqkcabn1/pppppppppp/10/10/10/10/PPPPPPPPPP/1NBQKCABN1/R8R w - - 0 1" +GRANDHOUSE = "r8r/1nbqkcabn1/pppppppppp/10/10/10/10/PPPPPPPPPP/1NBQKCABN1/R8R[] w - - 0 1" +XIANGQI = "rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1" +SHOGUN = "rnb+fkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNB+FKBNR[] w KQkq - 0 1" +JANGGI = "rnba1abnr/4k4/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/4K4/RNBA1ABNR w - - 0 1" + + +ini_text = """ +# Hybrid variant of Grand-chess and crazyhouse, using Grand-chess as a template +[grandhouse:grand] +startFen = r8r/1nbqkcabn1/pppppppppp/10/10/10/10/PPPPPPPPPP/1NBQKCABN1/R8R[] w - - 0 1 +pieceDrops = true +capturesToHand = true + +# Shogun chess +[shogun:crazyhouse] +startFen = rnb+fkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNB+FKBNR[] w KQkq - 0 1 +commoner = c +centaur = g +archbishop = a +chancellor = m +fers = f +promotionRegionWhite = *6 *7 *8 +promotionRegionBlack = *3 *2 *1 +promotionLimit = g:1 a:1 m:1 q:1 +promotionPieceTypes = - +promotedPieceType = p:c n:g b:a r:m f:q +mandatoryPawnPromotion = false +firstRankPawnDrops = true +promotionZonePawnDrops = true +dropRegionWhite = *1 *2 *3 *4 *5 +dropRegionBlack = *4 *5 *6 *7 *8 +immobilityIllegal = true + +# Asymmetric variant with one army using pieces that move like knights but attack like other pieces (kniroo and knibis) +[orda:chess] +startFen = lhaykahl/8/pppppppp/8/8/8/PPPPPPPP/RNBQKBNR w KQ - 0 1 +centaur = h +knibis = a +kniroo = l +silver = y +promotionPieceTypes = qh +flagPiece = k +flagRegionWhite = *8 +flagRegionBlack = *1 + +[diana:losalamos] +pieceToCharTable = PNBRQ................Kpnbrq................k +bishop = b +promotionPieceTypes = rbn +castling = true +castlingKingsideFile = e +castlingQueensideFile = b +startFen = rbnkbr/pppppp/6/6/PPPPPP/RBNKBR w KQkq - 0 1 + +[passchess:chess] +pass = true + +[royalduck:duck] +extinctionPseudoRoyal = true + +[makhouse:makruk] +startFen = rnsmksnr/8/pppppppp/8/8/PPPPPPPP/8/RNSKMSNR[] w - - 0 1 +pieceDrops = true +capturesToHand = true +firstRankPawnDrops = true +promotionZonePawnDrops = true +immobilityIllegal = true + +[wazirking:chess] +fers = q +king = k:W +startFen = 7k/5Kq1/8/8/8/8/8/8 w - - 0 1 +stalemateValue = loss +nFoldValue = loss + +[betzatest] +maxRank = 7 +maxFile = 7 +customPiece1 = a:lhN +customPiece2 = b:rhN +customPiece3 = c:hlN +customPiece4 = d:hrN +startFen = 7/7/7/3A3/7/7/7 w - - 0 1 + +[cannonshogi:shogi] +dropNoDoubled = - +shogiPawnDropMateIllegal = false +soldier = p +cannon = u +customPiece1 = a:pR +customPiece2 = c:mBcpB +customPiece3 = i:pB +customPiece4 = w:mRpRmFpB2 +customPiece5 = f:mBpBmWpR2 +promotedPieceType = u:w a:w c:f i:f +startFen = lnsgkgsnl/1rci1uab1/p1p1p1p1p/9/9/9/P1P1P1P1P/1BAU1ICR1/LNSGKGSNL[-] w 0 1 + +[fogofwar:chess] +king = - +commoner = k +castlingKingPiece = k +extinctionValue = loss +extinctionPieceTypes = k + +[coregaldrop:coregal] +pieceDrops = true +startFen = rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[Qq] w KQkq - 0 1 + +[cannonatomic:atomic] +cannon = c + +[multipawn:chess] +soldier = s +pawnTypes = ps + +[repetitionloss:chess] +nFoldValue = loss +""" + +sf.load_variant_config(ini_text) + + +variant_positions = { + "chess": { + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1": (False, False), # startpos + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -": (False, False), # startpos + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR": (False, False), # startpos + "rnbqkbnr/ppp2ppp/4p3/3pP3/8/8/PPPP1PPP/RNBQKBNR w KQkq d6 0 3": (False, False), + "k7/8/8/8/8/8/8/K7 w - - 0 1": (True, True), # K vs K + "k7/b7/8/8/8/8/8/K7 w - - 0 1": (True, True), # K vs KB + "k7/n7/8/8/8/8/8/K7 w - - 0 1": (True, True), # K vs KN + "k7/p7/8/8/8/8/8/K7 w - - 0 1": (True, False), # K vs KP + "k7/r7/8/8/8/8/8/K7 w - - 0 1": (True, False), # K vs KR + "k7/q7/8/8/8/8/8/K7 w - - 0 1": (True, False), # K vs KQ + "k7/nn6/8/8/8/8/8/K7 w - - 0 1": (True, False), # K vsNN K + "k7/bb6/8/8/8/8/8/K7 w - - 0 1": (True, False), # K vs KBB opp color + "k7/b1b5/8/8/8/8/8/K7 w - - 0 1": (True, True), # K vs KBB same color + "kb6/8/8/8/8/8/8/K1B6 w - - 0 1": (True, True), # KB vs KB same color + "kb6/8/8/8/8/8/8/KB7 w - - 0 1": (False, False), # KB vs KB opp color + "8/8/8/8/8/6KN/8/6nk w - - 0 1": (False, False), # KN vs KN + }, + "atomic": { + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1": (False, False), # startpos + "8/8/8/8/3K4/3k4/8/8 b - - 0 1": (True, True), # K vs K + "k7/p7/8/8/8/8/8/K7 w - - 0 1": (True, False), # K vs KP + "k7/q7/8/8/8/8/8/K7 w - - 0 1": (True, False), # K vs KQ + }, + "crazyhouse": { + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR/ w KQkq - 0 1": (False, False), # lichess style startpos + }, + "3check": { + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 3+3 0 1": (False, False), # startpos + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 +0+2": (False, False), # lichess style check count + "k7/n7/8/8/8/8/8/K7 w - - 1+2 0 1": (True, False), # K vs KN + "k7/b7/8/8/8/8/8/K7 w - - 3+1 0 1": (True, False), # K vs KB + }, + "horde": { + "rnbqkbnr/pppppppp/8/1PP2PP1/PPPPPPPP/PPPPPPPP/PPPPPPPP/PPPPPPPP w kq - 0 1": (False, False), # startpos + }, + "racingkings": { + "8/8/8/8/8/8/krbnNBRK/qrbnNBRQ w - - 0 1": (False, False), # startpos + "8/8/8/8/8/8/K6k/8 w - - 0 1": (False, False), # KvK + }, + "placement": { + "8/pppppppp/8/8/8/8/PPPPPPPP/8[KQRRBBNNkqrrbbnn] w - - 0 1": (False, False), # startpos + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 0 1": (False, False), # chess startpos + "k7/8/8/8/8/8/8/K7[] w - - 0 1": (True, True), # K vs K + }, + "newzealand": { + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1": (False, False), # startpos + }, + "amazon": { + "rnbakbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBAKBNR w KQkq - 0 1": (False, False), # startpos + "8/8/8/8/A7/6k1/8/1K6 w - - 0 1": (False, True), # KA vs K + }, + "seirawan": { + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[HEhe] w KQBCDFGkqbcdfg - 0 1": (False, False), # startpos + "k7/8/8/8/8/8/8/K7[] w - - 0 1": (True, True), # K vs K + "k7/8/8/8/8/8/8/KH6[] w - - 0 1": (False, True), # KH vs K + "k7/8/8/8/8/8/8/4K3[E] w E - 0 1": (False, True), # KE vs K + }, + "cambodian": { + "rnsmksnr/8/pppppppp/8/8/PPPPPPPP/8/RNSKMSNR w DEde 0 0 1": (False, False), # startpos + "1ns1ksn1/r6r/pppmpppp/3p4/8/PPPPPPPP/RK2N2R/1NS1MS2 w Ee - 6 5": (False, False), + }, + "sittuyin": { + "8/8/4pppp/pppp4/4PPPP/PPPP4/8/8[KFRRSSNNkfrrssnn] w - - 0 1": (False, False), # startpos + "k7/8/8/8/8/8/8/K7 w - - 0 1": (True, True), # K vs K, skip pocket + "k6P/8/8/8/8/8/8/K7[] w - - 0 1": (True, True), # KP vs K + "k6P/8/8/8/8/8/8/K6p[] w - - 0 1": (False, False), # KP vs KP + "k7/8/8/8/8/8/8/KFF5[] w - - 0 1": (False, True), # KFF vs K + "k7/8/8/8/8/8/8/KS6[] w - - 0 1": (False, True), # KS vs K + }, + "makpong": { + "8/8/8/4k2K/5m~2/4m~3/8/8 w - 128 8 58": (True, False), # KFF vs K + "k7/n7/8/8/8/8/8/K7 w - - 0 1": (True, False), # K vs KN + "k7/8/8/8/8/8/8/K7 w - - 0 1": (True, True), # K vs K + }, + "xiangqi": { + "rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1": (False, False), # startpos + "5k3/4a4/3CN4/9/1PP5p/9/8P/4C4/4A4/2B1K4 w - - 0 46": (False, False), # issue #53 + "4k4/9/9/9/9/9/9/9/9/4K4 w - - 0 1": (True, True), # K vs K + "4k4/9/9/4p4/9/9/9/9/9/4KR3 w - - 0 1": (False, False), # KR vs KP + "4k4/9/9/9/9/9/9/9/9/3KN4 w - - 0 1": (False, True), # KN vs K + "4k4/9/4b4/9/9/9/9/4B4/9/4K4 w - - 0 1": (True, True), # KB vs KB + "4k4/9/9/9/9/9/9/9/4A4/4KC3 w - - 0 1": (False, True), # KCA vs K + }, + "janggi": { + JANGGI: (False, False), # startpos + "rhea1aehr/4k4/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/4K4/RHEA1AEHR w - - 0 1": (False, False), # startpos + "5k3/4a4/3CN4/9/1PP5p/9/8P/4C4/4A4/2B1K4 w - - 0 46": (False, False), # issue #53 + "4k4/9/9/9/9/4B4/4B4/9/9/4K4 w - - 0 1": (False, True), # KEE vs K + "4k4/9/9/9/9/9/9/9/4A4/4KC3 w - - 0 1": (False, True), # KCA vs K + }, + "shako": { + "k9/10/10/10/10/10/10/10/10/KC8 w - - 0 1": (True, True), # KC vs K + "k9/10/10/10/10/10/10/10/10/KCC7 w - - 0 1": (False, True), # KCC vs K + "k9/10/10/10/10/10/10/10/10/KEC7 w - - 0 1": (False, True), # KEC vs K + "k9/10/10/10/10/10/10/10/10/KNE7 w - - 0 1": (False, True), # KNE vs K + "kb8/10/10/10/10/10/10/10/10/KE8 w - - 0 1": (False, False), # KE vs KB opp color + "kb8/10/10/10/10/10/10/10/10/K1E7 w - - 0 1": (True, True), # KE vs KB same color + }, + "orda": { + "k7/8/8/8/8/8/8/K7 w - - 0 1": (False, False), # K vs K + }, + "tencubed": { + "2cwamwc2/1rnbqkbnr1/pppppppppp/10/10/10/10/PPPPPPPPPP/1RNBQKBNR1/2CWAMWC2 w - - 0 1": (False, False), # startpos + "10/5k4/10/10/10/10/10/10/5KC3/10 w - - 0 1": (False, True), # KC vs K + "10/5k4/10/10/10/10/10/10/5K4/10 w - - 0 1": (True, True), # K vs K + }, + "wazirking": { + "7k/6K1/8/8/8/8/8/8 b - - 0 1": (False, False), # K vs K + }, + "multipawn": { + "k7/p7/8/8/8/8/8/K7 w - - 0 1": (True, False), # K vs KP + "k7/s7/8/8/8/8/8/K7 w - - 0 1": (True, False), # K vs KS + }, + "repetitionloss": { + "k7/8/8/8/8/8/8/K7 w - - 0 1": (False, False), # K vs K + }, +} + +invalid_variant_positions = { + "chess": ( + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 a", # invalid full move + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - b 1", # invalid half move + "rnbqkbnr/ppp2ppp/4p3/3pP3/8/8/PPPP1PPP/RNBQKBNR w KQkq -6 0 3", # invalid en passant + "rnbqkbnr/ppp2ppp/4p3/3pP3/8/8/PPPP1PPP/RNBQKBNR w KQkq .6 0 3", # invalid en passant + "rnbqkbnr/ppp2ppp/4p3/3pP3/8/8/PPPP1PPP/RNBQKBNR w KQkq d- 0 3", # invalid en passant + "rnbqkbnr/ppp2ppp/4p3/3pP3/8/8/PPPP1PPP/RNBQKBNR w KQkq 0 3", # invalid/missing en passant + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 123 - 0 1", # invalid castling + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR g KQkq - 0 1", # invalid side to move + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNH w KQkq - 0 1", # invalid piece type + "rnbqkbnr/pppppppp/7/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", # invalid file count + "rnbqkbnr/pppppppp/9/7/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", # invalid file count + "rnbqkbnr/pppppppp/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", # invalid rank count + "rnbqkbn1/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", # missing castling rook + "1nbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", # missing castling rook + "rnbqkbnr/pppppppp/8/8/8/4K3/PPPPPPPP/RNBQ1BNR w KQkq - 0 1", # king not on castling rank + "rnbqkbnr/pppppppp/8/8/8/RNBQKBNR/PPPPPPPP/8 w KQkq - 0 1", # not on castling rank + "8/pppppppp/rnbqkbnr/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", # not on castling rank + ), + "atomic": ( + "rnbqkbnr/pppppppp/8/8/8/RNBQKBNR/PPPPPPPP/8 w KQkq - 0 1", # wrong castling rank + ), + "3check": ( + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 3+a 0 1", # invalid check count + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 +a+2", # invalid lichess check count + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 +1+4", # invalid lichess check count + ), + "horde": ( + "rnbqkbnr/pppppppp/8/1PP2PP1/PPPPPPPP/PPPPPPPP/PPPPPPPP/PPPPPPPK w kq - 0 1", # wrong king count + "rnbq1bnr/pppppppp/8/1PP2PP1/PPPPPPPP/PPPPPPPP/PPPPPPPP/PPPPPPPP w kq - 0 1", # wrong king count + ), + "sittuyin": ( + "8/8/4pppp/pppp4/4PPPP/PPPP4/8/8[FRRSSNNkfrrssnn] w - - 0 1", # wrong king count + ), + "shako": { + "c8c/ernbqkbnre/pppppppppp/10/10/10/10/PPPPPPPPPP/C8C/ERNBQKBNRE w KQkq - 0 1", # not on castling rank + }, + "seirawan": { + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQK1NR[HEhe] w KQBCDFGkqbcdfg - 0 1", # white gating flag + "rnbqkb1r/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[HEhe] w KQBCDFGkqbcdfg - 0 1", # black gating flag + } +} + + +class TestPyffish(unittest.TestCase): + def test_version(self): + result = sf.version() + self.assertEqual(len(result), 3) + + def test_info(self): + result = sf.info() + self.assertTrue(result.startswith("Fairy-Stockfish")) + + def test_variants_loaded(self): + variants = sf.variants() + self.assertTrue("shogun" in variants) + + def test_set_option(self): + result = sf.set_option("UCI_Variant", "capablanca") + self.assertIsNone(result) + + def test_two_boards(self): + self.assertFalse(sf.two_boards("chess")) + self.assertTrue(sf.two_boards("bughouse")) + + def test_captures_to_hand(self): + self.assertFalse(sf.captures_to_hand("seirawan")) + self.assertTrue(sf.captures_to_hand("shouse")) + + def test_start_fen(self): + result = sf.start_fen("capablanca") + self.assertEqual(result, CAPA) + + result = sf.start_fen("capahouse") + self.assertEqual(result, CAPAHOUSE) + + result = sf.start_fen("xiangqi") + self.assertEqual(result, XIANGQI) + + result = sf.start_fen("grandhouse") + self.assertEqual(result, GRANDHOUSE) + + result = sf.start_fen("shogun") + self.assertEqual(result, SHOGUN) + + def test_legal_moves(self): + fen = "10/10/10/10/10/k9/10/K9 w - - 0 1" + result = sf.legal_moves("capablanca", fen, []) + self.assertEqual(result, ["a1b1"]) + + result = sf.legal_moves("grand", GRAND, ["a3a5"]) + self.assertIn("a10b10", result) + + result = sf.legal_moves("xiangqi", XIANGQI, ["h3h10"]) + self.assertIn("i10h10", result) + + result = sf.legal_moves("xiangqi", XIANGQI, ["h3h10"]) + self.assertIn("i10h10", result) + + result = sf.legal_moves("shogun", SHOGUN, ["c2c4", "b8c6", "b2b4", "b7b5", "c4b5", "c6b8"]) + self.assertIn("b5b6+", result) + + # Seirawan gating but no castling + fen = "rnbq3r/pp2bkpp/8/2p1p2K/2p1P3/8/PPPP1PPP/RNB4R[EHeh] b ABCHabcdh - 0 10" + result = sf.legal_moves("seirawan", fen, []) + self.assertIn("c8g4h", result) + + # Drop pseudo-royals into check + result = sf.legal_moves("coregaldrop", sf.start_fen("coregaldrop"), []) + self.assertIn("Q@a3", result) + self.assertNotIn("Q@a6", result) + + # In Cannon Shogi the FGC and FSC can also move one square diagonally and, besides, + # move or capture two squares diagonally, by leaping an adjacent piece. + fen = "lnsg1gsnl/1rc1kuab1/p1+A1p1p1p/3P5/6i2/6P2/P1P1P3P/1B1U1ICR1/LNSGKGSNL[] w - - 1 3" + result = sf.legal_moves("cannonshogi", fen, []) + # mF + self.assertIn("c7b6", result) + self.assertIn("c7d8", result) + self.assertNotIn("c7d6", result) + self.assertNotIn("c7b8", result) + # pB2 + self.assertIn("c7a9", result) + self.assertIn("c7e5", result) + self.assertNotIn("c7a5", result) + self.assertNotIn("c7e9", result) + # verify distance limited to 2 + self.assertNotIn("c7f4", result) + self.assertNotIn("c7g3", result) + + # Cambodian queen cannot capture with its leap + # Cambodian king cannot leap to escape check + result = sf.legal_moves("cambodian", CAMBODIAN, ["b1d2", "g8e7", "d2e4", "d6d5", "e4d6"]) + self.assertNotIn("d8d6", result) + self.assertNotIn("e8g7", result) + self.assertNotIn("e8c7", result) + + # In Janggi stalemate position pass move (in place king move) is possible + fen = "4k4/c7R/9/3R1R3/9/9/9/9/9/3K5 b - - 0 1" + result = sf.legal_moves("janggi", fen, []) + self.assertEqual(result, ["e10e10"]) + + # pawn promotion of dropped pawns beyond promotion rank + result = sf.legal_moves("makhouse", "rnsmksnr/8/1ppP1ppp/p3p3/8/PPP1PPPP/8/RNSKMSNR[p] w - - 0 4", []) + self.assertIn("d6d7m", result) + self.assertNotIn("d6d7", result) + + # Test configurable piece perft + legals = ['a3a4', 'b3b4', 'c3c4', 'd3d4', 'e3e4', 'f3f4', 'g3g4', 'e1e2', 'f1f2', 'b1a2', 'b1b2', 'b1c2', 'c1b2', 'c1c2', 'c1d2', 'a1a2', 'g1g2', 'd1c2', 'd1d2', 'd1e2'] + result = sf.legal_moves("yarishogi", sf.start_fen("yarishogi"), []) + self.assertCountEqual(legals, result) + + # Test betza parsing + result = sf.legal_moves("betzatest", "7/7/7/3A3/7/7/7 w - - 0 1", []) + self.assertEqual(['d4c2', 'd4b3', 'd4b5', 'd4c6'], result) + result = sf.legal_moves("betzatest", "7/7/7/3B3/7/7/7 w - - 0 1", []) + self.assertEqual(['d4e2', 'd4f3', 'd4f5', 'd4e6'], result) + result = sf.legal_moves("betzatest", "7/7/7/3C3/7/7/7 w - - 0 1", []) + self.assertEqual(['d4e2', 'd4b3', 'd4f5', 'd4c6'], result) + result = sf.legal_moves("betzatest", "7/7/7/3D3/7/7/7 w - - 0 1", []) + self.assertEqual(['d4c2', 'd4f3', 'd4b5', 'd4e6'], result) + + + def test_castling(self): + legals = ['f5f4', 'a7a6', 'b7b6', 'c7c6', 'd7d6', 'e7e6', 'i7i6', 'j7j6', 'a7a5', 'b7b5', 'c7c5', 'e7e5', 'i7i5', 'j7j5', 'b8a6', 'b8c6', 'h6g4', 'h6i4', 'h6j5', 'h6f7', 'h6g8', 'h6i8', 'd5a2', 'd5b3', 'd5f3', 'd5c4', 'd5e4', 'd5c6', 'd5e6', 'd5f7', 'd5g8', 'j8g8', 'j8h8', 'j8i8', 'e8f7', 'c8b6', 'c8d6', 'g6g2', 'g6g3', 'g6f4', 'g6g4', 'g6h4', 'g6e5', 'g6g5', 'g6i5', 'g6a6', 'g6b6', 'g6c6', 'g6d6', 'g6e6', 'g6f6', 'g6h8', 'f8f7', 'f8g8', 'f8i8'] + moves = ['b2b4', 'f7f5', 'c2c3', 'g8d5', 'a2a4', 'h8g6', 'f2f3', 'i8h6', 'h2h3'] + result = sf.legal_moves("capablanca", CAPA, moves) + self.assertCountEqual(legals, result) + self.assertIn("f8i8", result) + + moves = ['a2a4', 'f7f5', 'b2b3', 'g8d5', 'b1a3', 'i8h6', 'c1a2', 'h8g6', 'c2c4'] + result = sf.legal_moves("capablanca", CAPA, moves) + self.assertIn("f8i8", result) + + moves = ['f2f4', 'g7g6', 'g1d4', 'j7j6', 'h1g3', 'b8a6', 'i1h3', 'h7h6'] + result = sf.legal_moves("capablanca", CAPA, moves) + self.assertIn("f1i1", result) + + # Check that chess960 castling notation is used for otherwise ambiguous castling move + # d1e1 is a normal king move, so castling has to be d1f1 + result = sf.legal_moves("diana", "rbnk1r/pppbpp/3p2/5P/PPPPPB/RBNK1R w KQkq - 2 3", []) + self.assertIn("d1f1", result) + + # Atomic960 castling + fen = "7k/8/8/8/8/8/2PP4/1RK4q w Q - 0 1" + moves = sf.legal_moves("atomic", fen, [], True) + # 'c1b1' is the castling move (king to rook square in 960 encoding) and must be illegal + self.assertNotIn("c1b1", moves) + # A normal king/commoner move like c1b2 should remain legal + self.assertIn("c1b2", moves) + + # Atomic960 anti-discovered check with cannon + fen = "8/8/8/8/8/6k1/8/c5KR w K - 0 1" + moves = sf.legal_moves("cannonatomic", fen, [], True) + self.assertNotIn("g1h1", moves) + self.assertIn("g1f1", moves) + + # Check that in variants where castling rooks are not in the corner + # the castling rook is nevertheless assigned correctly + result = sf.legal_moves("shako", "c8c/ernbqkbnre/pppppppppp/10/10/10/10/PPPPPPPPPP/5K2RR/10 w Kkq - 0 1", []) + self.assertIn("f2h2", result) + result = sf.legal_moves("shako", "c8c/ernbqkbnre/pppppppppp/10/10/10/10/PPPPPPPPPP/RR3K4/10 w Qkq - 0 1", []) + self.assertIn("f2d2", result) + + def test_get_fen(self): + result = sf.get_fen("chess", CHESS, []) + self.assertEqual(result, CHESS) + + # incomplete FENs + result = sf.get_fen("chess", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR", []) + self.assertEqual(result, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - - 0 1") + result = sf.get_fen("chess", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -", []) + self.assertEqual(result, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") + result = sf.get_fen("chess", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 1 2", []) + self.assertEqual(result, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - - 1 2") + + # invalid castling rights + result = sf.get_fen("chess", "8/rnbqkbnr/pppppppp/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", []) + self.assertEqual(result, "8/rnbqkbnr/pppppppp/8/8/8/PPPPPPPP/RNBQKBNR w KQ - 0 1") + result = sf.get_fen("chess", "r7/1nbqkbnr/pppppppp/8/8/P6P/RPPPPPPR/1NBQKBN1 w KQkq - 0 1", []) + self.assertEqual(result, "r7/1nbqkbnr/pppppppp/8/8/P6P/RPPPPPPR/1NBQKBN1 w - - 0 1") + + # alternative piece symbols + result = sf.get_fen("janggi", "rhea1aehr/4k4/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/4K4/RHEA1AEHR w - - 0 1", []) + self.assertEqual(result, JANGGI) + + result = sf.get_fen("capablanca", CAPA, []) + self.assertEqual(result, CAPA) + + result = sf.get_fen("xiangqi", XIANGQI, []) + self.assertEqual(result, XIANGQI) + + result = sf.get_fen("seirawan", SEIRAWAN, []) + self.assertEqual(result, SEIRAWAN) + + # test idempotence for S-Chess960 gating flags + fen1 = sf.get_fen("seirawan", SEIRAWAN, [], True) + fen2 = sf.get_fen("seirawan", fen1, [], True) + self.assertEqual(fen1, fen2) + + fen = "rnab1kbcnr/ppppPppppp/10/4q5/10/10/PPPPP1PPPP/RNABQKBCNR[p] b KQkq - 0 3" + result = sf.get_fen("capahouse", CAPA, ["f2f4", "e7e5", "f4e5", "e8e5", "P@e7"]) + self.assertEqual(result, fen) + + fen0 = "reb1k2r/ppppqppp/2nb1n2/4p3/4P3/N1P2N2/PB1PQPPP/RE2KBHR[h] b KQkqac - 2 6" + fen1 = "reb2rk1/ppppqppp/2nb1n2/4p3/4P3/N1P2N2/PB1PQPPP/RE2KBHR[h] w KQac - 3 7" + result = sf.get_fen("seirawan", fen0, ["e8g8"]) + self.assertEqual(result, fen1) + + # handle invalid castling/gating flags + fen0 = "rnbq3r/pp2bkpp/8/2p1p2K/2p1P3/8/PPPP1PPP/RNB4R[EHeh] b QBCEHabcdk - 0 10" + fen1 = "rnbq3r/pp2bkpp/8/2p1p2K/2p1P3/8/PPPP1PPP/RNB4R[EHeh] b ABCHabcdh - 0 10" + result = sf.get_fen("seirawan", fen0, []) + self.assertEqual(result, fen1) + + result = sf.get_fen("chess", CHESS, [], True, False, False) + self.assertEqual(result, CHESS960) + + # test O-O-O + fen = "rbkqnrbn/pppppppp/8/8/8/8/PPPPPPPP/RBKQNRBN w AFaf - 0 1" + moves = ["d2d4", "f7f5", "e1f3", "h8g6", "h1g3", "c7c6", "c2c3", "e7e6", "b1d3", "d7d5", "d1c2", "b8d6", "e2e3", "d8d7", "c1a1"] + result = sf.get_fen("chess", fen, moves, True, False, False) + self.assertEqual(result, "r1k1nrb1/pp1q2pp/2pbp1n1/3p1p2/3P4/2PBPNN1/PPQ2PPP/2KR1RB1 b fa - 2 8") + + # passing should not affect castling rights + fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" + result = sf.get_fen("passchess", fen, ["e1e1", "e8e8"]) + self.assertEqual(result, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 2 2") + + # only irreversible moves should reset 50 move rule counter + fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" + result = sf.get_fen("pawnsideways", fen, ["e2e4", "g8f6", "e4d4"]) + self.assertEqual(result, "rnbqkb1r/pppppppp/5n2/8/3P4/8/PPPP1PPP/RNBQKBNR b KQkq - 2 2") + result = sf.get_fen("pawnback", fen, ["e2e4", "e7e6"]) + self.assertEqual(result, "rnbqkbnr/pppp1ppp/4p3/8/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 2 2") + result = sf.get_fen("pocketknight", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[Nn] w KQkq - 0 1", ["N@e4"]) + self.assertEqual(result, "rnbqkbnr/pppppppp/8/8/4N3/8/PPPPPPPP/RNBQKBNR[n] b KQkq - 0 1") + + # duck chess en passant + fen = "r1b1k3/pp3pb1/4p3/2p2p2/2PpP2q/1P1P1P2/P1K1*3/RN1Q2N1 b q e3 0 17" + result = sf.get_fen("duck", fen, []) + self.assertEqual(result, fen) + + # SFEN + result = sf.get_fen("shogi", SHOGI, [], False, True) + self.assertEqual(result, SHOGI_SFEN) + + # makruk FEN + fen = "rnsmksnr/8/1pM~1pppp/p7/8/PPPP1PPP/8/RNSKMSNR b - - 0 3" + result = sf.get_fen("makruk", MAKRUK, ["e3e4", "d6d5", "e4d5", "a6a5", "d5c6m"], False, False, True) + self.assertEqual(result, fen) + result = sf.get_fen("makruk", fen, [], False, False, True) + self.assertEqual(result, fen) + + # makruk piece honor counting + fen = "8/3k4/8/2K1S1P1/8/8/8/8 w - - 0 1" + moves = ["g5g6m"] + result = sf.get_fen("makruk", fen, moves, False, False, True) + self.assertEqual(result, "8/3k4/6M~1/2K1S3/8/8/8/8 b - 88 8 1") + + fen = "8/2K3k1/5m2/4S1S1/8/8/8/8 w - 128 97 1" + moves = ["e5f6"] + result = sf.get_fen("makruk", fen, moves, False, False, True) + self.assertEqual(result, "8/2K3k1/5S2/6S1/8/8/8/8 b - 44 8 1") + + # ignore count_started for piece honor counting + fen = "8/3k4/8/2K1S1P1/8/8/8/8 w - - 0 1" + moves = ["g5g6m"] + result = sf.get_fen("makruk", fen, moves, False, False, True, -1) + self.assertEqual(result, "8/3k4/6M~1/2K1S3/8/8/8/8 b - 88 8 1") + + fen = "8/2K3k1/5m2/4S1S1/8/8/8/8 w - 128 1 30" + moves = ["e5f6"] + result = sf.get_fen("makruk", fen, moves, False, False, True, 58) + self.assertEqual(result, "8/2K3k1/5S2/6S1/8/8/8/8 b - 44 8 30") + + # makruk board honor counting + fen = "3k4/2m5/8/4MP2/3KS3/8/8/8 w - - 0 1" + moves = ["f5f6m"] + result = sf.get_fen("makruk", fen, moves, False, False, True) + self.assertEqual(result, "3k4/2m5/5M~2/4M3/3KS3/8/8/8 b - 128 0 1") + + fen = "3k4/2m5/5M~2/4M3/3KS3/8/8/8 w - 128 0 33" + moves = ["d4d5"] + result = sf.get_fen("makruk", fen, moves, False, False, True) + self.assertEqual(result, "3k4/2m5/5M~2/3KM3/4S3/8/8/8 b - 128 1 33") + + fen = "3k4/2m5/5M~2/4M3/3KS3/8/8/8 w - 128 36 1" + moves = ["d4d5"] + result = sf.get_fen("makruk", fen, moves, False, False, True) + self.assertEqual(result, "3k4/2m5/5M~2/3KM3/4S3/8/8/8 b - 128 37 1") + + fen = "3k4/2m5/5M~2/4M3/3KS3/8/8/8 w - 128 0 33" + moves = ["d4d5"] + result = sf.get_fen("makruk", fen, moves, False, False, True, -1) + self.assertEqual(result, "3k4/2m5/5M~2/3KM3/4S3/8/8/8 b - 128 0 33") + + fen = "3k4/2m5/5M~2/4M3/3KS3/8/8/8 w - 128 7 33" + moves = ["d4d5"] + result = sf.get_fen("makruk", fen, moves, False, False, True, 58) + self.assertEqual(result, "3k4/2m5/5M~2/3KM3/4S3/8/8/8 b - 128 8 33") + + # ouk piece honor counting + fen = "8/3k4/8/2K1S1P1/8/8/8/8 w - - 0 1" + moves = ["g5g6m"] + result = sf.get_fen("cambodian", fen, moves, False, False, True) + self.assertEqual(result, "8/3k4/6M~1/2K1S3/8/8/8/8 b - 86 8 1") + + fen = "8/2K3k1/5m2/4S1S1/8/8/8/8 w - 128 97 1" + moves = ["e5f6"] + result = sf.get_fen("cambodian", fen, moves, False, False, True) + self.assertEqual(result, "8/2K3k1/5S2/6S1/8/8/8/8 b - 42 8 1") + + # adjust to board honor counting if it's faster + fen = "8/3k4/8/2K1S1P1/8/8/8/8 w - - 0 1" + moves = ["g5g6m"] + result = sf.get_fen("cambodian", fen, moves, False, False, True, -1) + self.assertEqual(result, "8/3k4/6M~1/2K1S3/8/8/8/8 b - 86 8 1") + + fen = "8/2K3k1/5m2/4S1S1/8/8/8/8 w - 126 101 80" + moves = ["e5f6"] + result = sf.get_fen("cambodian", fen, moves, False, False, True, 58) + self.assertEqual(result, "8/2K3k1/5S2/6S1/8/8/8/8 b - 126 102 80") + + # pawn promotion triggers piece honor counting + fen = "8/8/4k3/5P2/8/2RMK3/8/8 w - 126 41 50" + moves = ["f5f6m"] + result = sf.get_fen("cambodian", fen, moves, False, False, True, 58) + self.assertEqual(result, "8/8/4kM~2/8/8/2RMK3/8/8 b - 30 10 50") + + # king capturing the last unpromoted pawn triggers piece honor counting + fen = "8/8/4k3/5P2/8/2RMK3/8/8 b - 126 42 50" + moves = ["e6f5"] + result = sf.get_fen("cambodian", fen, moves, False, False, True, 58) + self.assertEqual(result, "8/8/8/5k2/8/2RMK3/8/8 w - 30 7 51") + + # ouk board honor counting + fen = "3k4/2m5/8/4MP2/3KS3/8/8/8 w - - 0 1" + moves = ["f5f6m"] + result = sf.get_fen("cambodian", fen, moves, False, False, True) + self.assertEqual(result, "3k4/2m5/5M~2/4M3/3KS3/8/8/8 b - 126 0 1") + + fen = "3k4/2m5/5M~2/4M3/3KS3/8/8/8 w - 126 0 33" + moves = ["d4d5"] + result = sf.get_fen("cambodian", fen, moves, False, False, True) + self.assertEqual(result, "3k4/2m5/5M~2/3KM3/4S3/8/8/8 b - 126 1 33") + + fen = "3k4/2m5/5M~2/4M3/3KS3/8/8/8 w - 126 36 1" + moves = ["d4d5"] + result = sf.get_fen("cambodian", fen, moves, False, False, True) + self.assertEqual(result, "3k4/2m5/5M~2/3KM3/4S3/8/8/8 b - 126 37 1") + + fen = "3k4/2m5/5M~2/4M3/3KS3/8/8/8 w - 126 0 33" + moves = ["d4d5"] + result = sf.get_fen("cambodian", fen, moves, False, False, True, -1) + self.assertEqual(result, "3k4/2m5/5M~2/3KM3/4S3/8/8/8 b - 126 0 33") + + fen = "3k4/2m5/5M~2/4M3/3KS3/8/8/8 w - 126 7 33" + moves = ["d4d5"] + result = sf.get_fen("cambodian", fen, moves, False, False, True, 58) + self.assertEqual(result, "3k4/2m5/5M~2/3KM3/4S3/8/8/8 b - 126 8 33") + + # asean counting + fen = "4k3/3r4/2K5/8/3R4/8/8/8 w - - 0 1" + moves = ["d4d7"] + result = sf.get_fen("asean", fen, moves, False, False, False) + self.assertEqual(result, "4k3/3R4/2K5/8/8/8/8/8 b - 32 0 1") + + fen = "4k3/3r4/2K5/8/3R4/1P6/8/8 w - - 0 1" + moves = ["d4d7"] + result = sf.get_fen("asean", fen, moves, False, False, False) + self.assertEqual(result, "4k3/3R4/2K5/8/8/1P6/8/8 b - - 0 1") + + fen = "8/2P1k3/2K5/8/8/8/8/8 w - - 0 1" + moves = ["c7c8b"] + result = sf.get_fen("asean", fen, moves, False, False, False) + self.assertEqual(result, "2B5/4k3/2K5/8/8/8/8/8 b - 88 0 1") + + fen = "8/8/4K3/3Q4/1k1N4/5b2/8/8 w - - 0 1" + moves = ["d4f3"] + result = sf.get_fen("asean", fen, moves, False, False, False) + self.assertEqual(result, "8/8/4K3/3Q4/1k6/5N2/8/8 b - 128 0 1") + + fen = "3Q4/4P3/4K3/3Q4/1k6/8/8/8 w - - 0 1" + moves = ["e7e8q"] + result = sf.get_fen("asean", fen, moves, False, False, False) + self.assertEqual(result, "3QQ3/8/4K3/3Q4/1k6/8/8/8 b - - 0 1") + + # Cambodian king loses its leap ability when it is "aimed" by a rook + fen = "rnsmk1nr/4s3/pppppppp/8/8/PPPPPPPP/R7/1NSKMSNR w DEde - 2 2" + moves = ["a2e2"] + result = sf.get_fen("cambodian", fen, moves, False, False, True) + self.assertEqual(result, "rnsmk1nr/4s3/pppppppp/8/8/PPPPPPPP/4R3/1NSKMSNR b DEd - 3 2") + + fen = "1nsmksnr/r7/pppppppp/8/8/PPPPPPPP/2SN4/R2KMSNR b DEde - 3 2" + moves = ["a7d7"] + result = sf.get_fen("cambodian", fen, moves, False, False, True) + self.assertEqual(result, "1nsmksnr/3r4/pppppppp/8/8/PPPPPPPP/2SN4/R2KMSNR w Ede - 4 3") + + fen = "rnsmksnr/8/1ppppppp/8/8/1PPPPPPP/8/RNSKMSNR w DEde - 0 1" + moves = ["a1a8"] + result = sf.get_fen("cambodian", fen, moves, False, False, True) + self.assertEqual(result, "Rnsmksnr/8/1ppppppp/8/8/1PPPPPPP/8/1NSKMSNR b DEd - 0 1") + + def test_get_san(self): + fen = "4k3/8/3R4/8/1R3R2/8/3R4/4K3 w - - 0 1" + result = sf.get_san("chess", fen, "b4d4") + self.assertEqual(result, "Rbd4") + + result = sf.get_san("chess", fen, "f4d4") + self.assertEqual(result, "Rfd4") + + result = sf.get_san("chess", fen, "d2d4") + self.assertEqual(result, "R2d4") + + result = sf.get_san("chess", fen, "d6d4") + self.assertEqual(result, "R6d4") + + fen = "4k3/8/3R4/3P4/1RP1PR2/8/3R4/4K3 w - - 0 1" + result = sf.get_san("chess", fen, "d2d4") + self.assertEqual(result, "Rd4") + + fen = "1r2k3/P1P5/8/8/8/8/8/4K3 w - - 0 1" + result = sf.get_san("chess", fen, "c7b8q") + self.assertEqual(result, "cxb8=Q+") + + fen = "1r2k3/P1P5/8/8/8/8/8/4K3 w - - 0 1" + result = sf.get_san("chess", fen, "c7b8q", False, sf.NOTATION_LAN) + self.assertEqual(result, "c7xb8=Q+") + + result = sf.get_san("capablanca", CAPA, "e2e4") + self.assertEqual(result, "e4") + + result = sf.get_san("capablanca", CAPA, "e2e4", False, sf.NOTATION_LAN) + self.assertEqual(result, "e2-e4") + + result = sf.get_san("capablanca", CAPA, "h1i3") + self.assertEqual(result, "Ci3") + + result = sf.get_san("sittuyin", SITTUYIN, "R@a1") + self.assertEqual(result, "R@a1") + + fen = "3rr3/1kn3n1/1ss1p1pp/1pPpP3/6PP/p3KN2/2SSFN2/3R3R[] b - - 0 14" + result = sf.get_san("sittuyin", fen, "c6c5") + self.assertEqual(result, "Scxc5") + + fen = "7R/1r6/3k1np1/3s2N1/3s3P/4n3/6p1/2R3K1[] w - - 2 55" + result = sf.get_san("sittuyin", fen, "h4h4f") + self.assertEqual(result, "h4=F") + + fen = "k7/2K3P1/8/4P3/8/8/8/1R6[] w - - 0 1" + result = sf.get_san("sittuyin", fen, "e5f6f") + self.assertEqual(result, "e5f6=F") + + result = sf.get_san("shogi", SHOGI, "i3i4") + self.assertEqual(result, "P-16") + + result = sf.get_san("shogi", SHOGI, "i3i4", False, sf.NOTATION_SHOGI_HOSKING) + self.assertEqual(result, "P16") + + result = sf.get_san("shogi", SHOGI, "f1e2", False, sf.NOTATION_SHOGI_HOSKING) + self.assertEqual(result, "G49-58") + result = sf.get_san("shogi", SHOGI, "f1e2", False, sf.NOTATION_SHOGI_HODGES) + self.assertEqual(result, "G4i-5h") + result = sf.get_san("shogi", SHOGI, "f1e2", False, sf.NOTATION_SHOGI_HODGES_NUMBER) + self.assertEqual(result, "G49-58") + + # Disambiguation of promotion moves + fen = "p1ksS/n1n2/4P/5/+L1K1+L[] b - - 3 9" + result = sf.get_san("kyotoshogi", fen, "c4b2+", False, sf.NOTATION_SHOGI_HODGES_NUMBER) + self.assertEqual(result, "N32-44+") + result = sf.get_san("kyotoshogi", fen, "a4b2+", False, sf.NOTATION_SHOGI_HODGES_NUMBER) + self.assertEqual(result, "N52-44+") + + # Demotion + fen = "p+nks+l/5/5/L4/1SK+NP[-] b 0 1" + result = sf.get_san("kyotoshogi", fen, "e5e4-", False, sf.NOTATION_SAN) + self.assertEqual(result, "Ge4=L") + + fen = "lnsgkgsnl/1r5b1/pppppp1pp/6p2/9/2P6/PP1PPPPPP/1B5R1/LNSGKGSNL w -" + result = sf.get_san("shogi", fen, "b2h8", False, sf.NOTATION_SHOGI_HODGES) + self.assertEqual(result, "Bx2b=") + result = sf.get_san("shogi", fen, "b2h8+", False, sf.NOTATION_SHOGI_HODGES) + self.assertEqual(result, "Bx2b+") + + fen = "lnsgkg1nl/1r5s1/pppppp1pp/6p2/9/2P6/PP1PPPPPP/7R1/LNSGKGSNL[Bb] w " + result = sf.get_san("shogi", fen, "B@g7", False, sf.NOTATION_SHOGI_HODGES) + self.assertEqual(result, "B*3c") + result = sf.get_san("shogi", fen, "B@g7", False, sf.NOTATION_SHOGI_HODGES_NUMBER) + self.assertEqual(result, "B*33") + + fen = "lnsgkg1nl/1r4s+B1/pppppp1pp/6p2/9/2P6/PP1PPPPPP/7R1/LNSGKGSNL[B] w " + result = sf.get_san("shogi", fen, "h8g7", False, sf.NOTATION_SHOGI_HODGES) + self.assertEqual(result, "+B-3c") + + fen = "lnk2gsnl/7b1/p1p+SGp1pp/6p2/1pP6/4P4/PP3PPPP/1S2G2R1/L2GK1bNL[PRppns] w " + result = sf.get_san("shogi", fen, "d7d8", False, sf.NOTATION_SHOGI_HODGES) + self.assertEqual(result, "+S-6b") + + result = sf.get_san("xiangqi", XIANGQI, "h1g3") + self.assertEqual(result, "Hg3") + + result = sf.get_san("xiangqi", XIANGQI, "h1g3", False, sf.NOTATION_XIANGQI_WXF) + self.assertEqual(result, "H2+3") + + result = sf.get_san("xiangqi", XIANGQI, "c1e3") + self.assertEqual(result, "Ece3") + + result = sf.get_san("xiangqi", XIANGQI, "c1e3", False, sf.NOTATION_XIANGQI_WXF) + self.assertEqual(result, "E7+5") + + result = sf.get_san("xiangqi", XIANGQI, "h3h10") + self.assertEqual(result, "Cxh10") + + result = sf.get_san("xiangqi", XIANGQI, "h3h10", False, sf.NOTATION_XIANGQI_WXF) + self.assertEqual(result, "C2+7") + + result = sf.get_san("xiangqi", XIANGQI, "h3h5") + self.assertEqual(result, "Ch5") + + # WXF notation does not denote check or checkmate + fen = "4k4/4a3R/9/9/9/9/9/9/4K4/9 w - - 0 1" + result = sf.get_san("xiangqi", fen, "i9e9", False) + self.assertEqual(result, "Rxe9+") + result = sf.get_san("xiangqi", fen, "i9e9", False, sf.NOTATION_XIANGQI_WXF) + self.assertEqual(result, "R1=5") + result = sf.get_san("xiangqi", fen, "i9i10", False) + self.assertEqual(result, "Ri10#") + result = sf.get_san("xiangqi", fen, "i9i10", False, sf.NOTATION_XIANGQI_WXF) + self.assertEqual(result, "R1+1") + + # skip disambiguation for elephants and advisors, but not for pieces that require it + fen = "rnbakabnr/9/1c5c1/p1p1p1p1p/4P4/1NB6/P1P1P3P/1C1A3C1/9/RNBAK4 w - - 0 1" + result = sf.get_san("xiangqi", fen, "c5e3", False, sf.NOTATION_XIANGQI_WXF) + self.assertEqual(result, "E7-5") + result = sf.get_san("xiangqi", fen, "d1e2", False, sf.NOTATION_XIANGQI_WXF) + self.assertEqual(result, "A6+5") + result = sf.get_san("xiangqi", fen, "b5c7", False, sf.NOTATION_XIANGQI_WXF) + self.assertEqual(result, "H++7") + result = sf.get_san("xiangqi", fen, "e6e7", False, sf.NOTATION_XIANGQI_WXF) + self.assertEqual(result, "P++1") + result = sf.get_san("xiangqi", fen, "e4e5", False, sf.NOTATION_XIANGQI_WXF) + self.assertEqual(result, "P-+1") + + # Tandem pawns + fen = "rnbakabnr/9/1c5c1/p1p1P1p1p/4P4/9/P3P3P/1C5C1/9/RNBAKABNR w - - 0 1" + result = sf.get_san("xiangqi", fen, "e7d7", False, sf.NOTATION_XIANGQI_WXF) + self.assertEqual(result, "15=6") + result = sf.get_san("xiangqi", fen, "e6d6", False, sf.NOTATION_XIANGQI_WXF) + self.assertEqual(result, "25=6") + result = sf.get_san("xiangqi", fen, "e4e5", False, sf.NOTATION_XIANGQI_WXF) + self.assertEqual(result, "35+1") + + # use tandem pawn notation for pair of tandem pawns + fen = "5k3/9/3P5/3P1P1P1/5P3/9/9/9/9/4K4 w - - 0 1" + result = sf.get_san("xiangqi", fen, "d7e7", False, sf.NOTATION_XIANGQI_WXF) + self.assertEqual(result, "26=5") + result = sf.get_san("xiangqi", fen, "f6e6", False, sf.NOTATION_XIANGQI_WXF) + self.assertEqual(result, "24=5") + + fen = "1rb1ka2r/4a4/2ncb1nc1/p1p1p1p1p/9/2P6/P3PNP1P/2N1C2C1/9/R1BAKAB1R w - - 1 7" + result = sf.get_san("xiangqi", fen, "c3e2") + self.assertEqual(result, "Hce2") + + result = sf.get_san("xiangqi", fen, "c3d5") + self.assertEqual(result, "Hd5") + + result = sf.get_san("janggi", JANGGI, "b1c3", False, sf.NOTATION_JANGGI) + self.assertEqual(result, "H02-83") + + fen = "1b1aa2b1/5k3/3ncn3/1pp1pp3/5r2p/9/P1PPB1PPB/2N1CCN1c/9/R2AKAR2 w - - 19 17" + result = sf.get_san("janggi", fen, "d1e2", False, sf.NOTATION_SAN) + self.assertEqual(result, "Ade2") + + fen = "1Pbcka3/3nNn1c1/N2CaC3/1pB6/9/9/5P3/9/4K4/9 w - - 0 23" + result = sf.get_san("janggi", fen, "f8f10", False, sf.NOTATION_SAN) + self.assertEqual(result, "Cfxf10") + + result = sf.get_san("makruk", MAKRUK, "e3e4") + self.assertEqual(result, "e4") + result = sf.get_san("makruk", MAKRUK, "e3e4", False, sf.NOTATION_THAI_SAN) + self.assertEqual(result, "จ๔") + result = sf.get_san("makruk", MAKRUK, "e3e4", False, sf.NOTATION_THAI_LAN) + self.assertEqual(result, "บ จ๓-จ๔") + + fen = "r1smksnr/3n4/pppp1ppp/4p3/4PP2/PPPP2PP/8/RNSKMSNR w - - 0 1" + result = sf.get_san("makruk", fen, "f4e5") + self.assertEqual(result, "fxe5") + result = sf.get_san("makruk", fen, "f4e5", False, sf.NOTATION_THAI_SAN) + self.assertEqual(result, "ฉxจ๕") + result = sf.get_san("makruk", fen, "f4e5", False, sf.NOTATION_THAI_LAN) + self.assertEqual(result, "บ ฉ๔xจ๕") + + fen = "rnsm1s1r/4n1k1/1ppppppp/p7/2PPP3/PP3PPP/4N2R/RNSKMS2 b - - 1 5" + result = sf.get_san("makruk", fen, "f8f7") + self.assertEqual(result, "Sf7") + result = sf.get_san("makruk", fen, "f8f7", False, sf.NOTATION_THAI_SAN) + self.assertEqual(result, "ค-ฉ๗") + result = sf.get_san("makruk", fen, "f8f7", False, sf.NOTATION_THAI_LAN) + self.assertEqual(result, "ค ฉ๘-ฉ๗") + + fen = "4k3/8/8/4S3/8/2S5/8/4K3 w - - 0 1" + result = sf.get_san("makruk", fen, "e5d4") + self.assertEqual(result, "Sed4") + result = sf.get_san("makruk", fen, "c3d4") + self.assertEqual(result, "Scd4") + result = sf.get_san("makruk", fen, "e5d4", False, sf.NOTATION_THAI_SAN) + self.assertEqual(result, "คจ-ง๔") + result = sf.get_san("makruk", fen, "c3d4", False, sf.NOTATION_THAI_SAN) + self.assertEqual(result, "คค-ง๔") + result = sf.get_san("makruk", fen, "e5d4", False, sf.NOTATION_THAI_LAN) + self.assertEqual(result, "ค จ๕-ง๔") + result = sf.get_san("makruk", fen, "c3d4", False, sf.NOTATION_THAI_LAN) + self.assertEqual(result, "ค ค๓-ง๔") + + # Distinction between the regular met and the promoted pawn + fen = "4k3/8/4M3/4S3/8/2S5/8/4K3 w - - 0 1" + result = sf.get_san("makruk", fen, "e6d5", False, sf.NOTATION_THAI_SAN) + self.assertEqual(result, "ม็-ง๕") + fen = "4k3/8/4M~3/4S3/8/2S5/8/4K3 w - - 0 1" + result = sf.get_san("makruk", fen, "e6d5", False, sf.NOTATION_THAI_SAN) + self.assertEqual(result, "ง-ง๕") + + fen = "4k3/8/8/3S4/8/3S4/8/4K3 w - - 0 1" + result = sf.get_san("makruk", fen, "d3d4") + self.assertEqual(result, "Sd4") + result = sf.get_san("makruk", fen, "d3d4", False, sf.NOTATION_THAI_SAN) + self.assertEqual(result, "ค-ง๔") + result = sf.get_san("makruk", fen, "d3d4", False, sf.NOTATION_THAI_LAN) + self.assertEqual(result, "ค ง๓-ง๔") + + + UCI_moves = ["e2e4", "e7e5", "g1f3", "b8c6h", "f1c4", "f8c5e"] + SAN_moves = ["e4", "e5", "Nf3", "Nc6/H", "Bc4", "Bc5/E"] + + fen = SEIRAWAN + for i, move in enumerate(UCI_moves): + result = sf.get_san("seirawan", fen, move) + self.assertEqual(result, SAN_moves[i]) + fen = sf.get_fen("seirawan", SEIRAWAN, UCI_moves[:i + 1]) + + result = sf.get_san("seirawan", fen, "e1g1") + self.assertEqual(result, "O-O") + + result = sf.get_san("seirawan", fen, "e1g1h") + self.assertEqual(result, "O-O/He1") + result = sf.get_san("seirawan", fen, "e1g1e") + self.assertEqual(result, "O-O/Ee1") + + result = sf.get_san("seirawan", fen, "h1e1h") + self.assertEqual(result, "O-O/Hh1") + result = sf.get_san("seirawan", fen, "h1e1e") + self.assertEqual(result, "O-O/Eh1") + + # Disambiguation only when necessary + fen = "rnbqkb1r/ppp1pppp/5n2/3p4/3P4/5N2/PPP1PPPP/RNBQKB1R[EHeh] w KQABCDEFHkqabcdefh - 2 3" + result = sf.get_san("seirawan", fen, "b1d2e") + self.assertEqual(result, "Nd2/E") + result = sf.get_san("seirawan", fen, "b1d2") + self.assertEqual(result, "Nbd2") + + def test_get_san_moves(self): + UCI_moves = ["e2e4", "e7e5", "g1f3", "b8c6h", "f1c4", "f8c5e"] + SAN_moves = ["e4", "e5", "Nf3", "Nc6/H", "Bc4", "Bc5/E"] + result = sf.get_san_moves("seirawan", SEIRAWAN, UCI_moves) + self.assertEqual(result, SAN_moves) + + UCI_moves = ["c3c4", "g7g6", "b2h8"] + SAN_moves = ["P-76", "P-34", "Bx22="] + result = sf.get_san_moves("shogi", SHOGI, UCI_moves) + self.assertEqual(result, SAN_moves) + + UCI_moves = ["h3e3", "h10g8", "h1g3", "c10e8", "a1a3", "i10h10"] + SAN_moves = ["C2=5", "H8+7", "H2+3", "E3+5", "R9+2", "R9=8"] + result = sf.get_san_moves("xiangqi", XIANGQI, UCI_moves, False, sf.NOTATION_XIANGQI_WXF) + self.assertEqual(result, SAN_moves) + + UCI_moves = ["e2e4", "d7d5", "f1a6+", "d8d6"] + SAN_moves = ["e4", "d5", "Ba6=A", "Qd6"] + result = sf.get_san_moves("shogun", SHOGUN, UCI_moves) + self.assertEqual(result, SAN_moves) + + def test_gives_check(self): + result = sf.gives_check("capablanca", CAPA, []) + self.assertFalse(result) + + result = sf.gives_check("capablanca", CAPA, ["e2e4"]) + self.assertFalse(result) + + moves = ["g2g3", "d7d5", "a2a3", "c8h3"] + result = sf.gives_check("capablanca", CAPA, moves) + self.assertTrue(result) + + # Test giving check to pseudo royal piece + result = sf.gives_check("atomic", CHESS, []) + self.assertFalse(result) + + result = sf.gives_check("atomic", CHESS, ["e2e4"]) + self.assertFalse(result) + + result = sf.gives_check("atomic", CHESS, ["e2e4", "d7d5", "f1b5"]) + self.assertTrue(result) + + result = sf.gives_check("atomic", "rnbqkbnr/ppp2ppp/8/8/8/8/PPP2PPP/RNBQKBNR w KQkq - 0 4", ["d1d7"]) + self.assertTrue(result) + + result = sf.gives_check("atomic", "8/8/kK6/8/8/8/Q7/8 b - - 0 1", []) + self.assertFalse(result) + + # pseudo-royal duple check + result = sf.gives_check("spartan", "lgkcckw1/hhhhhhhh/1N3lN1/8/8/8/PPPPPPPP/R1BQKB1R b KQ - 11 6", []) + self.assertTrue(result) + result = sf.gives_check("spartan", "lgkcckwl/hhhhhhhh/6N1/8/8/8/PPPPPPPP/RNBQKB1R b KQ - 5 3", []) + self.assertFalse(result) + result = sf.gives_check("spartan", "lgkcckwl/hhhhhhhh/8/8/8/8/PPPPPPPP/RNBQKBNR w KQ - 0 1", []) + self.assertFalse(result) + + # Shako castling discovered check + result = sf.gives_check("shako", "10/5r4/2p3pBk1/1p6Pr/p3p5/9e/1PP2P4/P2P2PP2/ER3K2R1/8C1 w K - 7 38", ["f2h2"]) + self.assertTrue(result) + + # Janggi palace discovered check + result = sf.gives_check("janggi", "4ka3/4a4/9/4R4/2B6/9/9/5K3/4p4/3r5 b - - 0 113", ["e2f2"]) + self.assertTrue(result) + + def test_is_capture(self): + result = sf.is_capture("chess", CHESS, [], "e2e4") + self.assertFalse(result) + + result = sf.is_capture("chess", CHESS, ["e2e4", "e7e5", "g1f3", "b8c6", "f1c4", "f8c5"], "e1g1") + self.assertFalse(result) + + result = sf.is_capture("chess", CHESS, ["e2e4", "g8f6", "e4e5", "d7d5"], "e5f6") + self.assertTrue(result) + + # en passant + result = sf.is_capture("chess", CHESS, ["e2e4", "g8f6", "e4e5", "d7d5"], "e5d6") + self.assertTrue(result) + + # 960 castling + result = sf.is_capture("chess", "bqrbkrnn/pppppppp/8/8/8/8/PPPPPPPP/BQRBKRNN w CFcf - 0 1", ["g1f3", "h8g6"], "e1f1", True) + self.assertFalse(result) + + # Sittuyin in-place promotion + result = sf.is_capture("sittuyin", "8/2k5/8/4P3/4P1N1/5K2/8/8[] w - - 0 1", [], "e5e5f") + self.assertFalse(result) + + def test_piece_to_partner(self): + # take the rook and promote to queen + result = sf.piece_to_partner("bughouse", "r2qkbnr/1Ppppppp/2n5/8/8/8/1PPPPPPP/RNBQKBNR[] w KQkq - 0 1", ["b7a8q"]) + self.assertEqual(result, "r") + + # take back the queen (promoted pawn) + result = sf.piece_to_partner("bughouse", "r2qkbnr/1Ppppppp/2n5/8/8/8/1PPPPPPP/RNBQKBNR[] w KQkq - 0 1", ["b7a8q", "d8a8"]) + self.assertEqual(result, "P") + + # just a simple move (no take) + result = sf.piece_to_partner("bughouse", "r2qkbnr/1Ppppppp/2n5/8/8/8/1PPPPPPP/RNBQKBNR[] w KQkq - 0 1", ["b7a8q", "d8b8"]) + self.assertEqual(result, "") + + # silver takes the pawn and promotes to gold + result = sf.piece_to_partner("shogi", "lnsgkgsnl/1r5b1/ppppppppp/S8/9/9/PPPPPPPPP/1B5R1/LNSGKG1NL[] w 0 1", ["a6a7+"]) + self.assertEqual(result, "p") + + # take back the gold (promoted silver) + result = sf.piece_to_partner("shogi", "lnsgkgsnl/1r5b1/ppppppppp/S8/9/9/PPPPPPPPP/1B5R1/LNSGKG1NL[] w 0 1", ["a6a7+", "a9a7"]) + self.assertEqual(result, "S") + + def test_game_result(self): + result = sf.game_result("chess", CHESS, ["f2f3", "e7e5", "g2g4", "d8h4"]) + self.assertEqual(result, -sf.VALUE_MATE) + + # shogi pawn drop mate + result = sf.game_result("shogi", "lnsg3nk/1r2b1gs1/ppppppp1p/7N1/7p1/9/PPPPPPPP1/1B5R1/LNSGKGS1L[P] w 0 1", ["P@i8"]) + self.assertEqual(result, sf.VALUE_MATE) + + # losers checkmate + result = sf.game_result("losers", CHESS, ["f2f3", "e7e5", "g2g4", "d8h4"]) + self.assertEqual(result, sf.VALUE_MATE) + + # suicide stalemate + result = sf.game_result("suicide", "8/8/8/7p/7P/8/8/8 w - - 0 1", []) + self.assertEqual(result, sf.VALUE_DRAW) + result = sf.game_result("suicide", "8/8/8/7p/7P/7P/8/8 w - - 0 1", []) + self.assertEqual(result, -sf.VALUE_MATE) + result = sf.game_result("suicide", "8/8/8/7p/7P/8/8/n7 w - - 0 1", []) + self.assertEqual(result, sf.VALUE_MATE) + + # armageddon + # black gets stalemated + result = sf.game_result("armageddon", "k7/P7/K7/8/8/8/8/8 b - - 0 1", []) + self.assertEqual(result, sf.VALUE_MATE) + # white gets stalemated + result = sf.game_result("armageddon", "8/8/8/8/8/k7/p7/K7 w - - 0 1", []) + self.assertEqual(result, -sf.VALUE_MATE) + # 50 move rule + result = sf.game_result("armageddon", "3n4/8/8/3k4/8/3K4/8/3BB3 w - - 100 80", []) + self.assertEqual(result, -sf.VALUE_MATE) + + # atomic check- and stalemate + # checkmate + result = sf.game_result("atomic", "BQ6/Rk6/8/8/8/8/8/4K3 b - - 0 1", []) + self.assertEqual(result, -sf.VALUE_MATE) + # stalemate + result = sf.game_result("atomic", "KQ6/Rk6/2B5/8/8/8/8/8 b - - 0 1", []) + self.assertEqual(result, sf.VALUE_DRAW) + + # royalduck checkmate and stalemate + result = sf.game_result("royalduck", "r1bqkbnr/pp1*p1p1/n2p1pQp/1Bp5/8/2N1PN2/PPPP1PPP/R1B1K2R b KQkq - 1 6", []) + self.assertEqual(result, -sf.VALUE_MATE) + result = sf.game_result("royalduck", "rnbqk1nr/pppp1ppp/4p3/8/7P/5Pb1/PPPPP*P1/RNBQKBNR w KQkq - 1 4", []) + self.assertEqual(result, sf.VALUE_MATE) + + def _check_immediate_game_end(self, variant, fen, moves, game_end, game_result=None): + with self.subTest(variant=variant, fen=fen, game_end=game_end, game_result=game_result): + result = sf.is_immediate_game_end(variant, fen, moves) + self.assertEqual(result[0], game_end) + if game_result is not None: + self.assertEqual(result[1], game_result) + + def test_is_immediate_game_end(self): + self._check_immediate_game_end("capablanca", CAPA, [], False) + + # bikjang (facing kings) + moves = "e2e3 e9f9 h3d3 e7f7 i1i3 h10i8 i3h3 c10e7 h3h8 i10i9 h8b8 i9g9 d3f3 f9e9 f3f10 e7c10 f10c10 b10c8 c10g10 g9f9 b8c8 a10b10 b3f3 f9h9 a1a2 h9f9 a2d2 b10b9 d2d10 e9d10 c8c10 d10d9 f3f9 i8g9 f9b9 a7a6 g10g7 f7f6 e4e5 c7d7 g1e4 i7i6 e4b6 d9d8 c10c8 d8d9 b9g9 d7d6 b6e8 i6h6 e5e6 f6e6 c1e4 a6b6 e4b6 d6d5 c4c5 d9d10 e3d3 h6i6 c5c6 d5c5" + self._check_immediate_game_end("janggi", JANGGI, moves.split(), False) + + moves = "e2e3 e9f9 h3d3 e7f7 i1i3 h10i8 i3h3 c10e7 h3h8 i10i9 h8b8 i9g9 d3f3 f9e9 f3f10 e7c10 f10c10 b10c8 c10g10 g9f9 b8c8 a10b10 b3f3 f9h9 a1a2 h9f9 a2d2 b10b9 d2d10 e9d10 c8c10 d10d9 f3f9 i8g9 f9b9 a7a6 g10g7 f7f6 e4e5 c7d7 g1e4 i7i6 e4b6 d9d8 c10c8 d8d9 b9g9 d7d6 b6e8 i6h6 e5e6 f6e6 c1e4 a6b6 e4b6 d6d5 c4c5 d9d10 e3d3 h6i6 c5c6 d5c5 d3d3" + self._check_immediate_game_end("janggi", JANGGI, moves.split(), True, -sf.VALUE_MATE) + + # full board adjudication + self._check_immediate_game_end("flipello", "pppppppp/pppppppp/pppPpppp/pPpPpppp/pppppppp/pPpPPPPP/ppPpPPpp/pppppppp[PPpp] b - - 63 32", [], True, sf.VALUE_MATE) + self._check_immediate_game_end("ataxx", "PPPpppp/pppPPPp/pPPPPPP/PPPPPPp/ppPPPpp/pPPPPpP/pPPPPPP b - - 99 50", [], True, -sf.VALUE_MATE) + self._check_immediate_game_end("ataxx", "PPPpppp/pppPPPp/pPP*PPP/PP*P*Pp/ppP*Ppp/pPPPPpP/pPPPPPP b - - 99 50", [], True, -sf.VALUE_MATE) + + # dobutsu flag rules + self._check_immediate_game_end("dobutsu", "1L1/1g1/1G1/1l1[] w - - 0 1", ["b2a2"], True, sf.VALUE_MATE) + self._check_immediate_game_end("dobutsu", "1L1/1g1/1G1/1l1[] w - - 0 1", ["b4a4"], True, -sf.VALUE_MATE) + self._check_immediate_game_end("dobutsu", "1L1/1g1/1G1/1l1[] w - - 0 1", ["b2b3"], True, sf.VALUE_DRAW) + self._check_immediate_game_end("dobutsu", "1L1/1g1/1G1/1l1[] w - - 0 1", ["b4b3"], False) + + def _check_optional_game_end(self, variant, fen, moves, game_end, game_result=None): + with self.subTest(variant=variant, fen=fen, game_end=game_end, game_result=game_result): + result = sf.is_optional_game_end(variant, fen, moves) + self.assertEqual(result[0], game_end) + if game_result is not None: + self.assertEqual(result[1], game_result) + + def test_is_optional_game_end(self): + self._check_optional_game_end("capablanca", CAPA, [], False) + + # sittuyin stalemate due to optional promotion + self._check_optional_game_end("sittuyin", "1k4PK/3r4/8/8/8/8/8/8[] w - - 0 1", [], True, sf.VALUE_DRAW) + + # Xiangqi chasing rules + # Also see http://www.asianxiangqi.org/English/AXF_rules_Eng.pdf + # Direct chase by cannon + self._check_optional_game_end("xiangqi", "2bakabnr/9/r1n1c4/2p1p1p1p/PP7/9/4P1P1P/2C3NC1/9/1NBAKAB1R w - - 0 1", ["c3a3", "a8b8", "a3b3", "b8a8", "b3a3", "a8b8", "a3b3", "b8a8", "b3a3"], True, sf.VALUE_MATE) + # Chase with chasing side to move + self._check_optional_game_end("xiangqi", "2bakabnr/9/r1n1c4/2p1p1p1p/PP7/9/4P1P1P/2C3NC1/9/1NBAKAB1R w - - 0 1", ["c3a3", "a8b8", "a3b3", "b8a8", "b3a3", "a8b8", "a3b3", "b8a8", "b3a3", "a8b8", "a3b3", "b8a8"], True, -sf.VALUE_MATE) + # Discovered chase by cannon (including pawn capture) + self._check_optional_game_end("xiangqi", "2bakabr1/9/9/r1p1p1p2/p7R/P8/9/9/9/CC1AKA3 w - - 0 1", ["a5a6", "a7b7", "a6b6", "b7a7", "b6a6", "a7b7", "a6b6", "b7a7", "b6a6"], True, sf.VALUE_MATE) + # Chase by soldier (draw) + self._check_optional_game_end("xiangqi", "2bakabr1/9/9/r1p1p1p2/p7R/P8/9/9/9/1C1AKA3 w - - 0 1", ["a5a6", "a7b7", "a6b6", "b7a7", "b6a6", "a7b7", "a6b6", "b7a7", "b6a6"], True, sf.VALUE_DRAW) + # Discovered and anti-discovered chase by cannon + self._check_optional_game_end("xiangqi", "5k3/9/9/5C3/5c3/5C3/9/9/5p3/4K4 w - - 0 1", ["f5d5", "f6d6", "d5f5", "d6f6", "f5d5", "f6d6", "d5f5", "d6f6"], True, -sf.VALUE_MATE) + # Mutual chase (draw) + self._check_optional_game_end("xiangqi", "4k4/7n1/9/4pR3/9/9/4P4/9/9/4K4 w - - 0 1", ["f7h7"] + 2 * ["h9f8", "h7h8", "f8g6", "h8g8", "g6i7", "g8g7", "i7h9", "g7h7"], True, sf.VALUE_DRAW) + # Perpetual check vs. intermittent checks + self._check_optional_game_end("xiangqi", "9/3kc4/3a5/3P5/9/4p4/9/4K4/9/3C5 w - - 0 1", 2 * ['d7e7', 'e5d5', 'e7d7', 'd5e5'], True, sf.VALUE_MATE) + # Perpetual check by soldier + self._check_optional_game_end("xiangqi", "3k5/9/9/9/9/5p3/9/5p3/5K3/5C3 w - - 0 1", 2 * ['f2e2', 'f3e3', 'e2f2', 'e3f3'], True, sf.VALUE_MATE) + self._check_optional_game_end("xiangqi", "3k5/4P4/4b4/3C5/4c4/9/9/9/9/5K3 w - - 0 1", 2 * ['d7e7', 'e8g6', 'e7d7', 'g6e8'], True, sf.VALUE_MATE) + self._check_optional_game_end("xiangqi", "3k5/9/9/9/9/9/9/9/cr1CAK3/9 w - - 0 1", 2 * ['d2d4', 'b2b4', 'd4d2', 'b4b2'], True, sf.VALUE_MATE) + self._check_optional_game_end("xiangqi", "5k3/9/9/5C3/5c3/5C3/9/9/5p3/4K4 w - - 0 1", 2 * ['f5d5', 'f6d6', 'd5f5', 'd6f6'], True, -sf.VALUE_MATE) + self._check_optional_game_end("xiangqi", "4k4/9/4b4/2c2nR2/9/9/9/9/9/3K5 w - - 0 1", 2 * ['g7g6', 'f7g9', 'g6g7', 'g9f7'], True, sf.VALUE_MATE) + self._check_optional_game_end("xiangqi", "3P5/3k5/3nn4/9/9/9/9/9/9/5K3 w - - 0 1", 2 * ['d10e10', 'd9e9', 'e10d10', 'e9d9'], True, sf.VALUE_MATE) + self._check_optional_game_end("xiangqi", "4ck3/9/9/9/9/2r1R4/9/9/4A4/3AK4 w - - 0 1", 2 * ['e5e4', 'c5c4', 'e4e5', 'c4c5'], True, sf.VALUE_MATE) + self._check_optional_game_end("xiangqi", "4k4/9/9/c1c6/9/r8/9/9/C8/3K5 w - - 0 1", 2 * ['a2c2', 'a5c5', 'c2a2', 'c5a5'], True, sf.VALUE_MATE) + # Mutual perpetual check + self._check_optional_game_end("xiangqi", "9/4c4/3k5/3r5/9/9/4C4/9/4K4/3R5 w - - 0 1", 2 * ['e4d4', 'd7e7', 'd4e4', 'e7d7'], True, sf.VALUE_DRAW) + self._check_optional_game_end("xiangqi", "3k5/6c2/9/7P1/6c2/6P2/9/9/9/5K3 w - - 0 1", 2 * ['h7g7', 'g6h6', 'g7h7', 'h6g6'], True, sf.VALUE_DRAW) + self._check_optional_game_end("xiangqi", "4ck3/9/9/9/9/2r1R1N2/6N2/9/4A4/3AK4 w - - 0 1", 2 * ['e5e4', 'c5c4', 'e4e5', 'c4c5'], True, sf.VALUE_DRAW) + self._check_optional_game_end("xiangqi", "5k3/9/9/c8/9/P1P6/9/2C6/9/3K5 w - - 0 1", 2 * ['c3a3', 'a7c7', 'a3c3', 'c7a7'], True, sf.VALUE_DRAW) + self._check_optional_game_end("xiangqi", "4k4/9/r1r6/9/PPPP5/9/9/9/1C7/5K3 w - - 0 1", ['b2a2'] + 2 * ['a8b8', 'a2c2', 'c8d8', 'c2b2', 'b8a8', 'b2d2', 'd8c8', 'd2a2'], True, sf.VALUE_DRAW) + + # Corner cases + # D106: Chariot chases cannon, but attack actually does not change (draw) + self._check_optional_game_end("xiangqi", "3k2b2/4P4/4b4/9/8p/6Bc1/6P1P/3AB4/4pp3/1p1K3R1[] w - - 0 1", 2 * ["h1h2", "h5h4", "h2h1", "h4h5"], True, sf.VALUE_DRAW) + # D39: Chased chariot pinned by horse + mutual chase (controversial if pinned chariot chases) + self._check_optional_game_end("xiangqi", "2baka1r1/C4rN2/9/1Rp1p4/9/9/4P4/9/4A4/4KA3 w - - 0 1", ["b7b9"] + 2 * ["f10e9", "b9b10", "e9f10", "b10b9"], True, sf.VALUE_MATE) + # D39: Chased chariot pinned by horse + mutual chase (controversial if pinned chariot chases) + self._check_optional_game_end("xiangqi", "5k3/9/9/9/9/9/7r1/9/2nRA3c/4K4 w - - 0 1", 2 * ['e2f1', 'h4h2', 'f1e2', 'h2h4'], True, sf.VALUE_MATE) + # Creating pins to undermine root + self._check_optional_game_end("xiangqi", "4k4/4c4/9/4p4/9/9/3rn4/3NR4/4K4/9 b - - 0 1", 2 * ['e4g5', 'e2f2', 'g5e4', 'f2e2'], True, -sf.VALUE_MATE) + # Discovered check capture threat by rook + self._check_optional_game_end("xiangqi", "5k3/9/9/9/9/1N2P1C2/9/4BC3/9/cr1RK4 w - - 0 1", 2 * ['b5c3', 'b1c1', 'c3b5', 'c1b1'], True, sf.VALUE_MATE) + # Creating a pin to undermine root + discovered check threat by horse + self._check_optional_game_end("xiangqi", "5k3/9/9/9/9/4c4/3n5/3NBA3/4A4/4K4 w - - 0 1", 2 * ['e1d1', 'e5d5', 'd1e1', 'd5e5'], True, sf.VALUE_MATE) + # Creating a pin to undermine root + discovered check threat by rook + self._check_optional_game_end("xiangqi", "5k3/9/9/9/9/4c4/3r5/3NB4/4A4/4K4 w - - 0 1", 2 * ['e1d1', 'e5d5', 'd1e1', 'd5e5'], True, sf.VALUE_MATE) + # X-Ray protected discovered check + self._check_optional_game_end("xiangqi", "5k3/9/9/9/9/9/9/9/9/3NK1cr1 w - - 0 1", 2 * ['d1c3', 'h1h3', 'c3d1', 'h3h1'], True, sf.VALUE_MATE) + # No overprotection by king + self._check_optional_game_end("xiangqi", "3k5/9/9/3n5/9/9/3r5/9/9/3NK4 w - - 0 1", 2 * ['d1c3', 'd4c4', 'c3d1', 'c4d4'], True, sf.VALUE_DRAW) + # Overprotection by king + self._check_optional_game_end("xiangqi", "3k5/9/9/9/9/9/3r5/9/9/3NK4 w - - 0 1", 2 * ['d1c3', 'd4c4', 'c3d1', 'c4d4'], True, sf.VALUE_MATE) + # Mutual pins by flying generals + self._check_optional_game_end("xiangqi", "4k4/9/9/9/4n4/9/5C3/9/4N4/4K4 w - - 0 1", 2 * ['e2g1', 'e10f10', 'g1e2', 'f10e10'], True) #, sf.VALUE_MATE) + # Fake protection by cannon + self._check_optional_game_end("xiangqi", "5k3/9/9/9/9/1C7/1r7/9/1C7/4K4 w - - 0 1", 2 * ['b5c5', 'b4c4', 'c5b5', 'c4b4'], True, sf.VALUE_MATE) + # Fake protection by cannon + mutual chase + self._check_optional_game_end("xiangqi", "4ka3/c2R1R2c/4b4/9/9/9/9/9/9/4K4 w - - 0 1", 2 * ['f9f7', 'f10e9', 'f7f9', 'e9f10'], True, sf.VALUE_DRAW) + + def test_has_insufficient_material(self): + for variant, positions in variant_positions.items(): + for fen, expected_result in positions.items(): + with self.subTest(variant=variant, fen=fen): + result = sf.has_insufficient_material(variant, fen, []) + self.assertEqual(result, expected_result) + + def test_validate_fen(self): + # valid + for variant, positions in variant_positions.items(): + for fen in positions: + with self.subTest(variant=variant, fen=fen): + self.assertEqual(sf.validate_fen(fen, variant), sf.FEN_OK) + # invalid + for variant, positions in invalid_variant_positions.items(): + for fen in positions: + with self.subTest(variant=variant, fen=fen): + self.assertNotEqual(sf.validate_fen(fen, variant), sf.FEN_OK) + # chess960 + self.assertEqual(sf.validate_fen(CHESS960, "chess", True), sf.FEN_OK) + self.assertEqual(sf.validate_fen("nrbqbkrn/pppppppp/8/8/8/8/PPPPPPPP/NRBQBKRN w BGbg - 0 1", "newzealand", True), sf.FEN_OK, "{}: {}".format(variant, fen)) + # all variants starting positions + for variant in sf.variants(): + with self.subTest(variant=variant): + fen = sf.start_fen(variant) + self.assertEqual(sf.validate_fen(fen, variant), sf.FEN_OK) + + def test_validate_fen_promoted_pieces(self): + # Test promoted piece validation specifically + + # Valid promoted pieces should pass + valid_promoted_fens = { + "shogi": [ + "lnsgkgsnl/1r5b1/pppppp+ppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL[-] w - - 0 1", # promoted pawn + "lnsgkgsnl/1r5+b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL[-] w - - 0 1", # promoted bishop + "lnsgkgsnl/1+r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL[-] w - - 0 1", # promoted rook + "ln+sgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL[-] w - - 0 1", # promoted silver + "l+nsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL[-] w - - 0 1", # promoted knight + "+lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL[-] w - - 0 1", # promoted lance + ] + } + + # Invalid promoted pieces should fail with FEN_INVALID_PROMOTED_PIECE (-12) + invalid_promoted_fens = { + "kyotoshogi": [ + "p+nks+l/5/5/5/+LS+K+NP[-] w 0 1", # promoted king (+K) - kings cannot be promoted + ], + "shogi": [ + "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSG++KGSNL[-] w - - 0 1", # double promotion (++K) + ] + } + + # Non-shogi variants should ignore promoted piece syntax ('+' should be invalid character) + non_shogi_promoted_fens = { + "chess": [ + "rnb+qkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", # '+' not valid in chess + ] + } + + # Test valid promoted pieces + for variant, fens in valid_promoted_fens.items(): + for fen in fens: + with self.subTest(variant=variant, fen=fen, test_type="valid_promoted"): + result = sf.validate_fen(fen, variant) + self.assertEqual(result, sf.FEN_OK, f"Expected valid promoted piece FEN to pass: {fen}") + + # Test invalid promoted pieces (should return FEN_INVALID_PROMOTED_PIECE = -12) + for variant, fens in invalid_promoted_fens.items(): + for fen in fens: + with self.subTest(variant=variant, fen=fen, test_type="invalid_promoted"): + result = sf.validate_fen(fen, variant) + self.assertEqual(result, sf.FEN_INVALID_PROMOTED_PIECE, + f"Expected invalid promoted piece FEN to return -12: {fen}, got {result}") + + # Test non-shogi variants (should fail with character validation, not promoted piece validation) + for variant, fens in non_shogi_promoted_fens.items(): + for fen in fens: + with self.subTest(variant=variant, fen=fen, test_type="non_shogi"): + result = sf.validate_fen(fen, variant) + # Should fail with character validation (FEN_INVALID_CHAR = -10), not promoted piece validation + self.assertEqual(result, -10, + f"Expected non-shogi variant to fail with character error (-10): {fen}, got {result}") + + def test_get_fog_fen(self): + fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" # startpos + result = sf.get_fog_fen(fen, "fogofwar") + self.assertEqual(result, "********/********/********/********/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") + + fen = "rnbqkbnr/p1p2ppp/8/Pp1pp3/4P3/8/1PPP1PPP/RNBQKBNR w KQkq b6 0 1" + result = sf.get_fog_fen(fen, "fogofwar") + self.assertEqual(result, "********/********/2******/Pp*p***1/4P3/4*3/1PPP1PPP/RNBQKBNR w KQkq b6 0 1") + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/Fairy-Stockfish-master/tests/instrumented.sh b/Fairy-Stockfish-master/tests/instrumented.sh new file mode 100644 index 0000000000000000000000000000000000000000..eb53488023589913f524b94cfca93cf73866df2e --- /dev/null +++ b/Fairy-Stockfish-master/tests/instrumented.sh @@ -0,0 +1,145 @@ +#!/bin/bash +# check for errors under valgrind or sanitizers. + +error() +{ + echo "instrumented testing failed on line $1" + exit 1 +} +trap 'error ${LINENO}' ERR + +# define suitable post and prefixes for testing options +case $1 in + --valgrind) + echo "valgrind testing started" + prefix='' + exeprefix='valgrind --error-exitcode=42 --errors-for-leak-kinds=all --leak-check=full' + postfix='1>/dev/null' + threads="1" + ;; + --valgrind-thread) + echo "valgrind-thread testing started" + prefix='' + exeprefix='valgrind --fair-sched=try --error-exitcode=42' + postfix='1>/dev/null' + threads="2" + ;; + --sanitizer-undefined) + echo "sanitizer-undefined testing started" + prefix='!' + exeprefix='' + postfix='2>&1 | grep -A50 "runtime error:"' + threads="1" + ;; + --sanitizer-thread) + echo "sanitizer-thread testing started" + prefix='!' + exeprefix='' + postfix='2>&1 | grep -A50 "WARNING: ThreadSanitizer:"' + threads="2" + +cat << EOF > tsan.supp +race:Stockfish::TTEntry::move +race:Stockfish::TTEntry::depth +race:Stockfish::TTEntry::bound +race:Stockfish::TTEntry::save +race:Stockfish::TTEntry::value +race:Stockfish::TTEntry::eval +race:Stockfish::TTEntry::is_pv + +race:Stockfish::TranspositionTable::probe +race:Stockfish::TranspositionTable::hashfull + +EOF + + export TSAN_OPTIONS="suppressions=./tsan.supp" + + ;; + *) + echo "unknown testing started" + prefix='' + exeprefix='' + postfix='' + threads="1" + ;; +esac + +# simple command line testing +for args in "eval" \ + "go nodes 1000" \ + "go depth 10" \ + "go movetime 1000" \ + "go wtime 8000 btime 8000 winc 500 binc 500" \ + "bench 128 $threads 8 default depth" +do + + echo "$prefix $exeprefix ./stockfish $args $postfix" + eval "$prefix $exeprefix ./stockfish $args $postfix" + +done + +# more general testing, following an uci protocol exchange +cat << EOF > game.exp + set timeout 240 + spawn $exeprefix ./stockfish + + send "uci\n" + expect "uciok" + + send "setoption name Threads value $threads\n" + + send "ucinewgame\n" + send "position startpos\n" + send "go nodes 1000\n" + expect "bestmove" + + send "position startpos moves e2e4 e7e6\n" + send "go nodes 1000\n" + expect "bestmove" + + send "position fen 5rk1/1K4p1/8/8/3B4/8/8/8 b - - 0 1\n" + send "go depth 10\n" + expect "bestmove" + + send "quit\n" + expect eof + + # return error code of the spawned program, useful for valgrind + lassign [wait] pid spawnid os_error_flag value + exit \$value +EOF + +#download TB as needed +if [ ! -d ../tests/syzygy ]; then + curl -sL https://api.github.com/repos/niklasf/python-chess/tarball/9b9aa13f9f36d08aadfabff872882f4ab1494e95 | tar -xzf - + mv niklasf-python-chess-9b9aa13 ../tests/syzygy +fi + +cat << EOF > syzygy.exp + set timeout 600 + spawn $exeprefix ./stockfish + send "uci\n" + send "setoption name SyzygyPath value ../tests/syzygy/\n" + expect "info string Found 35 tablebases" {} timeout {exit 1} + send "bench 128 1 8 default depth\n" + send "quit\n" + expect eof + + # return error code of the spawned program, useful for valgrind + lassign [wait] pid spawnid os_error_flag value + exit \$value +EOF + +for exp in game.exp syzygy.exp +do + + echo "$prefix expect $exp $postfix" + eval "$prefix expect $exp $postfix" + + rm $exp + +done + +rm -f tsan.supp + +echo "instrumented testing OK" diff --git a/Fairy-Stockfish-master/tests/js/README.md b/Fairy-Stockfish-master/tests/js/README.md new file mode 100644 index 0000000000000000000000000000000000000000..bf66b54bbbc27afbe6ff7d19f20afcb7f8fecdfb --- /dev/null +++ b/Fairy-Stockfish-master/tests/js/README.md @@ -0,0 +1,297 @@ +

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 3check-crazyhouse 5check ai-wok almost amazon anti-losalamos antichess\ +armageddon asean ataxx atomic breakthrough bughouse cambodian capablanca\ +capahouse caparandom centaur cfour chancellor chaturanga chess chessgi chigorin\ +clobber clobber10 codrus coffeehouse courier crazyhouse dobutsu embassy euroshogi\ +extinction fairy fischerandom flipello flipersi gardner gemini giveaway gorogoro\ +gothic grand grandhouse hoppelpoppel horde indiangreat janggi janggicasual\ +janggihouse janggimodern janggitraditional janus jesonmor judkins karouk kinglet\ +kingofthehill knightmate koedem kyotoshogi loop losalamos losers makpong makruk\ +makrukhouse manchu micro mini minishogi minixiangqi modern newzealand nocastle\ +nocheckatomic normal orda pawnsonly peasant placement pocketknight racingkings\ +seirawan semitorpedo shako shatar shatranj shogi shogun shouse sittuyin suicide\ +supply threekings tictactoe upsidedown weak xiangqi xiangqihouse +``` + +### 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 including fen validation: +```javascript +fen = "rnb1kbnr/ppp1pppp/8/3q4/8/8/PPPP1PPP/RNBQKBNR w KQkq - 0 3"; +if (ffish.validateFen(fen) == 1) { // ffish.validateFen(fen) can return different error codes, it returns 1 for FEN_OK + board.setFen(fen); +} +else { + console.error(`Fen couldn't be parsed.`); +} +``` + +Alternatively, you can initialize a board with a custom FEN directly: +```javascript +let board2 = new ffish.Board("chess", "rnb1kbnr/ppp1pppp/8/3q4/8/8/PPPP1PPP/RNBQKBNR w KQkq - 0 3"); +``` + +### ASCII board + +You can show an ASCII representation of the board using the `toString()` method + +```javascript +let board = new ffish.Board("chess", "rnb1kbnr/ppp1pppp/8/3q4/8/8/PPPP1PPP/RNBQKBNR w KQkq - 0 3"); +console.log(board.toString()) +``` +``` +r n b . k b n r +p p p . p p p p +. . . . . . . . +. . . q . . . . +. . . . . . . . +. . . . . . . . +P P P P . P P P +R N B Q K B N R +``` + +or a more detailed representation using `.toVerboseString()`. + +```javascript +let board = new ffish.Board("chess", "rnb1kbnr/ppp1pppp/8/3q4/8/8/PPPP1PPP/RNBQKBNR w KQkq - 0 3"); +console.log(board.toVerboseString()) +``` +``` ++---+---+---+---+---+---+---+---+ +| r | n | b | | k | b | n | r |8 ++---+---+---+---+---+---+---+---+ +| p | p | p | | p | p | p | p |7 ++---+---+---+---+---+---+---+---+ +| | | | | | | | |6 ++---+---+---+---+---+---+---+---+ +| | | | q | | | | |5 ++---+---+---+---+---+---+---+---+ +| | | | | | | | |4 ++---+---+---+---+---+---+---+---+ +| | | | | | | | |3 ++---+---+---+---+---+---+---+---+ +| P | P | P | P | | P | P | P |2 ++---+---+---+---+---+---+---+---+ +| R | N | B | Q | K | B | N | R |1 * ++---+---+---+---+---+---+---+---+ + a b c d e f g h + +Fen: rnb1kbnr/ppp1pppp/8/3q4/8/8/PPPP1PPP/RNBQKBNR w KQkq - 0 3 +Sfen: rnb1kbnr/ppp1pppp/8/3q4/8/8/PPPP1PPP/RNBQKBNR b - 5 +Key: 39B6F80E84D75BFB +Checkers: +``` + +## Move generation and application + +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]}, ${legalMovesSan[i]}`) +} +``` + +## Memory management + +Unfortunately, it is impossible for Emscripten to call the destructor on C++ objects. +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")); + const mainlineMoves = game.mainlineMoves().split(" "); + + let board = new ffish.Board(game.headers("Variant").toLowerCase()); + for (let idx = 0; idx < mainlineMoves.length; ++idx) { + board.push(mainlineMoves[idx]); + } + // or use board.pushMoves(game.mainlineMoves()); to push all moves at once + + let finalFen = board.fen(); + board.delete(); + game.delete(); +} +``` + +## Custom variants + +Fairy-Stockfish also allows defining custom variants by loading a configuration file. + +See e.g. the configuration 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(); + }); +``` + +## 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 instructions + +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 add the flag `largeboards=no`. + +The pre-compiled wasm binary is built with `largeboards=yes`. + +It is recommended to set `debug=yes` before running tests. + + +### Compile as standard module + +```bash +cd src +make -f Makefile_js build +``` + +### 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 src +make -f Makefile_js build es6=yes +``` + +Make sure that the wasm file is in the `public` directory. + +Reference: [emscripten/#10114](https://github.com/emscripten-core/emscripten/issues/10114) + +### Compile in docker +Instead of installing emscripten natively you can also run the compilation in docker from this directory using e.g. + +```bash +DOCKER_USER=$(id -u):$(id -g) docker compose run --rm emscripten make -f Makefile_js build es6=yes +``` + +## Instructions to run the tests +```bash +npm install +npm test +``` + +## Instructions to run the example server +```bash +npm install +``` +```bash +node index.js +``` + +## Example Projects + +### ffish-test + +A simple toy website which demonstrates the core functionality of ffish.js and [chessgroundx](https://github.com/gbtami/chessgroundx). + +Source code: https://github.com/thearst3rd/ffish-test + +See it deployed at: https://thearst3rd.github.io/ffish-test/ diff --git a/Fairy-Stockfish-master/tests/js/docker-compose.yml b/Fairy-Stockfish-master/tests/js/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..ed3ecc3a6e4916ab4139487680812f1b8d10b61a --- /dev/null +++ b/Fairy-Stockfish-master/tests/js/docker-compose.yml @@ -0,0 +1,15 @@ +version: "3.7" + +services: + # https://github.com/emscripten-core/emsdk/blob/main/docker/README.md + emscripten: + image: emscripten/emsdk:1.39.16 + # Allow writing to host filesystem as non-root + user: ${DOCKER_USER:-1000:1000} + working_dir: /app/src + volumes: + - ../..:/app + - emscripten-cache:/emsdk/upstream/emscripten/cache + +volumes: + emscripten-cache: diff --git a/Fairy-Stockfish-master/tests/js/ffish.d.ts b/Fairy-Stockfish-master/tests/js/ffish.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..f92fff49e1b3efab8b8f046235245f13e93bc9a6 --- /dev/null +++ b/Fairy-Stockfish-master/tests/js/ffish.d.ts @@ -0,0 +1,103 @@ +export declare function Module(opts?: ModuleOptions): Promise; +export default Module; + +export interface ModuleOptions { + arguments?: string[]; + buffer?: ArrayBuffer | SharedArrayBuffer; + wasmMemory?: WebAssembly.Memory; + locateFile?: (file: string, prefix: string) => string; + logReadFiles?: boolean; + printWithColors?: boolean; + onAbort?: (status: string | number) => void; + onRuntimeInitialized?: (loadedModule: FairyStockfish) => void; + noExitRuntime?: boolean; + noInitialRun?: boolean; + preInit?: () => void | (() => void)[]; + preRun?: () => void | (() => void)[]; + print?: (text: string) => void; + printErr?: (text: string) => void; + mainScriptUrlOrBlob?: string; +} + +export interface FairyStockfish { + Board: Board; + Game: Game; + Notation: typeof Notation; + Termination: typeof Termination; + info(): string; + setOption(name: string, value: T): void; + setOptionInt(name: string, value: number): void; + setOptionBool(name: string, value: boolean): void; + readGamePGN(pgn: string): Game; + variants(): string; + loadVariantConfig(variantInitContent: string): void; + capturesToHand(uciVariant: string): boolean; + startingFen(uciVariant: string): string; + validateFen(fen: string, uciVariant?: string, chess960?: boolean): number; +} + +export interface Board { + new(uciVariant?: string, fen?: string, is960?: boolean): Board; + delete(): void; + legalMoves(): string; + legalMovesSan(): string; + numberLegalMoves(): number; + push(uciMove: string): boolean; + pushSan(sanMove: string, notation?: Notation): boolean; + pop(): void; + reset(): void; + is960(): boolean; + fen(showPromoted?: boolean, countStarted?: number): string; + setFen(fen: string): void; + sanMove(uciMove: string, notation?: Notation): string; + variationSan(uciMoves: string, notation?: Notation, moveNumbers?: boolean): string; + turn(): boolean; + fullmoveNumber(): number; + halfmoveClock(): number; + gamePly(): number; + hasInsufficientMaterial(turn: boolean): boolean; + isInsufficientMaterial(): boolean; + isGameOver(claimDraw?: boolean): boolean; + result(claimDraw?: boolean): string; + checkedPieces(): string; + isCheck(): boolean; + isBikjang(): boolean; + isCapture(uciMove: string): boolean; + moveStack(): string; + pushMoves(uciMoves: string): void; + pushSanMoves(sanMoves: string, notation?: Notation): void; + pocket(color: boolean): string; + toString(): string; + toVerboseString(): string; + variant(): string; +} + +export interface Game { + delete(): void; + headerKeys(): string; + headers(item: string): string; + mainlineMoves(): string; +} + +export declare enum Notation { + DEFAULT, + SAN, + LAN, + SHOGI_HOSKING, + SHOGI_HODGES, + SHOGI_HODGES_NUMBER, + JANGGI, + XIANGQI_WXF, + THAI_SAN, + THAI_LAN, +} + +export declare enum Termination { + ONGOING, + CHECKMATE, + STALEMATE, + INSUFFICIENT_MATERIAL, + N_MOVE_RULE, + N_FOLD_REPETITION, + VARIANT_END, +} diff --git a/Fairy-Stockfish-master/tests/js/index.js b/Fairy-Stockfish-master/tests/js/index.js new file mode 100644 index 0000000000000000000000000000000000000000..2ef91eaac5497ccc94489207e5c1ea9e9bc3fd2d --- /dev/null +++ b/Fairy-Stockfish-master/tests/js/index.js @@ -0,0 +1,91 @@ +const express = require('express') +const ffish = require('./ffish.js'); +const { PerformanceObserver, performance } = require('perf_hooks'); +const { Chess } = require('chess.js') +const { Crazyhouse } = require('crazyhouse.js') + +const app = express(); + +app.get('/', (req, res) => { + +let board = new ffish.Board("chess"); //, "rnb1kbnr/ppp1pppp/8/3q4/8/8/PPPP1PPP/RNBQKBNR w KQkq - 0 3", false); +let legalMoves = board.legalMoves(); + +let it = 1000; + +console.log("Standard Chess") +console.log("==================") + +var t0 = performance.now() +for (let i = 0; i < it; ++i) { + legalMoves = board.legalMoves().split(" "); +} +var t1 = performance.now() +console.log(`Call to board.legalMoves()+legalMoves.split(" ") took ${(t1 - t0).toFixed(2)} milliseconds.`) + +var t0 = performance.now() +for (let i = 0; i < it; ++i) { + legalMoves = board.legalMovesSan().split(" ") +} +var t1 = performance.now() +console.log(`board.legalMovesSan().split(" ").length: ${legalMoves.length}`) +console.log(`Call to board.legalMovesSan()+legalMoves.split(" ") took ${(t1 - t0).toFixed(2)} milliseconds.`) + + +// pass in a FEN string to load a particular position +const chess = new Chess( + "rnb1kbnr/ppp1pppp/8/3q4/8/8/PPPP1PPP/RNBQKBNR w KQkq - 0 3" +) +var t0 = performance.now() +for (let i = 0; i < it; ++i) { + legalMoves = chess.moves() +} +var t1 = performance.now() +console.log(`chess.moves().length: ${legalMoves.length}`) +console.log(`Call to chess.moves() took ${(t1 - t0).toFixed(2)} milliseconds.`) + +console.log("Crazyhouse") +console.log("===========") + +let crazyhouseFen = "rnb1kb1r/ppp2ppp/4pn2/8/3P4/2N2Q2/PPP2PPP/R1B1KB1R/QPnp b KQkq - 0 6"; +board = new ffish.Board("crazyhouse", crazyhouseFen); + +var t0 = performance.now() +for (let i = 0; i < it; ++i) { + legalMoves = board.legalMovesSan().split(" ") +} +var t1 = performance.now() +console.log(`board.legalMoves().split(" ").length: ${legalMoves.length}`) +console.log(`Call to board.legalMoves() took ${(t1 - t0).toFixed(2)} milliseconds.`) + +cz_moves = ["e4", "d5", "exd5", "Qxd5", "Nf3", "Nf6", "Nc3", "e6", "d4", "Qxf3", "Qxf3"] +// pass in a FEN string to load a particular position +const crazyhouse = new Crazyhouse() + +for (let idx = 0; idx < cz_moves.length; ++idx) { + crazyhouse.move(cz_moves[idx]) +} + +var t0 = performance.now() +for (let i = 0; i < it; ++i) { + legalMoves = crazyhouse.moves() +} +var t1 = performance.now() +console.log(`crazyhouse.moves().length: ${legalMoves.length}`) +console.log(`Call to crazyhouse.moves() took ${(t1 - t0).toFixed(2)} milliseconds.`) + + +let legalMovesSan = board.legalMovesSan().split(" "); + +for (var i = 0; i < legalMovesSan.length; i++) { + console.log(`${i}: ${legalMoves[i]}, ${legalMovesSan[i]}`); +} +console.log(board.fen()); + + res.send(String("Test server of ffish.js")); +}); + +app.listen(8000, () => { + console.log('Test server of ffish.js listening on port 8000.') + console.log('http://127.0.0.1:8000/') +}); diff --git a/Fairy-Stockfish-master/tests/js/package.json b/Fairy-Stockfish-master/tests/js/package.json new file mode 100644 index 0000000000000000000000000000000000000000..8769d908475fe7a600b205faee82f52b2cc9e085 --- /dev/null +++ b/Fairy-Stockfish-master/tests/js/package.json @@ -0,0 +1,43 @@ +{ + "name": "ffish", + "version": "0.7.9", + "description": "A high performance WebAssembly chess variant library based on Fairy-Stockfish", + "main": "ffish.js", + "types": "ffish.d.ts", + "scripts": { + "test": "mocha --timeout 80000", + "dev": "node index" + }, + "author": [ + { + "name": "Fabian Fichter", + "url": "https://github.com/ianfab" + }, + { + "name": "Johannes Czech", + "url": "https://github.com/QueensGambit" + } + ], + "repository": { + "type": "git", + "url": "git+https://github.com/ianfab/Fairy-Stockfish.git" + }, + "keywords": [ + "wasm", + "library", + "chess-variants", + "fairy-stockfish" + ], + "license": "GPL-3.0", + "homepage": "https://github.com/ianfab/Fairy-Stockfish/tree/master/tests/js#readme", + "dependencies": {}, + "devDependencies": { + "chai": "^4.2.0", + "chess": "^0.4.3", + "chess.js": "^0.11.0", + "crazyhouse.js": "0.0.8", + "express": "^4.17.1", + "mocha": "^8.0.1", + "performance": "^1.4.0" + } +} diff --git a/Fairy-Stockfish-master/tests/js/test.js b/Fairy-Stockfish-master/tests/js/test.js new file mode 100644 index 0000000000000000000000000000000000000000..09ca57a46319944bc20ff9730168b5eb1765a06d --- /dev/null +++ b/Fairy-Stockfish-master/tests/js/test.js @@ -0,0 +1,945 @@ +before(() => { + chai = require('chai'); + return new Promise((resolve) => { + pgnDir = __dirname + '/../pgn/'; + srcDir = __dirname + '/../../src/'; + ffish = require('./ffish.js'); + WHITE = true; + BLACK = false; + ffish['onRuntimeInitialized'] = () => { + resolve(); + } + }); +}); + +describe('ffish.loadVariantConfig(config)', function () { + it("it loads a custom variant configuration from a string", () => { + fs = require('fs'); + let configFilePath = srcDir + 'variants.ini'; + fs.readFile(configFilePath, 'utf8', function (err,data) { + if (err) { + return console.log(err); + } + ffish.loadVariantConfig(data) + let board = new ffish.Board("tictactoe"); + chai.expect(board.fen()).to.equal("3/3/3[PPPPPpppp] w - - 0 1"); + board.delete(); + }); + }); +}); + +describe('Board()', function () { + it("it creates a chess board from the default position", () => { + const board = new ffish.Board(); + chai.expect(board.fen()).to.equal("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); + chai.expect(board.is960()).to.equal(false); + board.delete(); + }); +}); + +describe('Board(uciVariant) ', function () { + it("it creates a board object from a given UCI-variant", () => { + const board = new ffish.Board("chess"); + chai.expect(board.fen()).to.equal("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); + chai.expect(board.is960()).to.equal(false); + board.delete(); + }); +}); + +describe('Board(uciVariant) with large board', function () { + it("it creates a large-board object from a given UCI-variant", () => { + const board = new ffish.Board("xiangqi"); + chai.expect(board.fen()).to.equal("rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1"); + chai.expect(board.is960()).to.equal(false); + board.delete(); + }); +}); + +describe('Board(uciVariant, fen) ', function () { + it("it creates a board object for a given UCI-variant with a given FEN", () => { + const board = new ffish.Board("crazyhouse", "rnbqkb1r/pp3ppp/5p2/2pp4/8/5N2/PPPP1PPP/RNBQKB1R/Np w KQkq - 0 5"); + chai.expect(board.fen()).to.equal("rnbqkb1r/pp3ppp/5p2/2pp4/8/5N2/PPPP1PPP/RNBQKB1R[Np] w KQkq - 0 5"); + chai.expect(board.is960()).to.equal(false); + board.delete(); + }); +}); + +describe('Board(uciVariant, fen, is960)', function () { + it("it creates a board object for a given UCI-variant with a given FEN and is960 identifier", () => { + const board = new ffish.Board("chess", "rnknb1rq/pp2ppbp/3p2p1/2p5/4PP2/2N1N1P1/PPPP3P/R1K1BBRQ b KQkq - 1 5", true); + chai.expect(board.fen()).to.equal("rnknb1rq/pp2ppbp/3p2p1/2p5/4PP2/2N1N1P1/PPPP3P/R1K1BBRQ b GAga - 1 5"); + chai.expect(board.is960()).to.equal(true); + board.delete(); + }); +}); + +describe('board.legalMoves()', function () { + it("it returns all legal moves in uci notation as a concatenated string", () => { + const crazyhouseBoard = new ffish.Board("crazyhouse", "r1b3nr/pppp1kpp/2n5/2b1p3/4P3/2N5/PPPP1PPP/R1B1K1NR/QPbq w KQ - 0 7"); + const expectedMovesCrazyhouse = 'a2a3 b2b3 d2d3 f2f3 g2g3 h2h3 a2a4 b2b4 d2d4 f2f4 g2g4 h2h4 c3b1 c3d1 c3e2 c3a4 c3b5 c3d5' + + ' g1e2 g1f3 g1h3 a1b1 P@e2 P@a3 P@b3 P@d3 P@e3 P@f3 P@g3 P@h3 P@a4 P@b4 P@c4 P@d4 P@f4 P@g4 P@h4 P@a5 P@b5' + + ' P@d5 P@f5 P@g5 P@h5 P@a6 P@b6 P@d6 P@e6 P@f6 P@g6 P@h6 P@e7 Q@b1 Q@d1 Q@f1 Q@e2 Q@a3 Q@b3 Q@d3 Q@e3 Q@f3 ' + + 'Q@g3 Q@h3 Q@a4 Q@b4 Q@c4 Q@d4 Q@f4 Q@g4 Q@h4 Q@a5 Q@b5 Q@d5 Q@f5 Q@g5 Q@h5 Q@a6 Q@b6 Q@d6 Q@e6 Q@f6 Q@g6' + + ' Q@h6 Q@e7 Q@b8 Q@d8 Q@e8 Q@f8 e1d1 e1f1 e1e2'; + chai.expect(crazyhouseBoard.legalMoves().split(' ').sort().join()).to.equal(expectedMovesCrazyhouse.split(' ').sort().join()); + crazyhouseBoard.delete(); + // test atomic(960) castling rights + const atomicBoard = new ffish.Board("atomic", "b1rkr1q1/pp3p2/n1p4p/6p1/1P2P1PP/8/PbR2P1Q/BN1KRN1B b Eec - 0 10", true); + const expectedMovesAtomic = 'c6c5 h6h5 b7b6 f7f6 b7b5 f7f5 g5h4 a6b4 a6c5 a6c7 a6b8 b2a1 b2c1 b2a3 b2c3 b2d4 b2e5 b2f6 b2g7' + + ' b2h8 c8c7 c8b8 e8e4 e8e5 e8e6 e8e7 e8f8 g8g6 g8g7 g8h7 g8f8 g8h8 d8c8 d8d7 d8e7'; + chai.expect(atomicBoard.legalMoves().split(' ').sort().join()).to.equal(expectedMovesAtomic.split(' ').sort().join()); + atomicBoard.delete(); + }); +}); + +describe('board.legalMovesSan()', function () { + it("it returns all legal moves in SAN notation as a concatenated string", () => { + const board = new ffish.Board("crazyhouse", "r1b3nr/pppp1kpp/2n5/2b1p3/4P3/2N5/PPPP1PPP/R1B1K1NR/QPbq w KQ - 0 7"); + const expectedMoves = 'a3 b3 d3 f3 g3 h3 a4 b4 d4 f4 g4 h4 Nb1 Nd1 Nce2 Na4 Nb5 Nd5 Nge2 Nf3 Nh3 Rb1 P@e2 P@a3' + + ' P@b3 P@d3 P@e3 P@f3 P@g3 P@h3 P@a4 P@b4 P@c4 P@d4 P@f4 P@g4 P@h4 P@a5 P@b5 P@d5 P@f5 P@g5 P@h5 P@a6 P@b6' + + ' P@d6 P@e6+ P@f6 P@g6+ P@h6 P@e7 Q@b1 Q@d1 Q@f1 Q@e2 Q@a3 Q@b3+ Q@d3 Q@e3 Q@f3+ Q@g3 Q@h3 Q@a4 Q@b4 Q@c4+' + + ' Q@d4 Q@f4+ Q@g4 Q@h4 Q@a5 Q@b5 Q@d5+ Q@f5+ Q@g5 Q@h5+ Q@a6 Q@b6 Q@d6 Q@e6+ Q@f6+ Q@g6+ Q@h6 Q@e7+ Q@b8' + + ' Q@d8 Q@e8+ Q@f8+ Kd1 Kf1 Ke2'; + chai.expect(board.legalMovesSan().split(' ').sort().join()).to.equal(expectedMoves.split(' ').sort().join()); + board.delete(); + }); +}); + +describe('board.numberLegalMoves()', function () { + it("it returns all legal moves in uci notation as a concatenated string", () => { + const board = new ffish.Board("crazyhouse", "r1b3nr/pppp1kpp/2n5/2b1p3/4P3/2N5/PPPP1PPP/R1B1K1NR/QPbq w KQ - 0 7"); + chai.expect(board.numberLegalMoves()).to.equal(90); + board.delete(); + const yariboard = new ffish.Board("yarishogi"); + chai.expect(yariboard.numberLegalMoves()).to.equal(20); + yariboard.delete(); + }); +}); + +describe('board.push(uciMove)', function () { + it("it pushes a move in uci notation to the board", () => { + let board = new ffish.Board(); + board.push("e2e4"); + board.push("e7e5"); + board.push("g1f3"); + chai.expect(board.fen()).to.equal("rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2"); + chai.expect(board.push("q2q7")).to.equal(false); + chai.expect(board.fen()).to.equal("rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2"); + board.delete(); + }); +}); + +describe('board.pushSan(sanMove)', function () { + it("it pushes a move in san notation to the board", () => { + let board = new ffish.Board(); + board.pushSan("e4"); + board.pushSan("e5"); + board.pushSan("Nf3"); + chai.expect(board.fen()).to.equal("rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2"); + chai.expect(board.pushSan("Nf3")).to.equal(false); + chai.expect(board.fen()).to.equal("rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2"); + board.delete(); + }); +}); + +describe('board.pushSan(sanMove, notation)', function () { + it("it pushes a move in san notation to the board", () => { + let board = new ffish.Board(); + board.pushSan("e4", ffish.Notation.SAN); + board.pushSan("e5", ffish.Notation.SAN); + board.pushSan("Nf3", ffish.Notation.SAN); + chai.expect(board.fen()).to.equal("rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2"); + board.delete(); + }); +}); + +describe('board.pop()', function () { + it("it undos the last move", () => { + let board = new ffish.Board(); + board.push("e2e4"); + board.push("e7e5"); + board.pop(); + board.push("e7e5"); + board.push("g1f3"); + board.push("b8c6"); + board.push("f1b5"); + board.pop(); + board.pop(); + chai.expect(board.fen()).to.equal("rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2"); + board.delete(); + }); +}); + +describe('board.reset()', function () { + it("it resets the board to its starting position", () => { + let board = new ffish.Board(); + board.pushSan("e4"); + board.pushSan("e5"); + board.pushSan("Nf3"); + board.reset(); + chai.expect(board.fen()).to.equal("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); + board.delete(); + }); +}); + +describe('board.is960()', function () { + it("it checks if the board originates from a 960 position", () => { + let board = new ffish.Board(); + chai.expect(board.is960()).to.equal(false); + const board2 = new ffish.Board("chess", "rnknb1rq/pp2ppbp/3p2p1/2p5/4PP2/2N1N1P1/PPPP3P/R1K1BBRQ b KQkq - 1 5", true); + chai.expect(board2.is960()).to.equal(true); + board.delete(); + }); +}); + +describe('board.fen()', function () { + it("it returns the current position in fen format", () => { + let board = new ffish.Board(); + chai.expect(board.fen()).to.equal("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); + board.delete(); + }); +}); + +describe('board.fen(showPromoted)', function () { + it("it returns the current position in fen format. showPromoted makes promoted pieces always followed by the symbol ~ regardless of variant.", () => { + let board = new ffish.Board("makruk", "8/6ks/3M~2r1/2K1M3/8/3R4/8/8 w - 128 18 50"); + chai.expect(board.fen(true)).to.equal("8/6ks/3M~2r1/2K1M3/8/3R4/8/8 w - 128 18 50"); + chai.expect(board.fen(false)).to.equal("8/6ks/3M2r1/2K1M3/8/3R4/8/8 w - 128 18 50"); + }); +}); + +describe('board.fen(showPromoted, countStarted)', function () { + it("it returns the current position in fen format. showPromoted makes promoted pieces always followed by the symbol ~ regardless of variant. countStarted overwrites the start of makruk's board honor counting.", () => { + let board = new ffish.Board("makruk", "8/6ks/3M~2r1/2K1M3/8/3R4/8/8 w - 128 18 50"); + chai.expect(board.fen(true, 0)).to.equal("8/6ks/3M~2r1/2K1M3/8/3R4/8/8 w - 128 18 50"); + chai.expect(board.fen(true, -1)).to.equal("8/6ks/3M~2r1/2K1M3/8/3R4/8/8 w - 128 0 50"); + chai.expect(board.fen(true, 89)).to.equal("8/6ks/3M~2r1/2K1M3/8/3R4/8/8 w - 128 10 50"); + }); +}); + +describe('board.setFen(fen)', function () { + it("it sets a custom position via fen", () => { + let board = new ffish.Board(); + board.setFen("r1bqkbnr/ppp2ppp/2np4/1B6/3NP3/8/PPP2PPP/RNBQK2R b KQkq - 0 5"); + chai.expect(board.fen()).to.equal("r1bqkbnr/ppp2ppp/2np4/1B6/3NP3/8/PPP2PPP/RNBQK2R b KQkq - 0 5"); + board.delete(); + }); +}); + +describe('board.sanMove(uciMove)', function () { + it("it converts an uci move into san", () => { + const board = new ffish.Board(); + const san = board.sanMove("g1f3"); + chai.expect(san).to.equal("Nf3"); + board.delete(); + }); +}); + +describe('board.sanMove(ffish.Notation)', function () { + it("it converts an uci move into san using a given notation", () => { + const board = new ffish.Board(); + chai.expect(board.sanMove("g1f3", ffish.Notation.DEFAULT)).to.equal("Nf3"); + chai.expect(board.sanMove("g1f3", ffish.Notation.SAN)).to.equal("Nf3"); + chai.expect(board.sanMove("g1f3", ffish.Notation.LAN)).to.equal("Ng1-f3"); + chai.expect(board.sanMove("g1f3", ffish.Notation.SHOGI_HOSKING)).to.equal("N36"); + chai.expect(board.sanMove("g1f3", ffish.Notation.SHOGI_HODGES)).to.equal("N-3f"); + chai.expect(board.sanMove("g1f3", ffish.Notation.SHOGI_HODGES_NUMBER)).to.equal("N-36"); + chai.expect(board.sanMove("g1f3", ffish.Notation.JANGGI)).to.equal("N87-66"); + chai.expect(board.sanMove("g1f3", ffish.Notation.XIANGQI_WXF)).to.equal("N2+3"); + board.delete(); + }); +}); + +describe('board.variationSan(uciMoves)', function () { + it("it converts a list of uci moves into san notation. The board will not changed by this method.", () => { + let board = new ffish.Board(); + board.push("e2e4") + const sanMoves = board.variationSan("e7e5 g1f3 b8c6 f1c4"); + chai.expect(sanMoves).to.equal("1...e5 2. Nf3 Nc6 3. Bc4"); + const sanMovesInvalid = board.variationSan("e7e5 g1f3 b8c6 f1c7"); + chai.expect(sanMovesInvalid).to.equal(""); + board.delete(); + }); +}); + +describe('board.variationSan(uciMoves, notation)', function () { + it("it converts a list of uci moves into san notation given a notation format", () => { + let board = new ffish.Board(); + board.push("e2e4") + const sanMoves = board.variationSan("e7e5 g1f3 b8c6 f1c4", ffish.Notation.LAN); + chai.expect(sanMoves).to.equal("1...e7-e5 2. Ng1-f3 Nb8-c6 3. Bf1-c4"); + board.delete(); + }); +}); + +describe('board.variationSan(uciMoves, notation, moveNumbers)', function () { + it("it converts a list of uci moves into san notation given a notation format and optionally disabling move numbers", () => { + let board = new ffish.Board(); + board.push("e2e4") + const sanMoves = board.variationSan("e7e5 g1f3 b8c6 f1c4", ffish.Notation.SAN, false); + chai.expect(sanMoves).to.equal("e5 Nf3 Nc6 Bc4"); + board.delete(); + }); +}); + +describe('board.turn()', function () { + it("it returns the side to move", () => { + let board = new ffish.Board(); + chai.expect(board.turn()).to.equal(true); + board.push("e2e4"); + chai.expect(board.turn()).to.equal(false); + board.delete(); + }); +}); + +describe('board.fullmoveNumber()', function () { + it("it returns the move number starting with 1 and is increment after each move of the 2nd player", () => { + let board = new ffish.Board(); + chai.expect(board.fullmoveNumber()).to.equal(1); + board.push("e2e4"); + chai.expect(board.fullmoveNumber()).to.equal(1); + board.push("e7e5"); + board.push("g1f3"); + board.push("g8f6"); + board.push("f3e5"); + chai.expect(board.fullmoveNumber()).to.equal(3); + board.delete(); + }); +}); + +describe('board.halfmoveClock()', function () { + it("it returns the halfmoveClock / 50-move-rule-counter", () => { + let board = new ffish.Board(); + chai.expect(board.halfmoveClock()).to.equal(0); + board.push("e2e4"); + board.push("e7e5"); + chai.expect(board.halfmoveClock()).to.equal(0); + board.push("g1f3"); + board.push("g8f6"); + chai.expect(board.halfmoveClock()).to.equal(2); + board.push("f3e5"); + chai.expect(board.halfmoveClock()).to.equal(0); + board.delete(); + }); +}); + +describe('board.gamePly()', function () { + it("it returns the current game ply", () => { + let board = new ffish.Board(); + chai.expect(board.gamePly()).to.equal(0); + board.push("e2e4"); + chai.expect(board.gamePly()).to.equal(1); + board.push("e7e5"); + board.push("g1f3"); + board.push("g8f6"); + board.push("f3e5"); + chai.expect(board.gamePly()).to.equal(5); + board.delete(); + }); +}); + +describe('board.hasInsufficientMaterial(side)', function () { + it("it returns if the given side has insufficient mating material", () => { + let board = new ffish.Board(); + chai.expect(board.hasInsufficientMaterial(true)).to.equal(false); + chai.expect(board.hasInsufficientMaterial(false)).to.equal(false); + board.setFen("8/5k2/8/8/8/2K5/6R1/8 w - - 0 1"); + chai.expect(board.hasInsufficientMaterial(true)).to.equal(false); + chai.expect(board.hasInsufficientMaterial(false)).to.equal(true); + board.setFen("8/5k2/8/8/8/2K5/6q1/8 w - - 0 1"); + chai.expect(board.hasInsufficientMaterial(true)).to.equal(true); + chai.expect(board.hasInsufficientMaterial(false)).to.equal(false); + board.setFen("8/5k2/8/8/8/2K5/6B1/8 w - - 0 1"); + chai.expect(board.hasInsufficientMaterial(true)).to.equal(true); + chai.expect(board.hasInsufficientMaterial(false)).to.equal(true); + board.delete(); + }); +}); + +describe('board.isInsufficientMaterial()', function () { + it("it returns if the game is drawn due to insufficient material", () => { + let board = new ffish.Board(); + chai.expect(board.isInsufficientMaterial()).to.equal(false); + board.setFen("8/5k2/8/8/8/2K5/6R1/8 w - - 0 1"); + chai.expect(board.isInsufficientMaterial()).to.equal(false); + board.setFen("8/5k2/8/8/8/2K5/6q1/8 w - - 0 1"); + chai.expect(board.isInsufficientMaterial()).to.equal(false); + board.setFen("8/5k2/8/8/8/2K5/6B1/8 w - - 0 1"); + chai.expect(board.isInsufficientMaterial()).to.equal(true); + board.delete(); + }); +}); + +describe('board.isGameOver()', function () { + it("it checks if the game is over", () => { + // No legal moves + let board = new ffish.Board(); + chai.expect(board.isGameOver()).to.equal(false); + board.setFen("r1bqkb1r/pppp1ppp/2n2n2/4p2Q/2B1P3/8/PPPP1PPP/RNB1K1NR w KQkq - 4 4"); + board.pushSan("Qxf7#"); + chai.expect(board.isGameOver()).to.equal(true); + + // Insufficient material + board.setFen("3Rk3/8/8/8/8/8/2N5/3K4 b - - 0 1"); + chai.expect(board.isGameOver()).to.equal(false); + board.pushSan("Kxd8"); + chai.expect(board.isGameOver()).to.equal(true); + + // Optional draw claimed + board.reset(); + board.pushSanMoves("Nf3 Nc6 Ng1 Nb8 Nf3 Nc6 Ng1"); + chai.expect(board.isGameOver(false)).to.equal(false); + chai.expect(board.isGameOver(true)).to.equal(false); + board.pushSan("Nb8"); + chai.expect(board.isGameOver(false)).to.equal(false); + chai.expect(board.isGameOver(true)).to.equal(true); + board.delete(); + }); +}); + +describe('board.result()', function () { + it("it returns a string representing the winner of the game", () => { + // Scholar's mate (win for white) + let board = new ffish.Board(); + chai.expect(board.result()).to.equal("*"); + board.pushSanMoves("e4 e5 Bc4 Nc6 Qh5 Nf6"); + chai.expect(board.result()).to.equal("*"); + board.pushSan("Qxf7#"); + chai.expect(board.result()).to.equal("1-0"); + + // Fool's mate (win for black) + board.reset(); + board.pushSanMoves("f3 e5 g4"); + chai.expect(board.result()).to.equal("*"); + board.pushSan("Qh4#"); + chai.expect(board.result()).to.equal("0-1"); + + // Stalemate + board.setFen("2Q2bnr/4p1pq/5pkr/7p/7P/4P3/PPPP1PP1/RNB1KBNR w KQ - 1 10"); + chai.expect(board.result()).to.equal("*"); + board.pushSan("Qe6"); + chai.expect(board.result()).to.equal("1/2-1/2"); + + // Draw claimed by n-fold repetition + board.reset(); + board.pushSanMoves("Nf3 Nc6 Ng1 Nb8 Nf3 Nc6 Ng1"); + chai.expect(board.result(false)).to.equal("*"); + chai.expect(board.result(true)).to.equal("*"); + board.pushSan("Nb8"); + chai.expect(board.result(false)).to.equal("*"); + chai.expect(board.result(true)).to.equal("1/2-1/2"); + + // Draw claimed by n-move rule + board.setFen("rnbqkbn1/ppppppp1/6r1/7p/2R4P/8/PPPPPPP1/RNBQKBN1 b Qq - 99 51"); + chai.expect(board.result(false)).to.equal("*"); + chai.expect(board.result(true)).to.equal("*"); + board.pushSan("Rh6"); + chai.expect(board.result(false)).to.equal("*"); + chai.expect(board.result(true)).to.equal("1/2-1/2"); + + // Insufficient material + board.setFen("3Rk3/8/8/8/8/8/2N5/3K4 b - - 0 1"); + chai.expect(board.result()).to.equal("*"); + board.pushSan("Kxd8"); + chai.expect(board.result()).to.equal("1/2-1/2"); + + // Stalemate with material counting - black draw odds (armageddon) + board.delete(); + board = new ffish.Board("armageddon"); + board.setFen("2Q2bnr/4p1pq/5pkr/7p/7P/4P3/PPPP1PP1/RNB1KBNR w KQ - 1 10"); + chai.expect(board.result()).to.equal("*"); + board.pushSan("Qe6"); + chai.expect(board.result()).to.equal("0-1"); + + // Atomic chess exploded king (variant ending) + board.delete(); + board = new ffish.Board("atomic"); + board.pushMoves("e2e4 e7e5 d1h5 a7a6"); + chai.expect(board.result()).to.equal("*"); + board.push("h5f7"); + chai.expect(board.result()).to.equal("1-0"); + + // Exploded king AND insufficient material - exploded king takes priority + board.setFen("3qk3/8/8/8/8/8/8/3QK3 w - - 0 1"); + chai.expect(board.result()).to.equal("*"); + board.push("d1d8"); + chai.expect(board.result()).to.equal("1-0"); + board.delete(); + }) +}) + +describe('board.checkedPieces()', function () { + it("it returns the squares of all checked royal pieces in a concatenated string", () => { + let board = new ffish.Board(); + chai.expect(board.checkedPieces()).to.equal(""); + board.setFen("rnbqkb1r/pppp1Bpp/5n2/4p3/4P3/8/PPPP1PPP/RNBQK1NR b KQkq - 0 3"); + chai.expect(board.checkedPieces()).to.equal("e8"); + board.setFen("r1bqkb1r/pppp1ppp/2n2n2/4p2Q/2B1P3/8/PPPP1PPP/RNB1K1NR w KQkq - 4 4"); + board.pushSan("Qxf7#"); + chai.expect(board.checkedPieces()).to.equal("e8"); + board.delete(); + + board = new ffish.Board("atomic"); + chai.expect(board.checkedPieces()).to.equal(""); + board.setFen("rnbqkbnr/ppp1pppp/8/1B1p4/4P3/8/PPPP1PPP/RNBQK1NR b KQkq - 3 2"); + chai.expect(board.checkedPieces()).to.equal("e8"); + board.setFen("rnbqkbnr/ppp2ppp/8/8/8/8/PPP2PPP/RNBQKBNR w KQkq - 0 4"); + board.pushSan("Qd7"); + chai.expect(board.checkedPieces()).to.equal("e8"); + board.setFen("8/8/kK6/8/8/8/Q7/8 b - - 0 1") + chai.expect(board.checkedPieces()).to.equal(""); + board.delete(); + + board = new ffish.Board("spartan"); + chai.expect(board.checkedPieces()).to.equal(""); + board.setFen("lgkcckw1/hhhhhhhh/1N3lN1/8/8/8/PPPPPPPP/R1BQKB1R b KQ - 11 6"); + chai.expect(board.checkedPieces().split(' ').sort().join()).to.equal("c8,f8"); + chai.expect(board.isCheck()).to.equal(true); + board.setFen("lgkcckwl/hhhhhhhh/6N1/8/8/8/PPPPPPPP/RNBQKB1R b KQ - 5 3") + chai.expect(board.checkedPieces()).to.equal(""); + board.delete(); + + board = new ffish.Board("shako"); + board.setFen("10/5r4/2p3pBk1/1p6Pr/p3p5/9e/1PP2P4/P2P2PP2/ER3K2R1/8C1 w K - 7 38") + board.pushMoves("f2h2"); + chai.expect(board.checkedPieces()).to.equal("i8"); + board.delete(); + + board = new ffish.Board("janggi"); + board.setFen("4ka3/4a4/9/4R4/2B6/9/9/5K3/4p4/3r5 b - - 0 113") + board.pushMoves("e2f2"); + chai.expect(board.checkedPieces()).to.equal("f3"); + board.delete(); + }) +}) + +describe('board.isCheck()', function () { + it("it checks if a player is in check", () => { + let board = new ffish.Board(); + chai.expect(board.isCheck()).to.equal(false); + board.setFen("rnbqkb1r/pppp1Bpp/5n2/4p3/4P3/8/PPPP1PPP/RNBQK1NR b KQkq - 0 3"); + chai.expect(board.isCheck()).to.equal(true); + board.setFen("r1bqkb1r/pppp1ppp/2n2n2/4p2Q/2B1P3/8/PPPP1PPP/RNB1K1NR w KQkq - 4 4"); + board.pushSan("Qxf7#"); + chai.expect(board.isCheck()).to.equal(true); + board.delete(); + + board = new ffish.Board("atomic"); + chai.expect(board.isCheck()).to.equal(false); + board.setFen("rnbqkbnr/ppp1pppp/8/1B1p4/4P3/8/PPPP1PPP/RNBQK1NR b KQkq - 3 2"); + chai.expect(board.isCheck()).to.equal(true); + board.setFen("rnbqkbnr/ppp2ppp/8/8/8/8/PPP2PPP/RNBQKBNR w KQkq - 0 4"); + board.pushSan("Qd7"); + chai.expect(board.isCheck()).to.equal(true); + board.setFen("8/8/kK6/8/8/8/Q7/8 b - - 0 1") + chai.expect(board.isCheck()).to.equal(false); + board.delete(); + + board = new ffish.Board("spartan"); + board.setFen("lgkcckw1/hhhhhhhh/1N3lN1/8/8/8/PPPPPPPP/R1BQKB1R b KQ - 11 6"); + chai.expect(board.isCheck()).to.equal(true); + board.setFen("lgkcckwl/hhhhhhhh/6N1/8/8/8/PPPPPPPP/RNBQKB1R b KQ - 5 3") + chai.expect(board.isCheck()).to.equal(false); + board.setFen("lgkcckwl/hhhhhhhh/8/8/8/8/PPPPPPPP/RNBQKBNR w KQ - 0 1") + chai.expect(board.isCheck()).to.equal(false); + board.delete(); + + board = new ffish.Board("shako"); + board.setFen("10/5r4/2p3pBk1/1p6Pr/p3p5/9e/1PP2P4/P2P2PP2/ER3K2R1/8C1 w K - 7 38") + board.pushMoves("f2h2"); + chai.expect(board.isCheck()).to.equal(true); + board.delete(); + + board = new ffish.Board("janggi"); + board.setFen("4ka3/4a4/9/4R4/2B6/9/9/5K3/4p4/3r5 b - - 0 113") + board.pushMoves("e2f2"); + chai.expect(board.isCheck()).to.equal(true); + board.delete(); + }) +}) + +describe('board.isBikjang()', function () { + it("it checks if a player is in bikjang (only relevant for janggi)", () => { + let board = new ffish.Board("janggi"); + chai.expect(board.isBikjang()).to.equal(false); + board.setFen("rnba1abnr/4k4/1c5c1/p1p3p1p/9/9/P1P3P1P/1C5C1/4K4/RNBA1ABNR w - - 0 1"); + chai.expect(board.isBikjang()).to.equal(true); + board.delete(); + }); +}); + +describe('board.isCapture(move)', function() { + it("it checks if a move is a capture", () => { + let board = new ffish.Board(); + chai.expect(board.isCapture("e2e4")).to.equal(false); + board.pushMoves("e2e4 e7e5 g1f3 b8c6 f1c4 f8c5"); + chai.expect(board.isCapture("e1g1")).to.equal(false); + board.reset(); + board.pushMoves("e2e4 g8f6 e4e5 d7d5"); + chai.expect(board.isCapture("e5f6")).to.equal(true); + chai.expect(board.isCapture("e5d6")).to.equal(true); + board.delete(); + + board = new ffish.Board("chess", "bqrbkrnn/pppppppp/8/8/8/8/PPPPPPPP/BQRBKRNN w CFcf - 0 1", true); + board.pushMoves("g1f3 h8g6"); + chai.expect(board.isCapture("e1f1")).to.equal(false); + board.delete(); + + board = new ffish.Board("sittuyin", "8/2k5/8/4P3/4P1N1/5K2/8/8[] w - - 0 1"); + chai.expect(board.isCapture("e5e5f")).to.equal(false); + board.delete(); + }); +}); + +describe('board.moveStack()', function () { + it("it returns the move stack in UCI notation", () => { + let board = new ffish.Board(); + chai.expect(board.isBikjang()).to.equal(false); + board.push("e2e4"); + board.push("e7e5"); + board.push("g1f3"); + chai.expect(board.moveStack()).to.equal("e2e4 e7e5 g1f3"); + board.setFen("r1bqkb1r/pppp1ppp/2n2n2/4p2Q/2B1P3/8/PPPP1PPP/RNB1K1NR w KQkq - 4 4"); + board.pushSan("Qxf7#"); + chai.expect(board.moveStack()).to.equal("h5f7"); + board.delete(); + }); +}); + +describe('board.pushMoves(uciMoves)', function () { + it("it pushes multiple uci moves on the board, passed as a string with ' ' as delimiter", () => { + let board = new ffish.Board(); + board.pushMoves("e2e4 e7e5 g1f3"); + chai.expect(board.fen()).to.equal("rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2"); + board.delete(); + }); +}); + +describe('board.pushSanMoves(sanMoves)', function () { + it("it pushes multiple san moves on the board, passed as a string with ' ' as delimiter", () => { + let board = new ffish.Board(); + board.pushSanMoves("e4 e5 Nf3"); + chai.expect(board.fen()).to.equal("rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2"); + board.delete(); + }); +}); + +describe('board.pushSanMoves(sanMoves, notation)', function () { + it("it pushes multiple san moves on the board, passed as a string with ' ' as delimiter", () => { + let board = new ffish.Board(); + board.pushSanMoves("e4 e5 Nf3", ffish.Notation.SAN); + chai.expect(board.fen()).to.equal("rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2"); + board.delete(); + }); +}); + +describe('board.pocket(turn)', function () { + it("it returns the pocket for the given player as a string with no delimiter. All pieces are returned in lower case.", () => { + let board = new ffish.Board("crazyhouse", "rnb1kbnr/ppp1pppp/8/8/8/5q2/PPPP1PPP/RNBQKB1R/Pnp w KQkq - 0 4"); + chai.expect(board.pocket(WHITE)).to.equal("p"); + chai.expect(board.pocket(BLACK)).to.equal("np"); + board.delete(); + let board2 = new ffish.Board("crazyhouse", "rnb1kbnr/ppp1pppp/8/8/8/5q2/PPPP1PPP/RNBQKB1R[Pnp] w KQkq - 0 4"); + chai.expect(board2.pocket(WHITE)).to.equal("p"); + chai.expect(board2.pocket(BLACK)).to.equal("np"); + board2.delete(); + }); +}); + +describe('board.toString()', function () { + it("it returns a compact string representation of the board.", () => { + const board = new ffish.Board("chess", "rnb1kbnr/ppp1pppp/8/3q4/8/8/PPPP1PPP/RNBQKBNR w KQkq - 0 3"); + chai.expect(board.toString()).to.equal("r n b . k b n r\n" + + "p p p . p p p p\n" + + ". . . . . . . .\n" + + ". . . q . . . .\n" + + ". . . . . . . .\n" + + ". . . . . . . .\n" + + "P P P P . P P P\n" + + "R N B Q K B N R"); + board.delete(); + const board2 = new ffish.Board("xiangqi"); + chai.expect(board2.toString()).to.equal("r n b a k a b n r\n" + + ". . . . . . . . .\n" + + ". c . . . . . c .\n" + + "p . p . p . p . p\n" + + ". . . . . . . . .\n" + + ". . . . . . . . .\n" + + "P . P . P . P . P\n" + + ". C . . . . . C .\n" + + ". . . . . . . . .\n" + + "R N B A K A B N R"); + board2.delete(); + }); +}); + +describe('board.toVerboseString()', function () { + it("it returns a verbose string representation of the board.", () => { + const board = new ffish.Board("chess", "rnb1kbnr/ppp1pppp/8/3q4/8/8/PPPP1PPP/RNBQKBNR w KQkq - 0 3"); + chai.expect(board.toVerboseString()).to.equal("\n +---+---+---+---+---+---+---+---+\n" + + " | r | n | b | | k | b | n | r |8 \n" + + " +---+---+---+---+---+---+---+---+\n" + + " | p | p | p | | p | p | p | p |7\n" + + " +---+---+---+---+---+---+---+---+\n" + + " | | | | | | | | |6\n" + + " +---+---+---+---+---+---+---+---+\n" + + " | | | | q | | | | |5\n" + + " +---+---+---+---+---+---+---+---+\n" + + " | | | | | | | | |4\n" + + " +---+---+---+---+---+---+---+---+\n" + + " | | | | | | | | |3\n" + + " +---+---+---+---+---+---+---+---+\n" + + " | P | P | P | P | | P | P | P |2\n" + + " +---+---+---+---+---+---+---+---+\n" + + " | R | N | B | Q | K | B | N | R |1 *\n" + + " +---+---+---+---+---+---+---+---+\n" + + " a b c d e f g h\n\n" + + "Fen: rnb1kbnr/ppp1pppp/8/3q4/8/8/PPPP1PPP/RNBQKBNR w KQkq - 0 3\n" + + "Sfen: rnb1kbnr/ppp1pppp/8/3q4/8/8/PPPP1PPP/RNBQKBNR b - 5\n" + + "Key: 39B6F80E84D75BFB\nCheckers: \nChased: ") + board.delete(); + const board2 = new ffish.Board("xiangqi"); + chai.expect(board2.toVerboseString()).to.equal("\n +---+---+---+---+---+---+---+---+---+\n" + + " | r | n | b | a | k | a | b | n | r |10 \n" + + " +---+---+---+---+---+---+---+---+---+\n" + + " | | | | | | | | | |9\n" + + " +---+---+---+---+---+---+---+---+---+\n" + + " | | c | | | | | | c | |8\n" + + " +---+---+---+---+---+---+---+---+---+\n" + + " | p | | p | | p | | p | | p |7\n" + + " +---+---+---+---+---+---+---+---+---+\n" + + " | | | | | | | | | |6\n" + + " +---+---+---+---+---+---+---+---+---+\n" + + " | | | | | | | | | |5\n" + + " +---+---+---+---+---+---+---+---+---+\n" + + " | P | | P | | P | | P | | P |4\n" + + " +---+---+---+---+---+---+---+---+---+\n" + + " | | C | | | | | | C | |3\n" + + " +---+---+---+---+---+---+---+---+---+\n" + + " | | | | | | | | | |2\n" + + " +---+---+---+---+---+---+---+---+---+\n" + + " | R | N | B | A | K | A | B | N | R |1 *\n" + + " +---+---+---+---+---+---+---+---+---+\n" + + " a b c d e f g h i\n\n" + + "Fen: rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1\n" + + "Sfen: rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR b - 1\n" + + "Key: CF494C075A7D927E\nCheckers: \nChased: "); + board2.delete(); + }); +}); + +describe('board.variant()', function () { + it("it returns the uci-variant of the board.", () => { + const board = new ffish.Board("chess"); + chai.expect(board.variant()).to.equal("chess"); + board.delete(); + const board2 = new ffish.Board(""); + chai.expect(board2.variant()).to.equal("chess"); + board2.delete(); + const board3 = new ffish.Board("standard"); + chai.expect(board3.variant()).to.equal("chess"); + board3.delete(); + const board4 = new ffish.Board("Standard"); + chai.expect(board4.variant()).to.equal("chess"); + board4.delete(); + const board5 = new ffish.Board("atomic"); + chai.expect(board5.variant()).to.equal("atomic"); + board5.delete(); + }); +}); + +describe('ffish.info()', function () { + it("it returns the version of the Fairy-Stockfish binary", () => { + chai.expect(ffish.info()).to.be.a('string'); + }); +}); + +describe('ffish.setOption(name, value)', function () { + it("it sets a string uci option value pair", () => { + ffish.setOption("VariantPath", "variants.ini"); + chai.expect(true).to.equal(true); + }); +}); + +describe('ffish.setOptionInt(name, value)', function () { + it("it sets an int uci option value pair", () => { + ffish.setOptionInt("Threads", 4); + chai.expect(true).to.equal(true); + }); +}); + +describe('ffish.setOptionBool(name, value)', function () { + it("it sets a boolean uci option value pair", () => { + ffish.setOptionBool("Ponder", true); + chai.expect(true).to.equal(true); + }); +}); + +describe('ffish.capturesToHand(uciVariant)', function () { + it("it checks if the given uci-variant rules contain capturesToHand", () => { + chai.expect(ffish.capturesToHand("seirawan")).to.equal(false); + chai.expect(ffish.capturesToHand("shouse")).to.equal(true); + }); +}); + +describe('ffish.startingFen(uciVariant)', function () { + it("it returns the starting fen for the given uci-variant.", () => { + chai.expect(ffish.startingFen("chess")).to.equal("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); + }); +}); + +describe('ffish.validateFen(fen)', function () { + it("it validates a given chess fen and returns +1 if fen is valid. Otherwise an error code will be returned.", () => { + chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")).to.equal(1); + chai.expect(ffish.validateFen("6k1/R7/2p4p/2P2p1P/PPb2Bp1/2P1K1P1/5r2/8 b - - 4 39")).to.equal(1); + }); +}); + +describe('ffish.validateFen(fen, uciVariant)', function () { + it("it validates a given fen and returns +1 if fen is valid. Otherwise an error code will be returned.", () => { + // check if starting fens are valid for all variants + const variants = ffish.variants().split(" ") + for (let idx = 0; idx < variants.length; ++idx) { + const variant = variants[idx]; + const startFen = ffish.startingFen(variant); + chai.expect(ffish.validateFen(startFen, variant)).to.equal(1, "Invalid start FEN for " + variant); + // check if the FEN is still valid if board.fen() is returned + const board = new ffish.Board(variant); + chai.expect(ffish.validateFen(board.fen(), variant)).to.equal(1); + board.delete(); + } + // alternative or skipped pocket formulation + chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR/RB w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(1); + chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR/ w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(1); + chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(1); + chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[-] w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(1); + + // error id checks + chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[]wKQkq-3+301", "3check-crazyhouse")).to.equal(-10); + chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-6); + chai.expect(ffish.validateFen("rnbqkbnr/ppppXppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-10); + chai.expect(ffish.validateFen("rnbqkbnr/pppppKpp/8/8/8/8/PPPPPPPP/RNBQ1BNR[] w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-9); + chai.expect(ffish.validateFen("rnbqkbnr/ppppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-8); + chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-8); + chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[77] w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-7); + chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] o KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-6); + chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w K6kq - 3+3 0 1", "3check-crazyhouse")).to.equal(-5); + chai.expect(ffish.validateFen("rnbq1bnr/pppkpppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-5); + chai.expect(ffish.validateFen("rnbqkbn1/pppppppr/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-5); + chai.expect(ffish.validateFen("rnbkqbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR/RB w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-5); + chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq ss 3+3 0 1", "3check-crazyhouse")).to.equal(-4); + chai.expect(ffish.validateFen("rnbqkknr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-3); + chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 3+3 x 1", "3check-crazyhouse")).to.equal(-2); + chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 3+3 0 -13", "3check-crazyhouse")).to.equal(-1); + chai.expect(ffish.validateFen("", "chess")).to.equal(0); + }); +}); + +describe('ffish.validateFen(fen, uciVariant, chess960)', function () { + it("it validates a given X-FEN and returns +1 if fen is valid. Otherwise an error code will be returned.", () => { + chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w AHah - 0 1", "chess", true)).to.equal(1); + chai.expect(ffish.validateFen("nrbqbkrn/pppppppp/8/8/8/8/PPPPPPPP/NRBQBKRN w BGbg - 0 1", "chess", true)).to.equal(1); + }); +}); + +describe('ffish.readGamePGN(pgn)', function () { + it("it reads a pgn string and returns a game object", () => { + fs = require('fs'); + let pgnFiles = ['deep_blue_kasparov_1997.pgn', 'lichess_pgn_2018.12.21_JannLee_vs_CrazyAra.j9eQS4TF.pgn', 'c60_ruy_lopez.pgn', 'pychess-variants_zJxHRVm1.pgn', 'Syrov - Dgebuadze.pgn', 'pychess-variants_YHEWvfWF.pgn'] + + let expectedFens = ["1r6/5kp1/RqQb1p1p/1p1PpP2/1Pp1B3/2P4P/6P1/5K2 b - - 14 45", + "3r2kr/2pb1Q2/4ppp1/3pN2p/1P1P4/3PbP2/P1P3PP/6NK[PPqrrbbnn] b - - 0 37", + "r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R b KQkq - 3 3", + "r1bQkb1r/ppp1pppp/2P5/2n2q2/8/2N2N2/PPP2PPP/R1BEKB1R[Hh] b KQCFkqcf - 0 8", + "5rk1/4p3/2p3rR/2p1P3/2Pp1B2/1P1P2P1/2N1n3/6K1 w - - 1 44", + "r1q3r1/pp3p2/2kN1bp1/8/3P1H2/6P1/PPP2BKP/R2E1R2[h] b acg - 0 20"] + + for (let idx = 0; idx < pgnFiles.length; ++idx) { + let pgnFilePath = pgnDir + pgnFiles[idx]; + + fs.readFile(pgnFilePath, 'utf8', function (err,data) { + if (err) { + return console.log(err); + } + let game = ffish.readGamePGN(data); + + const is960 = game.headers('Variant').endsWith('960'); + const variant = game.headers('Variant').toLowerCase().replace('960', ''); + const fen = game.headers('FEN'); + let board = new ffish.Board(variant, fen, is960); + const mainlineMoves = game.mainlineMoves().split(" "); + for (let idx2 = 0; idx2 < mainlineMoves.length; ++idx2) { + board.push(mainlineMoves[idx2]); + chai.expect(ffish.validateFen(board.fen(), variant, is960)).to.equal(1); + } + chai.expect(board.fen()).to.equal(expectedFens[idx]); + board.delete(); + game.delete(); + }); + } + }); +}); + +describe('game.headerKeys()', function () { + it("it returns all available header keys of the loaded game", () => { + fs = require('fs'); + let pgnFile = 'lichess_pgn_2018.12.21_JannLee_vs_CrazyAra.j9eQS4TF.pgn' + let pgnFilePath = pgnDir + pgnFile; + + fs.readFile(pgnFilePath, 'utf8', function (err,data) { + if (err) { + return console.log(err); + } + let game = ffish.readGamePGN(data); + chai.expect(game.headerKeys()).to.equal('Annotator Termination Variant ECO WhiteTitle BlackRatingDiff UTCTime Result WhiteElo Black UTCDate TimeControl BlackElo Event WhiteRatingDiff BlackTitle White Date Opening Site'); + game.delete(); + }); + }); +}); + + +describe('game.headers(key)', function () { + it("it returns the value for a given header key of a loaded game", () => { + fs = require('fs'); + let pgnFile = 'pychess-variants_zJxHRVm1.pgn'; + let pgnFilePath = pgnDir + pgnFile; + + fs.readFile(pgnFilePath, 'utf8', function (err,data) { + if (err) { + return console.log(err); + } + let game = ffish.readGamePGN(data); + chai.expect(game.headers("White")).to.equal("catask"); + chai.expect(game.headers("Black")).to.equal("Fairy-Stockfish"); + chai.expect(game.headers("Variant")).to.equal("Seirawan"); + chai.expect(game.headers("FEN")).to.equal("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[HEhe] w KQBCDFGkqbcdfg - 0 1"); + + game.delete(); + }); + }); +}); + +describe('game.mainlineMoves()', function () { + it("it returns the mainline of the loaded game in UCI notation", () => { + fs = require('fs'); + let pgnFile = 'lichess_pgn_2018.12.21_JannLee_vs_CrazyAra.j9eQS4TF.pgn'; + let pgnFilePath = pgnDir + pgnFile; + + fs.readFile(pgnFilePath, 'utf8', function (err,data) { + if (err) { + return console.log(err); + } + let game = ffish.readGamePGN(data); + chai.expect(game.mainlineMoves()).to.equal('e2e4 b8c6 b1c3 g8f6 d2d4 d7d5 e4e5 f6e4 f1b5 a7a6 b5c6 b7c6 g1e2 c8f5 e1g1 e7e6 f2f3 e4c3 b2c3 h7h5 N@e3 N@h4 N@a5 B@d7 e3f5 h4f5 B@b7 N@e3 a5c6 e3d1 c6d8 a8d8 f1d1 Q@b5 b7a6 b5a6 P@d3 N@e3 c1e3 f5e3 P@d6 e3d1 a1d1 B@e3 g1h1 f8d6 e5d6 a6d6 B@b4 d6b4 c3b4 P@f2 Q@f1 R@g1 f1g1 f2g1q d1g1 P@f2 N@g6 f2g1q e2g1 Q@e7 Q@d6 f7g6 d6e7 e8e7 R@f7 e7f7 N@e5 f7g8 N@f6 g7f6 Q@f7'); + game.delete(); + }); + }); +}); + +describe('ffish.variants()', function () { + it("it returns all currently available variants", () => { + chai.expect(ffish.variants().includes("chess")).to.equal(true); + chai.expect(ffish.variants().includes("crazyhouse")).to.equal(true); + chai.expect(ffish.variants().includes("janggi")).to.equal(true); + }); +}); diff --git a/Fairy-Stockfish-master/tests/perft.sh b/Fairy-Stockfish-master/tests/perft.sh new file mode 100644 index 0000000000000000000000000000000000000000..e50bd3f99bb0209c30ae5fc6a4fcbbf972158cb8 --- /dev/null +++ b/Fairy-Stockfish-master/tests/perft.sh @@ -0,0 +1,210 @@ +#!/bin/bash +# verify perft numbers (positions from www.chessprogramming.org/Perft_Results) + +error() +{ + echo "perft testing failed on line $1" + exit 1 +} +trap 'error ${LINENO}' ERR + +echo "perft testing started" + +cat << EOF > perft.exp + set timeout 60 + lassign \$argv var pos depth result chess960 + if {\$chess960 eq ""} {set chess960 false} + spawn ./stockfish + send "setoption name UCI_Chess960 value \$chess960\\n" + send "setoption name UCI_Variant value \$var\\n" + send "position \$pos\\ngo perft \$depth\\n" + expect "Nodes searched? \$result" {} timeout {exit 1} + send "quit\\n" + expect eof +EOF + +# chess +if [[ $1 == "" || $1 == "chess" ]]; then + expect perft.exp chess startpos 5 4865609 > /dev/null + expect perft.exp chess "fen r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq -" 5 193690690 > /dev/null + expect perft.exp chess "fen 8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - -" 6 11030083 > /dev/null + expect perft.exp chess "fen r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1" 5 15833292 > /dev/null + expect perft.exp chess "fen rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8" 5 89941194 > /dev/null + expect perft.exp chess "fen r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10" 5 164075551 > /dev/null +fi + +# variants +if [[ $1 == "all" || $1 == "variant" ]]; then + # small board + expect perft.exp losalamos startpos 5 191846 > /dev/null + expect perft.exp losalamos "fen 6/2P3/6/1K1k2/6/6 w - - 0 1" 6 187431 > /dev/null + # fairy + expect perft.exp torpedo startpos 4 209719 > /dev/null + expect perft.exp torpedo "fen rnbqkbnr/1ppppppp/8/6P1/p7/8/PPPPPP1P/RNBQKBNR w KQkq - 0 1" 4 232819 > /dev/null + expect perft.exp berolina "fen rnbqkbnr/pppp1ppp/8/2p5/5P2/8/PPP1PPPP/RNBQKBNR w KQkq c5d6 2 2" 3 46643 > /dev/null + expect perft.exp berolina "fen k7/6P1/8/8/8/2K2p2/4p3/8 w - - 0 1" 3 1983 > /dev/null + expect perft.exp berolina "fen rnbqkbnr/pp1p1ppp/8/2pPp3/8/8/PP1PPPPP/RNBQKBNR w KQkq d6c5 0 1" 2 1047 > /dev/null + expect perft.exp pawnsideways startpos 3 10022 > /dev/null + expect perft.exp pawnback startpos 3 9222 > /dev/null + expect perft.exp legan startpos 4 8138 > /dev/null + expect perft.exp makruk startpos 4 273026 > /dev/null + expect perft.exp cambodian startpos 4 361719 > /dev/null + expect perft.exp cambodian "fen r1s1ks1r/3nm3/pppNpppp/3n4/5P2/PPPPPNPP/8/R1SKMS1R b DEe 0 0 5" 2 72 > /dev/null + expect perft.exp karouk "fen rn1mksnr/3s4/pppppppp/8/4N3/PPPPPPPP/8/R1SKMSNR b DEde - 3 2" 4 358460 > /dev/null + expect perft.exp makpong "fen 3mk3/r3s1R1/1psppnp1/p1pn4/1P2NP2/P1PPP1P1/4NS2/R1SKM3 w - - 0 1" 4 593103 > /dev/null + expect perft.exp asean startpos 4 273026 > /dev/null + expect perft.exp ai-wok startpos 4 485045 > /dev/null + expect perft.exp ai-wok "fen 8/8/8/2sp2k1/7p/3P4/6K1/7r w - - 0 1" 5 30055 > /dev/null + expect perft.exp sittuyin startpos 3 580096 > /dev/null + expect perft.exp sittuyin "fen 8/8/6R1/s3r3/P5R1/1KP3p1/1F2kr2/8[] b - - 0 72" 4 652686 > /dev/null + expect perft.exp sittuyin "fen 2r5/6k1/6p1/3s2P1/3npR2/8/p2N2F1/3K4[] w - - 1 50" 4 373984 > /dev/null + expect perft.exp sittuyin "fen 8/6s1/5P2/3n4/pR2K2S/1P6/1k4p1/8[] w - - 1 50" 4 268869 > /dev/null + expect perft.exp sittuyin "fen 1k5K/3r2P1/8/8/8/8/8/8[] w - - 0 1" 5 68662 > /dev/null + expect perft.exp almost startpos 3 11895 > /dev/null + expect perft.exp almost "fen 8/1k5P/8/8/8/3K4/p7/8 b - - 0 1" 2 140 > /dev/null + expect perft.exp sortofalmost startpos 3 10815 > /dev/null + expect perft.exp sortofalmost "fen 8/1k5P/8/8/8/3K4/p7/8 b - - 0 1" 2 139 > /dev/null + expect perft.exp chigorin startpos 3 11408 > /dev/null + expect perft.exp chigorin "fen 8/1k5P/8/8/8/3K4/p7/8 b - - 0 1" 2 117 > /dev/null + expect perft.exp perfect startpos 3 15082 > /dev/null + expect perft.exp perfect "fen c3k2r/pppppppp/8/8/8/8/PPPPPPPP/C3K2R w KQkq - 0 1" 3 17500 > /dev/null + expect perft.exp spartan startpos 3 14244 > /dev/null + # duple check & mate + expect perft.exp spartan "fen k6k/hh2Q2h/8/8/8/8/8/4K3 w - - 0 1" 3 6130 > /dev/null + # self duple check with promotions + expect perft.exp spartan "fen 8/8/8/8/6Q1/8/2h3h1/4K1k1 b - - 0 1" 3 3456 > /dev/null + expect perft.exp shatar startpos 4 177344 > /dev/null + expect perft.exp shatranj startpos 4 68122 > /dev/null + expect perft.exp amazon startpos 4 318185 > /dev/null + expect perft.exp nightrider startpos 4 419019 > /dev/null + expect perft.exp grasshopper startpos 4 635298 > /dev/null + expect perft.exp hoppelpoppel startpos 4 202459 > /dev/null + expect perft.exp newzealand startpos 4 200310 > /dev/null + # alternative goals + expect perft.exp racingkings startpos 4 296242 > /dev/null + expect perft.exp racingkings "fen 6r1/2K5/5k2/8/3R4/8/8/8 w - - 0 1" 4 86041 > /dev/null + expect perft.exp racingkings "fen 6R1/2k5/5K2/8/3r4/8/8/8 b - - 0 1" 4 86009 > /dev/null + expect perft.exp racingkings "fen 4brn1/2K2k2/8/8/8/8/8/8 w - - 0 1" 6 265932 > /dev/null + expect perft.exp kingofthehill "fen rnb2b1r/ppp2ppp/3k4/8/1PKp1pn1/3Pq3/PBP1P2P/RN1Q1B1R w - - 4 12" 3 19003 > /dev/null + expect perft.exp 3check "fen 7r/1p4p1/pk3p2/RN6/8/P5Pp/3p1P1P/4R1K1 w - - 1+3 1 39" 3 12407 > /dev/null + expect perft.exp 3check "fen 7r/1p4p1/pk3p2/RN6/8/P5Pp/3p1P1P/4R1K1 w - - 1 39 +2+0" 3 12407 > /dev/null + expect perft.exp atomic startpos 4 197326 > /dev/null + expect perft.exp atomic "fen rn2kb1r/1pp1p2p/p2q1pp1/3P4/2P3b1/4PN2/PP3PPP/R2QKB1R b KQkq - 0 1" 4 1434825 > /dev/null + expect perft.exp atomic "fen rn1qkb1r/p5pp/2p5/3p4/N3P3/5P2/PPP4P/R1BQK3 w Qkq - 0 1" 4 714499 > /dev/null + expect perft.exp atomic "fen r4b1r/2kb1N2/p2Bpnp1/8/2Pp3p/1P1PPP2/P5PP/R3K2R b KQ - 0 1" 2 148 > /dev/null + expect perft.exp atomar startpos 4 197779 > /dev/null + expect perft.exp atomar "fen 7r/6Pb/1np5/1p2kp2/P1P1n3/1P2KP1B/5N2/8 w - - 0 1" 3 24644 > /dev/null + expect perft.exp nocheckatomic startpos 4 197779 > /dev/null + expect perft.exp nocheckatomic "fen 7r/6Pb/1np5/1p2kp2/P1P1n3/1P2KP1B/5N2/8 w - - 0 1" 3 21347 > /dev/null + expect perft.exp antichess startpos 4 153299 > /dev/null + expect perft.exp giveaway startpos 4 153299 > /dev/null + expect perft.exp giveaway "fen 8/1p6/8/8/8/8/P7/8 w - - 0 1" 4 3 > /dev/null + expect perft.exp giveaway "fen 8/2p5/8/8/8/8/P7/8 w - - 0 1" 12 2557 > /dev/null + expect perft.exp codrus startpos 4 153299 > /dev/null + expect perft.exp codrus "fen 5bnr/2pp2pp/P4k2/R3Q1P1/8/2N1K3/1P1PP1PP/2B2BNR w - - 1 15" 4 19 > /dev/null + expect perft.exp horde startpos 4 23310 > /dev/null + expect perft.exp horde "fen 4k3/pp4q1/3P2p1/8/P3PP2/PPP2r2/PPP5/PPPP4 b - - 0 1" 4 56539 > /dev/null + expect perft.exp horde "fen k7/5p2/4p2P/3p2P1/2p2P2/1p2P2P/p2P2P1/2P2P2 w - - 0 1" 4 33781 > /dev/null + expect perft.exp horde "fen 4k3/7r/8/P7/2p1n2P/3p2P1/1P3P2/PPP1PPP1 w - - 0 1" 4 128809 > /dev/null + expect perft.exp horde "fen rnbqkbnr/6p1/2p1Pp1P/P1PPPP2/Pp4PP/1p2PPPP/1P2PPPP/PP1nPPPP b kq a3 0 18" 4 197287 > /dev/null + expect perft.exp coregal startpos 4 195896 > /dev/null + expect perft.exp coregal "fen rn2kb1r/ppp1pppp/6q1/8/2PP2b1/5B2/PP3P1P/R1BQK1NR w KQkq - 1 9" 3 20421 > /dev/null + expect perft.exp coregal "fen 2Q5/3Pq2k/6p1/4Bp1p/5P1P/8/8/K7 w - - 2 72" 4 55970 > /dev/null + expect perft.exp coregal "fen r3kb1r/1pp1pppp/p1q2n2/3P4/6b1/2N2N2/PPP2PPP/R1BQ1RK1 b kq - 0 9" 4 136511 > /dev/null + expect perft.exp knightmate startpos 4 139774 > /dev/null + expect perft.exp losers startpos 4 152955 > /dev/null + expect perft.exp kinglet startpos 4 197742 > /dev/null + expect perft.exp threekings startpos 4 199514 > /dev/null + expect perft.exp dobutsu startpos 6 71677 > /dev/null + expect perft.exp dobutsu "fen g1e/1cL/lC1/E1G[] w - - 4 3" 4 2290 > /dev/null + + # pockets + expect perft.exp crazyhouse startpos 4 197281 > /dev/null + expect perft.exp crazyhouse "fen 2k5/8/8/8/8/8/8/4K3[QRBNPqrbnp] w - - 0 1" 2 75353 > /dev/null + expect perft.exp crazyhouse "fen 2k5/8/8/8/8/8/8/4K3[Qn] w - - 0 1" 3 88634 > /dev/null + expect perft.exp crazyhouse "fen 2k5/8/8/8/8/8/8/4K3/Qn w - - 0 1" 3 88634 > /dev/null + expect perft.exp crazyhouse "fen r1bqk2r/pppp1ppp/2n1p3/4P3/1b1Pn3/2NB1N2/PPP2PPP/R1BQK2R[] b KQkq - 0 1" 3 58057 > /dev/null + expect perft.exp loop startpos 4 197281 > /dev/null + expect perft.exp loop "fen 5R2/2p1Nb2/2B4k/6p1/8/P3PP2/1PPqR3/3R1BKn[QBNPPPPrrrnppp] b - - 1 1" 2 31983 > /dev/null + expect perft.exp chessgi startpos 4 197281 > /dev/null + expect perft.exp chessgi "fen 5Rp1/2p1Nb2/2B4k/6p1/8/P3PP2/1PPqR3/3R1BKn[QBNPPPPrrrnpp] b - - 1 48" 2 32816 > /dev/null + expect perft.exp pocketknight startpos 3 88617 > /dev/null + expect perft.exp placement startpos 3 50560 > /dev/null + expect perft.exp placement "fen rnbq1bnr/pppppppp/8/8/8/8/PPPPPPPP/QR1BKNN1[BRk] w - - 0 1" 6 17804 > /dev/null + expect perft.exp placement "fen 1n1r1q2/pppppppp/8/8/8/8/PPPPPPPP/1N1B1R1N[KQRBkrbbn] b - - 0 4" 5 145152 > /dev/null + expect perft.exp placement "fen r3k3/pppppppp/8/8/8/8/PPPPPPPP/R6R[Kr] w q - 0 1" 4 18492 > /dev/null + expect perft.exp seirawan startpos 4 782599 > /dev/null + expect perft.exp seirawan "fen reb1k2r/ppppqppp/2nb1n2/4p3/4P3/N1P2N2/PB1PQPPP/RE2KBHR[h] b KQkqc - 3 7" 4 890467 > /dev/null + expect perft.exp shouse startpos 3 546694 > /dev/null + expect perft.exp euroshogi startpos 4 380499 > /dev/null + expect perft.exp minishogi startpos 5 533203 > /dev/null + expect perft.exp kyotoshogi startpos 5 225903 > /dev/null + expect perft.exp micro startpos 5 71328 > /dev/null + expect perft.exp torishogi startpos 4 103857 > /dev/null + expect perft.exp koedem "fen rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNB2BNR[KQ] w kq - 0 1" 1 34 > /dev/null + expect perft.exp koedem "fen rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNB1KBNR[Q] w KQkq - 0 1" 1 54 > /dev/null + # non-chess + expect perft.exp ataxx startpos 4 155888 > /dev/null + expect perft.exp ataxx "fen 7/7/7/7/ppppppp/ppppppp/PPPPPPP w 0 1" 5 452980 > /dev/null + expect perft.exp breakthrough startpos 4 256036 > /dev/null + expect perft.exp breakthrough "fen 1p2pp1p/2p2ppp/2P5/8/8/3P2P1/1p1P2PP/1PP1PP1P w - - 1 26" 4 121264 > /dev/null + expect perft.exp clobber startpos 3 80063 > /dev/null + expect perft.exp flipello startpos 7 55092 > /dev/null + expect perft.exp flipersi startpos 9 38208 > /dev/null + # 960 variants + expect perft.exp atomic "fen 8/8/8/8/8/8/2k5/rR4KR w KQ - 0 1" 4 61401 true > /dev/null + expect perft.exp atomic "fen r3k1rR/5K2/8/8/8/8/8/8 b kq - 0 1" 4 98729 true > /dev/null + expect perft.exp atomic "fen Rr2k1rR/3K4/3p4/8/8/8/7P/8 w kq - 0 1" 4 241478 true > /dev/null + expect perft.exp atomic "fen 1R4kr/4K3/8/8/8/8/8/8 b k - 0 1" 4 17915 true > /dev/null + expect perft.exp extinction "fen rnbqb1kr/pppppppp/8/8/8/8/PPPPPPPP/RNBQB1KR w AHah - 0 1" 4 195286 true > /dev/null + expect perft.exp seirawan "fen qbbrnkrn/pppppppp/8/8/8/8/PPPPPPPP/QBBRNKRN[HEhe] w ABCDEFGHabcdefgh - 0 1" 3 21170 true > /dev/null +fi + +# large-board variants +if [[ $1 == "all" || $1 == "largeboard" ]]; then + expect perft.exp shogi startpos 4 719731 > /dev/null + expect perft.exp shoshogi startpos 4 445372 > /dev/null # configurable pieces + expect perft.exp yarishogi startpos 4 158404 > /dev/null # configurable pieces + expect perft.exp capablanca startpos 4 805128 > /dev/null + expect perft.exp embassy startpos 4 809539 > /dev/null + expect perft.exp janus startpos 4 772074 > /dev/null + expect perft.exp modern startpos 4 433729 > /dev/null + expect perft.exp chancellor startpos 4 436656 > /dev/null + expect perft.exp courier startpos 4 500337 > /dev/null + expect perft.exp grand startpos 3 259514 > /dev/null + expect perft.exp grand "fen r8r/1nbqkcabn1/ppp2ppppp/3p6/4pP4/10/10/PPPPP1PPPP/1NBQKCABN1/R8R w - e7 0 3" 2 5768 > /dev/null + expect perft.exp opulent startpos 3 133829 > /dev/null + expect perft.exp tencubed startpos 3 68230 > /dev/null + expect perft.exp centaur startpos 3 24490 > /dev/null + expect perft.exp gustav3 startpos 4 331659 > /dev/null + expect perft.exp omicron startpos 4 967381 > /dev/null + expect perft.exp troitzky startpos 3 8766 > /dev/null + expect perft.exp wolf startpos 3 13722 > /dev/null + expect perft.exp wolf "fen 8/k5SP/8/8/8/8/8/8/8/7K w - - 0 1" 4 10587 > /dev/null + expect perft.exp shako "fen 4kc3c/ernbq1b1re/ppp3p1pp/3p2pp2/4p5/5P4/2PN2P3/PP1PP2PPP/ER1BQKBNR1/5C3C w KQ - 0 9" 3 26325 > /dev/null + expect perft.exp shako "fen 4ncr1k1/1cr2P4/pp2p2pp1/P7PN/2Ep1p4/B3P1eN2/2P1n1P3/1B1P1K4/9p/5C2CR w - - 0 1" 3 180467 > /dev/null + expect perft.exp shako "fen r5k3/4q2c2/1ebppnp3/1pp3BeEQ/10/2PE2P3/1P3P4/5NP2P/rR3KB3/7C2 w Q - 3 35" 2 4940 > /dev/null + expect perft.exp shako "fen 10/rr3k4/ppppp5/10/10/10/10/6PPPP/5K2RR/10 w Kq - 0 1" 2 460 > /dev/null + expect perft.exp xiangqi startpos 4 3290240 > /dev/null + expect perft.exp xiangqi "fen 1rbaka2R/5r3/6n2/2p1p1p2/4P1bP1/PpC3Bc1/1nPR2P2/2N2AN2/1c2K1p2/2BAC4 w - - 0 1" 4 4485547 > /dev/null + expect perft.exp xiangqi "fen 4kcP1N/8n/3rb4/9/9/9/9/3p1A3/4K4/5CB2 w - - 0 1" 4 92741 > /dev/null + expect perft.exp manchu startpos 4 798554 > /dev/null + expect perft.exp janggi startpos 4 1065277 > /dev/null + expect perft.exp janggi "fen 1n1kaabn1/cr2N4/5C1c1/p1pNp3p/9/9/P1PbP1P1P/3r1p3/4A4/R1BA1KB1R b - - 0 1" 4 76763 > /dev/null + expect perft.exp janggi "fen 1Pbcka3/3nNn1c1/N2CaC3/1pB6/9/9/5P3/9/4K4/9 w - - 0 23" 4 151202 > /dev/null + expect perft.exp jesonmor startpos 3 27960 > /dev/null + expect perft.exp jesonmor "fen nn1nnn1nn/9/3n1n3/9/9/9/3N1N3/9/NN1NNN1NN w - - 4 3" 3 37564 > /dev/null + + # non-chess + expect perft.exp flipello10 startpos 7 55180 > /dev/null +fi + +# special variants +if [[ $1 == "all" ]]; then + expect perft.exp duck startpos 1 640 > /dev/null + expect perft.exp amazons startpos 1 2176 > /dev/null +fi + +rm perft.exp + +echo "perft testing OK" diff --git a/Fairy-Stockfish-master/tests/pgn/Syrov - Dgebuadze.pgn b/Fairy-Stockfish-master/tests/pgn/Syrov - Dgebuadze.pgn new file mode 100644 index 0000000000000000000000000000000000000000..8b306442d7caf52ec6274839de00f764afd6ae32 --- /dev/null +++ b/Fairy-Stockfish-master/tests/pgn/Syrov - Dgebuadze.pgn @@ -0,0 +1,137 @@ +[Event "RLP-Open R7"] +[Site "?"] +[Date "????.??.??"] +[Round "1.2"] +[White "Syrov, Arkadi"] +[Black "Dgebuadze, Alexandre"] +[Result "0-1"] +[ECO "B31"] +[WhiteElo "2291"] +[BlackElo "2508"] +[Annotator "Syrov, Arkadi"] +[PlyCount "86"] + +1. e4 {[%emt 0:00:01]} c5 {[%emt 0:00:21] keine völlige Überraschung, da ich +gesehen habe, dass er schon c5 gespielt hat, aber nur gegen Morra. Ich hatte +hauptsächlich mit Französisch gerechnet.} 2. Nf3 {[%emt 0:00:05]} Nc6 { +[%emt 0:00:04]} 3. Bb5 {[%emt 0:00:04]} g6 {[%emt 0:00:09]} 4. Bxc6 {[%emt 0: +00:05]} bxc6 {[%emt 0:00:07]} 5. O-O {[%emt 0:00:05]} Bg7 {[%emt 0:00:16]} 6. +Re1 {[%emt 0:00:05] jetzt begann er lange zu denken, was ich nicht erwartet +habe. Bis hierher habe ich gefühlt 10 Partien in der Datenbank.} d5 {[%emt 0: +21:13] das ist nicht die Hauptvariante. Während ich herumlief und auf seinen +Zug wartete, habe ich versucht mich an die Theorie nach Sh6 c3 zu erinnern. +Von d5 wusste ich nichts, also reagierte ich Standardmäßig.} 7. d3 {[%emt 0: +02:47]} Bg4 {[%emt 0:01:22]} 8. Nbd2 {[%emt 0:03:46] Im nachhinein ist das +eine schlechte Entscheidung. Ich hätte gleich h3 spielen sollen. Ich wollte +auf f3 mit dem Springer schlagen und habe mich selbst ausgetrixt, denn er +bekommt eine neue Option.} (8. h3 Bxf3 9. Qxf3 $14 {sollte etwas besser sein.}) +8... Nf6 {[%emt 0:03:45]} 9. e5 {[%emt 0:05:32]} (9. h3 {wäre glaube ich +immer noch besser gewesen und dann wieder mit der Dame auf f3 schlagen. Aber +ich habe die ganze Idee mit Le6 nicht betrachtet.}) 9... Nd7 {[%emt 0:02:01]} +10. h3 {[%emt 0:00:14]} Be6 {[%emt 0:09:32]} 11. c4 $2 {[%emt 0:17:01] die +Standardidee um die Doppelbauern festzulegen.} (11. Ng5 $1 {war meine +Alternative. Ich habe es wegen:} Nxe5 {nicht gespielt.} (11... Nf8 {war der +Plan meines Gegners, was auch sinnvoll aussieht. Aber konkret hat Weiß eine +starke Antwort (Natürlich habe ich alles folgende nicht gesehen):} 12. Nb3 $1 +h6 13. Nxe6 Nxe6 14. Be3 Bxe5 (14... Qb6 15. f4 $14 {[%csl Gc5][%cal Gd1f3, +Gf3f2]} a5 (15... O-O 16. Qf3 a5 17. Qf2 d4 18. Bc1 $16 {ist einfach traurig +für Schwarz}) 16. Qf3 a4 17. Nxc5 $1 Nxc5 18. Qf2 Nxd3 19. cxd3 Qb5 20. Bc5 e6 +(20... Qxd3 $2 21. Bxe7 $3 Kxe7 22. Qc5+ $18) 21. d4 $14 {[%csl Gc6]}) 15. Nxc5 +Nxc5 16. Bxc5 Bf6 17. Qd2 $1 $14 {Schwarz kann nicht rochieren, da h6 hängt +und es ist klar, dass Weiß einen kleinen Vorteil haben sollte.}) 12. f4 Nd7 +13. Nxe6 (13. Rxe6 fxe6 14. Nxe6 Qb6 15. Nxg7+ Kf7 {schien auch nicht +funktionieren. Ich habe keine Figuren, die im Angriff sind.}) 13... fxe6 14. +Rxe6 O-O {und ich wusste nicht was das soll. Denn der Bauer auf f4 ist Schwach, +ich dachte während der Partie, dass Weiß hier schlechter steht. Was +anscheinend eine riesige Fehleinschätzung ist. Denn nach} (14... Rc8 15. Nf3 +c4 {soll Schwarz probieren, aber es ist immer noch besser für Weiß} 16. dxc4 +Nc5 17. Re1 $14) 15. Nf3 {hängt c6 und e7 und nach} Rc8 (15... Qc7 16. Rxe7 +Rae8 17. Qe2 Rxe7 18. Qxe7 $16 {ist einfach ein Bauer}) 16. Qe1 Bf6 17. f5 $1 { +hat Schwarz riesige Probleme.}) (11. d4 {habe ich auch kurz überlegt, aber +ich habe nicht ganz verstanden, was das mir bringt, außer die Stellung zu +öffnen, was meinem Gegner zugute kommen sollte.}) (11. b3 {war auch eine +Möglichkeit an der ich überlegt habe und dann ohne c4 spielen. Oder in einem +besseren Moment c4 spielen. Vielleicht wäre das eine bessere Möglichkeit.}) ( +11. Qe2 h6 12. b3 {[%cal Ga2a4] und ohne c4 spielen sieht auch sehr gut aus.}) +11... h6 {[%emt 0:07:58] ein logischer Zug um Sg5 aus der Stellung zu nehmen. +Und manchmal wird g5 zur Idee.} 12. Qe2 {[%emt 0:00:48] sah logisch aus, um c4 +zu decken, den e5 zu decken und dann meinen Problemspringer auf d2 zu +überführen.} Qb6 {[%emt 0:02:13] ein etwas mysteriöser Zug. Die Idee ist +wahrscheinlich in manchen Varianten Db4 zu haben und b2 im Visier zu behalten. +Ich war hier ehrlich gesagt etwas verloren und wusste nicht wirklich was ich +als nächstes machen sollte und begann etwas kreativ zu werden, bis ich alle +meine Ideen selbst wiederlegt habe.} 13. Nb1 {[%emt 0:13:59][%cal Gb1c3,Gb2b3, +Gc3a4] wenn ich noch einen Zug machen darf, steht Schwarz glaube ich ganz +schlecht, aber den gibt mir Schwarz natürlich nicht.} (13. a4 a5 14. Ra3 { +war eine sau komische Idee, die ich hatte um den c5 unter Druck zu setzen, +aber ich war mir über die Bewertung der Schlussstellung nicht sicher.} Nb8 { +ich glaube Schwarz sollte das schwache b4 Feld ausnutzen.} 15. Rb3 Qa7 { +hier steht die Dame dachte ich am besten} (15... Qc7 16. Rc3 Na6 17. b3 Nb4 18. +Ba3 {habe ich vielleicht motive mit nimmt auf d5 und d4, da die Dame auf c7 in +der Turmlinie steht.}) 16. Rc3 Na6 17. b3 Nb4 18. Ba3 O-O {ich kann den c5 +nicht noch mehr angreifen, da nach Tec1 d4 kommt und ich weiß nicht, was ich +weiter machen soll.}) (13. b3 a5 {hat mir nicht gefallen, da ich entweder a4 +spielen muss, oder a4 zulassen muss. In beiden fällen hat mir die Perspektive +nicht gefallen, da entweder b4 sehr schwach wird und Sb4 und Lf5 kommen, oder +all dass und der b3 wird schwach. (Bzw a2, wenn ich nicht auf b3 mit dem +Bauern nehme).}) (13. Nf1 {ich wusste nicht, wohin der Springer von hier gehen +will.}) (13. Nb3 {ich dachte das läuft einfach in a5 und a4 mit Tempo.}) 13... +a5 {[%emt 0:00:21] sonst käme Sc3 und auf a5 dann, b3 oder Sa4.} 14. Bf4 { +[%emt 0:07:12] wieder war ich etwas verloren und hatte keine Ahnung wie ich +meine Figuren hinstellen soll. Es ist klar, dass er a4 im nächsten Zug machen +wird und was ich mache ist mir ein Rätsel.} a4 {[%emt 0:02:34]} 15. Na3 { +[%emt 0:02:47] ich habe wirklich keine Ahnung was hier richtig ist und was +nicht.} g5 {[%emt 0:03:33]} 16. Bg3 {[%emt 0:03:12]} O-O {[%emt 0:09:10]} ( +16... g4 17. hxg4 Bxg4 18. Rab1 {[%cal Ge2e3] ich dachte nicht, dass es so +viel für Schwarz bringt.}) 17. Rab1 {[%emt 0:00:59] um b2 zu decken, damit +sich die Dame bewegen kann. Vielleicht wäre h4 sofort richtig gewesen!?} Rfe8 +{[%emt 0:02:02]} (17... Bf5 $1 {gefolgt von e6 sieht stark aus} 18. Nc2 e6 19. +Ne3 Bg6 $15) 18. h4 {[%emt 0:01:14]} f6 {[%emt 0:04:19]} (18... g4 19. Nh2 h5 +20. f3 {hier war ich ziemlich optimistisch gute Chancen zu haben.}) {Hier habe +ich überlegt, dass ich eigentlich nichts machen will. Ich will glaube ich +alles so lassen wie es ist. Aber ich konnte einfach keinen Zug finden, der +irgendwie sinnvoll ist. Im nachhinein sieht Sc2 gut aus.} 19. hxg5 {[%emt 0:05: +39] ich glaube das ist der Verlustzug. Ich wollte einfach hg, hg einschieben, +und dann weiterschauen. Aber das hat ein Problem...} (19. Nc2 {und die +Stellung ist noch spielbar}) 19... fxg5 {[%emt 0:01:10] nachdem dieser Zug kam, +verstand ich, dass ich in riesigen Problemen stecke. Es kommt einfach Tf8-f5 +und was mache ich?} 20. Nh2 {[%emt 0:00:59] ich wollte dem Lg4 und Tf8-Motiv +aus dem Weg gehen. Und vielleicht Dh5. Ich wollte noch viellecht f4 spielen um +etwas Gegenspiel zu bekommen.} (20. Nc2 {hätte ich spielen sollen} Rf8 21. +Red1 Bg4 22. e6 Bxf3 23. gxf3 Nf6 24. f4 gxf4 25. Bxf4 Qd8 $15 {[%cal Gd8e8, +Ge8h5] aber das soll Schwarz noch finden.}) 20... Rf8 {[%emt 0:00:51]} 21. Qe3 +{[%emt 0:06:01]} Rf5 {[%emt 0:02:37]} 22. f4 {[%emt 0:00:14] sonst fliegt e5 +ersatzlos und damit auch meine Stellung, dachte ich. Wenn ich es nicht mache, +geht es noch halbwegs, laut dem Blechheinie} (22. Nc2 $15) 22... Raf8 {[%emt 0: +01:40]} 23. fxg5 {[%emt 0:01:14] auch glaube ich ohne wirklich sinnvolle +Alternative} Rxg5 {[%emt 0:00:26]} 24. Bf4 {[%emt 0:00:42]} Rg6 {[%emt 0:07:46] +} (24... Rxf4 25. Qxf4 Bxe5 26. Qh4 {und ich dachte ich hätte noch +gegenchancen.}) (24... Bxe5 {war eine Idee, die ich gar nicht gesehen habe. +Aber ich habe noch eine Möglichkeit eine völlige Chaos-Stellung zu erreichen: +} 25. Bxg5 Bd4 26. Qxd4 cxd4 27. Bxe7 {und was hier abgeht, weiß nur +Stockfish.}) 25. Nf3 {[%emt 0:03:59] ich glaube das verliert forciert.} (25. +Re2 {ist vielleicht der Zug um weiterzuspielen. Jetzt kann ich vielleicht den +Turm nach f1 stellen und ich glaube ich verliere nichts sofort.}) 25... d4 { +[%emt 0:01:46]} (25... Rg4 26. Bh2 d4 27. Qe2 {ist glaube ich noch spielbar}) +26. Qd2 {[%emt 0:01:38]} Qb4 {[%emt 0:02:22]} (26... Rg4 27. Re4 (27. Bxh6 $2 +Rxf3 28. Bxg7 Kxg7 $19) 27... Bf5 28. Nh2 {wäre dachte ich noch spielbar} Rg6 +29. Re2 Qb4 {ist aber auch traurig und laut dem PC gewonnen}) 27. Qc1 {[%emt 0: +03:50] das Endspiel ist hoffnungslos.} Rg4 {[%emt 0:00:40]} 28. Nc2 {[%emt 0: +00:51]} (28. Bd2 Qb8 29. Nh2 Rg6 {war so traurig, dass ich es gleich verworfen +habe und lieber eine Qualle gegeben habe. Leider muss er sie nicht sofort +nehmen}) 28... Qb6 {[%emt 0:01:06]} 29. Re4 {[%emt 0:00:42]} Bf5 {[%emt 0:00: +08]} 30. Nh2 {[%emt 0:00:06]} Rg6 {[%emt 0:00:57]} 31. Qe1 {[%emt 0:00:46]} Qd8 +{[%emt 0:02:31] der Rest ist eigentlich nicht mehr interessant. Ich glaube +nicht, dass ich danach noch irgendeine Chance habe.} (31... Bxe4 32. Qxe4 { +ist vielleicht noch nicht ganz klar, da der schwarze Turm auf g6 etwas in der +Luft rumhängt.}) 32. b3 {[%emt 0:01:17]} Bxe4 {[%emt 0:01:05]} 33. Qxe4 { +[%emt 0:00:03]} Re6 {[%emt 0:01:13]} 34. Nf3 {[%emt 0:00:18]} Qe8 {[%emt 0:00: +31]} 35. g3 {[%emt 0:01:23]} Qh5 {[%emt 0:01:00]} 36. Re1 {[%emt 0:02:55]} axb3 +{[%emt 0:00:17]} 37. axb3 {[%emt 0:00:04]} Qg4 {[%emt 0:02:41]} 38. Re2 { +[%emt 0:04:05]} Nf6 {[%emt 0:02:39] noch eine schöne letzte ressource.} 39. +Nh2 {[%emt 0:00:38]} Nxe4 {[%emt 0:00:55]} 40. Nxg4 {[%emt 0:00:04]} Nc3 { +[%emt 0:00:11]} 41. Rh2 {[%emt 0:02:24]} Rg6 {[%emt 0:00:10]} 42. Nxh6+ { +[%emt 0:00:15]} Bxh6 {[%emt 0:00:10]} 43. Rxh6 {[%emt 0:00:04]} Ne2+ {[%emt 0: +00:06] Zwei Fehler: 1: Sg5 falsch eingeschätzt. Und 2: hg fg übersehen.} 0-1 + + diff --git a/Fairy-Stockfish-master/tests/pgn/c60_ruy_lopez.pgn b/Fairy-Stockfish-master/tests/pgn/c60_ruy_lopez.pgn new file mode 100644 index 0000000000000000000000000000000000000000..4ec04a04f43797271b4d1d29310312c377e064ba --- /dev/null +++ b/Fairy-Stockfish-master/tests/pgn/c60_ruy_lopez.pgn @@ -0,0 +1,10 @@ +[Event "?"] +[Site "?"] +[Date "2020.09.24"] +[Round "?"] +[White "White"] +[Black "Black"] +[Result "*"] + +1. e4 e5 2. Nf3 Nc6 3. Bb5 * + diff --git a/Fairy-Stockfish-master/tests/pgn/deep_blue_kasparov_1997.pgn b/Fairy-Stockfish-master/tests/pgn/deep_blue_kasparov_1997.pgn new file mode 100644 index 0000000000000000000000000000000000000000..ce5b2eb054eacf306a667bdafaac64557d58996f --- /dev/null +++ b/Fairy-Stockfish-master/tests/pgn/deep_blue_kasparov_1997.pgn @@ -0,0 +1,22 @@ +[Event "IBM Man-Machine"] +[Site "New York, NY USA"] +[Date "1997.05.04"] +[EventDate "?"] +[Round "2"] +[Result "1-0"] +[White "Deep Blue (Computer)"] +[Black "Garry Kasparov"] +[ECO "C93"] +[WhiteElo "?"] +[BlackElo "?"] +[PlyCount "89"] + +1.e4 e5 2.Nf3 Nc6 3.Bb5 a6 4.Ba4 Nf6 5.O-O Be7 6.Re1 b5 7.Bb3 +d6 8.c3 O-O 9.h3 h6 10.d4 Re8 11.Nbd2 Bf8 12.Nf1 Bd7 13.Ng3 +Na5 14.Bc2 c5 15.b3 Nc6 16.d5 Ne7 17.Be3 Ng6 18.Qd2 Nh7 19.a4 +Nh4 20.Nxh4 Qxh4 21.Qe2 Qd8 22.b4 Qc7 23.Rec1 c4 24.Ra3 Rec8 +25.Rca1 Qd8 26.f4 Nf6 27.fxe5 dxe5 28.Qf1 Ne8 29.Qf2 Nd6 +30.Bb6 Qe8 31.R3a2 Be7 32.Bc5 Bf8 33.Nf5 Bxf5 34.exf5 f6 +35.Bxd6 Bxd6 36.axb5 axb5 37.Be4 Rxa2 38.Qxa2 Qd7 39.Qa7 Rc7 +40.Qb6 Rb7 41.Ra8+ Kf7 42.Qa6 Qc7 43.Qc6 Qb6+ 44.Kf1 Rb8 +45.Ra6 1-0 \ No newline at end of file diff --git a/Fairy-Stockfish-master/tests/pgn/lichess_pgn_2018.12.21_JannLee_vs_CrazyAra.j9eQS4TF.pgn b/Fairy-Stockfish-master/tests/pgn/lichess_pgn_2018.12.21_JannLee_vs_CrazyAra.j9eQS4TF.pgn new file mode 100644 index 0000000000000000000000000000000000000000..74115e0d8c6782ec35be2cf756e1d7328b76531b --- /dev/null +++ b/Fairy-Stockfish-master/tests/pgn/lichess_pgn_2018.12.21_JannLee_vs_CrazyAra.j9eQS4TF.pgn @@ -0,0 +1,24 @@ +[Event "Rated Crazyhouse game"] +[Site "https://lichess.org/j9eQS4TF"] +[Date "2018.12.21"] +[White "JannLee"] +[Black "CrazyAra"] +[Result "1-0"] +[UTCDate "2018.12.21"] +[UTCTime "18:33:01"] +[WhiteElo "2641"] +[BlackElo "2610"] +[WhiteRatingDiff "+6"] +[BlackRatingDiff "-13"] +[WhiteTitle "LM"] +[BlackTitle "BOT"] +[Variant "Crazyhouse"] +[TimeControl "300+15"] +[ECO "B00"] +[Opening "Nimzowitsch Defense: Scandinavian Variation, Bogoljubov Variation, Vehre Variation"] +[Termination "Normal"] +[Annotator "lichess.org"] + +1. e4 { [%eval 1.52] [%clk 0:05:00] } 1... Nc6 { [%eval 0.77] [%clk 0:05:00] } 2. Nc3 { [%eval 0.66] [%clk 0:05:11] } 2... Nf6?! { (0.66 → 1.19) Inaccuracy. e5 was best. } { [%eval 1.19] [%clk 0:05:08] } (2... e5 3. Nf3 Nf6 4. Bc4 Bc5 5. O-O d6 6. d3 O-O 7. Ng5) 3. d4 { [%eval 0.67] [%clk 0:05:11] } 3... d5 { [%eval 0.84] [%clk 0:05:08] } { B00 Nimzowitsch Defense: Scandinavian Variation, Bogoljubov Variation, Vehre Variation } 4. e5 { [%eval 0.61] [%clk 0:05:24] } 4... Ne4 { [%eval 0.75] [%clk 0:05:15] } 5. Bb5 { [%eval 2.46] [%clk 0:05:26] } 5... a6 { [%eval 3.05] [%clk 0:05:07] } 6. Bxc6+ { [%eval 2.5] [%clk 0:05:28] } 6... bxc6 { [%eval 3.2] [%clk 0:05:14] } 7. Nge2 { [%eval 0.48] [%clk 0:05:17] } 7... Bf5?? { (0.48 → 2.75) Blunder. Bg4 was best. } { [%eval 2.75] [%clk 0:05:03] } (7... Bg4 8. O-O Nxc3 9. bxc3 B@h3 10. f3 Bxg2 11. fxg4 Bxf1 12. Qxf1) 8. O-O { [%eval 3.0] [%clk 0:05:20] } 8... e6 { [%eval 2.65] [%clk 0:04:53] } 9. f3 { [%eval 2.62] [%clk 0:05:17] } 9... Nxc3 { [%eval 2.61] [%clk 0:05:01] } 10. bxc3 { [%eval 2.55] [%clk 0:05:32] } 10... h5?! { (2.55 → 3.85) Inaccuracy. B@a4 was best. } { [%eval 3.85] [%clk 0:04:50] } (10... B@a4 11. N@e3) 11. N@e3 { [%eval 3.49] [%clk 0:03:36] } 11... N@h4? { (3.49 → 5.60) Mistake. Bh7 was best. } { [%eval 5.6] [%clk 0:04:38] } (11... Bh7 12. Rb1 B@a4 13. Nf4 h4 14. Rb7 Be7 15. N@h5 Rg8 16. c4 dxc4 17. Nxc4 Baxc2 18. Qd2) 12. N@a5?? { (5.60 → 2.40) Blunder. Nxf5 was best. } { [%eval 2.4] [%clk 0:03:09] } (12. Nxf5 Nxf5 13. B@b7 B@a4 14. N@a5 N@e3 15. Bxe3 Nxe3 16. Qd2 Nxc2 17. Bxa8 Qxa8 18. Rab1 P@b4) 12... B@d7?? { (2.40 → 4.92) Blunder. Nxg2 was best. } { [%eval 4.92] [%clk 0:04:32] } (12... Nxg2 13. Nxg2 P@d7 14. Rb1 h4 15. Ngf4 B@g5 16. Rf2 Bfe7 17. Rg2 Kf8 18. Bd2 Kg8 19. N@h5) 13. Nxf5 { [%eval 3.65] [%clk 0:03:20] } 13... Nxf5 { [%eval 5.1] [%clk 0:04:39] } 14. B@b7 { [%eval 4.07] [%clk 0:03:33] } 14... N@e3 { [%eval 4.93] [%clk 0:04:26] } 15. Nxc6?? { (4.93 → 1.71) Blunder. Bxe3 was best. } { [%eval 1.71] [%clk 0:03:14] } (15. Bxe3 Nxe3) 15... Nxd1 { [%eval 3.35] [%clk 0:04:16] } 16. Nxd8 { [%eval 1.06] [%clk 0:03:26] } 16... Rxd8?? { (1.06 → 4.55) Blunder. Nxd4 was best. } { [%eval 4.55] [%clk 0:04:24] } (16... Nxd4 17. cxd4 P@f2+ 18. Rxf2 Nxf2 19. Kxf2 Rxd8 20. Bd2 Q@h4+ 21. Q@g3 Qxg3+ 22. hxg3 R@h2 23. N@f4) 17. Rxd1 { [%eval 4.69] [%clk 0:03:28] } 17... Q@b5 { [%eval 5.71] [%clk 0:04:13] } 18. Bxa6?? { (5.71 → 1.41) Blunder. P@g6 was best. } { [%eval 1.41] [%clk 0:03:21] } (18. P@g6 fxg6 19. Bxa6 Qxa6 20. P@f7+ Ke7 21. Nf4 Bb5 22. c4 Bxc4 23. Q@g5+ P@f6 24. exf6+ gxf6) 18... Qxa6 { [%eval 1.22] [%clk 0:04:03] } 19. P@d3?? { (1.22 → -2.85) Blunder. P@g6 was best. } { [%eval -2.85] [%clk 0:03:31] } (19. P@g6 B@g8) 19... N@e3?? { (-2.85 → 0.05) Blunder. h4 was best. } { [%eval 0.05] [%clk 0:03:55] } (19... h4) 20. Bxe3 { [%eval -0.32] [%clk 0:03:37] } 20... Nxe3 { [%eval 0.0] [%clk 0:04:01] } 21. P@d6?? { (0.00 → -2.41) Blunder. N@g6 was best. } { [%eval -2.41] [%clk 0:01:29] } (21. N@g6 Nxd1 22. Rxd1 B@e3+ 23. P@f2 fxg6 24. fxe3 B@h4 25. B@g3 Bxg3 26. hxg3 B@g5 27. B@f4 Bfe7) 21... Nxd1?? { (-2.41 → Mate in 3) Checkmate is now unavoidable. Bxd6 was best. } { [%eval #3] [%clk 0:03:55] } (21... Bxd6 22. exd6) 22. Rxd1?? { (Mate in 3 → -1.88) Lost forced checkmate sequence. N@f6+ was best. } { [%eval -1.88] [%clk 0:01:44] } (22. N@f6+ gxf6 23. N@g7+ Bxg7 24. Q@e7#) 22... B@e3+?? { (-1.88 → 0.00) Blunder. Bxd6 was best. } { [%eval 0.0] [%clk 0:04:02] } (22... Bxd6 23. exd6 Qxd6 24. B@e3 B@h4 25. N@e5 O-O 26. g3 B@g5 27. N@f4 Qxe5 28. dxe5 N@h3+ 29. Nxh3) 23. Kh1 { [%eval -0.42] [%clk 0:01:51] } 23... Bxd6 { [%eval 0.07] [%clk 0:03:55] } 24. exd6 { [%eval 0.55] [%clk 0:02:02] } 24... Qxd6 { [%eval 1.38] [%clk 0:04:01] } 25. B@b4?? { (1.38 → -8.92) Blunder. B@e5 was best. } { [%eval -8.92] [%clk 0:01:08] } (25. B@e5 B@e7) 25... Qxb4?? { (-8.92 → 0.53) Blunder. P@h3 was best. } { [%eval 0.53] [%clk 0:03:55] } (25... P@h3) 26. cxb4 { [%eval -0.77] [%clk 0:01:21] } 26... P@f2?? { (-0.77 → 4.95) Blunder. B@f6 was best. } { [%eval 4.95] [%clk 0:03:49] } (26... B@f6 27. Q@g3 B@d6 28. N@e5 P@h3 29. Q@f1 Bxd4 30. Nxf7 Kxf7 31. P@g6+ Kf8 32. Qxh3 N@f2+ 33. Qxf2) 27. Q@f1?? { (4.95 → -0.96) Blunder. N@g6 was best. } { [%eval -0.96] [%clk 0:00:19] } (27. N@g6) 27... R@g1+?? { (-0.96 → 8.81) Blunder. B@f6 was best. } { [%eval 8.81] [%clk 0:03:42] } (27... B@f6 28. N@h3 B@h4 29. N@e5 Bxe5 30. dxe5 N@f5 31. Nxf2 Bexf2 32. P@h7 Kf8 33. N@h3 Be3 34. B@g1) 28. Qxg1?? { (8.81 → 0.00) Blunder. Nxg1 was best. } { [%eval 0.0] [%clk 0:00:23] } (28. Nxg1) 28... fxg1=Q+ { [%eval 0.0] [%clk 0:03:49] } 29. Rxg1 { [%eval 0.07] [%clk 0:00:37] } 29... P@f2 { [%eval 0.58] [%clk 0:03:30] } 30. N@g6?! { (0.58 → 0.00) Inaccuracy. Rf1 was best. } { [%eval 0.0] [%clk 0:00:27] } (30. Rf1 B@d6) 30... fxg1=Q+?? { (0.00 → 6.00) Blunder. fxg6 was best. } { [%eval 6.0] [%clk 0:03:34] } (30... fxg6 31. P@f7+ Kf8 32. R@g8+ Ke7 33. f8=Q+ Rxf8 34. Rxg7+ P@f7 35. P@f6+ Kxf6 36. Q@e5+ Ke7 37. Qxe3) 31. Nxg1 { [%eval 4.02] [%clk 0:00:41] } 31... Q@e7? { (4.02 → 8.29) Mistake. B@e7 was best. } { [%eval 8.29] [%clk 0:03:18] } (31... B@e7 32. Q@f6) 32. Q@d6?? { (8.29 → -5.15) Blunder. Q@f6 was best. } { [%eval -5.15] [%clk 0:00:27] } (32. Q@f6 B@g5) 32... fxg6?? { (-5.15 → Mate in 6) Checkmate is now unavoidable. B@f6 was best. } { [%eval #6] [%clk 0:03:10] } (32... B@f6 33. Qxe7+ Bxe7 34. Q@e1 Bxg1 35. Qxg1 R@a1 36. B@e1 Q@e2 37. R@f1 fxg6 38. N@e5 N@f7 39. Nxf7) 33. Qxe7+ { [%eval #5] [%clk 0:00:40] } 33... Kxe7 { [%eval #5] [%clk 0:03:25] } 34. R@f7+ { [%eval #4] [%clk 0:00:54] } 34... Kxf7 { [%eval #4] [%clk 0:03:35] } 35. N@e5+ { [%eval #3] [%clk 0:01:08] } 35... Kg8 { [%eval #2] [%clk 0:03:30] } 36. N@f6+ { [%eval #1] [%clk 0:01:19] } 36... gxf6 { [%eval #1] [%clk 0:03:42] } 37. Q@f7# { [%clk 0:01:18] } { White wins by checkmate. } 1-0 + + diff --git a/Fairy-Stockfish-master/tests/pgn/pychess-variants_YHEWvfWF.pgn b/Fairy-Stockfish-master/tests/pgn/pychess-variants_YHEWvfWF.pgn new file mode 100644 index 0000000000000000000000000000000000000000..de98f855c64b0d81b60c13f46319c7e23a4c670c --- /dev/null +++ b/Fairy-Stockfish-master/tests/pgn/pychess-variants_YHEWvfWF.pgn @@ -0,0 +1,15 @@ +[Event "PyChess rated game"] +[Site "https://www.pychess.org/YHEWvfWF"] +[Date "2021.07.03"] +[Round "-"] +[White "TheFinnisher"] +[Black "SmartAlice"] +[Result "1-0"] +[TimeControl "180+2"] +[WhiteElo "1947?"] +[BlackElo "1592"] +[Variant "Seirawan960"] +[FEN "rnqkbnrb/pppppppp/8/8/8/8/PPPPPPPP/RNQKBNRB[HEhe] w HECBFQKhecbfqk - 0 1"] +[SetUp "1"] + +1. g3 g6 2. Nc3 Nc6 3. f4 e5 4. fxe5 Nxe5 5. d4 Nc4 6. e3 Ne6 7. Bf2 c6 8. Nd2 Nxd2 9. Qxd2/H d5 10. O-O/Ed1 Ng5 11. Bg2 Bd7 12. e4 h6 13. exd5 Bh3 14. Qxg5+ hxg5 15. Hxg5+ Kc7/E 16. dxc6 Bxg2 17. Kxg2 Bf6 18. Hf4+ Ed6 19. Nb5+ Kxc6 20. Nxd6 1-0 diff --git a/Fairy-Stockfish-master/tests/pgn/pychess-variants_zJxHRVm1.pgn b/Fairy-Stockfish-master/tests/pgn/pychess-variants_zJxHRVm1.pgn new file mode 100644 index 0000000000000000000000000000000000000000..8956e301d1f304fc6f862aefdf1de736200e3854 --- /dev/null +++ b/Fairy-Stockfish-master/tests/pgn/pychess-variants_zJxHRVm1.pgn @@ -0,0 +1,15 @@ +[Event "PyChess casual game"] +[Site "https://www.pychess.org/zJxHRVm1"] +[Date "2020.05.28"] +[Round "-"] +[White "catask"] +[Black "Fairy-Stockfish"] +[Result "1-0"] +[TimeControl "300+3"] +[WhiteElo "1500?"] +[BlackElo "1500?"] +[Variant "Seirawan"] +[FEN "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[HEhe] w KQBCDFGkqbcdfg - 0 1"] +[SetUp "1"] + +1. e4 d5 2. exd5 Qxd5/E 3. Nc3 Qf5 4. d4 Nf6 5. Nf3 Nc6 6. d5 Ne4 7. dxc6 Nc5 8. Qxd8/E# 1-0 diff --git a/Fairy-Stockfish-master/tests/protocol.sh b/Fairy-Stockfish-master/tests/protocol.sh new file mode 100644 index 0000000000000000000000000000000000000000..18c3a456fc054717f2e84cef39ed421f1625c6cf --- /dev/null +++ b/Fairy-Stockfish-master/tests/protocol.sh @@ -0,0 +1,85 @@ +#!/bin/bash +# verify protocol implementations + +error() +{ + echo "protocol testing failed on line $1" + exit 1 +} +trap 'error ${LINENO}' ERR + +echo "protocol testing started" + +cat << EOF > uci.exp + spawn ./stockfish + send "uci\\n" + expect "default chess" + expect "uciok" + send "quit\\n" + expect eof +EOF + +cat << EOF > ucci.exp + spawn ./stockfish + send "ucci\\n" + expect "option UCI_Variant" + expect "default xiangqi" + expect "ucciok" + send "quit\\n" + expect eof +EOF + +cat << EOF > usi.exp + spawn ./stockfish + send "usi\\n" + expect "default shogi" + expect "usiok" + send "quit\\n" + expect eof +EOF + +cat << EOF > ucicyclone.exp + spawn ./stockfish + send "uci\\n" + expect "uciok" + send "startpos\\n" + send "d\\n" + expect "rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1" + send "quit\\n" + expect eof +EOF + +cat << EOF > ucicyclone2.exp + spawn ./stockfish ucicyclone + send "uci\\n" + expect "uciok" + send "position startpos\\n" + send "d\\n" + expect "rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1" + send "quit\\n" + expect eof +EOF + +cat << EOF > xboard.exp + spawn ./stockfish load variants.ini + send "xboard\\n" + send "protover 2\\n" + expect "feature done=1" + send "ping\\n" + expect "pong" + send "ping\\n" + expect "pong" + send "variant 3check-crazyhouse\\n" + expect "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR\\\\\[] w KQkq - 3+3 0 1" + send "quit\\n" + expect eof +EOF + +for exp in uci.exp ucci.exp usi.exp ucicyclone.exp ucicyclone2.exp xboard.exp +do + echo "Testing $exp" + timeout 5 expect $exp > /dev/null + rm $exp +done + +echo "protocol testing OK" diff --git a/Fairy-Stockfish-master/tests/regression.sh b/Fairy-Stockfish-master/tests/regression.sh new file mode 100644 index 0000000000000000000000000000000000000000..e6b3f1c8dfbd140464322343ad68eadb2a5d6f5b --- /dev/null +++ b/Fairy-Stockfish-master/tests/regression.sh @@ -0,0 +1,47 @@ +#!/bin/bash +# regression test variant bench numbers +# arguments: ./old_engine ./new_engine "bench_args" [variant1 variant2 variant3 ...] +# examples: +# Pure bench: ./regression.sh ./old_engine ./new_engine "" chess xiangqi shogi +# Perft bench: ./regression.sh ./old_engine ./new_engine "16 1 5 default perft" chess xiangqi shogi +# Auto-detect common variants: ./regression.sh ./old_engine ./new_engine "" + +echo "variant $1 $2" +bench_args="$3" + +# If no variants provided, extract them from UCI output +if [ $# -eq 3 ]; then + echo "No variants specified, extracting from UCI output..." + + # Extract variants from first engine + variants1=$(echo "uci" | $1 2>/dev/null | grep "option name UCI_Variant" | grep -o 'var [^[:space:]]*' | sed 's/var //' | sort) + + # Extract variants from second engine + variants2=$(echo "uci" | $2 2>/dev/null | grep "option name UCI_Variant" | grep -o 'var [^[:space:]]*' | sed 's/var //' | sort) + + # Find intersection of variants (common to both engines) + variants=$(comm -12 <(echo "$variants1") <(echo "$variants2")) + + echo "Common variants found: $(echo $variants | wc -w)" + + # Convert to array for processing + variant_array=($variants) +else + # Use provided variants + variant_array=("${@:4}") +fi + +for var in "${variant_array[@]}" +do + ref=`$1 bench $var $bench_args 2>&1 | grep "Nodes searched : " | awk '{print $4}'` + signature=`$2 bench $var $bench_args 2>&1 | grep "Nodes searched : " | awk '{print $4}'` + if [ -z "$ref" ]; then + echo "${var} none ${signature} <-- no reference" + elif [ -z "$signature" ]; then + echo "${var} ${ref} none <-- no new" + elif [ "$ref" != "$signature" ]; then + echo "${var} ${ref} ${signature} <-- mismatch" + else + echo "${var} ${ref} OK" + fi +done diff --git a/Fairy-Stockfish-master/tests/reprosearch.sh b/Fairy-Stockfish-master/tests/reprosearch.sh new file mode 100644 index 0000000000000000000000000000000000000000..e16ba4aed91849fe7ce5f70f9cc6c811bc768b86 --- /dev/null +++ b/Fairy-Stockfish-master/tests/reprosearch.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# verify reproducible search + +error() +{ + echo "reprosearch testing failed on line $1" + exit 1 +} +trap 'error ${LINENO}' ERR + +echo "reprosearch testing started" + +# repeat two short games, separated by ucinewgame. +# with go nodes $nodes they should result in exactly +# the same node count for each iteration. +cat << EOF > repeat.exp + set timeout 10 + spawn ./stockfish + lassign \$argv nodes + + send "uci\n" + expect "uciok" + + send "ucinewgame\n" + send "position startpos\n" + send "go nodes \$nodes\n" + expect "bestmove" + + send "position startpos moves e2e4 e7e6\n" + send "go nodes \$nodes\n" + expect "bestmove" + + send "ucinewgame\n" + send "position startpos\n" + send "go nodes \$nodes\n" + expect "bestmove" + + send "position startpos moves e2e4 e7e6\n" + send "go nodes \$nodes\n" + expect "bestmove" + + send "quit\n" + expect eof +EOF + +# to increase the likelihood of finding a non-reproducible case, +# the allowed number of nodes are varied systematically +for i in `seq 1 20` +do + + nodes=$((100*3**i/2**i)) + echo "reprosearch testing with $nodes nodes" + + # each line should appear exactly an even number of times + expect repeat.exp $nodes 2>&1 | grep -o "nodes [0-9]*" | sort | uniq -c | awk '{if ($1%2!=0) exit(1)}' + +done + +rm repeat.exp + +echo "reprosearch testing OK" diff --git a/Fairy-Stockfish-master/tests/signature.sh b/Fairy-Stockfish-master/tests/signature.sh new file mode 100644 index 0000000000000000000000000000000000000000..2e5c183a073cc8094ffb5f06e2526e01999a4c6a --- /dev/null +++ b/Fairy-Stockfish-master/tests/signature.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# obtain and optionally verify Bench / signature +# if no reference is given, the output is deliberately limited to just the signature + +error() +{ + echo "running bench for signature failed on line $1" + exit 1 +} +trap 'error ${LINENO}' ERR + +# obtain + +signature=`./stockfish bench 2>&1 | grep "Nodes searched : " | awk '{print $4}'` + +if [ $# -gt 0 ]; then + # compare to given reference + if [ "$1" != "$signature" ]; then + if [ -z "$signature" ]; then + echo "No signature obtained from bench. Code crashed or assert triggered ?" + else + echo "signature mismatch: reference $1 obtained: $signature ." + fi + exit 1 + else + echo "signature OK: $signature" + fi +else + # just report signature + echo $signature +fi