name: Stockfish
on:
push:
+ tags:
+ - '*'
branches:
- master
- tools
- master
- tools
jobs:
+ Prerelease:
+ if: github.ref == 'refs/heads/master'
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+
+ # 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@v0.2.1
+ if: env.COMMIT_SHA != 'null'
+ with:
+ tag_name: ${{ env.COMMIT_SHA }}
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
Sanitizers:
uses: ./.github/workflows/stockfish_sanitizers.yml
Tests:
Compiles:
uses: ./.github/workflows/stockfish_compile_test.yml
Binaries:
- if: github.ref == 'refs/heads/master'
+ 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'
+ if: github.ref == 'refs/heads/master' || (startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag')
uses: ./.github/workflows/stockfish_arm_binaries.yml
compiler: aarch64-linux-android21-clang++
emu: qemu-aarch64
comp: ndk
- shell: bash {0}
+ shell: bash
- name: Android NDK arm
os: ubuntu-22.04
compiler: armv7a-linux-androideabi21-clang++
emu: qemu-arm
comp: ndk
- shell: bash {0}
+ 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
cp "Top CPU Contributors.txt" stockfish/
cp Copying.txt stockfish/
cp AUTHORS stockfish/
+ cp CITATION.cff stockfish/
+ cp README.md stockfish/
tar -cvf stockfish-android-$BINARY.tar stockfish
- name: Upload binaries
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@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@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
COMPILER: ${{ matrix.config.compiler }}
COMP: ${{ matrix.config.comp }}
EXT: ${{ matrix.config.ext }}
- OS: ${{ matrix.config.os }}
+ NAME: ${{ matrix.config.simple_name }}
BINARY: ${{ matrix.binaries }}
strategy:
matrix:
config:
- name: Ubuntu 20.04 GCC
os: ubuntu-20.04
+ simple_name: ubuntu
compiler: g++
comp: gcc
- shell: bash {0}
+ shell: bash
+ archive_ext: tar
- name: MacOS 12 Apple Clang
os: macos-12
+ simple_name: macos
compiler: clang++
comp: clang
- shell: bash {0}
+ 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
+ archive_ext: zip
binaries:
- x86-64
- x86-64-modern
- x86-64-avx2
+ - x86-64-bmi2
exclude:
- binaries: x86-64-avx2
- config: {os: macos-12}
+ config: { os: macos-12 }
+ - binaries: x86-64-bmi2
+ config: { os: macos-12 }
defaults:
run:
working-directory: src
uses: msys2/setup-msys2@v2
with:
msystem: ${{ matrix.config.msys_sys }}
- install: mingw-w64-${{ matrix.config.msys_env }} make git
+ install: mingw-w64-${{ matrix.config.msys_env }} make git zip
- name: Download the used network from the fishtest framework
run: make net
- name: Compile ${{ matrix.binaries }} build
run: |
- make clean
make -j2 profile-build ARCH=$BINARY COMP=$COMP
make strip ARCH=$BINARY COMP=$COMP
- mv ./stockfish$EXT ../stockfish-$OS-$BINARY$EXT
+ mv ./stockfish$EXT ../stockfish-$NAME-$BINARY$EXT
- name: Remove non src files
- run: rm -f *.o .depend *.nnue
+ run: git clean -fx
- name: Download wiki
run: |
git clone https://github.com/official-stockfish/Stockfish.wiki.git ../wiki
- cd ../wiki
- rm -rf .git
+ rm -rf ../wiki/.git
- - name: Create tar archive.
+ - name: Create directory.
run: |
cd ..
mkdir stockfish
cp -r wiki stockfish/
cp -r src stockfish/
- cp stockfish-$OS-$BINARY$EXT stockfish/
+ cp stockfish-$NAME-$BINARY$EXT stockfish/
cp "Top CPU Contributors.txt" stockfish/
cp Copying.txt stockfish/
cp AUTHORS stockfish/
- tar -cvf stockfish-$OS-$BINARY.tar stockfish
+ cp CITATION.cff stockfish/
+ cp README.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.os }}-${{ matrix.binaries }}.tar
+ 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@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@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 }}
os: ubuntu-20.04
compiler: g++
comp: gcc
- shell: bash {0}
+ shell: bash
- name: Ubuntu 20.04 Clang
os: ubuntu-20.04
compiler: clang++
comp: clang
- shell: bash {0}
+ shell: bash
- name: MacOS 12 Apple Clang
os: macos-12
compiler: clang++
comp: clang
- shell: bash {0}
+ shell: bash
- name: MacOS 12 GCC 11
os: macos-12
compiler: g++-11
comp: gcc
- shell: bash {0}
+ shell: bash
- name: Windows 2022 Mingw-w64 GCC x86_64
os: windows-2022
compiler: g++
os: ubuntu-20.04
compiler: g++
comp: gcc
- shell: bash {0}
+ shell: bash
sanitizers:
- name: Run with thread sanitizer
make_option: sanitize=thread
comp: gcc
run_32bit_tests: true
run_64bit_tests: true
- shell: bash {0}
+ 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 {0}
+ shell: bash
- name: Android NDK aarch64
os: ubuntu-22.04
compiler: aarch64-linux-android21-clang++
comp: ndk
run_armv8_tests: true
- shell: bash {0}
+ shell: bash
- name: Android NDK arm
os: ubuntu-22.04
compiler: armv7a-linux-androideabi21-clang++
comp: ndk
run_armv7_tests: true
- shell: bash {0}
+ shell: bash
- name: MacOS 12 Apple Clang
os: macos-12
compiler: clang++
comp: clang
run_64bit_tests: true
- shell: bash {0}
+ shell: bash
- name: MacOS 12 GCC 11
os: macos-12
compiler: g++-11
comp: gcc
run_64bit_tests: true
- shell: bash {0}
+ shell: bash
- name: Windows 2022 Mingw-w64 GCC x86_64
os: windows-2022
compiler: g++
echo "ANDROID_NDK_BIN=$ANDROID_NDK_BIN" >> $GITHUB_ENV
fi
+ - 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
- name: Extract the bench number from the commit history
run: |
- git log HEAD | grep "\b[Bb]ench[ :]\+[0-9]\{7\}" | head -n 1 | sed "s/[^0-9]*\([0-9]*\).*/\1/g" > git_sig
- [ -s git_sig ] && echo "benchref=$(cat git_sig)" >> $GITHUB_ENV && echo "Reference bench:" $(cat git_sig) || echo "No bench found"
+ 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: |
# x86-32 tests
- name: Test debug x86-32 build
- if: ${{ matrix.config.run_32bit_tests }}
+ if: matrix.config.run_32bit_tests
run: |
export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG"
make clean
../tests/signature.sh $benchref
- name: Test x86-32 build
- if: ${{ matrix.config.run_32bit_tests }}
+ 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 }}
+ 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 }}
+ 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 }}
+ if: matrix.config.run_32bit_tests
run: |
make clean
make -j2 ARCH=general-32 build
# x86-64 tests
- name: Test debug x86-64-modern build
- if: ${{ matrix.config.run_64bit_tests }}
+ if: matrix.config.run_64bit_tests
run: |
export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG"
make clean
make -j2 ARCH=x86-64-modern optimize=no debug=yes build
../tests/signature.sh $benchref
+ - name: Test x86-64-bmi2 build
+ if: matrix.config.run_64bit_tests && runner.os != 'macOS'
+ 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 && runner.os != 'macOS'
+ run: |
+ make clean
+ make -j2 ARCH=x86-64-avx2 build
+ ../tests/signature.sh $benchref
+
- name: Test x86-64-modern build
- if: ${{ matrix.config.run_64bit_tests }}
+ if: matrix.config.run_64bit_tests
run: |
make clean
make -j2 ARCH=x86-64-modern build
../tests/signature.sh $benchref
- name: Test x86-64-ssse3 build
- if: ${{ matrix.config.run_64bit_tests }}
+ 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 }}
+ 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 }}
+ if: matrix.config.run_64bit_tests
run: |
make clean
make -j2 ARCH=x86-64 build
# armv8 tests
- name: Test armv8 build
- if: ${{ matrix.config.run_armv8_tests }}
+ if: matrix.config.run_armv8_tests
run: |
export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH
export LDFLAGS="-static -Wno-unused-command-line-argument"
# armv7 tests
- name: Test armv7 build
- if: ${{ matrix.config.run_armv7_tests }}
+ if: matrix.config.run_armv7_tests
run: |
export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH
export LDFLAGS="-static -Wno-unused-command-line-argument"
../tests/signature.sh $benchref
- name: Test armv7-neon build
- if: ${{ matrix.config.run_armv7_tests }}
+ if: matrix.config.run_armv7_tests
run: |
export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH
export LDFLAGS="-static -Wno-unused-command-line-argument"
# Other tests
- name: Check perft and search reproducibility
- if: ${{ matrix.config.run_64bit_tests }}
+ if: matrix.config.run_64bit_tests
run: |
make clean
make -j2 ARCH=x86-64-modern build
-# List of authors for Stockfish
-
-# Founders of the Stockfish project and fishtest infrastructure
+# Founders of the Stockfish project and Fishtest infrastructure
Tord Romstad (romstad)
Marco Costalba (mcostalba)
Joona Kiiski (zamar)
Gary Linscott (glinscott)
-# Authors and inventors of NNUE, training, NNUE port
+# Authors and inventors of NNUE, training, and NNUE port
Yu Nasu (ynasu87)
Motohiro Isozaki (yaneurao)
Hisayori Noda (nodchip)
-# all other authors of the code in alphabetical order
+# All other authors of Stockfish code (in alphabetical order)
Aditya (absimaldata)
Adrian Petrescu (apetresc)
Ajith Chandy Jose (ajithcj)
Alexander Pagel (Lolligerhans)
Alfredo Menezes (lonfom169)
Ali AlZhrani (Cooffe)
+Andreas Matthies (Matthies)
Andrei Vetrov (proukornew)
Andrew Grant (AndyGrant)
Andrey Neporada (nepal)
Chris Cain (ceebo)
clefrks
Dale Weiler (graphitemaster)
-Dan Schmidt (dfannius)
Daniel Axtens (daxtens)
Daniel Dugovic (ddugovic)
+Daniel Monroe (Ergodice)
+Dan Schmidt (dfannius)
Dariusz Orzechowski (dorzechowski)
-David Zar
David (dav1312)
+David Zar
Daylen Yang (daylen)
Deshawn Mohan-Smith (GoldenRare)
Dieter Dobbelaere (ddobbelaere)
Elvin Liu (solarlight2)
erbsenzaehler
Ernesto Gatti
-Linmiao Xu (linrock)
Fabian Beuke (madnight)
Fabian Fichter (ianfab)
Fanael Linithien (Fanael)
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, kurt22i)
-Jake Senne (w1wwwwww)
Jarrod Torriero (DU-jdto)
-Jean Gauthier (OuaisBla)
Jean-Francois Romang (jromang)
+Jean Gauthier (OuaisBla)
Jekaa
Jerry Donald Watson (jerrydonaldwatson)
jjoshua2
-Jonathan Calovski (Mysseno)
Jonathan Buladas Dumale (SFisGOD)
+Jonathan Calovski (Mysseno)
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)
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)
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)
-Nguyen Pham (nguyenpham)
Norman Schmidt (FireFather)
notruck
Ofek Shochat (OfekShochat, ghostway)
Ondrej Mosnáček (WOnder93)
+Ondřej Mišina (AndrovT)
Oskar Werkelin Ahlin
Pablo Vazquez
Panthee
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 (R-Peleg)
Richard Lloyd (Richard-Lloyd)
+rn5f107s2
Rodrigo Exterckötter Tjäder
Rodrigo Roim (roim)
-Ron Britvich (Britvich)
Ronald de Man (syzygy1, syzygy)
+Ron Britvich (Britvich)
rqs
Rui Coelho (ruicoelhopedro)
Ryan Schmitt
Steinar Gunderson (sesse)
Stéphane Nicolet (snicolet)
Syine Mineta (MinetaS)
-Prokop Randáček (ProkopRandacek)
Thanar2
thaspel
theo77186
+Tomasz Sobczyk (Sopel97)
Tom Truscott
Tom Vijlbrief (tomtor)
-Tomasz Sobczyk (Sopel97)
Torsten Franz (torfranz, tfranzer)
Torsten Hellwig (Torom)
Tracey Emery (basepr1me)
Unai Corzo (unaiic)
Uri Blass (uriblass)
Vince Negri (cuddlestmonkey)
+Viren
+windfishballad
xefoci7612
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
-Contributors to Fishtest with >10,000 CPU hours, as of 2022-11-19.
+Contributors to Fishtest with >10,000 CPU hours, as of 2023-06-20.
Thank you!
Username CPU Hours Games played
------------------------------------------------------------------
-noobpwnftw 36475307 2748033975
-technologov 14570711 760073590
+noobpwnftw 37457426 2850540907
+technologov 14135647 742892808
+linrock 4423514 303254809
mlang 3026000 200065824
-dew 1689222 100034318
-grandphish2 1442171 86798057
-okrout 1439985 133471766
-pemo 1405374 44189811
-linrock 1299003 28382783
-TueRens 1163420 71159522
-JojoM 897158 55177114
+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 635982 40652394
-oz 590763 41201352
-sebastronomy 581517 23307132
-cw 517915 34865769
-fastgm 504266 30264740
-CSU_Dynasty 479901 31846710
-ctoks 433503 28180725
+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
-leszek 416883 27493447
-bcross 409982 28062127
-velislav 345954 22232274
+maximmasiutin 424795 26577722
+bcross 415722 29060963
+olafm 395922 32268020
+rpngn 348378 24560289
+velislav 342567 22138992
Fisherman 327231 21829379
+mgrabiak 300612 20608380
Dantist 296386 18031762
-mgrabiak 288928 18869896
-rpngn 259965 16281463
-robal 237653 15148350
-ncfish1 231764 15275003
-nordlandia 226923 14624832
+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
-thirdlife 198844 5453268
Thanar 179852 12365359
vdv 175544 9904472
-armo9494 168201 11136452
spams 157128 10319326
-marrco 151599 9551115
sqrt2 147963 9724586
-vdbergh 137690 8971569
+DesolatedDodo 146350 9536172
+Calis007 143165 9478764
+vdbergh 138650 9064413
CoffeeOne 137100 5024116
+armo9494 136191 9460264
malala 136182 8002293
-DesolatedDodo 135276 8657464
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 106518 7062598
+skiminki 107583 7218170
+jcAEie 105675 8238962
MaZePallas 102823 6633619
sterni1971 100532 5880772
sunu 100167 7040199
zeryl 99331 6221261
+thirdlife 99124 2242380
ElbertoOne 99028 7023771
-DMBK 97572 6950312
-Calis007 96779 5611552
-cuistot 93111 5536500
+cuistot 98853 6069816
+bigpen0r 94809 6529203
brabos 92118 6186135
-Wolfgang 91769 5720158
+Wolfgang 91939 6105872
psk 89957 5984901
+sschnee 88235 5268000
racerschmacer 85805 6122790
-jcAEie 85527 5630616
+Fifis 85722 5709729
+Dubslow 84986 6042456
Vizvezdenec 83761 5344740
-sschnee 83557 4853690
0x3C33 82614 5271253
BRAVONE 81239 5054681
-Dubslow 78461 5042980
nssy 76497 5259388
jromang 76106 5236025
teddybaer 75125 5407666
-yurikvelo 73933 5031096
-tolkki963 73885 4721430
+tolkki963 74762 5149662
+megaman7de 74351 4940352
+Wencey 74181 4711488
Pking_cda 73776 5293873
-Bobo1239 71675 4860987
+yurikvelo 73150 5004382
+markkulix 72607 5304642
+Bobo1239 70579 4794999
solarlight 70517 5028306
dv8silencer 70287 3883992
-Gelma 69304 3980932
manap 66273 4121774
-megaman7de 65419 4120200
-markkulix 65331 4114860
-bigpen0r 64932 4683883
tinker 64333 4268790
qurashee 61208 3429862
-AGI 58325 4258646
+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
-Spprtr 52736 3410019
+javran 53785 4627608
finfish 51360 3370515
eva42 51272 3599691
eastorwest 51117 3454811
rap 49985 3219146
-unixwizard 49734 2536230
-pb00067 49727 3298270
+pb00067 49733 3298934
+OuaisBla 48626 3445134
ronaldjerum 47654 3240695
biffhero 46564 3111352
-GPUex 45861 2926502
-Fifis 45843 3088497
-oryx 45578 3493978
VoyagerOne 45476 3452465
-Wencey 44943 2654490
+jmdana 44893 3065205
+maposora 44597 4039578
+oryx 44570 3454238
speedycpu 43842 3003273
jbwiebe 43305 2805433
+GPUex 42378 3133332
Antihistamine 41788 2761312
mhunt 41735 2691355
-olafm 41277 3284344
homyur 39893 2850481
gri 39871 2515779
-MarcusTullius 38303 2251097
Garf 37741 2999686
-kdave 37424 2557406
SC 37299 2731694
csnodgrass 36207 2688994
-jmdana 36157 2210661
strelock 34716 2074055
+szupaw 34102 2880346
EthanOConnor 33370 2090311
slakovv 32915 2021889
-gopeto 31669 2060958
+Gelma 31771 1551204
+gopeto 31671 2060990
+kdave 31157 2198362
manapbk 30987 1810399
Prcuvu 30377 2170122
anst 30301 2190091
jkiiski 30136 1904470
-spcc 30135 1903728
+spcc 29925 1901692
hyperbolic.tom 29840 2017394
-xwziegtm 29763 2347412
chuckstablers 29659 2093438
Pyafue 29650 1902349
belzedar94 28846 1811530
-OuaisBla 27636 1578800
chriswk 26902 1868317
+xwziegtm 26897 2124586
achambord 26582 1767323
Patrick_G 26276 1801617
yorkman 26193 1992080
-Ulysses 25289 1674274
+Ulysses 25288 1689730
SFTUser 25182 1675689
nabildanial 24942 1519409
Sharaf_DG 24765 1786697
+Maxim 24705 1502062
rodneyc 24376 1416402
agg177 23890 1395014
-Ente 23747 1674582
-Karpovbot 23629 1313186
+Goatminola 23763 1956036
+Ente 23639 1671638
+Jopo12321 23467 1483172
JanErik 23408 1703875
Isidor 23388 1680691
Norabor 23371 1603244
-cisco2015 22934 1763773
+cisco2015 22920 1763301
+jsys14 22824 1591906
Zirie 22542 1472937
team-oh 22272 1636708
Roady 22220 1465606
dex 21612 1467203
nesoneg 21494 1463031
user213718 21454 1404128
-AndreasKrug 21227 1577833
sphinx 21211 1384728
+AndreasKrug 21097 1634811
jjoshua2 21001 1423089
+Zake9298 20938 1565848
horst.prack 20878 1465656
-jsys14 20729 1221010
0xB00B1ES 20590 1208666
j3corre 20405 941444
Adrian.Schmidt123 20316 1281436
-bonsi 20022 1300682
wei 19973 1745989
-dapper 19754 1167758
-Zake9298 19745 1458416
+notchris 19958 1800128
+Serpensin 19840 1697528
+Gaster319 19712 1677310
fishtester 19617 1257388
rstoesser 19569 1293588
eudhan 19274 1283717
+votoanthuan 19108 1609992
vulcan 18871 1729392
-Jopo12321 18803 1036284
+Karpovbot 18766 1053178
+qoo_charly_cai 18543 1284937
jundery 18445 1115855
ville 17883 1384026
-5t0ckf15hTr4in3r 17809 1105858
chris 17698 1487385
-dju 17697 994333
purplefishies 17595 1092533
+dju 17414 981289
iisiraider 17275 1049015
DragonLord 17014 1162790
-Karby 16457 1010138
-Goatminola 16278 1145026
+redstone59 16842 1461780
+Alb11747 16787 1213926
IgorLeMasson 16064 1147232
-Gaster319 16056 1109070
-redstone59 15953 1161664
+Karby 15982 979610
scuzzi 15757 968735
ako027ako 15671 1173203
Nikolay.IT 15154 1068349
Andrew Grant 15114 895539
Naven94 15054 834762
OssumOpossum 14857 1007129
-qoo_charly_cai 14490 847865
+ZacHFX 14783 1021842
enedene 14476 905279
-szupaw 14252 929130
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
-pirt 12925 985437
-Skiff84 12923 649994
mabichito 12903 749391
thijsk 12886 722107
AdrianSA 12860 804972
Flopzee 12698 894821
+korposzczur 12606 838168
fatmurphy 12547 853210
-woutboat 12419 836696
SapphireBrand 12416 969604
-Oakwen 12406 840961
+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 11871 773654
-Jackfish 11867 773550
-dbernier 11705 821780
+MooTheCow 11870 773598
+FormazChar 11766 885707
whelanh 11557 245188
-Maxim 11543 836024
-Nullvalue 11534 731410
-icewulf 11528 650470
-FormazChar 11523 861599
+3cho 11494 1031076
infinity 11470 727027
aga 11412 695127
torbjo 11395 729145
Thomas A. Anderson 11372 732094
savage84 11358 670860
-ali-al-zhrani 11272 781310
d64 11263 789184
-Bourbaki 11108 709144
+ali-al-zhrani 11245 779246
snicolet 11106 869170
-Alb11747 10855 696920
+dapper 11032 771402
+ols 10947 624903
+Karmatron 10828 677458
basepi 10637 744851
Cubox 10621 826448
-Karmatron 10616 674818
michaelrpg 10509 739239
OIVAS7572 10420 995586
-Garruk 10348 704905
+jojo2357 10419 929708
+WoodMan777 10380 873720
+Garruk 10365 706465
dzjp 10343 732529
-ols 10259 570669
endif
### Source and object files
-SRCS = benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp main.cpp \
- material.cpp misc.cpp movegen.cpp movepick.cpp pawns.cpp position.cpp psqt.cpp \
+SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \
+ misc.cpp movegen.cpp movepick.cpp position.cpp psqt.cpp \
search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \
nnue/evaluate_nnue.cpp nnue/features/half_ka_v2_hm.cpp \
hashprobe.grpc.pb.cc hashprobe.pb.cc
endif
### 3.7.1 Try to include git commit sha for versioning
-GIT_SHA = $(shell git rev-parse --short HEAD 2>/dev/null)
+GIT_SHA = $(shell git rev-parse HEAD 2>/dev/null | cut -c 1-8)
ifneq ($(GIT_SHA), )
CXXFLAGS += -DGIT_SHA=$(GIT_SHA)
endif
list.emplace_back("setoption name Hash value " + ttSize);
list.emplace_back("ucinewgame");
- size_t posCounter = 0;
-
for (const string& fen : fens)
if (fen.find("setoption") != string::npos)
list.emplace_back(fen);
else
{
- if (evalType == "classical" || (evalType == "mixed" && posCounter % 2 == 0))
- list.emplace_back("setoption name Use NNUE value false");
- else if (evalType == "NNUE" || (evalType == "mixed" && posCounter % 2 != 0))
- list.emplace_back("setoption name Use NNUE value true");
list.emplace_back("position fen " + fen);
list.emplace_back(go);
- ++posCounter;
}
- list.emplace_back("setoption name Use NNUE value true");
-
return list;
}
+++ /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 <cassert>
-#include <vector>
-#include <bitset>
-
-#include "bitboard.h"
-#include "types.h"
-
-namespace Stockfish {
-
-namespace {
-
- // There are 24 possible pawn squares: files A to D and ranks from 2 to 7.
- // Positions with the pawn on files E to H will be mirrored before probing.
- constexpr unsigned MAX_INDEX = 2*24*64*64; // stm * psq * wksq * bksq = 196608
-
- std::bitset<MAX_INDEX> KPKBitbase;
-
- // A KPK bitbase index is an integer in [0, IndexMax] range
- //
- // Information is mapped in a way that minimizes the number of iterations:
- //
- // bit 0- 5: white king square (from SQ_A1 to SQ_H8)
- // bit 6-11: black king square (from SQ_A1 to SQ_H8)
- // bit 12: side to move (WHITE or BLACK)
- // bit 13-14: white pawn file (from FILE_A to FILE_D)
- // bit 15-17: white pawn RANK_7 - rank (from RANK_7 - RANK_7 to RANK_7 - RANK_2)
- unsigned index(Color stm, Square bksq, Square wksq, Square psq) {
- return int(wksq) | (bksq << 6) | (stm << 12) | (file_of(psq) << 13) | ((RANK_7 - rank_of(psq)) << 15);
- }
-
- enum Result {
- INVALID = 0,
- UNKNOWN = 1,
- DRAW = 2,
- WIN = 4
- };
-
- Result& operator|=(Result& r, Result v) { return r = Result(r | v); }
-
- struct KPKPosition {
- KPKPosition() = default;
- explicit KPKPosition(unsigned idx);
- operator Result() const { return result; }
- Result classify(const std::vector<KPKPosition>& db);
-
- Color stm;
- Square ksq[COLOR_NB], psq;
- Result result;
- };
-
-} // namespace
-
-bool Bitbases::probe(Square wksq, Square wpsq, Square bksq, Color stm) {
-
- assert(file_of(wpsq) <= FILE_D);
-
- return KPKBitbase[index(stm, bksq, wksq, wpsq)];
-}
-
-
-void Bitbases::init() {
-
- 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);
-
- // Fill the bitbase with the decisive results
- for (idx = 0; idx < MAX_INDEX; ++idx)
- if (db[idx] == WIN)
- KPKBitbase.set(idx);
-}
-
-namespace {
-
- KPKPosition::KPKPosition(unsigned idx) {
-
- ksq[WHITE] = Square((idx >> 0) & 0x3F);
- ksq[BLACK] = Square((idx >> 6) & 0x3F);
- stm = Color ((idx >> 12) & 0x01);
- psq = make_square(File((idx >> 13) & 0x3), Rank(RANK_7 - ((idx >> 15) & 0x7)));
-
- // Invalid if two pieces are on the same square or if a king can be captured
- if ( distance(ksq[WHITE], ksq[BLACK]) <= 1
- || ksq[WHITE] == psq
- || ksq[BLACK] == psq
- || (stm == WHITE && (pawn_attacks_bb(WHITE, psq) & ksq[BLACK])))
- result = INVALID;
-
- // Win if the pawn can be promoted without getting captured
- else if ( stm == WHITE
- && rank_of(psq) == RANK_7
- && ksq[WHITE] != psq + NORTH
- && ( distance(ksq[BLACK], psq + NORTH) > 1
- || (distance(ksq[WHITE], psq + NORTH) == 1)))
- result = WIN;
-
- // Draw if it is stalemate or the black king can capture the pawn
- else if ( stm == BLACK
- && ( !(attacks_bb<KING>(ksq[BLACK]) & ~(attacks_bb<KING>(ksq[WHITE]) | pawn_attacks_bb(WHITE, psq)))
- || (attacks_bb<KING>(ksq[BLACK]) & ~attacks_bb<KING>(ksq[WHITE]) & psq)))
- result = DRAW;
-
- // Position will be classified later
- else
- result = UNKNOWN;
- }
-
- 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.
- const Result Good = (stm == WHITE ? WIN : DRAW);
- const Result Bad = (stm == WHITE ? DRAW : WIN);
-
- Result r = INVALID;
- Bitboard b = attacks_bb<KING>(ksq[stm]);
-
- while (b)
- r |= stm == WHITE ? db[index(BLACK, ksq[BLACK], pop_lsb(b), psq)]
- : db[index(WHITE, pop_lsb(b), ksq[WHITE], psq)];
-
- if (stm == WHITE)
- {
- if (rank_of(psq) < RANK_7) // Single push
- r |= db[index(BLACK, ksq[BLACK], ksq[WHITE], psq + NORTH)];
-
- if ( rank_of(psq) == RANK_2 // Double push
- && psq + NORTH != ksq[WHITE]
- && psq + NORTH != ksq[BLACK])
- r |= db[index(BLACK, ksq[BLACK], ksq[WHITE], psq + NORTH + NORTH)];
- }
-
- return result = r & Good ? Good : r & UNKNOWN ? UNKNOWN : Bad;
- }
-
-} // namespace
-
-} // namespace Stockfish
namespace Stockfish {
-namespace Bitbases {
-
-void init();
-bool probe(Square wksq, Square wpsq, Square bksq, Color us);
-
-} // namespace Stockfish::Bitbases
-
namespace Bitboards {
void init();
+++ /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 <cassert>
-
-#include "bitboard.h"
-#include "endgame.h"
-#include "movegen.h"
-
-namespace Stockfish {
-
-namespace {
-
- // Used to drive the king towards the edge of the board
- // in KX vs K and KQ vs KR endgames.
- // Values range from 27 (center squares) to 90 (in the corners)
- inline int push_to_edge(Square s) {
- int rd = edge_distance(rank_of(s)), fd = edge_distance(file_of(s));
- return 90 - (7 * fd * fd / 2 + 7 * rd * rd / 2);
- }
-
- // Used to drive the king towards A1H8 corners in KBN vs K endgames.
- // Values range from 0 on A8H1 diagonal to 7 in A1H8 corners
- inline int push_to_corner(Square s) {
- return abs(7 - rank_of(s) - file_of(s));
- }
-
- // Drive a piece close to or away from another piece
- inline int push_close(Square s1, Square s2) { return 140 - 20 * distance(s1, s2); }
- inline int push_away(Square s1, Square s2) { return 120 - push_close(s1, s2); }
-
-#ifndef NDEBUG
- bool verify_material(const Position& pos, Color c, Value npm, int pawnsCnt) {
- return pos.non_pawn_material(c) == npm && pos.count<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 = flip_file(sq);
-
- return strongSide == WHITE ? sq : flip_rank(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<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 strongKing = pos.square<KING>(strongSide);
- Square weakKing = pos.square<KING>(weakSide);
-
- Value result = pos.non_pawn_material(strongSide)
- + pos.count<PAWN>(strongSide) * PawnValueEg
- + push_to_edge(weakKing)
- + push_close(strongKing, weakKing);
-
- 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_TB_WIN_IN_MAX_PLY - 1);
-
- return strongSide == pos.side_to_move() ? result : -result;
-}
-
-
-/// Mate with KBN vs K. This is similar to KX vs K, but we have to drive the
-/// defending king towards a corner square that our bishop attacks.
-template<>
-Value Endgame<KBNK>::operator()(const Position& pos) const {
-
- assert(verify_material(pos, strongSide, KnightValueMg + BishopValueMg, 0));
- assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
-
- Square strongKing = pos.square<KING>(strongSide);
- Square strongBishop = pos.square<BISHOP>(strongSide);
- Square weakKing = pos.square<KING>(weakSide);
-
- // If our bishop does not attack A1/H8, we flip the enemy king square
- // to drive to opposite corners (A8/H1).
-
- Value result = (VALUE_KNOWN_WIN + 3520)
- + push_close(strongKing, weakKing)
- + 420 * push_to_corner(opposite_colors(strongBishop, SQ_A1) ? flip_file(weakKing) : weakKing);
-
- assert(abs(result) < VALUE_TB_WIN_IN_MAX_PLY);
- return strongSide == pos.side_to_move() ? result : -result;
-}
-
-
-/// KP vs K. This endgame is evaluated with the help of a bitbase
-template<>
-Value Endgame<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 strongKing = normalize(pos, strongSide, pos.square<KING>(strongSide));
- Square strongPawn = normalize(pos, strongSide, pos.square<PAWN>(strongSide));
- Square weakKing = normalize(pos, strongSide, pos.square<KING>(weakSide));
-
- Color us = strongSide == pos.side_to_move() ? WHITE : BLACK;
-
- if (!Bitbases::probe(strongKing, strongPawn, weakKing, us))
- return VALUE_DRAW;
-
- Value result = VALUE_KNOWN_WIN + PawnValueEg + Value(rank_of(strongPawn));
-
- return strongSide == pos.side_to_move() ? result : -result;
-}
-
-
-/// KR vs KP. This is a somewhat tricky endgame to evaluate precisely without
-/// a bitbase. The function below returns drawish scores when the pawn is
-/// far advanced with support of the king, while the attacking king is far
-/// away.
-template<>
-Value Endgame<KRKP>::operator()(const Position& pos) const {
-
- assert(verify_material(pos, strongSide, RookValueMg, 0));
- assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
-
- Square strongKing = pos.square<KING>(strongSide);
- Square weakKing = pos.square<KING>(weakSide);
- Square strongRook = pos.square<ROOK>(strongSide);
- Square weakPawn = pos.square<PAWN>(weakSide);
- Square queeningSquare = make_square(file_of(weakPawn), relative_rank(weakSide, RANK_8));
- Value result;
-
- // If the stronger side's king is in front of the pawn, it's a win
- if (forward_file_bb(strongSide, strongKing) & weakPawn)
- result = RookValueEg - distance(strongKing, weakPawn);
-
- // If the weaker side's king is too far from the pawn and the rook,
- // it's a win.
- else if ( distance(weakKing, weakPawn) >= 3 + (pos.side_to_move() == weakSide)
- && distance(weakKing, strongRook) >= 3)
- result = RookValueEg - distance(strongKing, weakPawn);
-
- // If the pawn is far advanced and supported by the defending king,
- // the position is drawish
- else if ( relative_rank(strongSide, weakKing) <= RANK_3
- && distance(weakKing, weakPawn) == 1
- && relative_rank(strongSide, strongKing) >= RANK_4
- && distance(strongKing, weakPawn) > 2 + (pos.side_to_move() == strongSide))
- result = Value(80) - 8 * distance(strongKing, weakPawn);
-
- else
- result = Value(200) - 8 * ( distance(strongKing, weakPawn + pawn_push(weakSide))
- - distance(weakKing, weakPawn + pawn_push(weakSide))
- - distance(weakPawn, queeningSquare));
-
- return strongSide == pos.side_to_move() ? result : -result;
-}
-
-
-/// KR vs KB. This is very simple, and always returns drawish scores. The
-/// score is slightly bigger when the defending king is close to the edge.
-template<>
-Value Endgame<KRKB>::operator()(const Position& pos) const {
-
- assert(verify_material(pos, strongSide, RookValueMg, 0));
- assert(verify_material(pos, weakSide, BishopValueMg, 0));
-
- Value result = Value(push_to_edge(pos.square<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 weakKing = pos.square<KING>(weakSide);
- Square weakKnight = pos.square<KNIGHT>(weakSide);
- Value result = Value(push_to_edge(weakKing) + push_away(weakKing, weakKnight));
- return strongSide == pos.side_to_move() ? result : -result;
-}
-
-
-/// KQ vs KP. In general, this is a win for the stronger side, but there are a
-/// few important exceptions. A pawn on 7th rank and on the A,C,F or H files
-/// with a king positioned next to it can be a draw, so in that case, we only
-/// use the distance between the kings.
-template<>
-Value Endgame<KQKP>::operator()(const Position& pos) const {
-
- assert(verify_material(pos, strongSide, QueenValueMg, 0));
- assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
-
- Square strongKing = pos.square<KING>(strongSide);
- Square weakKing = pos.square<KING>(weakSide);
- Square weakPawn = pos.square<PAWN>(weakSide);
-
- Value result = Value(push_close(strongKing, weakKing));
-
- if ( relative_rank(weakSide, weakPawn) != RANK_7
- || distance(weakKing, weakPawn) != 1
- || ((FileBBB | FileDBB | FileEBB | FileGBB) & weakPawn))
- result += QueenValueEg - PawnValueEg;
-
- return strongSide == pos.side_to_move() ? result : -result;
-}
-
-
-/// KQ vs KR. This is almost identical to KX vs K: we give the attacking
-/// king a bonus for having the kings close together, and for forcing the
-/// defending king towards the edge. If we also take care to avoid null move for
-/// the defending side in the search, this is usually sufficient to win KQ vs KR.
-template<>
-Value Endgame<KQKR>::operator()(const Position& pos) const {
-
- assert(verify_material(pos, strongSide, QueenValueMg, 0));
- assert(verify_material(pos, weakSide, RookValueMg, 0));
-
- Square strongKing = pos.square<KING>(strongSide);
- Square weakKing = pos.square<KING>(weakSide);
-
- Value result = QueenValueEg
- - RookValueEg
- + push_to_edge(weakKing)
- + push_close(strongKing, weakKing);
-
- return strongSide == pos.side_to_move() ? result : -result;
-}
-
-
-/// KNN vs KP. Very drawish, but there are some mate opportunities if we can
-/// press the weakSide King to a corner before the pawn advances too much.
-template<>
-Value Endgame<KNNKP>::operator()(const Position& pos) const {
-
- assert(verify_material(pos, strongSide, 2 * KnightValueMg, 0));
- assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
-
- Square weakKing = pos.square<KING>(weakSide);
- Square weakPawn = pos.square<PAWN>(weakSide);
-
- Value result = PawnValueEg
- + 2 * push_to_edge(weakKing)
- - 10 * relative_rank(weakSide, weakPawn);
-
- 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 strongPawns = pos.pieces(strongSide, PAWN);
- Bitboard allPawns = pos.pieces(PAWN);
-
- Square strongBishop = pos.square<BISHOP>(strongSide);
- Square weakKing = pos.square<KING>(weakSide);
- Square strongKing = pos.square<KING>(strongSide);
-
- // All strongSide pawns are on a single rook file?
- if (!(strongPawns & ~FileABB) || !(strongPawns & ~FileHBB))
- {
- Square queeningSquare = relative_square(strongSide, make_square(file_of(lsb(strongPawns)), RANK_8));
-
- if ( opposite_colors(queeningSquare, strongBishop)
- && distance(queeningSquare, weakKing) <= 1)
- return SCALE_FACTOR_DRAW;
- }
-
- // If all the pawns are on the same B or G file, then it's potentially a draw
- if ((!(allPawns & ~FileBBB) || !(allPawns & ~FileGBB))
- && pos.non_pawn_material(weakSide) == 0
- && pos.count<PAWN>(weakSide) >= 1)
- {
- // Get the least advanced weakSide pawn
- Square weakPawn = frontmost_sq(strongSide, pos.pieces(weakSide, PAWN));
-
- // There's potential for a draw if our pawn is blocked on the 7th rank,
- // the bishop cannot attack it or they only have one pawn left.
- if ( relative_rank(strongSide, weakPawn) == RANK_7
- && (strongPawns & (weakPawn + pawn_push(weakSide)))
- && (opposite_colors(strongBishop, weakPawn) || !more_than_one(strongPawns)))
- {
- int strongKingDist = distance(weakPawn, strongKing);
- int weakKingDist = distance(weakPawn, weakKing);
-
- // It's a draw if the weak king is on its back two ranks, within 2
- // squares of the blocking pawn and the strong king is not
- // closer. (I think this rule only fails in practically
- // unreachable positions such as 5k1K/6p1/6P1/8/8/3B4/8/8 w
- // and positions where qsearch will immediately correct the
- // problem such as 8/4k1p1/6P1/1K6/3B4/8/8/8 w).
- if ( relative_rank(strongSide, weakKing) >= RANK_7
- && weakKingDist <= 2
- && weakKingDist <= strongKingDist)
- return SCALE_FACTOR_DRAW;
- }
- }
-
- return SCALE_FACTOR_NONE;
-}
-
-
-/// KQ vs KR and one or more pawns. It tests for fortress draws with a rook on
-/// the third rank defended by a pawn.
-template<>
-ScaleFactor Endgame<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 strongKing = pos.square<KING>(strongSide);
- Square weakKing = pos.square<KING>(weakSide);
- Square weakRook = pos.square<ROOK>(weakSide);
-
- if ( relative_rank(weakSide, weakKing) <= RANK_2
- && relative_rank(weakSide, strongKing) >= RANK_4
- && relative_rank(weakSide, weakRook) == RANK_3
- && ( pos.pieces(weakSide, PAWN)
- & attacks_bb<KING>(weakKing)
- & pawn_attacks_bb(strongSide, weakRook)))
- return SCALE_FACTOR_DRAW;
-
- return SCALE_FACTOR_NONE;
-}
-
-
-/// KRP vs KR. This function knows a handful of the most important classes of
-/// drawn positions, but is far from perfect. It would probably be a good idea
-/// to add more knowledge in the future.
-///
-/// It would also be nice to rewrite the actual code for this function,
-/// which is mostly copied from Glaurung 1.x, and isn't very pretty.
-template<>
-ScaleFactor Endgame<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 strongKing = normalize(pos, strongSide, pos.square<KING>(strongSide));
- Square strongRook = normalize(pos, strongSide, pos.square<ROOK>(strongSide));
- Square strongPawn = normalize(pos, strongSide, pos.square<PAWN>(strongSide));
- Square weakKing = normalize(pos, strongSide, pos.square<KING>(weakSide));
- Square weakRook = normalize(pos, strongSide, pos.square<ROOK>(weakSide));
-
- File pawnFile = file_of(strongPawn);
- Rank pawnRank = rank_of(strongPawn);
- Square queeningSquare = make_square(pawnFile, RANK_8);
- int tempo = (pos.side_to_move() == strongSide);
-
- // If the pawn is not too far advanced and the defending king defends the
- // queening square, use the third-rank defence.
- if ( pawnRank <= RANK_5
- && distance(weakKing, queeningSquare) <= 1
- && strongKing <= SQ_H5
- && (rank_of(weakRook) == RANK_6 || (pawnRank <= RANK_3 && rank_of(strongRook) != RANK_6)))
- return SCALE_FACTOR_DRAW;
-
- // The defending side saves a draw by checking from behind in case the pawn
- // has advanced to the 6th rank with the king behind.
- if ( pawnRank == RANK_6
- && distance(weakKing, queeningSquare) <= 1
- && rank_of(strongKing) + tempo <= RANK_6
- && (rank_of(weakRook) == RANK_1 || (!tempo && distance<File>(weakRook, strongPawn) >= 3)))
- return SCALE_FACTOR_DRAW;
-
- if ( pawnRank >= RANK_6
- && weakKing == queeningSquare
- && rank_of(weakRook) == RANK_1
- && (!tempo || distance(strongKing, strongPawn) >= 2))
- return SCALE_FACTOR_DRAW;
-
- // White pawn on a7 and rook on a8 is a draw if black's king is on g7 or h7
- // and the black rook is behind the pawn.
- if ( strongPawn == SQ_A7
- && strongRook == SQ_A8
- && (weakKing == SQ_H7 || weakKing == SQ_G7)
- && file_of(weakRook) == FILE_A
- && (rank_of(weakRook) <= RANK_3 || file_of(strongKing) >= FILE_D || rank_of(strongKing) <= RANK_5))
- return SCALE_FACTOR_DRAW;
-
- // If the defending king blocks the pawn and the attacking king is too far
- // away, it's a draw.
- if ( pawnRank <= RANK_5
- && weakKing == strongPawn + NORTH
- && distance(strongKing, strongPawn) - tempo >= 2
- && distance(strongKing, weakRook) - tempo >= 2)
- return SCALE_FACTOR_DRAW;
-
- // Pawn on the 7th rank supported by the rook from behind usually wins if the
- // attacking king is closer to the queening square than the defending king,
- // and the defending king cannot gain tempi by threatening the attacking rook.
- if ( pawnRank == RANK_7
- && pawnFile != FILE_A
- && file_of(strongRook) == pawnFile
- && strongRook != queeningSquare
- && (distance(strongKing, queeningSquare) < distance(weakKing, queeningSquare) - 2 + tempo)
- && (distance(strongKing, queeningSquare) < distance(weakKing, strongRook) + tempo))
- return ScaleFactor(SCALE_FACTOR_MAX - 2 * distance(strongKing, queeningSquare));
-
- // Similar to the above, but with the pawn further back
- if ( pawnFile != FILE_A
- && file_of(strongRook) == pawnFile
- && strongRook < strongPawn
- && (distance(strongKing, queeningSquare) < distance(weakKing, queeningSquare) - 2 + tempo)
- && (distance(strongKing, strongPawn + NORTH) < distance(weakKing, strongPawn + NORTH) - 2 + tempo)
- && ( distance(weakKing, strongRook) + tempo >= 3
- || ( distance(strongKing, queeningSquare) < distance(weakKing, strongRook) + tempo
- && (distance(strongKing, strongPawn + NORTH) < distance(weakKing, strongPawn) + tempo))))
- return ScaleFactor( SCALE_FACTOR_MAX
- - 8 * distance(strongPawn, queeningSquare)
- - 2 * distance(strongKing, queeningSquare));
-
- // If the pawn is not far advanced and the defending king is somewhere in
- // the pawn's path, it's probably a draw.
- if (pawnRank <= RANK_4 && weakKing > strongPawn)
- {
- if (file_of(weakKing) == file_of(strongPawn))
- return ScaleFactor(10);
- if ( distance<File>(weakKing, strongPawn) == 1
- && distance(strongKing, weakKing) > 2)
- return ScaleFactor(24 - 2 * distance(strongKing, weakKing));
- }
- 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 weakKing = pos.square<KING>(weakSide);
- Square weakBishop = pos.square<BISHOP>(weakSide);
- Square strongKing = pos.square<KING>(strongSide);
- Square strongPawn = pos.square<PAWN>(strongSide);
- Rank pawnRank = relative_rank(strongSide, strongPawn);
- Direction push = pawn_push(strongSide);
-
- // If the pawn is on the 5th rank and the pawn (currently) is on
- // the same color square as the bishop then there is a chance of
- // a fortress. Depending on the king position give a moderate
- // reduction or a stronger one if the defending king is near the
- // corner but not trapped there.
- if (pawnRank == RANK_5 && !opposite_colors(weakBishop, strongPawn))
- {
- int d = distance(strongPawn + 3 * push, weakKing);
-
- if (d <= 2 && !(d == 0 && weakKing == strongKing + 2 * push))
- return ScaleFactor(24);
- else
- return ScaleFactor(48);
- }
-
- // When the pawn has moved to the 6th rank we can be fairly sure
- // it's drawn if the bishop attacks the square in front of the
- // pawn from a reasonable distance and the defending king is near
- // the corner
- if ( pawnRank == RANK_6
- && distance(strongPawn + 2 * push, weakKing) <= 1
- && (attacks_bb<BISHOP>(weakBishop) & (strongPawn + push))
- && distance<File>(weakBishop, strongPawn) >= 2)
- return ScaleFactor(8);
- }
-
- return SCALE_FACTOR_NONE;
-}
-
-/// KRPP vs KRP. There is just a single rule: if the stronger side has no passed
-/// pawns and the defending king is actively placed, the position is drawish.
-template<>
-ScaleFactor Endgame<KRPPKRP>::operator()(const Position& pos) const {
-
- assert(verify_material(pos, strongSide, RookValueMg, 2));
- assert(verify_material(pos, weakSide, RookValueMg, 1));
-
- Square strongPawn1 = lsb(pos.pieces(strongSide, PAWN));
- Square strongPawn2 = msb(pos.pieces(strongSide, PAWN));
- Square weakKing = pos.square<KING>(weakSide);
-
- // Does the stronger side have a passed pawn?
- if (pos.pawn_passed(strongSide, strongPawn1) || pos.pawn_passed(strongSide, strongPawn2))
- return SCALE_FACTOR_NONE;
-
- Rank pawnRank = std::max(relative_rank(strongSide, strongPawn1), relative_rank(strongSide, strongPawn2));
-
- if ( distance<File>(weakKing, strongPawn1) <= 1
- && distance<File>(weakKing, strongPawn2) <= 1
- && relative_rank(strongSide, weakKing) > pawnRank)
- {
- assert(pawnRank > RANK_1 && pawnRank < RANK_7);
- return ScaleFactor(7 * pawnRank);
- }
- return SCALE_FACTOR_NONE;
-}
-
-
-/// K and two or more pawns vs K. There is just a single rule here: if all pawns
-/// are on the same rook file and are blocked by the defending king, it's a draw.
-template<>
-ScaleFactor Endgame<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 weakKing = pos.square<KING>(weakSide);
- Bitboard strongPawns = pos.pieces(strongSide, PAWN);
-
- // If all pawns are ahead of the king on a single rook file, it's a draw.
- if ( !(strongPawns & ~(FileABB | FileHBB))
- && !(strongPawns & ~passed_pawn_span(weakSide, weakKing)))
- return SCALE_FACTOR_DRAW;
-
- return SCALE_FACTOR_NONE;
-}
-
-
-/// KBP vs KB. There are two rules: if the defending king is somewhere along the
-/// path of the pawn, and the square of the king is not of the same color as the
-/// stronger side's bishop, it's a draw. If the two bishops have opposite color,
-/// it's almost always a draw.
-template<>
-ScaleFactor Endgame<KBPKB>::operator()(const Position& pos) const {
-
- assert(verify_material(pos, strongSide, BishopValueMg, 1));
- assert(verify_material(pos, weakSide, BishopValueMg, 0));
-
- Square strongPawn = pos.square<PAWN>(strongSide);
- Square strongBishop = pos.square<BISHOP>(strongSide);
- Square weakBishop = pos.square<BISHOP>(weakSide);
- Square weakKing = pos.square<KING>(weakSide);
-
- // Case 1: Defending king blocks the pawn, and cannot be driven away
- if ( (forward_file_bb(strongSide, strongPawn) & weakKing)
- && ( opposite_colors(weakKing, strongBishop)
- || relative_rank(strongSide, weakKing) <= RANK_6))
- return SCALE_FACTOR_DRAW;
-
- // Case 2: Opposite colored bishops
- if (opposite_colors(strongBishop, weakBishop))
- return SCALE_FACTOR_DRAW;
-
- return SCALE_FACTOR_NONE;
-}
-
-
-/// KBPP vs KB. It detects a few basic draws with opposite-colored bishops
-template<>
-ScaleFactor Endgame<KBPPKB>::operator()(const Position& pos) const {
-
- assert(verify_material(pos, strongSide, BishopValueMg, 2));
- assert(verify_material(pos, weakSide, BishopValueMg, 0));
-
- Square strongBishop = pos.square<BISHOP>(strongSide);
- Square weakBishop = pos.square<BISHOP>(weakSide);
-
- if (!opposite_colors(strongBishop, weakBishop))
- return SCALE_FACTOR_NONE;
-
- Square weakKing = pos.square<KING>(weakSide);
- Square strongPawn1 = lsb(pos.pieces(strongSide, PAWN));
- Square strongPawn2 = msb(pos.pieces(strongSide, PAWN));
- Square blockSq1, blockSq2;
-
- if (relative_rank(strongSide, strongPawn1) > relative_rank(strongSide, strongPawn2))
- {
- blockSq1 = strongPawn1 + pawn_push(strongSide);
- blockSq2 = make_square(file_of(strongPawn2), rank_of(strongPawn1));
- }
- else
- {
- blockSq1 = strongPawn2 + pawn_push(strongSide);
- blockSq2 = make_square(file_of(strongPawn1), rank_of(strongPawn2));
- }
-
- switch (distance<File>(strongPawn1, strongPawn2))
- {
- case 0:
- // Both pawns are on the same file. It's an easy draw if the defender firmly
- // controls some square in the frontmost pawn's path.
- if ( file_of(weakKing) == file_of(blockSq1)
- && relative_rank(strongSide, weakKing) >= relative_rank(strongSide, blockSq1)
- && opposite_colors(weakKing, strongBishop))
- return SCALE_FACTOR_DRAW;
- else
- return SCALE_FACTOR_NONE;
-
- case 1:
- // Pawns on adjacent files. It's a draw if the defender firmly controls the
- // square in front of the frontmost pawn's path, and the square diagonally
- // behind this square on the file of the other pawn.
- if ( weakKing == blockSq1
- && opposite_colors(weakKing, strongBishop)
- && ( weakBishop == blockSq2
- || (attacks_bb<BISHOP>(blockSq2, pos.pieces()) & pos.pieces(weakSide, BISHOP))
- || distance<Rank>(strongPawn1, strongPawn2) >= 2))
- return SCALE_FACTOR_DRAW;
-
- else if ( weakKing == blockSq2
- && opposite_colors(weakKing, strongBishop)
- && ( weakBishop == blockSq1
- || (attacks_bb<BISHOP>(blockSq1, pos.pieces()) & pos.pieces(weakSide, BISHOP))))
- return SCALE_FACTOR_DRAW;
- else
- return SCALE_FACTOR_NONE;
-
- default:
- // The pawns are not on the same file or adjacent files. No scaling.
- return SCALE_FACTOR_NONE;
- }
-}
-
-
-/// KBP vs KN. There is a single rule: if the defending king is somewhere along
-/// the path of the pawn, and the square of the king is not of the same color as
-/// the stronger side's bishop, it's a draw.
-template<>
-ScaleFactor Endgame<KBPKN>::operator()(const Position& pos) const {
-
- assert(verify_material(pos, strongSide, BishopValueMg, 1));
- assert(verify_material(pos, weakSide, KnightValueMg, 0));
-
- Square strongPawn = pos.square<PAWN>(strongSide);
- Square strongBishop = pos.square<BISHOP>(strongSide);
- Square weakKing = pos.square<KING>(weakSide);
-
- if ( file_of(weakKing) == file_of(strongPawn)
- && relative_rank(strongSide, strongPawn) < relative_rank(strongSide, weakKing)
- && ( opposite_colors(weakKing, strongBishop)
- || relative_rank(strongSide, weakKing) <= RANK_6))
- return SCALE_FACTOR_DRAW;
-
- return SCALE_FACTOR_NONE;
-}
-
-
-/// KP vs KP. This is done by removing the weakest side's pawn and probing the
-/// KP vs K bitbase: if the weakest side has a draw without the pawn, it probably
-/// has at least a draw with the pawn as well. The exception is when the stronger
-/// side's pawn is far advanced and not on a rook file; in this case it is often
-/// possible to win (e.g. 8/4k3/3p4/3P4/6K1/8/8/8 w - - 0 1).
-template<>
-ScaleFactor Endgame<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 strongKing = normalize(pos, strongSide, pos.square<KING>(strongSide));
- Square weakKing = normalize(pos, strongSide, pos.square<KING>(weakSide));
- Square strongPawn = 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(strongPawn) >= RANK_5 && file_of(strongPawn) != FILE_A)
- return SCALE_FACTOR_NONE;
-
- // Probe the KPK bitbase with the weakest side's pawn removed. If it's a draw,
- // it's probably at least a draw even with the pawn.
- return Bitbases::probe(strongKing, strongPawn, weakKing, us) ? SCALE_FACTOR_NONE : SCALE_FACTOR_DRAW;
-}
-
-} // 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 ENDGAME_H_INCLUDED
-#define ENDGAME_H_INCLUDED
-
-#include <memory>
-#include <string>
-#include <type_traits>
-#include <unordered_map>
-#include <utility>
-
-#include "position.h"
-#include "types.h"
-
-namespace Stockfish {
-
-/// EndgameCode lists all supported endgame functions by corresponding codes
-
-enum EndgameCode {
-
- EVALUATION_FUNCTIONS,
- KNNK, // KNN vs K
- KNNKP, // KNN vs KP
- KXK, // Generic "mate lone king" eval
- KBNK, // KBN vs K
- KPK, // KP vs K
- KRKP, // KR vs KP
- KRKB, // KR vs KB
- KRKN, // KR vs KN
- KQKP, // KQ vs KP
- KQKR, // KQ vs KR
-
- SCALING_FUNCTIONS,
- KBPsK, // KB and pawns vs K
- KQKRPs, // KQ vs KR and pawns
- KRPKR, // KRP vs KR
- KRPKB, // KRP vs KB
- KRPPKRP, // KRPP vs KRP
- KPsK, // K and pawns vs K
- KBPKB, // KBP vs KB
- KBPPKB, // KBPP vs KB
- KBPKN, // KBP vs KN
- KPKP // KP vs KP
-};
-
-
-/// Endgame functions can be of two types depending on whether they return a
-/// Value or a ScaleFactor.
-
-template<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;
- }
-}
-
-} // namespace Stockfish
-
-#endif // #ifndef ENDGAME_H_INCLUDED
#include <algorithm>
#include <cassert>
-#include <cstdlib>
-#include <cstring> // For std::memset
#include <fstream>
#include <iomanip>
#include <sstream>
#include "bitboard.h"
#include "evaluate.h"
-#include "material.h"
#include "misc.h"
-#include "pawns.h"
#include "thread.h"
#include "timeman.h"
#include "uci.h"
namespace Eval {
- bool useNNUE;
string currentEvalFileName = "None";
+ static double to_cp(Value v) { return double(v) / UCI::NormalizeToPawnValue; }
+
/// NNUE::init() tries to load a NNUE network at startup time, or when the engine
/// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue"
/// The name of the NNUE network is always retrieved from the EvalFile option.
void NNUE::init() {
- useNNUE = Options["Use NNUE"];
- if (!useNNUE)
- return;
-
string eval_file = string(Options["EvalFile"]);
if (eval_file.empty())
eval_file = EvalFileDefaultName;
if (eval_file.empty())
eval_file = EvalFileDefaultName;
- if (useNNUE && currentEvalFileName != eval_file)
+ if (currentEvalFileName != eval_file)
{
- string msg1 = "If the UCI option \"Use NNUE\" is set to true, network evaluation parameters compatible with the engine must be available.";
+ string msg1 = "Network evaluation parameters compatible with the engine must be available.";
string msg2 = "The option is set to true, but the network file " + eval_file + " was not loaded successfully.";
string msg3 = "The UCI option EvalFile might need to specify the full path, including the directory name, to the network file.";
string msg4 = "The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/" + std::string(EvalFileDefaultName);
exit(EXIT_FAILURE);
}
- if (useNNUE)
- sync_cout << "info string NNUE evaluation using " << eval_file << " enabled" << sync_endl;
- else
- sync_cout << "info string classical evaluation enabled" << sync_endl;
- }
-}
-
-namespace Trace {
-
- enum Tracing { NO_TRACE, TRACE };
-
- enum Term { // The first 8 entries are reserved for PieceType
- MATERIAL = 8, IMBALANCE, MOBILITY, THREAT, PASSED, SPACE, WINNABLE, TOTAL, TERM_NB
- };
-
- Score scores[TERM_NB][COLOR_NB];
-
- static double to_cp(Value v) { return double(v) / UCI::NormalizeToPawnValue; }
-
- static void add(int idx, Color c, Score s) {
- scores[idx][c] = s;
- }
-
- static void add(int idx, Score w, Score b = SCORE_ZERO) {
- scores[idx][WHITE] = w;
- scores[idx][BLACK] = b;
- }
-
- static 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;
- }
-
- static std::ostream& operator<<(std::ostream& os, Term t) {
-
- if (t == MATERIAL || t == IMBALANCE || t == WINNABLE || t == TOTAL)
- os << " ---- ----" << " | " << " ---- ----";
- else
- os << scores[t][WHITE] << " | " << scores[t][BLACK];
-
- os << " | " << scores[t][WHITE] - scores[t][BLACK] << " |\n";
- return os;
+ sync_cout << "info string NNUE evaluation using " << eval_file << sync_endl;
}
}
-using namespace Trace;
-
-namespace {
-
- // Threshold for lazy and space evaluation
- constexpr Value LazyThreshold1 = Value(3622);
- constexpr Value LazyThreshold2 = Value(1962);
- constexpr Value SpaceThreshold = Value(11551);
-
- // KingAttackWeights[PieceType] contains king attack weights by piece type
- constexpr int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 76, 46, 45, 14 };
-
- // SafeCheck[PieceType][single/multiple] contains safe check bonus by piece type,
- // higher if multiple safe checks are possible for that piece type.
- constexpr int SafeCheck[][2] = {
- {}, {}, {805, 1292}, {650, 984}, {1071, 1886}, {730, 1128}
- };
-
-#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,-79), S(-53,-57), S(-12,-31), S( -3,-17), S( 3, 7), S( 12, 13), // Knight
- S( 21, 16), S( 28, 21), S( 37, 26) },
- { S(-47,-59), S(-20,-25), S( 14, -8), S( 29, 12), S( 39, 21), S( 53, 40), // Bishop
- S( 53, 56), S( 60, 58), S( 62, 65), S( 69, 72), S( 78, 78), S( 83, 87),
- S( 91, 88), S( 96, 98) },
- { S(-60,-82), S(-24,-15), S( 0, 17) ,S( 3, 43), S( 4, 72), S( 14,100), // Rook
- S( 20,102), S( 30,122), S( 41,133), S(41 ,139), S( 41,153), S( 45,160),
- S( 57,165), S( 58,170), S( 67,175) },
- { S(-29,-49), S(-16,-29), S( -8, -8), S( -8, 17), S( 18, 39), S( 25, 54), // Queen
- S( 23, 59), S( 37, 73), S( 41, 76), S( 54, 95), S( 65, 95) ,S( 68,101),
- S( 69,124), S( 70,128), S( 70,132), S( 70,133) ,S( 71,136), S( 72,140),
- S( 74,147), S( 76,149), S( 90,153), S(104,169), S(105,171), S(106,171),
- S(112,178), S(114,185), S(114,187), S(119,221) }
- };
-
- // BishopPawns[distance from edge] contains a file-dependent penalty for pawns on
- // squares of the same color as our bishop.
- constexpr Score BishopPawns[int(FILE_NB) / 2] = {
- S(3, 8), S(3, 9), S(2, 7), S(3, 7)
- };
-
- // KingProtector[knight/bishop] contains penalty for each distance unit to own king
- constexpr Score KingProtector[] = { S(9, 9), S(7, 9) };
-
- // Outpost[knight/bishop] contains bonuses for each knight or bishop occupying a
- // pawn protected square on rank 4 to 6 which is also safe from a pawn attack.
- constexpr Score Outpost[] = { S(54, 34), S(31, 25) };
-
- // PassedRank[Rank] contains a bonus according to the rank of a passed pawn
- constexpr Score PassedRank[RANK_NB] = {
- S(0, 0), S(2, 38), S(15, 36), S(22, 50), S(64, 81), S(166, 184), S(284, 269)
- };
-
- constexpr Score RookOnClosedFile = S(10, 5);
- constexpr Score RookOnOpenFile[] = { S(18, 8), S(49, 26) };
-
- // ThreatByMinor/ByRook[attacked PieceType] contains bonuses according to
- // which piece type attacks which one. Attacks on lesser pieces which are
- // pawn-defended are not considered.
- constexpr Score ThreatByMinor[PIECE_TYPE_NB] = {
- S(0, 0), S(6, 37), S(64, 50), S(82, 57), S(103, 130), S(81, 163)
- };
-
- constexpr Score ThreatByRook[PIECE_TYPE_NB] = {
- S(0, 0), S(3, 44), S(36, 71), S(44, 59), S(0, 39), S(60, 39)
- };
-
- constexpr Value CorneredBishop = Value(50);
-
- // Assorted bonuses and penalties
- constexpr Score UncontestedOutpost = S( 0, 10);
- constexpr Score BishopOnKingRing = S( 24, 0);
- constexpr Score BishopXRayPawns = S( 4, 5);
- constexpr Score FlankAttacks = S( 8, 0);
- constexpr Score Hanging = S( 72, 40);
- constexpr Score KnightOnQueen = S( 16, 11);
- constexpr Score LongDiagonalBishop = S( 45, 0);
- constexpr Score MinorBehindPawn = S( 18, 3);
- constexpr Score PassedFile = S( 13, 8);
- constexpr Score PawnlessFlank = S( 19, 97);
- constexpr Score ReachableOutpost = S( 33, 19);
- constexpr Score RestrictedPiece = S( 6, 7);
- constexpr Score RookOnKingRing = S( 16, 0);
- constexpr Score SliderOnQueen = S( 62, 21);
- constexpr Score ThreatByKing = S( 24, 87);
- constexpr Score ThreatByPawnPush = S( 48, 39);
- constexpr Score ThreatBySafePawn = S(167, 99);
- constexpr Score TrappedRook = S( 55, 13);
- constexpr Score WeakQueenProtection = S( 14, 0);
- constexpr Score WeakQueen = S( 57, 19);
-
-
-#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;
- Value winnable(Score score) const;
-
- const Position& pos;
- Material::Entry* me;
- Pawns::Entry* pe;
- Bitboard mobilityArea[COLOR_NB];
- Score mobility[COLOR_NB] = { SCORE_ZERO, SCORE_ZERO };
-
- // attackedBy[color][piece type] is a bitboard representing all squares
- // attacked by a given color and piece type. Special "piece types" which
- // is also calculated is ALL_PIECES.
- Bitboard attackedBy[COLOR_NB][PIECE_TYPE_NB];
-
- // attackedBy2[color] are the squares attacked by at least 2 units of a given
- // color, including x-rays. But diagonal x-rays through pawns are not computed.
- Bitboard attackedBy2[COLOR_NB];
-
- // kingRing[color] are the squares adjacent to the king plus some other
- // very near squares, depending on king position.
- Bitboard kingRing[COLOR_NB];
-
- // kingAttackersCount[color] is the number of pieces of the given color
- // which attack a square in the kingRing of the enemy king.
- int kingAttackersCount[COLOR_NB];
-
- // 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;
- 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] = attacks_bb<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(std::clamp(file_of(ksq), FILE_B, FILE_G),
- std::clamp(rank_of(ksq), RANK_2, RANK_7));
- kingRing[Us] = attacks_bb<KING>(s) | s;
-
- 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;
- [[maybe_unused]] constexpr Direction Down = -pawn_push(Us);
- [[maybe_unused]] constexpr Bitboard OutpostRanks = (Us == WHITE ? Rank4BB | Rank5BB | Rank6BB
- : Rank5BB | Rank4BB | Rank3BB);
- Bitboard b1 = pos.pieces(Us, Pt);
- Bitboard b, bb;
- Score score = SCORE_ZERO;
-
- attackedBy[Us][Pt] = 0;
-
- while (b1)
- {
- Square s = pop_lsb(b1);
-
- // Find attacked squares, including x-ray attacks for bishops and rooks
- b = Pt == BISHOP ? attacks_bb<BISHOP>(s, pos.pieces() ^ pos.pieces(QUEEN))
- : Pt == ROOK ? attacks_bb< ROOK>(s, pos.pieces() ^ pos.pieces(QUEEN) ^ pos.pieces(Us, ROOK))
- : attacks_bb<Pt>(s, pos.pieces());
-
- if (pos.blockers_for_king(Us) & s)
- b &= line_bb(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])
- {
- kingAttackersCount[Us]++;
- kingAttackersWeight[Us] += KingAttackWeights[Pt];
- kingAttacksCount[Us] += popcount(b & attackedBy[Them][KING]);
- }
-
- else if (Pt == ROOK && (file_bb(s) & kingRing[Them]))
- score += RookOnKingRing;
-
- else if (Pt == BISHOP && (attacks_bb<BISHOP>(s, pos.pieces(PAWN)) & kingRing[Them]))
- score += BishopOnKingRing;
-
- int mob = popcount(b & mobilityArea[Us]);
- mobility[Us] += MobilityBonus[Pt - 2][mob];
-
- if constexpr (Pt == BISHOP || Pt == KNIGHT)
- {
- // Bonus if the piece is on an outpost square or can reach one
- // Bonus for knights (UncontestedOutpost) if few relevant targets
- bb = OutpostRanks & (attackedBy[Us][PAWN] | shift<Down>(pos.pieces(PAWN)))
- & ~pe->pawn_attacks_span(Them);
- Bitboard targets = pos.pieces(Them) & ~pos.pieces(PAWN);
-
- if ( Pt == KNIGHT
- && bb & s & ~CenterFiles // on a side outpost
- && !(b & targets) // no relevant attacks
- && (!more_than_one(targets & (s & QueenSide ? QueenSide : KingSide))))
- score += UncontestedOutpost * popcount(pos.pieces(PAWN) & (s & QueenSide ? QueenSide : KingSide));
- else if (bb & s)
- score += Outpost[Pt == BISHOP];
- else if (Pt == KNIGHT && bb & b & ~pos.pieces(Us))
- score += ReachableOutpost;
-
- // Bonus for a knight or bishop shielded by pawn
- if (shift<Down>(pos.pieces(PAWN)) & s)
- score += MinorBehindPawn;
-
- // Penalty if the piece is far from the king
- score -= KingProtector[Pt == BISHOP] * distance(pos.square<KING>(Us), s);
-
- if constexpr (Pt == BISHOP)
- {
- // Penalty according to the number of our pawns on the same color square as the
- // bishop, bigger when the center files are blocked with pawns and smaller
- // when the bishop is outside the pawn chain.
- Bitboard blocked = pos.pieces(Us, PAWN) & shift<Down>(pos.pieces());
-
- score -= BishopPawns[edge_distance(file_of(s))] * pos.pawns_on_same_color_squares(Us, s)
- * (!(attackedBy[Us][PAWN] & s) + popcount(blocked & CenterFiles));
-
- // Penalty for all enemy pawns x-rayed
- score -= BishopXRayPawns * popcount(attacks_bb<BISHOP>(s) & pos.pieces(Them, PAWN));
-
- // 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)) ? 4 * make_score(CorneredBishop, CorneredBishop)
- : 3 * make_score(CorneredBishop, CorneredBishop);
- }
- }
- }
-
- if constexpr (Pt == ROOK)
- {
- // Bonuses for rook on a (semi-)open or closed file
- if (pos.is_on_semiopen_file(Us, s))
- {
- score += RookOnOpenFile[pos.is_on_semiopen_file(Them, s)];
- }
- else
- {
- // If our pawn on this file is blocked, increase penalty
- if ( pos.pieces(Us, PAWN)
- & shift<Down>(pos.pieces())
- & file_bb(s))
- {
- score -= RookOnClosedFile;
- }
-
- // Penalty when trapped by the king, even more if the king cannot castle
- if (mob <= 3)
- {
- File kf = file_of(pos.square<KING>(Us));
- if ((kf < FILE_E) == (file_of(s) < kf))
- score -= TrappedRook * (1 + !pos.castling_rights(Us));
- }
- }
- }
-
- if constexpr (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 constexpr (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;
- 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 & attackedBy[Them][ROOK] & safe;
- if (rookChecks)
- kingDanger += SafeCheck[ROOK][more_than_one(rookChecks)];
- else
- unsafeChecks |= b1 & attackedBy[Them][ROOK];
-
- // Enemy queen safe checks: count them only if the checks are from squares from
- // which opponent cannot give a rook check, because rook checks are more valuable.
- queenChecks = (b1 | b2) & attackedBy[Them][QUEEN] & safe
- & ~(attackedBy[Us][QUEEN] | rookChecks);
- if (queenChecks)
- kingDanger += SafeCheck[QUEEN][more_than_one(queenChecks)];
-
- // Enemy bishops checks: count them only if they are from squares from which
- // opponent cannot give a queen check, because queen checks are more valuable.
- bishopChecks = b2 & attackedBy[Them][BISHOP] & safe
- & ~queenChecks;
- if (bishopChecks)
- kingDanger += SafeCheck[BISHOP][more_than_one(bishopChecks)];
-
- else
- unsafeChecks |= b2 & attackedBy[Them][BISHOP];
-
- // Enemy knights checks
- knightChecks = attacks_bb<KNIGHT>(ksq) & attackedBy[Them][KNIGHT];
- if (knightChecks & safe)
- kingDanger += SafeCheck[KNIGHT][more_than_one(knightChecks & safe)];
- 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] // (~10 Elo)
- + 183 * popcount(kingRing[Us] & weak) // (~15 Elo)
- + 148 * popcount(unsafeChecks) // (~4 Elo)
- + 98 * popcount(pos.blockers_for_king(Us)) // (~2 Elo)
- + 69 * kingAttacksCount[Them] // (~0.5 Elo)
- + 3 * kingFlankAttack * kingFlankAttack / 8 // (~0.5 Elo)
- + mg_value(mobility[Them] - mobility[Us]) // (~0.5 Elo)
- - 873 * !pos.count<QUEEN>(Them) // (~24 Elo)
- - 100 * bool(attackedBy[Us][KNIGHT] & attackedBy[Us][KING]) // (~5 Elo)
- - 6 * mg_value(score) / 8 // (~8 Elo)
- - 4 * kingFlankDefense // (~5 Elo)
- + 37; // (~0.5 Elo)
-
- // 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 constexpr (T)
- Trace::add(KING, Us, score);
-
- return score;
- }
-
-
- // Evaluation::threats() assigns bonuses according to the types of the
- // attacking and the attacked pieces.
-
- template<Tracing T> template<Color Us>
- Score Evaluation<T>::threats() const {
-
- constexpr Color Them = ~Us;
- constexpr Direction Up = pawn_push(Us);
- constexpr Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB);
-
- Bitboard b, weak, defended, nonPawnEnemies, stronglyProtected, safe;
- Score score = SCORE_ZERO;
-
- // 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);
-
- // Additional bonus if weak piece is only protected by a queen
- score += WeakQueenProtection * popcount(weak & attackedBy[Them][QUEEN]);
- }
-
- // Bonus for restricting their piece moves
- b = attackedBy[Them][ALL_PIECES]
- & ~stronglyProtected
- & attackedBy[Us][ALL_PIECES];
- score += RestrictedPiece * popcount(b);
-
- // Protected or unattacked squares
- safe = ~attackedBy[Them][ALL_PIECES] | attackedBy[Us][ALL_PIECES];
-
- // Bonus for attacking enemy pieces with our relatively safe pawns
- b = pos.pieces(Us, PAWN) & safe;
- b = pawn_attacks_bb<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)
- {
- bool queenImbalance = pos.count<QUEEN>() == 1;
-
- Square s = pos.square<QUEEN>(Them);
- safe = mobilityArea[Us]
- & ~pos.pieces(Us, PAWN)
- & ~stronglyProtected;
-
- b = attackedBy[Us][KNIGHT] & attacks_bb<KNIGHT>(s);
-
- score += KnightOnQueen * popcount(b & safe) * (1 + queenImbalance);
-
- b = (attackedBy[Us][BISHOP] & attacks_bb<BISHOP>(s, pos.pieces()))
- | (attackedBy[Us][ROOK ] & attacks_bb<ROOK >(s, pos.pieces()));
-
- score += SliderOnQueen * popcount(b & safe & attackedBy2[Us]) * (1 + queenImbalance);
- }
-
- if constexpr (T)
- Trace::add(THREAT, Us, score);
-
- return score;
- }
-
- // Evaluation::passed() evaluates the passed pawns and candidate passed
- // pawns of the given color.
-
- template<Tracing T> template<Color Us>
- Score Evaluation<T>::passed() const {
-
- constexpr Color Them = ~Us;
- constexpr Direction Up = pawn_push(Us);
- constexpr Direction Down = -Up;
-
- auto king_proximity = [&](Color c, Square s) {
- return std::min(distance(pos.square<KING>(c), s), 5);
- };
-
- Bitboard b, bb, squaresToQueen, unsafeSquares, blockedPassers, helpers;
- Score score = SCORE_ZERO;
-
- b = pe->passed_pawns(Us);
-
- blockedPassers = b & shift<Down>(pos.pieces(Them, PAWN));
- if (blockedPassers)
- {
- helpers = shift<Up>(pos.pieces(Us, PAWN))
- & ~pos.pieces(Them)
- & (~attackedBy2[Them] | attackedBy[Us][ALL_PIECES]);
-
- // Remove blocked candidate passers that don't have help to pass
- b &= ~blockedPassers
- | shift<WEST>(helpers)
- | shift<EAST>(helpers);
- }
-
- while (b)
- {
- 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] | pos.pieces(Them);
-
- // If there are no enemy pieces or attacks on passed pawn span, assign a big bonus.
- // Or if there is some, but they are all attacked by our pawns, assign a bit smaller bonus.
- // Otherwise assign a smaller bonus if the path to queen is not attacked
- // and even smaller bonus if it is attacked but block square is not.
- int k = !unsafeSquares ? 36 :
- !(unsafeSquares & ~attackedBy[Us][PAWN]) ? 30 :
- !(unsafeSquares & squaresToQueen) ? 17 :
- !(unsafeSquares & blockSq) ? 7 :
- 0 ;
-
- // Assign a larger bonus if the block square is defended
- if ((pos.pieces(Us) & bb) || (attackedBy[Us][ALL_PIECES] & blockSq))
- k += 5;
-
- bonus += make_score(k * w, k * w);
- }
- } // r > RANK_3
-
- score += bonus - PassedFile * edge_distance(file_of(s));
- }
-
- if constexpr (T)
- Trace::add(PASSED, Us, score);
-
- return score;
- }
-
-
- // Evaluation::space() computes a space evaluation for a given side, aiming to improve game
- // play in the opening. It is based on the number of safe squares on the four central files
- // on ranks 2 to 4. Completely safe squares behind a friendly pawn are counted twice.
- // Finally, the space bonus is multiplied by a weight which decreases according to occupancy.
-
- template<Tracing T> template<Color Us>
- Score Evaluation<T>::space() const {
-
- // Early exit if, for example, both queens or 6 minor pieces have been exchanged
- if (pos.non_pawn_material() < SpaceThreshold)
- return SCORE_ZERO;
-
- constexpr Color Them = ~Us;
- constexpr Direction Down = -pawn_push(Us);
- constexpr Bitboard SpaceMask =
- Us == WHITE ? CenterFiles & (Rank2BB | Rank3BB | Rank4BB)
- : CenterFiles & (Rank7BB | Rank6BB | Rank5BB);
-
- // Find the available squares for our pieces inside the area defined by SpaceMask
- Bitboard safe = SpaceMask
- & ~pos.pieces(Us, PAWN)
- & ~attackedBy[Them][PAWN];
-
- // Find all squares which are at most three squares behind some friendly pawn
- Bitboard behind = pos.pieces(Us, PAWN);
- behind |= shift<Down>(behind);
- behind |= shift<Down+Down>(behind);
-
- // Compute space score based on the number of safe squares and number of our pieces
- // increased with number of total blocked pawns in position.
- int bonus = popcount(safe) + popcount(behind & safe & ~attackedBy[Them][ALL_PIECES]);
- int weight = pos.count<ALL_PIECES>(Us) - 3 + std::min(pe->blocked_count(), 9);
- Score score = make_score(bonus * weight * weight / 16, 0);
-
- if constexpr (T)
- Trace::add(SPACE, Us, score);
-
- return score;
- }
-
-
- // Evaluation::winnable() adjusts the midgame and endgame score components, based on
- // the known attacking/defending status of the players. The final value is derived
- // by interpolation from the midgame and endgame values.
-
- template<Tracing T>
- Value Evaluation<T>::winnable(Score score) const {
-
- int outflanking = distance<File>(pos.square<KING>(WHITE), pos.square<KING>(BLACK))
- + int(rank_of(pos.square<KING>(WHITE)) - rank_of(pos.square<KING>(BLACK)));
-
- bool pawnsOnBothFlanks = (pos.pieces(PAWN) & QueenSide)
- && (pos.pieces(PAWN) & KingSide);
-
- bool almostUnwinnable = outflanking < 0
- && !pawnsOnBothFlanks;
-
- bool infiltration = rank_of(pos.square<KING>(WHITE)) > RANK_4
- || rank_of(pos.square<KING>(BLACK)) < RANK_5;
-
- // Compute the initiative bonus for the attacking side
- int complexity = 9 * pe->passed_count()
- + 12 * pos.count<PAWN>()
- + 9 * outflanking
- + 21 * pawnsOnBothFlanks
- + 24 * infiltration
- + 51 * !pos.non_pawn_material()
- - 43 * almostUnwinnable
- -110 ;
-
- Value mg = mg_value(score);
- Value eg = eg_value(score);
-
- // Now apply the bonus: note that we find the attacking side by extracting the
- // sign of the midgame or endgame values, and that we carefully cap the bonus
- // so that the midgame and endgame scores do not change sign after the bonus.
- int u = ((mg > 0) - (mg < 0)) * std::clamp(complexity + 50, -abs(mg), 0);
- int v = ((eg > 0) - (eg < 0)) * std::max(complexity, -abs(eg));
-
- mg += u;
- eg += v;
-
- // Compute the scale factor for the winning side
- Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK;
- int sf = me->scale_factor(pos, strongSide);
-
- // If scale factor is not already specific, scale up/down via general heuristics
- if (sf == SCALE_FACTOR_NORMAL)
- {
- if (pos.opposite_bishops())
- {
- // For pure opposite colored bishops endgames use scale factor
- // based on the number of passed pawns of the strong side.
- if ( pos.non_pawn_material(WHITE) == BishopValueMg
- && pos.non_pawn_material(BLACK) == BishopValueMg)
- sf = 18 + 4 * popcount(pe->passed_pawns(strongSide));
- // For every other opposite colored bishops endgames use scale factor
- // based on the number of all pieces of the strong side.
- else
- sf = 22 + 3 * pos.count<ALL_PIECES>(strongSide);
- }
- // For rook endgames with strong side not having overwhelming pawn number advantage
- // and its pawns being on one flank and weak side protecting its pieces with a king
- // use lower scale factor.
- else if ( pos.non_pawn_material(WHITE) == RookValueMg
- && pos.non_pawn_material(BLACK) == RookValueMg
- && pos.count<PAWN>(strongSide) - pos.count<PAWN>(~strongSide) <= 1
- && bool(KingSide & pos.pieces(strongSide, PAWN)) != bool(QueenSide & pos.pieces(strongSide, PAWN))
- && (attacks_bb<KING>(pos.square<KING>(~strongSide)) & pos.pieces(~strongSide, PAWN)))
- sf = 36;
- // For queen vs no queen endgames use scale factor
- // based on number of minors of side that doesn't have queen.
- else if (pos.count<QUEEN>() == 1)
- sf = 37 + 3 * (pos.count<QUEEN>(WHITE) == 1 ? pos.count<BISHOP>(BLACK) + pos.count<KNIGHT>(BLACK)
- : pos.count<BISHOP>(WHITE) + pos.count<KNIGHT>(WHITE));
- // In every other case use scale factor based on
- // the number of pawns of the strong side reduced if pawns are on a single flank.
- else
- sf = std::min(sf, 36 + 7 * pos.count<PAWN>(strongSide)) - 4 * !pawnsOnBothFlanks;
-
- // Reduce scale factor in case of pawns being on a single flank
- sf -= 4 * !pawnsOnBothFlanks;
- }
-
- // Interpolate between the middlegame and (scaled by 'sf') endgame score
- v = mg * int(me->game_phase())
- + eg * int(PHASE_MIDGAME - me->game_phase()) * ScaleFactor(sf) / SCALE_FACTOR_NORMAL;
- v /= PHASE_MIDGAME;
-
- if constexpr (T)
- {
- Trace::add(WINNABLE, make_score(u, eg * ScaleFactor(sf) / SCALE_FACTOR_NORMAL - eg_value(score)));
- Trace::add(TOTAL, make_score(mg, eg * ScaleFactor(sf) / SCALE_FACTOR_NORMAL));
- }
-
- return Value(v);
- }
-
-
- // Evaluation::value() is the main function of the class. It computes the various
- // parts of the evaluation and returns the value of the position from the point
- // of view of the side to move.
-
- template<Tracing T>
- Value Evaluation<T>::value() {
-
- 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();
-
- // Probe the pawn hash table
- pe = Pawns::probe(pos);
- score += pe->pawn_score(WHITE) - pe->pawn_score(BLACK);
-
- // Early exit if score is high
- auto lazy_skip = [&](Value lazyThreshold) {
- return abs(mg_value(score) + eg_value(score)) > lazyThreshold
- + std::abs(pos.this_thread()->bestValue) * 5 / 4
- + pos.non_pawn_material() / 32;
- };
-
- if (lazy_skip(LazyThreshold1))
- goto make_v;
-
- // Main evaluation begins here
- initialize<WHITE>();
- initialize<BLACK>();
-
- // Pieces evaluated first (also populates attackedBy, attackedBy2).
- // Note that the order of evaluation of the terms is left unspecified.
- 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];
-
- // More complex interactions that require fully populated attack bitboards
- score += king< WHITE>() - king< BLACK>()
- + passed< WHITE>() - passed< BLACK>();
-
- if (lazy_skip(LazyThreshold2))
- goto make_v;
-
- score += threats<WHITE>() - threats<BLACK>()
- + space< WHITE>() - space< BLACK>();
-
-make_v:
- // Derive single value from mg and eg parts of score
- Value v = winnable(score);
-
- // In case of tracing add all remaining individual evaluation terms
- if constexpr (T)
- {
- Trace::add(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]);
- }
-
- // Evaluation grain
- v = (v / 16) * 16;
-
- // Side to move point of view
- v = (pos.side_to_move() == WHITE ? v : -v);
-
- return v;
- }
-
-} // namespace Eval
-
-
/// evaluate() is the evaluator for the outer world. It returns a static
/// evaluation of the position from the point of view of the side to move.
-Value Eval::evaluate(const Position& pos, int* complexity) {
+Value Eval::evaluate(const Position& pos) {
+
+ assert(!pos.checkers());
Value v;
Value psq = pos.psq_eg_stm();
- // We use the much less accurate but faster Classical eval when the NNUE
- // option is set to false. Otherwise we use the NNUE eval unless the
- // PSQ advantage is decisive. (~4 Elo at STC, 1 Elo at LTC)
- bool useClassical = !useNNUE || abs(psq) > 2048;
-
- if (useClassical)
- v = Evaluation<NO_TRACE>(pos).value();
- else
- {
- int nnueComplexity;
- int scale = 1001 + 5 * pos.count<PAWN>() + 61 * pos.non_pawn_material() / 4096;
+ int nnueComplexity;
+ int npm = pos.non_pawn_material() / 64;
- Color stm = pos.side_to_move();
- Value optimism = pos.this_thread()->optimism[stm];
+ Color stm = pos.side_to_move();
+ Value optimism = pos.this_thread()->optimism[stm];
- Value nnue = NNUE::evaluate(pos, true, &nnueComplexity);
+ Value nnue = NNUE::evaluate(pos, true, &nnueComplexity);
- // Blend nnue complexity with (semi)classical complexity
- nnueComplexity = ( 406 * nnueComplexity
- + (424 + optimism) * abs(psq - nnue)
- ) / 1024;
-
- // Return hybrid NNUE complexity to caller
- if (complexity)
- *complexity = nnueComplexity;
-
- optimism = optimism * (272 + nnueComplexity) / 256;
- v = (nnue * scale + optimism * (scale - 748)) / 1024;
- }
+ // Blend optimism with nnue complexity and (semi)classical complexity
+ optimism += optimism * (nnueComplexity + abs(psq - nnue)) / 512;
+ v = (nnue * (945 + npm) + optimism * (150 + npm)) / 1024;
// Damp down the evaluation linearly when shuffling
v = v * (200 - pos.rule50_count()) / 214;
// 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);
- // When not using NNUE, return classical complexity to caller
- if (complexity && useClassical)
- *complexity = abs(v - psq);
-
return v;
}
if (pos.checkers())
return "Final evaluation: none (in check)";
- std::stringstream ss;
- ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2);
-
- Value v;
-
- std::memset(scores, 0, sizeof(scores));
-
// Reset any global variable used in eval
pos.this_thread()->bestValue = VALUE_ZERO;
pos.this_thread()->optimism[WHITE] = VALUE_ZERO;
pos.this_thread()->optimism[BLACK] = VALUE_ZERO;
- v = Evaluation<TRACE>(pos).value();
-
- ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2)
- << " Contributing terms for the classical eval:\n"
- << "+------------+-------------+-------------+-------------+\n"
- << "| Term | White | Black | Total |\n"
- << "| | MG EG | MG EG | MG EG |\n"
- << "+------------+-------------+-------------+-------------+\n"
- << "| Material | " << Term(MATERIAL)
- << "| Imbalance | " << Term(IMBALANCE)
- << "| Pawns | " << Term(PAWN)
- << "| Knights | " << Term(KNIGHT)
- << "| Bishops | " << Term(BISHOP)
- << "| Rooks | " << Term(ROOK)
- << "| Queens | " << Term(QUEEN)
- << "| Mobility | " << Term(MOBILITY)
- << "|King safety | " << Term(KING)
- << "| Threats | " << Term(THREAT)
- << "| Passed | " << Term(PASSED)
- << "| Space | " << Term(SPACE)
- << "| Winnable | " << Term(WINNABLE)
- << "+------------+-------------+-------------+-------------+\n"
- << "| Total | " << Term(TOTAL)
- << "+------------+-------------+-------------+-------------+\n";
-
- if (Eval::useNNUE)
- ss << '\n' << NNUE::trace(pos) << '\n';
+ std::stringstream ss;
+ ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2);
+ ss << '\n' << NNUE::trace(pos) << '\n';
ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15);
+ Value v;
+ v = NNUE::evaluate(pos, false);
v = pos.side_to_move() == WHITE ? v : -v;
- ss << "\nClassical evaluation " << to_cp(v) << " (white side)\n";
- if (Eval::useNNUE)
- {
- v = NNUE::evaluate(pos, false);
- v = pos.side_to_move() == WHITE ? v : -v;
- ss << "NNUE evaluation " << to_cp(v) << " (white side)\n";
- }
+ ss << "NNUE evaluation " << to_cp(v) << " (white side)\n";
v = evaluate(pos);
v = pos.side_to_move() == WHITE ? v : -v;
ss << "Final evaluation " << to_cp(v) << " (white side)";
- if (Eval::useNNUE)
- ss << " [with scaled NNUE, hybrid, ...]";
+ ss << " [with scaled NNUE, ...]";
ss << "\n";
return ss.str();
namespace Eval {
std::string trace(Position& pos);
- Value evaluate(const Position& pos, int* complexity = nullptr);
+ Value evaluate(const Position& pos);
extern bool useNNUE;
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-dabb1ed23026.nnue"
+ #define EvalFileDefaultName "nn-c38c3d8d3920.nnue"
namespace NNUE {
#include <thread>
#include "bitboard.h"
-#include "endgame.h"
#include "position.h"
#include "psqt.h"
#include "search.h"
PSQT::init();
Bitboards::init();
Position::init();
- Bitbases::init();
- Endgames::init();
Threads.set(size_t(Options["Threads"]));
Search::clear(); // After threads are up
Eval::NNUE::init();
+++ /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 <cassert>
-#include <cstring> // For std::memset
-
-#include "material.h"
-#include "thread.h"
-
-using namespace std;
-
-namespace Stockfish {
-
-namespace {
- #define S(mg, eg) make_score(mg, eg)
-
- // Polynomial material imbalance parameters
-
- // One Score parameter for each pair (our piece, another of our pieces)
- constexpr Score QuadraticOurs[][PIECE_TYPE_NB] = {
- // OUR PIECE 2
- // bishop pair pawn knight bishop rook queen
- {S(1419, 1455) }, // Bishop pair
- {S( 101, 28), S( 37, 39) }, // Pawn
- {S( 57, 64), S(249, 187), S(-49, -62) }, // Knight OUR PIECE 1
- {S( 0, 0), S(118, 137), S( 10, 27), S( 0, 0) }, // Bishop
- {S( -63, -68), S( -5, 3), S(100, 81), S(132, 118), S(-246, -244) }, // Rook
- {S(-210, -211), S( 37, 14), S(147, 141), S(161, 105), S(-158, -174), S(-9,-31) } // Queen
- };
-
- // One Score parameter for each pair (our piece, their piece)
- constexpr Score QuadraticTheirs[][PIECE_TYPE_NB] = {
- // THEIR PIECE
- // bishop pair pawn knight bishop rook queen
- { }, // Bishop pair
- {S( 33, 30) }, // Pawn
- {S( 46, 18), S(106, 84) }, // Knight OUR PIECE
- {S( 75, 35), S( 59, 44), S( 60, 15) }, // Bishop
- {S( 26, 35), S( 6, 22), S( 38, 39), S(-12, -2) }, // Rook
- {S( 97, 93), S(100, 163), S(-58, -91), S(112, 192), S(276, 225) } // Queen
- };
-
- #undef S
-
- // Endgame evaluation and scaling functions are accessed directly and not through
- // the function maps because they correspond to more than one material hash key.
- Endgame<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>
- Score imbalance(const int pieceCount[][PIECE_TYPE_NB]) {
-
- constexpr Color Them = ~Us;
-
- Score bonus = SCORE_ZERO;
-
- // Second-degree polynomial material imbalance, by Tord Romstad
- for (int pt1 = NO_PIECE_TYPE; pt1 <= QUEEN; ++pt1)
- {
- if (!pieceCount[Us][pt1])
- continue;
-
- int v = QuadraticOurs[pt1][pt1] * pieceCount[Us][pt1];
-
- for (int pt2 = NO_PIECE_TYPE; pt2 < pt1; ++pt2)
- v += QuadraticOurs[pt1][pt2] * pieceCount[Us][pt2]
- + QuadraticTheirs[pt1][pt2] * pieceCount[Them][pt2];
-
- bonus += pieceCount[Us][pt1] * v;
- }
-
- 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 = std::clamp(npm_w + npm_b, EndgameLimit, MidgameLimit);
-
- // Map total non-pawn material into [PHASE_ENDGAME, PHASE_MIDGAME]
- e->gamePhase = Phase(((npm - EndgameLimit) * PHASE_MIDGAME) / (MidgameLimit - EndgameLimit));
-
- // 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->score = (imbalance<WHITE>(pieceCount) - imbalance<BLACK>(pieceCount)) / 16;
- return e;
-}
-
-} // namespace Material
-
-} // 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 MATERIAL_H_INCLUDED
-#define MATERIAL_H_INCLUDED
-
-#include "endgame.h"
-#include "misc.h"
-#include "position.h"
-#include "types.h"
-
-namespace Stockfish::Material {
-
-/// Material::Entry contains various information about a material configuration.
-/// It contains a material imbalance evaluation, a function pointer to a special
-/// endgame evaluation function (which in most cases is nullptr, meaning that the
-/// standard evaluation function will be used), and scale factors.
-///
-/// The scale factors are used to scale the evaluation score up or down. For
-/// instance, in KRB vs KR endgames, the score is scaled down by a factor of 4,
-/// which will result in scores of absolute value less than one pawn.
-
-struct Entry {
-
- Score imbalance() const { return score; }
- Phase game_phase() const { return (Phase)gamePhase; }
- bool specialized_eval_exists() const { return evaluationFunction != nullptr; }
- Value evaluate(const Position& pos) const { return (*evaluationFunction)(pos); }
-
- // 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)
- Score score;
- int16_t gamePhase;
- uint8_t factor[COLOR_NB];
-};
-
-using Table = HashTable<Entry, 8192>;
-
-Entry* probe(const Position& pos);
-
-} // namespace Stockfish::Material
-
-#endif // #ifndef MATERIAL_H_INCLUDED
return nullptr;
// Dynamically link OpenProcessToken, LookupPrivilegeValue and AdjustTokenPrivileges
- HMODULE k32 = GetModuleHandle("Advapi32.dll");
- auto fun6 = (fun6_t)(void(*)())GetProcAddress(k32, "OpenProcessToken");
+
+ 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(k32, "LookupPrivilegeValueA");
+ auto fun7 = (fun7_t)(void(*)())GetProcAddress(hAdvapi32, "LookupPrivilegeValueA");
if (!fun7)
return nullptr;
- auto fun8 = (fun8_t)(void(*)())GetProcAddress(k32, "AdjustTokenPrivileges");
+ auto fun8 = (fun8_t)(void(*)())GetProcAddress(hAdvapi32, "AdjustTokenPrivileges");
if (!fun8)
return nullptr;
-
// We need SeLockMemoryPrivilege, so try to enable it for the process
- // OpenProcessToken()
- if (!fun6(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken))
- return nullptr;
+ if (!fun6( // OpenProcessToken()
+ GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken))
+ return nullptr;
- // LookupPrivilegeValueA()
- if (fun7(nullptr, SE_LOCK_MEMORY_NAME, &luid))
+ if (fun7( // LookupPrivilegeValue(nullptr, SE_LOCK_MEMORY_NAME, &luid)
+ nullptr, "SeLockMemoryPrivilege", &luid))
{
TOKEN_PRIVILEGES tp { };
TOKEN_PRIVILEGES prevTp { };
// Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() succeeds,
// we still need to query GetLastError() to ensure that the privileges were actually obtained.
- // AdjustTokenPrivileges()
- if (fun8(
+ if (fun8( // AdjustTokenPrivileges()
hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, &prevTpLen) &&
GetLastError() == ERROR_SUCCESS)
{
nullptr, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE);
// Privilege no longer needed, restore previous state
- // AdjustTokenPrivileges ()
- fun8(hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr);
+ fun8( // AdjustTokenPrivileges ()
+ hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr);
}
}
namespace {
- template<GenType Type, Direction D>
+ template<GenType Type, Direction D, bool Enemy>
ExtMove* make_promotions(ExtMove* moveList, [[maybe_unused]] Square to) {
if constexpr (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
+ {
*moveList++ = make<PROMOTION>(to - D, to, QUEEN);
+ if constexpr (Enemy && Type == CAPTURES)
+ {
+ *moveList++ = make<PROMOTION>(to - D, to, ROOK);
+ *moveList++ = make<PROMOTION>(to - D, to, BISHOP);
+ *moveList++ = make<PROMOTION>(to - D, to, KNIGHT);
+ }
+ }
- if constexpr (Type == QUIETS || Type == EVASIONS || Type == NON_EVASIONS)
+ if constexpr ((Type == QUIETS && !Enemy) || Type == EVASIONS || Type == NON_EVASIONS)
{
*moveList++ = make<PROMOTION>(to - D, to, ROOK);
*moveList++ = make<PROMOTION>(to - D, to, BISHOP);
b3 &= target;
while (b1)
- moveList = make_promotions<Type, UpRight>(moveList, pop_lsb(b1));
+ moveList = make_promotions<Type, UpRight, true>(moveList, pop_lsb(b1));
while (b2)
- moveList = make_promotions<Type, UpLeft >(moveList, pop_lsb(b2));
+ moveList = make_promotions<Type, UpLeft, true>(moveList, pop_lsb(b2));
while (b3)
- moveList = make_promotions<Type, Up >(moveList, pop_lsb(b3));
+ moveList = make_promotions<Type, Up, false>(moveList, pop_lsb(b3));
}
// Standard and en passant captures
}
void hint_common_parent_position(const Position& pos) {
- if (Eval::useNNUE)
- featureTransformer->hint_common_access(pos);
+ featureTransformer->hint_common_access(pos);
}
// Evaluation function. Perform differential calculation.
This file contains the definition for a fully connected layer (aka affine transform).
Two approaches are employed, depending on the sizes of the transform.
- Approach 1:
+ Approach 1 (a specialization for large inputs):
- used when the PaddedInputDimensions >= 128
- uses AVX512 if possible
- processes inputs in batches of 2*InputSimdWidth
depends on the architecture (the amount of registers)
- accumulate + hadd is used
- Approach 2:
+ Approach 2 (a specialization for small inputs):
- used when the PaddedInputDimensions < 128
- - does not use AVX512
- 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
constexpr IndexType LargeInputSize = std::numeric_limits<IndexType>::max();
#endif
- // A specialization for large inputs.
+ // A specialization for large inputs
template <IndexType InDims, IndexType OutDims>
class AffineTransform<InDims, OutDims, std::enable_if_t<(ceil_to_multiple<IndexType>(InDims, MaxSimdWidth) >= LargeInputSize)>> {
public:
using OutputBuffer = OutputType[PaddedOutputDimensions];
- static_assert(PaddedInputDimensions >= LargeInputSize, "Something went wrong. This specialization should not have been chosen.");
+ static_assert(PaddedInputDimensions >= LargeInputSize, "Something went wrong. This specialization (for large inputs) should not have been chosen.");
#if defined (USE_AVX512)
static constexpr IndexType InputSimdWidth = 64;
alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions];
};
+ // A specialization for small inputs
template <IndexType InDims, IndexType OutDims>
class AffineTransform<InDims, OutDims, std::enable_if_t<(ceil_to_multiple<IndexType>(InDims, MaxSimdWidth) < LargeInputSize)>> {
public:
using OutputBuffer = OutputType[PaddedOutputDimensions];
- static_assert(PaddedInputDimensions < LargeInputSize, "Something went wrong. This specialization should not have been chosen.");
-
-#if defined (USE_SSSE3)
- static constexpr IndexType OutputSimdWidth = SimdWidth / 4;
- static constexpr IndexType InputSimdWidth = SimdWidth;
-#endif
+ static_assert(PaddedInputDimensions < LargeInputSize, "Something went wrong. This specialization (for small inputs) should not have been chosen.");
// Hash value embedded in the evaluation file
static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {
const OutputType* propagate(
const InputType* input, OutputType* output) const {
-#if defined (USE_AVX2)
+#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
#if defined (USE_SSSE3)
const auto inputVector = reinterpret_cast<const vec_t*>(input);
+ static constexpr IndexType OutputSimdWidth = sizeof(vec_t) / sizeof(OutputType);
+
static_assert(OutputDimensions % OutputSimdWidth == 0 || OutputDimensions == 1);
if constexpr (OutputDimensions % OutputSimdWidth == 0)
--- /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 <iostream>
+#include <algorithm>
+#include <array>
+#include <type_traits>
+#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 defined(__GNUC__) // GCC, Clang, ICC
+
+ static inline IndexType lsb_(std::uint32_t b) {
+ assert(b);
+ return IndexType(__builtin_ctzl(b));
+ }
+
+#elif defined(_MSC_VER) // MSVC
+
+ static inline IndexType lsb_(std::uint32_t b) {
+ assert(b);
+ unsigned long idx;
+ _BitScanForward(&idx, b);
+ return (IndexType) idx;
+ }
+
+#else // Compiler is neither GCC nor MSVC compatible
+
+#error "Compiler not supported."
+
+#endif
+
+
+#if defined(USE_SSSE3)
+ 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 (int i = 0; i < 256; ++i)
+ {
+ int j = i;
+ int k = 0;
+ while(j)
+ {
+ const IndexType lsbIndex = lsb_(std::uint32_t(j));
+ j &= j - 1;
+ v[i][k] = lsbIndex;
+ ++k;
+ }
+ }
+ 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_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;
+ #define vec_nnz(a) _mm256_movemask_ps(_mm256_castsi256_ps(_mm256_cmpgt_epi32(a, _mm256_setzero_si256())))
+#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
+ 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;
+ __m128i base = _mm_set1_epi16(0);
+ __m128i increment = _mm_set1_epi16(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 = _mm_loadu_si128(reinterpret_cast<const __m128i*>(&lookup_indices[lookup]));
+ _mm_storeu_si128(reinterpret_cast<__m128i*>(out + count), _mm_add_epi16(base, offsets));
+ count += popcount(lookup);
+ base = _mm_add_epi16(base, increment);
+ }
+ }
+ count_out = count;
+ }
+# undef vec_nnz
+#endif
+
+ // Sparse input implementation
+ template <IndexType InDims, IndexType OutDims>
+ class AffineTransformSparseInput {
+ public:
+ // Input/output type
+ // 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 defined (USE_SSSE3)
+ 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 IndexType get_weight_index_scrambled(IndexType i)
+ {
+ return
+ (i / ChunkSize) % (PaddedInputDimensions / ChunkSize) * OutputDimensions * ChunkSize +
+ i / PaddedInputDimensions * ChunkSize +
+ i % ChunkSize;
+ }
+
+ static 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
+ const OutputType* propagate(
+ const InputType* input, OutputType* output) const {
+
+#if defined (USE_SSSE3)
+#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
+#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
+#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
+#endif
+ static constexpr IndexType OutputSimdWidth = sizeof(vec_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 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 j = 0; j < count; ++j)
+ {
+ const auto i = nnz[j];
+ const vec_t in = vec_set_32(input32[i]);
+ const auto col = reinterpret_cast<const vec_t*>(&weights[i * OutputDimensions * ChunkSize]);
+ for (IndexType k = 0; k < NumRegs; ++k)
+ vec_add_dpbusd_32(acc[k], in, col[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
+#else
+ // Use dense implementation for the other architectures.
+ affine_transform_non_ssse3<
+ InputDimensions,
+ PaddedInputDimensions,
+ OutputDimensions>(output, weights, biases, input);
+#endif
+
+ return output;
+ }
+
+ 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
#include "features/half_ka_v2_hm.h"
+#include "layers/affine_transform_sparse_input.h"
#include "layers/affine_transform.h"
#include "layers/clipped_relu.h"
#include "layers/sqr_clipped_relu.h"
using FeatureSet = Features::HalfKAv2_hm;
// Number of input feature dimensions after conversion
-constexpr IndexType TransformedFeatureDimensions = 1024;
+constexpr IndexType TransformedFeatureDimensions = 2048;
constexpr IndexType PSQTBuckets = 8;
constexpr IndexType LayerStacks = 8;
static constexpr int FC_0_OUTPUTS = 15;
static constexpr int FC_1_OUTPUTS = 32;
- Layers::AffineTransform<TransformedFeatureDimensions, FC_0_OUTPUTS + 1> fc_0;
+ 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;
// 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;
return (n + base - 1) / base * base;
}
+
// read_little_endian() is our utility to read an integer (signed or unsigned, any size)
// from a stream in little-endian order. We swap the byte order after the read if
// necessary to return a result with the byte ordering of the compiling machine.
return result;
}
+
// write_little_endian() is our utility to write an integer (signed or unsigned, any size)
// to a stream in little-endian order. We swap the byte order before the write if
// necessary to always write in little endian order, independently of the byte
}
}
+
// read_little_endian(s, out, N) : 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>
out[i] = read_little_endian<IntType>(stream);
}
+
// write_little_endian(s, values, N) : 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>
write_little_endian<IntType>(stream, values[i]);
}
+
+ // read_leb_128(s, out, N) : 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_leb_128(s, values, N) : 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
// Read network parameters
bool read_parameters(std::istream& stream) {
- read_little_endian<BiasType >(stream, biases , HalfDimensions );
- read_little_endian<WeightType >(stream, weights , HalfDimensions * InputDimensions);
- read_little_endian<PSQTWeightType>(stream, psqtWeights, PSQTBuckets * InputDimensions);
+ 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_little_endian<BiasType >(stream, biases , HalfDimensions );
- write_little_endian<WeightType >(stream, weights , HalfDimensions * InputDimensions);
- write_little_endian<PSQTWeightType>(stream, psqtWeights, PSQTBuckets * InputDimensions);
+ 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();
}
+++ /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 <algorithm>
-#include <cassert>
-
-#include "bitboard.h"
-#include "pawns.h"
-#include "position.h"
-#include "thread.h"
-
-namespace Stockfish {
-
-namespace {
-
- #define V Value
- #define S(mg, eg) make_score(mg, eg)
-
- // Pawn penalties
- constexpr Score Backward = S( 6, 19);
- constexpr Score Doubled = S(11, 51);
- constexpr Score DoubledEarly = S(17, 7);
- constexpr Score Isolated = S( 1, 20);
- constexpr Score WeakLever = S( 2, 57);
- constexpr Score WeakUnopposed = S(15, 18);
-
- // Bonus for blocked pawns at 5th or 6th rank
- constexpr Score BlockedPawn[2] = { S(-19, -8), S(-7, 3) };
-
- constexpr Score BlockedStorm[RANK_NB] = {
- S(0, 0), S(0, 0), S(64, 75), S(-3, 14), S(-12, 19), S(-7, 4), S(-10, 5)
- };
-
- // Connected pawn bonus
- constexpr int Connected[RANK_NB] = { 0, 3, 7, 7, 15, 54, 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(-2), V(85), V(95), V(53), V(39), V(23), V(25) },
- { V(-55), V(64), V(32), V(-55), V(-30), V(-11), V(-61) },
- { V(-11), V(75), V(19), V(-6), V(26), V(9), V(-47) },
- { V(-41), V(-11), V(-27), V(-58), V(-42), V(-66), V(-163) }
- };
-
- // 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(94), V(-280), V(-170), V(90), V(59), V(47), V(53) },
- { V(43), V(-17), V(128), V(39), V(26), V(-17), V(15) },
- { V(-9), V(62), V(170), V(34), V(-5), V(-20), V(-11) },
- { V(-27), V(-19), V(106), V(10), V(2), V(-13), V(-24) }
- };
-
-
- // KingOnFile[semi-open Us][semi-open Them] contains bonuses/penalties
- // for king when the king is on a semi-open or open file.
- constexpr Score KingOnFile[2][2] = {{ S(-18,11), S(-6,-3) },
- { S( 0, 0), S( 5,-4) }};
-
- #undef S
- #undef V
-
-
- /// evaluate() calculates a score for the static pawn structure of the given position.
- /// We cannot use the location of pieces or king in this function, as the evaluation
- /// of the pawn structure will be stored in a small cache for speed reasons, and will
- /// be re-used even when the pieces have moved.
-
- template<Color Us>
- Score evaluate(const Position& pos, Pawns::Entry* e) {
-
- constexpr Color Them = ~Us;
- constexpr Direction Up = pawn_push(Us);
- constexpr Direction Down = -Up;
-
- Bitboard neighbours, stoppers, support, phalanx, opposed;
- Bitboard lever, leverPush, blocked;
- Square s;
- bool backward, passed, doubled;
- Score score = SCORE_ZERO;
- Bitboard b = pos.pieces(Us, PAWN);
-
- Bitboard ourPawns = pos.pieces( Us, PAWN);
- Bitboard theirPawns = pos.pieces(Them, PAWN);
-
- Bitboard doubleAttackThem = pawn_double_attacks_bb<Them>(theirPawns);
-
- e->passedPawns[Us] = 0;
- e->kingSquares[Us] = SQ_NONE;
- e->pawnAttacks[Us] = e->pawnAttacksSpan[Us] = pawn_attacks_bb<Us>(ourPawns);
- e->blockedCount += popcount(shift<Up>(ourPawns) & (theirPawns | doubleAttackThem));
-
- // Loop through all pawns of the current color and score each pawn
- while (b)
- {
- s = pop_lsb(b);
-
- assert(pos.piece_on(s) == make_piece(Us, PAWN));
-
- Rank r = relative_rank(Us, s);
-
- // Flag the pawn
- opposed = theirPawns & forward_file_bb(Us, s);
- blocked = theirPawns & (s + Up);
- stoppers = theirPawns & passed_pawn_span(Us, s);
- lever = theirPawns & pawn_attacks_bb(Us, s);
- leverPush = theirPawns & pawn_attacks_bb(Us, s + Up);
- doubled = ourPawns & (s - Up);
- neighbours = ourPawns & adjacent_files_bb(s);
- phalanx = neighbours & rank_bb(s);
- support = neighbours & rank_bb(s - Up);
-
- if (doubled)
- {
- // Additional doubled penalty if none of their pawns is fixed
- if (!(ourPawns & shift<Down>(theirPawns | pawn_attacks_bb<Them>(theirPawns))))
- score -= DoubledEarly;
- }
-
- // A pawn is backward when it is behind all pawns of the same color on
- // the adjacent files and cannot safely advance.
- backward = !(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.
- // (Refined in Evaluation::passed)
- passed = !(stoppers ^ lever)
- || ( !(stoppers ^ leverPush)
- && popcount(phalanx) >= popcount(leverPush))
- || ( stoppers == blocked && r >= RANK_5
- && (shift<Up>(support) & ~(theirPawns | doubleAttackThem)));
-
- passed &= !(forward_file_bb(Us, s) & ourPawns);
-
- // Passed pawns will be properly scored later in evaluation when we have
- // full attack info.
- if (passed)
- e->passedPawns[Us] |= s;
-
- // Score this pawn
- if (support | phalanx)
- {
- int v = Connected[r] * (2 + bool(phalanx) - bool(opposed))
- + 22 * popcount(support);
-
- score += make_score(v, v * (r - 2) / 4);
- }
-
- else if (!neighbours)
- {
- if ( opposed
- && (ourPawns & forward_file_bb(Them, s))
- && !(theirPawns & adjacent_files_bb(s)))
- score -= Doubled;
- else
- score -= Isolated
- + WeakUnopposed * !opposed;
- }
-
- else if (backward)
- score -= Backward
- + WeakUnopposed * !opposed * bool(~(FileABB | FileHBB) & s);
-
- if (!support)
- score -= Doubled * doubled
- + WeakLever * more_than_one(lever);
-
- if (blocked && r >= RANK_5)
- score += BlockedPawn[r - RANK_5];
- }
-
- 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->blockedCount = 0;
- 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) const {
-
- constexpr Color Them = ~Us;
-
- Bitboard b = pos.pieces(PAWN) & ~forward_ranks_bb(Them, ksq);
- Bitboard ourPawns = b & pos.pieces(Us) & ~pawnAttacks[Them];
- Bitboard theirPawns = b & pos.pieces(Them);
-
- Score bonus = make_score(5, 5);
-
- File center = std::clamp(file_of(ksq), FILE_B, FILE_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;
-
- int d = edge_distance(f);
- bonus += make_score(ShelterStrength[d][ourRank], 0);
-
- if (ourRank && (ourRank == theirRank - 1))
- bonus -= BlockedStorm[theirRank];
- else
- bonus -= make_score(UnblockedStorm[d][theirRank], 0);
- }
-
- // King On File
- bonus -= KingOnFile[pos.is_on_semiopen_file(Us, ksq)][pos.is_on_semiopen_file(Them, ksq)];
-
- return bonus;
-}
-
-
-/// Entry::do_king_safety() calculates a bonus for king safety. It is called only
-/// when king square changes, which is about 20% of total king_safety() calls.
-
-template<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 = 6;
-
- if (pawns & attacks_bb<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
-
-} // 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 PAWNS_H_INCLUDED
-#define PAWNS_H_INCLUDED
-
-#include "misc.h"
-#include "position.h"
-#include "types.h"
-
-namespace Stockfish::Pawns {
-
-/// Pawns::Entry contains various information about a pawn structure. A lookup
-/// to the pawn hash table (performed by calling the probe function) returns a
-/// pointer to an Entry object.
-
-struct Entry {
-
- Score pawn_score(Color c) const { return scores[c]; }
- Bitboard pawn_attacks(Color c) const { return pawnAttacks[c]; }
- Bitboard passed_pawns(Color c) const { return passedPawns[c]; }
- Bitboard pawn_attacks_span(Color c) const { return pawnAttacksSpan[c]; }
- int passed_count() const { return popcount(passedPawns[WHITE] | passedPawns[BLACK]); }
- int blocked_count() const { return blockedCount; }
-
- template<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) const;
-
- Key key;
- Score scores[COLOR_NB];
- Bitboard passedPawns[COLOR_NB];
- Bitboard pawnAttacks[COLOR_NB];
- Bitboard pawnAttacksSpan[COLOR_NB];
- Square kingSquares[COLOR_NB];
- Score kingSafety[COLOR_NB];
- int castlingRights[COLOR_NB];
- int blockedCount;
-};
-
-using Table = HashTable<Entry, 131072>;
-
-Entry* probe(const Position& pos);
-
-} // namespace Stockfish::Pawns
-
-#endif // #ifndef PAWNS_H_INCLUDED
/// 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.
+/// The function is only used when a new position is set up
void Position::set_state() const {
return true;
// Is there a discovered check?
- if ( (blockers_for_king(~sideToMove) & from)
- && !aligned(from, to, square<KING>(~sideToMove)))
- return true;
+ if (blockers_for_king(~sideToMove) & from)
+ return !aligned(from, to, square<KING>(~sideToMove))
+ || type_of(m) == CASTLING;
switch (type_of(m))
{
default: //CASTLING
{
// Castling is encoded as 'king captures the rook'
- Square ksq = square<KING>(~sideToMove);
Square rto = relative_square(sideToMove, to > from ? SQ_F1 : SQ_D1);
- return (attacks_bb<ROOK>(rto) & ksq)
- && (attacks_bb<ROOK>(rto, pieces() ^ from ^ to) & ksq);
+ return check_squares(ROOK) & rto;
}
}
}
else
st->nonPawnMaterial[them] -= PieceValue[MG][captured];
- if (Eval::useNNUE)
- {
- dp.dirty_num = 2; // 1 piece moved, 1 piece captured
- dp.piece[1] = captured;
- dp.from[1] = capsq;
- dp.to[1] = SQ_NONE;
- }
+ 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]];
- prefetch(thisThread->materialTable[st->materialKey]);
// Reset rule 50 counter
st->rule50 = 0;
// Move the piece. The tricky Chess960 castling is handled earlier
if (type_of(m) != CASTLING)
{
- if (Eval::useNNUE)
- {
- dp.piece[0] = pc;
- dp.from[0] = from;
- dp.to[0] = to;
- }
+ dp.piece[0] = pc;
+ dp.from[0] = from;
+ dp.to[0] = to;
move_piece(from, to);
}
remove_piece(to);
put_piece(promotion, to);
- if (Eval::useNNUE)
- {
- // Promoting pawn to SQ_NONE, promoted piece from SQ_NONE
- dp.to[0] = SQ_NONE;
- dp.piece[dp.dirty_num] = promotion;
- dp.from[dp.dirty_num] = SQ_NONE;
- dp.to[dp.dirty_num] = to;
- dp.dirty_num++;
- }
+ // 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];
rto = relative_square(us, kingSide ? SQ_F1 : SQ_D1);
to = relative_square(us, kingSide ? SQ_G1 : SQ_C1);
- if (Do && Eval::useNNUE)
+ if (Do)
{
auto& dp = st->dirtyPiece;
dp.piece[0] = make_piece(us, KING);
std::string fen() const;
// Position representation
- Bitboard pieces(PieceType pt) const;
- Bitboard pieces(PieceType pt1, PieceType pt2) const;
+ Bitboard pieces(PieceType pt = ALL_PIECES) const;
+ template<typename ...PieceTypes> Bitboard pieces(PieceType pt, PieceTypes... pts) const;
Bitboard pieces(Color c) const;
- Bitboard pieces(Color c, PieceType pt) const;
- Bitboard pieces(Color c, PieceType pt1, PieceType pt2) 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;
void undo_null_move();
// Static Exchange Evaluation
- bool see_ge(Move m, Bitboard& occupied, Value threshold = VALUE_ZERO) const;
bool see_ge(Move m, Value threshold = VALUE_ZERO) const;
+ bool see_ge(Move m, Bitboard& occupied, Value threshold = VALUE_ZERO) const;
// Accessing hash keys
Key key() const;
return piece_on(from_sq(m));
}
-inline Bitboard Position::pieces(PieceType pt = ALL_PIECES) const {
+inline Bitboard Position::pieces(PieceType pt) const {
return byTypeBB[pt];
}
-inline Bitboard Position::pieces(PieceType pt1, PieceType pt2) const {
- return pieces(pt1) | pieces(pt2);
+template<typename ...PieceTypes>
+inline Bitboard Position::pieces(PieceType pt, PieceTypes... pts) const {
+ return pieces(pt) | pieces(pts...);
}
inline Bitboard Position::pieces(Color c) const {
return byColorBB[c];
}
-inline Bitboard Position::pieces(Color c, PieceType pt) const {
- return pieces(c) & pieces(pt);
-}
-
-inline Bitboard Position::pieces(Color c, PieceType pt1, PieceType pt2) const {
- return pieces(c) & (pieces(pt1) | pieces(pt2));
+template<typename ...PieceTypes>
+inline Bitboard Position::pieces(Color c, PieceTypes... pts) const {
+ return pieces(c) & pieces(pts...);
}
template<PieceType Pt> inline int Position::count(Color c) const {
// Futility margin
Value futility_margin(Depth d, bool improving) {
- return Value(154 * (d - improving));
+ return Value(140 * (d - improving));
}
- // Reductions lookup table, initialized at startup
+ // Reductions lookup table initialized at startup
int Reductions[MAX_MOVES]; // [depth or moveNumber]
Depth reduction(bool i, Depth d, int mn, Value delta, Value rootDelta) {
int r = Reductions[d] * Reductions[mn];
- return (r + 1449 - int(delta) * 937 / int(rootDelta)) / 1024 + (!i && r > 941);
+ return (r + 1372 - int(delta) * 1073 / int(rootDelta)) / 1024 + (!i && r > 936);
}
constexpr int futility_move_count(bool improving, Depth depth) {
// History and stats update bonus, based on depth
int stat_bonus(Depth d) {
- return std::min(341 * d - 470, 1710);
+ return std::min(336 * d - 547, 1561);
}
// Add a small random component to draw evaluations to avoid 3-fold blindness
// Skill structure is used to implement strength limit. If we have an uci_elo then
// we convert it 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)
+ // (goldfish 1.13 = 2000) and a fit through Ordo derived Elo for a match (TC 60+0.6)
// results spanning a wide range of k values.
struct Skill {
Skill(int skill_level, int uci_elo) {
void Search::init() {
for (int i = 1; i < MAX_MOVES; ++i)
- Reductions[i] = int((19.47 + std::log(Threads.size()) / 2) * std::log(i));
+ Reductions[i] = int((20.57 + std::log(Threads.size()) / 2) * std::log(i));
}
ss->pv = pv;
- bestValue = delta = alpha = -VALUE_INFINITE;
- beta = VALUE_INFINITE;
- optimism[WHITE] = optimism[BLACK] = VALUE_ZERO;
+ bestValue = -VALUE_INFINITE;
if (mainThread)
{
-
- int rootComplexity;
- Eval::evaluate(rootPos, &rootComplexity);
-
- mainThread->complexity = std::min(1.03 + (rootComplexity - 241) / 1552.0, 1.45);
-
if (mainThread->bestPreviousScore == VALUE_INFINITE)
for (int i = 0; i < 4; ++i)
mainThread->iterValue[i] = VALUE_ZERO;
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.
+ // use behind-the-scenes to retrieve a set of possible moves.
if (skill.enabled())
multiPV = std::max(multiPV, (size_t)4);
if (mainThread)
totBestMoveChanges /= 2;
- // Save the last iteration's scores before first PV line is searched and
+ // 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;
selDepth = 0;
// Reset aspiration window starting size
- if (rootDepth >= 4)
- {
- Value prev = rootMoves[pvIdx].averageScore;
- delta = Value(10) + int(prev) * prev / 16502;
- alpha = std::max(prev - delta,-VALUE_INFINITE);
- beta = std::min(prev + delta, VALUE_INFINITE);
-
- // Adjust optimism based on root move's previousScore
- int opt = 120 * prev / (std::abs(prev) + 161);
- optimism[ us] = Value(opt);
- optimism[~us] = -optimism[us];
- }
+ Value prev = rootMoves[pvIdx].averageScore;
+ delta = Value(10) + int(prev) * prev / 15799;
+ alpha = std::max(prev - delta,-VALUE_INFINITE);
+ beta = std::min(prev + delta, VALUE_INFINITE);
+
+ // Adjust optimism based on root move's previousScore
+ int opt = 109 * prev / (std::abs(prev) + 141);
+ optimism[ us] = Value(opt);
+ 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
int failedHighCnt = 0;
while (true)
{
- // Adjust the effective depth searched, but ensuring at least one effective increment for every
+ // 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 are set to -VALUE_INFINITE
+ // 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 case of MultiPV
+ // 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);
else
break;
- delta += delta / 4 + 2;
+ delta += delta / 3;
assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE);
}
if (!mainThread)
continue;
- // If skill level is enabled and time is up, pick a sub-optimal best move
+ // 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);
double reduction = (1.4 + mainThread->previousTimeReduction) / (2.08 * timeReduction);
double bestMoveInstability = 1 + 1.8 * totBestMoveChanges / Threads.size();
- double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability * mainThread->complexity;
+ double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability;
// Cap used time in case of a single legal move for a better viewer experience in tournaments
// yielding correct scores and sufficiently fast moves.
mainThread->previousTimeReduction = timeReduction;
- // If skill level is enabled, swap best PV line with the sub-optimal one
+ // 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)));
constexpr bool PvNode = nodeType != NonPV;
constexpr bool rootNode = nodeType == Root;
- // 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 ( !rootNode
&& pos.rule50_count() >= 3
bool givesCheck, improving, priorCapture, singularQuietLMR;
bool capture, moveCountPruning, ttCapture;
Piece movedPiece;
- int moveCount, captureCount, quietCount, improvement, complexity;
+ int moveCount, captureCount, quietCount, improvement;
// Step 1. Initialize node
Thread* thisThread = pos.this_thread();
// 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);
if (alpha >= beta)
// At non-PV nodes we check for an early TT cutoff
if ( !PvNode
- && ss->ttHit
&& !excludedMove
&& tte->depth() > depth - (tte->bound() == BOUND_EXACT)
- && ttValue != VALUE_NONE // Possible in case of TT access race
+ && 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 (~2 Elo)
ss->staticEval = eval = VALUE_NONE;
improving = false;
improvement = 0;
- complexity = 0;
goto moves_loop;
}
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;
- complexity = abs(ss->staticEval - pos.psq_eg_stm());
}
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, &complexity);
- else // Fall back to (semi)classical complexity for TT hits, the NNUE complexity is lost
- {
- complexity = abs(ss->staticEval - pos.psq_eg_stm());
- if (PvNode)
- Eval::NNUE::hint_common_parent_position(pos);
- }
+ ss->staticEval = eval = evaluate(pos);
+ else if (PvNode)
+ Eval::NNUE::hint_common_parent_position(pos);
// ttValue can be used as a better position evaluation (~7 Elo)
if ( ttValue != VALUE_NONE
}
else
{
- ss->staticEval = eval = evaluate(pos, &complexity);
- // Save static evaluation into transposition table
+ 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);
}
// 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(-19 * int((ss-1)->staticEval + ss->staticEval), -1920, 1920);
+ int bonus = std::clamp(-18 * int((ss-1)->staticEval + ss->staticEval), -1817, 1817);
thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << bonus;
}
// margin and the improving flag are used in various pruning heuristics.
improvement = (ss-2)->staticEval != VALUE_NONE ? ss->staticEval - (ss-2)->staticEval
: (ss-4)->staticEval != VALUE_NONE ? ss->staticEval - (ss-4)->staticEval
- : 156;
+ : 173;
improving = improvement > 0;
// Step 7. Razoring (~1 Elo).
// If eval is really low check with qsearch if it can exceed alpha, if it can't,
// return a fail low.
- if (eval < alpha - 426 - 256 * depth * depth)
+ if (eval < alpha - 456 - 252 * depth * depth)
{
value = qsearch<NonPV>(pos, ss, alpha - 1, alpha);
if (value < alpha)
// The depth condition is important for mate finding.
if ( !ss->ttPv
&& depth < 9
- && eval - futility_margin(depth, improving) - (ss-1)->statScore / 280 >= beta
+ && eval - futility_margin(depth, improving) - (ss-1)->statScore / 306 >= beta
&& eval >= beta
- && eval < 25128) // larger than VALUE_KNOWN_WIN, but smaller than TB wins
+ && eval < 24923) // larger than VALUE_KNOWN_WIN, but smaller than TB wins
return eval;
// Step 9. Null move search with verification search (~35 Elo)
if ( !PvNode
&& (ss-1)->currentMove != MOVE_NULL
- && (ss-1)->statScore < 18755
+ && (ss-1)->statScore < 17329
&& eval >= beta
&& eval >= ss->staticEval
- && ss->staticEval >= beta - 20 * depth - improvement / 13 + 253 + complexity / 25
+ && ss->staticEval >= beta - 21 * depth + 258
&& !excludedMove
&& pos.non_pawn_material(us)
- && (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor))
+ && ss->ply >= thisThread->nmpMinPly
+ && beta > VALUE_TB_LOSS_IN_MAX_PLY)
{
assert(eval - beta >= 0);
- // Null move dynamic reduction based on depth, eval and complexity of position
- Depth R = std::min(int(eval - beta) / 172, 6) + depth / 3 + 4 - (complexity > 825);
+ // Null move dynamic reduction based on depth and eval
+ Depth R = std::min(int(eval - beta) / 173, 6) + depth / 3 + 4;
ss->currentMove = MOVE_NULL;
ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0];
if (nullValue >= beta)
{
// Do not return unproven mate or TB scores
- if (nullValue >= VALUE_TB_WIN_IN_MAX_PLY)
- nullValue = beta;
+ nullValue = std::min(nullValue, VALUE_TB_WIN_IN_MAX_PLY-1);
- if (thisThread->nmpMinPly || (abs(beta) < VALUE_KNOWN_WIN && depth < 14))
+ if (thisThread->nmpMinPly || depth < 14)
return nullValue;
assert(!thisThread->nmpMinPly); // Recursive verification is not allowed
// Do verification search at high depths, with null move pruning disabled
- // for us, until ply exceeds nmpMinPly.
+ // until ply exceeds nmpMinPly.
thisThread->nmpMinPly = ss->ply + 3 * (depth-R) / 4;
- thisThread->nmpColor = us;
Value v = search<NonPV>(pos, ss, beta-1, beta, depth-R, false);
}
}
- probCutBeta = beta + 186 - 54 * improving;
+ // Step 10. If the position doesn't have a ttMove, decrease depth by 2
+ // (or by 4 if the TT entry for the current position was hit and the stored depth is greater than or equal to the current depth).
+ // Use qsearch if depth is equal or below zero (~9 Elo)
+ if ( PvNode
+ && !ttMove)
+ depth -= 2 + 2 * (ss->ttHit && tte->depth() >= depth);
+
+ if (depth <= 0)
+ return qsearch<PV>(pos, ss, alpha, beta);
+
+ if ( cutNode
+ && depth >= 8
+ && !ttMove)
+ depth -= 2;
- // Step 10. ProbCut (~10 Elo)
+ probCutBeta = beta + 168 - 61 * 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 > 4
+ && depth > 3
&& abs(beta) < VALUE_TB_WIN_IN_MAX_PLY
- // if value from transposition table is lower than probCutBeta, don't attempt probCut
+ // If value from transposition table is lower than probCutBeta, don't attempt probCut
// there and in further interactions with transposition table cutoff depth is set to depth - 3
// because probCut search has depth set to depth - 4 but we also do a move before it
- // so effective depth is equal to depth - 3
- && !( ss->ttHit
- && tte->depth() >= depth - 3
+ // So effective depth is equal to depth - 3
+ && !( tte->depth() >= depth - 3
&& ttValue != VALUE_NONE
&& ttValue < probCutBeta))
{
Eval::NNUE::hint_common_parent_position(pos);
}
- // Step 11. If the position is not in TT, decrease depth by 2 (or by 4 if the TT entry for the current position was hit and the stored depth is greater than or equal to the current depth).
- // Use qsearch if depth is equal or below zero (~9 Elo)
- if ( PvNode
- && !ttMove)
- depth -= 2 + 2 * (ss->ttHit && tte->depth() >= depth);
-
- if (depth <= 0)
- return qsearch<PV>(pos, ss, alpha, beta);
-
- if ( cutNode
- && depth >= 7
- && !ttMove)
- depth -= 2;
-
moves_loop: // When in check, search starts here
// Step 12. A small Probcut idea, when we are in check (~4 Elo)
- probCutBeta = beta + 391;
+ probCutBeta = beta + 413;
if ( ss->inCheck
&& !PvNode
- && depth >= 2
&& ttCapture
&& (tte->bound() & BOUND_LOWER)
- && tte->depth() >= depth - 3
+ && tte->depth() >= depth - 4
&& ttValue >= probCutBeta
&& abs(ttValue) <= VALUE_KNOWN_WIN
&& abs(beta) <= VALUE_KNOWN_WIN)
moveCountPruning = singularQuietLMR = false;
// Indicate PvNodes that will probably fail low if the node was searched
- // at a depth equal or greater than the current depth, and the result of this search was a fail low.
+ // 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)
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
+ // Move List. As a consequence, any illegal move is also skipped. 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))
moveCountPruning = moveCount >= futility_move_count(improving, depth);
// Reduced depth of the next LMR search
- int lmrDepth = std::max(newDepth - r, 0);
+ int lmrDepth = newDepth - r;
if ( capture
|| givesCheck)
{
// Futility pruning for captures (~2 Elo)
if ( !givesCheck
- && lmrDepth < 6
+ && lmrDepth < 7
&& !ss->inCheck
- && ss->staticEval + 182 + 230 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))]
+ && ss->staticEval + 197 + 248 * lmrDepth + PieceValue[EG][pos.piece_on(to_sq(move))]
+ captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] / 7 < alpha)
continue;
Bitboard occupied;
// SEE based pruning (~11 Elo)
- if (!pos.see_ge(move, occupied, Value(-206) * depth))
+ if (!pos.see_ge(move, occupied, Value(-205) * depth))
{
- if (depth < 2 - capture)
- continue;
- // don't prune move if a heavy enemy piece (KQR) is under attack after the exchanges
- Bitboard leftEnemies = (pos.pieces(~us, QUEEN, ROOK) | pos.pieces(~us, KING)) & occupied;
- Bitboard attacks = 0;
- occupied |= to_sq(move);
- while (leftEnemies && !attacks)
- {
+ if (depth < 2 - capture)
+ continue;
+ // Don't prune the move if opponent Queen/Rook is under discovered attack after the exchanges
+ // Don't prune the move if opponent King is under discovered attack after or during the exchanges
+ Bitboard leftEnemies = (pos.pieces(~us, KING, QUEEN, ROOK)) & occupied;
+ Bitboard attacks = 0;
+ occupied |= to_sq(move);
+ while (leftEnemies && !attacks)
+ {
Square sq = pop_lsb(leftEnemies);
attacks |= pos.attackers_to(sq, occupied) & pos.pieces(us) & occupied;
- // exclude Queen/Rook(s) which were already threatened before SEE
+ // Don't consider pieces that were already threatened/hanging before SEE exchanges
if (attacks && (sq != pos.square<KING>(~us) && (pos.attackers_to(sq, pos.pieces()) & pos.pieces(us))))
- attacks = 0;
- }
- if (!attacks)
- continue;
+ attacks = 0;
+ }
+ if (!attacks)
+ continue;
}
}
else
+ (*contHist[3])[movedPiece][to_sq(move)];
// Continuation history based pruning (~2 Elo)
- if ( lmrDepth < 5
- && history < -4405 * (depth - 1))
+ if ( lmrDepth < 6
+ && history < -3832 * depth)
continue;
history += 2 * thisThread->mainHistory[us][from_to(move)];
- lmrDepth += history / 7278;
+ lmrDepth += history / 7011;
lmrDepth = std::max(lmrDepth, -2);
// Futility pruning: parent node (~13 Elo)
if ( !ss->inCheck
- && lmrDepth < 13
- && ss->staticEval + 103 + 138 * lmrDepth <= alpha)
+ && lmrDepth < 12
+ && ss->staticEval + 112 + 138 * lmrDepth <= alpha)
continue;
lmrDepth = std::max(lmrDepth, 0);
// Prune moves with negative SEE (~4 Elo)
- if (!pos.see_ge(move, Value(-24 * lmrDepth * lmrDepth - 16 * lmrDepth)))
+ if (!pos.see_ge(move, Value(-27 * lmrDepth * lmrDepth - 16 * lmrDepth)))
continue;
}
}
// 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.
+ // 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 this type of time controls.
if ( !rootNode
- && depth >= 4 - (thisThread->completedDepth > 21) + 2 * (PvNode && tte->is_pv())
+ && depth >= 4 - (thisThread->completedDepth > 22) + 2 * (PvNode && tte->is_pv())
&& move == ttMove
&& !excludedMove // Avoid recursive singular search
/* && ttValue != VALUE_NONE Already implicit in the next condition */
&& (tte->bound() & BOUND_LOWER)
&& tte->depth() >= depth - 3)
{
- Value singularBeta = ttValue - (3 + 2 * (ss->ttPv && !PvNode)) * depth / 2;
+ Value singularBeta = ttValue - (82 + 65 * (ss->ttPv && !PvNode)) * depth / 64;
Depth singularDepth = (depth - 1) / 2;
ss->excludedMove = move;
// Avoid search explosion by limiting the number of double extensions
if ( !PvNode
- && value < singularBeta - 25
- && ss->doubleExtensions <= 10)
+ && value < singularBeta - 21
+ && ss->doubleExtensions <= 11)
{
extension = 2;
depth += depth < 13;
// 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.
+ // a softbound.
else if (singularBeta >= beta)
return singularBeta;
else if (ttValue >= beta)
extension = -2 - !PvNode;
+ // If we are on a cutNode, reduce it based on depth (negative extension) (~1 Elo)
+ else if (cutNode)
+ extension = depth > 8 && depth < 17 ? -3 : -1;
+
// If the eval of ttMove is less than value, we reduce it (negative extension) (~1 Elo)
else if (ttValue <= value)
extension = -1;
// Check extensions (~1 Elo)
else if ( givesCheck
- && depth > 10
- && abs(ss->staticEval) > 88)
+ && depth > 9)
extension = 1;
// Quiet ttMove extensions (~1 Elo)
else if ( PvNode
&& move == ttMove
&& move == ss->killers[0]
- && (*contHist[0])[movedPiece][to_sq(move)] >= 5705)
+ && (*contHist[0])[movedPiece][to_sq(move)] >= 5168)
extension = 1;
}
// Decrease reduction if position is or has been on the PV
// and node is not likely to fail low. (~3 Elo)
+ // Decrease further on cutNodes. (~1 Elo)
if ( ss->ttPv
&& !likelyFailLow)
- r -= 2;
+ r -= cutNode && tte->depth() >= depth + 3 ? 3 : 2;
// Decrease reduction if opponent's move count is high (~1 Elo)
- if ((ss-1)->moveCount > 7)
+ if ((ss-1)->moveCount > 8)
r--;
// Increase reduction for cut nodes (~3 Elo)
// Decrease reduction for PvNodes based on depth (~2 Elo)
if (PvNode)
- r -= 1 + 12 / (3 + depth);
+ r -= 1 + (depth < 6);
// Decrease reduction if ttMove has been singularly extended (~1 Elo)
if (singularQuietLMR)
if ((ss+1)->cutoffCnt > 3)
r++;
- // Decrease reduction if move is a killer and we have a good history (~1 Elo)
- if (move == ss->killers[0]
- && (*contHist[0])[movedPiece][to_sq(move)] >= 3722)
+ else if (move == ttMove)
r--;
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)]
- - 4082;
+ - 4006;
// Decrease/increase reduction for moves with a good/bad history (~25 Elo)
- r -= ss->statScore / (11079 + 4626 * (depth > 6 && depth < 19));
+ r -= ss->statScore / (11124 + 4740 * (depth > 5 && depth < 22));
// 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
+ // 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 + (PvNode && ss->ply <= 1)
value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, d, true);
- // Do full depth search when reduced LMR search fails high
+ // 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 result
+ // 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 > (alpha + 58 + 12 * (newDepth - d));
- const bool doEvenDeeperSearch = value > alpha + 588 && ss->doubleExtensions <= 5;
+ const bool doDeeperSearch = value > (bestValue + 64 + 11 * (newDepth - d));
+ const bool doEvenDeeperSearch = value > alpha + 711 && ss->doubleExtensions <= 6;
const bool doShallowerSearch = value < bestValue + newDepth;
ss->doubleExtensions = ss->doubleExtensions + doEvenDeeperSearch;
if (newDepth > d)
value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode);
- int bonus = value > alpha ? stat_bonus(newDepth)
- : -stat_bonus(newDepth);
+ int bonus = value <= alpha ? -stat_bonus(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. If expected reduction is high, reduce its depth by 1.
+ // Step 18. Full-depth search when LMR is skipped. If expected reduction is high, reduce its depth by 1.
else if (!PvNode || moveCount > 1)
{
// Increase reduction for cut nodes and not ttMove (~1 Elo)
if (!ttMove && cutNode)
r += 2;
- value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, newDepth - (r > 4), !cutNode);
+ 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
(ss+1)->pv[0] = MOVE_NONE;
value = -search<PV>(pos, ss+1, -beta, -alpha, newDepth, false);
-
- if (moveCount > 1 && newDepth >= depth && !capture)
- update_continuation_histories(ss, movedPiece, to_sq(move), -stat_bonus(newDepth));
}
// Step 19. Undo move
++thisThread->bestMoveChanges;
}
else
- // All other moves but the PV are set to the lowest value: this
+ // 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 (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
+ if (value >= beta)
{
-
- // Reduce other moves if we have found at least one score improvement (~1 Elo)
- if ( depth > 1
- && ((improving && complexity > 971) || (value < (5 * alpha + 75 * beta) / 87) || depth < 6)
- && beta < 12535
- && value > -12535) {
- bool extraReduction = depth > 2 && alpha > -12535 && bestValue != -VALUE_INFINITE && (value - bestValue) > (7 * (beta - alpha)) / 8;
- depth -= 1 + extraReduction;
- }
-
- assert(depth > 0);
- alpha = value;
+ ss->cutoffCnt += 1 + !ttMove;
+ assert(value >= beta); // Fail high
+ break;
}
else
{
- ss->cutoffCnt++;
- assert(value >= beta); // Fail high
- break;
+ // Reduce other moves if we have found at least one score improvement (~2 Elo)
+ if ( depth > 2
+ && depth < 12
+ && beta < 14362
+ && value > -12393)
+ 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 the move is worse than some previously searched move, remember it, to update its stats later
if (move != bestMove)
{
if (capture && captureCount < 32)
}
// 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
+ // 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)
ss->inCheck ? mated_in(ss->ply)
: VALUE_DRAW;
- // If there is a move which produces search value greater than alpha we update stats of searched moves
+ // 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);
// Bonus for prior countermove that caused the fail low
else if (!priorCapture && prevSq != SQ_NONE)
{
- int bonus = (depth > 5) + (PvNode || cutNode) + (bestValue < alpha - 97 * depth) + ((ss-1)->moveCount > 10);
+ int bonus = (depth > 5) + (PvNode || cutNode) + (bestValue < alpha - 113 * depth) + ((ss-1)->moveCount > 12);
update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth) * bonus);
}
// At non-PV nodes we check for an early TT cutoff
if ( !PvNode
- && ss->ttHit
&& tte->depth() >= ttDepth
- && ttValue != VALUE_NONE // Only in case of TT access race
+ && ttValue != VALUE_NONE // Only in case of TT access race or if !ttHit
&& (tte->bound() & (ttValue >= beta ? BOUND_LOWER : BOUND_UPPER)))
return ttValue;
// Step 4. Static evaluation of the position
if (ss->inCheck)
- {
- ss->staticEval = VALUE_NONE;
bestValue = futilityBase = -VALUE_INFINITE;
- }
else
{
if (ss->ttHit)
}
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;
+ ss->staticEval = bestValue = (ss-1)->currentMove != MOVE_NULL ? evaluate(pos)
+ : -(ss-1)->staticEval;
// Stand pat. Return immediately if static value is at least beta
if (bestValue >= beta)
if (PvNode && bestValue > alpha)
alpha = bestValue;
- futilityBase = bestValue + 168;
+ futilityBase = bestValue + 200;
}
const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory,
// to search the moves. Because the depth is <= 0 here, only captures,
// queen promotions, and other checks (only if depth >= DEPTH_QS_CHECKS)
// will be generated.
- Square prevSq = (ss-1)->currentMove != MOVE_NULL ? to_sq((ss-1)->currentMove) : SQ_NONE;
+ Square prevSq = is_ok((ss-1)->currentMove) ? to_sq((ss-1)->currentMove) : SQ_NONE;
MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory,
&thisThread->captureHistory,
contHist,
// or a beta cutoff occurs.
while ((move = mp.next_move()) != MOVE_NONE)
{
- assert(is_ok(move));
-
- // Check for legality
- if (!pos.legal(move))
- continue;
-
- givesCheck = pos.gives_check(move);
- capture = pos.capture_stage(move);
+ assert(is_ok(move));
- moveCount++;
+ // Check for legality
+ if (!pos.legal(move))
+ continue;
- // Step 6. Pruning.
- if (bestValue > VALUE_TB_LOSS_IN_MAX_PLY)
- {
- // Futility pruning and moveCount pruning (~10 Elo)
- if ( !givesCheck
- && to_sq(move) != prevSq
- && futilityBase > -VALUE_KNOWN_WIN
- && type_of(move) != PROMOTION)
- {
- if (moveCount > 2)
- continue;
+ givesCheck = pos.gives_check(move);
+ capture = pos.capture_stage(move);
- futilityValue = futilityBase + PieceValue[EG][pos.piece_on(to_sq(move))];
+ moveCount++;
- if (futilityValue <= alpha)
- {
- bestValue = std::max(bestValue, futilityValue);
- continue;
- }
+ // Step 6. Pruning.
+ if (bestValue > VALUE_TB_LOSS_IN_MAX_PLY)
+ {
+ // Futility pruning and moveCount pruning (~10 Elo)
+ if ( !givesCheck
+ && to_sq(move) != prevSq
+ && futilityBase > -VALUE_KNOWN_WIN
+ && type_of(move) != PROMOTION)
+ {
+ if (moveCount > 2)
+ continue;
- if (futilityBase <= alpha && !pos.see_ge(move, VALUE_ZERO + 1))
- {
- bestValue = std::max(bestValue, futilityBase);
- continue;
- }
- }
+ futilityValue = futilityBase + PieceValue[EG][pos.piece_on(to_sq(move))];
- // We prune after 2nd quiet check evasion where being 'in check' is implicitly checked through the counter
- // and being a 'quiet' apart from being a tt move is assumed after an increment because captures are pushed ahead.
- if (quietCheckEvasions > 1)
- break;
+ if (futilityValue <= alpha)
+ {
+ bestValue = std::max(bestValue, futilityValue);
+ continue;
+ }
- // 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;
+ if (futilityBase <= alpha && !pos.see_ge(move, VALUE_ZERO + 1))
+ {
+ bestValue = std::max(bestValue, futilityBase);
+ continue;
+ }
+ }
- // Do not search moves with bad enough SEE values (~5 Elo)
- if (!pos.see_ge(move, Value(-110)))
- 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(-95)))
+ continue;
+ }
- // Speculative prefetch as early as possible
- prefetch(TT.first_entry(pos.key_after(move)));
+ // 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)];
+ // 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;
+ 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);
+ // 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);
+ assert(value > -VALUE_INFINITE && value < VALUE_INFINITE);
- // Step 8. Check for a new best move
- if (value > bestValue)
- {
- bestValue = value;
+ // Step 8. Check for a new best move
+ if (value > bestValue)
+ {
+ bestValue = value;
- if (value > alpha)
- {
- bestMove = move;
+ if (value > alpha)
+ {
+ bestMove = move;
- if (PvNode) // Update pv even in fail-high case
- update_pv(ss->pv, move, (ss+1)->pv);
+ 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
- }
- }
+ if (PvNode && value < beta) // Update alpha here!
+ alpha = value;
+ else
+ break; // Fail high
+ }
+ }
}
// Step 9. Check for mate
if (!pos.capture_stage(bestMove))
{
- int bonus2 = bestValue > beta + 153 ? bonus1 // larger bonus
+ int bonus2 = bestValue > beta + 145 ? bonus1 // larger bonus
: stat_bonus(depth); // smaller bonus
// Increase stats for the best move in case it was a quiet move
for (int i : {1, 2, 4, 6})
{
- // Only update first 2 continuation histories if we are in check
+ // Only update the first 2 continuation histories if we are in check
if (ss->inCheck && i > 2)
break;
if (is_ok((ss-i)->currentMove))
}
}
- // When playing with strength handicap, choose best move among a set of RootMoves
+ // 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) {
/// 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.
+/// otherwise in case of 'ponder on' we have nothing to think about.
bool RootMove::extract_ponder_from_tt(Position& pos) {
#include <thread>
#include <vector>
-#include "material.h"
#include "movepick.h"
-#include "pawns.h"
#include "position.h"
#include "search.h"
#include "thread_win32_osx.h"
void wait_for_search_finished();
size_t id() const { return idx; }
- Pawns::Table pawnsTable;
- Material::Table materialTable;
size_t pvIdx, pvLast;
std::atomic<uint64_t> nodes, tbHits, bestMoveChanges;
int selDepth, nmpMinPly;
- Color nmpColor;
Value bestValue, optimism[COLOR_NB];
Position rootPos;
void search() override;
void check_time();
- double complexity;
double previousTimeReduction;
Value bestPreviousScore;
Value bestPreviousAverageScore;
void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
+ // 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 slowMover = TimePoint(Options["Slow Mover"]);
TimePoint npmsec = TimePoint(Options["nodestime"]);
limits.npmsec = npmsec;
}
- startTime = limits.startTime;
-
// Maximum move horizon of 50 moves
int mtg = limits.movestogo ? std::min(limits.movestogo, 50) : 50;
std::vector<std::thread> threads;
- for (size_t idx = 0; idx < Options["Threads"]; ++idx)
+ for (size_t idx = 0; idx < size_t(Options["Threads"]); ++idx)
{
threads.emplace_back([this, idx]() {
// Each thread will zero its part of the hash table
const size_t stride = size_t(clusterCount / Options["Threads"]),
start = size_t(stride * idx),
- len = idx != Options["Threads"] - 1 ?
+ len = idx != size_t(Options["Threads"]) - 1 ?
stride : clusterCount - start;
std::memset(&table[start], 0, len * sizeof(Cluster));
// 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.33677609, -4.30175627, 33.08810557, 365.60223431};
- constexpr double bs[] = { -2.50471102, 14.23235405, -14.33066859, 71.42705250 };
+ 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]));
// 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 selfplay at fishtest LTC time control.
-const int NormalizeToPawnValue = 394;
+const int NormalizeToPawnValue = 328;
class Option;
Option& operator=(const std::string&);
void operator<<(const Option&);
- operator double() const;
+ operator int() const;
operator std::string() const;
bool operator==(const char*) const;
o["SyzygyProbeDepth"] << Option(1, 1, 100);
o["Syzygy50MoveRule"] << Option(true);
o["SyzygyProbeLimit"] << Option(7, 0, 7);
- o["Use NNUE"] << Option(true, on_use_NNUE);
o["EvalFile"] << Option(EvalFileDefaultName, on_eval_file);
o["RPCServerAddress"] << Option("<empty>", on_rpc_server_address);
}
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 {
+Option::operator int() const {
assert(type == "check" || type == "spin");
- return (type == "spin" ? stof(currentValue) : currentValue == "true");
+ return (type == "spin" ? std::stoi(currentValue) : currentValue == "true");
}
Option::operator std::string() const {
"go depth 10" \
"go movetime 1000" \
"go wtime 8000 btime 8000 winc 500 binc 500" \
- "bench 128 $threads 8 default depth"
+ "bench 128 $threads 8 default depth" \
+ "export_net verify.nnue"
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 240