--- /dev/null
+AccessModifierOffset: -1
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: Consecutive
+AlignConsecutiveDeclarations: Consecutive
+AlignEscapedNewlines: DontAlign
+AlignOperands: AlignAfterOperator
+AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortEnumsOnASingleLine: false
+AllowShortIfStatementsOnASingleLine: false
+AlwaysBreakTemplateDeclarations: Yes
+BasedOnStyle: WebKit
+BitFieldColonSpacing: After
+BinPackParameters: false
+BreakBeforeBinaryOperators: NonAssignment
+BreakBeforeBraces: Custom
+BraceWrapping:
+ AfterFunction: false
+ AfterClass: false
+ AfterControlStatement: true
+ BeforeElse: true
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializers: AfterColon
+BreakStringLiterals: false
+ColumnLimit: 100
+ContinuationIndentWidth: 2
+Cpp11BracedListStyle: true
+IndentGotoLabels: false
+IndentPPDirectives: BeforeHash
+IndentWidth: 4
+MaxEmptyLinesToKeep: 2
+NamespaceIndentation: None
+PackConstructorInitializers: Never
+ReflowComments: false
+SortIncludes: false
+SortUsingDeclarations: false
+SpaceAfterCStyleCast: true
+SpaceAfterTemplateKeyword: false
+SpaceBeforeCaseColon: true
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeInheritanceColon: false
+SpaceInEmptyBlock: false
+SpacesBeforeTrailingComments: 2
--- /dev/null
+name: Report issue
+description: Create a report to help us fix issues with the engine
+body:
+- type: textarea
+ attributes:
+ label: Describe the issue
+ description: A clear and concise description of what you're experiencing.
+ validations:
+ required: true
+
+- type: textarea
+ attributes:
+ label: Expected behavior
+ description: A clear and concise description of what you expected to happen.
+ validations:
+ required: true
+
+- type: textarea
+ attributes:
+ label: Steps to reproduce
+ description: |
+ Steps to reproduce the behavior.
+ You can also use this section to paste the command line output.
+ placeholder: |
+ ```
+ position startpos moves g2g4 e7e5 f2f3
+ go mate 1
+ info string NNUE evaluation using nn-6877cd24400e.nnue enabled
+ info depth 1 seldepth 1 multipv 1 score mate 1 nodes 33 nps 11000 tbhits 0 time 3 pv d8h4
+ bestmove d8h4
+ ```
+ validations:
+ required: true
+
+- type: textarea
+ attributes:
+ label: Anything else?
+ description: |
+ Anything that will give us more context about the issue you are encountering.
+ You can also use this section to propose ideas on how to solve the issue.
+ validations:
+ required: false
+
+- type: dropdown
+ attributes:
+ label: Operating system
+ options:
+ - All
+ - Windows
+ - Linux
+ - MacOS
+ - Android
+ - Other or N/A
+ validations:
+ required: true
+
+- type: input
+ attributes:
+ label: Stockfish version
+ description: |
+ This can be found by running the engine.
+ You can also use the commit ID.
+ placeholder: Stockfish 15 / e6e324e
+ validations:
+ required: true
--- /dev/null
+blank_issues_enabled: false
+contact_links:
+ - name: Discord server
+ url: https://discord.gg/GWDRS3kU6R
+ about: Feel free to ask for support or have a chat with us in our Discord server!
+ - name: Discussions, Q&A, ideas, show us something...
+ url: https://github.com/official-stockfish/Stockfish/discussions/new
+ about: Do you have an idea for Stockfish? Do you want to show something that you made? Please open a discussion about it!
--- /dev/null
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ 'master' ]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [ 'master' ]
+ schedule:
+ - cron: '17 18 * * 1'
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ 'cpp' ]
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
+ # Use only 'java' to analyze code written in Java, Kotlin or both
+ # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
+ # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v2
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+
+ # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
+ # queries: security-extended,security-and-quality
+
+ - name: Build
+ working-directory: src
+ run: make -j build ARCH=x86-64-modern
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v2
+ with:
+ category: "/language:${{matrix.language}}"
--- /dev/null
+[
+ # Mappings for libcxx's internal headers
+ { include: [ "<__fwd/fstream.h>", private, "<iosfwd>", public ] },
+ { include: [ "<__fwd/ios.h>", private, "<iosfwd>", public ] },
+ { include: [ "<__fwd/istream.h>", private, "<iosfwd>", public ] },
+ { include: [ "<__fwd/ostream.h>", private, "<iosfwd>", public ] },
+ { include: [ "<__fwd/sstream.h>", private, "<iosfwd>", public ] },
+ { include: [ "<__fwd/streambuf.h>", private, "<iosfwd>", public ] },
+ { include: [ "<__fwd/string_view.h>", private, "<string_view>", public ] },
+
+ # Mappings for includes between public headers
+ { include: [ "<ios>", public, "<iostream>", public ] },
+ { include: [ "<streambuf>", public, "<iostream>", public ] },
+ { include: [ "<istream>", public, "<iostream>", public ] },
+ { include: [ "<ostream>", public, "<iostream>", public ] },
+ { include: [ "<iosfwd>", public, "<iostream>", public ] },
+
+ # Missing mappings in include-what-you-use's libcxx.imp
+ { include: ["@<__condition_variable/.*>", private, "<condition_variable>", public ] },
+ { include: ["@<__mutex/.*>", private, "<mutex>", public ] },
+]
--- /dev/null
+name: Stockfish
+on:
+ push:
+ tags:
+ - '*'
+ branches:
+ - master
+ - tools
+ - github_ci
+ pull_request:
+ branches:
+ - master
+ - tools
+jobs:
+ Prerelease:
+ if: github.ref == 'refs/heads/master'
+ runs-on: ubuntu-latest
+ steps:
+ # returns null if no pre-release exists
+ - name: Get Commit SHA of Latest Pre-release
+ run: |
+ # Install required packages
+ sudo apt-get update
+ sudo apt-get install -y curl jq
+
+ echo "COMMIT_SHA=$(jq -r 'map(select(.prerelease)) | first | .tag_name' <<< $(curl -s https://api.github.com/repos/${{ github.repository_owner }}/Stockfish/releases))" >> $GITHUB_ENV
+
+ # delete old previous pre-release and tag
+ - uses: dev-drprasad/delete-tag-and-release@8cd619d00037e4aeb781909c9a6b03940507d0da # @v1.0.1
+ if: env.COMMIT_SHA != 'null'
+ with:
+ tag_name: ${{ env.COMMIT_SHA }}
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+
+ Analyzers:
+ uses: ./.github/workflows/stockfish_analyzers.yml
+ Sanitizers:
+ uses: ./.github/workflows/stockfish_sanitizers.yml
+ Tests:
+ uses: ./.github/workflows/stockfish_test.yml
+ Compiles:
+ uses: ./.github/workflows/stockfish_compile_test.yml
+ Binaries:
+ if: github.ref == 'refs/heads/master' || (startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag')
+ uses: ./.github/workflows/stockfish_binaries.yml
+ ARM_Binaries:
+ if: github.ref == 'refs/heads/master' || (startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag')
+ uses: ./.github/workflows/stockfish_arm_binaries.yml
--- /dev/null
+name: Stockfish
+on:
+ workflow_call:
+jobs:
+ Analyzers:
+ name: Check includes
+ runs-on: ubuntu-22.04
+ defaults:
+ run:
+ working-directory: Stockfish/src
+ shell: bash
+ steps:
+ - name: Checkout Stockfish
+ uses: actions/checkout@v4
+ with:
+ path: Stockfish
+
+ - name: Checkout include-what-you-use
+ uses: actions/checkout@v4
+ with:
+ repository: include-what-you-use/include-what-you-use
+ ref: f25caa280dc3277c4086ec345ad279a2463fea0f
+ path: include-what-you-use
+
+ - name: Download required linux packages
+ run: |
+ sudo add-apt-repository 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main'
+ wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
+ sudo apt update
+ sudo apt install -y libclang-17-dev clang-17 libc++-17-dev
+
+ - name: Set up include-what-you-use
+ run: |
+ mkdir build && cd build
+ cmake -G "Unix Makefiles" -DCMAKE_PREFIX_PATH="/usr/lib/llvm-17" ..
+ sudo make install
+ working-directory: include-what-you-use
+
+ - name: Check include-what-you-use
+ run: include-what-you-use --version
+
+ - name: Check includes
+ run: >
+ make analyze
+ COMP=clang
+ CXX=include-what-you-use
+ CXXFLAGS="-stdlib=libc++ -Xiwyu --comment_style=long -Xiwyu --mapping='${{ github.workspace }}/Stockfish/.github/workflows/libcxx17.imp' -Xiwyu --error"
--- /dev/null
+name: Stockfish
+on:
+ workflow_call:
+jobs:
+ Stockfish:
+ name: ${{ matrix.config.name }} ${{ matrix.binaries }}
+ runs-on: ${{ matrix.config.os }}
+ env:
+ COMPILER: ${{ matrix.config.compiler }}
+ COMP: ${{ matrix.config.comp }}
+ EMU: ${{ matrix.config.emu }}
+ EXT: ${{ matrix.config.ext }}
+ OS: ${{ matrix.config.os }}
+ BINARY: ${{ matrix.binaries }}
+ strategy:
+ matrix:
+ config:
+ - name: Android NDK aarch64
+ os: ubuntu-22.04
+ compiler: aarch64-linux-android21-clang++
+ emu: qemu-aarch64
+ comp: ndk
+ shell: bash
+ - name: Android NDK arm
+ os: ubuntu-22.04
+ compiler: armv7a-linux-androideabi21-clang++
+ emu: qemu-arm
+ comp: ndk
+ shell: bash
+ binaries:
+ - armv8-dotprod
+ - armv8
+ - armv7
+ - armv7-neon
+ exclude:
+ - binaries: armv8-dotprod
+ config: {compiler: armv7a-linux-androideabi21-clang++}
+ - binaries: armv8
+ config: {compiler: armv7a-linux-androideabi21-clang++}
+ - binaries: armv7
+ config: {compiler: aarch64-linux-android21-clang++}
+ - binaries: armv7-neon
+ config: {compiler: aarch64-linux-android21-clang++}
+ defaults:
+ run:
+ working-directory: src
+ shell: ${{ matrix.config.shell }}
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Download required linux packages
+ if: runner.os == 'Linux'
+ run: |
+ sudo apt update
+ sudo apt install qemu-user
+
+ - name: Install NDK
+ if: runner.os == 'Linux'
+ run: |
+ if [ $COMP == ndk ]; then
+ NDKV="21.4.7075529"
+ ANDROID_ROOT=/usr/local/lib/android
+ ANDROID_SDK_ROOT=$ANDROID_ROOT/sdk
+ SDKMANAGER=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager
+ echo "y" | $SDKMANAGER "ndk;$NDKV"
+ ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/$NDKV
+ ANDROID_NDK_BIN=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin
+ echo "ANDROID_NDK_BIN=$ANDROID_NDK_BIN" >> $GITHUB_ENV
+ fi
+
+ - name: Extract the bench number from the commit history
+ run: |
+ for hash in $(git rev-list -100 HEAD); do
+ benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true
+ done
+ [[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $hash" && echo "Reference bench: $benchref" || echo "No bench found"
+
+ - name: Download the used network from the fishtest framework
+ run: make net
+
+ - name: Check compiler
+ run: |
+ if [ $COMP == ndk ]; then
+ export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH
+ fi
+ $COMPILER -v
+
+ - name: Test help target
+ run: make help
+
+ - name: Check git
+ run: git --version
+
+ # Compile profile guided builds
+
+ - name: Compile ${{ matrix.binaries }} build
+ run: |
+ if [ $COMP == ndk ]; then
+ export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH
+ export LDFLAGS="-static -Wno-unused-command-line-argument"
+ fi
+ make clean
+ make -j2 profile-build ARCH=$BINARY COMP=$COMP WINE_PATH=$EMU
+ make strip ARCH=$BINARY COMP=$COMP
+ WINE_PATH=$EMU ../tests/signature.sh $benchref
+ mv ./stockfish$EXT ../stockfish-android-$BINARY$EXT
+
+ - name: Remove non src files
+ run: rm -f *.o .depend *.nnue
+
+ - name: Download wiki
+ run: |
+ git clone https://github.com/official-stockfish/Stockfish.wiki.git ../wiki
+ cd ../wiki
+ rm -rf .git
+
+ - name: Create tar archive.
+ run: |
+ cd ..
+ mkdir stockfish
+ cp -r wiki stockfish/
+ cp -r src stockfish/
+ cp stockfish-android-$BINARY$EXT stockfish/
+ cp "Top CPU Contributors.txt" stockfish/
+ cp Copying.txt stockfish/
+ cp AUTHORS stockfish/
+ cp CITATION.cff stockfish/
+ cp README.md stockfish/
+ cp CONTRIBUTING.md stockfish/
+ tar -cvf stockfish-android-$BINARY.tar stockfish
+
+ - name: Upload binaries
+ uses: actions/upload-artifact@v3
+ with:
+ name: stockfish-android-${{ matrix.binaries }}
+ path: stockfish-android-${{ matrix.binaries }}.tar
+
+ - name: Release
+ if: startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag'
+ uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1
+ with:
+ files: stockfish-android-${{ matrix.binaries }}.tar
+
+ - name: Get last commit sha
+ id: last_commit
+ run: echo "COMMIT_SHA=$(git rev-parse HEAD | cut -c 1-8)" >> $GITHUB_ENV
+
+ - name: Get commit date
+ id: commit_date
+ run: echo "COMMIT_DATE=$(git show -s --date=format:'%Y%m%d' --format=%cd HEAD)" >> $GITHUB_ENV
+
+ # Make sure that an old ci which still runs on master doesn't recreate a prerelease
+ - name: Check Pullable Commits
+ id: check_commits
+ run: |
+ git fetch
+ CHANGES=$(git rev-list HEAD..origin/master --count)
+ echo "CHANGES=$CHANGES" >> $GITHUB_ENV
+
+ - name: Prerelease
+ if: github.ref_name == 'master' && env.CHANGES == '0'
+ continue-on-error: true
+ uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1
+ with:
+ name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }}
+ tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }}
+ prerelease: true
+ files: stockfish-android-${{ matrix.binaries }}.tar
--- /dev/null
+name: Stockfish
+on:
+ workflow_call:
+jobs:
+ Stockfish:
+ name: ${{ matrix.config.name }} ${{ matrix.binaries }}
+ runs-on: ${{ matrix.config.os }}
+ env:
+ COMPILER: ${{ matrix.config.compiler }}
+ COMP: ${{ matrix.config.comp }}
+ EXT: ${{ matrix.config.ext }}
+ SDE: ${{ matrix.config.sde }}
+ NAME: ${{ matrix.config.simple_name }}
+ BINARY: ${{ matrix.binaries }}
+ strategy:
+ fail-fast: false
+ matrix:
+ config:
+ - name: Ubuntu 20.04 GCC
+ os: ubuntu-20.04
+ simple_name: ubuntu
+ compiler: g++
+ comp: gcc
+ shell: bash
+ archive_ext: tar
+ sde: /home/runner/work/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.27.0-2023-09-13-lin/sde -future --
+ - name: MacOS 13 Apple Clang
+ os: macos-13
+ simple_name: macos
+ compiler: clang++
+ comp: clang
+ shell: bash
+ archive_ext: tar
+ - name: Windows 2022 Mingw-w64 GCC x86_64
+ os: windows-2022
+ simple_name: windows
+ compiler: g++
+ comp: mingw
+ msys_sys: mingw64
+ msys_env: x86_64-gcc
+ shell: msys2 {0}
+ ext: .exe
+ sde: /d/a/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.27.0-2023-09-13-win/sde.exe -future --
+ archive_ext: zip
+ binaries:
+ - x86-64
+ - x86-64-sse41-popcnt
+ - x86-64-avx2
+ - x86-64-bmi2
+ - x86-64-avxvnni
+ - x86-64-avx512
+ - x86-64-vnni256
+ - x86-64-vnni512
+ exclude:
+ - binaries: x86-64-avxvnni
+ config: { ubuntu-20.04 }
+ - binaries: x86-64-avxvnni
+ config: { os: macos-13 }
+ - binaries: x86-64-avx512
+ config: { os: macos-13 }
+ - binaries: x86-64-vnni256
+ config: { os: macos-13 }
+ - binaries: x86-64-vnni512
+ config: { os: macos-13 }
+ defaults:
+ run:
+ working-directory: src
+ shell: ${{ matrix.config.shell }}
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Download required macOS packages
+ if: runner.os == 'macOS'
+ run: brew install coreutils
+
+ - name: Install fixed GCC on Linux
+ if: runner.os == 'Linux'
+ uses: egor-tensin/setup-gcc@eaa888eb19115a521fa72b65cd94fe1f25bbcaac # @v1.3
+ with:
+ version: 11
+
+ - name: Setup msys and install required packages
+ if: runner.os == 'Windows'
+ uses: msys2/setup-msys2@v2
+ with:
+ msystem: ${{ matrix.config.msys_sys }}
+ install: mingw-w64-${{ matrix.config.msys_env }} make git zip
+
+ - name: Download SDE package
+ if: runner.os == 'Linux' || runner.os == 'Windows'
+ uses: petarpetrovt/setup-sde@91a1a03434384e064706634125a15f7446d2aafb # @v2.3
+ with:
+ environmentVariableName: SDE_DIR
+ sdeVersion: 9.27.0
+
+ - name: Download the used network from the fishtest framework
+ run: make net
+
+ - name: Extract the bench number from the commit history
+ run: |
+ for hash in $(git rev-list -100 HEAD); do
+ benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true
+ done
+ [[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $hash" && echo "Reference bench: $benchref" || echo "No bench found"
+
+ - name: Check compiler
+ run: $COMPILER -v
+
+ - name: Show g++ cpu info
+ if: runner.os != 'macOS'
+ run: g++ -Q -march=native --help=target
+
+ - name: Show clang++ cpu info
+ if: runner.os == 'macOS'
+ run: clang++ -E - -march=native -###
+
+ - name: Test help target
+ run: make help
+
+ - name: Check git
+ run: git --version
+
+ # Compile profile guided builds
+
+ - name: Compile ${{ matrix.binaries }} build
+ run: |
+ make -j2 profile-build ARCH=$BINARY COMP=$COMP WINE_PATH="$SDE"
+ make strip ARCH=$BINARY COMP=$COMP
+ WINE_PATH="$SDE" ../tests/signature.sh $benchref
+ mv ./stockfish$EXT ../stockfish-$NAME-$BINARY$EXT
+
+ - name: Remove non src files
+ run: git clean -fx
+
+ - name: Download wiki
+ run: |
+ git clone https://github.com/official-stockfish/Stockfish.wiki.git ../wiki
+ rm -rf ../wiki/.git
+
+ - name: Create directory.
+ run: |
+ cd ..
+ mkdir stockfish
+ cp -r wiki stockfish/
+ cp -r src stockfish/
+ cp stockfish-$NAME-$BINARY$EXT stockfish/
+ cp "Top CPU Contributors.txt" stockfish/
+ cp Copying.txt stockfish/
+ cp AUTHORS stockfish/
+ cp CITATION.cff stockfish/
+ cp README.md stockfish/
+ cp CONTRIBUTING.md stockfish/
+
+ - name: Create tar
+ if: runner.os != 'Windows'
+ run: |
+ cd ..
+ tar -cvf stockfish-$NAME-$BINARY.tar stockfish
+
+ - name: Create zip
+ if: runner.os == 'Windows'
+ run: |
+ cd ..
+ zip -r stockfish-$NAME-$BINARY.zip stockfish
+
+ - name: Upload binaries
+ if: runner.os != 'Windows'
+ uses: actions/upload-artifact@v3
+ with:
+ name: stockfish-${{ matrix.config.os }}-${{ matrix.binaries }}
+ path: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.tar
+
+ # Artifacts automatically get zipped
+ # to avoid double zipping, we use the unzipped directory
+ - name: Upload binaries
+ if: runner.os == 'Windows'
+ uses: actions/upload-artifact@v3
+ with:
+ name: stockfish-${{ matrix.config.os }}-${{ matrix.binaries }}
+ path: stockfish
+
+ - name: Release
+ if: startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag'
+ uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1
+ with:
+ files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.${{ matrix.config.archive_ext }}
+
+ - name: Get last commit sha
+ id: last_commit
+ run: echo "COMMIT_SHA=$(git rev-parse HEAD | cut -c 1-8)" >> $GITHUB_ENV
+
+ - name: Get commit date
+ id: commit_date
+ run: echo "COMMIT_DATE=$(git show -s --date=format:'%Y%m%d' --format=%cd HEAD)" >> $GITHUB_ENV
+
+ # Make sure that an old ci which still runs on master doesn't recreate a prerelease
+ - name: Check Pullable Commits
+ id: check_commits
+ run: |
+ git fetch
+ CHANGES=$(git rev-list HEAD..origin/master --count)
+ echo "CHANGES=$CHANGES" >> $GITHUB_ENV
+
+ - name: Prerelease
+ if: github.ref_name == 'master' && env.CHANGES == '0'
+ continue-on-error: true
+ uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # @v1
+ with:
+ name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }}
+ tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }}
+ prerelease: true
+ files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.${{ matrix.config.archive_ext }}
--- /dev/null
+name: Stockfish
+on:
+ workflow_call:
+jobs:
+ Stockfish:
+ name: ${{ matrix.config.name }}
+ runs-on: ${{ matrix.config.os }}
+ env:
+ COMPILER: ${{ matrix.config.compiler }}
+ COMP: ${{ matrix.config.comp }}
+ strategy:
+ matrix:
+ config:
+ - name: Ubuntu 20.04 GCC
+ os: ubuntu-20.04
+ compiler: g++
+ comp: gcc
+ shell: bash
+ - name: Ubuntu 20.04 Clang
+ os: ubuntu-20.04
+ compiler: clang++
+ comp: clang
+ shell: bash
+ - name: MacOS 13 Apple Clang
+ os: macos-13
+ compiler: clang++
+ comp: clang
+ shell: bash
+ - name: MacOS 13 GCC 11
+ os: macos-13
+ compiler: g++-11
+ comp: gcc
+ shell: bash
+ - name: Windows 2022 Mingw-w64 GCC x86_64
+ os: windows-2022
+ compiler: g++
+ comp: mingw
+ msys_sys: mingw64
+ msys_env: x86_64-gcc
+ shell: msys2 {0}
+ - name: Windows 2022 Mingw-w64 Clang x86_64
+ os: windows-2022
+ compiler: clang++
+ comp: clang
+ msys_sys: clang64
+ msys_env: clang-x86_64-clang
+ shell: msys2 {0}
+
+ defaults:
+ run:
+ working-directory: src
+ shell: ${{ matrix.config.shell }}
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup msys and install required packages
+ if: runner.os == 'Windows'
+ uses: msys2/setup-msys2@v2
+ with:
+ msystem: ${{matrix.config.msys_sys}}
+ install: mingw-w64-${{matrix.config.msys_env}} make git
+
+ - name: Download the used network from the fishtest framework
+ run: make net
+
+ - name: Check compiler
+ run: $COMPILER -v
+
+ - name: Test help target
+ run: make help
+
+ - name: Check git
+ run: git --version
+
+ # 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
--- /dev/null
+# This workflow will run clang-format and comment on the PR.
+# Because of security reasons, it is crucial that this workflow
+# executes no shell script nor runs make.
+# Read this before editing: https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
+
+name: Stockfish
+on:
+ pull_request_target:
+ branches:
+ - 'master'
+ paths:
+ - '**.cpp'
+ - '**.h'
+jobs:
+ Stockfish:
+ name: clang-format check
+ runs-on: ubuntu-20.04
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.pull_request.head.sha }}
+
+ - name: Run clang-format style check
+ uses: jidicula/clang-format-action@f62da5e3d3a2d88ff364771d9d938773a618ab5e # @v4.11.0
+ id: clang-format
+ continue-on-error: true
+ with:
+ clang-format-version: '17'
+ exclude-regex: 'incbin'
+
+ - name: Comment on PR
+ if: steps.clang-format.outcome == 'failure'
+ uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308 # @v2.4.3
+ with:
+ message: |
+ clang-format 17 needs to be run on this PR.
+ If you do not have clang-format installed, the maintainer will run it when merging.
+ For the exact version please see https://packages.ubuntu.com/mantic/clang-format-17.
+
+ _(execution **${{ github.run_id }}** / attempt **${{ github.run_attempt }}**)_
+ comment_tag: execution
+
+ - name: Comment on PR
+ if: steps.clang-format.outcome != 'failure'
+ uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308 # @v2.4.3
+ with:
+ message: |
+ _(execution **${{ github.run_id }}** / attempt **${{ github.run_attempt }}**)_
+ create_if_not_exists: false
+ comment_tag: execution
+ mode: delete
--- /dev/null
+name: Stockfish
+on:
+ workflow_call:
+jobs:
+ Stockfish:
+ name: ${{ matrix.sanitizers.name }}
+ runs-on: ${{ matrix.config.os }}
+ env:
+ COMPILER: ${{ matrix.config.compiler }}
+ COMP: ${{ matrix.config.comp }}
+ CXXFLAGS: "-Werror"
+ strategy:
+ matrix:
+ config:
+ - name: Ubuntu 22.04 GCC
+ os: ubuntu-22.04
+ compiler: g++
+ comp: gcc
+ shell: bash
+ sanitizers:
+ - name: Run with thread sanitizer
+ make_option: sanitize=thread
+ instrumented_option: sanitizer-thread
+ - name: Run with UB sanitizer
+ make_option: sanitize=undefined
+ instrumented_option: sanitizer-undefined
+ - name: Run under valgrind
+ make_option: ""
+ instrumented_option: valgrind
+ - name: Run under valgrind-thread
+ make_option: ""
+ instrumented_option: valgrind-thread
+ defaults:
+ run:
+ working-directory: src
+ shell: ${{ matrix.config.shell }}
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Download required linux 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: Check compiler
+ run: $COMPILER -v
+
+ - name: Test help target
+ run: make help
+
+ - name: Check git
+ run: git --version
+
+ # Sanitizers
+
+ - name: ${{ matrix.sanitizers.name }}
+ run: |
+ export CXXFLAGS="-O1 -fno-inline"
+ make clean
+ make -j2 ARCH=x86-64-sse41-popcnt ${{ matrix.sanitizers.make_option }} debug=yes optimize=no build > /dev/null
+ ../tests/instrumented.sh --${{ matrix.sanitizers.instrumented_option }}
--- /dev/null
+name: Stockfish
+on:
+ workflow_call:
+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 20.04 GCC
+ os: ubuntu-20.04
+ compiler: g++
+ comp: gcc
+ run_32bit_tests: true
+ run_64bit_tests: true
+ shell: bash
+ - name: Ubuntu 20.04 Clang
+ os: ubuntu-20.04
+ compiler: clang++
+ comp: clang
+ run_32bit_tests: true
+ run_64bit_tests: true
+ shell: bash
+ - name: Android NDK aarch64
+ os: ubuntu-22.04
+ compiler: aarch64-linux-android21-clang++
+ comp: ndk
+ run_armv8_tests: true
+ shell: bash
+ - name: Android NDK arm
+ os: ubuntu-22.04
+ compiler: armv7a-linux-androideabi21-clang++
+ comp: ndk
+ run_armv7_tests: true
+ shell: bash
+ - name: Linux GCC riscv64
+ os: ubuntu-22.04
+ compiler: g++
+ comp: gcc
+ run_riscv64_tests: true
+ base_image: 'riscv64/alpine:edge'
+ platform: linux/riscv64
+ shell: bash
+ - name: Linux GCC ppc64
+ os: ubuntu-22.04
+ compiler: g++
+ comp: gcc
+ run_ppc64_tests: true
+ base_image: 'ppc64le/alpine:latest'
+ platform: linux/ppc64le
+ shell: bash
+ - name: MacOS 13 Apple Clang
+ os: macos-13
+ compiler: clang++
+ comp: clang
+ run_64bit_tests: true
+ shell: bash
+ - name: MacOS 13 GCC 11
+ os: macos-13
+ compiler: g++-11
+ comp: gcc
+ run_64bit_tests: true
+ shell: bash
+ - name: Windows 2022 Mingw-w64 GCC x86_64
+ os: windows-2022
+ compiler: g++
+ comp: mingw
+ run_64bit_tests: true
+ msys_sys: mingw64
+ msys_env: x86_64-gcc
+ shell: msys2 {0}
+ - name: Windows 2022 Mingw-w64 GCC i686
+ os: windows-2022
+ compiler: g++
+ comp: mingw
+ run_32bit_tests: true
+ msys_sys: mingw32
+ msys_env: i686-gcc
+ shell: msys2 {0}
+ - name: Windows 2022 Mingw-w64 Clang x86_64
+ os: windows-2022
+ compiler: clang++
+ comp: clang
+ run_64bit_tests: true
+ msys_sys: clang64
+ msys_env: clang-x86_64-clang
+ shell: msys2 {0}
+ defaults:
+ run:
+ working-directory: src
+ shell: ${{ matrix.config.shell }}
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Download required linux packages
+ if: runner.os == 'Linux'
+ run: |
+ sudo apt update
+ sudo apt install expect valgrind g++-multilib qemu-user-static
+
+ - name: Install NDK
+ if: runner.os == 'Linux'
+ run: |
+ if [ $COMP == ndk ]; then
+ NDKV="21.4.7075529"
+ ANDROID_ROOT=/usr/local/lib/android
+ ANDROID_SDK_ROOT=$ANDROID_ROOT/sdk
+ SDKMANAGER=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager
+ echo "y" | $SDKMANAGER "ndk;$NDKV"
+ ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/$NDKV
+ ANDROID_NDK_BIN=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin
+ echo "ANDROID_NDK_BIN=$ANDROID_NDK_BIN" >> $GITHUB_ENV
+ fi
+
+ - name: Set up QEMU
+ if: matrix.config.base_image
+ uses: docker/setup-qemu-action@v3
+
+ - name: Set up Docker Buildx
+ if: matrix.config.base_image
+ uses: docker/setup-buildx-action@v3
+
+ - name: Build Docker container
+ if: matrix.config.base_image
+ run: |
+ docker buildx build --load -t sf_builder - << EOF
+ FROM ${{ matrix.config.base_image }}
+ WORKDIR /app
+ RUN apk update && apk add make g++
+ CMD ["sh", "script.sh"]
+ EOF
+
+ - name: Download required macOS packages
+ if: runner.os == 'macOS'
+ run: brew install coreutils
+
+ - name: Setup msys and install required packages
+ if: runner.os == 'Windows'
+ uses: msys2/setup-msys2@v2
+ with:
+ msystem: ${{ matrix.config.msys_sys }}
+ install: mingw-w64-${{ matrix.config.msys_env }} make git expect
+
+ - name: Download the used network from the fishtest framework
+ run: make net
+
+ - name: Extract the bench number from the commit history
+ run: |
+ for hash in $(git rev-list -100 HEAD); do
+ benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true
+ done
+ [[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $hash" && echo "Reference bench: $benchref" || echo "No bench found"
+
+ - name: Check compiler
+ run: |
+ if [ -z "${{ matrix.config.base_image }}" ]; then
+ if [ $COMP == ndk ]; then
+ export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH
+ fi
+ $COMPILER -v
+ else
+ echo "$COMPILER -v" > script.sh
+ docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}/src:/app sf_builder
+ fi
+
+ - name: Test help target
+ run: make help
+
+ - name: Check git
+ run: git --version
+
+ # x86-32 tests
+
+ - name: Test debug x86-32 build
+ if: matrix.config.run_32bit_tests
+ 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 x86-32 build
+ if: matrix.config.run_32bit_tests
+ run: |
+ make clean
+ make -j2 ARCH=x86-32 build
+ ../tests/signature.sh $benchref
+
+ - name: Test x86-32-sse41-popcnt build
+ if: matrix.config.run_32bit_tests
+ run: |
+ make clean
+ make -j2 ARCH=x86-32-sse41-popcnt build
+ ../tests/signature.sh $benchref
+
+ - name: Test x86-32-sse2 build
+ if: matrix.config.run_32bit_tests
+ run: |
+ make clean
+ make -j2 ARCH=x86-32-sse2 build
+ ../tests/signature.sh $benchref
+
+ - name: Test general-32 build
+ if: matrix.config.run_32bit_tests
+ run: |
+ make clean
+ make -j2 ARCH=general-32 build
+ ../tests/signature.sh $benchref
+
+ # x86-64 tests
+
+ - name: Test debug x86-64-avx2 build
+ if: matrix.config.run_64bit_tests
+ run: |
+ export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG"
+ make clean
+ make -j2 ARCH=x86-64-avx2 optimize=no debug=yes build
+ ../tests/signature.sh $benchref
+
+ - name: Test x86-64-bmi2 build
+ if: matrix.config.run_64bit_tests
+ run: |
+ make clean
+ make -j2 ARCH=x86-64-bmi2 build
+ ../tests/signature.sh $benchref
+
+ - name: Test x86-64-avx2 build
+ if: matrix.config.run_64bit_tests
+ run: |
+ make clean
+ make -j2 ARCH=x86-64-avx2 build
+ ../tests/signature.sh $benchref
+
+ # Test a deprecated arch
+ - name: Test x86-64-modern build
+ if: matrix.config.run_64bit_tests
+ run: |
+ make clean
+ make -j2 ARCH=x86-64-modern build
+ ../tests/signature.sh $benchref
+
+ - name: Test x86-64-sse41-popcnt build
+ if: matrix.config.run_64bit_tests
+ run: |
+ make clean
+ make -j2 ARCH=x86-64-sse41-popcnt build
+ ../tests/signature.sh $benchref
+
+ - name: Test x86-64-ssse3 build
+ if: matrix.config.run_64bit_tests
+ run: |
+ make clean
+ make -j2 ARCH=x86-64-ssse3 build
+ ../tests/signature.sh $benchref
+
+ - name: Test x86-64-sse3-popcnt build
+ if: matrix.config.run_64bit_tests
+ run: |
+ make clean
+ make -j2 ARCH=x86-64-sse3-popcnt build
+ ../tests/signature.sh $benchref
+
+ - name: Test x86-64 build
+ if: matrix.config.run_64bit_tests
+ run: |
+ make clean
+ make -j2 ARCH=x86-64 build
+ ../tests/signature.sh $benchref
+
+ - name: Test general-64 build
+ if: matrix.config.run_64bit_tests
+ run: |
+ make clean
+ make -j2 ARCH=general-64 build
+ ../tests/signature.sh $benchref
+
+ # armv8 tests
+
+ - name: Test armv8 build
+ if: matrix.config.run_armv8_tests
+ run: |
+ export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH
+ export LDFLAGS="-static -Wno-unused-command-line-argument"
+ make clean
+ make -j2 ARCH=armv8 build
+ ../tests/signature.sh $benchref
+
+ - name: Test armv8-dotprod build
+ if: matrix.config.run_armv8_tests
+ run: |
+ export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH
+ export LDFLAGS="-static -Wno-unused-command-line-argument"
+ make clean
+ make -j2 ARCH=armv8-dotprod build
+ ../tests/signature.sh $benchref
+
+ # armv7 tests
+
+ - name: Test armv7 build
+ if: matrix.config.run_armv7_tests
+ run: |
+ export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH
+ export LDFLAGS="-static -Wno-unused-command-line-argument"
+ make clean
+ make -j2 ARCH=armv7 build
+ ../tests/signature.sh $benchref
+
+ - name: Test armv7-neon build
+ if: matrix.config.run_armv7_tests
+ run: |
+ export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH
+ export LDFLAGS="-static -Wno-unused-command-line-argument"
+ make clean
+ make -j2 ARCH=armv7-neon build
+ ../tests/signature.sh $benchref
+
+ # riscv64 tests
+
+ - name: Test riscv64 build
+ if: matrix.config.run_riscv64_tests
+ run: |
+ echo "export LDFLAGS='-static' && make clean && make -j2 ARCH=riscv64 build" > script.sh
+ docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}/src:/app sf_builder
+ ../tests/signature.sh $benchref
+
+ # ppc64 tests
+
+ - name: Test ppc64 build
+ if: matrix.config.run_ppc64_tests
+ run: |
+ echo "export LDFLAGS='-static' && make clean && make -j2 ARCH=ppc-64 build" > script.sh
+ docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}/src:/app sf_builder
+ ../tests/signature.sh $benchref
+
+ # Other tests
+
+ - name: Check perft and search reproducibility
+ if: matrix.config.run_64bit_tests
+ run: |
+ make clean
+ make -j2 ARCH=x86-64-avx2 build
+ ../tests/perft.sh
+ ../tests/reprosearch.sh
--- /dev/null
+# Files from build
+**/*.o
+**/*.s
+src/.depend
+
+# Built binary
+src/stockfish*
+src/-lstdc++.res
+
+# Neural network for the NNUE evaluation
+**/*.nnue
+
+++ /dev/null
-language: cpp
-dist: xenial
-
-matrix:
- include:
- - os: linux
- compiler: gcc
- addons:
- apt:
- sources: ['ubuntu-toolchain-r-test']
- packages: ['g++-8', 'g++-8-multilib', 'g++-multilib', 'valgrind', 'expect', 'curl']
- env:
- - COMPILER=g++-8
- - COMP=gcc
-
- - os: linux
- compiler: clang
- addons:
- apt:
- sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-xenial-6.0']
- packages: ['clang-6.0', 'llvm-6.0-dev', 'g++-multilib', 'valgrind', 'expect', 'curl']
- env:
- - COMPILER=clang++-6.0
- - COMP=clang
- - LDFLAGS=-fuse-ld=lld
-
- - os: osx
- compiler: gcc
- env:
- - COMPILER=g++
- - COMP=gcc
-
- - os: osx
- compiler: clang
- env:
- - COMPILER=clang++ V='Apple LLVM 9.4.1' # Apple LLVM version 9.1.0 (clang-902.0.39.2)
- - COMP=clang
-
-branches:
- only:
- - master
-
-before_script:
- - cd src
-
-script:
- # Obtain bench reference from git log
- - git log HEAD | grep "\b[Bb]ench[ :]\+[0-9]\{7\}" | head -n 1 | sed "s/[^0-9]*\([0-9]*\).*/\1/g" > git_sig
- - export benchref=$(cat git_sig)
- - echo "Reference bench:" $benchref
- #
- # Verify bench number against various builds
- - export CXXFLAGS=-Werror
- - make clean && make -j2 ARCH=x86-64 optimize=no debug=yes build && ../tests/signature.sh $benchref
- - make clean && make -j2 ARCH=x86-32 optimize=no debug=yes build && ../tests/signature.sh $benchref
- - make clean && make -j2 ARCH=x86-32 build && ../tests/signature.sh $benchref
-
- #
- # Check perft and reproducible search
- - ../tests/perft.sh
- - ../tests/reprosearch.sh
- #
- # Valgrind
- #
- - export CXXFLAGS="-O1 -fno-inline"
- - if [ -x "$(command -v valgrind )" ]; then make clean && make -j2 ARCH=x86-64 debug=yes optimize=no build > /dev/null && ../tests/instrumented.sh --valgrind; fi
- - if [ -x "$(command -v valgrind )" ]; then ../tests/instrumented.sh --valgrind-thread; fi
- #
- # Sanitizer
- #
- # Use g++-8 as a proxy for having sanitizers, might need revision as they become available for more recent versions of clang/gcc
- - if [[ "$COMPILER" == "g++-8" ]]; then make clean && make -j2 ARCH=x86-64 sanitize=undefined optimize=no debug=yes build > /dev/null && ../tests/instrumented.sh --sanitizer-undefined; fi
- - if [[ "$COMPILER" == "g++-8" ]]; then make clean && make -j2 ARCH=x86-64 sanitize=thread optimize=no debug=yes build > /dev/null && ../tests/instrumented.sh --sanitizer-thread; fi
-# List of authors for Stockfish, as of January 7, 2020
-
+# 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, and NNUE port
+Yu Nasu (ynasu87)
+Motohiro Isozaki (yaneurao)
+Hisayori Noda (nodchip)
+
+# All other authors of Stockfish code (in alphabetical order)
Aditya (absimaldata)
Adrian Petrescu (apetresc)
Ajith Chandy Jose (ajithcj)
Alayan Feh (Alayan-stk-2)
Alexander Kure
Alexander Pagel (Lolligerhans)
+Alfredo Menezes (lonfom169)
Ali AlZhrani (Cooffe)
+Andreas Matthies (Matthies)
+Andrei Vetrov (proukornew)
Andrew Grant (AndyGrant)
Andrey Neporada (nepal)
Andy Duplain
+Antoine Champion (antoinechampion)
Aram Tumanian (atumanian)
Arjun Temurnikar
+Artem Solopiy (EntityFX)
Auguste Pop
+Balazs Szilagyi
Balint Pfliegel
+Ben Chaney (Chaneybenjamini)
Ben Koshy (BKSpurgeon)
Bill Henry (VoyagerOne)
Bojun Guo (noobpwnftw, Nooby)
+borg323
+Boštjan Mejak (PedanticHacker)
braich
Brian Sheppard (SapphireBrand, briansheppard-toast)
+Bruno de Melo Costa (BM123499)
+Bruno Pellanda (pellanda)
Bryan Cross (crossbr)
candirufish
Chess13234
Chris Cain (ceebo)
-Dan Schmidt (dfannius)
+clefrks
+Cody Ho (aesrentai)
+Dale Weiler (graphitemaster)
Daniel Axtens (daxtens)
Daniel Dugovic (ddugovic)
-Dariusz Orzechowski
+Daniel Monroe (Ergodice)
+Dan Schmidt (dfannius)
+Dariusz Orzechowski (dorzechowski)
+David (dav1312)
David Zar
Daylen Yang (daylen)
+Deshawn Mohan-Smith (GoldenRare)
+Dieter Dobbelaere (ddobbelaere)
DiscanX
+Dominik Schlösser (domschl)
double-beep
+Douglas Matos Gomes (dsmsgms)
+Dubslow
Eduardo Cáceres (eduherminio)
Eelco de Groot (KingDefender)
Elvin Liu (solarlight2)
Ernesto Gatti
Fabian Beuke (madnight)
Fabian Fichter (ianfab)
+Fanael Linithien (Fanael)
fanon
Fauzi Akram Dabat (FauziAkram)
Felix Wittmann
gamander
+Gabriele Lombardo (gabe)
+Gary Heckman (gheckman)
+George Sobala (gsobala)
gguliash
+Giacomo Lorenzetti (G-Lorenz)
Gian-Carlo Pascutto (gcp)
+Goh CJ (cj5716)
Gontran Lemaire (gonlem)
Goodkov Vasiliy Aleksandrovich (goodkov)
Gregor Cramer
GuardianRM
-Günther Demetz (pb00067, pb00068)
Guy Vreuls (gvreuls)
+Günther Demetz (pb00067, pb00068)
Henri Wiechers
Hiraoka Takuya (HiraokaTakuya)
homoSapiensSapiens
Hongzhi Cheng
Ivan Ivec (IIvec)
Jacques B. (Timshel)
+Jake Senne (w1wwwwww)
Jan Ondruš (hxim)
-Jared Kish (Kurtbusch)
+Jared Kish (Kurtbusch, kurt22i)
Jarrod Torriero (DU-jdto)
-Jean Gauthier (OuaisBla)
+Jasper Shovelton (Beanie496)
Jean-Francois Romang (jromang)
+Jean Gauthier (OuaisBla)
Jekaa
Jerry Donald Watson (jerrydonaldwatson)
+jjoshua2
+Jonathan Buladas Dumale (SFisGOD)
Jonathan Calovski (Mysseno)
-Jonathan Dumale (SFisGOD)
+Jonathan McDermid (jonathanmcdermid)
Joost VandeVondele (vondele)
-Jörg Oster (joergoster)
Joseph Ellis (jhellis3)
Joseph R. Prostko
+Jörg Oster (joergoster)
+Julian Willemer (NightlyKing)
jundery
Justin Blanchard (UncombedCoconut)
Kelly Wilson
Ken Takusagawa
+Kian E (KJE-98)
kinderchocolate
Kiran Panditrao (Krgp)
Kojirion
+Krystian Kuzniarek (kuzkry)
Leonardo Ljubičić (ICCF World Champion)
Leonid Pechenik (lp--)
+Liam Keegan (lkeegan)
+Linmiao Xu (linrock)
Linus Arver (listx)
loco-loco
Lub van den Berg (ElbertoOne)
Malcolm Campbell (xoto10)
Mark Tenzer (31m059)
marotear
+Matt Ginsberg (mattginsberg)
Matthew Lai (matthewlai)
Matthew Sullivan (Matt14916)
+Max A. (Disservin)
+Maxim Masiutin (maximmasiutin)
+Maxim Molchanov (Maxim)
Michael An (man)
Michael Byrne (MichaelB7)
Michael Chaly (Vizvezdenec)
Michel Van den Bergh (vdbergh)
Miguel Lahoz (miguel-l)
Mikael Bäckman (mbootsector)
+Mike Babigian (Farseer)
Mira
Miroslav Fontán (Hexik)
Moez Jellouli (MJZ1977)
Mohammed Li (tthsqe12)
+Muzhen J (XInTheDark)
Nathan Rugg (nmrugg)
-Nick Pelling (nickpelling)
+Nguyen Pham (nguyenpham)
Nicklas Persson (NicklasPersson)
+Nick Pelling (nickpelling)
Niklas Fiekas (niklasf)
Nikolay Kostov (NikolayIT)
+Norman Schmidt (FireFather)
+notruck
+Ofek Shochat (OfekShochat, ghostway)
Ondrej Mosnáček (WOnder93)
+Ondřej Mišina (AndrovT)
Oskar Werkelin Ahlin
Pablo Vazquez
Panthee
Pascal Romaret
Pasquale Pigazzini (ppigazzini)
Patrick Jansen (mibere)
-pellanda
+Peter Schneider (pschneider1968)
Peter Zsifkovits (CoffeeOne)
+PikaCat
+Praveen Kumar Tummala (praveentml)
+Prokop Randáček (ProkopRandacek)
+Rahul Dsilva (silversolver1)
Ralph Stößer (Ralph Stoesser)
Raminder Singh
renouve
-Reuven Peleg
-Richard Lloyd
+Reuven Peleg (R-Peleg)
+Richard Lloyd (Richard-Lloyd)
+rn5f107s2
+Robert Nürnberg (robertnurnberg)
Rodrigo Exterckötter Tjäder
-Ron Britvich (Britvich)
+Rodrigo Roim (roim)
Ronald de Man (syzygy1, syzygy)
+Ron Britvich (Britvich)
+rqs
+Rui Coelho (ruicoelhopedro)
Ryan Schmitt
Ryan Takker
Sami Kiminki (skiminki)
Sebastian Buchwald (UniQP)
Sergei Antonov (saproj)
Sergei Ivanov (svivanov72)
+Sergio Vieri (sergiovieri)
sf-x
+Shahin M. Shahin (peregrine)
Shane Booth (shane31)
+Shawn Varghese (xXH4CKST3RXx)
+Siad Daboul (Topologist)
Stefan Geschwentner (locutus2)
Stefano Cardanobile (Stefano80)
+Stefano Di Martino (StefanoD)
Steinar Gunderson (sesse)
Stéphane Nicolet (snicolet)
+Stephen Touset (stouset)
+Syine Mineta (MinetaS)
+Taras Vuk (TarasVuk)
Thanar2
thaspel
theo77186
+Ting-Hsuan Huang (fffelix-huang)
+Tomasz Sobczyk (Sopel97)
Tom Truscott
Tom Vijlbrief (tomtor)
Torsten Franz (torfranz, tfranzer)
+Torsten Hellwig (Torom)
Tracey Emery (basepr1me)
+tttak
+Unai Corzo (unaiic)
Uri Blass (uriblass)
Vince Negri (cuddlestmonkey)
-
+Viren
+windfishballad
+xefoci7612
+Xiang Wang (KatyushaScarlet)
+zz4032
# Additionally, we acknowledge the authors and maintainers of fishtest,
-# an amazing and essential framework for the development of Stockfish!
+# an amazing and essential framework for Stockfish development!
#
-# https://github.com/glinscott/fishtest/blob/master/AUTHORS
-
-
-
-
+# https://github.com/official-stockfish/fishtest/blob/master/AUTHORS
--- /dev/null
+# This CITATION.cff file was generated with cffinit.
+# Visit https://bit.ly/cffinit to generate yours today!
+
+cff-version: 1.2.0
+title: Stockfish
+message: >-
+ Please cite this software using the metadata from this
+ file.
+type: software
+authors:
+ - name: The Stockfish developers (see AUTHORS file)
+repository-code: 'https://github.com/official-stockfish/Stockfish'
+url: 'https://stockfishchess.org/'
+repository-artifact: 'https://stockfishchess.org/download/'
+abstract: Stockfish is a free and strong UCI chess engine.
+keywords:
+ - chess
+ - artificial intelligence (AI)
+ - tree search
+ - alpha-beta search
+ - neural networks (NN)
+ - efficiently updatable neural networks (NNUE)
+license: GPL-3.0
--- /dev/null
+# Contributing to Stockfish
+
+Welcome to the Stockfish project! We are excited that you are interested in
+contributing. This document outlines the guidelines and steps to follow when
+making contributions to Stockfish.
+
+## Table of Contents
+
+- [Building Stockfish](#building-stockfish)
+- [Making Contributions](#making-contributions)
+ - [Reporting Issues](#reporting-issues)
+ - [Submitting Pull Requests](#submitting-pull-requests)
+- [Code Style](#code-style)
+- [Community and Communication](#community-and-communication)
+- [License](#license)
+
+## Building Stockfish
+
+In case you do not have a C++ compiler installed, you can follow the
+instructions from our wiki.
+
+- [Linux][linux-compiling-link]
+- [Windows][windows-compiling-link]
+- [macOS][macos-compiling-link]
+
+## Making Contributions
+
+### Reporting Issues
+
+If you find a bug, please open an issue on the
+[issue tracker][issue-tracker-link]. Be sure to include relevant information
+like your operating system, build environment, and a detailed description of the
+problem.
+
+_Please note that Stockfish's development is not focused on adding new features.
+Thus any issue regarding missing features will potentially be closed without
+further discussion._
+
+### Submitting Pull Requests
+
+- Functional changes need to be tested on fishtest. See
+ [Creating my First Test][creating-my-first-test] for more details.
+ The accompanying pull request should include a link to the test results and
+ the new bench.
+
+- Non-functional changes (e.g. refactoring, code style, documentation) do not
+ need to be tested on fishtest, unless they might impact performance.
+
+- Provide a clear and concise description of the changes in the pull request
+ description.
+
+_First time contributors should add their name to [AUTHORS](../AUTHORS)._
+
+_Stockfish's development is not focused on adding new features. Thus any pull
+request introducing new features will potentially be closed without further
+discussion._
+
+## Code Style
+
+Changes to Stockfish C++ code should respect our coding style defined by
+[.clang-format](.clang-format). You can format your changes by running
+`make format`. This requires clang-format version 17 to be installed on your system.
+
+## Community and Communication
+
+- Join the [Stockfish discord][discord-link] to discuss ideas, issues, and
+ development.
+- Participate in the [Stockfish GitHub discussions][discussions-link] for
+ broader conversations.
+
+## License
+
+By contributing to Stockfish, you agree that your contributions will be licensed
+under the GNU General Public License v3.0. See [Copying.txt][copying-link] for
+more details.
+
+Thank you for contributing to Stockfish and helping us make it even better!
+
+
+[copying-link]: https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt
+[discord-link]: https://discord.gg/GWDRS3kU6R
+[discussions-link]: https://github.com/official-stockfish/Stockfish/discussions/new
+[creating-my-first-test]: https://github.com/official-stockfish/fishtest/wiki/Creating-my-first-test#create-your-test
+[issue-tracker-link]: https://github.com/official-stockfish/Stockfish/issues
+[linux-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source#linux
+[windows-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source#windows
+[macos-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source#macos
- GNU GENERAL PUBLIC LICENSE\r
- Version 3, 29 June 2007\r
-\r
- Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\r
- Everyone is permitted to copy and distribute verbatim copies\r
- of this license document, but changing it is not allowed.\r
-\r
- Preamble\r
-\r
- The GNU General Public License is a free, copyleft license for\r
-software and other kinds of works.\r
-\r
- The licenses for most software and other practical works are designed\r
-to take away your freedom to share and change the works. By contrast,\r
-the GNU General Public License is intended to guarantee your freedom to\r
-share and change all versions of a program--to make sure it remains free\r
-software for all its users. We, the Free Software Foundation, use the\r
-GNU General Public License for most of our software; it applies also to\r
-any other work released this way by its authors. You can apply it to\r
-your programs, too.\r
-\r
- When we speak of free software, we are referring to freedom, not\r
-price. Our General Public Licenses are designed to make sure that you\r
-have the freedom to distribute copies of free software (and charge for\r
-them if you wish), that you receive source code or can get it if you\r
-want it, that you can change the software or use pieces of it in new\r
-free programs, and that you know you can do these things.\r
-\r
- To protect your rights, we need to prevent others from denying you\r
-these rights or asking you to surrender the rights. Therefore, you have\r
-certain responsibilities if you distribute copies of the software, or if\r
-you modify it: responsibilities to respect the freedom of others.\r
-\r
- For example, if you distribute copies of such a program, whether\r
-gratis or for a fee, you must pass on to the recipients the same\r
-freedoms that you received. You must make sure that they, too, receive\r
-or can get the source code. And you must show them these terms so they\r
-know their rights.\r
-\r
- Developers that use the GNU GPL protect your rights with two steps:\r
-(1) assert copyright on the software, and (2) offer you this License\r
-giving you legal permission to copy, distribute and/or modify it.\r
-\r
- For the developers' and authors' protection, the GPL clearly explains\r
-that there is no warranty for this free software. For both users' and\r
-authors' sake, the GPL requires that modified versions be marked as\r
-changed, so that their problems will not be attributed erroneously to\r
-authors of previous versions.\r
-\r
- Some devices are designed to deny users access to install or run\r
-modified versions of the software inside them, although the manufacturer\r
-can do so. This is fundamentally incompatible with the aim of\r
-protecting users' freedom to change the software. The systematic\r
-pattern of such abuse occurs in the area of products for individuals to\r
-use, which is precisely where it is most unacceptable. Therefore, we\r
-have designed this version of the GPL to prohibit the practice for those\r
-products. If such problems arise substantially in other domains, we\r
-stand ready to extend this provision to those domains in future versions\r
-of the GPL, as needed to protect the freedom of users.\r
-\r
- Finally, every program is threatened constantly by software patents.\r
-States should not allow patents to restrict development and use of\r
-software on general-purpose computers, but in those that do, we wish to\r
-avoid the special danger that patents applied to a free program could\r
-make it effectively proprietary. To prevent this, the GPL assures that\r
-patents cannot be used to render the program non-free.\r
-\r
- The precise terms and conditions for copying, distribution and\r
-modification follow.\r
-\r
- TERMS AND CONDITIONS\r
-\r
- 0. Definitions.\r
-\r
- "This License" refers to version 3 of the GNU General Public License.\r
-\r
- "Copyright" also means copyright-like laws that apply to other kinds of\r
-works, such as semiconductor masks.\r
-\r
- "The Program" refers to any copyrightable work licensed under this\r
-License. Each licensee is addressed as "you". "Licensees" and\r
-"recipients" may be individuals or organizations.\r
-\r
- To "modify" a work means to copy from or adapt all or part of the work\r
-in a fashion requiring copyright permission, other than the making of an\r
-exact copy. The resulting work is called a "modified version" of the\r
-earlier work or a work "based on" the earlier work.\r
-\r
- A "covered work" means either the unmodified Program or a work based\r
-on the Program.\r
-\r
- To "propagate" a work means to do anything with it that, without\r
-permission, would make you directly or secondarily liable for\r
-infringement under applicable copyright law, except executing it on a\r
-computer or modifying a private copy. Propagation includes copying,\r
-distribution (with or without modification), making available to the\r
-public, and in some countries other activities as well.\r
-\r
- To "convey" a work means any kind of propagation that enables other\r
-parties to make or receive copies. Mere interaction with a user through\r
-a computer network, with no transfer of a copy, is not conveying.\r
-\r
- An interactive user interface displays "Appropriate Legal Notices"\r
-to the extent that it includes a convenient and prominently visible\r
-feature that (1) displays an appropriate copyright notice, and (2)\r
-tells the user that there is no warranty for the work (except to the\r
-extent that warranties are provided), that licensees may convey the\r
-work under this License, and how to view a copy of this License. If\r
-the interface presents a list of user commands or options, such as a\r
-menu, a prominent item in the list meets this criterion.\r
-\r
- 1. Source Code.\r
-\r
- The "source code" for a work means the preferred form of the work\r
-for making modifications to it. "Object code" means any non-source\r
-form of a work.\r
-\r
- A "Standard Interface" means an interface that either is an official\r
-standard defined by a recognized standards body, or, in the case of\r
-interfaces specified for a particular programming language, one that\r
-is widely used among developers working in that language.\r
-\r
- The "System Libraries" of an executable work include anything, other\r
-than the work as a whole, that (a) is included in the normal form of\r
-packaging a Major Component, but which is not part of that Major\r
-Component, and (b) serves only to enable use of the work with that\r
-Major Component, or to implement a Standard Interface for which an\r
-implementation is available to the public in source code form. A\r
-"Major Component", in this context, means a major essential component\r
-(kernel, window system, and so on) of the specific operating system\r
-(if any) on which the executable work runs, or a compiler used to\r
-produce the work, or an object code interpreter used to run it.\r
-\r
- The "Corresponding Source" for a work in object code form means all\r
-the source code needed to generate, install, and (for an executable\r
-work) run the object code and to modify the work, including scripts to\r
-control those activities. However, it does not include the work's\r
-System Libraries, or general-purpose tools or generally available free\r
-programs which are used unmodified in performing those activities but\r
-which are not part of the work. For example, Corresponding Source\r
-includes interface definition files associated with source files for\r
-the work, and the source code for shared libraries and dynamically\r
-linked subprograms that the work is specifically designed to require,\r
-such as by intimate data communication or control flow between those\r
-subprograms and other parts of the work.\r
-\r
- The Corresponding Source need not include anything that users\r
-can regenerate automatically from other parts of the Corresponding\r
-Source.\r
-\r
- The Corresponding Source for a work in source code form is that\r
-same work.\r
-\r
- 2. Basic Permissions.\r
-\r
- All rights granted under this License are granted for the term of\r
-copyright on the Program, and are irrevocable provided the stated\r
-conditions are met. This License explicitly affirms your unlimited\r
-permission to run the unmodified Program. The output from running a\r
-covered work is covered by this License only if the output, given its\r
-content, constitutes a covered work. This License acknowledges your\r
-rights of fair use or other equivalent, as provided by copyright law.\r
-\r
- You may make, run and propagate covered works that you do not\r
-convey, without conditions so long as your license otherwise remains\r
-in force. You may convey covered works to others for the sole purpose\r
-of having them make modifications exclusively for you, or provide you\r
-with facilities for running those works, provided that you comply with\r
-the terms of this License in conveying all material for which you do\r
-not control copyright. Those thus making or running the covered works\r
-for you must do so exclusively on your behalf, under your direction\r
-and control, on terms that prohibit them from making any copies of\r
-your copyrighted material outside their relationship with you.\r
-\r
- Conveying under any other circumstances is permitted solely under\r
-the conditions stated below. Sublicensing is not allowed; section 10\r
-makes it unnecessary.\r
-\r
- 3. Protecting Users' Legal Rights From Anti-Circumvention Law.\r
-\r
- No covered work shall be deemed part of an effective technological\r
-measure under any applicable law fulfilling obligations under article\r
-11 of the WIPO copyright treaty adopted on 20 December 1996, or\r
-similar laws prohibiting or restricting circumvention of such\r
-measures.\r
-\r
- When you convey a covered work, you waive any legal power to forbid\r
-circumvention of technological measures to the extent such circumvention\r
-is effected by exercising rights under this License with respect to\r
-the covered work, and you disclaim any intention to limit operation or\r
-modification of the work as a means of enforcing, against the work's\r
-users, your or third parties' legal rights to forbid circumvention of\r
-technological measures.\r
-\r
- 4. Conveying Verbatim Copies.\r
-\r
- You may convey verbatim copies of the Program's source code as you\r
-receive it, in any medium, provided that you conspicuously and\r
-appropriately publish on each copy an appropriate copyright notice;\r
-keep intact all notices stating that this License and any\r
-non-permissive terms added in accord with section 7 apply to the code;\r
-keep intact all notices of the absence of any warranty; and give all\r
-recipients a copy of this License along with the Program.\r
-\r
- You may charge any price or no price for each copy that you convey,\r
-and you may offer support or warranty protection for a fee.\r
-\r
- 5. Conveying Modified Source Versions.\r
-\r
- You may convey a work based on the Program, or the modifications to\r
-produce it from the Program, in the form of source code under the\r
-terms of section 4, provided that you also meet all of these conditions:\r
-\r
- a) The work must carry prominent notices stating that you modified\r
- it, and giving a relevant date.\r
-\r
- b) The work must carry prominent notices stating that it is\r
- released under this License and any conditions added under section\r
- 7. This requirement modifies the requirement in section 4 to\r
- "keep intact all notices".\r
-\r
- c) You must license the entire work, as a whole, under this\r
- License to anyone who comes into possession of a copy. This\r
- License will therefore apply, along with any applicable section 7\r
- additional terms, to the whole of the work, and all its parts,\r
- regardless of how they are packaged. This License gives no\r
- permission to license the work in any other way, but it does not\r
- invalidate such permission if you have separately received it.\r
-\r
- d) If the work has interactive user interfaces, each must display\r
- Appropriate Legal Notices; however, if the Program has interactive\r
- interfaces that do not display Appropriate Legal Notices, your\r
- work need not make them do so.\r
-\r
- A compilation of a covered work with other separate and independent\r
-works, which are not by their nature extensions of the covered work,\r
-and which are not combined with it such as to form a larger program,\r
-in or on a volume of a storage or distribution medium, is called an\r
-"aggregate" if the compilation and its resulting copyright are not\r
-used to limit the access or legal rights of the compilation's users\r
-beyond what the individual works permit. Inclusion of a covered work\r
-in an aggregate does not cause this License to apply to the other\r
-parts of the aggregate.\r
-\r
- 6. Conveying Non-Source Forms.\r
-\r
- You may convey a covered work in object code form under the terms\r
-of sections 4 and 5, provided that you also convey the\r
-machine-readable Corresponding Source under the terms of this License,\r
-in one of these ways:\r
-\r
- a) Convey the object code in, or embodied in, a physical product\r
- (including a physical distribution medium), accompanied by the\r
- Corresponding Source fixed on a durable physical medium\r
- customarily used for software interchange.\r
-\r
- b) Convey the object code in, or embodied in, a physical product\r
- (including a physical distribution medium), accompanied by a\r
- written offer, valid for at least three years and valid for as\r
- long as you offer spare parts or customer support for that product\r
- model, to give anyone who possesses the object code either (1) a\r
- copy of the Corresponding Source for all the software in the\r
- product that is covered by this License, on a durable physical\r
- medium customarily used for software interchange, for a price no\r
- more than your reasonable cost of physically performing this\r
- conveying of source, or (2) access to copy the\r
- Corresponding Source from a network server at no charge.\r
-\r
- c) Convey individual copies of the object code with a copy of the\r
- written offer to provide the Corresponding Source. This\r
- alternative is allowed only occasionally and noncommercially, and\r
- only if you received the object code with such an offer, in accord\r
- with subsection 6b.\r
-\r
- d) Convey the object code by offering access from a designated\r
- place (gratis or for a charge), and offer equivalent access to the\r
- Corresponding Source in the same way through the same place at no\r
- further charge. You need not require recipients to copy the\r
- Corresponding Source along with the object code. If the place to\r
- copy the object code is a network server, the Corresponding Source\r
- may be on a different server (operated by you or a third party)\r
- that supports equivalent copying facilities, provided you maintain\r
- clear directions next to the object code saying where to find the\r
- Corresponding Source. Regardless of what server hosts the\r
- Corresponding Source, you remain obligated to ensure that it is\r
- available for as long as needed to satisfy these requirements.\r
-\r
- e) Convey the object code using peer-to-peer transmission, provided\r
- you inform other peers where the object code and Corresponding\r
- Source of the work are being offered to the general public at no\r
- charge under subsection 6d.\r
-\r
- A separable portion of the object code, whose source code is excluded\r
-from the Corresponding Source as a System Library, need not be\r
-included in conveying the object code work.\r
-\r
- A "User Product" is either (1) a "consumer product", which means any\r
-tangible personal property which is normally used for personal, family,\r
-or household purposes, or (2) anything designed or sold for incorporation\r
-into a dwelling. In determining whether a product is a consumer product,\r
-doubtful cases shall be resolved in favor of coverage. For a particular\r
-product received by a particular user, "normally used" refers to a\r
-typical or common use of that class of product, regardless of the status\r
-of the particular user or of the way in which the particular user\r
-actually uses, or expects or is expected to use, the product. A product\r
-is a consumer product regardless of whether the product has substantial\r
-commercial, industrial or non-consumer uses, unless such uses represent\r
-the only significant mode of use of the product.\r
-\r
- "Installation Information" for a User Product means any methods,\r
-procedures, authorization keys, or other information required to install\r
-and execute modified versions of a covered work in that User Product from\r
-a modified version of its Corresponding Source. The information must\r
-suffice to ensure that the continued functioning of the modified object\r
-code is in no case prevented or interfered with solely because\r
-modification has been made.\r
-\r
- If you convey an object code work under this section in, or with, or\r
-specifically for use in, a User Product, and the conveying occurs as\r
-part of a transaction in which the right of possession and use of the\r
-User Product is transferred to the recipient in perpetuity or for a\r
-fixed term (regardless of how the transaction is characterized), the\r
-Corresponding Source conveyed under this section must be accompanied\r
-by the Installation Information. But this requirement does not apply\r
-if neither you nor any third party retains the ability to install\r
-modified object code on the User Product (for example, the work has\r
-been installed in ROM).\r
-\r
- The requirement to provide Installation Information does not include a\r
-requirement to continue to provide support service, warranty, or updates\r
-for a work that has been modified or installed by the recipient, or for\r
-the User Product in which it has been modified or installed. Access to a\r
-network may be denied when the modification itself materially and\r
-adversely affects the operation of the network or violates the rules and\r
-protocols for communication across the network.\r
-\r
- Corresponding Source conveyed, and Installation Information provided,\r
-in accord with this section must be in a format that is publicly\r
-documented (and with an implementation available to the public in\r
-source code form), and must require no special password or key for\r
-unpacking, reading or copying.\r
-\r
- 7. Additional Terms.\r
-\r
- "Additional permissions" are terms that supplement the terms of this\r
-License by making exceptions from one or more of its conditions.\r
-Additional permissions that are applicable to the entire Program shall\r
-be treated as though they were included in this License, to the extent\r
-that they are valid under applicable law. If additional permissions\r
-apply only to part of the Program, that part may be used separately\r
-under those permissions, but the entire Program remains governed by\r
-this License without regard to the additional permissions.\r
-\r
- When you convey a copy of a covered work, you may at your option\r
-remove any additional permissions from that copy, or from any part of\r
-it. (Additional permissions may be written to require their own\r
-removal in certain cases when you modify the work.) You may place\r
-additional permissions on material, added by you to a covered work,\r
-for which you have or can give appropriate copyright permission.\r
-\r
- Notwithstanding any other provision of this License, for material you\r
-add to a covered work, you may (if authorized by the copyright holders of\r
-that material) supplement the terms of this License with terms:\r
-\r
- a) Disclaiming warranty or limiting liability differently from the\r
- terms of sections 15 and 16 of this License; or\r
-\r
- b) Requiring preservation of specified reasonable legal notices or\r
- author attributions in that material or in the Appropriate Legal\r
- Notices displayed by works containing it; or\r
-\r
- c) Prohibiting misrepresentation of the origin of that material, or\r
- requiring that modified versions of such material be marked in\r
- reasonable ways as different from the original version; or\r
-\r
- d) Limiting the use for publicity purposes of names of licensors or\r
- authors of the material; or\r
-\r
- e) Declining to grant rights under trademark law for use of some\r
- trade names, trademarks, or service marks; or\r
-\r
- f) Requiring indemnification of licensors and authors of that\r
- material by anyone who conveys the material (or modified versions of\r
- it) with contractual assumptions of liability to the recipient, for\r
- any liability that these contractual assumptions directly impose on\r
- those licensors and authors.\r
-\r
- All other non-permissive additional terms are considered "further\r
-restrictions" within the meaning of section 10. If the Program as you\r
-received it, or any part of it, contains a notice stating that it is\r
-governed by this License along with a term that is a further\r
-restriction, you may remove that term. If a license document contains\r
-a further restriction but permits relicensing or conveying under this\r
-License, you may add to a covered work material governed by the terms\r
-of that license document, provided that the further restriction does\r
-not survive such relicensing or conveying.\r
-\r
- If you add terms to a covered work in accord with this section, you\r
-must place, in the relevant source files, a statement of the\r
-additional terms that apply to those files, or a notice indicating\r
-where to find the applicable terms.\r
-\r
- Additional terms, permissive or non-permissive, may be stated in the\r
-form of a separately written license, or stated as exceptions;\r
-the above requirements apply either way.\r
-\r
- 8. Termination.\r
-\r
- You may not propagate or modify a covered work except as expressly\r
-provided under this License. Any attempt otherwise to propagate or\r
-modify it is void, and will automatically terminate your rights under\r
-this License (including any patent licenses granted under the third\r
-paragraph of section 11).\r
-\r
- However, if you cease all violation of this License, then your\r
-license from a particular copyright holder is reinstated (a)\r
-provisionally, unless and until the copyright holder explicitly and\r
-finally terminates your license, and (b) permanently, if the copyright\r
-holder fails to notify you of the violation by some reasonable means\r
-prior to 60 days after the cessation.\r
-\r
- Moreover, your license from a particular copyright holder is\r
-reinstated permanently if the copyright holder notifies you of the\r
-violation by some reasonable means, this is the first time you have\r
-received notice of violation of this License (for any work) from that\r
-copyright holder, and you cure the violation prior to 30 days after\r
-your receipt of the notice.\r
-\r
- Termination of your rights under this section does not terminate the\r
-licenses of parties who have received copies or rights from you under\r
-this License. If your rights have been terminated and not permanently\r
-reinstated, you do not qualify to receive new licenses for the same\r
-material under section 10.\r
-\r
- 9. Acceptance Not Required for Having Copies.\r
-\r
- You are not required to accept this License in order to receive or\r
-run a copy of the Program. Ancillary propagation of a covered work\r
-occurring solely as a consequence of using peer-to-peer transmission\r
-to receive a copy likewise does not require acceptance. However,\r
-nothing other than this License grants you permission to propagate or\r
-modify any covered work. These actions infringe copyright if you do\r
-not accept this License. Therefore, by modifying or propagating a\r
-covered work, you indicate your acceptance of this License to do so.\r
-\r
- 10. Automatic Licensing of Downstream Recipients.\r
-\r
- Each time you convey a covered work, the recipient automatically\r
-receives a license from the original licensors, to run, modify and\r
-propagate that work, subject to this License. You are not responsible\r
-for enforcing compliance by third parties with this License.\r
-\r
- An "entity transaction" is a transaction transferring control of an\r
-organization, or substantially all assets of one, or subdividing an\r
-organization, or merging organizations. If propagation of a covered\r
-work results from an entity transaction, each party to that\r
-transaction who receives a copy of the work also receives whatever\r
-licenses to the work the party's predecessor in interest had or could\r
-give under the previous paragraph, plus a right to possession of the\r
-Corresponding Source of the work from the predecessor in interest, if\r
-the predecessor has it or can get it with reasonable efforts.\r
-\r
- You may not impose any further restrictions on the exercise of the\r
-rights granted or affirmed under this License. For example, you may\r
-not impose a license fee, royalty, or other charge for exercise of\r
-rights granted under this License, and you may not initiate litigation\r
-(including a cross-claim or counterclaim in a lawsuit) alleging that\r
-any patent claim is infringed by making, using, selling, offering for\r
-sale, or importing the Program or any portion of it.\r
-\r
- 11. Patents.\r
-\r
- A "contributor" is a copyright holder who authorizes use under this\r
-License of the Program or a work on which the Program is based. The\r
-work thus licensed is called the contributor's "contributor version".\r
-\r
- A contributor's "essential patent claims" are all patent claims\r
-owned or controlled by the contributor, whether already acquired or\r
-hereafter acquired, that would be infringed by some manner, permitted\r
-by this License, of making, using, or selling its contributor version,\r
-but do not include claims that would be infringed only as a\r
-consequence of further modification of the contributor version. For\r
-purposes of this definition, "control" includes the right to grant\r
-patent sublicenses in a manner consistent with the requirements of\r
-this License.\r
-\r
- Each contributor grants you a non-exclusive, worldwide, royalty-free\r
-patent license under the contributor's essential patent claims, to\r
-make, use, sell, offer for sale, import and otherwise run, modify and\r
-propagate the contents of its contributor version.\r
-\r
- In the following three paragraphs, a "patent license" is any express\r
-agreement or commitment, however denominated, not to enforce a patent\r
-(such as an express permission to practice a patent or covenant not to\r
-sue for patent infringement). To "grant" such a patent license to a\r
-party means to make such an agreement or commitment not to enforce a\r
-patent against the party.\r
-\r
- If you convey a covered work, knowingly relying on a patent license,\r
-and the Corresponding Source of the work is not available for anyone\r
-to copy, free of charge and under the terms of this License, through a\r
-publicly available network server or other readily accessible means,\r
-then you must either (1) cause the Corresponding Source to be so\r
-available, or (2) arrange to deprive yourself of the benefit of the\r
-patent license for this particular work, or (3) arrange, in a manner\r
-consistent with the requirements of this License, to extend the patent\r
-license to downstream recipients. "Knowingly relying" means you have\r
-actual knowledge that, but for the patent license, your conveying the\r
-covered work in a country, or your recipient's use of the covered work\r
-in a country, would infringe one or more identifiable patents in that\r
-country that you have reason to believe are valid.\r
-\r
- If, pursuant to or in connection with a single transaction or\r
-arrangement, you convey, or propagate by procuring conveyance of, a\r
-covered work, and grant a patent license to some of the parties\r
-receiving the covered work authorizing them to use, propagate, modify\r
-or convey a specific copy of the covered work, then the patent license\r
-you grant is automatically extended to all recipients of the covered\r
-work and works based on it.\r
-\r
- A patent license is "discriminatory" if it does not include within\r
-the scope of its coverage, prohibits the exercise of, or is\r
-conditioned on the non-exercise of one or more of the rights that are\r
-specifically granted under this License. You may not convey a covered\r
-work if you are a party to an arrangement with a third party that is\r
-in the business of distributing software, under which you make payment\r
-to the third party based on the extent of your activity of conveying\r
-the work, and under which the third party grants, to any of the\r
-parties who would receive the covered work from you, a discriminatory\r
-patent license (a) in connection with copies of the covered work\r
-conveyed by you (or copies made from those copies), or (b) primarily\r
-for and in connection with specific products or compilations that\r
-contain the covered work, unless you entered into that arrangement,\r
-or that patent license was granted, prior to 28 March 2007.\r
-\r
- Nothing in this License shall be construed as excluding or limiting\r
-any implied license or other defenses to infringement that may\r
-otherwise be available to you under applicable patent law.\r
-\r
- 12. No Surrender of Others' Freedom.\r
-\r
- If conditions are imposed on you (whether by court order, agreement or\r
-otherwise) that contradict the conditions of this License, they do not\r
-excuse you from the conditions of this License. If you cannot convey a\r
-covered work so as to satisfy simultaneously your obligations under this\r
-License and any other pertinent obligations, then as a consequence you may\r
-not convey it at all. For example, if you agree to terms that obligate you\r
-to collect a royalty for further conveying from those to whom you convey\r
-the Program, the only way you could satisfy both those terms and this\r
-License would be to refrain entirely from conveying the Program.\r
-\r
- 13. Use with the GNU Affero General Public License.\r
-\r
- Notwithstanding any other provision of this License, you have\r
-permission to link or combine any covered work with a work licensed\r
-under version 3 of the GNU Affero General Public License into a single\r
-combined work, and to convey the resulting work. The terms of this\r
-License will continue to apply to the part which is the covered work,\r
-but the special requirements of the GNU Affero General Public License,\r
-section 13, concerning interaction through a network will apply to the\r
-combination as such.\r
-\r
- 14. Revised Versions of this License.\r
-\r
- The Free Software Foundation may publish revised and/or new versions of\r
-the GNU General Public License from time to time. Such new versions will\r
-be similar in spirit to the present version, but may differ in detail to\r
-address new problems or concerns.\r
-\r
- Each version is given a distinguishing version number. If the\r
-Program specifies that a certain numbered version of the GNU General\r
-Public License "or any later version" applies to it, you have the\r
-option of following the terms and conditions either of that numbered\r
-version or of any later version published by the Free Software\r
-Foundation. If the Program does not specify a version number of the\r
-GNU General Public License, you may choose any version ever published\r
-by the Free Software Foundation.\r
-\r
- If the Program specifies that a proxy can decide which future\r
-versions of the GNU General Public License can be used, that proxy's\r
-public statement of acceptance of a version permanently authorizes you\r
-to choose that version for the Program.\r
-\r
- Later license versions may give you additional or different\r
-permissions. However, no additional obligations are imposed on any\r
-author or copyright holder as a result of your choosing to follow a\r
-later version.\r
-\r
- 15. Disclaimer of Warranty.\r
-\r
- THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\r
-APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\r
-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY\r
-OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\r
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
-PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\r
-IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\r
-ALL NECESSARY SERVICING, REPAIR OR CORRECTION.\r
-\r
- 16. Limitation of Liability.\r
-\r
- IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\r
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\r
-THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\r
-GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\r
-USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\r
-DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\r
-PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\r
-EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\r
-SUCH DAMAGES.\r
-\r
- 17. Interpretation of Sections 15 and 16.\r
-\r
- If the disclaimer of warranty and limitation of liability provided\r
-above cannot be given local legal effect according to their terms,\r
-reviewing courts shall apply local law that most closely approximates\r
-an absolute waiver of all civil liability in connection with the\r
-Program, unless a warranty or assumption of liability accompanies a\r
-copy of the Program in return for a fee.\r
-\r
- END OF TERMS AND CONDITIONS\r
-\r
- How to Apply These Terms to Your New Programs\r
-\r
- If you develop a new program, and you want it to be of the greatest\r
-possible use to the public, the best way to achieve this is to make it\r
-free software which everyone can redistribute and change under these terms.\r
-\r
- To do so, attach the following notices to the program. It is safest\r
-to attach them to the start of each source file to most effectively\r
-state the exclusion of warranty; and each file should have at least\r
-the "copyright" line and a pointer to where the full notice is found.\r
-\r
- <one line to give the program's name and a brief idea of what it does.>\r
- Copyright (C) <year> <name of author>\r
-\r
- This program is free software: you can redistribute it and/or modify\r
- it under the terms of the GNU General Public License as published by\r
- the Free Software Foundation, either version 3 of the License, or\r
- (at your option) any later version.\r
-\r
- This program is distributed in the hope that it will be useful,\r
- but WITHOUT ANY WARRANTY; without even the implied warranty of\r
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
- GNU General Public License for more details.\r
-\r
- You should have received a copy of the GNU General Public License\r
- along with this program. If not, see <http://www.gnu.org/licenses/>.\r
-\r
-Also add information on how to contact you by electronic and paper mail.\r
-\r
- If the program does terminal interaction, make it output a short\r
-notice like this when it starts in an interactive mode:\r
-\r
- <program> Copyright (C) <year> <name of author>\r
- This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\r
- This is free software, and you are welcome to redistribute it\r
- under certain conditions; type `show c' for details.\r
-\r
-The hypothetical commands `show w' and `show c' should show the appropriate\r
-parts of the General Public License. Of course, your program's commands\r
-might be different; for a GUI interface, you would use an "about box".\r
-\r
- You should also get your employer (if you work as a programmer) or school,\r
-if any, to sign a "copyright disclaimer" for the program, if necessary.\r
-For more information on this, and how to apply and follow the GNU GPL, see\r
-<http://www.gnu.org/licenses/>.\r
-\r
- The GNU General Public License does not permit incorporating your program\r
-into proprietary programs. If your program is a subroutine library, you\r
-may consider it more useful to permit linking proprietary applications with\r
-the library. If this is what you want to do, use the GNU Lesser General\r
-Public License instead of this License. But first, please read\r
-<http://www.gnu.org/philosophy/why-not-lgpl.html>.\r
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ 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.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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 <https://www.gnu.org/licenses/>.
+
+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:
+
+ <program> Copyright (C) <year> <name of author>
+ 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
+<https://www.gnu.org/licenses/>.
+
+ 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
+<https://www.gnu.org/licenses/why-not-lgpl.html>.
--- /dev/null
+<div align="center">
+
+ [![Stockfish][stockfish128-logo]][website-link]
+
+ <h3>Stockfish</h3>
+
+ A free and strong UCI chess engine.
+ <br>
+ <strong>[Explore Stockfish docs »][wiki-link]</strong>
+ <br>
+ <br>
+ [Report bug][issue-link]
+ ·
+ [Open a discussion][discussions-link]
+ ·
+ [Discord][discord-link]
+ ·
+ [Blog][website-blog-link]
+
+ [![Build][build-badge]][build-link]
+ [![License][license-badge]][license-link]
+ <br>
+ [![Release][release-badge]][release-link]
+ [![Commits][commits-badge]][commits-link]
+ <br>
+ [![Website][website-badge]][website-link]
+ [![Fishtest][fishtest-badge]][fishtest-link]
+ [![Discord][discord-badge]][discord-link]
+
+</div>
+
+## Overview
+
+[Stockfish][website-link] is a **free and strong UCI chess engine** derived from
+Glaurung 2.1 that analyzes chess positions and computes the optimal moves.
+
+Stockfish **does not include a graphical user interface** (GUI) that is required
+to display a chessboard and to make it easy to input moves. These GUIs are
+developed independently from Stockfish and are available online. **Read the
+documentation for your GUI** of choice for information about how to use
+Stockfish with it.
+
+See also the Stockfish [documentation][wiki-usage-link] for further usage help.
+
+## Files
+
+This distribution of Stockfish consists of the following files:
+
+ * [README.md][readme-link], the file you are currently reading.
+
+ * [Copying.txt][license-link], a text file containing the GNU General Public
+ License version 3.
+
+ * [AUTHORS][authors-link], a text file with the list of authors for the project.
+
+ * [src][src-link], a subdirectory containing the full source code, including a
+ Makefile that can be used to compile Stockfish on Unix-like systems.
+
+ * a file with the .nnue extension, storing the neural network for the NNUE
+ evaluation. Binary distributions will have this file embedded.
+
+## Contributing
+
+__See [Contributing Guide](CONTRIBUTING.md).__
+
+### Donating hardware
+
+Improving Stockfish requires a massive amount of testing. You can donate your
+hardware resources by installing the [Fishtest Worker][worker-link] and viewing
+the current tests on [Fishtest][fishtest-link].
+
+### Improving the code
+
+In the [chessprogramming wiki][programming-link], many techniques used in
+Stockfish are explained with a lot of background information.
+The [section on Stockfish][programmingsf-link] describes many features
+and techniques used by Stockfish. However, it is generic rather than
+focused on Stockfish's precise implementation.
+
+The engine testing is done on [Fishtest][fishtest-link].
+If you want to help improve Stockfish, please read this [guideline][guideline-link]
+first, where the basics of Stockfish development are explained.
+
+Discussions about Stockfish take place these days mainly in the Stockfish
+[Discord server][discord-link]. This is also the best place to ask questions
+about the codebase and how to improve it.
+
+## Compiling Stockfish
+
+Stockfish has support for 32 or 64-bit CPUs, certain hardware instructions,
+big-endian machines such as Power PC, and other platforms.
+
+On Unix-like systems, it should be easy to compile Stockfish directly from the
+source code with the included Makefile in the folder `src`. In general, it is
+recommended to run `make help` to see a list of make targets with corresponding
+descriptions. An example suitable for most Intel and AMD chips:
+
+```
+cd src
+make -j profile-build ARCH=x86-64-avx2
+```
+
+Detailed compilation instructions for all platforms can be found in our
+[documentation][wiki-compile-link]. Our wiki also has information about
+the [UCI commands][wiki-uci-link] supported by Stockfish.
+
+## Terms of use
+
+Stockfish is free and distributed under the
+[**GNU General Public License version 3**][license-link] (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 Stockfish in some way,
+you MUST always include the license and 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 GPL v3.
+
+
+[authors-link]: https://github.com/official-stockfish/Stockfish/blob/master/AUTHORS
+[build-link]: https://github.com/official-stockfish/Stockfish/actions/workflows/stockfish.yml
+[commits-link]: https://github.com/official-stockfish/Stockfish/commits/master
+[discord-link]: https://discord.gg/GWDRS3kU6R
+[issue-link]: https://github.com/official-stockfish/Stockfish/issues/new?assignees=&labels=&template=BUG-REPORT.yml
+[discussions-link]: https://github.com/official-stockfish/Stockfish/discussions/new
+[fishtest-link]: https://tests.stockfishchess.org/tests
+[guideline-link]: https://github.com/official-stockfish/fishtest/wiki/Creating-my-first-test
+[license-link]: https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt
+[programming-link]: https://www.chessprogramming.org/Main_Page
+[programmingsf-link]: https://www.chessprogramming.org/Stockfish
+[readme-link]: https://github.com/official-stockfish/Stockfish/blob/master/README.md
+[release-link]: https://github.com/official-stockfish/Stockfish/releases/latest
+[src-link]: https://github.com/official-stockfish/Stockfish/tree/master/src
+[stockfish128-logo]: https://stockfishchess.org/images/logo/icon_128x128.png
+[uci-link]: https://backscattering.de/chess/uci/
+[website-link]: https://stockfishchess.org
+[website-blog-link]: https://stockfishchess.org/blog/
+[wiki-link]: https://github.com/official-stockfish/Stockfish/wiki
+[wiki-compile-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source
+[wiki-uci-link]: https://github.com/official-stockfish/Stockfish/wiki/UCI-&-Commands
+[wiki-usage-link]: https://github.com/official-stockfish/Stockfish/wiki/Download-and-usage
+[worker-link]: https://github.com/official-stockfish/fishtest/wiki/Running-the-worker
+
+[build-badge]: https://img.shields.io/github/actions/workflow/status/official-stockfish/Stockfish/stockfish.yml?branch=master&style=for-the-badge&label=stockfish&logo=github
+[commits-badge]: https://img.shields.io/github/commits-since/official-stockfish/Stockfish/latest?style=for-the-badge
+[discord-badge]: https://img.shields.io/discord/435943710472011776?style=for-the-badge&label=discord&logo=Discord
+[fishtest-badge]: https://img.shields.io/website?style=for-the-badge&down_color=red&down_message=Offline&label=Fishtest&up_color=success&up_message=Online&url=https%3A%2F%2Ftests.stockfishchess.org%2Ftests%2Ffinished
+[license-badge]: https://img.shields.io/github/license/official-stockfish/Stockfish?style=for-the-badge&label=license&color=success
+[release-badge]: https://img.shields.io/github/v/release/official-stockfish/Stockfish?style=for-the-badge&label=official%20release
+[website-badge]: https://img.shields.io/website?style=for-the-badge&down_color=red&down_message=Offline&label=website&up_color=success&up_message=Online&url=https%3A%2F%2Fstockfishchess.org
+++ /dev/null
-## Overview
-
-[![Build Status](https://travis-ci.org/official-stockfish/Stockfish.svg?branch=master)](https://travis-ci.org/official-stockfish/Stockfish)
-[![Build Status](https://ci.appveyor.com/api/projects/status/github/official-stockfish/Stockfish?branch=master&svg=true)](https://ci.appveyor.com/project/mcostalba/stockfish/branch/master)
-
-[Stockfish](https://stockfishchess.org) is a free, powerful UCI chess engine
-derived from Glaurung 2.1. It is not a complete chess program and requires a
-UCI-compatible GUI (e.g. XBoard with PolyGlot, Scid, Cute Chess, eboard, Arena,
-Sigma Chess, Shredder, Chess Partner or Fritz) in order to be used comfortably.
-Read the documentation for your GUI of choice for information about how to use
-Stockfish with it.
-
-
-## Files
-
-This distribution of Stockfish consists of the following files:
-
- * Readme.md, the file you are currently reading.
-
- * Copying.txt, a text file containing the GNU General Public License version 3.
-
- * src, a subdirectory containing the full source code, including a Makefile
- that can be used to compile Stockfish on Unix-like systems.
-
-
-## UCI parameters
-
-Currently, Stockfish has the following UCI options:
-
- * #### Debug Log File
- Write all communication to and from the engine into a text file.
-
- * #### Contempt
- A positive value for contempt favors middle game positions and avoids draws.
-
- * #### Analysis Contempt
- By default, contempt is set to prefer the side to move. Set this option to "White"
- or "Black" to analyse with contempt for that side, or "Off" to disable contempt.
-
- * #### Threads
- The number of CPU threads used for searching a position. For best performance, set
- this equal to the number of CPU cores available.
-
- * #### Hash
- The size of the hash table in MB.
-
- * #### Clear Hash
- Clear the hash table.
-
- * #### Ponder
- Let Stockfish ponder its next move while the opponent is thinking.
-
- * #### MultiPV
- Output the N best lines (principal variations, PVs) when searching.
- Leave at 1 for best performance.
-
- * #### Skill Level
- Lower the Skill Level in order to make Stockfish play weaker (see also UCI_LimitStrength).
- Internally, MultiPV is enabled, and with a certain probability depending on the Skill Level a
- weaker move will be played.
-
- * #### UCI_LimitStrength
- Enable weaker play aiming for an Elo rating as set by UCI_Elo. This option overrides Skill Level.
-
- * #### UCI_Elo
- If enabled by UCI_LimitStrength, aim for an engine strength of the given Elo.
- This Elo rating has been calibrated at a time control of 60s+0.6s and anchored to CCRL 40/4.
-
- * #### Move Overhead
- Assume a time delay of x ms due to network and GUI overheads. This is useful to
- avoid losses on time in those cases.
-
- * #### Minimum Thinking Time
- Search for at least x ms per move.
-
- * #### Slow Mover
- Lower values will make Stockfish take less time in games, higher values will
- make it think longer.
-
- * #### nodestime
- Tells the engine to use nodes searched instead of wall time to account for
- elapsed time. Useful for engine testing.
-
- * #### UCI_Chess960
- An option handled by your GUI. If true, Stockfish will play Chess960.
-
- * #### UCI_AnalyseMode
- An option handled by your GUI.
-
- * #### SyzygyPath
- Path to the folders/directories storing the Syzygy tablebase files. Multiple
- directories are to be separated by ";" on Windows and by ":" on Unix-based
- operating systems. Do not use spaces around the ";" or ":".
-
- Example: `C:\tablebases\wdl345;C:\tablebases\wdl6;D:\tablebases\dtz345;D:\tablebases\dtz6`
-
- It is recommended to store .rtbw files on an SSD. There is no loss in storing
- the .rtbz files on a regular HD. It is recommended to verify all md5 checksums
- of the downloaded tablebase files (`md5sum -c checksum.md5`) as corruption will
- lead to engine crashes.
-
- * #### SyzygyProbeDepth
- Minimum remaining search depth for which a position is probed. Set this option
- to a higher value to probe less agressively if you experience too much slowdown
- (in terms of nps) due to TB probing.
-
- * #### Syzygy50MoveRule
- Disable to let fifty-move rule draws detected by Syzygy tablebase probes count
- as wins or losses. This is useful for ICCF correspondence games.
-
- * #### SyzygyProbeLimit
- Limit Syzygy tablebase probing to positions with at most this many pieces left
- (including kings and pawns).
-
-
-## What to expect from Syzygybases?
-
-If the engine is searching a position that is not in the tablebases (e.g.
-a position with 8 pieces), it will access the tablebases during the search.
-If the engine reports a very large score (typically 153.xx), this means
-that it has found a winning line into a tablebase position.
-
-If the engine is given a position to search that is in the tablebases, it
-will use the tablebases at the beginning of the search to preselect all
-good moves, i.e. all moves that preserve the win or preserve the draw while
-taking into account the 50-move rule.
-It will then perform a search only on those moves. **The engine will not move
-immediately**, unless there is only a single good move. **The engine likely
-will not report a mate score even if the position is known to be won.**
-
-It is therefore clear that this behaviour is not identical to what one might
-be used to with Nalimov tablebases. There are technical reasons for this
-difference, the main technical reason being that Nalimov tablebases use the
-DTM metric (distance-to-mate), while Syzygybases use a variation of the
-DTZ metric (distance-to-zero, zero meaning any move that resets the 50-move
-counter). This special metric is one of the reasons that Syzygybases are
-more compact than Nalimov tablebases, while still storing all information
-needed for optimal play and in addition being able to take into account
-the 50-move rule.
-
-
-## Compiling Stockfish yourself from the sources
-
-On Unix-like systems, it should be possible to compile Stockfish
-directly from the source code with the included Makefile.
-
-Stockfish has support for 32 or 64-bit CPUs, the hardware POPCNT
-instruction, big-endian machines such as Power PC, and other platforms.
-
-In general it is recommended to run `make help` to see a list of make
-targets with corresponding descriptions. When not using the Makefile to
-compile (for instance with Microsoft MSVC) you need to manually
-set/unset some switches in the compiler command line; see file *types.h*
-for a quick reference.
-
-When reporting an issue or a bug, please tell us which version and
-compiler you used to create your executable. These informations can
-be found by typing the following commands in a console:
-
-```
- ./stockfish
- compiler
-```
-
-## Understanding the code base and participating in the project
-
-Stockfish's improvement over the last couple of years has been a great
-community effort. There are a few ways to help contribute to its growth.
-
-### Donating hardware
-
-Improving Stockfish requires a massive amount of testing. You can donate
-your hardware resources by installing the [Fishtest Worker](https://github.com/glinscott/fishtest/wiki/Running-the-worker:-overview)
-and view the current tests on [Fishtest](https://tests.stockfishchess.org/tests).
-
-### Improving the code
-
-If you want to help improve the code, there are several valuable resources:
-
-* [In this wiki,](https://www.chessprogramming.org) many techniques used in
-Stockfish are explained with a lot of background information.
-
-* [The section on Stockfish](https://www.chessprogramming.org/Stockfish)
-describes many features and techniques used by Stockfish. However, it is
-generic rather than being focused on Stockfish's precise implementation.
-Nevertheless, a helpful resource.
-
-* The latest source can always be found on [GitHub](https://github.com/official-stockfish/Stockfish).
-Discussions about Stockfish take place in the [FishCooking](https://groups.google.com/forum/#!forum/fishcooking)
-group and engine testing is done on [Fishtest](https://tests.stockfishchess.org/tests).
-If you want to help improve Stockfish, please read this [guideline](https://github.com/glinscott/fishtest/wiki/Creating-my-first-test)
-first, where the basics of Stockfish development are explained.
-
-
-## Terms of use
-
-Stockfish is free, and distributed under the **GNU General Public License version 3**
-(GPL v3). Essentially, this means that 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 web site, 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 Stockfish in
-some way, you must always include the full source code, or a pointer
-to where the source code can be found. 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*.
-Contributors with >10,000 CPU hours as of January 7, 2020
+Contributors to Fishtest with >10,000 CPU hours, as of 2023-06-20.
Thank you!
-Username CPU Hours Games played
---------------------------------------------------
-noobpwnftw 9305707 695548021
-mlang 780050 61648867
-dew 621626 43921547
-mibere 524702 42238645
-crunchy 354587 27344275
-cw 354495 27274181
-fastgm 332801 22804359
-JojoM 295750 20437451
-CSU_Dynasty 262015 21828122
-Fisherman 232181 18939229
-ctoks 218866 17622052
-glinscott 201989 13780820
-tvijlbrief 201204 15337115
-velislav 188630 14348485
-gvreuls 187164 15149976
-bking_US 180289 11876016
-nordlandia 172076 13467830
-leszek 157152 11443978
-Thanar 148021 12365359
-spams 141975 10319326
-drabel 138073 11121749
-vdv 137850 9394330
-mgrabiak 133578 10454324
-TueRens 132485 10878471
-bcross 129683 11557084
-marrco 126078 9356740
-sqrt2 125830 9724586
-robal 122873 9593418
-vdbergh 120766 8926915
-malala 115926 8002293
-CoffeeOne 114241 5004100
-dsmith 113189 7570238
-BrunoBanani 104644 7436849
-Data 92328 8220352
-mhoram 89333 6695109
-davar 87924 7009424
-xoto 81094 6869316
-ElbertoOne 80899 7023771
-grandphish2 78067 6160199
-brabos 77212 6186135
-psk 75733 5984901
-BRAVONE 73875 5054681
-sunu 70771 5597972
-sterni1971 70605 5590573
-MaZePallas 66886 5188978
-Vizvezdenec 63708 4967313
-nssy 63462 5259388
-jromang 61634 4940891
-teddybaer 61231 5407666
-Pking_cda 60099 5293873
-solarlight 57469 5028306
-dv8silencer 56913 3883992
-tinker 54936 4086118
-renouve 49732 3501516
-Freja 49543 3733019
-robnjr 46972 4053117
-rap 46563 3219146
-Bobo1239 46036 3817196
-ttruscott 45304 3649765
-racerschmacer 44881 3975413
-finfish 44764 3370515
-eva42 41783 3599691
-biffhero 40263 3111352
-bigpen0r 39817 3291647
-mhunt 38871 2691355
-ronaldjerum 38820 3240695
-Antihistamine 38785 2761312
-pb00067 38038 3086320
-speedycpu 37591 3003273
-rkl 37207 3289580
-VoyagerOne 37050 3441673
-jbwiebe 35320 2805433
-cuistot 34191 2146279
-homyur 33927 2850481
-manap 32873 2327384
-gri 32538 2515779
-oryx 31267 2899051
-EthanOConnor 30959 2090311
-SC 30832 2730764
-csnodgrass 29505 2688994
-jmdana 29458 2205261
-strelock 28219 2067805
-jkiiski 27832 1904470
-Pyafue 27533 1902349
-Garf 27515 2747562
-eastorwest 27421 2317535
-slakovv 26903 2021889
-Prcuvu 24835 2170122
-anst 24714 2190091
-hyperbolic.tom 24319 2017394
-Patrick_G 23687 1801617
-Sharaf_DG 22896 1786697
-nabildanial 22195 1519409
-chriswk 21931 1868317
-achambord 21665 1767323
-Zirie 20887 1472937
-team-oh 20217 1636708
-Isidor 20096 1680691
-ncfish1 19931 1520927
-nesoneg 19875 1463031
-Spprtr 19853 1548165
-JanErik 19849 1703875
-agg177 19478 1395014
-SFTUser 19231 1567999
-xor12 19017 1680165
-sg4032 18431 1641865
-rstoesser 18118 1293588
-MazeOfGalious 17917 1629593
-j3corre 17743 941444
-cisco2015 17725 1690126
-ianh2105 17706 1632562
-dex 17678 1467203
-jundery 17194 1115855
-iisiraider 17019 1101015
-horst.prack 17012 1465656
-Adrian.Schmidt123 16563 1281436
-purplefishies 16342 1092533
-wei 16274 1745989
-ville 16144 1384026
-eudhan 15712 1283717
-OuaisBla 15581 972000
-DragonLord 15559 1162790
-dju 14716 875569
-chris 14479 1487385
-0xB00B1ES 14079 1001120
-OssumOpossum 13776 1007129
-enedene 13460 905279
-bpfliegel 13346 884523
-Ente 13198 1156722
-IgorLeMasson 13087 1147232
-jpulman 13000 870599
-ako027ako 12775 1173203
-Nikolay.IT 12352 1068349
-Andrew Grant 12327 895539
-joster 12008 950160
-AdrianSA 11996 804972
-Nesa92 11455 1111993
-fatmurphy 11345 853210
-Dark_wizzie 11108 1007152
-modolief 10869 896470
-mschmidt 10757 803401
-infinity 10594 727027
-mabichito 10524 749391
-Thomas A. Anderson 10474 732094
-thijsk 10431 719357
-Flopzee 10339 894821
-crocogoat 10104 1013854
-SapphireBrand 10104 969604
-stocky 10017 699440
+Username CPU Hours Games played
+------------------------------------------------------------------
+noobpwnftw 37457426 2850540907
+technologov 14135647 742892808
+linrock 4423514 303254809
+mlang 3026000 200065824
+dew 1689162 100033738
+okrout 1578136 148855886
+pemo 1508508 48814305
+grandphish2 1461406 91540343
+TueRens 1194790 70400852
+JojoM 947612 61773190
+tvijlbrief 796125 51897690
+sebastronomy 742434 38218524
+mibere 703840 46867607
+gvreuls 651026 42988582
+oz 543438 39314736
+cw 517858 34869755
+fastgm 503862 30260818
+leszek 467278 33514883
+CSU_Dynasty 464940 31177118
+ctoks 434416 28506889
+crunchy 427035 27344275
+maximmasiutin 424795 26577722
+bcross 415722 29060963
+olafm 395922 32268020
+rpngn 348378 24560289
+velislav 342567 22138992
+Fisherman 327231 21829379
+mgrabiak 300612 20608380
+Dantist 296386 18031762
+nordlandia 246201 16189678
+robal 241300 15656382
+marrco 234581 17714473
+ncfish1 227517 15233777
+glinscott 208125 13277240
+drabel 204167 13930674
+mhoram 202894 12601997
+bking_US 198894 11876016
+Thanar 179852 12365359
+vdv 175544 9904472
+spams 157128 10319326
+sqrt2 147963 9724586
+DesolatedDodo 146350 9536172
+Calis007 143165 9478764
+vdbergh 138650 9064413
+CoffeeOne 137100 5024116
+armo9494 136191 9460264
+malala 136182 8002293
+xoto 133759 9159372
+davar 129023 8376525
+DMBK 122960 8980062
+dsmith 122059 7570238
+amicic 119661 7938029
+Data 113305 8220352
+BrunoBanani 112960 7436849
+CypressChess 108331 7759788
+skiminki 107583 7218170
+jcAEie 105675 8238962
+MaZePallas 102823 6633619
+sterni1971 100532 5880772
+sunu 100167 7040199
+zeryl 99331 6221261
+thirdlife 99124 2242380
+ElbertoOne 99028 7023771
+cuistot 98853 6069816
+bigpen0r 94809 6529203
+brabos 92118 6186135
+Wolfgang 91939 6105872
+psk 89957 5984901
+sschnee 88235 5268000
+racerschmacer 85805 6122790
+Fifis 85722 5709729
+Dubslow 84986 6042456
+Vizvezdenec 83761 5344740
+0x3C33 82614 5271253
+BRAVONE 81239 5054681
+nssy 76497 5259388
+jromang 76106 5236025
+teddybaer 75125 5407666
+tolkki963 74762 5149662
+megaman7de 74351 4940352
+Wencey 74181 4711488
+Pking_cda 73776 5293873
+yurikvelo 73150 5004382
+markkulix 72607 5304642
+Bobo1239 70579 4794999
+solarlight 70517 5028306
+dv8silencer 70287 3883992
+manap 66273 4121774
+tinker 64333 4268790
+qurashee 61208 3429862
+Mineta 59357 4418202
+Spprtr 58723 3911011
+AGI 58147 4325994
+robnjr 57262 4053117
+Freja 56938 3733019
+MaxKlaxxMiner 56879 3423958
+MarcusTullius 56746 3762951
+ttruscott 56010 3680085
+rkl 55132 4164467
+renouve 53811 3501516
+javran 53785 4627608
+finfish 51360 3370515
+eva42 51272 3599691
+eastorwest 51117 3454811
+rap 49985 3219146
+pb00067 49733 3298934
+OuaisBla 48626 3445134
+ronaldjerum 47654 3240695
+biffhero 46564 3111352
+VoyagerOne 45476 3452465
+jmdana 44893 3065205
+maposora 44597 4039578
+oryx 44570 3454238
+speedycpu 43842 3003273
+jbwiebe 43305 2805433
+GPUex 42378 3133332
+Antihistamine 41788 2761312
+mhunt 41735 2691355
+homyur 39893 2850481
+gri 39871 2515779
+Garf 37741 2999686
+SC 37299 2731694
+csnodgrass 36207 2688994
+strelock 34716 2074055
+szupaw 34102 2880346
+EthanOConnor 33370 2090311
+slakovv 32915 2021889
+Gelma 31771 1551204
+gopeto 31671 2060990
+kdave 31157 2198362
+manapbk 30987 1810399
+Prcuvu 30377 2170122
+anst 30301 2190091
+jkiiski 30136 1904470
+spcc 29925 1901692
+hyperbolic.tom 29840 2017394
+chuckstablers 29659 2093438
+Pyafue 29650 1902349
+belzedar94 28846 1811530
+chriswk 26902 1868317
+xwziegtm 26897 2124586
+achambord 26582 1767323
+Patrick_G 26276 1801617
+yorkman 26193 1992080
+Ulysses 25288 1689730
+SFTUser 25182 1675689
+nabildanial 24942 1519409
+Sharaf_DG 24765 1786697
+Maxim 24705 1502062
+rodneyc 24376 1416402
+agg177 23890 1395014
+Goatminola 23763 1956036
+Ente 23639 1671638
+Jopo12321 23467 1483172
+JanErik 23408 1703875
+Isidor 23388 1680691
+Norabor 23371 1603244
+cisco2015 22920 1763301
+jsys14 22824 1591906
+Zirie 22542 1472937
+team-oh 22272 1636708
+Roady 22220 1465606
+MazeOfGalious 21978 1629593
+sg4032 21947 1643353
+ianh2105 21725 1632562
+xor12 21628 1680365
+dex 21612 1467203
+nesoneg 21494 1463031
+user213718 21454 1404128
+sphinx 21211 1384728
+AndreasKrug 21097 1634811
+jjoshua2 21001 1423089
+Zake9298 20938 1565848
+horst.prack 20878 1465656
+0xB00B1ES 20590 1208666
+j3corre 20405 941444
+Adrian.Schmidt123 20316 1281436
+wei 19973 1745989
+notchris 19958 1800128
+Serpensin 19840 1697528
+Gaster319 19712 1677310
+fishtester 19617 1257388
+rstoesser 19569 1293588
+eudhan 19274 1283717
+votoanthuan 19108 1609992
+vulcan 18871 1729392
+Karpovbot 18766 1053178
+qoo_charly_cai 18543 1284937
+jundery 18445 1115855
+ville 17883 1384026
+chris 17698 1487385
+purplefishies 17595 1092533
+dju 17414 981289
+iisiraider 17275 1049015
+DragonLord 17014 1162790
+redstone59 16842 1461780
+Alb11747 16787 1213926
+IgorLeMasson 16064 1147232
+Karby 15982 979610
+scuzzi 15757 968735
+ako027ako 15671 1173203
+Nikolay.IT 15154 1068349
+Andrew Grant 15114 895539
+Naven94 15054 834762
+OssumOpossum 14857 1007129
+ZacHFX 14783 1021842
+enedene 14476 905279
+bpfliegel 14233 882523
+mpx86 14019 759568
+jpulman 13982 870599
+Skiff84 13826 721996
+crocogoat 13803 1117422
+Nesa92 13786 1114691
+joster 13710 946160
+mbeier 13650 1044928
+Hjax 13535 915487
+Nullvalue 13468 1140498
+Dark_wizzie 13422 1007152
+Rudolphous 13244 883140
+pirt 13100 1009897
+Machariel 13010 863104
+infinigon 12991 943216
+mabichito 12903 749391
+thijsk 12886 722107
+AdrianSA 12860 804972
+Flopzee 12698 894821
+korposzczur 12606 838168
+fatmurphy 12547 853210
+SapphireBrand 12416 969604
+Oakwen 12399 844109
+deflectooor 12386 579392
+modolief 12386 896470
+Farseer 12249 694108
+Jackfish 12213 805008
+pgontarz 12151 848794
+dbernier 12103 860824
+getraideBFF 12072 1024966
+stocky 11954 699440
+mschmidt 11941 803401
+MooTheCow 11870 773598
+FormazChar 11766 885707
+whelanh 11557 245188
+3cho 11494 1031076
+infinity 11470 727027
+aga 11412 695127
+torbjo 11395 729145
+Thomas A. Anderson 11372 732094
+savage84 11358 670860
+d64 11263 789184
+ali-al-zhrani 11245 779246
+snicolet 11106 869170
+dapper 11032 771402
+ols 10947 624903
+Karmatron 10828 677458
+basepi 10637 744851
+Cubox 10621 826448
+michaelrpg 10509 739239
+OIVAS7572 10420 995586
+jojo2357 10419 929708
+WoodMan777 10380 873720
+Garruk 10365 706465
+dzjp 10343 732529
+++ /dev/null
-version: 1.0.{build}
-clone_depth: 50
-
-branches:
- only:
- - master
- - appveyor
-
-# Operating system (build VM template)
-os: Visual Studio 2017
-
-# 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 | select -ExpandProperty FullName
- $src = $src -join ' '
- $src = $src.Replace("\", "/")
-
- # Build CMakeLists.txt
- $t = 'cmake_minimum_required(VERSION 3.8)',
- 'project(Stockfish)',
- 'set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/src)',
- 'set(source_files', $src, ')',
- 'add_executable(stockfish ${source_files})'
-
- # Write CMakeLists.txt withouth 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 15 2017"
- If (${env:PLATFORM} -eq 'x64') { $g = $g + ' Win64' }
- cmake -G "${g}" .
- Write-Host "Generated files for: " $g
-
-build_script:
- - cmake --build . --config %CONFIGURATION% -- /verbosity:minimal
-
-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 }
--- /dev/null
+#!/bin/sh
+
+#
+# Returns properties of the native system.
+# best architecture as supported by the CPU
+# filename of the best binary uploaded as an artifact during CI
+#
+
+# Check if all the given flags are present in the CPU flags list
+check_flags() {
+ for flag; do
+ printf '%s\n' "$flags" | grep -q -w "$flag" || return 1
+ done
+}
+
+# Set the CPU flags list
+# remove underscores and points from flags, e.g. gcc uses avx512vnni, while some cpuinfo can have avx512_vnni, some systems use sse4_1 others sse4.1
+get_flags() {
+ flags=$(awk '/^flags[ \t]*:|^Features[ \t]*:/{gsub(/^flags[ \t]*:[ \t]*|^Features[ \t]*:[ \t]*|[_.]/, ""); line=$0} END{print line}' /proc/cpuinfo)
+}
+
+# Check for gcc march "znver1" or "znver2" https://en.wikichip.org/wiki/amd/cpuid
+check_znver_1_2() {
+ vendor_id=$(awk '/^vendor_id/{print $3; exit}' /proc/cpuinfo)
+ cpu_family=$(awk '/^cpu family/{print $4; exit}' /proc/cpuinfo)
+ [ "$vendor_id" = "AuthenticAMD" ] && [ "$cpu_family" = "23" ] && znver_1_2=true
+}
+
+# Set the file CPU x86_64 architecture
+set_arch_x86_64() {
+ if check_flags 'avx512vnni' 'avx512dq' 'avx512f' 'avx512bw' 'avx512vl'; then
+ true_arch='x86-64-vnni256'
+ elif check_flags 'avx512f' 'avx512bw'; then
+ true_arch='x86-64-avx512'
+ elif [ -z "${znver_1_2+1}" ] && check_flags 'bmi2'; then
+ true_arch='x86-64-bmi2'
+ elif check_flags 'avx2'; then
+ true_arch='x86-64-avx2'
+ elif check_flags 'sse41' && check_flags 'popcnt'; then
+ true_arch='x86-64-sse41-popcnt'
+ else
+ true_arch='x86-64'
+ fi
+}
+
+# Check the system type
+uname_s=$(uname -s)
+uname_m=$(uname -m)
+case $uname_s in
+ 'Darwin') # Mac OSX system
+ case $uname_m in
+ 'arm64')
+ true_arch='apple-silicon'
+ file_arch='x86-64-sse41-popcnt' # Supported by Rosetta 2
+ ;;
+ 'x86_64')
+ flags=$(sysctl -n machdep.cpu.features machdep.cpu.leaf7_features | tr '\n' ' ' | tr '[:upper:]' '[:lower:]' | tr -d '_.')
+ set_arch_x86_64
+ if [ "$true_arch" = 'x86-64-vnni256' ] || [ "$true_arch" = 'x86-64-avx512' ]; then
+ file_arch='x86-64-bmi2'
+ fi
+ ;;
+ esac
+ file_os='macos'
+ file_ext='tar'
+ ;;
+ 'Linux') # Linux system
+ get_flags
+ case $uname_m in
+ 'x86_64')
+ file_os='ubuntu'
+ check_znver_1_2
+ set_arch_x86_64
+ ;;
+ 'i686')
+ file_os='ubuntu'
+ true_arch='x86-32'
+ ;;
+ 'aarch64')
+ file_os='android'
+ true_arch='armv8'
+ if check_flags 'dotprod'; then
+ true_arch="$true_arch-dotprod"
+ fi
+ ;;
+ 'armv7'*)
+ file_os='android'
+ true_arch='armv7'
+ if check_flags 'neon'; then
+ true_arch="$true_arch-neon"
+ fi
+ ;;
+ *) # Unsupported machine type, exit with error
+ printf 'Unsupported machine type: %s\n' "$uname_m"
+ exit 1
+ ;;
+ esac
+ file_ext='tar'
+ ;;
+ 'CYGWIN'*|'MINGW'*|'MSYS'*) # Windows system with POSIX compatibility layer
+ get_flags
+ check_znver_1_2
+ set_arch_x86_64
+ file_os='windows'
+ file_ext='zip'
+ ;;
+ *)
+ # Unknown system type, exit with error
+ printf 'Unsupported system type: %s\n' "$uname_s"
+ exit 1
+ ;;
+esac
+
+if [ -z "$file_arch" ]; then
+ file_arch=$true_arch
+fi
+
+file_name="stockfish-$file_os-$file_arch.$file_ext"
+
+printf '%s %s\n' "$true_arch" "$file_name"
# Stockfish, a UCI chess playing engine derived from Glaurung 2.1
-# Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
-# Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
-# Copyright (C) 2015-2019 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+# Copyright (C) 2004-2023 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
### Section 1. General Configuration
### ==========================================================================
+### Establish the operating system name
+KERNEL := $(shell uname -s)
+ifeq ($(KERNEL),Linux)
+ OS := $(shell uname -o)
+endif
+
+### Target Windows OS
+ifeq ($(OS),Windows_NT)
+ ifneq ($(COMP),ndk)
+ target_windows = yes
+ endif
+else ifeq ($(COMP),mingw)
+ target_windows = yes
+ ifeq ($(WINE_PATH),)
+ WINE_PATH := $(shell which wine)
+ endif
+endif
+
### Executable name
-ifeq ($(COMP),mingw)
-EXE = stockfish.exe
+ifeq ($(target_windows),yes)
+ EXE = stockfish.exe
else
-EXE = stockfish
+ EXE = stockfish
endif
### Installation dir definitions
BINDIR = $(PREFIX)/bin
### Built-in benchmark for pgo-builds
-PGOBENCH = ./$(EXE) bench
+PGOBENCH = $(WINE_PATH) ./$(EXE) bench
-### Object files
-OBJS = benchmark.o bitbase.o bitboard.o endgame.o evaluate.o main.o \
- material.o misc.o movegen.o movepick.o pawns.o position.o psqt.o \
- search.o thread.o timeman.o tt.o uci.o ucioption.o syzygy/tbprobe.o
+### Source and object files
+SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \
+ misc.cpp movegen.cpp movepick.cpp position.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_hm.cpp \
+ hashprobe.grpc.pb.cc hashprobe.pb.cc
+CLISRCS = client.cpp hashprobe.grpc.pb.cc hashprobe.pb.cc uci.cpp
-### Establish the operating system name
-KERNEL = $(shell uname -s)
-ifeq ($(KERNEL),Linux)
- OS = $(shell uname -o)
-endif
+HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h \
+ nnue/evaluate_nnue.h nnue/features/half_ka_v2_hm.h nnue/layers/affine_transform.h \
+ nnue/layers/affine_transform_sparse_input.h nnue/layers/clipped_relu.h nnue/layers/simd.h \
+ nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \
+ nnue/nnue_common.h nnue/nnue_feature_transformer.h position.h \
+ search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \
+ tt.h tune.h types.h uci.h
+
+OBJS = $(notdir $(SRCS:.cpp=.o))
+CLIOBJS = $(notdir $(CLISRCS:.cpp=.o))
+
+VPATH = syzygy:nnue:nnue/features
### ==========================================================================
### Section 2. High-level Configuration
### ==========================================================================
#
-# flag --- Comp switch --- Description
+# flag --- Comp switch --- Description
# ----------------------------------------------------------------------------
#
-# debug = yes/no --- -DNDEBUG --- Enable/Disable debug mode
-# sanitize = undefined/thread/no (-fsanitize )
-# --- ( undefined ) --- enable undefined behavior checks
-# --- ( thread ) --- enable threading error checks
-# 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
-# sse = yes/no --- -msse --- Use Intel Streaming SIMD Extensions
-# pext = yes/no --- -DUSE_PEXT --- Use pext x86_64 asm-instruction
+# debug = yes/no --- -DNDEBUG --- Enable/Disable debug mode
+# sanitize = none/<sanitizer> ... (-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
+# avxvnni = yes/no --- -mavxvnni --- Use Intel Vector Neural Network Instructions AVX
+# avx512 = yes/no --- -mavx512bw --- Use Intel Advanced Vector Extensions 512
+# vnni256 = yes/no --- -mavx256vnni --- Use Intel Vector Neural Network Instructions 512 with 256bit operands
+# vnni512 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 512
+# neon = yes/no --- -DUSE_NEON --- Use ARM SIMD architecture
+# dotprod = yes/no --- -DUSE_NEON_DOTPROD --- Use ARM advanced SIMD Int8 dot product instructions
#
# 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=yes sanitize="address undefined"
+
### 2.1. General and architecture defaults
+
+ifeq ($(ARCH),)
+ ARCH = native
+endif
+
+ifeq ($(ARCH), native)
+ override ARCH := $(shell $(SHELL) ../scripts/get_native_properties.sh | cut -d " " -f 1)
+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-avxvnni 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 armv8-dotprod apple-silicon general-64 general-32 riscv64 loongarch64))
+ SUPPORTED_ARCH=true
+else
+ SUPPORTED_ARCH=false
+endif
+
optimize = yes
debug = no
-sanitize = no
-bits = 32
+sanitize = none
+bits = 64
prefetch = no
popcnt = no
-sse = no
pext = no
+sse = no
+mmx = no
+sse2 = no
+ssse3 = no
+sse41 = no
+avx2 = no
+avxvnni = no
+avx512 = no
+vnni256 = no
+vnni512 = no
+neon = no
+dotprod = no
+arm_version = 0
+STRIP = strip
+
+ifneq ($(shell which clang-format-17 2> /dev/null),)
+ CLANG-FORMAT = clang-format-17
+else
+ CLANG-FORMAT = clang-format
+endif
### 2.2 Architecture specific
-ifeq ($(ARCH),general-32)
- arch = any
-endif
+ifeq ($(findstring x86,$(ARCH)),x86)
-ifeq ($(ARCH),x86-32-old)
+# x86-32/64
+
+ifeq ($(findstring x86-32,$(ARCH)),x86-32)
arch = i386
+ bits = 32
+ sse = no
+ mmx = yes
+else
+ arch = x86_64
+ sse = yes
+ sse2 = yes
endif
-ifeq ($(ARCH),x86-32)
- arch = i386
- prefetch = yes
+ifeq ($(findstring -sse,$(ARCH)),-sse)
sse = yes
endif
-ifeq ($(ARCH),general-64)
- arch = any
- bits = 64
+ifeq ($(findstring -popcnt,$(ARCH)),-popcnt)
+ popcnt = yes
endif
-ifeq ($(ARCH),x86-64)
- arch = x86_64
- bits = 64
- prefetch = yes
+ifeq ($(findstring -mmx,$(ARCH)),-mmx)
+ mmx = yes
+endif
+
+ifeq ($(findstring -sse2,$(ARCH)),-sse2)
sse = yes
+ sse2 = yes
endif
-ifeq ($(ARCH),x86-64-modern)
- arch = x86_64
- bits = 64
- prefetch = yes
+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)
+ $(warning *** ARCH=$(ARCH) is deprecated, defaulting to ARCH=x86-64-sse41-popcnt. Execute `make help` for a list of available architectures. ***)
+ $(shell sleep 5)
popcnt = yes
sse = yes
+ sse2 = yes
+ ssse3 = yes
+ sse41 = yes
endif
-ifeq ($(ARCH),x86-64-bmi2)
- arch = x86_64
- bits = 64
- prefetch = yes
+ifeq ($(findstring -avx2,$(ARCH)),-avx2)
+ popcnt = yes
+ sse = yes
+ sse2 = yes
+ ssse3 = yes
+ sse41 = yes
+ avx2 = yes
+endif
+
+ifeq ($(findstring -avxvnni,$(ARCH)),-avxvnni)
popcnt = yes
sse = yes
+ sse2 = yes
+ ssse3 = yes
+ sse41 = yes
+ avx2 = yes
+ avxvnni = yes
pext = 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
+ arm_version = 7
+endif
+
+ifeq ($(ARCH),armv7-neon)
+ arch = armv7
+ prefetch = yes
+ popcnt = yes
+ neon = yes
+ bits = 32
+ arm_version = 7
+endif
+
+ifeq ($(ARCH),armv8)
+ arch = armv8
+ prefetch = yes
+ popcnt = yes
+ neon = yes
+ arm_version = 8
+endif
+
+ifeq ($(ARCH),armv8-dotprod)
+ arch = armv8
+ prefetch = yes
+ popcnt = yes
+ neon = yes
+ dotprod = yes
+ arm_version = 8
+endif
+
+ifeq ($(ARCH),apple-silicon)
+ arch = arm64
+ prefetch = yes
+ popcnt = yes
+ neon = yes
+ dotprod = yes
+ arm_version = 8
endif
ifeq ($(ARCH),ppc-32)
arch = ppc
+ bits = 32
endif
ifeq ($(ARCH),ppc-64)
arch = ppc64
- bits = 64
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
+
+ifeq ($(ARCH),riscv64)
+ arch = riscv64
+endif
+
+ifeq ($(ARCH),loongarch64)
+ arch = loongarch64
+endif
+endif
+
### ==========================================================================
-### Section 3. Low-level configuration
+### Section 3. Low-level Configuration
### ==========================================================================
### 3.1 Selecting compiler (default = gcc)
+ifeq ($(MAKELEVEL),0)
+ export ENV_CXXFLAGS := $(CXXFLAGS)
+ export ENV_DEPENDFLAGS := $(DEPENDFLAGS)
+ export ENV_LDFLAGS := $(LDFLAGS)
+endif
-CXXFLAGS += -Wall -Wcast-qual -fno-exceptions -std=c++11 $(EXTRACXXFLAGS)
-DEPENDFLAGS += -std=c++11
-LDFLAGS += $(EXTRALDFLAGS)
+CXXFLAGS = $(ENV_CXXFLAGS) -Wall -Wcast-qual -fno-exceptions -std=c++17 $(EXTRACXXFLAGS)
+DEPENDFLAGS = $(ENV_DEPENDFLAGS) -std=c++17
+LDFLAGS = $(ENV_LDFLAGS) $(EXTRALDFLAGS)
ifeq ($(COMP),)
COMP=gcc
ifeq ($(COMP),gcc)
comp=gcc
CXX=g++
- CXXFLAGS += -pedantic -Wextra -Wshadow
+ CXXFLAGS += -pedantic -Wextra -Wmissing-declarations
- ifeq ($(ARCH),armv7)
+ ifeq ($(arch),$(filter $(arch),armv7 armv8 riscv64))
ifeq ($(OS),Android)
CXXFLAGS += -m$(bits)
LDFLAGS += -m$(bits)
endif
+ ifeq ($(ARCH),riscv64)
+ CXXFLAGS += -latomic
+ endif
+ else ifeq ($(ARCH),loongarch64)
+ CXXFLAGS += -latomic
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 ($(target_windows),yes)
+ LDFLAGS += -static
+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
+ ifeq ($(bits),64)
+ ifeq ($(shell which x86_64-w64-mingw32-c++-posix 2> /dev/null),)
+ CXX=x86_64-w64-mingw32-c++
else
- ifeq ($(shell which i686-w64-mingw32-c++-posix),)
- CXX=i686-w64-mingw32-c++
- else
- CXX=i686-w64-mingw32-c++-posix
- endif
+ CXX=x86_64-w64-mingw32-c++-posix
endif
else
- CXX=g++
+ ifeq ($(shell which i686-w64-mingw32-c++-posix 2> /dev/null),)
+ CXX=i686-w64-mingw32-c++
+ else
+ CXX=i686-w64-mingw32-c++-posix
+ endif
endif
-
- CXXFLAGS += -Wextra -Wshadow
- LDFLAGS += -static
+ CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-declarations
endif
-ifeq ($(COMP),icc)
- comp=icc
- CXX=icpc
- CXXFLAGS += -diag-disable 1476,10120 -Wcheck -Wabi -Wdeprecated -strict-ansi
+ifeq ($(COMP),icx)
+ comp=icx
+ CXX=icpx
+ CXXFLAGS += --intel -pedantic -Wextra -Wshadow -Wmissing-prototypes \
+ -Wconditional-uninitialized -Wabi -Wdeprecated
endif
ifeq ($(COMP),clang)
comp=clang
CXX=clang++
- CXXFLAGS += -pedantic -Wextra -Wshadow
+ ifeq ($(target_windows),yes)
+ CXX=x86_64-w64-mingw32-clang++
+ endif
- ifneq ($(KERNEL),Darwin)
- ifneq ($(KERNEL),OpenBSD)
+ CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-prototypes \
+ -Wconditional-uninitialized
+
+ ifeq ($(filter $(KERNEL),Darwin OpenBSD FreeBSD),)
+ ifeq ($(target_windows),)
+ ifneq ($(RTLIB),compiler-rt)
LDFLAGS += -latomic
endif
endif
+ endif
- ifeq ($(ARCH),armv7)
+ ifeq ($(arch),$(filter $(arch),armv7 armv8 riscv64))
ifeq ($(OS),Android)
CXXFLAGS += -m$(bits)
LDFLAGS += -m$(bits)
endif
+ ifeq ($(ARCH),riscv64)
+ CXXFLAGS += -latomic
+ endif
+ else ifeq ($(ARCH),loongarch64)
+ CXXFLAGS += -latomic
else
CXXFLAGS += -m$(bits)
LDFLAGS += -m$(bits)
endif
endif
-ifeq ($(comp),icc)
- profile_make = icc-profile-make
- profile_use = icc-profile-use
-else
-ifeq ($(comp),clang)
+ifeq ($(KERNEL),Darwin)
+ CXXFLAGS += -mmacosx-version-min=10.14
+ LDFLAGS += -mmacosx-version-min=10.14
+ ifneq ($(arch),any)
+ CXXFLAGS += -arch $(arch)
+ LDFLAGS += -arch $(arch)
+ endif
+ 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
+ ifneq ($(shell which arm-linux-androideabi-strip 2>/dev/null),)
+ STRIP=arm-linux-androideabi-strip
+ else
+ STRIP=llvm-strip
+ endif
+ endif
+ ifeq ($(arch),armv8)
+ CXX=aarch64-linux-android21-clang++
+ ifneq ($(shell which aarch64-linux-android-strip 2>/dev/null),)
+ STRIP=aarch64-linux-android-strip
+ else
+ STRIP=llvm-strip
+ endif
+ endif
+ LDFLAGS += -static-libstdc++ -pie -lm -latomic
+endif
+
+ifeq ($(comp),icx)
+ profile_make = icx-profile-make
+ profile_use = icx-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
-endif
-
-ifeq ($(KERNEL),Darwin)
- CXXFLAGS += -arch $(arch) -mmacosx-version-min=10.9
- LDFLAGS += -arch $(arch) -mmacosx-version-min=10.9
+ ifeq ($(KERNEL),Darwin)
+ EXTRAPROFILEFLAGS = -fvisibility=hidden
+ endif
endif
### Travis CI script uses COMPILER to overwrite CXX
CXX=$(COMPCXX)
endif
+### Sometimes gcc is really clang
+ifeq ($(COMP),gcc)
+ gccversion := $(shell $(CXX) --version 2>/dev/null)
+ 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)
- LDFLAGS += -lpthread
+ ifneq ($(COMP),ndk)
+ LDFLAGS += -lpthread
+ endif
endif
endif
endif
endif
### 3.2.2 Debugging with undefined behavior sanitizers
-ifneq ($(sanitize),no)
- CXXFLAGS += -g3 -fsanitize=$(sanitize) -fuse-ld=gold
- LDFLAGS += -fsanitize=$(sanitize) -fuse-ld=gold
+ifneq ($(sanitize),none)
+ CXXFLAGS += -g3 $(addprefix -fsanitize=,$(sanitize))
+ LDFLAGS += $(addprefix -fsanitize=,$(sanitize))
endif
### 3.3 Optimization
ifeq ($(optimize),yes)
- CXXFLAGS += -O3
+ CXXFLAGS += -O3 -g -funroll-loops
ifeq ($(comp),gcc)
ifeq ($(OS), Android)
endif
endif
- ifeq ($(comp),$(filter $(comp),gcc clang icc))
- ifeq ($(KERNEL),Darwin)
+ ifeq ($(KERNEL),Darwin)
+ ifeq ($(comp),$(filter $(comp),clang icx))
CXXFLAGS += -mdynamic-no-pic
endif
+
+ ifeq ($(comp),gcc)
+ ifneq ($(arch),arm64)
+ CXXFLAGS += -mdynamic-no-pic
+ endif
+ 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
CXXFLAGS += -DIS_64BIT
endif
-### 3.5 prefetch
+### 3.5 prefetch and popcount
ifeq ($(prefetch),yes)
ifeq ($(sse),yes)
CXXFLAGS += -msse
- DEPENDFLAGS += -msse
endif
else
CXXFLAGS += -DNO_PREFETCH
endif
-### 3.6 popcnt
ifeq ($(popcnt),yes)
- ifeq ($(arch),ppc64)
+ 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
+### 3.6 SIMD architectures
+ifeq ($(avx2),yes)
+ CXXFLAGS += -DUSE_AVX2
+ ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
+ CXXFLAGS += -mavx2 -mbmi
+ endif
+endif
+
+ifeq ($(avxvnni),yes)
+ CXXFLAGS += -DUSE_VNNI -DUSE_AVXVNNI
+ ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
+ CXXFLAGS += -mavxvnni
+ endif
+endif
+
+ifeq ($(avx512),yes)
+ CXXFLAGS += -DUSE_AVX512
+ ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
+ CXXFLAGS += -mavx512f -mavx512bw
+ endif
+endif
+
+ifeq ($(vnni256),yes)
+ CXXFLAGS += -DUSE_VNNI
+ ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
+ 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 icx))
+ CXXFLAGS += -mavx512f -mavx512bw -mavx512vnni -mavx512dq -mavx512vl -mprefer-vector-width=512
+ endif
+endif
+
+ifeq ($(sse41),yes)
+ CXXFLAGS += -DUSE_SSE41
+ ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
+ CXXFLAGS += -msse4.1
+ endif
+endif
+
+ifeq ($(ssse3),yes)
+ CXXFLAGS += -DUSE_SSSE3
+ ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
+ CXXFLAGS += -mssse3
+ endif
+endif
+
+ifeq ($(sse2),yes)
+ CXXFLAGS += -DUSE_SSE2
+ ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
+ CXXFLAGS += -msse2
+ endif
+endif
+
+ifeq ($(mmx),yes)
+ ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
+ CXXFLAGS += -mmmx
+ endif
+endif
+
+ifeq ($(neon),yes)
+ CXXFLAGS += -DUSE_NEON=$(arm_version)
+ ifeq ($(KERNEL),Linux)
+ ifneq ($(COMP),ndk)
+ ifneq ($(arch),armv8)
+ CXXFLAGS += -mfpu=neon
+ endif
+ endif
+ endif
+endif
+
+ifeq ($(dotprod),yes)
+ CXXFLAGS += -march=armv8.2-a+dotprod -DUSE_NEON_DOTPROD
+endif
+
### 3.7 pext
ifeq ($(pext),yes)
CXXFLAGS += -DUSE_PEXT
- ifeq ($(comp),$(filter $(comp),gcc clang mingw))
- CXXFLAGS += -msse4 -mbmi2
+ ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
+ CXXFLAGS += -mbmi2
endif
endif
-### 3.8 Link Time Optimization, it works since gcc 4.5 but not on mingw under Windows.
+### 3.8.1 Try to include git commit sha for versioning
+GIT_SHA := $(shell git rev-parse HEAD 2>/dev/null | cut -c 1-8)
+ifneq ($(GIT_SHA), )
+ CXXFLAGS += -DGIT_SHA=$(GIT_SHA)
+endif
+
+### 3.8.2 Try to include git commit date for versioning
+GIT_DATE := $(shell git show -s --date=format:'%Y%m%d' --format=%cd HEAD 2>/dev/null)
+ifneq ($(GIT_DATE), )
+ CXXFLAGS += -DGIT_DATE=$(GIT_DATE)
+endif
+
+### 3.8.3 Try to include architecture
+ifneq ($(ARCH), )
+ CXXFLAGS += -DARCH=$(ARCH)
+endif
+
+### 3.9 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),$(filter $(comp),gcc clang))
- CXXFLAGS += -flto
+ ifeq ($(comp),$(filter $(comp),clang icx))
+ CXXFLAGS += -flto=full
+ ifeq ($(comp),icx)
+ CXXFLAGS += -fwhole-program-vtables
+ endif
+ ifeq ($(target_windows),yes)
+ CXXFLAGS += -fuse-ld=lld
+ endif
LDFLAGS += $(CXXFLAGS)
- endif
- ifeq ($(comp),mingw)
- ifeq ($(KERNEL),Linux)
- CXXFLAGS += -flto
+# 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 -flto-partition=one
+ LDFLAGS += $(CXXFLAGS) -flto=jobserver
+ else
+ CXXFLAGS += -flto=full
LDFLAGS += $(CXXFLAGS)
endif
+
+# To use LTO and static linking on Windows,
+# the tool chain requires gcc version 10.1 or later.
+ else ifeq ($(comp),mingw)
+ CXXFLAGS += -flto -flto-partition=one
+ LDFLAGS += $(CXXFLAGS) -save-temps
endif
endif
endif
-### 3.9 Android 5 can only run position independent executables. Note that this
+### 3.10 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
+### Section 4. Public Targets
### ==========================================================================
help:
@echo ""
@echo "To compile stockfish, type: "
@echo ""
- @echo "make target ARCH=arch [COMP=compiler] [COMPCXX=cxx]"
+ @echo "make -j target [ARCH=arch] [COMP=compiler] [COMPCXX=cxx]"
@echo ""
@echo "Supported targets:"
@echo ""
- @echo "build > Standard build"
- @echo "profile-build > PGO build"
+ @echo "help > Display architecture details"
+ @echo "profile-build > standard build with profile-guided optimization"
+ @echo "build > skip profile-guided optimization"
+ @echo "net > Download the default nnue net"
@echo "strip > Strip executable"
@echo "install > Install executable"
@echo "clean > Clean up"
@echo ""
@echo "Supported archs:"
@echo ""
- @echo "x86-64-bmi2 > x86 64-bit with pext support (also enables SSE4)"
- @echo "x86-64-modern > x86 64-bit with popcnt support (also enables SSE3)"
- @echo "x86-64 > x86 64-bit generic"
- @echo "x86-32 > x86 32-bit (also enables SSE)"
- @echo "x86-32-old > x86 32-bit fall back for old hardware"
+ @echo "native > select the best architecture for the host processor (default)"
+ @echo "x86-64-vnni512 > x86 64-bit with vnni 512bit support"
+ @echo "x86-64-vnni256 > x86 64-bit with vnni 512bit support, limit operands to 256bit wide"
+ @echo "x86-64-avx512 > x86 64-bit with avx512 support"
+ @echo "x86-64-avxvnni > x86 64-bit with vnni 256bit 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 > deprecated, 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 compile 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 compile 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 "armv8-dotprod > ARMv8 64-bit with popcnt, neon and dot product support"
+ @echo "e2k > Elbrus 2000"
+ @echo "apple-silicon > Apple silicon ARM64"
@echo "general-64 > unspecified 64-bit"
@echo "general-32 > unspecified 32-bit"
+ @echo "riscv64 > RISC-V 64-bit"
+ @echo "loongarch64 > LoongArch 64-bit"
@echo ""
@echo "Supported compilers:"
@echo ""
- @echo "gcc > Gnu compiler (default)"
- @echo "mingw > Gnu compiler with MinGW under Windows"
+ @echo "gcc > GNU compiler (default)"
+ @echo "mingw > GNU compiler with MinGW under Windows"
@echo "clang > LLVM Clang compiler"
- @echo "icc > Intel compiler"
+ @echo "icx > Intel oneAPI DPC++/C++ 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 "Simple examples. If you don't know what to do, you likely want to run one of: "
@echo ""
- @echo "make build ARCH=x86-64 (This is for 64-bit systems)"
- @echo "make build ARCH=x86-32 (This is for 32-bit systems)"
+ @echo "make -j profile-build ARCH=x86-64-avx2 # typically a fast compile for common systems "
+ @echo "make -j profile-build ARCH=x86-64-sse41-popcnt # A more portable compile for 64-bit systems "
+ @echo "make -j profile-build ARCH=x86-64 # A portable compile for 64-bit systems "
@echo ""
@echo "Advanced examples, for experienced users: "
@echo ""
- @echo "make build ARCH=x86-64 COMP=clang"
- @echo "make profile-build ARCH=x86-64-bmi2 COMP=gcc COMPCXX=g++-4.8"
+ @echo "make -j profile-build ARCH=x86-64-avxvnni"
+ @echo "make -j profile-build ARCH=x86-64-avxvnni COMP=gcc COMPCXX=g++-12.0"
+ @echo "make -j build ARCH=x86-64-ssse3 COMP=clang"
@echo ""
+ifneq ($(SUPPORTED_ARCH), true)
+ @echo "Specify a supported architecture with the ARCH option for more details"
+ @echo ""
+endif
-.PHONY: help build profile-build strip install clean objclean profileclean help \
- config-sanity icc-profile-use icc-profile-make gcc-profile-use gcc-profile-make \
- clang-profile-use clang-profile-make
+.PHONY: help analyze build profile-build strip install clean net \
+ objclean profileclean config-sanity \
+ icx-profile-use icx-profile-make \
+ gcc-profile-use gcc-profile-make \
+ clang-profile-use clang-profile-make FORCE \
+ format analyze
-build: config-sanity
+analyze: net config-sanity objclean
+ $(MAKE) -k ARCH=$(ARCH) COMP=$(COMP) $(OBJS)
+
+build: net config-sanity
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) all
-profile-build: config-sanity objclean profileclean
+profile-build: 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
+ $(PGOBENCH) > PGOBENCH.out 2>&1
+ tail -n 4 PGOBENCH.out
@echo ""
@echo "Step 3/4. Building optimized executable ..."
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) objclean
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) profileclean
strip:
- strip $(EXE)
+ $(STRIP) $(EXE)
install:
-mkdir -p -m 755 $(BINDIR)
-cp $(EXE) $(BINDIR)
- -strip $(BINDIR)/$(EXE)
+ $(STRIP) $(BINDIR)/$(EXE)
-#clean all
+# clean all
clean: objclean profileclean
@rm -f .depend *~ core
# clean binaries and objects
objclean:
- @rm -f $(EXE) *.o ./syzygy/*.o
+ @rm -f stockfish stockfish.exe *.o ./syzygy/*.o ./nnue/*.o ./nnue/features/*.o
# clean auxiliary profiling files
profileclean:
@rm -rf profdir
- @rm -f bench.txt *.gcda ./syzygy/*.gcda *.gcno ./syzygy/*.gcno
+ @rm -f bench.txt *.gcda *.gcno ./syzygy/*.gcda ./nnue/*.gcda ./nnue/features/*.gcda *.s PGOBENCH.out
@rm -f stockfish.profdata *.profraw
-
+ @rm -f stockfish.*args*
+ @rm -f stockfish.*lt*
+ @rm -f stockfish.res
+ @rm -f ./-lstdc++.res
+
+# set up shell variables for the net stuff
+netvariables:
+ $(eval nnuenet := $(shell grep EvalFileDefaultName evaluate.h | grep define | sed 's/.*\(nn-[a-z0-9]\{12\}.nnue\).*/\1/'))
+ $(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))
+ $(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))
+
+# evaluation network (nnue)
+net: netvariables
+ @echo "Default net: $(nnuenet)"
+ @if [ "x$(curl_or_wget)" = "x" ]; then \
+ echo "Neither curl nor wget is installed. Install one of these tools unless the net has been downloaded manually"; \
+ fi
+ @if [ "x$(shasum_command)" = "x" ]; then \
+ echo "shasum / sha256sum not found, skipping net validation"; \
+ elif test -f "$(nnuenet)"; then \
+ if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \
+ echo "Removing invalid network"; rm -f $(nnuenet); \
+ fi; \
+ fi;
+ @for nnuedownloadurl in "$(nnuedownloadurl1)" "$(nnuedownloadurl2)"; do \
+ if test -f "$(nnuenet)"; then \
+ echo "$(nnuenet) available : OK"; break; \
+ else \
+ if [ "x$(curl_or_wget)" != "x" ]; then \
+ echo "Downloading $${nnuedownloadurl}"; $(curl_or_wget) $${nnuedownloadurl} > $(nnuenet);\
+ else \
+ echo "No net found and download not possible"; exit 1;\
+ 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); \
+ fi; \
+ fi; \
+ done
+ @if ! test -f "$(nnuenet)"; then \
+ echo "Failed to download $(nnuenet)."; \
+ fi;
+ @if [ "x$(shasum_command)" != "x" ]; then \
+ if [ "$(nnuenet)" = "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \
+ echo "Network validated"; break; \
+ fi; \
+ fi; \
+
+format:
+ $(CLANG-FORMAT) -i $(SRCS) $(HEADERS) -style=file
+
+# default target
default:
help
### ==========================================================================
-### Section 5. Private targets
+### Section 5. Private Targets
### ==========================================================================
-all: $(EXE) .depend
+all: $(EXE) client .depend
-config-sanity:
+config-sanity: net
@echo ""
@echo "Config:"
@echo "debug: '$(debug)'"
@echo "os: '$(OS)'"
@echo "prefetch: '$(prefetch)'"
@echo "popcnt: '$(popcnt)'"
- @echo "sse: '$(sse)'"
@echo "pext: '$(pext)'"
+ @echo "sse: '$(sse)'"
+ @echo "mmx: '$(mmx)'"
+ @echo "sse2: '$(sse2)'"
+ @echo "ssse3: '$(ssse3)'"
+ @echo "sse41: '$(sse41)'"
+ @echo "avx2: '$(avx2)'"
+ @echo "avxvnni: '$(avxvnni)'"
+ @echo "avx512: '$(avx512)'"
+ @echo "vnni256: '$(vnni256)'"
+ @echo "vnni512: '$(vnni512)'"
+ @echo "neon: '$(neon)'"
+ @echo "dotprod: '$(dotprod)'"
+ @echo "arm_version: '$(arm_version)'"
+ @echo "target_windows: '$(target_windows)'"
@echo ""
@echo "Flags:"
@echo "CXX: $(CXX)"
@echo "Testing config sanity. If this fails, try 'make help' ..."
@echo ""
@test "$(debug)" = "yes" || test "$(debug)" = "no"
- @test "$(sanitize)" = "undefined" || test "$(sanitize)" = "thread" || test "$(sanitize)" = "address" || test "$(sanitize)" = "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)" = "armv7"
+ test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || test "$(arch)" = "e2k" || \
+ test "$(arch)" = "armv7" || test "$(arch)" = "armv8" || test "$(arch)" = "arm64" || test "$(arch)" = "riscv64" || test "$(arch)" = "loongarch64"
@test "$(bits)" = "32" || test "$(bits)" = "64"
@test "$(prefetch)" = "yes" || test "$(prefetch)" = "no"
@test "$(popcnt)" = "yes" || test "$(popcnt)" = "no"
- @test "$(sse)" = "yes" || test "$(sse)" = "no"
@test "$(pext)" = "yes" || test "$(pext)" = "no"
- @test "$(comp)" = "gcc" || test "$(comp)" = "icc" || test "$(comp)" = "mingw" || test "$(comp)" = "clang"
+ @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)" = "icx" || test "$(comp)" = "mingw" || test "$(comp)" = "clang" \
+ || test "$(comp)" = "armv7a-linux-androideabi16-clang" || test "$(comp)" = "aarch64-linux-android21-clang"
$(EXE): $(OBJS)
- $(CXX) -o $@ $(OBJS) $(LDFLAGS)
+ +$(CXX) -o $@ $(OBJS) $(LDFLAGS)
+
+# Force recompilation to ensure version info is up-to-date
+misc.o: FORCE
+FORCE:
clang-profile-make:
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
all
clang-profile-use:
- llvm-profdata merge -output=stockfish.profdata *.profraw
+ $(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' \
+ EXTRACXXFLAGS='-fprofile-generate=profdir' \
+ EXTRACXXFLAGS+=$(EXTRAPROFILEFLAGS) \
EXTRALDFLAGS='-lgcov' \
all
gcc-profile-use:
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
- EXTRACXXFLAGS='-fprofile-use -fno-peel-loops -fno-tracer' \
+ EXTRACXXFLAGS='-fprofile-use=profdir -fno-peel-loops -fno-tracer' \
+ EXTRACXXFLAGS+=$(EXTRAPROFILEFLAGS) \
EXTRALDFLAGS='-lgcov' \
all
-icc-profile-make:
- @mkdir -p profdir
+icx-profile-make:
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
- EXTRACXXFLAGS='-prof-gen=srcpos -prof_dir ./profdir' \
+ EXTRACXXFLAGS='-fprofile-instr-generate ' \
+ EXTRALDFLAGS=' -fprofile-instr-generate' \
all
-icc-profile-use:
+icx-profile-use:
+ $(XCRUN) llvm-profdata merge -output=stockfish.profdata *.profraw
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
- EXTRACXXFLAGS='-prof_use -prof_dir ./profdir' \
+ EXTRACXXFLAGS='-fprofile-instr-use=stockfish.profdata' \
+ EXTRALDFLAGS='-fprofile-use ' \
all
-.depend:
- -@$(CXX) $(DEPENDFLAGS) -MM $(OBJS:.o=.cpp) > $@ 2> /dev/null
+### GRPC
--include .depend
+PROTOS_PATH = .
+PROTOC = protoc
+GRPC_CPP_PLUGIN = grpc_cpp_plugin
+GRPC_CPP_PLUGIN_PATH ?= `which $(GRPC_CPP_PLUGIN)`
+
+%.grpc.pb.h %.grpc.pb.cc: %.proto
+ $(PROTOC) -I $(PROTOS_PATH) --grpc_out=. --plugin=protoc-gen-grpc=$(GRPC_CPP_PLUGIN_PATH) $<
+
+# oh my
+%.cpp: %.cc
+ cp $< $@
+%.pb.h %.pb.cc: %.proto
+ $(PROTOC) -I $(PROTOS_PATH) --cpp_out=. $<
+
+#LDFLAGS += -Wl,-Bstatic -Wl,-\( -lprotobuf -lgrpc++_unsecure -lgrpc_unsecure -lgrpc -lz -Wl,-\) -Wl,-Bdynamic -ldl
+LDFLAGS += -Wl,--start-group /usr/lib/x86_64-linux-gnu/libprotobuf.a /usr/lib/x86_64-linux-gnu/libgrpc++_unsecure.a /usr/lib/x86_64-linux-gnu/libgrpc_unsecure.a /usr/lib/x86_64-linux-gnu/libgrpc.a /usr/lib/x86_64-linux-gnu/libaddress_sorting.a /usr/lib/x86_64-linux-gnu/libupb.a /usr/lib/x86_64-linux-gnu/libcares.a /usr/lib/x86_64-linux-gnu/libgpr.a /usr/lib/x86_64-linux-gnu/libabsl_*.a -Wl,--end-group -ldl -lz
+#LDFLAGS += /usr/lib/x86_64-linux-gnu/libprotobuf.a /usr/lib/libgrpc++_unsecure.a /usr/lib/libgrpc_unsecure.a /usr/lib/libgrpc.a /usr/lib/x86_64-linux-gnu/libcares.a -ldl -lz
+
+client: $(CLIOBJS)
+ $(CXX) -o $@ $(CLIOBJS) $(LDFLAGS)
+
+# Other stuff
+
+.depend: $(SRCS)
+ -@$(CXX) $(DEPENDFLAGS) -MM $(SRCS) > $@ 2> /dev/null
+
+ifeq (, $(filter $(MAKECMDGOALS), help strip install clean net objclean profileclean config-sanity))
+-include .depend
+endif
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+ Copyright (C) 2004-2023 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
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "benchmark.h"
+
+#include <cstdlib>
#include <fstream>
#include <iostream>
-#include <istream>
#include <vector>
#include "position.h"
-using namespace std;
-
namespace {
-const vector<string> Defaults = {
+// clang-format off
+const std::vector<std::string> 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",
"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
// Chess 960
"setoption name UCI_Chess960 value true",
- "bbqnnrkr/pppppppp/8/8/8/8/PPPPPPPP/BBQNNRKR w KQkq - 0 1 moves g2g3 d7d5 d2d4 c8h3 c1g5 e8d6 g5e7 f7f6",
+ "bbqnnrkr/pppppppp/8/8/8/8/PPPPPPPP/BBQNNRKR w HFhf - 0 1 moves g2g3 d7d5 d2d4 c8h3 c1g5 e8d6 g5e7 f7f6",
+ "nqbnrkrb/pppppppp/8/8/8/8/PPPPPPPP/NQBNRKRB w KQkq - 0 1",
"setoption name UCI_Chess960 value false"
};
-
-} // namespace
-
-/// 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 and the type of the limit:
-/// depth, perft, nodes and movetime (in millisecs).
-///
-/// 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<string> setup_bench(const Position& current, istream& is) {
-
- vector<string> fens, list;
- string go, token;
-
- // 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";
-
- go = limitType == "eval" ? "eval" : "go " + limitType + " " + limit;
-
- if (fenFile == "default")
- 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("ucinewgame");
-
- for (const string& fen : fens)
- if (fen.find("setoption") != string::npos)
- list.emplace_back(fen);
- else
- {
- list.emplace_back("position fen " + fen);
- list.emplace_back(go);
- }
-
- return list;
+// clang-format on
+
+} // namespace
+
+namespace Stockfish {
+
+// 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, and the type of the limit:
+// depth, perft, nodes and movetime (in milliseconds). Examples:
+//
+// bench : search default positions up to depth 13
+// bench 64 1 15 : search default positions up to depth 15 (TT = 64MB)
+// bench 64 1 100000 default nodes : search default positions for 100K nodes each
+// bench 64 4 5000 current movetime : search current position with 4 threads for 5 sec
+// bench 16 1 5 blah perft : run a perft 5 on positions in file "blah"
+std::vector<std::string> setup_bench(const Position& current, std::istream& is) {
+
+ std::vector<std::string> fens, list;
+ std::string go, token;
+
+ // Assign default values to missing arguments
+ std::string ttSize = (is >> token) ? token : "16";
+ std::string threads = (is >> token) ? token : "1";
+ std::string limit = (is >> token) ? token : "13";
+ std::string fenFile = (is >> token) ? token : "default";
+ std::string limitType = (is >> token) ? token : "depth";
+
+ go = limitType == "eval" ? "eval" : "go " + limitType + " " + limit;
+
+ if (fenFile == "default")
+ fens = Defaults;
+
+ else if (fenFile == "current")
+ fens.push_back(current.fen());
+
+ else
+ {
+ std::string fen;
+ std::ifstream file(fenFile);
+
+ if (!file.is_open())
+ {
+ std::cerr << "Unable to open file " << fenFile << std::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("ucinewgame");
+
+ for (const std::string& fen : fens)
+ if (fen.find("setoption") != std::string::npos)
+ list.emplace_back(fen);
+ else
+ {
+ list.emplace_back("position fen " + fen);
+ list.emplace_back(go);
+ }
+
+ return list;
}
+
+} // namespace Stockfish
\ No newline at end of file
--- /dev/null
+/*
+ Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+ Copyright (C) 2004-2023 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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef BENCHMARK_H_INCLUDED
+#define BENCHMARK_H_INCLUDED
+
+#include <iosfwd>
+#include <string>
+#include <vector>
+
+namespace Stockfish {
+
+class Position;
+
+std::vector<std::string> setup_bench(const Position&, std::istream&);
+
+} // namespace Stockfish
+
+#endif // #ifndef BENCHMARK_H_INCLUDED
+++ /dev/null
-/*
- Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
-
- 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 <http://www.gnu.org/licenses/>.
-*/
-
-#include <cassert>
-#include <numeric>
-#include <vector>
-
-#include "bitboard.h"
-#include "types.h"
-
-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
-
- // Each uint32_t stores results of 32 positions, one per bit
- uint32_t KPKBitbase[MAX_INDEX / 32];
-
- // 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 us, Square bksq, Square wksq, Square psq) {
- return int(wksq) | (bksq << 6) | (us << 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<KPKPosition>& db)
- { return us == WHITE ? classify<WHITE>(db) : classify<BLACK>(db); }
-
- template<Color Us> Result classify(const std::vector<KPKPosition>& db);
-
- Color us;
- Square ksq[COLOR_NB], psq;
- Result result;
- };
-
-} // namespace
-
-
-bool Bitbases::probe(Square wksq, Square wpsq, Square bksq, Color us) {
-
- assert(file_of(wpsq) <= FILE_D);
-
- unsigned idx = index(us, bksq, wksq, wpsq);
- return KPKBitbase[idx / 32] & (1 << (idx & 0x1F));
-}
-
-
-void Bitbases::init() {
-
- std::vector<KPKPosition> 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);
-
- // Map 32 results into one KPKBitbase[] entry
- for (idx = 0; idx < MAX_INDEX; ++idx)
- if (db[idx] == WIN)
- KPKBitbase[idx / 32] |= 1 << (idx & 0x1F);
-}
-
-
-namespace {
-
- KPKPosition::KPKPosition(unsigned idx) {
-
- ksq[WHITE] = Square((idx >> 0) & 0x3F);
- ksq[BLACK] = Square((idx >> 6) & 0x3F);
- us = Color ((idx >> 12) & 0x01);
- psq = make_square(File((idx >> 13) & 0x3), Rank(RANK_7 - ((idx >> 15) & 0x7)));
-
- // Check 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
- || (us == WHITE && (PawnAttacks[WHITE][psq] & ksq[BLACK])))
- result = INVALID;
-
- // Immediate win if a pawn can be promoted without getting captured
- else if ( us == WHITE
- && rank_of(psq) == RANK_7
- && ksq[us] != psq + NORTH
- && ( distance(ksq[~us], psq + NORTH) > 1
- || (PseudoAttacks[KING][ksq[us]] & (psq + NORTH))))
- result = WIN;
-
- // Immediate draw if it is a stalemate or a king captures undefended pawn
- else if ( us == BLACK
- && ( !(PseudoAttacks[KING][ksq[us]] & ~(PseudoAttacks[KING][ksq[~us]] | PawnAttacks[~us][psq]))
- || (PseudoAttacks[KING][ksq[us]] & psq & ~PseudoAttacks[KING][ksq[~us]])))
- result = DRAW;
-
- // Position will be classified later
- else
- result = UNKNOWN;
- }
-
- template<Color Us>
- Result KPKPosition::classify(const std::vector<KPKPosition>& 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.
-
- constexpr Color Them = (Us == WHITE ? BLACK : WHITE);
- constexpr Result Good = (Us == WHITE ? WIN : DRAW);
- constexpr Result Bad = (Us == WHITE ? DRAW : WIN);
-
- Result r = INVALID;
- Bitboard b = PseudoAttacks[KING][ksq[Us]];
-
- while (b)
- r |= Us == WHITE ? db[index(Them, ksq[Them] , pop_lsb(&b), psq)]
- : db[index(Them, pop_lsb(&b), ksq[Them] , psq)];
-
- if (Us == WHITE)
- {
- if (rank_of(psq) < RANK_7) // Single push
- r |= db[index(Them, ksq[Them], ksq[Us], psq + NORTH)];
-
- if ( rank_of(psq) == RANK_2 // Double push
- && psq + NORTH != ksq[Us]
- && psq + NORTH != ksq[Them])
- r |= db[index(Them, ksq[Them], ksq[Us], psq + NORTH + NORTH)];
- }
-
- return result = r & Good ? Good : r & UNKNOWN ? UNKNOWN : Bad;
- }
-
-} // namespace
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+ Copyright (C) 2004-2023 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
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "bitboard.h"
+
#include <algorithm>
#include <bitset>
+#include <initializer_list>
-#include "bitboard.h"
#include "misc.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[PIECE_TYPE_NB][SQUARE_NB];
Bitboard PawnAttacks[COLOR_NB][SQUARE_NB];
namespace {
- Bitboard RookTable[0x19000]; // To store rook attacks
- Bitboard BishopTable[0x1480]; // To store bishop attacks
+Bitboard RookTable[0x19000]; // To store rook attacks
+Bitboard BishopTable[0x1480]; // To store bishop attacks
+
+void init_magics(PieceType pt, Bitboard table[], Magic magics[]);
- void init_magics(Bitboard table[], Magic magics[], Direction directions[]);
}
+// Returns the bitboard of target square for the given step
+// from the given square. If the step is off the board, returns empty bitboard.
+inline Bitboard safe_destination(Square s, int step) {
+ Square to = Square(s + step);
+ return is_ok(to) && distance(s, to) <= 2 ? square_bb(to) : Bitboard(0);
+}
-/// Bitboards::pretty() returns an ASCII representation of a bitboard suitable
-/// to be printed to standard output. Useful for debugging.
-const std::string Bitboards::pretty(Bitboard b) {
+// 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";
+ std::string s = "+---+---+---+---+---+---+---+---+\n";
- for (Rank r = RANK_8; r >= RANK_1; --r)
- {
- for (File f = FILE_A; f <= FILE_H; ++f)
- s += b & make_square(f, r) ? "| X " : "| ";
+ for (Rank r = RANK_8; r >= RANK_1; --r)
+ {
+ for (File f = FILE_A; f <= FILE_H; ++f)
+ s += b & make_square(f, r) ? "| X " : "| ";
- s += "|\n+---+---+---+---+---+---+---+---+\n";
- }
+ s += "| " + std::to_string(1 + r) + "\n+---+---+---+---+---+---+---+---+\n";
+ }
+ s += " a b c d e f g h\n";
- return s;
+ return s;
}
-/// Bitboards::init() initializes various bitboard tables. It is called at
-/// startup and relies on global objects to be already zero-initialized.
-
+// 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] = std::bitset<16>(i).count();
-
- for (Square s = SQ_A1; s <= SQ_H8; ++s)
- SquareBB[s] = (1ULL << s);
-
- for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
- for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
- SquareDistance[s1][s2] = std::max(distance<File>(s1, s2), distance<Rank>(s1, s2));
-
- for (Square s = SQ_A1; s <= SQ_H8; ++s)
- {
- PawnAttacks[WHITE][s] = pawn_attacks_bb<WHITE>(square_bb(s));
- PawnAttacks[BLACK][s] = pawn_attacks_bb<BLACK>(square_bb(s));
- }
-
- // Helper returning the target bitboard of a step from a square
- auto landing_square_bb = [&](Square s, int step)
- {
- Square to = Square(s + step);
- return is_ok(to) && distance(s, to) <= 2 ? square_bb(to) : Bitboard(0);
- };
-
- for (Square s = SQ_A1; s <= SQ_H8; ++s)
- {
- for (int step : {-9, -8, -7, -1, 1, 7, 8, 9} )
- PseudoAttacks[KING][s] |= landing_square_bb(s, step);
-
- for (int step : {-17, -15, -10, -6, 6, 10, 15, 17} )
- PseudoAttacks[KNIGHT][s] |= landing_square_bb(s, step);
- }
-
- Direction RookDirections[] = { NORTH, EAST, SOUTH, WEST };
- Direction BishopDirections[] = { NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST };
-
- init_magics(RookTable, RookMagics, RookDirections);
- init_magics(BishopTable, BishopMagics, BishopDirections);
-
- for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
- {
- PseudoAttacks[QUEEN][s1] = PseudoAttacks[BISHOP][s1] = attacks_bb<BISHOP>(s1, 0);
- PseudoAttacks[QUEEN][s1] |= PseudoAttacks[ ROOK][s1] = attacks_bb< ROOK>(s1, 0);
-
- for (PieceType pt : { BISHOP, ROOK })
- for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
- if (PseudoAttacks[pt][s1] & s2)
- LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2;
- }
-}
+ for (unsigned i = 0; i < (1 << 16); ++i)
+ PopCnt16[i] = uint8_t(std::bitset<16>(i).count());
+ for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
+ for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
+ SquareDistance[s1][s2] = std::max(distance<File>(s1, s2), distance<Rank>(s1, s2));
-namespace {
+ init_magics(ROOK, RookTable, RookMagics);
+ init_magics(BISHOP, BishopTable, BishopMagics);
- Bitboard sliding_attack(Direction directions[], Square sq, Bitboard occupied) {
+ for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
+ {
+ PawnAttacks[WHITE][s1] = pawn_attacks_bb<WHITE>(square_bb(s1));
+ PawnAttacks[BLACK][s1] = pawn_attacks_bb<BLACK>(square_bb(s1));
- Bitboard attack = 0;
+ for (int step : {-9, -8, -7, -1, 1, 7, 8, 9})
+ PseudoAttacks[KING][s1] |= safe_destination(s1, step);
- for (int i = 0; i < 4; ++i)
- for (Square s = sq + directions[i];
- is_ok(s) && distance(s, s - directions[i]) == 1;
- s += directions[i])
- {
- attack |= s;
+ for (int step : {-17, -15, -10, -6, 6, 10, 15, 17})
+ PseudoAttacks[KNIGHT][s1] |= safe_destination(s1, step);
- if (occupied & s)
- break;
- }
+ PseudoAttacks[QUEEN][s1] = PseudoAttacks[BISHOP][s1] = attacks_bb<BISHOP>(s1, 0);
+ PseudoAttacks[QUEEN][s1] |= PseudoAttacks[ROOK][s1] = attacks_bb<ROOK>(s1, 0);
- return attack;
- }
+ for (PieceType pt : {BISHOP, ROOK})
+ for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
+ {
+ if (PseudoAttacks[pt][s1] & s2)
+ {
+ LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2;
+ BetweenBB[s1][s2] =
+ (attacks_bb(pt, s1, square_bb(s2)) & attacks_bb(pt, s2, square_bb(s1)));
+ }
+ BetweenBB[s1][s2] |= s2;
+ }
+ }
+}
+namespace {
+
+Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) {
- // 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.
+ Bitboard attacks = 0;
+ Direction RookDirections[4] = {NORTH, SOUTH, EAST, WEST};
+ Direction BishopDirections[4] = {NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST};
- void init_magics(Bitboard table[], Magic magics[], Direction directions[]) {
+ for (Direction d : (pt == ROOK ? RookDirections : BishopDirections))
+ {
+ Square s = sq;
+ while (safe_destination(s, d) && !(occupied & s))
+ attacks |= (s += d);
+ }
+
+ return attacks;
+}
+
+
+// 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.
+void init_magics(PieceType pt, Bitboard table[], Magic magics[]) {
// Optimal PRNG seeds to pick the correct magics in the shortest time
- int seeds[][RANK_NB] = { { 8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020 },
- { 728, 10316, 55013, 32803, 12281, 15100, 16645, 255 } };
+ int seeds[][RANK_NB] = {{8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020},
+ {728, 10316, 55013, 32803, 12281, 15100, 16645, 255}};
Bitboard occupancy[4096], reference[4096], edges, b;
- int epoch[4096] = {}, cnt = 0, size = 0;
+ int epoch[4096] = {}, cnt = 0, size = 0;
for (Square s = SQ_A1; s <= SQ_H8; ++s)
{
// 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];
- m.mask = sliding_attack(directions, s, 0) & ~edges;
- m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask);
+ m.mask = sliding_attack(pt, s, 0) & ~edges;
+ m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask);
// Set the offset for the attacks table of the square. We have individual
// table sizes for each square with "Fancy Magic Bitboards".
// Use Carry-Rippler trick to enumerate all subsets of masks[s] and
// store the corresponding sliding attack bitboard in reference[].
b = size = 0;
- do {
+ do
+ {
occupancy[size] = b;
- reference[size] = sliding_attack(directions, s, b);
+ reference[size] = sliding_attack(pt, s, b);
if (HasPext)
m.attacks[pext(b, m.mask)] = reference[size];
// 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 (int i = 0; i < size;)
{
- for (m.magic = 0; popcount((m.magic * m.mask) >> 56) < 6; )
+ for (m.magic = 0; popcount((m.magic * m.mask) >> 56) < 6;)
m.magic = rng.sparse_rand<Bitboard>();
// A good magic must map every possible occupancy to an index that
if (epoch[idx] < cnt)
{
- epoch[idx] = cnt;
+ epoch[idx] = cnt;
m.attacks[idx] = reference[i];
}
else if (m.attacks[idx] != reference[i])
}
}
}
- }
}
+}
+
+} // namespace Stockfish
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+ Copyright (C) 2004-2023 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
#ifndef BITBOARD_H_INCLUDED
#define BITBOARD_H_INCLUDED
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <cstdint>
+#include <cstdlib>
#include <string>
#include "types.h"
-namespace Bitbases {
-
-void init();
-bool probe(Square wksq, Square wpsq, Square bksq, Color us);
-
-}
+namespace Stockfish {
namespace Bitboards {
-void init();
-const std::string pretty(Bitboard b);
+void init();
+std::string pretty(Bitboard b);
-}
-
-constexpr Bitboard AllSquares = ~Bitboard(0);
-constexpr Bitboard DarkSquares = 0xAA55AA55AA55AA55ULL;
+} // namespace Stockfish::Bitboards
constexpr Bitboard FileABB = 0x0101010101010101ULL;
constexpr Bitboard FileBBB = FileABB << 1;
constexpr Bitboard Rank7BB = Rank1BB << (8 * 6);
constexpr Bitboard Rank8BB = Rank1BB << (8 * 7);
-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[PIECE_TYPE_NB][SQUARE_NB];
extern Bitboard PawnAttacks[COLOR_NB][SQUARE_NB];
-/// Magic holds all magic bitboards relevant data for a single square
+// Magic holds all magic bitboards relevant data for a single square
struct Magic {
- Bitboard mask;
- Bitboard magic;
- Bitboard* attacks;
- unsigned shift;
+ Bitboard mask;
+ Bitboard magic;
+ Bitboard* attacks;
+ unsigned shift;
- // Compute the attack's index using the 'magic bitboards' approach
- unsigned index(Bitboard occupied) const {
+ // Compute the attack's index using the 'magic bitboards' approach
+ unsigned index(Bitboard occupied) const {
- if (HasPext)
- return unsigned(pext(occupied, mask));
+ if (HasPext)
+ return unsigned(pext(occupied, mask));
- if (Is64Bit)
- return unsigned(((occupied & mask) * magic) >> shift);
+ if (Is64Bit)
+ return unsigned(((occupied & mask) * magic) >> shift);
- unsigned lo = unsigned(occupied) & unsigned(mask);
- unsigned hi = unsigned(occupied >> 32) & unsigned(mask >> 32);
- return (lo * unsigned(magic) ^ hi * unsigned(magic >> 32)) >> shift;
- }
+ 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 RookMagics[SQUARE_NB];
extern Magic BishopMagics[SQUARE_NB];
inline Bitboard square_bb(Square s) {
- assert(s >= SQ_A1 && s <= SQ_H8);
- return SquareBB[s];
+ assert(is_ok(s));
+ return (1ULL << 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); }
+// 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&(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 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 s, Square s2) { return square_bb(s) | square_bb(s2); }
+inline Bitboard operator|(Square s1, Square s2) { return square_bb(s1) | s2; }
-constexpr bool more_than_one(Bitboard b) {
- return b & (b - 1);
-}
+constexpr bool more_than_one(Bitboard b) { return b & (b - 1); }
-inline bool opposite_colors(Square s1, Square s2) {
- return bool(DarkSquares & s1) != bool(DarkSquares & s2);
-}
+// rank_bb() and file_bb() return a bitboard representing all the squares on
+// the given file or rank.
-/// 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 << (8 * r); }
-inline Bitboard rank_bb(Rank r) {
- return Rank1BB << (8 * r);
-}
+constexpr Bitboard rank_bb(Square s) { return rank_bb(rank_of(s)); }
-inline Bitboard rank_bb(Square s) {
- return rank_bb(rank_of(s));
-}
+constexpr Bitboard file_bb(File f) { return FileABB << f; }
-inline Bitboard file_bb(File f) {
- return FileABB << f;
-}
-
-inline Bitboard file_bb(Square s) {
- return file_bb(file_of(s));
-}
+constexpr Bitboard file_bb(Square s) { return file_bb(file_of(s)); }
-/// shift() moves a bitboard one step along direction D
-
+// Moves a bitboard one or two steps as specified by the direction D
template<Direction D>
constexpr Bitboard shift(Bitboard b) {
- return D == NORTH ? b << 8 : D == SOUTH ? b >> 8
- : D == NORTH+NORTH? b <<16 : D == SOUTH+SOUTH? b >>16
- : D == EAST ? (b & ~FileHBB) << 1 : D == WEST ? (b & ~FileABB) >> 1
- : D == NORTH_EAST ? (b & ~FileHBB) << 9 : D == NORTH_WEST ? (b & ~FileABB) << 7
- : D == SOUTH_EAST ? (b & ~FileHBB) >> 7 : D == SOUTH_WEST ? (b & ~FileABB) >> 9
- : 0;
+ return D == NORTH ? b << 8
+ : D == SOUTH ? b >> 8
+ : D == NORTH + NORTH ? b << 16
+ : D == SOUTH + SOUTH ? b >> 16
+ : D == EAST ? (b & ~FileHBB) << 1
+ : D == WEST ? (b & ~FileABB) >> 1
+ : D == NORTH_EAST ? (b & ~FileHBB) << 9
+ : D == NORTH_WEST ? (b & ~FileABB) << 7
+ : D == SOUTH_EAST ? (b & ~FileHBB) >> 7
+ : D == SOUTH_WEST ? (b & ~FileABB) >> 9
+ : 0;
}
-/// pawn_attacks_bb() returns the squares attacked by pawns of the given color
-/// from the squares in the given bitboard.
-
+// Returns the squares attacked by pawns of the given color
+// from the squares in the given bitboard.
template<Color C>
constexpr Bitboard pawn_attacks_bb(Bitboard b) {
- return C == WHITE ? shift<NORTH_WEST>(b) | shift<NORTH_EAST>(b)
- : shift<SOUTH_WEST>(b) | shift<SOUTH_EAST>(b);
+ return C == WHITE ? shift<NORTH_WEST>(b) | shift<NORTH_EAST>(b)
+ : shift<SOUTH_WEST>(b) | shift<SOUTH_EAST>(b);
}
+inline Bitboard pawn_attacks_bb(Color c, Square s) {
-/// pawn_double_attacks_bb() returns the squares doubly attacked by pawns of the
-/// given color from the squares in the given bitboard.
-
-template<Color C>
-constexpr Bitboard pawn_double_attacks_bb(Bitboard b) {
- return C == WHITE ? shift<NORTH_WEST>(b) & shift<NORTH_EAST>(b)
- : shift<SOUTH_WEST>(b) & shift<SOUTH_EAST>(b);
+ assert(is_ok(s));
+ return PawnAttacks[c][s];
}
+// 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) {
-/// adjacent_files_bb() returns a bitboard representing all the squares on the
-/// adjacent files of the given one.
+ assert(is_ok(s1) && is_ok(s2));
-inline Bitboard adjacent_files_bb(Square s) {
- return shift<EAST>(file_bb(s)) | shift<WEST>(file_bb(s));
+ return LineBB[s1][s2];
}
-/// between_bb() returns squares that are linearly between the given squares
-/// If the given squares are not on a same file/rank/diagonal, return 0.
-
+// 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) {
- return LineBB[s1][s2] & ( (AllSquares << (s1 + (s1 < s2)))
- ^(AllSquares << (s2 + !(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.
+ assert(is_ok(s1) && is_ok(s2));
-inline Bitboard forward_ranks_bb(Color c, Square s) {
- return c == WHITE ? ~Rank1BB << 8 * (rank_of(s) - RANK_1)
- : ~Rank8BB >> 8 * (RANK_8 - rank_of(s));
+ return BetweenBB[s1][s2];
}
+// 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; }
-/// 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.
-
-inline Bitboard forward_file_bb(Color c, Square s) {
- return forward_ranks_bb(c, s) & file_bb(s);
-}
+// distance() functions return the distance between x and y, defined as the
+// number of steps for a king in x to reach y.
-/// 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.
+template<typename T1 = Square>
+inline int distance(Square x, Square y);
-inline Bitboard pawn_attack_span(Color c, Square s) {
- return forward_ranks_bb(c, s) & adjacent_files_bb(s);
+template<>
+inline int distance<File>(Square x, Square y) {
+ return std::abs(file_of(x) - file_of(y));
}
-
-/// 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.
-
-inline Bitboard passed_pawn_span(Color c, Square s) {
- return forward_ranks_bb(c, s) & (adjacent_files_bb(s) | file_bb(s));
+template<>
+inline int distance<Rank>(Square x, Square y) {
+ return std::abs(rank_of(x) - rank_of(y));
}
-
-/// 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 LineBB[s1][s2] & s3;
+template<>
+inline int distance<Square>(Square x, Square y) {
+ return SquareDistance[x][y];
}
+inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); }
-/// distance() functions return the distance between x and y, defined as the
-/// number of steps for a king in x to reach y.
+// Returns the pseudo attacks of the given piece type
+// assuming an empty board.
+template<PieceType Pt>
+inline Bitboard attacks_bb(Square s) {
-template<typename T1 = Square> inline int distance(Square x, Square y);
-template<> inline int distance<File>(Square x, Square y) { return std::abs(file_of(x) - file_of(y)); }
-template<> inline int distance<Rank>(Square x, Square y) { return std::abs(rank_of(x) - rank_of(y)); }
-template<> inline int distance<Square>(Square x, Square y) { return SquareDistance[x][y]; }
+ assert((Pt != PAWN) && (is_ok(s)));
-template<class T> constexpr const T& clamp(const T& v, const T& lo, const T& hi) {
- return v < lo ? lo : v > hi ? hi : v;
+ return PseudoAttacks[Pt][s];
}
-/// attacks_bb() returns a bitboard representing all the squares attacked by a
-/// piece of type Pt (bishop or rook) placed on 's'.
+// 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<PieceType Pt>
inline Bitboard attacks_bb(Square s, Bitboard occupied) {
- const Magic& m = Pt == ROOK ? RookMagics[s] : BishopMagics[s];
- return m.attacks[m.index(occupied)];
+ assert((Pt != PAWN) && (is_ok(s)));
+
+ switch (Pt)
+ {
+ case BISHOP :
+ return BishopMagics[s].attacks[BishopMagics[s].index(occupied)];
+ case ROOK :
+ return RookMagics[s].attacks[RookMagics[s].index(occupied)];
+ case QUEEN :
+ return attacks_bb<BISHOP>(s, occupied) | attacks_bb<ROOK>(s, occupied);
+ default :
+ return PseudoAttacks[Pt][s];
+ }
}
+// 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.
inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) {
- assert(pt != PAWN);
-
- switch (pt)
- {
- case BISHOP: return attacks_bb<BISHOP>(s, occupied);
- case ROOK : return attacks_bb< ROOK>(s, occupied);
- case QUEEN : return attacks_bb<BISHOP>(s, occupied) | attacks_bb<ROOK>(s, occupied);
- default : return PseudoAttacks[pt][s];
- }
+ assert((pt != PAWN) && (is_ok(s)));
+
+ switch (pt)
+ {
+ case BISHOP :
+ return attacks_bb<BISHOP>(s, occupied);
+ case ROOK :
+ return attacks_bb<ROOK>(s, occupied);
+ case QUEEN :
+ return attacks_bb<BISHOP>(s, occupied) | attacks_bb<ROOK>(s, occupied);
+ default :
+ return PseudoAttacks[pt][s];
+ }
}
-/// popcount() counts the number of non-zero bits in a bitboard
-
+// Counts the number of non-zero bits in a bitboard.
inline int popcount(Bitboard b) {
#ifndef USE_POPCNT
- 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]];
+ 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]];
-#elif defined(_MSC_VER) || defined(__INTEL_COMPILER)
+#elif defined(_MSC_VER)
- return (int)_mm_popcnt_u64(b);
+ return int(_mm_popcnt_u64(b));
-#else // Assumed gcc or compatible compiler
+#else // Assumed gcc or compatible compiler
- return __builtin_popcountll(b);
+ return __builtin_popcountll(b);
#endif
}
-
-/// lsb() and msb() return the least/most significant bit in a non-zero bitboard
-
-#if defined(__GNUC__) // GCC, Clang, ICC
-
+// Returns the least significant bit in a non-zero bitboard.
inline Square lsb(Bitboard b) {
- assert(b);
- return Square(__builtin_ctzll(b));
-}
+ assert(b);
-inline Square msb(Bitboard b) {
- assert(b);
- return Square(63 ^ __builtin_clzll(b));
-}
+#if defined(__GNUC__) // GCC, Clang, ICX
-#elif defined(_MSC_VER) // MSVC
+ return Square(__builtin_ctzll(b));
-#ifdef _WIN64 // MSVC, WIN64
+#elif defined(_MSC_VER)
+ #ifdef _WIN64 // MSVC, WIN64
-inline Square lsb(Bitboard b) {
- assert(b);
- unsigned long idx;
- _BitScanForward64(&idx, b);
- return (Square) idx;
-}
+ unsigned long idx;
+ _BitScanForward64(&idx, b);
+ return Square(idx);
-inline Square msb(Bitboard b) {
- assert(b);
- unsigned long idx;
- _BitScanReverse64(&idx, b);
- return (Square) idx;
-}
-
-#else // MSVC, WIN32
+ #else // MSVC, WIN32
+ unsigned long idx;
-inline Square lsb(Bitboard b) {
- assert(b);
- unsigned long idx;
-
- if (b & 0xffffffff) {
- _BitScanForward(&idx, int32_t(b));
- return Square(idx);
- } else {
- _BitScanForward(&idx, int32_t(b >> 32));
- return Square(idx + 32);
- }
+ if (b & 0xffffffff)
+ {
+ _BitScanForward(&idx, int32_t(b));
+ return Square(idx);
+ }
+ else
+ {
+ _BitScanForward(&idx, int32_t(b >> 32));
+ return Square(idx + 32);
+ }
+ #endif
+#else // Compiler is neither GCC nor MSVC compatible
+ #error "Compiler not supported."
+#endif
}
+// Returns the most significant bit in a non-zero bitboard.
inline Square msb(Bitboard b) {
- assert(b);
- unsigned long idx;
-
- if (b >> 32) {
- _BitScanReverse(&idx, int32_t(b >> 32));
- return Square(idx + 32);
- } else {
- _BitScanReverse(&idx, int32_t(b));
- return Square(idx);
- }
-}
+ assert(b);
-#endif
+#if defined(__GNUC__) // GCC, Clang, ICX
-#else // Compiler is neither GCC nor MSVC compatible
+ return Square(63 ^ __builtin_clzll(b));
-#error "Compiler not supported."
+#elif defined(_MSC_VER)
+ #ifdef _WIN64 // MSVC, WIN64
-#endif
+ unsigned long idx;
+ _BitScanReverse64(&idx, b);
+ return Square(idx);
+ #else // MSVC, WIN32
-/// pop_lsb() finds and clears the least significant bit in a non-zero bitboard
+ unsigned long idx;
-inline Square pop_lsb(Bitboard* b) {
- const Square s = lsb(*b);
- *b &= *b - 1;
- return s;
+ if (b >> 32)
+ {
+ _BitScanReverse(&idx, int32_t(b >> 32));
+ return Square(idx + 32);
+ }
+ else
+ {
+ _BitScanReverse(&idx, int32_t(b));
+ return Square(idx);
+ }
+ #endif
+#else // Compiler is neither GCC nor MSVC compatible
+ #error "Compiler not supported."
+#endif
}
+// 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;
+}
-/// frontmost_sq() returns the most advanced square for the given color
-inline Square frontmost_sq(Color c, Bitboard b) {
- return c == WHITE ? msb(b) : lsb(b);
+// 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;
}
-#endif // #ifndef BITBOARD_H_INCLUDED
+} // namespace Stockfish
+
+#endif // #ifndef BITBOARD_H_INCLUDED
--- /dev/null
+#include <iostream>
+#include <memory>
+#include <string>
+
+#include <grpc++/grpc++.h>
+
+#include "hashprobe.grpc.pb.h"
+#include "types.h"
+#include "uci.h"
+
+using grpc::Channel;
+using grpc::ClientContext;
+using grpc::Status;
+using namespace hashprobe;
+
+std::string FormatMove(const HashProbeMove &move) {
+ if (move.pretty().empty()) return "MOVE_NONE";
+ return move.pretty();
+}
+
+int main(int argc, char** argv) {
+ std::shared_ptr<Channel> channel(grpc::CreateChannel(
+ "localhost:50051", grpc::InsecureChannelCredentials()));
+ std::unique_ptr<HashProbe::Stub> stub(HashProbe::NewStub(channel));
+
+ for ( ;; ) {
+ char buf[256];
+ if (fgets(buf, sizeof(buf), stdin) == nullptr || buf[0] == '\n') {
+ exit(0);
+ }
+
+ char *ptr = strchr(buf, '\n');
+ if (ptr != nullptr) *ptr = 0;
+
+ HashProbeRequest request;
+ request.set_fen(buf);
+
+ HashProbeResponse response;
+ ClientContext context;
+ Status status = stub->Probe(&context, request, &response);
+
+ if (status.ok()) {
+ for (const HashProbeLine &line : response.line()) {
+ std::cout << FormatMove(line.move()) << " ";
+ std::cout << line.found() << " ";
+ for (const HashProbeMove &move : line.pv()) {
+ std::cout << FormatMove(move) << ",";
+ }
+ std::cout << " ";
+ switch (line.bound()) {
+ case HashProbeLine::BOUND_NONE:
+ std::cout << "?";
+ break;
+ case HashProbeLine::BOUND_EXACT:
+ std::cout << "==";
+ break;
+ case HashProbeLine::BOUND_UPPER:
+ std::cout << "<=";
+ break;
+ case HashProbeLine::BOUND_LOWER:
+ std::cout << ">=";
+ break;
+ }
+ switch (line.value().score_type()) {
+ case HashProbeScore::SCORE_CP:
+ std::cout << " cp " << line.value().score_cp() << " ";
+ break;
+ case HashProbeScore::SCORE_MATE:
+ std::cout << " mate " << line.value().score_mate() << " ";
+ break;
+ }
+ std::cout << line.depth() << std::endl;
+ }
+ std::cout << "END" << std::endl;
+ } else {
+ std::cout << "ERROR" << std::endl;
+ }
+ }
+
+ return 0;
+}
+++ /dev/null
-/*
- Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
-
- 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 <http://www.gnu.org/licenses/>.
-*/
-
-#include <cassert>
-
-#include "bitboard.h"
-#include "endgame.h"
-#include "movegen.h"
-
-using std::string;
-
-namespace {
-
- // Table used to drive the king towards the edge of the board
- // in KX vs K and KQ vs KR endgames.
- constexpr int PushToEdges[SQUARE_NB] = {
- 100, 90, 80, 70, 70, 80, 90, 100,
- 90, 70, 60, 50, 50, 60, 70, 90,
- 80, 60, 40, 30, 30, 40, 60, 80,
- 70, 50, 30, 20, 20, 30, 50, 70,
- 70, 50, 30, 20, 20, 30, 50, 70,
- 80, 60, 40, 30, 30, 40, 60, 80,
- 90, 70, 60, 50, 50, 60, 70, 90,
- 100, 90, 80, 70, 70, 80, 90, 100
- };
-
- // Table used to drive the king towards a corner square of the
- // right color in KBN vs K endgames.
- constexpr int PushToCorners[SQUARE_NB] = {
- 6400, 6080, 5760, 5440, 5120, 4800, 4480, 4160,
- 6080, 5760, 5440, 5120, 4800, 4480, 4160, 4480,
- 5760, 5440, 4960, 4480, 4480, 4000, 4480, 4800,
- 5440, 5120, 4480, 3840, 3520, 4480, 4800, 5120,
- 5120, 4800, 4480, 3520, 3840, 4480, 5120, 5440,
- 4800, 4480, 4000, 4480, 4480, 4960, 5440, 5760,
- 4480, 4160, 4480, 4800, 5120, 5440, 5760, 6080,
- 4160, 4480, 4800, 5120, 5440, 5760, 6080, 6400
- };
-
- // Tables used to drive a piece towards or away from another piece
- constexpr int PushClose[8] = { 0, 0, 100, 80, 60, 40, 20, 10 };
- constexpr int PushAway [8] = { 0, 5, 20, 40, 60, 80, 90, 100 };
-
- // Pawn Rank based scaling factors used in KRPPKRP endgame
- constexpr int KRPPKRPScaleFactors[RANK_NB] = { 0, 9, 10, 14, 21, 44, 0, 0 };
-
-#ifndef NDEBUG
- bool verify_material(const Position& pos, Color c, Value npm, int pawnsCnt) {
- return pos.non_pawn_material(c) == npm && pos.count<PAWN>(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<PAWN>(strongSide) == 1);
-
- if (file_of(pos.square<PAWN>(strongSide)) >= FILE_E)
- sq = Square(int(sq) ^ 7); // Mirror SQ_H1 -> SQ_A1
-
- return strongSide == WHITE ? sq : ~sq;
- }
-
-} // namespace
-
-
-namespace Endgames {
-
- std::pair<Map<Value>, Map<ScaleFactor>> maps;
-
- void init() {
-
- add<KPK>("KPK");
- add<KNNK>("KNNK");
- add<KBNK>("KBNK");
- add<KRKP>("KRKP");
- add<KRKB>("KRKB");
- add<KRKN>("KRKN");
- add<KQKP>("KQKP");
- add<KQKR>("KQKR");
- add<KNNKP>("KNNKP");
-
- add<KNPK>("KNPK");
- add<KNPKB>("KNPKB");
- add<KRPKR>("KRPKR");
- add<KRPKB>("KRPKB");
- add<KBPKB>("KBPKB");
- add<KBPKN>("KBPKN");
- add<KBPPKB>("KBPPKB");
- add<KRPPKRP>("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<KXK>::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<LEGAL>(pos).size())
- return VALUE_DRAW;
-
- Square winnerKSq = pos.square<KING>(strongSide);
- Square loserKSq = pos.square<KING>(weakSide);
-
- Value result = pos.non_pawn_material(strongSide)
- + pos.count<PAWN>(strongSide) * PawnValueEg
- + PushToEdges[loserKSq]
- + PushClose[distance(winnerKSq, loserKSq)];
-
- if ( pos.count<QUEEN>(strongSide)
- || pos.count<ROOK>(strongSide)
- ||(pos.count<BISHOP>(strongSide) && pos.count<KNIGHT>(strongSide))
- || ( (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;
-}
-
-
-/// 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<KBNK>::operator()(const Position& pos) const {
-
- assert(verify_material(pos, strongSide, KnightValueMg + BishopValueMg, 0));
- assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
-
- Square winnerKSq = pos.square<KING>(strongSide);
- Square loserKSq = pos.square<KING>(weakSide);
- Square bishopSq = pos.square<BISHOP>(strongSide);
-
- // 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
- + PushClose[distance(winnerKSq, loserKSq)]
- + PushToCorners[opposite_colors(bishopSq, SQ_A1) ? ~loserKSq : loserKSq];
-
- assert(abs(result) < VALUE_MATE_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<KPK>::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 wksq = normalize(pos, strongSide, pos.square<KING>(strongSide));
- Square bksq = normalize(pos, strongSide, pos.square<KING>(weakSide));
- Square psq = normalize(pos, strongSide, pos.square<PAWN>(strongSide));
-
- Color us = strongSide == pos.side_to_move() ? WHITE : BLACK;
-
- if (!Bitbases::probe(wksq, psq, bksq, us))
- return VALUE_DRAW;
-
- Value result = VALUE_KNOWN_WIN + PawnValueEg + Value(rank_of(psq));
-
- 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<KRKP>::operator()(const Position& pos) const {
-
- assert(verify_material(pos, strongSide, RookValueMg, 0));
- assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
-
- Square wksq = relative_square(strongSide, pos.square<KING>(strongSide));
- Square bksq = relative_square(strongSide, pos.square<KING>(weakSide));
- Square rsq = relative_square(strongSide, pos.square<ROOK>(strongSide));
- Square psq = relative_square(strongSide, pos.square<PAWN>(weakSide));
-
- Square queeningSq = make_square(file_of(psq), RANK_1);
- Value result;
-
- // If the stronger side's king is in front of the pawn, it's a win
- if (forward_file_bb(WHITE, wksq) & psq)
- result = RookValueEg - distance(wksq, psq);
-
- // If the weaker side's king is too far from the pawn and the rook,
- // it's a win.
- else if ( distance(bksq, psq) >= 3 + (pos.side_to_move() == weakSide)
- && distance(bksq, rsq) >= 3)
- result = RookValueEg - distance(wksq, psq);
-
- // If the pawn is far advanced and supported by the defending king,
- // the position is drawish
- else if ( rank_of(bksq) <= RANK_3
- && distance(bksq, psq) == 1
- && rank_of(wksq) >= RANK_4
- && distance(wksq, psq) > 2 + (pos.side_to_move() == strongSide))
- result = Value(80) - 8 * distance(wksq, psq);
-
- else
- result = Value(200) - 8 * ( distance(wksq, psq + SOUTH)
- - distance(bksq, psq + SOUTH)
- - distance(psq, queeningSq));
-
- 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<KRKB>::operator()(const Position& pos) const {
-
- assert(verify_material(pos, strongSide, RookValueMg, 0));
- assert(verify_material(pos, weakSide, BishopValueMg, 0));
-
- Value result = Value(PushToEdges[pos.square<KING>(weakSide)]);
- 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<KRKN>::operator()(const Position& pos) const {
-
- assert(verify_material(pos, strongSide, RookValueMg, 0));
- assert(verify_material(pos, weakSide, KnightValueMg, 0));
-
- Square bksq = pos.square<KING>(weakSide);
- Square bnsq = pos.square<KNIGHT>(weakSide);
- Value result = Value(PushToEdges[bksq] + PushAway[distance(bksq, bnsq)]);
- 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<KQKP>::operator()(const Position& pos) const {
-
- assert(verify_material(pos, strongSide, QueenValueMg, 0));
- assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
-
- Square winnerKSq = pos.square<KING>(strongSide);
- Square loserKSq = pos.square<KING>(weakSide);
- Square pawnSq = pos.square<PAWN>(weakSide);
-
- Value result = Value(PushClose[distance(winnerKSq, loserKSq)]);
-
- if ( relative_rank(weakSide, pawnSq) != RANK_7
- || distance(loserKSq, pawnSq) != 1
- || !((FileABB | FileCBB | FileFBB | FileHBB) & pawnSq))
- 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<KQKR>::operator()(const Position& pos) const {
-
- assert(verify_material(pos, strongSide, QueenValueMg, 0));
- assert(verify_material(pos, weakSide, RookValueMg, 0));
-
- Square winnerKSq = pos.square<KING>(strongSide);
- Square loserKSq = pos.square<KING>(weakSide);
-
- Value result = QueenValueEg
- - RookValueEg
- + PushToEdges[loserKSq]
- + PushClose[distance(winnerKSq, loserKSq)];
-
- return strongSide == pos.side_to_move() ? result : -result;
-}
-
-
-/// KNN vs KP. Simply push the opposing king to the corner
-template<>
-Value Endgame<KNNKP>::operator()(const Position& pos) const {
-
- assert(verify_material(pos, strongSide, 2 * KnightValueMg, 0));
- assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
-
- Value result = 2 * KnightValueEg
- - PawnValueEg
- + PushToEdges[pos.square<KING>(weakSide)];
-
- return strongSide == pos.side_to_move() ? result : -result;
-}
-
-
-/// Some cases of trivial draws
-template<> Value Endgame<KNNK>::operator()(const Position&) const { return VALUE_DRAW; }
-
-
-/// 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<KBPsK>::operator()(const Position& pos) const {
-
- assert(pos.non_pawn_material(strongSide) == BishopValueMg);
- assert(pos.count<PAWN>(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 pawns = pos.pieces(strongSide, PAWN);
- File pawnsFile = file_of(lsb(pawns));
-
- // All pawns are on a single rook file?
- if ( (pawnsFile == FILE_A || pawnsFile == FILE_H)
- && !(pawns & ~file_bb(pawnsFile)))
- {
- Square bishopSq = pos.square<BISHOP>(strongSide);
- Square queeningSq = relative_square(strongSide, make_square(pawnsFile, RANK_8));
- Square kingSq = pos.square<KING>(weakSide);
-
- if ( opposite_colors(queeningSq, bishopSq)
- && distance(queeningSq, kingSq) <= 1)
- return SCALE_FACTOR_DRAW;
- }
-
- // If all the pawns are on the same B or G file, then it's potentially a draw
- if ( (pawnsFile == FILE_B || pawnsFile == FILE_G)
- && !(pos.pieces(PAWN) & ~file_bb(pawnsFile))
- && pos.non_pawn_material(weakSide) == 0
- && pos.count<PAWN>(weakSide) >= 1)
- {
- // Get weakSide pawn that is closest to the home rank
- Square weakPawnSq = frontmost_sq(strongSide, pos.pieces(weakSide, PAWN));
-
- Square strongKingSq = pos.square<KING>(strongSide);
- Square weakKingSq = pos.square<KING>(weakSide);
- Square bishopSq = pos.square<BISHOP>(strongSide);
-
- // 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, weakPawnSq) == RANK_7
- && (pos.pieces(strongSide, PAWN) & (weakPawnSq + pawn_push(weakSide)))
- && (opposite_colors(bishopSq, weakPawnSq) || pos.count<PAWN>(strongSide) == 1))
- {
- int strongKingDist = distance(weakPawnSq, strongKingSq);
- int weakKingDist = distance(weakPawnSq, weakKingSq);
-
- // 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, weakKingSq) >= 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<KQKRPs>::operator()(const Position& pos) const {
-
- assert(verify_material(pos, strongSide, QueenValueMg, 0));
- assert(pos.count<ROOK>(weakSide) == 1);
- assert(pos.count<PAWN>(weakSide) >= 1);
-
- Square kingSq = pos.square<KING>(weakSide);
- Square rsq = pos.square<ROOK>(weakSide);
-
- if ( relative_rank(weakSide, kingSq) <= RANK_2
- && relative_rank(weakSide, pos.square<KING>(strongSide)) >= RANK_4
- && relative_rank(weakSide, rsq) == RANK_3
- && ( pos.pieces(weakSide, PAWN)
- & pos.attacks_from<KING>(kingSq)
- & pos.attacks_from<PAWN>(rsq, strongSide)))
- 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<KRPKR>::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 wksq = normalize(pos, strongSide, pos.square<KING>(strongSide));
- Square bksq = normalize(pos, strongSide, pos.square<KING>(weakSide));
- Square wrsq = normalize(pos, strongSide, pos.square<ROOK>(strongSide));
- Square wpsq = normalize(pos, strongSide, pos.square<PAWN>(strongSide));
- Square brsq = normalize(pos, strongSide, pos.square<ROOK>(weakSide));
-
- File f = file_of(wpsq);
- Rank r = rank_of(wpsq);
- Square queeningSq = make_square(f, 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 ( r <= RANK_5
- && distance(bksq, queeningSq) <= 1
- && wksq <= SQ_H5
- && (rank_of(brsq) == RANK_6 || (r <= RANK_3 && rank_of(wrsq) != 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 ( r == RANK_6
- && distance(bksq, queeningSq) <= 1
- && rank_of(wksq) + tempo <= RANK_6
- && (rank_of(brsq) == RANK_1 || (!tempo && distance<File>(brsq, wpsq) >= 3)))
- return SCALE_FACTOR_DRAW;
-
- if ( r >= RANK_6
- && bksq == queeningSq
- && rank_of(brsq) == RANK_1
- && (!tempo || distance(wksq, wpsq) >= 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 ( wpsq == SQ_A7
- && wrsq == SQ_A8
- && (bksq == SQ_H7 || bksq == SQ_G7)
- && file_of(brsq) == FILE_A
- && (rank_of(brsq) <= RANK_3 || file_of(wksq) >= FILE_D || rank_of(wksq) <= 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 ( r <= RANK_5
- && bksq == wpsq + NORTH
- && distance(wksq, wpsq) - tempo >= 2
- && distance(wksq, brsq) - 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 ( r == RANK_7
- && f != FILE_A
- && file_of(wrsq) == f
- && wrsq != queeningSq
- && (distance(wksq, queeningSq) < distance(bksq, queeningSq) - 2 + tempo)
- && (distance(wksq, queeningSq) < distance(bksq, wrsq) + tempo))
- return ScaleFactor(SCALE_FACTOR_MAX - 2 * distance(wksq, queeningSq));
-
- // Similar to the above, but with the pawn further back
- if ( f != FILE_A
- && file_of(wrsq) == f
- && wrsq < wpsq
- && (distance(wksq, queeningSq) < distance(bksq, queeningSq) - 2 + tempo)
- && (distance(wksq, wpsq + NORTH) < distance(bksq, wpsq + NORTH) - 2 + tempo)
- && ( distance(bksq, wrsq) + tempo >= 3
- || ( distance(wksq, queeningSq) < distance(bksq, wrsq) + tempo
- && (distance(wksq, wpsq + NORTH) < distance(bksq, wrsq) + tempo))))
- return ScaleFactor( SCALE_FACTOR_MAX
- - 8 * distance(wpsq, queeningSq)
- - 2 * distance(wksq, queeningSq));
-
- // If the pawn is not far advanced and the defending king is somewhere in
- // the pawn's path, it's probably a draw.
- if (r <= RANK_4 && bksq > wpsq)
- {
- if (file_of(bksq) == file_of(wpsq))
- return ScaleFactor(10);
- if ( distance<File>(bksq, wpsq) == 1
- && distance(wksq, bksq) > 2)
- return ScaleFactor(24 - 2 * distance(wksq, bksq));
- }
- return SCALE_FACTOR_NONE;
-}
-
-template<>
-ScaleFactor Endgame<KRPKB>::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 ksq = pos.square<KING>(weakSide);
- Square bsq = pos.square<BISHOP>(weakSide);
- Square psq = pos.square<PAWN>(strongSide);
- Rank rk = relative_rank(strongSide, psq);
- 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 (rk == RANK_5 && !opposite_colors(bsq, psq))
- {
- int d = distance(psq + 3 * push, ksq);
-
- if (d <= 2 && !(d == 0 && ksq == pos.square<KING>(strongSide) + 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 ( rk == RANK_6
- && distance(psq + 2 * push, ksq) <= 1
- && (PseudoAttacks[BISHOP][bsq] & (psq + push))
- && distance<File>(bsq, psq) >= 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<KRPPKRP>::operator()(const Position& pos) const {
-
- assert(verify_material(pos, strongSide, RookValueMg, 2));
- assert(verify_material(pos, weakSide, RookValueMg, 1));
-
- Square wpsq1 = pos.squares<PAWN>(strongSide)[0];
- Square wpsq2 = pos.squares<PAWN>(strongSide)[1];
- Square bksq = pos.square<KING>(weakSide);
-
- // Does the stronger side have a passed pawn?
- if (pos.pawn_passed(strongSide, wpsq1) || pos.pawn_passed(strongSide, wpsq2))
- return SCALE_FACTOR_NONE;
-
- Rank r = std::max(relative_rank(strongSide, wpsq1), relative_rank(strongSide, wpsq2));
-
- if ( distance<File>(bksq, wpsq1) <= 1
- && distance<File>(bksq, wpsq2) <= 1
- && relative_rank(strongSide, bksq) > r)
- {
- assert(r > RANK_1 && r < RANK_7);
- return ScaleFactor(KRPPKRPScaleFactors[r]);
- }
- 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<KPsK>::operator()(const Position& pos) const {
-
- assert(pos.non_pawn_material(strongSide) == VALUE_ZERO);
- assert(pos.count<PAWN>(strongSide) >= 2);
- assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
-
- Square ksq = pos.square<KING>(weakSide);
- Bitboard pawns = pos.pieces(strongSide, PAWN);
-
- // If all pawns are ahead of the king, on a single rook file and
- // the king is within one file of the pawns, it's a draw.
- if ( !(pawns & ~forward_ranks_bb(weakSide, ksq))
- && !((pawns & ~FileABB) && (pawns & ~FileHBB))
- && distance<File>(ksq, lsb(pawns)) <= 1)
- 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<KBPKB>::operator()(const Position& pos) const {
-
- assert(verify_material(pos, strongSide, BishopValueMg, 1));
- assert(verify_material(pos, weakSide, BishopValueMg, 0));
-
- Square pawnSq = pos.square<PAWN>(strongSide);
- Square strongBishopSq = pos.square<BISHOP>(strongSide);
- Square weakBishopSq = pos.square<BISHOP>(weakSide);
- Square weakKingSq = pos.square<KING>(weakSide);
-
- // Case 1: Defending king blocks the pawn, and cannot be driven away
- if ( file_of(weakKingSq) == file_of(pawnSq)
- && relative_rank(strongSide, pawnSq) < relative_rank(strongSide, weakKingSq)
- && ( opposite_colors(weakKingSq, strongBishopSq)
- || relative_rank(strongSide, weakKingSq) <= RANK_6))
- return SCALE_FACTOR_DRAW;
-
- // Case 2: Opposite colored bishops
- if (opposite_colors(strongBishopSq, weakBishopSq))
- return SCALE_FACTOR_DRAW;
-
- return SCALE_FACTOR_NONE;
-}
-
-
-/// KBPP vs KB. It detects a few basic draws with opposite-colored bishops
-template<>
-ScaleFactor Endgame<KBPPKB>::operator()(const Position& pos) const {
-
- assert(verify_material(pos, strongSide, BishopValueMg, 2));
- assert(verify_material(pos, weakSide, BishopValueMg, 0));
-
- Square wbsq = pos.square<BISHOP>(strongSide);
- Square bbsq = pos.square<BISHOP>(weakSide);
-
- if (!opposite_colors(wbsq, bbsq))
- return SCALE_FACTOR_NONE;
-
- Square ksq = pos.square<KING>(weakSide);
- Square psq1 = pos.squares<PAWN>(strongSide)[0];
- Square psq2 = pos.squares<PAWN>(strongSide)[1];
- Square blockSq1, blockSq2;
-
- if (relative_rank(strongSide, psq1) > relative_rank(strongSide, psq2))
- {
- blockSq1 = psq1 + pawn_push(strongSide);
- blockSq2 = make_square(file_of(psq2), rank_of(psq1));
- }
- else
- {
- blockSq1 = psq2 + pawn_push(strongSide);
- blockSq2 = make_square(file_of(psq1), rank_of(psq2));
- }
-
- switch (distance<File>(psq1, psq2))
- {
- 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(ksq) == file_of(blockSq1)
- && relative_rank(strongSide, ksq) >= relative_rank(strongSide, blockSq1)
- && opposite_colors(ksq, wbsq))
- 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 ( ksq == blockSq1
- && opposite_colors(ksq, wbsq)
- && ( bbsq == blockSq2
- || (pos.attacks_from<BISHOP>(blockSq2) & pos.pieces(weakSide, BISHOP))
- || distance<Rank>(psq1, psq2) >= 2))
- return SCALE_FACTOR_DRAW;
-
- else if ( ksq == blockSq2
- && opposite_colors(ksq, wbsq)
- && ( bbsq == blockSq1
- || (pos.attacks_from<BISHOP>(blockSq1) & 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<KBPKN>::operator()(const Position& pos) const {
-
- assert(verify_material(pos, strongSide, BishopValueMg, 1));
- assert(verify_material(pos, weakSide, KnightValueMg, 0));
-
- Square pawnSq = pos.square<PAWN>(strongSide);
- Square strongBishopSq = pos.square<BISHOP>(strongSide);
- Square weakKingSq = pos.square<KING>(weakSide);
-
- if ( file_of(weakKingSq) == file_of(pawnSq)
- && relative_rank(strongSide, pawnSq) < relative_rank(strongSide, weakKingSq)
- && ( opposite_colors(weakKingSq, strongBishopSq)
- || relative_rank(strongSide, weakKingSq) <= RANK_6))
- return SCALE_FACTOR_DRAW;
-
- return SCALE_FACTOR_NONE;
-}
-
-
-/// KNP vs K. There is a single rule: if the pawn is a rook pawn on the 7th rank
-/// and the defending king prevents the pawn from advancing, the position is drawn.
-template<>
-ScaleFactor Endgame<KNPK>::operator()(const Position& pos) const {
-
- assert(verify_material(pos, strongSide, KnightValueMg, 1));
- assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
-
- // Assume strongSide is white and the pawn is on files A-D
- Square pawnSq = normalize(pos, strongSide, pos.square<PAWN>(strongSide));
- Square weakKingSq = normalize(pos, strongSide, pos.square<KING>(weakSide));
-
- if (pawnSq == SQ_A7 && distance(SQ_A8, weakKingSq) <= 1)
- return SCALE_FACTOR_DRAW;
-
- return SCALE_FACTOR_NONE;
-}
-
-
-/// KNP vs KB. If knight can block bishop from taking pawn, it's a win.
-/// Otherwise the position is drawn.
-template<>
-ScaleFactor Endgame<KNPKB>::operator()(const Position& pos) const {
-
- assert(verify_material(pos, strongSide, KnightValueMg, 1));
- assert(verify_material(pos, weakSide, BishopValueMg, 0));
-
- Square pawnSq = pos.square<PAWN>(strongSide);
- Square bishopSq = pos.square<BISHOP>(weakSide);
- Square weakKingSq = pos.square<KING>(weakSide);
-
- // King needs to get close to promoting pawn to prevent knight from blocking.
- // Rules for this are very tricky, so just approximate.
- if (forward_file_bb(strongSide, pawnSq) & pos.attacks_from<BISHOP>(bishopSq))
- return ScaleFactor(distance(weakKingSq, pawnSq));
-
- 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<KPKP>::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 wksq = normalize(pos, strongSide, pos.square<KING>(strongSide));
- Square bksq = normalize(pos, strongSide, pos.square<KING>(weakSide));
- Square psq = normalize(pos, strongSide, pos.square<PAWN>(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(psq) >= RANK_5 && file_of(psq) != 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.
- return Bitbases::probe(wksq, psq, bksq, us) ? SCALE_FACTOR_NONE : SCALE_FACTOR_DRAW;
-}
+++ /dev/null
-/*
- Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
-
- 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 <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef ENDGAME_H_INCLUDED
-#define ENDGAME_H_INCLUDED
-
-#include <unordered_map>
-#include <memory>
-#include <string>
-#include <type_traits>
-#include <utility>
-
-#include "position.h"
-#include "types.h"
-
-
-/// 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
-
- 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
- KNPK, // KNP vs K
- KNPKB, // KNP vs KB
- KPKP // KP vs KP
-};
-
-
-/// Endgame functions can be of two types depending on whether they return a
-/// Value or a ScaleFactor.
-
-template<EndgameCode E> using
-eg_type = typename std::conditional<(E < SCALING_FUNCTIONS), Value, ScaleFactor>::type;
-
-
-/// Base and derived functors for endgame evaluation and scaling functions
-
-template<typename T>
-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<EndgameCode E, typename T = eg_type<E>>
-struct Endgame : public EndgameBase<T> {
-
- explicit Endgame(Color c) : EndgameBase<T>(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<typename T> using Ptr = std::unique_ptr<EndgameBase<T>>;
- template<typename T> using Map = std::unordered_map<Key, Ptr<T>>;
-
- extern std::pair<Map<Value>, Map<ScaleFactor>> maps;
-
- void init();
-
- template<typename T>
- Map<T>& map() {
- return std::get<std::is_same<T, ScaleFactor>::value>(maps);
- }
-
- template<EndgameCode E, typename T = eg_type<E>>
- void add(const std::string& code) {
-
- StateInfo st;
- map<T>()[Position().set(code, WHITE, &st).material_key()] = Ptr<T>(new Endgame<E>(WHITE));
- map<T>()[Position().set(code, BLACK, &st).material_key()] = Ptr<T>(new Endgame<E>(BLACK));
- }
-
- template<typename T>
- const EndgameBase<T>* probe(Key key) {
- auto it = map<T>().find(key);
- return it != map<T>().end() ? it->second.get() : nullptr;
- }
-}
-
-#endif // #ifndef ENDGAME_H_INCLUDED
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+ Copyright (C) 2004-2023 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
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "evaluate.h"
+
#include <algorithm>
#include <cassert>
-#include <cstring> // For std::memset
+#include <cmath>
+#include <cstdlib>
+#include <fstream>
#include <iomanip>
+#include <iostream>
#include <sstream>
+#include <vector>
-#include "bitboard.h"
-#include "evaluate.h"
-#include "material.h"
-#include "pawns.h"
+#include "incbin/incbin.h"
+#include "misc.h"
+#include "nnue/evaluate_nnue.h"
+#include "position.h"
#include "thread.h"
-
-namespace Trace {
-
- enum Tracing { NO_TRACE, TRACE };
-
- enum Term { // The first 8 entries are reserved for PieceType
- MATERIAL = 8, IMBALANCE, MOBILITY, THREAT, PASSED, SPACE, INITIATIVE, 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 == INITIATIVE || 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 LazyThreshold = Value(1400);
- constexpr Value SpaceThreshold = Value(12222);
-
- // KingAttackWeights[PieceType] contains king attack weights by piece type
- constexpr int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 81, 52, 44, 10 };
-
- // Penalties for enemy's safe checks
- constexpr int QueenSafeCheck = 780;
- constexpr int RookSafeCheck = 1080;
- constexpr int BishopSafeCheck = 635;
- constexpr int KnightSafeCheck = 790;
-
-#define S(mg, eg) make_score(mg, eg)
-
- // MobilityBonus[PieceType-2][attacked] contains bonuses for middle and end game,
- // indexed by piece type and number of attacked squares in the mobility area.
- constexpr Score MobilityBonus[][32] = {
- { S(-62,-81), S(-53,-56), S(-12,-30), S( -4,-14), S( 3, 8), S( 13, 15), // Knights
- S( 22, 23), S( 28, 27), S( 33, 33) },
- { S(-48,-59), S(-20,-23), S( 16, -3), S( 26, 13), S( 38, 24), S( 51, 42), // Bishops
- S( 55, 54), S( 63, 57), S( 63, 65), S( 68, 73), S( 81, 78), S( 81, 86),
- S( 91, 88), S( 98, 97) },
- { S(-58,-76), S(-27,-18), S(-15, 28), S(-10, 55), S( -5, 69), S( -2, 82), // Rooks
- S( 9,112), S( 16,118), S( 30,132), S( 29,142), S( 32,155), S( 38,165),
- S( 46,166), S( 48,169), S( 58,171) },
- { S(-39,-36), S(-21,-15), S( 3, 8), S( 3, 18), S( 14, 34), S( 22, 54), // Queens
- S( 28, 61), S( 41, 73), S( 43, 79), S( 48, 92), S( 56, 94), S( 60,104),
- S( 60,113), S( 66,120), S( 67,123), S( 70,126), S( 71,133), S( 73,136),
- S( 79,140), S( 88,143), S( 88,148), S( 99,166), S(102,170), S(102,175),
- S(106,184), S(109,191), S(113,206), S(116,212) }
- };
-
- // RookOnFile[semiopen/open] contains bonuses for each rook when there is
- // no (friendly) pawn on the rook file.
- constexpr Score RookOnFile[] = { S(21, 4), S(47, 25) };
-
- // 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(6, 32), S(59, 41), S(79, 56), S(90, 119), S(79, 161)
- };
-
- constexpr Score ThreatByRook[PIECE_TYPE_NB] = {
- S(0, 0), S(3, 44), S(38, 71), S(38, 61), S(0, 38), S(51, 38)
- };
-
- // PassedRank[Rank] contains a bonus according to the rank of a passed pawn
- constexpr Score PassedRank[RANK_NB] = {
- S(0, 0), S(10, 28), S(17, 33), S(15, 41), S(62, 72), S(168, 177), S(276, 260)
- };
-
- // Assorted bonuses and penalties
- constexpr Score BishopPawns = S( 3, 7);
- constexpr Score CorneredBishop = S( 50, 50);
- constexpr Score FlankAttacks = S( 8, 0);
- constexpr Score Hanging = S( 69, 36);
- constexpr Score KingProtector = S( 7, 8);
- constexpr Score KnightOnQueen = S( 16, 12);
- constexpr Score LongDiagonalBishop = S( 45, 0);
- constexpr Score MinorBehindPawn = S( 18, 3);
- constexpr Score Outpost = S( 30, 21);
- constexpr Score PassedFile = S( 11, 8);
- constexpr Score PawnlessFlank = S( 17, 95);
- constexpr Score RestrictedPiece = S( 7, 7);
- constexpr Score ReachableOutpost = S( 32, 10);
- constexpr Score RookOnQueenFile = S( 7, 6);
- constexpr Score SliderOnQueen = S( 59, 18);
- constexpr Score ThreatByKing = S( 24, 89);
- constexpr Score ThreatByPawnPush = S( 48, 39);
- constexpr Score ThreatBySafePawn = S(173, 94);
- constexpr Score TrappedRook = S( 52, 10);
- constexpr Score WeakQueen = S( 49, 15);
-
-#undef S
-
- // Evaluation class computes and stores attacks tables and other working data
- template<Tracing T>
- class Evaluation {
-
- public:
- Evaluation() = delete;
- explicit Evaluation(const Position& p) : pos(p) {}
- Evaluation& operator=(const Evaluation&) = delete;
- Value value();
-
- private:
- template<Color Us> void initialize();
- template<Color Us, PieceType Pt> Score pieces();
- template<Color Us> Score king() const;
- template<Color Us> Score threats() const;
- template<Color Us> Score passed() const;
- template<Color Us> Score space() const;
- ScaleFactor scale_factor(Value eg) const;
- Score initiative(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];
-
- // 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];
-
- // 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<Tracing T> template<Color Us>
- void Evaluation<T>::initialize() {
-
- constexpr Color Them = (Us == WHITE ? BLACK : WHITE);
- constexpr Direction Up = pawn_push(Us);
- constexpr Direction Down = -Up;
- constexpr Bitboard LowRanks = (Us == WHITE ? Rank2BB | Rank3BB : Rank7BB | Rank6BB);
-
- const Square ksq = pos.square<KING>(Us);
-
- Bitboard dblAttackByPawn = pawn_double_attacks_bb<Us>(pos.pieces(Us, PAWN));
-
- // Find our pawns that are blocked or on the first two ranks
- Bitboard b = pos.pieces(Us, PAWN) & (shift<Down>(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.
- mobilityArea[Us] = ~(b | pos.pieces(Us, KING, QUEEN) | pos.blockers_for_king(Us) | pe->pawn_attacks(Them));
-
- // Initialize attackedBy[] for king and pawns
- attackedBy[Us][KING] = pos.attacks_from<KING>(ksq);
- attackedBy[Us][PAWN] = pe->pawn_attacks(Us);
- attackedBy[Us][ALL_PIECES] = attackedBy[Us][KING] | attackedBy[Us][PAWN];
- attackedBy2[Us] = dblAttackByPawn | (attackedBy[Us][KING] & attackedBy[Us][PAWN]);
-
- // Init our king safety tables
- Square s = make_square(clamp(file_of(ksq), FILE_B, FILE_G),
- clamp(rank_of(ksq), RANK_2, RANK_7));
- kingRing[Us] = PseudoAttacks[KING][s] | s;
-
- kingAttackersCount[Them] = popcount(kingRing[Us] & pe->pawn_attacks(Them));
- kingAttacksCount[Them] = kingAttackersWeight[Them] = 0;
-
- // Remove from kingRing[] the squares defended by two pawns
- kingRing[Us] &= ~dblAttackByPawn;
- }
-
-
- // Evaluation::pieces() scores pieces of a given color and type
- template<Tracing T> template<Color Us, PieceType Pt>
- Score Evaluation<T>::pieces() {
-
- constexpr Color Them = (Us == WHITE ? BLACK : WHITE);
- constexpr Direction Down = -pawn_push(Us);
- constexpr Bitboard OutpostRanks = (Us == WHITE ? Rank4BB | Rank5BB | Rank6BB
- : Rank5BB | Rank4BB | Rank3BB);
- const Square* pl = pos.squares<Pt>(Us);
-
- Bitboard b, bb;
- Score score = SCORE_ZERO;
-
- attackedBy[Us][Pt] = 0;
-
- for (Square s = *pl; s != SQ_NONE; s = *++pl)
- {
- // Find attacked squares, including x-ray attacks for bishops and rooks
- b = Pt == BISHOP ? attacks_bb<BISHOP>(s, pos.pieces() ^ pos.pieces(QUEEN))
- : Pt == ROOK ? attacks_bb< ROOK>(s, pos.pieces() ^ pos.pieces(QUEEN) ^ pos.pieces(Us, ROOK))
- : pos.attacks_from<Pt>(s);
-
- if (pos.blockers_for_king(Us) & s)
- b &= LineBB[pos.square<KING>(Us)][s];
-
- attackedBy2[Us] |= attackedBy[Us][ALL_PIECES] & b;
- attackedBy[Us][Pt] |= b;
- attackedBy[Us][ALL_PIECES] |= b;
-
- if (b & kingRing[Them])
+#include "types.h"
+#include "uci.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};
+const unsigned char* const gEmbeddedNNUEEnd = &gEmbeddedNNUEData[1];
+const unsigned int gEmbeddedNNUESize = 1;
+#endif
+
+
+namespace Stockfish {
+
+namespace Eval {
+
+std::string currentEvalFileName = "None";
+
+// 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() {
+
+ std::string eval_file = std::string(Options["EvalFile"]);
+ if (eval_file.empty())
+ eval_file = EvalFileDefaultName;
+
+#if defined(DEFAULT_NNUE_DIRECTORY)
+ std::vector<std::string> dirs = {"<internal>", "", CommandLine::binaryDirectory,
+ stringify(DEFAULT_NNUE_DIRECTORY)};
+#else
+ std::vector<std::string> dirs = {"<internal>", "", CommandLine::binaryDirectory};
+#endif
+
+ for (const std::string& directory : dirs)
+ if (currentEvalFileName != eval_file)
{
- kingAttackersCount[Us]++;
- kingAttackersWeight[Us] += KingAttackWeights[Pt];
- kingAttacksCount[Us] += popcount(b & attackedBy[Them][KING]);
- }
-
- int mob = popcount(b & mobilityArea[Us]);
-
- mobility[Us] += MobilityBonus[Pt - 2][mob];
-
- if (Pt == BISHOP || Pt == KNIGHT)
- {
- // Bonus if piece is on an outpost square or can reach one
- bb = OutpostRanks & attackedBy[Us][PAWN] & ~pe->pawn_attacks_span(Them);
- if (bb & s)
- score += Outpost * (Pt == KNIGHT ? 2 : 1);
-
- else if (Pt == KNIGHT && bb & b & ~pos.pieces(Us))
- score += ReachableOutpost;
-
- // Knight and Bishop bonus for being right behind a pawn
- if (shift<Down>(pos.pieces(PAWN)) & s)
- score += MinorBehindPawn;
-
- // Penalty if the piece is far from the king
- score -= KingProtector * distance(s, pos.square<KING>(Us));
-
- if (Pt == BISHOP)
+ if (directory != "<internal>")
{
- // Penalty according to number of pawns on the same color square as the
- // bishop, bigger when the center files are blocked with pawns.
- Bitboard blocked = pos.pieces(Us, PAWN) & shift<Down>(pos.pieces());
-
- score -= BishopPawns * pos.pawns_on_same_color_squares(Us, s)
- * (1 + popcount(blocked & CenterFiles));
-
- // Bonus for bishop on a long diagonal which can "see" both center squares
- if (more_than_one(attacks_bb<BISHOP>(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)) ? CorneredBishop * 4
- : pos.piece_on(s + d + d) == make_piece(Us, PAWN) ? CorneredBishop * 2
- : CorneredBishop;
- }
+ std::ifstream stream(directory + eval_file, std::ios::binary);
+ if (NNUE::load_eval(eval_file, stream))
+ currentEvalFileName = eval_file;
}
- }
- if (Pt == ROOK)
- {
- // Bonus for rook on the same file as a queen
- if (file_bb(s) & pos.pieces(QUEEN))
- score += RookOnQueenFile;
-
- // Bonus for rook on an open or semi-open file
- if (pos.is_on_semiopen_file(Us, s))
- score += RookOnFile[pos.is_on_semiopen_file(Them, s)];
-
- // Penalty when trapped by the king, even more if the king cannot castle
- else if (mob <= 3)
+ if (directory == "<internal>" && eval_file == EvalFileDefaultName)
{
- File kf = file_of(pos.square<KING>(Us));
- if ((kf < FILE_E) == (file_of(s) < kf))
- score -= TrappedRook * (1 + !pos.castling_rights(Us));
+ // C++ way to prepare a buffer for a memory stream
+ class MemoryBuffer: public std::basic_streambuf<char> {
+ public:
+ MemoryBuffer(char* p, size_t n) {
+ setg(p, p, p + n);
+ setp(p, p + n);
+ }
+ };
+
+ MemoryBuffer buffer(
+ const_cast<char*>(reinterpret_cast<const char*>(gEmbeddedNNUEData)),
+ size_t(gEmbeddedNNUESize));
+ (void) gEmbeddedNNUEEnd; // Silence warning on unused variable
+
+ std::istream stream(&buffer);
+ if (NNUE::load_eval(eval_file, stream))
+ currentEvalFileName = eval_file;
}
}
+}
- 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))
- score -= WeakQueen;
- }
- }
- if (T)
- Trace::add(Pt, Us, score);
-
- return score;
- }
-
-
- // Evaluation::king() assigns bonuses and penalties to a king of a given color
- template<Tracing T> template<Color Us>
- Score Evaluation<T>::king() const {
-
- constexpr Color Them = (Us == WHITE ? BLACK : WHITE);
- constexpr Bitboard Camp = (Us == WHITE ? AllSquares ^ Rank6BB ^ Rank7BB ^ Rank8BB
- : AllSquares ^ Rank1BB ^ Rank2BB ^ Rank3BB);
-
- Bitboard weak, b1, b2, b3, safe, unsafeChecks = 0;
- Bitboard rookChecks, queenChecks, bishopChecks, knightChecks;
- int kingDanger = 0;
- const Square ksq = pos.square<KING>(Us);
-
- // Init the score with king shelter and enemy pawns storm
- Score score = pe->king_safety<Us>(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);
- safe &= ~attackedBy[Us][ALL_PIECES] | (weak & attackedBy2[Them]);
-
- b1 = attacks_bb<ROOK >(ksq, pos.pieces() ^ pos.pieces(Us, QUEEN));
- b2 = attacks_bb<BISHOP>(ksq, pos.pieces() ^ pos.pieces(Us, QUEEN));
-
- // Enemy rooks checks
- rookChecks = b1 & safe & attackedBy[Them][ROOK];
-
- if (rookChecks)
- kingDanger += RookSafeCheck;
- else
- unsafeChecks |= b1 & attackedBy[Them][ROOK];
-
- // 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)
- & attackedBy[Them][QUEEN]
- & safe
- & ~attackedBy[Us][QUEEN]
- & ~rookChecks;
-
- if (queenChecks)
- kingDanger += QueenSafeCheck;
-
- // Enemy bishops checks: we count them only if they are from squares from
- // which we can't give a queen check, because queen checks are more valuable.
- bishopChecks = b2
- & attackedBy[Them][BISHOP]
- & safe
- & ~queenChecks;
-
- if (bishopChecks)
- kingDanger += BishopSafeCheck;
- else
- unsafeChecks |= b2 & attackedBy[Them][BISHOP];
-
- // Enemy knights checks
- knightChecks = pos.attacks_from<KNIGHT>(ksq) & attackedBy[Them][KNIGHT];
-
- if (knightChecks & safe)
- kingDanger += KnightSafeCheck;
- else
- unsafeChecks |= knightChecks;
-
- // 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[file_of(ksq)] & Camp;
- b2 = b1 & attackedBy2[Them];
- b3 = attackedBy[Us][ALL_PIECES] & KingFlank[file_of(ksq)] & Camp;
-
- int kingFlankAttack = popcount(b1) + popcount(b2);
- int kingFlankDefense = popcount(b3);
-
- kingDanger += kingAttackersCount[Them] * kingAttackersWeight[Them]
- + 185 * popcount(kingRing[Us] & weak)
- + 148 * popcount(unsafeChecks)
- + 98 * popcount(pos.blockers_for_king(Us))
- + 69 * kingAttacksCount[Them]
- + 3 * kingFlankAttack * kingFlankAttack / 8
- + mg_value(mobility[Them] - mobility[Us])
- - 873 * !pos.count<QUEEN>(Them)
- - 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(kingDanger * kingDanger / 4096, kingDanger / 16);
-
- // Penalty when our king is on a pawnless flank
- if (!(pos.pieces(PAWN) & KingFlank[file_of(ksq)]))
- score -= PawnlessFlank;
-
- // Penalty if king flank is under attack, potentially moving toward the king
- score -= FlankAttacks * kingFlankAttack;
-
- if (T)
- Trace::add(KING, Us, score);
-
- return score;
- }
-
-
- // Evaluation::threats() assigns bonuses according to the types of the
- // attacking and the attacked pieces.
- template<Tracing T> template<Color Us>
- Score Evaluation<T>::threats() const {
-
- constexpr Color Them = (Us == WHITE ? BLACK : WHITE);
- constexpr Direction Up = pawn_push(Us);
- constexpr Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB);
-
- Bitboard b, weak, defended, nonPawnEnemies, stronglyProtected, safe;
- Score score = SCORE_ZERO;
-
- // Non-pawn enemies
- nonPawnEnemies = pos.pieces(Them) & ~pos.pieces(PAWN);
-
- // 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]
- | (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);
- }
-
- // 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<Us>(b) & nonPawnEnemies;
- score += ThreatBySafePawn * popcount(b);
-
- // Find squares where our pawns can push on the next move
- b = shift<Up>(pos.pieces(Us, PAWN)) & ~pos.pieces();
- b |= shift<Up>(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<Us>(b) & nonPawnEnemies;
- score += ThreatByPawnPush * popcount(b);
-
- // Bonus for threats on the next moves against enemy queen
- if (pos.count<QUEEN>(Them) == 1)
- {
- Square s = pos.square<QUEEN>(Them);
- safe = mobilityArea[Us] & ~stronglyProtected;
-
- b = attackedBy[Us][KNIGHT] & pos.attacks_from<KNIGHT>(s);
-
- score += KnightOnQueen * popcount(b & safe);
-
- b = (attackedBy[Us][BISHOP] & pos.attacks_from<BISHOP>(s))
- | (attackedBy[Us][ROOK ] & pos.attacks_from<ROOK >(s));
-
- score += SliderOnQueen * popcount(b & safe & attackedBy2[Us]);
- }
-
- if (T)
- Trace::add(THREAT, Us, score);
-
- return score;
- }
-
- // Evaluation::passed() evaluates the passed pawns and candidate passed
- // pawns of the given color.
-
- template<Tracing T> template<Color Us>
- Score Evaluation<T>::passed() const {
-
- constexpr Color Them = (Us == WHITE ? BLACK : WHITE);
- constexpr Direction Up = pawn_push(Us);
-
- auto king_proximity = [&](Color c, Square s) {
- return std::min(distance(pos.square<KING>(c), s), 5);
- };
-
- Bitboard b, bb, squaresToQueen, unsafeSquares;
- Score score = SCORE_ZERO;
+// Verifies that the last net used was loaded successfully
+void NNUE::verify() {
- b = pe->passed_pawns(Us);
+ std::string eval_file = std::string(Options["EvalFile"]);
+ if (eval_file.empty())
+ eval_file = EvalFileDefaultName;
- while (b)
+ if (currentEvalFileName != eval_file)
{
- Square s = pop_lsb(&b);
-
- assert(!(pos.pieces(Them, PAWN) & forward_file_bb(Us, s + Up)));
-
- int r = relative_rank(Us, s);
-
- 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];
-
- // If there are no enemy attacks on passed pawn span, assign a big 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 ? 35 :
- !(unsafeSquares & squaresToQueen) ? 20 :
- !(unsafeSquares & blockSq) ? 9 :
- 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
-
- // Scale down bonus for candidate passers which need more than one
- // pawn push to become passed, or have a pawn in front of them.
- if ( !pos.pawn_passed(Us, s + Up)
- || (pos.pieces(PAWN) & (s + Up)))
- bonus = bonus / 2;
-
- score += bonus - PassedFile * map_to_queenside(file_of(s));
+ std::string msg1 =
+ "Network evaluation parameters compatible with the engine must be available.";
+ std::string msg2 = "The network file " + eval_file + " was not loaded successfully.";
+ std::string msg3 = "The UCI option EvalFile might need to specify the full path, "
+ "including the directory name, to the network file.";
+ std::string msg4 = "The default net can be downloaded from: "
+ "https://tests.stockfishchess.org/api/nn/"
+ + std::string(EvalFileDefaultName);
+ std::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 (T)
- Trace::add(PASSED, Us, score);
-
- return score;
- }
-
-
- // Evaluation::space() computes the space evaluation for a given side. The
- // space evaluation is a simple bonus based on the number of safe squares
- // available for minor pieces on the central four files on ranks 2--4. Safe
- // squares one, two or three squares behind a friendly pawn are counted
- // twice. Finally, the space bonus is multiplied by a weight. The aim is to
- // improve play on game opening.
-
- template<Tracing T> template<Color Us>
- Score Evaluation<T>::space() const {
-
- if (pos.non_pawn_material() < SpaceThreshold)
- return SCORE_ZERO;
-
- constexpr Color Them = (Us == WHITE ? BLACK : WHITE);
- 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<Down>(behind);
- behind |= shift<Down+Down>(behind);
-
- int bonus = popcount(safe) + popcount(behind & safe & ~attackedBy[Them][ALL_PIECES]);
- int weight = pos.count<ALL_PIECES>(Us) - 1;
- Score score = make_score(bonus * weight * weight / 16, 0);
-
- if (T)
- Trace::add(SPACE, Us, score);
-
- return score;
- }
-
-
- // Evaluation::initiative() computes the initiative correction value
- // for the position. It is a second order bonus/malus based on the
- // known attacking/defending status of the players.
-
- template<Tracing T>
- Score Evaluation<T>::initiative(Score score) const {
-
- Value mg = mg_value(score);
- Value eg = eg_value(score);
-
- int outflanking = distance<File>(pos.square<KING>(WHITE), pos.square<KING>(BLACK))
- - distance<Rank>(pos.square<KING>(WHITE), pos.square<KING>(BLACK));
-
- bool infiltration = rank_of(pos.square<KING>(WHITE)) > RANK_4
- || rank_of(pos.square<KING>(BLACK)) < RANK_5;
-
- bool pawnsOnBothFlanks = (pos.pieces(PAWN) & QueenSide)
- && (pos.pieces(PAWN) & KingSide);
-
- bool almostUnwinnable = !pe->passed_count()
- && outflanking < 0
- && !pawnsOnBothFlanks;
-
- // Compute the initiative bonus for the attacking side
- int complexity = 9 * pe->passed_count()
- + 11 * pos.count<PAWN>()
- + 9 * outflanking
- + 12 * infiltration
- + 21 * pawnsOnBothFlanks
- + 51 * !pos.non_pawn_material()
- - 43 * almostUnwinnable
- - 100 ;
-
- // 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::max(std::min(complexity + 50, 0), -abs(mg));
- int v = ((eg > 0) - (eg < 0)) * std::max(complexity, -abs(eg));
-
- if (T)
- Trace::add(INITIATIVE, make_score(u, v));
-
- return make_score(u, v);
- }
-
-
- // Evaluation::scale_factor() computes the scale factor for the winning side
-
- template<Tracing T>
- ScaleFactor Evaluation<T>::scale_factor(Value eg) const {
-
- Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK;
- int sf = me->scale_factor(pos, strongSide);
-
- // If scale is not already specific, scale down the endgame via general heuristics
- if (sf == SCALE_FACTOR_NORMAL)
- {
- if ( pos.opposite_bishops()
- && pos.non_pawn_material() == 2 * BishopValueMg)
- sf = 22 ;
- else
- sf = std::min(sf, 36 + (pos.opposite_bishops() ? 2 : 7) * pos.count<PAWN>(strongSide));
-
- sf = std::max(0, sf - (pos.rule50_count() - 12) / 4);
- }
+ sync_cout << "info string NNUE evaluation using " << eval_file << sync_endl;
+}
+}
- return ScaleFactor(sf);
- }
+// Returns a static, purely materialistic evaluation of the position from
+// the point of view of the given color. It can be divided by PawnValue to get
+// an approximation of the material advantage on the board in terms of pawns.
+Value Eval::simple_eval(const Position& pos, Color c) {
+ return PawnValue * (pos.count<PAWN>(c) - pos.count<PAWN>(~c))
+ + (pos.non_pawn_material(c) - pos.non_pawn_material(~c));
+}
- // 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<Tracing T>
- Value Evaluation<T>::value() {
+// 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) {
assert(!pos.checkers());
- // 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() + me->imbalance() + pos.this_thread()->contempt;
-
- // Probe the pawn hash table
- pe = Pawns::probe(pos);
- score += pe->pawn_score(WHITE) - pe->pawn_score(BLACK);
-
- // Early exit if score is high
- Value v = (mg_value(score) + eg_value(score)) / 2;
- if (abs(v) > LazyThreshold + pos.non_pawn_material() / 64)
- return pos.side_to_move() == WHITE ? v : -v;
-
- // Main evaluation begins here
-
- initialize<WHITE>();
- initialize<BLACK>();
-
- // Pieces should be evaluated first (populate attack tables)
- score += pieces<WHITE, KNIGHT>() - pieces<BLACK, KNIGHT>()
- + pieces<WHITE, BISHOP>() - pieces<BLACK, BISHOP>()
- + pieces<WHITE, ROOK >() - pieces<BLACK, ROOK >()
- + pieces<WHITE, QUEEN >() - pieces<BLACK, QUEEN >();
-
- score += mobility[WHITE] - mobility[BLACK];
+ Value v;
+ Color stm = pos.side_to_move();
+ int shuffling = pos.rule50_count();
+ int simpleEval = simple_eval(pos, stm) + (int(pos.key() & 7) - 3);
- score += king< WHITE>() - king< BLACK>()
- + threats<WHITE>() - threats<BLACK>()
- + passed< WHITE>() - passed< BLACK>()
- + space< WHITE>() - space< BLACK>();
+ bool lazy = std::abs(simpleEval) >= RookValue + KnightValue + 16 * shuffling * shuffling
+ + std::abs(pos.this_thread()->bestValue)
+ + std::abs(pos.this_thread()->rootSimpleEval);
- score += initiative(score);
+ if (lazy)
+ v = Value(simpleEval);
+ else
+ {
+ int nnueComplexity;
+ Value nnue = NNUE::evaluate(pos, true, &nnueComplexity);
- // Interpolate between a middlegame and a (scaled by 'sf') endgame score
- ScaleFactor sf = scale_factor(eg_value(score));
- v = mg_value(score) * int(me->game_phase())
- + eg_value(score) * int(PHASE_MIDGAME - me->game_phase()) * sf / SCALE_FACTOR_NORMAL;
+ Value optimism = pos.this_thread()->optimism[stm];
- v /= PHASE_MIDGAME;
+ // Blend optimism and eval with nnue complexity and material imbalance
+ optimism += optimism * (nnueComplexity + std::abs(simpleEval - nnue)) / 512;
+ nnue -= nnue * (nnueComplexity + std::abs(simpleEval - nnue)) / 32768;
- // In case of tracing add all remaining individual evaluation terms
- if (T)
- {
- Trace::add(MATERIAL, pos.psq_score());
- Trace::add(IMBALANCE, me->imbalance());
- Trace::add(PAWN, pe->pawn_score(WHITE), pe->pawn_score(BLACK));
- Trace::add(MOBILITY, mobility[WHITE], mobility[BLACK]);
- Trace::add(TOTAL, score);
+ int npm = pos.non_pawn_material() / 64;
+ v = (nnue * (915 + npm + 9 * pos.count<PAWN>()) + optimism * (154 + npm)) / 1024;
}
- return (pos.side_to_move() == WHITE ? v : -v) // Side to move point of view
- + Eval::Tempo;
- }
-
-} // namespace
-
+ // Damp down the evaluation linearly when shuffling
+ v = v * (200 - shuffling) / 214;
-/// 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.
+ // 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);
-Value Eval::evaluate(const Position& pos) {
- return Evaluation<NO_TRACE>(pos).value();
+ return v;
}
+// 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) {
-/// 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.
-
-std::string Eval::trace(const Position& pos) {
-
- if (pos.checkers())
- return "Total evaluation: none (in check)";
+ if (pos.checkers())
+ return "Final evaluation: none (in check)";
- std::memset(scores, 0, sizeof(scores));
+ // Reset any global variable used in eval
+ pos.this_thread()->bestValue = VALUE_ZERO;
+ pos.this_thread()->rootSimpleEval = VALUE_ZERO;
+ pos.this_thread()->optimism[WHITE] = VALUE_ZERO;
+ pos.this_thread()->optimism[BLACK] = VALUE_ZERO;
- pos.this_thread()->contempt = SCORE_ZERO; // Reset any dynamic contempt
+ std::stringstream ss;
+ ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2);
+ ss << '\n' << NNUE::trace(pos) << '\n';
- Value v = Evaluation<TRACE>(pos).value();
+ ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15);
- v = pos.side_to_move() == WHITE ? v : -v; // Trace scores are from white's point of view
+ Value v;
+ v = NNUE::evaluate(pos, false);
+ v = pos.side_to_move() == WHITE ? v : -v;
+ ss << "NNUE evaluation " << 0.01 * UCI::to_cp(v) << " (white side)\n";
- std::stringstream ss;
- ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2)
- << " 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)
- << " Initiative | " << Term(INITIATIVE)
- << " ------------+-------------+-------------+------------\n"
- << " Total | " << Term(TOTAL);
+ v = evaluate(pos);
+ v = pos.side_to_move() == WHITE ? v : -v;
+ ss << "Final evaluation " << 0.01 * UCI::to_cp(v) << " (white side)";
+ ss << " [with scaled NNUE, ...]";
+ ss << "\n";
- ss << "\nTotal evaluation: " << to_cp(v) << " (white side)\n";
-
- return ss.str();
+ return ss.str();
}
+
+} // namespace Stockfish
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+ Copyright (C) 2004-2023 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
#include "types.h"
+namespace Stockfish {
+
class Position;
namespace Eval {
-constexpr Value Tempo = Value(28); // Must be visible to search
-
-std::string trace(const Position& pos);
+std::string trace(Position& pos);
+Value simple_eval(const Position& pos, Color c);
Value evaluate(const Position& pos);
-}
-#endif // #ifndef EVALUATE_H_INCLUDED
+extern std::string currentEvalFileName;
+
+// 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-0000000000a0.nnue"
+
+namespace NNUE {
+
+void init();
+void verify();
+
+} // namespace NNUE
+
+} // namespace Eval
+
+} // namespace Stockfish
+
+#endif // #ifndef EVALUATE_H_INCLUDED
--- /dev/null
+#ifndef HASHPROBE_H_INCLUDED
+#define HASHPROBE_H_INCLUDED
+
+#include "position.h"
+#include "types.h"
+
+#include <deque>
+#include <string>
+
+#include <grpc/grpc.h>
+#include <grpc++/server.h>
+#include <grpc++/server_builder.h>
+#include "hashprobe.grpc.pb.h"
+
+class HashProbeImpl final : public hashprobe::HashProbe::Service {
+public:
+ grpc::Status Probe(grpc::ServerContext* context,
+ const hashprobe::HashProbeRequest* request,
+ hashprobe::HashProbeResponse *response);
+
+private:
+ void FillMove(Stockfish::Position* pos, Stockfish::Move move, hashprobe::HashProbeMove* decoded);
+ void ProbeMove(Stockfish::Position* pos, std::deque<Stockfish::StateInfo>* setup_states, bool invert, hashprobe::HashProbeLine* response);
+ void FillValue(Stockfish::Value value, hashprobe::HashProbeScore* score);
+};
+
+class HashProbeThread {
+public:
+ HashProbeThread(const std::string &server_address);
+ void Shutdown();
+
+private:
+ HashProbeImpl service;
+ grpc::ServerBuilder builder;
+ std::unique_ptr<grpc::Server> server;
+};
+
+#endif
--- /dev/null
+syntax = "proto3";
+package hashprobe;
+
+message HashProbeRequest {
+ string fen = 1;
+}
+message HashProbeResponse {
+ HashProbeLine root = 2;
+ repeated HashProbeLine line = 1;
+}
+message HashProbeLine {
+ HashProbeMove move = 1;
+ bool found = 2;
+
+ repeated HashProbeMove pv = 3;
+ HashProbeScore value = 4; // Dynamic eval (may be inexact, see the "bound" field)
+ HashProbeScore eval = 5; // Static eval
+ int32 depth = 6;
+
+ enum ValueBound {
+ BOUND_NONE = 0;
+ BOUND_UPPER = 1;
+ BOUND_LOWER = 2;
+ BOUND_EXACT = 3;
+ };
+ ValueBound bound = 7;
+}
+
+message HashProbeMove {
+ string from_sq = 1; // a1, a2, etc.
+ string to_sq = 2;
+ string promotion = 3; // Q, R, etc.
+
+ string pretty = 4; // e.g. Rxf6+
+}
+message HashProbeScore {
+ enum ScoreType {
+ SCORE_NONE = 0;
+ SCORE_CP = 1;
+ SCORE_MATE = 2;
+ }
+ ScoreType score_type = 1;
+ int32 score_cp = 2;
+ int32 score_mate = 3;
+}
+
+service HashProbe {
+ rpc Probe(HashProbeRequest) returns (HashProbeResponse) {}
+}
--- /dev/null
+The file "incbin.h" is free and unencumbered software released into
+the public domain by Dale Weiler, see:
+ <https://github.com/graphitemaster/incbin>
+
+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 <http://unlicense.org/>
--- /dev/null
+/**
+ * @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 <limits.h>
+#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 <prefix>FooData[];
+ * // const unsigned char *const <prefix>FooEnd;
+ * // const unsigned int <prefix>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 <prefix>foo_data[];
+ * // const unsigned char *const <prefix>foo_end;
+ * // const unsigned int <prefix>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 <prefix>FooData[];
+ * // extern const unsigned char *const <prefix>FooEnd;
+ * // extern const unsigned int <prefix>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 <prefix>IconData[];
+ * // const unsigned char *const <prefix>IconEnd;
+ * // const unsigned int <prefix>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
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+ Copyright (C) 2004-2023 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
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <deque>
+#include <cstddef>
#include <iostream>
+#include <stack>
+#include <thread>
#include "bitboard.h"
+#include "evaluate.h"
+#include "misc.h"
#include "position.h"
#include "search.h"
#include "thread.h"
-#include "tt.h"
+#include "tune.h"
+#include "types.h"
#include "uci.h"
-#include "endgame.h"
-#include "syzygy/tbprobe.h"
-namespace PSQT {
- void init();
+#include <grpc/grpc.h>
+#include <grpc++/server.h>
+#include <grpc++/server_builder.h>
+#include "hashprobe.h"
+#include "hashprobe.grpc.pb.h"
+#include "tt.h"
+
+using grpc::Server;
+using grpc::ServerBuilder;
+using grpc::ServerContext;
+using grpc::Status;
+using grpc::StatusCode;
+using namespace hashprobe;
+using namespace Stockfish;
+
+Status HashProbeImpl::Probe(ServerContext* context,
+ const HashProbeRequest* request,
+ HashProbeResponse *response) {
+ Position pos;
+ StateInfo st;
+ pos.set(request->fen(), /*isChess960=*/false, &st, Threads.main());
+ if (!pos.pos_is_ok()) {
+ return Status(StatusCode::INVALID_ARGUMENT, "Invalid FEN");
+ }
+
+ bool invert = (pos.side_to_move() == BLACK);
+ StateListPtr setup_states = StateListPtr(new std::deque<StateInfo>(1));
+
+ ProbeMove(&pos, setup_states.get(), invert, response->mutable_root());
+
+ MoveList<LEGAL> moves(pos);
+ for (const ExtMove* em = moves.begin(); em != moves.end(); ++em) {
+ HashProbeLine *line = response->add_line();
+ FillMove(&pos, em->move, line->mutable_move());
+ setup_states->push_back(StateInfo());
+ pos.do_move(em->move, setup_states->back());
+ ProbeMove(&pos, setup_states.get(), !invert, line);
+ pos.undo_move(em->move);
+ }
+
+ return Status::OK;
+}
+
+void HashProbeImpl::FillMove(Position *pos, Move move, HashProbeMove* decoded) {
+ if (!is_ok(move)) return;
+
+ Square from = from_sq(move);
+ Square to = to_sq(move);
+
+ if (type_of(move) == CASTLING) {
+ to = make_square(to > from ? FILE_G : FILE_C, rank_of(from));
+ }
+
+ Piece moved_piece = pos->moved_piece(move);
+ std::string pretty;
+ if (type_of(move) == CASTLING) {
+ if (to > from) {
+ pretty = "O-O";
+ } else {
+ pretty = "O-O-O";
+ }
+ } else if (type_of(moved_piece) == PAWN) {
+ if (type_of(move) == EN_PASSANT || pos->piece_on(to) != NO_PIECE) {
+ // Capture.
+ pretty = char('a' + file_of(from));
+ pretty += "x";
+ }
+ pretty += UCI::square(to);
+ if (type_of(move) == PROMOTION) {
+ pretty += "=";
+ pretty += " PNBRQK"[promotion_type(move)];
+ }
+ } else {
+ pretty = " PNBRQK"[type_of(moved_piece)];
+ Bitboard attackers = pos->attackers_to(to) & pos->pieces(color_of(moved_piece), type_of(moved_piece));
+ if (more_than_one(attackers)) {
+ // Remove all illegal moves to disambiguate.
+ Bitboard att_copy = attackers;
+ while (att_copy) {
+ Square s = pop_lsb(att_copy);
+ Move m = make_move(s, to);
+ if (!pos->pseudo_legal(m) || !pos->legal(m)) {
+ attackers &= ~square_bb(s);
+ }
+ }
+ }
+ if (more_than_one(attackers)) {
+ // Disambiguate by file if possible.
+ Bitboard attackers_this_file = attackers & file_bb(file_of(from));
+ if (attackers != attackers_this_file) {
+ pretty += char('a' + file_of(from));
+ attackers = attackers_this_file;
+ }
+ if (more_than_one(attackers)) {
+ // Still ambiguous, so need to disambiguate by rank.
+ pretty += char('1' + rank_of(from));
+ }
+ }
+
+ if (type_of(move) == EN_PASSANT || pos->piece_on(to) != NO_PIECE) {
+ pretty += "x";
+ }
+
+ pretty += UCI::square(to);
+ }
+
+ if (pos->gives_check(move)) {
+ // Check if mate.
+ StateInfo si;
+ pos->do_move(move, si, true);
+ if (MoveList<LEGAL>(*pos).size() > 0) {
+ pretty += "+";
+ } else {
+ pretty += "#";
+ }
+ pos->undo_move(move);
+ }
+
+ decoded->set_pretty(pretty);
+}
+
+void HashProbeImpl::ProbeMove(Position* pos, std::deque<StateInfo>* setup_states, bool invert, HashProbeLine* response) {
+ bool found;
+ TTEntry *entry = TT.probe(pos->key(), found);
+ response->set_found(found);
+ if (found) {
+ TTEntry entry_copy = *entry;
+ Value value = entry_copy.value();
+ Value eval = entry_copy.eval();
+ Bound bound = entry_copy.bound();
+
+ if (invert) {
+ value = -value;
+ eval = -eval;
+ if (bound == BOUND_UPPER) {
+ bound = BOUND_LOWER;
+ } else if (bound == BOUND_LOWER) {
+ bound = BOUND_UPPER;
+ }
+ }
+
+ response->set_depth(entry_copy.depth());
+ FillValue(eval, response->mutable_eval());
+ if (entry_copy.depth() > DEPTH_NONE) {
+ FillValue(value, response->mutable_value());
+ }
+ response->set_bound(HashProbeLine::ValueBound(bound));
+
+ // Follow the PV until we hit an illegal move.
+ std::stack<Move> pv;
+ std::set<Key> seen;
+ while (is_ok(entry_copy.move()) &&
+ pos->pseudo_legal(entry_copy.move()) &&
+ pos->legal(entry_copy.move())) {
+ FillMove(pos, entry_copy.move(), response->add_pv());
+ if (seen.count(pos->key())) break;
+ pv.push(entry_copy.move());
+ seen.insert(pos->key());
+ setup_states->push_back(StateInfo());
+ pos->do_move(entry_copy.move(), setup_states->back());
+ entry = TT.probe(pos->key(), found);
+ if (!found) {
+ break;
+ }
+ entry_copy = *entry;
+ }
+
+ // Unroll the PV back again, so the Position object remains unchanged.
+ while (!pv.empty()) {
+ pos->undo_move(pv.top());
+ pv.pop();
+ }
+ }
+}
+
+void HashProbeImpl::FillValue(Value value, HashProbeScore* score) {
+ if (abs(value) < VALUE_MATE - MAX_PLY) {
+ score->set_score_type(HashProbeScore::SCORE_CP);
+ score->set_score_cp(value * 100 / UCI::NormalizeToPawnValue);
+ } else {
+ score->set_score_type(HashProbeScore::SCORE_MATE);
+ score->set_score_mate((value > 0 ? VALUE_MATE - value + 1 : -VALUE_MATE - value) / 2);
+ }
+}
+
+HashProbeThread::HashProbeThread(const std::string &server_address) {
+ builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
+ builder.RegisterService(&service);
+ server = std::move(builder.BuildAndStart());
+ std::cout << "Server listening on " << server_address << std::endl;
+ std::thread([this]{ server->Wait(); }).detach();
+}
+
+void HashProbeThread::Shutdown() {
+ server->Shutdown();
}
int main(int argc, char* argv[]) {
- std::cout << engine_info() << std::endl;
+ std::cout << engine_info() << std::endl;
- UCI::init(Options);
- PSQT::init();
- Bitboards::init();
- Position::init();
- Bitbases::init();
- Endgames::init();
- Threads.set(Options["Threads"]);
- Search::clear(); // After threads are up
+ CommandLine::init(argc, argv);
+ UCI::init(Options);
+ Tune::init();
+ Bitboards::init();
+ Position::init();
+ Threads.set(size_t(Options["Threads"]));
+ Search::clear(); // After threads are up
+ Eval::NNUE::init();
- UCI::loop(argc, argv);
+ UCI::loop(argc, argv);
- Threads.set(0);
- return 0;
+ Threads.set(0);
+ return 0;
}
+++ /dev/null
-/*
- Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
-
- 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 <http://www.gnu.org/licenses/>.
-*/
-
-#include <cassert>
-#include <cstring> // For std::memset
-
-#include "material.h"
-#include "thread.h"
-
-using namespace std;
-
-namespace {
-
- // Polynomial material imbalance parameters
-
- constexpr int QuadraticOurs[][PIECE_TYPE_NB] = {
- // OUR PIECES
- // pair pawn knight bishop rook queen
- {1438 }, // Bishop pair
- { 40, 38 }, // Pawn
- { 32, 255, -62 }, // Knight OUR PIECES
- { 0, 104, 4, 0 }, // Bishop
- { -26, -2, 47, 105, -208 }, // Rook
- {-189, 24, 117, 133, -134, -6 } // Queen
- };
-
- constexpr int QuadraticTheirs[][PIECE_TYPE_NB] = {
- // THEIR PIECES
- // pair pawn knight bishop rook queen
- { 0 }, // Bishop pair
- { 36, 0 }, // Pawn
- { 9, 63, 0 }, // Knight OUR PIECES
- { 59, 65, 42, 0 }, // Bishop
- { 46, 39, 24, -24, 0 }, // Rook
- { 97, 100, -42, 137, 268, 0 } // Queen
- };
-
- // 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<KXK> EvaluateKXK[] = { Endgame<KXK>(WHITE), Endgame<KXK>(BLACK) };
-
- Endgame<KBPsK> ScaleKBPsK[] = { Endgame<KBPsK>(WHITE), Endgame<KBPsK>(BLACK) };
- Endgame<KQKRPs> ScaleKQKRPs[] = { Endgame<KQKRPs>(WHITE), Endgame<KQKRPs>(BLACK) };
- Endgame<KPsK> ScaleKPsK[] = { Endgame<KPsK>(WHITE), Endgame<KPsK>(BLACK) };
- Endgame<KPKP> ScaleKPKP[] = { Endgame<KPKP>(WHITE), Endgame<KPKP>(BLACK) };
-
- // Helper used to detect a given material distribution
- bool is_KXK(const Position& pos, Color us) {
- return !more_than_one(pos.pieces(~us))
- && pos.non_pawn_material(us) >= RookValueMg;
- }
-
- bool is_KBPsK(const Position& pos, Color us) {
- return pos.non_pawn_material(us) == BishopValueMg
- && pos.count<PAWN >(us) >= 1;
- }
-
- bool is_KQKRPs(const Position& pos, Color us) {
- return !pos.count<PAWN>(us)
- && pos.non_pawn_material(us) == QueenValueMg
- && pos.count<ROOK>(~us) == 1
- && pos.count<PAWN>(~us) >= 1;
- }
-
- /// imbalance() calculates the imbalance by comparing the piece count of each
- /// piece type for both colors.
- template<Color Us>
- int imbalance(const int pieceCount[][PIECE_TYPE_NB]) {
-
- constexpr Color Them = (Us == WHITE ? BLACK : WHITE);
-
- int bonus = 0;
-
- // Second-degree polynomial material imbalance, by Tord Romstad
- for (int pt1 = NO_PIECE_TYPE; pt1 <= QUEEN; ++pt1)
- {
- if (!pieceCount[Us][pt1])
- continue;
-
- int v = 0;
-
- 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;
- }
-
- 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();
- 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 = clamp(npm_w + npm_b, EndgameLimit, MidgameLimit);
-
- // Map total non-pawn material into [PHASE_ENDGAME, PHASE_MIDGAME]
- e->gamePhase = Phase(((npm - EndgameLimit) * PHASE_MIDGAME) / (MidgameLimit - EndgameLimit));
-
- // 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 ((e->evaluationFunction = Endgames::probe<Value>(key)) != nullptr)
- 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<ScaleFactor>(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<PAWN>(BLACK))
- {
- assert(pos.count<PAWN>(WHITE) >= 2);
-
- e->scalingFunction[WHITE] = &ScaleKPsK[WHITE];
- }
- else if (!pos.count<PAWN>(WHITE))
- {
- assert(pos.count<PAWN>(BLACK) >= 2);
-
- e->scalingFunction[BLACK] = &ScaleKPsK[BLACK];
- }
- else if (pos.count<PAWN>(WHITE) == 1 && pos.count<PAWN>(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<PAWN>(WHITE) && npm_w - npm_b <= BishopValueMg)
- e->factor[WHITE] = uint8_t(npm_w < RookValueMg ? SCALE_FACTOR_DRAW :
- npm_b <= BishopValueMg ? 4 : 14);
-
- if (!pos.count<PAWN>(BLACK) && npm_b - npm_w <= BishopValueMg)
- e->factor[BLACK] = uint8_t(npm_b < RookValueMg ? SCALE_FACTOR_DRAW :
- npm_w <= BishopValueMg ? 4 : 14);
-
- // 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<BISHOP>(WHITE) > 1, pos.count<PAWN>(WHITE), pos.count<KNIGHT>(WHITE),
- pos.count<BISHOP>(WHITE) , pos.count<ROOK>(WHITE), pos.count<QUEEN >(WHITE) },
- { pos.count<BISHOP>(BLACK) > 1, pos.count<PAWN>(BLACK), pos.count<KNIGHT>(BLACK),
- pos.count<BISHOP>(BLACK) , pos.count<ROOK>(BLACK), pos.count<QUEEN >(BLACK) } };
-
- e->value = int16_t((imbalance<WHITE>(pieceCount) - imbalance<BLACK>(pieceCount)) / 16);
- return e;
-}
-
-} // namespace Material
+++ /dev/null
-/*
- Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
-
- 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 <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef MATERIAL_H_INCLUDED
-#define MATERIAL_H_INCLUDED
-
-#include "endgame.h"
-#include "misc.h"
-#include "position.h"
-#include "types.h"
-
-namespace 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 make_score(value, value); }
- Phase game_phase() const { return gamePhase; }
- bool specialized_eval_exists() const { return evaluationFunction != nullptr; }
- Value evaluate(const Position& pos) const { return (*evaluationFunction)(pos); }
-
- // 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<Value>* evaluationFunction;
- const EndgameBase<ScaleFactor>* scalingFunction[COLOR_NB]; // Could be one for each
- // side (e.g. KPKP, KBPsK)
- int16_t value;
- uint8_t factor[COLOR_NB];
- Phase gamePhase;
-};
-
-typedef HashTable<Entry, 8192> Table;
-
-Entry* probe(const Position& pos);
-
-} // namespace Material
-
-#endif // #ifndef MATERIAL_H_INCLUDED
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+ Copyright (C) 2004-2023 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
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "misc.h"
+
#ifdef _WIN32
-#if _WIN32_WINNT < 0x0601
-#undef _WIN32_WINNT
-#define _WIN32_WINNT 0x0601 // Force to include needed API prototypes
-#endif
+ #if _WIN32_WINNT < 0x0601
+ #undef _WIN32_WINNT
+ #define _WIN32_WINNT 0x0601 // Force to include needed API prototypes
+ #endif
-#ifndef NOMINMAX
-#define NOMINMAX
-#endif
+ #ifndef NOMINMAX
+ #define NOMINMAX
+ #endif
-#include <windows.h>
+ #include <windows.h>
// 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);
+using fun1_t = bool (*)(LOGICAL_PROCESSOR_RELATIONSHIP,
+ PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX,
+ PDWORD);
+using fun2_t = bool (*)(USHORT, PGROUP_AFFINITY);
+using fun3_t = bool (*)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY);
+using fun4_t = bool (*)(USHORT, PGROUP_AFFINITY, USHORT, PUSHORT);
+using fun5_t = WORD (*)();
+using fun6_t = bool (*)(HANDLE, DWORD, PHANDLE);
+using fun7_t = bool (*)(LPCSTR, LPCSTR, PLUID);
+using fun8_t = bool (*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, PDWORD);
}
#endif
+#include <atomic>
+#include <cmath>
+#include <cstdlib>
#include <fstream>
#include <iomanip>
#include <iostream>
+#include <mutex>
#include <sstream>
-#include <vector>
+#include <string_view>
-#include "misc.h"
-#include "thread.h"
+#include "types.h"
-using namespace std;
+#if defined(__linux__) && !defined(__ANDROID__)
+ #include <sys/mman.h>
+#endif
+
+#if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__) \
+ || (defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC) && !defined(_WIN32)) \
+ || defined(__e2k__)
+ #define POSIXALIGNEDALLOC
+ #include <stdlib.h>
+#endif
+
+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 = "11";
+// Version number or dev.
+constexpr std::string_view version = "dev";
-/// 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
+// 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
+struct Tie: public std::streambuf { // MSVC requires split streambuf for cin and cout
- Tie(streambuf* b, streambuf* l) : buf(b), logBuf(l) {}
+ Tie(std::streambuf* b, std::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(), ">> "); }
+ 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;
+ std::streambuf *buf, *logBuf;
- int log(int c, const char* prefix) {
+ int log(int c, const char* prefix) {
- static int last = '\n'; // Single log file
+ static int last = '\n'; // Single log file
- if (last == '\n')
- logBuf->sputn(prefix, 3);
+ if (last == '\n')
+ logBuf->sputn(prefix, 3);
- return last = logBuf->sputc((char)c);
- }
+ return last = logBuf->sputc(char(c));
+ }
};
class Logger {
- Logger() : in(cin.rdbuf(), file.rdbuf()), out(cout.rdbuf(), file.rdbuf()) {}
- ~Logger() { start(""); }
+ Logger() :
+ in(std::cin.rdbuf(), file.rdbuf()),
+ out(std::cout.rdbuf(), file.rdbuf()) {}
+ ~Logger() { start(""); }
- ofstream file;
- Tie in, out;
+ std::ofstream file;
+ Tie in, out;
-public:
- static void start(const std::string& fname) {
+ public:
+ static void start(const std::string& fname) {
- static Logger l;
+ static Logger l;
- if (!fname.empty() && !l.file.is_open())
- {
- l.file.open(fname, ifstream::out);
-
- if (!l.file.is_open())
+ if (l.file.is_open())
{
- cerr << "Unable to open debug log file " << fname << endl;
- exit(EXIT_FAILURE);
+ std::cout.rdbuf(l.out.buf);
+ std::cin.rdbuf(l.in.buf);
+ l.file.close();
}
- cin.rdbuf(&l.in);
- cout.rdbuf(&l.out);
+ if (!fname.empty())
+ {
+ l.file.open(fname, std::ifstream::out);
+
+ if (!l.file.is_open())
+ {
+ std::cerr << "Unable to open debug log file " << fname << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ std::cin.rdbuf(&l.in);
+ std::cout.rdbuf(&l.out);
+ }
}
- else if (fname.empty() && l.file.is_open())
+};
+
+} // namespace
+
+
+// Returns the full name of the current Stockfish version.
+// For local dev compiles we try to append the commit sha and commit date
+// from git if that fails only the local compilation date is set and "nogit" is specified:
+// Stockfish dev-YYYYMMDD-SHA
+// or
+// Stockfish dev-YYYYMMDD-nogit
+//
+// For releases (non-dev builds) we only include the version number:
+// Stockfish version
+std::string engine_info(bool to_uci) {
+ std::stringstream ss;
+ ss << "Stockfish " << version << std::setfill('0');
+
+ if constexpr (version == "dev")
{
- cout.rdbuf(l.out.buf);
- cin.rdbuf(l.in.buf);
- l.file.close();
+ ss << "-";
+#ifdef GIT_DATE
+ ss << stringify(GIT_DATE);
+#else
+ constexpr std::string_view months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec");
+ std::string month, day, year;
+ std::stringstream date(__DATE__); // From compiler, format is "Sep 21 2008"
+
+ date >> month >> day >> year;
+ ss << year << std::setw(2) << std::setfill('0') << (1 + months.find(month) / 4)
+ << std::setw(2) << std::setfill('0') << day;
+#endif
+
+ ss << "-";
+
+#ifdef GIT_SHA
+ ss << stringify(GIT_SHA);
+#else
+ ss << "nogit";
+#endif
+ ss << "-asn";
}
- }
-};
-} // namespace
+ ss << (to_uci ? "\nid author " : " by ") << "the Stockfish developers (see AUTHORS file)";
-/// engine_info() returns the full name of the current Stockfish version. This
-/// will be either "Stockfish <Tag> DD-MM-YY" (where DD-MM-YY is the date when
-/// the program was compiled) or "Stockfish <Version>", depending on whether
-/// Version is empty.
+ return ss.str();
+}
-const string engine_info(bool to_uci) {
- 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"
+// Returns a string trying to describe the compiler we use
+std::string compiler_info() {
+
+#define make_version_string(major, minor, patch) \
+ stringify(major) "." stringify(minor) "." stringify(patch)
+
+ // Predefined macros hell:
+ //
+ // __GNUC__ Compiler is GCC, Clang or ICX
+ // __clang__ Compiler is Clang or ICX
+ // __INTEL_LLVM_COMPILER Compiler is ICX
+ // _MSC_VER Compiler is MSVC
+ // _WIN32 Building on Windows (any)
+ // _WIN64 Building on Windows 64 bit
+
+ std::string compiler = "\nCompiled by : ";
+
+#if defined(__INTEL_LLVM_COMPILER)
+ compiler += "ICX ";
+ compiler += stringify(__INTEL_LLVM_COMPILER);
+#elif defined(__clang__)
+ compiler += "clang++ ";
+ compiler += make_version_string(__clang_major__, __clang_minor__, __clang_patchlevel__);
+#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
- ss << "Stockfish " << Version << setfill('0');
+#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
- if (Version.empty())
- {
- date >> month >> day >> year;
- ss << setw(2) << day << setw(2) << (1 + months.find(month) / 4) << year.substr(2);
- }
+ compiler += "\nCompilation architecture : ";
+#if defined(ARCH)
+ compiler += stringify(ARCH);
+#else
+ compiler += "(undefined architecture)";
+#endif
- ss << (Is64Bit ? " 64" : "")
- << (HasPext ? " BMI2" : (HasPopCnt ? " POPCNT" : ""))
- << (to_uci ? "\nid author ": " by ")
- << "T. Romstad, M. Costalba, J. Kiiski, G. Linscott";
+ compiler += "\nCompilation settings : ";
+ 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_NEON_DOTPROD)
+ compiler += " NEON_DOTPROD";
+#elif defined(USE_NEON)
+ compiler += " NEON";
+#endif
+
+#if !defined(NDEBUG)
+ compiler += " DEBUG";
+#endif
+
+ compiler += "\nCompiler __VERSION__ macro : ";
+#ifdef __VERSION__
+ compiler += __VERSION__;
+#else
+ compiler += "(undefined macro)";
+#endif
- return ss.str();
+ compiler += "\n";
+
+ return compiler;
}
-/// compiler_info() returns a string trying to describe the compiler we use
-
-const std::string compiler_info() {
-
- #define STRINGIFY2(x) #x
- #define STRINGIFY(x) STRINGIFY2(x)
- #define VER_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 += VER_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 __GNUC__
- compiler += "g++ (GNUC) ";
- compiler += VER_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 += "\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
+constexpr int MaxDebugSlots = 32;
+
+namespace {
+
+template<size_t N>
+struct DebugInfo {
+ std::atomic<int64_t> data[N] = {0};
+
+ constexpr inline std::atomic<int64_t>& operator[](int index) { return data[index]; }
+};
+
+DebugInfo<2> hit[MaxDebugSlots];
+DebugInfo<2> mean[MaxDebugSlots];
+DebugInfo<3> stdev[MaxDebugSlots];
+DebugInfo<6> correl[MaxDebugSlots];
+
+} // namespace
+
+void dbg_hit_on(bool cond, int slot) {
+
+ ++hit[slot][0];
+ if (cond)
+ ++hit[slot][1];
}
+void dbg_mean_of(int64_t value, int slot) {
-/// Debug functions used mainly to collect run-time statistics
-static std::atomic<int64_t> hits[2], means[2];
+ ++mean[slot][0];
+ mean[slot][1] += value;
+}
-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_stdev_of(int64_t value, int slot) {
-void dbg_print() {
+ ++stdev[slot][0];
+ stdev[slot][1] += value;
+ stdev[slot][2] += value * value;
+}
- if (hits[0])
- cerr << "Total " << hits[0] << " Hits " << hits[1]
- << " hit rate (%) " << 100 * hits[1] / hits[0] << endl;
+void dbg_correl_of(int64_t value1, int64_t value2, int slot) {
- if (means[0])
- cerr << "Total " << means[0] << " Mean "
- << (double)means[1] / means[0] << endl;
+ ++correl[slot][0];
+ correl[slot][1] += value1;
+ correl[slot][2] += value1 * value1;
+ correl[slot][3] += value2;
+ correl[slot][4] += value2 * value2;
+ correl[slot][5] += value1 * value2;
}
+void dbg_print() {
+
+ int64_t n;
+ auto E = [&n](int64_t x) { return double(x) / n; };
+ auto sqr = [](double x) { return x * x; };
-/// Used to serialize access to std::cout to avoid multiple threads writing at
-/// the same time.
+ for (int i = 0; i < MaxDebugSlots; ++i)
+ if ((n = hit[i][0]))
+ std::cerr << "Hit #" << i << ": Total " << n << " Hits " << hit[i][1]
+ << " Hit Rate (%) " << 100.0 * E(hit[i][1]) << std::endl;
+ for (int i = 0; i < MaxDebugSlots; ++i)
+ if ((n = mean[i][0]))
+ {
+ std::cerr << "Mean #" << i << ": Total " << n << " Mean " << E(mean[i][1]) << std::endl;
+ }
+
+ for (int i = 0; i < MaxDebugSlots; ++i)
+ if ((n = stdev[i][0]))
+ {
+ double r = sqrt(E(stdev[i][2]) - sqr(E(stdev[i][1])));
+ std::cerr << "Stdev #" << i << ": Total " << n << " Stdev " << r << std::endl;
+ }
+
+ for (int i = 0; i < MaxDebugSlots; ++i)
+ if ((n = correl[i][0]))
+ {
+ double r = (E(correl[i][5]) - E(correl[i][1]) * E(correl[i][3]))
+ / (sqrt(E(correl[i][2]) - sqr(E(correl[i][1])))
+ * sqrt(E(correl[i][4]) - sqr(E(correl[i][3]))));
+ std::cerr << "Correl. #" << i << ": Total " << n << " Coefficient " << r << std::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;
+ static std::mutex m;
- if (sc == IO_LOCK)
- m.lock();
+ if (sc == IO_LOCK)
+ m.lock();
- if (sc == IO_UNLOCK)
- m.unlock();
+ if (sc == IO_UNLOCK)
+ m.unlock();
- return os;
+ return os;
}
-/// Trampoline helper to avoid moving Logger to misc.h
+// 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*) {}
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
+ #if defined(_MSC_VER)
+ _mm_prefetch((char*) addr, _MM_HINT_T0);
+ #else
+ __builtin_prefetch(addr);
+ #endif
+}
+
+#endif
+
+
+// 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) && !defined(_M_ARM) && !defined(_M_ARM64)
+ return _mm_malloc(size, alignment);
+#elif defined(_WIN32)
+ return _aligned_malloc(size, alignment);
+#else
+ return std::aligned_alloc(alignment, size);
+#endif
+}
+
+void std_aligned_free(void* ptr) {
+
+#if defined(POSIXALIGNEDALLOC)
+ free(ptr);
+#elif defined(_WIN32) && !defined(_M_ARM) && !defined(_M_ARM64)
+ _mm_free(ptr);
+#elif defined(_WIN32)
+ _aligned_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([[maybe_unused]] 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;
+
+ // Dynamically link OpenProcessToken, LookupPrivilegeValue and AdjustTokenPrivileges
+
+ HMODULE hAdvapi32 = GetModuleHandle(TEXT("advapi32.dll"));
+
+ if (!hAdvapi32)
+ hAdvapi32 = LoadLibrary(TEXT("advapi32.dll"));
+
+ auto fun6 = fun6_t((void (*)()) GetProcAddress(hAdvapi32, "OpenProcessToken"));
+ if (!fun6)
+ return nullptr;
+ auto fun7 = fun7_t((void (*)()) GetProcAddress(hAdvapi32, "LookupPrivilegeValueA"));
+ if (!fun7)
+ return nullptr;
+ auto fun8 = fun8_t((void (*)()) GetProcAddress(hAdvapi32, "AdjustTokenPrivileges"));
+ if (!fun8)
+ return nullptr;
+
+ // We need SeLockMemoryPrivilege, so try to enable it for the process
+ if (!fun6( // OpenProcessToken()
+ GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken))
+ return nullptr;
+
+ if (fun7( // LookupPrivilegeValue(nullptr, SE_LOCK_MEMORY_NAME, &luid)
+ nullptr, "SeLockMemoryPrivilege", &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 (fun8( // 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(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES,
+ PAGE_READWRITE);
+
+ // Privilege no longer needed, restore previous state
+ fun8( // AdjustTokenPrivileges ()
+ hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr);
+ }
+ }
+
+ 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(nullptr, 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
#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<int> 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;
-}
+// Retrieves logical processor information using Windows-specific
+// API and returns the best node id for the thread with index idx. Original
+// code from Texel by Peter Österlund.
+static int best_node(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(TEXT("Kernel32.dll"));
+ auto fun1 = (fun1_t) (void (*)()) GetProcAddress(k32, "GetLogicalProcessorInformationEx");
+ if (!fun1)
+ return -1;
+
+ // First call to GetLogicalProcessorInformationEx() to get returnLength.
+ // We expect the call 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 to GetLogicalProcessorInformationEx(), now we expect to succeed
+ if (!fun1(RelationAll, buffer, &returnLength))
+ {
+ free(buffer);
+ return -1;
+ }
+ while (byteOffset < returnLength)
+ {
+ if (ptr->Relationship == RelationNumaNode)
+ nodes++;
-/// bindThisThread() set the group affinity of the current thread
+ 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<int> groups;
+
+ // Run as many threads as possible on the same node until the core limit is
+ // reached, then move on to 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
+ // still have threads to allocate, 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;
+}
+
+
+// Sets 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);
+ // Use only local variables to be thread-safe
+ int node = best_node(idx);
- if (group == -1)
- return;
+ if (node == -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");
+ // Early exit if the needed API are not available at runtime
+ HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll"));
+ auto fun2 = fun2_t((void (*)()) GetProcAddress(k32, "GetNumaNodeProcessorMaskEx"));
+ auto fun3 = fun3_t((void (*)()) GetProcAddress(k32, "SetThreadGroupAffinity"));
+ auto fun4 = fun4_t((void (*)()) GetProcAddress(k32, "GetNumaNodeProcessorMask2"));
+ auto fun5 = fun5_t((void (*)()) GetProcAddress(k32, "GetMaximumProcessorGroupCount"));
- if (!fun2 || !fun3)
- return;
+ if (!fun2 || !fun3)
+ return;
- GROUP_AFFINITY affinity;
- if (fun2(group, &affinity))
- fun3(GetCurrentThread(), &affinity, nullptr);
+ if (!fun4 || !fun5)
+ {
+ GROUP_AFFINITY affinity;
+ if (fun2(node, &affinity)) // GetNumaNodeProcessorMaskEx
+ fun3(GetCurrentThread(), &affinity, nullptr); // SetThreadGroupAffinity
+ }
+ else
+ {
+ // If a numa node has more than one processor group, we assume they are
+ // sized equal and we spread threads evenly across the groups.
+ USHORT elements, returnedElements;
+ elements = fun5(); // GetMaximumProcessorGroupCount
+ GROUP_AFFINITY* affinity = (GROUP_AFFINITY*) malloc(elements * sizeof(GROUP_AFFINITY));
+ if (fun4(node, affinity, elements, &returnedElements)) // GetNumaNodeProcessorMask2
+ fun3(GetCurrentThread(), &affinity[idx % returnedElements],
+ nullptr); // SetThreadGroupAffinity
+ free(affinity);
+ }
}
#endif
-} // namespace WinProcGroup
+} // namespace WinProcGroup
+
+#ifdef _WIN32
+ #include <direct.h>
+ #define GETCWD _getcwd
+#else
+ #include <unistd.h>
+ #define GETCWD getcwd
+#endif
+
+namespace CommandLine {
+
+std::string argv0; // path+name of the executable binary, as given by argv[0]
+std::string binaryDirectory; // path of the executable directory
+std::string workingDirectory; // path of the working directory
+
+void init([[maybe_unused]] int argc, char* argv[]) {
+ std::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
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+ Copyright (C) 2004-2023 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
#include <cassert>
#include <chrono>
-#include <ostream>
+#include <cstddef>
+#include <cstdint>
+#include <iosfwd>
#include <string>
-#include <vector>
-#include "types.h"
+#define stringify2(x) #x
+#define stringify(x) stringify2(x)
-const std::string engine_info(bool to_uci = false);
-const std::string compiler_info();
+namespace Stockfish {
+
+std::string engine_info(bool to_uci = false);
+std::string compiler_info();
+
+// 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.
void prefetch(void* addr);
-void start_logger(const std::string& fname);
-void dbg_hit_on(bool b);
-void dbg_hit_on(bool c, bool b);
-void dbg_mean_of(int v);
+void start_logger(const std::string& fname);
+void* std_aligned_alloc(size_t alignment, size_t size);
+void std_aligned_free(void* ptr);
+// memory aligned by page size, min alignment: 4096 bytes
+void* aligned_large_pages_alloc(size_t size);
+// nop if mem == nullptr
+void aligned_large_pages_free(void* mem);
+
+void dbg_hit_on(bool cond, int slot = 0);
+void dbg_mean_of(int64_t value, int slot = 0);
+void dbg_stdev_of(int64_t value, int slot = 0);
+void dbg_correl_of(int64_t value1, int64_t value2, int slot = 0);
void dbg_print();
-typedef std::chrono::milliseconds::rep TimePoint; // A value in milliseconds
-
+using TimePoint = std::chrono::milliseconds::rep; // 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::milliseconds>
- (std::chrono::steady_clock::now().time_since_epoch()).count();
+ return std::chrono::duration_cast<std::chrono::milliseconds>(
+ std::chrono::steady_clock::now().time_since_epoch())
+ .count();
}
-template<class Entry, int Size>
-struct HashTable {
- Entry* operator[](Key key) { return &table[(uint32_t)key & (Size - 1)]; }
-private:
- std::vector<Entry> table = std::vector<Entry>(Size); // Allocate on the heap
+enum SyncCout {
+ IO_LOCK,
+ IO_UNLOCK
};
-
-
-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
-/// 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
-/// <http://vigna.di.unimi.it/ftp/papers/xorshift.pdf>
+// 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<uintptr_t Alignment, typename T>
+T* align_ptr_up(T* ptr) {
+ static_assert(alignof(T) < Alignment);
-class PRNG {
+ const uintptr_t ptrint = reinterpret_cast<uintptr_t>(reinterpret_cast<char*>(ptr));
+ return reinterpret_cast<T*>(
+ reinterpret_cast<char*>((ptrint + (Alignment - 1)) / Alignment * Alignment));
+}
- uint64_t s;
- uint64_t rand64() {
+// 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);
- s ^= s >> 12, s ^= s << 25, s ^= s >> 27;
- return s * 2685821657736338717LL;
- }
-public:
- PRNG(uint64_t seed) : s(seed) { assert(seed); }
+template<typename T, std::size_t MaxSize>
+class ValueList {
- template<typename T> T rand() { return T(rand64()); }
+ public:
+ std::size_t size() const { return size_; }
+ void push_back(const T& value) { values_[size_++] = value; }
+ const T* begin() const { return values_; }
+ const T* end() const { return values_ + size_; }
+ const T& operator[](int index) const { return values_[index]; }
- /// Special generator used to fast init magic numbers.
- /// Output values only have 1/8th of their bits set on average.
- template<typename T> T sparse_rand()
- { return T(rand64() & rand64() & rand64()); }
+ private:
+ T values_[MaxSize];
+ std::size_t size_ = 0;
};
-/// 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.
+// 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
+// <http://vigna.di.unimi.it/ftp/papers/xorshift.pdf>
+
+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<typename T>
+ 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<typename T>
+ 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__ using uint128 = unsigned __int128;
+ 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 being limited to using 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);
+void bindThisThread(size_t idx);
}
-#endif // #ifndef MISC_H_INCLUDED
+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
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+ Copyright (C) 2004-2023 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
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "movegen.h"
+
#include <cassert>
+#include <initializer_list>
-#include "movegen.h"
+#include "bitboard.h"
#include "position.h"
+namespace Stockfish {
+
namespace {
- template<GenType Type, Direction D>
- ExtMove* make_promotions(ExtMove* moveList, Square to, Square ksq) {
+template<GenType Type, Direction D, bool Enemy>
+ExtMove* make_promotions(ExtMove* moveList, [[maybe_unused]] Square to) {
- if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
+ constexpr bool all = Type == EVASIONS || Type == NON_EVASIONS;
+
+ if constexpr (Type == CAPTURES || all)
*moveList++ = make<PROMOTION>(to - D, to, QUEEN);
- if (Type == QUIETS || Type == EVASIONS || Type == NON_EVASIONS)
+ if constexpr ((Type == CAPTURES && Enemy) || (Type == QUIETS && !Enemy) || all)
{
*moveList++ = make<PROMOTION>(to - D, to, ROOK);
*moveList++ = make<PROMOTION>(to - D, to, BISHOP);
*moveList++ = make<PROMOTION>(to - D, to, KNIGHT);
}
- // Knight promotion is the only promotion that can give a direct check
- // that's not already included in the queen promotion.
- if (Type == QUIET_CHECKS && (PseudoAttacks[KNIGHT][to] & ksq))
- *moveList++ = make<PROMOTION>(to - D, to, KNIGHT);
- else
- (void)ksq; // Silence a warning under MSVC
-
return moveList;
- }
+}
- template<Color Us, GenType Type>
- ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard target) {
+template<Color Us, GenType Type>
+ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard target) {
- // Compute some compile time parameters relative to the white side
- constexpr Color Them = (Us == WHITE ? BLACK : WHITE);
- constexpr Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB);
- constexpr Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB);
+ constexpr Color Them = ~Us;
+ constexpr Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB);
+ constexpr Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB);
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 Square ksq = pos.square<KING>(Them);
- Bitboard emptySquares;
+ const Bitboard emptySquares = ~pos.pieces();
+ const Bitboard enemies = Type == EVASIONS ? pos.checkers() : pos.pieces(Them);
- Bitboard pawnsOn7 = pos.pieces(Us, PAWN) & TRank7BB;
+ Bitboard pawnsOn7 = pos.pieces(Us, PAWN) & TRank7BB;
Bitboard pawnsNotOn7 = pos.pieces(Us, PAWN) & ~TRank7BB;
- Bitboard enemies = (Type == EVASIONS ? pos.pieces(Them) & target:
- Type == CAPTURES ? target : pos.pieces(Them));
-
// Single and double pawn pushes, no promotions
- if (Type != CAPTURES)
+ if constexpr (Type != CAPTURES)
{
- emptySquares = (Type == QUIETS || Type == QUIET_CHECKS ? target : ~pos.pieces());
-
- Bitboard b1 = shift<Up>(pawnsNotOn7) & emptySquares;
+ Bitboard b1 = shift<Up>(pawnsNotOn7) & emptySquares;
Bitboard b2 = shift<Up>(b1 & TRank3BB) & emptySquares;
- if (Type == EVASIONS) // Consider only blocking squares
+ if constexpr (Type == EVASIONS) // Consider only blocking squares
{
b1 &= target;
b2 &= target;
}
- if (Type == QUIET_CHECKS)
+ if constexpr (Type == QUIET_CHECKS)
{
- b1 &= pos.attacks_from<PAWN>(ksq, Them);
- b2 &= pos.attacks_from<PAWN>(ksq, Them);
-
- // Add pawn pushes which give discovered check. This is possible only
- // if the pawn is not on the same file as the enemy king, because we
- // don't generate captures. Note that a possible discovery check
- // promotion has been already generated amongst the captures.
- Bitboard dcCandidateQuiets = pos.blockers_for_king(Them) & pawnsNotOn7;
- if (dcCandidateQuiets)
- {
- Bitboard dc1 = shift<Up>(dcCandidateQuiets) & emptySquares & ~file_bb(ksq);
- Bitboard dc2 = shift<Up>(dc1 & TRank3BB) & emptySquares;
-
- b1 |= dc1;
- b2 |= dc2;
- }
+ // 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<KING>(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<Up + Up>(dcCandidatePawns);
}
while (b1)
{
- Square to = pop_lsb(&b1);
+ Square to = pop_lsb(b1);
*moveList++ = make_move(to - Up, to);
}
while (b2)
{
- Square to = pop_lsb(&b2);
+ Square to = pop_lsb(b2);
*moveList++ = make_move(to - Up - Up, to);
}
}
// Promotions and underpromotions
if (pawnsOn7)
{
- if (Type == CAPTURES)
- emptySquares = ~pos.pieces();
-
- if (Type == EVASIONS)
- emptySquares &= target;
-
Bitboard b1 = shift<UpRight>(pawnsOn7) & enemies;
- Bitboard b2 = shift<UpLeft >(pawnsOn7) & enemies;
- Bitboard b3 = shift<Up >(pawnsOn7) & emptySquares;
+ Bitboard b2 = shift<UpLeft>(pawnsOn7) & enemies;
+ Bitboard b3 = shift<Up>(pawnsOn7) & emptySquares;
+
+ if constexpr (Type == EVASIONS)
+ b3 &= target;
while (b1)
- moveList = make_promotions<Type, UpRight>(moveList, pop_lsb(&b1), ksq);
+ moveList = make_promotions<Type, UpRight, true>(moveList, pop_lsb(b1));
while (b2)
- moveList = make_promotions<Type, UpLeft >(moveList, pop_lsb(&b2), ksq);
+ moveList = make_promotions<Type, UpLeft, true>(moveList, pop_lsb(b2));
while (b3)
- moveList = make_promotions<Type, Up >(moveList, pop_lsb(&b3), ksq);
+ moveList = make_promotions<Type, Up, false>(moveList, pop_lsb(b3));
}
- // Standard and en-passant captures
- if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
+ // Standard and en passant captures
+ if constexpr (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
{
Bitboard b1 = shift<UpRight>(pawnsNotOn7) & enemies;
- Bitboard b2 = shift<UpLeft >(pawnsNotOn7) & enemies;
+ Bitboard b2 = shift<UpLeft>(pawnsNotOn7) & enemies;
while (b1)
{
- Square to = pop_lsb(&b1);
+ Square to = pop_lsb(b1);
*moveList++ = make_move(to - UpRight, to);
}
while (b2)
{
- Square to = pop_lsb(&b2);
+ Square to = pop_lsb(b2);
*moveList++ = make_move(to - UpLeft, to);
}
{
assert(rank_of(pos.ep_square()) == relative_rank(Us, RANK_6));
- // An en passant capture can be an evasion only if the checking piece
- // is the double pushed pawn and so is in the target. Otherwise this
- // is a discovery check and we are forced to do otherwise.
- if (Type == EVASIONS && !(target & (pos.ep_square() - Up)))
+ // An en passant capture cannot resolve a discovered check
+ if (Type == EVASIONS && (target & (pos.ep_square() + Up)))
return moveList;
- b1 = pawnsNotOn7 & pos.attacks_from<PAWN>(pos.ep_square(), Them);
+ b1 = pawnsNotOn7 & pawn_attacks_bb(Them, pos.ep_square());
assert(b1);
while (b1)
- *moveList++ = make<ENPASSANT>(pop_lsb(&b1), pos.ep_square());
+ *moveList++ = make<EN_PASSANT>(pop_lsb(b1), pos.ep_square());
}
}
return moveList;
- }
+}
- template<PieceType Pt, bool Checks>
- ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Color us,
- Bitboard target) {
+template<Color Us, PieceType Pt, bool Checks>
+ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target) {
static_assert(Pt != KING && Pt != PAWN, "Unsupported piece type in generate_moves()");
- const Square* pl = pos.squares<Pt>(us);
+ Bitboard bb = pos.pieces(Us, Pt);
- for (Square from = *pl; from != SQ_NONE; from = *++pl)
+ while (bb)
{
- if (Checks)
- {
- if ( (Pt == BISHOP || Pt == ROOK || Pt == QUEEN)
- && !(PseudoAttacks[Pt][from] & target & pos.check_squares(Pt)))
- continue;
-
- if (pos.blockers_for_king(~us) & from)
- continue;
- }
-
- Bitboard b = pos.attacks_from<Pt>(from) & target;
+ Square from = pop_lsb(bb);
+ Bitboard b = attacks_bb<Pt>(from, pos.pieces()) & target;
- if (Checks)
+ // To check, you either move freely a blocker or make a direct check.
+ if (Checks && (Pt == QUEEN || !(pos.blockers_for_king(~Us) & from)))
b &= pos.check_squares(Pt);
while (b)
- *moveList++ = make_move(from, pop_lsb(&b));
+ *moveList++ = make_move(from, pop_lsb(b));
}
return moveList;
- }
+}
- template<Color Us, GenType Type>
- ExtMove* generate_all(const Position& pos, ExtMove* moveList, Bitboard target) {
+template<Color Us, GenType Type>
+ExtMove* generate_all(const Position& pos, ExtMove* moveList) {
- constexpr CastlingRights OO = Us & KING_SIDE;
- constexpr CastlingRights OOO = Us & QUEEN_SIDE;
- constexpr bool Checks = Type == QUIET_CHECKS; // Reduce template instantations
+ static_assert(Type != LEGAL, "Unsupported type in generate_all()");
- moveList = generate_pawn_moves<Us, Type>(pos, moveList, target);
- moveList = generate_moves<KNIGHT, Checks>(pos, moveList, Us, target);
- moveList = generate_moves<BISHOP, Checks>(pos, moveList, Us, target);
- moveList = generate_moves< ROOK, Checks>(pos, moveList, Us, target);
- moveList = generate_moves< QUEEN, Checks>(pos, moveList, Us, target);
+ constexpr bool Checks = Type == QUIET_CHECKS; // Reduce template instantiations
+ const Square ksq = pos.square<KING>(Us);
+ Bitboard target;
- if (Type != QUIET_CHECKS && Type != EVASIONS)
+ // Skip generating non-king moves when in double check
+ if (Type != EVASIONS || !more_than_one(pos.checkers()))
{
- Square ksq = pos.square<KING>(Us);
- Bitboard b = pos.attacks_from<KING>(ksq) & target;
- while (b)
- *moveList++ = make_move(ksq, pop_lsb(&b));
+ 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
+
+ moveList = generate_pawn_moves<Us, Type>(pos, moveList, target);
+ moveList = generate_moves<Us, KNIGHT, Checks>(pos, moveList, target);
+ moveList = generate_moves<Us, BISHOP, Checks>(pos, moveList, target);
+ moveList = generate_moves<Us, ROOK, Checks>(pos, moveList, target);
+ moveList = generate_moves<Us, QUEEN, Checks>(pos, moveList, target);
+ }
- if (Type != CAPTURES && pos.can_castle(CastlingRights(OO | OOO)))
- {
- if (!pos.castling_impeded(OO) && pos.can_castle(OO))
- *moveList++ = make<CASTLING>(ksq, pos.castling_rook_square(OO));
+ if (!Checks || pos.blockers_for_king(~Us) & ksq)
+ {
+ Bitboard b = attacks_bb<KING>(ksq) & (Type == EVASIONS ? ~pos.pieces(Us) : target);
+ if (Checks)
+ b &= ~attacks_bb<QUEEN>(pos.square<KING>(~Us));
- if (!pos.castling_impeded(OOO) && pos.can_castle(OOO))
- *moveList++ = make<CASTLING>(ksq, pos.castling_rook_square(OOO));
- }
+ while (b)
+ *moveList++ = make_move(ksq, pop_lsb(b));
+
+ 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<CASTLING>(ksq, pos.castling_rook_square(cr));
}
return moveList;
- }
-
-} // namespace
+}
+} // namespace
-/// <CAPTURES> Generates all pseudo-legal captures and queen promotions
-/// <QUIETS> Generates all pseudo-legal non-captures and underpromotions
-/// <NON_EVASIONS> Generates all pseudo-legal captures and non-captures
-///
-/// Returns a pointer to the end of the move list.
+// <CAPTURES> Generates all pseudo-legal captures plus queen promotions
+// <QUIETS> Generates all pseudo-legal non-captures and underpromotions
+// <EVASIONS> Generates all pseudo-legal check evasions
+// <NON_EVASIONS> Generates all pseudo-legal captures and non-captures
+// <QUIET_CHECKS> Generates all pseudo-legal non-captures giving check,
+// except castling and promotions
+//
+// Returns a pointer to the end of the move list.
template<GenType Type>
ExtMove* generate(const Position& pos, ExtMove* moveList) {
- static_assert(Type == CAPTURES || Type == QUIETS || Type == NON_EVASIONS, "Unsupported type in generate()");
- assert(!pos.checkers());
-
- Color us = pos.side_to_move();
+ static_assert(Type != LEGAL, "Unsupported type in generate()");
+ assert((Type == EVASIONS) == bool(pos.checkers()));
- Bitboard target = Type == CAPTURES ? pos.pieces(~us)
- : Type == QUIETS ? ~pos.pieces()
- : Type == NON_EVASIONS ? ~pos.pieces(us) : 0;
+ Color us = pos.side_to_move();
- return us == WHITE ? generate_all<WHITE, Type>(pos, moveList, target)
- : generate_all<BLACK, Type>(pos, moveList, target);
+ return us == WHITE ? generate_all<WHITE, Type>(pos, moveList)
+ : generate_all<BLACK, Type>(pos, moveList);
}
// Explicit template instantiations
template ExtMove* generate<CAPTURES>(const Position&, ExtMove*);
template ExtMove* generate<QUIETS>(const Position&, ExtMove*);
+template ExtMove* generate<EVASIONS>(const Position&, ExtMove*);
+template ExtMove* generate<QUIET_CHECKS>(const Position&, ExtMove*);
template ExtMove* generate<NON_EVASIONS>(const Position&, ExtMove*);
-/// generate<QUIET_CHECKS> generates all pseudo-legal non-captures and knight
-/// underpromotions that give check. Returns a pointer to the end of the move list.
-template<>
-ExtMove* generate<QUIET_CHECKS>(const Position& pos, ExtMove* moveList) {
-
- assert(!pos.checkers());
-
- Color us = pos.side_to_move();
- Bitboard dc = pos.blockers_for_king(~us) & pos.pieces(us);
-
- while (dc)
- {
- Square from = pop_lsb(&dc);
- PieceType pt = type_of(pos.piece_on(from));
-
- if (pt == PAWN)
- continue; // Will be generated together with direct checks
-
- Bitboard b = pos.attacks_from(pt, from) & ~pos.pieces();
+// generate<LEGAL> generates all the legal moves in the given position
- if (pt == KING)
- b &= ~PseudoAttacks[QUEEN][pos.square<KING>(~us)];
-
- while (b)
- *moveList++ = make_move(from, pop_lsb(&b));
- }
-
- return us == WHITE ? generate_all<WHITE, QUIET_CHECKS>(pos, moveList, ~pos.pieces())
- : generate_all<BLACK, QUIET_CHECKS>(pos, moveList, ~pos.pieces());
-}
-
-
-/// generate<EVASIONS> generates all pseudo-legal check evasions when the side
-/// to move is in check. Returns a pointer to the end of the move list.
template<>
-ExtMove* generate<EVASIONS>(const Position& pos, ExtMove* moveList) {
-
- assert(pos.checkers());
-
- Color us = pos.side_to_move();
- Square ksq = pos.square<KING>(us);
- Bitboard sliderAttacks = 0;
- Bitboard sliders = pos.checkers() & ~pos.pieces(KNIGHT, PAWN);
-
- // Find all the squares attacked by slider checkers. We will remove them from
- // the king evasions in order to skip known illegal moves, which avoids any
- // useless legality checks later on.
- while (sliders)
- {
- Square checksq = pop_lsb(&sliders);
- sliderAttacks |= LineBB[checksq][ksq] ^ checksq;
- }
-
- // Generate evasions for king, capture and non capture moves
- Bitboard b = pos.attacks_from<KING>(ksq) & ~pos.pieces(us) & ~sliderAttacks;
- while (b)
- *moveList++ = make_move(ksq, pop_lsb(&b));
-
- if (more_than_one(pos.checkers()))
- return moveList; // Double check, only a king move can save the day
-
- // Generate blocking evasions or captures of the checking piece
- Square checksq = lsb(pos.checkers());
- Bitboard target = between_bb(checksq, ksq) | checksq;
-
- return us == WHITE ? generate_all<WHITE, EVASIONS>(pos, moveList, target)
- : generate_all<BLACK, EVASIONS>(pos, moveList, target);
-}
-
+ExtMove* generate<LEGAL>(const Position& pos, ExtMove* moveList) {
-/// generate<LEGAL> generates all the legal moves in the given position
+ Color us = pos.side_to_move();
+ Bitboard pinned = pos.blockers_for_king(us) & pos.pieces(us);
+ Square ksq = pos.square<KING>(us);
+ ExtMove* cur = moveList;
-template<>
-ExtMove* generate<LEGAL>(const Position& pos, ExtMove* moveList) {
+ moveList =
+ pos.checkers() ? generate<EVASIONS>(pos, moveList) : generate<NON_EVASIONS>(pos, moveList);
+ while (cur != moveList)
+ if (((pinned & from_sq(*cur)) || from_sq(*cur) == ksq || type_of(*cur) == EN_PASSANT)
+ && !pos.legal(*cur))
+ *cur = (--moveList)->move;
+ else
+ ++cur;
- Color us = pos.side_to_move();
- Bitboard pinned = pos.blockers_for_king(us) & pos.pieces(us);
- Square ksq = pos.square<KING>(us);
- ExtMove* cur = moveList;
-
- moveList = pos.checkers() ? generate<EVASIONS >(pos, moveList)
- : generate<NON_EVASIONS>(pos, moveList);
- while (cur != moveList)
- if ( (pinned || from_sq(*cur) == ksq || type_of(*cur) == ENPASSANT)
- && !pos.legal(*cur))
- *cur = (--moveList)->move;
- else
- ++cur;
-
- return moveList;
+ return moveList;
}
+
+} // namespace Stockfish
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+ Copyright (C) 2004-2023 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
#ifndef MOVEGEN_H_INCLUDED
#define MOVEGEN_H_INCLUDED
-#include <algorithm>
+#include <algorithm> // IWYU pragma: keep
+#include <cstddef>
#include "types.h"
+namespace Stockfish {
+
class Position;
enum GenType {
- CAPTURES,
- QUIETS,
- QUIET_CHECKS,
- EVASIONS,
- NON_EVASIONS,
- LEGAL
+ CAPTURES,
+ QUIETS,
+ QUIET_CHECKS,
+ EVASIONS,
+ NON_EVASIONS,
+ LEGAL
};
struct ExtMove {
- Move move;
- int value;
+ Move move;
+ int value;
- operator Move() const { return move; }
- void operator=(Move m) { move = m; }
+ 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;
+ // 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;
-}
+inline bool operator<(const ExtMove& f, const ExtMove& s) { return f.value < s.value; }
template<GenType>
ExtMove* generate(const Position& pos, ExtMove* moveList);
-/// 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.
+// The MoveList struct wraps the generate() function and returns a convenient
+// list of moves. Using MoveList is sometimes preferable to directly calling
+// the lower level generate() function.
template<GenType T>
struct MoveList {
- explicit MoveList(const Position& pos) : last(generate<T>(pos, moveList)) {}
- 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();
- }
+ explicit MoveList(const Position& pos) :
+ last(generate<T>(pos, moveList)) {}
+ 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 moveList[MAX_MOVES], *last;
+ private:
+ ExtMove moveList[MAX_MOVES], *last;
};
-#endif // #ifndef MOVEGEN_H_INCLUDED
+} // namespace Stockfish
+
+#endif // #ifndef MOVEGEN_H_INCLUDED
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+ Copyright (C) 2004-2023 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
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "movepick.h"
+
+#include <algorithm>
#include <cassert>
+#include <iterator>
+#include <utility>
-#include "movepick.h"
+#include "bitboard.h"
+#include "position.h"
-namespace {
+namespace Stockfish {
- 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
- };
+namespace {
- // 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) {
+enum Stages {
+ // generate main search moves
+ MAIN_TT,
+ CAPTURE_INIT,
+ GOOD_CAPTURE,
+ REFUTATION,
+ QUIET_INIT,
+ QUIET,
+ BAD_CAPTURE,
+
+ // generate evasion moves
+ EVASION_TT,
+ EVASION_INIT,
+ EVASION,
+
+ // generate probcut moves
+ PROBCUT_TT,
+ PROBCUT_INIT,
+ PROBCUT,
+
+ // generate qsearch moves
+ QSEARCH_TT,
+ QCAPTURE_INIT,
+ QCAPTURE,
+ QCHECK_INIT,
+ QCHECK
+};
+
+// Sort 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;
+ *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 CapturePieceToHistory* cph, const PieceToHistory** ch, Move cm, Move* killers)
- : pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch),
- refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}}, depth(d) {
-
- assert(d > 0);
-
- stage = pos.checkers() ? EVASION_TT : MAIN_TT;
- ttMove = ttm && pos.pseudo_legal(ttm) ? ttm : MOVE_NONE;
- stage += (ttMove == MOVE_NONE);
}
-/// MovePicker constructor for quiescence search
-MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh,
- const CapturePieceToHistory* cph, const PieceToHistory** ch, Square rs)
- : pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch), recaptureSquare(rs), depth(d) {
-
- assert(d <= 0);
-
- stage = pos.checkers() ? EVASION_TT : QSEARCH_TT;
- ttMove = ttm
- && (depth > DEPTH_QS_RECAPTURES || to_sq(ttm) == recaptureSquare)
- && pos.pseudo_legal(ttm) ? ttm : MOVE_NONE;
- stage += (ttMove == MOVE_NONE);
+} // namespace
+
+
+// Constructors of the MovePicker class. As arguments, we pass information
+// to help it 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 a 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 CapturePieceToHistory* cph,
+ const PieceToHistory** ch,
+ const PawnHistory* ph,
+ Move cm,
+ const Move* killers) :
+ pos(p),
+ mainHistory(mh),
+ captureHistory(cph),
+ continuationHistory(ch),
+ pawnHistory(ph),
+ ttMove(ttm),
+ refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}},
+ depth(d) {
+ assert(d > 0);
+
+ stage = (pos.checkers() ? EVASION_TT : MAIN_TT) + !(ttm && 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 CapturePieceToHistory* cph)
- : pos(p), captureHistory(cph), threshold(th) {
-
- assert(!pos.checkers());
+// Constructor for quiescence search
+MovePicker::MovePicker(const Position& p,
+ Move ttm,
+ Depth d,
+ const ButterflyHistory* mh,
+ const CapturePieceToHistory* cph,
+ const PieceToHistory** ch,
+ const PawnHistory* ph) :
+ pos(p),
+ mainHistory(mh),
+ captureHistory(cph),
+ continuationHistory(ch),
+ pawnHistory(ph),
+ ttMove(ttm),
+ depth(d) {
+ assert(d <= 0);
+
+ stage = (pos.checkers() ? EVASION_TT : QSEARCH_TT) + !(ttm && pos.pseudo_legal(ttm));
+}
- stage = PROBCUT_TT;
- ttMove = ttm
- && pos.capture(ttm)
- && pos.pseudo_legal(ttm)
- && pos.see_ge(ttm, threshold) ? ttm : MOVE_NONE;
- stage += (ttMove == MOVE_NONE);
+// 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 CapturePieceToHistory* cph) :
+ pos(p),
+ captureHistory(cph),
+ ttMove(ttm),
+ threshold(th) {
+ assert(!pos.checkers());
+
+ stage = PROBCUT_TT
+ + !(ttm && pos.capture_stage(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.
+// 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 history tables.
template<GenType Type>
void MovePicker::score() {
- static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type");
-
- for (auto& m : *this)
- if (Type == CAPTURES)
- m.value = int(PieceValue[MG][pos.piece_on(to_sq(m))]) * 6
- + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))];
-
- else if (Type == QUIETS)
- m.value = (*mainHistory)[pos.side_to_move()][from_to(m)]
- + 2 * (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)]
- + 2 * (*continuationHistory[1])[pos.moved_piece(m)][to_sq(m)]
- + 2 * (*continuationHistory[3])[pos.moved_piece(m)][to_sq(m)]
- + (*continuationHistory[5])[pos.moved_piece(m)][to_sq(m)];
-
- 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)]
- + (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)]
- - (1 << 28);
- }
+ static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type");
+
+ [[maybe_unused]] Bitboard threatenedByPawn, threatenedByMinor, threatenedByRook,
+ threatenedPieces;
+ if constexpr (Type == QUIETS)
+ {
+ Color us = pos.side_to_move();
+
+ threatenedByPawn = pos.attacks_by<PAWN>(~us);
+ threatenedByMinor =
+ pos.attacks_by<KNIGHT>(~us) | pos.attacks_by<BISHOP>(~us) | threatenedByPawn;
+ threatenedByRook = pos.attacks_by<ROOK>(~us) | threatenedByMinor;
+
+ // Pieces threatened by pieces of lesser material value
+ threatenedPieces = (pos.pieces(us, QUEEN) & threatenedByRook)
+ | (pos.pieces(us, ROOK) & threatenedByMinor)
+ | (pos.pieces(us, KNIGHT, BISHOP) & threatenedByPawn);
+ }
+
+ for (auto& m : *this)
+ if constexpr (Type == CAPTURES)
+ m.value =
+ (7 * int(PieceValue[pos.piece_on(to_sq(m))])
+ + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))])
+ / 16;
+
+ else if constexpr (Type == QUIETS)
+ {
+ Piece pc = pos.moved_piece(m);
+ PieceType pt = type_of(pos.moved_piece(m));
+ Square from = from_sq(m);
+ Square to = to_sq(m);
+
+ // histories
+ m.value = 2 * (*mainHistory)[pos.side_to_move()][from_to(m)];
+ m.value += 2 * (*pawnHistory)[pawn_structure(pos)][pc][to];
+ m.value += 2 * (*continuationHistory[0])[pc][to];
+ m.value += (*continuationHistory[1])[pc][to];
+ m.value += (*continuationHistory[2])[pc][to] / 4;
+ m.value += (*continuationHistory[3])[pc][to];
+ m.value += (*continuationHistory[5])[pc][to];
+
+ // bonus for checks
+ m.value += bool(pos.check_squares(pt) & to) * 16384;
+
+ // bonus for escaping from capture
+ m.value += threatenedPieces & from ? (pt == QUEEN && !(to & threatenedByRook) ? 50000
+ : pt == ROOK && !(to & threatenedByMinor) ? 25000
+ : !(to & threatenedByPawn) ? 15000
+ : 0)
+ : 0;
+
+ // malus for putting piece en prise
+ m.value -= !(threatenedPieces & from)
+ ? (pt == QUEEN ? bool(to & threatenedByRook) * 50000
+ + bool(to & threatenedByMinor) * 10000
+ + bool(to & threatenedByPawn) * 20000
+ : pt == ROOK ? bool(to & threatenedByMinor) * 25000
+ + bool(to & threatenedByPawn) * 10000
+ : pt != PAWN ? bool(to & threatenedByPawn) * 15000
+ : 0)
+ : 0;
+ }
+
+ else // Type == EVASIONS
+ {
+ if (pos.capture_stage(m))
+ m.value = PieceValue[pos.piece_on(to_sq(m))] - Value(type_of(pos.moved_piece(m)))
+ + (1 << 28);
+ else
+ m.value = (*mainHistory)[pos.side_to_move()][from_to(m)]
+ + (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)]
+ + (*pawnHistory)[pawn_structure(pos)][pos.moved_piece(m)][to_sq(m)];
+ }
}
-/// MovePicker::select() returns the next move satisfying a predicate function.
-/// It never returns the TT move.
+// Returns the next move satisfying a predicate function.
+// It never returns the TT move.
template<MovePicker::PickType T, typename Pred>
Move MovePicker::select(Pred filter) {
- while (cur < endMoves)
- {
- if (T == Best)
- std::swap(*cur, *std::max_element(cur, endMoves));
+ while (cur < endMoves)
+ {
+ if constexpr (T == Best)
+ std::swap(*cur, *std::max_element(cur, endMoves));
- if (*cur != ttMove && filter())
- return *cur++;
+ if (*cur != ttMove && filter())
+ return *cur++;
- cur++;
- }
- return MOVE_NONE;
+ 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.
+// 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;
- return ttMove;
-
- case CAPTURE_INIT:
- case PROBCUT_INIT:
- case QCAPTURE_INIT:
- cur = endBadCaptures = moves;
- endMoves = generate<CAPTURES>(pos, cur);
-
- score<CAPTURES>();
- ++stage;
- goto top;
-
- case GOOD_CAPTURE:
- if (select<Best>([&](){
- return pos.see_ge(*cur, Value(-55 * cur->value / 1024)) ?
- // 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<Next>([&](){ return *cur != MOVE_NONE
- && !pos.capture(*cur)
- && pos.pseudo_legal(*cur); }))
- return *(cur - 1);
- ++stage;
- /* fallthrough */
-
- case QUIET_INIT:
- if (!skipQuiets)
- {
- cur = endBadCaptures;
- endMoves = generate<QUIETS>(pos, cur);
-
- score<QUIETS>();
- partial_insertion_sort(cur, endMoves, -3000 * depth);
- }
-
- ++stage;
- /* fallthrough */
-
- case QUIET:
- if ( !skipQuiets
- && select<Next>([&](){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<Next>([](){ return true; });
-
- case EVASION_INIT:
- cur = moves;
- endMoves = generate<EVASIONS>(pos, cur);
-
- score<EVASIONS>();
- ++stage;
- /* fallthrough */
-
- case EVASION:
- return select<Best>([](){ return true; });
-
- case PROBCUT:
- return select<Best>([&](){ return pos.see_ge(*cur, threshold); });
-
- case QCAPTURE:
- if (select<Best>([&](){ 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<QUIET_CHECKS>(pos, cur);
-
- ++stage;
- /* fallthrough */
-
- case QCHECK:
- return select<Next>([](){ return true; });
- }
-
- assert(false);
- return MOVE_NONE; // Silence warning
+ switch (stage)
+ {
+
+ case MAIN_TT :
+ case EVASION_TT :
+ case QSEARCH_TT :
+ case PROBCUT_TT :
+ ++stage;
+ return ttMove;
+
+ case CAPTURE_INIT :
+ case PROBCUT_INIT :
+ case QCAPTURE_INIT :
+ cur = endBadCaptures = moves;
+ endMoves = generate<CAPTURES>(pos, cur);
+
+ score<CAPTURES>();
+ partial_insertion_sort(cur, endMoves, std::numeric_limits<int>::min());
+ ++stage;
+ goto top;
+
+ case GOOD_CAPTURE :
+ if (select<Next>([&]() {
+ // Move losing capture to endBadCaptures to be tried later
+ return pos.see_ge(*cur, Value(-cur->value)) ? 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<Next>([&]() {
+ return *cur != MOVE_NONE && !pos.capture_stage(*cur) && pos.pseudo_legal(*cur);
+ }))
+ return *(cur - 1);
+ ++stage;
+ [[fallthrough]];
+
+ case QUIET_INIT :
+ if (!skipQuiets)
+ {
+ cur = endBadCaptures;
+ endMoves = generate<QUIETS>(pos, cur);
+
+ score<QUIETS>();
+ partial_insertion_sort(cur, endMoves, -1960 - 3130 * depth);
+ }
+
+ ++stage;
+ [[fallthrough]];
+
+ case QUIET :
+ if (!skipQuiets && select<Next>([&]() {
+ 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<Next>([]() { return true; });
+
+ case EVASION_INIT :
+ cur = moves;
+ endMoves = generate<EVASIONS>(pos, cur);
+
+ score<EVASIONS>();
+ ++stage;
+ [[fallthrough]];
+
+ case EVASION :
+ return select<Best>([]() { return true; });
+
+ case PROBCUT :
+ return select<Next>([&]() { return pos.see_ge(*cur, threshold); });
+
+ case QCAPTURE :
+ if (select<Next>([]() { return true; }))
+ 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<QUIET_CHECKS>(pos, cur);
+
+ ++stage;
+ [[fallthrough]];
+
+ case QCHECK :
+ return select<Next>([]() { return true; });
+ }
+
+ assert(false);
+ return MOVE_NONE; // Silence warning
}
+
+} // namespace Stockfish
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+ Copyright (C) 2004-2023 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
#define MOVEPICK_H_INCLUDED
#include <array>
+#include <cassert>
+#include <cmath>
+#include <cstdint>
+#include <cstdlib>
#include <limits>
-#include <type_traits>
+#include <type_traits> // IWYU pragma: keep
#include "movegen.h"
-#include "position.h"
#include "types.h"
+#include "position.h"
-/// 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<typename T, int D>
-class StatsEntry {
+namespace Stockfish {
- T entry;
+constexpr int PAWN_HISTORY_SIZE = 512; // has to be a power of 2
-public:
- void operator=(const T& v) { entry = v; }
- T* operator&() { return &entry; }
- T* operator->() { return &entry; }
- operator const T&() const { return entry; }
+static_assert((PAWN_HISTORY_SIZE & (PAWN_HISTORY_SIZE - 1)) == 0,
+ "PAWN_HISTORY_SIZE has to be a power of 2");
- void operator<<(int bonus) {
- assert(abs(bonus) <= D); // Ensure range is [-D, D]
- static_assert(D <= std::numeric_limits<T>::max(), "D overflows T");
+inline int pawn_structure(const Position& pos) { return pos.pawn_key() & (PAWN_HISTORY_SIZE - 1); }
- entry += bonus - entry * abs(bonus) / D;
+// 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 a 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<typename T, int D>
+class StatsEntry {
- assert(abs(entry) <= D);
- }
-};
+ T entry;
-/// 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 <typename T, int D, int Size, int... Sizes>
-struct Stats : public std::array<Stats<T, D, Sizes...>, Size>
-{
- typedef Stats<T, D, Size, Sizes...> stats;
-
- void fill(const T& v) {
-
- // For standard-layout 'this' points to first struct member
- assert(std::is_standard_layout<stats>::value);
-
- typedef StatsEntry<T, D> entry;
- entry* p = reinterpret_cast<entry*>(this);
- std::fill(p, p + sizeof(*this) / sizeof(entry), v);
- }
-};
+ public:
+ void operator=(const T& v) { entry = v; }
+ T* operator&() { return &entry; }
+ T* operator->() { return &entry; }
+ operator const T&() const { return entry; }
-template <typename T, int D, int Size>
-struct Stats<T, D, Size> : public std::array<StatsEntry<T, D>, Size> {};
+ void operator<<(int bonus) {
+ assert(std::abs(bonus) <= D); // Ensure range is [-D, D]
+ static_assert(D <= std::numeric_limits<T>::max(), "D overflows T");
-/// In stats table, D=0 means that the template parameter is not used
-enum StatsParams { NOT_USED = 0 };
-enum StatsType { NoCaptures, Captures };
+ entry += bonus - entry * std::abs(bonus) / D;
-/// 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<int16_t, 10692, COLOR_NB, int(SQUARE_NB) * int(SQUARE_NB)> ButterflyHistory;
+ assert(std::abs(entry) <= D);
+ }
+};
-/// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous
-/// move, see www.chessprogramming.org/Countermove_Heuristic
-typedef Stats<Move, NOT_USED, PIECE_NB, SQUARE_NB> CounterMoveHistory;
+// Stats is a generic N-dimensional array used to store various statistics.
+// The first template parameter T is the base type of the array, and 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<typename T, int D, int Size, int... Sizes>
+struct Stats: public std::array<Stats<T, D, Sizes...>, Size> {
+ using stats = Stats<T, D, Size, Sizes...>;
-/// CapturePieceToHistory is addressed by a move's [piece][to][captured piece type]
-typedef Stats<int16_t, 10692, PIECE_NB, SQUARE_NB, PIECE_TYPE_NB> CapturePieceToHistory;
+ void fill(const T& v) {
-/// PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to]
-typedef Stats<int16_t, 29952, PIECE_NB, SQUARE_NB> PieceToHistory;
+ // For standard-layout 'this' points to the first struct member
+ assert(std::is_standard_layout_v<stats>);
-/// 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<PieceToHistory, NOT_USED, PIECE_NB, SQUARE_NB> ContinuationHistory;
+ using entry = StatsEntry<T, D>;
+ entry* p = reinterpret_cast<entry*>(this);
+ std::fill(p, p + sizeof(*this) / sizeof(entry), v);
+ }
+};
+template<typename T, int D, int Size>
+struct Stats<T, D, Size>: public std::array<StatsEntry<T, D>, Size> {};
-/// 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.
+// In stats table, D=0 means that the template parameter is not used
+enum StatsParams {
+ NOT_USED = 0
+};
+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 (~11 elo)
+using ButterflyHistory = Stats<int16_t, 7183, COLOR_NB, int(SQUARE_NB) * int(SQUARE_NB)>;
+
+// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous
+// move, see www.chessprogramming.org/Countermove_Heuristic
+using CounterMoveHistory = Stats<Move, NOT_USED, PIECE_NB, SQUARE_NB>;
+
+// CapturePieceToHistory is addressed by a move's [piece][to][captured piece type]
+using CapturePieceToHistory = Stats<int16_t, 10692, PIECE_NB, SQUARE_NB, PIECE_TYPE_NB>;
+
+// PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to]
+using PieceToHistory = Stats<int16_t, 29952, PIECE_NB, SQUARE_NB>;
+
+// 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.
+// (~63 elo)
+using ContinuationHistory = Stats<PieceToHistory, NOT_USED, PIECE_NB, SQUARE_NB>;
+
+// PawnHistory is addressed by the pawn structure and a move's [piece][to]
+using PawnHistory = Stats<int16_t, 8192, PAWN_HISTORY_SIZE, PIECE_NB, SQUARE_NB>;
+
+// 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 CapturePieceToHistory*);
- MovePicker(const Position&, Move, Depth, const ButterflyHistory*,
- const CapturePieceToHistory*,
- const PieceToHistory**,
- Square);
- MovePicker(const Position&, Move, Depth, const ButterflyHistory*,
- const CapturePieceToHistory*,
- const PieceToHistory**,
- Move,
- Move*);
- Move next_move(bool skipQuiets = false);
-
-private:
- template<PickType T, typename Pred> Move select(Pred);
- template<GenType> void score();
- ExtMove* begin() { return cur; }
- ExtMove* end() { return endMoves; }
-
- const Position& pos;
- const ButterflyHistory* mainHistory;
- const CapturePieceToHistory* captureHistory;
- const PieceToHistory** continuationHistory;
- Move ttMove;
- ExtMove refutations[3], *cur, *endMoves, *endBadCaptures;
- int stage;
- Square recaptureSquare;
- Value threshold;
- Depth depth;
- ExtMove moves[MAX_MOVES];
+ enum PickType {
+ Next,
+ Best
+ };
+
+ public:
+ MovePicker(const MovePicker&) = delete;
+ MovePicker& operator=(const MovePicker&) = delete;
+ MovePicker(const Position&,
+ Move,
+ Depth,
+ const ButterflyHistory*,
+ const CapturePieceToHistory*,
+ const PieceToHistory**,
+ const PawnHistory*,
+ Move,
+ const Move*);
+ MovePicker(const Position&,
+ Move,
+ Depth,
+ const ButterflyHistory*,
+ const CapturePieceToHistory*,
+ const PieceToHistory**,
+ const PawnHistory*);
+ MovePicker(const Position&, Move, Value, const CapturePieceToHistory*);
+ Move next_move(bool skipQuiets = false);
+
+ private:
+ template<PickType T, typename Pred>
+ Move select(Pred);
+ template<GenType>
+ void score();
+ ExtMove* begin() { return cur; }
+ ExtMove* end() { return endMoves; }
+
+ const Position& pos;
+ const ButterflyHistory* mainHistory;
+ const CapturePieceToHistory* captureHistory;
+ const PieceToHistory** continuationHistory;
+ const PawnHistory* pawnHistory;
+ Move ttMove;
+ ExtMove refutations[3], *cur, *endMoves, *endBadCaptures;
+ int stage;
+ Value threshold;
+ Depth depth;
+ ExtMove moves[MAX_MOVES];
};
-#endif // #ifndef MOVEPICK_H_INCLUDED
+} // namespace Stockfish
+
+#endif // #ifndef MOVEPICK_H_INCLUDED
--- /dev/null
+/*
+ Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+ Copyright (C) 2004-2023 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 <http://www.gnu.org/licenses/>.
+*/
+
+// Code for calculating NNUE evaluation function
+
+#include "evaluate_nnue.h"
+
+#include <cmath>
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+#include <sstream>
+#include <string_view>
+
+#include "../evaluate.h"
+#include "../misc.h"
+#include "../position.h"
+#include "../types.h"
+#include "../uci.h"
+#include "nnue_accumulator.h"
+#include "nnue_common.h"
+
+namespace Stockfish::Eval::NNUE {
+
+// Input feature converter
+LargePagePtr<FeatureTransformer> featureTransformer;
+
+// Evaluation function
+AlignedPtr<Network> network[LayerStacks];
+
+// Evaluation function file name
+std::string fileName;
+std::string netDescription;
+
+namespace Detail {
+
+// Initialize the evaluation function parameters
+template<typename T>
+void initialize(AlignedPtr<T>& pointer) {
+
+ pointer.reset(reinterpret_cast<T*>(std_aligned_alloc(alignof(T), sizeof(T))));
+ std::memset(pointer.get(), 0, sizeof(T));
+}
+
+template<typename T>
+void initialize(LargePagePtr<T>& pointer) {
+
+ static_assert(alignof(T) <= 4096,
+ "aligned_large_pages_alloc() may fail for such a big alignment requirement of T");
+ pointer.reset(reinterpret_cast<T*>(aligned_large_pages_alloc(sizeof(T))));
+ std::memset(pointer.get(), 0, sizeof(T));
+}
+
+// Read evaluation function parameters
+template<typename T>
+bool read_parameters(std::istream& stream, T& reference) {
+
+ std::uint32_t header;
+ header = read_little_endian<std::uint32_t>(stream);
+ if (!stream || header != T::get_hash_value())
+ return false;
+ return reference.read_parameters(stream);
+}
+
+// Write evaluation function parameters
+template<typename T>
+bool write_parameters(std::ostream& stream, const T& reference) {
+
+ write_little_endian<std::uint32_t>(stream, T::get_hash_value());
+ return reference.write_parameters(stream);
+}
+
+} // namespace Detail
+
+
+// Initialize the evaluation function parameters
+static void initialize() {
+
+ Detail::initialize(featureTransformer);
+ for (std::size_t i = 0; i < LayerStacks; ++i)
+ Detail::initialize(network[i]);
+}
+
+// Read network header
+static bool read_header(std::istream& stream, std::uint32_t* hashValue, std::string* desc) {
+ std::uint32_t version, size;
+
+ version = read_little_endian<std::uint32_t>(stream);
+ *hashValue = read_little_endian<std::uint32_t>(stream);
+ size = read_little_endian<std::uint32_t>(stream);
+ if (!stream || version != Version)
+ return false;
+ desc->resize(size);
+ stream.read(&(*desc)[0], size);
+ return !stream.fail();
+}
+
+// Write network header
+static bool write_header(std::ostream& stream, std::uint32_t hashValue, const std::string& desc) {
+ write_little_endian<std::uint32_t>(stream, Version);
+ write_little_endian<std::uint32_t>(stream, hashValue);
+ write_little_endian<std::uint32_t>(stream, std::uint32_t(desc.size()));
+ stream.write(&desc[0], desc.size());
+ return !stream.fail();
+}
+
+// Read network parameters
+static 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
+static 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);
+}
+
+void hint_common_parent_position(const Position& pos) {
+ featureTransformer->hint_common_access(pos);
+}
+
+// Evaluation function. Perform differential calculation.
+Value evaluate(const Position& pos, bool adjusted, int* complexity) {
+
+ // 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;
+ constexpr int delta = 24;
+
+#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN)
+ TransformedFeatureType
+ transformedFeaturesUnaligned[FeatureTransformer::BufferSize
+ + alignment / sizeof(TransformedFeatureType)];
+
+ auto* transformedFeatures = align_ptr_up<alignment>(&transformedFeaturesUnaligned[0]);
+#else
+ alignas(alignment) TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize];
+#endif
+
+ ASSERT_ALIGNED(transformedFeatures, alignment);
+
+ const int bucket = (pos.count<ALL_PIECES>() - 1) / 4;
+ const auto psqt = featureTransformer->transform(pos, transformedFeatures, bucket);
+ const auto positional = network[bucket]->propagate(transformedFeatures);
+
+ if (complexity)
+ *complexity = std::abs(psqt - positional) / OutputScale;
+
+ // Give more value to positional evaluation when adjusted flag is set
+ if (adjusted)
+ return static_cast<Value>(((1024 - delta) * psqt + (1024 + delta) * positional)
+ / (1024 * OutputScale));
+ else
+ return static_cast<Value>((psqt + positional) / 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)];
+
+ auto* transformedFeatures = align_ptr_up<alignment>(&transformedFeaturesUnaligned[0]);
+#else
+ alignas(alignment) TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize];
+#endif
+
+ ASSERT_ALIGNED(transformedFeatures, alignment);
+
+ NnueEvalTrace t{};
+ t.correctBucket = (pos.count<ALL_PIECES>() - 1) / 4;
+ for (IndexType bucket = 0; bucket < LayerStacks; ++bucket)
+ {
+ const auto materialist = featureTransformer->transform(pos, transformedFeatures, bucket);
+ const auto positional = network[bucket]->propagate(transformedFeatures);
+
+ t.psqt[bucket] = static_cast<Value>(materialist / OutputScale);
+ t.positional[bucket] = static_cast<Value>(positional / OutputScale);
+ }
+
+ return t;
+}
+
+constexpr std::string_view PieceToChar(" PNBRQK pnbrqk");
+
+
+// Converts a Value into (centi)pawns and writes it in a buffer.
+// The buffer must have capacity for at least 5 chars.
+static void format_cp_compact(Value v, char* buffer) {
+
+ buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' ');
+
+ int cp = std::abs(UCI::to_cp(v));
+ if (cp >= 10000)
+ {
+ buffer[1] = '0' + cp / 10000;
+ cp %= 10000;
+ buffer[2] = '0' + cp / 1000;
+ cp %= 1000;
+ buffer[3] = '0' + 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;
+ }
+}
+
+
+// Converts a Value into pawns, always keeping two decimals
+static void format_cp_aligned_dot(Value v, std::stringstream& stream) {
+
+ const double pawns = std::abs(0.01 * UCI::to_cp(v));
+
+ stream << (v < 0 ? '-'
+ : v > 0 ? '+'
+ : ' ')
+ << std::setiosflags(std::ios::fixed) << std::setw(6) << std::setprecision(2) << pawns;
+}
+
+
+// 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 * 8 + 1][8 * 8 + 2];
+ std::memset(board, ' ', sizeof(board));
+ for (int row = 0; row < 3 * 8 + 1; ++row)
+ board[row][8 * 8 + 1] = '\0';
+
+ // A lambda to output one box of the board
+ auto writeSquare = [&board](File file, Rank rank, Piece pc, Value value) {
+ const int x = int(file) * 8;
+ const int y = (7 - 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] = PieceToChar[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 <= FILE_H; ++f)
+ for (Rank r = RANK_1; r <= RANK_8; ++r)
+ {
+ Square sq = make_square(f, r);
+ Piece pc = pos.piece_on(sq);
+ Value v = VALUE_NONE;
+
+ if (pc != NO_PIECE && type_of(pc) != 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);
+ 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 * 8 + 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)
+ {
+ ss << "| " << bucket << " ";
+ ss << " | ";
+ format_cp_aligned_dot(t.psqt[bucket], ss);
+ ss << " "
+ << " | ";
+ format_cp_aligned_dot(t.positional[bucket], ss);
+ ss << " "
+ << " | ";
+ format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss);
+ ss << " "
+ << " |";
+ 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<std::string>& filename) {
+
+ std::string actualFilename;
+ std::string msg;
+
+ if (filename.has_value())
+ actualFilename = filename.value();
+ else
+ {
+ if (currentEvalFileName != 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
--- /dev/null
+/*
+ Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+ Copyright (C) 2004-2023 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 <http://www.gnu.org/licenses/>.
+*/
+
+// header used in NNUE evaluation function
+
+#ifndef NNUE_EVALUATE_NNUE_H_INCLUDED
+#define NNUE_EVALUATE_NNUE_H_INCLUDED
+
+#include <cstdint>
+#include <iosfwd>
+#include <memory>
+#include <optional>
+#include <string>
+
+#include "../misc.h"
+#include "nnue_architecture.h"
+#include "nnue_feature_transformer.h"
+
+namespace Stockfish {
+class Position;
+enum Value : int;
+}
+
+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<typename T>
+struct AlignedDeleter {
+ void operator()(T* ptr) const {
+ ptr->~T();
+ std_aligned_free(ptr);
+ }
+};
+
+template<typename T>
+struct LargePageDeleter {
+ void operator()(T* ptr) const {
+ ptr->~T();
+ aligned_large_pages_free(ptr);
+ }
+};
+
+template<typename T>
+using AlignedPtr = std::unique_ptr<T, AlignedDeleter<T>>;
+
+template<typename T>
+using LargePagePtr = std::unique_ptr<T, LargePageDeleter<T>>;
+
+std::string trace(Position& pos);
+Value evaluate(const Position& pos, bool adjusted = false, int* complexity = nullptr);
+void hint_common_parent_position(const Position& pos);
+
+bool load_eval(std::string name, std::istream& stream);
+bool save_eval(std::ostream& stream);
+bool save_eval(const std::optional<std::string>& filename);
+
+} // namespace Stockfish::Eval::NNUE
+
+#endif // #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED
--- /dev/null
+/*
+ Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+ Copyright (C) 2004-2023 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 <http://www.gnu.org/licenses/>.
+*/
+
+//Definition of input features HalfKAv2_hm of NNUE evaluation function
+
+#include "half_ka_v2_hm.h"
+
+#include "../../bitboard.h"
+#include "../../position.h"
+#include "../../types.h"
+#include "../nnue_common.h"
+
+namespace Stockfish::Eval::NNUE::Features {
+
+// Index of a feature for a given king position and another piece on some square
+template<Color Perspective>
+inline IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq) {
+ return IndexType((int(s) ^ OrientTBL[Perspective][ksq]) + PieceSquareIndex[Perspective][pc]
+ + KingBuckets[Perspective][ksq]);
+}
+
+// Get a list of indices for active features
+template<Color Perspective>
+void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active) {
+ Square ksq = pos.square<KING>(Perspective);
+ Bitboard bb = pos.pieces();
+ while (bb)
+ {
+ Square s = pop_lsb(bb);
+ active.push_back(make_index<Perspective>(s, pos.piece_on(s), ksq));
+ }
+}
+
+// Explicit template instantiations
+template void HalfKAv2_hm::append_active_indices<WHITE>(const Position& pos, IndexList& active);
+template void HalfKAv2_hm::append_active_indices<BLACK>(const Position& pos, IndexList& active);
+
+// Get a list of indices for recently changed features
+template<Color Perspective>
+void HalfKAv2_hm::append_changed_indices(Square ksq,
+ const DirtyPiece& dp,
+ IndexList& removed,
+ IndexList& added) {
+ for (int i = 0; i < dp.dirty_num; ++i)
+ {
+ if (dp.from[i] != SQ_NONE)
+ removed.push_back(make_index<Perspective>(dp.from[i], dp.piece[i], ksq));
+ if (dp.to[i] != SQ_NONE)
+ added.push_back(make_index<Perspective>(dp.to[i], dp.piece[i], ksq));
+ }
+}
+
+// Explicit template instantiations
+template void HalfKAv2_hm::append_changed_indices<WHITE>(Square ksq,
+ const DirtyPiece& dp,
+ IndexList& removed,
+ IndexList& added);
+template void HalfKAv2_hm::append_changed_indices<BLACK>(Square ksq,
+ const DirtyPiece& dp,
+ IndexList& removed,
+ IndexList& added);
+
+int HalfKAv2_hm::update_cost(const StateInfo* st) { return st->dirtyPiece.dirty_num; }
+
+int HalfKAv2_hm::refresh_cost(const Position& pos) { return pos.count<ALL_PIECES>(); }
+
+bool HalfKAv2_hm::requires_refresh(const StateInfo* st, Color perspective) {
+ return st->dirtyPiece.piece[0] == make_piece(perspective, KING);
+}
+
+} // namespace Stockfish::Eval::NNUE::Features
--- /dev/null
+/*
+ Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+ Copyright (C) 2004-2023 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 <http://www.gnu.org/licenses/>.
+*/
+
+//Definition of input features HalfKP of NNUE evaluation function
+
+#ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED
+#define NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED
+
+#include <cstdint>
+
+#include "../../misc.h"
+#include "../../types.h"
+#include "../nnue_common.h"
+
+namespace Stockfish {
+struct StateInfo;
+class Position;
+}
+
+namespace Stockfish::Eval::NNUE::Features {
+
+// Feature HalfKAv2_hm: Combination of the position of own king
+// and the position of pieces. Position mirrored such that king always on e..h files.
+class HalfKAv2_hm {
+
+ // unique number for each piece type on each square
+ enum {
+ PS_NONE = 0,
+ PS_W_PAWN = 0,
+ PS_B_PAWN = 1 * SQUARE_NB,
+ PS_W_KNIGHT = 2 * SQUARE_NB,
+ PS_B_KNIGHT = 3 * SQUARE_NB,
+ PS_W_BISHOP = 4 * SQUARE_NB,
+ PS_B_BISHOP = 5 * SQUARE_NB,
+ PS_W_ROOK = 6 * SQUARE_NB,
+ PS_B_ROOK = 7 * SQUARE_NB,
+ PS_W_QUEEN = 8 * SQUARE_NB,
+ PS_B_QUEEN = 9 * SQUARE_NB,
+ PS_KING = 10 * SQUARE_NB,
+ PS_NB = 11 * SQUARE_NB
+ };
+
+ static constexpr IndexType 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_KING, PS_NONE,
+ PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE},
+ {PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE,
+ PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE}};
+
+ // Index of a feature for a given king position and another piece on some square
+ template<Color Perspective>
+ static IndexType make_index(Square s, Piece pc, Square ksq);
+
+ public:
+ // Feature name
+ static constexpr const char* Name = "HalfKAv2_hm(Friend)";
+
+ // Hash value embedded in the evaluation file
+ static constexpr std::uint32_t HashValue = 0x7f234cb8u;
+
+ // Number of feature dimensions
+ static constexpr IndexType Dimensions =
+ static_cast<IndexType>(SQUARE_NB) * static_cast<IndexType>(PS_NB) / 2;
+
+#define B(v) (v * PS_NB)
+ // clang-format off
+ static constexpr int KingBuckets[COLOR_NB][SQUARE_NB] = {
+ { B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28),
+ B(24), B(25), B(26), B(27), B(27), B(26), B(25), B(24),
+ B(20), B(21), B(22), B(23), B(23), B(22), B(21), B(20),
+ B(16), B(17), B(18), B(19), B(19), B(18), B(17), B(16),
+ B(12), B(13), B(14), B(15), B(15), B(14), B(13), B(12),
+ B( 8), B( 9), B(10), B(11), B(11), B(10), B( 9), B( 8),
+ B( 4), B( 5), B( 6), B( 7), B( 7), B( 6), B( 5), B( 4),
+ B( 0), B( 1), B( 2), B( 3), B( 3), B( 2), B( 1), B( 0) },
+ { B( 0), B( 1), B( 2), B( 3), B( 3), B( 2), B( 1), B( 0),
+ B( 4), B( 5), B( 6), B( 7), B( 7), B( 6), B( 5), B( 4),
+ B( 8), B( 9), B(10), B(11), B(11), B(10), B( 9), B( 8),
+ B(12), B(13), B(14), B(15), B(15), B(14), B(13), B(12),
+ B(16), B(17), B(18), B(19), B(19), B(18), B(17), B(16),
+ B(20), B(21), B(22), B(23), B(23), B(22), B(21), B(20),
+ B(24), B(25), B(26), B(27), B(27), B(26), B(25), B(24),
+ B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28) }
+ };
+ // clang-format on
+#undef B
+ // clang-format off
+ // Orient a square according to perspective (rotates by 180 for black)
+ static constexpr int OrientTBL[COLOR_NB][SQUARE_NB] = {
+ { SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
+ SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
+ SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
+ SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
+ SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
+ SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
+ SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
+ SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1 },
+ { SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
+ SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
+ SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
+ SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
+ SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
+ SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
+ SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
+ SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8 }
+ };
+ // clang-format on
+
+ // Maximum number of simultaneously active features.
+ static constexpr IndexType MaxActiveDimensions = 32;
+ using IndexList = ValueList<IndexType, MaxActiveDimensions>;
+
+ // Get a list of indices for active features
+ template<Color Perspective>
+ static void append_active_indices(const Position& pos, IndexList& active);
+
+ // Get a list of indices for recently changed features
+ template<Color Perspective>
+ static void
+ append_changed_indices(Square ksq, const DirtyPiece& dp, IndexList& removed, IndexList& added);
+
+ // Returns the cost of updating one perspective, the most costly one.
+ // Assumes no refresh needed.
+ static int update_cost(const 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(const StateInfo* st, Color perspective);
+};
+
+} // namespace Stockfish::Eval::NNUE::Features
+
+#endif // #ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED
--- /dev/null
+/*
+ Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+ Copyright (C) 2004-2023 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 <http://www.gnu.org/licenses/>.
+*/
+
+// Definition of layer AffineTransform of NNUE evaluation function
+
+#ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED
+#define NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED
+
+#include <cstdint>
+#include <iostream>
+
+#include "../nnue_common.h"
+#include "simd.h"
+
+/*
+ This file contains the definition for a fully connected layer (aka affine transform).
+
+ - expected use-case is for when PaddedInputDimensions == 32 and InputDimensions <= 32.
+ - that's why AVX512 is hard to implement
+ - expected use-case is small layers
+ - inputs are processed in chunks of 4, weights are respectively transposed
+ - accumulation happens directly to int32s
+*/
+
+namespace Stockfish::Eval::NNUE::Layers {
+
+// Fallback implementation for older/other architectures.
+// Requires the input to be padded to at least 16 values.
+#if !defined(USE_SSSE3)
+template<IndexType InputDimensions, IndexType PaddedInputDimensions, IndexType OutputDimensions>
+static void affine_transform_non_ssse3(std::int32_t* output,
+ const std::int8_t* weights,
+ const std::int32_t* biases,
+ const std::uint8_t* input) {
+ #if defined(USE_SSE2) || defined(USE_NEON_DOTPROD) || defined(USE_NEON)
+ #if defined(USE_SSE2)
+ // At least a multiple of 16, with SSE2.
+ constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 16) / 16;
+ const __m128i Zeros = _mm_setzero_si128();
+ const auto inputVector = reinterpret_cast<const __m128i*>(input);
+
+ #elif defined(USE_NEON_DOTPROD)
+ constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 16) / 16;
+ const auto inputVector = reinterpret_cast<const int8x16_t*>(input);
+
+ #elif defined(USE_NEON)
+ constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 16) / 16;
+ const auto inputVector = reinterpret_cast<const int8x8_t*>(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<const __m128i*>(&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_NEON_DOTPROD)
+ int32x4_t sum = {biases[i]};
+ const auto row = reinterpret_cast<const int8x16_t*>(&weights[offset]);
+ for (IndexType j = 0; j < NumChunks; ++j)
+ {
+ sum = vdotq_s32(sum, inputVector[j], row[j]);
+ }
+ output[i] = vaddvq_s32(sum);
+
+ #elif defined(USE_NEON)
+ int32x4_t sum = {biases[i]};
+ const auto row = reinterpret_cast<const int8x8_t*>(&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];
+
+ #endif
+ }
+ #else
+ std::memcpy(output, biases, sizeof(std::int32_t) * OutputDimensions);
+
+ // Traverse weights in transpose order to take advantage of input sparsity
+ for (IndexType i = 0; i < InputDimensions; ++i)
+ if (input[i])
+ {
+ const std::int8_t* w = &weights[i];
+ const int in = input[i];
+ for (IndexType j = 0; j < OutputDimensions; ++j)
+ output[j] += w[j * PaddedInputDimensions] * in;
+ }
+ #endif
+}
+#endif
+
+template<IndexType InDims, IndexType OutDims>
+class AffineTransform {
+ public:
+ // Input/output type
+ using InputType = std::uint8_t;
+ using OutputType = std::int32_t;
+
+ // Number of input/output dimensions
+ static constexpr IndexType InputDimensions = InDims;
+ static constexpr IndexType OutputDimensions = OutDims;
+
+ static constexpr IndexType PaddedInputDimensions =
+ ceil_to_multiple<IndexType>(InputDimensions, MaxSimdWidth);
+ static constexpr IndexType PaddedOutputDimensions =
+ ceil_to_multiple<IndexType>(OutputDimensions, MaxSimdWidth);
+
+ using OutputBuffer = OutputType[PaddedOutputDimensions];
+
+ // Hash value embedded in the evaluation file
+ static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {
+ std::uint32_t hashValue = 0xCC03DAE4u;
+ hashValue += OutputDimensions;
+ hashValue ^= prevHash >> 1;
+ hashValue ^= prevHash << 31;
+ return hashValue;
+ }
+
+ static constexpr IndexType get_weight_index_scrambled(IndexType i) {
+ return (i / 4) % (PaddedInputDimensions / 4) * OutputDimensions * 4
+ + i / PaddedInputDimensions * 4 + i % 4;
+ }
+
+ static constexpr IndexType get_weight_index(IndexType i) {
+#if defined(USE_SSSE3)
+ return get_weight_index_scrambled(i);
+#else
+ return i;
+#endif
+ }
+
+ // Read network parameters
+ bool read_parameters(std::istream& stream) {
+ read_little_endian<BiasType>(stream, biases, OutputDimensions);
+ for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)
+ weights[get_weight_index(i)] = read_little_endian<WeightType>(stream);
+
+ return !stream.fail();
+ }
+
+ // Write network parameters
+ bool write_parameters(std::ostream& stream) const {
+ write_little_endian<BiasType>(stream, biases, OutputDimensions);
+
+ for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)
+ write_little_endian<WeightType>(stream, weights[get_weight_index(i)]);
+
+ return !stream.fail();
+ }
+ // Forward propagation
+ void propagate(const InputType* input, OutputType* output) const {
+
+#if defined(USE_SSSE3)
+
+ if constexpr (OutputDimensions > 1)
+ {
+
+ #if defined(USE_AVX512)
+ using vec_t = __m512i;
+ #define vec_setzero _mm512_setzero_si512
+ #define vec_set_32 _mm512_set1_epi32
+ #define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32
+ #define vec_add_dpbusd_32x2 Simd::m512_add_dpbusd_epi32x2
+ #define vec_hadd Simd::m512_hadd
+ #elif defined(USE_AVX2)
+ using vec_t = __m256i;
+ #define vec_setzero _mm256_setzero_si256
+ #define vec_set_32 _mm256_set1_epi32
+ #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32
+ #define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2
+ #define vec_hadd Simd::m256_hadd
+ #elif defined(USE_SSSE3)
+ using vec_t = __m128i;
+ #define vec_setzero _mm_setzero_si128
+ #define vec_set_32 _mm_set1_epi32
+ #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32
+ #define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2
+ #define vec_hadd Simd::m128_hadd
+ #endif
+
+ static constexpr IndexType OutputSimdWidth = sizeof(vec_t) / sizeof(OutputType);
+
+ static_assert(OutputDimensions % OutputSimdWidth == 0);
+
+ constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 8) / 4;
+ constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth;
+
+ const auto input32 = reinterpret_cast<const std::int32_t*>(input);
+ const vec_t* biasvec = reinterpret_cast<const vec_t*>(biases);
+ vec_t acc[NumRegs];
+ for (IndexType k = 0; k < NumRegs; ++k)
+ acc[k] = biasvec[k];
+
+ for (IndexType i = 0; i < NumChunks; i += 2)
+ {
+ const vec_t in0 = vec_set_32(input32[i + 0]);
+ const vec_t in1 = vec_set_32(input32[i + 1]);
+ const auto col0 =
+ reinterpret_cast<const vec_t*>(&weights[(i + 0) * OutputDimensions * 4]);
+ const auto col1 =
+ reinterpret_cast<const vec_t*>(&weights[(i + 1) * OutputDimensions * 4]);
+ for (IndexType k = 0; k < NumRegs; ++k)
+ vec_add_dpbusd_32x2(acc[k], in0, col0[k], in1, col1[k]);
+ }
+
+ vec_t* outptr = reinterpret_cast<vec_t*>(output);
+ for (IndexType k = 0; k < NumRegs; ++k)
+ outptr[k] = acc[k];
+
+ #undef vec_setzero
+ #undef vec_set_32
+ #undef vec_add_dpbusd_32
+ #undef vec_add_dpbusd_32x2
+ #undef vec_hadd
+ }
+ else if constexpr (OutputDimensions == 1)
+ {
+
+ // We cannot use AVX512 for the last layer because there are only 32 inputs
+ // and the buffer is not padded to 64 elements.
+ #if defined(USE_AVX2)
+ using vec_t = __m256i;
+ #define vec_setzero _mm256_setzero_si256
+ #define vec_set_32 _mm256_set1_epi32
+ #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32
+ #define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2
+ #define vec_hadd Simd::m256_hadd
+ #elif defined(USE_SSSE3)
+ using vec_t = __m128i;
+ #define vec_setzero _mm_setzero_si128
+ #define vec_set_32 _mm_set1_epi32
+ #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32
+ #define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2
+ #define vec_hadd Simd::m128_hadd
+ #endif
+
+ const auto inputVector = reinterpret_cast<const vec_t*>(input);
+
+ static constexpr IndexType InputSimdWidth = sizeof(vec_t) / sizeof(InputType);
+
+ static_assert(PaddedInputDimensions % InputSimdWidth == 0);
+
+ constexpr IndexType NumChunks = PaddedInputDimensions / InputSimdWidth;
+ vec_t sum0 = vec_setzero();
+ const auto row0 = reinterpret_cast<const vec_t*>(&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]);
+
+ #undef vec_setzero
+ #undef vec_set_32
+ #undef vec_add_dpbusd_32
+ #undef vec_add_dpbusd_32x2
+ #undef vec_hadd
+ }
+#else
+ // Use old implementation for the other architectures.
+ affine_transform_non_ssse3<InputDimensions, PaddedInputDimensions, OutputDimensions>(
+ output, weights, biases, input);
+#endif
+ }
+
+ private:
+ using BiasType = OutputType;
+ using WeightType = std::int8_t;
+
+ alignas(CacheLineSize) BiasType biases[OutputDimensions];
+ alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions];
+};
+
+} // namespace Stockfish::Eval::NNUE::Layers
+
+#endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED
--- /dev/null
+/*
+ Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+ Copyright (C) 2004-2023 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 <http://www.gnu.org/licenses/>.
+*/
+
+// Definition of layer AffineTransformSparseInput of NNUE evaluation function
+
+#ifndef NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED
+#define NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED
+
+#include <algorithm>
+#include <array>
+#include <cstdint>
+#include <iostream>
+
+#include "../../bitboard.h"
+#include "../nnue_common.h"
+#include "affine_transform.h"
+#include "simd.h"
+
+/*
+ This file contains the definition for a fully connected layer (aka affine transform) with block sparse input.
+*/
+
+namespace Stockfish::Eval::NNUE::Layers {
+
+#if (USE_SSSE3 | (USE_NEON >= 8))
+alignas(CacheLineSize) static inline const
+ std::array<std::array<std::uint16_t, 8>, 256> lookup_indices = []() {
+ std::array<std::array<std::uint16_t, 8>, 256> v{};
+ for (unsigned i = 0; i < 256; ++i)
+ {
+ std::uint64_t j = i, k = 0;
+ while (j)
+ v[i][k++] = pop_lsb(j);
+ }
+ return v;
+ }();
+
+// Find indices of nonzero numbers in an int32_t array
+template<const IndexType InputDimensions>
+void find_nnz(const std::int32_t* input, std::uint16_t* out, IndexType& count_out) {
+ #if defined(USE_SSSE3)
+ #if defined(USE_AVX512)
+ using vec_t = __m512i;
+ #define vec_nnz(a) _mm512_cmpgt_epi32_mask(a, _mm512_setzero_si512())
+ #elif defined(USE_AVX2)
+ using vec_t = __m256i;
+ #if defined(USE_VNNI) && !defined(USE_AVXVNNI)
+ #define vec_nnz(a) _mm256_cmpgt_epi32_mask(a, _mm256_setzero_si256())
+ #else
+ #define vec_nnz(a) \
+ _mm256_movemask_ps( \
+ _mm256_castsi256_ps(_mm256_cmpgt_epi32(a, _mm256_setzero_si256())))
+ #endif
+ #elif defined(USE_SSSE3)
+ using vec_t = __m128i;
+ #define vec_nnz(a) \
+ _mm_movemask_ps(_mm_castsi128_ps(_mm_cmpgt_epi32(a, _mm_setzero_si128())))
+ #endif
+ using vec128_t = __m128i;
+ #define vec128_zero _mm_setzero_si128()
+ #define vec128_set_16(a) _mm_set1_epi16(a)
+ #define vec128_load(a) _mm_load_si128(a)
+ #define vec128_storeu(a, b) _mm_storeu_si128(a, b)
+ #define vec128_add(a, b) _mm_add_epi16(a, b)
+ #elif defined(USE_NEON)
+ using vec_t = uint32x4_t;
+ static const std::uint32_t Mask[4] = {1, 2, 4, 8};
+ #define vec_nnz(a) vaddvq_u32(vandq_u32(vtstq_u32(a, a), vld1q_u32(Mask)))
+ using vec128_t = uint16x8_t;
+ #define vec128_zero vdupq_n_u16(0)
+ #define vec128_set_16(a) vdupq_n_u16(a)
+ #define vec128_load(a) vld1q_u16(reinterpret_cast<const std::uint16_t*>(a))
+ #define vec128_storeu(a, b) vst1q_u16(reinterpret_cast<std::uint16_t*>(a), b)
+ #define vec128_add(a, b) vaddq_u16(a, b)
+ #endif
+ constexpr IndexType InputSimdWidth = sizeof(vec_t) / sizeof(std::int32_t);
+ // Inputs are processed InputSimdWidth at a time and outputs are processed 8 at a time so we process in chunks of max(InputSimdWidth, 8)
+ constexpr IndexType ChunkSize = std::max<IndexType>(InputSimdWidth, 8);
+ constexpr IndexType NumChunks = InputDimensions / ChunkSize;
+ constexpr IndexType InputsPerChunk = ChunkSize / InputSimdWidth;
+ constexpr IndexType OutputsPerChunk = ChunkSize / 8;
+
+ const auto inputVector = reinterpret_cast<const vec_t*>(input);
+ IndexType count = 0;
+ vec128_t base = vec128_zero;
+ const vec128_t increment = vec128_set_16(8);
+ for (IndexType i = 0; i < NumChunks; ++i)
+ {
+ // bitmask of nonzero values in this chunk
+ unsigned nnz = 0;
+ for (IndexType j = 0; j < InputsPerChunk; ++j)
+ {
+ const vec_t inputChunk = inputVector[i * InputsPerChunk + j];
+ nnz |= unsigned(vec_nnz(inputChunk)) << (j * InputSimdWidth);
+ }
+ for (IndexType j = 0; j < OutputsPerChunk; ++j)
+ {
+ const auto lookup = (nnz >> (j * 8)) & 0xFF;
+ const auto offsets =
+ vec128_load(reinterpret_cast<const vec128_t*>(&lookup_indices[lookup]));
+ vec128_storeu(reinterpret_cast<vec128_t*>(out + count), vec128_add(base, offsets));
+ count += popcount(lookup);
+ base = vec128_add(base, increment);
+ }
+ }
+ count_out = count;
+}
+ #undef vec_nnz
+ #undef vec128_zero
+ #undef vec128_set_16
+ #undef vec128_load
+ #undef vec128_storeu
+ #undef vec128_add
+#endif
+
+// Sparse input implementation
+template<IndexType InDims, IndexType OutDims>
+class AffineTransformSparseInput {
+ public:
+ // Input/output type
+ using InputType = std::uint8_t;
+ using OutputType = std::int32_t;
+
+ // Number of input/output dimensions
+ static constexpr IndexType InputDimensions = InDims;
+ static constexpr IndexType OutputDimensions = OutDims;
+
+ static_assert(OutputDimensions % 16 == 0,
+ "Only implemented for OutputDimensions divisible by 16.");
+
+ static constexpr IndexType PaddedInputDimensions =
+ ceil_to_multiple<IndexType>(InputDimensions, MaxSimdWidth);
+ static constexpr IndexType PaddedOutputDimensions =
+ ceil_to_multiple<IndexType>(OutputDimensions, MaxSimdWidth);
+
+#if (USE_SSSE3 | (USE_NEON >= 8))
+ static constexpr IndexType ChunkSize = 4;
+#else
+ static constexpr IndexType ChunkSize = 1;
+#endif
+
+ using OutputBuffer = OutputType[PaddedOutputDimensions];
+
+ // Hash value embedded in the evaluation file
+ static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {
+ std::uint32_t hashValue = 0xCC03DAE4u;
+ hashValue += OutputDimensions;
+ hashValue ^= prevHash >> 1;
+ hashValue ^= prevHash << 31;
+ return hashValue;
+ }
+
+ static constexpr IndexType get_weight_index_scrambled(IndexType i) {
+ return (i / ChunkSize) % (PaddedInputDimensions / ChunkSize) * OutputDimensions * ChunkSize
+ + i / PaddedInputDimensions * ChunkSize + i % ChunkSize;
+ }
+
+ static constexpr IndexType get_weight_index(IndexType i) {
+#if (USE_SSSE3 | (USE_NEON >= 8))
+ return get_weight_index_scrambled(i);
+#else
+ return i;
+#endif
+ }
+
+ // Read network parameters
+ bool read_parameters(std::istream& stream) {
+ read_little_endian<BiasType>(stream, biases, OutputDimensions);
+ for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)
+ weights[get_weight_index(i)] = read_little_endian<WeightType>(stream);
+
+ return !stream.fail();
+ }
+
+ // Write network parameters
+ bool write_parameters(std::ostream& stream) const {
+ write_little_endian<BiasType>(stream, biases, OutputDimensions);
+
+ for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)
+ write_little_endian<WeightType>(stream, weights[get_weight_index(i)]);
+
+ return !stream.fail();
+ }
+ // Forward propagation
+ void propagate(const InputType* input, OutputType* output) const {
+
+#if (USE_SSSE3 | (USE_NEON >= 8))
+ #if defined(USE_AVX512)
+ using invec_t = __m512i;
+ using outvec_t = __m512i;
+ #define vec_set_32 _mm512_set1_epi32
+ #define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32
+ #elif defined(USE_AVX2)
+ using invec_t = __m256i;
+ using outvec_t = __m256i;
+ #define vec_set_32 _mm256_set1_epi32
+ #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32
+ #elif defined(USE_SSSE3)
+ using invec_t = __m128i;
+ using outvec_t = __m128i;
+ #define vec_set_32 _mm_set1_epi32
+ #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32
+ #elif defined(USE_NEON_DOTPROD)
+ using invec_t = int8x16_t;
+ using outvec_t = int32x4_t;
+ #define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a))
+ #define vec_add_dpbusd_32 Simd::dotprod_m128_add_dpbusd_epi32
+ #elif defined(USE_NEON)
+ using invec_t = int8x16_t;
+ using outvec_t = int32x4_t;
+ #define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a))
+ #define vec_add_dpbusd_32 Simd::neon_m128_add_dpbusd_epi32
+ #endif
+ static constexpr IndexType OutputSimdWidth = sizeof(outvec_t) / sizeof(OutputType);
+
+ constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 8) / ChunkSize;
+ constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth;
+ std::uint16_t nnz[NumChunks];
+ IndexType count;
+
+ const auto input32 = reinterpret_cast<const std::int32_t*>(input);
+
+ // Find indices of nonzero 32bit blocks
+ find_nnz<NumChunks>(input32, nnz, count);
+
+ const outvec_t* biasvec = reinterpret_cast<const outvec_t*>(biases);
+ outvec_t acc[NumRegs];
+ for (IndexType k = 0; k < NumRegs; ++k)
+ acc[k] = biasvec[k];
+
+ for (IndexType j = 0; j < count; ++j)
+ {
+ const auto i = nnz[j];
+ const invec_t in = vec_set_32(input32[i]);
+ const auto col =
+ reinterpret_cast<const invec_t*>(&weights[i * OutputDimensions * ChunkSize]);
+ for (IndexType k = 0; k < NumRegs; ++k)
+ vec_add_dpbusd_32(acc[k], in, col[k]);
+ }
+
+ outvec_t* outptr = reinterpret_cast<outvec_t*>(output);
+ for (IndexType k = 0; k < NumRegs; ++k)
+ outptr[k] = acc[k];
+ #undef vec_set_32
+ #undef vec_add_dpbusd_32
+#else
+ // Use dense implementation for the other architectures.
+ affine_transform_non_ssse3<InputDimensions, PaddedInputDimensions, OutputDimensions>(
+ output, weights, biases, input);
+#endif
+ }
+
+ private:
+ using BiasType = OutputType;
+ using WeightType = std::int8_t;
+
+ alignas(CacheLineSize) BiasType biases[OutputDimensions];
+ alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions];
+};
+
+} // namespace Stockfish::Eval::NNUE::Layers
+
+#endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED
--- /dev/null
+/*
+ Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+ Copyright (C) 2004-2023 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 <http://www.gnu.org/licenses/>.
+*/
+
+// Definition of layer ClippedReLU of NNUE evaluation function
+
+#ifndef NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED
+#define NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED
+
+#include <algorithm>
+#include <cstdint>
+#include <iosfwd>
+
+#include "../nnue_common.h"
+
+namespace Stockfish::Eval::NNUE::Layers {
+
+// Clipped ReLU
+template<IndexType InDims>
+class ClippedReLU {
+ public:
+ // Input/output type
+ using InputType = std::int32_t;
+ using OutputType = std::uint8_t;
+
+ // Number of input/output dimensions
+ static constexpr IndexType InputDimensions = InDims;
+ static constexpr IndexType OutputDimensions = InputDimensions;
+ static constexpr IndexType PaddedOutputDimensions =
+ ceil_to_multiple<IndexType>(OutputDimensions, 32);
+
+ using OutputBuffer = OutputType[PaddedOutputDimensions];
+
+ // Hash value embedded in the evaluation file
+ static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {
+ std::uint32_t hashValue = 0x538D24C7u;
+ hashValue += prevHash;
+ return hashValue;
+ }
+
+ // Read network parameters
+ bool read_parameters(std::istream&) { return true; }
+
+ // Write network parameters
+ bool write_parameters(std::ostream&) const { return true; }
+
+ // Forward propagation
+ void propagate(const InputType* input, OutputType* output) const {
+
+#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<const __m256i*>(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<const __m128i*>(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<const __m128i*>(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_NEON)
+ constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2);
+ const int8x8_t Zero = {0};
+ const auto in = reinterpret_cast<const int32x4_t*>(input);
+ const auto out = reinterpret_cast<int8x8_t*>(output);
+ for (IndexType i = 0; i < NumChunks; ++i)
+ {
+ int16x8_t shifted;
+ const auto pack = reinterpret_cast<int16x4_t*>(&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<OutputType>(std::clamp(input[i] >> WeightScaleBits, 0, 127));
+ }
+ }
+};
+
+} // namespace Stockfish::Eval::NNUE::Layers
+
+#endif // NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED
--- /dev/null
+/*
+ Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+ Copyright (C) 2004-2023 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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef STOCKFISH_SIMD_H_INCLUDED
+#define STOCKFISH_SIMD_H_INCLUDED
+
+#if defined(USE_AVX2)
+ #include <immintrin.h>
+
+#elif defined(USE_SSE41)
+ #include <smmintrin.h>
+
+#elif defined(USE_SSSE3)
+ #include <tmmintrin.h>
+
+#elif defined(USE_SSE2)
+ #include <emmintrin.h>
+
+#elif defined(USE_NEON)
+ #include <arm_neon.h>
+#endif
+
+namespace Stockfish::Simd {
+
+#if defined(USE_AVX512)
+
+[[maybe_unused]] static int m512_hadd(__m512i sum, int bias) {
+ return _mm512_reduce_add_epi32(sum) + bias;
+}
+
+/*
+ Parameters:
+ sum0 = [zmm0.i128[0], zmm0.i128[1], zmm0.i128[2], zmm0.i128[3]]
+ sum1 = [zmm1.i128[0], zmm1.i128[1], zmm1.i128[2], zmm1.i128[3]]
+ sum2 = [zmm2.i128[0], zmm2.i128[1], zmm2.i128[2], zmm2.i128[3]]
+ sum3 = [zmm3.i128[0], zmm3.i128[1], zmm3.i128[2], zmm3.i128[3]]
+
+ Returns:
+ ret = [
+ reduce_add_epi32(zmm0.i128[0]), reduce_add_epi32(zmm1.i128[0]), reduce_add_epi32(zmm2.i128[0]), reduce_add_epi32(zmm3.i128[0]),
+ reduce_add_epi32(zmm0.i128[1]), reduce_add_epi32(zmm1.i128[1]), reduce_add_epi32(zmm2.i128[1]), reduce_add_epi32(zmm3.i128[1]),
+ reduce_add_epi32(zmm0.i128[2]), reduce_add_epi32(zmm1.i128[2]), reduce_add_epi32(zmm2.i128[2]), reduce_add_epi32(zmm3.i128[2]),
+ reduce_add_epi32(zmm0.i128[3]), reduce_add_epi32(zmm1.i128[3]), reduce_add_epi32(zmm2.i128[3]), reduce_add_epi32(zmm3.i128[3])
+ ]
+ */
+[[maybe_unused]] static __m512i
+m512_hadd128x16_interleave(__m512i sum0, __m512i sum1, __m512i sum2, __m512i sum3) {
+
+ __m512i sum01a = _mm512_unpacklo_epi32(sum0, sum1);
+ __m512i sum01b = _mm512_unpackhi_epi32(sum0, sum1);
+
+ __m512i sum23a = _mm512_unpacklo_epi32(sum2, sum3);
+ __m512i sum23b = _mm512_unpackhi_epi32(sum2, sum3);
+
+ __m512i sum01 = _mm512_add_epi32(sum01a, sum01b);
+ __m512i sum23 = _mm512_add_epi32(sum23a, sum23b);
+
+ __m512i sum0123a = _mm512_unpacklo_epi64(sum01, sum23);
+ __m512i sum0123b = _mm512_unpackhi_epi64(sum01, sum23);
+
+ return _mm512_add_epi32(sum0123a, sum0123b);
+}
+
+[[maybe_unused]] static void 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, _mm512_set1_epi16(1));
+ acc = _mm512_add_epi32(acc, product0);
+ #endif
+}
+
+[[maybe_unused]] static void
+m512_add_dpbusd_epi32x2(__m512i& acc, __m512i a0, __m512i b0, __m512i a1, __m512i b1) {
+
+ #if defined(USE_VNNI)
+ acc = _mm512_dpbusd_epi32(acc, a0, b0);
+ acc = _mm512_dpbusd_epi32(acc, a1, b1);
+ #else
+ __m512i product0 = _mm512_maddubs_epi16(a0, b0);
+ __m512i product1 = _mm512_maddubs_epi16(a1, b1);
+ product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1));
+ product1 = _mm512_madd_epi16(product1, _mm512_set1_epi16(1));
+ acc = _mm512_add_epi32(acc, _mm512_add_epi32(product0, product1));
+ #endif
+}
+
+#endif
+
+#if defined(USE_AVX2)
+
+[[maybe_unused]] static int m256_hadd(__m256i sum, int bias) {
+ __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]] static void 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, _mm256_set1_epi16(1));
+ acc = _mm256_add_epi32(acc, product0);
+ #endif
+}
+
+[[maybe_unused]] static void
+m256_add_dpbusd_epi32x2(__m256i& acc, __m256i a0, __m256i b0, __m256i a1, __m256i b1) {
+
+ #if defined(USE_VNNI)
+ acc = _mm256_dpbusd_epi32(acc, a0, b0);
+ acc = _mm256_dpbusd_epi32(acc, a1, b1);
+ #else
+ __m256i product0 = _mm256_maddubs_epi16(a0, b0);
+ __m256i product1 = _mm256_maddubs_epi16(a1, b1);
+ product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1));
+ product1 = _mm256_madd_epi16(product1, _mm256_set1_epi16(1));
+ acc = _mm256_add_epi32(acc, _mm256_add_epi32(product0, product1));
+ #endif
+}
+
+#endif
+
+#if defined(USE_SSSE3)
+
+[[maybe_unused]] static int m128_hadd(__m128i sum, int bias) {
+ 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]] static void m128_add_dpbusd_epi32(__m128i& acc, __m128i a, __m128i b) {
+
+ __m128i product0 = _mm_maddubs_epi16(a, b);
+ product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1));
+ acc = _mm_add_epi32(acc, product0);
+}
+
+[[maybe_unused]] static void
+m128_add_dpbusd_epi32x2(__m128i& acc, __m128i a0, __m128i b0, __m128i a1, __m128i b1) {
+
+ __m128i product0 = _mm_maddubs_epi16(a0, b0);
+ __m128i product1 = _mm_maddubs_epi16(a1, b1);
+ product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1));
+ product1 = _mm_madd_epi16(product1, _mm_set1_epi16(1));
+ acc = _mm_add_epi32(acc, _mm_add_epi32(product0, product1));
+}
+
+#endif
+
+#if defined(USE_NEON_DOTPROD)
+
+[[maybe_unused]] static void dotprod_m128_add_dpbusd_epi32x2(
+ int32x4_t& acc, int8x16_t a0, int8x16_t b0, int8x16_t a1, int8x16_t b1) {
+
+ acc = vdotq_s32(acc, a0, b0);
+ acc = vdotq_s32(acc, a1, b1);
+}
+
+[[maybe_unused]] static void
+dotprod_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) {
+
+ acc = vdotq_s32(acc, a, b);
+}
+#endif
+
+#if defined(USE_NEON)
+
+[[maybe_unused]] static int neon_m128_reduce_add_epi32(int32x4_t s) {
+ #if USE_NEON >= 8
+ return vaddvq_s32(s);
+ #else
+ return s[0] + s[1] + s[2] + s[3];
+ #endif
+}
+
+[[maybe_unused]] static int neon_m128_hadd(int32x4_t sum, int bias) {
+ return neon_m128_reduce_add_epi32(sum) + bias;
+}
+
+[[maybe_unused]] static void
+neon_m128_add_dpbusd_epi32x2(int32x4_t& acc, int8x8_t a0, int8x8_t b0, int8x8_t a1, int8x8_t b1) {
+
+ int16x8_t product = vmull_s8(a0, b0);
+ product = vmlal_s8(product, a1, b1);
+ acc = vpadalq_s16(acc, product);
+}
+#endif
+
+#if USE_NEON >= 8
+[[maybe_unused]] static void neon_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) {
+
+ int16x8_t product0 = vmull_s8(vget_low_s8(a), vget_low_s8(b));
+ int16x8_t product1 = vmull_high_s8(a, b);
+ int16x8_t sum = vpaddq_s16(product0, product1);
+ acc = vpadalq_s16(acc, sum);
+}
+#endif
+}
+
+#endif // STOCKFISH_SIMD_H_INCLUDED
--- /dev/null
+/*
+ Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+ Copyright (C) 2004-2023 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 <http://www.gnu.org/licenses/>.
+*/
+
+// Definition of layer ClippedReLU of NNUE evaluation function
+
+#ifndef NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED
+#define NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED
+
+#include <algorithm>
+#include <cstdint>
+#include <iosfwd>
+
+#include "../nnue_common.h"
+
+namespace Stockfish::Eval::NNUE::Layers {
+
+// Clipped ReLU
+template<IndexType InDims>
+class SqrClippedReLU {
+ public:
+ // Input/output type
+ using InputType = std::int32_t;
+ using OutputType = std::uint8_t;
+
+ // Number of input/output dimensions
+ static constexpr IndexType InputDimensions = InDims;
+ static constexpr IndexType OutputDimensions = InputDimensions;
+ static constexpr IndexType PaddedOutputDimensions =
+ ceil_to_multiple<IndexType>(OutputDimensions, 32);
+
+ using OutputBuffer = OutputType[PaddedOutputDimensions];
+
+ // Hash value embedded in the evaluation file
+ static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {
+ std::uint32_t hashValue = 0x538D24C7u;
+ hashValue += prevHash;
+ return hashValue;
+ }
+
+ // Read network parameters
+ bool read_parameters(std::istream&) { return true; }
+
+ // Write network parameters
+ bool write_parameters(std::ostream&) const { return true; }
+
+ // Forward propagation
+ void propagate(const InputType* input, OutputType* output) const {
+
+#if defined(USE_SSE2)
+ constexpr IndexType NumChunks = InputDimensions / 16;
+
+ static_assert(WeightScaleBits == 6);
+ const auto in = reinterpret_cast<const __m128i*>(input);
+ const auto out = reinterpret_cast<__m128i*>(output);
+ for (IndexType i = 0; i < NumChunks; ++i)
+ {
+ __m128i words0 =
+ _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1]));
+ __m128i words1 =
+ _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3]));
+
+ // We shift by WeightScaleBits * 2 = 12 and divide by 128
+ // which is an additional shift-right of 7, meaning 19 in total.
+ // MulHi strips the lower 16 bits so we need to shift out 3 more to match.
+ words0 = _mm_srli_epi16(_mm_mulhi_epi16(words0, words0), 3);
+ words1 = _mm_srli_epi16(_mm_mulhi_epi16(words1, words1), 3);
+
+ _mm_store_si128(&out[i], _mm_packs_epi16(words0, words1));
+ }
+ constexpr IndexType Start = NumChunks * 16;
+
+#else
+ constexpr IndexType Start = 0;
+#endif
+
+ for (IndexType i = Start; i < InputDimensions; ++i)
+ {
+ output[i] = static_cast<OutputType>(
+ // Really should be /127 but we need to make it fast so we right shift
+ // by an extra 7 bits instead. Needs to be accounted for in the trainer.
+ std::min(127ll, ((long long) (input[i]) * input[i]) >> (2 * WeightScaleBits + 7)));
+ }
+ }
+};
+
+} // namespace Stockfish::Eval::NNUE::Layers
+
+#endif // NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED
--- /dev/null
+/*
+ Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+ Copyright (C) 2004-2023 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 <http://www.gnu.org/licenses/>.
+*/
+
+// Class for difference calculation of NNUE evaluation function
+
+#ifndef NNUE_ACCUMULATOR_H_INCLUDED
+#define NNUE_ACCUMULATOR_H_INCLUDED
+
+#include <cstdint>
+
+#include "nnue_architecture.h"
+#include "nnue_common.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
--- /dev/null
+/*
+ Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+ Copyright (C) 2004-2023 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 <http://www.gnu.org/licenses/>.
+*/
+
+// Input features and network structure used in NNUE evaluation function
+
+#ifndef NNUE_ARCHITECTURE_H_INCLUDED
+#define NNUE_ARCHITECTURE_H_INCLUDED
+
+#include <cstdint>
+#include <cstring>
+#include <iosfwd>
+
+#include "features/half_ka_v2_hm.h"
+#include "layers/affine_transform.h"
+#include "layers/affine_transform_sparse_input.h"
+#include "layers/clipped_relu.h"
+#include "layers/sqr_clipped_relu.h"
+#include "nnue_common.h"
+
+namespace Stockfish::Eval::NNUE {
+
+// Input features used in evaluation function
+using FeatureSet = Features::HalfKAv2_hm;
+
+// Number of input feature dimensions after conversion
+constexpr IndexType TransformedFeatureDimensions = 2560;
+constexpr IndexType PSQTBuckets = 8;
+constexpr IndexType LayerStacks = 8;
+
+struct Network {
+ static constexpr int FC_0_OUTPUTS = 15;
+ static constexpr int FC_1_OUTPUTS = 32;
+
+ Layers::AffineTransformSparseInput<TransformedFeatureDimensions, FC_0_OUTPUTS + 1> fc_0;
+ Layers::SqrClippedReLU<FC_0_OUTPUTS + 1> ac_sqr_0;
+ Layers::ClippedReLU<FC_0_OUTPUTS + 1> ac_0;
+ Layers::AffineTransform<FC_0_OUTPUTS * 2, FC_1_OUTPUTS> fc_1;
+ Layers::ClippedReLU<FC_1_OUTPUTS> ac_1;
+ Layers::AffineTransform<FC_1_OUTPUTS, 1> fc_2;
+
+ // Hash value embedded in the evaluation file
+ static constexpr std::uint32_t get_hash_value() {
+ // input slice hash
+ std::uint32_t hashValue = 0xEC42E90Du;
+ hashValue ^= TransformedFeatureDimensions * 2;
+
+ hashValue = decltype(fc_0)::get_hash_value(hashValue);
+ hashValue = decltype(ac_0)::get_hash_value(hashValue);
+ hashValue = decltype(fc_1)::get_hash_value(hashValue);
+ hashValue = decltype(ac_1)::get_hash_value(hashValue);
+ hashValue = decltype(fc_2)::get_hash_value(hashValue);
+
+ return hashValue;
+ }
+
+ // Read network parameters
+ bool read_parameters(std::istream& stream) {
+ return fc_0.read_parameters(stream) && ac_0.read_parameters(stream)
+ && fc_1.read_parameters(stream) && ac_1.read_parameters(stream)
+ && fc_2.read_parameters(stream);
+ }
+
+ // Write network parameters
+ bool write_parameters(std::ostream& stream) const {
+ return fc_0.write_parameters(stream) && ac_0.write_parameters(stream)
+ && fc_1.write_parameters(stream) && ac_1.write_parameters(stream)
+ && fc_2.write_parameters(stream);
+ }
+
+ std::int32_t propagate(const TransformedFeatureType* transformedFeatures) {
+ struct alignas(CacheLineSize) Buffer {
+ alignas(CacheLineSize) decltype(fc_0)::OutputBuffer fc_0_out;
+ alignas(CacheLineSize) decltype(ac_sqr_0)::OutputType
+ ac_sqr_0_out[ceil_to_multiple<IndexType>(FC_0_OUTPUTS * 2, 32)];
+ alignas(CacheLineSize) decltype(ac_0)::OutputBuffer ac_0_out;
+ alignas(CacheLineSize) decltype(fc_1)::OutputBuffer fc_1_out;
+ alignas(CacheLineSize) decltype(ac_1)::OutputBuffer ac_1_out;
+ alignas(CacheLineSize) decltype(fc_2)::OutputBuffer fc_2_out;
+
+ Buffer() { std::memset(this, 0, sizeof(*this)); }
+ };
+
+#if defined(__clang__) && (__APPLE__)
+ // workaround for a bug reported with xcode 12
+ static thread_local auto tlsBuffer = std::make_unique<Buffer>();
+ // Access TLS only once, cache result.
+ Buffer& buffer = *tlsBuffer;
+#else
+ alignas(CacheLineSize) static thread_local Buffer buffer;
+#endif
+
+ fc_0.propagate(transformedFeatures, buffer.fc_0_out);
+ ac_sqr_0.propagate(buffer.fc_0_out, buffer.ac_sqr_0_out);
+ ac_0.propagate(buffer.fc_0_out, buffer.ac_0_out);
+ std::memcpy(buffer.ac_sqr_0_out + FC_0_OUTPUTS, buffer.ac_0_out,
+ FC_0_OUTPUTS * sizeof(decltype(ac_0)::OutputType));
+ fc_1.propagate(buffer.ac_sqr_0_out, buffer.fc_1_out);
+ ac_1.propagate(buffer.fc_1_out, buffer.ac_1_out);
+ fc_2.propagate(buffer.ac_1_out, buffer.fc_2_out);
+
+ // buffer.fc_0_out[FC_0_OUTPUTS] is such that 1.0 is equal to 127*(1<<WeightScaleBits) in
+ // quantized form, but we want 1.0 to be equal to 600*OutputScale
+ std::int32_t fwdOut =
+ int(buffer.fc_0_out[FC_0_OUTPUTS]) * (600 * OutputScale) / (127 * (1 << WeightScaleBits));
+ std::int32_t outputValue = buffer.fc_2_out[0] + fwdOut;
+
+ return outputValue;
+ }
+};
+
+} // namespace Stockfish::Eval::NNUE
+
+#endif // #ifndef NNUE_ARCHITECTURE_H_INCLUDED
--- /dev/null
+/*
+ Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+ Copyright (C) 2004-2023 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 <http://www.gnu.org/licenses/>.
+*/
+
+// Constants used in NNUE evaluation function
+
+#ifndef NNUE_COMMON_H_INCLUDED
+#define NNUE_COMMON_H_INCLUDED
+
+#include <algorithm>
+#include <cassert>
+#include <cstdint>
+#include <cstring>
+#include <iostream>
+#include <type_traits>
+
+#include "../misc.h"
+
+#if defined(USE_AVX2)
+ #include <immintrin.h>
+
+#elif defined(USE_SSE41)
+ #include <smmintrin.h>
+
+#elif defined(USE_SSSE3)
+ #include <tmmintrin.h>
+
+#elif defined(USE_SSE2)
+ #include <emmintrin.h>
+
+#elif defined(USE_NEON)
+ #include <arm_neon.h>
+#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;
+
+constexpr const char Leb128MagicString[] = "COMPRESSED_LEB128";
+constexpr const std::size_t Leb128MagicStringSize = sizeof(Leb128MagicString) - 1;
+
+// 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_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<typename IntType>
+constexpr IntType ceil_to_multiple(IntType n, IntType base) {
+ return (n + base - 1) / base * base;
+}
+
+
+// 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<typename IntType>
+inline IntType read_little_endian(std::istream& stream) {
+ IntType result;
+
+ if (IsLittleEndian)
+ stream.read(reinterpret_cast<char*>(&result), sizeof(IntType));
+ else
+ {
+ std::uint8_t u[sizeof(IntType)];
+ std::make_unsigned_t<IntType> v = 0;
+
+ stream.read(reinterpret_cast<char*>(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;
+}
+
+
+// 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<typename IntType>
+inline void write_little_endian(std::ostream& stream, IntType value) {
+
+ if (IsLittleEndian)
+ stream.write(reinterpret_cast<const char*>(&value), sizeof(IntType));
+ else
+ {
+ std::uint8_t u[sizeof(IntType)];
+ std::make_unsigned_t<IntType> 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] = std::uint8_t(v);
+ v >>= 8;
+ }
+ }
+ u[i] = std::uint8_t(v);
+
+ stream.write(reinterpret_cast<char*>(u), sizeof(IntType));
+ }
+}
+
+
+// Read integers in bulk from a little indian stream.
+// This reads N integers from stream s and put them in array out.
+template<typename IntType>
+inline void read_little_endian(std::istream& stream, IntType* out, std::size_t count) {
+ if (IsLittleEndian)
+ stream.read(reinterpret_cast<char*>(out), sizeof(IntType) * count);
+ else
+ for (std::size_t i = 0; i < count; ++i)
+ out[i] = read_little_endian<IntType>(stream);
+}
+
+
+// Write integers in bulk to a little indian stream.
+// This takes N integers from array values and writes them on stream s.
+template<typename IntType>
+inline void write_little_endian(std::ostream& stream, const IntType* values, std::size_t count) {
+ if (IsLittleEndian)
+ stream.write(reinterpret_cast<const char*>(values), sizeof(IntType) * count);
+ else
+ for (std::size_t i = 0; i < count; ++i)
+ write_little_endian<IntType>(stream, values[i]);
+}
+
+
+// Read N signed integers from the stream s, putting them in
+// the array out. The stream is assumed to be compressed using the signed LEB128 format.
+// See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme.
+template<typename IntType>
+inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count) {
+
+ // Check the presence of our LEB128 magic string
+ char leb128MagicString[Leb128MagicStringSize];
+ stream.read(leb128MagicString, Leb128MagicStringSize);
+ assert(strncmp(Leb128MagicString, leb128MagicString, Leb128MagicStringSize) == 0);
+
+ static_assert(std::is_signed_v<IntType>, "Not implemented for unsigned types");
+
+ const std::uint32_t BUF_SIZE = 4096;
+ std::uint8_t buf[BUF_SIZE];
+
+ auto bytes_left = read_little_endian<std::uint32_t>(stream);
+
+ std::uint32_t buf_pos = BUF_SIZE;
+ for (std::size_t i = 0; i < count; ++i)
+ {
+ IntType result = 0;
+ size_t shift = 0;
+ do
+ {
+ if (buf_pos == BUF_SIZE)
+ {
+ stream.read(reinterpret_cast<char*>(buf), std::min(bytes_left, BUF_SIZE));
+ buf_pos = 0;
+ }
+
+ std::uint8_t byte = buf[buf_pos++];
+ --bytes_left;
+ result |= (byte & 0x7f) << shift;
+ shift += 7;
+
+ if ((byte & 0x80) == 0)
+ {
+ out[i] = (sizeof(IntType) * 8 <= shift || (byte & 0x40) == 0)
+ ? result
+ : result | ~((1 << shift) - 1);
+ break;
+ }
+ } while (shift < sizeof(IntType) * 8);
+ }
+
+ assert(bytes_left == 0);
+}
+
+
+// Write signed integers to a stream with LEB128 compression.
+// This takes N integers from array values, compress them with the LEB128 algorithm and
+// writes the result on the stream s.
+// See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme.
+template<typename IntType>
+inline void write_leb_128(std::ostream& stream, const IntType* values, std::size_t count) {
+
+ // Write our LEB128 magic string
+ stream.write(Leb128MagicString, Leb128MagicStringSize);
+
+ static_assert(std::is_signed_v<IntType>, "Not implemented for unsigned types");
+
+ std::uint32_t byte_count = 0;
+ for (std::size_t i = 0; i < count; ++i)
+ {
+ IntType value = values[i];
+ std::uint8_t byte;
+ do
+ {
+ byte = value & 0x7f;
+ value >>= 7;
+ ++byte_count;
+ } while ((byte & 0x40) == 0 ? value != 0 : value != -1);
+ }
+
+ write_little_endian(stream, byte_count);
+
+ const std::uint32_t BUF_SIZE = 4096;
+ std::uint8_t buf[BUF_SIZE];
+ std::uint32_t buf_pos = 0;
+
+ auto flush = [&]() {
+ if (buf_pos > 0)
+ {
+ stream.write(reinterpret_cast<char*>(buf), buf_pos);
+ buf_pos = 0;
+ }
+ };
+
+ auto write = [&](std::uint8_t byte) {
+ buf[buf_pos++] = byte;
+ if (buf_pos == BUF_SIZE)
+ flush();
+ };
+
+ for (std::size_t i = 0; i < count; ++i)
+ {
+ IntType value = values[i];
+ while (true)
+ {
+ std::uint8_t byte = value & 0x7f;
+ value >>= 7;
+ if ((byte & 0x40) == 0 ? value == 0 : value == -1)
+ {
+ write(byte);
+ break;
+ }
+ write(byte | 0x80);
+ }
+ }
+
+ flush();
+}
+
+} // namespace Stockfish::Eval::NNUE
+
+#endif // #ifndef NNUE_COMMON_H_INCLUDED
--- /dev/null
+/*
+ Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+ Copyright (C) 2004-2023 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 <http://www.gnu.org/licenses/>.
+*/
+
+// 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 <algorithm>
+#include <cassert>
+#include <cstdint>
+#include <cstring>
+#include <iosfwd>
+#include <utility>
+
+#include "../position.h"
+#include "../types.h"
+#include "nnue_accumulator.h"
+#include "nnue_architecture.h"
+#include "nnue_common.h"
+
+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
+using vec_t = __m512i;
+using psqt_vec_t = __m256i;
+ #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_mul_16(a, b) _mm512_mullo_epi16(a, b)
+ #define vec_zero() _mm512_setzero_epi32()
+ #define vec_set_16(a) _mm512_set1_epi16(a)
+ #define vec_max_16(a, b) _mm512_max_epi16(a, b)
+ #define vec_min_16(a, b) _mm512_min_epi16(a, b)
+inline vec_t vec_msb_pack_16(vec_t a, vec_t b) {
+ vec_t compacted = _mm512_packs_epi16(_mm512_srli_epi16(a, 7), _mm512_srli_epi16(b, 7));
+ return _mm512_permutexvar_epi64(_mm512_setr_epi64(0, 2, 4, 6, 1, 3, 5, 7), compacted);
+}
+ #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
+ #define MaxChunkSize 64
+
+#elif USE_AVX2
+using vec_t = __m256i;
+using psqt_vec_t = __m256i;
+ #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_mul_16(a, b) _mm256_mullo_epi16(a, b)
+ #define vec_zero() _mm256_setzero_si256()
+ #define vec_set_16(a) _mm256_set1_epi16(a)
+ #define vec_max_16(a, b) _mm256_max_epi16(a, b)
+ #define vec_min_16(a, b) _mm256_min_epi16(a, b)
+inline vec_t vec_msb_pack_16(vec_t a, vec_t b) {
+ vec_t compacted = _mm256_packs_epi16(_mm256_srli_epi16(a, 7), _mm256_srli_epi16(b, 7));
+ return _mm256_permute4x64_epi64(compacted, 0b11011000);
+}
+ #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
+ #define MaxChunkSize 32
+
+#elif USE_SSE2
+using vec_t = __m128i;
+using psqt_vec_t = __m128i;
+ #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_mul_16(a, b) _mm_mullo_epi16(a, b)
+ #define vec_zero() _mm_setzero_si128()
+ #define vec_set_16(a) _mm_set1_epi16(a)
+ #define vec_max_16(a, b) _mm_max_epi16(a, b)
+ #define vec_min_16(a, b) _mm_min_epi16(a, b)
+ #define vec_msb_pack_16(a, b) _mm_packs_epi16(_mm_srli_epi16(a, 7), _mm_srli_epi16(b, 7))
+ #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)
+ #define MaxChunkSize 16
+
+#elif USE_NEON
+using vec_t = int16x8_t;
+using psqt_vec_t = int32x4_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_mul_16(a, b) vmulq_s16(a, b)
+ #define vec_zero() \
+ vec_t { 0 }
+ #define vec_set_16(a) vdupq_n_s16(a)
+ #define vec_max_16(a, b) vmaxq_s16(a, b)
+ #define vec_min_16(a, b) vminq_s16(a, b)
+inline vec_t vec_msb_pack_16(vec_t a, vec_t b) {
+ const int8x8_t shifta = vshrn_n_s16(a, 7);
+ const int8x8_t shiftb = vshrn_n_s16(b, 7);
+ const int8x16_t compacted = vcombine_s8(shifta, shiftb);
+ return *reinterpret_cast<const vec_t*>(&compacted);
+}
+ #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
+ #define MaxChunkSize 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.
+ #if defined(__GNUC__)
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Wignored-attributes"
+ #endif
+
+template<typename SIMDRegisterType, typename LaneType, int NumLanes, int MaxRegisters>
+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<vec_t, WeightType, TransformedFeatureDimensions, NumRegistersSIMD>();
+static constexpr int NumPsqtRegs =
+ BestRegisterCount<psqt_vec_t, PSQTWeightType, PSQTBuckets, NumRegistersSIMD>();
+ #if defined(__GNUC__)
+ #pragma GCC diagnostic pop
+ #endif
+#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;
+
+ // 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 * 2);
+ }
+
+ // Read network parameters
+ bool read_parameters(std::istream& stream) {
+
+ read_leb_128<BiasType>(stream, biases, HalfDimensions);
+ read_leb_128<WeightType>(stream, weights, HalfDimensions * InputDimensions);
+ read_leb_128<PSQTWeightType>(stream, psqtWeights, PSQTBuckets * InputDimensions);
+
+ return !stream.fail();
+ }
+
+ // Write network parameters
+ bool write_parameters(std::ostream& stream) const {
+
+ write_leb_128<BiasType>(stream, biases, HalfDimensions);
+ write_leb_128<WeightType>(stream, weights, HalfDimensions * InputDimensions);
+ write_leb_128<PSQTWeightType>(stream, psqtWeights, PSQTBuckets * InputDimensions);
+
+ return !stream.fail();
+ }
+
+ // Convert input features
+ std::int32_t transform(const Position& pos, OutputType* output, int bucket) const {
+ update_accumulator<WHITE>(pos);
+ update_accumulator<BLACK>(pos);
+
+ 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;
+
+
+ for (IndexType p = 0; p < 2; ++p)
+ {
+ const IndexType offset = (HalfDimensions / 2) * p;
+
+#if defined(VECTOR)
+
+ constexpr IndexType OutputChunkSize = MaxChunkSize;
+ static_assert((HalfDimensions / 2) % OutputChunkSize == 0);
+ constexpr IndexType NumOutputChunks = HalfDimensions / 2 / OutputChunkSize;
+
+ vec_t Zero = vec_zero();
+ vec_t One = vec_set_16(127);
+
+ const vec_t* in0 = reinterpret_cast<const vec_t*>(&(accumulation[perspectives[p]][0]));
+ const vec_t* in1 =
+ reinterpret_cast<const vec_t*>(&(accumulation[perspectives[p]][HalfDimensions / 2]));
+ vec_t* out = reinterpret_cast<vec_t*>(output + offset);
+
+ for (IndexType j = 0; j < NumOutputChunks; j += 1)
+ {
+ const vec_t sum0a = vec_max_16(vec_min_16(in0[j * 2 + 0], One), Zero);
+ const vec_t sum0b = vec_max_16(vec_min_16(in0[j * 2 + 1], One), Zero);
+ const vec_t sum1a = vec_max_16(vec_min_16(in1[j * 2 + 0], One), Zero);
+ const vec_t sum1b = vec_max_16(vec_min_16(in1[j * 2 + 1], One), Zero);
+
+ const vec_t pa = vec_mul_16(sum0a, sum1a);
+ const vec_t pb = vec_mul_16(sum0b, sum1b);
+
+ out[j] = vec_msb_pack_16(pa, pb);
+ }
+
+#else
+
+ for (IndexType j = 0; j < HalfDimensions / 2; ++j)
+ {
+ BiasType sum0 = accumulation[static_cast<int>(perspectives[p])][j + 0];
+ BiasType sum1 =
+ accumulation[static_cast<int>(perspectives[p])][j + HalfDimensions / 2];
+ sum0 = std::clamp<BiasType>(sum0, 0, 127);
+ sum1 = std::clamp<BiasType>(sum1, 0, 127);
+ output[offset + j] = static_cast<OutputType>(unsigned(sum0 * sum1) / 128);
+ }
+
+#endif
+ }
+
+ return psqt;
+ } // end of function transform()
+
+ void hint_common_access(const Position& pos) const {
+ hint_common_access_for_perspective<WHITE>(pos);
+ hint_common_access_for_perspective<BLACK>(pos);
+ }
+
+ private:
+ template<Color Perspective>
+ [[nodiscard]] std::pair<StateInfo*, StateInfo*>
+ try_find_computed_accumulator(const Position& pos) const {
+ // 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)
+ || (gain -= FeatureSet::update_cost(st) + 1) < 0)
+ break;
+ next = st;
+ st = st->previous;
+ }
+ return {st, next};
+ }
+
+ // NOTE: The parameter states_to_update is an array of position states, ending with nullptr.
+ // All states must be sequential, that is states_to_update[i] must either be reachable
+ // by repeatedly applying ->previous from states_to_update[i+1] or
+ // states_to_update[i] == nullptr.
+ // computed_st must be reachable by repeatedly applying ->previous on
+ // states_to_update[0], if not nullptr.
+ template<Color Perspective, size_t N>
+ void update_accumulator_incremental(const Position& pos,
+ StateInfo* computed_st,
+ StateInfo* states_to_update[N]) const {
+ static_assert(N > 0);
+ assert(states_to_update[N - 1] == nullptr);
+
+#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
+
+ if (states_to_update[0] == nullptr)
+ return;
+
+ // Update incrementally going back through states_to_update.
+
+ // Gather all features to be updated.
+ const Square ksq = pos.square<KING>(Perspective);
+
+ // 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.
+ FeatureSet::IndexList removed[N - 1], added[N - 1];
+
+ {
+ int i =
+ N
+ - 2; // last potential state to update. Skip last element because it must be nullptr.
+ while (states_to_update[i] == nullptr)
+ --i;
+
+ StateInfo* st2 = states_to_update[i];
+
+ for (; i >= 0; --i)
+ {
+ states_to_update[i]->accumulator.computed[Perspective] = true;
+
+ const StateInfo* end_state = i == 0 ? computed_st : states_to_update[i - 1];
+
+ for (; st2 != end_state; st2 = st2->previous)
+ FeatureSet::append_changed_indices<Perspective>(ksq, st2->dirtyPiece,
+ removed[i], added[i]);
+ }
+ }
+
+ StateInfo* st = computed_st;
+
+ // Now update the accumulators listed in states_to_update[], where the last element is a sentinel.
+#ifdef VECTOR
+
+ if (states_to_update[1] == nullptr && (removed[0].size() == 1 || removed[0].size() == 2)
+ && added[0].size() == 1)
+ {
+ assert(states_to_update[0]);
+
+ auto accIn =
+ reinterpret_cast<const vec_t*>(&st->accumulator.accumulation[Perspective][0]);
+ auto accOut = reinterpret_cast<vec_t*>(
+ &states_to_update[0]->accumulator.accumulation[Perspective][0]);
+
+ const IndexType offsetR0 = HalfDimensions * removed[0][0];
+ auto columnR0 = reinterpret_cast<const vec_t*>(&weights[offsetR0]);
+ const IndexType offsetA = HalfDimensions * added[0][0];
+ auto columnA = reinterpret_cast<const vec_t*>(&weights[offsetA]);
+
+ if (removed[0].size() == 1)
+ {
+ for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t);
+ ++k)
+ accOut[k] = vec_add_16(vec_sub_16(accIn[k], columnR0[k]), columnA[k]);
+ }
+ else
+ {
+ const IndexType offsetR1 = HalfDimensions * removed[0][1];
+ auto columnR1 = reinterpret_cast<const vec_t*>(&weights[offsetR1]);
+
+ for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t);
+ ++k)
+ accOut[k] = vec_sub_16(vec_add_16(accIn[k], columnA[k]),
+ vec_add_16(columnR0[k], columnR1[k]));
+ }
+
+ auto accPsqtIn = reinterpret_cast<const psqt_vec_t*>(
+ &st->accumulator.psqtAccumulation[Perspective][0]);
+ auto accPsqtOut = reinterpret_cast<psqt_vec_t*>(
+ &states_to_update[0]->accumulator.psqtAccumulation[Perspective][0]);
+
+ const IndexType offsetPsqtR0 = PSQTBuckets * removed[0][0];
+ auto columnPsqtR0 = reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offsetPsqtR0]);
+ const IndexType offsetPsqtA = PSQTBuckets * added[0][0];
+ auto columnPsqtA = reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offsetPsqtA]);
+
+ if (removed[0].size() == 1)
+ {
+ for (std::size_t k = 0; k < PSQTBuckets * sizeof(std::int32_t) / sizeof(psqt_vec_t);
+ ++k)
+ accPsqtOut[k] = vec_add_psqt_32(vec_sub_psqt_32(accPsqtIn[k], columnPsqtR0[k]),
+ columnPsqtA[k]);
+ }
+ else
+ {
+ const IndexType offsetPsqtR1 = PSQTBuckets * removed[0][1];
+ auto columnPsqtR1 = reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offsetPsqtR1]);
+
+ for (std::size_t k = 0; k < PSQTBuckets * sizeof(std::int32_t) / sizeof(psqt_vec_t);
+ ++k)
+ accPsqtOut[k] =
+ vec_sub_psqt_32(vec_add_psqt_32(accPsqtIn[k], columnPsqtA[k]),
+ vec_add_psqt_32(columnPsqtR0[k], columnPsqtR1[k]));
+ }
+ }
+ else
+ {
+ for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j)
+ {
+ // Load accumulator
+ auto accTileIn = reinterpret_cast<const vec_t*>(
+ &st->accumulator.accumulation[Perspective][j * TileHeight]);
+ for (IndexType k = 0; k < NumRegs; ++k)
+ acc[k] = vec_load(&accTileIn[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<const vec_t*>(&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<const vec_t*>(&weights[offset]);
+ for (IndexType k = 0; k < NumRegs; ++k)
+ acc[k] = vec_add_16(acc[k], column[k]);
+ }
+
+ // Store accumulator
+ auto accTileOut = reinterpret_cast<vec_t*>(
+ &states_to_update[i]->accumulator.accumulation[Perspective][j * TileHeight]);
+ for (IndexType k = 0; k < NumRegs; ++k)
+ vec_store(&accTileOut[k], acc[k]);
+ }
+ }
+
+ for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j)
+ {
+ // Load accumulator
+ auto accTilePsqtIn = reinterpret_cast<const psqt_vec_t*>(
+ &st->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]);
+ for (std::size_t k = 0; k < NumPsqtRegs; ++k)
+ psqt[k] = vec_load_psqt(&accTilePsqtIn[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<const psqt_vec_t*>(&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<const psqt_vec_t*>(&psqtWeights[offset]);
+ for (std::size_t k = 0; k < NumPsqtRegs; ++k)
+ psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]);
+ }
+
+ // Store accumulator
+ auto accTilePsqtOut = reinterpret_cast<psqt_vec_t*>(
+ &states_to_update[i]
+ ->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]);
+ for (std::size_t k = 0; k < NumPsqtRegs; ++k)
+ vec_store_psqt(&accTilePsqtOut[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
+ }
+
+ template<Color Perspective>
+ void update_accumulator_refresh(const Position& pos) const {
+#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
+
+ // Refresh the accumulator
+ // Could be extracted to a separate function because it's done in 2 places,
+ // but it's unclear if compilers would correctly handle register allocation.
+ auto& accumulator = pos.state()->accumulator;
+ accumulator.computed[Perspective] = true;
+ FeatureSet::IndexList active;
+ FeatureSet::append_active_indices<Perspective>(pos, active);
+
+#ifdef VECTOR
+ for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j)
+ {
+ auto biasesTile = reinterpret_cast<const vec_t*>(&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<const vec_t*>(&weights[offset]);
+
+ for (unsigned k = 0; k < NumRegs; ++k)
+ acc[k] = vec_add_16(acc[k], column[k]);
+ }
+
+ auto accTile =
+ reinterpret_cast<vec_t*>(&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<const psqt_vec_t*>(&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<psqt_vec_t*>(
+ &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
+ }
+
+ template<Color Perspective>
+ void hint_common_access_for_perspective(const Position& pos) const {
+
+ // Works like update_accumulator, but performs less work.
+ // Updates ONLY the accumulator for pos.
+
+ // Look for a usable accumulator of an earlier position. We keep track
+ // of the estimated gain in terms of features to be added/subtracted.
+ // Fast early exit.
+ if (pos.state()->accumulator.computed[Perspective])
+ return;
+
+ auto [oldest_st, _] = try_find_computed_accumulator<Perspective>(pos);
+
+ if (oldest_st->accumulator.computed[Perspective])
+ {
+ // Only update current position accumulator to minimize work.
+ StateInfo* states_to_update[2] = {pos.state(), nullptr};
+ update_accumulator_incremental<Perspective, 2>(pos, oldest_st, states_to_update);
+ }
+ else
+ {
+ update_accumulator_refresh<Perspective>(pos);
+ }
+ }
+
+ template<Color Perspective>
+ void update_accumulator(const Position& pos) const {
+
+ auto [oldest_st, next] = try_find_computed_accumulator<Perspective>(pos);
+
+ if (oldest_st->accumulator.computed[Perspective])
+ {
+ if (next == nullptr)
+ return;
+
+ // Now update the accumulators listed in states_to_update[], where the last element is a sentinel.
+ // Currently we update 2 accumulators.
+ // 1. for the current position
+ // 2. the next accumulator after the computed one
+ // The heuristic may change in the future.
+ StateInfo* states_to_update[3] = {next, next == pos.state() ? nullptr : pos.state(),
+ nullptr};
+
+ update_accumulator_incremental<Perspective, 3>(pos, oldest_st, states_to_update);
+ }
+ else
+ {
+ update_accumulator_refresh<Perspective>(pos);
+ }
+ }
+
+ 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
+++ /dev/null
-/*
- Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
-
- 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 <http://www.gnu.org/licenses/>.
-*/
-
-#include <algorithm>
-#include <cassert>
-
-#include "bitboard.h"
-#include "pawns.h"
-#include "position.h"
-#include "thread.h"
-
-namespace {
-
- #define V Value
- #define S(mg, eg) make_score(mg, eg)
-
- // Pawn penalties
- constexpr Score Backward = S( 9, 24);
- constexpr Score BlockedStorm = S(82, 82);
- constexpr Score Doubled = S(11, 56);
- constexpr Score Isolated = S( 5, 15);
- constexpr Score WeakLever = S( 0, 56);
- constexpr Score WeakUnopposed = S(13, 27);
-
- // Connected pawn bonus
- constexpr int Connected[RANK_NB] = { 0, 7, 8, 12, 29, 48, 86 };
-
- // 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( -6), V( 81), V( 93), V( 58), V( 39), V( 18), V( 25) },
- { V(-43), V( 61), V( 35), V(-49), V(-29), V(-11), V( -63) },
- { V(-10), V( 75), V( 23), V( -2), V( 32), V( 3), V( -45) },
- { V(-39), V(-13), V(-29), V(-52), V(-48), V(-67), V(-166) }
- };
-
- // 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( 85), V(-289), V(-166), V(97), V(50), V( 45), V( 50) },
- { V( 46), V( -25), V( 122), V(45), V(37), V(-10), V( 20) },
- { V( -6), V( 51), V( 168), V(34), V(-2), V(-22), V(-14) },
- { V(-15), V( -11), V( 101), V( 4), V(11), V(-15), V(-29) }
- };
-
- #undef S
- #undef V
-
- template<Color Us>
- Score evaluate(const Position& pos, Pawns::Entry* e) {
-
- constexpr Color Them = (Us == WHITE ? BLACK : WHITE);
- constexpr Direction Up = pawn_push(Us);
-
- Bitboard neighbours, stoppers, support, phalanx, opposed;
- Bitboard lever, leverPush, blocked;
- Square s;
- bool backward, passed, doubled;
- Score score = SCORE_ZERO;
- const Square* pl = pos.squares<PAWN>(Us);
-
- Bitboard ourPawns = pos.pieces( Us, PAWN);
- Bitboard theirPawns = pos.pieces(Them, PAWN);
-
- Bitboard doubleAttackThem = pawn_double_attacks_bb<Them>(theirPawns);
-
- e->passedPawns[Us] = 0;
- e->kingSquares[Us] = SQ_NONE;
- e->pawnAttacks[Us] = e->pawnAttacksSpan[Us] = pawn_attacks_bb<Us>(ourPawns);
-
- // Loop through all pawns of the current color and score each pawn
- while ((s = *pl++) != SQ_NONE)
- {
- assert(pos.piece_on(s) == make_piece(Us, PAWN));
-
- Rank r = relative_rank(Us, s);
-
- // Flag the pawn
- opposed = theirPawns & forward_file_bb(Us, s);
- blocked = theirPawns & (s + Up);
- stoppers = theirPawns & passed_pawn_span(Us, s);
- lever = theirPawns & PawnAttacks[Us][s];
- leverPush = theirPawns & PawnAttacks[Us][s + Up];
- doubled = ourPawns & (s - Up);
- neighbours = ourPawns & adjacent_files_bb(s);
- phalanx = neighbours & rank_bb(s);
- support = neighbours & rank_bb(s - Up);
-
- // A pawn is backward when it is behind all pawns of the same color on
- // the adjacent files and cannot safely advance.
- backward = !(neighbours & forward_ranks_bb(Them, s + Up))
- && (leverPush | 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.
- passed = !(stoppers ^ lever)
- || ( !(stoppers ^ leverPush)
- && popcount(phalanx) >= popcount(leverPush))
- || ( stoppers == blocked && r >= RANK_5
- && (shift<Up>(support) & ~(theirPawns | doubleAttackThem)));
-
- // Passed pawns will be properly scored later in evaluation when we have
- // full attack info.
- if (passed)
- e->passedPawns[Us] |= s;
-
- // Score this pawn
- if (support | phalanx)
- {
- int v = Connected[r] * (2 + bool(phalanx) - bool(opposed))
- + 21 * popcount(support);
-
- score += make_score(v, v * (r - 2) / 4);
- }
-
- else if (!neighbours)
- score -= Isolated
- + WeakUnopposed * !opposed;
-
- else if (backward)
- score -= Backward
- + WeakUnopposed * !opposed;
-
- if (!support)
- score -= Doubled * doubled
- + WeakLever * more_than_one(lever);
- }
-
- 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)
- return e;
-
- e->key = key;
- e->scores[WHITE] = evaluate<WHITE>(pos, e);
- e->scores[BLACK] = evaluate<BLACK>(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<Color Us>
-Score Entry::evaluate_shelter(const Position& pos, Square ksq) {
-
- constexpr Color Them = (Us == WHITE ? BLACK : WHITE);
-
- Bitboard b = pos.pieces(PAWN) & ~forward_ranks_bb(Them, ksq);
- Bitboard ourPawns = b & pos.pieces(Us);
- Bitboard theirPawns = b & pos.pieces(Them);
-
- Score bonus = make_score(5, 5);
-
- File center = clamp(file_of(ksq), FILE_B, FILE_G);
- 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)) : 0;
-
- b = theirPawns & file_bb(f);
- int theirRank = b ? relative_rank(Us, frontmost_sq(Them, b)) : 0;
-
- File d = map_to_queenside(f);
- bonus += make_score(ShelterStrength[d][ourRank], 0);
-
- if (ourRank && (ourRank == theirRank - 1))
- bonus -= BlockedStorm * int(theirRank == RANK_3);
- else
- bonus -= make_score(UnblockedStorm[d][theirRank], 0);
- }
-
- 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<Color Us>
-Score Entry::do_king_safety(const Position& pos) {
-
- Square ksq = pos.square<KING>(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<Us>(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<Us>(pos, relative_square(Us, SQ_G1)), compare);
-
- if (pos.can_castle(Us & QUEEN_SIDE))
- shelter = std::max(shelter, evaluate_shelter<Us>(pos, relative_square(Us, SQ_C1)), compare);
-
- // In endgame we like to bring our king near our closest pawn
- Bitboard pawns = pos.pieces(Us, PAWN);
- int minPawnDist = pawns ? 8 : 0;
-
- if (pawns & PseudoAttacks[KING][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<WHITE>(const Position& pos);
-template Score Entry::do_king_safety<BLACK>(const Position& pos);
-
-} // namespace Pawns
+++ /dev/null
-/*
- Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
-
- 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 <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef PAWNS_H_INCLUDED
-#define PAWNS_H_INCLUDED
-
-#include "misc.h"
-#include "position.h"
-#include "types.h"
-
-namespace 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]); }
-
- template<Color Us>
- Score king_safety(const Position& pos) {
- return kingSquares[Us] == pos.square<KING>(Us) && castlingRights[Us] == pos.castling_rights(Us)
- ? kingSafety[Us] : (kingSafety[Us] = do_king_safety<Us>(pos));
- }
-
- template<Color Us>
- Score do_king_safety(const Position& pos);
-
- template<Color Us>
- Score evaluate_shelter(const Position& pos, Square ksq);
-
- 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];
-};
-
-typedef HashTable<Entry, 131072> Table;
-
-Entry* probe(const Position& pos);
-
-} // namespace Pawns
-
-#endif // #ifndef PAWNS_H_INCLUDED
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+ Copyright (C) 2004-2023 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
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "position.h"
+
#include <algorithm>
+#include <atomic>
#include <cassert>
-#include <cstddef> // For offsetof()
-#include <cstring> // For std::memset, std::memcmp
+#include <cctype>
+#include <cstddef>
+#include <cstring>
+#include <initializer_list>
#include <iomanip>
+#include <iostream>
#include <sstream>
+#include <string_view>
+#include <utility>
#include "bitboard.h"
#include "misc.h"
#include "movegen.h"
-#include "position.h"
+#include "nnue/nnue_common.h"
+#include "syzygy/tbprobe.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 psq[PIECE_NB][SQUARE_NB];
+Key enpassant[FILE_NB];
+Key castling[CASTLING_RIGHT_NB];
+Key side, noPawns;
}
namespace {
-const string PieceToChar(" PNBRQK pnbrqk");
+constexpr std::string_view PieceToChar(" PNBRQK pnbrqk");
-constexpr Piece Pieces[] = { W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING,
- B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING };
-} // namespace
+constexpr Piece Pieces[] = {W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING,
+ B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING};
+} // namespace
-/// operator<<(Position) returns an ASCII representation of the position
-
+// Returns an ASCII representation of the position
std::ostream& operator<<(std::ostream& os, const Position& pos) {
- os << "\n +---+---+---+---+---+---+---+---+\n";
-
- for (Rank r = RANK_8; r >= RANK_1; --r)
- {
- for (File f = FILE_A; f <= FILE_H; ++f)
- os << " | " << PieceToChar[pos.piece_on(make_square(f, r))];
-
- os << " |\n +---+---+---+---+---+---+---+---+\n";
- }
-
- os << "\nFen: " << pos.fen() << "\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(pop_lsb(&b)) << " ";
-
- if ( int(Tablebases::MaxCardinality) >= popcount(pos.pieces())
- && !pos.can_castle(ANY_CASTLING))
- {
- StateInfo st;
- Position p;
- p.set(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;
+ os << "\n +---+---+---+---+---+---+---+---+\n";
+
+ for (Rank r = RANK_8; r >= RANK_1; --r)
+ {
+ for (File f = FILE_A; f <= FILE_H; ++f)
+ os << " | " << PieceToChar[pos.piece_on(make_square(f, r))];
+
+ os << " | " << (1 + r) << "\n +---+---+---+---+---+---+---+---+\n";
+ }
+
+ os << " a b c d e f g h\n"
+ << "\nFen: " << pos.fen() << "\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(pop_lsb(b)) << " ";
+
+ if (int(Tablebases::MaxCardinality) >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING))
+ {
+ StateInfo st;
+ ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize);
+
+ Position p;
+ p.set(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
+// Implements Marcel van Kervinck's cuckoo algorithm to detect repetition of positions
+// for 3-fold repetition draws. The algorithm uses two hash tables with Zobrist hashes
+// to allow fast detection of recurring positions. For details see:
+// http://web.archive.org/web/20201107002606/https://marcelk.net/2013-04-06/paper/upcoming-rep-v2.pdf
// First and second hash functions for indexing the cuckoo tables
inline int H1(Key h) { return h & 0x1fff; }
inline int H2(Key h) { return (h >> 16) & 0x1fff; }
// Cuckoo tables with Zobrist hashes of valid reversible moves, and the moves themselves
-Key cuckoo[8192];
+Key cuckoo[8192];
Move cuckooMove[8192];
-/// Position::init() initializes at startup the various arrays used to compute
-/// hash keys.
-
+// Initializes at startup the various arrays used to compute hash keys
void Position::init() {
- PRNG rng(1070372);
-
- for (Piece pc : Pieces)
- for (Square s = SQ_A1; s <= SQ_H8; ++s)
- Zobrist::psq[pc][s] = rng.rand<Key>();
-
- for (File f = FILE_A; f <= FILE_H; ++f)
- Zobrist::enpassant[f] = rng.rand<Key>();
-
- for (int cr = NO_CASTLING; cr <= ANY_CASTLING; ++cr)
- {
- Zobrist::castling[cr] = 0;
- Bitboard b = cr;
- while (b)
- {
- Key k = Zobrist::castling[1ULL << pop_lsb(&b)];
- Zobrist::castling[cr] ^= k ? k : rng.rand<Key>();
- }
- }
-
- Zobrist::side = rng.rand<Key>();
- Zobrist::noPawns = rng.rand<Key>();
-
- // Prepare the cuckoo tables
- std::memset(cuckoo, 0, sizeof(cuckoo));
- std::memset(cuckooMove, 0, sizeof(cuckooMove));
- int count = 0;
- for (Piece pc : Pieces)
- for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
- for (Square s2 = Square(s1 + 1); s2 <= SQ_H8; ++s2)
- if (PseudoAttacks[type_of(pc)][s1] & 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++;
- }
- assert(count == 3668);
+ PRNG rng(1070372);
+
+ for (Piece pc : Pieces)
+ for (Square s = SQ_A1; s <= SQ_H8; ++s)
+ Zobrist::psq[pc][s] = rng.rand<Key>();
+
+ for (File f = FILE_A; f <= FILE_H; ++f)
+ Zobrist::enpassant[f] = rng.rand<Key>();
+
+ for (int cr = NO_CASTLING; cr <= ANY_CASTLING; ++cr)
+ Zobrist::castling[cr] = rng.rand<Key>();
+
+ Zobrist::side = rng.rand<Key>();
+ Zobrist::noPawns = rng.rand<Key>();
+
+ // Prepare the cuckoo tables
+ std::memset(cuckoo, 0, sizeof(cuckoo));
+ std::memset(cuckooMove, 0, sizeof(cuckooMove));
+ [[maybe_unused]] int count = 0;
+ for (Piece pc : Pieces)
+ for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
+ for (Square s2 = Square(s1 + 1); s2 <= SQ_H8; ++s2)
+ if ((type_of(pc) != PAWN) && (attacks_bb(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++;
+ }
+ assert(count == 3668);
}
-/// 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.
-
+// 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 string& fenStr, bool isChess960, StateInfo* si, Thread* th) {
-/*
+ /*
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:
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. 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.
+ 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
incremented after Black's move.
*/
- unsigned char col, row, token;
- size_t idx;
- Square sq = SQ_A8;
- std::istringstream ss(fenStr);
-
- std::memset(this, 0, sizeof(Position));
- std::memset(si, 0, sizeof(StateInfo));
- std::fill_n(&pieceList[0][0], sizeof(pieceList) / sizeof(Square), SQ_NONE);
- st = si;
-
- ss >> std::noskipws;
-
- // 1. Piece placement
- while ((ss >> token) && !isspace(token))
- {
- if (isdigit(token))
- sq += (token - '0') * EAST; // Advance the given number of files
-
- else if (token == '/')
- sq += 2 * SOUTH;
-
- else if ((idx = PieceToChar.find(token)) != string::npos)
- {
- put_piece(Piece(idx), sq);
- ++sq;
- }
- }
-
- // 2. Active color
- ss >> token;
- sideToMove = (token == 'w' ? WHITE : BLACK);
- ss >> token;
-
- // 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;
- Piece rook = make_piece(c, ROOK);
-
- token = char(toupper(token));
-
- if (token == 'K')
- for (rsq = relative_square(c, SQ_H1); piece_on(rsq) != rook; --rsq) {}
-
- else if (token == 'Q')
- for (rsq = relative_square(c, SQ_A1); piece_on(rsq) != rook; ++rsq) {}
-
- else if (token >= 'A' && token <= 'H')
- rsq = make_square(File(token - 'A'), relative_rank(c, RANK_1));
-
- else
- continue;
-
- set_castling_right(c, rsq);
- }
-
- // 4. En passant square. Ignore if no pawn capture is possible
- if ( ((ss >> col) && (col >= 'a' && col <= 'h'))
- && ((ss >> row) && (row == '3' || row == '6')))
- {
- st->epSquare = make_square(File(col - 'a'), Rank(row - '1'));
-
- if ( !(attackers_to(st->epSquare) & pieces(sideToMove, PAWN))
- || !(pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove))))
- st->epSquare = SQ_NONE;
- }
- else
- st->epSquare = SQ_NONE;
-
- // 5-6. Halfmove clock and fullmove number
- 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);
-
- chess960 = isChess960;
- thisThread = th;
- set_state(st);
-
- assert(pos_is_ok());
-
- return *this;
-}
+ unsigned char col, row, token;
+ size_t idx;
+ Square sq = SQ_A8;
+ std::istringstream ss(fenStr);
+ std::memset(this, 0, sizeof(Position));
+ std::memset(si, 0, sizeof(StateInfo));
+ st = si;
-/// Position::set_castling_right() is a helper function used to set castling
-/// rights given the corresponding color and the rook starting square.
+ ss >> std::noskipws;
-void Position::set_castling_right(Color c, Square rfrom) {
+ // 1. Piece placement
+ while ((ss >> token) && !isspace(token))
+ {
+ if (isdigit(token))
+ sq += (token - '0') * EAST; // Advance the given number of files
- Square kfrom = square<KING>(c);
- CastlingRights cr = c & (kfrom < rfrom ? KING_SIDE: QUEEN_SIDE);
+ else if (token == '/')
+ sq += 2 * SOUTH;
- st->castlingRights |= cr;
- castlingRightsMask[kfrom] |= cr;
- castlingRightsMask[rfrom] |= cr;
- castlingRookSquare[cr] = rfrom;
+ else if ((idx = PieceToChar.find(token)) != string::npos)
+ {
+ put_piece(Piece(idx), sq);
+ ++sq;
+ }
+ }
- Square kto = relative_square(c, cr & KING_SIDE ? SQ_G1 : SQ_C1);
- Square rto = relative_square(c, cr & KING_SIDE ? SQ_F1 : SQ_D1);
+ // 2. Active color
+ ss >> token;
+ sideToMove = (token == 'w' ? WHITE : BLACK);
+ ss >> token;
+
+ // 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;
+ Piece rook = make_piece(c, ROOK);
- castlingPath[cr] = (between_bb(rfrom, rto) | between_bb(kfrom, kto) | rto | kto)
- & ~(square_bb(kfrom) | rfrom);
-}
+ token = char(toupper(token));
+
+ if (token == 'K')
+ for (rsq = relative_square(c, SQ_H1); piece_on(rsq) != rook; --rsq)
+ {}
+ else if (token == 'Q')
+ for (rsq = relative_square(c, SQ_A1); piece_on(rsq) != rook; ++rsq)
+ {}
-/// Position::set_check_info() sets king attacks to detect if a move gives check
+ else if (token >= 'A' && token <= 'H')
+ rsq = make_square(File(token - 'A'), relative_rank(c, RANK_1));
-void Position::set_check_info(StateInfo* si) const {
+ else
+ continue;
- si->blockersForKing[WHITE] = slider_blockers(pieces(BLACK), square<KING>(WHITE), si->pinners[BLACK]);
- si->blockersForKing[BLACK] = slider_blockers(pieces(WHITE), square<KING>(BLACK), si->pinners[WHITE]);
+ set_castling_right(c, rsq);
+ }
- Square ksq = square<KING>(~sideToMove);
+ // 4. En passant square.
+ // Ignore if square is invalid or not on side to move relative rank 6.
+ bool enpassant = false;
- si->checkSquares[PAWN] = attacks_from<PAWN>(ksq, ~sideToMove);
- si->checkSquares[KNIGHT] = attacks_from<KNIGHT>(ksq);
- si->checkSquares[BISHOP] = attacks_from<BISHOP>(ksq);
- si->checkSquares[ROOK] = attacks_from<ROOK>(ksq);
- si->checkSquares[QUEEN] = si->checkSquares[BISHOP] | si->checkSquares[ROOK];
- si->checkSquares[KING] = 0;
-}
+ if (((ss >> col) && (col >= 'a' && col <= 'h'))
+ && ((ss >> row) && (row == (sideToMove == WHITE ? '6' : '3'))))
+ {
+ st->epSquare = make_square(File(col - 'a'), Rank(row - '1'));
+
+ // En passant square will be considered only if
+ // a) side to move have a pawn threatening epSquare
+ // b) there is an enemy pawn in front of epSquare
+ // c) there is no piece on epSquare or behind epSquare
+ enpassant = pawn_attacks_bb(~sideToMove, st->epSquare) & pieces(sideToMove, PAWN)
+ && (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove)))
+ && !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove))));
+ }
+ if (!enpassant)
+ st->epSquare = SQ_NONE;
-/// 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.
+ // 5-6. Halfmove clock and fullmove number
+ ss >> std::skipws >> st->rule50 >> gamePly;
-void Position::set_state(StateInfo* si) const {
+ // 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);
- si->key = si->materialKey = 0;
- si->pawnKey = Zobrist::noPawns;
- si->nonPawnMaterial[WHITE] = si->nonPawnMaterial[BLACK] = VALUE_ZERO;
- si->checkersBB = attackers_to(square<KING>(sideToMove)) & pieces(~sideToMove);
+ chess960 = isChess960;
+ thisThread = th;
+ set_state();
- set_check_info(si);
+ assert(pos_is_ok());
- for (Bitboard b = pieces(); b; )
- {
- Square s = pop_lsb(&b);
- Piece pc = piece_on(s);
- si->key ^= Zobrist::psq[pc][s];
+ return *this;
+}
- 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];
- }
+// 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) {
- if (si->epSquare != SQ_NONE)
- si->key ^= Zobrist::enpassant[file_of(si->epSquare)];
+ Square kfrom = square<KING>(c);
+ CastlingRights cr = c & (kfrom < rfrom ? KING_SIDE : QUEEN_SIDE);
- if (sideToMove == BLACK)
- si->key ^= Zobrist::side;
+ st->castlingRights |= cr;
+ castlingRightsMask[kfrom] |= cr;
+ castlingRightsMask[rfrom] |= cr;
+ castlingRookSquare[cr] = rfrom;
- si->key ^= Zobrist::castling[si->castlingRights];
+ Square kto = relative_square(c, cr & KING_SIDE ? SQ_G1 : SQ_C1);
+ Square rto = relative_square(c, cr & KING_SIDE ? SQ_F1 : SQ_D1);
- for (Piece pc : Pieces)
- for (int cnt = 0; cnt < pieceCount[pc]; ++cnt)
- si->materialKey ^= Zobrist::psq[pc][cnt];
+ castlingPath[cr] = (between_bb(rfrom, rto) | between_bb(kfrom, kto)) & ~(kfrom | rfrom);
}
-/// 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.
+// Sets king attacks to detect if a move gives check
+void Position::set_check_info() const {
-Position& Position::set(const string& code, Color c, StateInfo* si) {
+ update_slider_blockers(WHITE);
+ update_slider_blockers(BLACK);
- assert(code.length() > 0 && code.length() < 8);
- assert(code[0] == 'K');
+ Square ksq = square<KING>(~sideToMove);
- string sides[] = { code.substr(code.find('K', 1)), // Weak
- code.substr(0, code.find('K', 1)) }; // Strong
+ st->checkSquares[PAWN] = pawn_attacks_bb(~sideToMove, ksq);
+ st->checkSquares[KNIGHT] = attacks_bb<KNIGHT>(ksq);
+ st->checkSquares[BISHOP] = attacks_bb<BISHOP>(ksq, pieces());
+ st->checkSquares[ROOK] = attacks_bb<ROOK>(ksq, pieces());
+ st->checkSquares[QUEEN] = st->checkSquares[BISHOP] | st->checkSquares[ROOK];
+ st->checkSquares[KING] = 0;
+}
- std::transform(sides[c].begin(), sides[c].end(), sides[c].begin(), tolower);
- string fenStr = "8/" + sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/"
- + sides[1] + char(8 - sides[1].length() + '0') + "/8 w - - 0 10";
+// 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
+void Position::set_state() const {
- return set(fenStr, false, si, nullptr);
-}
+ st->key = st->materialKey = 0;
+ st->pawnKey = Zobrist::noPawns;
+ st->nonPawnMaterial[WHITE] = st->nonPawnMaterial[BLACK] = VALUE_ZERO;
+ st->checkersBB = attackers_to(square<KING>(sideToMove)) & pieces(~sideToMove);
+ set_check_info();
-/// 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.
+ for (Bitboard b = pieces(); b;)
+ {
+ Square s = pop_lsb(b);
+ Piece pc = piece_on(s);
+ st->key ^= Zobrist::psq[pc][s];
-const string Position::fen() const {
+ if (type_of(pc) == PAWN)
+ st->pawnKey ^= Zobrist::psq[pc][s];
- int emptyCnt;
- std::ostringstream ss;
+ else if (type_of(pc) != KING)
+ st->nonPawnMaterial[color_of(pc)] += PieceValue[pc];
+ }
- for (Rank r = RANK_8; r >= RANK_1; --r)
- {
- for (File f = FILE_A; f <= FILE_H; ++f)
- {
- for (emptyCnt = 0; f <= FILE_H && empty(make_square(f, r)); ++f)
- ++emptyCnt;
+ if (st->epSquare != SQ_NONE)
+ st->key ^= Zobrist::enpassant[file_of(st->epSquare)];
- if (emptyCnt)
- ss << emptyCnt;
+ if (sideToMove == BLACK)
+ st->key ^= Zobrist::side;
- if (f <= FILE_H)
- ss << PieceToChar[piece_on(make_square(f, r))];
- }
+ st->key ^= Zobrist::castling[st->castlingRights];
- if (r > RANK_1)
- ss << '/';
- }
+ for (Piece pc : Pieces)
+ for (int cnt = 0; cnt < pieceCount[pc]; ++cnt)
+ st->materialKey ^= Zobrist::psq[pc][cnt];
+}
- ss << (sideToMove == WHITE ? " w " : " b ");
- if (can_castle(WHITE_OO))
- ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OO ))) : 'K');
+// Overload to initialize the position object with the given endgame code string
+// like "KBPKN". It's mainly a helper to get the material key out of an endgame code.
+Position& Position::set(const string& code, Color c, StateInfo* si) {
- if (can_castle(WHITE_OOO))
- ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OOO))) : 'Q');
+ assert(code[0] == 'K');
- if (can_castle(BLACK_OO))
- ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OO ))) : 'k');
+ string sides[] = {code.substr(code.find('K', 1)), // Weak
+ code.substr(0, std::min(code.find('v'), code.find('K', 1)))}; // Strong
- if (can_castle(BLACK_OOO))
- ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OOO))) : 'q');
+ assert(sides[0].length() > 0 && sides[0].length() < 8);
+ assert(sides[1].length() > 0 && sides[1].length() < 8);
- if (!can_castle(ANY_CASTLING))
- ss << '-';
+ std::transform(sides[c].begin(), sides[c].end(), sides[c].begin(), tolower);
- ss << (ep_square() == SQ_NONE ? " - " : " " + UCI::square(ep_square()) + " ")
- << st->rule50 << " " << 1 + (gamePly - (sideToMove == BLACK)) / 2;
+ string fenStr = "8/" + sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/" + sides[1]
+ + char(8 - sides[1].length() + '0') + "/8 w - - 0 10";
- return ss.str();
+ return set(fenStr, false, si, nullptr);
}
-/// 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.
+// 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() const {
-Bitboard Position::slider_blockers(Bitboard sliders, Square s, Bitboard& pinners) const {
+ int emptyCnt;
+ std::ostringstream ss;
- Bitboard blockers = 0;
- pinners = 0;
+ for (Rank r = RANK_8; r >= RANK_1; --r)
+ {
+ for (File f = FILE_A; f <= FILE_H; ++f)
+ {
+ for (emptyCnt = 0; f <= FILE_H && empty(make_square(f, r)); ++f)
+ ++emptyCnt;
- // Snipers are sliders that attack 's' when a piece and other snipers are removed
- Bitboard snipers = ( (PseudoAttacks[ ROOK][s] & pieces(QUEEN, ROOK))
- | (PseudoAttacks[BISHOP][s] & pieces(QUEEN, BISHOP))) & sliders;
- Bitboard occupancy = pieces() ^ snipers;
+ if (emptyCnt)
+ ss << emptyCnt;
- while (snipers)
- {
- Square sniperSq = pop_lsb(&snipers);
- Bitboard b = between_bb(s, sniperSq) & occupancy;
+ if (f <= FILE_H)
+ ss << PieceToChar[piece_on(make_square(f, r))];
+ }
- if (b && !more_than_one(b))
- {
- blockers |= b;
- if (b & pieces(color_of(piece_on(s))))
- pinners |= sniperSq;
+ if (r > RANK_1)
+ ss << '/';
}
- }
- return blockers;
+
+ ss << (sideToMove == WHITE ? " w " : " b ");
+
+ 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 (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 (!can_castle(ANY_CASTLING))
+ ss << '-';
+
+ ss << (ep_square() == SQ_NONE ? " - " : " " + UCI::square(ep_square()) + " ") << st->rule50
+ << " " << 1 + (gamePly - (sideToMove == BLACK)) / 2;
+
+ return ss.str();
}
+// Calculates st->blockersForKing[c] and st->pinners[~c],
+// which store respectively the pieces preventing king of color c from being in check
+// and the slider pieces of color ~c pinning pieces of color c to the king.
+void Position::update_slider_blockers(Color c) const {
-/// Position::attackers_to() computes a bitboard of all pieces which attack a
-/// given square. Slider attacks use the occupied bitboard to indicate occupancy.
+ Square ksq = square<KING>(c);
-Bitboard Position::attackers_to(Square s, Bitboard occupied) const {
+ st->blockersForKing[c] = 0;
+ st->pinners[~c] = 0;
+
+ // Snipers are sliders that attack 's' when a piece and other snipers are removed
+ Bitboard snipers = ((attacks_bb<ROOK>(ksq) & pieces(QUEEN, ROOK))
+ | (attacks_bb<BISHOP>(ksq) & pieces(QUEEN, BISHOP)))
+ & pieces(~c);
+ Bitboard occupancy = pieces() ^ snipers;
- return (attacks_from<PAWN>(s, BLACK) & pieces(WHITE, PAWN))
- | (attacks_from<PAWN>(s, WHITE) & pieces(BLACK, PAWN))
- | (attacks_from<KNIGHT>(s) & pieces(KNIGHT))
- | (attacks_bb< ROOK>(s, occupied) & pieces( ROOK, QUEEN))
- | (attacks_bb<BISHOP>(s, occupied) & pieces(BISHOP, QUEEN))
- | (attacks_from<KING>(s) & pieces(KING));
+ while (snipers)
+ {
+ Square sniperSq = pop_lsb(snipers);
+ Bitboard b = between_bb(ksq, sniperSq) & occupancy;
+
+ if (b && !more_than_one(b))
+ {
+ st->blockersForKing[c] |= b;
+ if (b & pieces(c))
+ st->pinners[~c] |= sniperSq;
+ }
+ }
}
-/// Position::legal() tests whether a pseudo-legal move is legal
+// 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) const {
+
+ return (pawn_attacks_bb(BLACK, s) & pieces(WHITE, PAWN))
+ | (pawn_attacks_bb(WHITE, s) & pieces(BLACK, PAWN))
+ | (attacks_bb<KNIGHT>(s) & pieces(KNIGHT))
+ | (attacks_bb<ROOK>(s, occupied) & pieces(ROOK, QUEEN))
+ | (attacks_bb<BISHOP>(s, occupied) & pieces(BISHOP, QUEEN))
+ | (attacks_bb<KING>(s) & pieces(KING));
+}
+
+// Tests whether a pseudo-legal move is legal
bool Position::legal(Move m) const {
- assert(is_ok(m));
+ assert(is_ok(m));
- Color us = sideToMove;
- Square from = from_sq(m);
- Square to = to_sq(m);
+ Color us = sideToMove;
+ Square from = from_sq(m);
+ Square to = to_sq(m);
- assert(color_of(moved_piece(m)) == us);
- assert(piece_on(square<KING>(us)) == make_piece(us, KING));
+ assert(color_of(moved_piece(m)) == us);
+ assert(piece_on(square<KING>(us)) == make_piece(us, KING));
- // 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) == ENPASSANT)
- {
- Square ksq = square<KING>(us);
- Square capsq = to - pawn_push(us);
- Bitboard occupied = (pieces() ^ from ^ capsq) | to;
+ // 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)
+ {
+ Square ksq = square<KING>(us);
+ Square capsq = to - pawn_push(us);
+ Bitboard occupied = (pieces() ^ from ^ capsq) | to;
- assert(to == ep_square());
- assert(moved_piece(m) == make_piece(us, PAWN));
- assert(piece_on(capsq) == make_piece(~us, PAWN));
- assert(piece_on(to) == NO_PIECE);
+ assert(to == ep_square());
+ assert(moved_piece(m) == make_piece(us, PAWN));
+ assert(piece_on(capsq) == make_piece(~us, PAWN));
+ assert(piece_on(to) == NO_PIECE);
- return !(attacks_bb< ROOK>(ksq, occupied) & pieces(~us, QUEEN, ROOK))
+ return !(attacks_bb<ROOK>(ksq, occupied) & pieces(~us, QUEEN, ROOK))
&& !(attacks_bb<BISHOP>(ksq, occupied) & pieces(~us, QUEEN, BISHOP));
- }
-
- // 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 = relative_square(us, to > from ? SQ_G1 : SQ_C1);
- Direction step = to > from ? WEST : EAST;
-
- for (Square s = to; s != from; s += step)
- if (attackers_to(s) & pieces(~us))
- return false;
-
- // In case of Chess960, verify that when moving the castling rook we do
- // not discover some hidden checker.
- // For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1.
- return !chess960
- || !(attacks_bb<ROOK>(to, pieces() ^ to_sq(m)) & pieces(~us, ROOK, QUEEN));
- }
-
- // If the moving piece is a king, check whether the destination square is
- // attacked by the opponent.
- if (type_of(piece_on(from)) == KING)
- return !(attackers_to(to) & pieces(~us));
-
- // A non-king move is legal if and only if it is not pinned or it
- // is moving along the ray towards or away from the king.
- return !(blockers_for_king(us) & from)
- || aligned(from, to, square<KING>(us));
-}
+ }
+
+ // 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 = relative_square(us, to > from ? SQ_G1 : SQ_C1);
+ Direction step = to > from ? WEST : EAST;
+
+ for (Square s = to; s != from; s += step)
+ if (attackers_to(s) & pieces(~us))
+ 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 !chess960 || !(blockers_for_king(us) & to_sq(m));
+ }
+
+ // If the moving piece is a king, check whether the destination square is
+ // attacked by the opponent.
+ if (type_of(piece_on(from)) == KING)
+ return !(attackers_to(to, pieces() ^ from) & pieces(~us));
+ // A non-king move is legal if and only if it is not pinned or it
+ // is moving along the ray towards or away from the king.
+ return !(blockers_for_king(us) & from) || aligned(from, to, square<KING>(us));
+}
-/// 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.
+// 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);
-
- // Use a slower but simpler function for uncommon cases
- if (type_of(m) != NORMAL)
- return MoveList<LEGAL>(*this).contains(m);
-
- // Is not a promotion, so promotion piece must be empty
- if (promotion_type(m) - KNIGHT != 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 ((Rank8BB | Rank1BB) & to)
- return false;
-
- if ( !(attacks_from<PAWN>(from, us) & pieces(~us) & to) // Not a capture
- && !((from + pawn_push(us) == to) && empty(to)) // Not a single push
- && !( (from + 2 * pawn_push(us) == to) // Not a double push
- && (rank_of(from) == relative_rank(us, RANK_2))
- && empty(to)
- && empty(to - pawn_push(us))))
- return false;
- }
- else if (!(attacks_from(type_of(pc), from) & 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())
- {
- 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
- if (!((between_bb(lsb(checkers()), square<KING>(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) & pieces(~us))
- return false;
- }
-
- return true;
-}
+ Color us = sideToMove;
+ Square from = from_sq(m);
+ Square to = to_sq(m);
+ Piece pc = moved_piece(m);
+ // Use a slower but simpler function for uncommon cases
+ // yet we skip the legality check of MoveList<LEGAL>().
+ if (type_of(m) != NORMAL)
+ return checkers() ? MoveList<EVASIONS>(*this).contains(m)
+ : MoveList<NON_EVASIONS>(*this).contains(m);
-/// Position::gives_check() tests whether a pseudo-legal move gives a check
+ // Is not a promotion, so the promotion piece must be empty
+ assert(promotion_type(m) - KNIGHT == NO_PIECE_TYPE);
-bool Position::gives_check(Move m) const {
+ // 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;
- assert(is_ok(m));
- assert(color_of(moved_piece(m)) == sideToMove);
-
- Square from = from_sq(m);
- Square to = to_sq(m);
-
- // Is there a direct check?
- if (st->checkSquares[type_of(piece_on(from))] & to)
- return true;
-
- // Is there a discovered check?
- if ( (st->blockersForKing[~sideToMove] & from)
- && !aligned(from, to, square<KING>(~sideToMove)))
- return true;
-
- switch (type_of(m))
- {
- case NORMAL:
- return false;
-
- case PROMOTION:
- return attacks_bb(promotion_type(m), to, pieces() ^ from) & square<KING>(~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 ENPASSANT:
- {
- Square capsq = make_square(file_of(to), rank_of(from));
- Bitboard b = (pieces() ^ from ^ capsq) | to;
-
- return (attacks_bb< ROOK>(square<KING>(~sideToMove), b) & pieces(sideToMove, QUEEN, ROOK))
- | (attacks_bb<BISHOP>(square<KING>(~sideToMove), b) & pieces(sideToMove, QUEEN, BISHOP));
- }
- case CASTLING:
- {
- Square kfrom = from;
- Square rfrom = to; // Castling is encoded as 'King captures the rook'
- Square kto = relative_square(sideToMove, rfrom > kfrom ? SQ_G1 : SQ_C1);
- Square rto = relative_square(sideToMove, rfrom > kfrom ? SQ_F1 : SQ_D1);
-
- return (PseudoAttacks[ROOK][rto] & square<KING>(~sideToMove))
- && (attacks_bb<ROOK>(rto, (pieces() ^ kfrom ^ rfrom) | rto | kto) & square<KING>(~sideToMove));
- }
- default:
- assert(false);
- 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 ((Rank8BB | Rank1BB) & to)
+ return false;
+
+ if (!(pawn_attacks_bb(us, from) & pieces(~us) & to) // Not a capture
+ && !((from + pawn_push(us) == to) && empty(to)) // Not a single push
+ && !((from + 2 * pawn_push(us) == to) // Not a double push
+ && (relative_rank(us, from) == RANK_2) && empty(to) && empty(to - pawn_push(us))))
+ return false;
+ }
+ else if (!(attacks_bb(type_of(pc), from, pieces()) & 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())
+ {
+ 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 interposition or a capture of the checking piece
+ if (!(between_bb(square<KING>(us), lsb(checkers())) & to))
+ return false;
+ }
+ // In case of king moves under check we have to remove the king so as to catch
+ // invalid moves like b1a1 when opposite queen is on c1.
+ else if (attackers_to(to, pieces() ^ from) & pieces(~us))
+ return false;
+ }
+
+ return true;
}
-/// 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.
+// Tests whether a pseudo-legal move gives a check
+bool Position::gives_check(Move m) const {
-void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
+ assert(is_ok(m));
+ assert(color_of(moved_piece(m)) == sideToMove);
- assert(is_ok(m));
- assert(&newSt != st);
-
- thisThread->nodes.fetch_add(1, std::memory_order_relaxed);
- 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(&newSt, st, offsetof(StateInfo, key));
- newSt.previous = st;
- st = &newSt;
-
- // 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;
-
- Color us = sideToMove;
- Color them = ~us;
- Square from = from_sq(m);
- Square to = to_sq(m);
- Piece pc = piece_on(from);
- Piece captured = type_of(m) == ENPASSANT ? make_piece(them, PAWN) : piece_on(to);
-
- assert(color_of(pc) == us);
- assert(captured == NO_PIECE || color_of(captured) == (type_of(m) != CASTLING ? them : us));
- assert(type_of(captured) != KING);
-
- if (type_of(m) == CASTLING)
- {
- assert(pc == make_piece(us, KING));
- assert(captured == make_piece(us, ROOK));
-
- Square rfrom, rto;
- do_castling<true>(us, from, to, rfrom, rto);
-
- k ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto];
- captured = NO_PIECE;
- }
-
- if (captured)
- {
- Square capsq = to;
-
- // If the captured piece is a pawn, update pawn hash key, otherwise
- // update non-pawn material.
- if (type_of(captured) == PAWN)
- {
- if (type_of(m) == ENPASSANT)
- {
- capsq -= pawn_push(us);
-
- assert(pc == make_piece(us, PAWN));
- assert(to == st->epSquare);
- assert(relative_rank(us, to) == RANK_6);
- assert(piece_on(to) == NO_PIECE);
- assert(piece_on(capsq) == make_piece(them, PAWN));
-
- board[capsq] = NO_PIECE; // Not done by remove_piece()
- }
-
- st->pawnKey ^= Zobrist::psq[captured][capsq];
- }
- else
- st->nonPawnMaterial[them] -= PieceValue[MG][captured];
-
- // Update board and piece lists
- remove_piece(captured, capsq);
-
- // Update material hash key and prefetch access to materialTable
- k ^= Zobrist::psq[captured][capsq];
- st->materialKey ^= Zobrist::psq[captured][pieceCount[captured]];
- prefetch(thisThread->materialTable[st->materialKey]);
-
- // Reset rule 50 counter
- st->rule50 = 0;
- }
-
- // Update hash key
- k ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to];
-
- // Reset en passant square
- if (st->epSquare != SQ_NONE)
- {
- k ^= Zobrist::enpassant[file_of(st->epSquare)];
- st->epSquare = SQ_NONE;
- }
-
- // Update castling rights if needed
- if (st->castlingRights && (castlingRightsMask[from] | castlingRightsMask[to]))
- {
- int cr = castlingRightsMask[from] | castlingRightsMask[to];
- k ^= Zobrist::castling[st->castlingRights & cr];
- st->castlingRights &= ~cr;
- }
-
- // Move the piece. The tricky Chess960 castling is handled earlier
- if (type_of(m) != CASTLING)
- move_piece(pc, from, to);
-
- // If the moving piece is a pawn do some special extra work
- if (type_of(pc) == PAWN)
- {
- // Set en-passant square if the moved pawn can be captured
- if ( (int(to) ^ int(from)) == 16
- && (attacks_from<PAWN>(to - pawn_push(us), us) & pieces(them, PAWN)))
- {
- st->epSquare = to - pawn_push(us);
- k ^= Zobrist::enpassant[file_of(st->epSquare)];
- }
-
- else if (type_of(m) == PROMOTION)
- {
- Piece promotion = make_piece(us, promotion_type(m));
-
- assert(relative_rank(us, to) == RANK_8);
- assert(type_of(promotion) >= KNIGHT && type_of(promotion) <= QUEEN);
-
- remove_piece(pc, to);
- put_piece(promotion, to);
-
- // 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];
- }
-
- // Update pawn hash key
- st->pawnKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to];
-
- // Reset rule 50 draw counter
- st->rule50 = 0;
- }
-
- // Set capture piece
- st->capturedPiece = captured;
-
- // Update the key with the final value
- st->key = k;
-
- // Calculate checkers bitboard (if move gives check)
- st->checkersBB = givesCheck ? attackers_to(square<KING>(them)) & pieces(us) : 0;
-
- sideToMove = ~sideToMove;
-
- // 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 = 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());
+ Square from = from_sq(m);
+ Square to = to_sq(m);
+
+ // Is there a direct check?
+ if (check_squares(type_of(piece_on(from))) & to)
+ return true;
+
+ // Is there a discovered check?
+ if (blockers_for_king(~sideToMove) & from)
+ return !aligned(from, to, square<KING>(~sideToMove)) || type_of(m) == CASTLING;
+
+ switch (type_of(m))
+ {
+ case NORMAL :
+ return false;
+
+ case PROMOTION :
+ return attacks_bb(promotion_type(m), to, pieces() ^ from) & square<KING>(~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 = make_square(file_of(to), rank_of(from));
+ Bitboard b = (pieces() ^ from ^ capsq) | to;
+
+ return (attacks_bb<ROOK>(square<KING>(~sideToMove), b) & pieces(sideToMove, QUEEN, ROOK))
+ | (attacks_bb<BISHOP>(square<KING>(~sideToMove), b)
+ & pieces(sideToMove, QUEEN, BISHOP));
+ }
+ default : //CASTLING
+ {
+ // Castling is encoded as 'king captures the rook'
+ Square rto = relative_square(sideToMove, to > from ? SQ_F1 : SQ_D1);
+
+ return check_squares(ROOK) & rto;
+ }
+ }
}
-/// 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.
+// 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);
+
+ thisThread->nodes.fetch_add(1, std::memory_order_relaxed);
+ 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(&newSt, st, offsetof(StateInfo, key));
+ newSt.previous = st;
+ st = &newSt;
+
+ // 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;
+
+ // 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 = piece_on(from);
+ Piece captured = type_of(m) == EN_PASSANT ? make_piece(them, PAWN) : piece_on(to);
+
+ assert(color_of(pc) == us);
+ assert(captured == NO_PIECE || color_of(captured) == (type_of(m) != CASTLING ? them : us));
+ assert(type_of(captured) != KING);
+
+ if (type_of(m) == CASTLING)
+ {
+ assert(pc == make_piece(us, KING));
+ assert(captured == make_piece(us, ROOK));
+ Square rfrom, rto;
+ do_castling<true>(us, from, to, rfrom, rto);
+
+ k ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto];
+ captured = NO_PIECE;
+ }
+
+ if (captured)
+ {
+ Square capsq = to;
+
+ // If the captured piece is a pawn, update pawn hash key, otherwise
+ // update non-pawn material.
+ if (type_of(captured) == PAWN)
+ {
+ if (type_of(m) == EN_PASSANT)
+ {
+ capsq -= pawn_push(us);
+
+ assert(pc == make_piece(us, PAWN));
+ assert(to == st->epSquare);
+ assert(relative_rank(us, to) == RANK_6);
+ assert(piece_on(to) == NO_PIECE);
+ assert(piece_on(capsq) == make_piece(them, PAWN));
+ }
+
+ st->pawnKey ^= Zobrist::psq[captured][capsq];
+ }
+ else
+ st->nonPawnMaterial[them] -= PieceValue[captured];
+
+ 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
+ remove_piece(capsq);
+
+ // Update material hash key and prefetch access to materialTable
+ k ^= Zobrist::psq[captured][capsq];
+ st->materialKey ^= Zobrist::psq[captured][pieceCount[captured]];
+
+ // Reset rule 50 counter
+ st->rule50 = 0;
+ }
+
+ // Update hash key
+ k ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to];
+
+ // Reset en passant square
+ if (st->epSquare != SQ_NONE)
+ {
+ k ^= Zobrist::enpassant[file_of(st->epSquare)];
+ st->epSquare = SQ_NONE;
+ }
+
+ // Update castling rights if needed
+ if (st->castlingRights && (castlingRightsMask[from] | castlingRightsMask[to]))
+ {
+ k ^= Zobrist::castling[st->castlingRights];
+ st->castlingRights &= ~(castlingRightsMask[from] | castlingRightsMask[to]);
+ k ^= Zobrist::castling[st->castlingRights];
+ }
+
+ // Move the piece. The tricky Chess960 castling is handled earlier
+ if (type_of(m) != CASTLING)
+ {
+ 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)
+ {
+ // Set en passant square if the moved pawn can be captured
+ if ((int(to) ^ int(from)) == 16
+ && (pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN)))
+ {
+ st->epSquare = to - pawn_push(us);
+ k ^= Zobrist::enpassant[file_of(st->epSquare)];
+ }
+
+ else if (type_of(m) == PROMOTION)
+ {
+ Piece promotion = make_piece(us, promotion_type(m));
+
+ assert(relative_rank(us, to) == RANK_8);
+ assert(type_of(promotion) >= KNIGHT && type_of(promotion) <= QUEEN);
+
+ remove_piece(to);
+ put_piece(promotion, to);
+
+ // Promoting pawn to SQ_NONE, promoted piece from SQ_NONE
+ dp.to[0] = SQ_NONE;
+ dp.piece[dp.dirty_num] = promotion;
+ dp.from[dp.dirty_num] = SQ_NONE;
+ dp.to[dp.dirty_num] = to;
+ dp.dirty_num++;
+
+ // Update hash keys
+ k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[promotion][to];
+ st->pawnKey ^= Zobrist::psq[pc][to];
+ st->materialKey ^=
+ Zobrist::psq[promotion][pieceCount[promotion] - 1] ^ Zobrist::psq[pc][pieceCount[pc]];
+
+ // Update material
+ st->nonPawnMaterial[us] += PieceValue[promotion];
+ }
+
+ // Update pawn hash key
+ st->pawnKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to];
+
+ // Reset rule 50 draw counter
+ st->rule50 = 0;
+ }
+
+ // Set capture piece
+ st->capturedPiece = captured;
+
+ // Update the key with the final value
+ st->key = k;
+
+ // Calculate checkers bitboard (if move gives check)
+ st->checkersBB = givesCheck ? attackers_to(square<KING>(them)) & pieces(us) : 0;
+
+ sideToMove = ~sideToMove;
+
+ // Update king attacks used for fast check detection
+ set_check_info();
+
+ // 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 = 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());
+}
+
+
+// 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(empty(from) || type_of(m) == CASTLING);
- assert(type_of(st->capturedPiece) != KING);
-
- if (type_of(m) == PROMOTION)
- {
- assert(relative_rank(us, to) == RANK_8);
- assert(type_of(pc) == promotion_type(m));
- assert(type_of(pc) >= KNIGHT && type_of(pc) <= QUEEN);
-
- remove_piece(pc, to);
- pc = make_piece(us, PAWN);
- put_piece(pc, to);
- }
-
- if (type_of(m) == CASTLING)
- {
- Square rfrom, rto;
- do_castling<false>(us, from, to, rfrom, rto);
- }
- else
- {
- move_piece(pc, to, from); // Put the piece back at the source square
-
- if (st->capturedPiece)
- {
- Square capsq = to;
-
- if (type_of(m) == ENPASSANT)
- {
- capsq -= pawn_push(us);
-
- assert(type_of(pc) == PAWN);
- assert(to == st->previous->epSquare);
- assert(relative_rank(us, to) == RANK_6);
- assert(piece_on(capsq) == NO_PIECE);
- assert(st->capturedPiece == make_piece(~us, PAWN));
- }
-
- put_piece(st->capturedPiece, capsq); // Restore the captured piece
- }
- }
-
- // Finally point our state pointer back to the previous state
- st = st->previous;
- --gamePly;
-
- assert(pos_is_ok());
+ 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(empty(from) || type_of(m) == CASTLING);
+ assert(type_of(st->capturedPiece) != KING);
+
+ if (type_of(m) == PROMOTION)
+ {
+ assert(relative_rank(us, to) == RANK_8);
+ assert(type_of(pc) == promotion_type(m));
+ assert(type_of(pc) >= KNIGHT && type_of(pc) <= QUEEN);
+
+ remove_piece(to);
+ pc = make_piece(us, PAWN);
+ put_piece(pc, to);
+ }
+
+ if (type_of(m) == CASTLING)
+ {
+ Square rfrom, rto;
+ do_castling<false>(us, from, to, rfrom, rto);
+ }
+ 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 -= pawn_push(us);
+
+ assert(type_of(pc) == PAWN);
+ assert(to == st->previous->epSquare);
+ assert(relative_rank(us, to) == RANK_6);
+ assert(piece_on(capsq) == NO_PIECE);
+ assert(st->capturedPiece == make_piece(~us, PAWN));
+ }
+
+ put_piece(st->capturedPiece, capsq); // Restore the captured piece
+ }
+ }
+
+ // 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.
+// Helper used to do/undo a castling move. This is a bit
+// tricky in Chess960 where from/to squares can overlap.
template<bool Do>
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"
- rto = relative_square(us, kingSide ? SQ_F1 : SQ_D1);
- to = relative_square(us, kingSide ? SQ_G1 : SQ_C1);
-
- // Remove both pieces first since squares could overlap in Chess960
- remove_piece(make_piece(us, KING), Do ? from : to);
- remove_piece(make_piece(us, ROOK), Do ? rfrom : rto);
- board[Do ? from : to] = board[Do ? rfrom : rto] = NO_PIECE; // Since remove_piece doesn't do it for us
- put_piece(make_piece(us, KING), Do ? to : from);
- put_piece(make_piece(us, ROOK), Do ? rto : rfrom);
-}
+ bool kingSide = to > from;
+ rfrom = to; // Castling is encoded as "king captures friendly rook"
+ rto = relative_square(us, kingSide ? SQ_F1 : SQ_D1);
+ to = relative_square(us, kingSide ? SQ_G1 : SQ_C1);
+ if (Do)
+ {
+ auto& dp = st->dirtyPiece;
+ dp.piece[0] = make_piece(us, KING);
+ dp.from[0] = from;
+ dp.to[0] = to;
+ dp.piece[1] = make_piece(us, ROOK);
+ 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; // remove_piece does not do this for us
+ put_piece(make_piece(us, KING), Do ? to : from);
+ put_piece(make_piece(us, ROOK), Do ? rto : rfrom);
+}
-/// Position::do(undo)_null_move() is used to do(undo) a "null move": it flips
-/// the side to move without executing any move on the board.
+// 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);
+ assert(!checkers());
+ assert(&newSt != st);
+
+ std::memcpy(&newSt, st, offsetof(StateInfo, accumulator));
- std::memcpy(&newSt, st, sizeof(StateInfo));
- newSt.previous = st;
- st = &newSt;
+ newSt.previous = st;
+ st = &newSt;
- if (st->epSquare != SQ_NONE)
- {
- st->key ^= Zobrist::enpassant[file_of(st->epSquare)];
- st->epSquare = SQ_NONE;
- }
+ 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;
+
+ if (st->epSquare != SQ_NONE)
+ {
+ st->key ^= Zobrist::enpassant[file_of(st->epSquare)];
+ st->epSquare = SQ_NONE;
+ }
- st->key ^= Zobrist::side;
- prefetch(TT.first_entry(st->key));
+ st->key ^= Zobrist::side;
+ ++st->rule50;
+ prefetch(TT.first_entry(key()));
- ++st->rule50;
- st->pliesFromNull = 0;
+ st->pliesFromNull = 0;
- sideToMove = ~sideToMove;
+ sideToMove = ~sideToMove;
- set_check_info(st);
+ set_check_info();
- st->repetition = 0;
+ st->repetition = 0;
- assert(pos_is_ok());
+ assert(pos_is_ok());
}
+
+// Must be used to undo a "null move"
void Position::undo_null_move() {
- assert(!checkers());
+ assert(!checkers());
- st = st->previous;
- sideToMove = ~sideToMove;
+ 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.
-
+// 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 = piece_on(from);
- Piece captured = piece_on(to);
- Key k = st->key ^ Zobrist::side;
+ Square from = from_sq(m);
+ Square to = to_sq(m);
+ Piece pc = piece_on(from);
+ Piece captured = piece_on(to);
+ Key k = st->key ^ Zobrist::side;
- if (captured)
- k ^= Zobrist::psq[captured][to];
+ if (captured)
+ k ^= Zobrist::psq[captured][to];
- return k ^ Zobrist::psq[pc][to] ^ Zobrist::psq[pc][from];
-}
+ k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[pc][from];
+ return (captured || type_of(pc) == PAWN) ? k : adjust_key50<true>(k);
+}
-/// 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.
+// Tests if the SEE (Static Exchange Evaluation)
+// 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)
- return VALUE_ZERO >= threshold;
-
- Square from = from_sq(m), to = to_sq(m);
-
- int swap = PieceValue[MG][piece_on(to)] - threshold;
- if (swap < 0)
- return false;
-
- swap = PieceValue[MG][piece_on(from)] - swap;
- if (swap <= 0)
- return true;
-
- Bitboard occupied = pieces() ^ from ^ to;
- Color stm = color_of(piece_on(from));
- Bitboard attackers = attackers_to(to, occupied);
- Bitboard stmAttackers, bb;
- int res = 1;
-
- 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 (except the king) as long as
- // there are pinners on their original square.
- if (st->pinners[~stm] & occupied)
- stmAttackers &= ~st->blockersForKing[stm];
-
- 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 ^= lsb(bb);
- attackers |= attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN);
- }
-
- else if ((bb = stmAttackers & pieces(KNIGHT)))
- {
- if ((swap = KnightValueMg - swap) < res)
- break;
-
- occupied ^= lsb(bb);
- }
-
- else if ((bb = stmAttackers & pieces(BISHOP)))
- {
- if ((swap = BishopValueMg - swap) < res)
- break;
-
- occupied ^= lsb(bb);
- attackers |= attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN);
- }
-
- else if ((bb = stmAttackers & pieces(ROOK)))
- {
- if ((swap = RookValueMg - swap) < res)
- break;
-
- occupied ^= lsb(bb);
- attackers |= attacks_bb<ROOK>(to, occupied) & pieces(ROOK, QUEEN);
- }
-
- else if ((bb = stmAttackers & pieces(QUEEN)))
- {
- if ((swap = QueenValueMg - swap) < res)
- break;
-
- occupied ^= lsb(bb);
- attackers |= (attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN))
- | (attacks_bb<ROOK >(to, occupied) & pieces(ROOK , QUEEN));
- }
-
- 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);
-}
+ assert(is_ok(m));
-/// Position::is_draw() tests whether the position is drawn by 50-move rule
-/// or by repetition. It does not detect stalemates.
+ // Only deal with normal moves, assume others pass a simple SEE
+ if (type_of(m) != NORMAL)
+ return VALUE_ZERO >= threshold;
-bool Position::is_draw(int ply) const {
+ Square from = from_sq(m), to = to_sq(m);
- if (st->rule50 > 99 && (!checkers() || MoveList<LEGAL>(*this).size()))
- return true;
+ int swap = PieceValue[piece_on(to)] - threshold;
+ if (swap < 0)
+ return false;
- // Return a draw score if a position repeats once earlier but strictly
- // after the root, or repeats twice before or at the root.
- if (st->repetition && st->repetition < ply)
- return true;
+ swap = PieceValue[piece_on(from)] - swap;
+ if (swap <= 0)
+ return true;
- return false;
+ assert(color_of(piece_on(from)) == sideToMove);
+ Bitboard occupied = pieces() ^ from ^ to; // xoring to is important for pinned piece logic
+ Color stm = sideToMove;
+ Bitboard attackers = attackers_to(to, occupied);
+ Bitboard stmAttackers, bb;
+ int res = 1;
+
+ 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);
+
+ 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 = PawnValue - swap) < res)
+ break;
+ occupied ^= least_significant_square_bb(bb);
+
+ attackers |= attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN);
+ }
+
+ else if ((bb = stmAttackers & pieces(KNIGHT)))
+ {
+ if ((swap = KnightValue - swap) < res)
+ break;
+ occupied ^= least_significant_square_bb(bb);
+ }
+
+ else if ((bb = stmAttackers & pieces(BISHOP)))
+ {
+ if ((swap = BishopValue - swap) < res)
+ break;
+ occupied ^= least_significant_square_bb(bb);
+
+ attackers |= attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN);
+ }
+
+ else if ((bb = stmAttackers & pieces(ROOK)))
+ {
+ if ((swap = RookValue - swap) < res)
+ break;
+ occupied ^= least_significant_square_bb(bb);
+
+ attackers |= attacks_bb<ROOK>(to, occupied) & pieces(ROOK, QUEEN);
+ }
+
+ else if ((bb = stmAttackers & pieces(QUEEN)))
+ {
+ if ((swap = QueenValue - swap) < res)
+ break;
+ occupied ^= least_significant_square_bb(bb);
+
+ attackers |= (attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN))
+ | (attacks_bb<ROOK>(to, occupied) & pieces(ROOK, QUEEN));
+ }
+
+ else // KING
+ // If we "capture" with the king but the opponent still has attackers,
+ // reverse the result.
+ return (attackers & ~pieces(stm)) ? res ^ 1 : res;
+ }
+
+ return bool(res);
}
+// Tests whether the position is drawn by 50-move rule
+// or by repetition. It does not detect stalemates.
+bool Position::is_draw(int ply) const {
-// Position::has_repeated() tests whether there has been at least one repetition
-// of positions since the last capture or pawn move.
+ if (st->rule50 > 99 && (!checkers() || MoveList<LEGAL>(*this).size()))
+ return true;
+ // Return a draw score if a position repeats once earlier but strictly
+ // after the root, or repeats twice before or at the root.
+ return st->repetition && st->repetition < ply;
+}
+
+
+// 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 = std::min(st->rule50, st->pliesFromNull);
+ int end = std::min(st->rule50, st->pliesFromNull);
while (end-- >= 4)
{
if (stc->repetition)
}
-/// 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.
-
+// 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 j;
- int end = std::min(st->rule50, st->pliesFromNull);
+ int end = std::min(st->rule50, st->pliesFromNull);
- if (end < 3)
- return false;
+ if (end < 3)
+ 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) & 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;
-}
+ 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::flip() flips position with the white and black sides reversed. This
-/// is only useful for debugging e.g. for finding evaluation symmetry bugs.
+// 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());
+ string f, token;
+ std::stringstream ss(fen());
- for (Rank r = RANK_8; r >= RANK_1; --r) // Piece placement
- {
- std::getline(ss, token, r > RANK_1 ? '/' : ' ');
- f.insert(0, token + (f.empty() ? " " : "/"));
- }
+ for (Rank r = RANK_8; r >= RANK_1; --r) // Piece placement
+ {
+ std::getline(ss, token, r > RANK_1 ? '/' : ' ');
+ f.insert(0, token + (f.empty() ? " " : "/"));
+ }
- ss >> token; // Active color
- f += (token == "w" ? "B " : "W "); // Will be lowercased later
+ ss >> token; // Active color
+ f += (token == "w" ? "B " : "W "); // Will be lowercased later
- ss >> token; // Castling availability
- f += token + " ";
+ ss >> token; // Castling availability
+ f += token + " ";
- std::transform(f.begin(), f.end(), f.begin(),
- [](char c) { return char(islower(c) ? toupper(c) : tolower(c)); });
+ 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"));
+ 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;
+ std::getline(ss, token); // Half and full moves
+ f += token;
- set(f, is_chess960(), st, this_thread());
+ set(f, is_chess960(), st, this_thread());
- assert(pos_is_ok());
+ 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.
-
+// Performs some consistency checks for the position object
+// and raise an assert 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)
- || piece_on(square<KING>(WHITE)) != W_KING
- || piece_on(square<KING>(BLACK)) != B_KING
- || ( ep_square() != SQ_NONE
- && relative_rank(sideToMove, ep_square()) != RANK_6))
- assert(0 && "pos_is_ok: Default");
-
- if (Fast)
- return true;
-
- if ( pieceCount[W_KING] != 1
- || pieceCount[B_KING] != 1
- || attackers_to(square<KING>(~sideToMove)) & pieces(sideToMove))
- assert(0 && "pos_is_ok: Kings");
-
- if ( (pieces(PAWN) & (Rank1BB | Rank8BB))
- || pieceCount[W_PAWN] > 8
- || pieceCount[B_PAWN] > 8)
- assert(0 && "pos_is_ok: Pawns");
-
- if ( (pieces(WHITE) & pieces(BLACK))
- || (pieces(WHITE) | pieces(BLACK)) != pieces()
- || popcount(pieces(WHITE)) > 16
- || popcount(pieces(BLACK)) > 16)
- 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;
- set_state(&si);
- if (std::memcmp(&si, st, sizeof(StateInfo)))
- assert(0 && "pos_is_ok: State");
-
- for (Piece pc : Pieces)
- {
- if ( pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc)))
- || pieceCount[pc] != std::count(board, board + SQUARE_NB, pc))
- assert(0 && "pos_is_ok: Pieces");
-
- for (int i = 0; i < pieceCount[pc]; ++i)
- if (board[pieceList[pc][i]] != pc || index[pieceList[pc][i]] != i)
- assert(0 && "pos_is_ok: Index");
- }
-
- for (Color c : { WHITE, BLACK })
- for (CastlingRights cr : {c & KING_SIDE, c & QUEEN_SIDE})
- {
- if (!can_castle(cr))
- continue;
-
- if ( piece_on(castlingRookSquare[cr]) != make_piece(c, ROOK)
- || castlingRightsMask[castlingRookSquare[cr]] != cr
- || (castlingRightsMask[square<KING>(c)] & cr) != cr)
- assert(0 && "pos_is_ok: Castling");
- }
-
- return true;
+ constexpr bool Fast = true; // Quick (default) or full check?
+
+ if ((sideToMove != WHITE && sideToMove != BLACK) || piece_on(square<KING>(WHITE)) != W_KING
+ || piece_on(square<KING>(BLACK)) != B_KING
+ || (ep_square() != SQ_NONE && relative_rank(sideToMove, ep_square()) != RANK_6))
+ assert(0 && "pos_is_ok: Default");
+
+ if (Fast)
+ return true;
+
+ if (pieceCount[W_KING] != 1 || pieceCount[B_KING] != 1
+ || attackers_to(square<KING>(~sideToMove)) & pieces(sideToMove))
+ assert(0 && "pos_is_ok: Kings");
+
+ if ((pieces(PAWN) & (Rank1BB | Rank8BB)) || pieceCount[W_PAWN] > 8 || pieceCount[B_PAWN] > 8)
+ assert(0 && "pos_is_ok: Pawns");
+
+ if ((pieces(WHITE) & pieces(BLACK)) || (pieces(WHITE) | pieces(BLACK)) != pieces()
+ || popcount(pieces(WHITE)) > 16 || popcount(pieces(BLACK)) > 16)
+ 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");
+
+
+ for (Piece pc : Pieces)
+ if (pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc)))
+ || 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 (piece_on(castlingRookSquare[cr]) != make_piece(c, ROOK)
+ || castlingRightsMask[castlingRookSquare[cr]] != cr
+ || (castlingRightsMask[square<KING>(c)] & cr) != cr)
+ assert(0 && "pos_is_ok: Castling");
+ }
+
+ return true;
}
+
+} // namespace Stockfish
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+ Copyright (C) 2004-2023 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
#include <cassert>
#include <deque>
-#include <memory> // For std::unique_ptr
+#include <iosfwd>
+#include <memory>
#include <string>
#include "bitboard.h"
+#include "nnue/nnue_accumulator.h"
#include "types.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.
+// 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;
- Square epSquare;
-
- // Not copied when making a move (will be recomputed anyhow)
- Key key;
- Bitboard checkersBB;
- Piece capturedPiece;
- StateInfo* previous;
- Bitboard blockersForKing[COLOR_NB];
- Bitboard pinners[COLOR_NB];
- Bitboard checkSquares[PIECE_TYPE_NB];
- int repetition;
+ // Copied when making a move
+ Key materialKey;
+ Key pawnKey;
+ Value nonPawnMaterial[COLOR_NB];
+ int castlingRights;
+ int rule50;
+ int pliesFromNull;
+ Square epSquare;
+
+ // Not copied when making a move (will be recomputed anyhow)
+ Key key;
+ Bitboard checkersBB;
+ StateInfo* previous;
+ Bitboard blockersForKing[COLOR_NB];
+ Bitboard pinners[COLOR_NB];
+ Bitboard checkSquares[PIECE_TYPE_NB];
+ Piece capturedPiece;
+ 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<std::deque<StateInfo>> StateListPtr;
+// 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.
+using StateListPtr = std::unique_ptr<std::deque<StateInfo>>;
-/// 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.
+
+// 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 std::string& fenStr, bool isChess960, StateInfo* si, Thread* th);
- Position& set(const std::string& code, Color c, StateInfo* si);
- const std::string fen() const;
-
- // Position representation
- Bitboard pieces() const;
- Bitboard pieces(PieceType pt) 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;
- Piece piece_on(Square s) const;
- Square ep_square() const;
- bool empty(Square s) const;
- template<PieceType Pt> int count(Color c) const;
- template<PieceType Pt> int count() const;
- template<PieceType Pt> const Square* squares(Color c) const;
- template<PieceType Pt> Square square(Color c) const;
- bool is_on_semiopen_file(Color c, Square s) const;
-
- // Castling
- int 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;
- bool is_discovery_check_on_king(Color c, Move m) const;
-
- // Attacks to/from a given square
- Bitboard attackers_to(Square s) const;
- Bitboard attackers_to(Square s, Bitboard occupied) const;
- Bitboard attacks_from(PieceType pt, Square s) const;
- template<PieceType> Bitboard attacks_from(Square s) const;
- template<PieceType> Bitboard attacks_from(Square s, Color c) const;
- Bitboard slider_blockers(Bitboard sliders, Square s, Bitboard& pinners) const;
-
- // Properties of moves
- bool legal(Move m) const;
- bool pseudo_legal(const Move m) const;
- bool capture(Move m) const;
- bool capture_or_promotion(Move m) const;
- bool gives_check(Move m) const;
- bool advanced_pawn_push(Move m) const;
- Piece moved_piece(Move m) const;
- Piece captured_piece() const;
-
- // Piece specific
- bool pawn_passed(Color c, Square s) const;
- bool opposite_bishops() 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
- 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() 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_draw(int ply) const;
- bool has_game_cycle(int ply) const;
- bool has_repeated() const;
- int rule50_count() const;
- Score psq_score() const;
- Value non_pawn_material(Color c) const;
- Value non_pawn_material() const;
-
- // Position consistency check, for debugging
- bool pos_is_ok() const;
- void flip();
-
-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 put_piece(Piece pc, Square s);
- void remove_piece(Piece pc, Square s);
- void move_piece(Piece pc, Square from, Square to);
- template<bool Do>
- void do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto);
-
- // Data members
- Piece board[SQUARE_NB];
- Bitboard byTypeBB[PIECE_TYPE_NB];
- Bitboard byColorBB[COLOR_NB];
- int pieceCount[PIECE_NB];
- Square pieceList[PIECE_NB][16];
- int index[SQUARE_NB];
- int castlingRightsMask[SQUARE_NB];
- Square castlingRookSquare[CASTLING_RIGHT_NB];
- Bitboard castlingPath[CASTLING_RIGHT_NB];
- int gamePly;
- Color sideToMove;
- Score psq;
- Thread* thisThread;
- StateInfo* st;
- bool chess960;
+ public:
+ static void init();
+
+ Position() = default;
+ Position(const Position&) = delete;
+ Position& operator=(const Position&) = delete;
+
+ // FEN string input/output
+ Position& set(const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th);
+ Position& set(const std::string& code, Color c, StateInfo* si);
+ std::string fen() const;
+
+ // Position representation
+ Bitboard pieces(PieceType pt = ALL_PIECES) const;
+ template<typename... PieceTypes>
+ Bitboard pieces(PieceType pt, PieceTypes... pts) const;
+ Bitboard pieces(Color c) const;
+ template<typename... PieceTypes>
+ Bitboard pieces(Color c, PieceTypes... pts) const;
+ Piece piece_on(Square s) const;
+ Square ep_square() const;
+ bool empty(Square s) const;
+ template<PieceType Pt>
+ int count(Color c) const;
+ template<PieceType Pt>
+ int count() const;
+ template<PieceType Pt>
+ Square square(Color c) 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;
+
+ // Attacks to/from a given square
+ Bitboard attackers_to(Square s) const;
+ Bitboard attackers_to(Square s, Bitboard occupied) const;
+ void update_slider_blockers(Color c) const;
+ template<PieceType Pt>
+ Bitboard attacks_by(Color c) const;
+
+ // Properties of moves
+ bool legal(Move m) const;
+ bool pseudo_legal(const Move m) const;
+ bool capture(Move m) const;
+ bool capture_stage(Move m) const;
+ bool gives_check(Move m) const;
+ Piece moved_piece(Move m) const;
+ Piece captured_piece() 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
+ 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() 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_draw(int ply) const;
+ bool has_game_cycle(int ply) const;
+ bool has_repeated() const;
+ int rule50_count() const;
+ Value non_pawn_material(Color c) const;
+ Value non_pawn_material() 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);
+ 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() const;
+ void set_check_info() const;
+
+ // Other helpers
+ void move_piece(Square from, Square to);
+ template<bool Do>
+ void do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto);
+ template<bool AfterMove>
+ Key adjust_key50(Key k) const;
+
+ // Data members
+ Piece board[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;
+ bool chess960;
};
-namespace PSQT {
- extern Score psq[PIECE_NB][SQUARE_NB];
-}
-
-extern std::ostream& operator<<(std::ostream& os, const Position& pos);
+std::ostream& operator<<(std::ostream& os, const Position& pos);
-inline Color Position::side_to_move() const {
- return sideToMove;
-}
-
-inline bool Position::empty(Square s) const {
- return board[s] == NO_PIECE;
-}
+inline Color Position::side_to_move() const { return sideToMove; }
inline Piece Position::piece_on(Square s) const {
- return board[s];
+ assert(is_ok(s));
+ return board[s];
}
-inline Piece Position::moved_piece(Move m) const {
- return board[from_sq(m)];
-}
-
-inline Bitboard Position::pieces() const {
- return byTypeBB[ALL_PIECES];
-}
-
-inline Bitboard Position::pieces(PieceType pt) const {
- return byTypeBB[pt];
-}
-
-inline Bitboard Position::pieces(PieceType pt1, PieceType pt2) const {
- return byTypeBB[pt1] | byTypeBB[pt2];
-}
+inline bool Position::empty(Square s) const { return piece_on(s) == NO_PIECE; }
-inline Bitboard Position::pieces(Color c) const {
- return byColorBB[c];
-}
+inline Piece Position::moved_piece(Move m) const { return piece_on(from_sq(m)); }
-inline Bitboard Position::pieces(Color c, PieceType pt) const {
- return byColorBB[c] & byTypeBB[pt];
-}
+inline Bitboard Position::pieces(PieceType pt) const { return byTypeBB[pt]; }
-inline Bitboard Position::pieces(Color c, PieceType pt1, PieceType pt2) const {
- return byColorBB[c] & (byTypeBB[pt1] | byTypeBB[pt2]);
+template<typename... PieceTypes>
+inline Bitboard Position::pieces(PieceType pt, PieceTypes... pts) const {
+ return pieces(pt) | pieces(pts...);
}
-template<PieceType Pt> inline int Position::count(Color c) const {
- return pieceCount[make_piece(c, Pt)];
-}
+inline Bitboard Position::pieces(Color c) const { return byColorBB[c]; }
-template<PieceType Pt> inline int Position::count() const {
- return pieceCount[make_piece(WHITE, Pt)] + pieceCount[make_piece(BLACK, Pt)];
+template<typename... PieceTypes>
+inline Bitboard Position::pieces(Color c, PieceTypes... pts) const {
+ return pieces(c) & pieces(pts...);
}
-template<PieceType Pt> inline const Square* Position::squares(Color c) const {
- return pieceList[make_piece(c, Pt)];
+template<PieceType Pt>
+inline int Position::count(Color c) const {
+ return pieceCount[make_piece(c, Pt)];
}
-template<PieceType Pt> inline Square Position::square(Color c) const {
- assert(pieceCount[make_piece(c, Pt)] == 1);
- return pieceList[make_piece(c, Pt)][0];
+template<PieceType Pt>
+inline int Position::count() const {
+ return count<Pt>(WHITE) + count<Pt>(BLACK);
}
-inline Square Position::ep_square() const {
- return st->epSquare;
+template<PieceType Pt>
+inline Square Position::square(Color c) const {
+ assert(count<Pt>(c) == 1);
+ return lsb(pieces(c, Pt));
}
-inline bool Position::is_on_semiopen_file(Color c, Square s) const {
- return !(pieces(c, PAWN) & file_bb(s));
-}
+inline Square Position::ep_square() const { return st->epSquare; }
-inline bool Position::can_castle(CastlingRights cr) const {
- return st->castlingRights & cr;
-}
+inline bool Position::can_castle(CastlingRights cr) const { return st->castlingRights & cr; }
-inline int Position::castling_rights(Color c) const {
- return st->castlingRights & (c == WHITE ? WHITE_CASTLING : BLACK_CASTLING);
+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);
+ assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO);
- return byTypeBB[ALL_PIECES] & castlingPath[cr];
+ 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];
-}
+ assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO);
-template<PieceType Pt>
-inline Bitboard Position::attacks_from(Square s) const {
- static_assert(Pt != PAWN, "Pawn attacks need color");
-
- return Pt == BISHOP || Pt == ROOK ? attacks_bb<Pt>(s, byTypeBB[ALL_PIECES])
- : Pt == QUEEN ? attacks_from<ROOK>(s) | attacks_from<BISHOP>(s)
- : PseudoAttacks[Pt][s];
+ return castlingRookSquare[cr];
}
-template<>
-inline Bitboard Position::attacks_from<PAWN>(Square s, Color c) const {
- return PawnAttacks[c][s];
-}
+inline Bitboard Position::attackers_to(Square s) const { return attackers_to(s, pieces()); }
-inline Bitboard Position::attacks_from(PieceType pt, Square s) const {
- return attacks_bb(pt, s, byTypeBB[ALL_PIECES]);
-}
+template<PieceType Pt>
+inline Bitboard Position::attacks_by(Color c) const {
-inline Bitboard Position::attackers_to(Square s) const {
- return attackers_to(s, byTypeBB[ALL_PIECES]);
+ if constexpr (Pt == PAWN)
+ return c == WHITE ? pawn_attacks_bb<WHITE>(pieces(WHITE, PAWN))
+ : pawn_attacks_bb<BLACK>(pieces(BLACK, PAWN));
+ else
+ {
+ Bitboard threats = 0;
+ Bitboard attackers = pieces(c, Pt);
+ while (attackers)
+ threats |= attacks_bb<Pt>(pop_lsb(attackers), pieces());
+ return threats;
+ }
}
-inline Bitboard Position::checkers() const {
- return st->checkersBB;
-}
+inline Bitboard Position::checkers() const { return st->checkersBB; }
-inline Bitboard Position::blockers_for_king(Color c) const {
- return st->blockersForKing[c];
-}
+inline Bitboard Position::blockers_for_king(Color c) const { return st->blockersForKing[c]; }
-inline Bitboard Position::check_squares(PieceType pt) const {
- return st->checkSquares[pt];
-}
+inline Bitboard Position::pinners(Color c) const { return st->pinners[c]; }
-inline bool Position::is_discovery_check_on_king(Color c, Move m) const {
- return st->blockersForKing[c] & from_sq(m);
-}
+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 Key Position::key() const { return adjust_key50<false>(st->key); }
-inline bool Position::advanced_pawn_push(Move m) const {
- return type_of(moved_piece(m)) == PAWN
- && relative_rank(sideToMove, to_sq(m)) > RANK_5;
+template<bool AfterMove>
+inline Key Position::adjust_key50(Key k) const {
+ return st->rule50 < 14 - AfterMove ? k : k ^ make_key((st->rule50 - (14 - AfterMove)) / 8);
}
-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::pawn_key() const { return st->pawnKey; }
-inline Key Position::key() const {
- return st->key;
-}
+inline Key Position::material_key() const { return st->materialKey; }
-inline Key Position::pawn_key() const {
- return st->pawnKey;
-}
+inline Value Position::non_pawn_material(Color c) const { return st->nonPawnMaterial[c]; }
-inline Key Position::material_key() const {
- return st->materialKey;
+inline Value Position::non_pawn_material() const {
+ return non_pawn_material(WHITE) + non_pawn_material(BLACK);
}
-inline Score Position::psq_score() const {
- return psq;
-}
+inline int Position::game_ply() const { return gamePly; }
-inline Value Position::non_pawn_material(Color c) const {
- return st->nonPawnMaterial[c];
-}
+inline int Position::rule50_count() const { return st->rule50; }
-inline Value Position::non_pawn_material() const {
- return st->nonPawnMaterial[WHITE] + st->nonPawnMaterial[BLACK];
-}
+inline bool Position::is_chess960() const { return chess960; }
-inline int Position::game_ply() const {
- return gamePly;
+inline bool Position::capture(Move m) const {
+ assert(is_ok(m));
+ return (!empty(to_sq(m)) && type_of(m) != CASTLING) || type_of(m) == EN_PASSANT;
}
-inline int Position::rule50_count() const {
- return st->rule50;
+// Returns true if a move is generated from the capture stage, having also
+// queen promotions covered, i.e. consistency with the capture stage move generation
+// is needed to avoid the generation of duplicate moves.
+inline bool Position::capture_stage(Move m) const {
+ assert(is_ok(m));
+ return capture(m) || promotion_type(m) == QUEEN;
}
-inline bool Position::opposite_bishops() const {
- return pieceCount[W_BISHOP] == 1
- && pieceCount[B_BISHOP] == 1
- && opposite_colors(square<BISHOP>(WHITE), square<BISHOP>(BLACK));
-}
+inline Piece Position::captured_piece() const { return st->capturedPiece; }
-inline bool Position::is_chess960() const {
- return chess960;
-}
+inline Thread* Position::this_thread() const { return thisThread; }
-inline bool Position::capture_or_promotion(Move m) const {
- assert(is_ok(m));
- return type_of(m) != NORMAL ? type_of(m) != CASTLING : !empty(to_sq(m));
-}
+inline void Position::put_piece(Piece pc, Square s) {
-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) || type_of(m) == ENPASSANT;
+ 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)]++;
}
-inline Piece Position::captured_piece() const {
- return st->capturedPiece;
-}
+inline void Position::remove_piece(Square s) {
-inline Thread* Position::this_thread() const {
- return thisThread;
+ 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)]--;
}
-inline void Position::put_piece(Piece pc, Square s) {
+inline void Position::move_piece(Square from, Square to) {
- board[s] = pc;
- byTypeBB[ALL_PIECES] |= s;
- byTypeBB[type_of(pc)] |= s;
- byColorBB[color_of(pc)] |= s;
- index[s] = pieceCount[pc]++;
- pieceList[pc][index[s]] = s;
- pieceCount[make_piece(color_of(pc), ALL_PIECES)]++;
- psq += PSQT::psq[pc][s];
+ Piece pc = board[from];
+ Bitboard fromTo = from | to;
+ byTypeBB[ALL_PIECES] ^= fromTo;
+ byTypeBB[type_of(pc)] ^= fromTo;
+ byColorBB[color_of(pc)] ^= fromTo;
+ board[from] = NO_PIECE;
+ board[to] = pc;
}
-inline void Position::remove_piece(Piece pc, Square s) {
-
- // WARNING: This is not a reversible operation. If we remove a piece in
- // do_move() and then replace it in undo_move() we will put it at the end of
- // the list and not in its original place, it means index[] and pieceList[]
- // are not invariant to a do_move() + undo_move() sequence.
- byTypeBB[ALL_PIECES] ^= s;
- byTypeBB[type_of(pc)] ^= s;
- byColorBB[color_of(pc)] ^= s;
- /* board[s] = NO_PIECE; Not needed, overwritten by the capturing one */
- Square lastSquare = pieceList[pc][--pieceCount[pc]];
- index[lastSquare] = index[s];
- pieceList[pc][index[lastSquare]] = lastSquare;
- pieceList[pc][pieceCount[pc]] = SQ_NONE;
- pieceCount[make_piece(color_of(pc), ALL_PIECES)]--;
- psq -= PSQT::psq[pc][s];
-}
+inline void Position::do_move(Move m, StateInfo& newSt) { do_move(m, newSt, gives_check(m)); }
-inline void Position::move_piece(Piece pc, Square from, Square to) {
-
- // index[from] is not updated and becomes stale. This works as long as index[]
- // is accessed just by known occupied squares.
- Bitboard fromTo = from | to;
- byTypeBB[ALL_PIECES] ^= fromTo;
- byTypeBB[type_of(pc)] ^= fromTo;
- byColorBB[color_of(pc)] ^= fromTo;
- board[from] = NO_PIECE;
- board[to] = pc;
- index[to] = index[from];
- pieceList[pc][index[to]] = to;
- psq += PSQT::psq[pc][to] - PSQT::psq[pc][from];
-}
+inline StateInfo* Position::state() const { return st; }
-inline void Position::do_move(Move m, StateInfo& newSt) {
- do_move(m, newSt, gives_check(m));
-}
+} // namespace Stockfish
-#endif // #ifndef POSITION_H_INCLUDED
+#endif // #ifndef POSITION_H_INCLUDED
+++ /dev/null
-/*
- Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
-
- 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 <http://www.gnu.org/licenses/>.
-*/
-
-#include <algorithm>
-
-#include "types.h"
-
-Value PieceValue[PHASE_NB][PIECE_NB] = {
- { VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg },
- { VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg }
-};
-
-namespace PSQT {
-
-#define S(mg, eg) make_score(mg, eg)
-
-// Bonus[PieceType][Square / 2] contains Piece-Square scores. For each piece
-// type on a given square a (middlegame, endgame) score pair is assigned. Table
-// is defined for files A..D and white side: it is symmetric for black side and
-// second half of the files.
-constexpr Score Bonus[][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(-53,-57), S( -5,-30), S( -8,-37), S(-23,-12) },
- { S(-15,-37), S( 8,-13), S( 19,-17), S( 4, 1) },
- { S( -7,-16), S( 21, -1), S( -5, -2), S( 17, 10) },
- { S( -5,-20), S( 11, -6), S( 25, 0), S( 39, 17) },
- { S(-12,-17), S( 29, -1), S( 22,-14), S( 31, 15) },
- { S(-16,-30), S( 6, 6), S( 1, 4), S( 11, 6) },
- { S(-17,-31), S(-14,-20), S( 5, -1), S( 0, 1) },
- { S(-48,-46), S( 1,-42), S(-14,-37), S(-23,-24) }
- },
- { // 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,-55), 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,-12), S( 8, 1) },
- { S(-5,-50), S( 6,-27), S(10,-24), S( 8, -8) },
- { S(-2,-75), S(-2,-52), S( 1,-43), S(-2,-36) }
- },
- { // King
- { 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( 3,-10), S( 3, -6), S( 10, 10), S( 19, 0), S( 16, 14), S( 19, 7), S( 7, -5), S( -5,-19) },
- { S( -9,-10), S(-15,-10), S( 11,-10), S( 15, 4), S( 32, 4), S( 22, 3), S( 5, -6), S(-22, -4) },
- { S( -8, 6), S(-23, -2), S( 6, -8), S( 20, -4), S( 40,-13), S( 17,-12), S( 4,-10), S(-12, -9) },
- { S( 13, 9), S( 0, 4), S(-13, 3), S( 1,-12), S( 11,-12), S( -2, -6), S(-13, 13), S( 5, 8) },
- { S( -5, 28), S(-12, 20), S( -7, 21), S( 22, 28), S( -8, 30), S( -5, 7), S(-15, 6), S(-18, 13) },
- { S( -7, 0), S( 7,-11), S( -3, 12), S(-13, 21), S( 5, 25), S(-16, 19), S( 10, 4), S( -8, 7) }
- };
-
-#undef S
-
-Score psq[PIECE_NB][SQUARE_NB];
-
-// init() initializes piece-square tables: the white halves of the tables are
-// copied from Bonus[] 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() {
-
- for (Piece pc = W_PAWN; pc <= W_KING; ++pc)
- {
- PieceValue[MG][~pc] = PieceValue[MG][pc];
- PieceValue[EG][~pc] = PieceValue[EG][pc];
-
- Score score = make_score(PieceValue[MG][pc], PieceValue[EG][pc]);
-
- for (Square s = SQ_A1; s <= SQ_H8; ++s)
- {
- File f = map_to_queenside(file_of(s));
- psq[ pc][ s] = score + (type_of(pc) == PAWN ? PBonus[rank_of(s)][file_of(s)]
- : Bonus[pc][rank_of(s)][f]);
- psq[~pc][~s] = -psq[pc][s];
- }
- }
-}
-
-} // namespace PSQT
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+ Copyright (C) 2004-2023 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
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "search.h"
+
#include <algorithm>
+#include <array>
+#include <atomic>
#include <cassert>
#include <cmath>
-#include <cstring> // For std::memset
+#include <cstdlib>
+#include <cstring>
+#include <initializer_list>
#include <iostream>
#include <sstream>
+#include <string>
+#include <utility>
+#include "bitboard.h"
#include "evaluate.h"
#include "misc.h"
#include "movegen.h"
#include "movepick.h"
+#include "nnue/evaluate_nnue.h"
+#include "nnue/nnue_common.h"
#include "position.h"
-#include "search.h"
+#include "syzygy/tbprobe.h"
#include "thread.h"
#include "timeman.h"
#include "tt.h"
#include "uci.h"
-#include "syzygy/tbprobe.h"
+
+namespace Stockfish {
namespace Search {
- LimitsType Limits;
+LimitsType Limits;
}
namespace Tablebases {
- int Cardinality;
- bool RootInTB;
- bool UseRule50;
- Depth ProbeDepth;
+int Cardinality;
+bool RootInTB;
+bool UseRule50;
+Depth ProbeDepth;
}
namespace TB = Tablebases;
namespace {
- // Different node types, used as a template parameter
- enum NodeType { NonPV, PV };
-
- constexpr uint64_t ttHitAverageWindow = 4096;
- constexpr uint64_t ttHitAverageResolution = 1024;
-
- // Razor and futility margins
- constexpr int RazorMargin = 531;
- Value futility_margin(Depth d, bool improving) {
- return Value(217 * (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 + 511) / 1024 + (!i && r > 1007);
- }
-
- constexpr int futility_move_count(bool improving, Depth depth) {
- return (5 + depth * depth) * (1 + improving) / 2 - 1;
- }
-
- // History and stats update bonus, based on depth
- int stat_bonus(Depth d) {
- return d > 15 ? -8 : 19 * d * d + 155 * d - 132;
- }
-
- // Add a small random component to draw evaluations to avoid 3fold-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 + level; }
- Move pick_best(size_t multiPV);
+// Different node types, used as a template parameter
+enum NodeType {
+ NonPV,
+ PV,
+ Root
+};
- int level;
- Move best = MOVE_NONE;
- };
-
- // Breadcrumbs are used to mark nodes as being searched by a given thread
- struct Breadcrumb {
- std::atomic<Thread*> thread;
- std::atomic<Key> key;
- };
- std::array<Breadcrumb, 1024> breadcrumbs;
-
- // ThreadHolding structure keeps track of which thread left breadcrumbs at the given
- // node for potential reductions. A free node will be marked upon entering the moves
- // loop by the constructor, and unmarked upon leaving that loop by the destructor.
- struct ThreadHolding {
- explicit ThreadHolding(Thread* thisThread, Key posKey, int ply) {
- location = ply < 8 ? &breadcrumbs[posKey & (breadcrumbs.size() - 1)] : nullptr;
- otherThread = false;
- owning = false;
- if (location)
- {
- // See if another already marked this location, if not, mark it ourselves
- Thread* tmp = (*location).thread.load(std::memory_order_relaxed);
- if (tmp == nullptr)
- {
- (*location).thread.store(thisThread, std::memory_order_relaxed);
- (*location).key.store(posKey, std::memory_order_relaxed);
- owning = true;
- }
- else if ( tmp != thisThread
- && (*location).key.load(std::memory_order_relaxed) == posKey)
- otherThread = true;
- }
- }
+// Futility margin
+Value futility_margin(Depth d, bool noTtCutNode, bool improving) {
+ return Value((116 - 44 * noTtCutNode) * (d - improving));
+}
- ~ThreadHolding() {
- if (owning) // Free the marked location
- (*location).thread.store(nullptr, std::memory_order_relaxed);
- }
+// Reductions lookup table initialized at startup
+int Reductions[MAX_MOVES]; // [depth or moveNumber]
- bool marked() { return otherThread; }
+Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) {
+ int reductionScale = Reductions[d] * Reductions[mn];
+ return (reductionScale + 1346 - int(delta) * 896 / int(rootDelta)) / 1024
+ + (!i && reductionScale > 880);
+}
- private:
- Breadcrumb* location;
- bool otherThread, owning;
- };
+constexpr int futility_move_count(bool improving, Depth depth) {
+ return improving ? (3 + depth * depth) : (3 + depth * depth) / 2;
+}
- template <NodeType NT>
- Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode);
+// History and stats update bonus, based on depth
+int stat_bonus(Depth d) { return std::min(268 * d - 352, 1153); }
- template <NodeType NT>
- Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = 0);
+// History and stats update malus, based on depth
+int stat_malus(Depth d) { return std::min(400 * d - 354, 1201); }
- 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);
- 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);
+// Add a small random component to draw evaluations to avoid 3-fold blindness
+Value value_draw(const Thread* thisThread) {
+ return VALUE_DRAW - 1 + Value(thisThread->nodes & 0x2);
+}
- // 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<bool Root>
- uint64_t perft(Position& pos, Depth depth) {
+// Skill structure is used to implement strength limit. If we have a UCI_Elo,
+// we convert it to an appropriate skill level, anchored to the Stash engine.
+// This method is based on a fit of the Elo results for games played between
+// Stockfish at various skill levels and various versions of the Stash engine.
+// Skill 0 .. 19 now covers CCRL Blitz Elo from 1320 to 3190, approximately
+// Reference: https://github.com/vondele/Stockfish/commit/a08b8d4e9711c2
+struct Skill {
+ Skill(int skill_level, int uci_elo) {
+ if (uci_elo)
+ {
+ double e = double(uci_elo - 1320) / (3190 - 1320);
+ level = std::clamp((((37.2473 * e - 40.8525) * e + 22.2943) * e - 0.311438), 0.0, 19.0);
+ }
+ else
+ level = double(skill_level);
+ }
+ bool enabled() const { return level < 20.0; }
+ bool time_to_pick(Depth depth) const { return depth == 1 + int(level); }
+ Move pick_best(size_t multiPV);
+
+ double level;
+ Move best = MOVE_NONE;
+};
+
+template<NodeType nodeType>
+Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode);
+
+template<NodeType nodeType>
+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, const 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);
+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);
+
+// Utility to verify move generation. All the leaf nodes up
+// to the given depth are generated and counted, and the sum is returned.
+template<bool Root>
+uint64_t perft(Position& pos, Depth depth) {
StateInfo st;
- uint64_t cnt, nodes = 0;
+ ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize);
+
+ uint64_t cnt, nodes = 0;
const bool leaf = (depth == 2);
for (const auto& m : MoveList<LEGAL>(pos))
sync_cout << UCI::move(m, pos.is_chess960()) << ": " << cnt << sync_endl;
}
return nodes;
- }
-
-} // namespace
+}
+} // namespace
-/// Search::init() is called at startup to initialize various lookup tables
+// Called at startup to initialize various lookup tables
void Search::init() {
- for (int i = 1; i < MAX_MOVES; ++i)
- Reductions[i] = int((24.8 + std::log(Threads.size()) / 2) * std::log(i));
+ for (int i = 1; i < MAX_MOVES; ++i)
+ Reductions[i] = int((20.37 + std::log(Threads.size()) / 2) * std::log(i));
}
-/// Search::clear() resets search state to its initial value
-
+// Resets search state to its initial value
void Search::clear() {
- Threads.main()->wait_for_search_finished();
+ Threads.main()->wait_for_search_finished();
- Time.availableNodes = 0;
- TT.clear();
- Threads.clear();
- Tablebases::init(Options["SyzygyPath"]); // Free mapped files
+ 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".
-
+// Called 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<true>(rootPos, Limits.perft);
- sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl;
- return;
- }
-
- Color us = rootPos.side_to_move();
- Time.init(Limits, us, rootPos.game_ply());
- TT.new_search();
-
- if (rootMoves.empty())
- {
- rootMoves.emplace_back(MOVE_NONE);
- sync_cout << "info depth 0 score "
- << UCI::value(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW)
- << sync_endl;
- }
- else
- {
- for (Thread* th : Threads)
- {
- th->bestMoveChanges = 0;
- if (th != this)
- th->start_searching();
- }
-
- Thread::search(); // Let's start searching!
- }
-
- // 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
- for (Thread* th : Threads)
- if (th != this)
- th->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();
-
- Thread* bestThread = this;
-
- // Check if there are threads with a better score than main thread
- if ( Options["MultiPV"] == 1
- && !Limits.depth
- && !(Skill(Options["Skill Level"]).enabled() || Options["UCI_LimitStrength"])
- && rootMoves[0].pv[0] != MOVE_NONE)
- {
- std::map<Move, int64_t> votes;
- Value minScore = this->rootMoves[0].score;
-
- // Find out minimum score
- for (Thread* th: Threads)
- minScore = std::min(minScore, th->rootMoves[0].score);
-
- // Vote according to score and depth, and select the best thread
- for (Thread* th : Threads)
- {
- votes[th->rootMoves[0].pv[0]] +=
- (th->rootMoves[0].score - minScore + 14) * int(th->completedDepth);
-
- if (bestThread->rootMoves[0].score >= VALUE_MATE_IN_MAX_PLY)
- {
- // Make sure we pick the shortest mate
- if (th->rootMoves[0].score > bestThread->rootMoves[0].score)
- bestThread = th;
- }
- else if ( th->rootMoves[0].score >= VALUE_MATE_IN_MAX_PLY
- || votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]])
- bestThread = th;
- }
- }
-
- previousScore = 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;
-
- sync_cout << "bestmove " << UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960());
-
- if (bestThread->rootMoves[0].pv.size() > 1 || bestThread->rootMoves[0].extract_ponder_from_tt(rootPos))
- std::cout << " ponder " << UCI::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960());
-
- std::cout << sync_endl;
-}
+ if (Limits.perft)
+ {
+ nodes = perft<true>(rootPos, Limits.perft);
+ sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl;
+ return;
+ }
+
+ Color us = rootPos.side_to_move();
+ Time.init(Limits, us, rootPos.game_ply());
+ TT.new_search();
+
+ Eval::NNUE::verify();
+
+ if (rootMoves.empty())
+ {
+ rootMoves.emplace_back(MOVE_NONE);
+ sync_cout << "info depth 0 score "
+ << UCI::value(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW) << sync_endl;
+ }
+ else
+ {
+ Threads.start_searching(); // start non-main threads
+ Thread::search(); // main thread start searching
+ }
+ // 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.
-/// 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.
+ 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();
+
+ Thread* bestThread = this;
+ Skill skill =
+ Skill(Options["Skill Level"], Options["UCI_LimitStrength"] ? int(Options["UCI_Elo"]) : 0);
+
+ if (int(Options["MultiPV"]) == 1 && !Limits.depth && !skill.enabled()
+ && rootMoves[0].pv[0] != MOVE_NONE)
+ bestThread = Threads.get_best_thread();
+
+ bestPreviousScore = bestThread->rootMoves[0].score;
+ bestPreviousAverageScore = bestThread->rootMoves[0].averageScore;
+
+ // Send again PV info if we have a new best thread
+ if (bestThread != this)
+ sync_cout << UCI::pv(bestThread->rootPos, bestThread->completedDepth) << sync_endl;
+
+ sync_cout << "bestmove " << UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960());
+
+ if (bestThread->rootMoves[0].pv.size() > 1
+ || bestThread->rootMoves[0].extract_ponder_from_tt(rootPos))
+ std::cout << " ponder " << UCI::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960());
+
+ std::cout << sync_endl;
+}
+
+// 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 statScores 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
-
- ss->pv = pv;
-
- bestValue = delta = alpha = -VALUE_INFINITE;
- beta = VALUE_INFINITE;
-
- if (mainThread)
- {
- if (mainThread->previousScore == 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->previousScore;
- }
-
- size_t multiPV = 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 floatLevel = Options["UCI_LimitStrength"] ?
- clamp(std::pow((Options["UCI_Elo"] - 1346.6) / 143.4, 1 / 0.806), 0.0, 20.0) :
- double(Options["Skill Level"]);
- int intLevel = int(floatLevel) +
- ((floatLevel - int(floatLevel)) * 1024 > rng.rand<unsigned>() % 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;
-
- int ct = int(Options["Contempt"]) * PawnValueEg / 100; // From centipawns
-
- // In analysis mode, adjust contempt in accordance with user preference
- if (Limits.infinite || Options["UCI_AnalyseMode"])
- ct = Options["Analysis Contempt"] == "Off" ? 0
- : Options["Analysis Contempt"] == "Both" ? ct
- : Options["Analysis Contempt"] == "White" && us == BLACK ? -ct
- : Options["Analysis Contempt"] == "Black" && us == WHITE ? -ct
- : ct;
-
- // Evaluation score is from the white point of view
- contempt = (us == WHITE ? make_score(ct, ct / 2)
- : -make_score(ct, ct / 2));
-
- 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 previousScore = rootMoves[pvIdx].previousScore;
- delta = Value(21 + abs(previousScore) / 256);
- alpha = std::max(previousScore - delta,-VALUE_INFINITE);
- beta = std::min(previousScore + delta, VALUE_INFINITE);
-
- // Adjust contempt based on root move's previousScore (dynamic contempt)
- int dct = ct + (102 - ct / 2) * previousScore / (abs(previousScore) + 157);
-
- contempt = (us == WHITE ? make_score(dct, dct / 2)
- : -make_score(dct, dct / 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 = ::search<PV>(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
- {
- ++rootMoves[pvIdx].bestMoveCount;
- 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 = (332 + 6 * (mainThread->previousScore - bestValue)
- + 6 * (mainThread->iterValue[iterIdx] - bestValue)) / 704.0;
- fallingEval = clamp(fallingEval, 0.5, 1.5);
-
- // If the bestMove is stable over several iterations, reduce time accordingly
- timeReduction = lastBestMoveDepth + 9 < completedDepth ? 1.94 : 0.91;
- double reduction = (1.41 + mainThread->previousTimeReduction) / (2.27 * 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 + totBestMoveChanges / Threads.size();
-
- // Stop the search if we have only one legal move, or if available time elapsed
- if ( rootMoves.size() == 1
- || Time.elapsed() > Time.optimum() * fallingEval * reduction * bestMoveInstability)
- {
- // 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
- Threads.stop = true;
- }
- else if ( Threads.increaseDepth
- && !mainThread->ponder
- && Time.elapsed() > Time.optimum() * fallingEval * reduction * bestMoveInstability * 0.6)
- 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)));
+ // Allocate stack with extra size to allow access from (ss - 7) to (ss + 2):
+ // (ss - 7) is needed for update_continuation_histories(ss - 1) which accesses (ss - 6),
+ // (ss + 2) is needed for initialization of cutOffCnt and killers.
+ Stack stack[MAX_PLY + 10], *ss = stack + 7;
+ Move pv[MAX_PLY + 1];
+ Value 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
+ (ss - i)->staticEval = VALUE_NONE;
+ }
+
+ for (int i = 0; i <= MAX_PLY + 2; ++i)
+ (ss + i)->ply = i;
+
+ ss->pv = pv;
+
+ bestValue = -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;
+ }
+
+ size_t multiPV = size_t(Options["MultiPV"]);
+ Skill skill(Options["Skill Level"], Options["UCI_LimitStrength"] ? int(Options["UCI_Elo"]) : 0);
+
+ // 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());
+
+ 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 the 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
+ Value avg = rootMoves[pvIdx].averageScore;
+ delta = Value(9) + int(avg) * avg / 14847;
+ alpha = std::max(avg - delta, -VALUE_INFINITE);
+ beta = std::min(avg + delta, VALUE_INFINITE);
+
+ // Adjust optimism based on root move's averageScore (~4 Elo)
+ optimism[us] = 121 * avg / (std::abs(avg) + 109);
+ optimism[~us] = -optimism[us];
+
+ // 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)
+ {
+ // Adjust the effective depth searched, but ensure at least one effective increment
+ // for every four searchAgain steps (see issue #2717).
+ Depth adjustedDepth =
+ std::max(1, rootDepth - failedHighCnt - 3 * (searchAgainCounter + 1) / 4);
+ bestValue = Stockfish::search<Root>(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 is 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 the 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) << 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 / 3;
+
+ 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) << 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 the 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);
+
+ // 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;
+ }
+
+ // Do we have time for the next iteration? Can we stop searching now?
+ if (Limits.use_time_management() && !Threads.stop && !mainThread->stopOnPonderhit)
+ {
+ double fallingEval = (66 + 14 * (mainThread->bestPreviousAverageScore - bestValue)
+ + 6 * (mainThread->iterValue[iterIdx] - bestValue))
+ / 616.6;
+ fallingEval = std::clamp(fallingEval, 0.51, 1.51);
+
+ // If the bestMove is stable over several iterations, reduce time accordingly
+ timeReduction = lastBestMoveDepth + 8 < completedDepth ? 1.56 : 0.69;
+ double reduction = (1.4 + mainThread->previousTimeReduction) / (2.17 * timeReduction);
+ double bestMoveInstability = 1 + 1.79 * 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
+ if (rootMoves.size() == 1)
+ totalTime = std::min(500.0, totalTime);
+
+ // 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
+ Threads.stop = true;
+ }
+ else if (!mainThread->ponder && Time.elapsed() > totalTime * 0.50)
+ Threads.increaseDepth = false;
+ else
+ Threads.increaseDepth = true;
+ }
+
+ mainThread->iterValue[iterIdx] = bestValue;
+ iterIdx = (iterIdx + 1) & 3;
+ }
+
+ if (!mainThread)
+ return;
+
+ mainThread->previousTimeReduction = timeReduction;
+
+ // If the skill level is enabled, swap the 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
+// Main search function for both PV and non-PV nodes
+template<NodeType nodeType>
+Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) {
- template <NodeType NT>
- Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) {
+ constexpr bool PvNode = nodeType != NonPV;
+ constexpr bool rootNode = nodeType == Root;
- constexpr bool PvNode = NT == PV;
- const bool rootNode = PvNode && ss->ply == 0;
+ // Dive into quiescence search when the depth reaches zero
+ if (depth <= 0)
+ return qsearch < PvNode ? PV : NonPV > (pos, ss, alpha, beta);
- // Check if we have an upcoming move which draws by repetition, or
+ // Check if we have an upcoming move that draws by repetition, or
// if the opponent had an alternative move earlier to this position.
- if ( pos.rule50_count() >= 3
- && alpha < VALUE_DRAW
- && !rootNode
- && pos.has_game_cycle(ss->ply))
+ if (!rootNode && 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<NT>(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];
+ Move pv[MAX_PLY + 1], capturesSearched[32], quietsSearched[32];
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;
- bool ttHit, ttPv, inCheck, givesCheck, improving, didLMR, priorCapture;
- bool captureOrPromotion, doFullDepthSearch, moveCountPruning, ttCapture, singularLMR;
- Piece movedPiece;
- int moveCount, captureCount, quietCount;
+ Key posKey;
+ Move ttMove, move, excludedMove, bestMove;
+ Depth extension, newDepth;
+ Value bestValue, value, ttValue, eval, maxValue, probCutBeta;
+ bool givesCheck, improving, priorCapture, singularQuietLMR;
+ bool capture, moveCountPruning, ttCapture;
+ Piece movedPiece;
+ int moveCount, captureCount, quietCount;
// Step 1. Initialize node
Thread* thisThread = pos.this_thread();
- inCheck = pos.checkers();
- priorCapture = pos.captured_piece();
- Color us = pos.side_to_move();
+ 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;
+ bestValue = -VALUE_INFINITE;
+ maxValue = VALUE_INFINITE;
// Check for the available remaining time
if (thisThread == Threads.main())
if (!rootNode)
{
// Step 2. Check for aborted search and immediate draw
- if ( Threads.stop.load(std::memory_order_relaxed)
- || pos.is_draw(ss->ply)
+ if (Threads.stop.load(std::memory_order_relaxed) || pos.is_draw(ss->ply)
|| ss->ply >= MAX_PLY)
- return (ss->ply >= MAX_PLY && !inCheck) ? evaluate(pos)
- : value_draw(pos.this_thread());
+ 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
+ // 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.
+ // signs apply 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);
+ beta = std::min(mate_in(ss->ply + 1), beta);
if (alpha >= beta)
return alpha;
}
+ else
+ thisThread->rootDelta = beta - alpha;
assert(0 <= ss->ply && ss->ply < MAX_PLY);
- (ss+1)->ply = ss->ply + 1;
- (ss+1)->excludedMove = bestMove = MOVE_NONE;
- (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE;
- 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+4)->statScore = 0;
- else
- (ss+2)->statScore = 0;
+ (ss + 1)->excludedMove = bestMove = MOVE_NONE;
+ (ss + 2)->killers[0] = (ss + 2)->killers[1] = MOVE_NONE;
+ (ss + 2)->cutoffCnt = 0;
+ ss->doubleExtensions = (ss - 1)->doubleExtensions;
+ Square prevSq = is_ok((ss - 1)->currentMove) ? to_sq((ss - 1)->currentMove) : SQ_NONE;
+ ss->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.
+ // Step 4. Transposition table lookup.
excludedMove = ss->excludedMove;
- posKey = pos.key() ^ Key(excludedMove << 16); // Isn't a very good hash
- tte = TT.probe(posKey, ttHit);
- ttValue = ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE;
- ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0]
- : ttHit ? tte->move() : MOVE_NONE;
- ttPv = PvNode || (ttHit && tte->is_pv());
- // thisThread->ttHitAverage can be used to approximate the running average of ttHit
- thisThread->ttHitAverage = (ttHitAverageWindow - 1) * thisThread->ttHitAverage / ttHitAverageWindow
- + ttHitAverageResolution * ttHit;
+ 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 = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0]
+ : ss->ttHit ? tte->move()
+ : MOVE_NONE;
+ ttCapture = ttMove && pos.capture_stage(ttMove);
+
+ // At this point, if excluded, skip straight to step 6, static eval. However,
+ // to save indentation, we list the condition in all code between here and there.
+ if (!excludedMove)
+ ss->ttPv = PvNode || (ss->ttHit && tte->is_pv());
// At non-PV nodes we check for an early TT cutoff
- if ( !PvNode
- && 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 (!PvNode && !excludedMove && tte->depth() > depth
+ && ttValue != VALUE_NONE // Possible in case of TT access race or if !ttHit
+ && (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER)))
{
- // If ttMove is quiet, update move sorting heuristics on TT hit
+ // If ttMove is quiet, update move sorting heuristics on TT hit (~2 Elo)
if (ttMove)
{
if (ttValue >= beta)
{
- if (!pos.capture_or_promotion(ttMove))
+ // Bonus for a quiet ttMove that fails high (~2 Elo)
+ if (!ttCapture)
update_quiet_stats(pos, ss, ttMove, stat_bonus(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));
+ // Extra penalty for early quiet moves of
+ // the previous ply (~0 Elo on STC, ~2 Elo on LTC).
+ if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 2 && !priorCapture)
+ update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq,
+ -stat_malus(depth + 1));
}
- // Penalty for a quiet ttMove that fails low
- else if (!pos.capture_or_promotion(ttMove))
+ // Penalty for a quiet ttMove that fails low (~1 Elo)
+ else if (!ttCapture)
{
- int penalty = -stat_bonus(depth);
+ int penalty = -stat_malus(depth);
thisThread->mainHistory[us][from_to(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)
+ if (!rootNode && !excludedMove && TB::Cardinality)
{
int piecesCount = pos.count<ALL_PIECES>();
- if ( piecesCount <= TB::Cardinality
- && (piecesCount < TB::Cardinality || depth >= TB::ProbeDepth)
- && pos.rule50_count() == 0
+ if (piecesCount <= TB::Cardinality
+ && (piecesCount < TB::Cardinality || depth >= TB::ProbeDepth) && pos.rule50_count() == 0
&& !pos.can_castle(ANY_CASTLING))
{
TB::ProbeState err;
- TB::WDLScore wdl = Tablebases::probe_wdl(pos, &err);
+ TB::WDLScore wdl = Tablebases::probe_wdl(pos, &err);
// Force check of time on the next occasion
if (thisThread == Threads.main())
int drawScore = TB::UseRule50 ? 1 : 0;
- value = wdl < -drawScore ? -VALUE_MATE + MAX_PLY + ss->ply + 1
- : wdl > drawScore ? VALUE_MATE - MAX_PLY - ss->ply - 1
- : VALUE_DRAW + 2 * wdl * drawScore;
+ Value tbValue = VALUE_TB - ss->ply;
- Bound b = wdl < -drawScore ? BOUND_UPPER
- : wdl > drawScore ? BOUND_LOWER : BOUND_EXACT;
+ // use the range VALUE_TB to VALUE_TB_WIN_IN_MAX_PLY to score
+ value = wdl < -drawScore ? -tbValue
+ : wdl > drawScore ? tbValue
+ : VALUE_DRAW + 2 * wdl * drawScore;
- if ( b == BOUND_EXACT
- || (b == BOUND_LOWER ? value >= beta : value <= alpha))
+ 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), ttPv, b,
- std::min(MAX_PLY - 1, depth + 6),
- MOVE_NONE, VALUE_NONE);
+ 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;
}
}
}
+ CapturePieceToHistory& captureHistory = thisThread->captureHistory;
+
// Step 6. Static evaluation of the position
- if (inCheck)
+ if (ss->inCheck)
{
+ // Skip early pruning when in check
ss->staticEval = eval = VALUE_NONE;
- improving = false;
- goto moves_loop; // Skip early pruning when in check
+ improving = false;
+ goto moves_loop;
}
- else if (ttHit)
+ else if (excludedMove)
+ {
+ // Providing the hint that this node's accumulator will be used often
+ // brings significant Elo gain (~13 Elo).
+ Eval::NNUE::hint_common_parent_position(pos);
+ eval = ss->staticEval;
+ }
+ 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);
+ else if (PvNode)
+ Eval::NNUE::hint_common_parent_position(pos);
- 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)))
+ // ttValue can be used as a better position evaluation (~7 Elo)
+ if (ttValue != VALUE_NONE && (tte->bound() & (ttValue > eval ? BOUND_LOWER : BOUND_UPPER)))
eval = ttValue;
}
else
{
- if ((ss-1)->currentMove != MOVE_NULL)
- {
- int bonus = -(ss-1)->statScore / 512;
-
- ss->staticEval = eval = evaluate(pos) + bonus;
- }
- else
- ss->staticEval = eval = -(ss-1)->staticEval + 2 * Eval::Tempo;
+ ss->staticEval = eval = evaluate(pos);
+ // Save static evaluation into the transposition table
+ tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval);
+ }
- tte->save(posKey, VALUE_NONE, ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval);
+ // Use static evaluation difference to improve quiet move ordering (~4 Elo)
+ if (is_ok((ss - 1)->currentMove) && !(ss - 1)->inCheck && !priorCapture)
+ {
+ int bonus = std::clamp(-13 * int((ss - 1)->staticEval + ss->staticEval), -1652, 1546);
+ thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)] << bonus;
+ if (type_of(pos.piece_on(prevSq)) != PAWN && type_of((ss - 1)->currentMove) != PROMOTION)
+ thisThread->pawnHistory[pawn_structure(pos)][pos.piece_on(prevSq)][prevSq] << bonus / 4;
}
+ // Set up the improving flag, which is true if current static evaluation is
+ // bigger than the previous static evaluation at our turn (if we were in
+ // check at our previous move we look at static evaluation at move prior to it
+ // and if we were in check at move prior to it flag is set to true) and is
+ // false otherwise. The improving flag is used in various pruning heuristics.
+ improving = (ss - 2)->staticEval != VALUE_NONE ? ss->staticEval > (ss - 2)->staticEval
+ : (ss - 4)->staticEval != VALUE_NONE ? ss->staticEval > (ss - 4)->staticEval
+ : true;
+
// Step 7. Razoring (~1 Elo)
- if ( !rootNode // The required rootNode PV handling is not available in qsearch
- && depth < 2
- && eval <= alpha - RazorMargin)
- return qsearch<NT>(pos, ss, alpha, beta);
-
- improving = (ss-2)->staticEval == VALUE_NONE ? (ss->staticEval >= (ss-4)->staticEval
- || (ss-4)->staticEval == VALUE_NONE) : ss->staticEval >= (ss-2)->staticEval;
-
- // Step 8. Futility pruning: child node (~50 Elo)
- if ( !PvNode
- && depth < 6
- && eval - futility_margin(depth, improving) >= beta
- && eval < VALUE_KNOWN_WIN) // Do not return unproven wins
- return eval;
-
- // Step 9. Null move search with verification search (~40 Elo)
- if ( !PvNode
- && (ss-1)->currentMove != MOVE_NULL
- && (ss-1)->statScore < 23397
- && eval >= beta
- && eval >= ss->staticEval
- && ss->staticEval >= beta - 32 * depth + 292 - improving * 30
- && !excludedMove
- && pos.non_pawn_material(us)
- && (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor))
+ // If eval is really low check with qsearch if it can exceed alpha, if it can't,
+ // return a fail low.
+ // Adjust razor margin according to cutoffCnt. (~1 Elo)
+ if (eval < alpha - 472 - (284 - 165 * ((ss + 1)->cutoffCnt > 3)) * depth * depth)
+ {
+ value = qsearch<NonPV>(pos, ss, alpha - 1, alpha);
+ if (value < alpha)
+ return value;
+ }
+
+ // Step 8. Futility pruning: child node (~40 Elo)
+ // The depth condition is important for mate finding.
+ if (!ss->ttPv && depth < 9
+ && eval - futility_margin(depth, cutNode && !ss->ttHit, improving)
+ - (ss - 1)->statScore / 337
+ >= beta
+ && eval >= beta && eval < 29008 // smaller than TB wins
+ && (!ttMove || ttCapture))
+ return (eval + beta) / 2;
+
+ // Step 9. Null move search with verification search (~35 Elo)
+ if (!PvNode && (ss - 1)->currentMove != MOVE_NULL && (ss - 1)->statScore < 17496 && eval >= beta
+ && eval >= ss->staticEval && ss->staticEval >= beta - 23 * depth + 304 && !excludedMove
+ && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly
+ && beta > VALUE_TB_LOSS_IN_MAX_PLY)
{
assert(eval - beta >= 0);
- // Null move dynamic reduction based on depth and value
- Depth R = (854 + 68 * depth) / 258 + std::min(int(eval - beta) / 192, 3);
+ // Null move dynamic reduction based on depth and eval
+ Depth R = std::min(int(eval - beta) / 144, 6) + depth / 3 + 4;
- ss->currentMove = MOVE_NULL;
+ ss->currentMove = MOVE_NULL;
ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0];
pos.do_null_move(st);
- Value nullValue = -search<NonPV>(pos, ss+1, -beta, -beta+1, depth-R, !cutNode);
+ Value nullValue = -search<NonPV>(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 >= beta && nullValue < VALUE_TB_WIN_IN_MAX_PLY)
{
- // Do not return unproven mate scores
- if (nullValue >= VALUE_MATE_IN_MAX_PLY)
- nullValue = beta;
-
- if (thisThread->nmpMinPly || (abs(beta) < VALUE_KNOWN_WIN && depth < 13))
+ if (thisThread->nmpMinPly || depth < 15)
return nullValue;
- assert(!thisThread->nmpMinPly); // Recursive verification is not allowed
+ 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;
+ // until ply exceeds nmpMinPly.
+ thisThread->nmpMinPly = ss->ply + 3 * (depth - R) / 4;
- Value v = search<NonPV>(pos, ss, beta-1, beta, depth-R, false);
+ Value v = search<NonPV>(pos, ss, beta - 1, beta, depth - R, false);
thisThread->nmpMinPly = 0;
}
}
- // Step 10. ProbCut (~10 Elo)
- // If we have a good enough capture and a reduced search returns a value
+ // Step 10. Internal iterative reductions (~9 Elo)
+ // For PV nodes without a ttMove, we decrease depth by 2,
+ // or by 4 if the current position is present in the TT and
+ // the stored depth is greater than or equal to the current depth.
+ // Use qsearch if depth <= 0.
+ if (PvNode && !ttMove)
+ depth -= 2 + 2 * (ss->ttHit && tte->depth() >= depth);
+
+ if (depth <= 0)
+ return qsearch<PV>(pos, ss, alpha, beta);
+
+ // For cutNodes without a ttMove, we decrease depth by 2 if depth is high enough.
+ if (cutNode && depth >= 8 && !ttMove)
+ depth -= 2;
+
+ probCutBeta = beta + 163 - 67 * improving;
+
+ // Step 11. ProbCut (~10 Elo)
+ // If we have a good enough capture (or queen promotion) and a reduced search returns a value
// much above beta, we can (almost) safely prune the previous move.
- if ( !PvNode
- && depth >= 5
- && abs(beta) < VALUE_MATE_IN_MAX_PLY)
+ if (
+ !PvNode && depth > 3
+ && std::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
+ && !(tte->depth() >= depth - 3 && ttValue != VALUE_NONE && ttValue < probCutBeta))
{
- Value raisedBeta = std::min(beta + 189 - 45 * improving, VALUE_INFINITE);
- MovePicker mp(pos, ttMove, raisedBeta - ss->staticEval, &thisThread->captureHistory);
- int probCutCount = 0;
+ assert(probCutBeta < VALUE_INFINITE);
+
+ MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory);
- while ( (move = mp.next_move()) != MOVE_NONE
- && probCutCount < 2 + 2 * cutNode)
+ while ((move = mp.next_move()) != MOVE_NONE)
if (move != excludedMove && pos.legal(move))
{
- assert(pos.capture_or_promotion(move));
- assert(depth >= 5);
+ assert(pos.capture_stage(move));
- captureOrPromotion = true;
- probCutCount++;
+ // Prefetch the TT entry for the resulting position
+ prefetch(TT.first_entry(pos.key_after(move)));
ss->currentMove = move;
- ss->continuationHistory = &thisThread->continuationHistory[inCheck]
- [captureOrPromotion]
- [pos.moved_piece(move)]
- [to_sq(move)];
+ ss->continuationHistory =
+ &thisThread
+ ->continuationHistory[ss->inCheck][true][pos.moved_piece(move)][to_sq(move)];
pos.do_move(move, st);
// Perform a preliminary qsearch to verify that the move holds
- value = -qsearch<NonPV>(pos, ss+1, -raisedBeta, -raisedBeta+1);
+ value = -qsearch<NonPV>(pos, ss + 1, -probCutBeta, -probCutBeta + 1);
// If the qsearch held, perform the regular search
- if (value >= raisedBeta)
- value = -search<NonPV>(pos, ss+1, -raisedBeta, -raisedBeta+1, depth - 4, !cutNode);
+ if (value >= probCutBeta)
+ value = -search<NonPV>(pos, ss + 1, -probCutBeta, -probCutBeta + 1, depth - 4,
+ !cutNode);
pos.undo_move(move);
- if (value >= raisedBeta)
- return value;
+ if (value >= probCutBeta)
+ {
+ // Save ProbCut data into transposition table
+ tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, BOUND_LOWER, depth - 3,
+ move, ss->staticEval);
+ return value - (probCutBeta - beta);
+ }
}
- }
-
- // Step 11. Internal iterative deepening (~1 Elo)
- if (depth >= 7 && !ttMove)
- {
- search<NT>(pos, ss, alpha, beta, depth - 7, cutNode);
- tte = TT.probe(posKey, ttHit);
- ttValue = ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE;
- ttMove = ttHit ? tte->move() : MOVE_NONE;
+ Eval::NNUE::hint_common_parent_position(pos);
}
-moves_loop: // When in check, search starts from here
+moves_loop: // When in check, search starts here
- const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory,
- nullptr , (ss-4)->continuationHistory,
- nullptr , (ss-6)->continuationHistory };
+ // Step 12. A small Probcut idea, when we are in check (~4 Elo)
+ probCutBeta = beta + 425;
+ if (ss->inCheck && !PvNode && ttCapture && (tte->bound() & BOUND_LOWER)
+ && tte->depth() >= depth - 4 && ttValue >= probCutBeta
+ && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && std::abs(beta) < VALUE_TB_WIN_IN_MAX_PLY)
+ return probCutBeta;
- Move countermove = thisThread->counterMoves[pos.piece_on(prevSq)][prevSq];
+ const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory,
+ (ss - 2)->continuationHistory,
+ (ss - 3)->continuationHistory,
+ (ss - 4)->continuationHistory,
+ nullptr,
+ (ss - 6)->continuationHistory};
- MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory,
- &thisThread->captureHistory,
- contHist,
- countermove,
- ss->killers);
+ Move countermove =
+ prevSq != SQ_NONE ? thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] : MOVE_NONE;
- value = bestValue;
- singularLMR = moveCountPruning = false;
- ttCapture = ttMove && pos.capture_or_promotion(ttMove);
+ MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &captureHistory, contHist,
+ &thisThread->pawnHistory, countermove, ss->killers);
- // Mark this node as being searched
- ThreadHolding th(thisThread, posKey, ss->ply);
+ value = bestValue;
+ moveCountPruning = singularQuietLMR = false;
- // Step 12. Loop through all pseudo-legal moves until no moves remain
+ // Indicate PvNodes that will probably fail low if the node was searched
+ // at a depth equal to 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 13. 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;
-
- ss->moveCount = ++moveCount;
-
- if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000)
- sync_cout << "info depth " << depth
- << " currmove " << UCI::move(move, pos.is_chess960())
- << " 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)
- && bestValue > VALUE_MATED_IN_MAX_PLY)
- {
- // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold
- moveCountPruning = moveCount >= futility_move_count(improving, depth);
-
- if ( !captureOrPromotion
- && !givesCheck)
- {
- // Reduced depth of the next LMR search
- int lmrDepth = std::max(newDepth - reduction(improving, depth, moveCount), 0);
-
- // Countermoves based pruning (~20 Elo)
- if ( lmrDepth < 4 + ((ss-1)->statScore > 0 || (ss-1)->moveCount == 1)
- && (*contHist[0])[movedPiece][to_sq(move)] < CounterMovePruneThreshold
- && (*contHist[1])[movedPiece][to_sq(move)] < CounterMovePruneThreshold)
- continue;
-
- // Futility pruning: parent node (~5 Elo)
- if ( lmrDepth < 6
- && !inCheck
- && ss->staticEval + 235 + 172 * lmrDepth <= alpha
- && thisThread->mainHistory[us][from_to(move)]
- + (*contHist[0])[movedPiece][to_sq(move)]
- + (*contHist[1])[movedPiece][to_sq(move)]
- + (*contHist[3])[movedPiece][to_sq(move)] < 25000)
- continue;
-
- // Prune moves with negative SEE (~20 Elo)
- if (!pos.see_ge(move, Value(-(32 - std::min(lmrDepth, 18)) * lmrDepth * lmrDepth)))
- continue;
- }
- else if (!pos.see_ge(move, Value(-194) * depth)) // (~25 Elo)
- 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 ( depth >= 6
- && move == ttMove
- && !rootNode
- && !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
- && pos.legal(move))
- {
- Value singularBeta = ttValue - 2 * depth;
- Depth halfDepth = depth / 2;
- ss->excludedMove = move;
- value = search<NonPV>(pos, ss, singularBeta - 1, singularBeta, halfDepth, cutNode);
- ss->excludedMove = MOVE_NONE;
-
- if (value < singularBeta)
- {
- extension = 1;
- singularLMR = 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;
- }
-
- // Check extension (~2 Elo)
- else if ( givesCheck
- && (pos.is_discovery_check_on_king(~us, move) || pos.see_ge(move)))
- extension = 1;
-
- // Passed pawn extension
- else if ( move == ss->killers[0]
- && pos.advanced_pawn_push(move)
- && pos.pawn_passed(us, to_sq(move)))
- extension = 1;
-
- // Last captures extension
- else if ( PieceValue[EG][pos.captured_piece()] > PawnValueEg
- && pos.non_pawn_material() <= 2 * RookValueMg)
- extension = 1;
-
- // Castling extension
- if (type_of(move) == CASTLING)
- extension = 1;
-
- // Add extension to new depth
- newDepth += extension;
-
- // Speculative prefetch as early as possible
- prefetch(TT.first_entry(pos.key_after(move)));
-
- // Check for legality just before making the move
- if (!rootNode && !pos.legal(move))
- {
- ss->moveCount = --moveCount;
- continue;
- }
-
- // Update the current move (this must be done after singular extension search)
- ss->currentMove = move;
- ss->continuationHistory = &thisThread->continuationHistory[inCheck]
- [captureOrPromotion]
- [movedPiece]
- [to_sq(move)];
-
- // Step 15. Make the move
- pos.do_move(move, st, givesCheck);
-
- // Step 16. Reduced depth search (LMR, ~200 Elo). If the move fails high it will be
- // re-searched at full depth.
- if ( depth >= 3
- && moveCount > 1 + rootNode + (rootNode && bestValue < alpha)
- && (!rootNode || thisThread->best_move_count(move) == 0)
- && ( !captureOrPromotion
- || moveCountPruning
- || ss->staticEval + PieceValue[EG][pos.captured_piece()] <= alpha
- || cutNode
- || thisThread->ttHitAverage < 375 * ttHitAverageResolution * ttHitAverageWindow / 1024))
- {
- Depth r = reduction(improving, depth, moveCount);
-
- // Decrease reduction if the ttHit running average is large
- if (thisThread->ttHitAverage > 500 * ttHitAverageResolution * ttHitAverageWindow / 1024)
- r--;
-
- // Reduction if other threads are searching this position.
- if (th.marked())
- r++;
-
- // Decrease reduction if position is or has been on the PV (~10 Elo)
- if (ttPv)
- r -= 2;
-
- // Decrease reduction if opponent's move count is high (~5 Elo)
- if ((ss-1)->moveCount > 14)
- r--;
-
- // Decrease reduction if ttMove has been singularly extended (~3 Elo)
- if (singularLMR)
- r -= 2;
-
- if (!captureOrPromotion)
- {
- // Increase reduction if ttMove is a capture (~5 Elo)
- if (ttCapture)
- r++;
-
- // Increase reduction for cut nodes (~10 Elo)
- if (cutNode)
- r += 2;
-
- // Decrease reduction for moves that escape a capture. Filter out
- // castling moves, because they are coded as "king captures rook" and
- // hence break make_move(). (~2 Elo)
- else if ( type_of(move) == NORMAL
- && !pos.see_ge(reverse_move(move)))
- r -= 2;
-
- ss->statScore = thisThread->mainHistory[us][from_to(move)]
- + (*contHist[0])[movedPiece][to_sq(move)]
- + (*contHist[1])[movedPiece][to_sq(move)]
- + (*contHist[3])[movedPiece][to_sq(move)]
- - 4926;
-
- // Reset statScore to zero if negative and most stats shows >= 0
- if ( ss->statScore < 0
- && (*contHist[0])[movedPiece][to_sq(move)] >= 0
- && (*contHist[1])[movedPiece][to_sq(move)] >= 0
- && thisThread->mainHistory[us][from_to(move)] >= 0)
- ss->statScore = 0;
-
- // Decrease/increase reduction by comparing opponent's stat score (~10 Elo)
- if (ss->statScore >= -102 && (ss-1)->statScore < -114)
- r--;
-
- else if ((ss-1)->statScore >= -116 && ss->statScore < -154)
- r++;
-
- // Decrease/increase reduction for moves with a good/bad history (~30 Elo)
- r -= ss->statScore / 16384;
- }
-
- // Increase reduction for captures/promotions if late move and at low depth
- else if (depth < 8 && moveCount > 2)
- r++;
-
- Depth d = clamp(newDepth - r, 1, newDepth);
-
- value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, d, true);
-
- 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<NonPV>(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode);
-
- if (didLMR && !captureOrPromotion)
- {
- int bonus = value > alpha ? stat_bonus(newDepth)
- : -stat_bonus(newDepth);
-
- if (move == ss->killers[0])
- bonus += bonus / 4;
-
- 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<PV>(pos, ss+1, -beta, -alpha, 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: When
- // the best move changes frequently, we allocate some more time.
- 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
- ss->statScore = 0;
- break;
- }
- }
- }
-
- if (move != bestMove)
- {
- if (captureOrPromotion && captureCount < 32)
- capturesSearched[captureCount++] = move;
-
- else if (!captureOrPromotion && quietCount < 64)
- quietsSearched[quietCount++] = move;
- }
- }
+ assert(is_ok(move));
+
+ if (move == excludedMove)
+ continue;
+
+ // Check for legality
+ if (!pos.legal(move))
+ continue;
+
+ // At root obey the "searchmoves" option and skip moves not listed in Root
+ // Move List. In MultiPV mode we also skip PV moves that 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;
+
+ ss->moveCount = ++moveCount;
+
+ if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000)
+ sync_cout << "info depth " << depth << " currmove "
+ << UCI::move(move, pos.is_chess960()) << " currmovenumber "
+ << moveCount + thisThread->pvIdx << sync_endl;
+ if (PvNode)
+ (ss + 1)->pv = nullptr;
+
+ extension = 0;
+ capture = pos.capture_stage(move);
+ movedPiece = pos.moved_piece(move);
+ givesCheck = pos.gives_check(move);
+
+ // Calculate new depth for this move
+ newDepth = depth - 1;
+
+ Value delta = beta - alpha;
+
+ Depth r = reduction(improving, depth, moveCount, delta, thisThread->rootDelta);
+
+ // Step 14. Pruning at shallow depth (~120 Elo).
+ // Depth conditions are important for mate finding.
+ if (!rootNode && pos.non_pawn_material(us) && bestValue > VALUE_TB_LOSS_IN_MAX_PLY)
+ {
+ // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold (~8 Elo)
+ if (!moveCountPruning)
+ moveCountPruning = moveCount >= futility_move_count(improving, depth);
+
+ // Reduced depth of the next LMR search
+ int lmrDepth = newDepth - r;
+
+ if (capture || givesCheck)
+ {
+ // Futility pruning for captures (~2 Elo)
+ if (!givesCheck && lmrDepth < 7 && !ss->inCheck)
+ {
+ Piece capturedPiece = pos.piece_on(to_sq(move));
+ int futilityEval =
+ ss->staticEval + 238 + 305 * lmrDepth + PieceValue[capturedPiece]
+ + captureHistory[movedPiece][to_sq(move)][type_of(capturedPiece)] / 7;
+ if (futilityEval < alpha)
+ continue;
+ }
+
+ // SEE based pruning for captures and checks (~11 Elo)
+ if (!pos.see_ge(move, Value(-187) * depth))
+ continue;
+ }
+ else
+ {
+ int history = (*contHist[0])[movedPiece][to_sq(move)]
+ + (*contHist[1])[movedPiece][to_sq(move)]
+ + (*contHist[3])[movedPiece][to_sq(move)]
+ + thisThread->pawnHistory[pawn_structure(pos)][movedPiece][to_sq(move)];
+
+ // Continuation history based pruning (~2 Elo)
+ if (lmrDepth < 6 && history < -3752 * depth)
+ continue;
+
+ history += 2 * thisThread->mainHistory[us][from_to(move)];
+
+ lmrDepth += history / 7838;
+ lmrDepth = std::max(lmrDepth, -1);
+
+ // Futility pruning: parent node (~13 Elo)
+ if (!ss->inCheck && lmrDepth < 14
+ && ss->staticEval + (bestValue < ss->staticEval - 57 ? 124 : 71)
+ + 118 * lmrDepth
+ <= alpha)
+ continue;
+
+ lmrDepth = std::max(lmrDepth, 0);
+
+ // Prune moves with negative SEE (~4 Elo)
+ if (!pos.see_ge(move, Value(-26 * lmrDepth * lmrDepth)))
+ continue;
+ }
+ }
+
+ // Step 15. Extensions (~100 Elo)
+ // We take care to not overdo to avoid search getting stuck.
+ if (ss->ply < thisThread->rootDepth * 2)
+ {
+ // Singular extension search (~94 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 the position excluding the ttMove and if the result
+ // is lower than ttValue minus a margin, then we will extend the ttMove.
+
+ // Note: the depth margin and singularBeta margin are known for having non-linear
+ // scaling. Their values are optimized to time controls of 180+1.8 and longer
+ // so changing them requires tests at these types of time controls.
+ // Recursive singular search is avoided.
+ if (!rootNode && move == ttMove && !excludedMove
+ && depth >= 4 - (thisThread->completedDepth > 27) + 2 * (PvNode && tte->is_pv())
+ && std::abs(ttValue) < VALUE_TB_WIN_IN_MAX_PLY && (tte->bound() & BOUND_LOWER)
+ && tte->depth() >= depth - 3)
+ {
+ Value singularBeta = ttValue - (66 + 58 * (ss->ttPv && !PvNode)) * depth / 64;
+ Depth singularDepth = newDepth / 2;
+
+ ss->excludedMove = move;
+ value =
+ search<NonPV>(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode);
+ ss->excludedMove = MOVE_NONE;
- // 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;
- */
+ if (value < singularBeta)
+ {
+ extension = 1;
+ singularQuietLMR = !ttCapture;
+
+ // Avoid search explosion by limiting the number of double extensions
+ if (!PvNode && value < singularBeta - 17 && ss->doubleExtensions <= 11)
+ {
+ extension = 2;
+ depth += depth < 15;
+ }
+ }
+
+ // Multi-cut pruning
+ // Our ttMove is assumed to fail high based on the bound of the TT entry,
+ // and if after excluding the ttMove with a reduced search we fail high over the original beta,
+ // we assume this expected cut-node is not singular (multiple moves fail high),
+ // and we can prune the whole subtree by returning a softbound.
+ else if (singularBeta >= beta)
+ return singularBeta;
+
+ // Negative extensions
+ // If other moves failed high over (ttValue - margin) without the ttMove on a reduced search,
+ // but we cannot do multi-cut because (ttValue - margin) is lower than the original beta,
+ // we do not know if the ttMove is singular or can do a multi-cut,
+ // so we reduce the ttMove in favor of other moves based on some conditions:
+
+ // If the ttMove is assumed to fail high over current beta (~7 Elo)
+ else if (ttValue >= beta)
+ extension = -2 - !PvNode;
+
+ // If we are on a cutNode but the ttMove is not assumed to fail high over current beta (~1 Elo)
+ else if (cutNode)
+ extension = depth < 19 ? -2 : -1;
+
+ // If the ttMove is assumed to fail low over the value of the reduced search (~1 Elo)
+ else if (ttValue <= value)
+ extension = -1;
+ }
+
+ // Check extensions (~1 Elo)
+ else if (givesCheck && depth > 10)
+ extension = 1;
+
+ // Quiet ttMove extensions (~1 Elo)
+ else if (PvNode && move == ttMove && move == ss->killers[0]
+ && (*contHist[0])[movedPiece][to_sq(move)] >= 4325)
+ extension = 1;
+
+ // Recapture extensions (~1 Elo)
+ else if (PvNode && move == ttMove && to_sq(move) == prevSq
+ && captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))]
+ > 4146)
+ extension = 1;
+ }
+
+ // Add extension to new depth
+ newDepth += extension;
+ ss->doubleExtensions = (ss - 1)->doubleExtensions + (extension == 2);
+
+ // Speculative prefetch as early as possible
+ prefetch(TT.first_entry(pos.key_after(move)));
+
+ // Update the current move (this must be done after singular extension search)
+ ss->currentMove = move;
+ ss->continuationHistory =
+ &thisThread->continuationHistory[ss->inCheck][capture][movedPiece][to_sq(move)];
+
+ // Step 16. Make the move
+ pos.do_move(move, st, givesCheck);
+
+ // Decrease reduction if position is or has been on the PV (~4 Elo)
+ if (ss->ttPv && !likelyFailLow)
+ r -= cutNode && tte->depth() >= depth ? 3 : 2;
+
+ // Decrease reduction if opponent's move count is high (~1 Elo)
+ if ((ss - 1)->moveCount > 7)
+ r--;
+
+ // Increase reduction for cut nodes (~3 Elo)
+ if (cutNode)
+ r += 2;
+
+ // Increase reduction if ttMove is a capture (~3 Elo)
+ if (ttCapture)
+ r++;
+
+ // Decrease reduction for PvNodes (~2 Elo)
+ if (PvNode)
+ r--;
+
+ // Decrease reduction if a quiet ttMove has been singularly extended (~1 Elo)
+ if (singularQuietLMR)
+ r--;
+
+ // Increase reduction on repetition (~1 Elo)
+ if (move == (ss - 4)->currentMove && pos.has_repeated())
+ r += 2;
+
+ // Increase reduction if next ply has a lot of fail high (~5 Elo)
+ if ((ss + 1)->cutoffCnt > 3)
+ r++;
+
+ // Set reduction to 0 for first picked move (ttMove) (~2 Elo)
+ // Nullifies all previous reduction adjustments to ttMove and leaves only history to do them
+ else if (move == ttMove)
+ r = 0;
+
+ ss->statScore = 2 * thisThread->mainHistory[us][from_to(move)]
+ + (*contHist[0])[movedPiece][to_sq(move)]
+ + (*contHist[1])[movedPiece][to_sq(move)]
+ + (*contHist[3])[movedPiece][to_sq(move)] - 3817;
+
+ // Decrease/increase reduction for moves with a good/bad history (~25 Elo)
+ r -= ss->statScore / 14767;
- // Step 20. Check for mate and stalemate
+ // Step 17. Late moves reduction / extension (LMR, ~117 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 >= 2 && moveCount > 1 + rootNode
+ && (!ss->ttPv || !capture || (cutNode && (ss - 1)->moveCount > 1)))
+ {
+ // In general we want to cap the LMR depth search at newDepth, but when
+ // reduction is negative, we allow this move a limited search extension
+ // beyond the first move depth. This may lead to hidden double extensions.
+ // To prevent problems when the max value is less than the min value,
+ // std::clamp has been replaced by a more robust implementation.
+ Depth d = std::max(1, std::min(newDepth - r, newDepth + 1));
+
+ value = -search<NonPV>(pos, ss + 1, -(alpha + 1), -alpha, d, true);
+
+ // Do a full-depth search when reduced LMR search fails high
+ if (value > alpha && d < newDepth)
+ {
+ // Adjust full-depth search based on LMR results - if the result
+ // was good enough search deeper, if it was bad enough search shallower.
+ const bool doDeeperSearch = value > (bestValue + 53 + 2 * newDepth); // (~1 Elo)
+ const bool doShallowerSearch = value < bestValue + newDepth; // (~2 Elo)
+
+ newDepth += doDeeperSearch - doShallowerSearch;
+
+ if (newDepth > d)
+ value = -search<NonPV>(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode);
+
+ int bonus = value <= alpha ? -stat_malus(newDepth)
+ : value >= beta ? stat_bonus(newDepth)
+ : 0;
+
+ update_continuation_histories(ss, movedPiece, to_sq(move), bonus);
+ }
+ }
+
+ // Step 18. Full-depth search when LMR is skipped
+ else if (!PvNode || moveCount > 1)
+ {
+ // Increase reduction if ttMove is not present (~1 Elo)
+ if (!ttMove)
+ r += 2;
+
+ // Note that if expected reduction is high, we reduce search depth by 1 here
+ value = -search<NonPV>(pos, ss + 1, -(alpha + 1), -alpha, newDepth - (r > 3), !cutNode);
+ }
+
+ // For PV nodes only, do a full PV search on the first move or after a fail high,
+ // otherwise let the parent node fail low with value <= alpha and try another move.
+ if (PvNode && (moveCount == 1 || value > alpha))
+ {
+ (ss + 1)->pv = pv;
+ (ss + 1)->pv[0] = MOVE_NONE;
+
+ value = -search<PV>(pos, ss + 1, -beta, -alpha, newDepth, false);
+ }
+
+ // Step 19. Undo move
+ pos.undo_move(move);
+
+ assert(value > -VALUE_INFINITE && value < VALUE_INFINITE);
+
+ // Step 20. 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);
+
+ rm.averageScore =
+ rm.averageScore != -VALUE_INFINITE ? (2 * value + rm.averageScore) / 3 : value;
+
+ // PV move or new best move?
+ if (moveCount == 1 || value > alpha)
+ {
+ rm.score = rm.uciScore = value;
+ rm.selDepth = thisThread->selDepth;
+ rm.scoreLowerbound = rm.scoreUpperbound = false;
+
+ if (value >= beta)
+ {
+ rm.scoreLowerbound = true;
+ rm.uciScore = beta;
+ }
+ else if (value <= alpha)
+ {
+ rm.scoreUpperbound = true;
+ rm.uciScore = alpha;
+ }
+
+ 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. In MultiPV mode,
+ // we must take care to only do this for the first PV line.
+ if (moveCount > 1 && !thisThread->pvIdx)
+ ++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 (value >= beta)
+ {
+ ss->cutoffCnt += 1 + !ttMove;
+ assert(value >= beta); // Fail high
+ break;
+ }
+ else
+ {
+ // Reduce other moves if we have found at least one score improvement (~2 Elo)
+ if (depth > 2 && depth < 12 && beta < 13782 && value > -11541)
+ depth -= 2;
+
+ assert(depth > 0);
+ alpha = value; // Update alpha! Always alpha < beta
+ }
+ }
+ }
+
+ // If the move is worse than some previously searched move,
+ // remember it, to update its stats later.
+ if (move != bestMove && moveCount <= 32)
+ {
+ if (capture)
+ capturesSearched[captureCount++] = move;
+
+ else
+ quietsSearched[quietCount++] = move;
+ }
+ }
+
+ // Step 21. 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 || !inCheck || excludedMove || !MoveList<LEGAL>(pos).size());
+ assert(moveCount || !ss->inCheck || excludedMove || !MoveList<LEGAL>(pos).size());
if (!moveCount)
- bestValue = excludedMove ? alpha
- : inCheck ? mated_in(ss->ply) : VALUE_DRAW;
+ bestValue = excludedMove ? alpha : ss->inCheck ? mated_in(ss->ply) : VALUE_DRAW;
+ // If there is a move that produces search value greater than alpha we update the stats of searched moves
else if (bestMove)
- update_all_stats(pos, ss, bestMove, bestValue, beta, prevSq,
- quietsSearched, quietCount, capturesSearched, captureCount, depth);
+ 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));
+ else if (!priorCapture && prevSq != SQ_NONE)
+ {
+ int bonus = (depth > 6) + (PvNode || cutNode) + ((ss - 1)->statScore < -18782)
+ + ((ss - 1)->moveCount > 10);
+ update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq,
+ stat_bonus(depth) * bonus);
+ thisThread->mainHistory[~us][from_to((ss - 1)->currentMove)]
+ << stat_bonus(depth) * bonus / 2;
+ }
if (PvNode)
bestValue = std::min(bestValue, maxValue);
- if (!excludedMove)
- tte->save(posKey, value_to_tt(bestValue, ss->ply), ttPv,
- bestValue >= beta ? BOUND_LOWER :
- PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER,
+ // 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. (~7 Elo)
+ if (bestValue <= alpha)
+ ss->ttPv = ss->ttPv || ((ss - 1)->ttPv && depth > 3);
+
+ // 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 <NodeType NT>
- Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) {
+// Quiescence search function, which is called by the main search
+// function with zero depth, or recursively with further decreasing depth per call.
+// (~155 Elo)
+template<NodeType nodeType>
+Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) {
- constexpr bool PvNode = NT == PV;
+ 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];
+ // Check if we have an upcoming move that draws by repetition, or
+ // if the opponent had an alternative move earlier to this position.
+ if (alpha < VALUE_DRAW && pos.has_game_cycle(ss->ply))
+ {
+ alpha = value_draw(pos.this_thread());
+ if (alpha >= beta)
+ return alpha;
+ }
+
+ 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 ttHit, pvHit, inCheck, givesCheck, captureOrPromotion, evasionPrunable;
- int moveCount;
+ Key posKey;
+ Move ttMove, move, bestMove;
+ Depth ttDepth;
+ Value bestValue, value, ttValue, futilityValue, futilityBase;
+ bool pvHit, givesCheck, capture;
+ int moveCount;
+ Color us = pos.side_to_move();
+ // Step 1. Initialize node
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;
+ (ss + 1)->pv = pv;
+ ss->pv[0] = MOVE_NONE;
}
Thread* thisThread = pos.this_thread();
- (ss+1)->ply = ss->ply + 1;
- bestMove = MOVE_NONE;
- inCheck = pos.checkers();
- moveCount = 0;
+ bestMove = MOVE_NONE;
+ ss->inCheck = pos.checkers();
+ moveCount = 0;
+
+ // 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;
- // Check for an immediate draw or maximum ply reached
- if ( pos.is_draw(ss->ply)
- || ss->ply >= MAX_PLY)
- return (ss->ply >= MAX_PLY && !inCheck) ? evaluate(pos) : VALUE_DRAW;
+ // Step 2. Check for an immediate draw or maximum ply reached
+ if (pos.is_draw(ss->ply) || ss->ply >= MAX_PLY)
+ return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) : VALUE_DRAW;
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 = inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS
- : DEPTH_QS_NO_CHECKS;
- // Transposition table lookup
- posKey = pos.key();
- tte = TT.probe(posKey, ttHit);
- ttValue = ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE;
- ttMove = ttHit ? tte->move() : MOVE_NONE;
- pvHit = ttHit && tte->is_pv();
-
- if ( !PvNode
- && ttHit
- && tte->depth() >= ttDepth
- && ttValue != VALUE_NONE // Only in case of TT access race
- && (ttValue >= beta ? (tte->bound() & BOUND_LOWER)
- : (tte->bound() & BOUND_UPPER)))
+ // Decide the replacement and cutoff priority of the qsearch TT entries
+ ttDepth = ss->inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS : DEPTH_QS_NO_CHECKS;
+
+ // Step 3. 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();
+
+ // At non-PV nodes we check for an early TT cutoff
+ if (!PvNode && tte->depth() >= ttDepth
+ && ttValue != VALUE_NONE // Only in case of TT access race or if !ttHit
+ && (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER)))
return ttValue;
- // Evaluate the position statically
- if (inCheck)
- {
- ss->staticEval = VALUE_NONE;
+ // Step 4. Static evaluation of the position
+ if (ss->inCheck)
bestValue = futilityBase = -VALUE_INFINITE;
- }
else
{
- if (ttHit)
+ 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
+ // ttValue can be used as a better position evaluation (~13 Elo)
+ 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
ss->staticEval = bestValue =
- (ss-1)->currentMove != MOVE_NULL ? evaluate(pos)
- : -(ss-1)->staticEval + 2 * Eval::Tempo;
+ (ss - 1)->currentMove != MOVE_NULL ? evaluate(pos) : -(ss - 1)->staticEval;
// Stand pat. Return immediately if static value is at least beta
if (bestValue >= beta)
{
- if (!ttHit)
- tte->save(posKey, value_to_tt(bestValue, ss->ply), pvHit, BOUND_LOWER,
- DEPTH_NONE, MOVE_NONE, ss->staticEval);
+ 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)
+ if (bestValue > alpha)
alpha = bestValue;
- futilityBase = bestValue + 154;
+ futilityBase = ss->staticEval + 182;
}
- const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory,
- nullptr , (ss-4)->continuationHistory,
- nullptr , (ss-6)->continuationHistory };
+ const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory,
+ (ss - 2)->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 checks (only if depth >= DEPTH_QS_CHECKS) will
- // be generated.
- MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory,
- &thisThread->captureHistory,
- contHist,
- to_sq((ss-1)->currentMove));
-
- // Loop through the moves until no moves remain or a beta cutoff occurs
+ // queen promotions, and other checks (only if depth >= DEPTH_QS_CHECKS)
+ // will be generated.
+ Square prevSq = is_ok((ss - 1)->currentMove) ? to_sq((ss - 1)->currentMove) : SQ_NONE;
+ MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory,
+ contHist, &thisThread->pawnHistory);
+
+ int quietCheckEvasions = 0;
+
+ // Step 5. Loop through all pseudo-legal 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
- if ( !inCheck
- && !givesCheck
- && futilityBase > -VALUE_KNOWN_WIN
- && !pos.advanced_pawn_push(move))
- {
- assert(type_of(move) != ENPASSANT); // Due to !pos.advanced_pawn_push
-
- 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;
- }
- }
-
- // Detect non-capture evasions that are candidates to be pruned
- evasionPrunable = inCheck
- && (depth != 0 || moveCount > 2)
- && bestValue > VALUE_MATED_IN_MAX_PLY
- && !pos.capture(move);
-
- // Don't search moves with negative SEE values
- if ( (!inCheck || evasionPrunable) && !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[inCheck]
- [captureOrPromotion]
- [pos.moved_piece(move)]
- [to_sq(move)];
-
- // Make and search the move
- pos.do_move(move, st, givesCheck);
- value = -qsearch<NT>(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
- }
- }
+ assert(is_ok(move));
+
+ // Check for legality
+ if (!pos.legal(move))
+ continue;
+
+ givesCheck = pos.gives_check(move);
+ capture = pos.capture_stage(move);
+
+ moveCount++;
+
+ // Step 6. Pruning
+ if (bestValue > VALUE_TB_LOSS_IN_MAX_PLY && pos.non_pawn_material(us))
+ {
+ // Futility pruning and moveCount pruning (~10 Elo)
+ if (!givesCheck && to_sq(move) != prevSq && futilityBase > VALUE_TB_LOSS_IN_MAX_PLY
+ && type_of(move) != PROMOTION)
+ {
+ if (moveCount > 2)
+ continue;
+
+ futilityValue = futilityBase + PieceValue[pos.piece_on(to_sq(move))];
+
+ // If static eval + value of piece we are going to capture is much lower
+ // than alpha we can prune this move.
+ if (futilityValue <= alpha)
+ {
+ bestValue = std::max(bestValue, futilityValue);
+ continue;
+ }
+
+ // If static eval is much lower than alpha and move is not winning material
+ // we can prune this move.
+ if (futilityBase <= alpha && !pos.see_ge(move, VALUE_ZERO + 1))
+ {
+ bestValue = std::max(bestValue, futilityBase);
+ continue;
+ }
+
+ // If static exchange evaluation is much worse than what is needed to not
+ // fall below alpha we can prune this move.
+ if (futilityBase > alpha && !pos.see_ge(move, (alpha - futilityBase) * 4))
+ {
+ bestValue = alpha;
+ continue;
+ }
+ }
+
+ // We prune after the second quiet check evasion move, where being 'in check' is
+ // implicitly checked through the counter, and being a 'quiet move' apart from
+ // being a tt move is assumed after an increment because captures are pushed ahead.
+ if (quietCheckEvasions > 1)
+ break;
+
+ // Continuation history based pruning (~3 Elo)
+ if (!capture && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < 0
+ && (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < 0)
+ continue;
+
+ // Do not search moves with bad enough SEE values (~5 Elo)
+ if (!pos.see_ge(move, Value(-77)))
+ continue;
+ }
+
+ // Speculative prefetch as early as possible
+ prefetch(TT.first_entry(pos.key_after(move)));
+
+ // Update the current move
+ ss->currentMove = move;
+ ss->continuationHistory =
+ &thisThread
+ ->continuationHistory[ss->inCheck][capture][pos.moved_piece(move)][to_sq(move)];
+
+ quietCheckEvasions += !capture && ss->inCheck;
+
+ // Step 7. Make and search the move
+ pos.do_move(move, st, givesCheck);
+ value = -qsearch<nodeType>(pos, ss + 1, -beta, -alpha, depth - 1);
+ pos.undo_move(move);
+
+ assert(value > -VALUE_INFINITE && value < VALUE_INFINITE);
+
+ // Step 8. 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 (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
+ // Step 9. Check for mate
+ // All legal moves have been searched. A special case: if we're in check
// and no legal moves were found, it is checkmate.
- if (inCheck && bestValue == -VALUE_INFINITE)
- return mated_in(ss->ply); // Plies to mate from the root
+ if (ss->inCheck && bestValue == -VALUE_INFINITE)
+ {
+ assert(!MoveList<LEGAL>(pos).size());
+
+ return mated_in(ss->ply); // Plies to mate from the root
+ }
+
+ if (abs(bestValue) < VALUE_TB_WIN_IN_MAX_PLY)
+ bestValue = bestValue >= beta ? (3 * bestValue + beta) / 4 : bestValue;
+ // 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);
+ bestValue >= beta ? BOUND_LOWER : BOUND_UPPER, ttDepth, bestMove, ss->staticEval);
assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE);
return bestValue;
- }
-
+}
- // value_to_tt() adjusts a mate score from "plies to mate from the root" to
- // "plies to mate from the current position". Non-mate scores are unchanged.
- // The function is called before storing a value in the transposition table.
- Value value_to_tt(Value v, int ply) {
+// 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_MATE_IN_MAX_PLY ? v + ply
- : v <= VALUE_MATED_IN_MAX_PLY ? v - ply : v;
- }
+ 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 score
- // from the transposition table (which refers to the plies to mate/be mated
- // from current position) to "plies to mate/be mated from the root".
+// 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, to avoid potentially false mate or TB scores related to the 50 moves rule
+// and the graph history interaction, we return highest non-TB score instead.
- Value value_from_tt(Value v, int ply, int r50c) {
+Value value_from_tt(Value v, int ply, int r50c) {
- return v == VALUE_NONE ? VALUE_NONE
- : v >= VALUE_MATE_IN_MAX_PLY ? VALUE_MATE - v > 99 - r50c ? VALUE_MATE_IN_MAX_PLY : v - ply
- : v <= VALUE_MATED_IN_MAX_PLY ? VALUE_MATE + v > 99 - r50c ? VALUE_MATED_IN_MAX_PLY : v + ply : v;
- }
+ if (v == VALUE_NONE)
+ return VALUE_NONE;
+ // handle TB win or better
+ if (v >= VALUE_TB_WIN_IN_MAX_PLY)
+ {
+ // Downgrade a potentially false mate score
+ if (v >= VALUE_MATE_IN_MAX_PLY && VALUE_MATE - v > 100 - r50c)
+ return VALUE_TB_WIN_IN_MAX_PLY - 1;
- // update_pv() adds current move and appends child pv[]
+ // Downgrade a potentially false TB score.
+ if (VALUE_TB - v > 100 - r50c)
+ return VALUE_TB_WIN_IN_MAX_PLY - 1;
- void update_pv(Move* pv, Move move, Move* childPv) {
+ return v - ply;
+ }
- for (*pv++ = move; childPv && *childPv != MOVE_NONE; )
- *pv++ = *childPv++;
- *pv = MOVE_NONE;
- }
+ // handle TB loss or worse
+ if (v <= VALUE_TB_LOSS_IN_MAX_PLY)
+ {
+ // Downgrade a potentially false mate score.
+ if (v <= VALUE_MATED_IN_MAX_PLY && VALUE_MATE + v > 100 - r50c)
+ return VALUE_TB_LOSS_IN_MAX_PLY + 1;
+
+ // Downgrade a potentially false TB score.
+ if (VALUE_TB + v > 100 - r50c)
+ return VALUE_TB_LOSS_IN_MAX_PLY + 1;
+
+ return v + ply;
+ }
+ return v;
+}
- // 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) {
+// Adds current move and appends child pv[]
+void update_pv(Move* pv, Move move, const Move* childPv) {
- int bonus1, bonus2;
- Color us = pos.side_to_move();
- Thread* thisThread = pos.this_thread();
+ for (*pv++ = move; childPv && *childPv != MOVE_NONE;)
+ *pv++ = *childPv++;
+ *pv = MOVE_NONE;
+}
+
+
+// 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) {
+
+ 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)));
+ Piece moved_piece = pos.moved_piece(bestMove);
+ PieceType captured;
- bonus1 = stat_bonus(depth + 1);
- bonus2 = bestValue > beta + PawnValueMg ? bonus1 // larger bonus
- : stat_bonus(depth); // smaller bonus
+ int quietMoveBonus = stat_bonus(depth + 1);
+ int quietMoveMalus = stat_malus(depth);
- if (!pos.capture_or_promotion(bestMove))
+ if (!pos.capture_stage(bestMove))
{
- update_quiet_stats(pos, ss, bestMove, bonus2);
+ int bestMoveBonus = bestValue > beta + 173 ? quietMoveBonus // larger bonus
+ : stat_bonus(depth); // smaller bonus
- // Decrease all the non-best quiet moves
+ // Increase stats for the best move in case it was a quiet move
+ update_quiet_stats(pos, ss, bestMove, bestMoveBonus);
+ thisThread->pawnHistory[pawn_structure(pos)][moved_piece][to_sq(bestMove)]
+ << quietMoveBonus;
+
+ // Decrease stats for all non-best quiet moves
for (int i = 0; i < quietCount; ++i)
{
- thisThread->mainHistory[us][from_to(quietsSearched[i])] << -bonus2;
- update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), to_sq(quietsSearched[i]), -bonus2);
+ thisThread->pawnHistory[pawn_structure(pos)][pos.moved_piece(quietsSearched[i])]
+ [to_sq(quietsSearched[i])]
+ << -quietMoveMalus;
+ thisThread->mainHistory[us][from_to(quietsSearched[i])] << -quietMoveMalus;
+ update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]),
+ to_sq(quietsSearched[i]), -quietMoveMalus);
}
}
else
- captureHistory[moved_piece][to_sq(bestMove)][captured] << bonus1;
+ {
+ // Increase stats for the best move in case it was a capture move
+ captured = type_of(pos.piece_on(to_sq(bestMove)));
+ captureHistory[moved_piece][to_sq(bestMove)][captured] << quietMoveBonus;
+ }
- // Extra penalty for a quiet TT or main killer move in previous ply when it gets refuted
- if ( ((ss-1)->moveCount == 1 || ((ss-1)->currentMove == (ss-1)->killers[0]))
+ // 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 (prevSq != SQ_NONE
+ && ((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);
+ update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -quietMoveMalus);
- // Decrease all the non-best capture moves
+ // 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])));
- captureHistory[moved_piece][to_sq(capturesSearched[i])][captured] << -bonus1;
+ captured = type_of(pos.piece_on(to_sq(capturesSearched[i])));
+ captureHistory[moved_piece][to_sq(capturesSearched[i])][captured] << -quietMoveMalus;
}
- }
-
-
- // update_continuation_histories() updates histories of the move pairs formed
- // by moves at ply -1, -2, and -4 with current move.
+}
- void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) {
- for (int i : {1, 2, 4, 6})
- if (is_ok((ss-i)->currentMove))
- (*(ss-i)->continuationHistory)[pc][to] << bonus;
- }
+// Updates histories of the move pairs formed
+// by moves at ply -1, -2, -3, -4, and -6 with current move.
+void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) {
+ for (int i : {1, 2, 3, 4, 6})
+ {
+ // Only update the first 2 continuation histories if we are in check
+ if (ss->inCheck && i > 2)
+ break;
+ if (is_ok((ss - i)->currentMove))
+ (*(ss - i)->continuationHistory)[pc][to] << bonus / (1 + 3 * (i == 3));
+ }
+}
- // update_quiet_stats() updates move sorting heuristics
- void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus) {
+// Updates move sorting heuristics
+void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus) {
+ // Update killers
if (ss->killers[0] != move)
{
ss->killers[1] = ss->killers[0];
ss->killers[0] = move;
}
- Color us = pos.side_to_move();
+ Color us = pos.side_to_move();
Thread* thisThread = pos.this_thread();
thisThread->mainHistory[us][from_to(move)] << bonus;
update_continuation_histories(ss, pos.moved_piece(move), to_sq(move), bonus);
- if (type_of(pos.moved_piece(move)) != PAWN)
- thisThread->mainHistory[us][from_to(reverse_move(move))] << -bonus;
-
- if (is_ok((ss-1)->currentMove))
+ // Update countermove history
+ if (is_ok((ss - 1)->currentMove))
{
- Square prevSq = to_sq((ss-1)->currentMove);
+ Square prevSq = to_sq((ss - 1)->currentMove);
thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] = move;
}
- }
-
- // 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) {
+// When playing with strength handicap, choose the 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
+ 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;
+ Value topScore = rootMoves[0].score;
+ int delta = std::min(topScore - rootMoves[multiPV - 1].score, PawnValue);
+ int maxScore = -VALUE_INFINITE;
+ double weakness = 120 - 2 * level;
// 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
for (size_t i = 0; i < multiPV; ++i)
{
// This is our magic formula
- int push = ( weakness * int(topScore - rootMoves[i].score)
- + delta * (rng.rand<unsigned>() % weakness)) / 128;
+ int push = int((weakness * int(topScore - rootMoves[i].score)
+ + delta * (rng.rand<unsigned>() % int(weakness)))
+ / 128);
if (rootMoves[i].score + push >= maxScore)
{
maxScore = rootMoves[i].score + push;
- best = rootMoves[i].pv[0];
+ best = rootMoves[i].pv[0];
}
}
return best;
- }
+}
-} // namespace
+} // 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.
+// 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;
+ 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;
+ // When using nodes, ensure checking rate is not lower than 0.1% of nodes
+ callsCnt = Limits.nodes ? std::min(512, int(Limits.nodes / 1024)) : 512;
- static TimePoint lastInfoTime = now();
+ static TimePoint lastInfoTime = now();
- TimePoint elapsed = Time.elapsed();
- TimePoint tick = Limits.startTime + elapsed;
+ TimePoint elapsed = Time.elapsed();
+ TimePoint tick = Limits.startTime + elapsed;
- if (tick - lastInfoTime >= 1000)
- {
- lastInfoTime = tick;
- dbg_print();
- }
+ if (tick - lastInfoTime >= 1000)
+ {
+ lastInfoTime = tick;
+ dbg_print();
+ }
- // We should not stop pondering until told so by the GUI
- if (ponder)
- return;
+ // We should not stop pondering until told so by the GUI
+ if (ponder)
+ 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;
+ if ((Limits.use_time_management() && (elapsed > Time.maximum() || 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) {
+// 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) {
- 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);
+ 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;
+ for (size_t i = 0; i < multiPV; ++i)
+ {
+ bool updated = rootMoves[i].score != -VALUE_INFINITE;
- if (depth == 1 && !updated)
- continue;
+ if (depth == 1 && !updated && i > 0)
+ continue;
- Depth d = updated ? depth : depth - 1;
- Value v = updated ? rootMoves[i].score : rootMoves[i].previousScore;
+ Depth d = updated ? depth : std::max(1, depth - 1);
+ Value v = updated ? rootMoves[i].uciScore : rootMoves[i].previousScore;
- bool tb = TB::RootInTB && abs(v) < VALUE_MATE - MAX_PLY;
- v = tb ? rootMoves[i].tbScore : v;
+ if (v == -VALUE_INFINITE)
+ v = VALUE_ZERO;
- if (ss.rdbuf()->in_avail()) // Not at first line
- ss << "\n";
+ bool tb = TB::RootInTB && std::abs(v) <= VALUE_TB;
+ v = tb ? rootMoves[i].tbScore : v;
- ss << "info"
- << " depth " << d
- << " seldepth " << rootMoves[i].selDepth
- << " multipv " << i + 1
- << " score " << UCI::value(v);
+ if (ss.rdbuf()->in_avail()) // Not at first line
+ ss << "\n";
- if (!tb && i == pvIdx)
- ss << (v >= beta ? " lowerbound" : v <= alpha ? " upperbound" : "");
+ ss << "info"
+ << " depth " << d << " seldepth " << rootMoves[i].selDepth << " multipv " << i + 1
+ << " score " << UCI::value(v);
- ss << " nodes " << nodesSearched
- << " nps " << nodesSearched * 1000 / elapsed;
+ if (Options["UCI_ShowWDL"])
+ ss << UCI::wdl(v, pos.game_ply());
- if (elapsed > 1000) // Earlier makes little sense
- ss << " hashfull " << TT.hashfull();
+ if (i == pvIdx && !tb && updated) // tablebase- and previous-scores are exact
+ ss << (rootMoves[i].scoreLowerbound
+ ? " lowerbound"
+ : (rootMoves[i].scoreUpperbound ? " upperbound" : ""));
- ss << " tbhits " << tbHits
- << " time " << elapsed
- << " pv";
+ ss << " nodes " << nodesSearched << " nps " << nodesSearched * 1000 / elapsed
+ << " hashfull " << TT.hashfull() << " tbhits " << tbHits << " time " << elapsed << " pv";
- for (Move m : rootMoves[i].pv)
- ss << " " << UCI::move(m, pos.is_chess960());
- }
+ for (Move m : rootMoves[i].pv)
+ ss << " " << UCI::move(m, pos.is_chess960());
+ }
- return ss.str();
+ 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.
-
+// 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 about.
bool RootMove::extract_ponder_from_tt(Position& pos) {
StateInfo st;
+ ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize);
+
bool ttHit;
assert(pv.size() == 1);
if (ttHit)
{
- Move m = tte->move(); // Local copy to be SMP safe
+ Move m = tte->move(); // Local copy to be SMP safe
if (MoveList<LEGAL>(pos).contains(m))
pv.push_back(m);
}
void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) {
- RootInTB = false;
- UseRule50 = bool(Options["Syzygy50MoveRule"]);
- ProbeDepth = int(Options["SyzygyProbeDepth"]);
- Cardinality = int(Options["SyzygyProbeLimit"]);
+ 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
if (Cardinality > MaxCardinality)
{
Cardinality = MaxCardinality;
- ProbeDepth = 0;
+ ProbeDepth = 0;
}
if (Cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING))
{
// DTZ tables are missing; try to rank moves using WDL tables
dtz_available = false;
- RootInTB = root_probe_wdl(pos, rootMoves);
+ RootInTB = root_probe_wdl(pos, rootMoves);
}
}
if (RootInTB)
{
// Sort moves according to TB rank
- std::sort(rootMoves.begin(), rootMoves.end(),
- [](const RootMove &a, const RootMove &b) { return a.tbRank > b.tbRank; } );
+ 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)
m.tbRank = 0;
}
}
+
+} // namespace Stockfish
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+ Copyright (C) 2004-2023 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
#ifndef SEARCH_H_INCLUDED
#define SEARCH_H_INCLUDED
+#include <cstdint>
#include <vector>
#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.
+// 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;
+ 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;
+ int cutoffCnt;
};
-/// 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.
-
+// 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;
- int bestMoveCount = 0;
- Value tbScore;
- std::vector<Move> pv;
+ explicit RootMove(Move m) :
+ pv(1, m) {}
+ bool extract_ponder_from_tt(Position& pos);
+ bool operator==(const Move& m) const { return pv[0] == m; }
+ // Sort in descending order
+ bool operator<(const RootMove& m) const {
+ return m.score != score ? m.score < score : m.previousScore < previousScore;
+ }
+
+ Value score = -VALUE_INFINITE;
+ Value previousScore = -VALUE_INFINITE;
+ Value averageScore = -VALUE_INFINITE;
+ Value uciScore = -VALUE_INFINITE;
+ bool scoreLowerbound = false;
+ bool scoreUpperbound = false;
+ int selDepth = 0;
+ int tbRank = 0;
+ Value tbScore;
+ std::vector<Move> pv;
};
-typedef std::vector<RootMove> RootMoves;
+using RootMoves = std::vector<RootMove>;
-/// 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.
+// 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;
- }
+ // Init explicitly due to broken value-initialization of non POD in MSVC
+ LimitsType() {
+ 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 !(mate | movetime | depth | nodes | perft | infinite);
- }
+ bool use_time_management() const { return time[WHITE] || time[BLACK]; }
- std::vector<Move> searchmoves;
- TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime;
- int movestogo, depth, mate, perft, infinite;
- int64_t nodes;
+ std::vector<Move> searchmoves;
+ 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 Search
+
+} // namespace Stockfish
-#endif // #ifndef SEARCH_H_INCLUDED
+#endif // #ifndef SEARCH_H_INCLUDED
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (c) 2013 Ronald de Man
- Copyright (C) 2016-2020 Marco Costalba, Lucas Braesch
+ Copyright (C) 2004-2023 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
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "tbprobe.h"
+
+#include <sys/stat.h>
#include <algorithm>
#include <atomic>
+#include <cassert>
#include <cstdint>
-#include <cstring> // For std::memset and std::memcpy
+#include <cstdlib>
+#include <cstring>
#include <deque>
#include <fstream>
+#include <initializer_list>
#include <iostream>
-#include <list>
+#include <mutex>
#include <sstream>
+#include <string_view>
#include <type_traits>
-#include <mutex>
+#include <utility>
+#include <vector>
#include "../bitboard.h"
+#include "../misc.h"
#include "../movegen.h"
#include "../position.h"
#include "../search.h"
#include "../types.h"
#include "../uci.h"
-#include "tbprobe.h"
-
#ifndef _WIN32
-#include <fcntl.h>
-#include <unistd.h>
-#include <sys/mman.h>
-#include <sys/stat.h>
+ #include <fcntl.h>
+ #include <sys/mman.h>
+ #include <unistd.h>
#else
-#define WIN32_LEAN_AND_MEAN
-#ifndef NOMINMAX
-# define NOMINMAX // Disable macros min() and max()
-#endif
-#include <windows.h>
+ #define WIN32_LEAN_AND_MEAN
+ #ifndef NOMINMAX
+ #define NOMINMAX // Disable macros min() and max()
+ #endif
+ #include <windows.h>
#endif
-using namespace Tablebases;
+using namespace Stockfish::Tablebases;
+
+int Stockfish::Tablebases::MaxCardinality;
-int Tablebases::MaxCardinality;
+namespace Stockfish {
namespace {
-constexpr int TBPIECES = 7; // Max number of supported pieces
+constexpr int TBPIECES = 7; // Max number of supported pieces
+constexpr int MAX_DTZ =
+ 1 << 18; // Max DTZ supported, large enough to deal with the syzygy TB limit.
-enum { BigEndian, LittleEndian };
-enum TBType { KEY, WDL, DTZ }; // Used as template parameter
+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 };
+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 s = Square(int(s) ^ i); }
-inline Square operator^(Square s, int i) { return Square(int(s) ^ i); }
+inline Square operator^(Square s, int i) { return Square(int(s) ^ i); }
-const std::string PieceToChar = " PNBRQK pnbrqk";
+constexpr std::string_view PieceToChar = " PNBRQK pnbrqk";
int MapPawns[SQUARE_NB];
int MapB1H1H7[SQUARE_NB];
int MapA1D1D4[SQUARE_NB];
-int MapKK[10][SQUARE_NB]; // [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]
+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
-};
+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<typename T, int Half = sizeof(T) / 2, int End = sizeof(T) - 1>
-inline void swap_endian(T& x)
-{
- static_assert(std::is_unsigned<T>::value, "Argument of swap_endian not unsigned");
+inline void swap_endian(T& x) {
+ static_assert(std::is_unsigned_v<T>, "Argument of swap_endian not unsigned");
- uint8_t tmp, *c = (uint8_t*)&x;
+ 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>(uint8_t&) {}
-
-template<typename T, int LE> T number(void* addr)
-{
- static const union { uint32_t i; char c[4]; } Le = { 0x01020304 };
- static const bool IsLittleEndian = (Le.c[0] == 4);
+template<>
+inline void swap_endian<uint8_t>(uint8_t&) {}
+template<typename T, int LE>
+T number(void* addr) {
T v;
- if ((uintptr_t)addr & (alignof(T) - 1)) // Unaligned pointer (very rare)
+ if (uintptr_t(addr) & (alignof(T) - 1)) // Unaligned pointer (very rare)
std::memcpy(&v, addr, sizeof(T));
else
- v = *((T*)addr);
+ v = *((T*) addr);
if (LE != IsLittleEndian)
swap_endian(v);
// 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 wdl == WDLWin ? 1
+ : wdl == WDLCursedWin ? 101
+ : wdl == WDLBlessedLoss ? -101
+ : wdl == WDLLoss ? -1
+ : 0;
}
// Return the sign of a number (-1, 0, 1)
-template <typename T> int sign_of(T val) {
+template<typename T>
+int sign_of(T val) {
return (T(0) < val) - (val < T(0));
}
-// Numbers in little endian used by sparseIndex[] to point into blockLength[]
+// 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
+using Sym = uint16_t; // 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.
+ 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 the symbol has length 1,
+ // then the left-hand symbol is the stored value.
template<Side S>
Sym get() {
- return S == Left ? ((lr[1] & 0xF) << 8) | lr[0] :
- S == Right ? (lr[2] << 4) | (lr[1] >> 4) : (assert(false), Sym(-1));
+ return S == Left ? ((lr[1] & 0xF) << 8) | lr[0]
+ : S == Right ? (lr[2] << 4) | (lr[1] >> 4)
+ : (assert(false), Sym(-1));
}
};
// 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 {
+class TBFile: public std::ifstream {
std::string fname;
-public:
+ 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.
constexpr char SepChar = ';';
#endif
std::stringstream ss(Paths);
- std::string path;
+ std::string path;
- while (std::getline(ss, path, SepChar)) {
+ while (std::getline(ss, path, SepChar))
+ {
fname = path + "/" + f;
std::ifstream::open(fname);
if (is_open())
}
}
- // Memory map the file and check it. File should be already open and will be
- // closed after mapping.
+ // Memory map the file and check it.
uint8_t* map(void** baseAddress, uint64_t* mapping, TBType type) {
-
- assert(is_open());
-
- close(); // Need to re-open to get native file descriptor
+ if (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);
+ int fd = ::open(fname.c_str(), O_RDONLY);
if (fd == -1)
return *baseAddress = nullptr, nullptr;
exit(EXIT_FAILURE);
}
- *mapping = statbuf.st_size;
+ *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)
}
#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);
+ HANDLE fd = CreateFileA(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;
exit(EXIT_FAILURE);
}
- *mapping = (uint64_t)mmap;
+ *mapping = uint64_t(mmap);
*baseAddress = MapViewOfFile(mmap, FILE_MAP_READ, 0, 0, 0);
if (!*baseAddress)
exit(EXIT_FAILURE);
}
#endif
- uint8_t* data = (uint8_t*)*baseAddress;
+ uint8_t* data = (uint8_t*) *baseAddress;
- constexpr uint8_t Magics[][4] = { { 0xD7, 0x66, 0x0C, 0xA5 },
- { 0x71, 0xE8, 0x23, 0x5D } };
+ constexpr uint8_t Magics[][4] = {{0xD7, 0x66, 0x0C, 0xA5}, {0x71, 0xE8, 0x23, 0x5D}};
if (memcmp(data, Magics[type == WDL], 4))
{
return *baseAddress = nullptr, nullptr;
}
- return data + 4; // Skip Magics's header
+ return data + 4; // Skip Magics's header
}
static void unmap(void* baseAddress, uint64_t mapping) {
munmap(baseAddress, mapping);
#else
UnmapViewOfFile(baseAddress);
- CloseHandle((HANDLE)mapping);
+ 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 contains low-level indexing information to access TB data.
+// There are 8, 4, or 2 PairsData records for each TBTable, according to the 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<uint64_t> base64; // base64[l - min_sym_len] is the 64bit-padded lowest symbol of length l
- std::vector<uint8_t> 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)
+ 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<uint64_t>
+ base64; // base64[l - min_sym_len] is the 64bit-padded lowest symbol of length l
+ std::vector<uint8_t>
+ 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.
// first access, when the corresponding file is memory mapped.
template<TBType Type>
struct TBTable {
- typedef typename std::conditional<Type == WDL, WDLScore, int>::type Ret;
+ using Ret = std::conditional_t<Type == WDL, WDLScore, int>;
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) {}
+ 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>& wdl);
};
template<>
-TBTable<WDL>::TBTable(const std::string& code) : TBTable() {
+TBTable<WDL>::TBTable(const std::string& code) :
+ TBTable() {
StateInfo st;
- Position pos;
+ Position pos;
- key = pos.set(code, WHITE, &st).material_key();
+ key = pos.set(code, WHITE, &st).material_key();
pieceCount = pos.count<ALL_PIECES>();
- hasPawns = pos.pieces(PAWN);
+ hasPawns = pos.pieces(PAWN);
hasUniquePieces = false;
- for (Color c : { WHITE, BLACK })
+ 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<PAWN>(BLACK)
- || ( pos.count<PAWN>(WHITE)
- && pos.count<PAWN>(BLACK) >= pos.count<PAWN>(WHITE));
+ // is the side with fewer pawns because this leads to better compression.
+ bool c = !pos.count<PAWN>(BLACK)
+ || (pos.count<PAWN>(WHITE) && pos.count<PAWN>(BLACK) >= pos.count<PAWN>(WHITE));
pawnCount[0] = pos.count<PAWN>(c ? WHITE : BLACK);
pawnCount[1] = pos.count<PAWN>(c ? BLACK : WHITE);
}
template<>
-TBTable<DTZ>::TBTable(const TBTable<WDL>& wdl) : TBTable() {
+TBTable<DTZ>::TBTable(const TBTable<WDL>& 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;
+ 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];
+ 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
+// each TB file found. It supports a fast, hash-based, table lookup. Populated
// at init time, accessed at probe time.
class TBTables {
- typedef std::tuple<Key, TBTable<WDL>*, TBTable<DTZ>*> Entry;
+ struct Entry {
+ Key key;
+ TBTable<WDL>* wdl;
+ TBTable<DTZ>* dtz;
+
+ template<TBType Type>
+ TBTable<Type>* get() const {
+ return (TBTable<Type>*) (Type == WDL ? (void*) wdl : (void*) dtz);
+ }
+ };
- static constexpr int Size = 1 << 12; // 4K table, indexed by key's 12 lsb
+ 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<TBTable<DTZ>> dtzTable;
void insert(Key key, TBTable<WDL>* wdl, TBTable<DTZ>* dtz) {
- uint32_t homeBucket = (uint32_t)key & (Size - 1);
- Entry entry = std::make_tuple(key, wdl, 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 = std::get<KEY>(hashTable[bucket]);
- if (otherKey == key || !std::get<WDL>(hashTable[bucket])) {
+ for (uint32_t bucket = homeBucket; bucket < Size + Overflow - 1; ++bucket)
+ {
+ Key otherKey = hashTable[bucket].key;
+ if (otherKey == key || !hashTable[bucket].get<WDL>())
+ {
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) {
- swap(entry, hashTable[bucket]);
- key = otherKey;
+ uint32_t otherHomeBucket = uint32_t(otherKey) & (Size - 1);
+ if (otherHomeBucket > homeBucket)
+ {
+ std::swap(entry, hashTable[bucket]);
+ key = otherKey;
homeBucket = otherHomeBucket;
}
}
exit(EXIT_FAILURE);
}
-public:
+ public:
template<TBType Type>
TBTable<Type>* get(Key key) {
- for (const Entry* entry = &hashTable[(uint32_t)key & (Size - 1)]; ; ++entry) {
- if (std::get<KEY>(*entry) == key || !std::get<Type>(*entry))
- return std::get<Type>(*entry);
+ for (const Entry* entry = &hashTable[uint32_t(key) & (Size - 1)];; ++entry)
+ {
+ if (entry->key == key || !entry->get<Type>())
+ return entry->get<Type>();
}
}
dtzTable.clear();
}
size_t size() const { return wdlTable.size(); }
- void add(const std::vector<PieceType>& pieces);
+ void add(const std::vector<PieceType>& pieces);
};
TBTables TBTables;
for (PieceType pt : pieces)
code += PieceToChar[pt];
- TBFile file(code.insert(code.find('K', 1), "v") + ".rtbw"); // KRK -> KRvK
+ TBFile file(code.insert(code.find('K', 1), "v") + ".rtbw"); // KRK -> KRvK
- if (!file.is_open()) // Only WDL file is checked
+ if (!file.is_open()) // Only WDL file is checked
return;
file.close();
- MaxCardinality = std::max((int)pieces.size(), MaxCardinality);
+ 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().key, &wdlTable.back(), &dtzTable.back());
insert(wdlTable.back().key2, &wdlTable.back(), &dtzTable.back());
}
// 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
+// Huffman codes are 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.
+// 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
// 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 = idx / d->span;
+ uint32_t k = uint32_t(idx / d->span);
// Then we read the corresponding SparseIndex[] entry
- uint32_t block = number<uint32_t, LittleEndian>(&d->sparseIndex[k].block);
- int offset = number<uint16_t, LittleEndian>(&d->sparseIndex[k].offset);
+ uint32_t block = number<uint32_t, LittleEndian>(&d->sparseIndex[k].block);
+ int offset = number<uint16_t, LittleEndian>(&d->sparseIndex[k].offset);
- // Now compute the difference idx - I(k). From definition of k we know that
+ // Now compute the difference idx - I(k). From the definition of k, we know that
//
// idx = k * d->span + idx % 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,
+ // Move to the 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;
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));
+ 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<uint64_t, BigEndian>(ptr); ptr += 2;
+ // is at the beginning of this 64-bit sequence.
+ uint64_t buf64 = number<uint64_t, BigEndian>(ptr);
+ ptr += 2;
int buf64Size = 64;
Sym sym;
- while (true) {
- int len = 0; // This is the symbol length - d->min_sym_len
+ 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
// 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 = (buf64 - d->base64[len]) >> (64 - len - d->minSymLen);
+ 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<Sym, LittleEndian>(&d->lowestSym[len]);
- // If our offset is within the number of values represented by symbol sym
- // we are done...
+ // 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
+ len += d->minSymLen; // Get the real length
+ buf64 <<= len; // Consume the just processed symbol
buf64Size -= len;
- if (buf64Size <= 32) { // Refill the buffer
+ if (buf64Size <= 32)
+ { // Refill the buffer
buf64Size += 32;
- buf64 |= (uint64_t)number<uint32_t, BigEndian>(ptr++) << (64 - buf64Size);
+ buf64 |= uint64_t(number<uint32_t, BigEndian>(ptr++)) << (64 - buf64Size);
}
}
- // Ok, now we have our symbol that expands into d->symlen[sym] + 1 symbols.
+ // 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]) {
-
+ while (d->symlen[sym])
+ {
Sym left = d->btree[sym].get<LR::Left>();
// 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
+ // we know that, for instance, the tenth 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 {
+ else
+ {
offset -= d->symlen[left] + 1;
sym = d->btree[sym].get<LR::Right>();
}
bool check_dtz_stm(TBTable<DTZ>* entry, int stm, File f) {
auto flags = entry->get(stm, f)->flags;
- return (flags & TBFlag::STM) == stm
- || ((entry->key == entry->key2) && !entry->hasPawns);
+ 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.
+// the original values are stored in the TB file and read during map[] init.
WDLScore map_score(TBTable<WDL>*, File, int value, WDLScore) { return WDLScore(value - 2); }
int map_score(TBTable<DTZ>* entry, File f, int value, WDLScore wdl) {
- constexpr int WDLMap[] = { 1, 3, 0, 2, 0 };
+ constexpr int WDLMap[] = {1, 3, 0, 2, 0};
auto flags = entry->get(0, f)->flags;
- uint8_t* map = entry->map;
+ uint8_t* map = entry->map;
uint16_t* idx = entry->get(0, f)->map_idx;
- if (flags & TBFlag::Mapped) {
+ if (flags & TBFlag::Mapped)
+ {
if (flags & TBFlag::Wide)
- value = ((uint16_t *)map)[idx[WDLMap[wdl + 2]] + value];
+ 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)
+ // want to return plies, so we have to 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;
}
+// A temporary fix for the compiler bug with AVX-512. (#4450)
+#ifdef USE_AVX512
+ #if defined(__clang__) && defined(__clang_major__) && __clang_major__ >= 15
+ #define CLANG_AVX512_BUG_FIX __attribute__((optnone))
+ #endif
+#endif
+
+#ifndef CLANG_AVX512_BUG_FIX
+ #define CLANG_AVX512_BUG_FIX
+#endif
+
// 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
+// encode k pieces of the 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<typename T, typename Ret = typename T::Ret>
-Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result) {
+CLANG_AVX512_BUG_FIX 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;
+ 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;
+ 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
+ // only stores 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
+ // TB files are calculated for white as the stronger side. For instance, we
+ // have KRvK, not KvKR. A position where the 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);
// 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) {
+ 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.
leadPawns = b = pos.pieces(color_of(pc), PAWN);
do
- squares[size++] = pop_lsb(&b) ^ flipSquares;
+ squares[size++] = pop_lsb(b) ^ flipSquares;
while (b);
leadPawnsCnt = size;
std::swap(squares[0], *std::max_element(squares, squares + leadPawnsCnt, pawns_comp));
- tbFile = map_to_queenside(file_of(squares[0]));
+ tbFile = File(edge_distance(file_of(squares[0])));
}
// DTZ tables are one-sided, i.e. they store positions only for white to
// 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;
+ do
+ {
+ Square s = pop_lsb(b);
+ squares[size] = s ^ flipSquares;
pieces[size++] = Piece(pos.piece_on(s) ^ flipColor);
} while (b);
// the triangle A1-D1-D4.
if (file_of(squares[0]) > FILE_D)
for (int i = 0; i < size; ++i)
- squares[i] ^= 7; // Horizontal flip: SQ_H1 -> SQ_A1
+ squares[i] = flip_file(squares[i]);
// Encode leading pawns starting with the one with minimum MapPawns[] and
// proceeding in ascending order.
- if (entry->hasPawns) {
+ if (entry->hasPawns)
+ {
idx = LeadPawnIdx[leadPawnsCnt][squares[0]];
- std::sort(squares + 1, squares + leadPawnsCnt, pawns_comp);
+ 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
+ goto encode_remaining; // With pawns we have finished special treatments
}
- // In positions withouth pawns, we further flip the squares to ensure leading
+ // 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] ^= SQ_A8; // Vertical flip: SQ_A8 -> SQ_A1
+ 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) {
+ 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_C3
+ 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;
// 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 (inlcuded kings) we encode them
+ // In case we have at least 3 unique pieces (including kings) we encode them
// together.
- if (entry->hasUniquePieces) {
+ if (entry->hasUniquePieces)
+ {
- int adjust1 = squares[1] > squares[0];
+ 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 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;
+ idx = (MapA1D1D4[squares[0]] * 63 + (squares[1] - adjust1)) * 62 + squares[2] - adjust2;
- // First piece is on a1-h8 diagonal, second below: map this occurence to
+ // 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;
+ 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]];
+ 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
+ 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]];
idx *= d->groupIdx[0];
Square* groupSq = squares + d->groupLen[0];
- // Encode remainig pawns then pieces according to square, in ascending order
+ // Encode remaining pawns and then pieces according to square, in ascending order
bool remainingPawns = entry->hasPawns && entry->pawnCount[1];
while (d->groupLen[++next])
{
- std::sort(groupSq, groupSq + 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).
+ // groups (similar to what was done earlier for leading group pieces).
for (int i = 0; i < d->groupLen[next]; ++i)
{
- auto f = [&](Square s) { return groupSq[i] > s; };
+ 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];
}
}
// 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 withouth pawns, can be formed by 3 different
+// a group contains pieces of the 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[].
//
else
d->groupLen[++n] = 1;
- d->groupLen[++n] = 0; // Zero-terminated
+ 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
//
// 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 -> remainig pawns -> remaining pieces. In particular the
+ // 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;
+ 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
+ if (k == order[0]) // Leading pawns or pieces
{
d->groupIdx[0] = idx;
- idx *= e.hasPawns ? LeadPawnsSize[d->groupLen[0]][f]
- : e.hasUniquePieces ? 31332 : 462;
+ idx *= e.hasPawns ? LeadPawnsSize[d->groupLen[0]][f] : e.hasUniquePieces ? 31332 : 462;
}
- else if (k == order[1]) // Remaining pawns
+ else if (k == order[1]) // Remaining pawns
{
d->groupIdx[1] = idx;
idx *= Binomial[d->groupLen[1]][48 - d->groupLen[0]];
}
- else // Remainig pieces
+ else // Remaining pieces
{
d->groupIdx[next] = idx;
idx *= Binomial[d->groupLen[next]][freeSquares];
d->groupIdx[n] = idx;
}
-// In Recursive Pairing each symbol represents a pair of childern symbols. So
+// 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.
+// symbol until reaching the leaves that represent the symbol value.
uint8_t set_symlen(PairsData* d, Sym s, std::vector<bool>& visited) {
- visited[s] = true; // We can set it now because tree is acyclic
- Sym sr = d->btree[s].get<LR::Right>();
+ visited[s] = true; // We can set it now because tree is acyclic
+ Sym sr = d->btree[s].get<LR::Right>();
if (sr == 0xFFF)
return 0;
d->flags = *data++;
- if (d->flags & TBFlag::SingleValue) {
+ 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
+ d->span = d->sparseIndexSize = 0; // Broken MSVC zero-init
+ d->minSymLen = *data++; // Here we store the single value
return data;
}
// 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 = (tbSize + d->span - 1) / d->span; // Round up
- auto padding = number<uint8_t, LittleEndian>(data++);
- d->blocksNum = number<uint32_t, LittleEndian>(data); data += sizeof(uint32_t);
- d->blockLengthSize = d->blocksNum + padding; // Padded to ensure SparseIndex[]
- // does not point out of range.
+ d->sizeofBlock = 1ULL << *data++;
+ d->span = 1ULL << *data++;
+ d->sparseIndexSize = size_t((tbSize + d->span - 1) / d->span); // Round up
+ auto padding = number<uint8_t, LittleEndian>(data++);
+ d->blocksNum = number<uint32_t, LittleEndian>(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->lowestSym = (Sym*) data;
d->base64.resize(d->maxSymLen - d->minSymLen + 1);
+ // See https://en.wikipedia.org/wiki/Huffman_coding
// The canonical code is ordered such that longer symbols (in terms of
- // the number of bits of their Huffman code) have lower numeric value,
+ // the number of bits of their Huffman code) have a 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 http://www.eecs.harvard.edu/~michaelm/E210/huffman.pdf
- for (int i = d->base64.size() - 2; i >= 0; --i) {
+
+ // Implementation note: we first cast the unsigned size_t "base64.size()"
+ // to a signed int "base64_size" variable and then we are able to subtract 2,
+ // avoiding unsigned overflow warnings.
+
+ int base64_size = static_cast<int>(d->base64.size());
+ for (int i = base64_size - 2; i >= 0; --i)
+ {
d->base64[i] = (d->base64[i + 1] + number<Sym, LittleEndian>(&d->lowestSym[i])
- - number<Sym, LittleEndian>(&d->lowestSym[i + 1])) / 2;
+ - number<Sym, LittleEndian>(&d->lowestSym[i + 1]))
+ / 2;
- assert(d->base64[i] * 2 >= d->base64[i+1]);
+ 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
+ for (int i = 0; i < 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<uint16_t, LittleEndian>(data)); data += sizeof(uint16_t);
- d->btree = (LR*)data;
+ data += base64_size * sizeof(Sym);
+ d->symlen.resize(number<uint16_t, LittleEndian>(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
+ // See https://web.archive.org/web/20201106232444/http://www.larsson.dogma.net/dcc99.pdf
std::vector<bool> visited(d->symlen.size());
for (Sym sym = 0; sym < d->symlen.size(); ++sym)
e.map = data;
- for (File f = FILE_A; f <= maxFile; ++f) {
+ 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);
+ 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<uint16_t, LittleEndian>(data) + 2;
}
}
- else {
- for (int i = 0; i < 4; ++i) {
- e.get(0, f)->map_idx[i] = (uint16_t)(data - e.map + 1);
+ 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
+ return data += uintptr_t(data) & 1; // Word alignment
}
-// Populate entry's PairsData records with data from the just memory mapped file.
+// Populate entry's PairsData records with data from the just memory-mapped file.
// Called at first access.
template<typename T>
void set(T& e, uint8_t* data) {
PairsData* d;
- enum { Split = 1, HasPawns = 2 };
+ enum {
+ Split = 1,
+ HasPawns = 2
+ };
- assert(e.hasPawns == bool(*data & HasPawns));
+ assert(e.hasPawns == bool(*data & HasPawns));
assert((e.key != e.key2) == bool(*data & Split));
- data++; // First byte stores flags
+ data++; // First byte stores flags
- const int sides = T::Sides == 2 && (e.key != e.key2) ? 2 : 1;
+ 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
+ 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 (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 } };
+ 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++)
- e.get(i, f)->pieces[k] = Piece(i ? *data >> 4 : *data & 0xF);
+ e.get(i, f)->pieces[k] = Piece(i ? *data >> 4 : *data & 0xF);
for (int i = 0; i < sides; ++i)
set_groups(e, e.get(i, f), order[i], f);
}
- data += (uintptr_t)data & 1; // Word alignment
+ data += uintptr_t(data) & 1; // Word alignment
for (File f = FILE_A; f <= maxFile; ++f)
for (int i = 0; i < sides; i++)
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;
+ 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;
+ 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
+ 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
+// 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<TBType Type>
void* mapped(TBTable<Type>& e, const Position& pos) {
// 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
+ return e.baseAddress; // Could be nullptr if file does not exist
- std::unique_lock<std::mutex> lk(mutex);
+ std::scoped_lock<std::mutex> lk(mutex);
- if (e.ready.load(std::memory_order_relaxed)) // Recheck under lock
+ 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) {
+ 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");
+ 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);
template<TBType Type, typename Ret = typename TBTable<Type>::Ret>
Ret probe_table(const Position& pos, ProbeState* result, WDLScore wdl = WDLDraw) {
- if (pos.count<ALL_PIECES>() == 2) // KvK
+ if (pos.count<ALL_PIECES>() == 2) // KvK
return Ret(WDLDraw);
TBTable<Type>* entry = TBTables.get<Type>(pos.material_key());
}
// 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"
+// to store a winning value so the generator treats such positions as "don't care"
// 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
// 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
+// (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<bool CheckZeroingMoves>
WDLScore search(Position& pos, ProbeState* result) {
- WDLScore value, bestValue = WDLLoss;
+ WDLScore value, bestValue = WDLLoss;
StateInfo st;
- auto moveList = MoveList<LEGAL>(pos);
+ auto moveList = MoveList<LEGAL>(pos);
size_t totalCount = moveList.size(), moveCount = 0;
- for (const Move& move : moveList)
+ for (const Move move : moveList)
{
- if ( !pos.capture(move)
- && (!CheckZeroingMoves || type_of(pos.moved_piece(move)) != PAWN))
+ if (!pos.capture(move) && (!CheckZeroingMoves || type_of(pos.moved_piece(move)) != PAWN))
continue;
moveCount++;
if (value >= WDLWin)
{
- *result = ZEROING_BEST_MOVE; // Winning DTZ-zeroing move
+ *result = ZEROING_BEST_MOVE; // Winning DTZ-zeroing move
return value;
}
}
// 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 = (bestValue > WDLDraw || noMoreMoves ? ZEROING_BEST_MOVE : OK), bestValue;
return *result = OK, value;
}
-} // namespace
+} // 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.
+// 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;
+ TBFile::Paths = paths;
if (paths.empty() || paths == "<empty>")
return;
for (auto s : diagonal)
MapA1D1D4[s] = code++;
- // MapKK[] encodes all the 461 possible legal positions of two kings where
+ // MapKK[] encodes all the 462 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.
+ // diagonal, the other one shall not be above the a1-h8 diagonal.
std::vector<std::pair<int, Square>> 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
+ 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[KING][s1] | s1) & s2)
- continue; // Illegal position
+ continue; // Illegal position
else if (!off_A1H8(s1) && off_A1H8(s2) > 0)
- continue; // First on diagonal, second above
+ continue; // First on diagonal, second above
else if (!off_A1H8(s1) && !off_A1H8(s2))
bothOnDiagonal.emplace_back(idx, s2);
MapKK[idx][s2] = code++;
}
- // Legal positions with both kings on diagonal are encoded as last ones
+ // Legal positions with both kings on a diagonal are encoded as last ones
for (auto p : bothOnDiagonal)
MapKK[p.first][p.second] = code++;
- // Binomial[] stores the Binomial Coefficents using Pascal rule. There
+ // 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);
+ 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
+ // highest MapPawns[] is the leading pawn, the one nearest the edge, and
+ // among pawns with the same file, the one with the 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
+ // Restart the index at every file because TB table is split
// by file, so we can reuse the same index for different files.
int idx = 0;
// due to mirroring: sq == a3 -> no a2, h2, so MapPawns[a3] = 45
if (leadPawnsCnt == 1)
{
- MapPawns[sq] = availableSquares--;
- MapPawns[sq ^ 7] = availableSquares--; // Horizontal flip
+ MapPawns[sq] = availableSquares--;
+ MapPawns[flip_file(sq)] = availableSquares--;
}
LeadPawnIdx[leadPawnsCnt][sq] = idx;
idx += Binomial[leadPawnsCnt - 1][MapPawns[sq]];
LeadPawnsSize[leadPawnsCnt][f] = idx;
}
- // Add entries in TB tables if the corresponding ".rtbw" file exsists
- for (PieceType p1 = PAWN; p1 < KING; ++p1) {
+ // Add entries in TB tables if the corresponding ".rtbw" file exists
+ for (PieceType p1 = PAWN; p1 < KING; ++p1)
+ {
TBTables.add({KING, p1, KING});
- for (PieceType p2 = PAWN; p2 <= p1; ++p2) {
+ for (PieceType p2 = PAWN; p2 <= p1; ++p2)
+ {
TBTables.add({KING, p1, p2, KING});
TBTables.add({KING, p1, KING, p2});
for (PieceType p3 = PAWN; p3 < KING; ++p3)
TBTables.add({KING, p1, p2, KING, p3});
- for (PieceType p3 = PAWN; p3 <= p2; ++p3) {
+ for (PieceType p3 = PAWN; p3 <= p2; ++p3)
+ {
TBTables.add({KING, p1, p2, p3, KING});
- for (PieceType p4 = PAWN; p4 <= p3; ++p4) {
+ 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, KING, p5});
}
- for (PieceType p4 = PAWN; p4 < KING; ++p4) {
+ for (PieceType p4 = PAWN; p4 < KING; ++p4)
+ {
TBTables.add({KING, p1, p2, p3, KING, p4});
for (PieceType p5 = PAWN; p5 <= p4; ++p5)
// 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-movecounter <= 100.
+// 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;
+ *result = OK;
WDLScore wdl = search<true>(pos, result);
- if (*result == FAIL || wdl == WDLDraw) // DTZ tables don't store draws
+ 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
+ // 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);
// 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;
+ int minDTZ = 0xFFFF;
- for (const Move& move : MoveList<LEGAL>(pos))
+ for (const Move move : MoveList<LEGAL>(pos))
{
bool zeroing = pos.capture(move) || type_of(pos.moved_piece(move)) == PAWN;
// 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<false>(pos, result))
- : -probe_dtz(pos, result);
+ // winning position we could make a losing capture or go for a draw).
+ dtz = zeroing ? -dtz_before_zeroing(search<false>(pos, result)) : -probe_dtz(pos, result);
// If the move mates, force minDTZ to 1
if (dtz == 1 && pos.checkers() && MoveList<LEGAL>(pos).size() == 0)
// A return value false indicates that not all probes were successful.
bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) {
- ProbeState result;
- StateInfo st;
+ ProbeState result = OK;
+ 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;
+ int dtz, bound = Options["Syzygy50MoveRule"] ? (MAX_DTZ - 100) : 1;
// Probe and rank each move
for (auto& m : rootMoves)
{
// 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);
+ 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;
+ 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<LEGAL>(pos).size() == 0)
+ if (pos.checkers() && dtz == 2 && MoveList<LEGAL>(pos).size() == 0)
dtz = 1;
pos.undo_move(m.pv[0]);
// 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;
+ int r = dtz > 0 ? (dtz + cnt50 <= 99 && !rep ? MAX_DTZ : MAX_DTZ - (dtz + cnt50))
+ : dtz < 0 ? (-dtz * 2 + cnt50 < 100 ? -MAX_DTZ : -MAX_DTZ + (-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;
+ m.tbScore = r >= bound ? VALUE_MATE - MAX_PLY - 1
+ : r > 0 ? Value((std::max(3, r - (MAX_DTZ - 200)) * int(PawnValue)) / 200)
+ : r == 0 ? VALUE_DRAW
+ : r > -bound ? Value((std::min(-3, r + (MAX_DTZ - 200)) * int(PawnValue)) / 200)
+ : -VALUE_MATE + MAX_PLY + 1;
}
return true;
// 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 };
+ static const int WDL_to_rank[] = {-MAX_DTZ, -MAX_DTZ + 101, 0, MAX_DTZ - 101, MAX_DTZ};
- ProbeState result;
- StateInfo st;
+ ProbeState result = OK;
+ StateInfo st;
+ WDLScore wdl;
bool rule50 = Options["Syzygy50MoveRule"];
{
pos.do_move(m.pv[0], st);
- WDLScore wdl = -probe_wdl(pos, &result);
+ if (pos.is_draw(1))
+ wdl = WDLDraw;
+ else
+ wdl = -probe_wdl(pos, &result);
pos.undo_move(m.pv[0]);
m.tbRank = WDL_to_rank[wdl + 2];
if (!rule50)
- wdl = wdl > WDLDraw ? WDLWin
- : wdl < WDLDraw ? WDLLoss : WDLDraw;
+ wdl = wdl > WDLDraw ? WDLWin : wdl < WDLDraw ? WDLLoss : WDLDraw;
m.tbScore = WDL_to_value[wdl + 2];
}
return true;
}
+
+} // namespace Stockfish
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (c) 2013 Ronald de Man
- Copyright (C) 2016-2020 Marco Costalba, Lucas Braesch
+ Copyright (C) 2004-2023 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
#ifndef TBPROBE_H
#define TBPROBE_H
-#include <ostream>
+#include <string>
#include "../search.h"
-namespace Tablebases {
+namespace Stockfish {
+class Position;
+}
-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
+namespace Stockfish::Tablebases {
- WDLScoreNone = -1000
+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
};
// Possible states after a probing operation
enum ProbeState {
- FAIL = 0, // Probe failed (missing file table)
- OK = 1, // Probe succesful
- CHANGE_STM = -1, // DTZ should check the other side
- ZEROING_BEST_MOVE = 2 // Best move zeroes DTZ (capture or pawn move)
+ 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);
+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");
+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);
- return os;
-}
-
-}
+} // namespace Stockfish::Tablebases
#endif
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+ Copyright (C) 2004-2023 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
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#include <cassert>
+#include "thread.h"
-#include <algorithm> // For std::count
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <cstdlib>
+#include <deque>
+#include <initializer_list>
+#include <map>
+#include <memory>
+#include <utility>
+
+#include "evaluate.h"
+#include "misc.h"
#include "movegen.h"
#include "search.h"
-#include "thread.h"
-#include "uci.h"
#include "syzygy/tbprobe.h"
#include "tt.h"
+#include "uci.h"
-ThreadPool Threads; // Global object
+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) {
+// 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();
+ wait_for_search_finished();
}
-/// Thread destructor wakes up the thread in idle_loop() and waits
-/// for its termination. Thread should be already waiting.
-
+// Destructor wakes up the thread in idle_loop() and waits
+// for its termination. Thread should be already waiting.
Thread::~Thread() {
- assert(!searching);
+ assert(!searching);
- exit = true;
- start_searching();
- stdThread.join();
+ exit = true;
+ start_searching();
+ stdThread.join();
}
-/// Thread::bestMoveCount(Move move) return best move counter for the given root move
-
-int Thread::best_move_count(Move move) {
-
- auto rm = std::find(rootMoves.begin() + pvIdx,
- rootMoves.begin() + pvLast, move);
-
- return rm != rootMoves.begin() + pvLast ? rm->bestMoveCount : 0;
-}
-
-/// Thread::clear() reset histories, usually before a new game
+// Reset histories, usually before a new game
void Thread::clear() {
- counterMoves.fill(MOVE_NONE);
- mainHistory.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);
+ counterMoves.fill(MOVE_NONE);
+ mainHistory.fill(0);
+ captureHistory.fill(0);
+ pawnHistory.fill(0);
- for (bool inCheck : { false, true })
- for (StatsType c : { NoCaptures, Captures })
- continuationHistory[inCheck][c][NO_PIECE][0]->fill(Search::CounterMovePruneThreshold - 1);
+ for (bool inCheck : {false, true})
+ for (StatsType c : {NoCaptures, Captures})
+ for (auto& to : continuationHistory[inCheck][c])
+ for (auto& h : to)
+ h->fill(-71);
}
-/// Thread::start_searching() wakes up the thread that will start the search
+// Wakes up the thread that will start the search
void Thread::start_searching() {
-
- std::lock_guard<std::mutex> lk(mutex);
- searching = true;
- cv.notify_one(); // Wake up the thread in idle_loop()
+ mutex.lock();
+ searching = true;
+ mutex.unlock(); // Unlock before notifying saves a few CPU-cycles
+ 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.
-
+// Blocks on the condition variable
+// until the thread has finished searching.
void Thread::wait_for_search_finished() {
- std::unique_lock<std::mutex> lk(mutex);
- cv.wait(lk, [&]{ return !searching; });
+ std::unique_lock<std::mutex> 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.
+// Thread gets parked here, 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);
+ // 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<std::mutex> lk(mutex);
- searching = false;
- cv.notify_one(); // Wake up anyone waiting for search finished
- cv.wait(lk, [&]{ return searching; });
+ while (true)
+ {
+ std::unique_lock<std::mutex> lk(mutex);
+ searching = false;
+ cv.notify_one(); // Wake up anyone waiting for search finished
+ cv.wait(lk, [&] { return searching; });
- if (exit)
- return;
+ if (exit)
+ return;
- lk.unlock();
+ lk.unlock();
- search();
- }
+ 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.
-
+// 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();
+ if (threads.size() > 0) // destroy any existing thread(s)
+ {
+ main()->wait_for_search_finished();
- while (size() > 0)
- delete back(), pop_back();
- }
+ while (threads.size() > 0)
+ delete threads.back(), threads.pop_back();
+ }
- if (requested > 0) { // create new thread(s)
- push_back(new MainThread(0));
+ if (requested > 0) // create new thread(s)
+ {
+ threads.push_back(new MainThread(0));
- while (size() < requested)
- push_back(new Thread(size()));
- clear();
+ while (threads.size() < requested)
+ threads.push_back(new Thread(threads.size()));
+ clear();
- // Reallocate the hash with the new threadpool size
- TT.resize(Options["Hash"]);
+ // Reallocate the hash with the new threadpool size
+ TT.resize(size_t(Options["Hash"]));
- // Init thread number dependent search params.
- Search::init();
- }
+ // Init thread number dependent search params.
+ Search::init();
+ }
}
-/// ThreadPool::clear() sets threadPool data to initial values.
+// Sets threadPool data to initial values
void ThreadPool::clear() {
- for (Thread* th : *this)
- th->clear();
+ for (Thread* th : threads)
+ th->clear();
- main()->callsCnt = 0;
- main()->previousScore = VALUE_INFINITE;
- main()->previousTimeReduction = 1.0;
+ main()->callsCnt = 0;
+ main()->bestPreviousScore = VALUE_INFINITE;
+ main()->bestPreviousAverageScore = 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();
+// 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 = false;
+ increaseDepth = true;
+ main()->ponder = ponderMode;
+ Search::Limits = limits;
+ Search::RootMoves rootMoves;
+
+ for (const auto& m : MoveList<LEGAL>(pos))
+ if (limits.searchmoves.empty()
+ || std::count(limits.searchmoves.begin(), limits.searchmoves.end(), 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() == nullptr.
+ 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 : threads)
+ {
+ th->nodes = th->tbHits = th->nmpMinPly = th->bestMoveChanges = 0;
+ th->rootDepth = th->completedDepth = 0;
+ th->rootMoves = rootMoves;
+ th->rootPos.set(pos.fen(), pos.is_chess960(), &th->rootState, th);
+ th->rootState = setupStates->back();
+ th->rootSimpleEval = Eval::simple_eval(pos, pos.side_to_move());
+ }
+
+ main()->start_searching();
+}
- main()->stopOnPonderhit = stop = false;
- increaseDepth = true;
- main()->ponder = ponderMode;
- Search::Limits = limits;
- Search::RootMoves rootMoves;
+Thread* ThreadPool::get_best_thread() const {
+
+ Thread* bestThread = threads.front();
+ std::map<Move, int64_t> votes;
+ Value minScore = VALUE_NONE;
+
+ // Find the minimum score of all threads
+ for (Thread* th : threads)
+ minScore = std::min(minScore, th->rootMoves[0].score);
+
+ // Vote according to score and depth, and select the best thread
+ auto thread_value = [minScore](Thread* th) {
+ return (th->rootMoves[0].score - minScore + 14) * int(th->completedDepth);
+ };
+
+ for (Thread* th : threads)
+ votes[th->rootMoves[0].pv[0]] += thread_value(th);
+
+ for (Thread* th : threads)
+ if (std::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]]
+ || (votes[th->rootMoves[0].pv[0]] == votes[bestThread->rootMoves[0].pv[0]]
+ && thread_value(th) * int(th->rootMoves[0].pv.size() > 2)
+ > thread_value(bestThread)
+ * int(bestThread->rootMoves[0].pv.size() > 2)))))
+ bestThread = th;
+
+ return bestThread;
+}
- for (const auto& m : MoveList<LEGAL>(pos))
- if ( limits.searchmoves.empty()
- || std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m))
- rootMoves.emplace_back(m);
- if (!rootMoves.empty())
- Tablebases::rank_root_moves(pos, rootMoves);
+// Start non-main threads
- // 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());
+void ThreadPool::start_searching() {
- if (states.get())
- setupStates = std::move(states); // Ownership transfer, states is now empty
+ for (Thread* th : threads)
+ if (th != threads.front())
+ th->start_searching();
+}
- // 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 to not lose the info
- // we need to backup and later restore setupStates->back(). Note that setupStates
- // is shared by threads but is accessed in read-only mode.
- StateInfo tmp = setupStates->back();
- for (Thread* th : *this)
- {
- th->nodes = th->tbHits = th->nmpMinPly = 0;
- th->rootDepth = th->completedDepth = 0;
- th->rootMoves = rootMoves;
- th->rootPos.set(pos.fen(), pos.is_chess960(), &setupStates->back(), th);
- }
+// Wait for non-main threads
- setupStates->back() = tmp;
+void ThreadPool::wait_for_search_finished() const {
- main()->start_searching();
+ for (Thread* th : threads)
+ if (th != threads.front())
+ th->wait_for_search_finished();
}
+
+} // namespace Stockfish
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+ Copyright (C) 2004-2023 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
#include <atomic>
#include <condition_variable>
+#include <cstddef>
+#include <cstdint>
#include <mutex>
-#include <thread>
#include <vector>
-#include "material.h"
#include "movepick.h"
-#include "pawns.h"
#include "position.h"
#include "search.h"
#include "thread_win32_osx.h"
+#include "types.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.
-
+// Thread class keeps together all the thread-related stuff.
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();
- int best_move_count(Move move);
-
- Pawns::Table pawnsTable;
- Material::Table materialTable;
- size_t pvIdx, pvLast;
- uint64_t ttHitAverage;
- int selDepth, nmpMinPly;
- Color nmpColor;
- std::atomic<uint64_t> nodes, tbHits, bestMoveChanges;
-
- Position rootPos;
- Search::RootMoves rootMoves;
- Depth rootDepth, completedDepth;
- CounterMoveHistory counterMoves;
- ButterflyHistory mainHistory;
- CapturePieceToHistory captureHistory;
- ContinuationHistory continuationHistory[2][2];
- Score contempt;
+ 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; }
+
+ size_t pvIdx, pvLast;
+ std::atomic<uint64_t> nodes, tbHits, bestMoveChanges;
+ int selDepth, nmpMinPly;
+ Value bestValue, optimism[COLOR_NB];
+
+ Position rootPos;
+ StateInfo rootState;
+ Search::RootMoves rootMoves;
+ Depth rootDepth, completedDepth;
+ Value rootDelta;
+ Value rootSimpleEval;
+ CounterMoveHistory counterMoves;
+ ButterflyHistory mainHistory;
+ CapturePieceToHistory captureHistory;
+ ContinuationHistory continuationHistory[2][2];
+ PawnHistory pawnHistory;
};
-/// MainThread is a derived class specific for main thread
-
-struct MainThread : public Thread {
+// MainThread is a derived class specific for main thread
+struct MainThread: public Thread {
- using Thread::Thread;
+ using Thread::Thread;
- void search() override;
- void check_time();
+ void search() override;
+ void check_time();
- double previousTimeReduction;
- Value previousScore;
- Value iterValue[4];
- int callsCnt;
- bool stopOnPonderhit;
- std::atomic_bool ponder;
+ double previousTimeReduction;
+ Value bestPreviousScore;
+ Value bestPreviousAverageScore;
+ Value iterValue[4];
+ int callsCnt;
+ bool stopOnPonderhit;
+ std::atomic_bool ponder;
};
-/// 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.
+// 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 {
-struct ThreadPool : public std::vector<Thread*> {
+ void start_thinking(Position&, StateListPtr&, const Search::LimitsType&, bool = false);
+ void clear();
+ void set(size_t);
- void start_thinking(Position&, StateListPtr&, const Search::LimitsType&, bool = false);
- void clear();
- void set(size_t);
+ MainThread* main() const { return static_cast<MainThread*>(threads.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;
- MainThread* main() const { return static_cast<MainThread*>(front()); }
- uint64_t nodes_searched() const { return accumulate(&Thread::nodes); }
- uint64_t tb_hits() const { return accumulate(&Thread::tbHits); }
+ std::atomic_bool stop, increaseDepth;
- std::atomic_bool stop, increaseDepth;
+ auto cbegin() const noexcept { return threads.cbegin(); }
+ auto begin() noexcept { return threads.begin(); }
+ auto end() noexcept { return threads.end(); }
+ auto cend() const noexcept { return threads.cend(); }
+ auto size() const noexcept { return threads.size(); }
+ auto empty() const noexcept { return threads.empty(); }
-private:
- StateListPtr setupStates;
+ private:
+ StateListPtr setupStates;
+ std::vector<Thread*> threads;
- uint64_t accumulate(std::atomic<uint64_t> Thread::* member) const {
+ uint64_t accumulate(std::atomic<uint64_t> Thread::*member) const {
- uint64_t sum = 0;
- for (Thread* th : *this)
- sum += (th->*member).load(std::memory_order_relaxed);
- return sum;
- }
+ uint64_t sum = 0;
+ for (Thread* th : threads)
+ sum += (th->*member).load(std::memory_order_relaxed);
+ return sum;
+ }
};
extern ThreadPool Threads;
-#endif // #ifndef THREAD_H_INCLUDED
+} // namespace Stockfish
+
+#endif // #ifndef THREAD_H_INCLUDED
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+ Copyright (C) 2004-2023 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
#include <thread>
-/// 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.
+// 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__)
+#if defined(__APPLE__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(USE_PTHREADS)
-#include <pthread.h>
+ #include <pthread.h>
+
+namespace Stockfish {
static const size_t TH_STACK_SIZE = 8 * 1024 * 1024;
-template <class T, class P = std::pair<T*, void(T::*)()>>
-void* start_routine(void* ptr)
-{
- P* p = reinterpret_cast<P*>(ptr);
- (p->first->*(p->second))(); // Call member function pointer
- delete p;
- return NULL;
+template<class T, class P = std::pair<T*, void (T::*)()>>
+void* start_routine(void* ptr) {
+ P* p = reinterpret_cast<P*>(ptr);
+ (p->first->*(p->second))(); // Call member function pointer
+ delete p;
+ return nullptr;
}
class NativeThread {
- pthread_t thread;
-
-public:
- template<class T, class P = std::pair<T*, void(T::*)()>>
- 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<T>, new P(obj, fun));
- }
- void join() { pthread_join(thread, NULL); }
+ pthread_t thread;
+
+ public:
+ template<class T, class P = std::pair<T*, void (T::*)()>>
+ 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<T>, new P(obj, fun));
+ }
+ void join() { pthread_join(thread, nullptr); }
};
-#else // Default case: use STL classes
+} // namespace Stockfish
+
+#else // Default case: use STL classes
+
+namespace Stockfish {
+
+using NativeThread = std::thread;
-typedef std::thread NativeThread;
+} // namespace Stockfish
#endif
-#endif // #ifndef THREAD_WIN32_OSX_H_INCLUDED
+#endif // #ifndef THREAD_WIN32_OSX_H_INCLUDED
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+ Copyright (C) 2004-2023 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
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "timeman.h"
+
#include <algorithm>
-#include <cfloat>
#include <cmath>
#include "search.h"
-#include "timeman.h"
#include "uci.h"
-TimeManagement Time; // Our global time management object
-
-namespace {
-
- enum TimeType { OptimumTime, MaxTime };
-
- constexpr int MoveHorizon = 50; // Plan time management at most this many moves ahead
- constexpr double MaxRatio = 7.3; // When in trouble, we can step over reserved time with this ratio
- constexpr double StealRatio = 0.34; // However we must not steal time from remaining moves over this ratio
-
-
- // move_importance() is a skew-logistic function based on naive statistical
- // analysis of "how many games are still undecided after n half-moves". Game
- // is considered "undecided" as long as neither side has >275cp advantage.
- // Data was extracted from the CCRL game database with some simple filtering criteria.
-
- double move_importance(int ply) {
-
- constexpr double XScale = 6.85;
- constexpr double XShift = 64.5;
- constexpr double Skew = 0.171;
+namespace Stockfish {
- return pow((1 + exp((ply - XShift) / XScale)), -Skew) + DBL_MIN; // Ensure non-zero
- }
+TimeManagement Time; // Our global time management object
- template<TimeType T>
- TimePoint remaining(TimePoint myTime, int movesToGo, int ply, TimePoint slowMover) {
-
- constexpr double TMaxRatio = (T == OptimumTime ? 1.0 : MaxRatio);
- constexpr double TStealRatio = (T == OptimumTime ? 0.0 : StealRatio);
-
- double moveImportance = (move_importance(ply) * slowMover) / 100.0;
- double otherMovesImportance = 0.0;
-
- for (int i = 1; i < movesToGo; ++i)
- otherMovesImportance += move_importance(ply + 2 * i);
-
- double ratio1 = (TMaxRatio * moveImportance) / (TMaxRatio * moveImportance + otherMovesImportance);
- double ratio2 = (moveImportance + TStealRatio * otherMovesImportance) / (moveImportance + otherMovesImportance);
-
- return TimePoint(myTime * std::min(ratio1, ratio2)); // Intel C++ asks for an explicit cast
- }
-
-} // namespace
-
-
-/// init() is called at the beginning of the search and calculates the allowed
-/// thinking time out of the time control and current game ply. We support four
-/// different kinds of time controls, passed in 'limits':
-///
-/// inc == 0 && movestogo == 0 means: x basetime [sudden death!]
-/// inc == 0 && movestogo != 0 means: x moves in y minutes
-/// inc > 0 && movestogo == 0 means: x basetime + z increment
-/// inc > 0 && movestogo != 0 means: x moves in y minutes + z increment
+// 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(Search::LimitsType& limits, Color us, int ply) {
- TimePoint minThinkingTime = Options["Minimum Thinking Time"];
- TimePoint moveOverhead = Options["Move Overhead"];
- TimePoint slowMover = Options["Slow Mover"];
- TimePoint npmsec = Options["nodestime"];
- TimePoint hypMyTime;
-
- // 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;
- optimumTime = maximumTime = std::max(limits.time[us], minThinkingTime);
-
- const int maxMTG = limits.movestogo ? std::min(limits.movestogo, MoveHorizon) : MoveHorizon;
-
- // We calculate optimum time usage for different hypothetical "moves to go" values
- // and choose the minimum of calculated search time values. Usually the greatest
- // hypMTG gives the minimum values.
- for (int hypMTG = 1; hypMTG <= maxMTG; ++hypMTG)
- {
- // Calculate thinking time for hypothetical "moves to go"-value
- hypMyTime = limits.time[us]
- + limits.inc[us] * (hypMTG - 1)
- - moveOverhead * (2 + std::min(hypMTG, 40));
-
- hypMyTime = std::max(hypMyTime, TimePoint(0));
-
- TimePoint t1 = minThinkingTime + remaining<OptimumTime>(hypMyTime, hypMTG, ply, slowMover);
- TimePoint t2 = minThinkingTime + remaining<MaxTime >(hypMyTime, hypMTG, ply, slowMover);
-
- optimumTime = std::min(t1, optimumTime);
- maximumTime = std::min(t2, maximumTime);
- }
-
- if (Options["Ponder"])
- optimumTime += optimumTime / 4;
+ // If we have no time, no need to initialize TM, except for the start time,
+ // which is used by movetime.
+ startTime = limits.startTime;
+ if (limits.time[us] == 0)
+ return;
+
+ TimePoint moveOverhead = TimePoint(Options["Move Overhead"]);
+ 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;
+ }
+
+ // 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));
+
+ // Use extra time with larger increments
+ double optExtra = std::clamp(1.0 + 12.5 * limits.inc[us] / limits.time[us], 1.0, 1.11);
+
+ // Calculate time constants based on current time left.
+ double optConstant = std::min(0.00334 + 0.0003 * std::log10(limits.time[us] / 1000.0), 0.0049);
+ double maxConstant = std::max(3.4 + 3.0 * std::log10(limits.time[us] / 1000.0), 2.76);
+
+ // 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.0120 + std::pow(ply + 3.1, 0.44) * optConstant,
+ 0.21 * limits.time[us] / double(timeLeft))
+ * optExtra;
+ maxScale = std::min(6.9, maxConstant + ply / 12.2);
+ }
+
+ // x moves in y seconds (+ z increment)
+ else
+ {
+ optScale = std::min((0.88 + ply / 116.4) / mtg, 0.88 * limits.time[us] / double(timeLeft));
+ maxScale = std::min(6.3, 1.5 + 0.11 * mtg);
+ }
+
+ // Limit the maximum possible time for this move
+ optimumTime = TimePoint(optScale * timeLeft);
+ maximumTime =
+ TimePoint(std::min(0.84 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10;
+
+ if (Options["Ponder"])
+ optimumTime += optimumTime / 4;
}
+
+} // namespace Stockfish
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+ Copyright (C) 2004-2023 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
#ifndef TIMEMAN_H_INCLUDED
#define TIMEMAN_H_INCLUDED
+#include <cstdint>
+
#include "misc.h"
#include "search.h"
#include "thread.h"
+#include "types.h"
-/// The TimeManagement class computes the optimal time to think depending on
-/// the maximum available time, the game move number and other parameters.
+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(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;
+ public:
+ void init(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;
-#endif // #ifndef TIMEMAN_H_INCLUDED
+} // namespace Stockfish
+
+#endif // #ifndef TIMEMAN_H_INCLUDED
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+ Copyright (C) 2004-2023 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
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#include <cstring> // For std::memset
+#include "tt.h"
+
+#include <cassert>
+#include <cstdlib>
+#include <cstring>
#include <iostream>
#include <thread>
+#include <vector>
-#include "bitboard.h"
#include "misc.h"
#include "thread.h"
-#include "tt.h"
#include "uci.h"
-TranspositionTable TT; // Our global transposition table
+namespace Stockfish {
-/// TTEntry::save populates the TTEntry with a new node's data, possibly
-/// overwriting an old position. Update is not atomic and can be racy.
+TranspositionTable TT; // Our global transposition table
+// Populates the TTEntry with a new node's data, possibly
+// overwriting an old position. The 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 || (k >> 48) != key16)
- move16 = (uint16_t)m;
-
- // Overwrite less valuable entries
- if ( (k >> 48) != key16
- || d - DEPTH_OFFSET > depth8 - 4
- || b == BOUND_EXACT)
- {
- assert(d >= DEPTH_OFFSET);
-
- key16 = (uint16_t)(k >> 48);
- value16 = (int16_t)v;
- eval16 = (int16_t)ev;
- genBound8 = (uint8_t)(TT.generation8 | uint8_t(pv) << 2 | b);
- depth8 = (uint8_t)(d - DEPTH_OFFSET);
- }
+ // Preserve any existing move for the same position
+ if (m || uint16_t(k) != key16)
+ move16 = uint16_t(m);
+
+ // Overwrite less valuable entries (cheapest checks first)
+ if (b == BOUND_EXACT || uint16_t(k) != key16 || d - DEPTH_OFFSET + 2 * pv > 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.
-
+// 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();
+ Threads.main()->wait_for_search_finished();
- clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster);
+ aligned_large_pages_free(table);
- free(mem);
- mem = malloc(clusterCount * sizeof(Cluster) + CacheLineSize - 1);
+ clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster);
- if (!mem)
- {
- std::cerr << "Failed to allocate " << mbSize
- << "MB for transposition table." << std::endl;
- exit(EXIT_FAILURE);
- }
+ table = static_cast<Cluster*>(aligned_large_pages_alloc(clusterCount * sizeof(Cluster)));
+ if (!table)
+ {
+ std::cerr << "Failed to allocate " << mbSize << "MB for transposition table." << std::endl;
+ exit(EXIT_FAILURE);
+ }
- table = (Cluster*)((uintptr_t(mem) + CacheLineSize - 1) & ~(CacheLineSize - 1));
- clear();
+ clear();
}
-/// TranspositionTable::clear() initializes the entire transposition table to zero,
-// in a multi-threaded way.
-
+// Initializes the entire transposition table to zero,
+// in a multi-threaded way.
void TranspositionTable::clear() {
- std::vector<std::thread> threads;
-
- for (size_t idx = 0; idx < Options["Threads"]; ++idx)
- {
- threads.emplace_back([this, idx]() {
+ std::vector<std::thread> threads;
- // Thread binding gives faster search on systems with a first-touch policy
- if (Options["Threads"] > 8)
- WinProcGroup::bindThisThread(idx);
+ for (size_t idx = 0; idx < size_t(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 = clusterCount / Options["Threads"],
- start = stride * idx,
- len = idx != Options["Threads"] - 1 ?
- stride : clusterCount - start;
+ // 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 != size_t(Options["Threads"]) - 1 ? stride : clusterCount - start;
- std::memset(&table[start], 0, len * sizeof(Cluster));
- });
- }
+ std::memset(&table[start], 0, len * sizeof(Cluster));
+ });
+ }
- for (std::thread& th: threads)
- th.join();
+ 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.
+// 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 = key >> 48; // Use the high 16 bits as key inside the cluster
-
- for (int i = 0; i < ClusterSize; ++i)
- if (!tte[i].key16 || tte[i].key16 == key16)
- {
- tte[i].genBound8 = uint8_t(generation8 | (tte[i].genBound8 & 0x7)); // Refresh
-
- return found = (bool)tte[i].key16, &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 263 (256 is the modulus plus 7 to keep the unrelated
- // lowest three bits from affecting the result) to calculate the entry
- // age correctly even after generation8 overflows into the next cycle.
- if ( replace->depth8 - ((263 + generation8 - replace->genBound8) & 0xF8)
- > tte[i].depth8 - ((263 + generation8 - tte[i].genBound8) & 0xF8))
- replace = &tte[i];
-
- return found = false, replace;
+ 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.
+// 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 / ClusterSize; ++i)
- for (int j = 0; j < ClusterSize; ++j)
- cnt += (table[i].entry[j].genBound8 & 0xF8) == generation8;
+ 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 * 1000 / (ClusterSize * (1000 / ClusterSize));
+ return cnt / ClusterSize;
}
+
+} // namespace Stockfish
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+ Copyright (C) 2004-2023 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
#ifndef TT_H_INCLUDED
#define TT_H_INCLUDED
+#include <cstddef>
+#include <cstdint>
+
#include "misc.h"
#include "types.h"
-/// TTEntry struct is the 10 bytes transposition table entry, defined as below:
-///
-/// key 16 bit
-/// move 16 bit
-/// value 16 bit
-/// eval value 16 bit
-/// generation 5 bit
-/// pv node 1 bit
-/// bound type 2 bit
-/// depth 8 bit
-
+namespace Stockfish {
+
+// TTEntry struct is the 10 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 16 bit
+// value 16 bit
+// eval value 16 bit
struct TTEntry {
- Move move() const { return (Move )move16; }
- 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;
- uint16_t move16;
- int16_t value16;
- int16_t eval16;
- uint8_t genBound8;
- uint8_t depth8;
+ Move move() const { return Move(move16); }
+ 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;
+ uint16_t move16;
+ int16_t value16;
+ int16_t eval16;
};
-/// A TranspositionTable consists of a power of 2 number of clusters and each
-/// cluster consists of ClusterSize number of TTEntry. Each non-empty entry
-/// contains information of exactly one position. The size of a cluster should
-/// divide the size of a cache line size, to ensure that clusters never cross
-/// cache lines. This ensures best cache performance, as the cacheline is
-/// prefetched, as soon as possible.
-
+// 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 CacheLineSize = 64;
- static constexpr int ClusterSize = 3;
-
- struct Cluster {
- TTEntry entry[ClusterSize];
- char padding[2]; // Align to a divisor of the cache line size
- };
-
- static_assert(CacheLineSize % sizeof(Cluster) == 0, "Cluster size incorrect");
-
-public:
- ~TranspositionTable() { free(mem); }
- void new_search() { generation8 += 8; } // Lower 3 bits are used by PV flag and Bound
- TTEntry* probe(const Key key, bool& found) const;
- int hashfull() const;
- void resize(size_t mbSize);
- void clear();
-
- // The 32 lowest order bits of the key are used to get the index of the cluster
- TTEntry* first_entry(const Key key) const {
- return &table[(uint32_t(key) * uint64_t(clusterCount)) >> 32].entry[0];
- }
-
-private:
- friend struct TTEntry;
-
- size_t clusterCount;
- Cluster* table;
- void* mem;
- uint8_t generation8; // Size must be not bigger than TTEntry::genBound8
+ static constexpr int ClusterSize = 3;
+
+ struct Cluster {
+ TTEntry entry[ClusterSize];
+ char padding[2]; // Pad to 32 bytes
+ };
+
+ static_assert(sizeof(Cluster) == 32, "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;
-#endif // #ifndef TT_H_INCLUDED
+} // namespace Stockfish
+
+#endif // #ifndef TT_H_INCLUDED
--- /dev/null
+/*
+ Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+ Copyright (C) 2004-2023 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "tune.h"
+
+#include <algorithm>
+#include <iostream>
+#include <map>
+#include <sstream>
+#include <string>
+
+#include "uci.h"
+
+namespace Stockfish {
+enum Value : int;
+}
+
+using std::string;
+
+namespace Stockfish {
+
+bool Tune::update_on_last;
+const UCI::Option* LastOption = nullptr;
+static std::map<std::string, int> 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<int>::init_option() {
+ make_option(name, value, range);
+}
+
+template<>
+void Tune::Entry<int>::read_option() {
+ if (Options.count(name))
+ value = int(Options[name]);
+}
+
+template<>
+void Tune::Entry<Value>::init_option() {
+ make_option(name, value, range);
+}
+
+template<>
+void Tune::Entry<Value>::read_option() {
+ if (Options.count(name))
+ value = Value(int(Options[name]));
+}
+
+// Instead of a variable here we have a PostUpdate function: just call it
+template<>
+void Tune::Entry<Tune::PostUpdate>::init_option() {}
+template<>
+void Tune::Entry<Tune::PostUpdate>::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
+
+
+namespace Stockfish {
+
+void Tune::read_results() { /* ...insert your values here... */
+}
+
+} // namespace Stockfish
--- /dev/null
+/*
+ Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+ Copyright (C) 2004-2023 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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef TUNE_H_INCLUDED
+#define TUNE_H_INCLUDED
+
+#include <cstddef>
+#include <memory>
+#include <string>
+#include <type_traits> // IWYU pragma: keep
+#include <utility>
+#include <vector>
+
+namespace Stockfish {
+enum Value : int;
+
+using Range = std::pair<int, int>; // Option's min-max values
+using RangeFun = Range(int);
+
+// Default Range function, to calculate Option's min-max values
+inline Range default_range(int v) { return v > 0 ? Range(0, 2 * v) : Range(2 * v, 0); }
+
+struct SetRange {
+ explicit SetRange(RangeFun f) :
+ fun(f) {}
+ SetRange(int min, int max) :
+ fun(nullptr),
+ range(min, max) {}
+ Range operator()(int v) const { return fun ? fun(v) : range; }
+
+ RangeFun* fun;
+ Range range;
+};
+
+#define SetDefaultRange SetRange(default_range)
+
+
+// Tune class implements the 'magic' code that makes the setup of a fishtest tuning
+// session as easy as it can be. Mainly you have just to remove const qualifiers
+// from the variables you want to tune and flag them for tuning, so if you have:
+//
+// const Value myValue[][2] = { { V(100), V(20) }, { V(7), V(78) } };
+//
+// If you have a my_post_update() function to run after values have been updated,
+// and a my_range() function to set custom Option's min-max values, then you just
+// remove the 'const' qualifiers and write somewhere below in the file:
+//
+// TUNE(SetRange(my_range), myValue, my_post_update);
+//
+// You can also set the range directly, and restore the default at the end
+//
+// TUNE(SetRange(-100, 100), myValue, SetDefaultRange);
+//
+// In case update function is slow and you have many parameters, you can add:
+//
+// UPDATE_ON_LAST();
+//
+// And the values update, including post update function call, will be done only
+// once, after the engine receives the last UCI option, that is the one defined
+// and created as the last one, so the GUI should send the options in the same
+// order in which have been defined.
+
+class Tune {
+
+ using PostUpdate = void(); // Post-update function
+
+ Tune() { read_results(); }
+ Tune(const Tune&) = delete;
+ void operator=(const Tune&) = delete;
+ void read_results();
+
+ static Tune& instance() {
+ static Tune t;
+ return t;
+ } // Singleton
+
+ // Use polymorphism to accommodate Entry of different types in the same vector
+ struct EntryBase {
+ virtual ~EntryBase() = default;
+ virtual void init_option() = 0;
+ virtual void read_option() = 0;
+ };
+
+ template<typename T>
+ struct Entry: public EntryBase {
+
+ static_assert(!std::is_const_v<T>, "Parameter cannot be const!");
+
+ static_assert(std::is_same_v<T, int> || std::is_same_v<T, Value>
+ || std::is_same_v<T, PostUpdate>,
+ "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<typename T, typename... Args>
+ int add(const SetRange& range, std::string&& names, T& value, Args&&... args) {
+ list.push_back(std::unique_ptr<EntryBase>(new Entry<T>(next(names), value, range)));
+ return add(range, std::move(names), args...);
+ }
+
+ // Template specialization for arrays: recursively handle multi-dimensional arrays
+ template<typename T, size_t N, typename... Args>
+ 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<typename... Args>
+ int add(const SetRange&, std::string&& names, SetRange& value, Args&&... args) {
+ return add(value, (next(names), std::move(names)), args...);
+ }
+
+ std::vector<std::unique_ptr<EntryBase>> list;
+
+ public:
+ template<typename... Args>
+ 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 the compiler initializes calling Tune::add()
+#define STRINGIFY(x) #x
+#define UNIQUE2(x, y) x##y
+#define UNIQUE(x, y) UNIQUE2(x, y) // Two indirection levels to expand __LINE__
+#define TUNE(...) int UNIQUE(p, __LINE__) = 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
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+ Copyright (C) 2004-2023 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
*/
#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 <cassert>
-#include <cctype>
-#include <climits>
-#include <cstdint>
-#include <cstdlib>
-#include <algorithm>
-
-#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'
-#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(_WIN64) && defined(_MSC_VER) // No Makefile used
-# include <intrin.h> // Microsoft header for _BitScanForward64()
-# define IS_64BIT
-#endif
-
-#if defined(USE_POPCNT) && (defined(__INTEL_COMPILER) || defined(_MSC_VER))
-# include <nmmintrin.h> // Intel and Microsoft header for _mm_popcnt_u64()
-#endif
-
-#if !defined(NO_PREFETCH) && (defined(__INTEL_COMPILER) || defined(_MSC_VER))
-# include <xmmintrin.h> // Intel and Microsoft header for _mm_prefetch()
-#endif
-
-#if defined(USE_PEXT)
-# include <immintrin.h> // Header for _pext_u64() intrinsic
-# define pext(b, m) _pext_u64(b, m)
-#else
-# define pext(b, m) 0
-#endif
-
-#ifdef USE_POPCNT
+ #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 <cassert>
+ #include <cstdint>
+
+ #if defined(_MSC_VER)
+ // Disable some silly and noisy warnings 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'
+ #endif
+
+// Predefined macros hell:
+//
+// __GNUC__ Compiler is GCC, Clang or ICX
+// __clang__ Compiler is Clang or ICX
+// __INTEL_LLVM_COMPILER Compiler is ICX
+// _MSC_VER Compiler is MSVC
+// _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<uintptr_t>(ptr) % alignment == 0)
+
+ #if defined(_WIN64) && defined(_MSC_VER) // No Makefile used
+ #include <intrin.h> // Microsoft header for _BitScanForward64()
+ #define IS_64BIT
+ #endif
+
+ #if defined(USE_POPCNT) && defined(_MSC_VER)
+ #include <nmmintrin.h> // Microsoft header for _mm_popcnt_u64()
+ #endif
+
+ #if !defined(NO_PREFETCH) && defined(_MSC_VER)
+ #include <xmmintrin.h> // Microsoft header for _mm_prefetch()
+ #endif
+
+ #if defined(USE_PEXT)
+ #include <immintrin.h> // Header for _pext_u64() intrinsic
+ #define pext(b, m) _pext_u64(b, m)
+ #else
+ #define pext(b, m) 0
+ #endif
+
+namespace Stockfish {
+
+ #ifdef USE_POPCNT
constexpr bool HasPopCnt = true;
-#else
+ #else
constexpr bool HasPopCnt = false;
-#endif
+ #endif
-#ifdef USE_PEXT
+ #ifdef USE_PEXT
constexpr bool HasPext = true;
-#else
+ #else
constexpr bool HasPext = false;
-#endif
+ #endif
-#ifdef IS_64BIT
+ #ifdef IS_64BIT
constexpr bool Is64Bit = true;
-#else
+ #else
constexpr bool Is64Bit = false;
-#endif
+ #endif
-typedef uint64_t Key;
-typedef uint64_t Bitboard;
+using Key = uint64_t;
+using Bitboard = uint64_t;
constexpr int MAX_MOVES = 256;
constexpr int MAX_PLY = 246;
-/// 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.
+// 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 = 65
+ MOVE_NONE,
+ MOVE_NULL = 65
};
enum MoveType {
- NORMAL,
- PROMOTION = 1 << 14,
- ENPASSANT = 2 << 14,
- CASTLING = 3 << 14
+ NORMAL,
+ PROMOTION = 1 << 14,
+ EN_PASSANT = 2 << 14,
+ CASTLING = 3 << 14
};
enum Color {
- WHITE, BLACK, COLOR_NB = 2
+ 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 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
+ 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 Bound {
- BOUND_NONE,
- BOUND_UPPER,
- BOUND_LOWER,
- BOUND_EXACT = BOUND_UPPER | BOUND_LOWER
+ 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,
- VALUE_INFINITE = 32001,
- VALUE_NONE = 32002,
-
- VALUE_MATE_IN_MAX_PLY = VALUE_MATE - 2 * MAX_PLY,
- VALUE_MATED_IN_MAX_PLY = -VALUE_MATE + 2 * MAX_PLY,
-
- PawnValueMg = 128, PawnValueEg = 213,
- KnightValueMg = 781, KnightValueEg = 854,
- BishopValueMg = 825, BishopValueEg = 915,
- RookValueMg = 1276, RookValueEg = 1380,
- QueenValueMg = 2538, QueenValueEg = 2682,
-
- MidgameLimit = 15258, EndgameLimit = 3915
+ VALUE_ZERO = 0,
+ VALUE_DRAW = 0,
+ VALUE_NONE = 32002,
+ VALUE_INFINITE = 32001,
+
+ VALUE_MATE = 32000,
+ VALUE_MATE_IN_MAX_PLY = VALUE_MATE - MAX_PLY,
+ VALUE_MATED_IN_MAX_PLY = -VALUE_MATE_IN_MAX_PLY,
+
+ VALUE_TB = VALUE_MATE_IN_MAX_PLY - 1,
+ VALUE_TB_WIN_IN_MAX_PLY = VALUE_TB - MAX_PLY,
+ VALUE_TB_LOSS_IN_MAX_PLY = -VALUE_TB_WIN_IN_MAX_PLY,
+
+ // In the code, we make the assumption that these values
+ // are such that non_pawn_material() can be used to uniquely
+ // identify the material on the board.
+ PawnValue = 208,
+ KnightValue = 781,
+ BishopValue = 825,
+ RookValue = 1276,
+ QueenValue = 2538,
};
+// clang-format off
enum PieceType {
- NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING,
- ALL_PIECES = 0,
- PIECE_TYPE_NB = 8
+ NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING,
+ ALL_PIECES = 0,
+ PIECE_TYPE_NB = 8
};
enum Piece {
- NO_PIECE,
- W_PAWN = 1, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING,
- B_PAWN = 9, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING,
- PIECE_NB = 16
+ NO_PIECE,
+ W_PAWN = PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING,
+ B_PAWN = PAWN + 8, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING,
+ PIECE_NB = 16
};
+// clang-format on
-extern Value PieceValue[PHASE_NB][PIECE_NB];
+constexpr Value PieceValue[PIECE_NB] = {
+ VALUE_ZERO, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, VALUE_ZERO, VALUE_ZERO,
+ VALUE_ZERO, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, VALUE_ZERO, VALUE_ZERO};
-typedef int Depth;
+using Depth = int;
enum : int {
+ DEPTH_QS_CHECKS = 0,
+ DEPTH_QS_NO_CHECKS = -1,
- DEPTH_QS_CHECKS = 0,
- DEPTH_QS_NO_CHECKS = -1,
- DEPTH_QS_RECAPTURES = -5,
+ DEPTH_NONE = -6,
- DEPTH_NONE = -6,
- DEPTH_OFFSET = DEPTH_NONE,
+ DEPTH_OFFSET = -7 // value used only for TT entry occupancy check
};
+// clang-format off
enum Square : int {
- 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,
- SQ_NONE,
-
- SQUARE_NB = 64
+ 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,
+ SQ_NONE,
+
+ SQUARE_ZERO = 0,
+ SQUARE_NB = 64
};
+// clang-format on
enum Direction : int {
- NORTH = 8,
- EAST = 1,
- SOUTH = -NORTH,
- WEST = -EAST,
-
- NORTH_EAST = NORTH + EAST,
- SOUTH_EAST = SOUTH + EAST,
- SOUTH_WEST = SOUTH + WEST,
- NORTH_WEST = NORTH + WEST
+ NORTH = 8,
+ 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 {
- FILE_A, FILE_B, FILE_C, FILE_D, FILE_E, FILE_F, FILE_G, FILE_H, FILE_NB
+ FILE_A,
+ FILE_B,
+ FILE_C,
+ FILE_D,
+ FILE_E,
+ FILE_F,
+ FILE_G,
+ FILE_H,
+ FILE_NB
};
enum Rank : int {
- RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, RANK_NB
+ RANK_1,
+ RANK_2,
+ RANK_3,
+ RANK_4,
+ RANK_5,
+ RANK_6,
+ RANK_7,
+ RANK_8,
+ RANK_NB
};
+// Keep track of what a move changes on the board (used by NNUE)
+struct DirtyPiece {
-/// 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);
-}
+ // Number of changed pieces
+ int dirty_num;
-/// 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);
-}
+ // Max 3 pieces can change in one move. A promotion with capture moves
+ // both the pawn and the captured piece to SQ_NONE and the piece promoted
+ // to from SQ_NONE to the capture square.
+ Piece piece[3];
-inline Value mg_value(Score s) {
- union { uint16_t u; int16_t s; } mg = { uint16_t(unsigned(s)) };
- return Value(mg.s);
-}
+ // From and to squares, which may be SQ_NONE
+ Square from[3];
+ Square to[3];
+};
-#define ENABLE_BASE_OPERATORS_ON(T) \
-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 d) { return T(-int(d)); } \
-inline T& operator+=(T& d1, T d2) { return d1 = d1 + d2; } \
-inline T& operator-=(T& d1, T 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); }
+ #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(PieceType)
-ENABLE_INCR_OPERATORS_ON(Piece)
ENABLE_INCR_OPERATORS_ON(Square)
ENABLE_INCR_OPERATORS_ON(File)
ENABLE_INCR_OPERATORS_ON(Rank)
-ENABLE_BASE_OPERATORS_ON(Score)
+ #undef ENABLE_FULL_OPERATORS_ON
+ #undef ENABLE_INCR_OPERATORS_ON
+ #undef ENABLE_BASE_OPERATORS_ON
-#undef ENABLE_FULL_OPERATORS_ON
-#undef ENABLE_INCR_OPERATORS_ON
-#undef ENABLE_BASE_OPERATORS_ON
-
-/// Additional operators to add integers to a Value
-constexpr Value operator+(Value v, int i) { return Value(int(v) + i); }
-constexpr Value operator-(Value v, int i) { return Value(int(v) - i); }
-inline Value& operator+=(Value& v, int i) { return v = v + i; }
-inline Value& operator-=(Value& v, int i) { return v = v - i; }
-
-/// Additional operators to add a Direction to a Square
+// 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; }
+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;
+// Toggle color
+constexpr Color operator~(Color c) { return Color(c ^ BLACK); }
-/// 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 Score(int(s) * int(b));
-}
-
-constexpr Color operator~(Color c) {
- return Color(c ^ BLACK); // Toggle color
-}
-
-constexpr Square operator~(Square s) {
- return Square(s ^ SQ_A8); // Vertical flip SQ_A1 -> SQ_A8
-}
+// Swap A1 <-> A8
+constexpr Square flip_rank(Square s) { return Square(s ^ SQ_A8); }
-constexpr Piece operator~(Piece pc) {
- return Piece(pc ^ 8); // Swap color of piece B_KNIGHT -> W_KNIGHT
-}
+// Swap A1 <-> H1
+constexpr Square flip_file(Square s) { return Square(s ^ SQ_H1); }
-inline File map_to_queenside(File f) {
- return std::min(f, File(FILE_H - f)); // Map files ABCDEFGH to files ABCDDCBA
-}
+// Swap color of piece B_KNIGHT <-> W_KNIGHT
+constexpr Piece operator~(Piece pc) { return Piece(pc ^ 8); }
constexpr CastlingRights operator&(Color c, CastlingRights cr) {
- return CastlingRights((c == WHITE ? WHITE_CASTLING : BLACK_CASTLING) & cr);
+ return CastlingRights((c == WHITE ? WHITE_CASTLING : BLACK_CASTLING) & cr);
}
-constexpr Value mate_in(int ply) {
- return VALUE_MATE - ply;
-}
+constexpr Value mate_in(int ply) { return VALUE_MATE - ply; }
-constexpr Value mated_in(int ply) {
- return -VALUE_MATE + ply;
-}
+constexpr Value mated_in(int ply) { return -VALUE_MATE + ply; }
-constexpr Square make_square(File f, Rank r) {
- return Square((r << 3) + f);
-}
+constexpr Square make_square(File f, Rank r) { return Square((r << 3) + f); }
-constexpr Piece make_piece(Color c, PieceType pt) {
- return Piece((c << 3) + pt);
-}
+constexpr Piece make_piece(Color c, PieceType pt) { return Piece((c << 3) + pt); }
-constexpr PieceType type_of(Piece pc) {
- return PieceType(pc & 7);
-}
+constexpr PieceType type_of(Piece pc) { return PieceType(pc & 7); }
inline Color color_of(Piece pc) {
- assert(pc != NO_PIECE);
- return Color(pc >> 3);
+ assert(pc != NO_PIECE);
+ return Color(pc >> 3);
}
-constexpr bool is_ok(Square s) {
- return s >= SQ_A1 && s <= SQ_H8;
-}
+constexpr bool is_ok(Move m) { return m != MOVE_NONE && m != MOVE_NULL; }
-constexpr File file_of(Square s) {
- return File(s & 7);
-}
+constexpr bool is_ok(Square s) { return s >= SQ_A1 && s <= SQ_H8; }
-constexpr Rank rank_of(Square s) {
- return Rank(s >> 3);
-}
+constexpr File file_of(Square s) { return File(s & 7); }
-constexpr Square relative_square(Color c, Square s) {
- return Square(s ^ (c * 56));
-}
+constexpr Rank rank_of(Square s) { return Rank(s >> 3); }
-constexpr Rank relative_rank(Color c, Rank r) {
- return Rank(r ^ (c * 7));
-}
+constexpr Square relative_square(Color c, Square s) { return Square(s ^ (c * 56)); }
-constexpr Rank relative_rank(Color c, Square s) {
- return relative_rank(c, rank_of(s));
-}
+constexpr Rank relative_rank(Color c, Rank r) { return Rank(r ^ (c * 7)); }
-constexpr Direction pawn_push(Color c) {
- return c == WHITE ? NORTH : SOUTH;
-}
+constexpr Rank relative_rank(Color c, Square s) { return relative_rank(c, rank_of(s)); }
+
+constexpr Direction pawn_push(Color c) { return c == WHITE ? NORTH : SOUTH; }
constexpr Square from_sq(Move m) {
- return Square((m >> 6) & 0x3F);
+ assert(is_ok(m));
+ return Square((m >> 6) & 0x3F);
}
constexpr Square to_sq(Move m) {
- return Square(m & 0x3F);
+ assert(is_ok(m));
+ return Square(m & 0x3F);
}
-constexpr int from_to(Move m) {
- return m & 0xFFF;
-}
+constexpr int from_to(Move m) { return m & 0xFFF; }
-constexpr MoveType type_of(Move m) {
- return MoveType(m & (3 << 14));
-}
+constexpr MoveType type_of(Move m) { return MoveType(m & (3 << 14)); }
-constexpr PieceType promotion_type(Move m) {
- return PieceType(((m >> 12) & 3) + KNIGHT);
-}
+constexpr PieceType promotion_type(Move m) { return PieceType(((m >> 12) & 3) + KNIGHT); }
-constexpr Move make_move(Square from, Square to) {
- return Move((from << 6) + to);
-}
-
-constexpr Move reverse_move(Move m) {
- return make_move(to_sq(m), from_sq(m));
-}
+constexpr Move make_move(Square from, Square to) { return Move((from << 6) + to); }
template<MoveType T>
constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) {
- return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to);
+ return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to);
}
-constexpr bool is_ok(Move m) {
- return from_sq(m) != to_sq(m); // Catch MOVE_NULL and MOVE_NONE
+// Based on a congruential pseudo-random number generator
+constexpr Key make_key(uint64_t seed) {
+ return seed * 6364136223846793005ULL + 1442695040888963407ULL;
}
-#endif // #ifndef TYPES_H_INCLUDED
+} // namespace Stockfish
+
+#endif // #ifndef TYPES_H_INCLUDED
+
+#include "tune.h" // Global visibility to tuning setup
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+ Copyright (C) 2004-2023 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
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include "uci.h"
+
+#include <algorithm>
#include <cassert>
+#include <cctype>
+#include <cmath>
+#include <cstdint>
+#include <cstdlib>
+#include <deque>
#include <iostream>
+#include <memory>
+#include <optional>
#include <sstream>
#include <string>
+#include <vector>
+#include "benchmark.h"
#include "evaluate.h"
+#include "misc.h"
#include "movegen.h"
+#include "nnue/evaluate_nnue.h"
#include "position.h"
#include "search.h"
#include "thread.h"
-#include "timeman.h"
-#include "tt.h"
-#include "uci.h"
-#include "syzygy/tbprobe.h"
-using namespace std;
-
-extern vector<string> setup_bench(const Position&, istream&);
+namespace Stockfish {
namespace {
- // FEN string of the initial position, normal chess
- const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
-
+// FEN string for the initial position in standard chess
+const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
- // 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) {
+// Called when the engine receives the "position" UCI command.
+// It sets up the position that is described in the given FEN string ("fen") or
+// the initial position ("startpos") and then makes the moves given in the following
+// move list ("moves").
+void position(Position& pos, std::istringstream& is, StateListPtr& states) {
- Move m;
- string token, fen;
+ Move m;
+ std::string token, fen;
is >> token;
if (token == "startpos")
{
fen = StartFEN;
- is >> token; // Consume "moves" token if any
+ is >> token; // Consume the "moves" token, if any
}
else if (token == "fen")
while (is >> token && token != "moves")
else
return;
- states = StateListPtr(new std::deque<StateInfo>(1)); // Drop old and create a new one
+ states = StateListPtr(new std::deque<StateInfo>(1)); // Drop the old state and create a new one
pos.set(fen, Options["UCI_Chess960"], &states->back(), Threads.main());
- // Parse move list (if any)
+ // Parse the move list, if any
while (is >> token && (m = UCI::to_move(pos, token)) != MOVE_NONE)
{
states->emplace_back();
pos.do_move(m, states->back());
}
- }
+}
+
+// Prints the evaluation of the current position,
+// consistent with the UCI options set so far.
+void trace_eval(Position& pos) {
+
+ StateListPtr states(new std::deque<StateInfo>(1));
+ Position p;
+ p.set(pos.fen(), Options["UCI_Chess960"], &states->back(), Threads.main());
+
+ 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").
+// Called when the engine receives the "setoption" UCI command.
+// The function updates the UCI option ("name") to the given value ("value").
- void setoption(istringstream& is) {
+void setoption(std::istringstream& is) {
- string token, name, value;
+ Threads.main()->wait_for_search_finished();
- is >> token; // Consume "name" token
+ std::string token, name, value;
- // Read option name (can contain spaces)
+ is >> token; // Consume the "name" token
+
+ // Read the option name (can contain spaces)
while (is >> token && token != "value")
name += (name.empty() ? "" : " ") + token;
- // Read option value (can contain spaces)
+ // Read the option value (can contain spaces)
while (is >> token)
value += (value.empty() ? "" : " ") + token;
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.
+// Called when the engine receives the "go" UCI command. The function sets the
+// thinking time and other parameters from the input string then stars with a search
- void go(Position& pos, istringstream& is, StateListPtr& states) {
+void go(Position& pos, std::istringstream& is, StateListPtr& states) {
Search::LimitsType limits;
- string token;
- bool ponderMode = false;
+ std::string token;
+ bool ponderMode = false;
- limits.startTime = now(); // As early as possible!
+ limits.startTime = now(); // The search starts as early as possible
while (is >> token)
- if (token == "searchmoves")
+ 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[WHITE];
- else if (token == "btime") is >> limits.time[BLACK];
- else if (token == "winc") is >> limits.inc[WHITE];
- else if (token == "binc") is >> limits.inc[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;
+ else if (token == "wtime")
+ is >> limits.time[WHITE];
+ else if (token == "btime")
+ is >> limits.time[BLACK];
+ else if (token == "winc")
+ is >> limits.inc[WHITE];
+ else if (token == "binc")
+ is >> limits.inc[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;
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.
+// Called when the engine receives the "bench" command.
+// First, a list of UCI commands is set up according to the bench
+// parameters, then it is run one by one, printing a summary at the end.
- void bench(Position& pos, istream& args, StateListPtr& states) {
+void bench(Position& pos, std::istream& args, StateListPtr& states) {
- string token;
- uint64_t num, nodes = 0, cnt = 1;
+ std::string token;
+ uint64_t num, nodes = 0, cnt = 1;
- vector<string> list = setup_bench(pos, args);
- num = count_if(list.begin(), list.end(), [](string s) { return s.find("go ") == 0 || s.find("eval") == 0; });
+ std::vector<std::string> list = setup_bench(pos, args);
+
+ num = count_if(list.begin(), list.end(),
+ [](const std::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;
+ std::istringstream is(cmd);
+ is >> std::skipws >> token;
if (token == "go" || token == "eval")
{
- cerr << "\nPosition: " << cnt++ << '/' << num << endl;
+ std::cerr << "\nPosition: " << cnt++ << '/' << num << " (" << pos.fen() << ")"
+ << std::endl;
if (token == "go")
{
- go(pos, is, states);
- Threads.main()->wait_for_search_finished();
- nodes += Threads.nodes_searched();
+ go(pos, is, states);
+ Threads.main()->wait_for_search_finished();
+ nodes += Threads.nodes_searched();
}
else
- sync_cout << "\n" << Eval::trace(pos) << sync_endl;
+ 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
+ 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 a while
}
- elapsed = now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero'
+ elapsed = now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero'
- dbg_print(); // Just before exiting
+ dbg_print();
- cerr << "\n==========================="
- << "\nTotal time (ms) : " << elapsed
- << "\nNodes searched : " << nodes
- << "\nNodes/second : " << 1000 * nodes / elapsed << endl;
- }
+ std::cerr << "\n==========================="
+ << "\nTotal time (ms) : " << elapsed << "\nNodes searched : " << nodes
+ << "\nNodes/second : " << 1000 * nodes / elapsed << std::endl;
+}
+
+// The win rate model returns the probability of winning (in per mille units) given an
+// eval and a game ply. It fits the LTC fishtest statistics rather accurately.
+int win_rate_model(Value v, int ply) {
+
+ // The model only captures up to 240 plies, so limit the input and then rescale
+ double m = std::min(240, ply) / 64.0;
-} // namespace
+ // The coefficients of a third-order polynomial fit is based on the fishtest data
+ // for two parameters that need to transform eval to the argument of a logistic
+ // function.
+ constexpr double as[] = {0.38036525, -2.82015070, 23.17882135, 307.36768407};
+ constexpr double bs[] = {-2.29434733, 13.27689788, -14.26828904, 63.45318330};
+ // Enforce that NormalizeToPawnValue corresponds to a 50% win rate at ply 64
+ static_assert(UCI::NormalizeToPawnValue == int(as[0] + as[1] + as[2] + as[3]));
-/// 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.
+ 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 the eval to centipawns with limited range
+ double x = std::clamp(double(v), -4000.0, 4000.0);
+
+ // Return the win rate in per mille units, rounded to the nearest integer
+ return int(0.5 + 1000 / (1 + std::exp((a - x) / b)));
+}
+
+} // namespace
+
+
+// Waits for a command from the stdin, parses it, and then calls the appropriate
+// function. It also intercepts an end-of-file (EOF) indication from the stdin to ensure a
+// graceful exit if the GUI dies unexpectedly. When called with some command-line arguments,
+// like running 'bench', the function returns immediately after the command is executed.
+// In addition to the UCI ones, some additional debug commands are also supported.
void UCI::loop(int argc, char* argv[]) {
- Position pos;
- string token, cmd;
- StateListPtr states(new std::deque<StateInfo>(1));
-
- pos.set(StartFEN, false, &states->back(), Threads.main());
-
- for (int i = 1; i < argc; ++i)
- cmd += std::string(argv[i]) + " ";
-
- 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")
- sync_cout << "id name " << engine_info(true)
- << "\n" << Options
- << "\nuciok" << sync_endl;
-
- else if (token == "setoption") setoption(is);
- else if (token == "go") go(pos, is, states);
- else if (token == "position") position(pos, is, states);
- else if (token == "ucinewgame") 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") sync_cout << Eval::trace(pos) << sync_endl;
- else if (token == "compiler") sync_cout << compiler_info() << sync_endl;
- else
- sync_cout << "Unknown command: " << cmd << sync_endl;
-
- } while (token != "quit" && argc == 1); // Command line args are one-shot
+ Position pos;
+ std::string token, cmd;
+ StateListPtr states(new std::deque<StateInfo>(1));
+
+ pos.set(StartFEN, false, &states->back(), Threads.main());
+
+ for (int i = 1; i < argc; ++i)
+ cmd += std::string(argv[i]) + " ";
+
+ do
+ {
+ if (argc == 1
+ && !getline(std::cin, cmd)) // Wait for an input or an end-of-file (EOF) indication
+ cmd = "quit";
+
+ std::istringstream is(cmd);
+
+ token.clear(); // Avoid a stale if getline() returns nothing or a blank line
+ is >> std::skipws >> token;
+
+ if (token == "quit" || token == "stop")
+ Threads.stop = true;
+
+ // The GUI sends 'ponderhit' to tell that the user has played the expected move.
+ // So, 'ponderhit' is sent if pondering was done on the same move that the user
+ // has played. The search should continue, but should also switch from pondering
+ // to the normal search.
+ else if (token == "ponderhit")
+ Threads.main()->ponder = false; // Switch to the normal search
+
+ else if (token == "uci")
+ sync_cout << "id name " << engine_info(true) << "\n"
+ << Options << "\nuciok" << sync_endl;
+
+ else if (token == "setoption")
+ setoption(is);
+ else if (token == "go")
+ go(pos, is, states);
+ else if (token == "position")
+ position(pos, is, states);
+ else if (token == "ucinewgame")
+ Search::clear();
+ else if (token == "isready")
+ sync_cout << "readyok" << sync_endl;
+
+ // Add custom non-UCI commands, mainly for debugging purposes.
+ // These commands must not be used 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<std::string> filename;
+ std::string f;
+ if (is >> std::skipws >> f)
+ filename = f;
+ Eval::NNUE::save_eval(filename);
+ }
+ else if (token == "--help" || token == "help" || token == "--license" || token == "license")
+ sync_cout
+ << "\nStockfish is a powerful chess engine for playing and analyzing."
+ "\nIt is released as free software licensed under the GNU GPLv3 License."
+ "\nStockfish is normally used with a graphical user interface (GUI) and implements"
+ "\nthe Universal Chess Interface (UCI) protocol to communicate with a GUI, an API, etc."
+ "\nFor any further information, visit https://github.com/official-stockfish/Stockfish#readme"
+ "\nor read the corresponding README.md and Copying.txt files distributed along with this program.\n"
+ << sync_endl;
+ else if (!token.empty() && token[0] != '#')
+ sync_cout << "Unknown command: '" << cmd << "'. Type help for more information."
+ << sync_endl;
+
+ } while (token != "quit" && argc == 1); // The command-line arguments are one-shot
}
-/// UCI::value() converts a Value to a string suitable for use with the UCI
-/// protocol specification:
-///
-/// cp <x> The score from the engine's point of view in centipawns.
-/// mate <y> Mate in y moves, not plies. If the engine is getting mated
-/// use negative values for y.
+// Turns a Value to an integer centipawn number,
+// without treatment of mate and similar special scores.
+int UCI::to_cp(Value v) { return 100 * v / UCI::NormalizeToPawnValue; }
-string UCI::value(Value v) {
+// Converts a Value to a string by adhering to the UCI protocol specification:
+//
+// cp <x> The score from the engine's point of view in centipawns.
+// mate <y> Mate in 'y' moves (not plies). If the engine is getting mated,
+// uses negative values for 'y'.
+std::string UCI::value(Value v) {
- assert(-VALUE_INFINITE < v && v < VALUE_INFINITE);
+ assert(-VALUE_INFINITE < v && v < VALUE_INFINITE);
- stringstream ss;
+ std::stringstream ss;
- if (abs(v) < VALUE_MATE - MAX_PLY)
- ss << "cp " << v * 100 / PawnValueEg;
- else
- ss << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2;
+ if (std::abs(v) < VALUE_TB_WIN_IN_MAX_PLY)
+ ss << "cp " << UCI::to_cp(v);
+ else if (std::abs(v) <= VALUE_TB)
+ {
+ const int ply = VALUE_TB - std::abs(v); // recompute ss->ply
+ ss << "cp " << (v > 0 ? 20000 - ply : -20000 + ply);
+ }
+ else
+ ss << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2;
- return ss.str();
+ return ss.str();
}
-/// UCI::square() converts a Square to a string in algebraic notation (g1, a7, etc.)
+// Reports the win-draw-loss (WDL) statistics given an evaluation
+// and a game ply based on the data gathered for fishtest LTC games.
+std::string UCI::wdl(Value v, int ply) {
-std::string UCI::square(Square s) {
- return std::string{ char('a' + file_of(s)), char('1' + rank_of(s)) };
+ std::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::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'.
+// Converts a Square to a string in algebraic notation (g1, a7, etc.)
+std::string UCI::square(Square s) {
+ return std::string{char('a' + file_of(s)), char('1' + rank_of(s))};
+}
-string UCI::move(Move m, bool chess960) {
- Square from = from_sq(m);
- Square to = to_sq(m);
+// Converts a Move to a string in coordinate notation (g1f3, a7a8q).
+// The only special case is castling where the e1g1 notation is printed in
+// standard chess mode and in e1h1 notation it is printed in Chess960 mode.
+// Internally, all castling moves are always encoded as 'king captures rook'.
+std::string UCI::move(Move m, bool chess960) {
- if (m == MOVE_NONE)
- return "(none)";
+ if (m == MOVE_NONE)
+ return "(none)";
- if (m == MOVE_NULL)
- return "0000";
+ if (m == MOVE_NULL)
+ return "0000";
- if (type_of(m) == CASTLING && !chess960)
- to = make_square(to > from ? FILE_G : FILE_C, rank_of(from));
+ Square from = from_sq(m);
+ Square to = to_sq(m);
- string move = UCI::square(from) + UCI::square(to);
+ if (type_of(m) == CASTLING && !chess960)
+ to = make_square(to > from ? FILE_G : FILE_C, rank_of(from));
- if (type_of(m) == PROMOTION)
- move += " pnbrqk"[promotion_type(m)];
+ std::string move = UCI::square(from) + UCI::square(to);
- return move;
-}
+ if (type_of(m) == PROMOTION)
+ move += " pnbrqk"[promotion_type(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) {
+// 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, std::string& str) {
- if (str.length() == 5) // Junior could send promotion piece in uppercase
- str[4] = char(tolower(str[4]));
+ if (str.length() == 5)
+ str[4] = char(tolower(str[4])); // The promotion piece character must be lowercased
- for (const auto& m : MoveList<LEGAL>(pos))
- if (str == UCI::move(m, pos.is_chess960()))
- return m;
+ for (const auto& m : MoveList<LEGAL>(pos))
+ if (str == UCI::move(m, pos.is_chess960()))
+ return m;
- return MOVE_NONE;
+ return MOVE_NONE;
}
+
+} // namespace Stockfish
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+ Copyright (C) 2004-2023 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
#ifndef UCI_H_INCLUDED
#define UCI_H_INCLUDED
+#include <cstddef>
+#include <iosfwd>
#include <map>
#include <string>
#include "types.h"
+namespace Stockfish {
+
class Position;
namespace UCI {
+// Normalizes the internal value as reported by evaluate or search
+// to the UCI centipawn result used in output. This value is derived from
+// the win_rate_model() such that Stockfish outputs an advantage of
+// "100 centipawns" for a position if the engine has a 50% probability to win
+// from this position in self-play at fishtest LTC time control.
+const int NormalizeToPawnValue = 328;
+
class Option;
-/// Custom comparator because UCI options should be case insensitive
+// Define a custom comparator, because the UCI options should be case-insensitive
struct CaseInsensitiveLess {
- bool operator() (const std::string&, const std::string&) const;
+ bool operator()(const std::string&, const std::string&) const;
};
-/// Our options container is actually a std::map
-typedef std::map<std::string, Option, CaseInsensitiveLess> OptionsMap;
+// The options container is defined as a std::map
+using OptionsMap = std::map<std::string, Option, CaseInsensitiveLess>;
-/// Option class implements an option as defined by UCI protocol
+// The Option class implements each option as specified by the UCI protocol
class Option {
- typedef void (*OnChange)(const Option&);
+ using OnChange = void (*)(const Option&);
-public:
- Option(OnChange = nullptr);
- Option(bool v, OnChange = nullptr);
- Option(const char* v, OnChange = nullptr);
- Option(double v, int minv, int maxv, OnChange = nullptr);
- Option(const char* v, const char* cur, OnChange = nullptr);
+ public:
+ Option(OnChange = nullptr);
+ Option(bool v, OnChange = nullptr);
+ Option(const char* v, OnChange = nullptr);
+ Option(double v, int minv, int maxv, OnChange = nullptr);
+ Option(const char* v, const char* cur, OnChange = nullptr);
- Option& operator=(const std::string&);
- void operator<<(const Option&);
- operator double() const;
- operator std::string() const;
- bool operator==(const char*) const;
+ Option& operator=(const std::string&);
+ void operator<<(const Option&);
+ operator int() const;
+ operator std::string() const;
+ bool operator==(const char*) const;
-private:
- friend std::ostream& operator<<(std::ostream&, const OptionsMap&);
+ private:
+ friend std::ostream& operator<<(std::ostream&, const OptionsMap&);
- std::string defaultValue, currentValue, type;
- int min, max;
- size_t idx;
- OnChange on_change;
+ std::string defaultValue, currentValue, type;
+ int min, max;
+ size_t idx;
+ OnChange on_change;
};
-void init(OptionsMap&);
-void loop(int argc, char* argv[]);
+void init(OptionsMap&);
+void loop(int argc, char* argv[]);
+int to_cp(Value v);
std::string value(Value v);
std::string square(Square s);
std::string move(Move m, bool chess960);
-std::string pv(const Position& pos, Depth depth, Value alpha, Value beta);
-Move to_move(const Position& pos, std::string& str);
+std::string pv(const Position& pos, Depth depth);
+std::string wdl(Value v, int ply);
+Move to_move(const Position& pos, std::string& str);
-} // namespace UCI
+} // namespace UCI
extern UCI::OptionsMap Options;
-#endif // #ifndef UCI_H_INCLUDED
+} // namespace Stockfish
+
+#endif // #ifndef UCI_H_INCLUDED
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
- Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
- Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
- Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+ Copyright (C) 2004-2023 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
#include <algorithm>
#include <cassert>
+#include <cctype>
+#include <cstddef>
+#include <iosfwd>
+#include <istream>
+#include <map>
#include <ostream>
#include <sstream>
+#include <string>
+#include "evaluate.h"
#include "misc.h"
#include "search.h"
+#include "syzygy/tbprobe.h"
#include "thread.h"
#include "tt.h"
+#include "types.h"
#include "uci.h"
-#include "syzygy/tbprobe.h"
+#include "hashprobe.h"
using std::string;
-UCI::OptionsMap Options; // Global object
+namespace Stockfish {
-namespace UCI {
+UCI::OptionsMap Options; // Global object
+std::unique_ptr<HashProbeThread> hash_probe_thread;
-/// '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(o); }
-void on_logger(const Option& o) { start_logger(o); }
-void on_threads(const Option& o) { Threads.set(o); }
-void on_tb_path(const Option& o) { Tablebases::init(o); }
+namespace UCI {
+// 'On change' actions, triggered by an option's value change
+static void on_clear_hash(const Option&) { Search::clear(); }
+static void on_hash_size(const Option& o) { TT.resize(size_t(o)); }
+static void on_logger(const Option& o) { start_logger(o); }
+static void on_threads(const Option& o) { Threads.set(size_t(o)); }
+static void on_tb_path(const Option& o) { Tablebases::init(o); }
+static void on_use_NNUE(const Option& ) { Eval::NNUE::init(); }
+static void on_eval_file(const Option& ) { Eval::NNUE::init(); }
+static void on_rpc_server_address(const Option& o) {
+ if (hash_probe_thread) {
+ hash_probe_thread->Shutdown();
+ }
+ std::string addr = o;
+ hash_probe_thread.reset(new HashProbeThread(addr));
+}
-/// Our case insensitive less() function as required by UCI protocol
-bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const {
+// 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); });
+ return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(),
+ [](char c1, char c2) { return tolower(c1) < tolower(c2); });
}
-/// init() initializes the UCI options to their hard-coded default values
-
+// Initializes the UCI options to their hard-coded default values
void init(OptionsMap& o) {
- // at most 2^32 clusters.
- constexpr int MaxHashMB = Is64Bit ? 131072 : 2048;
-
- o["Debug Log File"] << Option("", on_logger);
- o["Contempt"] << Option(24, -100, 100);
- o["Analysis Contempt"] << Option("Both var Off var White var Black var Both", "Both");
- 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, 0, 20);
- o["Move Overhead"] << Option(30, 0, 5000);
- o["Minimum Thinking Time"] << Option(20, 0, 5000);
- o["Slow Mover"] << Option(84, 10, 1000);
- o["nodestime"] << Option(0, 0, 10000);
- o["UCI_Chess960"] << Option(false);
- o["UCI_AnalyseMode"] << Option(false);
- o["UCI_LimitStrength"] << Option(false);
- o["UCI_Elo"] << Option(1350, 1350, 2850);
- o["SyzygyPath"] << Option("<empty>", on_tb_path);
- o["SyzygyProbeDepth"] << Option(1, 1, 100);
- o["Syzygy50MoveRule"] << Option(true);
- o["SyzygyProbeLimit"] << Option(7, 0, 7);
+ constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048;
+
+ o["Debug Log File"] << Option("", on_logger);
+ o["Threads"] << Option(1, 1, 1024, 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, 0, 20);
+ o["Move Overhead"] << Option(10, 0, 5000);
+ o["nodestime"] << Option(0, 0, 10000);
+ o["UCI_Chess960"] << Option(false);
+ o["UCI_LimitStrength"] << Option(false);
+ o["UCI_Elo"] << Option(1320, 1320, 3190);
+ o["UCI_ShowWDL"] << Option(false);
+ o["SyzygyPath"] << Option("<empty>", on_tb_path);
+ o["SyzygyProbeDepth"] << Option(1, 1, 100);
+ o["Syzygy50MoveRule"] << Option(true);
+ o["SyzygyProbeLimit"] << Option(7, 0, 7);
+ o["EvalFile"] << Option(EvalFileDefaultName, on_eval_file);
+ o["RPCServerAddress"] << Option("<empty>", on_rpc_server_address);
}
-/// 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.
-
+// 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) {
- for (size_t idx = 0; idx < om.size(); ++idx)
- for (const auto& it : om)
- if (it.second.idx == idx)
- {
- const Option& o = it.second;
- os << "\noption name " << it.first << " type " << o.type;
+ for (size_t idx = 0; idx < om.size(); ++idx)
+ for (const auto& it : om)
+ if (it.second.idx == idx)
+ {
+ const Option& o = it.second;
+ 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 == "string" || o.type == "check" || o.type == "combo")
+ os << " default " << o.defaultValue;
- if (o.type == "spin")
- os << " default " << int(stof(o.defaultValue))
- << " min " << o.min
- << " max " << o.max;
+ if (o.type == "spin")
+ os << " default " << int(stof(o.defaultValue)) << " min " << o.min << " max "
+ << o.max;
- break;
- }
+ break;
+ }
- return os;
+ return os;
}
-/// Option class constructors and conversion operators
+// 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(bool v, OnChange f) : type("check"), min(0), max(0), on_change(f)
-{ defaultValue = currentValue = (v ? "true" : "false"); }
+Option::Option(const char* v, OnChange f) :
+ type("string"),
+ min(0),
+ max(0),
+ on_change(f) {
+ defaultValue = currentValue = v;
+}
-Option::Option(OnChange f) : type("button"), min(0), max(0), on_change(f)
-{}
+Option::Option(bool v, OnChange f) :
+ type("check"),
+ min(0),
+ max(0),
+ on_change(f) {
+ defaultValue = currentValue = (v ? "true" : "false");
+}
-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::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::Option(const char* v, const char* cur, OnChange f) : type("combo"), min(0), max(0), on_change(f)
-{ defaultValue = v; currentValue = cur; }
+Option::Option(const char* v, const char* cur, OnChange f) :
+ type("combo"),
+ min(0),
+ max(0),
+ on_change(f) {
+ defaultValue = v;
+ currentValue = cur;
+}
-Option::operator double() const {
- assert(type == "check" || type == "spin");
- return (type == "spin" ? stof(currentValue) : currentValue == "true");
+Option::operator int() const {
+ assert(type == "check" || type == "spin");
+ return (type == "spin" ? std::stoi(currentValue) : currentValue == "true");
}
Option::operator std::string() const {
- assert(type == "string");
- return currentValue;
+ assert(type == "string");
+ return currentValue;
}
bool Option::operator==(const char* s) const {
- assert(type == "combo");
- return !CaseInsensitiveLess()(currentValue, s)
- && !CaseInsensitiveLess()(s, currentValue);
+ assert(type == "combo");
+ return !CaseInsensitiveLess()(currentValue, s) && !CaseInsensitiveLess()(s, currentValue);
}
-/// operator<<() inits options and assigns idx in the correct printing order
+// Inits options and assigns idx in the correct printing order
void Option::operator<<(const Option& o) {
- static size_t insert_order = 0;
+ static size_t insert_order = 0;
- *this = o;
- idx = insert_order++;
+ *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.
-
+// 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());
+ assert(!type.empty());
- if ( (type != "button" && v.empty())
- || (type == "check" && v != "true" && v != "false")
- || (type == "spin" && (stof(v) < min || stof(v) > max)))
- return *this;
+ if ((type != "button" && type != "string" && v.empty())
+ || (type == "check" && v != "true" && v != "false")
+ || (type == "spin" && (stof(v) < min || stof(v) > max)))
+ return *this;
- if (type == "combo")
- {
- OptionsMap comboMap; // To have case insensitive compare
- string token;
- std::istringstream ss(defaultValue);
- while (ss >> token)
- comboMap[token] << Option();
- if (!comboMap.count(v) || v == "var")
- return *this;
- }
+ if (type == "combo")
+ {
+ OptionsMap comboMap; // To have case insensitive compare
+ string token;
+ std::istringstream ss(defaultValue);
+ while (ss >> token)
+ comboMap[token] << Option();
+ if (!comboMap.count(v) || v == "var")
+ return *this;
+ }
- if (type != "button")
- currentValue = v;
+ if (type != "button")
+ currentValue = v;
- if (on_change)
- on_change(*this);
+ if (on_change)
+ on_change(*this);
- return *this;
+ return *this;
}
-} // namespace UCI
+} // namespace UCI
+
+} // namespace Stockfish
--valgrind)
echo "valgrind testing started"
prefix=''
- exeprefix='valgrind --error-exitcode=42'
+ 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 --error-exitcode=42'
+ exeprefix='valgrind --fair-sched=try --error-exitcode=42'
postfix='1>/dev/null'
threads="2"
;;
threads="2"
cat << EOF > tsan.supp
-race:TTEntry::move
-race:TTEntry::depth
-race:TTEntry::bound
-race:TTEntry::save
-race:TTEntry::value
-race:TTEntry::eval
-race:TTEntry::is_pv
+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:TranspositionTable::probe
-race:TranspositionTable::hashfull
+race:Stockfish::TranspositionTable::probe
+race:Stockfish::TranspositionTable::hashfull
EOF
;;
esac
+cat << EOF > bench_tmp.epd
+Rn6/1rbq1bk1/2p2n1p/2Bp1p2/3Pp1pP/1N2P1P1/2Q1NPB1/6K1 w - - 2 26
+rnbqkb1r/ppp1pp2/5n1p/3p2p1/P2PP3/5P2/1PP3PP/RNBQKBNR w KQkq - 0 3
+3qnrk1/4bp1p/1p2p1pP/p2bN3/1P1P1B2/P2BQ3/5PP1/4R1K1 w - - 9 28
+r4rk1/1b2ppbp/pq4pn/2pp1PB1/1p2P3/1P1P1NN1/1PP3PP/R2Q1RK1 w - - 0 13
+EOF
+
# simple command line testing
for args in "eval" \
"go nodes 1000" \
"go depth 10" \
+ "go perft 4" \
"go movetime 1000" \
"go wtime 8000 btime 8000 winc 500 binc 500" \
- "bench 128 $threads 10 default depth"
+ "go wtime 1000 btime 1000 winc 0 binc 0" \
+ "go wtime 1000 btime 1000 winc 0 binc 0" \
+ "go wtime 1000 btime 1000 winc 0 binc 0 movestogo 5" \
+ "go movetime 200" \
+ "go nodes 20000 searchmoves e2e4 d2d4" \
+ "bench 128 $threads 8 default depth" \
+ "bench 128 $threads 3 bench_tmp.epd depth" \
+ "export_net verify.nnue" \
+ "d" \
+ "compiler" \
+ "license" \
+ "uci"
do
echo "$prefix $exeprefix ./stockfish $args $postfix"
done
+# verify the generated net equals the base net
+network=`./stockfish uci | grep 'option name EvalFile type string default' | awk '{print $NF}'`
+echo "Comparing $network to the written verify.nnue"
+diff $network verify.nnue
+
# more general testing, following an uci protocol exchange
cat << EOF > game.exp
- set timeout 10
+ set timeout 240
spawn $exeprefix ./stockfish
send "uci\n"
expect "uciok"
+ # send "setoption name Debug Log File value debug.log\n"
send "setoption name Threads value $threads\n"
send "ucinewgame\n"
expect "bestmove"
send "position fen 5rk1/1K4p1/8/8/3B4/8/8/8 b - - 0 1\n"
- send "go depth 30\n"
+ send "go depth 10\n"
+ expect "bestmove"
+
+ send "setoption name UCI_ShowWDL value true\n"
+ send "position startpos\n"
+ send "flip\n"
+ send "go depth 5\n"
+ expect "bestmove"
+
+ send "setoption name Skill Level value 10\n"
+ send "position startpos\n"
+ send "go depth 5\n"
expect "bestmove"
+ send "setoption name Clear Hash\n"
+
+ send "setoption name EvalFile value verify.nnue\n"
+ send "position startpos\n"
+ send "go depth 5\n"
+ expect "bestmove"
+
+ send "setoption name MultiPV value 4\n"
+ send "position startpos\n"
+ send "go depth 5\n"
+
send "quit\n"
expect eof
send "uci\n"
send "setoption name SyzygyPath value ../tests/syzygy/\n"
expect "info string Found 35 tablebases" {} timeout {exit 1}
- send "bench 128 1 10 default depth\n"
+ send "bench 128 1 8 default depth\n"
+ send "ucinewgame\n"
+ send "position fen 4k3/PP6/8/8/8/8/8/4K3 w - - 0 1\n"
+ send "go depth 5\n"
+ expect "bestmove"
+ send "position fen 8/1P6/2B5/8/4K3/8/6k1/8 w - - 0 1\n"
+ send "go depth 5\n"
+ expect "bestmove"
send "quit\n"
expect eof
done
-rm -f tsan.supp
+rm -f tsan.supp bench_tmp.epd
echo "instrumented testing OK"
echo "reprosearch testing started"
-# repeat two short games, separated by ucinewgame.
+# 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
expect eof
EOF
-# to increase the likelyhood of finding a non-reproducible case,
+# 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
# obtain
-signature=`./stockfish bench 2>&1 | grep "Nodes searched : " | awk '{print $4}'`
+signature=`eval "$WINE_PATH ./stockfish bench 2>&1" | grep "Nodes searched : " | awk '{print $4}'`
if [ $# -gt 0 ]; then
# compare to given reference