mirror of
https://github.com/opelly27/Stockfish.git
synced 2026-05-20 13:17:44 +00:00
Compare commits
168 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 84f3e86790 | |||
| 9587eeeb5e | |||
| 33f3cfae00 | |||
| 62d3106caa | |||
| f0abde241d | |||
| d89730d5c8 | |||
| c3092c54bc | |||
| 6c197c3964 | |||
| 1f3bd968bb | |||
| 5e91c5dcc8 | |||
| 4006f2c913 | |||
| bf5ce1c214 | |||
| 804a29c738 | |||
| 76a039027d | |||
| 7225d254f9 | |||
| c5b2a92cd1 | |||
| 67818ee948 | |||
| fb83da0892 | |||
| 268c00b648 | |||
| 110068808b | |||
| 69d3be42a1 | |||
| c7194bd924 | |||
| 16836f39b2 | |||
| 2810a1ea85 | |||
| 547c4a216a | |||
| de24fcebc8 | |||
| ca41ee6632 | |||
| aecfca2dc2 | |||
| a84e3ac287 | |||
| ab5cd8340f | |||
| 11483fe6d9 | |||
| 208c53df0f | |||
| 527d832a6d | |||
| bbe9857684 | |||
| e9966d9a8e | |||
| 8a3f155b1c | |||
| 6f15e7fab2 | |||
| bc3c215490 | |||
| 4c72c95359 | |||
| 1ea488d34c | |||
| 995ee4b311 | |||
| 42b7dbcb5e | |||
| 4d657618e9 | |||
| c44c62efc2 | |||
| 3af083a7cd | |||
| 4b10578acb | |||
| b081e52239 | |||
| d0cb9b286f | |||
| 1c65310c0e | |||
| b0eb5a1ba3 | |||
| 902309020a | |||
| 15e190e942 | |||
| fd8e88427b | |||
| 784263596f | |||
| 16566a8fcf | |||
| d1ec10cd4f | |||
| 8c3d9d996a | |||
| 616eb60008 | |||
| a5e3b4edde | |||
| fb8095718b | |||
| d40d04c17c | |||
| 7f2c8a2b81 | |||
| 81c58855e4 | |||
| 86575bcdd8 | |||
| 383b12e1a5 | |||
| d940e59dad | |||
| 669b5d83ef | |||
| cdf5cfdb92 | |||
| 09c6917d05 | |||
| 6c1af710d1 | |||
| 20ceeac8b3 | |||
| b36a1fa1b4 | |||
| dd1adce748 | |||
| 83c9e5911e | |||
| d116e27f0f | |||
| c6ce612f0a | |||
| beb327f910 | |||
| cca643669d | |||
| d4763424d2 | |||
| 86ee4eb84d | |||
| 66ed8b6c47 | |||
| 8a1de2655c | |||
| fcaf0736fe | |||
| a91cb9fc1b | |||
| c527c3ad44 | |||
| eb4a124b88 | |||
| 7f8166db89 | |||
| 353e20674b | |||
| 4776dc0e12 | |||
| bb5589b829 | |||
| 221893bf67 | |||
| bde1506ba5 | |||
| 345b2d153a | |||
| ecac132bca | |||
| 6f35af7ad3 | |||
| d87adcc006 | |||
| 0e51ff1074 | |||
| ca4e399ea6 | |||
| 5c58f67126 | |||
| de9fc53af5 | |||
| db59696aaf | |||
| d7a2d5a445 | |||
| 2c5f0efa13 | |||
| 6596f0eac0 | |||
| f83cb95740 | |||
| 195a4fec6d | |||
| 85bcf4741e | |||
| fbc7a328c6 | |||
| 3cb1c6c3c6 | |||
| c14f4877cf | |||
| 375e4eeaf5 | |||
| 84f3bf594d | |||
| 0b8ce4b303 | |||
| 209e94203f | |||
| b7ecdaada7 | |||
| f2430bf034 | |||
| 8c73339a36 | |||
| 58746d9fb8 | |||
| c8e8e48b14 | |||
| 8b229381da | |||
| 6ecab03dee | |||
| ff27109313 | |||
| 07caca2587 | |||
| ddcbacd04d | |||
| ec2002c594 | |||
| 442e1e0f93 | |||
| c077bfb413 | |||
| 47be966d30 | |||
| 37e3863927 | |||
| 9690cd6295 | |||
| e7c1c8c1ab | |||
| 0424273d0b | |||
| 5a7b45eac9 | |||
| 960d59d541 | |||
| c6839a2615 | |||
| f27339d35b | |||
| 09f53dbfa5 | |||
| 2e1369d030 | |||
| 8352977b91 | |||
| b8c00efa27 | |||
| ab930f8d3f | |||
| 10ead8a724 | |||
| be5a2f015e | |||
| 4e8986483a | |||
| 0aee1ec4ab | |||
| 0c878adb36 | |||
| ddd4224640 | |||
| 0f37da0e34 | |||
| c390b734c4 | |||
| 6ccb1cac5a | |||
| 3b70932b0d | |||
| a910ba71ee | |||
| 71e0b5385e | |||
| d878bc8cda | |||
| 1d3efff472 | |||
| 39437f4e55 | |||
| 6d0eabd5fe | |||
| 0ae00454ba | |||
| 01b6088af3 | |||
| 18fc21eba0 | |||
| 56e698ef83 | |||
| f63d112c71 | |||
| 7ed817d7e4 | |||
| 6f1013794c | |||
| f3c83ed46c | |||
| 75dfdeac11 | |||
| 7a7bcd6359 | |||
| bcf9282844 |
+20
-13
@@ -1,5 +1,5 @@
|
|||||||
language: cpp
|
language: cpp
|
||||||
dist: xenial
|
dist: bionic
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
@@ -7,7 +7,6 @@ matrix:
|
|||||||
compiler: gcc
|
compiler: gcc
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
sources: ['ubuntu-toolchain-r-test']
|
|
||||||
packages: ['g++-8', 'g++-8-multilib', 'g++-multilib', 'valgrind', 'expect', 'curl']
|
packages: ['g++-8', 'g++-8-multilib', 'g++-multilib', 'valgrind', 'expect', 'curl']
|
||||||
env:
|
env:
|
||||||
- COMPILER=g++-8
|
- COMPILER=g++-8
|
||||||
@@ -17,23 +16,23 @@ matrix:
|
|||||||
compiler: clang
|
compiler: clang
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-xenial-6.0']
|
packages: ['clang-10', 'llvm-10-dev', 'g++-multilib', 'valgrind', 'expect', 'curl']
|
||||||
packages: ['clang-6.0', 'llvm-6.0-dev', 'g++-multilib', 'valgrind', 'expect', 'curl']
|
|
||||||
env:
|
env:
|
||||||
- COMPILER=clang++-6.0
|
- COMPILER=clang++-10
|
||||||
- COMP=clang
|
- COMP=clang
|
||||||
- LDFLAGS=-fuse-ld=lld
|
|
||||||
|
|
||||||
- os: osx
|
- os: osx
|
||||||
|
osx_image: xcode12
|
||||||
compiler: gcc
|
compiler: gcc
|
||||||
env:
|
env:
|
||||||
- COMPILER=g++
|
- COMPILER=g++
|
||||||
- COMP=gcc
|
- COMP=gcc
|
||||||
|
|
||||||
- os: osx
|
- os: osx
|
||||||
|
osx_image: xcode12
|
||||||
compiler: clang
|
compiler: clang
|
||||||
env:
|
env:
|
||||||
- COMPILER=clang++ V='Apple LLVM 9.4.1' # Apple LLVM version 9.1.0 (clang-902.0.39.2)
|
- COMPILER=clang++
|
||||||
- COMP=clang
|
- COMP=clang
|
||||||
|
|
||||||
branches:
|
branches:
|
||||||
@@ -48,26 +47,34 @@ script:
|
|||||||
- git log HEAD | grep "\b[Bb]ench[ :]\+[0-9]\{7\}" | head -n 1 | sed "s/[^0-9]*\([0-9]*\).*/\1/g" > git_sig
|
- git log HEAD | grep "\b[Bb]ench[ :]\+[0-9]\{7\}" | head -n 1 | sed "s/[^0-9]*\([0-9]*\).*/\1/g" > git_sig
|
||||||
- export benchref=$(cat git_sig)
|
- export benchref=$(cat git_sig)
|
||||||
- echo "Reference bench:" $benchref
|
- echo "Reference bench:" $benchref
|
||||||
|
|
||||||
|
#
|
||||||
|
# Compiler version string
|
||||||
|
- $COMPILER -v
|
||||||
|
|
||||||
#
|
#
|
||||||
# Verify bench number against various builds
|
# Verify bench number against various builds
|
||||||
- export CXXFLAGS=-Werror
|
- export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG"
|
||||||
- make clean && make -j2 ARCH=x86-64 optimize=no debug=yes build && ../tests/signature.sh $benchref
|
- make clean && make -j2 ARCH=x86-64 optimize=no debug=yes build && ../tests/signature.sh $benchref
|
||||||
- make clean && make -j2 ARCH=x86-32 optimize=no debug=yes build && ../tests/signature.sh $benchref
|
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then make clean && make -j2 ARCH=x86-32 optimize=no debug=yes build && ../tests/signature.sh $benchref; fi
|
||||||
- make clean && make -j2 ARCH=x86-32 build && ../tests/signature.sh $benchref
|
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then make clean && make -j2 ARCH=x86-32 build && ../tests/signature.sh $benchref; fi
|
||||||
|
|
||||||
#
|
#
|
||||||
# Check perft and reproducible search
|
# Check perft and reproducible search
|
||||||
|
- export CXXFLAGS="-Werror"
|
||||||
|
- make clean && make -j2 ARCH=x86-64 build
|
||||||
- ../tests/perft.sh
|
- ../tests/perft.sh
|
||||||
- ../tests/reprosearch.sh
|
- ../tests/reprosearch.sh
|
||||||
|
|
||||||
#
|
#
|
||||||
# Valgrind
|
# Valgrind
|
||||||
#
|
#
|
||||||
- export CXXFLAGS="-O1 -fno-inline"
|
- export CXXFLAGS="-O1 -fno-inline"
|
||||||
- if [ -x "$(command -v valgrind )" ]; then make clean && make -j2 ARCH=x86-64 debug=yes optimize=no build > /dev/null && ../tests/instrumented.sh --valgrind; fi
|
- if [ -x "$(command -v valgrind )" ]; then make clean && make -j2 ARCH=x86-64 debug=yes optimize=no build > /dev/null && ../tests/instrumented.sh --valgrind; fi
|
||||||
- if [ -x "$(command -v valgrind )" ]; then ../tests/instrumented.sh --valgrind-thread; fi
|
- if [ -x "$(command -v valgrind )" ]; then ../tests/instrumented.sh --valgrind-thread; fi
|
||||||
|
|
||||||
#
|
#
|
||||||
# Sanitizer
|
# Sanitizer
|
||||||
#
|
#
|
||||||
# Use g++-8 as a proxy for having sanitizers, might need revision as they become available for more recent versions of clang/gcc
|
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then make clean && make -j2 ARCH=x86-64 sanitize=undefined optimize=no debug=yes build > /dev/null && ../tests/instrumented.sh --sanitizer-undefined; fi
|
||||||
- if [[ "$COMPILER" == "g++-8" ]]; then make clean && make -j2 ARCH=x86-64 sanitize=undefined optimize=no debug=yes build > /dev/null && ../tests/instrumented.sh --sanitizer-undefined; fi
|
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then make clean && make -j2 ARCH=x86-64 sanitize=thread optimize=no debug=yes build > /dev/null && ../tests/instrumented.sh --sanitizer-thread; fi
|
||||||
- if [[ "$COMPILER" == "g++-8" ]]; then make clean && make -j2 ARCH=x86-64 sanitize=thread optimize=no debug=yes build > /dev/null && ../tests/instrumented.sh --sanitizer-thread; fi
|
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
# List of authors for Stockfish, as of January 7, 2020
|
# List of authors for Stockfish, as of August 4, 2020
|
||||||
|
|
||||||
|
# Founders of the Stockfish project and fishtest infrastructure
|
||||||
Tord Romstad (romstad)
|
Tord Romstad (romstad)
|
||||||
Marco Costalba (mcostalba)
|
Marco Costalba (mcostalba)
|
||||||
Joona Kiiski (zamar)
|
Joona Kiiski (zamar)
|
||||||
Gary Linscott (glinscott)
|
Gary Linscott (glinscott)
|
||||||
|
|
||||||
|
# Authors and inventors of NNUE, training, NNUE port
|
||||||
|
Yu Nasu (ynasu87)
|
||||||
|
Motohiro Isozaki (yaneurao)
|
||||||
|
Hisayori Noda (nodchip)
|
||||||
|
|
||||||
|
# all other authors of the code in alphabetical order
|
||||||
Aditya (absimaldata)
|
Aditya (absimaldata)
|
||||||
Adrian Petrescu (apetresc)
|
Adrian Petrescu (apetresc)
|
||||||
Ajith Chandy Jose (ajithcj)
|
Ajith Chandy Jose (ajithcj)
|
||||||
@@ -36,18 +43,21 @@ Dariusz Orzechowski
|
|||||||
David Zar
|
David Zar
|
||||||
Daylen Yang (daylen)
|
Daylen Yang (daylen)
|
||||||
DiscanX
|
DiscanX
|
||||||
|
Dominik Schlösser (domschl)
|
||||||
double-beep
|
double-beep
|
||||||
Eduardo Cáceres (eduherminio)
|
Eduardo Cáceres (eduherminio)
|
||||||
Eelco de Groot (KingDefender)
|
Eelco de Groot (KingDefender)
|
||||||
Elvin Liu (solarlight2)
|
Elvin Liu (solarlight2)
|
||||||
erbsenzaehler
|
erbsenzaehler
|
||||||
Ernesto Gatti
|
Ernesto Gatti
|
||||||
|
Linmiao Xu (linrock)
|
||||||
Fabian Beuke (madnight)
|
Fabian Beuke (madnight)
|
||||||
Fabian Fichter (ianfab)
|
Fabian Fichter (ianfab)
|
||||||
fanon
|
fanon
|
||||||
Fauzi Akram Dabat (FauziAkram)
|
Fauzi Akram Dabat (FauziAkram)
|
||||||
Felix Wittmann
|
Felix Wittmann
|
||||||
gamander
|
gamander
|
||||||
|
Gary Heckman (gheckman)
|
||||||
gguliash
|
gguliash
|
||||||
Gian-Carlo Pascutto (gcp)
|
Gian-Carlo Pascutto (gcp)
|
||||||
Gontran Lemaire (gonlem)
|
Gontran Lemaire (gonlem)
|
||||||
@@ -113,6 +123,8 @@ Nick Pelling (nickpelling)
|
|||||||
Nicklas Persson (NicklasPersson)
|
Nicklas Persson (NicklasPersson)
|
||||||
Niklas Fiekas (niklasf)
|
Niklas Fiekas (niklasf)
|
||||||
Nikolay Kostov (NikolayIT)
|
Nikolay Kostov (NikolayIT)
|
||||||
|
Nguyen Pham (nguyenpham)
|
||||||
|
Norman Schmidt (FireFather)
|
||||||
Ondrej Mosnáček (WOnder93)
|
Ondrej Mosnáček (WOnder93)
|
||||||
Oskar Werkelin Ahlin
|
Oskar Werkelin Ahlin
|
||||||
Pablo Vazquez
|
Pablo Vazquez
|
||||||
@@ -122,6 +134,8 @@ Pasquale Pigazzini (ppigazzini)
|
|||||||
Patrick Jansen (mibere)
|
Patrick Jansen (mibere)
|
||||||
pellanda
|
pellanda
|
||||||
Peter Zsifkovits (CoffeeOne)
|
Peter Zsifkovits (CoffeeOne)
|
||||||
|
Praveen Kumar Tummala (praveentml)
|
||||||
|
Rahul Dsilva (silversolver1)
|
||||||
Ralph Stößer (Ralph Stoesser)
|
Ralph Stößer (Ralph Stoesser)
|
||||||
Raminder Singh
|
Raminder Singh
|
||||||
renouve
|
renouve
|
||||||
@@ -130,6 +144,7 @@ Richard Lloyd
|
|||||||
Rodrigo Exterckötter Tjäder
|
Rodrigo Exterckötter Tjäder
|
||||||
Ron Britvich (Britvich)
|
Ron Britvich (Britvich)
|
||||||
Ronald de Man (syzygy1, syzygy)
|
Ronald de Man (syzygy1, syzygy)
|
||||||
|
rqs
|
||||||
Ryan Schmitt
|
Ryan Schmitt
|
||||||
Ryan Takker
|
Ryan Takker
|
||||||
Sami Kiminki (skiminki)
|
Sami Kiminki (skiminki)
|
||||||
@@ -138,6 +153,7 @@ Sergei Antonov (saproj)
|
|||||||
Sergei Ivanov (svivanov72)
|
Sergei Ivanov (svivanov72)
|
||||||
sf-x
|
sf-x
|
||||||
Shane Booth (shane31)
|
Shane Booth (shane31)
|
||||||
|
Shawn Varghese (xXH4CKST3RXx)
|
||||||
Stefan Geschwentner (locutus2)
|
Stefan Geschwentner (locutus2)
|
||||||
Stefano Cardanobile (Stefano80)
|
Stefano Cardanobile (Stefano80)
|
||||||
Steinar Gunderson (sesse)
|
Steinar Gunderson (sesse)
|
||||||
@@ -147,17 +163,17 @@ thaspel
|
|||||||
theo77186
|
theo77186
|
||||||
Tom Truscott
|
Tom Truscott
|
||||||
Tom Vijlbrief (tomtor)
|
Tom Vijlbrief (tomtor)
|
||||||
|
Tomasz Sobczyk (Sopel97)
|
||||||
Torsten Franz (torfranz, tfranzer)
|
Torsten Franz (torfranz, tfranzer)
|
||||||
Tracey Emery (basepr1me)
|
Tracey Emery (basepr1me)
|
||||||
|
tttak
|
||||||
|
Unai Corzo (unaiic)
|
||||||
Uri Blass (uriblass)
|
Uri Blass (uriblass)
|
||||||
Vince Negri (cuddlestmonkey)
|
Vince Negri (cuddlestmonkey)
|
||||||
|
zz4032
|
||||||
|
|
||||||
|
|
||||||
# Additionally, we acknowledge the authors and maintainers of fishtest,
|
# 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 the development of Stockfish!
|
||||||
#
|
#
|
||||||
# https://github.com/glinscott/fishtest/blob/master/AUTHORS
|
# https://github.com/glinscott/fishtest/blob/master/AUTHORS
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+128
-49
@@ -4,7 +4,13 @@
|
|||||||
[](https://ci.appveyor.com/project/mcostalba/stockfish/branch/master)
|
[](https://ci.appveyor.com/project/mcostalba/stockfish/branch/master)
|
||||||
|
|
||||||
[Stockfish](https://stockfishchess.org) is a free, powerful UCI chess engine
|
[Stockfish](https://stockfishchess.org) is a free, powerful UCI chess engine
|
||||||
derived from Glaurung 2.1. It is not a complete chess program and requires a
|
derived from Glaurung 2.1. It features two evaluation functions, the classical
|
||||||
|
evaluation based on handcrafted terms, and the NNUE evaluation based on
|
||||||
|
efficiently updateable neural networks. The classical evaluation runs efficiently
|
||||||
|
on most 64bit CPU architectures, while the NNUE evaluation benefits strongly from the
|
||||||
|
vector intrinsics available on modern CPUs (avx2 or similar).
|
||||||
|
|
||||||
|
Stockfish is not a complete chess program and requires a
|
||||||
UCI-compatible GUI (e.g. XBoard with PolyGlot, Scid, Cute Chess, eboard, Arena,
|
UCI-compatible GUI (e.g. XBoard with PolyGlot, Scid, Cute Chess, eboard, Arena,
|
||||||
Sigma Chess, Shredder, Chess Partner or Fritz) in order to be used comfortably.
|
Sigma Chess, Shredder, Chess Partner or Fritz) in order to be used comfortably.
|
||||||
Read the documentation for your GUI of choice for information about how to use
|
Read the documentation for your GUI of choice for information about how to use
|
||||||
@@ -22,30 +28,26 @@ This distribution of Stockfish consists of the following files:
|
|||||||
* src, a subdirectory containing the full source code, including a Makefile
|
* src, a subdirectory containing the full source code, including a Makefile
|
||||||
that can be used to compile Stockfish on Unix-like systems.
|
that can be used to compile Stockfish on Unix-like systems.
|
||||||
|
|
||||||
|
To use the NNUE evaluation an additional data file with neural network parameters
|
||||||
|
needs to be downloaded. The filename for the default set can be found as the default
|
||||||
|
value of the `EvalFile` UCI option, with the format
|
||||||
|
`nn-[SHA256 first 12 digits].nnue` (e.g. nn-c157e0a5755b.nnue). This file can be downloaded from
|
||||||
|
```
|
||||||
|
https://tests.stockfishchess.org/api/nn/[filename]
|
||||||
|
```
|
||||||
|
replacing `[filename]` as needed.
|
||||||
|
|
||||||
## UCI parameters
|
|
||||||
|
## UCI options
|
||||||
|
|
||||||
Currently, Stockfish has the following UCI options:
|
Currently, Stockfish has the following UCI options:
|
||||||
|
|
||||||
* #### Debug Log File
|
|
||||||
Write all communication to and from the engine into a text file.
|
|
||||||
|
|
||||||
* #### Contempt
|
|
||||||
A positive value for contempt favors middle game positions and avoids draws.
|
|
||||||
|
|
||||||
* #### Analysis Contempt
|
|
||||||
By default, contempt is set to prefer the side to move. Set this option to "White"
|
|
||||||
or "Black" to analyse with contempt for that side, or "Off" to disable contempt.
|
|
||||||
|
|
||||||
* #### Threads
|
* #### Threads
|
||||||
The number of CPU threads used for searching a position. For best performance, set
|
The number of CPU threads used for searching a position. For best performance, set
|
||||||
this equal to the number of CPU cores available.
|
this equal to the number of CPU cores available.
|
||||||
|
|
||||||
* #### Hash
|
* #### Hash
|
||||||
The size of the hash table in MB.
|
The size of the hash table in MB. It is recommended to set Hash after setting Threads.
|
||||||
|
|
||||||
* #### Clear Hash
|
|
||||||
Clear the hash table.
|
|
||||||
|
|
||||||
* #### Ponder
|
* #### Ponder
|
||||||
Let Stockfish ponder its next move while the opponent is thinking.
|
Let Stockfish ponder its next move while the opponent is thinking.
|
||||||
@@ -54,10 +56,32 @@ Currently, Stockfish has the following UCI options:
|
|||||||
Output the N best lines (principal variations, PVs) when searching.
|
Output the N best lines (principal variations, PVs) when searching.
|
||||||
Leave at 1 for best performance.
|
Leave at 1 for best performance.
|
||||||
|
|
||||||
* #### Skill Level
|
* #### Use NNUE
|
||||||
Lower the Skill Level in order to make Stockfish play weaker (see also UCI_LimitStrength).
|
Toggle between the NNUE and classical evaluation functions. If set to "true",
|
||||||
Internally, MultiPV is enabled, and with a certain probability depending on the Skill Level a
|
the network parameters must be availabe to load from file (see also EvalFile).
|
||||||
weaker move will be played.
|
|
||||||
|
* #### EvalFile
|
||||||
|
The name of the file of the NNUE evaluation parameters. Depending on the GUI the
|
||||||
|
filename should include the full path to the folder/directory that contains the file.
|
||||||
|
|
||||||
|
* #### Contempt
|
||||||
|
A positive value for contempt favors middle game positions and avoids draws,
|
||||||
|
effective for the classical evaluation only.
|
||||||
|
|
||||||
|
* #### Analysis Contempt
|
||||||
|
By default, contempt is set to prefer the side to move. Set this option to "White"
|
||||||
|
or "Black" to analyse with contempt for that side, or "Off" to disable contempt.
|
||||||
|
|
||||||
|
* #### UCI_AnalyseMode
|
||||||
|
An option handled by your GUI.
|
||||||
|
|
||||||
|
* #### UCI_Chess960
|
||||||
|
An option handled by your GUI. If true, Stockfish will play Chess960.
|
||||||
|
|
||||||
|
* #### UCI_ShowWDL
|
||||||
|
If enabled, show approximate WDL statistics as part of the engine output.
|
||||||
|
These WDL numbers model expected game outcomes for a given evaluation and
|
||||||
|
game ply for engine self-play at fishtest LTC conditions (60+0.6s per game).
|
||||||
|
|
||||||
* #### UCI_LimitStrength
|
* #### UCI_LimitStrength
|
||||||
Enable weaker play aiming for an Elo rating as set by UCI_Elo. This option overrides Skill Level.
|
Enable weaker play aiming for an Elo rating as set by UCI_Elo. This option overrides Skill Level.
|
||||||
@@ -66,26 +90,10 @@ Currently, Stockfish has the following UCI options:
|
|||||||
If enabled by UCI_LimitStrength, aim for an engine strength of the given Elo.
|
If enabled by UCI_LimitStrength, aim for an engine strength of the given Elo.
|
||||||
This Elo rating has been calibrated at a time control of 60s+0.6s and anchored to CCRL 40/4.
|
This Elo rating has been calibrated at a time control of 60s+0.6s and anchored to CCRL 40/4.
|
||||||
|
|
||||||
* #### Move Overhead
|
* #### Skill Level
|
||||||
Assume a time delay of x ms due to network and GUI overheads. This is useful to
|
Lower the Skill Level in order to make Stockfish play weaker (see also UCI_LimitStrength).
|
||||||
avoid losses on time in those cases.
|
Internally, MultiPV is enabled, and with a certain probability depending on the Skill Level a
|
||||||
|
weaker move will be played.
|
||||||
* #### Minimum Thinking Time
|
|
||||||
Search for at least x ms per move.
|
|
||||||
|
|
||||||
* #### Slow Mover
|
|
||||||
Lower values will make Stockfish take less time in games, higher values will
|
|
||||||
make it think longer.
|
|
||||||
|
|
||||||
* #### nodestime
|
|
||||||
Tells the engine to use nodes searched instead of wall time to account for
|
|
||||||
elapsed time. Useful for engine testing.
|
|
||||||
|
|
||||||
* #### UCI_Chess960
|
|
||||||
An option handled by your GUI. If true, Stockfish will play Chess960.
|
|
||||||
|
|
||||||
* #### UCI_AnalyseMode
|
|
||||||
An option handled by your GUI.
|
|
||||||
|
|
||||||
* #### SyzygyPath
|
* #### SyzygyPath
|
||||||
Path to the folders/directories storing the Syzygy tablebase files. Multiple
|
Path to the folders/directories storing the Syzygy tablebase files. Multiple
|
||||||
@@ -112,6 +120,47 @@ Currently, Stockfish has the following UCI options:
|
|||||||
Limit Syzygy tablebase probing to positions with at most this many pieces left
|
Limit Syzygy tablebase probing to positions with at most this many pieces left
|
||||||
(including kings and pawns).
|
(including kings and pawns).
|
||||||
|
|
||||||
|
* #### Move Overhead
|
||||||
|
Assume a time delay of x ms due to network and GUI overheads. This is useful to
|
||||||
|
avoid losses on time in those cases.
|
||||||
|
|
||||||
|
* #### Slow Mover
|
||||||
|
Lower values will make Stockfish take less time in games, higher values will
|
||||||
|
make it think longer.
|
||||||
|
|
||||||
|
* #### nodestime
|
||||||
|
Tells the engine to use nodes searched instead of wall time to account for
|
||||||
|
elapsed time. Useful for engine testing.
|
||||||
|
|
||||||
|
* #### Clear Hash
|
||||||
|
Clear the hash table.
|
||||||
|
|
||||||
|
* #### Debug Log File
|
||||||
|
Write all communication to and from the engine into a text file.
|
||||||
|
|
||||||
|
## classical and NNUE evaluation
|
||||||
|
|
||||||
|
Both approaches assign a value to a position that is used in alpha-beta (PVS) search
|
||||||
|
to find the best move. The classical evaluation computes this value as a function
|
||||||
|
of various chess concepts, handcrafted by experts, tested and tuned using fishtest.
|
||||||
|
The NNUE evaluation computes this value with a neural network based on basic
|
||||||
|
inputs (e.g. piece positions only). The network is optimized and trained
|
||||||
|
on the evalutions of millions of positions at moderate search depth.
|
||||||
|
|
||||||
|
The NNUE evaluation was first introduced in shogi, and ported to Stockfish afterward.
|
||||||
|
It can be evaluated efficiently on CPUs, and exploits the fact that only parts
|
||||||
|
of the neural network need to be updated after a typical chess move.
|
||||||
|
[The nodchip repository](https://github.com/nodchip/Stockfish) provides additional
|
||||||
|
tools to train and develop the NNUE networks.
|
||||||
|
|
||||||
|
On CPUs supporting modern vector instructions (avx2 and similar), the NNUE evaluation
|
||||||
|
results in stronger playing strength, even if the nodes per second computed by the engine
|
||||||
|
is somewhat lower (roughly 60% of nps is typical).
|
||||||
|
|
||||||
|
Note that the NNUE evaluation depends on the Stockfish binary and the network parameter
|
||||||
|
file (see EvalFile). Not every parameter file is compatible with a given Stockfish binary.
|
||||||
|
The default value of the EvalFile UCI option is the name of a network that is guaranteed
|
||||||
|
to be compatible with that binary.
|
||||||
|
|
||||||
## What to expect from Syzygybases?
|
## What to expect from Syzygybases?
|
||||||
|
|
||||||
@@ -138,20 +187,50 @@ more compact than Nalimov tablebases, while still storing all information
|
|||||||
needed for optimal play and in addition being able to take into account
|
needed for optimal play and in addition being able to take into account
|
||||||
the 50-move rule.
|
the 50-move rule.
|
||||||
|
|
||||||
|
## Large Pages
|
||||||
|
|
||||||
|
Stockfish supports large pages on Linux and Windows. Large pages make
|
||||||
|
the hash access more efficient, improving the engine speed, especially
|
||||||
|
on large hash sizes. Typical increases are 5..10% in terms of nps, but
|
||||||
|
speed increases up to 30% have been measured. The support is
|
||||||
|
automatic. Stockfish attempts to use large pages when available and
|
||||||
|
will fall back to regular memory allocation when this is not the case.
|
||||||
|
|
||||||
|
### Support on Linux
|
||||||
|
|
||||||
|
Large page support on Linux is obtained by the Linux kernel
|
||||||
|
transparent huge pages functionality. Typically, transparent huge pages
|
||||||
|
are already enabled and no configuration is needed.
|
||||||
|
|
||||||
|
### Support on Windows
|
||||||
|
|
||||||
|
The use of large pages requires "Lock Pages in Memory" privilege. See
|
||||||
|
[Enable the Lock Pages in Memory Option (Windows)](https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/enable-the-lock-pages-in-memory-option-windows)
|
||||||
|
on how to enable this privilege. Logout/login may be needed
|
||||||
|
afterwards. Due to memory fragmentation, it may not always be
|
||||||
|
possible to allocate large pages even when enabled. A reboot
|
||||||
|
might alleviate this problem. To determine whether large pages
|
||||||
|
are in use, see the engine log.
|
||||||
|
|
||||||
## Compiling Stockfish yourself from the sources
|
## Compiling Stockfish yourself from the sources
|
||||||
|
|
||||||
On Unix-like systems, it should be possible to compile Stockfish
|
Stockfish has support for 32 or 64-bit CPUs, certain hardware
|
||||||
directly from the source code with the included Makefile.
|
instructions, big-endian machines such as Power PC, and other platforms.
|
||||||
|
|
||||||
Stockfish has support for 32 or 64-bit CPUs, the hardware POPCNT
|
On Unix-like systems, it should be easy to compile Stockfish
|
||||||
instruction, big-endian machines such as Power PC, and other platforms.
|
directly from the source code with the included Makefile in the folder
|
||||||
|
`src`. In general it is recommended to run `make help` to see a list of make
|
||||||
|
targets with corresponding descriptions.
|
||||||
|
|
||||||
In general it is recommended to run `make help` to see a list of make
|
```
|
||||||
targets with corresponding descriptions. When not using the Makefile to
|
cd src
|
||||||
compile (for instance with Microsoft MSVC) you need to manually
|
make help
|
||||||
set/unset some switches in the compiler command line; see file *types.h*
|
make build ARCH=x86-64-modern
|
||||||
for a quick reference.
|
```
|
||||||
|
|
||||||
|
When not using the Makefile to compile (for instance with Microsoft MSVC) you
|
||||||
|
need to manually set/unset some switches in the compiler command line; see
|
||||||
|
file *types.h* for a quick reference.
|
||||||
|
|
||||||
When reporting an issue or a bug, please tell us which version and
|
When reporting an issue or a bug, please tell us which version and
|
||||||
compiler you used to create your executable. These informations can
|
compiler you used to create your executable. These informations can
|
||||||
+10
-7
@@ -4,10 +4,9 @@ clone_depth: 50
|
|||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- master
|
- master
|
||||||
- appveyor
|
|
||||||
|
|
||||||
# Operating system (build VM template)
|
# Operating system (build VM template)
|
||||||
os: Visual Studio 2017
|
os: Visual Studio 2019
|
||||||
|
|
||||||
# Build platform, i.e. x86, x64, AnyCPU. This setting is optional.
|
# Build platform, i.e. x86, x64, AnyCPU. This setting is optional.
|
||||||
platform:
|
platform:
|
||||||
@@ -36,8 +35,11 @@ before_build:
|
|||||||
$src = $src.Replace("\", "/")
|
$src = $src.Replace("\", "/")
|
||||||
|
|
||||||
# Build CMakeLists.txt
|
# Build CMakeLists.txt
|
||||||
$t = 'cmake_minimum_required(VERSION 3.8)',
|
$t = 'cmake_minimum_required(VERSION 3.17)',
|
||||||
'project(Stockfish)',
|
'project(Stockfish)',
|
||||||
|
'set(CMAKE_CXX_STANDARD 17)',
|
||||||
|
'set(CMAKE_CXX_STANDARD_REQUIRED ON)',
|
||||||
|
'set (CMAKE_CXX_EXTENSIONS OFF)',
|
||||||
'set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/src)',
|
'set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/src)',
|
||||||
'set(source_files', $src, ')',
|
'set(source_files', $src, ')',
|
||||||
'add_executable(stockfish ${source_files})'
|
'add_executable(stockfish ${source_files})'
|
||||||
@@ -51,10 +53,11 @@ before_build:
|
|||||||
$b = git log HEAD | sls "\b[Bb]ench[ :]+[0-9]{7}" | select -first 1
|
$b = git log HEAD | sls "\b[Bb]ench[ :]+[0-9]{7}" | select -first 1
|
||||||
$bench = $b -match '\D+(\d+)' | % { $matches[1] }
|
$bench = $b -match '\D+(\d+)' | % { $matches[1] }
|
||||||
Write-Host "Reference bench:" $bench
|
Write-Host "Reference bench:" $bench
|
||||||
$g = "Visual Studio 15 2017"
|
$g = "Visual Studio 16 2019"
|
||||||
If (${env:PLATFORM} -eq 'x64') { $g = $g + ' Win64' }
|
If (${env:PLATFORM} -eq 'x64') { $a = "x64" }
|
||||||
cmake -G "${g}" .
|
If (${env:PLATFORM} -eq 'x86') { $a = "Win32" }
|
||||||
Write-Host "Generated files for: " $g
|
cmake -G "${g}" -A ${a} .
|
||||||
|
Write-Host "Generated files for: " $g $a
|
||||||
|
|
||||||
build_script:
|
build_script:
|
||||||
- cmake --build . --config %CONFIGURATION% -- /verbosity:minimal
|
- cmake --build . --config %CONFIGURATION% -- /verbosity:minimal
|
||||||
|
|||||||
+236
-41
@@ -35,10 +35,15 @@ BINDIR = $(PREFIX)/bin
|
|||||||
### Built-in benchmark for pgo-builds
|
### Built-in benchmark for pgo-builds
|
||||||
PGOBENCH = ./$(EXE) bench
|
PGOBENCH = ./$(EXE) bench
|
||||||
|
|
||||||
### Object files
|
### Source and object files
|
||||||
OBJS = benchmark.o bitbase.o bitboard.o endgame.o evaluate.o main.o \
|
SRCS = benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp main.cpp \
|
||||||
material.o misc.o movegen.o movepick.o pawns.o position.o psqt.o \
|
material.cpp misc.cpp movegen.cpp movepick.cpp pawns.cpp position.cpp psqt.cpp \
|
||||||
search.o thread.o timeman.o tt.o uci.o ucioption.o syzygy/tbprobe.o
|
search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \
|
||||||
|
nnue/evaluate_nnue.cpp nnue/features/half_kp.cpp
|
||||||
|
|
||||||
|
OBJS = $(notdir $(SRCS:.cpp=.o))
|
||||||
|
|
||||||
|
VPATH = syzygy:nnue:nnue/features
|
||||||
|
|
||||||
### Establish the operating system name
|
### Establish the operating system name
|
||||||
KERNEL = $(shell uname -s)
|
KERNEL = $(shell uname -s)
|
||||||
@@ -63,7 +68,14 @@ endif
|
|||||||
# prefetch = yes/no --- -DUSE_PREFETCH --- Use prefetch asm-instruction
|
# prefetch = yes/no --- -DUSE_PREFETCH --- Use prefetch asm-instruction
|
||||||
# popcnt = yes/no --- -DUSE_POPCNT --- Use popcnt asm-instruction
|
# popcnt = yes/no --- -DUSE_POPCNT --- Use popcnt asm-instruction
|
||||||
# sse = yes/no --- -msse --- Use Intel Streaming SIMD Extensions
|
# sse = yes/no --- -msse --- Use Intel Streaming SIMD Extensions
|
||||||
|
# sse3 = yes/no --- -msse3 --- Use Intel Streaming SIMD Extensions 3
|
||||||
|
# ssse3 = yes/no --- -mssse3 --- Use Intel Supplemental Streaming SIMD Extensions 3
|
||||||
|
# sse41 = yes/no --- -msse4.1 --- Use Intel Streaming SIMD Extensions 4.1
|
||||||
|
# sse42 = yes/no --- -msse4.2 --- Use Intel Streaming SIMD Extensions 4.2
|
||||||
|
# avx2 = yes/no --- -mavx2 --- Use Intel Advanced Vector Extensions 2
|
||||||
# pext = yes/no --- -DUSE_PEXT --- Use pext x86_64 asm-instruction
|
# pext = yes/no --- -DUSE_PEXT --- Use pext x86_64 asm-instruction
|
||||||
|
# avx512 = yes/no --- -mavx512bw --- Use Intel Advanced Vector Extensions 512
|
||||||
|
# neon = yes/no --- -DUSE_NEON --- Use ARM SIMD architecture
|
||||||
#
|
#
|
||||||
# Note that Makefile is space sensitive, so when adding new architectures
|
# Note that Makefile is space sensitive, so when adding new architectures
|
||||||
# or modifying existing flags, you have to make sure there are no extra spaces
|
# or modifying existing flags, you have to make sure there are no extra spaces
|
||||||
@@ -73,82 +85,179 @@ endif
|
|||||||
optimize = yes
|
optimize = yes
|
||||||
debug = no
|
debug = no
|
||||||
sanitize = no
|
sanitize = no
|
||||||
bits = 32
|
bits = 64
|
||||||
prefetch = no
|
prefetch = no
|
||||||
popcnt = no
|
popcnt = no
|
||||||
sse = no
|
sse = no
|
||||||
|
sse3 = no
|
||||||
|
ssse3 = no
|
||||||
|
sse41 = no
|
||||||
|
sse42 = no
|
||||||
|
avx2 = no
|
||||||
pext = no
|
pext = no
|
||||||
|
avx512 = no
|
||||||
|
neon = no
|
||||||
|
ARCH = x86-64-modern
|
||||||
|
|
||||||
### 2.2 Architecture specific
|
### 2.2 Architecture specific
|
||||||
|
|
||||||
ifeq ($(ARCH),general-32)
|
ifeq ($(ARCH),general-32)
|
||||||
arch = any
|
arch = any
|
||||||
|
bits = 32
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(ARCH),x86-32-old)
|
ifeq ($(ARCH),x86-32-old)
|
||||||
arch = i386
|
arch = i386
|
||||||
|
bits = 32
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(ARCH),x86-32)
|
ifeq ($(ARCH),x86-32)
|
||||||
arch = i386
|
arch = i386
|
||||||
|
bits = 32
|
||||||
prefetch = yes
|
prefetch = yes
|
||||||
sse = yes
|
sse = yes
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(ARCH),general-64)
|
ifeq ($(ARCH),general-64)
|
||||||
arch = any
|
arch = any
|
||||||
bits = 64
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(ARCH),x86-64)
|
ifeq ($(ARCH),x86-64)
|
||||||
arch = x86_64
|
arch = x86_64
|
||||||
bits = 64
|
|
||||||
prefetch = yes
|
prefetch = yes
|
||||||
sse = yes
|
sse = yes
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
ifeq ($(ARCH),x86-64-sse3)
|
||||||
|
arch = x86_64
|
||||||
|
prefetch = yes
|
||||||
|
sse = yes
|
||||||
|
sse3 = yes
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(ARCH),x86-64-sse3-popcnt)
|
||||||
|
arch = x86_64
|
||||||
|
prefetch = yes
|
||||||
|
sse = yes
|
||||||
|
sse3 = yes
|
||||||
|
popcnt = yes
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(ARCH),x86-64-ssse3)
|
||||||
|
arch = x86_64
|
||||||
|
prefetch = yes
|
||||||
|
sse = yes
|
||||||
|
sse3 = yes
|
||||||
|
ssse3 = yes
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(ARCH),x86-64-sse41)
|
||||||
|
arch = x86_64
|
||||||
|
prefetch = yes
|
||||||
|
popcnt = yes
|
||||||
|
sse = yes
|
||||||
|
sse3 = yes
|
||||||
|
ssse3 = yes
|
||||||
|
sse41 = yes
|
||||||
|
endif
|
||||||
|
|
||||||
ifeq ($(ARCH),x86-64-modern)
|
ifeq ($(ARCH),x86-64-modern)
|
||||||
arch = x86_64
|
arch = x86_64
|
||||||
bits = 64
|
|
||||||
prefetch = yes
|
prefetch = yes
|
||||||
popcnt = yes
|
popcnt = yes
|
||||||
sse = yes
|
sse = yes
|
||||||
|
sse3 = yes
|
||||||
|
ssse3 = yes
|
||||||
|
sse41 = yes
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(ARCH),x86-64-sse42)
|
||||||
|
arch = x86_64
|
||||||
|
prefetch = yes
|
||||||
|
popcnt = yes
|
||||||
|
sse = yes
|
||||||
|
sse3 = yes
|
||||||
|
ssse3 = yes
|
||||||
|
sse41 = yes
|
||||||
|
sse42 = yes
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(ARCH),x86-64-avx2)
|
||||||
|
arch = x86_64
|
||||||
|
prefetch = yes
|
||||||
|
popcnt = yes
|
||||||
|
sse = yes
|
||||||
|
sse3 = yes
|
||||||
|
ssse3 = yes
|
||||||
|
sse41 = yes
|
||||||
|
sse42 = yes
|
||||||
|
avx2 = yes
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(ARCH),x86-64-bmi2)
|
ifeq ($(ARCH),x86-64-bmi2)
|
||||||
arch = x86_64
|
arch = x86_64
|
||||||
bits = 64
|
|
||||||
prefetch = yes
|
prefetch = yes
|
||||||
popcnt = yes
|
popcnt = yes
|
||||||
sse = yes
|
sse = yes
|
||||||
|
sse3 = yes
|
||||||
|
ssse3 = yes
|
||||||
|
sse41 = yes
|
||||||
|
sse42 = yes
|
||||||
|
avx2 = yes
|
||||||
pext = yes
|
pext = yes
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
ifeq ($(ARCH),x86-64-avx512)
|
||||||
|
arch = x86_64
|
||||||
|
prefetch = yes
|
||||||
|
popcnt = yes
|
||||||
|
sse = yes
|
||||||
|
sse3 = yes
|
||||||
|
ssse3 = yes
|
||||||
|
sse41 = yes
|
||||||
|
sse42 = yes
|
||||||
|
avx2 = yes
|
||||||
|
pext = yes
|
||||||
|
avx512 = yes
|
||||||
|
endif
|
||||||
|
|
||||||
ifeq ($(ARCH),armv7)
|
ifeq ($(ARCH),armv7)
|
||||||
arch = armv7
|
arch = armv7
|
||||||
prefetch = yes
|
prefetch = yes
|
||||||
|
bits = 32
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(ARCH),armv8)
|
||||||
|
arch = armv8-a
|
||||||
|
prefetch = yes
|
||||||
|
popcnt = yes
|
||||||
|
neon = yes
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(ARCH),apple-silicon)
|
||||||
|
arch = arm64
|
||||||
|
prefetch = yes
|
||||||
|
popcnt = yes
|
||||||
|
neon = yes
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(ARCH),ppc-32)
|
ifeq ($(ARCH),ppc-32)
|
||||||
arch = ppc
|
arch = ppc
|
||||||
|
bits = 32
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(ARCH),ppc-64)
|
ifeq ($(ARCH),ppc-64)
|
||||||
arch = ppc64
|
arch = ppc64
|
||||||
bits = 64
|
|
||||||
popcnt = yes
|
popcnt = yes
|
||||||
prefetch = yes
|
prefetch = yes
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
||||||
### ==========================================================================
|
### ==========================================================================
|
||||||
### Section 3. Low-level configuration
|
### Section 3. Low-level Configuration
|
||||||
### ==========================================================================
|
### ==========================================================================
|
||||||
|
|
||||||
### 3.1 Selecting compiler (default = gcc)
|
### 3.1 Selecting compiler (default = gcc)
|
||||||
|
CXXFLAGS += -Wall -Wcast-qual -fno-exceptions -std=c++17 $(EXTRACXXFLAGS)
|
||||||
CXXFLAGS += -Wall -Wcast-qual -fno-exceptions -std=c++11 $(EXTRACXXFLAGS)
|
DEPENDFLAGS += -std=c++17
|
||||||
DEPENDFLAGS += -std=c++11
|
|
||||||
LDFLAGS += $(EXTRALDFLAGS)
|
LDFLAGS += $(EXTRALDFLAGS)
|
||||||
|
|
||||||
ifeq ($(COMP),)
|
ifeq ($(COMP),)
|
||||||
@@ -160,7 +269,7 @@ ifeq ($(COMP),gcc)
|
|||||||
CXX=g++
|
CXX=g++
|
||||||
CXXFLAGS += -pedantic -Wextra -Wshadow
|
CXXFLAGS += -pedantic -Wextra -Wshadow
|
||||||
|
|
||||||
ifeq ($(ARCH),armv7)
|
ifeq ($(ARCH),$(filter $(ARCH),armv7 armv8))
|
||||||
ifeq ($(OS),Android)
|
ifeq ($(OS),Android)
|
||||||
CXXFLAGS += -m$(bits)
|
CXXFLAGS += -m$(bits)
|
||||||
LDFLAGS += -m$(bits)
|
LDFLAGS += -m$(bits)
|
||||||
@@ -217,7 +326,7 @@ ifeq ($(COMP),clang)
|
|||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(ARCH),armv7)
|
ifeq ($(ARCH),$(filter $(ARCH),armv7 armv8))
|
||||||
ifeq ($(OS),Android)
|
ifeq ($(OS),Android)
|
||||||
CXXFLAGS += -m$(bits)
|
CXXFLAGS += -m$(bits)
|
||||||
LDFLAGS += -m$(bits)
|
LDFLAGS += -m$(bits)
|
||||||
@@ -242,8 +351,8 @@ endif
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(KERNEL),Darwin)
|
ifeq ($(KERNEL),Darwin)
|
||||||
CXXFLAGS += -arch $(arch) -mmacosx-version-min=10.9
|
CXXFLAGS += -arch $(arch) -mmacosx-version-min=10.15
|
||||||
LDFLAGS += -arch $(arch) -mmacosx-version-min=10.9
|
LDFLAGS += -arch $(arch) -mmacosx-version-min=10.15
|
||||||
endif
|
endif
|
||||||
|
|
||||||
### Travis CI script uses COMPILER to overwrite CXX
|
### Travis CI script uses COMPILER to overwrite CXX
|
||||||
@@ -276,8 +385,8 @@ endif
|
|||||||
|
|
||||||
### 3.2.2 Debugging with undefined behavior sanitizers
|
### 3.2.2 Debugging with undefined behavior sanitizers
|
||||||
ifneq ($(sanitize),no)
|
ifneq ($(sanitize),no)
|
||||||
CXXFLAGS += -g3 -fsanitize=$(sanitize) -fuse-ld=gold
|
CXXFLAGS += -g3 -fsanitize=$(sanitize)
|
||||||
LDFLAGS += -fsanitize=$(sanitize) -fuse-ld=gold
|
LDFLAGS += -fsanitize=$(sanitize)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
### 3.3 Optimization
|
### 3.3 Optimization
|
||||||
@@ -315,7 +424,7 @@ endif
|
|||||||
|
|
||||||
### 3.6 popcnt
|
### 3.6 popcnt
|
||||||
ifeq ($(popcnt),yes)
|
ifeq ($(popcnt),yes)
|
||||||
ifeq ($(arch),ppc64)
|
ifeq ($(arch),$(filter $(arch),ppc64 armv8-a arm64))
|
||||||
CXXFLAGS += -DUSE_POPCNT
|
CXXFLAGS += -DUSE_POPCNT
|
||||||
else ifeq ($(comp),icc)
|
else ifeq ($(comp),icc)
|
||||||
CXXFLAGS += -msse3 -DUSE_POPCNT
|
CXXFLAGS += -msse3 -DUSE_POPCNT
|
||||||
@@ -324,15 +433,65 @@ ifeq ($(popcnt),yes)
|
|||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
ifeq ($(avx2),yes)
|
||||||
|
CXXFLAGS += -DUSE_AVX2
|
||||||
|
ifeq ($(comp),$(filter $(comp),gcc clang mingw))
|
||||||
|
CXXFLAGS += -mavx2
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(avx512),yes)
|
||||||
|
CXXFLAGS += -DUSE_AVX512
|
||||||
|
ifeq ($(comp),$(filter $(comp),gcc clang mingw))
|
||||||
|
CXXFLAGS += -mavx512bw
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(sse42),yes)
|
||||||
|
CXXFLAGS += -DUSE_SSE42
|
||||||
|
ifeq ($(comp),$(filter $(comp),gcc clang mingw))
|
||||||
|
CXXFLAGS += -msse4.2
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(sse41),yes)
|
||||||
|
CXXFLAGS += -DUSE_SSE41
|
||||||
|
ifeq ($(comp),$(filter $(comp),gcc clang mingw))
|
||||||
|
CXXFLAGS += -msse4.1
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(ssse3),yes)
|
||||||
|
CXXFLAGS += -DUSE_SSSE3
|
||||||
|
ifeq ($(comp),$(filter $(comp),gcc clang mingw))
|
||||||
|
CXXFLAGS += -mssse3
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(sse3),yes)
|
||||||
|
CXXFLAGS += -DUSE_SSE3
|
||||||
|
ifeq ($(comp),$(filter $(comp),gcc clang mingw))
|
||||||
|
CXXFLAGS += -msse3
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(neon),yes)
|
||||||
|
CXXFLAGS += -DUSE_NEON
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(arch),x86_64)
|
||||||
|
CXXFLAGS += -DUSE_SSE2
|
||||||
|
endif
|
||||||
|
|
||||||
### 3.7 pext
|
### 3.7 pext
|
||||||
ifeq ($(pext),yes)
|
ifeq ($(pext),yes)
|
||||||
CXXFLAGS += -DUSE_PEXT
|
CXXFLAGS += -DUSE_PEXT
|
||||||
ifeq ($(comp),$(filter $(comp),gcc clang mingw))
|
ifeq ($(comp),$(filter $(comp),gcc clang mingw))
|
||||||
CXXFLAGS += -msse4 -mbmi2
|
CXXFLAGS += -mbmi2
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
### 3.8 Link Time Optimization, it works since gcc 4.5 but not on mingw under Windows.
|
### 3.8 Link Time Optimization
|
||||||
### This is a mix of compile and link time options because the lto link phase
|
### This is a mix of compile and link time options because the lto link phase
|
||||||
### needs access to the optimization flags.
|
### needs access to the optimization flags.
|
||||||
ifeq ($(optimize),yes)
|
ifeq ($(optimize),yes)
|
||||||
@@ -342,6 +501,9 @@ ifeq ($(debug), no)
|
|||||||
LDFLAGS += $(CXXFLAGS)
|
LDFLAGS += $(CXXFLAGS)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
# To use LTO and static linking on windows, the tool chain requires a recent gcc:
|
||||||
|
# gcc version 10.1 in msys2 or TDM-GCC version 9.2 are know to work, older might not.
|
||||||
|
# So, only enable it for a cross from Linux by default.
|
||||||
ifeq ($(comp),mingw)
|
ifeq ($(comp),mingw)
|
||||||
ifeq ($(KERNEL),Linux)
|
ifeq ($(KERNEL),Linux)
|
||||||
CXXFLAGS += -flto
|
CXXFLAGS += -flto
|
||||||
@@ -358,9 +520,8 @@ ifeq ($(OS), Android)
|
|||||||
LDFLAGS += -fPIE -pie
|
LDFLAGS += -fPIE -pie
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
||||||
### ==========================================================================
|
### ==========================================================================
|
||||||
### Section 4. Public targets
|
### Section 4. Public Targets
|
||||||
### ==========================================================================
|
### ==========================================================================
|
||||||
|
|
||||||
help:
|
help:
|
||||||
@@ -372,21 +533,31 @@ help:
|
|||||||
@echo "Supported targets:"
|
@echo "Supported targets:"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "build > Standard build"
|
@echo "build > Standard build"
|
||||||
@echo "profile-build > PGO build"
|
@echo "profile-build > Standard build with PGO"
|
||||||
@echo "strip > Strip executable"
|
@echo "strip > Strip executable"
|
||||||
@echo "install > Install executable"
|
@echo "install > Install executable"
|
||||||
@echo "clean > Clean up"
|
@echo "clean > Clean up"
|
||||||
|
@echo "net > Download the default nnue net"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Supported archs:"
|
@echo "Supported archs:"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "x86-64-bmi2 > x86 64-bit with pext support (also enables SSE4)"
|
@echo "x86-64-avx512 > x86 64-bit with avx512 support"
|
||||||
@echo "x86-64-modern > x86 64-bit with popcnt support (also enables SSE3)"
|
@echo "x86-64-bmi2 > x86 64-bit with bmi2 support"
|
||||||
|
@echo "x86-64-avx2 > x86 64-bit with avx2 support"
|
||||||
|
@echo "x86-64-sse42 > x86 64-bit with sse42 support"
|
||||||
|
@echo "x86-64-modern > x86 64-bit with sse41 support (x86-64-sse41)"
|
||||||
|
@echo "x86-64-sse41 > x86 64-bit with sse41 support"
|
||||||
|
@echo "x86-64-ssse3 > x86 64-bit with ssse3 support"
|
||||||
|
@echo "x86-64-sse3-popcnt > x86 64-bit with sse3 and popcnt support"
|
||||||
|
@echo "x86-64-sse3 > x86 64-bit with sse3 support"
|
||||||
@echo "x86-64 > x86 64-bit generic"
|
@echo "x86-64 > x86 64-bit generic"
|
||||||
@echo "x86-32 > x86 32-bit (also enables SSE)"
|
@echo "x86-32 > x86 32-bit (also enables SSE)"
|
||||||
@echo "x86-32-old > x86 32-bit fall back for old hardware"
|
@echo "x86-32-old > x86 32-bit fall back for old hardware"
|
||||||
@echo "ppc-64 > PPC 64-bit"
|
@echo "ppc-64 > PPC 64-bit"
|
||||||
@echo "ppc-32 > PPC 32-bit"
|
@echo "ppc-32 > PPC 32-bit"
|
||||||
@echo "armv7 > ARMv7 32-bit"
|
@echo "armv7 > ARMv7 32-bit"
|
||||||
|
@echo "armv8 > ARMv8 64-bit"
|
||||||
|
@echo "apple-silicon > Apple silicon ARM64"
|
||||||
@echo "general-64 > unspecified 64-bit"
|
@echo "general-64 > unspecified 64-bit"
|
||||||
@echo "general-32 > unspecified 32-bit"
|
@echo "general-32 > unspecified 32-bit"
|
||||||
@echo ""
|
@echo ""
|
||||||
@@ -399,17 +570,20 @@ help:
|
|||||||
@echo ""
|
@echo ""
|
||||||
@echo "Simple examples. If you don't know what to do, you likely want to run: "
|
@echo "Simple examples. If you don't know what to do, you likely want to run: "
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "make build ARCH=x86-64 (This is for 64-bit systems)"
|
@echo "make -j build ARCH=x86-64 (This is for 64-bit systems)"
|
||||||
@echo "make build ARCH=x86-32 (This is for 32-bit systems)"
|
@echo "make -j build ARCH=x86-32 (This is for 32-bit systems)"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Advanced examples, for experienced users: "
|
@echo "Advanced examples, for experienced users: "
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "make build ARCH=x86-64 COMP=clang"
|
@echo "make -j build ARCH=x86-64-modern COMP=clang"
|
||||||
@echo "make profile-build ARCH=x86-64-bmi2 COMP=gcc COMPCXX=g++-4.8"
|
@echo "make -j profile-build ARCH=x86-64-bmi2 COMP=gcc COMPCXX=g++-4.8"
|
||||||
@echo ""
|
@echo ""
|
||||||
|
@echo "The selected architecture $(ARCH) enables the following configuration: "
|
||||||
|
@echo ""
|
||||||
|
@$(MAKE) ARCH=$(ARCH) COMP=$(COMP) config-sanity
|
||||||
|
|
||||||
|
|
||||||
.PHONY: help build profile-build strip install clean objclean profileclean help \
|
.PHONY: help build profile-build strip install clean net objclean profileclean \
|
||||||
config-sanity icc-profile-use icc-profile-make gcc-profile-use gcc-profile-make \
|
config-sanity icc-profile-use icc-profile-make gcc-profile-use gcc-profile-make \
|
||||||
clang-profile-use clang-profile-make
|
clang-profile-use clang-profile-make
|
||||||
|
|
||||||
@@ -443,21 +617,28 @@ install:
|
|||||||
clean: objclean profileclean
|
clean: objclean profileclean
|
||||||
@rm -f .depend *~ core
|
@rm -f .depend *~ core
|
||||||
|
|
||||||
|
net:
|
||||||
|
$(eval nnuenet := $(shell grep EvalFile ucioption.cpp | grep Option | sed 's/.*\(nn-[a-z0-9]\{12\}.nnue\).*/\1/'))
|
||||||
|
@echo "Default net: $(nnuenet)"
|
||||||
|
$(eval nnuedownloadurl := https://tests.stockfishchess.org/api/nn/$(nnuenet))
|
||||||
|
$(eval curl_or_wget := $(shell if hash curl 2>/dev/null; then echo "curl -sL"; elif hash wget 2>/dev/null; then echo "wget -qO-"; fi))
|
||||||
|
@if test -f "$(nnuenet)"; then echo "Already available."; else echo "Downloading $(nnuedownloadurl)"; $(curl_or_wget) $(nnuedownloadurl) > $(nnuenet); fi
|
||||||
|
|
||||||
# clean binaries and objects
|
# clean binaries and objects
|
||||||
objclean:
|
objclean:
|
||||||
@rm -f $(EXE) *.o ./syzygy/*.o
|
@rm -f $(EXE) *.o ./syzygy/*.o ./nnue/*.o ./nnue/features/*.o
|
||||||
|
|
||||||
# clean auxiliary profiling files
|
# clean auxiliary profiling files
|
||||||
profileclean:
|
profileclean:
|
||||||
@rm -rf profdir
|
@rm -rf profdir
|
||||||
@rm -f bench.txt *.gcda ./syzygy/*.gcda *.gcno ./syzygy/*.gcno
|
@rm -f bench.txt *.gcda *.gcno ./syzygy/*.gcda ./nnue/*.gcda ./nnue/features/*.gcda
|
||||||
@rm -f stockfish.profdata *.profraw
|
@rm -f stockfish.profdata *.profraw
|
||||||
|
|
||||||
default:
|
default:
|
||||||
help
|
help
|
||||||
|
|
||||||
### ==========================================================================
|
### ==========================================================================
|
||||||
### Section 5. Private targets
|
### Section 5. Private Targets
|
||||||
### ==========================================================================
|
### ==========================================================================
|
||||||
|
|
||||||
all: $(EXE) .depend
|
all: $(EXE) .depend
|
||||||
@@ -475,7 +656,14 @@ config-sanity:
|
|||||||
@echo "prefetch: '$(prefetch)'"
|
@echo "prefetch: '$(prefetch)'"
|
||||||
@echo "popcnt: '$(popcnt)'"
|
@echo "popcnt: '$(popcnt)'"
|
||||||
@echo "sse: '$(sse)'"
|
@echo "sse: '$(sse)'"
|
||||||
|
@echo "sse3: '$(sse3)'"
|
||||||
|
@echo "ssse3: '$(ssse3)'"
|
||||||
|
@echo "sse41: '$(sse41)'"
|
||||||
|
@echo "sse42: '$(sse42)'"
|
||||||
|
@echo "avx2: '$(avx2)'"
|
||||||
@echo "pext: '$(pext)'"
|
@echo "pext: '$(pext)'"
|
||||||
|
@echo "avx512: '$(avx512)'"
|
||||||
|
@echo "neon: '$(neon)'"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Flags:"
|
@echo "Flags:"
|
||||||
@echo "CXX: $(CXX)"
|
@echo "CXX: $(CXX)"
|
||||||
@@ -488,12 +676,20 @@ config-sanity:
|
|||||||
@test "$(sanitize)" = "undefined" || test "$(sanitize)" = "thread" || test "$(sanitize)" = "address" || test "$(sanitize)" = "no"
|
@test "$(sanitize)" = "undefined" || test "$(sanitize)" = "thread" || test "$(sanitize)" = "address" || test "$(sanitize)" = "no"
|
||||||
@test "$(optimize)" = "yes" || test "$(optimize)" = "no"
|
@test "$(optimize)" = "yes" || test "$(optimize)" = "no"
|
||||||
@test "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \
|
@test "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \
|
||||||
test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || test "$(arch)" = "armv7"
|
test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || \
|
||||||
|
test "$(arch)" = "armv7" || test "$(arch)" = "armv8-a" || test "$(arch)" = "arm64"
|
||||||
@test "$(bits)" = "32" || test "$(bits)" = "64"
|
@test "$(bits)" = "32" || test "$(bits)" = "64"
|
||||||
@test "$(prefetch)" = "yes" || test "$(prefetch)" = "no"
|
@test "$(prefetch)" = "yes" || test "$(prefetch)" = "no"
|
||||||
@test "$(popcnt)" = "yes" || test "$(popcnt)" = "no"
|
@test "$(popcnt)" = "yes" || test "$(popcnt)" = "no"
|
||||||
@test "$(sse)" = "yes" || test "$(sse)" = "no"
|
@test "$(sse)" = "yes" || test "$(sse)" = "no"
|
||||||
|
@test "$(sse3)" = "yes" || test "$(sse3)" = "no"
|
||||||
|
@test "$(ssse3)" = "yes" || test "$(ssse3)" = "no"
|
||||||
|
@test "$(sse41)" = "yes" || test "$(sse41)" = "no"
|
||||||
|
@test "$(sse42)" = "yes" || test "$(sse42)" = "no"
|
||||||
|
@test "$(avx2)" = "yes" || test "$(avx2)" = "no"
|
||||||
@test "$(pext)" = "yes" || test "$(pext)" = "no"
|
@test "$(pext)" = "yes" || test "$(pext)" = "no"
|
||||||
|
@test "$(avx512)" = "yes" || test "$(avx512)" = "no"
|
||||||
|
@test "$(neon)" = "yes" || test "$(neon)" = "no"
|
||||||
@test "$(comp)" = "gcc" || test "$(comp)" = "icc" || test "$(comp)" = "mingw" || test "$(comp)" = "clang"
|
@test "$(comp)" = "gcc" || test "$(comp)" = "icc" || test "$(comp)" = "mingw" || test "$(comp)" = "clang"
|
||||||
|
|
||||||
$(EXE): $(OBJS)
|
$(EXE): $(OBJS)
|
||||||
@@ -536,7 +732,6 @@ icc-profile-use:
|
|||||||
all
|
all
|
||||||
|
|
||||||
.depend:
|
.depend:
|
||||||
-@$(CXX) $(DEPENDFLAGS) -MM $(OBJS:.o=.cpp) > $@ 2> /dev/null
|
-@$(CXX) $(DEPENDFLAGS) -MM $(SRCS) > $@ 2> /dev/null
|
||||||
|
|
||||||
-include .depend
|
-include .depend
|
||||||
|
|
||||||
|
|||||||
+3
-4
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -65,6 +63,7 @@ const vector<string> Defaults = {
|
|||||||
"4rrk1/1p1nq3/p7/2p1P1pp/3P2bp/3Q1Bn1/PPPB4/1K2R1NR w - - 40 21",
|
"4rrk1/1p1nq3/p7/2p1P1pp/3P2bp/3Q1Bn1/PPPB4/1K2R1NR w - - 40 21",
|
||||||
"r3k2r/3nnpbp/q2pp1p1/p7/Pp1PPPP1/4BNN1/1P5P/R2Q1RK1 w kq - 0 16",
|
"r3k2r/3nnpbp/q2pp1p1/p7/Pp1PPPP1/4BNN1/1P5P/R2Q1RK1 w kq - 0 16",
|
||||||
"3Qb1k1/1r2ppb1/pN1n2q1/Pp1Pp1Pr/4P2p/4BP2/4B1R1/1R5K b - - 11 40",
|
"3Qb1k1/1r2ppb1/pN1n2q1/Pp1Pp1Pr/4P2p/4BP2/4B1R1/1R5K b - - 11 40",
|
||||||
|
"4k3/3q1r2/1N2r1b1/3ppN2/2nPP3/1B1R2n1/2R1Q3/3K4 w - - 5 1",
|
||||||
|
|
||||||
// 5-man positions
|
// 5-man positions
|
||||||
"8/8/8/8/5kp1/P7/8/1K1N4 w - - 0 1", // Kc2 - mate
|
"8/8/8/8/5kp1/P7/8/1K1N4 w - - 0 1", // Kc2 - mate
|
||||||
@@ -87,7 +86,7 @@ const vector<string> Defaults = {
|
|||||||
|
|
||||||
// Chess 960
|
// Chess 960
|
||||||
"setoption name UCI_Chess960 value true",
|
"setoption name UCI_Chess960 value true",
|
||||||
"bbqnnrkr/pppppppp/8/8/8/8/PPPPPPPP/BBQNNRKR w KQkq - 0 1 moves g2g3 d7d5 d2d4 c8h3 c1g5 e8d6 g5e7 f7f6",
|
"bbqnnrkr/pppppppp/8/8/8/8/PPPPPPPP/BBQNNRKR w HFhf - 0 1 moves g2g3 d7d5 d2d4 c8h3 c1g5 e8d6 g5e7 f7f6",
|
||||||
"setoption name UCI_Chess960 value false"
|
"setoption name UCI_Chess960 value false"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+33
-43
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,8 +17,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <numeric>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <bitset>
|
||||||
|
|
||||||
#include "bitboard.h"
|
#include "bitboard.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
@@ -31,8 +29,7 @@ namespace {
|
|||||||
// Positions with the pawn on files E to H will be mirrored before probing.
|
// 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
|
constexpr unsigned MAX_INDEX = 2*24*64*64; // stm * psq * wksq * bksq = 196608
|
||||||
|
|
||||||
// Each uint32_t stores results of 32 positions, one per bit
|
std::bitset<MAX_INDEX> KPKBitbase;
|
||||||
uint32_t KPKBitbase[MAX_INDEX / 32];
|
|
||||||
|
|
||||||
// A KPK bitbase index is an integer in [0, IndexMax] range
|
// A KPK bitbase index is an integer in [0, IndexMax] range
|
||||||
//
|
//
|
||||||
@@ -43,8 +40,8 @@ namespace {
|
|||||||
// bit 12: side to move (WHITE or BLACK)
|
// bit 12: side to move (WHITE or BLACK)
|
||||||
// bit 13-14: white pawn file (from FILE_A to FILE_D)
|
// 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)
|
// bit 15-17: white pawn RANK_7 - rank (from RANK_7 - RANK_7 to RANK_7 - RANK_2)
|
||||||
unsigned index(Color us, Square bksq, Square wksq, Square psq) {
|
unsigned index(Color stm, Square bksq, Square wksq, Square psq) {
|
||||||
return int(wksq) | (bksq << 6) | (us << 12) | (file_of(psq) << 13) | ((RANK_7 - rank_of(psq)) << 15);
|
return int(wksq) | (bksq << 6) | (stm << 12) | (file_of(psq) << 13) | ((RANK_7 - rank_of(psq)) << 15);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Result {
|
enum Result {
|
||||||
@@ -60,12 +57,9 @@ namespace {
|
|||||||
KPKPosition() = default;
|
KPKPosition() = default;
|
||||||
explicit KPKPosition(unsigned idx);
|
explicit KPKPosition(unsigned idx);
|
||||||
operator Result() const { return result; }
|
operator Result() const { return result; }
|
||||||
Result classify(const std::vector<KPKPosition>& db)
|
Result classify(const std::vector<KPKPosition>& db);
|
||||||
{ return us == WHITE ? classify<WHITE>(db) : classify<BLACK>(db); }
|
|
||||||
|
|
||||||
template<Color Us> Result classify(const std::vector<KPKPosition>& db);
|
Color stm;
|
||||||
|
|
||||||
Color us;
|
|
||||||
Square ksq[COLOR_NB], psq;
|
Square ksq[COLOR_NB], psq;
|
||||||
Result result;
|
Result result;
|
||||||
};
|
};
|
||||||
@@ -73,12 +67,11 @@ namespace {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
|
||||||
bool Bitbases::probe(Square wksq, Square wpsq, Square bksq, Color us) {
|
bool Bitbases::probe(Square wksq, Square wpsq, Square bksq, Color stm) {
|
||||||
|
|
||||||
assert(file_of(wpsq) <= FILE_D);
|
assert(file_of(wpsq) <= FILE_D);
|
||||||
|
|
||||||
unsigned idx = index(us, bksq, wksq, wpsq);
|
return KPKBitbase[index(stm, bksq, wksq, wpsq)];
|
||||||
return KPKBitbase[idx / 32] & (1 << (idx & 0x1F));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -97,10 +90,10 @@ void Bitbases::init() {
|
|||||||
for (repeat = idx = 0; idx < MAX_INDEX; ++idx)
|
for (repeat = idx = 0; idx < MAX_INDEX; ++idx)
|
||||||
repeat |= (db[idx] == UNKNOWN && db[idx].classify(db) != UNKNOWN);
|
repeat |= (db[idx] == UNKNOWN && db[idx].classify(db) != UNKNOWN);
|
||||||
|
|
||||||
// Map 32 results into one KPKBitbase[] entry
|
// Fill the bitbase with the decisive results
|
||||||
for (idx = 0; idx < MAX_INDEX; ++idx)
|
for (idx = 0; idx < MAX_INDEX; ++idx)
|
||||||
if (db[idx] == WIN)
|
if (db[idx] == WIN)
|
||||||
KPKBitbase[idx / 32] |= 1 << (idx & 0x1F);
|
KPKBitbase.set(idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -110,28 +103,28 @@ namespace {
|
|||||||
|
|
||||||
ksq[WHITE] = Square((idx >> 0) & 0x3F);
|
ksq[WHITE] = Square((idx >> 0) & 0x3F);
|
||||||
ksq[BLACK] = Square((idx >> 6) & 0x3F);
|
ksq[BLACK] = Square((idx >> 6) & 0x3F);
|
||||||
us = Color ((idx >> 12) & 0x01);
|
stm = Color ((idx >> 12) & 0x01);
|
||||||
psq = make_square(File((idx >> 13) & 0x3), Rank(RANK_7 - ((idx >> 15) & 0x7)));
|
psq = make_square(File((idx >> 13) & 0x3), Rank(RANK_7 - ((idx >> 15) & 0x7)));
|
||||||
|
|
||||||
// Check if two pieces are on the same square or if a king can be captured
|
// Invalid if two pieces are on the same square or if a king can be captured
|
||||||
if ( distance(ksq[WHITE], ksq[BLACK]) <= 1
|
if ( distance(ksq[WHITE], ksq[BLACK]) <= 1
|
||||||
|| ksq[WHITE] == psq
|
|| ksq[WHITE] == psq
|
||||||
|| ksq[BLACK] == psq
|
|| ksq[BLACK] == psq
|
||||||
|| (us == WHITE && (PawnAttacks[WHITE][psq] & ksq[BLACK])))
|
|| (stm == WHITE && (pawn_attacks_bb(WHITE, psq) & ksq[BLACK])))
|
||||||
result = INVALID;
|
result = INVALID;
|
||||||
|
|
||||||
// Immediate win if a pawn can be promoted without getting captured
|
// Win if the pawn can be promoted without getting captured
|
||||||
else if ( us == WHITE
|
else if ( stm == WHITE
|
||||||
&& rank_of(psq) == RANK_7
|
&& rank_of(psq) == RANK_7
|
||||||
&& ksq[us] != psq + NORTH
|
&& ksq[WHITE] != psq + NORTH
|
||||||
&& ( distance(ksq[~us], psq + NORTH) > 1
|
&& ( distance(ksq[BLACK], psq + NORTH) > 1
|
||||||
|| (PseudoAttacks[KING][ksq[us]] & (psq + NORTH))))
|
|| (distance(ksq[WHITE], psq + NORTH) == 1)))
|
||||||
result = WIN;
|
result = WIN;
|
||||||
|
|
||||||
// Immediate draw if it is a stalemate or a king captures undefended pawn
|
// Draw if it is stalemate or the black king can capture the pawn
|
||||||
else if ( us == BLACK
|
else if ( stm == BLACK
|
||||||
&& ( !(PseudoAttacks[KING][ksq[us]] & ~(PseudoAttacks[KING][ksq[~us]] | PawnAttacks[~us][psq]))
|
&& ( !(attacks_bb<KING>(ksq[BLACK]) & ~(attacks_bb<KING>(ksq[WHITE]) | pawn_attacks_bb(WHITE, psq)))
|
||||||
|| (PseudoAttacks[KING][ksq[us]] & psq & ~PseudoAttacks[KING][ksq[~us]])))
|
|| (attacks_bb<KING>(ksq[BLACK]) & ~attacks_bb<KING>(ksq[WHITE]) & psq)))
|
||||||
result = DRAW;
|
result = DRAW;
|
||||||
|
|
||||||
// Position will be classified later
|
// Position will be classified later
|
||||||
@@ -139,7 +132,6 @@ namespace {
|
|||||||
result = UNKNOWN;
|
result = UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<Color Us>
|
|
||||||
Result KPKPosition::classify(const std::vector<KPKPosition>& db) {
|
Result KPKPosition::classify(const std::vector<KPKPosition>& db) {
|
||||||
|
|
||||||
// White to move: If one move leads to a position classified as WIN, the result
|
// White to move: If one move leads to a position classified as WIN, the result
|
||||||
@@ -151,27 +143,25 @@ namespace {
|
|||||||
// of the current position is DRAW. If all moves lead to positions classified
|
// 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
|
// as WIN, the position is classified as WIN, otherwise the current position is
|
||||||
// classified as UNKNOWN.
|
// classified as UNKNOWN.
|
||||||
|
const Result Good = (stm == WHITE ? WIN : DRAW);
|
||||||
constexpr Color Them = (Us == WHITE ? BLACK : WHITE);
|
const Result Bad = (stm == WHITE ? DRAW : WIN);
|
||||||
constexpr Result Good = (Us == WHITE ? WIN : DRAW);
|
|
||||||
constexpr Result Bad = (Us == WHITE ? DRAW : WIN);
|
|
||||||
|
|
||||||
Result r = INVALID;
|
Result r = INVALID;
|
||||||
Bitboard b = PseudoAttacks[KING][ksq[Us]];
|
Bitboard b = attacks_bb<KING>(ksq[stm]);
|
||||||
|
|
||||||
while (b)
|
while (b)
|
||||||
r |= Us == WHITE ? db[index(Them, ksq[Them] , pop_lsb(&b), psq)]
|
r |= stm == WHITE ? db[index(BLACK, ksq[BLACK] , pop_lsb(&b), psq)]
|
||||||
: db[index(Them, pop_lsb(&b), ksq[Them] , psq)];
|
: db[index(WHITE, pop_lsb(&b), ksq[WHITE], psq)];
|
||||||
|
|
||||||
if (Us == WHITE)
|
if (stm == WHITE)
|
||||||
{
|
{
|
||||||
if (rank_of(psq) < RANK_7) // Single push
|
if (rank_of(psq) < RANK_7) // Single push
|
||||||
r |= db[index(Them, ksq[Them], ksq[Us], psq + NORTH)];
|
r |= db[index(BLACK, ksq[BLACK], ksq[WHITE], psq + NORTH)];
|
||||||
|
|
||||||
if ( rank_of(psq) == RANK_2 // Double push
|
if ( rank_of(psq) == RANK_2 // Double push
|
||||||
&& psq + NORTH != ksq[Us]
|
&& psq + NORTH != ksq[WHITE]
|
||||||
&& psq + NORTH != ksq[Them])
|
&& psq + NORTH != ksq[BLACK])
|
||||||
r |= db[index(Them, ksq[Them], ksq[Us], psq + NORTH + NORTH)];
|
r |= db[index(BLACK, ksq[BLACK], ksq[WHITE], psq + NORTH + NORTH)];
|
||||||
}
|
}
|
||||||
|
|
||||||
return result = r & Good ? Good : r & UNKNOWN ? UNKNOWN : Bad;
|
return result = r & Good ? Good : r & UNKNOWN ? UNKNOWN : Bad;
|
||||||
|
|||||||
+28
-47
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -40,7 +38,7 @@ namespace {
|
|||||||
Bitboard RookTable[0x19000]; // To store rook attacks
|
Bitboard RookTable[0x19000]; // To store rook attacks
|
||||||
Bitboard BishopTable[0x1480]; // To store bishop attacks
|
Bitboard BishopTable[0x1480]; // To store bishop attacks
|
||||||
|
|
||||||
void init_magics(Bitboard table[], Magic magics[], Direction directions[]);
|
void init_magics(PieceType pt, Bitboard table[], Magic magics[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -56,8 +54,9 @@ const std::string Bitboards::pretty(Bitboard b) {
|
|||||||
for (File f = FILE_A; f <= FILE_H; ++f)
|
for (File f = FILE_A; f <= FILE_H; ++f)
|
||||||
s += b & make_square(f, r) ? "| X " : "| ";
|
s += b & make_square(f, r) ? "| X " : "| ";
|
||||||
|
|
||||||
s += "|\n+---+---+---+---+---+---+---+---+\n";
|
s += "| " + std::to_string(1 + r) + "\n+---+---+---+---+---+---+---+---+\n";
|
||||||
}
|
}
|
||||||
|
s += " a b c d e f g h\n";
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
@@ -69,7 +68,7 @@ const std::string Bitboards::pretty(Bitboard b) {
|
|||||||
void Bitboards::init() {
|
void Bitboards::init() {
|
||||||
|
|
||||||
for (unsigned i = 0; i < (1 << 16); ++i)
|
for (unsigned i = 0; i < (1 << 16); ++i)
|
||||||
PopCnt16[i] = std::bitset<16>(i).count();
|
PopCnt16[i] = uint8_t(std::bitset<16>(i).count());
|
||||||
|
|
||||||
for (Square s = SQ_A1; s <= SQ_H8; ++s)
|
for (Square s = SQ_A1; s <= SQ_H8; ++s)
|
||||||
SquareBB[s] = (1ULL << s);
|
SquareBB[s] = (1ULL << s);
|
||||||
@@ -78,36 +77,20 @@ void Bitboards::init() {
|
|||||||
for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
|
for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
|
||||||
SquareDistance[s1][s2] = std::max(distance<File>(s1, s2), distance<Rank>(s1, s2));
|
SquareDistance[s1][s2] = std::max(distance<File>(s1, s2), distance<Rank>(s1, s2));
|
||||||
|
|
||||||
for (Square s = SQ_A1; s <= SQ_H8; ++s)
|
init_magics(ROOK, RookTable, RookMagics);
|
||||||
{
|
init_magics(BISHOP, BishopTable, BishopMagics);
|
||||||
PawnAttacks[WHITE][s] = pawn_attacks_bb<WHITE>(square_bb(s));
|
|
||||||
PawnAttacks[BLACK][s] = pawn_attacks_bb<BLACK>(square_bb(s));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper returning the target bitboard of a step from a square
|
|
||||||
auto landing_square_bb = [&](Square s, int step)
|
|
||||||
{
|
|
||||||
Square to = Square(s + step);
|
|
||||||
return is_ok(to) && distance(s, to) <= 2 ? square_bb(to) : Bitboard(0);
|
|
||||||
};
|
|
||||||
|
|
||||||
for (Square s = SQ_A1; s <= SQ_H8; ++s)
|
|
||||||
{
|
|
||||||
for (int step : {-9, -8, -7, -1, 1, 7, 8, 9} )
|
|
||||||
PseudoAttacks[KING][s] |= landing_square_bb(s, step);
|
|
||||||
|
|
||||||
for (int step : {-17, -15, -10, -6, 6, 10, 15, 17} )
|
|
||||||
PseudoAttacks[KNIGHT][s] |= landing_square_bb(s, step);
|
|
||||||
}
|
|
||||||
|
|
||||||
Direction RookDirections[] = { NORTH, EAST, SOUTH, WEST };
|
|
||||||
Direction BishopDirections[] = { NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST };
|
|
||||||
|
|
||||||
init_magics(RookTable, RookMagics, RookDirections);
|
|
||||||
init_magics(BishopTable, BishopMagics, BishopDirections);
|
|
||||||
|
|
||||||
for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
|
for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
|
||||||
{
|
{
|
||||||
|
PawnAttacks[WHITE][s1] = pawn_attacks_bb<WHITE>(square_bb(s1));
|
||||||
|
PawnAttacks[BLACK][s1] = pawn_attacks_bb<BLACK>(square_bb(s1));
|
||||||
|
|
||||||
|
for (int step : {-9, -8, -7, -1, 1, 7, 8, 9} )
|
||||||
|
PseudoAttacks[KING][s1] |= safe_destination(s1, step);
|
||||||
|
|
||||||
|
for (int step : {-17, -15, -10, -6, 6, 10, 15, 17} )
|
||||||
|
PseudoAttacks[KNIGHT][s1] |= safe_destination(s1, step);
|
||||||
|
|
||||||
PseudoAttacks[QUEEN][s1] = PseudoAttacks[BISHOP][s1] = attacks_bb<BISHOP>(s1, 0);
|
PseudoAttacks[QUEEN][s1] = PseudoAttacks[BISHOP][s1] = attacks_bb<BISHOP>(s1, 0);
|
||||||
PseudoAttacks[QUEEN][s1] |= PseudoAttacks[ ROOK][s1] = attacks_bb< ROOK>(s1, 0);
|
PseudoAttacks[QUEEN][s1] |= PseudoAttacks[ ROOK][s1] = attacks_bb< ROOK>(s1, 0);
|
||||||
|
|
||||||
@@ -121,22 +104,20 @@ void Bitboards::init() {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
Bitboard sliding_attack(Direction directions[], Square sq, Bitboard occupied) {
|
Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) {
|
||||||
|
|
||||||
Bitboard attack = 0;
|
Bitboard attacks = 0;
|
||||||
|
Direction RookDirections[4] = {NORTH, SOUTH, EAST, WEST};
|
||||||
|
Direction BishopDirections[4] = {NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST};
|
||||||
|
|
||||||
for (int i = 0; i < 4; ++i)
|
for(Direction d : (pt == ROOK ? RookDirections : BishopDirections))
|
||||||
for (Square s = sq + directions[i];
|
|
||||||
is_ok(s) && distance(s, s - directions[i]) == 1;
|
|
||||||
s += directions[i])
|
|
||||||
{
|
{
|
||||||
attack |= s;
|
Square s = sq;
|
||||||
|
while(safe_destination(s, d) && !(occupied & s))
|
||||||
if (occupied & s)
|
attacks |= (s += d);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return attack;
|
return attacks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -145,7 +126,7 @@ namespace {
|
|||||||
// www.chessprogramming.org/Magic_Bitboards. In particular, here we use the so
|
// www.chessprogramming.org/Magic_Bitboards. In particular, here we use the so
|
||||||
// called "fancy" approach.
|
// called "fancy" approach.
|
||||||
|
|
||||||
void init_magics(Bitboard table[], Magic magics[], Direction directions[]) {
|
void init_magics(PieceType pt, Bitboard table[], Magic magics[]) {
|
||||||
|
|
||||||
// Optimal PRNG seeds to pick the correct magics in the shortest time
|
// Optimal PRNG seeds to pick the correct magics in the shortest time
|
||||||
int seeds[][RANK_NB] = { { 8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020 },
|
int seeds[][RANK_NB] = { { 8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020 },
|
||||||
@@ -165,7 +146,7 @@ namespace {
|
|||||||
// the number of 1s of the mask. Hence we deduce the size of the shift to
|
// the number of 1s of the mask. Hence we deduce the size of the shift to
|
||||||
// apply to the 64 or 32 bits word to get the index.
|
// apply to the 64 or 32 bits word to get the index.
|
||||||
Magic& m = magics[s];
|
Magic& m = magics[s];
|
||||||
m.mask = sliding_attack(directions, s, 0) & ~edges;
|
m.mask = sliding_attack(pt, s, 0) & ~edges;
|
||||||
m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask);
|
m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask);
|
||||||
|
|
||||||
// Set the offset for the attacks table of the square. We have individual
|
// Set the offset for the attacks table of the square. We have individual
|
||||||
@@ -177,7 +158,7 @@ namespace {
|
|||||||
b = size = 0;
|
b = size = 0;
|
||||||
do {
|
do {
|
||||||
occupancy[size] = b;
|
occupancy[size] = b;
|
||||||
reference[size] = sliding_attack(directions, s, b);
|
reference[size] = sliding_attack(pt, s, b);
|
||||||
|
|
||||||
if (HasPext)
|
if (HasPext)
|
||||||
m.attacks[pext(b, m.mask)] = reference[size];
|
m.attacks[pext(b, m.mask)] = reference[size];
|
||||||
|
|||||||
+89
-36
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -106,10 +104,11 @@ extern Magic RookMagics[SQUARE_NB];
|
|||||||
extern Magic BishopMagics[SQUARE_NB];
|
extern Magic BishopMagics[SQUARE_NB];
|
||||||
|
|
||||||
inline Bitboard square_bb(Square s) {
|
inline Bitboard square_bb(Square s) {
|
||||||
assert(s >= SQ_A1 && s <= SQ_H8);
|
assert(is_ok(s));
|
||||||
return SquareBB[s];
|
return SquareBB[s];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Overloads of bitwise operators between a Bitboard and a Square for testing
|
/// Overloads of bitwise operators between a Bitboard and a Square for testing
|
||||||
/// whether a given bit is set in a bitboard, and for setting and clearing bits.
|
/// whether a given bit is set in a bitboard, and for setting and clearing bits.
|
||||||
|
|
||||||
@@ -123,38 +122,39 @@ inline Bitboard operator&(Square s, Bitboard b) { return b & s; }
|
|||||||
inline Bitboard operator|(Square s, Bitboard b) { return b | s; }
|
inline Bitboard operator|(Square s, Bitboard b) { return b | s; }
|
||||||
inline Bitboard operator^(Square s, Bitboard b) { return b ^ s; }
|
inline Bitboard operator^(Square s, Bitboard b) { return b ^ s; }
|
||||||
|
|
||||||
inline Bitboard operator|(Square s, Square s2) { return square_bb(s) | square_bb(s2); }
|
inline Bitboard operator|(Square s1, Square s2) { return square_bb(s1) | s2; }
|
||||||
|
|
||||||
constexpr bool more_than_one(Bitboard b) {
|
constexpr bool more_than_one(Bitboard b) {
|
||||||
return b & (b - 1);
|
return b & (b - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool opposite_colors(Square s1, Square s2) {
|
|
||||||
return bool(DarkSquares & s1) != bool(DarkSquares & s2);
|
constexpr bool opposite_colors(Square s1, Square s2) {
|
||||||
|
return (s1 + rank_of(s1) + s2 + rank_of(s2)) & 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// rank_bb() and file_bb() return a bitboard representing all the squares on
|
/// rank_bb() and file_bb() return a bitboard representing all the squares on
|
||||||
/// the given file or rank.
|
/// the given file or rank.
|
||||||
|
|
||||||
inline Bitboard rank_bb(Rank r) {
|
constexpr Bitboard rank_bb(Rank r) {
|
||||||
return Rank1BB << (8 * r);
|
return Rank1BB << (8 * r);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Bitboard rank_bb(Square s) {
|
constexpr Bitboard rank_bb(Square s) {
|
||||||
return rank_bb(rank_of(s));
|
return rank_bb(rank_of(s));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Bitboard file_bb(File f) {
|
constexpr Bitboard file_bb(File f) {
|
||||||
return FileABB << f;
|
return FileABB << f;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Bitboard file_bb(Square s) {
|
constexpr Bitboard file_bb(Square s) {
|
||||||
return file_bb(file_of(s));
|
return file_bb(file_of(s));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// shift() moves a bitboard one step along direction D
|
/// shift() moves a bitboard one or two steps as specified by the direction D
|
||||||
|
|
||||||
template<Direction D>
|
template<Direction D>
|
||||||
constexpr Bitboard shift(Bitboard b) {
|
constexpr Bitboard shift(Bitboard b) {
|
||||||
@@ -176,6 +176,12 @@ constexpr Bitboard pawn_attacks_bb(Bitboard b) {
|
|||||||
: shift<SOUTH_WEST>(b) | shift<SOUTH_EAST>(b);
|
: shift<SOUTH_WEST>(b) | shift<SOUTH_EAST>(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline Bitboard pawn_attacks_bb(Color c, Square s) {
|
||||||
|
|
||||||
|
assert(is_ok(s));
|
||||||
|
return PawnAttacks[c][s];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// pawn_double_attacks_bb() returns the squares doubly attacked by pawns of the
|
/// pawn_double_attacks_bb() returns the squares doubly attacked by pawns of the
|
||||||
/// given color from the squares in the given bitboard.
|
/// given color from the squares in the given bitboard.
|
||||||
@@ -188,19 +194,33 @@ constexpr Bitboard pawn_double_attacks_bb(Bitboard b) {
|
|||||||
|
|
||||||
|
|
||||||
/// adjacent_files_bb() returns a bitboard representing all the squares on the
|
/// adjacent_files_bb() returns a bitboard representing all the squares on the
|
||||||
/// adjacent files of the given one.
|
/// adjacent files of a given square.
|
||||||
|
|
||||||
inline Bitboard adjacent_files_bb(Square s) {
|
constexpr Bitboard adjacent_files_bb(Square s) {
|
||||||
return shift<EAST>(file_bb(s)) | shift<WEST>(file_bb(s));
|
return shift<EAST>(file_bb(s)) | shift<WEST>(file_bb(s));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// between_bb() returns squares that are linearly between the given squares
|
/// line_bb() returns a bitboard representing an entire line (from board edge
|
||||||
/// If the given squares are not on a same file/rank/diagonal, return 0.
|
/// to board edge) that intersects the two given squares. If the given squares
|
||||||
|
/// are not on a same file/rank/diagonal, the function returns 0. For instance,
|
||||||
|
/// line_bb(SQ_C4, SQ_F7) will return a bitboard with the A2-G8 diagonal.
|
||||||
|
|
||||||
|
inline Bitboard line_bb(Square s1, Square s2) {
|
||||||
|
|
||||||
|
assert(is_ok(s1) && is_ok(s2));
|
||||||
|
return LineBB[s1][s2];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// between_bb() returns a bitboard representing squares that are linearly
|
||||||
|
/// between the two given squares (excluding the given squares). If the given
|
||||||
|
/// squares are not on a same file/rank/diagonal, we return 0. For instance,
|
||||||
|
/// between_bb(SQ_C4, SQ_F7) will return a bitboard with squares D5 and E6.
|
||||||
|
|
||||||
inline Bitboard between_bb(Square s1, Square s2) {
|
inline Bitboard between_bb(Square s1, Square s2) {
|
||||||
return LineBB[s1][s2] & ( (AllSquares << (s1 + (s1 < s2)))
|
Bitboard b = line_bb(s1, s2) & ((AllSquares << s1) ^ (AllSquares << s2));
|
||||||
^(AllSquares << (s2 + !(s1 < s2))));
|
return b & (b - 1); //exclude lsb
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -208,25 +228,25 @@ inline Bitboard between_bb(Square s1, Square s2) {
|
|||||||
/// in front of the given one, from the point of view of the given color. For instance,
|
/// in front of the given one, from the point of view of the given color. For instance,
|
||||||
/// forward_ranks_bb(BLACK, SQ_D3) will return the 16 squares on ranks 1 and 2.
|
/// forward_ranks_bb(BLACK, SQ_D3) will return the 16 squares on ranks 1 and 2.
|
||||||
|
|
||||||
inline Bitboard forward_ranks_bb(Color c, Square s) {
|
constexpr Bitboard forward_ranks_bb(Color c, Square s) {
|
||||||
return c == WHITE ? ~Rank1BB << 8 * (rank_of(s) - RANK_1)
|
return c == WHITE ? ~Rank1BB << 8 * relative_rank(WHITE, s)
|
||||||
: ~Rank8BB >> 8 * (RANK_8 - rank_of(s));
|
: ~Rank8BB >> 8 * relative_rank(BLACK, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// forward_file_bb() returns a bitboard representing all the squares along the
|
/// forward_file_bb() returns a bitboard representing all the squares along the
|
||||||
/// line in front of the given one, from the point of view of the given color.
|
/// line in front of the given one, from the point of view of the given color.
|
||||||
|
|
||||||
inline Bitboard forward_file_bb(Color c, Square s) {
|
constexpr Bitboard forward_file_bb(Color c, Square s) {
|
||||||
return forward_ranks_bb(c, s) & file_bb(s);
|
return forward_ranks_bb(c, s) & file_bb(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// pawn_attack_span() returns a bitboard representing all the squares that can
|
/// pawn_attack_span() returns a bitboard representing all the squares that can
|
||||||
/// be attacked by a pawn of the given color when it moves along its file,
|
/// be attacked by a pawn of the given color when it moves along its file, starting
|
||||||
/// starting from the given square.
|
/// from the given square.
|
||||||
|
|
||||||
inline Bitboard pawn_attack_span(Color c, Square s) {
|
constexpr Bitboard pawn_attack_span(Color c, Square s) {
|
||||||
return forward_ranks_bb(c, s) & adjacent_files_bb(s);
|
return forward_ranks_bb(c, s) & adjacent_files_bb(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,8 +254,8 @@ inline Bitboard pawn_attack_span(Color c, Square s) {
|
|||||||
/// passed_pawn_span() returns a bitboard which can be used to test if a pawn of
|
/// passed_pawn_span() returns a bitboard which can be used to test if a pawn of
|
||||||
/// the given color and on the given square is a passed pawn.
|
/// the given color and on the given square is a passed pawn.
|
||||||
|
|
||||||
inline Bitboard passed_pawn_span(Color c, Square s) {
|
constexpr Bitboard passed_pawn_span(Color c, Square s) {
|
||||||
return forward_ranks_bb(c, s) & (adjacent_files_bb(s) | file_bb(s));
|
return pawn_attack_span(c, s) | forward_file_bb(c, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -243,7 +263,7 @@ inline Bitboard passed_pawn_span(Color c, Square s) {
|
|||||||
/// straight or on a diagonal line.
|
/// straight or on a diagonal line.
|
||||||
|
|
||||||
inline bool aligned(Square s1, Square s2, Square s3) {
|
inline bool aligned(Square s1, Square s2, Square s3) {
|
||||||
return LineBB[s1][s2] & s3;
|
return line_bb(s1, s2) & s3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -255,23 +275,53 @@ template<> inline int distance<File>(Square x, Square y) { return std::abs(file_
|
|||||||
template<> inline int distance<Rank>(Square x, Square y) { return std::abs(rank_of(x) - rank_of(y)); }
|
template<> inline int distance<Rank>(Square x, Square y) { return std::abs(rank_of(x) - rank_of(y)); }
|
||||||
template<> inline int distance<Square>(Square x, Square y) { return SquareDistance[x][y]; }
|
template<> inline int distance<Square>(Square x, Square y) { return SquareDistance[x][y]; }
|
||||||
|
|
||||||
template<class T> constexpr const T& clamp(const T& v, const T& lo, const T& hi) {
|
inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); }
|
||||||
return v < lo ? lo : v > hi ? hi : v;
|
inline int edge_distance(Rank r) { return std::min(r, Rank(RANK_8 - r)); }
|
||||||
|
|
||||||
|
|
||||||
|
/// safe_destination() returns the bitboard of target square for the given step
|
||||||
|
/// from the given square. If the step is off the board, returns empty bitboard.
|
||||||
|
|
||||||
|
inline Bitboard safe_destination(Square s, int step)
|
||||||
|
{
|
||||||
|
Square to = Square(s + step);
|
||||||
|
return is_ok(to) && distance(s, to) <= 2 ? square_bb(to) : Bitboard(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// attacks_bb() returns a bitboard representing all the squares attacked by a
|
|
||||||
/// piece of type Pt (bishop or rook) placed on 's'.
|
/// attacks_bb(Square) returns the pseudo attacks of the give piece type
|
||||||
|
/// assuming an empty board.
|
||||||
|
|
||||||
|
template<PieceType Pt>
|
||||||
|
inline Bitboard attacks_bb(Square s) {
|
||||||
|
|
||||||
|
assert((Pt != PAWN) && (is_ok(s)));
|
||||||
|
|
||||||
|
return PseudoAttacks[Pt][s];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// attacks_bb(Square, Bitboard) returns the attacks by the given piece
|
||||||
|
/// assuming the board is occupied according to the passed Bitboard.
|
||||||
|
/// Sliding piece attacks do not continue passed an occupied square.
|
||||||
|
|
||||||
template<PieceType Pt>
|
template<PieceType Pt>
|
||||||
inline Bitboard attacks_bb(Square s, Bitboard occupied) {
|
inline Bitboard attacks_bb(Square s, Bitboard occupied) {
|
||||||
|
|
||||||
const Magic& m = Pt == ROOK ? RookMagics[s] : BishopMagics[s];
|
assert((Pt != PAWN) && (is_ok(s)));
|
||||||
return m.attacks[m.index(occupied)];
|
|
||||||
|
switch (Pt)
|
||||||
|
{
|
||||||
|
case BISHOP: return BishopMagics[s].attacks[BishopMagics[s].index(occupied)];
|
||||||
|
case ROOK : return RookMagics[s].attacks[ RookMagics[s].index(occupied)];
|
||||||
|
case QUEEN : return attacks_bb<BISHOP>(s, occupied) | attacks_bb<ROOK>(s, occupied);
|
||||||
|
default : return PseudoAttacks[Pt][s];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) {
|
inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) {
|
||||||
|
|
||||||
assert(pt != PAWN);
|
assert((pt != PAWN) && (is_ok(s)));
|
||||||
|
|
||||||
switch (pt)
|
switch (pt)
|
||||||
{
|
{
|
||||||
@@ -376,14 +426,17 @@ inline Square msb(Bitboard b) {
|
|||||||
/// pop_lsb() finds and clears the least significant bit in a non-zero bitboard
|
/// pop_lsb() finds and clears the least significant bit in a non-zero bitboard
|
||||||
|
|
||||||
inline Square pop_lsb(Bitboard* b) {
|
inline Square pop_lsb(Bitboard* b) {
|
||||||
|
assert(*b);
|
||||||
const Square s = lsb(*b);
|
const Square s = lsb(*b);
|
||||||
*b &= *b - 1;
|
*b &= *b - 1;
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// frontmost_sq() returns the most advanced square for the given color
|
/// frontmost_sq() returns the most advanced square for the given color,
|
||||||
|
/// requires a non-zero bitboard.
|
||||||
inline Square frontmost_sq(Color c, Bitboard b) {
|
inline Square frontmost_sq(Color c, Bitboard b) {
|
||||||
|
assert(b);
|
||||||
return c == WHITE ? msb(b) : lsb(b);
|
return c == WHITE ? msb(b) : lsb(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+231
-294
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -24,42 +22,25 @@
|
|||||||
#include "endgame.h"
|
#include "endgame.h"
|
||||||
#include "movegen.h"
|
#include "movegen.h"
|
||||||
|
|
||||||
using std::string;
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// Table used to drive the king towards the edge of the board
|
// Used to drive the king towards the edge of the board
|
||||||
// in KX vs K and KQ vs KR endgames.
|
// in KX vs K and KQ vs KR endgames.
|
||||||
constexpr int PushToEdges[SQUARE_NB] = {
|
// Values range from 27 (center squares) to 90 (in the corners)
|
||||||
100, 90, 80, 70, 70, 80, 90, 100,
|
inline int push_to_edge(Square s) {
|
||||||
90, 70, 60, 50, 50, 60, 70, 90,
|
int rd = edge_distance(rank_of(s)), fd = edge_distance(file_of(s));
|
||||||
80, 60, 40, 30, 30, 40, 60, 80,
|
return 90 - (7 * fd * fd / 2 + 7 * rd * rd / 2);
|
||||||
70, 50, 30, 20, 20, 30, 50, 70,
|
}
|
||||||
70, 50, 30, 20, 20, 30, 50, 70,
|
|
||||||
80, 60, 40, 30, 30, 40, 60, 80,
|
|
||||||
90, 70, 60, 50, 50, 60, 70, 90,
|
|
||||||
100, 90, 80, 70, 70, 80, 90, 100
|
|
||||||
};
|
|
||||||
|
|
||||||
// Table used to drive the king towards a corner square of the
|
// Used to drive the king towards A1H8 corners in KBN vs K endgames.
|
||||||
// right color in KBN vs K endgames.
|
// Values range from 0 on A8H1 diagonal to 7 in A1H8 corners
|
||||||
constexpr int PushToCorners[SQUARE_NB] = {
|
inline int push_to_corner(Square s) {
|
||||||
6400, 6080, 5760, 5440, 5120, 4800, 4480, 4160,
|
return abs(7 - rank_of(s) - file_of(s));
|
||||||
6080, 5760, 5440, 5120, 4800, 4480, 4160, 4480,
|
}
|
||||||
5760, 5440, 4960, 4480, 4480, 4000, 4480, 4800,
|
|
||||||
5440, 5120, 4480, 3840, 3520, 4480, 4800, 5120,
|
|
||||||
5120, 4800, 4480, 3520, 3840, 4480, 5120, 5440,
|
|
||||||
4800, 4480, 4000, 4480, 4480, 4960, 5440, 5760,
|
|
||||||
4480, 4160, 4480, 4800, 5120, 5440, 5760, 6080,
|
|
||||||
4160, 4480, 4800, 5120, 5440, 5760, 6080, 6400
|
|
||||||
};
|
|
||||||
|
|
||||||
// Tables used to drive a piece towards or away from another piece
|
// Drive a piece close to or away from another piece
|
||||||
constexpr int PushClose[8] = { 0, 0, 100, 80, 60, 40, 20, 10 };
|
inline int push_close(Square s1, Square s2) { return 140 - 20 * distance(s1, s2); }
|
||||||
constexpr int PushAway [8] = { 0, 5, 20, 40, 60, 80, 90, 100 };
|
inline int push_away(Square s1, Square s2) { return 120 - push_close(s1, s2); }
|
||||||
|
|
||||||
// Pawn Rank based scaling factors used in KRPPKRP endgame
|
|
||||||
constexpr int KRPPKRPScaleFactors[RANK_NB] = { 0, 9, 10, 14, 21, 44, 0, 0 };
|
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
bool verify_material(const Position& pos, Color c, Value npm, int pawnsCnt) {
|
bool verify_material(const Position& pos, Color c, Value npm, int pawnsCnt) {
|
||||||
@@ -74,9 +55,9 @@ namespace {
|
|||||||
assert(pos.count<PAWN>(strongSide) == 1);
|
assert(pos.count<PAWN>(strongSide) == 1);
|
||||||
|
|
||||||
if (file_of(pos.square<PAWN>(strongSide)) >= FILE_E)
|
if (file_of(pos.square<PAWN>(strongSide)) >= FILE_E)
|
||||||
sq = Square(int(sq) ^ 7); // Mirror SQ_H1 -> SQ_A1
|
sq = flip_file(sq);
|
||||||
|
|
||||||
return strongSide == WHITE ? sq : ~sq;
|
return strongSide == WHITE ? sq : flip_rank(sq);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
@@ -98,8 +79,6 @@ namespace Endgames {
|
|||||||
add<KQKR>("KQKR");
|
add<KQKR>("KQKR");
|
||||||
add<KNNKP>("KNNKP");
|
add<KNNKP>("KNNKP");
|
||||||
|
|
||||||
add<KNPK>("KNPK");
|
|
||||||
add<KNPKB>("KNPKB");
|
|
||||||
add<KRPKR>("KRPKR");
|
add<KRPKR>("KRPKR");
|
||||||
add<KRPKB>("KRPKB");
|
add<KRPKB>("KRPKB");
|
||||||
add<KBPKB>("KBPKB");
|
add<KBPKB>("KBPKB");
|
||||||
@@ -124,20 +103,20 @@ Value Endgame<KXK>::operator()(const Position& pos) const {
|
|||||||
if (pos.side_to_move() == weakSide && !MoveList<LEGAL>(pos).size())
|
if (pos.side_to_move() == weakSide && !MoveList<LEGAL>(pos).size())
|
||||||
return VALUE_DRAW;
|
return VALUE_DRAW;
|
||||||
|
|
||||||
Square winnerKSq = pos.square<KING>(strongSide);
|
Square strongKing = pos.square<KING>(strongSide);
|
||||||
Square loserKSq = pos.square<KING>(weakSide);
|
Square weakKing = pos.square<KING>(weakSide);
|
||||||
|
|
||||||
Value result = pos.non_pawn_material(strongSide)
|
Value result = pos.non_pawn_material(strongSide)
|
||||||
+ pos.count<PAWN>(strongSide) * PawnValueEg
|
+ pos.count<PAWN>(strongSide) * PawnValueEg
|
||||||
+ PushToEdges[loserKSq]
|
+ push_to_edge(weakKing)
|
||||||
+ PushClose[distance(winnerKSq, loserKSq)];
|
+ push_close(strongKing, weakKing);
|
||||||
|
|
||||||
if ( pos.count<QUEEN>(strongSide)
|
if ( pos.count<QUEEN>(strongSide)
|
||||||
|| pos.count<ROOK>(strongSide)
|
|| pos.count<ROOK>(strongSide)
|
||||||
||(pos.count<BISHOP>(strongSide) && pos.count<KNIGHT>(strongSide))
|
||(pos.count<BISHOP>(strongSide) && pos.count<KNIGHT>(strongSide))
|
||||||
|| ( (pos.pieces(strongSide, BISHOP) & ~DarkSquares)
|
|| ( (pos.pieces(strongSide, BISHOP) & ~DarkSquares)
|
||||||
&& (pos.pieces(strongSide, BISHOP) & DarkSquares)))
|
&& (pos.pieces(strongSide, BISHOP) & DarkSquares)))
|
||||||
result = std::min(result + VALUE_KNOWN_WIN, VALUE_MATE_IN_MAX_PLY - 1);
|
result = std::min(result + VALUE_KNOWN_WIN, VALUE_TB_WIN_IN_MAX_PLY - 1);
|
||||||
|
|
||||||
return strongSide == pos.side_to_move() ? result : -result;
|
return strongSide == pos.side_to_move() ? result : -result;
|
||||||
}
|
}
|
||||||
@@ -151,18 +130,18 @@ Value Endgame<KBNK>::operator()(const Position& pos) const {
|
|||||||
assert(verify_material(pos, strongSide, KnightValueMg + BishopValueMg, 0));
|
assert(verify_material(pos, strongSide, KnightValueMg + BishopValueMg, 0));
|
||||||
assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
|
assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
|
||||||
|
|
||||||
Square winnerKSq = pos.square<KING>(strongSide);
|
Square strongKing = pos.square<KING>(strongSide);
|
||||||
Square loserKSq = pos.square<KING>(weakSide);
|
Square strongBishop = pos.square<BISHOP>(strongSide);
|
||||||
Square bishopSq = pos.square<BISHOP>(strongSide);
|
Square weakKing = pos.square<KING>(weakSide);
|
||||||
|
|
||||||
// If our bishop does not attack A1/H8, we flip the enemy king square
|
// If our bishop does not attack A1/H8, we flip the enemy king square
|
||||||
// to drive to opposite corners (A8/H1).
|
// to drive to opposite corners (A8/H1).
|
||||||
|
|
||||||
Value result = VALUE_KNOWN_WIN
|
Value result = (VALUE_KNOWN_WIN + 3520)
|
||||||
+ PushClose[distance(winnerKSq, loserKSq)]
|
+ push_close(strongKing, weakKing)
|
||||||
+ PushToCorners[opposite_colors(bishopSq, SQ_A1) ? ~loserKSq : loserKSq];
|
+ 420 * push_to_corner(opposite_colors(strongBishop, SQ_A1) ? flip_file(weakKing) : weakKing);
|
||||||
|
|
||||||
assert(abs(result) < VALUE_MATE_IN_MAX_PLY);
|
assert(abs(result) < VALUE_TB_WIN_IN_MAX_PLY);
|
||||||
return strongSide == pos.side_to_move() ? result : -result;
|
return strongSide == pos.side_to_move() ? result : -result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,16 +154,16 @@ Value Endgame<KPK>::operator()(const Position& pos) const {
|
|||||||
assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
|
assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
|
||||||
|
|
||||||
// Assume strongSide is white and the pawn is on files A-D
|
// Assume strongSide is white and the pawn is on files A-D
|
||||||
Square wksq = normalize(pos, strongSide, pos.square<KING>(strongSide));
|
Square strongKing = normalize(pos, strongSide, pos.square<KING>(strongSide));
|
||||||
Square bksq = normalize(pos, strongSide, pos.square<KING>(weakSide));
|
Square strongPawn = normalize(pos, strongSide, pos.square<PAWN>(strongSide));
|
||||||
Square psq = 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;
|
Color us = strongSide == pos.side_to_move() ? WHITE : BLACK;
|
||||||
|
|
||||||
if (!Bitbases::probe(wksq, psq, bksq, us))
|
if (!Bitbases::probe(strongKing, strongPawn, weakKing, us))
|
||||||
return VALUE_DRAW;
|
return VALUE_DRAW;
|
||||||
|
|
||||||
Value result = VALUE_KNOWN_WIN + PawnValueEg + Value(rank_of(psq));
|
Value result = VALUE_KNOWN_WIN + PawnValueEg + Value(rank_of(strongPawn));
|
||||||
|
|
||||||
return strongSide == pos.side_to_move() ? result : -result;
|
return strongSide == pos.side_to_move() ? result : -result;
|
||||||
}
|
}
|
||||||
@@ -200,36 +179,35 @@ Value Endgame<KRKP>::operator()(const Position& pos) const {
|
|||||||
assert(verify_material(pos, strongSide, RookValueMg, 0));
|
assert(verify_material(pos, strongSide, RookValueMg, 0));
|
||||||
assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
|
assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
|
||||||
|
|
||||||
Square wksq = relative_square(strongSide, pos.square<KING>(strongSide));
|
Square strongKing = pos.square<KING>(strongSide);
|
||||||
Square bksq = relative_square(strongSide, pos.square<KING>(weakSide));
|
Square weakKing = pos.square<KING>(weakSide);
|
||||||
Square rsq = relative_square(strongSide, pos.square<ROOK>(strongSide));
|
Square strongRook = pos.square<ROOK>(strongSide);
|
||||||
Square psq = relative_square(strongSide, pos.square<PAWN>(weakSide));
|
Square weakPawn = pos.square<PAWN>(weakSide);
|
||||||
|
Square queeningSquare = make_square(file_of(weakPawn), relative_rank(weakSide, RANK_8));
|
||||||
Square queeningSq = make_square(file_of(psq), RANK_1);
|
|
||||||
Value result;
|
Value result;
|
||||||
|
|
||||||
// If the stronger side's king is in front of the pawn, it's a win
|
// If the stronger side's king is in front of the pawn, it's a win
|
||||||
if (forward_file_bb(WHITE, wksq) & psq)
|
if (forward_file_bb(strongSide, strongKing) & weakPawn)
|
||||||
result = RookValueEg - distance(wksq, psq);
|
result = RookValueEg - distance(strongKing, weakPawn);
|
||||||
|
|
||||||
// If the weaker side's king is too far from the pawn and the rook,
|
// If the weaker side's king is too far from the pawn and the rook,
|
||||||
// it's a win.
|
// it's a win.
|
||||||
else if ( distance(bksq, psq) >= 3 + (pos.side_to_move() == weakSide)
|
else if ( distance(weakKing, weakPawn) >= 3 + (pos.side_to_move() == weakSide)
|
||||||
&& distance(bksq, rsq) >= 3)
|
&& distance(weakKing, strongRook) >= 3)
|
||||||
result = RookValueEg - distance(wksq, psq);
|
result = RookValueEg - distance(strongKing, weakPawn);
|
||||||
|
|
||||||
// If the pawn is far advanced and supported by the defending king,
|
// If the pawn is far advanced and supported by the defending king,
|
||||||
// the position is drawish
|
// the position is drawish
|
||||||
else if ( rank_of(bksq) <= RANK_3
|
else if ( relative_rank(strongSide, weakKing) <= RANK_3
|
||||||
&& distance(bksq, psq) == 1
|
&& distance(weakKing, weakPawn) == 1
|
||||||
&& rank_of(wksq) >= RANK_4
|
&& relative_rank(strongSide, strongKing) >= RANK_4
|
||||||
&& distance(wksq, psq) > 2 + (pos.side_to_move() == strongSide))
|
&& distance(strongKing, weakPawn) > 2 + (pos.side_to_move() == strongSide))
|
||||||
result = Value(80) - 8 * distance(wksq, psq);
|
result = Value(80) - 8 * distance(strongKing, weakPawn);
|
||||||
|
|
||||||
else
|
else
|
||||||
result = Value(200) - 8 * ( distance(wksq, psq + SOUTH)
|
result = Value(200) - 8 * ( distance(strongKing, weakPawn + pawn_push(weakSide))
|
||||||
- distance(bksq, psq + SOUTH)
|
- distance(weakKing, weakPawn + pawn_push(weakSide))
|
||||||
- distance(psq, queeningSq));
|
- distance(weakPawn, queeningSquare));
|
||||||
|
|
||||||
return strongSide == pos.side_to_move() ? result : -result;
|
return strongSide == pos.side_to_move() ? result : -result;
|
||||||
}
|
}
|
||||||
@@ -243,7 +221,7 @@ Value Endgame<KRKB>::operator()(const Position& pos) const {
|
|||||||
assert(verify_material(pos, strongSide, RookValueMg, 0));
|
assert(verify_material(pos, strongSide, RookValueMg, 0));
|
||||||
assert(verify_material(pos, weakSide, BishopValueMg, 0));
|
assert(verify_material(pos, weakSide, BishopValueMg, 0));
|
||||||
|
|
||||||
Value result = Value(PushToEdges[pos.square<KING>(weakSide)]);
|
Value result = Value(push_to_edge(pos.square<KING>(weakSide)));
|
||||||
return strongSide == pos.side_to_move() ? result : -result;
|
return strongSide == pos.side_to_move() ? result : -result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,9 +234,9 @@ Value Endgame<KRKN>::operator()(const Position& pos) const {
|
|||||||
assert(verify_material(pos, strongSide, RookValueMg, 0));
|
assert(verify_material(pos, strongSide, RookValueMg, 0));
|
||||||
assert(verify_material(pos, weakSide, KnightValueMg, 0));
|
assert(verify_material(pos, weakSide, KnightValueMg, 0));
|
||||||
|
|
||||||
Square bksq = pos.square<KING>(weakSide);
|
Square weakKing = pos.square<KING>(weakSide);
|
||||||
Square bnsq = pos.square<KNIGHT>(weakSide);
|
Square weakKnight = pos.square<KNIGHT>(weakSide);
|
||||||
Value result = Value(PushToEdges[bksq] + PushAway[distance(bksq, bnsq)]);
|
Value result = Value(push_to_edge(weakKing) + push_away(weakKing, weakKnight));
|
||||||
return strongSide == pos.side_to_move() ? result : -result;
|
return strongSide == pos.side_to_move() ? result : -result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,22 +251,22 @@ Value Endgame<KQKP>::operator()(const Position& pos) const {
|
|||||||
assert(verify_material(pos, strongSide, QueenValueMg, 0));
|
assert(verify_material(pos, strongSide, QueenValueMg, 0));
|
||||||
assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
|
assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
|
||||||
|
|
||||||
Square winnerKSq = pos.square<KING>(strongSide);
|
Square strongKing = pos.square<KING>(strongSide);
|
||||||
Square loserKSq = pos.square<KING>(weakSide);
|
Square weakKing = pos.square<KING>(weakSide);
|
||||||
Square pawnSq = pos.square<PAWN>(weakSide);
|
Square weakPawn = pos.square<PAWN>(weakSide);
|
||||||
|
|
||||||
Value result = Value(PushClose[distance(winnerKSq, loserKSq)]);
|
Value result = Value(push_close(strongKing, weakKing));
|
||||||
|
|
||||||
if ( relative_rank(weakSide, pawnSq) != RANK_7
|
if ( relative_rank(weakSide, weakPawn) != RANK_7
|
||||||
|| distance(loserKSq, pawnSq) != 1
|
|| distance(weakKing, weakPawn) != 1
|
||||||
|| !((FileABB | FileCBB | FileFBB | FileHBB) & pawnSq))
|
|| ((FileBBB | FileDBB | FileEBB | FileGBB) & weakPawn))
|
||||||
result += QueenValueEg - PawnValueEg;
|
result += QueenValueEg - PawnValueEg;
|
||||||
|
|
||||||
return strongSide == pos.side_to_move() ? result : -result;
|
return strongSide == pos.side_to_move() ? result : -result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// KQ vs KR. This is almost identical to KX vs K: We give the attacking
|
/// 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
|
/// 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
|
/// 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.
|
/// the defending side in the search, this is usually sufficient to win KQ vs KR.
|
||||||
@@ -298,28 +276,32 @@ Value Endgame<KQKR>::operator()(const Position& pos) const {
|
|||||||
assert(verify_material(pos, strongSide, QueenValueMg, 0));
|
assert(verify_material(pos, strongSide, QueenValueMg, 0));
|
||||||
assert(verify_material(pos, weakSide, RookValueMg, 0));
|
assert(verify_material(pos, weakSide, RookValueMg, 0));
|
||||||
|
|
||||||
Square winnerKSq = pos.square<KING>(strongSide);
|
Square strongKing = pos.square<KING>(strongSide);
|
||||||
Square loserKSq = pos.square<KING>(weakSide);
|
Square weakKing = pos.square<KING>(weakSide);
|
||||||
|
|
||||||
Value result = QueenValueEg
|
Value result = QueenValueEg
|
||||||
- RookValueEg
|
- RookValueEg
|
||||||
+ PushToEdges[loserKSq]
|
+ push_to_edge(weakKing)
|
||||||
+ PushClose[distance(winnerKSq, loserKSq)];
|
+ push_close(strongKing, weakKing);
|
||||||
|
|
||||||
return strongSide == pos.side_to_move() ? result : -result;
|
return strongSide == pos.side_to_move() ? result : -result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// KNN vs KP. Simply push the opposing king to the corner
|
/// 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<>
|
template<>
|
||||||
Value Endgame<KNNKP>::operator()(const Position& pos) const {
|
Value Endgame<KNNKP>::operator()(const Position& pos) const {
|
||||||
|
|
||||||
assert(verify_material(pos, strongSide, 2 * KnightValueMg, 0));
|
assert(verify_material(pos, strongSide, 2 * KnightValueMg, 0));
|
||||||
assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
|
assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
|
||||||
|
|
||||||
Value result = 2 * KnightValueEg
|
Square weakKing = pos.square<KING>(weakSide);
|
||||||
- PawnValueEg
|
Square weakPawn = pos.square<PAWN>(weakSide);
|
||||||
+ PushToEdges[pos.square<KING>(weakSide)];
|
|
||||||
|
Value result = PawnValueEg
|
||||||
|
+ 2 * push_to_edge(weakKing)
|
||||||
|
- 10 * relative_rank(weakSide, weakPawn);
|
||||||
|
|
||||||
return strongSide == pos.side_to_move() ? result : -result;
|
return strongSide == pos.side_to_move() ? result : -result;
|
||||||
}
|
}
|
||||||
@@ -342,51 +324,47 @@ ScaleFactor Endgame<KBPsK>::operator()(const Position& pos) const {
|
|||||||
// No assertions about the material of weakSide, because we want draws to
|
// No assertions about the material of weakSide, because we want draws to
|
||||||
// be detected even when the weaker side has some pawns.
|
// be detected even when the weaker side has some pawns.
|
||||||
|
|
||||||
Bitboard pawns = pos.pieces(strongSide, PAWN);
|
Bitboard strongPawns = pos.pieces(strongSide, PAWN);
|
||||||
File pawnsFile = file_of(lsb(pawns));
|
Bitboard allPawns = pos.pieces(PAWN);
|
||||||
|
|
||||||
// All pawns are on a single rook file?
|
Square strongBishop = pos.square<BISHOP>(strongSide);
|
||||||
if ( (pawnsFile == FILE_A || pawnsFile == FILE_H)
|
Square weakKing = pos.square<KING>(weakSide);
|
||||||
&& !(pawns & ~file_bb(pawnsFile)))
|
Square strongKing = pos.square<KING>(strongSide);
|
||||||
|
|
||||||
|
// All strongSide pawns are on a single rook file?
|
||||||
|
if (!(strongPawns & ~FileABB) || !(strongPawns & ~FileHBB))
|
||||||
{
|
{
|
||||||
Square bishopSq = pos.square<BISHOP>(strongSide);
|
Square queeningSquare = relative_square(strongSide, make_square(file_of(lsb(strongPawns)), RANK_8));
|
||||||
Square queeningSq = relative_square(strongSide, make_square(pawnsFile, RANK_8));
|
|
||||||
Square kingSq = pos.square<KING>(weakSide);
|
|
||||||
|
|
||||||
if ( opposite_colors(queeningSq, bishopSq)
|
if ( opposite_colors(queeningSquare, strongBishop)
|
||||||
&& distance(queeningSq, kingSq) <= 1)
|
&& distance(queeningSquare, weakKing) <= 1)
|
||||||
return SCALE_FACTOR_DRAW;
|
return SCALE_FACTOR_DRAW;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If all the pawns are on the same B or G file, then it's potentially a draw
|
// If all the pawns are on the same B or G file, then it's potentially a draw
|
||||||
if ( (pawnsFile == FILE_B || pawnsFile == FILE_G)
|
if ((!(allPawns & ~FileBBB) || !(allPawns & ~FileGBB))
|
||||||
&& !(pos.pieces(PAWN) & ~file_bb(pawnsFile))
|
|
||||||
&& pos.non_pawn_material(weakSide) == 0
|
&& pos.non_pawn_material(weakSide) == 0
|
||||||
&& pos.count<PAWN>(weakSide) >= 1)
|
&& pos.count<PAWN>(weakSide) >= 1)
|
||||||
{
|
{
|
||||||
// Get weakSide pawn that is closest to the home rank
|
// Get the least advanced weakSide pawn
|
||||||
Square weakPawnSq = frontmost_sq(strongSide, pos.pieces(weakSide, PAWN));
|
Square weakPawn = frontmost_sq(strongSide, pos.pieces(weakSide, PAWN));
|
||||||
|
|
||||||
Square strongKingSq = pos.square<KING>(strongSide);
|
|
||||||
Square weakKingSq = pos.square<KING>(weakSide);
|
|
||||||
Square bishopSq = pos.square<BISHOP>(strongSide);
|
|
||||||
|
|
||||||
// There's potential for a draw if our pawn is blocked on the 7th rank,
|
// 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
|
// the bishop cannot attack it or they only have one pawn left.
|
||||||
if ( relative_rank(strongSide, weakPawnSq) == RANK_7
|
if ( relative_rank(strongSide, weakPawn) == RANK_7
|
||||||
&& (pos.pieces(strongSide, PAWN) & (weakPawnSq + pawn_push(weakSide)))
|
&& (strongPawns & (weakPawn + pawn_push(weakSide)))
|
||||||
&& (opposite_colors(bishopSq, weakPawnSq) || pos.count<PAWN>(strongSide) == 1))
|
&& (opposite_colors(strongBishop, weakPawn) || !more_than_one(strongPawns)))
|
||||||
{
|
{
|
||||||
int strongKingDist = distance(weakPawnSq, strongKingSq);
|
int strongKingDist = distance(weakPawn, strongKing);
|
||||||
int weakKingDist = distance(weakPawnSq, weakKingSq);
|
int weakKingDist = distance(weakPawn, weakKing);
|
||||||
|
|
||||||
// It's a draw if the weak king is on its back two ranks, within 2
|
// 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
|
// squares of the blocking pawn and the strong king is not
|
||||||
// closer. (I think this rule only fails in practically
|
// closer. (I think this rule only fails in practically
|
||||||
// unreachable positions such as 5k1K/6p1/6P1/8/8/3B4/8/8 w
|
// unreachable positions such as 5k1K/6p1/6P1/8/8/3B4/8/8 w
|
||||||
// and positions where qsearch will immediately correct the
|
// and positions where qsearch will immediately correct the
|
||||||
// problem such as 8/4k1p1/6P1/1K6/3B4/8/8/8 w)
|
// problem such as 8/4k1p1/6P1/1K6/3B4/8/8/8 w).
|
||||||
if ( relative_rank(strongSide, weakKingSq) >= RANK_7
|
if ( relative_rank(strongSide, weakKing) >= RANK_7
|
||||||
&& weakKingDist <= 2
|
&& weakKingDist <= 2
|
||||||
&& weakKingDist <= strongKingDist)
|
&& weakKingDist <= strongKingDist)
|
||||||
return SCALE_FACTOR_DRAW;
|
return SCALE_FACTOR_DRAW;
|
||||||
@@ -406,15 +384,16 @@ ScaleFactor Endgame<KQKRPs>::operator()(const Position& pos) const {
|
|||||||
assert(pos.count<ROOK>(weakSide) == 1);
|
assert(pos.count<ROOK>(weakSide) == 1);
|
||||||
assert(pos.count<PAWN>(weakSide) >= 1);
|
assert(pos.count<PAWN>(weakSide) >= 1);
|
||||||
|
|
||||||
Square kingSq = pos.square<KING>(weakSide);
|
Square strongKing = pos.square<KING>(strongSide);
|
||||||
Square rsq = pos.square<ROOK>(weakSide);
|
Square weakKing = pos.square<KING>(weakSide);
|
||||||
|
Square weakRook = pos.square<ROOK>(weakSide);
|
||||||
|
|
||||||
if ( relative_rank(weakSide, kingSq) <= RANK_2
|
if ( relative_rank(weakSide, weakKing) <= RANK_2
|
||||||
&& relative_rank(weakSide, pos.square<KING>(strongSide)) >= RANK_4
|
&& relative_rank(weakSide, strongKing) >= RANK_4
|
||||||
&& relative_rank(weakSide, rsq) == RANK_3
|
&& relative_rank(weakSide, weakRook) == RANK_3
|
||||||
&& ( pos.pieces(weakSide, PAWN)
|
&& ( pos.pieces(weakSide, PAWN)
|
||||||
& pos.attacks_from<KING>(kingSq)
|
& attacks_bb<KING>(weakKing)
|
||||||
& pos.attacks_from<PAWN>(rsq, strongSide)))
|
& pawn_attacks_bb(strongSide, weakRook)))
|
||||||
return SCALE_FACTOR_DRAW;
|
return SCALE_FACTOR_DRAW;
|
||||||
|
|
||||||
return SCALE_FACTOR_NONE;
|
return SCALE_FACTOR_NONE;
|
||||||
@@ -434,89 +413,89 @@ ScaleFactor Endgame<KRPKR>::operator()(const Position& pos) const {
|
|||||||
assert(verify_material(pos, weakSide, RookValueMg, 0));
|
assert(verify_material(pos, weakSide, RookValueMg, 0));
|
||||||
|
|
||||||
// Assume strongSide is white and the pawn is on files A-D
|
// Assume strongSide is white and the pawn is on files A-D
|
||||||
Square wksq = normalize(pos, strongSide, pos.square<KING>(strongSide));
|
Square strongKing = normalize(pos, strongSide, pos.square<KING>(strongSide));
|
||||||
Square bksq = normalize(pos, strongSide, pos.square<KING>(weakSide));
|
Square strongRook = normalize(pos, strongSide, pos.square<ROOK>(strongSide));
|
||||||
Square wrsq = normalize(pos, strongSide, pos.square<ROOK>(strongSide));
|
Square strongPawn = normalize(pos, strongSide, pos.square<PAWN>(strongSide));
|
||||||
Square wpsq = normalize(pos, strongSide, pos.square<PAWN>(strongSide));
|
Square weakKing = normalize(pos, strongSide, pos.square<KING>(weakSide));
|
||||||
Square brsq = normalize(pos, strongSide, pos.square<ROOK>(weakSide));
|
Square weakRook = normalize(pos, strongSide, pos.square<ROOK>(weakSide));
|
||||||
|
|
||||||
File f = file_of(wpsq);
|
File pawnFile = file_of(strongPawn);
|
||||||
Rank r = rank_of(wpsq);
|
Rank pawnRank = rank_of(strongPawn);
|
||||||
Square queeningSq = make_square(f, RANK_8);
|
Square queeningSquare = make_square(pawnFile, RANK_8);
|
||||||
int tempo = (pos.side_to_move() == strongSide);
|
int tempo = (pos.side_to_move() == strongSide);
|
||||||
|
|
||||||
// If the pawn is not too far advanced and the defending king defends the
|
// If the pawn is not too far advanced and the defending king defends the
|
||||||
// queening square, use the third-rank defence.
|
// queening square, use the third-rank defence.
|
||||||
if ( r <= RANK_5
|
if ( pawnRank <= RANK_5
|
||||||
&& distance(bksq, queeningSq) <= 1
|
&& distance(weakKing, queeningSquare) <= 1
|
||||||
&& wksq <= SQ_H5
|
&& strongKing <= SQ_H5
|
||||||
&& (rank_of(brsq) == RANK_6 || (r <= RANK_3 && rank_of(wrsq) != RANK_6)))
|
&& (rank_of(weakRook) == RANK_6 || (pawnRank <= RANK_3 && rank_of(strongRook) != RANK_6)))
|
||||||
return SCALE_FACTOR_DRAW;
|
return SCALE_FACTOR_DRAW;
|
||||||
|
|
||||||
// The defending side saves a draw by checking from behind in case the pawn
|
// The defending side saves a draw by checking from behind in case the pawn
|
||||||
// has advanced to the 6th rank with the king behind.
|
// has advanced to the 6th rank with the king behind.
|
||||||
if ( r == RANK_6
|
if ( pawnRank == RANK_6
|
||||||
&& distance(bksq, queeningSq) <= 1
|
&& distance(weakKing, queeningSquare) <= 1
|
||||||
&& rank_of(wksq) + tempo <= RANK_6
|
&& rank_of(strongKing) + tempo <= RANK_6
|
||||||
&& (rank_of(brsq) == RANK_1 || (!tempo && distance<File>(brsq, wpsq) >= 3)))
|
&& (rank_of(weakRook) == RANK_1 || (!tempo && distance<File>(weakRook, strongPawn) >= 3)))
|
||||||
return SCALE_FACTOR_DRAW;
|
return SCALE_FACTOR_DRAW;
|
||||||
|
|
||||||
if ( r >= RANK_6
|
if ( pawnRank >= RANK_6
|
||||||
&& bksq == queeningSq
|
&& weakKing == queeningSquare
|
||||||
&& rank_of(brsq) == RANK_1
|
&& rank_of(weakRook) == RANK_1
|
||||||
&& (!tempo || distance(wksq, wpsq) >= 2))
|
&& (!tempo || distance(strongKing, strongPawn) >= 2))
|
||||||
return SCALE_FACTOR_DRAW;
|
return SCALE_FACTOR_DRAW;
|
||||||
|
|
||||||
// White pawn on a7 and rook on a8 is a draw if black's king is on g7 or h7
|
// 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.
|
// and the black rook is behind the pawn.
|
||||||
if ( wpsq == SQ_A7
|
if ( strongPawn == SQ_A7
|
||||||
&& wrsq == SQ_A8
|
&& strongRook == SQ_A8
|
||||||
&& (bksq == SQ_H7 || bksq == SQ_G7)
|
&& (weakKing == SQ_H7 || weakKing == SQ_G7)
|
||||||
&& file_of(brsq) == FILE_A
|
&& file_of(weakRook) == FILE_A
|
||||||
&& (rank_of(brsq) <= RANK_3 || file_of(wksq) >= FILE_D || rank_of(wksq) <= RANK_5))
|
&& (rank_of(weakRook) <= RANK_3 || file_of(strongKing) >= FILE_D || rank_of(strongKing) <= RANK_5))
|
||||||
return SCALE_FACTOR_DRAW;
|
return SCALE_FACTOR_DRAW;
|
||||||
|
|
||||||
// If the defending king blocks the pawn and the attacking king is too far
|
// If the defending king blocks the pawn and the attacking king is too far
|
||||||
// away, it's a draw.
|
// away, it's a draw.
|
||||||
if ( r <= RANK_5
|
if ( pawnRank <= RANK_5
|
||||||
&& bksq == wpsq + NORTH
|
&& weakKing == strongPawn + NORTH
|
||||||
&& distance(wksq, wpsq) - tempo >= 2
|
&& distance(strongKing, strongPawn) - tempo >= 2
|
||||||
&& distance(wksq, brsq) - tempo >= 2)
|
&& distance(strongKing, weakRook) - tempo >= 2)
|
||||||
return SCALE_FACTOR_DRAW;
|
return SCALE_FACTOR_DRAW;
|
||||||
|
|
||||||
// Pawn on the 7th rank supported by the rook from behind usually wins if the
|
// 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,
|
// attacking king is closer to the queening square than the defending king,
|
||||||
// and the defending king cannot gain tempi by threatening the attacking rook.
|
// and the defending king cannot gain tempi by threatening the attacking rook.
|
||||||
if ( r == RANK_7
|
if ( pawnRank == RANK_7
|
||||||
&& f != FILE_A
|
&& pawnFile != FILE_A
|
||||||
&& file_of(wrsq) == f
|
&& file_of(strongRook) == pawnFile
|
||||||
&& wrsq != queeningSq
|
&& strongRook != queeningSquare
|
||||||
&& (distance(wksq, queeningSq) < distance(bksq, queeningSq) - 2 + tempo)
|
&& (distance(strongKing, queeningSquare) < distance(weakKing, queeningSquare) - 2 + tempo)
|
||||||
&& (distance(wksq, queeningSq) < distance(bksq, wrsq) + tempo))
|
&& (distance(strongKing, queeningSquare) < distance(weakKing, strongRook) + tempo))
|
||||||
return ScaleFactor(SCALE_FACTOR_MAX - 2 * distance(wksq, queeningSq));
|
return ScaleFactor(SCALE_FACTOR_MAX - 2 * distance(strongKing, queeningSquare));
|
||||||
|
|
||||||
// Similar to the above, but with the pawn further back
|
// Similar to the above, but with the pawn further back
|
||||||
if ( f != FILE_A
|
if ( pawnFile != FILE_A
|
||||||
&& file_of(wrsq) == f
|
&& file_of(strongRook) == pawnFile
|
||||||
&& wrsq < wpsq
|
&& strongRook < strongPawn
|
||||||
&& (distance(wksq, queeningSq) < distance(bksq, queeningSq) - 2 + tempo)
|
&& (distance(strongKing, queeningSquare) < distance(weakKing, queeningSquare) - 2 + tempo)
|
||||||
&& (distance(wksq, wpsq + NORTH) < distance(bksq, wpsq + NORTH) - 2 + tempo)
|
&& (distance(strongKing, strongPawn + NORTH) < distance(weakKing, strongPawn + NORTH) - 2 + tempo)
|
||||||
&& ( distance(bksq, wrsq) + tempo >= 3
|
&& ( distance(weakKing, strongRook) + tempo >= 3
|
||||||
|| ( distance(wksq, queeningSq) < distance(bksq, wrsq) + tempo
|
|| ( distance(strongKing, queeningSquare) < distance(weakKing, strongRook) + tempo
|
||||||
&& (distance(wksq, wpsq + NORTH) < distance(bksq, wrsq) + tempo))))
|
&& (distance(strongKing, strongPawn + NORTH) < distance(weakKing, strongPawn) + tempo))))
|
||||||
return ScaleFactor( SCALE_FACTOR_MAX
|
return ScaleFactor( SCALE_FACTOR_MAX
|
||||||
- 8 * distance(wpsq, queeningSq)
|
- 8 * distance(strongPawn, queeningSquare)
|
||||||
- 2 * distance(wksq, queeningSq));
|
- 2 * distance(strongKing, queeningSquare));
|
||||||
|
|
||||||
// If the pawn is not far advanced and the defending king is somewhere in
|
// If the pawn is not far advanced and the defending king is somewhere in
|
||||||
// the pawn's path, it's probably a draw.
|
// the pawn's path, it's probably a draw.
|
||||||
if (r <= RANK_4 && bksq > wpsq)
|
if (pawnRank <= RANK_4 && weakKing > strongPawn)
|
||||||
{
|
{
|
||||||
if (file_of(bksq) == file_of(wpsq))
|
if (file_of(weakKing) == file_of(strongPawn))
|
||||||
return ScaleFactor(10);
|
return ScaleFactor(10);
|
||||||
if ( distance<File>(bksq, wpsq) == 1
|
if ( distance<File>(weakKing, strongPawn) == 1
|
||||||
&& distance(wksq, bksq) > 2)
|
&& distance(strongKing, weakKing) > 2)
|
||||||
return ScaleFactor(24 - 2 * distance(wksq, bksq));
|
return ScaleFactor(24 - 2 * distance(strongKing, weakKing));
|
||||||
}
|
}
|
||||||
return SCALE_FACTOR_NONE;
|
return SCALE_FACTOR_NONE;
|
||||||
}
|
}
|
||||||
@@ -530,10 +509,11 @@ ScaleFactor Endgame<KRPKB>::operator()(const Position& pos) const {
|
|||||||
// Test for a rook pawn
|
// Test for a rook pawn
|
||||||
if (pos.pieces(PAWN) & (FileABB | FileHBB))
|
if (pos.pieces(PAWN) & (FileABB | FileHBB))
|
||||||
{
|
{
|
||||||
Square ksq = pos.square<KING>(weakSide);
|
Square weakKing = pos.square<KING>(weakSide);
|
||||||
Square bsq = pos.square<BISHOP>(weakSide);
|
Square weakBishop = pos.square<BISHOP>(weakSide);
|
||||||
Square psq = pos.square<PAWN>(strongSide);
|
Square strongKing = pos.square<KING>(strongSide);
|
||||||
Rank rk = relative_rank(strongSide, psq);
|
Square strongPawn = pos.square<PAWN>(strongSide);
|
||||||
|
Rank pawnRank = relative_rank(strongSide, strongPawn);
|
||||||
Direction push = pawn_push(strongSide);
|
Direction push = pawn_push(strongSide);
|
||||||
|
|
||||||
// If the pawn is on the 5th rank and the pawn (currently) is on
|
// If the pawn is on the 5th rank and the pawn (currently) is on
|
||||||
@@ -541,11 +521,11 @@ ScaleFactor Endgame<KRPKB>::operator()(const Position& pos) const {
|
|||||||
// a fortress. Depending on the king position give a moderate
|
// a fortress. Depending on the king position give a moderate
|
||||||
// reduction or a stronger one if the defending king is near the
|
// reduction or a stronger one if the defending king is near the
|
||||||
// corner but not trapped there.
|
// corner but not trapped there.
|
||||||
if (rk == RANK_5 && !opposite_colors(bsq, psq))
|
if (pawnRank == RANK_5 && !opposite_colors(weakBishop, strongPawn))
|
||||||
{
|
{
|
||||||
int d = distance(psq + 3 * push, ksq);
|
int d = distance(strongPawn + 3 * push, weakKing);
|
||||||
|
|
||||||
if (d <= 2 && !(d == 0 && ksq == pos.square<KING>(strongSide) + 2 * push))
|
if (d <= 2 && !(d == 0 && weakKing == strongKing + 2 * push))
|
||||||
return ScaleFactor(24);
|
return ScaleFactor(24);
|
||||||
else
|
else
|
||||||
return ScaleFactor(48);
|
return ScaleFactor(48);
|
||||||
@@ -555,10 +535,10 @@ ScaleFactor Endgame<KRPKB>::operator()(const Position& pos) const {
|
|||||||
// it's drawn if the bishop attacks the square in front of the
|
// it's drawn if the bishop attacks the square in front of the
|
||||||
// pawn from a reasonable distance and the defending king is near
|
// pawn from a reasonable distance and the defending king is near
|
||||||
// the corner
|
// the corner
|
||||||
if ( rk == RANK_6
|
if ( pawnRank == RANK_6
|
||||||
&& distance(psq + 2 * push, ksq) <= 1
|
&& distance(strongPawn + 2 * push, weakKing) <= 1
|
||||||
&& (PseudoAttacks[BISHOP][bsq] & (psq + push))
|
&& (attacks_bb<BISHOP>(weakBishop) & (strongPawn + push))
|
||||||
&& distance<File>(bsq, psq) >= 2)
|
&& distance<File>(weakBishop, strongPawn) >= 2)
|
||||||
return ScaleFactor(8);
|
return ScaleFactor(8);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -573,28 +553,28 @@ ScaleFactor Endgame<KRPPKRP>::operator()(const Position& pos) const {
|
|||||||
assert(verify_material(pos, strongSide, RookValueMg, 2));
|
assert(verify_material(pos, strongSide, RookValueMg, 2));
|
||||||
assert(verify_material(pos, weakSide, RookValueMg, 1));
|
assert(verify_material(pos, weakSide, RookValueMg, 1));
|
||||||
|
|
||||||
Square wpsq1 = pos.squares<PAWN>(strongSide)[0];
|
Square strongPawn1 = pos.squares<PAWN>(strongSide)[0];
|
||||||
Square wpsq2 = pos.squares<PAWN>(strongSide)[1];
|
Square strongPawn2 = pos.squares<PAWN>(strongSide)[1];
|
||||||
Square bksq = pos.square<KING>(weakSide);
|
Square weakKing = pos.square<KING>(weakSide);
|
||||||
|
|
||||||
// Does the stronger side have a passed pawn?
|
// Does the stronger side have a passed pawn?
|
||||||
if (pos.pawn_passed(strongSide, wpsq1) || pos.pawn_passed(strongSide, wpsq2))
|
if (pos.pawn_passed(strongSide, strongPawn1) || pos.pawn_passed(strongSide, strongPawn2))
|
||||||
return SCALE_FACTOR_NONE;
|
return SCALE_FACTOR_NONE;
|
||||||
|
|
||||||
Rank r = std::max(relative_rank(strongSide, wpsq1), relative_rank(strongSide, wpsq2));
|
Rank pawnRank = std::max(relative_rank(strongSide, strongPawn1), relative_rank(strongSide, strongPawn2));
|
||||||
|
|
||||||
if ( distance<File>(bksq, wpsq1) <= 1
|
if ( distance<File>(weakKing, strongPawn1) <= 1
|
||||||
&& distance<File>(bksq, wpsq2) <= 1
|
&& distance<File>(weakKing, strongPawn2) <= 1
|
||||||
&& relative_rank(strongSide, bksq) > r)
|
&& relative_rank(strongSide, weakKing) > pawnRank)
|
||||||
{
|
{
|
||||||
assert(r > RANK_1 && r < RANK_7);
|
assert(pawnRank > RANK_1 && pawnRank < RANK_7);
|
||||||
return ScaleFactor(KRPPKRPScaleFactors[r]);
|
return ScaleFactor(7 * pawnRank);
|
||||||
}
|
}
|
||||||
return SCALE_FACTOR_NONE;
|
return SCALE_FACTOR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// K and two or more pawns vs K. There is just a single rule here: If all pawns
|
/// 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.
|
/// are on the same rook file and are blocked by the defending king, it's a draw.
|
||||||
template<>
|
template<>
|
||||||
ScaleFactor Endgame<KPsK>::operator()(const Position& pos) const {
|
ScaleFactor Endgame<KPsK>::operator()(const Position& pos) const {
|
||||||
@@ -603,14 +583,12 @@ ScaleFactor Endgame<KPsK>::operator()(const Position& pos) const {
|
|||||||
assert(pos.count<PAWN>(strongSide) >= 2);
|
assert(pos.count<PAWN>(strongSide) >= 2);
|
||||||
assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
|
assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
|
||||||
|
|
||||||
Square ksq = pos.square<KING>(weakSide);
|
Square weakKing = pos.square<KING>(weakSide);
|
||||||
Bitboard pawns = pos.pieces(strongSide, PAWN);
|
Bitboard strongPawns = pos.pieces(strongSide, PAWN);
|
||||||
|
|
||||||
// If all pawns are ahead of the king, on a single rook file and
|
// If all pawns are ahead of the king on a single rook file, it's a draw.
|
||||||
// the king is within one file of the pawns, it's a draw.
|
if ( !(strongPawns & ~(FileABB | FileHBB))
|
||||||
if ( !(pawns & ~forward_ranks_bb(weakSide, ksq))
|
&& !(strongPawns & ~passed_pawn_span(weakSide, weakKing)))
|
||||||
&& !((pawns & ~FileABB) && (pawns & ~FileHBB))
|
|
||||||
&& distance<File>(ksq, lsb(pawns)) <= 1)
|
|
||||||
return SCALE_FACTOR_DRAW;
|
return SCALE_FACTOR_DRAW;
|
||||||
|
|
||||||
return SCALE_FACTOR_NONE;
|
return SCALE_FACTOR_NONE;
|
||||||
@@ -627,20 +605,19 @@ ScaleFactor Endgame<KBPKB>::operator()(const Position& pos) const {
|
|||||||
assert(verify_material(pos, strongSide, BishopValueMg, 1));
|
assert(verify_material(pos, strongSide, BishopValueMg, 1));
|
||||||
assert(verify_material(pos, weakSide, BishopValueMg, 0));
|
assert(verify_material(pos, weakSide, BishopValueMg, 0));
|
||||||
|
|
||||||
Square pawnSq = pos.square<PAWN>(strongSide);
|
Square strongPawn = pos.square<PAWN>(strongSide);
|
||||||
Square strongBishopSq = pos.square<BISHOP>(strongSide);
|
Square strongBishop = pos.square<BISHOP>(strongSide);
|
||||||
Square weakBishopSq = pos.square<BISHOP>(weakSide);
|
Square weakBishop = pos.square<BISHOP>(weakSide);
|
||||||
Square weakKingSq = pos.square<KING>(weakSide);
|
Square weakKing = pos.square<KING>(weakSide);
|
||||||
|
|
||||||
// Case 1: Defending king blocks the pawn, and cannot be driven away
|
// Case 1: Defending king blocks the pawn, and cannot be driven away
|
||||||
if ( file_of(weakKingSq) == file_of(pawnSq)
|
if ( (forward_file_bb(strongSide, strongPawn) & weakKing)
|
||||||
&& relative_rank(strongSide, pawnSq) < relative_rank(strongSide, weakKingSq)
|
&& ( opposite_colors(weakKing, strongBishop)
|
||||||
&& ( opposite_colors(weakKingSq, strongBishopSq)
|
|| relative_rank(strongSide, weakKing) <= RANK_6))
|
||||||
|| relative_rank(strongSide, weakKingSq) <= RANK_6))
|
|
||||||
return SCALE_FACTOR_DRAW;
|
return SCALE_FACTOR_DRAW;
|
||||||
|
|
||||||
// Case 2: Opposite colored bishops
|
// Case 2: Opposite colored bishops
|
||||||
if (opposite_colors(strongBishopSq, weakBishopSq))
|
if (opposite_colors(strongBishop, weakBishop))
|
||||||
return SCALE_FACTOR_DRAW;
|
return SCALE_FACTOR_DRAW;
|
||||||
|
|
||||||
return SCALE_FACTOR_NONE;
|
return SCALE_FACTOR_NONE;
|
||||||
@@ -654,36 +631,36 @@ ScaleFactor Endgame<KBPPKB>::operator()(const Position& pos) const {
|
|||||||
assert(verify_material(pos, strongSide, BishopValueMg, 2));
|
assert(verify_material(pos, strongSide, BishopValueMg, 2));
|
||||||
assert(verify_material(pos, weakSide, BishopValueMg, 0));
|
assert(verify_material(pos, weakSide, BishopValueMg, 0));
|
||||||
|
|
||||||
Square wbsq = pos.square<BISHOP>(strongSide);
|
Square strongBishop = pos.square<BISHOP>(strongSide);
|
||||||
Square bbsq = pos.square<BISHOP>(weakSide);
|
Square weakBishop = pos.square<BISHOP>(weakSide);
|
||||||
|
|
||||||
if (!opposite_colors(wbsq, bbsq))
|
if (!opposite_colors(strongBishop, weakBishop))
|
||||||
return SCALE_FACTOR_NONE;
|
return SCALE_FACTOR_NONE;
|
||||||
|
|
||||||
Square ksq = pos.square<KING>(weakSide);
|
Square weakKing = pos.square<KING>(weakSide);
|
||||||
Square psq1 = pos.squares<PAWN>(strongSide)[0];
|
Square strongPawn1 = pos.squares<PAWN>(strongSide)[0];
|
||||||
Square psq2 = pos.squares<PAWN>(strongSide)[1];
|
Square strongPawn2 = pos.squares<PAWN>(strongSide)[1];
|
||||||
Square blockSq1, blockSq2;
|
Square blockSq1, blockSq2;
|
||||||
|
|
||||||
if (relative_rank(strongSide, psq1) > relative_rank(strongSide, psq2))
|
if (relative_rank(strongSide, strongPawn1) > relative_rank(strongSide, strongPawn2))
|
||||||
{
|
{
|
||||||
blockSq1 = psq1 + pawn_push(strongSide);
|
blockSq1 = strongPawn1 + pawn_push(strongSide);
|
||||||
blockSq2 = make_square(file_of(psq2), rank_of(psq1));
|
blockSq2 = make_square(file_of(strongPawn2), rank_of(strongPawn1));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
blockSq1 = psq2 + pawn_push(strongSide);
|
blockSq1 = strongPawn2 + pawn_push(strongSide);
|
||||||
blockSq2 = make_square(file_of(psq1), rank_of(psq2));
|
blockSq2 = make_square(file_of(strongPawn1), rank_of(strongPawn2));
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (distance<File>(psq1, psq2))
|
switch (distance<File>(strongPawn1, strongPawn2))
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
// Both pawns are on the same file. It's an easy draw if the defender firmly
|
// 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.
|
// controls some square in the frontmost pawn's path.
|
||||||
if ( file_of(ksq) == file_of(blockSq1)
|
if ( file_of(weakKing) == file_of(blockSq1)
|
||||||
&& relative_rank(strongSide, ksq) >= relative_rank(strongSide, blockSq1)
|
&& relative_rank(strongSide, weakKing) >= relative_rank(strongSide, blockSq1)
|
||||||
&& opposite_colors(ksq, wbsq))
|
&& opposite_colors(weakKing, strongBishop))
|
||||||
return SCALE_FACTOR_DRAW;
|
return SCALE_FACTOR_DRAW;
|
||||||
else
|
else
|
||||||
return SCALE_FACTOR_NONE;
|
return SCALE_FACTOR_NONE;
|
||||||
@@ -692,17 +669,17 @@ ScaleFactor Endgame<KBPPKB>::operator()(const Position& pos) const {
|
|||||||
// Pawns on adjacent files. It's a draw if the defender firmly controls the
|
// 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
|
// square in front of the frontmost pawn's path, and the square diagonally
|
||||||
// behind this square on the file of the other pawn.
|
// behind this square on the file of the other pawn.
|
||||||
if ( ksq == blockSq1
|
if ( weakKing == blockSq1
|
||||||
&& opposite_colors(ksq, wbsq)
|
&& opposite_colors(weakKing, strongBishop)
|
||||||
&& ( bbsq == blockSq2
|
&& ( weakBishop == blockSq2
|
||||||
|| (pos.attacks_from<BISHOP>(blockSq2) & pos.pieces(weakSide, BISHOP))
|
|| (attacks_bb<BISHOP>(blockSq2, pos.pieces()) & pos.pieces(weakSide, BISHOP))
|
||||||
|| distance<Rank>(psq1, psq2) >= 2))
|
|| distance<Rank>(strongPawn1, strongPawn2) >= 2))
|
||||||
return SCALE_FACTOR_DRAW;
|
return SCALE_FACTOR_DRAW;
|
||||||
|
|
||||||
else if ( ksq == blockSq2
|
else if ( weakKing == blockSq2
|
||||||
&& opposite_colors(ksq, wbsq)
|
&& opposite_colors(weakKing, strongBishop)
|
||||||
&& ( bbsq == blockSq1
|
&& ( weakBishop == blockSq1
|
||||||
|| (pos.attacks_from<BISHOP>(blockSq1) & pos.pieces(weakSide, BISHOP))))
|
|| (attacks_bb<BISHOP>(blockSq1, pos.pieces()) & pos.pieces(weakSide, BISHOP))))
|
||||||
return SCALE_FACTOR_DRAW;
|
return SCALE_FACTOR_DRAW;
|
||||||
else
|
else
|
||||||
return SCALE_FACTOR_NONE;
|
return SCALE_FACTOR_NONE;
|
||||||
@@ -714,7 +691,7 @@ ScaleFactor Endgame<KBPPKB>::operator()(const Position& pos) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// KBP vs KN. There is a single rule: If the defending king is somewhere along
|
/// 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 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.
|
/// the stronger side's bishop, it's a draw.
|
||||||
template<>
|
template<>
|
||||||
@@ -723,62 +700,22 @@ ScaleFactor Endgame<KBPKN>::operator()(const Position& pos) const {
|
|||||||
assert(verify_material(pos, strongSide, BishopValueMg, 1));
|
assert(verify_material(pos, strongSide, BishopValueMg, 1));
|
||||||
assert(verify_material(pos, weakSide, KnightValueMg, 0));
|
assert(verify_material(pos, weakSide, KnightValueMg, 0));
|
||||||
|
|
||||||
Square pawnSq = pos.square<PAWN>(strongSide);
|
Square strongPawn = pos.square<PAWN>(strongSide);
|
||||||
Square strongBishopSq = pos.square<BISHOP>(strongSide);
|
Square strongBishop = pos.square<BISHOP>(strongSide);
|
||||||
Square weakKingSq = pos.square<KING>(weakSide);
|
Square weakKing = pos.square<KING>(weakSide);
|
||||||
|
|
||||||
if ( file_of(weakKingSq) == file_of(pawnSq)
|
if ( file_of(weakKing) == file_of(strongPawn)
|
||||||
&& relative_rank(strongSide, pawnSq) < relative_rank(strongSide, weakKingSq)
|
&& relative_rank(strongSide, strongPawn) < relative_rank(strongSide, weakKing)
|
||||||
&& ( opposite_colors(weakKingSq, strongBishopSq)
|
&& ( opposite_colors(weakKing, strongBishop)
|
||||||
|| relative_rank(strongSide, weakKingSq) <= RANK_6))
|
|| relative_rank(strongSide, weakKing) <= RANK_6))
|
||||||
return SCALE_FACTOR_DRAW;
|
return SCALE_FACTOR_DRAW;
|
||||||
|
|
||||||
return SCALE_FACTOR_NONE;
|
return SCALE_FACTOR_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// KNP vs K. There is a single rule: if the pawn is a rook pawn on the 7th rank
|
|
||||||
/// and the defending king prevents the pawn from advancing, the position is drawn.
|
|
||||||
template<>
|
|
||||||
ScaleFactor Endgame<KNPK>::operator()(const Position& pos) const {
|
|
||||||
|
|
||||||
assert(verify_material(pos, strongSide, KnightValueMg, 1));
|
|
||||||
assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
|
|
||||||
|
|
||||||
// Assume strongSide is white and the pawn is on files A-D
|
|
||||||
Square pawnSq = normalize(pos, strongSide, pos.square<PAWN>(strongSide));
|
|
||||||
Square weakKingSq = normalize(pos, strongSide, pos.square<KING>(weakSide));
|
|
||||||
|
|
||||||
if (pawnSq == SQ_A7 && distance(SQ_A8, weakKingSq) <= 1)
|
|
||||||
return SCALE_FACTOR_DRAW;
|
|
||||||
|
|
||||||
return SCALE_FACTOR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// KNP vs KB. If knight can block bishop from taking pawn, it's a win.
|
|
||||||
/// Otherwise the position is drawn.
|
|
||||||
template<>
|
|
||||||
ScaleFactor Endgame<KNPKB>::operator()(const Position& pos) const {
|
|
||||||
|
|
||||||
assert(verify_material(pos, strongSide, KnightValueMg, 1));
|
|
||||||
assert(verify_material(pos, weakSide, BishopValueMg, 0));
|
|
||||||
|
|
||||||
Square pawnSq = pos.square<PAWN>(strongSide);
|
|
||||||
Square bishopSq = pos.square<BISHOP>(weakSide);
|
|
||||||
Square weakKingSq = pos.square<KING>(weakSide);
|
|
||||||
|
|
||||||
// King needs to get close to promoting pawn to prevent knight from blocking.
|
|
||||||
// Rules for this are very tricky, so just approximate.
|
|
||||||
if (forward_file_bb(strongSide, pawnSq) & pos.attacks_from<BISHOP>(bishopSq))
|
|
||||||
return ScaleFactor(distance(weakKingSq, pawnSq));
|
|
||||||
|
|
||||||
return SCALE_FACTOR_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// KP vs KP. This is done by removing the weakest side's pawn and probing the
|
/// KP vs 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
|
/// 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
|
/// 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
|
/// 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).
|
/// possible to win (e.g. 8/4k3/3p4/3P4/6K1/8/8/8 w - - 0 1).
|
||||||
@@ -789,18 +726,18 @@ ScaleFactor Endgame<KPKP>::operator()(const Position& pos) const {
|
|||||||
assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
|
assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
|
||||||
|
|
||||||
// Assume strongSide is white and the pawn is on files A-D
|
// Assume strongSide is white and the pawn is on files A-D
|
||||||
Square wksq = normalize(pos, strongSide, pos.square<KING>(strongSide));
|
Square strongKing = normalize(pos, strongSide, pos.square<KING>(strongSide));
|
||||||
Square bksq = normalize(pos, strongSide, pos.square<KING>(weakSide));
|
Square weakKing = normalize(pos, strongSide, pos.square<KING>(weakSide));
|
||||||
Square psq = normalize(pos, strongSide, pos.square<PAWN>(strongSide));
|
Square strongPawn = normalize(pos, strongSide, pos.square<PAWN>(strongSide));
|
||||||
|
|
||||||
Color us = strongSide == pos.side_to_move() ? WHITE : BLACK;
|
Color us = strongSide == pos.side_to_move() ? WHITE : BLACK;
|
||||||
|
|
||||||
// If the pawn has advanced to the fifth rank or further, and is not a
|
// 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.
|
// rook pawn, it's too dangerous to assume that it's at least a draw.
|
||||||
if (rank_of(psq) >= RANK_5 && file_of(psq) != FILE_A)
|
if (rank_of(strongPawn) >= RANK_5 && file_of(strongPawn) != FILE_A)
|
||||||
return SCALE_FACTOR_NONE;
|
return SCALE_FACTOR_NONE;
|
||||||
|
|
||||||
// Probe the KPK bitbase with the weakest side's pawn removed. If it's a draw,
|
// 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.
|
// it's probably at least a draw even with the pawn.
|
||||||
return Bitbases::probe(wksq, psq, bksq, us) ? SCALE_FACTOR_NONE : SCALE_FACTOR_DRAW;
|
return Bitbases::probe(strongKing, strongPawn, weakKing, us) ? SCALE_FACTOR_NONE : SCALE_FACTOR_DRAW;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-6
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,10 +19,10 @@
|
|||||||
#ifndef ENDGAME_H_INCLUDED
|
#ifndef ENDGAME_H_INCLUDED
|
||||||
#define ENDGAME_H_INCLUDED
|
#define ENDGAME_H_INCLUDED
|
||||||
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
#include <unordered_map>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "position.h"
|
#include "position.h"
|
||||||
@@ -57,8 +55,6 @@ enum EndgameCode {
|
|||||||
KBPKB, // KBP vs KB
|
KBPKB, // KBP vs KB
|
||||||
KBPPKB, // KBPP vs KB
|
KBPPKB, // KBPP vs KB
|
||||||
KBPKN, // KBP vs KN
|
KBPKN, // KBP vs KN
|
||||||
KNPK, // KNP vs K
|
|
||||||
KNPKB, // KNP vs KB
|
|
||||||
KPKP // KP vs KP
|
KPKP // KP vs KP
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+284
-170
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -20,22 +18,57 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <cstdlib>
|
||||||
#include <cstring> // For std::memset
|
#include <cstring> // For std::memset
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
#include "bitboard.h"
|
#include "bitboard.h"
|
||||||
#include "evaluate.h"
|
#include "evaluate.h"
|
||||||
#include "material.h"
|
#include "material.h"
|
||||||
#include "pawns.h"
|
#include "pawns.h"
|
||||||
#include "thread.h"
|
#include "thread.h"
|
||||||
|
#include "uci.h"
|
||||||
|
|
||||||
|
namespace Eval {
|
||||||
|
|
||||||
|
bool useNNUE;
|
||||||
|
std::string eval_file_loaded="None";
|
||||||
|
|
||||||
|
void init_NNUE() {
|
||||||
|
|
||||||
|
useNNUE = Options["Use NNUE"];
|
||||||
|
std::string eval_file = std::string(Options["EvalFile"]);
|
||||||
|
if (useNNUE && eval_file_loaded != eval_file)
|
||||||
|
if (Eval::NNUE::load_eval_file(eval_file))
|
||||||
|
eval_file_loaded = eval_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
void verify_NNUE() {
|
||||||
|
|
||||||
|
std::string eval_file = std::string(Options["EvalFile"]);
|
||||||
|
if (useNNUE && eval_file_loaded != eval_file)
|
||||||
|
{
|
||||||
|
std::cerr << "Use of NNUE evaluation, but the file " << eval_file << " was not loaded successfully. "
|
||||||
|
<< "These network evaluation parameters must be available, compatible with this version of the code. "
|
||||||
|
<< "The UCI option EvalFile might need to specify the full path, including the directory/folder name, to the file." << std::endl;
|
||||||
|
std::exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useNNUE)
|
||||||
|
sync_cout << "info string NNUE evaluation using " << eval_file << " enabled." << sync_endl;
|
||||||
|
else
|
||||||
|
sync_cout << "info string classical evaluation enabled." << sync_endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
namespace Trace {
|
namespace Trace {
|
||||||
|
|
||||||
enum Tracing { NO_TRACE, TRACE };
|
enum Tracing { NO_TRACE, TRACE };
|
||||||
|
|
||||||
enum Term { // The first 8 entries are reserved for PieceType
|
enum Term { // The first 8 entries are reserved for PieceType
|
||||||
MATERIAL = 8, IMBALANCE, MOBILITY, THREAT, PASSED, SPACE, INITIATIVE, TOTAL, TERM_NB
|
MATERIAL = 8, IMBALANCE, MOBILITY, THREAT, PASSED, SPACE, WINNABLE, TOTAL, TERM_NB
|
||||||
};
|
};
|
||||||
|
|
||||||
Score scores[TERM_NB][COLOR_NB];
|
Score scores[TERM_NB][COLOR_NB];
|
||||||
@@ -59,7 +92,7 @@ namespace Trace {
|
|||||||
|
|
||||||
std::ostream& operator<<(std::ostream& os, Term t) {
|
std::ostream& operator<<(std::ostream& os, Term t) {
|
||||||
|
|
||||||
if (t == MATERIAL || t == IMBALANCE || t == INITIATIVE || t == TOTAL)
|
if (t == MATERIAL || t == IMBALANCE || t == WINNABLE || t == TOTAL)
|
||||||
os << " ---- ----" << " | " << " ---- ----";
|
os << " ---- ----" << " | " << " ---- ----";
|
||||||
else
|
else
|
||||||
os << scores[t][WHITE] << " | " << scores[t][BLACK];
|
os << scores[t][WHITE] << " | " << scores[t][BLACK];
|
||||||
@@ -74,79 +107,92 @@ using namespace Trace;
|
|||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// Threshold for lazy and space evaluation
|
// Threshold for lazy and space evaluation
|
||||||
constexpr Value LazyThreshold = Value(1400);
|
constexpr Value LazyThreshold1 = Value(1400);
|
||||||
|
constexpr Value LazyThreshold2 = Value(1300);
|
||||||
constexpr Value SpaceThreshold = Value(12222);
|
constexpr Value SpaceThreshold = Value(12222);
|
||||||
|
|
||||||
// KingAttackWeights[PieceType] contains king attack weights by piece type
|
// KingAttackWeights[PieceType] contains king attack weights by piece type
|
||||||
constexpr int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 81, 52, 44, 10 };
|
constexpr int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 81, 52, 44, 10 };
|
||||||
|
|
||||||
// Penalties for enemy's safe checks
|
// SafeCheck[PieceType][single/multiple] contains safe check bonus by piece type,
|
||||||
constexpr int QueenSafeCheck = 780;
|
// higher if multiple safe checks are possible for that piece type.
|
||||||
constexpr int RookSafeCheck = 1080;
|
constexpr int SafeCheck[][2] = {
|
||||||
constexpr int BishopSafeCheck = 635;
|
{}, {}, {792, 1283}, {645, 967}, {1084, 1897}, {772, 1119}
|
||||||
constexpr int KnightSafeCheck = 790;
|
};
|
||||||
|
|
||||||
#define S(mg, eg) make_score(mg, eg)
|
#define S(mg, eg) make_score(mg, eg)
|
||||||
|
|
||||||
// MobilityBonus[PieceType-2][attacked] contains bonuses for middle and end game,
|
// MobilityBonus[PieceType-2][attacked] contains bonuses for middle and end game,
|
||||||
// indexed by piece type and number of attacked squares in the mobility area.
|
// indexed by piece type and number of attacked squares in the mobility area.
|
||||||
constexpr Score MobilityBonus[][32] = {
|
constexpr Score MobilityBonus[][32] = {
|
||||||
{ S(-62,-81), S(-53,-56), S(-12,-30), S( -4,-14), S( 3, 8), S( 13, 15), // Knights
|
{ S(-62,-81), S(-53,-56), S(-12,-31), S( -4,-16), S( 3, 5), S( 13, 11), // Knight
|
||||||
S( 22, 23), S( 28, 27), S( 33, 33) },
|
S( 22, 17), S( 28, 20), S( 33, 25) },
|
||||||
{ S(-48,-59), S(-20,-23), S( 16, -3), S( 26, 13), S( 38, 24), S( 51, 42), // Bishops
|
{ S(-48,-59), S(-20,-23), S( 16, -3), S( 26, 13), S( 38, 24), S( 51, 42), // Bishop
|
||||||
S( 55, 54), S( 63, 57), S( 63, 65), S( 68, 73), S( 81, 78), S( 81, 86),
|
S( 55, 54), S( 63, 57), S( 63, 65), S( 68, 73), S( 81, 78), S( 81, 86),
|
||||||
S( 91, 88), S( 98, 97) },
|
S( 91, 88), S( 98, 97) },
|
||||||
{ S(-58,-76), S(-27,-18), S(-15, 28), S(-10, 55), S( -5, 69), S( -2, 82), // Rooks
|
{ S(-60,-78), S(-20,-17), S( 2, 23), S( 3, 39), S( 3, 70), S( 11, 99), // Rook
|
||||||
S( 9,112), S( 16,118), S( 30,132), S( 29,142), S( 32,155), S( 38,165),
|
S( 22,103), S( 31,121), S( 40,134), S( 40,139), S( 41,158), S( 48,164),
|
||||||
S( 46,166), S( 48,169), S( 58,171) },
|
S( 57,168), S( 57,169), S( 62,172) },
|
||||||
{ S(-39,-36), S(-21,-15), S( 3, 8), S( 3, 18), S( 14, 34), S( 22, 54), // Queens
|
{ S(-30,-48), S(-12,-30), S( -8, -7), S( -9, 19), S( 20, 40), S( 23, 55), // Queen
|
||||||
S( 28, 61), S( 41, 73), S( 43, 79), S( 48, 92), S( 56, 94), S( 60,104),
|
S( 23, 59), S( 35, 75), S( 38, 78), S( 53, 96), S( 64, 96), S( 65,100),
|
||||||
S( 60,113), S( 66,120), S( 67,123), S( 70,126), S( 71,133), S( 73,136),
|
S( 65,121), S( 66,127), S( 67,131), S( 67,133), S( 72,136), S( 72,141),
|
||||||
S( 79,140), S( 88,143), S( 88,148), S( 99,166), S(102,170), S(102,175),
|
S( 77,147), S( 79,150), S( 93,151), S(108,168), S(108,168), S(108,171),
|
||||||
S(106,184), S(109,191), S(113,206), S(116,212) }
|
S(110,182), S(114,182), S(114,192), S(116,219) }
|
||||||
};
|
};
|
||||||
|
|
||||||
// RookOnFile[semiopen/open] contains bonuses for each rook when there is
|
// KingProtector[knight/bishop] contains penalty for each distance unit to own king
|
||||||
// no (friendly) pawn on the rook file.
|
constexpr Score KingProtector[] = { S(8, 9), S(6, 9) };
|
||||||
constexpr Score RookOnFile[] = { S(21, 4), S(47, 25) };
|
|
||||||
|
|
||||||
// ThreatByMinor/ByRook[attacked PieceType] contains bonuses according to
|
// Outpost[knight/bishop] contains bonuses for each knight or bishop occupying a
|
||||||
// which piece type attacks which one. Attacks on lesser pieces which are
|
// pawn protected square on rank 4 to 6 which is also safe from a pawn attack.
|
||||||
// pawn-defended are not considered.
|
constexpr Score Outpost[] = { S(56, 36), S(30, 23) };
|
||||||
constexpr Score ThreatByMinor[PIECE_TYPE_NB] = {
|
|
||||||
S(0, 0), S(6, 32), S(59, 41), S(79, 56), S(90, 119), S(79, 161)
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr Score ThreatByRook[PIECE_TYPE_NB] = {
|
|
||||||
S(0, 0), S(3, 44), S(38, 71), S(38, 61), S(0, 38), S(51, 38)
|
|
||||||
};
|
|
||||||
|
|
||||||
// PassedRank[Rank] contains a bonus according to the rank of a passed pawn
|
// PassedRank[Rank] contains a bonus according to the rank of a passed pawn
|
||||||
constexpr Score PassedRank[RANK_NB] = {
|
constexpr Score PassedRank[RANK_NB] = {
|
||||||
S(0, 0), S(10, 28), S(17, 33), S(15, 41), S(62, 72), S(168, 177), S(276, 260)
|
S(0, 0), S(10, 28), S(17, 33), S(15, 41), S(62, 72), S(168, 177), S(276, 260)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// RookOnFile[semiopen/open] contains bonuses for each rook when there is
|
||||||
|
// no (friendly) pawn on the rook file.
|
||||||
|
constexpr Score RookOnFile[] = { S(19, 7), S(48, 29) };
|
||||||
|
|
||||||
|
// ThreatByMinor/ByRook[attacked PieceType] contains bonuses according to
|
||||||
|
// which piece type attacks which one. Attacks on lesser pieces which are
|
||||||
|
// pawn-defended are not considered.
|
||||||
|
constexpr Score ThreatByMinor[PIECE_TYPE_NB] = {
|
||||||
|
S(0, 0), S(5, 32), S(57, 41), S(77, 56), S(88, 119), S(79, 161)
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr Score ThreatByRook[PIECE_TYPE_NB] = {
|
||||||
|
S(0, 0), S(3, 46), S(37, 68), S(42, 60), S(0, 38), S(58, 41)
|
||||||
|
};
|
||||||
|
|
||||||
// Assorted bonuses and penalties
|
// Assorted bonuses and penalties
|
||||||
|
constexpr Score BadOutpost = S( -7, 36);
|
||||||
|
constexpr Score BishopOnKingRing = S( 24, 0);
|
||||||
constexpr Score BishopPawns = S( 3, 7);
|
constexpr Score BishopPawns = S( 3, 7);
|
||||||
|
constexpr Score BishopXRayPawns = S( 4, 5);
|
||||||
constexpr Score CorneredBishop = S( 50, 50);
|
constexpr Score CorneredBishop = S( 50, 50);
|
||||||
constexpr Score FlankAttacks = S( 8, 0);
|
constexpr Score FlankAttacks = S( 8, 0);
|
||||||
constexpr Score Hanging = S( 69, 36);
|
constexpr Score Hanging = S( 69, 36);
|
||||||
constexpr Score KingProtector = S( 7, 8);
|
constexpr Score KnightOnQueen = S( 16, 11);
|
||||||
constexpr Score KnightOnQueen = S( 16, 12);
|
|
||||||
constexpr Score LongDiagonalBishop = S( 45, 0);
|
constexpr Score LongDiagonalBishop = S( 45, 0);
|
||||||
constexpr Score MinorBehindPawn = S( 18, 3);
|
constexpr Score MinorBehindPawn = S( 18, 3);
|
||||||
constexpr Score Outpost = S( 30, 21);
|
|
||||||
constexpr Score PassedFile = S( 11, 8);
|
constexpr Score PassedFile = S( 11, 8);
|
||||||
constexpr Score PawnlessFlank = S( 17, 95);
|
constexpr Score PawnlessFlank = S( 17, 95);
|
||||||
|
constexpr Score QueenInfiltration = S( -2, 14);
|
||||||
|
constexpr Score ReachableOutpost = S( 31, 22);
|
||||||
constexpr Score RestrictedPiece = S( 7, 7);
|
constexpr Score RestrictedPiece = S( 7, 7);
|
||||||
constexpr Score ReachableOutpost = S( 32, 10);
|
constexpr Score RookOnKingRing = S( 16, 0);
|
||||||
constexpr Score RookOnQueenFile = S( 7, 6);
|
constexpr Score RookOnQueenFile = S( 6, 11);
|
||||||
constexpr Score SliderOnQueen = S( 59, 18);
|
constexpr Score SliderOnQueen = S( 60, 18);
|
||||||
constexpr Score ThreatByKing = S( 24, 89);
|
constexpr Score ThreatByKing = S( 24, 89);
|
||||||
constexpr Score ThreatByPawnPush = S( 48, 39);
|
constexpr Score ThreatByPawnPush = S( 48, 39);
|
||||||
constexpr Score ThreatBySafePawn = S(173, 94);
|
constexpr Score ThreatBySafePawn = S(173, 94);
|
||||||
constexpr Score TrappedRook = S( 52, 10);
|
constexpr Score TrappedRook = S( 55, 13);
|
||||||
constexpr Score WeakQueen = S( 49, 15);
|
constexpr Score WeakQueenProtection = S( 14, 0);
|
||||||
|
constexpr Score WeakQueen = S( 56, 15);
|
||||||
|
|
||||||
|
|
||||||
#undef S
|
#undef S
|
||||||
|
|
||||||
@@ -167,8 +213,7 @@ namespace {
|
|||||||
template<Color Us> Score threats() const;
|
template<Color Us> Score threats() const;
|
||||||
template<Color Us> Score passed() const;
|
template<Color Us> Score passed() const;
|
||||||
template<Color Us> Score space() const;
|
template<Color Us> Score space() const;
|
||||||
ScaleFactor scale_factor(Value eg) const;
|
Value winnable(Score score) const;
|
||||||
Score initiative(Score score) const;
|
|
||||||
|
|
||||||
const Position& pos;
|
const Position& pos;
|
||||||
Material::Entry* me;
|
Material::Entry* me;
|
||||||
@@ -210,10 +255,11 @@ namespace {
|
|||||||
|
|
||||||
// Evaluation::initialize() computes king and pawn attacks, and the king ring
|
// 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.
|
// bitboard for a given color. This is done at the beginning of the evaluation.
|
||||||
|
|
||||||
template<Tracing T> template<Color Us>
|
template<Tracing T> template<Color Us>
|
||||||
void Evaluation<T>::initialize() {
|
void Evaluation<T>::initialize() {
|
||||||
|
|
||||||
constexpr Color Them = (Us == WHITE ? BLACK : WHITE);
|
constexpr Color Them = ~Us;
|
||||||
constexpr Direction Up = pawn_push(Us);
|
constexpr Direction Up = pawn_push(Us);
|
||||||
constexpr Direction Down = -Up;
|
constexpr Direction Down = -Up;
|
||||||
constexpr Bitboard LowRanks = (Us == WHITE ? Rank2BB | Rank3BB : Rank7BB | Rank6BB);
|
constexpr Bitboard LowRanks = (Us == WHITE ? Rank2BB | Rank3BB : Rank7BB | Rank6BB);
|
||||||
@@ -230,15 +276,15 @@ namespace {
|
|||||||
mobilityArea[Us] = ~(b | pos.pieces(Us, KING, QUEEN) | pos.blockers_for_king(Us) | pe->pawn_attacks(Them));
|
mobilityArea[Us] = ~(b | pos.pieces(Us, KING, QUEEN) | pos.blockers_for_king(Us) | pe->pawn_attacks(Them));
|
||||||
|
|
||||||
// Initialize attackedBy[] for king and pawns
|
// Initialize attackedBy[] for king and pawns
|
||||||
attackedBy[Us][KING] = pos.attacks_from<KING>(ksq);
|
attackedBy[Us][KING] = attacks_bb<KING>(ksq);
|
||||||
attackedBy[Us][PAWN] = pe->pawn_attacks(Us);
|
attackedBy[Us][PAWN] = pe->pawn_attacks(Us);
|
||||||
attackedBy[Us][ALL_PIECES] = attackedBy[Us][KING] | attackedBy[Us][PAWN];
|
attackedBy[Us][ALL_PIECES] = attackedBy[Us][KING] | attackedBy[Us][PAWN];
|
||||||
attackedBy2[Us] = dblAttackByPawn | (attackedBy[Us][KING] & attackedBy[Us][PAWN]);
|
attackedBy2[Us] = dblAttackByPawn | (attackedBy[Us][KING] & attackedBy[Us][PAWN]);
|
||||||
|
|
||||||
// Init our king safety tables
|
// Init our king safety tables
|
||||||
Square s = make_square(clamp(file_of(ksq), FILE_B, FILE_G),
|
Square s = make_square(Utility::clamp(file_of(ksq), FILE_B, FILE_G),
|
||||||
clamp(rank_of(ksq), RANK_2, RANK_7));
|
Utility::clamp(rank_of(ksq), RANK_2, RANK_7));
|
||||||
kingRing[Us] = PseudoAttacks[KING][s] | s;
|
kingRing[Us] = attacks_bb<KING>(s) | s;
|
||||||
|
|
||||||
kingAttackersCount[Them] = popcount(kingRing[Us] & pe->pawn_attacks(Them));
|
kingAttackersCount[Them] = popcount(kingRing[Us] & pe->pawn_attacks(Them));
|
||||||
kingAttacksCount[Them] = kingAttackersWeight[Them] = 0;
|
kingAttacksCount[Them] = kingAttackersWeight[Them] = 0;
|
||||||
@@ -249,10 +295,11 @@ namespace {
|
|||||||
|
|
||||||
|
|
||||||
// Evaluation::pieces() scores pieces of a given color and type
|
// Evaluation::pieces() scores pieces of a given color and type
|
||||||
|
|
||||||
template<Tracing T> template<Color Us, PieceType Pt>
|
template<Tracing T> template<Color Us, PieceType Pt>
|
||||||
Score Evaluation<T>::pieces() {
|
Score Evaluation<T>::pieces() {
|
||||||
|
|
||||||
constexpr Color Them = (Us == WHITE ? BLACK : WHITE);
|
constexpr Color Them = ~Us;
|
||||||
constexpr Direction Down = -pawn_push(Us);
|
constexpr Direction Down = -pawn_push(Us);
|
||||||
constexpr Bitboard OutpostRanks = (Us == WHITE ? Rank4BB | Rank5BB | Rank6BB
|
constexpr Bitboard OutpostRanks = (Us == WHITE ? Rank4BB | Rank5BB | Rank6BB
|
||||||
: Rank5BB | Rank4BB | Rank3BB);
|
: Rank5BB | Rank4BB | Rank3BB);
|
||||||
@@ -268,10 +315,10 @@ namespace {
|
|||||||
// Find attacked squares, including x-ray attacks for bishops and rooks
|
// Find attacked squares, including x-ray attacks for bishops and rooks
|
||||||
b = Pt == BISHOP ? attacks_bb<BISHOP>(s, pos.pieces() ^ pos.pieces(QUEEN))
|
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))
|
: Pt == ROOK ? attacks_bb< ROOK>(s, pos.pieces() ^ pos.pieces(QUEEN) ^ pos.pieces(Us, ROOK))
|
||||||
: pos.attacks_from<Pt>(s);
|
: attacks_bb<Pt>(s, pos.pieces());
|
||||||
|
|
||||||
if (pos.blockers_for_king(Us) & s)
|
if (pos.blockers_for_king(Us) & s)
|
||||||
b &= LineBB[pos.square<KING>(Us)][s];
|
b &= line_bb(pos.square<KING>(Us), s);
|
||||||
|
|
||||||
attackedBy2[Us] |= attackedBy[Us][ALL_PIECES] & b;
|
attackedBy2[Us] |= attackedBy[Us][ALL_PIECES] & b;
|
||||||
attackedBy[Us][Pt] |= b;
|
attackedBy[Us][Pt] |= b;
|
||||||
@@ -284,35 +331,52 @@ namespace {
|
|||||||
kingAttacksCount[Us] += popcount(b & attackedBy[Them][KING]);
|
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]);
|
int mob = popcount(b & mobilityArea[Us]);
|
||||||
|
|
||||||
mobility[Us] += MobilityBonus[Pt - 2][mob];
|
mobility[Us] += MobilityBonus[Pt - 2][mob];
|
||||||
|
|
||||||
if (Pt == BISHOP || Pt == KNIGHT)
|
if (Pt == BISHOP || Pt == KNIGHT)
|
||||||
{
|
{
|
||||||
// Bonus if piece is on an outpost square or can reach one
|
// Bonus if the piece is on an outpost square or can reach one
|
||||||
|
// Reduced bonus for knights (BadOutpost) if few relevant targets
|
||||||
bb = OutpostRanks & attackedBy[Us][PAWN] & ~pe->pawn_attacks_span(Them);
|
bb = OutpostRanks & attackedBy[Us][PAWN] & ~pe->pawn_attacks_span(Them);
|
||||||
if (bb & s)
|
Bitboard targets = pos.pieces(Them) & ~pos.pieces(PAWN);
|
||||||
score += Outpost * (Pt == KNIGHT ? 2 : 1);
|
|
||||||
|
|
||||||
|
if ( Pt == KNIGHT
|
||||||
|
&& bb & s & ~CenterFiles // on a side outpost
|
||||||
|
&& !(b & targets) // no relevant attacks
|
||||||
|
&& (!more_than_one(targets & (s & QueenSide ? QueenSide : KingSide))))
|
||||||
|
score += BadOutpost;
|
||||||
|
else if (bb & s)
|
||||||
|
score += Outpost[Pt == BISHOP];
|
||||||
else if (Pt == KNIGHT && bb & b & ~pos.pieces(Us))
|
else if (Pt == KNIGHT && bb & b & ~pos.pieces(Us))
|
||||||
score += ReachableOutpost;
|
score += ReachableOutpost;
|
||||||
|
|
||||||
// Knight and Bishop bonus for being right behind a pawn
|
// Bonus for a knight or bishop shielded by pawn
|
||||||
if (shift<Down>(pos.pieces(PAWN)) & s)
|
if (shift<Down>(pos.pieces(PAWN)) & s)
|
||||||
score += MinorBehindPawn;
|
score += MinorBehindPawn;
|
||||||
|
|
||||||
// Penalty if the piece is far from the king
|
// Penalty if the piece is far from the king
|
||||||
score -= KingProtector * distance(s, pos.square<KING>(Us));
|
score -= KingProtector[Pt == BISHOP] * distance(pos.square<KING>(Us), s);
|
||||||
|
|
||||||
if (Pt == BISHOP)
|
if (Pt == BISHOP)
|
||||||
{
|
{
|
||||||
// Penalty according to number of pawns on the same color square as the
|
// 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.
|
// 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());
|
Bitboard blocked = pos.pieces(Us, PAWN) & shift<Down>(pos.pieces());
|
||||||
|
|
||||||
score -= BishopPawns * pos.pawns_on_same_color_squares(Us, s)
|
score -= BishopPawns * pos.pawns_on_same_color_squares(Us, s)
|
||||||
* (1 + popcount(blocked & CenterFiles));
|
* (!(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
|
// 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))
|
if (more_than_one(attacks_bb<BISHOP>(s, pos.pieces(PAWN)) & Center))
|
||||||
@@ -358,6 +422,10 @@ namespace {
|
|||||||
Bitboard queenPinners;
|
Bitboard queenPinners;
|
||||||
if (pos.slider_blockers(pos.pieces(Them, ROOK, BISHOP), s, queenPinners))
|
if (pos.slider_blockers(pos.pieces(Them, ROOK, BISHOP), s, queenPinners))
|
||||||
score -= WeakQueen;
|
score -= WeakQueen;
|
||||||
|
|
||||||
|
// Bonus for queen on weak square in enemy camp
|
||||||
|
if (relative_rank(Us, s) > RANK_4 && (~pe->pawn_attacks_span(Them) & s))
|
||||||
|
score += QueenInfiltration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (T)
|
if (T)
|
||||||
@@ -368,10 +436,11 @@ namespace {
|
|||||||
|
|
||||||
|
|
||||||
// Evaluation::king() assigns bonuses and penalties to a king of a given color
|
// Evaluation::king() assigns bonuses and penalties to a king of a given color
|
||||||
|
|
||||||
template<Tracing T> template<Color Us>
|
template<Tracing T> template<Color Us>
|
||||||
Score Evaluation<T>::king() const {
|
Score Evaluation<T>::king() const {
|
||||||
|
|
||||||
constexpr Color Them = (Us == WHITE ? BLACK : WHITE);
|
constexpr Color Them = ~Us;
|
||||||
constexpr Bitboard Camp = (Us == WHITE ? AllSquares ^ Rank6BB ^ Rank7BB ^ Rank8BB
|
constexpr Bitboard Camp = (Us == WHITE ? AllSquares ^ Rank6BB ^ Rank7BB ^ Rank8BB
|
||||||
: AllSquares ^ Rank1BB ^ Rank2BB ^ Rank3BB);
|
: AllSquares ^ Rank1BB ^ Rank2BB ^ Rank3BB);
|
||||||
|
|
||||||
@@ -396,41 +465,33 @@ namespace {
|
|||||||
b2 = attacks_bb<BISHOP>(ksq, pos.pieces() ^ pos.pieces(Us, QUEEN));
|
b2 = attacks_bb<BISHOP>(ksq, pos.pieces() ^ pos.pieces(Us, QUEEN));
|
||||||
|
|
||||||
// Enemy rooks checks
|
// Enemy rooks checks
|
||||||
rookChecks = b1 & safe & attackedBy[Them][ROOK];
|
rookChecks = b1 & attackedBy[Them][ROOK] & safe;
|
||||||
|
|
||||||
if (rookChecks)
|
if (rookChecks)
|
||||||
kingDanger += RookSafeCheck;
|
kingDanger += SafeCheck[ROOK][more_than_one(rookChecks)];
|
||||||
else
|
else
|
||||||
unsafeChecks |= b1 & attackedBy[Them][ROOK];
|
unsafeChecks |= b1 & attackedBy[Them][ROOK];
|
||||||
|
|
||||||
// Enemy queen safe checks: we count them only if they are from squares from
|
// Enemy queen safe checks: count them only if the checks are from squares from
|
||||||
// which we can't give a rook check, because rook checks are more valuable.
|
// which opponent cannot give a rook check, because rook checks are more valuable.
|
||||||
queenChecks = (b1 | b2)
|
queenChecks = (b1 | b2) & attackedBy[Them][QUEEN] & safe
|
||||||
& attackedBy[Them][QUEEN]
|
& ~(attackedBy[Us][QUEEN] | rookChecks);
|
||||||
& safe
|
|
||||||
& ~attackedBy[Us][QUEEN]
|
|
||||||
& ~rookChecks;
|
|
||||||
|
|
||||||
if (queenChecks)
|
if (queenChecks)
|
||||||
kingDanger += QueenSafeCheck;
|
kingDanger += SafeCheck[QUEEN][more_than_one(queenChecks)];
|
||||||
|
|
||||||
// Enemy bishops checks: we count them only if they are from squares from
|
// Enemy bishops checks: count them only if they are from squares from which
|
||||||
// which we can't give a queen check, because queen checks are more valuable.
|
// opponent cannot give a queen check, because queen checks are more valuable.
|
||||||
bishopChecks = b2
|
bishopChecks = b2 & attackedBy[Them][BISHOP] & safe
|
||||||
& attackedBy[Them][BISHOP]
|
|
||||||
& safe
|
|
||||||
& ~queenChecks;
|
& ~queenChecks;
|
||||||
|
|
||||||
if (bishopChecks)
|
if (bishopChecks)
|
||||||
kingDanger += BishopSafeCheck;
|
kingDanger += SafeCheck[BISHOP][more_than_one(bishopChecks)];
|
||||||
|
|
||||||
else
|
else
|
||||||
unsafeChecks |= b2 & attackedBy[Them][BISHOP];
|
unsafeChecks |= b2 & attackedBy[Them][BISHOP];
|
||||||
|
|
||||||
// Enemy knights checks
|
// Enemy knights checks
|
||||||
knightChecks = pos.attacks_from<KNIGHT>(ksq) & attackedBy[Them][KNIGHT];
|
knightChecks = attacks_bb<KNIGHT>(ksq) & attackedBy[Them][KNIGHT];
|
||||||
|
|
||||||
if (knightChecks & safe)
|
if (knightChecks & safe)
|
||||||
kingDanger += KnightSafeCheck;
|
kingDanger += SafeCheck[KNIGHT][more_than_one(knightChecks & safe)];
|
||||||
else
|
else
|
||||||
unsafeChecks |= knightChecks;
|
unsafeChecks |= knightChecks;
|
||||||
|
|
||||||
@@ -476,10 +537,11 @@ namespace {
|
|||||||
|
|
||||||
// Evaluation::threats() assigns bonuses according to the types of the
|
// Evaluation::threats() assigns bonuses according to the types of the
|
||||||
// attacking and the attacked pieces.
|
// attacking and the attacked pieces.
|
||||||
|
|
||||||
template<Tracing T> template<Color Us>
|
template<Tracing T> template<Color Us>
|
||||||
Score Evaluation<T>::threats() const {
|
Score Evaluation<T>::threats() const {
|
||||||
|
|
||||||
constexpr Color Them = (Us == WHITE ? BLACK : WHITE);
|
constexpr Color Them = ~Us;
|
||||||
constexpr Direction Up = pawn_push(Us);
|
constexpr Direction Up = pawn_push(Us);
|
||||||
constexpr Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB);
|
constexpr Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB);
|
||||||
|
|
||||||
@@ -517,13 +579,15 @@ namespace {
|
|||||||
b = ~attackedBy[Them][ALL_PIECES]
|
b = ~attackedBy[Them][ALL_PIECES]
|
||||||
| (nonPawnEnemies & attackedBy2[Us]);
|
| (nonPawnEnemies & attackedBy2[Us]);
|
||||||
score += Hanging * popcount(weak & b);
|
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
|
// Bonus for restricting their piece moves
|
||||||
b = attackedBy[Them][ALL_PIECES]
|
b = attackedBy[Them][ALL_PIECES]
|
||||||
& ~stronglyProtected
|
& ~stronglyProtected
|
||||||
& attackedBy[Us][ALL_PIECES];
|
& attackedBy[Us][ALL_PIECES];
|
||||||
|
|
||||||
score += RestrictedPiece * popcount(b);
|
score += RestrictedPiece * popcount(b);
|
||||||
|
|
||||||
// Protected or unattacked squares
|
// Protected or unattacked squares
|
||||||
@@ -548,17 +612,21 @@ namespace {
|
|||||||
// Bonus for threats on the next moves against enemy queen
|
// Bonus for threats on the next moves against enemy queen
|
||||||
if (pos.count<QUEEN>(Them) == 1)
|
if (pos.count<QUEEN>(Them) == 1)
|
||||||
{
|
{
|
||||||
|
bool queenImbalance = pos.count<QUEEN>() == 1;
|
||||||
|
|
||||||
Square s = pos.square<QUEEN>(Them);
|
Square s = pos.square<QUEEN>(Them);
|
||||||
safe = mobilityArea[Us] & ~stronglyProtected;
|
safe = mobilityArea[Us]
|
||||||
|
& ~pos.pieces(Us, PAWN)
|
||||||
|
& ~stronglyProtected;
|
||||||
|
|
||||||
b = attackedBy[Us][KNIGHT] & pos.attacks_from<KNIGHT>(s);
|
b = attackedBy[Us][KNIGHT] & attacks_bb<KNIGHT>(s);
|
||||||
|
|
||||||
score += KnightOnQueen * popcount(b & safe);
|
score += KnightOnQueen * popcount(b & safe) * (1 + queenImbalance);
|
||||||
|
|
||||||
b = (attackedBy[Us][BISHOP] & pos.attacks_from<BISHOP>(s))
|
b = (attackedBy[Us][BISHOP] & attacks_bb<BISHOP>(s, pos.pieces()))
|
||||||
| (attackedBy[Us][ROOK ] & pos.attacks_from<ROOK >(s));
|
| (attackedBy[Us][ROOK ] & attacks_bb<ROOK >(s, pos.pieces()));
|
||||||
|
|
||||||
score += SliderOnQueen * popcount(b & safe & attackedBy2[Us]);
|
score += SliderOnQueen * popcount(b & safe & attackedBy2[Us]) * (1 + queenImbalance);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (T)
|
if (T)
|
||||||
@@ -573,18 +641,32 @@ namespace {
|
|||||||
template<Tracing T> template<Color Us>
|
template<Tracing T> template<Color Us>
|
||||||
Score Evaluation<T>::passed() const {
|
Score Evaluation<T>::passed() const {
|
||||||
|
|
||||||
constexpr Color Them = (Us == WHITE ? BLACK : WHITE);
|
constexpr Color Them = ~Us;
|
||||||
constexpr Direction Up = pawn_push(Us);
|
constexpr Direction Up = pawn_push(Us);
|
||||||
|
constexpr Direction Down = -Up;
|
||||||
|
|
||||||
auto king_proximity = [&](Color c, Square s) {
|
auto king_proximity = [&](Color c, Square s) {
|
||||||
return std::min(distance(pos.square<KING>(c), s), 5);
|
return std::min(distance(pos.square<KING>(c), s), 5);
|
||||||
};
|
};
|
||||||
|
|
||||||
Bitboard b, bb, squaresToQueen, unsafeSquares;
|
Bitboard b, bb, squaresToQueen, unsafeSquares, blockedPassers, helpers;
|
||||||
Score score = SCORE_ZERO;
|
Score score = SCORE_ZERO;
|
||||||
|
|
||||||
b = pe->passed_pawns(Us);
|
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)
|
while (b)
|
||||||
{
|
{
|
||||||
Square s = pop_lsb(&b);
|
Square s = pop_lsb(&b);
|
||||||
@@ -635,13 +717,7 @@ namespace {
|
|||||||
}
|
}
|
||||||
} // r > RANK_3
|
} // r > RANK_3
|
||||||
|
|
||||||
// Scale down bonus for candidate passers which need more than one
|
score += bonus - PassedFile * edge_distance(file_of(s));
|
||||||
// pawn push to become passed, or have a pawn in front of them.
|
|
||||||
if ( !pos.pawn_passed(Us, s + Up)
|
|
||||||
|| (pos.pieces(PAWN) & (s + Up)))
|
|
||||||
bonus = bonus / 2;
|
|
||||||
|
|
||||||
score += bonus - PassedFile * map_to_queenside(file_of(s));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (T)
|
if (T)
|
||||||
@@ -651,20 +727,19 @@ namespace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Evaluation::space() computes the space evaluation for a given side. The
|
// Evaluation::space() computes a space evaluation for a given side, aiming to improve game
|
||||||
// space evaluation is a simple bonus based on the number of safe squares
|
// play in the opening. It is based on the number of safe squares on the 4 central files
|
||||||
// available for minor pieces on the central four files on ranks 2--4. Safe
|
// on ranks 2 to 4. Completely safe squares behind a friendly pawn are counted twice.
|
||||||
// squares one, two or three squares behind a friendly pawn are counted
|
// Finally, the space bonus is multiplied by a weight which decreases according to occupancy.
|
||||||
// twice. Finally, the space bonus is multiplied by a weight. The aim is to
|
|
||||||
// improve play on game opening.
|
|
||||||
|
|
||||||
template<Tracing T> template<Color Us>
|
template<Tracing T> template<Color Us>
|
||||||
Score Evaluation<T>::space() const {
|
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)
|
if (pos.non_pawn_material() < SpaceThreshold)
|
||||||
return SCORE_ZERO;
|
return SCORE_ZERO;
|
||||||
|
|
||||||
constexpr Color Them = (Us == WHITE ? BLACK : WHITE);
|
constexpr Color Them = ~Us;
|
||||||
constexpr Direction Down = -pawn_push(Us);
|
constexpr Direction Down = -pawn_push(Us);
|
||||||
constexpr Bitboard SpaceMask =
|
constexpr Bitboard SpaceMask =
|
||||||
Us == WHITE ? CenterFiles & (Rank2BB | Rank3BB | Rank4BB)
|
Us == WHITE ? CenterFiles & (Rank2BB | Rank3BB | Rank4BB)
|
||||||
@@ -681,7 +756,7 @@ namespace {
|
|||||||
behind |= shift<Down+Down>(behind);
|
behind |= shift<Down+Down>(behind);
|
||||||
|
|
||||||
int bonus = popcount(safe) + popcount(behind & safe & ~attackedBy[Them][ALL_PIECES]);
|
int bonus = popcount(safe) + popcount(behind & safe & ~attackedBy[Them][ALL_PIECES]);
|
||||||
int weight = pos.count<ALL_PIECES>(Us) - 1;
|
int weight = pos.count<ALL_PIECES>(Us) - 3 + std::min(pe->blocked_count(), 9);
|
||||||
Score score = make_score(bonus * weight * weight / 16, 0);
|
Score score = make_score(bonus * weight * weight / 16, 0);
|
||||||
|
|
||||||
if (T)
|
if (T)
|
||||||
@@ -691,73 +766,87 @@ namespace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Evaluation::initiative() computes the initiative correction value
|
// Evaluation::winnable() adjusts the midgame and endgame score components, based on
|
||||||
// for the position. It is a second order bonus/malus based on the
|
// the known attacking/defending status of the players. The final value is derived
|
||||||
// known attacking/defending status of the players.
|
// by interpolation from the midgame and endgame values.
|
||||||
|
|
||||||
template<Tracing T>
|
template<Tracing T>
|
||||||
Score Evaluation<T>::initiative(Score score) const {
|
Value Evaluation<T>::winnable(Score score) const {
|
||||||
|
|
||||||
Value mg = mg_value(score);
|
|
||||||
Value eg = eg_value(score);
|
|
||||||
|
|
||||||
int outflanking = distance<File>(pos.square<KING>(WHITE), pos.square<KING>(BLACK))
|
int outflanking = distance<File>(pos.square<KING>(WHITE), pos.square<KING>(BLACK))
|
||||||
- distance<Rank>(pos.square<KING>(WHITE), pos.square<KING>(BLACK));
|
- distance<Rank>(pos.square<KING>(WHITE), pos.square<KING>(BLACK));
|
||||||
|
|
||||||
bool infiltration = rank_of(pos.square<KING>(WHITE)) > RANK_4
|
|
||||||
|| rank_of(pos.square<KING>(BLACK)) < RANK_5;
|
|
||||||
|
|
||||||
bool pawnsOnBothFlanks = (pos.pieces(PAWN) & QueenSide)
|
bool pawnsOnBothFlanks = (pos.pieces(PAWN) & QueenSide)
|
||||||
&& (pos.pieces(PAWN) & KingSide);
|
&& (pos.pieces(PAWN) & KingSide);
|
||||||
|
|
||||||
bool almostUnwinnable = !pe->passed_count()
|
bool almostUnwinnable = outflanking < 0
|
||||||
&& outflanking < 0
|
|
||||||
&& !pawnsOnBothFlanks;
|
&& !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
|
// Compute the initiative bonus for the attacking side
|
||||||
int complexity = 9 * pe->passed_count()
|
int complexity = 9 * pe->passed_count()
|
||||||
+ 11 * pos.count<PAWN>()
|
+ 12 * pos.count<PAWN>()
|
||||||
+ 9 * outflanking
|
+ 9 * outflanking
|
||||||
+ 12 * infiltration
|
|
||||||
+ 21 * pawnsOnBothFlanks
|
+ 21 * pawnsOnBothFlanks
|
||||||
|
+ 24 * infiltration
|
||||||
+ 51 * !pos.non_pawn_material()
|
+ 51 * !pos.non_pawn_material()
|
||||||
- 43 * almostUnwinnable
|
- 43 * almostUnwinnable
|
||||||
- 100 ;
|
-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
|
// 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
|
// 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.
|
// so that the midgame and endgame scores do not change sign after the bonus.
|
||||||
int u = ((mg > 0) - (mg < 0)) * std::max(std::min(complexity + 50, 0), -abs(mg));
|
int u = ((mg > 0) - (mg < 0)) * Utility::clamp(complexity + 50, -abs(mg), 0);
|
||||||
int v = ((eg > 0) - (eg < 0)) * std::max(complexity, -abs(eg));
|
int v = ((eg > 0) - (eg < 0)) * std::max(complexity, -abs(eg));
|
||||||
|
|
||||||
if (T)
|
mg += u;
|
||||||
Trace::add(INITIATIVE, make_score(u, v));
|
eg += v;
|
||||||
|
|
||||||
return make_score(u, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Evaluation::scale_factor() computes the scale factor for the winning side
|
|
||||||
|
|
||||||
template<Tracing T>
|
|
||||||
ScaleFactor Evaluation<T>::scale_factor(Value eg) const {
|
|
||||||
|
|
||||||
|
// Compute the scale factor for the winning side
|
||||||
Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK;
|
Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK;
|
||||||
int sf = me->scale_factor(pos, strongSide);
|
int sf = me->scale_factor(pos, strongSide);
|
||||||
|
|
||||||
// If scale is not already specific, scale down the endgame via general heuristics
|
// If scale factor is not already specific, scale down via general heuristics
|
||||||
if (sf == SCALE_FACTOR_NORMAL)
|
if (sf == SCALE_FACTOR_NORMAL)
|
||||||
{
|
{
|
||||||
if ( pos.opposite_bishops()
|
if (pos.opposite_bishops())
|
||||||
&& pos.non_pawn_material() == 2 * BishopValueMg)
|
{
|
||||||
sf = 22 ;
|
if ( pos.non_pawn_material(WHITE) == BishopValueMg
|
||||||
|
&& pos.non_pawn_material(BLACK) == BishopValueMg)
|
||||||
|
sf = 18 + 4 * popcount(pe->passed_pawns(strongSide));
|
||||||
else
|
else
|
||||||
sf = std::min(sf, 36 + (pos.opposite_bishops() ? 2 : 7) * pos.count<PAWN>(strongSide));
|
sf = 22 + 3 * pos.count<ALL_PIECES>(strongSide);
|
||||||
|
}
|
||||||
sf = std::max(0, sf - (pos.rule50_count() - 12) / 4);
|
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;
|
||||||
|
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));
|
||||||
|
else
|
||||||
|
sf = std::min(sf, 36 + 7 * pos.count<PAWN>(strongSide));
|
||||||
}
|
}
|
||||||
|
|
||||||
return ScaleFactor(sf);
|
// 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 (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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -788,16 +877,19 @@ namespace {
|
|||||||
score += pe->pawn_score(WHITE) - pe->pawn_score(BLACK);
|
score += pe->pawn_score(WHITE) - pe->pawn_score(BLACK);
|
||||||
|
|
||||||
// Early exit if score is high
|
// Early exit if score is high
|
||||||
Value v = (mg_value(score) + eg_value(score)) / 2;
|
auto lazy_skip = [&](Value lazyThreshold) {
|
||||||
if (abs(v) > LazyThreshold + pos.non_pawn_material() / 64)
|
return abs(mg_value(score) + eg_value(score)) / 2 > lazyThreshold + pos.non_pawn_material() / 64;
|
||||||
return pos.side_to_move() == WHITE ? v : -v;
|
};
|
||||||
|
|
||||||
|
if (lazy_skip(LazyThreshold1))
|
||||||
|
goto make_v;
|
||||||
|
|
||||||
// Main evaluation begins here
|
// Main evaluation begins here
|
||||||
|
|
||||||
initialize<WHITE>();
|
initialize<WHITE>();
|
||||||
initialize<BLACK>();
|
initialize<BLACK>();
|
||||||
|
|
||||||
// Pieces should be evaluated first (populate attack tables)
|
// 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>()
|
score += pieces<WHITE, KNIGHT>() - pieces<BLACK, KNIGHT>()
|
||||||
+ pieces<WHITE, BISHOP>() - pieces<BLACK, BISHOP>()
|
+ pieces<WHITE, BISHOP>() - pieces<BLACK, BISHOP>()
|
||||||
+ pieces<WHITE, ROOK >() - pieces<BLACK, ROOK >()
|
+ pieces<WHITE, ROOK >() - pieces<BLACK, ROOK >()
|
||||||
@@ -805,19 +897,19 @@ namespace {
|
|||||||
|
|
||||||
score += mobility[WHITE] - mobility[BLACK];
|
score += mobility[WHITE] - mobility[BLACK];
|
||||||
|
|
||||||
|
// More complex interactions that require fully populated attack bitboards
|
||||||
score += king< WHITE>() - king< BLACK>()
|
score += king< WHITE>() - king< BLACK>()
|
||||||
+ threats<WHITE>() - threats<BLACK>()
|
+ passed< WHITE>() - passed< BLACK>();
|
||||||
+ passed< WHITE>() - passed< BLACK>()
|
|
||||||
|
if (lazy_skip(LazyThreshold2))
|
||||||
|
goto make_v;
|
||||||
|
|
||||||
|
score += threats<WHITE>() - threats<BLACK>()
|
||||||
+ space< WHITE>() - space< BLACK>();
|
+ space< WHITE>() - space< BLACK>();
|
||||||
|
|
||||||
score += initiative(score);
|
make_v:
|
||||||
|
// Derive single value from mg and eg parts of score
|
||||||
// Interpolate between a middlegame and a (scaled by 'sf') endgame score
|
Value v = winnable(score);
|
||||||
ScaleFactor sf = scale_factor(eg_value(score));
|
|
||||||
v = mg_value(score) * int(me->game_phase())
|
|
||||||
+ eg_value(score) * int(PHASE_MIDGAME - me->game_phase()) * sf / SCALE_FACTOR_NORMAL;
|
|
||||||
|
|
||||||
v /= PHASE_MIDGAME;
|
|
||||||
|
|
||||||
// In case of tracing add all remaining individual evaluation terms
|
// In case of tracing add all remaining individual evaluation terms
|
||||||
if (T)
|
if (T)
|
||||||
@@ -826,11 +918,18 @@ namespace {
|
|||||||
Trace::add(IMBALANCE, me->imbalance());
|
Trace::add(IMBALANCE, me->imbalance());
|
||||||
Trace::add(PAWN, pe->pawn_score(WHITE), pe->pawn_score(BLACK));
|
Trace::add(PAWN, pe->pawn_score(WHITE), pe->pawn_score(BLACK));
|
||||||
Trace::add(MOBILITY, mobility[WHITE], mobility[BLACK]);
|
Trace::add(MOBILITY, mobility[WHITE], mobility[BLACK]);
|
||||||
Trace::add(TOTAL, score);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (pos.side_to_move() == WHITE ? v : -v) // Side to move point of view
|
// Evaluation grain
|
||||||
+ Eval::Tempo;
|
v = (v / 16) * 16;
|
||||||
|
|
||||||
|
// Side to move point of view
|
||||||
|
v = (pos.side_to_move() == WHITE ? v : -v) + Tempo;
|
||||||
|
|
||||||
|
// Damp down the evaluation linearly when shuffling
|
||||||
|
v = v * (100 - pos.rule50_count()) / 100;
|
||||||
|
|
||||||
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
@@ -840,28 +939,40 @@ namespace {
|
|||||||
/// evaluation of the position from the point of view of the side to move.
|
/// evaluation of the position from the point of view of the side to move.
|
||||||
|
|
||||||
Value Eval::evaluate(const Position& pos) {
|
Value Eval::evaluate(const Position& pos) {
|
||||||
|
|
||||||
|
if (Eval::useNNUE)
|
||||||
|
return NNUE::evaluate(pos);
|
||||||
|
else
|
||||||
return Evaluation<NO_TRACE>(pos).value();
|
return Evaluation<NO_TRACE>(pos).value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// trace() is like evaluate(), but instead of returning a value, it returns
|
/// trace() is like evaluate(), but instead of returning a value, it returns
|
||||||
/// a string (suitable for outputting to stdout) that contains the detailed
|
/// a string (suitable for outputting to stdout) that contains the detailed
|
||||||
/// descriptions and values of each evaluation term. Useful for debugging.
|
/// descriptions and values of each evaluation term. Useful for debugging.
|
||||||
|
/// Trace scores are from white's point of view
|
||||||
|
|
||||||
std::string Eval::trace(const Position& pos) {
|
std::string Eval::trace(const Position& pos) {
|
||||||
|
|
||||||
if (pos.checkers())
|
if (pos.checkers())
|
||||||
return "Total evaluation: none (in check)";
|
return "Final evaluation: none (in check)";
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2);
|
||||||
|
|
||||||
|
Value v;
|
||||||
|
|
||||||
|
if (Eval::useNNUE)
|
||||||
|
{
|
||||||
|
v = NNUE::evaluate(pos);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
std::memset(scores, 0, sizeof(scores));
|
std::memset(scores, 0, sizeof(scores));
|
||||||
|
|
||||||
pos.this_thread()->contempt = SCORE_ZERO; // Reset any dynamic contempt
|
pos.this_thread()->contempt = SCORE_ZERO; // Reset any dynamic contempt
|
||||||
|
|
||||||
Value v = Evaluation<TRACE>(pos).value();
|
v = Evaluation<TRACE>(pos).value();
|
||||||
|
|
||||||
v = pos.side_to_move() == WHITE ? v : -v; // Trace scores are from white's point of view
|
|
||||||
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2)
|
ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2)
|
||||||
<< " Term | White | Black | Total \n"
|
<< " Term | White | Black | Total \n"
|
||||||
<< " | MG EG | MG EG | MG EG \n"
|
<< " | MG EG | MG EG | MG EG \n"
|
||||||
@@ -878,11 +989,14 @@ std::string Eval::trace(const Position& pos) {
|
|||||||
<< " Threats | " << Term(THREAT)
|
<< " Threats | " << Term(THREAT)
|
||||||
<< " Passed | " << Term(PASSED)
|
<< " Passed | " << Term(PASSED)
|
||||||
<< " Space | " << Term(SPACE)
|
<< " Space | " << Term(SPACE)
|
||||||
<< " Initiative | " << Term(INITIATIVE)
|
<< " Winnable | " << Term(WINNABLE)
|
||||||
<< " ------------+-------------+-------------+------------\n"
|
<< " ------------+-------------+-------------+------------\n"
|
||||||
<< " Total | " << Term(TOTAL);
|
<< " Total | " << Term(TOTAL);
|
||||||
|
}
|
||||||
|
|
||||||
ss << "\nTotal evaluation: " << to_cp(v) << " (white side)\n";
|
v = pos.side_to_move() == WHITE ? v : -v;
|
||||||
|
|
||||||
|
ss << "\nFinal evaluation: " << to_cp(v) << " (white side)\n";
|
||||||
|
|
||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-7
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -29,11 +27,23 @@ class Position;
|
|||||||
|
|
||||||
namespace Eval {
|
namespace Eval {
|
||||||
|
|
||||||
constexpr Value Tempo = Value(28); // Must be visible to search
|
std::string trace(const Position& pos);
|
||||||
|
Value evaluate(const Position& pos);
|
||||||
|
|
||||||
std::string trace(const Position& pos);
|
extern bool useNNUE;
|
||||||
|
extern std::string eval_file_loaded;
|
||||||
|
void init_NNUE();
|
||||||
|
void verify_NNUE();
|
||||||
|
|
||||||
Value evaluate(const Position& pos);
|
namespace NNUE {
|
||||||
}
|
|
||||||
|
Value evaluate(const Position& pos);
|
||||||
|
Value compute_eval(const Position& pos);
|
||||||
|
void update_eval(const Position& pos);
|
||||||
|
bool load_eval_file(const std::string& evalFile);
|
||||||
|
|
||||||
|
} // namespace NNUE
|
||||||
|
|
||||||
|
} // namespace Eval
|
||||||
|
|
||||||
#endif // #ifndef EVALUATE_H_INCLUDED
|
#endif // #ifndef EVALUATE_H_INCLUDED
|
||||||
|
|||||||
+5
-5
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,12 +19,12 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#include "bitboard.h"
|
#include "bitboard.h"
|
||||||
|
#include "endgame.h"
|
||||||
#include "position.h"
|
#include "position.h"
|
||||||
#include "search.h"
|
#include "search.h"
|
||||||
#include "thread.h"
|
#include "thread.h"
|
||||||
#include "tt.h"
|
#include "tt.h"
|
||||||
#include "uci.h"
|
#include "uci.h"
|
||||||
#include "endgame.h"
|
|
||||||
#include "syzygy/tbprobe.h"
|
#include "syzygy/tbprobe.h"
|
||||||
|
|
||||||
namespace PSQT {
|
namespace PSQT {
|
||||||
@@ -38,13 +36,15 @@ int main(int argc, char* argv[]) {
|
|||||||
std::cout << engine_info() << std::endl;
|
std::cout << engine_info() << std::endl;
|
||||||
|
|
||||||
UCI::init(Options);
|
UCI::init(Options);
|
||||||
|
Tune::init();
|
||||||
PSQT::init();
|
PSQT::init();
|
||||||
Bitboards::init();
|
Bitboards::init();
|
||||||
Position::init();
|
Position::init();
|
||||||
Bitbases::init();
|
Bitbases::init();
|
||||||
Endgames::init();
|
Endgames::init();
|
||||||
Threads.set(Options["Threads"]);
|
Threads.set(size_t(Options["Threads"]));
|
||||||
Search::clear(); // After threads are up
|
Search::clear(); // After threads are up
|
||||||
|
Eval::init_NNUE();
|
||||||
|
|
||||||
UCI::loop(argc, argv);
|
UCI::loop(argc, argv);
|
||||||
|
|
||||||
|
|||||||
+14
-13
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -44,12 +42,12 @@ namespace {
|
|||||||
constexpr int QuadraticTheirs[][PIECE_TYPE_NB] = {
|
constexpr int QuadraticTheirs[][PIECE_TYPE_NB] = {
|
||||||
// THEIR PIECES
|
// THEIR PIECES
|
||||||
// pair pawn knight bishop rook queen
|
// pair pawn knight bishop rook queen
|
||||||
{ 0 }, // Bishop pair
|
{ }, // Bishop pair
|
||||||
{ 36, 0 }, // Pawn
|
{ 36, }, // Pawn
|
||||||
{ 9, 63, 0 }, // Knight OUR PIECES
|
{ 9, 63, }, // Knight OUR PIECES
|
||||||
{ 59, 65, 42, 0 }, // Bishop
|
{ 59, 65, 42, }, // Bishop
|
||||||
{ 46, 39, 24, -24, 0 }, // Rook
|
{ 46, 39, 24, -24, }, // Rook
|
||||||
{ 97, 100, -42, 137, 268, 0 } // Queen
|
{ 97, 100, -42, 137, 268, } // Queen
|
||||||
};
|
};
|
||||||
|
|
||||||
// Endgame evaluation and scaling functions are accessed directly and not through
|
// Endgame evaluation and scaling functions are accessed directly and not through
|
||||||
@@ -79,12 +77,14 @@ namespace {
|
|||||||
&& pos.count<PAWN>(~us) >= 1;
|
&& pos.count<PAWN>(~us) >= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// imbalance() calculates the imbalance by comparing the piece count of each
|
/// imbalance() calculates the imbalance by comparing the piece count of each
|
||||||
/// piece type for both colors.
|
/// piece type for both colors.
|
||||||
|
|
||||||
template<Color Us>
|
template<Color Us>
|
||||||
int imbalance(const int pieceCount[][PIECE_TYPE_NB]) {
|
int imbalance(const int pieceCount[][PIECE_TYPE_NB]) {
|
||||||
|
|
||||||
constexpr Color Them = (Us == WHITE ? BLACK : WHITE);
|
constexpr Color Them = ~Us;
|
||||||
|
|
||||||
int bonus = 0;
|
int bonus = 0;
|
||||||
|
|
||||||
@@ -94,9 +94,9 @@ namespace {
|
|||||||
if (!pieceCount[Us][pt1])
|
if (!pieceCount[Us][pt1])
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
int v = 0;
|
int v = QuadraticOurs[pt1][pt1] * pieceCount[Us][pt1];
|
||||||
|
|
||||||
for (int pt2 = NO_PIECE_TYPE; pt2 <= pt1; ++pt2)
|
for (int pt2 = NO_PIECE_TYPE; pt2 < pt1; ++pt2)
|
||||||
v += QuadraticOurs[pt1][pt2] * pieceCount[Us][pt2]
|
v += QuadraticOurs[pt1][pt2] * pieceCount[Us][pt2]
|
||||||
+ QuadraticTheirs[pt1][pt2] * pieceCount[Them][pt2];
|
+ QuadraticTheirs[pt1][pt2] * pieceCount[Them][pt2];
|
||||||
|
|
||||||
@@ -110,6 +110,7 @@ namespace {
|
|||||||
|
|
||||||
namespace Material {
|
namespace Material {
|
||||||
|
|
||||||
|
|
||||||
/// Material::probe() looks up the current position's material configuration in
|
/// 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
|
/// 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
|
/// is found. Otherwise a new Entry is computed and stored there, so we don't
|
||||||
@@ -129,7 +130,7 @@ Entry* probe(const Position& pos) {
|
|||||||
|
|
||||||
Value npm_w = pos.non_pawn_material(WHITE);
|
Value npm_w = pos.non_pawn_material(WHITE);
|
||||||
Value npm_b = pos.non_pawn_material(BLACK);
|
Value npm_b = pos.non_pawn_material(BLACK);
|
||||||
Value npm = clamp(npm_w + npm_b, EndgameLimit, MidgameLimit);
|
Value npm = Utility::clamp(npm_w + npm_b, EndgameLimit, MidgameLimit);
|
||||||
|
|
||||||
// Map total non-pawn material into [PHASE_ENDGAME, PHASE_MIDGAME]
|
// Map total non-pawn material into [PHASE_ENDGAME, PHASE_MIDGAME]
|
||||||
e->gamePhase = Phase(((npm - EndgameLimit) * PHASE_MIDGAME) / (MidgameLimit - EndgameLimit));
|
e->gamePhase = Phase(((npm - EndgameLimit) * PHASE_MIDGAME) / (MidgameLimit - EndgameLimit));
|
||||||
|
|||||||
+2
-4
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -44,7 +42,7 @@ struct Entry {
|
|||||||
bool specialized_eval_exists() const { return evaluationFunction != nullptr; }
|
bool specialized_eval_exists() const { return evaluationFunction != nullptr; }
|
||||||
Value evaluate(const Position& pos) const { return (*evaluationFunction)(pos); }
|
Value evaluate(const Position& pos) const { return (*evaluationFunction)(pos); }
|
||||||
|
|
||||||
// scale_factor takes a position and a color as input and returns a scale factor
|
// 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
|
// 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
|
// 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
|
// the position. For instance, in KBP vs K endgames, the scaling function looks
|
||||||
|
|||||||
+194
-16
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -46,6 +44,12 @@ typedef bool(*fun3_t)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY);
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
#if defined(__linux__) && !defined(__ANDROID__)
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
#include "thread.h"
|
#include "thread.h"
|
||||||
@@ -56,7 +60,7 @@ namespace {
|
|||||||
|
|
||||||
/// Version number. If Version is left empty, then compile date in the format
|
/// Version number. If Version is left empty, then compile date in the format
|
||||||
/// DD-MM-YY and show in engine_info.
|
/// DD-MM-YY and show in engine_info.
|
||||||
const string Version = "11";
|
const string Version = "";
|
||||||
|
|
||||||
/// Our fancy logging facility. The trick here is to replace cin.rdbuf() and
|
/// Our fancy logging facility. The trick here is to replace cin.rdbuf() and
|
||||||
/// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We
|
/// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We
|
||||||
@@ -142,10 +146,8 @@ const string engine_info(bool to_uci) {
|
|||||||
ss << setw(2) << day << setw(2) << (1 + months.find(month) / 4) << year.substr(2);
|
ss << setw(2) << day << setw(2) << (1 + months.find(month) / 4) << year.substr(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
ss << (Is64Bit ? " 64" : "")
|
ss << (to_uci ? "\nid author ": " by ")
|
||||||
<< (HasPext ? " BMI2" : (HasPopCnt ? " POPCNT" : ""))
|
<< "the Stockfish developers (see AUTHORS file)";
|
||||||
<< (to_uci ? "\nid author ": " by ")
|
|
||||||
<< "T. Romstad, M. Costalba, J. Kiiski, G. Linscott";
|
|
||||||
|
|
||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
@@ -155,9 +157,9 @@ const string engine_info(bool to_uci) {
|
|||||||
|
|
||||||
const std::string compiler_info() {
|
const std::string compiler_info() {
|
||||||
|
|
||||||
#define STRINGIFY2(x) #x
|
#define stringify2(x) #x
|
||||||
#define STRINGIFY(x) STRINGIFY2(x)
|
#define stringify(x) stringify2(x)
|
||||||
#define VER_STRING(major, minor, patch) STRINGIFY(major) "." STRINGIFY(minor) "." STRINGIFY(patch)
|
#define make_version_string(major, minor, patch) stringify(major) "." stringify(minor) "." stringify(patch)
|
||||||
|
|
||||||
/// Predefined macros hell:
|
/// Predefined macros hell:
|
||||||
///
|
///
|
||||||
@@ -171,20 +173,20 @@ const std::string compiler_info() {
|
|||||||
|
|
||||||
#ifdef __clang__
|
#ifdef __clang__
|
||||||
compiler += "clang++ ";
|
compiler += "clang++ ";
|
||||||
compiler += VER_STRING(__clang_major__, __clang_minor__, __clang_patchlevel__);
|
compiler += make_version_string(__clang_major__, __clang_minor__, __clang_patchlevel__);
|
||||||
#elif __INTEL_COMPILER
|
#elif __INTEL_COMPILER
|
||||||
compiler += "Intel compiler ";
|
compiler += "Intel compiler ";
|
||||||
compiler += "(version ";
|
compiler += "(version ";
|
||||||
compiler += STRINGIFY(__INTEL_COMPILER) " update " STRINGIFY(__INTEL_COMPILER_UPDATE);
|
compiler += stringify(__INTEL_COMPILER) " update " stringify(__INTEL_COMPILER_UPDATE);
|
||||||
compiler += ")";
|
compiler += ")";
|
||||||
#elif _MSC_VER
|
#elif _MSC_VER
|
||||||
compiler += "MSVC ";
|
compiler += "MSVC ";
|
||||||
compiler += "(version ";
|
compiler += "(version ";
|
||||||
compiler += STRINGIFY(_MSC_FULL_VER) "." STRINGIFY(_MSC_BUILD);
|
compiler += stringify(_MSC_FULL_VER) "." stringify(_MSC_BUILD);
|
||||||
compiler += ")";
|
compiler += ")";
|
||||||
#elif __GNUC__
|
#elif __GNUC__
|
||||||
compiler += "g++ (GNUC) ";
|
compiler += "g++ (GNUC) ";
|
||||||
compiler += VER_STRING(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__);
|
compiler += make_version_string(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__);
|
||||||
#else
|
#else
|
||||||
compiler += "Unknown compiler ";
|
compiler += "Unknown compiler ";
|
||||||
compiler += "(unknown version)";
|
compiler += "(unknown version)";
|
||||||
@@ -210,7 +212,33 @@ const std::string compiler_info() {
|
|||||||
compiler += " on unknown system";
|
compiler += " on unknown system";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
compiler += "\n __VERSION__ macro expands to: ";
|
compiler += "\nCompilation settings include: ";
|
||||||
|
compiler += (Is64Bit ? " 64bit" : " 32bit");
|
||||||
|
#if defined(USE_AVX512)
|
||||||
|
compiler += " AVX512";
|
||||||
|
#endif
|
||||||
|
#if defined(USE_AVX2)
|
||||||
|
compiler += " AVX2";
|
||||||
|
#endif
|
||||||
|
#if defined(USE_SSE42)
|
||||||
|
compiler += " SSE42";
|
||||||
|
#endif
|
||||||
|
#if defined(USE_SSE41)
|
||||||
|
compiler += " SSE41";
|
||||||
|
#endif
|
||||||
|
#if defined(USE_SSSE3)
|
||||||
|
compiler += " SSSE3";
|
||||||
|
#endif
|
||||||
|
#if defined(USE_SSE3)
|
||||||
|
compiler += " SSE3";
|
||||||
|
#endif
|
||||||
|
compiler += (HasPext ? " BMI2" : "");
|
||||||
|
compiler += (HasPopCnt ? " POPCNT" : "");
|
||||||
|
#if !defined(NDEBUG)
|
||||||
|
compiler += " DEBUG";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
compiler += "\n__VERSION__ macro expands to: ";
|
||||||
#ifdef __VERSION__
|
#ifdef __VERSION__
|
||||||
compiler += __VERSION__;
|
compiler += __VERSION__;
|
||||||
#else
|
#else
|
||||||
@@ -288,6 +316,156 @@ void prefetch(void* addr) {
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/// Wrappers for systems where the c++17 implementation doesn't guarantee the availability of aligned_alloc.
|
||||||
|
/// Memory allocated with std_aligned_alloc must be freed with std_aligned_free.
|
||||||
|
///
|
||||||
|
|
||||||
|
void* std_aligned_alloc(size_t alignment, size_t size) {
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
return aligned_alloc(alignment, size);
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
return _mm_malloc(size, alignment);
|
||||||
|
#else
|
||||||
|
return std::aligned_alloc(alignment, size);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void std_aligned_free(void* ptr) {
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
free(ptr);
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
_mm_free(ptr);
|
||||||
|
#else
|
||||||
|
free(ptr);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// aligned_ttmem_alloc() will return suitably aligned memory, and if possible use large pages.
|
||||||
|
/// The returned pointer is the aligned one, while the mem argument is the one that needs
|
||||||
|
/// to be passed to free. With c++17 some of this functionality could be simplified.
|
||||||
|
|
||||||
|
#if defined(__linux__) && !defined(__ANDROID__)
|
||||||
|
|
||||||
|
void* aligned_ttmem_alloc(size_t allocSize, void*& mem) {
|
||||||
|
|
||||||
|
constexpr size_t alignment = 2 * 1024 * 1024; // assumed 2MB page sizes
|
||||||
|
size_t size = ((allocSize + alignment - 1) / alignment) * alignment; // multiple of alignment
|
||||||
|
if (posix_memalign(&mem, alignment, size))
|
||||||
|
mem = nullptr;
|
||||||
|
madvise(mem, allocSize, MADV_HUGEPAGE);
|
||||||
|
return mem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif defined(_WIN64)
|
||||||
|
|
||||||
|
static void* aligned_ttmem_alloc_large_pages(size_t allocSize) {
|
||||||
|
|
||||||
|
HANDLE hProcessToken { };
|
||||||
|
LUID luid { };
|
||||||
|
void* mem = nullptr;
|
||||||
|
|
||||||
|
const size_t largePageSize = GetLargePageMinimum();
|
||||||
|
if (!largePageSize)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
// We need SeLockMemoryPrivilege, so try to enable it for the process
|
||||||
|
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
if (LookupPrivilegeValue(NULL, SE_LOCK_MEMORY_NAME, &luid))
|
||||||
|
{
|
||||||
|
TOKEN_PRIVILEGES tp { };
|
||||||
|
TOKEN_PRIVILEGES prevTp { };
|
||||||
|
DWORD prevTpLen = 0;
|
||||||
|
|
||||||
|
tp.PrivilegeCount = 1;
|
||||||
|
tp.Privileges[0].Luid = luid;
|
||||||
|
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
|
||||||
|
|
||||||
|
// Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() succeeds,
|
||||||
|
// we still need to query GetLastError() to ensure that the privileges were actually obtained.
|
||||||
|
if (AdjustTokenPrivileges(
|
||||||
|
hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, &prevTpLen) &&
|
||||||
|
GetLastError() == ERROR_SUCCESS)
|
||||||
|
{
|
||||||
|
// Round up size to full pages and allocate
|
||||||
|
allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1);
|
||||||
|
mem = VirtualAlloc(
|
||||||
|
NULL, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE);
|
||||||
|
|
||||||
|
// Privilege no longer needed, restore previous state
|
||||||
|
AdjustTokenPrivileges(hProcessToken, FALSE, &prevTp, 0, NULL, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseHandle(hProcessToken);
|
||||||
|
|
||||||
|
return mem;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* aligned_ttmem_alloc(size_t allocSize, void*& mem) {
|
||||||
|
|
||||||
|
static bool firstCall = true;
|
||||||
|
|
||||||
|
// Try to allocate large pages
|
||||||
|
mem = aligned_ttmem_alloc_large_pages(allocSize);
|
||||||
|
|
||||||
|
// Suppress info strings on the first call. The first call occurs before 'uci'
|
||||||
|
// is received and in that case this output confuses some GUIs.
|
||||||
|
if (!firstCall)
|
||||||
|
{
|
||||||
|
if (mem)
|
||||||
|
sync_cout << "info string Hash table allocation: Windows large pages used." << sync_endl;
|
||||||
|
else
|
||||||
|
sync_cout << "info string Hash table allocation: Windows large pages not used." << sync_endl;
|
||||||
|
}
|
||||||
|
firstCall = false;
|
||||||
|
|
||||||
|
// Fall back to regular, page aligned, allocation if necessary
|
||||||
|
if (!mem)
|
||||||
|
mem = VirtualAlloc(NULL, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
|
||||||
|
|
||||||
|
return mem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
void* aligned_ttmem_alloc(size_t allocSize, void*& mem) {
|
||||||
|
|
||||||
|
constexpr size_t alignment = 64; // assumed cache line size
|
||||||
|
size_t size = allocSize + alignment - 1; // allocate some extra space
|
||||||
|
mem = malloc(size);
|
||||||
|
void* ret = reinterpret_cast<void*>((uintptr_t(mem) + alignment - 1) & ~uintptr_t(alignment - 1));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/// aligned_ttmem_free() will free the previously allocated ttmem
|
||||||
|
|
||||||
|
#if defined(_WIN64)
|
||||||
|
|
||||||
|
void aligned_ttmem_free(void* mem) {
|
||||||
|
|
||||||
|
if (mem && !VirtualFree(mem, 0, MEM_RELEASE))
|
||||||
|
{
|
||||||
|
DWORD err = GetLastError();
|
||||||
|
std::cerr << "Failed to free transposition table. Error code: 0x" <<
|
||||||
|
std::hex << err << std::dec << std::endl;
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
void aligned_ttmem_free(void *mem) {
|
||||||
|
free(mem);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
namespace WinProcGroup {
|
namespace WinProcGroup {
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
|
|||||||
+26
-3
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -33,6 +31,10 @@ const std::string engine_info(bool to_uci = false);
|
|||||||
const std::string compiler_info();
|
const std::string compiler_info();
|
||||||
void prefetch(void* addr);
|
void prefetch(void* addr);
|
||||||
void start_logger(const std::string& fname);
|
void start_logger(const std::string& fname);
|
||||||
|
void* std_aligned_alloc(size_t alignment, size_t size);
|
||||||
|
void std_aligned_free(void* ptr);
|
||||||
|
void* aligned_ttmem_alloc(size_t size, void*& mem);
|
||||||
|
void aligned_ttmem_free(void* mem); // nop if mem == nullptr
|
||||||
|
|
||||||
void dbg_hit_on(bool b);
|
void dbg_hit_on(bool b);
|
||||||
void dbg_hit_on(bool c, bool b);
|
void dbg_hit_on(bool c, bool b);
|
||||||
@@ -63,6 +65,14 @@ std::ostream& operator<<(std::ostream&, SyncCout);
|
|||||||
#define sync_cout std::cout << IO_LOCK
|
#define sync_cout std::cout << IO_LOCK
|
||||||
#define sync_endl std::endl << IO_UNLOCK
|
#define sync_endl std::endl << IO_UNLOCK
|
||||||
|
|
||||||
|
namespace Utility {
|
||||||
|
|
||||||
|
/// Clamp a value between lo and hi. Available in c++17.
|
||||||
|
template<class T> constexpr const T& clamp(const T& v, const T& lo, const T& hi) {
|
||||||
|
return v < lo ? lo : v > hi ? hi : v;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/// xorshift64star Pseudo-Random Number Generator
|
/// xorshift64star Pseudo-Random Number Generator
|
||||||
/// This class is based on original code written and dedicated
|
/// This class is based on original code written and dedicated
|
||||||
@@ -100,6 +110,19 @@ public:
|
|||||||
{ return T(rand64() & rand64() & rand64()); }
|
{ return T(rand64() & rand64() & rand64()); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline uint64_t mul_hi64(uint64_t a, uint64_t b) {
|
||||||
|
#if defined(__GNUC__) && defined(IS_64BIT)
|
||||||
|
__extension__ typedef unsigned __int128 uint128;
|
||||||
|
return ((uint128)a * (uint128)b) >> 64;
|
||||||
|
#else
|
||||||
|
uint64_t aL = (uint32_t)a, aH = a >> 32;
|
||||||
|
uint64_t bL = (uint32_t)b, bH = b >> 32;
|
||||||
|
uint64_t c1 = (aL * bL) >> 32;
|
||||||
|
uint64_t c2 = aH * bL + c1;
|
||||||
|
uint64_t c3 = aL * bH + (uint32_t)c2;
|
||||||
|
return aH * bH + (c2 >> 32) + (c3 >> 32);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
/// Under Windows it is not possible for a process to run on more than one
|
/// Under Windows it is not possible for a process to run on more than one
|
||||||
/// logical processor group. This usually means to be limited to use max 64
|
/// logical processor group. This usually means to be limited to use max 64
|
||||||
|
|||||||
+64
-67
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -29,22 +27,20 @@ namespace {
|
|||||||
ExtMove* make_promotions(ExtMove* moveList, Square to, Square ksq) {
|
ExtMove* make_promotions(ExtMove* moveList, Square to, Square ksq) {
|
||||||
|
|
||||||
if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
|
if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
|
||||||
|
{
|
||||||
*moveList++ = make<PROMOTION>(to - D, to, QUEEN);
|
*moveList++ = make<PROMOTION>(to - D, to, QUEEN);
|
||||||
|
if (attacks_bb<KNIGHT>(to) & ksq)
|
||||||
|
*moveList++ = make<PROMOTION>(to - D, to, KNIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
if (Type == QUIETS || Type == EVASIONS || Type == NON_EVASIONS)
|
if (Type == QUIETS || Type == EVASIONS || Type == NON_EVASIONS)
|
||||||
{
|
{
|
||||||
*moveList++ = make<PROMOTION>(to - D, to, ROOK);
|
*moveList++ = make<PROMOTION>(to - D, to, ROOK);
|
||||||
*moveList++ = make<PROMOTION>(to - D, to, BISHOP);
|
*moveList++ = make<PROMOTION>(to - D, to, BISHOP);
|
||||||
|
if (!(attacks_bb<KNIGHT>(to) & ksq))
|
||||||
*moveList++ = make<PROMOTION>(to - D, to, KNIGHT);
|
*moveList++ = make<PROMOTION>(to - D, to, KNIGHT);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Knight promotion is the only promotion that can give a direct check
|
|
||||||
// that's not already included in the queen promotion.
|
|
||||||
if (Type == QUIET_CHECKS && (PseudoAttacks[KNIGHT][to] & ksq))
|
|
||||||
*moveList++ = make<PROMOTION>(to - D, to, KNIGHT);
|
|
||||||
else
|
|
||||||
(void)ksq; // Silence a warning under MSVC
|
|
||||||
|
|
||||||
return moveList;
|
return moveList;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,8 +48,7 @@ namespace {
|
|||||||
template<Color Us, GenType Type>
|
template<Color Us, GenType Type>
|
||||||
ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard target) {
|
ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard target) {
|
||||||
|
|
||||||
// Compute some compile time parameters relative to the white side
|
constexpr Color Them = ~Us;
|
||||||
constexpr Color Them = (Us == WHITE ? BLACK : WHITE);
|
|
||||||
constexpr Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB);
|
constexpr Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB);
|
||||||
constexpr Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB);
|
constexpr Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB);
|
||||||
constexpr Direction Up = pawn_push(Us);
|
constexpr Direction Up = pawn_push(Us);
|
||||||
@@ -85,8 +80,8 @@ namespace {
|
|||||||
|
|
||||||
if (Type == QUIET_CHECKS)
|
if (Type == QUIET_CHECKS)
|
||||||
{
|
{
|
||||||
b1 &= pos.attacks_from<PAWN>(ksq, Them);
|
b1 &= pawn_attacks_bb(Them, ksq);
|
||||||
b2 &= pos.attacks_from<PAWN>(ksq, Them);
|
b2 &= pawn_attacks_bb(Them, ksq);
|
||||||
|
|
||||||
// Add pawn pushes which give discovered check. This is possible only
|
// Add pawn pushes which give discovered check. This is possible only
|
||||||
// if the pawn is not on the same file as the enemy king, because we
|
// if the pawn is not on the same file as the enemy king, because we
|
||||||
@@ -167,7 +162,7 @@ namespace {
|
|||||||
if (Type == EVASIONS && !(target & (pos.ep_square() - Up)))
|
if (Type == EVASIONS && !(target & (pos.ep_square() - Up)))
|
||||||
return moveList;
|
return moveList;
|
||||||
|
|
||||||
b1 = pawnsNotOn7 & pos.attacks_from<PAWN>(pos.ep_square(), Them);
|
b1 = pawnsNotOn7 & pawn_attacks_bb(Them, pos.ep_square());
|
||||||
|
|
||||||
assert(b1);
|
assert(b1);
|
||||||
|
|
||||||
@@ -180,27 +175,26 @@ namespace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
template<PieceType Pt, bool Checks>
|
template<Color Us, PieceType Pt, bool Checks>
|
||||||
ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Color us,
|
ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target) {
|
||||||
Bitboard target) {
|
|
||||||
|
|
||||||
static_assert(Pt != KING && Pt != PAWN, "Unsupported piece type in generate_moves()");
|
static_assert(Pt != KING && Pt != PAWN, "Unsupported piece type in generate_moves()");
|
||||||
|
|
||||||
const Square* pl = pos.squares<Pt>(us);
|
const Square* pl = pos.squares<Pt>(Us);
|
||||||
|
|
||||||
for (Square from = *pl; from != SQ_NONE; from = *++pl)
|
for (Square from = *pl; from != SQ_NONE; from = *++pl)
|
||||||
{
|
{
|
||||||
if (Checks)
|
if (Checks)
|
||||||
{
|
{
|
||||||
if ( (Pt == BISHOP || Pt == ROOK || Pt == QUEEN)
|
if ( (Pt == BISHOP || Pt == ROOK || Pt == QUEEN)
|
||||||
&& !(PseudoAttacks[Pt][from] & target & pos.check_squares(Pt)))
|
&& !(attacks_bb<Pt>(from) & target & pos.check_squares(Pt)))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (pos.blockers_for_king(~us) & from)
|
if (pos.blockers_for_king(~Us) & from)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bitboard b = pos.attacks_from<Pt>(from) & target;
|
Bitboard b = attacks_bb<Pt>(from, pos.pieces()) & target;
|
||||||
|
|
||||||
if (Checks)
|
if (Checks)
|
||||||
b &= pos.check_squares(Pt);
|
b &= pos.check_squares(Pt);
|
||||||
@@ -214,33 +208,49 @@ namespace {
|
|||||||
|
|
||||||
|
|
||||||
template<Color Us, GenType Type>
|
template<Color Us, GenType Type>
|
||||||
ExtMove* generate_all(const Position& pos, ExtMove* moveList, Bitboard target) {
|
ExtMove* generate_all(const Position& pos, ExtMove* moveList) {
|
||||||
|
|
||||||
constexpr CastlingRights OO = Us & KING_SIDE;
|
|
||||||
constexpr CastlingRights OOO = Us & QUEEN_SIDE;
|
|
||||||
constexpr bool Checks = Type == QUIET_CHECKS; // Reduce template instantations
|
constexpr bool Checks = Type == QUIET_CHECKS; // Reduce template instantations
|
||||||
|
Bitboard target;
|
||||||
|
|
||||||
|
switch (Type)
|
||||||
|
{
|
||||||
|
case CAPTURES:
|
||||||
|
target = pos.pieces(~Us);
|
||||||
|
break;
|
||||||
|
case QUIETS:
|
||||||
|
case QUIET_CHECKS:
|
||||||
|
target = ~pos.pieces();
|
||||||
|
break;
|
||||||
|
case EVASIONS:
|
||||||
|
{
|
||||||
|
Square checksq = lsb(pos.checkers());
|
||||||
|
target = between_bb(pos.square<KING>(Us), checksq) | checksq;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case NON_EVASIONS:
|
||||||
|
target = ~pos.pieces(Us);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
static_assert(true, "Unsupported type in generate_all()");
|
||||||
|
}
|
||||||
|
|
||||||
moveList = generate_pawn_moves<Us, Type>(pos, moveList, target);
|
moveList = generate_pawn_moves<Us, Type>(pos, moveList, target);
|
||||||
moveList = generate_moves<KNIGHT, Checks>(pos, moveList, Us, target);
|
moveList = generate_moves<Us, KNIGHT, Checks>(pos, moveList, target);
|
||||||
moveList = generate_moves<BISHOP, Checks>(pos, moveList, Us, target);
|
moveList = generate_moves<Us, BISHOP, Checks>(pos, moveList, target);
|
||||||
moveList = generate_moves< ROOK, Checks>(pos, moveList, Us, target);
|
moveList = generate_moves<Us, ROOK, Checks>(pos, moveList, target);
|
||||||
moveList = generate_moves< QUEEN, Checks>(pos, moveList, Us, target);
|
moveList = generate_moves<Us, QUEEN, Checks>(pos, moveList, target);
|
||||||
|
|
||||||
if (Type != QUIET_CHECKS && Type != EVASIONS)
|
if (Type != QUIET_CHECKS && Type != EVASIONS)
|
||||||
{
|
{
|
||||||
Square ksq = pos.square<KING>(Us);
|
Square ksq = pos.square<KING>(Us);
|
||||||
Bitboard b = pos.attacks_from<KING>(ksq) & target;
|
Bitboard b = attacks_bb<KING>(ksq) & target;
|
||||||
while (b)
|
while (b)
|
||||||
*moveList++ = make_move(ksq, pop_lsb(&b));
|
*moveList++ = make_move(ksq, pop_lsb(&b));
|
||||||
|
|
||||||
if (Type != CAPTURES && pos.can_castle(CastlingRights(OO | OOO)))
|
if ((Type != CAPTURES) && pos.can_castle(Us & ANY_CASTLING))
|
||||||
{
|
for(CastlingRights cr : { Us & KING_SIDE, Us & QUEEN_SIDE } )
|
||||||
if (!pos.castling_impeded(OO) && pos.can_castle(OO))
|
if (!pos.castling_impeded(cr) && pos.can_castle(cr))
|
||||||
*moveList++ = make<CASTLING>(ksq, pos.castling_rook_square(OO));
|
*moveList++ = make<CASTLING>(ksq, pos.castling_rook_square(cr));
|
||||||
|
|
||||||
if (!pos.castling_impeded(OOO) && pos.can_castle(OOO))
|
|
||||||
*moveList++ = make<CASTLING>(ksq, pos.castling_rook_square(OOO));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return moveList;
|
return moveList;
|
||||||
@@ -249,8 +259,8 @@ namespace {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
|
||||||
/// <CAPTURES> Generates all pseudo-legal captures and queen promotions
|
/// <CAPTURES> Generates all pseudo-legal captures plus queen and checking knight promotions
|
||||||
/// <QUIETS> Generates all pseudo-legal non-captures and underpromotions
|
/// <QUIETS> Generates all pseudo-legal non-captures and underpromotions(except checking knight)
|
||||||
/// <NON_EVASIONS> Generates all pseudo-legal captures and non-captures
|
/// <NON_EVASIONS> Generates all pseudo-legal captures and non-captures
|
||||||
///
|
///
|
||||||
/// Returns a pointer to the end of the move list.
|
/// Returns a pointer to the end of the move list.
|
||||||
@@ -263,12 +273,8 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) {
|
|||||||
|
|
||||||
Color us = pos.side_to_move();
|
Color us = pos.side_to_move();
|
||||||
|
|
||||||
Bitboard target = Type == CAPTURES ? pos.pieces(~us)
|
return us == WHITE ? generate_all<WHITE, Type>(pos, moveList)
|
||||||
: Type == QUIETS ? ~pos.pieces()
|
: generate_all<BLACK, Type>(pos, moveList);
|
||||||
: Type == NON_EVASIONS ? ~pos.pieces(us) : 0;
|
|
||||||
|
|
||||||
return us == WHITE ? generate_all<WHITE, Type>(pos, moveList, target)
|
|
||||||
: generate_all<BLACK, Type>(pos, moveList, target);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Explicit template instantiations
|
// Explicit template instantiations
|
||||||
@@ -277,35 +283,32 @@ template ExtMove* generate<QUIETS>(const Position&, ExtMove*);
|
|||||||
template ExtMove* generate<NON_EVASIONS>(const Position&, ExtMove*);
|
template ExtMove* generate<NON_EVASIONS>(const Position&, ExtMove*);
|
||||||
|
|
||||||
|
|
||||||
/// generate<QUIET_CHECKS> generates all pseudo-legal non-captures and knight
|
/// generate<QUIET_CHECKS> generates all pseudo-legal non-captures.
|
||||||
/// underpromotions that give check. Returns a pointer to the end of the move list.
|
/// Returns a pointer to the end of the move list.
|
||||||
template<>
|
template<>
|
||||||
ExtMove* generate<QUIET_CHECKS>(const Position& pos, ExtMove* moveList) {
|
ExtMove* generate<QUIET_CHECKS>(const Position& pos, ExtMove* moveList) {
|
||||||
|
|
||||||
assert(!pos.checkers());
|
assert(!pos.checkers());
|
||||||
|
|
||||||
Color us = pos.side_to_move();
|
Color us = pos.side_to_move();
|
||||||
Bitboard dc = pos.blockers_for_king(~us) & pos.pieces(us);
|
Bitboard dc = pos.blockers_for_king(~us) & pos.pieces(us) & ~pos.pieces(PAWN);
|
||||||
|
|
||||||
while (dc)
|
while (dc)
|
||||||
{
|
{
|
||||||
Square from = pop_lsb(&dc);
|
Square from = pop_lsb(&dc);
|
||||||
PieceType pt = type_of(pos.piece_on(from));
|
PieceType pt = type_of(pos.piece_on(from));
|
||||||
|
|
||||||
if (pt == PAWN)
|
Bitboard b = attacks_bb(pt, from, pos.pieces()) & ~pos.pieces();
|
||||||
continue; // Will be generated together with direct checks
|
|
||||||
|
|
||||||
Bitboard b = pos.attacks_from(pt, from) & ~pos.pieces();
|
|
||||||
|
|
||||||
if (pt == KING)
|
if (pt == KING)
|
||||||
b &= ~PseudoAttacks[QUEEN][pos.square<KING>(~us)];
|
b &= ~attacks_bb<QUEEN>(pos.square<KING>(~us));
|
||||||
|
|
||||||
while (b)
|
while (b)
|
||||||
*moveList++ = make_move(from, pop_lsb(&b));
|
*moveList++ = make_move(from, pop_lsb(&b));
|
||||||
}
|
}
|
||||||
|
|
||||||
return us == WHITE ? generate_all<WHITE, QUIET_CHECKS>(pos, moveList, ~pos.pieces())
|
return us == WHITE ? generate_all<WHITE, QUIET_CHECKS>(pos, moveList)
|
||||||
: generate_all<BLACK, QUIET_CHECKS>(pos, moveList, ~pos.pieces());
|
: generate_all<BLACK, QUIET_CHECKS>(pos, moveList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -325,13 +328,10 @@ ExtMove* generate<EVASIONS>(const Position& pos, ExtMove* moveList) {
|
|||||||
// the king evasions in order to skip known illegal moves, which avoids any
|
// the king evasions in order to skip known illegal moves, which avoids any
|
||||||
// useless legality checks later on.
|
// useless legality checks later on.
|
||||||
while (sliders)
|
while (sliders)
|
||||||
{
|
sliderAttacks |= line_bb(ksq, pop_lsb(&sliders)) & ~pos.checkers();
|
||||||
Square checksq = pop_lsb(&sliders);
|
|
||||||
sliderAttacks |= LineBB[checksq][ksq] ^ checksq;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate evasions for king, capture and non capture moves
|
// Generate evasions for king, capture and non capture moves
|
||||||
Bitboard b = pos.attacks_from<KING>(ksq) & ~pos.pieces(us) & ~sliderAttacks;
|
Bitboard b = attacks_bb<KING>(ksq) & ~pos.pieces(us) & ~sliderAttacks;
|
||||||
while (b)
|
while (b)
|
||||||
*moveList++ = make_move(ksq, pop_lsb(&b));
|
*moveList++ = make_move(ksq, pop_lsb(&b));
|
||||||
|
|
||||||
@@ -339,11 +339,8 @@ ExtMove* generate<EVASIONS>(const Position& pos, ExtMove* moveList) {
|
|||||||
return moveList; // Double check, only a king move can save the day
|
return moveList; // Double check, only a king move can save the day
|
||||||
|
|
||||||
// Generate blocking evasions or captures of the checking piece
|
// Generate blocking evasions or captures of the checking piece
|
||||||
Square checksq = lsb(pos.checkers());
|
return us == WHITE ? generate_all<WHITE, EVASIONS>(pos, moveList)
|
||||||
Bitboard target = between_bb(checksq, ksq) | checksq;
|
: generate_all<BLACK, EVASIONS>(pos, moveList);
|
||||||
|
|
||||||
return us == WHITE ? generate_all<WHITE, EVASIONS>(pos, moveList, target)
|
|
||||||
: generate_all<BLACK, EVASIONS>(pos, moveList, target);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+1
-3
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|||||||
+17
-24
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -56,45 +54,39 @@ namespace {
|
|||||||
/// ordering is at the current node.
|
/// ordering is at the current node.
|
||||||
|
|
||||||
/// MovePicker constructor for the main search
|
/// MovePicker constructor for the main search
|
||||||
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh,
|
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, const LowPlyHistory* lp,
|
||||||
const CapturePieceToHistory* cph, const PieceToHistory** ch, Move cm, Move* killers)
|
const CapturePieceToHistory* cph, const PieceToHistory** ch, Move cm, const Move* killers, int pl)
|
||||||
: pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch),
|
: pos(p), mainHistory(mh), lowPlyHistory(lp), captureHistory(cph), continuationHistory(ch),
|
||||||
refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}}, depth(d) {
|
ttMove(ttm), refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}}, depth(d), ply(pl) {
|
||||||
|
|
||||||
assert(d > 0);
|
assert(d > 0);
|
||||||
|
|
||||||
stage = pos.checkers() ? EVASION_TT : MAIN_TT;
|
stage = (pos.checkers() ? EVASION_TT : MAIN_TT) +
|
||||||
ttMove = ttm && pos.pseudo_legal(ttm) ? ttm : MOVE_NONE;
|
!(ttm && pos.pseudo_legal(ttm));
|
||||||
stage += (ttMove == MOVE_NONE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// MovePicker constructor for quiescence search
|
/// MovePicker constructor for quiescence search
|
||||||
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh,
|
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh,
|
||||||
const CapturePieceToHistory* cph, const PieceToHistory** ch, Square rs)
|
const CapturePieceToHistory* cph, const PieceToHistory** ch, Square rs)
|
||||||
: pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch), recaptureSquare(rs), depth(d) {
|
: pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch), ttMove(ttm), recaptureSquare(rs), depth(d) {
|
||||||
|
|
||||||
assert(d <= 0);
|
assert(d <= 0);
|
||||||
|
|
||||||
stage = pos.checkers() ? EVASION_TT : QSEARCH_TT;
|
stage = (pos.checkers() ? EVASION_TT : QSEARCH_TT) +
|
||||||
ttMove = ttm
|
!(ttm && (depth > DEPTH_QS_RECAPTURES || to_sq(ttm) == recaptureSquare)
|
||||||
&& (depth > DEPTH_QS_RECAPTURES || to_sq(ttm) == recaptureSquare)
|
&& pos.pseudo_legal(ttm));
|
||||||
&& pos.pseudo_legal(ttm) ? ttm : MOVE_NONE;
|
|
||||||
stage += (ttMove == MOVE_NONE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// MovePicker constructor for ProbCut: we generate captures with SEE greater
|
/// MovePicker constructor for ProbCut: we generate captures with SEE greater
|
||||||
/// than or equal to the given threshold.
|
/// than or equal to the given threshold.
|
||||||
MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph)
|
MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph)
|
||||||
: pos(p), captureHistory(cph), threshold(th) {
|
: pos(p), captureHistory(cph), ttMove(ttm), threshold(th) {
|
||||||
|
|
||||||
assert(!pos.checkers());
|
assert(!pos.checkers());
|
||||||
|
|
||||||
stage = PROBCUT_TT;
|
stage = PROBCUT_TT + !(ttm && pos.capture(ttm)
|
||||||
ttMove = ttm
|
|
||||||
&& pos.capture(ttm)
|
|
||||||
&& pos.pseudo_legal(ttm)
|
&& pos.pseudo_legal(ttm)
|
||||||
&& pos.see_ge(ttm, threshold) ? ttm : MOVE_NONE;
|
&& pos.see_ge(ttm, threshold));
|
||||||
stage += (ttMove == MOVE_NONE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// MovePicker::score() assigns a numerical value to each move in a list, used
|
/// MovePicker::score() assigns a numerical value to each move in a list, used
|
||||||
@@ -115,7 +107,8 @@ void MovePicker::score() {
|
|||||||
+ 2 * (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)]
|
+ 2 * (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)]
|
||||||
+ 2 * (*continuationHistory[1])[pos.moved_piece(m)][to_sq(m)]
|
+ 2 * (*continuationHistory[1])[pos.moved_piece(m)][to_sq(m)]
|
||||||
+ 2 * (*continuationHistory[3])[pos.moved_piece(m)][to_sq(m)]
|
+ 2 * (*continuationHistory[3])[pos.moved_piece(m)][to_sq(m)]
|
||||||
+ (*continuationHistory[5])[pos.moved_piece(m)][to_sq(m)];
|
+ (*continuationHistory[5])[pos.moved_piece(m)][to_sq(m)]
|
||||||
|
+ (ply < MAX_LPH ? std::min(4, depth / 3) * (*lowPlyHistory)[ply][from_to(m)] : 0);
|
||||||
|
|
||||||
else // Type == EVASIONS
|
else // Type == EVASIONS
|
||||||
{
|
{
|
||||||
@@ -174,7 +167,7 @@ top:
|
|||||||
|
|
||||||
case GOOD_CAPTURE:
|
case GOOD_CAPTURE:
|
||||||
if (select<Best>([&](){
|
if (select<Best>([&](){
|
||||||
return pos.see_ge(*cur, Value(-55 * cur->value / 1024)) ?
|
return pos.see_ge(*cur, Value(-69 * cur->value / 1024)) ?
|
||||||
// Move losing capture to endBadCaptures to be tried later
|
// Move losing capture to endBadCaptures to be tried later
|
||||||
true : (*endBadCaptures++ = *cur, false); }))
|
true : (*endBadCaptures++ = *cur, false); }))
|
||||||
return *(cur - 1);
|
return *(cur - 1);
|
||||||
|
|||||||
+12
-4
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -88,6 +86,12 @@ enum StatsType { NoCaptures, Captures };
|
|||||||
/// the move's from and to squares, see www.chessprogramming.org/Butterfly_Boards
|
/// the move's from and to squares, see www.chessprogramming.org/Butterfly_Boards
|
||||||
typedef Stats<int16_t, 10692, COLOR_NB, int(SQUARE_NB) * int(SQUARE_NB)> ButterflyHistory;
|
typedef Stats<int16_t, 10692, COLOR_NB, int(SQUARE_NB) * int(SQUARE_NB)> ButterflyHistory;
|
||||||
|
|
||||||
|
/// At higher depths LowPlyHistory records successful quiet moves near the root and quiet
|
||||||
|
/// moves which are/were in the PV (ttPv)
|
||||||
|
/// It is cleared with each new search and filled during iterative deepening
|
||||||
|
constexpr int MAX_LPH = 4;
|
||||||
|
typedef Stats<int16_t, 10692, MAX_LPH, int(SQUARE_NB) * int(SQUARE_NB)> LowPlyHistory;
|
||||||
|
|
||||||
/// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous
|
/// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous
|
||||||
/// move, see www.chessprogramming.org/Countermove_Heuristic
|
/// move, see www.chessprogramming.org/Countermove_Heuristic
|
||||||
typedef Stats<Move, NOT_USED, PIECE_NB, SQUARE_NB> CounterMoveHistory;
|
typedef Stats<Move, NOT_USED, PIECE_NB, SQUARE_NB> CounterMoveHistory;
|
||||||
@@ -123,10 +127,12 @@ public:
|
|||||||
const PieceToHistory**,
|
const PieceToHistory**,
|
||||||
Square);
|
Square);
|
||||||
MovePicker(const Position&, Move, Depth, const ButterflyHistory*,
|
MovePicker(const Position&, Move, Depth, const ButterflyHistory*,
|
||||||
|
const LowPlyHistory*,
|
||||||
const CapturePieceToHistory*,
|
const CapturePieceToHistory*,
|
||||||
const PieceToHistory**,
|
const PieceToHistory**,
|
||||||
Move,
|
Move,
|
||||||
Move*);
|
const Move*,
|
||||||
|
int);
|
||||||
Move next_move(bool skipQuiets = false);
|
Move next_move(bool skipQuiets = false);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -137,6 +143,7 @@ private:
|
|||||||
|
|
||||||
const Position& pos;
|
const Position& pos;
|
||||||
const ButterflyHistory* mainHistory;
|
const ButterflyHistory* mainHistory;
|
||||||
|
const LowPlyHistory* lowPlyHistory;
|
||||||
const CapturePieceToHistory* captureHistory;
|
const CapturePieceToHistory* captureHistory;
|
||||||
const PieceToHistory** continuationHistory;
|
const PieceToHistory** continuationHistory;
|
||||||
Move ttMove;
|
Move ttMove;
|
||||||
@@ -145,6 +152,7 @@ private:
|
|||||||
Square recaptureSquare;
|
Square recaptureSquare;
|
||||||
Value threshold;
|
Value threshold;
|
||||||
Depth depth;
|
Depth depth;
|
||||||
|
int ply;
|
||||||
ExtMove moves[MAX_MOVES];
|
ExtMove moves[MAX_MOVES];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Stockfish is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Definition of input features and network structure used in NNUE evaluation function
|
||||||
|
|
||||||
|
#ifndef NNUE_HALFKP_256X2_32_32_H_INCLUDED
|
||||||
|
#define NNUE_HALFKP_256X2_32_32_H_INCLUDED
|
||||||
|
|
||||||
|
#include "../features/feature_set.h"
|
||||||
|
#include "../features/half_kp.h"
|
||||||
|
|
||||||
|
#include "../layers/input_slice.h"
|
||||||
|
#include "../layers/affine_transform.h"
|
||||||
|
#include "../layers/clipped_relu.h"
|
||||||
|
|
||||||
|
namespace Eval::NNUE {
|
||||||
|
|
||||||
|
// Input features used in evaluation function
|
||||||
|
using RawFeatures = Features::FeatureSet<
|
||||||
|
Features::HalfKP<Features::Side::kFriend>>;
|
||||||
|
|
||||||
|
// Number of input feature dimensions after conversion
|
||||||
|
constexpr IndexType kTransformedFeatureDimensions = 256;
|
||||||
|
|
||||||
|
namespace Layers {
|
||||||
|
|
||||||
|
// Define network structure
|
||||||
|
using InputLayer = InputSlice<kTransformedFeatureDimensions * 2>;
|
||||||
|
using HiddenLayer1 = ClippedReLU<AffineTransform<InputLayer, 32>>;
|
||||||
|
using HiddenLayer2 = ClippedReLU<AffineTransform<HiddenLayer1, 32>>;
|
||||||
|
using OutputLayer = AffineTransform<HiddenLayer2, 1>;
|
||||||
|
|
||||||
|
} // namespace Layers
|
||||||
|
|
||||||
|
using Network = Layers::OutputLayer;
|
||||||
|
|
||||||
|
} // namespace Eval::NNUE
|
||||||
|
|
||||||
|
#endif // #ifndef NNUE_HALFKP_256X2_32_32_H_INCLUDED
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
/*
|
||||||
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Stockfish is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Code for calculating NNUE evaluation function
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include "../evaluate.h"
|
||||||
|
#include "../position.h"
|
||||||
|
#include "../misc.h"
|
||||||
|
#include "../uci.h"
|
||||||
|
|
||||||
|
#include "evaluate_nnue.h"
|
||||||
|
|
||||||
|
ExtPieceSquare kpp_board_index[PIECE_NB] = {
|
||||||
|
// convention: W - us, B - them
|
||||||
|
// viewed from other side, W and B are reversed
|
||||||
|
{ PS_NONE, PS_NONE },
|
||||||
|
{ PS_W_PAWN, PS_B_PAWN },
|
||||||
|
{ PS_W_KNIGHT, PS_B_KNIGHT },
|
||||||
|
{ PS_W_BISHOP, PS_B_BISHOP },
|
||||||
|
{ PS_W_ROOK, PS_B_ROOK },
|
||||||
|
{ PS_W_QUEEN, PS_B_QUEEN },
|
||||||
|
{ PS_W_KING, PS_B_KING },
|
||||||
|
{ PS_NONE, PS_NONE },
|
||||||
|
{ PS_NONE, PS_NONE },
|
||||||
|
{ PS_B_PAWN, PS_W_PAWN },
|
||||||
|
{ PS_B_KNIGHT, PS_W_KNIGHT },
|
||||||
|
{ PS_B_BISHOP, PS_W_BISHOP },
|
||||||
|
{ PS_B_ROOK, PS_W_ROOK },
|
||||||
|
{ PS_B_QUEEN, PS_W_QUEEN },
|
||||||
|
{ PS_B_KING, PS_W_KING },
|
||||||
|
{ PS_NONE, PS_NONE }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
namespace Eval::NNUE {
|
||||||
|
|
||||||
|
// Input feature converter
|
||||||
|
AlignedPtr<FeatureTransformer> feature_transformer;
|
||||||
|
|
||||||
|
// Evaluation function
|
||||||
|
AlignedPtr<Network> network;
|
||||||
|
|
||||||
|
// Evaluation function file name
|
||||||
|
std::string fileName;
|
||||||
|
|
||||||
|
namespace Detail {
|
||||||
|
|
||||||
|
// Initialize the evaluation function parameters
|
||||||
|
template <typename T>
|
||||||
|
void Initialize(AlignedPtr<T>& pointer) {
|
||||||
|
|
||||||
|
pointer.reset(reinterpret_cast<T*>(std_aligned_alloc(alignof(T), sizeof(T))));
|
||||||
|
std::memset(pointer.get(), 0, sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read evaluation function parameters
|
||||||
|
template <typename T>
|
||||||
|
bool ReadParameters(std::istream& stream, const AlignedPtr<T>& pointer) {
|
||||||
|
|
||||||
|
std::uint32_t header;
|
||||||
|
stream.read(reinterpret_cast<char*>(&header), sizeof(header));
|
||||||
|
if (!stream || header != T::GetHashValue()) return false;
|
||||||
|
return pointer->ReadParameters(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Detail
|
||||||
|
|
||||||
|
// Initialize the evaluation function parameters
|
||||||
|
void Initialize() {
|
||||||
|
|
||||||
|
Detail::Initialize(feature_transformer);
|
||||||
|
Detail::Initialize(network);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read network header
|
||||||
|
bool ReadHeader(std::istream& stream,
|
||||||
|
std::uint32_t* hash_value, std::string* architecture) {
|
||||||
|
|
||||||
|
std::uint32_t version, size;
|
||||||
|
stream.read(reinterpret_cast<char*>(&version), sizeof(version));
|
||||||
|
stream.read(reinterpret_cast<char*>(hash_value), sizeof(*hash_value));
|
||||||
|
stream.read(reinterpret_cast<char*>(&size), sizeof(size));
|
||||||
|
if (!stream || version != kVersion) return false;
|
||||||
|
architecture->resize(size);
|
||||||
|
stream.read(&(*architecture)[0], size);
|
||||||
|
return !stream.fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read network parameters
|
||||||
|
bool ReadParameters(std::istream& stream) {
|
||||||
|
|
||||||
|
std::uint32_t hash_value;
|
||||||
|
std::string architecture;
|
||||||
|
if (!ReadHeader(stream, &hash_value, &architecture)) return false;
|
||||||
|
if (hash_value != kHashValue) return false;
|
||||||
|
if (!Detail::ReadParameters(stream, feature_transformer)) return false;
|
||||||
|
if (!Detail::ReadParameters(stream, network)) return false;
|
||||||
|
return stream && stream.peek() == std::ios::traits_type::eof();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proceed with the difference calculation if possible
|
||||||
|
static void UpdateAccumulatorIfPossible(const Position& pos) {
|
||||||
|
|
||||||
|
feature_transformer->UpdateAccumulatorIfPossible(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the evaluation value
|
||||||
|
static Value ComputeScore(const Position& pos, bool refresh) {
|
||||||
|
|
||||||
|
auto& accumulator = pos.state()->accumulator;
|
||||||
|
if (!refresh && accumulator.computed_score) {
|
||||||
|
return accumulator.score;
|
||||||
|
}
|
||||||
|
|
||||||
|
alignas(kCacheLineSize) TransformedFeatureType
|
||||||
|
transformed_features[FeatureTransformer::kBufferSize];
|
||||||
|
feature_transformer->Transform(pos, transformed_features, refresh);
|
||||||
|
alignas(kCacheLineSize) char buffer[Network::kBufferSize];
|
||||||
|
const auto output = network->Propagate(transformed_features, buffer);
|
||||||
|
|
||||||
|
auto score = static_cast<Value>(output[0] / FV_SCALE);
|
||||||
|
|
||||||
|
accumulator.score = score;
|
||||||
|
accumulator.computed_score = true;
|
||||||
|
return accumulator.score;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the evaluation function file
|
||||||
|
bool load_eval_file(const std::string& evalFile) {
|
||||||
|
|
||||||
|
Initialize();
|
||||||
|
fileName = evalFile;
|
||||||
|
|
||||||
|
std::ifstream stream(evalFile, std::ios::binary);
|
||||||
|
|
||||||
|
const bool result = ReadParameters(stream);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluation function. Perform differential calculation.
|
||||||
|
Value evaluate(const Position& pos) {
|
||||||
|
Value v = ComputeScore(pos, false);
|
||||||
|
v = Utility::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1);
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluation function. Perform full calculation.
|
||||||
|
Value compute_eval(const Position& pos) {
|
||||||
|
return ComputeScore(pos, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proceed with the difference calculation if possible
|
||||||
|
void update_eval(const Position& pos) {
|
||||||
|
UpdateAccumulatorIfPossible(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Eval::NNUE
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Stockfish is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// header used in NNUE evaluation function
|
||||||
|
|
||||||
|
#ifndef NNUE_EVALUATE_NNUE_H_INCLUDED
|
||||||
|
#define NNUE_EVALUATE_NNUE_H_INCLUDED
|
||||||
|
|
||||||
|
#include "nnue_feature_transformer.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace Eval::NNUE {
|
||||||
|
|
||||||
|
// Hash value of evaluation function structure
|
||||||
|
constexpr std::uint32_t kHashValue =
|
||||||
|
FeatureTransformer::GetHashValue() ^ Network::GetHashValue();
|
||||||
|
|
||||||
|
// Deleter for automating release of memory area
|
||||||
|
template <typename T>
|
||||||
|
struct AlignedDeleter {
|
||||||
|
void operator()(T* ptr) const {
|
||||||
|
ptr->~T();
|
||||||
|
std_aligned_free(ptr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using AlignedPtr = std::unique_ptr<T, AlignedDeleter<T>>;
|
||||||
|
|
||||||
|
} // namespace Eval::NNUE
|
||||||
|
|
||||||
|
#endif // #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
/*
|
||||||
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Stockfish is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// A class template that represents the input feature set of the NNUE evaluation function
|
||||||
|
|
||||||
|
#ifndef NNUE_FEATURE_SET_H_INCLUDED
|
||||||
|
#define NNUE_FEATURE_SET_H_INCLUDED
|
||||||
|
|
||||||
|
#include "features_common.h"
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
namespace Eval::NNUE::Features {
|
||||||
|
|
||||||
|
// Class template that represents a list of values
|
||||||
|
template <typename T, T... Values>
|
||||||
|
struct CompileTimeList;
|
||||||
|
|
||||||
|
template <typename T, T First, T... Remaining>
|
||||||
|
struct CompileTimeList<T, First, Remaining...> {
|
||||||
|
static constexpr bool Contains(T value) {
|
||||||
|
return value == First || CompileTimeList<T, Remaining...>::Contains(value);
|
||||||
|
}
|
||||||
|
static constexpr std::array<T, sizeof...(Remaining) + 1>
|
||||||
|
kValues = {{First, Remaining...}};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Base class of feature set
|
||||||
|
template <typename Derived>
|
||||||
|
class FeatureSetBase {
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Get a list of indices for active features
|
||||||
|
template <typename IndexListType>
|
||||||
|
static void AppendActiveIndices(
|
||||||
|
const Position& pos, TriggerEvent trigger, IndexListType active[2]) {
|
||||||
|
|
||||||
|
for (Color perspective : { WHITE, BLACK }) {
|
||||||
|
Derived::CollectActiveIndices(
|
||||||
|
pos, trigger, perspective, &active[perspective]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a list of indices for recently changed features
|
||||||
|
template <typename PositionType, typename IndexListType>
|
||||||
|
static void AppendChangedIndices(
|
||||||
|
const PositionType& pos, TriggerEvent trigger,
|
||||||
|
IndexListType removed[2], IndexListType added[2], bool reset[2]) {
|
||||||
|
|
||||||
|
const auto& dp = pos.state()->dirtyPiece;
|
||||||
|
if (dp.dirty_num == 0) return;
|
||||||
|
|
||||||
|
for (Color perspective : { WHITE, BLACK }) {
|
||||||
|
reset[perspective] = false;
|
||||||
|
switch (trigger) {
|
||||||
|
case TriggerEvent::kFriendKingMoved:
|
||||||
|
reset[perspective] =
|
||||||
|
dp.pieceId[0] == PIECE_ID_KING + perspective;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (reset[perspective]) {
|
||||||
|
Derived::CollectActiveIndices(
|
||||||
|
pos, trigger, perspective, &added[perspective]);
|
||||||
|
} else {
|
||||||
|
Derived::CollectChangedIndices(
|
||||||
|
pos, trigger, perspective,
|
||||||
|
&removed[perspective], &added[perspective]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Class template that represents the feature set
|
||||||
|
template <typename FeatureType>
|
||||||
|
class FeatureSet<FeatureType> : public FeatureSetBase<FeatureSet<FeatureType>> {
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Hash value embedded in the evaluation file
|
||||||
|
static constexpr std::uint32_t kHashValue = FeatureType::kHashValue;
|
||||||
|
// Number of feature dimensions
|
||||||
|
static constexpr IndexType kDimensions = FeatureType::kDimensions;
|
||||||
|
// Maximum number of simultaneously active features
|
||||||
|
static constexpr IndexType kMaxActiveDimensions =
|
||||||
|
FeatureType::kMaxActiveDimensions;
|
||||||
|
// Trigger for full calculation instead of difference calculation
|
||||||
|
using SortedTriggerSet =
|
||||||
|
CompileTimeList<TriggerEvent, FeatureType::kRefreshTrigger>;
|
||||||
|
static constexpr auto kRefreshTriggers = SortedTriggerSet::kValues;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Get a list of indices for active features
|
||||||
|
static void CollectActiveIndices(
|
||||||
|
const Position& pos, const TriggerEvent trigger, const Color perspective,
|
||||||
|
IndexList* const active) {
|
||||||
|
if (FeatureType::kRefreshTrigger == trigger) {
|
||||||
|
FeatureType::AppendActiveIndices(pos, perspective, active);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a list of indices for recently changed features
|
||||||
|
static void CollectChangedIndices(
|
||||||
|
const Position& pos, const TriggerEvent trigger, const Color perspective,
|
||||||
|
IndexList* const removed, IndexList* const added) {
|
||||||
|
|
||||||
|
if (FeatureType::kRefreshTrigger == trigger) {
|
||||||
|
FeatureType::AppendChangedIndices(pos, perspective, removed, added);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the base class and the class template that recursively uses itself a friend
|
||||||
|
friend class FeatureSetBase<FeatureSet>;
|
||||||
|
template <typename... FeatureTypes>
|
||||||
|
friend class FeatureSet;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Eval::NNUE::Features
|
||||||
|
|
||||||
|
#endif // #ifndef NNUE_FEATURE_SET_H_INCLUDED
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
|
Copyright (C) 2004-2020 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//Common header of input features of NNUE evaluation function
|
||||||
|
|
||||||
|
#ifndef NNUE_FEATURES_COMMON_H_INCLUDED
|
||||||
|
#define NNUE_FEATURES_COMMON_H_INCLUDED
|
||||||
|
|
||||||
|
#include "../../evaluate.h"
|
||||||
|
#include "../nnue_common.h"
|
||||||
|
|
||||||
|
namespace Eval::NNUE::Features {
|
||||||
|
|
||||||
|
class IndexList;
|
||||||
|
|
||||||
|
template <typename... FeatureTypes>
|
||||||
|
class FeatureSet;
|
||||||
|
|
||||||
|
// Trigger to perform full calculations instead of difference only
|
||||||
|
enum class TriggerEvent {
|
||||||
|
kFriendKingMoved // calculate full evaluation when own king moves
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Side {
|
||||||
|
kFriend // side to move
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Eval::NNUE::Features
|
||||||
|
|
||||||
|
#endif // #ifndef NNUE_FEATURES_COMMON_H_INCLUDED
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Stockfish is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//Definition of input features HalfKP of NNUE evaluation function
|
||||||
|
|
||||||
|
#include "half_kp.h"
|
||||||
|
#include "index_list.h"
|
||||||
|
|
||||||
|
namespace Eval::NNUE::Features {
|
||||||
|
|
||||||
|
// Find the index of the feature quantity from the king position and PieceSquare
|
||||||
|
template <Side AssociatedKing>
|
||||||
|
inline IndexType HalfKP<AssociatedKing>::MakeIndex(Square sq_k, PieceSquare p) {
|
||||||
|
return static_cast<IndexType>(PS_END) * static_cast<IndexType>(sq_k) + p;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get pieces information
|
||||||
|
template <Side AssociatedKing>
|
||||||
|
inline void HalfKP<AssociatedKing>::GetPieces(
|
||||||
|
const Position& pos, Color perspective,
|
||||||
|
PieceSquare** pieces, Square* sq_target_k) {
|
||||||
|
|
||||||
|
*pieces = (perspective == BLACK) ?
|
||||||
|
pos.eval_list()->piece_list_fb() :
|
||||||
|
pos.eval_list()->piece_list_fw();
|
||||||
|
const PieceId target = (AssociatedKing == Side::kFriend) ?
|
||||||
|
static_cast<PieceId>(PIECE_ID_KING + perspective) :
|
||||||
|
static_cast<PieceId>(PIECE_ID_KING + ~perspective);
|
||||||
|
*sq_target_k = static_cast<Square>(((*pieces)[target] - PS_W_KING) % SQUARE_NB);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a list of indices for active features
|
||||||
|
template <Side AssociatedKing>
|
||||||
|
void HalfKP<AssociatedKing>::AppendActiveIndices(
|
||||||
|
const Position& pos, Color perspective, IndexList* active) {
|
||||||
|
|
||||||
|
// Do nothing if array size is small to avoid compiler warning
|
||||||
|
if (RawFeatures::kMaxActiveDimensions < kMaxActiveDimensions) return;
|
||||||
|
|
||||||
|
PieceSquare* pieces;
|
||||||
|
Square sq_target_k;
|
||||||
|
GetPieces(pos, perspective, &pieces, &sq_target_k);
|
||||||
|
for (PieceId i = PIECE_ID_ZERO; i < PIECE_ID_KING; ++i) {
|
||||||
|
if (pieces[i] != PS_NONE) {
|
||||||
|
active->push_back(MakeIndex(sq_target_k, pieces[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a list of indices for recently changed features
|
||||||
|
template <Side AssociatedKing>
|
||||||
|
void HalfKP<AssociatedKing>::AppendChangedIndices(
|
||||||
|
const Position& pos, Color perspective,
|
||||||
|
IndexList* removed, IndexList* added) {
|
||||||
|
|
||||||
|
PieceSquare* pieces;
|
||||||
|
Square sq_target_k;
|
||||||
|
GetPieces(pos, perspective, &pieces, &sq_target_k);
|
||||||
|
const auto& dp = pos.state()->dirtyPiece;
|
||||||
|
for (int i = 0; i < dp.dirty_num; ++i) {
|
||||||
|
if (dp.pieceId[i] >= PIECE_ID_KING) continue;
|
||||||
|
const auto old_p = static_cast<PieceSquare>(
|
||||||
|
dp.old_piece[i].from[perspective]);
|
||||||
|
if (old_p != PS_NONE) {
|
||||||
|
removed->push_back(MakeIndex(sq_target_k, old_p));
|
||||||
|
}
|
||||||
|
const auto new_p = static_cast<PieceSquare>(
|
||||||
|
dp.new_piece[i].from[perspective]);
|
||||||
|
if (new_p != PS_NONE) {
|
||||||
|
added->push_back(MakeIndex(sq_target_k, new_p));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template class HalfKP<Side::kFriend>;
|
||||||
|
|
||||||
|
} // namespace Eval::NNUE::Features
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Stockfish is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//Definition of input features HalfKP of NNUE evaluation function
|
||||||
|
|
||||||
|
#ifndef NNUE_FEATURES_HALF_KP_H_INCLUDED
|
||||||
|
#define NNUE_FEATURES_HALF_KP_H_INCLUDED
|
||||||
|
|
||||||
|
#include "../../evaluate.h"
|
||||||
|
#include "features_common.h"
|
||||||
|
|
||||||
|
namespace Eval::NNUE::Features {
|
||||||
|
|
||||||
|
// Feature HalfKP: Combination of the position of own king
|
||||||
|
// and the position of pieces other than kings
|
||||||
|
template <Side AssociatedKing>
|
||||||
|
class HalfKP {
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Feature name
|
||||||
|
static constexpr const char* kName = "HalfKP(Friend)";
|
||||||
|
// Hash value embedded in the evaluation file
|
||||||
|
static constexpr std::uint32_t kHashValue =
|
||||||
|
0x5D69D5B9u ^ (AssociatedKing == Side::kFriend);
|
||||||
|
// Number of feature dimensions
|
||||||
|
static constexpr IndexType kDimensions =
|
||||||
|
static_cast<IndexType>(SQUARE_NB) * static_cast<IndexType>(PS_END);
|
||||||
|
// Maximum number of simultaneously active features
|
||||||
|
static constexpr IndexType kMaxActiveDimensions = PIECE_ID_KING;
|
||||||
|
// Trigger for full calculation instead of difference calculation
|
||||||
|
static constexpr TriggerEvent kRefreshTrigger = TriggerEvent::kFriendKingMoved;
|
||||||
|
|
||||||
|
// Get a list of indices for active features
|
||||||
|
static void AppendActiveIndices(const Position& pos, Color perspective,
|
||||||
|
IndexList* active);
|
||||||
|
|
||||||
|
// Get a list of indices for recently changed features
|
||||||
|
static void AppendChangedIndices(const Position& pos, Color perspective,
|
||||||
|
IndexList* removed, IndexList* added);
|
||||||
|
|
||||||
|
// Index of a feature for a given king position and another piece on some square
|
||||||
|
static IndexType MakeIndex(Square sq_k, PieceSquare p);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Get pieces information
|
||||||
|
static void GetPieces(const Position& pos, Color perspective,
|
||||||
|
PieceSquare** pieces, Square* sq_target_k);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Eval::NNUE::Features
|
||||||
|
|
||||||
|
#endif // #ifndef NNUE_FEATURES_HALF_KP_H_INCLUDED
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
|
Copyright (C) 2004-2020 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 index list of input features
|
||||||
|
|
||||||
|
#ifndef NNUE_FEATURES_INDEX_LIST_H_INCLUDED
|
||||||
|
#define NNUE_FEATURES_INDEX_LIST_H_INCLUDED
|
||||||
|
|
||||||
|
#include "../../position.h"
|
||||||
|
#include "../nnue_architecture.h"
|
||||||
|
|
||||||
|
namespace Eval::NNUE::Features {
|
||||||
|
|
||||||
|
// Class template used for feature index list
|
||||||
|
template <typename T, std::size_t MaxSize>
|
||||||
|
class ValueList {
|
||||||
|
|
||||||
|
public:
|
||||||
|
std::size_t size() const { return size_; }
|
||||||
|
void resize(std::size_t size) { size_ = size; }
|
||||||
|
void push_back(const T& value) { values_[size_++] = value; }
|
||||||
|
T& operator[](std::size_t index) { return values_[index]; }
|
||||||
|
T* begin() { return values_; }
|
||||||
|
T* end() { return values_ + size_; }
|
||||||
|
const T& operator[](std::size_t index) const { return values_[index]; }
|
||||||
|
const T* begin() const { return values_; }
|
||||||
|
const T* end() const { return values_ + size_; }
|
||||||
|
|
||||||
|
void swap(ValueList& other) {
|
||||||
|
const std::size_t max_size = std::max(size_, other.size_);
|
||||||
|
for (std::size_t i = 0; i < max_size; ++i) {
|
||||||
|
std::swap(values_[i], other.values_[i]);
|
||||||
|
}
|
||||||
|
std::swap(size_, other.size_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
T values_[MaxSize];
|
||||||
|
std::size_t size_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
//Type of feature index list
|
||||||
|
class IndexList
|
||||||
|
: public ValueList<IndexType, RawFeatures::kMaxActiveDimensions> {
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Eval::NNUE::Features
|
||||||
|
|
||||||
|
#endif // NNUE_FEATURES_INDEX_LIST_H_INCLUDED
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
/*
|
||||||
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Stockfish is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Definition of layer AffineTransform of NNUE evaluation function
|
||||||
|
|
||||||
|
#ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED
|
||||||
|
#define NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include "../nnue_common.h"
|
||||||
|
|
||||||
|
namespace Eval::NNUE::Layers {
|
||||||
|
|
||||||
|
// Affine transformation layer
|
||||||
|
template <typename PreviousLayer, IndexType OutputDimensions>
|
||||||
|
class AffineTransform {
|
||||||
|
public:
|
||||||
|
// Input/output type
|
||||||
|
using InputType = typename PreviousLayer::OutputType;
|
||||||
|
using OutputType = std::int32_t;
|
||||||
|
static_assert(std::is_same<InputType, std::uint8_t>::value, "");
|
||||||
|
|
||||||
|
// Number of input/output dimensions
|
||||||
|
static constexpr IndexType kInputDimensions =
|
||||||
|
PreviousLayer::kOutputDimensions;
|
||||||
|
static constexpr IndexType kOutputDimensions = OutputDimensions;
|
||||||
|
static constexpr IndexType kPaddedInputDimensions =
|
||||||
|
CeilToMultiple<IndexType>(kInputDimensions, kMaxSimdWidth);
|
||||||
|
|
||||||
|
// Size of forward propagation buffer used in this layer
|
||||||
|
static constexpr std::size_t kSelfBufferSize =
|
||||||
|
CeilToMultiple(kOutputDimensions * sizeof(OutputType), kCacheLineSize);
|
||||||
|
|
||||||
|
// Size of the forward propagation buffer used from the input layer to this layer
|
||||||
|
static constexpr std::size_t kBufferSize =
|
||||||
|
PreviousLayer::kBufferSize + kSelfBufferSize;
|
||||||
|
|
||||||
|
// Hash value embedded in the evaluation file
|
||||||
|
static constexpr std::uint32_t GetHashValue() {
|
||||||
|
std::uint32_t hash_value = 0xCC03DAE4u;
|
||||||
|
hash_value += kOutputDimensions;
|
||||||
|
hash_value ^= PreviousLayer::GetHashValue() >> 1;
|
||||||
|
hash_value ^= PreviousLayer::GetHashValue() << 31;
|
||||||
|
return hash_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read network parameters
|
||||||
|
bool ReadParameters(std::istream& stream) {
|
||||||
|
if (!previous_layer_.ReadParameters(stream)) return false;
|
||||||
|
stream.read(reinterpret_cast<char*>(biases_),
|
||||||
|
kOutputDimensions * sizeof(BiasType));
|
||||||
|
stream.read(reinterpret_cast<char*>(weights_),
|
||||||
|
kOutputDimensions * kPaddedInputDimensions *
|
||||||
|
sizeof(WeightType));
|
||||||
|
return !stream.fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forward propagation
|
||||||
|
const OutputType* Propagate(
|
||||||
|
const TransformedFeatureType* transformed_features, char* buffer) const {
|
||||||
|
const auto input = previous_layer_.Propagate(
|
||||||
|
transformed_features, buffer + kSelfBufferSize);
|
||||||
|
const auto output = reinterpret_cast<OutputType*>(buffer);
|
||||||
|
|
||||||
|
#if defined(USE_AVX512)
|
||||||
|
constexpr IndexType kNumChunks = kPaddedInputDimensions / (kSimdWidth * 2);
|
||||||
|
const __m512i kOnes = _mm512_set1_epi16(1);
|
||||||
|
const auto input_vector = reinterpret_cast<const __m512i*>(input);
|
||||||
|
|
||||||
|
#elif defined(USE_AVX2)
|
||||||
|
constexpr IndexType kNumChunks = kPaddedInputDimensions / kSimdWidth;
|
||||||
|
const __m256i kOnes = _mm256_set1_epi16(1);
|
||||||
|
const auto input_vector = reinterpret_cast<const __m256i*>(input);
|
||||||
|
|
||||||
|
#elif defined(USE_SSSE3)
|
||||||
|
constexpr IndexType kNumChunks = kPaddedInputDimensions / kSimdWidth;
|
||||||
|
const __m128i kOnes = _mm_set1_epi16(1);
|
||||||
|
const auto input_vector = reinterpret_cast<const __m128i*>(input);
|
||||||
|
|
||||||
|
#elif defined(USE_NEON)
|
||||||
|
constexpr IndexType kNumChunks = kPaddedInputDimensions / kSimdWidth;
|
||||||
|
const auto input_vector = reinterpret_cast<const int8x8_t*>(input);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
for (IndexType i = 0; i < kOutputDimensions; ++i) {
|
||||||
|
const IndexType offset = i * kPaddedInputDimensions;
|
||||||
|
|
||||||
|
#if defined(USE_AVX512)
|
||||||
|
__m512i sum = _mm512_setzero_si512();
|
||||||
|
const auto row = reinterpret_cast<const __m512i*>(&weights_[offset]);
|
||||||
|
for (IndexType j = 0; j < kNumChunks; ++j) {
|
||||||
|
|
||||||
|
#if defined(__MINGW32__) || defined(__MINGW64__)
|
||||||
|
__m512i product = _mm512_maddubs_epi16(_mm512_loadu_si512(&input_vector[j]), _mm512_load_si512(&row[j]));
|
||||||
|
#else
|
||||||
|
__m512i product = _mm512_maddubs_epi16(_mm512_load_si512(&input_vector[j]), _mm512_load_si512(&row[j]));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
product = _mm512_madd_epi16(product, kOnes);
|
||||||
|
sum = _mm512_add_epi32(sum, product);
|
||||||
|
}
|
||||||
|
output[i] = _mm512_reduce_add_epi32(sum) + biases_[i];
|
||||||
|
|
||||||
|
// Note: Changing kMaxSimdWidth from 32 to 64 breaks loading existing networks.
|
||||||
|
// As a result kPaddedInputDimensions may not be an even multiple of 64(512bit)
|
||||||
|
// and we have to do one more 256bit chunk.
|
||||||
|
if (kPaddedInputDimensions != kNumChunks * kSimdWidth * 2)
|
||||||
|
{
|
||||||
|
const auto iv_256 = reinterpret_cast<const __m256i*>(input);
|
||||||
|
const auto row_256 = reinterpret_cast<const __m256i*>(&weights_[offset]);
|
||||||
|
int j = kNumChunks * 2;
|
||||||
|
|
||||||
|
#if defined(__MINGW32__) || defined(__MINGW64__) // See HACK comment below in AVX2.
|
||||||
|
__m256i sum256 = _mm256_maddubs_epi16(_mm256_loadu_si256(&iv_256[j]), _mm256_load_si256(&row_256[j]));
|
||||||
|
#else
|
||||||
|
__m256i sum256 = _mm256_maddubs_epi16(_mm256_load_si256(&iv_256[j]), _mm256_load_si256(&row_256[j]));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
sum256 = _mm256_madd_epi16(sum256, _mm256_set1_epi16(1));
|
||||||
|
sum256 = _mm256_hadd_epi32(sum256, sum256);
|
||||||
|
sum256 = _mm256_hadd_epi32(sum256, sum256);
|
||||||
|
const __m128i lo = _mm256_extracti128_si256(sum256, 0);
|
||||||
|
const __m128i hi = _mm256_extracti128_si256(sum256, 1);
|
||||||
|
output[i] += _mm_cvtsi128_si32(lo) + _mm_cvtsi128_si32(hi);
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif defined(USE_AVX2)
|
||||||
|
__m256i sum = _mm256_setzero_si256();
|
||||||
|
const auto row = reinterpret_cast<const __m256i*>(&weights_[offset]);
|
||||||
|
for (IndexType j = 0; j < kNumChunks; ++j) {
|
||||||
|
__m256i product = _mm256_maddubs_epi16(
|
||||||
|
|
||||||
|
#if defined(__MINGW32__) || defined(__MINGW64__)
|
||||||
|
// HACK: Use _mm256_loadu_si256() instead of _mm256_load_si256. Because the binary
|
||||||
|
// compiled with g++ in MSYS2 crashes here because the output memory is not aligned
|
||||||
|
// even though alignas is specified.
|
||||||
|
_mm256_loadu_si256
|
||||||
|
#else
|
||||||
|
_mm256_load_si256
|
||||||
|
#endif
|
||||||
|
|
||||||
|
(&input_vector[j]), _mm256_load_si256(&row[j]));
|
||||||
|
product = _mm256_madd_epi16(product, kOnes);
|
||||||
|
sum = _mm256_add_epi32(sum, product);
|
||||||
|
}
|
||||||
|
sum = _mm256_hadd_epi32(sum, sum);
|
||||||
|
sum = _mm256_hadd_epi32(sum, sum);
|
||||||
|
const __m128i lo = _mm256_extracti128_si256(sum, 0);
|
||||||
|
const __m128i hi = _mm256_extracti128_si256(sum, 1);
|
||||||
|
output[i] = _mm_cvtsi128_si32(lo) + _mm_cvtsi128_si32(hi) + biases_[i];
|
||||||
|
|
||||||
|
#elif defined(USE_SSSE3)
|
||||||
|
__m128i sum = _mm_cvtsi32_si128(biases_[i]);
|
||||||
|
const auto row = reinterpret_cast<const __m128i*>(&weights_[offset]);
|
||||||
|
for (IndexType j = 0; j < kNumChunks; ++j) {
|
||||||
|
__m128i product = _mm_maddubs_epi16(
|
||||||
|
_mm_load_si128(&input_vector[j]), _mm_load_si128(&row[j]));
|
||||||
|
product = _mm_madd_epi16(product, kOnes);
|
||||||
|
sum = _mm_add_epi32(sum, product);
|
||||||
|
}
|
||||||
|
sum = _mm_hadd_epi32(sum, sum);
|
||||||
|
sum = _mm_hadd_epi32(sum, sum);
|
||||||
|
output[i] = _mm_cvtsi128_si32(sum);
|
||||||
|
|
||||||
|
#elif defined(USE_NEON)
|
||||||
|
int32x4_t sum = {biases_[i]};
|
||||||
|
const auto row = reinterpret_cast<const int8x8_t*>(&weights_[offset]);
|
||||||
|
for (IndexType j = 0; j < kNumChunks; ++j) {
|
||||||
|
int16x8_t product = vmull_s8(input_vector[j * 2], row[j * 2]);
|
||||||
|
product = vmlal_s8(product, input_vector[j * 2 + 1], row[j * 2 + 1]);
|
||||||
|
sum = vpadalq_s16(sum, product);
|
||||||
|
}
|
||||||
|
output[i] = sum[0] + sum[1] + sum[2] + sum[3];
|
||||||
|
|
||||||
|
#else
|
||||||
|
OutputType sum = biases_[i];
|
||||||
|
for (IndexType j = 0; j < kInputDimensions; ++j) {
|
||||||
|
sum += weights_[offset + j] * input[j];
|
||||||
|
}
|
||||||
|
output[i] = sum;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
using BiasType = OutputType;
|
||||||
|
using WeightType = std::int8_t;
|
||||||
|
|
||||||
|
PreviousLayer previous_layer_;
|
||||||
|
|
||||||
|
alignas(kCacheLineSize) BiasType biases_[kOutputDimensions];
|
||||||
|
alignas(kCacheLineSize)
|
||||||
|
WeightType weights_[kOutputDimensions * kPaddedInputDimensions];
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Eval::NNUE::Layers
|
||||||
|
|
||||||
|
#endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
/*
|
||||||
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Stockfish is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Definition of layer ClippedReLU of NNUE evaluation function
|
||||||
|
|
||||||
|
#ifndef NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED
|
||||||
|
#define NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED
|
||||||
|
|
||||||
|
#include "../nnue_common.h"
|
||||||
|
|
||||||
|
namespace Eval::NNUE::Layers {
|
||||||
|
|
||||||
|
// Clipped ReLU
|
||||||
|
template <typename PreviousLayer>
|
||||||
|
class ClippedReLU {
|
||||||
|
public:
|
||||||
|
// Input/output type
|
||||||
|
using InputType = typename PreviousLayer::OutputType;
|
||||||
|
using OutputType = std::uint8_t;
|
||||||
|
static_assert(std::is_same<InputType, std::int32_t>::value, "");
|
||||||
|
|
||||||
|
// Number of input/output dimensions
|
||||||
|
static constexpr IndexType kInputDimensions =
|
||||||
|
PreviousLayer::kOutputDimensions;
|
||||||
|
static constexpr IndexType kOutputDimensions = kInputDimensions;
|
||||||
|
|
||||||
|
// Size of forward propagation buffer used in this layer
|
||||||
|
static constexpr std::size_t kSelfBufferSize =
|
||||||
|
CeilToMultiple(kOutputDimensions * sizeof(OutputType), kCacheLineSize);
|
||||||
|
|
||||||
|
// Size of the forward propagation buffer used from the input layer to this layer
|
||||||
|
static constexpr std::size_t kBufferSize =
|
||||||
|
PreviousLayer::kBufferSize + kSelfBufferSize;
|
||||||
|
|
||||||
|
// Hash value embedded in the evaluation file
|
||||||
|
static constexpr std::uint32_t GetHashValue() {
|
||||||
|
std::uint32_t hash_value = 0x538D24C7u;
|
||||||
|
hash_value += PreviousLayer::GetHashValue();
|
||||||
|
return hash_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read network parameters
|
||||||
|
bool ReadParameters(std::istream& stream) {
|
||||||
|
return previous_layer_.ReadParameters(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forward propagation
|
||||||
|
const OutputType* Propagate(
|
||||||
|
const TransformedFeatureType* transformed_features, char* buffer) const {
|
||||||
|
const auto input = previous_layer_.Propagate(
|
||||||
|
transformed_features, buffer + kSelfBufferSize);
|
||||||
|
const auto output = reinterpret_cast<OutputType*>(buffer);
|
||||||
|
|
||||||
|
#if defined(USE_AVX2)
|
||||||
|
constexpr IndexType kNumChunks = kInputDimensions / kSimdWidth;
|
||||||
|
const __m256i kZero = _mm256_setzero_si256();
|
||||||
|
const __m256i kOffsets = _mm256_set_epi32(7, 3, 6, 2, 5, 1, 4, 0);
|
||||||
|
const auto in = reinterpret_cast<const __m256i*>(input);
|
||||||
|
const auto out = reinterpret_cast<__m256i*>(output);
|
||||||
|
for (IndexType i = 0; i < kNumChunks; ++i) {
|
||||||
|
const __m256i words0 = _mm256_srai_epi16(_mm256_packs_epi32(
|
||||||
|
|
||||||
|
#if defined(__MINGW32__) || defined(__MINGW64__)
|
||||||
|
// HACK: Use _mm256_loadu_si256() instead of _mm256_load_si256. Because the binary
|
||||||
|
// compiled with g++ in MSYS2 crashes here because the output memory is not aligned
|
||||||
|
// even though alignas is specified.
|
||||||
|
_mm256_loadu_si256
|
||||||
|
#else
|
||||||
|
_mm256_load_si256
|
||||||
|
#endif
|
||||||
|
|
||||||
|
(&in[i * 4 + 0]),
|
||||||
|
|
||||||
|
#if defined(__MINGW32__) || defined(__MINGW64__)
|
||||||
|
_mm256_loadu_si256
|
||||||
|
#else
|
||||||
|
_mm256_load_si256
|
||||||
|
#endif
|
||||||
|
|
||||||
|
(&in[i * 4 + 1])), kWeightScaleBits);
|
||||||
|
const __m256i words1 = _mm256_srai_epi16(_mm256_packs_epi32(
|
||||||
|
|
||||||
|
#if defined(__MINGW32__) || defined(__MINGW64__)
|
||||||
|
_mm256_loadu_si256
|
||||||
|
#else
|
||||||
|
_mm256_load_si256
|
||||||
|
#endif
|
||||||
|
|
||||||
|
(&in[i * 4 + 2]),
|
||||||
|
|
||||||
|
#if defined(__MINGW32__) || defined(__MINGW64__)
|
||||||
|
_mm256_loadu_si256
|
||||||
|
#else
|
||||||
|
_mm256_load_si256
|
||||||
|
#endif
|
||||||
|
|
||||||
|
(&in[i * 4 + 3])), kWeightScaleBits);
|
||||||
|
|
||||||
|
#if defined(__MINGW32__) || defined(__MINGW64__)
|
||||||
|
_mm256_storeu_si256
|
||||||
|
#else
|
||||||
|
_mm256_store_si256
|
||||||
|
#endif
|
||||||
|
|
||||||
|
(&out[i], _mm256_permutevar8x32_epi32(_mm256_max_epi8(
|
||||||
|
_mm256_packs_epi16(words0, words1), kZero), kOffsets));
|
||||||
|
}
|
||||||
|
constexpr IndexType kStart = kNumChunks * kSimdWidth;
|
||||||
|
|
||||||
|
#elif defined(USE_SSSE3)
|
||||||
|
constexpr IndexType kNumChunks = kInputDimensions / kSimdWidth;
|
||||||
|
|
||||||
|
#ifdef USE_SSE41
|
||||||
|
const __m128i kZero = _mm_setzero_si128();
|
||||||
|
#else
|
||||||
|
const __m128i k0x80s = _mm_set1_epi8(-128);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const auto in = reinterpret_cast<const __m128i*>(input);
|
||||||
|
const auto out = reinterpret_cast<__m128i*>(output);
|
||||||
|
for (IndexType i = 0; i < kNumChunks; ++i) {
|
||||||
|
const __m128i words0 = _mm_srai_epi16(_mm_packs_epi32(
|
||||||
|
_mm_load_si128(&in[i * 4 + 0]),
|
||||||
|
_mm_load_si128(&in[i * 4 + 1])), kWeightScaleBits);
|
||||||
|
const __m128i words1 = _mm_srai_epi16(_mm_packs_epi32(
|
||||||
|
_mm_load_si128(&in[i * 4 + 2]),
|
||||||
|
_mm_load_si128(&in[i * 4 + 3])), kWeightScaleBits);
|
||||||
|
const __m128i packedbytes = _mm_packs_epi16(words0, words1);
|
||||||
|
_mm_store_si128(&out[i],
|
||||||
|
|
||||||
|
#ifdef USE_SSE41
|
||||||
|
_mm_max_epi8(packedbytes, kZero)
|
||||||
|
#else
|
||||||
|
_mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
constexpr IndexType kStart = kNumChunks * kSimdWidth;
|
||||||
|
|
||||||
|
#elif defined(USE_NEON)
|
||||||
|
constexpr IndexType kNumChunks = kInputDimensions / (kSimdWidth / 2);
|
||||||
|
const int8x8_t kZero = {0};
|
||||||
|
const auto in = reinterpret_cast<const int32x4_t*>(input);
|
||||||
|
const auto out = reinterpret_cast<int8x8_t*>(output);
|
||||||
|
for (IndexType i = 0; i < kNumChunks; ++i) {
|
||||||
|
int16x8_t shifted;
|
||||||
|
const auto pack = reinterpret_cast<int16x4_t*>(&shifted);
|
||||||
|
pack[0] = vqshrn_n_s32(in[i * 2 + 0], kWeightScaleBits);
|
||||||
|
pack[1] = vqshrn_n_s32(in[i * 2 + 1], kWeightScaleBits);
|
||||||
|
out[i] = vmax_s8(vqmovn_s16(shifted), kZero);
|
||||||
|
}
|
||||||
|
constexpr IndexType kStart = kNumChunks * (kSimdWidth / 2);
|
||||||
|
#else
|
||||||
|
constexpr IndexType kStart = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
for (IndexType i = kStart; i < kInputDimensions; ++i) {
|
||||||
|
output[i] = static_cast<OutputType>(
|
||||||
|
std::max(0, std::min(127, input[i] >> kWeightScaleBits)));
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
PreviousLayer previous_layer_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Eval::NNUE::Layers
|
||||||
|
|
||||||
|
#endif // NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
|
Copyright (C) 2004-2020 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// NNUE evaluation function layer InputSlice definition
|
||||||
|
|
||||||
|
#ifndef NNUE_LAYERS_INPUT_SLICE_H_INCLUDED
|
||||||
|
#define NNUE_LAYERS_INPUT_SLICE_H_INCLUDED
|
||||||
|
|
||||||
|
#include "../nnue_common.h"
|
||||||
|
|
||||||
|
namespace Eval::NNUE::Layers {
|
||||||
|
|
||||||
|
// Input layer
|
||||||
|
template <IndexType OutputDimensions, IndexType Offset = 0>
|
||||||
|
class InputSlice {
|
||||||
|
public:
|
||||||
|
// Need to maintain alignment
|
||||||
|
static_assert(Offset % kMaxSimdWidth == 0, "");
|
||||||
|
|
||||||
|
// Output type
|
||||||
|
using OutputType = TransformedFeatureType;
|
||||||
|
|
||||||
|
// Output dimensionality
|
||||||
|
static constexpr IndexType kOutputDimensions = OutputDimensions;
|
||||||
|
|
||||||
|
// Size of forward propagation buffer used from the input layer to this layer
|
||||||
|
static constexpr std::size_t kBufferSize = 0;
|
||||||
|
|
||||||
|
// Hash value embedded in the evaluation file
|
||||||
|
static constexpr std::uint32_t GetHashValue() {
|
||||||
|
std::uint32_t hash_value = 0xEC42E90Du;
|
||||||
|
hash_value ^= kOutputDimensions ^ (Offset << 10);
|
||||||
|
return hash_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read network parameters
|
||||||
|
bool ReadParameters(std::istream& /*stream*/) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forward propagation
|
||||||
|
const OutputType* Propagate(
|
||||||
|
const TransformedFeatureType* transformed_features,
|
||||||
|
char* /*buffer*/) const {
|
||||||
|
return transformed_features + Offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Layers
|
||||||
|
|
||||||
|
#endif // #ifndef NNUE_LAYERS_INPUT_SLICE_H_INCLUDED
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Stockfish is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Class for difference calculation of NNUE evaluation function
|
||||||
|
|
||||||
|
#ifndef NNUE_ACCUMULATOR_H_INCLUDED
|
||||||
|
#define NNUE_ACCUMULATOR_H_INCLUDED
|
||||||
|
|
||||||
|
#include "nnue_architecture.h"
|
||||||
|
|
||||||
|
namespace Eval::NNUE {
|
||||||
|
|
||||||
|
// Class that holds the result of affine transformation of input features
|
||||||
|
struct alignas(32) Accumulator {
|
||||||
|
std::int16_t
|
||||||
|
accumulation[2][kRefreshTriggers.size()][kTransformedFeatureDimensions];
|
||||||
|
Value score;
|
||||||
|
bool computed_accumulation;
|
||||||
|
bool computed_score;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Eval::NNUE
|
||||||
|
|
||||||
|
#endif // NNUE_ACCUMULATOR_H_INCLUDED
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Stockfish is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Input features and network structure used in NNUE evaluation function
|
||||||
|
|
||||||
|
#ifndef NNUE_ARCHITECTURE_H_INCLUDED
|
||||||
|
#define NNUE_ARCHITECTURE_H_INCLUDED
|
||||||
|
|
||||||
|
// Defines the network structure
|
||||||
|
#include "architectures/halfkp_256x2-32-32.h"
|
||||||
|
|
||||||
|
namespace Eval::NNUE {
|
||||||
|
|
||||||
|
static_assert(kTransformedFeatureDimensions % kMaxSimdWidth == 0, "");
|
||||||
|
static_assert(Network::kOutputDimensions == 1, "");
|
||||||
|
static_assert(std::is_same<Network::OutputType, std::int32_t>::value, "");
|
||||||
|
|
||||||
|
// Trigger for full calculation instead of difference calculation
|
||||||
|
constexpr auto kRefreshTriggers = RawFeatures::kRefreshTriggers;
|
||||||
|
|
||||||
|
} // namespace Eval::NNUE
|
||||||
|
|
||||||
|
#endif // #ifndef NNUE_ARCHITECTURE_H_INCLUDED
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Stockfish is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Constants used in NNUE evaluation function
|
||||||
|
|
||||||
|
#ifndef NNUE_COMMON_H_INCLUDED
|
||||||
|
#define NNUE_COMMON_H_INCLUDED
|
||||||
|
|
||||||
|
#if defined(USE_AVX2)
|
||||||
|
#include <immintrin.h>
|
||||||
|
|
||||||
|
#elif defined(USE_SSE41)
|
||||||
|
#include <smmintrin.h>
|
||||||
|
|
||||||
|
#elif defined(USE_SSSE3)
|
||||||
|
#include <tmmintrin.h>
|
||||||
|
|
||||||
|
#elif defined(USE_SSE2)
|
||||||
|
#include <emmintrin.h>
|
||||||
|
|
||||||
|
#elif defined(USE_NEON)
|
||||||
|
#include <arm_neon.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace Eval::NNUE {
|
||||||
|
|
||||||
|
// Version of the evaluation file
|
||||||
|
constexpr std::uint32_t kVersion = 0x7AF32F16u;
|
||||||
|
|
||||||
|
// Constant used in evaluation value calculation
|
||||||
|
constexpr int FV_SCALE = 16;
|
||||||
|
constexpr int kWeightScaleBits = 6;
|
||||||
|
|
||||||
|
// Size of cache line (in bytes)
|
||||||
|
constexpr std::size_t kCacheLineSize = 64;
|
||||||
|
|
||||||
|
// SIMD width (in bytes)
|
||||||
|
#if defined(USE_AVX2)
|
||||||
|
constexpr std::size_t kSimdWidth = 32;
|
||||||
|
|
||||||
|
#elif defined(USE_SSE2)
|
||||||
|
constexpr std::size_t kSimdWidth = 16;
|
||||||
|
|
||||||
|
#elif defined(USE_NEON)
|
||||||
|
constexpr std::size_t kSimdWidth = 16;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
constexpr std::size_t kMaxSimdWidth = 32;
|
||||||
|
|
||||||
|
// Type of input feature after conversion
|
||||||
|
using TransformedFeatureType = std::uint8_t;
|
||||||
|
using IndexType = std::uint32_t;
|
||||||
|
|
||||||
|
// Round n up to be a multiple of base
|
||||||
|
template <typename IntType>
|
||||||
|
constexpr IntType CeilToMultiple(IntType n, IntType base) {
|
||||||
|
return (n + base - 1) / base * base;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Eval::NNUE
|
||||||
|
|
||||||
|
#endif // #ifndef NNUE_COMMON_H_INCLUDED
|
||||||
@@ -0,0 +1,355 @@
|
|||||||
|
/*
|
||||||
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Stockfish is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// A class that converts the input features of the NNUE evaluation function
|
||||||
|
|
||||||
|
#ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED
|
||||||
|
#define NNUE_FEATURE_TRANSFORMER_H_INCLUDED
|
||||||
|
|
||||||
|
#include "nnue_common.h"
|
||||||
|
#include "nnue_architecture.h"
|
||||||
|
#include "features/index_list.h"
|
||||||
|
|
||||||
|
#include <cstring> // std::memset()
|
||||||
|
|
||||||
|
namespace Eval::NNUE {
|
||||||
|
|
||||||
|
// Input feature converter
|
||||||
|
class FeatureTransformer {
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Number of output dimensions for one side
|
||||||
|
static constexpr IndexType kHalfDimensions = kTransformedFeatureDimensions;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Output type
|
||||||
|
using OutputType = TransformedFeatureType;
|
||||||
|
|
||||||
|
// Number of input/output dimensions
|
||||||
|
static constexpr IndexType kInputDimensions = RawFeatures::kDimensions;
|
||||||
|
static constexpr IndexType kOutputDimensions = kHalfDimensions * 2;
|
||||||
|
|
||||||
|
// Size of forward propagation buffer
|
||||||
|
static constexpr std::size_t kBufferSize =
|
||||||
|
kOutputDimensions * sizeof(OutputType);
|
||||||
|
|
||||||
|
// Hash value embedded in the evaluation file
|
||||||
|
static constexpr std::uint32_t GetHashValue() {
|
||||||
|
return RawFeatures::kHashValue ^ kOutputDimensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read network parameters
|
||||||
|
bool ReadParameters(std::istream& stream) {
|
||||||
|
stream.read(reinterpret_cast<char*>(biases_),
|
||||||
|
kHalfDimensions * sizeof(BiasType));
|
||||||
|
stream.read(reinterpret_cast<char*>(weights_),
|
||||||
|
kHalfDimensions * kInputDimensions * sizeof(WeightType));
|
||||||
|
return !stream.fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proceed with the difference calculation if possible
|
||||||
|
bool UpdateAccumulatorIfPossible(const Position& pos) const {
|
||||||
|
const auto now = pos.state();
|
||||||
|
if (now->accumulator.computed_accumulation) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const auto prev = now->previous;
|
||||||
|
if (prev && prev->accumulator.computed_accumulation) {
|
||||||
|
UpdateAccumulator(pos);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert input features
|
||||||
|
void Transform(const Position& pos, OutputType* output, bool refresh) const {
|
||||||
|
if (refresh || !UpdateAccumulatorIfPossible(pos)) {
|
||||||
|
RefreshAccumulator(pos);
|
||||||
|
}
|
||||||
|
const auto& accumulation = pos.state()->accumulator.accumulation;
|
||||||
|
|
||||||
|
#if defined(USE_AVX2)
|
||||||
|
constexpr IndexType kNumChunks = kHalfDimensions / kSimdWidth;
|
||||||
|
constexpr int kControl = 0b11011000;
|
||||||
|
const __m256i kZero = _mm256_setzero_si256();
|
||||||
|
|
||||||
|
#elif defined(USE_SSSE3)
|
||||||
|
constexpr IndexType kNumChunks = kHalfDimensions / kSimdWidth;
|
||||||
|
|
||||||
|
#ifdef USE_SSE41
|
||||||
|
const __m128i kZero = _mm_setzero_si128();
|
||||||
|
#else
|
||||||
|
const __m128i k0x80s = _mm_set1_epi8(-128);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#elif defined(USE_NEON)
|
||||||
|
constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2);
|
||||||
|
const int8x8_t kZero = {0};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()};
|
||||||
|
for (IndexType p = 0; p < 2; ++p) {
|
||||||
|
const IndexType offset = kHalfDimensions * p;
|
||||||
|
|
||||||
|
#if defined(USE_AVX2)
|
||||||
|
auto out = reinterpret_cast<__m256i*>(&output[offset]);
|
||||||
|
for (IndexType j = 0; j < kNumChunks; ++j) {
|
||||||
|
__m256i sum0 =
|
||||||
|
|
||||||
|
#if defined(__MINGW32__) || defined(__MINGW64__)
|
||||||
|
// HACK: Use _mm256_loadu_si256() instead of _mm256_load_si256. Because the binary
|
||||||
|
// compiled with g++ in MSYS2 crashes here because the output memory is not aligned
|
||||||
|
// even though alignas is specified.
|
||||||
|
_mm256_loadu_si256
|
||||||
|
#else
|
||||||
|
_mm256_load_si256
|
||||||
|
#endif
|
||||||
|
|
||||||
|
(&reinterpret_cast<const __m256i*>(
|
||||||
|
accumulation[perspectives[p]][0])[j * 2 + 0]);
|
||||||
|
__m256i sum1 =
|
||||||
|
|
||||||
|
#if defined(__MINGW32__) || defined(__MINGW64__)
|
||||||
|
_mm256_loadu_si256
|
||||||
|
#else
|
||||||
|
_mm256_load_si256
|
||||||
|
#endif
|
||||||
|
|
||||||
|
(&reinterpret_cast<const __m256i*>(
|
||||||
|
accumulation[perspectives[p]][0])[j * 2 + 1]);
|
||||||
|
|
||||||
|
#if defined(__MINGW32__) || defined(__MINGW64__)
|
||||||
|
_mm256_storeu_si256
|
||||||
|
#else
|
||||||
|
_mm256_store_si256
|
||||||
|
#endif
|
||||||
|
|
||||||
|
(&out[j], _mm256_permute4x64_epi64(_mm256_max_epi8(
|
||||||
|
_mm256_packs_epi16(sum0, sum1), kZero), kControl));
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif defined(USE_SSSE3)
|
||||||
|
auto out = reinterpret_cast<__m128i*>(&output[offset]);
|
||||||
|
for (IndexType j = 0; j < kNumChunks; ++j) {
|
||||||
|
__m128i sum0 = _mm_load_si128(&reinterpret_cast<const __m128i*>(
|
||||||
|
accumulation[perspectives[p]][0])[j * 2 + 0]);
|
||||||
|
__m128i sum1 = _mm_load_si128(&reinterpret_cast<const __m128i*>(
|
||||||
|
accumulation[perspectives[p]][0])[j * 2 + 1]);
|
||||||
|
const __m128i packedbytes = _mm_packs_epi16(sum0, sum1);
|
||||||
|
|
||||||
|
_mm_store_si128(&out[j],
|
||||||
|
|
||||||
|
#ifdef USE_SSE41
|
||||||
|
_mm_max_epi8(packedbytes, kZero)
|
||||||
|
#else
|
||||||
|
_mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif defined(USE_NEON)
|
||||||
|
const auto out = reinterpret_cast<int8x8_t*>(&output[offset]);
|
||||||
|
for (IndexType j = 0; j < kNumChunks; ++j) {
|
||||||
|
int16x8_t sum = reinterpret_cast<const int16x8_t*>(
|
||||||
|
accumulation[perspectives[p]][0])[j];
|
||||||
|
out[j] = vmax_s8(vqmovn_s16(sum), kZero);
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
for (IndexType j = 0; j < kHalfDimensions; ++j) {
|
||||||
|
BiasType sum = accumulation[static_cast<int>(perspectives[p])][0][j];
|
||||||
|
output[offset + j] = static_cast<OutputType>(
|
||||||
|
std::max<int>(0, std::min<int>(127, sum)));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Calculate cumulative value without using difference calculation
|
||||||
|
void RefreshAccumulator(const Position& pos) const {
|
||||||
|
auto& accumulator = pos.state()->accumulator;
|
||||||
|
IndexType i = 0;
|
||||||
|
Features::IndexList active_indices[2];
|
||||||
|
RawFeatures::AppendActiveIndices(pos, kRefreshTriggers[i],
|
||||||
|
active_indices);
|
||||||
|
for (Color perspective : { WHITE, BLACK }) {
|
||||||
|
std::memcpy(accumulator.accumulation[perspective][i], biases_,
|
||||||
|
kHalfDimensions * sizeof(BiasType));
|
||||||
|
for (const auto index : active_indices[perspective]) {
|
||||||
|
const IndexType offset = kHalfDimensions * index;
|
||||||
|
|
||||||
|
#if defined(USE_AVX2)
|
||||||
|
auto accumulation = reinterpret_cast<__m256i*>(
|
||||||
|
&accumulator.accumulation[perspective][i][0]);
|
||||||
|
auto column = reinterpret_cast<const __m256i*>(&weights_[offset]);
|
||||||
|
constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2);
|
||||||
|
for (IndexType j = 0; j < kNumChunks; ++j) {
|
||||||
|
#if defined(__MINGW32__) || defined(__MINGW64__)
|
||||||
|
_mm256_storeu_si256(&accumulation[j], _mm256_add_epi16(_mm256_loadu_si256(&accumulation[j]), column[j]));
|
||||||
|
#else
|
||||||
|
accumulation[j] = _mm256_add_epi16(accumulation[j], column[j]);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif defined(USE_SSE2)
|
||||||
|
auto accumulation = reinterpret_cast<__m128i*>(
|
||||||
|
&accumulator.accumulation[perspective][i][0]);
|
||||||
|
auto column = reinterpret_cast<const __m128i*>(&weights_[offset]);
|
||||||
|
constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2);
|
||||||
|
for (IndexType j = 0; j < kNumChunks; ++j) {
|
||||||
|
accumulation[j] = _mm_add_epi16(accumulation[j], column[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif defined(USE_NEON)
|
||||||
|
auto accumulation = reinterpret_cast<int16x8_t*>(
|
||||||
|
&accumulator.accumulation[perspective][i][0]);
|
||||||
|
auto column = reinterpret_cast<const int16x8_t*>(&weights_[offset]);
|
||||||
|
constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2);
|
||||||
|
for (IndexType j = 0; j < kNumChunks; ++j) {
|
||||||
|
accumulation[j] = vaddq_s16(accumulation[j], column[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
for (IndexType j = 0; j < kHalfDimensions; ++j) {
|
||||||
|
accumulator.accumulation[perspective][i][j] += weights_[offset + j];
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
accumulator.computed_accumulation = true;
|
||||||
|
accumulator.computed_score = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate cumulative value using difference calculation
|
||||||
|
void UpdateAccumulator(const Position& pos) const {
|
||||||
|
const auto prev_accumulator = pos.state()->previous->accumulator;
|
||||||
|
auto& accumulator = pos.state()->accumulator;
|
||||||
|
IndexType i = 0;
|
||||||
|
Features::IndexList removed_indices[2], added_indices[2];
|
||||||
|
bool reset[2];
|
||||||
|
RawFeatures::AppendChangedIndices(pos, kRefreshTriggers[i],
|
||||||
|
removed_indices, added_indices, reset);
|
||||||
|
for (Color perspective : { WHITE, BLACK }) {
|
||||||
|
|
||||||
|
#if defined(USE_AVX2)
|
||||||
|
constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2);
|
||||||
|
auto accumulation = reinterpret_cast<__m256i*>(
|
||||||
|
&accumulator.accumulation[perspective][i][0]);
|
||||||
|
|
||||||
|
#elif defined(USE_SSE2)
|
||||||
|
constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2);
|
||||||
|
auto accumulation = reinterpret_cast<__m128i*>(
|
||||||
|
&accumulator.accumulation[perspective][i][0]);
|
||||||
|
|
||||||
|
#elif defined(USE_NEON)
|
||||||
|
constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2);
|
||||||
|
auto accumulation = reinterpret_cast<int16x8_t*>(
|
||||||
|
&accumulator.accumulation[perspective][i][0]);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (reset[perspective]) {
|
||||||
|
std::memcpy(accumulator.accumulation[perspective][i], biases_,
|
||||||
|
kHalfDimensions * sizeof(BiasType));
|
||||||
|
} else {
|
||||||
|
std::memcpy(accumulator.accumulation[perspective][i],
|
||||||
|
prev_accumulator.accumulation[perspective][i],
|
||||||
|
kHalfDimensions * sizeof(BiasType));
|
||||||
|
// Difference calculation for the deactivated features
|
||||||
|
for (const auto index : removed_indices[perspective]) {
|
||||||
|
const IndexType offset = kHalfDimensions * index;
|
||||||
|
|
||||||
|
#if defined(USE_AVX2)
|
||||||
|
auto column = reinterpret_cast<const __m256i*>(&weights_[offset]);
|
||||||
|
for (IndexType j = 0; j < kNumChunks; ++j) {
|
||||||
|
accumulation[j] = _mm256_sub_epi16(accumulation[j], column[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif defined(USE_SSE2)
|
||||||
|
auto column = reinterpret_cast<const __m128i*>(&weights_[offset]);
|
||||||
|
for (IndexType j = 0; j < kNumChunks; ++j) {
|
||||||
|
accumulation[j] = _mm_sub_epi16(accumulation[j], column[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif defined(USE_NEON)
|
||||||
|
auto column = reinterpret_cast<const int16x8_t*>(&weights_[offset]);
|
||||||
|
for (IndexType j = 0; j < kNumChunks; ++j) {
|
||||||
|
accumulation[j] = vsubq_s16(accumulation[j], column[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
for (IndexType j = 0; j < kHalfDimensions; ++j) {
|
||||||
|
accumulator.accumulation[perspective][i][j] -=
|
||||||
|
weights_[offset + j];
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{ // Difference calculation for the activated features
|
||||||
|
for (const auto index : added_indices[perspective]) {
|
||||||
|
const IndexType offset = kHalfDimensions * index;
|
||||||
|
|
||||||
|
#if defined(USE_AVX2)
|
||||||
|
auto column = reinterpret_cast<const __m256i*>(&weights_[offset]);
|
||||||
|
for (IndexType j = 0; j < kNumChunks; ++j) {
|
||||||
|
accumulation[j] = _mm256_add_epi16(accumulation[j], column[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif defined(USE_SSE2)
|
||||||
|
auto column = reinterpret_cast<const __m128i*>(&weights_[offset]);
|
||||||
|
for (IndexType j = 0; j < kNumChunks; ++j) {
|
||||||
|
accumulation[j] = _mm_add_epi16(accumulation[j], column[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif defined(USE_NEON)
|
||||||
|
auto column = reinterpret_cast<const int16x8_t*>(&weights_[offset]);
|
||||||
|
for (IndexType j = 0; j < kNumChunks; ++j) {
|
||||||
|
accumulation[j] = vaddq_s16(accumulation[j], column[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
for (IndexType j = 0; j < kHalfDimensions; ++j) {
|
||||||
|
accumulator.accumulation[perspective][i][j] +=
|
||||||
|
weights_[offset + j];
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
accumulator.computed_accumulation = true;
|
||||||
|
accumulator.computed_score = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
using BiasType = std::int16_t;
|
||||||
|
using WeightType = std::int16_t;
|
||||||
|
|
||||||
|
alignas(kCacheLineSize) BiasType biases_[kHalfDimensions];
|
||||||
|
alignas(kCacheLineSize)
|
||||||
|
WeightType weights_[kHalfDimensions * kInputDimensions];
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Eval::NNUE
|
||||||
|
|
||||||
|
#endif // #ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED
|
||||||
+41
-15
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -33,12 +31,18 @@ namespace {
|
|||||||
|
|
||||||
// Pawn penalties
|
// Pawn penalties
|
||||||
constexpr Score Backward = S( 9, 24);
|
constexpr Score Backward = S( 9, 24);
|
||||||
constexpr Score BlockedStorm = S(82, 82);
|
|
||||||
constexpr Score Doubled = S(11, 56);
|
constexpr Score Doubled = S(11, 56);
|
||||||
constexpr Score Isolated = S( 5, 15);
|
constexpr Score Isolated = S( 5, 15);
|
||||||
constexpr Score WeakLever = S( 0, 56);
|
constexpr Score WeakLever = S( 0, 56);
|
||||||
constexpr Score WeakUnopposed = S(13, 27);
|
constexpr Score WeakUnopposed = S(13, 27);
|
||||||
|
|
||||||
|
// Bonus for blocked pawns at 5th or 6th rank
|
||||||
|
constexpr Score BlockedPawn[2] = { S(-11, -4), S(-3, 4) };
|
||||||
|
|
||||||
|
constexpr Score BlockedStorm[RANK_NB] = {
|
||||||
|
S(0, 0), S(0, 0), S(76, 78), S(-10, 15), S(-7, 10), S(-4, 6), S(-1, 2)
|
||||||
|
};
|
||||||
|
|
||||||
// Connected pawn bonus
|
// Connected pawn bonus
|
||||||
constexpr int Connected[RANK_NB] = { 0, 7, 8, 12, 29, 48, 86 };
|
constexpr int Connected[RANK_NB] = { 0, 7, 8, 12, 29, 48, 86 };
|
||||||
|
|
||||||
@@ -65,10 +69,16 @@ namespace {
|
|||||||
#undef S
|
#undef S
|
||||||
#undef V
|
#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>
|
template<Color Us>
|
||||||
Score evaluate(const Position& pos, Pawns::Entry* e) {
|
Score evaluate(const Position& pos, Pawns::Entry* e) {
|
||||||
|
|
||||||
constexpr Color Them = (Us == WHITE ? BLACK : WHITE);
|
constexpr Color Them = ~Us;
|
||||||
constexpr Direction Up = pawn_push(Us);
|
constexpr Direction Up = pawn_push(Us);
|
||||||
|
|
||||||
Bitboard neighbours, stoppers, support, phalanx, opposed;
|
Bitboard neighbours, stoppers, support, phalanx, opposed;
|
||||||
@@ -86,6 +96,7 @@ namespace {
|
|||||||
e->passedPawns[Us] = 0;
|
e->passedPawns[Us] = 0;
|
||||||
e->kingSquares[Us] = SQ_NONE;
|
e->kingSquares[Us] = SQ_NONE;
|
||||||
e->pawnAttacks[Us] = e->pawnAttacksSpan[Us] = pawn_attacks_bb<Us>(ourPawns);
|
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
|
// Loop through all pawns of the current color and score each pawn
|
||||||
while ((s = *pl++) != SQ_NONE)
|
while ((s = *pl++) != SQ_NONE)
|
||||||
@@ -98,8 +109,8 @@ namespace {
|
|||||||
opposed = theirPawns & forward_file_bb(Us, s);
|
opposed = theirPawns & forward_file_bb(Us, s);
|
||||||
blocked = theirPawns & (s + Up);
|
blocked = theirPawns & (s + Up);
|
||||||
stoppers = theirPawns & passed_pawn_span(Us, s);
|
stoppers = theirPawns & passed_pawn_span(Us, s);
|
||||||
lever = theirPawns & PawnAttacks[Us][s];
|
lever = theirPawns & pawn_attacks_bb(Us, s);
|
||||||
leverPush = theirPawns & PawnAttacks[Us][s + Up];
|
leverPush = theirPawns & pawn_attacks_bb(Us, s + Up);
|
||||||
doubled = ourPawns & (s - Up);
|
doubled = ourPawns & (s - Up);
|
||||||
neighbours = ourPawns & adjacent_files_bb(s);
|
neighbours = ourPawns & adjacent_files_bb(s);
|
||||||
phalanx = neighbours & rank_bb(s);
|
phalanx = neighbours & rank_bb(s);
|
||||||
@@ -118,12 +129,15 @@ namespace {
|
|||||||
// (a) there is no stoppers except some levers
|
// (a) there is no stoppers except some levers
|
||||||
// (b) the only stoppers are the leverPush, but we outnumber them
|
// (b) the only stoppers are the leverPush, but we outnumber them
|
||||||
// (c) there is only one front stopper which can be levered.
|
// (c) there is only one front stopper which can be levered.
|
||||||
|
// (Refined in Evaluation::passed)
|
||||||
passed = !(stoppers ^ lever)
|
passed = !(stoppers ^ lever)
|
||||||
|| ( !(stoppers ^ leverPush)
|
|| ( !(stoppers ^ leverPush)
|
||||||
&& popcount(phalanx) >= popcount(leverPush))
|
&& popcount(phalanx) >= popcount(leverPush))
|
||||||
|| ( stoppers == blocked && r >= RANK_5
|
|| ( stoppers == blocked && r >= RANK_5
|
||||||
&& (shift<Up>(support) & ~(theirPawns | doubleAttackThem)));
|
&& (shift<Up>(support) & ~(theirPawns | doubleAttackThem)));
|
||||||
|
|
||||||
|
passed &= !(forward_file_bb(Us, s) & ourPawns);
|
||||||
|
|
||||||
// Passed pawns will be properly scored later in evaluation when we have
|
// Passed pawns will be properly scored later in evaluation when we have
|
||||||
// full attack info.
|
// full attack info.
|
||||||
if (passed)
|
if (passed)
|
||||||
@@ -139,8 +153,15 @@ namespace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
else if (!neighbours)
|
else if (!neighbours)
|
||||||
|
{
|
||||||
|
if ( opposed
|
||||||
|
&& (ourPawns & forward_file_bb(Them, s))
|
||||||
|
&& !(theirPawns & adjacent_files_bb(s)))
|
||||||
|
score -= Doubled;
|
||||||
|
else
|
||||||
score -= Isolated
|
score -= Isolated
|
||||||
+ WeakUnopposed * !opposed;
|
+ WeakUnopposed * !opposed;
|
||||||
|
}
|
||||||
|
|
||||||
else if (backward)
|
else if (backward)
|
||||||
score -= Backward
|
score -= Backward
|
||||||
@@ -149,6 +170,9 @@ namespace {
|
|||||||
if (!support)
|
if (!support)
|
||||||
score -= Doubled * doubled
|
score -= Doubled * doubled
|
||||||
+ WeakLever * more_than_one(lever);
|
+ WeakLever * more_than_one(lever);
|
||||||
|
|
||||||
|
if (blocked && r > RANK_4)
|
||||||
|
score += BlockedPawn[r-4];
|
||||||
}
|
}
|
||||||
|
|
||||||
return score;
|
return score;
|
||||||
@@ -158,6 +182,7 @@ namespace {
|
|||||||
|
|
||||||
namespace Pawns {
|
namespace Pawns {
|
||||||
|
|
||||||
|
|
||||||
/// Pawns::probe() looks up the current position's pawns configuration in
|
/// 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
|
/// 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
|
/// is found. Otherwise a new Entry is computed and stored there, so we don't
|
||||||
@@ -172,6 +197,7 @@ Entry* probe(const Position& pos) {
|
|||||||
return e;
|
return e;
|
||||||
|
|
||||||
e->key = key;
|
e->key = key;
|
||||||
|
e->blockedCount = 0;
|
||||||
e->scores[WHITE] = evaluate<WHITE>(pos, e);
|
e->scores[WHITE] = evaluate<WHITE>(pos, e);
|
||||||
e->scores[BLACK] = evaluate<BLACK>(pos, e);
|
e->scores[BLACK] = evaluate<BLACK>(pos, e);
|
||||||
|
|
||||||
@@ -183,17 +209,17 @@ Entry* probe(const Position& pos) {
|
|||||||
/// penalty for a king, looking at the king file and the two closest files.
|
/// penalty for a king, looking at the king file and the two closest files.
|
||||||
|
|
||||||
template<Color Us>
|
template<Color Us>
|
||||||
Score Entry::evaluate_shelter(const Position& pos, Square ksq) {
|
Score Entry::evaluate_shelter(const Position& pos, Square ksq) const {
|
||||||
|
|
||||||
constexpr Color Them = (Us == WHITE ? BLACK : WHITE);
|
constexpr Color Them = ~Us;
|
||||||
|
|
||||||
Bitboard b = pos.pieces(PAWN) & ~forward_ranks_bb(Them, ksq);
|
Bitboard b = pos.pieces(PAWN) & ~forward_ranks_bb(Them, ksq);
|
||||||
Bitboard ourPawns = b & pos.pieces(Us);
|
Bitboard ourPawns = b & pos.pieces(Us) & ~pawnAttacks[Them];
|
||||||
Bitboard theirPawns = b & pos.pieces(Them);
|
Bitboard theirPawns = b & pos.pieces(Them);
|
||||||
|
|
||||||
Score bonus = make_score(5, 5);
|
Score bonus = make_score(5, 5);
|
||||||
|
|
||||||
File center = clamp(file_of(ksq), FILE_B, FILE_G);
|
File center = Utility::clamp(file_of(ksq), FILE_B, FILE_G);
|
||||||
for (File f = File(center - 1); f <= File(center + 1); ++f)
|
for (File f = File(center - 1); f <= File(center + 1); ++f)
|
||||||
{
|
{
|
||||||
b = ourPawns & file_bb(f);
|
b = ourPawns & file_bb(f);
|
||||||
@@ -202,11 +228,11 @@ Score Entry::evaluate_shelter(const Position& pos, Square ksq) {
|
|||||||
b = theirPawns & file_bb(f);
|
b = theirPawns & file_bb(f);
|
||||||
int theirRank = b ? relative_rank(Us, frontmost_sq(Them, b)) : 0;
|
int theirRank = b ? relative_rank(Us, frontmost_sq(Them, b)) : 0;
|
||||||
|
|
||||||
File d = map_to_queenside(f);
|
int d = edge_distance(f);
|
||||||
bonus += make_score(ShelterStrength[d][ourRank], 0);
|
bonus += make_score(ShelterStrength[d][ourRank], 0);
|
||||||
|
|
||||||
if (ourRank && (ourRank == theirRank - 1))
|
if (ourRank && (ourRank == theirRank - 1))
|
||||||
bonus -= BlockedStorm * int(theirRank == RANK_3);
|
bonus -= BlockedStorm[theirRank];
|
||||||
else
|
else
|
||||||
bonus -= make_score(UnblockedStorm[d][theirRank], 0);
|
bonus -= make_score(UnblockedStorm[d][theirRank], 0);
|
||||||
}
|
}
|
||||||
@@ -238,9 +264,9 @@ Score Entry::do_king_safety(const Position& pos) {
|
|||||||
|
|
||||||
// In endgame we like to bring our king near our closest pawn
|
// In endgame we like to bring our king near our closest pawn
|
||||||
Bitboard pawns = pos.pieces(Us, PAWN);
|
Bitboard pawns = pos.pieces(Us, PAWN);
|
||||||
int minPawnDist = pawns ? 8 : 0;
|
int minPawnDist = 6;
|
||||||
|
|
||||||
if (pawns & PseudoAttacks[KING][ksq])
|
if (pawns & attacks_bb<KING>(ksq))
|
||||||
minPawnDist = 1;
|
minPawnDist = 1;
|
||||||
else while (pawns)
|
else while (pawns)
|
||||||
minPawnDist = std::min(minPawnDist, distance(ksq, pop_lsb(&pawns)));
|
minPawnDist = std::min(minPawnDist, distance(ksq, pop_lsb(&pawns)));
|
||||||
|
|||||||
+4
-4
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -38,6 +36,7 @@ struct Entry {
|
|||||||
Bitboard passed_pawns(Color c) const { return passedPawns[c]; }
|
Bitboard passed_pawns(Color c) const { return passedPawns[c]; }
|
||||||
Bitboard pawn_attacks_span(Color c) const { return pawnAttacksSpan[c]; }
|
Bitboard pawn_attacks_span(Color c) const { return pawnAttacksSpan[c]; }
|
||||||
int passed_count() const { return popcount(passedPawns[WHITE] | passedPawns[BLACK]); }
|
int passed_count() const { return popcount(passedPawns[WHITE] | passedPawns[BLACK]); }
|
||||||
|
int blocked_count() const { return blockedCount; }
|
||||||
|
|
||||||
template<Color Us>
|
template<Color Us>
|
||||||
Score king_safety(const Position& pos) {
|
Score king_safety(const Position& pos) {
|
||||||
@@ -49,7 +48,7 @@ struct Entry {
|
|||||||
Score do_king_safety(const Position& pos);
|
Score do_king_safety(const Position& pos);
|
||||||
|
|
||||||
template<Color Us>
|
template<Color Us>
|
||||||
Score evaluate_shelter(const Position& pos, Square ksq);
|
Score evaluate_shelter(const Position& pos, Square ksq) const;
|
||||||
|
|
||||||
Key key;
|
Key key;
|
||||||
Score scores[COLOR_NB];
|
Score scores[COLOR_NB];
|
||||||
@@ -59,6 +58,7 @@ struct Entry {
|
|||||||
Square kingSquares[COLOR_NB];
|
Square kingSquares[COLOR_NB];
|
||||||
Score kingSafety[COLOR_NB];
|
Score kingSafety[COLOR_NB];
|
||||||
int castlingRights[COLOR_NB];
|
int castlingRights[COLOR_NB];
|
||||||
|
int blockedCount;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef HashTable<Entry, 131072> Table;
|
typedef HashTable<Entry, 131072> Table;
|
||||||
|
|||||||
+164
-65
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -64,10 +62,11 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) {
|
|||||||
for (File f = FILE_A; f <= FILE_H; ++f)
|
for (File f = FILE_A; f <= FILE_H; ++f)
|
||||||
os << " | " << PieceToChar[pos.piece_on(make_square(f, r))];
|
os << " | " << PieceToChar[pos.piece_on(make_square(f, r))];
|
||||||
|
|
||||||
os << " |\n +---+---+---+---+---+---+---+---+\n";
|
os << " | " << (1 + r) << "\n +---+---+---+---+---+---+---+---+\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
os << "\nFen: " << pos.fen() << "\nKey: " << std::hex << std::uppercase
|
os << " a b c d e f g h\n"
|
||||||
|
<< "\nFen: " << pos.fen() << "\nKey: " << std::hex << std::uppercase
|
||||||
<< std::setfill('0') << std::setw(16) << pos.key()
|
<< std::setfill('0') << std::setw(16) << pos.key()
|
||||||
<< std::setfill(' ') << std::dec << "\nCheckers: ";
|
<< std::setfill(' ') << std::dec << "\nCheckers: ";
|
||||||
|
|
||||||
@@ -104,8 +103,7 @@ Key cuckoo[8192];
|
|||||||
Move cuckooMove[8192];
|
Move cuckooMove[8192];
|
||||||
|
|
||||||
|
|
||||||
/// Position::init() initializes at startup the various arrays used to compute
|
/// Position::init() initializes at startup the various arrays used to compute hash keys
|
||||||
/// hash keys.
|
|
||||||
|
|
||||||
void Position::init() {
|
void Position::init() {
|
||||||
|
|
||||||
@@ -119,15 +117,7 @@ void Position::init() {
|
|||||||
Zobrist::enpassant[f] = rng.rand<Key>();
|
Zobrist::enpassant[f] = rng.rand<Key>();
|
||||||
|
|
||||||
for (int cr = NO_CASTLING; cr <= ANY_CASTLING; ++cr)
|
for (int cr = NO_CASTLING; cr <= ANY_CASTLING; ++cr)
|
||||||
{
|
Zobrist::castling[cr] = rng.rand<Key>();
|
||||||
Zobrist::castling[cr] = 0;
|
|
||||||
Bitboard b = cr;
|
|
||||||
while (b)
|
|
||||||
{
|
|
||||||
Key k = Zobrist::castling[1ULL << pop_lsb(&b)];
|
|
||||||
Zobrist::castling[cr] ^= k ? k : rng.rand<Key>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Zobrist::side = rng.rand<Key>();
|
Zobrist::side = rng.rand<Key>();
|
||||||
Zobrist::noPawns = rng.rand<Key>();
|
Zobrist::noPawns = rng.rand<Key>();
|
||||||
@@ -139,7 +129,7 @@ void Position::init() {
|
|||||||
for (Piece pc : Pieces)
|
for (Piece pc : Pieces)
|
||||||
for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
|
for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
|
||||||
for (Square s2 = Square(s1 + 1); s2 <= SQ_H8; ++s2)
|
for (Square s2 = Square(s1 + 1); s2 <= SQ_H8; ++s2)
|
||||||
if (PseudoAttacks[type_of(pc)][s1] & s2)
|
if ((type_of(pc) != PAWN) && (attacks_bb(type_of(pc), s1, 0) & s2))
|
||||||
{
|
{
|
||||||
Move move = make_move(s1, s2);
|
Move move = make_move(s1, s2);
|
||||||
Key key = Zobrist::psq[pc][s1] ^ Zobrist::psq[pc][s2] ^ Zobrist::side;
|
Key key = Zobrist::psq[pc][s1] ^ Zobrist::psq[pc][s2] ^ Zobrist::side;
|
||||||
@@ -186,9 +176,9 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th
|
|||||||
|
|
||||||
4) En passant target square (in algebraic notation). If there's no en passant
|
4) En passant target square (in algebraic notation). If there's no en passant
|
||||||
target square, this is "-". If a pawn has just made a 2-square move, this
|
target square, this is "-". If a pawn has just made a 2-square move, this
|
||||||
is the position "behind" the pawn. This is recorded only if there is a pawn
|
is the position "behind" the pawn. Following X-FEN standard, this is recorded only
|
||||||
in position to make an en passant capture, and if there really is a pawn
|
if there is a pawn in position to make an en passant capture, and if there really
|
||||||
that might have advanced two squares.
|
is a pawn that might have advanced two squares.
|
||||||
|
|
||||||
5) Halfmove clock. This is the number of halfmoves since the last pawn advance
|
5) Halfmove clock. This is the number of halfmoves since the last pawn advance
|
||||||
or capture. This is used to determine if a draw can be claimed under the
|
or capture. This is used to determine if a draw can be claimed under the
|
||||||
@@ -208,6 +198,9 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th
|
|||||||
std::fill_n(&pieceList[0][0], sizeof(pieceList) / sizeof(Square), SQ_NONE);
|
std::fill_n(&pieceList[0][0], sizeof(pieceList) / sizeof(Square), SQ_NONE);
|
||||||
st = si;
|
st = si;
|
||||||
|
|
||||||
|
// Each piece on board gets a unique ID used to track the piece later
|
||||||
|
PieceId piece_id, next_piece_id = PIECE_ID_ZERO;
|
||||||
|
|
||||||
ss >> std::noskipws;
|
ss >> std::noskipws;
|
||||||
|
|
||||||
// 1. Piece placement
|
// 1. Piece placement
|
||||||
@@ -221,7 +214,19 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th
|
|||||||
|
|
||||||
else if ((idx = PieceToChar.find(token)) != string::npos)
|
else if ((idx = PieceToChar.find(token)) != string::npos)
|
||||||
{
|
{
|
||||||
put_piece(Piece(idx), sq);
|
auto pc = Piece(idx);
|
||||||
|
put_piece(pc, sq);
|
||||||
|
|
||||||
|
if (Eval::useNNUE)
|
||||||
|
{
|
||||||
|
// Kings get a fixed ID, other pieces get ID in order of placement
|
||||||
|
piece_id =
|
||||||
|
(idx == W_KING) ? PIECE_ID_WKING :
|
||||||
|
(idx == B_KING) ? PIECE_ID_BKING :
|
||||||
|
next_piece_id++;
|
||||||
|
evalList.put_piece(piece_id, sq, pc);
|
||||||
|
}
|
||||||
|
|
||||||
++sq;
|
++sq;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -259,17 +264,25 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th
|
|||||||
set_castling_right(c, rsq);
|
set_castling_right(c, rsq);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. En passant square. Ignore if no pawn capture is possible
|
// 4. En passant square.
|
||||||
|
// Ignore if square is invalid or not on side to move relative rank 6.
|
||||||
|
bool enpassant = false;
|
||||||
|
|
||||||
if ( ((ss >> col) && (col >= 'a' && col <= 'h'))
|
if ( ((ss >> col) && (col >= 'a' && col <= 'h'))
|
||||||
&& ((ss >> row) && (row == '3' || row == '6')))
|
&& ((ss >> row) && (row == (sideToMove == WHITE ? '6' : '3'))))
|
||||||
{
|
{
|
||||||
st->epSquare = make_square(File(col - 'a'), Rank(row - '1'));
|
st->epSquare = make_square(File(col - 'a'), Rank(row - '1'));
|
||||||
|
|
||||||
if ( !(attackers_to(st->epSquare) & pieces(sideToMove, PAWN))
|
// En passant square will be considered only if
|
||||||
|| !(pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove))))
|
// a) side to move have a pawn threatening epSquare
|
||||||
st->epSquare = SQ_NONE;
|
// b) there is an enemy pawn in front of epSquare
|
||||||
|
// c) there is no piece on epSquare or behind epSquare
|
||||||
|
enpassant = pawn_attacks_bb(~sideToMove, st->epSquare) & pieces(sideToMove, PAWN)
|
||||||
|
&& (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove)))
|
||||||
|
&& !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove))));
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
if (!enpassant)
|
||||||
st->epSquare = SQ_NONE;
|
st->epSquare = SQ_NONE;
|
||||||
|
|
||||||
// 5-6. Halfmove clock and fullmove number
|
// 5-6. Halfmove clock and fullmove number
|
||||||
@@ -306,7 +319,7 @@ void Position::set_castling_right(Color c, Square rfrom) {
|
|||||||
Square rto = relative_square(c, cr & KING_SIDE ? SQ_F1 : SQ_D1);
|
Square rto = relative_square(c, cr & KING_SIDE ? SQ_F1 : SQ_D1);
|
||||||
|
|
||||||
castlingPath[cr] = (between_bb(rfrom, rto) | between_bb(kfrom, kto) | rto | kto)
|
castlingPath[cr] = (between_bb(rfrom, rto) | between_bb(kfrom, kto) | rto | kto)
|
||||||
& ~(square_bb(kfrom) | rfrom);
|
& ~(kfrom | rfrom);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -319,10 +332,10 @@ void Position::set_check_info(StateInfo* si) const {
|
|||||||
|
|
||||||
Square ksq = square<KING>(~sideToMove);
|
Square ksq = square<KING>(~sideToMove);
|
||||||
|
|
||||||
si->checkSquares[PAWN] = attacks_from<PAWN>(ksq, ~sideToMove);
|
si->checkSquares[PAWN] = pawn_attacks_bb(~sideToMove, ksq);
|
||||||
si->checkSquares[KNIGHT] = attacks_from<KNIGHT>(ksq);
|
si->checkSquares[KNIGHT] = attacks_bb<KNIGHT>(ksq);
|
||||||
si->checkSquares[BISHOP] = attacks_from<BISHOP>(ksq);
|
si->checkSquares[BISHOP] = attacks_bb<BISHOP>(ksq, pieces());
|
||||||
si->checkSquares[ROOK] = attacks_from<ROOK>(ksq);
|
si->checkSquares[ROOK] = attacks_bb<ROOK>(ksq, pieces());
|
||||||
si->checkSquares[QUEEN] = si->checkSquares[BISHOP] | si->checkSquares[ROOK];
|
si->checkSquares[QUEEN] = si->checkSquares[BISHOP] | si->checkSquares[ROOK];
|
||||||
si->checkSquares[KING] = 0;
|
si->checkSquares[KING] = 0;
|
||||||
}
|
}
|
||||||
@@ -375,11 +388,13 @@ void Position::set_state(StateInfo* si) const {
|
|||||||
|
|
||||||
Position& Position::set(const string& code, Color c, StateInfo* si) {
|
Position& Position::set(const string& code, Color c, StateInfo* si) {
|
||||||
|
|
||||||
assert(code.length() > 0 && code.length() < 8);
|
|
||||||
assert(code[0] == 'K');
|
assert(code[0] == 'K');
|
||||||
|
|
||||||
string sides[] = { code.substr(code.find('K', 1)), // Weak
|
string sides[] = { code.substr(code.find('K', 1)), // Weak
|
||||||
code.substr(0, code.find('K', 1)) }; // Strong
|
code.substr(0, std::min(code.find('v'), code.find('K', 1))) }; // Strong
|
||||||
|
|
||||||
|
assert(sides[0].length() > 0 && sides[0].length() < 8);
|
||||||
|
assert(sides[1].length() > 0 && sides[1].length() < 8);
|
||||||
|
|
||||||
std::transform(sides[c].begin(), sides[c].end(), sides[c].begin(), tolower);
|
std::transform(sides[c].begin(), sides[c].end(), sides[c].begin(), tolower);
|
||||||
|
|
||||||
@@ -453,8 +468,8 @@ Bitboard Position::slider_blockers(Bitboard sliders, Square s, Bitboard& pinners
|
|||||||
pinners = 0;
|
pinners = 0;
|
||||||
|
|
||||||
// Snipers are sliders that attack 's' when a piece and other snipers are removed
|
// Snipers are sliders that attack 's' when a piece and other snipers are removed
|
||||||
Bitboard snipers = ( (PseudoAttacks[ ROOK][s] & pieces(QUEEN, ROOK))
|
Bitboard snipers = ( (attacks_bb< ROOK>(s) & pieces(QUEEN, ROOK))
|
||||||
| (PseudoAttacks[BISHOP][s] & pieces(QUEEN, BISHOP))) & sliders;
|
| (attacks_bb<BISHOP>(s) & pieces(QUEEN, BISHOP))) & sliders;
|
||||||
Bitboard occupancy = pieces() ^ snipers;
|
Bitboard occupancy = pieces() ^ snipers;
|
||||||
|
|
||||||
while (snipers)
|
while (snipers)
|
||||||
@@ -478,12 +493,12 @@ Bitboard Position::slider_blockers(Bitboard sliders, Square s, Bitboard& pinners
|
|||||||
|
|
||||||
Bitboard Position::attackers_to(Square s, Bitboard occupied) const {
|
Bitboard Position::attackers_to(Square s, Bitboard occupied) const {
|
||||||
|
|
||||||
return (attacks_from<PAWN>(s, BLACK) & pieces(WHITE, PAWN))
|
return (pawn_attacks_bb(BLACK, s) & pieces(WHITE, PAWN))
|
||||||
| (attacks_from<PAWN>(s, WHITE) & pieces(BLACK, PAWN))
|
| (pawn_attacks_bb(WHITE, s) & pieces(BLACK, PAWN))
|
||||||
| (attacks_from<KNIGHT>(s) & pieces(KNIGHT))
|
| (attacks_bb<KNIGHT>(s) & pieces(KNIGHT))
|
||||||
| (attacks_bb< ROOK>(s, occupied) & pieces( ROOK, QUEEN))
|
| (attacks_bb< ROOK>(s, occupied) & pieces( ROOK, QUEEN))
|
||||||
| (attacks_bb<BISHOP>(s, occupied) & pieces(BISHOP, QUEEN))
|
| (attacks_bb<BISHOP>(s, occupied) & pieces(BISHOP, QUEEN))
|
||||||
| (attacks_from<KING>(s) & pieces(KING));
|
| (attacks_bb<KING>(s) & pieces(KING));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -586,15 +601,15 @@ bool Position::pseudo_legal(const Move m) const {
|
|||||||
if ((Rank8BB | Rank1BB) & to)
|
if ((Rank8BB | Rank1BB) & to)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if ( !(attacks_from<PAWN>(from, us) & pieces(~us) & to) // Not a capture
|
if ( !(pawn_attacks_bb(us, from) & pieces(~us) & to) // Not a capture
|
||||||
&& !((from + pawn_push(us) == to) && empty(to)) // Not a single push
|
&& !((from + pawn_push(us) == to) && empty(to)) // Not a single push
|
||||||
&& !( (from + 2 * pawn_push(us) == to) // Not a double push
|
&& !( (from + 2 * pawn_push(us) == to) // Not a double push
|
||||||
&& (rank_of(from) == relative_rank(us, RANK_2))
|
&& (relative_rank(us, from) == RANK_2)
|
||||||
&& empty(to)
|
&& empty(to)
|
||||||
&& empty(to - pawn_push(us))))
|
&& empty(to - pawn_push(us))))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if (!(attacks_from(type_of(pc), from) & to))
|
else if (!(attacks_bb(type_of(pc), from, pieces()) & to))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Evasions generator already takes care to avoid some kind of illegal moves
|
// Evasions generator already takes care to avoid some kind of illegal moves
|
||||||
@@ -633,11 +648,11 @@ bool Position::gives_check(Move m) const {
|
|||||||
Square to = to_sq(m);
|
Square to = to_sq(m);
|
||||||
|
|
||||||
// Is there a direct check?
|
// Is there a direct check?
|
||||||
if (st->checkSquares[type_of(piece_on(from))] & to)
|
if (check_squares(type_of(piece_on(from))) & to)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// Is there a discovered check?
|
// Is there a discovered check?
|
||||||
if ( (st->blockersForKing[~sideToMove] & from)
|
if ( (blockers_for_king(~sideToMove) & from)
|
||||||
&& !aligned(from, to, square<KING>(~sideToMove)))
|
&& !aligned(from, to, square<KING>(~sideToMove)))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@@ -664,11 +679,11 @@ bool Position::gives_check(Move m) const {
|
|||||||
case CASTLING:
|
case CASTLING:
|
||||||
{
|
{
|
||||||
Square kfrom = from;
|
Square kfrom = from;
|
||||||
Square rfrom = to; // Castling is encoded as 'King captures the rook'
|
Square rfrom = to; // Castling is encoded as 'king captures the rook'
|
||||||
Square kto = relative_square(sideToMove, rfrom > kfrom ? SQ_G1 : SQ_C1);
|
Square kto = relative_square(sideToMove, rfrom > kfrom ? SQ_G1 : SQ_C1);
|
||||||
Square rto = relative_square(sideToMove, rfrom > kfrom ? SQ_F1 : SQ_D1);
|
Square rto = relative_square(sideToMove, rfrom > kfrom ? SQ_F1 : SQ_D1);
|
||||||
|
|
||||||
return (PseudoAttacks[ROOK][rto] & square<KING>(~sideToMove))
|
return (attacks_bb<ROOK>(rto) & square<KING>(~sideToMove))
|
||||||
&& (attacks_bb<ROOK>(rto, (pieces() ^ kfrom ^ rfrom) | rto | kto) & square<KING>(~sideToMove));
|
&& (attacks_bb<ROOK>(rto, (pieces() ^ kfrom ^ rfrom) | rto | kto) & square<KING>(~sideToMove));
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -703,6 +718,14 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
|
|||||||
++st->rule50;
|
++st->rule50;
|
||||||
++st->pliesFromNull;
|
++st->pliesFromNull;
|
||||||
|
|
||||||
|
// Used by NNUE
|
||||||
|
st->accumulator.computed_accumulation = false;
|
||||||
|
st->accumulator.computed_score = false;
|
||||||
|
PieceId dp0 = PIECE_ID_NONE;
|
||||||
|
PieceId dp1 = PIECE_ID_NONE;
|
||||||
|
auto& dp = st->dirtyPiece;
|
||||||
|
dp.dirty_num = 1;
|
||||||
|
|
||||||
Color us = sideToMove;
|
Color us = sideToMove;
|
||||||
Color them = ~us;
|
Color them = ~us;
|
||||||
Square from = from_sq(m);
|
Square from = from_sq(m);
|
||||||
@@ -743,8 +766,6 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
|
|||||||
assert(relative_rank(us, to) == RANK_6);
|
assert(relative_rank(us, to) == RANK_6);
|
||||||
assert(piece_on(to) == NO_PIECE);
|
assert(piece_on(to) == NO_PIECE);
|
||||||
assert(piece_on(capsq) == make_piece(them, PAWN));
|
assert(piece_on(capsq) == make_piece(them, PAWN));
|
||||||
|
|
||||||
board[capsq] = NO_PIECE; // Not done by remove_piece()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
st->pawnKey ^= Zobrist::psq[captured][capsq];
|
st->pawnKey ^= Zobrist::psq[captured][capsq];
|
||||||
@@ -752,8 +773,21 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
|
|||||||
else
|
else
|
||||||
st->nonPawnMaterial[them] -= PieceValue[MG][captured];
|
st->nonPawnMaterial[them] -= PieceValue[MG][captured];
|
||||||
|
|
||||||
|
if (Eval::useNNUE)
|
||||||
|
{
|
||||||
|
dp.dirty_num = 2; // 2 pieces moved
|
||||||
|
dp1 = piece_id_on(capsq);
|
||||||
|
dp.pieceId[1] = dp1;
|
||||||
|
dp.old_piece[1] = evalList.piece_with_id(dp1);
|
||||||
|
evalList.put_piece(dp1, capsq, NO_PIECE);
|
||||||
|
dp.new_piece[1] = evalList.piece_with_id(dp1);
|
||||||
|
}
|
||||||
|
|
||||||
// Update board and piece lists
|
// Update board and piece lists
|
||||||
remove_piece(captured, capsq);
|
remove_piece(capsq);
|
||||||
|
|
||||||
|
if (type_of(m) == ENPASSANT)
|
||||||
|
board[capsq] = NO_PIECE;
|
||||||
|
|
||||||
// Update material hash key and prefetch access to materialTable
|
// Update material hash key and prefetch access to materialTable
|
||||||
k ^= Zobrist::psq[captured][capsq];
|
k ^= Zobrist::psq[captured][capsq];
|
||||||
@@ -777,21 +811,32 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
|
|||||||
// Update castling rights if needed
|
// Update castling rights if needed
|
||||||
if (st->castlingRights && (castlingRightsMask[from] | castlingRightsMask[to]))
|
if (st->castlingRights && (castlingRightsMask[from] | castlingRightsMask[to]))
|
||||||
{
|
{
|
||||||
int cr = castlingRightsMask[from] | castlingRightsMask[to];
|
k ^= Zobrist::castling[st->castlingRights];
|
||||||
k ^= Zobrist::castling[st->castlingRights & cr];
|
st->castlingRights &= ~(castlingRightsMask[from] | castlingRightsMask[to]);
|
||||||
st->castlingRights &= ~cr;
|
k ^= Zobrist::castling[st->castlingRights];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move the piece. The tricky Chess960 castling is handled earlier
|
// Move the piece. The tricky Chess960 castling is handled earlier
|
||||||
if (type_of(m) != CASTLING)
|
if (type_of(m) != CASTLING)
|
||||||
move_piece(pc, from, to);
|
{
|
||||||
|
if (Eval::useNNUE)
|
||||||
|
{
|
||||||
|
dp0 = piece_id_on(from);
|
||||||
|
dp.pieceId[0] = dp0;
|
||||||
|
dp.old_piece[0] = evalList.piece_with_id(dp0);
|
||||||
|
evalList.put_piece(dp0, to, pc);
|
||||||
|
dp.new_piece[0] = evalList.piece_with_id(dp0);
|
||||||
|
}
|
||||||
|
|
||||||
|
move_piece(from, to);
|
||||||
|
}
|
||||||
|
|
||||||
// If the moving piece is a pawn do some special extra work
|
// If the moving piece is a pawn do some special extra work
|
||||||
if (type_of(pc) == PAWN)
|
if (type_of(pc) == PAWN)
|
||||||
{
|
{
|
||||||
// Set en-passant square if the moved pawn can be captured
|
// Set en-passant square if the moved pawn can be captured
|
||||||
if ( (int(to) ^ int(from)) == 16
|
if ( (int(to) ^ int(from)) == 16
|
||||||
&& (attacks_from<PAWN>(to - pawn_push(us), us) & pieces(them, PAWN)))
|
&& (pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN)))
|
||||||
{
|
{
|
||||||
st->epSquare = to - pawn_push(us);
|
st->epSquare = to - pawn_push(us);
|
||||||
k ^= Zobrist::enpassant[file_of(st->epSquare)];
|
k ^= Zobrist::enpassant[file_of(st->epSquare)];
|
||||||
@@ -804,9 +849,16 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
|
|||||||
assert(relative_rank(us, to) == RANK_8);
|
assert(relative_rank(us, to) == RANK_8);
|
||||||
assert(type_of(promotion) >= KNIGHT && type_of(promotion) <= QUEEN);
|
assert(type_of(promotion) >= KNIGHT && type_of(promotion) <= QUEEN);
|
||||||
|
|
||||||
remove_piece(pc, to);
|
remove_piece(to);
|
||||||
put_piece(promotion, to);
|
put_piece(promotion, to);
|
||||||
|
|
||||||
|
if (Eval::useNNUE)
|
||||||
|
{
|
||||||
|
dp0 = piece_id_on(to);
|
||||||
|
evalList.put_piece(dp0, to, promotion);
|
||||||
|
dp.new_piece[0] = evalList.piece_with_id(dp0);
|
||||||
|
}
|
||||||
|
|
||||||
// Update hash keys
|
// Update hash keys
|
||||||
k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[promotion][to];
|
k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[promotion][to];
|
||||||
st->pawnKey ^= Zobrist::psq[pc][to];
|
st->pawnKey ^= Zobrist::psq[pc][to];
|
||||||
@@ -884,7 +936,7 @@ void Position::undo_move(Move m) {
|
|||||||
assert(type_of(pc) == promotion_type(m));
|
assert(type_of(pc) == promotion_type(m));
|
||||||
assert(type_of(pc) >= KNIGHT && type_of(pc) <= QUEEN);
|
assert(type_of(pc) >= KNIGHT && type_of(pc) <= QUEEN);
|
||||||
|
|
||||||
remove_piece(pc, to);
|
remove_piece(to);
|
||||||
pc = make_piece(us, PAWN);
|
pc = make_piece(us, PAWN);
|
||||||
put_piece(pc, to);
|
put_piece(pc, to);
|
||||||
}
|
}
|
||||||
@@ -896,7 +948,13 @@ void Position::undo_move(Move m) {
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
move_piece(pc, to, from); // Put the piece back at the source square
|
move_piece(to, from); // Put the piece back at the source square
|
||||||
|
|
||||||
|
if (Eval::useNNUE)
|
||||||
|
{
|
||||||
|
PieceId dp0 = st->dirtyPiece.pieceId[0];
|
||||||
|
evalList.put_piece(dp0, from, pc);
|
||||||
|
}
|
||||||
|
|
||||||
if (st->capturedPiece)
|
if (st->capturedPiece)
|
||||||
{
|
{
|
||||||
@@ -914,6 +972,14 @@ void Position::undo_move(Move m) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
put_piece(st->capturedPiece, capsq); // Restore the captured piece
|
put_piece(st->capturedPiece, capsq); // Restore the captured piece
|
||||||
|
|
||||||
|
if (Eval::useNNUE)
|
||||||
|
{
|
||||||
|
PieceId dp1 = st->dirtyPiece.pieceId[1];
|
||||||
|
assert(evalList.piece_with_id(dp1).from[WHITE] == PS_NONE);
|
||||||
|
assert(evalList.piece_with_id(dp1).from[BLACK] == PS_NONE);
|
||||||
|
evalList.put_piece(dp1, capsq, st->capturedPiece);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -935,10 +1001,38 @@ void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Squ
|
|||||||
rto = relative_square(us, kingSide ? SQ_F1 : SQ_D1);
|
rto = relative_square(us, kingSide ? SQ_F1 : SQ_D1);
|
||||||
to = relative_square(us, kingSide ? SQ_G1 : SQ_C1);
|
to = relative_square(us, kingSide ? SQ_G1 : SQ_C1);
|
||||||
|
|
||||||
|
if (Eval::useNNUE)
|
||||||
|
{
|
||||||
|
PieceId dp0, dp1;
|
||||||
|
auto& dp = st->dirtyPiece;
|
||||||
|
dp.dirty_num = 2; // 2 pieces moved
|
||||||
|
|
||||||
|
if (Do)
|
||||||
|
{
|
||||||
|
dp0 = piece_id_on(from);
|
||||||
|
dp1 = piece_id_on(rfrom);
|
||||||
|
dp.pieceId[0] = dp0;
|
||||||
|
dp.old_piece[0] = evalList.piece_with_id(dp0);
|
||||||
|
evalList.put_piece(dp0, to, make_piece(us, KING));
|
||||||
|
dp.new_piece[0] = evalList.piece_with_id(dp0);
|
||||||
|
dp.pieceId[1] = dp1;
|
||||||
|
dp.old_piece[1] = evalList.piece_with_id(dp1);
|
||||||
|
evalList.put_piece(dp1, rto, make_piece(us, ROOK));
|
||||||
|
dp.new_piece[1] = evalList.piece_with_id(dp1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dp0 = piece_id_on(to);
|
||||||
|
dp1 = piece_id_on(rto);
|
||||||
|
evalList.put_piece(dp0, from, make_piece(us, KING));
|
||||||
|
evalList.put_piece(dp1, rfrom, make_piece(us, ROOK));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Remove both pieces first since squares could overlap in Chess960
|
// Remove both pieces first since squares could overlap in Chess960
|
||||||
remove_piece(make_piece(us, KING), Do ? from : to);
|
remove_piece(Do ? from : to);
|
||||||
remove_piece(make_piece(us, ROOK), Do ? rfrom : rto);
|
remove_piece(Do ? rfrom : rto);
|
||||||
board[Do ? from : to] = board[Do ? rfrom : rto] = NO_PIECE; // Since remove_piece doesn't do it for us
|
board[Do ? from : to] = board[Do ? rfrom : rto] = NO_PIECE; // Since remove_piece doesn't do this for us
|
||||||
put_piece(make_piece(us, KING), Do ? to : from);
|
put_piece(make_piece(us, KING), Do ? to : from);
|
||||||
put_piece(make_piece(us, ROOK), Do ? rto : rfrom);
|
put_piece(make_piece(us, ROOK), Do ? rto : rfrom);
|
||||||
}
|
}
|
||||||
@@ -952,7 +1046,14 @@ void Position::do_null_move(StateInfo& newSt) {
|
|||||||
assert(!checkers());
|
assert(!checkers());
|
||||||
assert(&newSt != st);
|
assert(&newSt != st);
|
||||||
|
|
||||||
|
if (Eval::useNNUE)
|
||||||
|
{
|
||||||
std::memcpy(&newSt, st, sizeof(StateInfo));
|
std::memcpy(&newSt, st, sizeof(StateInfo));
|
||||||
|
st->accumulator.computed_score = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
std::memcpy(&newSt, st, offsetof(StateInfo, accumulator));
|
||||||
|
|
||||||
newSt.previous = st;
|
newSt.previous = st;
|
||||||
st = &newSt;
|
st = &newSt;
|
||||||
|
|
||||||
@@ -1108,6 +1209,7 @@ bool Position::see_ge(Move m, Value threshold) const {
|
|||||||
return bool(res);
|
return bool(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Position::is_draw() tests whether the position is drawn by 50-move rule
|
/// Position::is_draw() tests whether the position is drawn by 50-move rule
|
||||||
/// or by repetition. It does not detect stalemates.
|
/// or by repetition. It does not detect stalemates.
|
||||||
|
|
||||||
@@ -1118,10 +1220,7 @@ bool Position::is_draw(int ply) const {
|
|||||||
|
|
||||||
// Return a draw score if a position repeats once earlier but strictly
|
// Return a draw score if a position repeats once earlier but strictly
|
||||||
// after the root, or repeats twice before or at the root.
|
// after the root, or repeats twice before or at the root.
|
||||||
if (st->repetition && st->repetition < ply)
|
return st->repetition && st->repetition < ply;
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+67
-54
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -27,8 +25,11 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "bitboard.h"
|
#include "bitboard.h"
|
||||||
|
#include "evaluate.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
|
#include "nnue/nnue_accumulator.h"
|
||||||
|
|
||||||
|
|
||||||
/// StateInfo struct stores information needed to restore a Position object to
|
/// StateInfo struct stores information needed to restore a Position object to
|
||||||
/// its previous state when we retract a move. Whenever a move is made on the
|
/// its previous state when we retract a move. Whenever a move is made on the
|
||||||
@@ -54,8 +55,13 @@ struct StateInfo {
|
|||||||
Bitboard pinners[COLOR_NB];
|
Bitboard pinners[COLOR_NB];
|
||||||
Bitboard checkSquares[PIECE_TYPE_NB];
|
Bitboard checkSquares[PIECE_TYPE_NB];
|
||||||
int repetition;
|
int repetition;
|
||||||
|
|
||||||
|
// Used by NNUE
|
||||||
|
Eval::NNUE::Accumulator accumulator;
|
||||||
|
DirtyPiece dirtyPiece;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/// A list to keep track of the position states along the setup moves (from the
|
/// A list to keep track of the position states along the setup moves (from the
|
||||||
/// start position to the position just before the search starts). Needed by
|
/// start position to the position just before the search starts). Needed by
|
||||||
/// 'draw by repetition' detection. Use a std::deque because pointers to
|
/// 'draw by repetition' detection. Use a std::deque because pointers to
|
||||||
@@ -83,7 +89,6 @@ public:
|
|||||||
const std::string fen() const;
|
const std::string fen() const;
|
||||||
|
|
||||||
// Position representation
|
// Position representation
|
||||||
Bitboard pieces() const;
|
|
||||||
Bitboard pieces(PieceType pt) const;
|
Bitboard pieces(PieceType pt) const;
|
||||||
Bitboard pieces(PieceType pt1, PieceType pt2) const;
|
Bitboard pieces(PieceType pt1, PieceType pt2) const;
|
||||||
Bitboard pieces(Color c) const;
|
Bitboard pieces(Color c) const;
|
||||||
@@ -99,7 +104,7 @@ public:
|
|||||||
bool is_on_semiopen_file(Color c, Square s) const;
|
bool is_on_semiopen_file(Color c, Square s) const;
|
||||||
|
|
||||||
// Castling
|
// Castling
|
||||||
int castling_rights(Color c) const;
|
CastlingRights castling_rights(Color c) const;
|
||||||
bool can_castle(CastlingRights cr) const;
|
bool can_castle(CastlingRights cr) const;
|
||||||
bool castling_impeded(CastlingRights cr) const;
|
bool castling_impeded(CastlingRights cr) const;
|
||||||
Square castling_rook_square(CastlingRights cr) const;
|
Square castling_rook_square(CastlingRights cr) const;
|
||||||
@@ -113,9 +118,6 @@ public:
|
|||||||
// Attacks to/from a given square
|
// Attacks to/from a given square
|
||||||
Bitboard attackers_to(Square s) const;
|
Bitboard attackers_to(Square s) const;
|
||||||
Bitboard attackers_to(Square s, Bitboard occupied) const;
|
Bitboard attackers_to(Square s, Bitboard occupied) const;
|
||||||
Bitboard attacks_from(PieceType pt, Square s) const;
|
|
||||||
template<PieceType> Bitboard attacks_from(Square s) const;
|
|
||||||
template<PieceType> Bitboard attacks_from(Square s, Color c) const;
|
|
||||||
Bitboard slider_blockers(Bitboard sliders, Square s, Bitboard& pinners) const;
|
Bitboard slider_blockers(Bitboard sliders, Square s, Bitboard& pinners) const;
|
||||||
|
|
||||||
// Properties of moves
|
// Properties of moves
|
||||||
@@ -166,6 +168,10 @@ public:
|
|||||||
bool pos_is_ok() const;
|
bool pos_is_ok() const;
|
||||||
void flip();
|
void flip();
|
||||||
|
|
||||||
|
// Used by NNUE
|
||||||
|
StateInfo* state() const;
|
||||||
|
const EvalList* eval_list() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Initialization helpers (used while setting up a position)
|
// Initialization helpers (used while setting up a position)
|
||||||
void set_castling_right(Color c, Square rfrom);
|
void set_castling_right(Color c, Square rfrom);
|
||||||
@@ -174,11 +180,14 @@ private:
|
|||||||
|
|
||||||
// Other helpers
|
// Other helpers
|
||||||
void put_piece(Piece pc, Square s);
|
void put_piece(Piece pc, Square s);
|
||||||
void remove_piece(Piece pc, Square s);
|
void remove_piece(Square s);
|
||||||
void move_piece(Piece pc, Square from, Square to);
|
void move_piece(Square from, Square to);
|
||||||
template<bool Do>
|
template<bool Do>
|
||||||
void do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto);
|
void do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto);
|
||||||
|
|
||||||
|
// ID of a piece on a given square
|
||||||
|
PieceId piece_id_on(Square sq) const;
|
||||||
|
|
||||||
// Data members
|
// Data members
|
||||||
Piece board[SQUARE_NB];
|
Piece board[SQUARE_NB];
|
||||||
Bitboard byTypeBB[PIECE_TYPE_NB];
|
Bitboard byTypeBB[PIECE_TYPE_NB];
|
||||||
@@ -195,6 +204,9 @@ private:
|
|||||||
Thread* thisThread;
|
Thread* thisThread;
|
||||||
StateInfo* st;
|
StateInfo* st;
|
||||||
bool chess960;
|
bool chess960;
|
||||||
|
|
||||||
|
// List of pieces used in NNUE evaluation function
|
||||||
|
EvalList evalList;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace PSQT {
|
namespace PSQT {
|
||||||
@@ -207,28 +219,25 @@ inline Color Position::side_to_move() const {
|
|||||||
return sideToMove;
|
return sideToMove;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool Position::empty(Square s) const {
|
|
||||||
return board[s] == NO_PIECE;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline Piece Position::piece_on(Square s) const {
|
inline Piece Position::piece_on(Square s) const {
|
||||||
|
assert(is_ok(s));
|
||||||
return board[s];
|
return board[s];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool Position::empty(Square s) const {
|
||||||
|
return piece_on(s) == NO_PIECE;
|
||||||
|
}
|
||||||
|
|
||||||
inline Piece Position::moved_piece(Move m) const {
|
inline Piece Position::moved_piece(Move m) const {
|
||||||
return board[from_sq(m)];
|
return piece_on(from_sq(m));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Bitboard Position::pieces() const {
|
inline Bitboard Position::pieces(PieceType pt = ALL_PIECES) const {
|
||||||
return byTypeBB[ALL_PIECES];
|
|
||||||
}
|
|
||||||
|
|
||||||
inline Bitboard Position::pieces(PieceType pt) const {
|
|
||||||
return byTypeBB[pt];
|
return byTypeBB[pt];
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Bitboard Position::pieces(PieceType pt1, PieceType pt2) const {
|
inline Bitboard Position::pieces(PieceType pt1, PieceType pt2) const {
|
||||||
return byTypeBB[pt1] | byTypeBB[pt2];
|
return pieces(pt1) | pieces(pt2);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Bitboard Position::pieces(Color c) const {
|
inline Bitboard Position::pieces(Color c) const {
|
||||||
@@ -236,11 +245,11 @@ inline Bitboard Position::pieces(Color c) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline Bitboard Position::pieces(Color c, PieceType pt) const {
|
inline Bitboard Position::pieces(Color c, PieceType pt) const {
|
||||||
return byColorBB[c] & byTypeBB[pt];
|
return pieces(c) & pieces(pt);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Bitboard Position::pieces(Color c, PieceType pt1, PieceType pt2) const {
|
inline Bitboard Position::pieces(Color c, PieceType pt1, PieceType pt2) const {
|
||||||
return byColorBB[c] & (byTypeBB[pt1] | byTypeBB[pt2]);
|
return pieces(c) & (pieces(pt1) | pieces(pt2));
|
||||||
}
|
}
|
||||||
|
|
||||||
template<PieceType Pt> inline int Position::count(Color c) const {
|
template<PieceType Pt> inline int Position::count(Color c) const {
|
||||||
@@ -248,7 +257,7 @@ template<PieceType Pt> inline int Position::count(Color c) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<PieceType Pt> inline int Position::count() const {
|
template<PieceType Pt> inline int Position::count() const {
|
||||||
return pieceCount[make_piece(WHITE, Pt)] + pieceCount[make_piece(BLACK, Pt)];
|
return count<Pt>(WHITE) + count<Pt>(BLACK);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<PieceType Pt> inline const Square* Position::squares(Color c) const {
|
template<PieceType Pt> inline const Square* Position::squares(Color c) const {
|
||||||
@@ -257,7 +266,7 @@ template<PieceType Pt> inline const Square* Position::squares(Color c) const {
|
|||||||
|
|
||||||
template<PieceType Pt> inline Square Position::square(Color c) const {
|
template<PieceType Pt> inline Square Position::square(Color c) const {
|
||||||
assert(pieceCount[make_piece(c, Pt)] == 1);
|
assert(pieceCount[make_piece(c, Pt)] == 1);
|
||||||
return pieceList[make_piece(c, Pt)][0];
|
return squares<Pt>(c)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Square Position::ep_square() const {
|
inline Square Position::ep_square() const {
|
||||||
@@ -272,14 +281,14 @@ inline bool Position::can_castle(CastlingRights cr) const {
|
|||||||
return st->castlingRights & cr;
|
return st->castlingRights & cr;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline int Position::castling_rights(Color c) const {
|
inline CastlingRights Position::castling_rights(Color c) const {
|
||||||
return st->castlingRights & (c == WHITE ? WHITE_CASTLING : BLACK_CASTLING);
|
return c & CastlingRights(st->castlingRights);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool Position::castling_impeded(CastlingRights cr) const {
|
inline bool Position::castling_impeded(CastlingRights cr) const {
|
||||||
assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO);
|
assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO);
|
||||||
|
|
||||||
return byTypeBB[ALL_PIECES] & castlingPath[cr];
|
return pieces() & castlingPath[cr];
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Square Position::castling_rook_square(CastlingRights cr) const {
|
inline Square Position::castling_rook_square(CastlingRights cr) const {
|
||||||
@@ -288,26 +297,8 @@ inline Square Position::castling_rook_square(CastlingRights cr) const {
|
|||||||
return castlingRookSquare[cr];
|
return castlingRookSquare[cr];
|
||||||
}
|
}
|
||||||
|
|
||||||
template<PieceType Pt>
|
|
||||||
inline Bitboard Position::attacks_from(Square s) const {
|
|
||||||
static_assert(Pt != PAWN, "Pawn attacks need color");
|
|
||||||
|
|
||||||
return Pt == BISHOP || Pt == ROOK ? attacks_bb<Pt>(s, byTypeBB[ALL_PIECES])
|
|
||||||
: Pt == QUEEN ? attacks_from<ROOK>(s) | attacks_from<BISHOP>(s)
|
|
||||||
: PseudoAttacks[Pt][s];
|
|
||||||
}
|
|
||||||
|
|
||||||
template<>
|
|
||||||
inline Bitboard Position::attacks_from<PAWN>(Square s, Color c) const {
|
|
||||||
return PawnAttacks[c][s];
|
|
||||||
}
|
|
||||||
|
|
||||||
inline Bitboard Position::attacks_from(PieceType pt, Square s) const {
|
|
||||||
return attacks_bb(pt, s, byTypeBB[ALL_PIECES]);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline Bitboard Position::attackers_to(Square s) const {
|
inline Bitboard Position::attackers_to(Square s) const {
|
||||||
return attackers_to(s, byTypeBB[ALL_PIECES]);
|
return attackers_to(s, pieces());
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Bitboard Position::checkers() const {
|
inline Bitboard Position::checkers() const {
|
||||||
@@ -360,7 +351,7 @@ inline Value Position::non_pawn_material(Color c) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline Value Position::non_pawn_material() const {
|
inline Value Position::non_pawn_material() const {
|
||||||
return st->nonPawnMaterial[WHITE] + st->nonPawnMaterial[BLACK];
|
return non_pawn_material(WHITE) + non_pawn_material(BLACK);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline int Position::game_ply() const {
|
inline int Position::game_ply() const {
|
||||||
@@ -372,8 +363,8 @@ inline int Position::rule50_count() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline bool Position::opposite_bishops() const {
|
inline bool Position::opposite_bishops() const {
|
||||||
return pieceCount[W_BISHOP] == 1
|
return count<BISHOP>(WHITE) == 1
|
||||||
&& pieceCount[B_BISHOP] == 1
|
&& count<BISHOP>(BLACK) == 1
|
||||||
&& opposite_colors(square<BISHOP>(WHITE), square<BISHOP>(BLACK));
|
&& opposite_colors(square<BISHOP>(WHITE), square<BISHOP>(BLACK));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,8 +394,7 @@ inline Thread* Position::this_thread() const {
|
|||||||
inline void Position::put_piece(Piece pc, Square s) {
|
inline void Position::put_piece(Piece pc, Square s) {
|
||||||
|
|
||||||
board[s] = pc;
|
board[s] = pc;
|
||||||
byTypeBB[ALL_PIECES] |= s;
|
byTypeBB[ALL_PIECES] |= byTypeBB[type_of(pc)] |= s;
|
||||||
byTypeBB[type_of(pc)] |= s;
|
|
||||||
byColorBB[color_of(pc)] |= s;
|
byColorBB[color_of(pc)] |= s;
|
||||||
index[s] = pieceCount[pc]++;
|
index[s] = pieceCount[pc]++;
|
||||||
pieceList[pc][index[s]] = s;
|
pieceList[pc][index[s]] = s;
|
||||||
@@ -412,12 +402,13 @@ inline void Position::put_piece(Piece pc, Square s) {
|
|||||||
psq += PSQT::psq[pc][s];
|
psq += PSQT::psq[pc][s];
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Position::remove_piece(Piece pc, Square s) {
|
inline void Position::remove_piece(Square s) {
|
||||||
|
|
||||||
// WARNING: This is not a reversible operation. If we remove a piece in
|
// WARNING: This is not a reversible operation. If we remove a piece in
|
||||||
// do_move() and then replace it in undo_move() we will put it at the end of
|
// do_move() and then replace it in undo_move() we will put it at the end of
|
||||||
// the list and not in its original place, it means index[] and pieceList[]
|
// the list and not in its original place, it means index[] and pieceList[]
|
||||||
// are not invariant to a do_move() + undo_move() sequence.
|
// are not invariant to a do_move() + undo_move() sequence.
|
||||||
|
Piece pc = board[s];
|
||||||
byTypeBB[ALL_PIECES] ^= s;
|
byTypeBB[ALL_PIECES] ^= s;
|
||||||
byTypeBB[type_of(pc)] ^= s;
|
byTypeBB[type_of(pc)] ^= s;
|
||||||
byColorBB[color_of(pc)] ^= s;
|
byColorBB[color_of(pc)] ^= s;
|
||||||
@@ -430,10 +421,11 @@ inline void Position::remove_piece(Piece pc, Square s) {
|
|||||||
psq -= PSQT::psq[pc][s];
|
psq -= PSQT::psq[pc][s];
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Position::move_piece(Piece pc, Square from, Square to) {
|
inline void Position::move_piece(Square from, Square to) {
|
||||||
|
|
||||||
// index[from] is not updated and becomes stale. This works as long as index[]
|
// index[from] is not updated and becomes stale. This works as long as index[]
|
||||||
// is accessed just by known occupied squares.
|
// is accessed just by known occupied squares.
|
||||||
|
Piece pc = board[from];
|
||||||
Bitboard fromTo = from | to;
|
Bitboard fromTo = from | to;
|
||||||
byTypeBB[ALL_PIECES] ^= fromTo;
|
byTypeBB[ALL_PIECES] ^= fromTo;
|
||||||
byTypeBB[type_of(pc)] ^= fromTo;
|
byTypeBB[type_of(pc)] ^= fromTo;
|
||||||
@@ -449,4 +441,25 @@ inline void Position::do_move(Move m, StateInfo& newSt) {
|
|||||||
do_move(m, newSt, gives_check(m));
|
do_move(m, newSt, gives_check(m));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline StateInfo* Position::state() const {
|
||||||
|
|
||||||
|
return st;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const EvalList* Position::eval_list() const {
|
||||||
|
|
||||||
|
return &evalList;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline PieceId Position::piece_id_on(Square sq) const
|
||||||
|
{
|
||||||
|
|
||||||
|
assert(piece_on(sq) != NO_PIECE);
|
||||||
|
|
||||||
|
PieceId pid = evalList.piece_id_list[sq];
|
||||||
|
assert(is_ok(pid));
|
||||||
|
|
||||||
|
return pid;
|
||||||
|
}
|
||||||
|
|
||||||
#endif // #ifndef POSITION_H_INCLUDED
|
#endif // #ifndef POSITION_H_INCLUDED
|
||||||
|
|||||||
+13
-21
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,11 +19,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
#include "bitboard.h"
|
||||||
Value PieceValue[PHASE_NB][PIECE_NB] = {
|
|
||||||
{ VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg },
|
|
||||||
{ VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg }
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace PSQT {
|
namespace PSQT {
|
||||||
|
|
||||||
@@ -95,9 +89,9 @@ constexpr Score PBonus[RANK_NB][FILE_NB] =
|
|||||||
{ },
|
{ },
|
||||||
{ S( 3,-10), S( 3, -6), S( 10, 10), S( 19, 0), S( 16, 14), S( 19, 7), S( 7, -5), S( -5,-19) },
|
{ S( 3,-10), S( 3, -6), S( 10, 10), S( 19, 0), S( 16, 14), S( 19, 7), S( 7, -5), S( -5,-19) },
|
||||||
{ S( -9,-10), S(-15,-10), S( 11,-10), S( 15, 4), S( 32, 4), S( 22, 3), S( 5, -6), S(-22, -4) },
|
{ S( -9,-10), S(-15,-10), S( 11,-10), S( 15, 4), S( 32, 4), S( 22, 3), S( 5, -6), S(-22, -4) },
|
||||||
{ S( -8, 6), S(-23, -2), S( 6, -8), S( 20, -4), S( 40,-13), S( 17,-12), S( 4,-10), S(-12, -9) },
|
{ S( -4, 6), S(-23, -2), S( 6, -8), S( 20, -4), S( 40,-13), S( 17,-12), S( 4,-10), S( -8, -9) },
|
||||||
{ S( 13, 9), S( 0, 4), S(-13, 3), S( 1,-12), S( 11,-12), S( -2, -6), S(-13, 13), S( 5, 8) },
|
{ S( 13, 10), S( 0, 5), S(-13, 4), S( 1, -5), S( 11, -5), S( -2, -5), S(-13, 14), S( 5, 9) },
|
||||||
{ S( -5, 28), S(-12, 20), S( -7, 21), S( 22, 28), S( -8, 30), S( -5, 7), S(-15, 6), S(-18, 13) },
|
{ S( 5, 28), S(-12, 20), S( -7, 21), S( 22, 28), S( -8, 30), S( -5, 7), S(-15, 6), S( -8, 13) },
|
||||||
{ S( -7, 0), S( 7,-11), S( -3, 12), S(-13, 21), S( 5, 25), S(-16, 19), S( 10, 4), S( -8, 7) }
|
{ S( -7, 0), S( 7,-11), S( -3, 12), S(-13, 21), S( 5, 25), S(-16, 19), S( 10, 4), S( -8, 7) }
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -105,24 +99,22 @@ constexpr Score PBonus[RANK_NB][FILE_NB] =
|
|||||||
|
|
||||||
Score psq[PIECE_NB][SQUARE_NB];
|
Score psq[PIECE_NB][SQUARE_NB];
|
||||||
|
|
||||||
// init() initializes piece-square tables: the white halves of the tables are
|
|
||||||
// copied from Bonus[] adding the piece value, then the black halves of the
|
// PSQT::init() initializes piece-square tables: the white halves of the tables are
|
||||||
// tables are initialized by flipping and changing the sign of the white scores.
|
// copied from Bonus[] and PBonus[], adding the piece value, then the black halves of
|
||||||
|
// the tables are initialized by flipping and changing the sign of the white scores.
|
||||||
void init() {
|
void init() {
|
||||||
|
|
||||||
for (Piece pc = W_PAWN; pc <= W_KING; ++pc)
|
for (Piece pc : {W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING})
|
||||||
{
|
{
|
||||||
PieceValue[MG][~pc] = PieceValue[MG][pc];
|
|
||||||
PieceValue[EG][~pc] = PieceValue[EG][pc];
|
|
||||||
|
|
||||||
Score score = make_score(PieceValue[MG][pc], PieceValue[EG][pc]);
|
Score score = make_score(PieceValue[MG][pc], PieceValue[EG][pc]);
|
||||||
|
|
||||||
for (Square s = SQ_A1; s <= SQ_H8; ++s)
|
for (Square s = SQ_A1; s <= SQ_H8; ++s)
|
||||||
{
|
{
|
||||||
File f = map_to_queenside(file_of(s));
|
File f = File(edge_distance(file_of(s)));
|
||||||
psq[ pc][ s] = score + (type_of(pc) == PAWN ? PBonus[rank_of(s)][file_of(s)]
|
psq[ pc][s] = score + (type_of(pc) == PAWN ? PBonus[rank_of(s)][file_of(s)]
|
||||||
: Bonus[pc][rank_of(s)][f]);
|
: Bonus[pc][rank_of(s)][f]);
|
||||||
psq[~pc][~s] = -psq[pc][s];
|
psq[~pc][flip_rank(s)] = -psq[pc][s];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+271
-189
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -61,13 +59,13 @@ namespace {
|
|||||||
// Different node types, used as a template parameter
|
// Different node types, used as a template parameter
|
||||||
enum NodeType { NonPV, PV };
|
enum NodeType { NonPV, PV };
|
||||||
|
|
||||||
constexpr uint64_t ttHitAverageWindow = 4096;
|
constexpr uint64_t TtHitAverageWindow = 4096;
|
||||||
constexpr uint64_t ttHitAverageResolution = 1024;
|
constexpr uint64_t TtHitAverageResolution = 1024;
|
||||||
|
|
||||||
// Razor and futility margins
|
// Razor and futility margins
|
||||||
constexpr int RazorMargin = 531;
|
constexpr int RazorMargin = 527;
|
||||||
Value futility_margin(Depth d, bool improving) {
|
Value futility_margin(Depth d, bool improving) {
|
||||||
return Value(217 * (d - improving));
|
return Value(227 * (d - improving));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reductions lookup table, initialized at startup
|
// Reductions lookup table, initialized at startup
|
||||||
@@ -75,16 +73,16 @@ namespace {
|
|||||||
|
|
||||||
Depth reduction(bool i, Depth d, int mn) {
|
Depth reduction(bool i, Depth d, int mn) {
|
||||||
int r = Reductions[d] * Reductions[mn];
|
int r = Reductions[d] * Reductions[mn];
|
||||||
return (r + 511) / 1024 + (!i && r > 1007);
|
return (r + 570) / 1024 + (!i && r > 1018);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr int futility_move_count(bool improving, Depth depth) {
|
constexpr int futility_move_count(bool improving, Depth depth) {
|
||||||
return (5 + depth * depth) * (1 + improving) / 2 - 1;
|
return (3 + depth * depth) / (2 - improving);
|
||||||
}
|
}
|
||||||
|
|
||||||
// History and stats update bonus, based on depth
|
// History and stats update bonus, based on depth
|
||||||
int stat_bonus(Depth d) {
|
int stat_bonus(Depth d) {
|
||||||
return d > 15 ? -8 : 19 * d * d + 155 * d - 132;
|
return d > 15 ? 27 : 17 * d * d + 133 * d - 134;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a small random component to draw evaluations to avoid 3fold-blindness
|
// Add a small random component to draw evaluations to avoid 3fold-blindness
|
||||||
@@ -156,7 +154,7 @@ namespace {
|
|||||||
Value value_from_tt(Value v, int ply, int r50c);
|
Value value_from_tt(Value v, int ply, int r50c);
|
||||||
void update_pv(Move* pv, Move move, Move* childPv);
|
void update_pv(Move* pv, Move move, Move* childPv);
|
||||||
void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus);
|
void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus);
|
||||||
void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus);
|
void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus, int depth);
|
||||||
void update_all_stats(const Position& pos, Stack* ss, Move bestMove, Value bestValue, Value beta, Square prevSq,
|
void update_all_stats(const Position& pos, Stack* ss, Move bestMove, Value bestValue, Value beta, Square prevSq,
|
||||||
Move* quietsSearched, int quietCount, Move* capturesSearched, int captureCount, Depth depth);
|
Move* quietsSearched, int quietCount, Move* capturesSearched, int captureCount, Depth depth);
|
||||||
|
|
||||||
@@ -194,7 +192,7 @@ namespace {
|
|||||||
void Search::init() {
|
void Search::init() {
|
||||||
|
|
||||||
for (int i = 1; i < MAX_MOVES; ++i)
|
for (int i = 1; i < MAX_MOVES; ++i)
|
||||||
Reductions[i] = int((24.8 + std::log(Threads.size()) / 2) * std::log(i));
|
Reductions[i] = int((24.8 + std::log(Threads.size())) * std::log(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -227,6 +225,8 @@ void MainThread::search() {
|
|||||||
Time.init(Limits, us, rootPos.game_ply());
|
Time.init(Limits, us, rootPos.game_ply());
|
||||||
TT.new_search();
|
TT.new_search();
|
||||||
|
|
||||||
|
Eval::verify_NNUE();
|
||||||
|
|
||||||
if (rootMoves.empty())
|
if (rootMoves.empty())
|
||||||
{
|
{
|
||||||
rootMoves.emplace_back(MOVE_NONE);
|
rootMoves.emplace_back(MOVE_NONE);
|
||||||
@@ -236,14 +236,8 @@ void MainThread::search() {
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (Thread* th : Threads)
|
Threads.start_searching(); // start non-main threads
|
||||||
{
|
Thread::search(); // main thread start searching
|
||||||
th->bestMoveChanges = 0;
|
|
||||||
if (th != this)
|
|
||||||
th->start_searching();
|
|
||||||
}
|
|
||||||
|
|
||||||
Thread::search(); // Let's start searching!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// When we reach the maximum depth, we can arrive here without a raise of
|
// When we reach the maximum depth, we can arrive here without a raise of
|
||||||
@@ -260,9 +254,7 @@ void MainThread::search() {
|
|||||||
Threads.stop = true;
|
Threads.stop = true;
|
||||||
|
|
||||||
// Wait until all threads have finished
|
// Wait until all threads have finished
|
||||||
for (Thread* th : Threads)
|
Threads.wait_for_search_finished();
|
||||||
if (th != this)
|
|
||||||
th->wait_for_search_finished();
|
|
||||||
|
|
||||||
// When playing in 'nodes as time' mode, subtract the searched nodes from
|
// When playing in 'nodes as time' mode, subtract the searched nodes from
|
||||||
// the available ones before exiting.
|
// the available ones before exiting.
|
||||||
@@ -271,38 +263,13 @@ void MainThread::search() {
|
|||||||
|
|
||||||
Thread* bestThread = this;
|
Thread* bestThread = this;
|
||||||
|
|
||||||
// Check if there are threads with a better score than main thread
|
if ( int(Options["MultiPV"]) == 1
|
||||||
if ( Options["MultiPV"] == 1
|
|
||||||
&& !Limits.depth
|
&& !Limits.depth
|
||||||
&& !(Skill(Options["Skill Level"]).enabled() || Options["UCI_LimitStrength"])
|
&& !(Skill(Options["Skill Level"]).enabled() || int(Options["UCI_LimitStrength"]))
|
||||||
&& rootMoves[0].pv[0] != MOVE_NONE)
|
&& rootMoves[0].pv[0] != MOVE_NONE)
|
||||||
{
|
bestThread = Threads.get_best_thread();
|
||||||
std::map<Move, int64_t> votes;
|
|
||||||
Value minScore = this->rootMoves[0].score;
|
|
||||||
|
|
||||||
// Find out minimum score
|
bestPreviousScore = bestThread->rootMoves[0].score;
|
||||||
for (Thread* th: Threads)
|
|
||||||
minScore = std::min(minScore, th->rootMoves[0].score);
|
|
||||||
|
|
||||||
// Vote according to score and depth, and select the best thread
|
|
||||||
for (Thread* th : Threads)
|
|
||||||
{
|
|
||||||
votes[th->rootMoves[0].pv[0]] +=
|
|
||||||
(th->rootMoves[0].score - minScore + 14) * int(th->completedDepth);
|
|
||||||
|
|
||||||
if (bestThread->rootMoves[0].score >= VALUE_MATE_IN_MAX_PLY)
|
|
||||||
{
|
|
||||||
// Make sure we pick the shortest mate
|
|
||||||
if (th->rootMoves[0].score > bestThread->rootMoves[0].score)
|
|
||||||
bestThread = th;
|
|
||||||
}
|
|
||||||
else if ( th->rootMoves[0].score >= VALUE_MATE_IN_MAX_PLY
|
|
||||||
|| votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]])
|
|
||||||
bestThread = th;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
previousScore = bestThread->rootMoves[0].score;
|
|
||||||
|
|
||||||
// Send again PV info if we have a new best thread
|
// Send again PV info if we have a new best thread
|
||||||
if (bestThread != this)
|
if (bestThread != this)
|
||||||
@@ -348,15 +315,18 @@ void Thread::search() {
|
|||||||
|
|
||||||
if (mainThread)
|
if (mainThread)
|
||||||
{
|
{
|
||||||
if (mainThread->previousScore == VALUE_INFINITE)
|
if (mainThread->bestPreviousScore == VALUE_INFINITE)
|
||||||
for (int i=0; i<4; ++i)
|
for (int i = 0; i < 4; ++i)
|
||||||
mainThread->iterValue[i] = VALUE_ZERO;
|
mainThread->iterValue[i] = VALUE_ZERO;
|
||||||
else
|
else
|
||||||
for (int i=0; i<4; ++i)
|
for (int i = 0; i < 4; ++i)
|
||||||
mainThread->iterValue[i] = mainThread->previousScore;
|
mainThread->iterValue[i] = mainThread->bestPreviousScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t multiPV = Options["MultiPV"];
|
std::copy(&lowPlyHistory[2][0], &lowPlyHistory.back().back() + 1, &lowPlyHistory[0][0]);
|
||||||
|
std::fill(&lowPlyHistory[MAX_LPH - 2][0], &lowPlyHistory.back().back() + 1, 0);
|
||||||
|
|
||||||
|
size_t multiPV = size_t(Options["MultiPV"]);
|
||||||
|
|
||||||
// Pick integer skill levels, but non-deterministically round up or down
|
// Pick integer skill levels, but non-deterministically round up or down
|
||||||
// such that the average integer skill corresponds to the input floating point one.
|
// such that the average integer skill corresponds to the input floating point one.
|
||||||
@@ -365,7 +335,7 @@ void Thread::search() {
|
|||||||
// for match (TC 60+0.6) results spanning a wide range of k values.
|
// for match (TC 60+0.6) results spanning a wide range of k values.
|
||||||
PRNG rng(now());
|
PRNG rng(now());
|
||||||
double floatLevel = Options["UCI_LimitStrength"] ?
|
double floatLevel = Options["UCI_LimitStrength"] ?
|
||||||
clamp(std::pow((Options["UCI_Elo"] - 1346.6) / 143.4, 1 / 0.806), 0.0, 20.0) :
|
Utility::clamp(std::pow((Options["UCI_Elo"] - 1346.6) / 143.4, 1 / 0.806), 0.0, 20.0) :
|
||||||
double(Options["Skill Level"]);
|
double(Options["Skill Level"]);
|
||||||
int intLevel = int(floatLevel) +
|
int intLevel = int(floatLevel) +
|
||||||
((floatLevel - int(floatLevel)) * 1024 > rng.rand<unsigned>() % 1024 ? 1 : 0);
|
((floatLevel - int(floatLevel)) * 1024 > rng.rand<unsigned>() % 1024 ? 1 : 0);
|
||||||
@@ -377,7 +347,7 @@ void Thread::search() {
|
|||||||
multiPV = std::max(multiPV, (size_t)4);
|
multiPV = std::max(multiPV, (size_t)4);
|
||||||
|
|
||||||
multiPV = std::min(multiPV, rootMoves.size());
|
multiPV = std::min(multiPV, rootMoves.size());
|
||||||
ttHitAverage = ttHitAverageWindow * ttHitAverageResolution / 2;
|
ttHitAverage = TtHitAverageWindow * TtHitAverageResolution / 2;
|
||||||
|
|
||||||
int ct = int(Options["Contempt"]) * PawnValueEg / 100; // From centipawns
|
int ct = int(Options["Contempt"]) * PawnValueEg / 100; // From centipawns
|
||||||
|
|
||||||
@@ -432,13 +402,13 @@ void Thread::search() {
|
|||||||
// Reset aspiration window starting size
|
// Reset aspiration window starting size
|
||||||
if (rootDepth >= 4)
|
if (rootDepth >= 4)
|
||||||
{
|
{
|
||||||
Value previousScore = rootMoves[pvIdx].previousScore;
|
Value prev = rootMoves[pvIdx].previousScore;
|
||||||
delta = Value(21 + abs(previousScore) / 256);
|
delta = Value(19);
|
||||||
alpha = std::max(previousScore - delta,-VALUE_INFINITE);
|
alpha = std::max(prev - delta,-VALUE_INFINITE);
|
||||||
beta = std::min(previousScore + delta, VALUE_INFINITE);
|
beta = std::min(prev + delta, VALUE_INFINITE);
|
||||||
|
|
||||||
// Adjust contempt based on root move's previousScore (dynamic contempt)
|
// Adjust contempt based on root move's previousScore (dynamic contempt)
|
||||||
int dct = ct + (102 - ct / 2) * previousScore / (abs(previousScore) + 157);
|
int dct = ct + (110 - ct / 2) * prev / (abs(prev) + 140);
|
||||||
|
|
||||||
contempt = (us == WHITE ? make_score(dct, dct / 2)
|
contempt = (us == WHITE ? make_score(dct, dct / 2)
|
||||||
: -make_score(dct, dct / 2));
|
: -make_score(dct, dct / 2));
|
||||||
@@ -536,13 +506,13 @@ void Thread::search() {
|
|||||||
&& !Threads.stop
|
&& !Threads.stop
|
||||||
&& !mainThread->stopOnPonderhit)
|
&& !mainThread->stopOnPonderhit)
|
||||||
{
|
{
|
||||||
double fallingEval = (332 + 6 * (mainThread->previousScore - bestValue)
|
double fallingEval = (296 + 6 * (mainThread->bestPreviousScore - bestValue)
|
||||||
+ 6 * (mainThread->iterValue[iterIdx] - bestValue)) / 704.0;
|
+ 6 * (mainThread->iterValue[iterIdx] - bestValue)) / 725.0;
|
||||||
fallingEval = clamp(fallingEval, 0.5, 1.5);
|
fallingEval = Utility::clamp(fallingEval, 0.5, 1.5);
|
||||||
|
|
||||||
// If the bestMove is stable over several iterations, reduce time accordingly
|
// If the bestMove is stable over several iterations, reduce time accordingly
|
||||||
timeReduction = lastBestMoveDepth + 9 < completedDepth ? 1.94 : 0.91;
|
timeReduction = lastBestMoveDepth + 10 < completedDepth ? 1.92 : 0.95;
|
||||||
double reduction = (1.41 + mainThread->previousTimeReduction) / (2.27 * timeReduction);
|
double reduction = (1.47 + mainThread->previousTimeReduction) / (2.22 * timeReduction);
|
||||||
|
|
||||||
// Use part of the gained time from a previous stable move for the current move
|
// Use part of the gained time from a previous stable move for the current move
|
||||||
for (Thread* th : Threads)
|
for (Thread* th : Threads)
|
||||||
@@ -552,9 +522,11 @@ void Thread::search() {
|
|||||||
}
|
}
|
||||||
double bestMoveInstability = 1 + totBestMoveChanges / Threads.size();
|
double bestMoveInstability = 1 + totBestMoveChanges / Threads.size();
|
||||||
|
|
||||||
// Stop the search if we have only one legal move, or if available time elapsed
|
double totalTime = rootMoves.size() == 1 ? 0 :
|
||||||
if ( rootMoves.size() == 1
|
Time.optimum() * fallingEval * reduction * bestMoveInstability;
|
||||||
|| Time.elapsed() > Time.optimum() * fallingEval * reduction * bestMoveInstability)
|
|
||||||
|
// Stop the search if we have exceeded the totalTime, at least 1ms search
|
||||||
|
if (Time.elapsed() > totalTime)
|
||||||
{
|
{
|
||||||
// If we are allowed to ponder do not stop the search now but
|
// If we are allowed to ponder do not stop the search now but
|
||||||
// keep pondering until the GUI sends "ponderhit" or "stop".
|
// keep pondering until the GUI sends "ponderhit" or "stop".
|
||||||
@@ -565,7 +537,7 @@ void Thread::search() {
|
|||||||
}
|
}
|
||||||
else if ( Threads.increaseDepth
|
else if ( Threads.increaseDepth
|
||||||
&& !mainThread->ponder
|
&& !mainThread->ponder
|
||||||
&& Time.elapsed() > Time.optimum() * fallingEval * reduction * bestMoveInstability * 0.6)
|
&& Time.elapsed() > totalTime * 0.56)
|
||||||
Threads.increaseDepth = false;
|
Threads.increaseDepth = false;
|
||||||
else
|
else
|
||||||
Threads.increaseDepth = true;
|
Threads.increaseDepth = true;
|
||||||
@@ -624,15 +596,16 @@ namespace {
|
|||||||
Key posKey;
|
Key posKey;
|
||||||
Move ttMove, move, excludedMove, bestMove;
|
Move ttMove, move, excludedMove, bestMove;
|
||||||
Depth extension, newDepth;
|
Depth extension, newDepth;
|
||||||
Value bestValue, value, ttValue, eval, maxValue;
|
Value bestValue, value, ttValue, eval, maxValue, probcutBeta;
|
||||||
bool ttHit, ttPv, inCheck, givesCheck, improving, didLMR, priorCapture;
|
bool ttHit, ttPv, formerPv, givesCheck, improving, didLMR, priorCapture;
|
||||||
bool captureOrPromotion, doFullDepthSearch, moveCountPruning, ttCapture, singularLMR;
|
bool captureOrPromotion, doFullDepthSearch, moveCountPruning,
|
||||||
|
ttCapture, singularQuietLMR;
|
||||||
Piece movedPiece;
|
Piece movedPiece;
|
||||||
int moveCount, captureCount, quietCount;
|
int moveCount, captureCount, quietCount;
|
||||||
|
|
||||||
// Step 1. Initialize node
|
// Step 1. Initialize node
|
||||||
Thread* thisThread = pos.this_thread();
|
Thread* thisThread = pos.this_thread();
|
||||||
inCheck = pos.checkers();
|
ss->inCheck = pos.checkers();
|
||||||
priorCapture = pos.captured_piece();
|
priorCapture = pos.captured_piece();
|
||||||
Color us = pos.side_to_move();
|
Color us = pos.side_to_move();
|
||||||
moveCount = captureCount = quietCount = ss->moveCount = 0;
|
moveCount = captureCount = quietCount = ss->moveCount = 0;
|
||||||
@@ -653,7 +626,7 @@ namespace {
|
|||||||
if ( Threads.stop.load(std::memory_order_relaxed)
|
if ( Threads.stop.load(std::memory_order_relaxed)
|
||||||
|| pos.is_draw(ss->ply)
|
|| pos.is_draw(ss->ply)
|
||||||
|| ss->ply >= MAX_PLY)
|
|| ss->ply >= MAX_PLY)
|
||||||
return (ss->ply >= MAX_PLY && !inCheck) ? evaluate(pos)
|
return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos)
|
||||||
: value_draw(pos.this_thread());
|
: value_draw(pos.this_thread());
|
||||||
|
|
||||||
// Step 3. Mate distance pruning. Even if we mate at the next move our score
|
// Step 3. Mate distance pruning. Even if we mate at the next move our score
|
||||||
@@ -689,15 +662,24 @@ namespace {
|
|||||||
// search to overwrite a previous full search TT value, so we use a different
|
// search to overwrite a previous full search TT value, so we use a different
|
||||||
// position key in case of an excluded move.
|
// position key in case of an excluded move.
|
||||||
excludedMove = ss->excludedMove;
|
excludedMove = ss->excludedMove;
|
||||||
posKey = pos.key() ^ Key(excludedMove << 16); // Isn't a very good hash
|
posKey = excludedMove == MOVE_NONE ? pos.key() : pos.key() ^ make_key(excludedMove);
|
||||||
tte = TT.probe(posKey, ttHit);
|
tte = TT.probe(posKey, ttHit);
|
||||||
ttValue = ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE;
|
ttValue = ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE;
|
||||||
ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0]
|
ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0]
|
||||||
: ttHit ? tte->move() : MOVE_NONE;
|
: ttHit ? tte->move() : MOVE_NONE;
|
||||||
ttPv = PvNode || (ttHit && tte->is_pv());
|
ttPv = PvNode || (ttHit && tte->is_pv());
|
||||||
|
formerPv = ttPv && !PvNode;
|
||||||
|
|
||||||
|
if ( ttPv
|
||||||
|
&& depth > 12
|
||||||
|
&& ss->ply - 1 < MAX_LPH
|
||||||
|
&& !priorCapture
|
||||||
|
&& is_ok((ss-1)->currentMove))
|
||||||
|
thisThread->lowPlyHistory[ss->ply - 1][from_to((ss-1)->currentMove)] << stat_bonus(depth - 5);
|
||||||
|
|
||||||
// thisThread->ttHitAverage can be used to approximate the running average of ttHit
|
// thisThread->ttHitAverage can be used to approximate the running average of ttHit
|
||||||
thisThread->ttHitAverage = (ttHitAverageWindow - 1) * thisThread->ttHitAverage / ttHitAverageWindow
|
thisThread->ttHitAverage = (TtHitAverageWindow - 1) * thisThread->ttHitAverage / TtHitAverageWindow
|
||||||
+ ttHitAverageResolution * ttHit;
|
+ TtHitAverageResolution * ttHit;
|
||||||
|
|
||||||
// At non-PV nodes we check for an early TT cutoff
|
// At non-PV nodes we check for an early TT cutoff
|
||||||
if ( !PvNode
|
if ( !PvNode
|
||||||
@@ -713,7 +695,7 @@ namespace {
|
|||||||
if (ttValue >= beta)
|
if (ttValue >= beta)
|
||||||
{
|
{
|
||||||
if (!pos.capture_or_promotion(ttMove))
|
if (!pos.capture_or_promotion(ttMove))
|
||||||
update_quiet_stats(pos, ss, ttMove, stat_bonus(depth));
|
update_quiet_stats(pos, ss, ttMove, stat_bonus(depth), depth);
|
||||||
|
|
||||||
// Extra penalty for early quiet moves of the previous ply
|
// Extra penalty for early quiet moves of the previous ply
|
||||||
if ((ss-1)->moveCount <= 2 && !priorCapture)
|
if ((ss-1)->moveCount <= 2 && !priorCapture)
|
||||||
@@ -755,8 +737,9 @@ namespace {
|
|||||||
|
|
||||||
int drawScore = TB::UseRule50 ? 1 : 0;
|
int drawScore = TB::UseRule50 ? 1 : 0;
|
||||||
|
|
||||||
value = wdl < -drawScore ? -VALUE_MATE + MAX_PLY + ss->ply + 1
|
// use the range VALUE_MATE_IN_MAX_PLY to VALUE_TB_WIN_IN_MAX_PLY to score
|
||||||
: wdl > drawScore ? VALUE_MATE - MAX_PLY - ss->ply - 1
|
value = wdl < -drawScore ? VALUE_MATED_IN_MAX_PLY + ss->ply + 1
|
||||||
|
: wdl > drawScore ? VALUE_MATE_IN_MAX_PLY - ss->ply - 1
|
||||||
: VALUE_DRAW + 2 * wdl * drawScore;
|
: VALUE_DRAW + 2 * wdl * drawScore;
|
||||||
|
|
||||||
Bound b = wdl < -drawScore ? BOUND_UPPER
|
Bound b = wdl < -drawScore ? BOUND_UPPER
|
||||||
@@ -783,12 +766,15 @@ namespace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CapturePieceToHistory& captureHistory = thisThread->captureHistory;
|
||||||
|
|
||||||
// Step 6. Static evaluation of the position
|
// Step 6. Static evaluation of the position
|
||||||
if (inCheck)
|
if (ss->inCheck)
|
||||||
{
|
{
|
||||||
|
// Skip early pruning when in check
|
||||||
ss->staticEval = eval = VALUE_NONE;
|
ss->staticEval = eval = VALUE_NONE;
|
||||||
improving = false;
|
improving = false;
|
||||||
goto moves_loop; // Skip early pruning when in check
|
goto moves_loop;
|
||||||
}
|
}
|
||||||
else if (ttHit)
|
else if (ttHit)
|
||||||
{
|
{
|
||||||
@@ -814,19 +800,19 @@ namespace {
|
|||||||
ss->staticEval = eval = evaluate(pos) + bonus;
|
ss->staticEval = eval = evaluate(pos) + bonus;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
ss->staticEval = eval = -(ss-1)->staticEval + 2 * Eval::Tempo;
|
ss->staticEval = eval = -(ss-1)->staticEval + 2 * Tempo;
|
||||||
|
|
||||||
tte->save(posKey, VALUE_NONE, ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval);
|
tte->save(posKey, VALUE_NONE, ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 7. Razoring (~1 Elo)
|
// Step 7. Razoring (~1 Elo)
|
||||||
if ( !rootNode // The required rootNode PV handling is not available in qsearch
|
if ( !rootNode // The required rootNode PV handling is not available in qsearch
|
||||||
&& depth < 2
|
&& depth == 1
|
||||||
&& eval <= alpha - RazorMargin)
|
&& eval <= alpha - RazorMargin)
|
||||||
return qsearch<NT>(pos, ss, alpha, beta);
|
return qsearch<NT>(pos, ss, alpha, beta);
|
||||||
|
|
||||||
improving = (ss-2)->staticEval == VALUE_NONE ? (ss->staticEval >= (ss-4)->staticEval
|
improving = (ss-2)->staticEval == VALUE_NONE ? (ss->staticEval > (ss-4)->staticEval
|
||||||
|| (ss-4)->staticEval == VALUE_NONE) : ss->staticEval >= (ss-2)->staticEval;
|
|| (ss-4)->staticEval == VALUE_NONE) : ss->staticEval > (ss-2)->staticEval;
|
||||||
|
|
||||||
// Step 8. Futility pruning: child node (~50 Elo)
|
// Step 8. Futility pruning: child node (~50 Elo)
|
||||||
if ( !PvNode
|
if ( !PvNode
|
||||||
@@ -838,10 +824,10 @@ namespace {
|
|||||||
// Step 9. Null move search with verification search (~40 Elo)
|
// Step 9. Null move search with verification search (~40 Elo)
|
||||||
if ( !PvNode
|
if ( !PvNode
|
||||||
&& (ss-1)->currentMove != MOVE_NULL
|
&& (ss-1)->currentMove != MOVE_NULL
|
||||||
&& (ss-1)->statScore < 23397
|
&& (ss-1)->statScore < 23824
|
||||||
&& eval >= beta
|
&& eval >= beta
|
||||||
&& eval >= ss->staticEval
|
&& eval >= ss->staticEval
|
||||||
&& ss->staticEval >= beta - 32 * depth + 292 - improving * 30
|
&& ss->staticEval >= beta - 33 * depth - 33 * improving + 112 * ttPv + 311
|
||||||
&& !excludedMove
|
&& !excludedMove
|
||||||
&& pos.non_pawn_material(us)
|
&& pos.non_pawn_material(us)
|
||||||
&& (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor))
|
&& (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor))
|
||||||
@@ -849,7 +835,7 @@ namespace {
|
|||||||
assert(eval - beta >= 0);
|
assert(eval - beta >= 0);
|
||||||
|
|
||||||
// Null move dynamic reduction based on depth and value
|
// Null move dynamic reduction based on depth and value
|
||||||
Depth R = (854 + 68 * depth) / 258 + std::min(int(eval - beta) / 192, 3);
|
Depth R = (737 + 77 * depth) / 246 + std::min(int(eval - beta) / 192, 3);
|
||||||
|
|
||||||
ss->currentMove = MOVE_NULL;
|
ss->currentMove = MOVE_NULL;
|
||||||
ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0];
|
ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0];
|
||||||
@@ -862,8 +848,8 @@ namespace {
|
|||||||
|
|
||||||
if (nullValue >= beta)
|
if (nullValue >= beta)
|
||||||
{
|
{
|
||||||
// Do not return unproven mate scores
|
// Do not return unproven mate or TB scores
|
||||||
if (nullValue >= VALUE_MATE_IN_MAX_PLY)
|
if (nullValue >= VALUE_TB_WIN_IN_MAX_PLY)
|
||||||
nullValue = beta;
|
nullValue = beta;
|
||||||
|
|
||||||
if (thisThread->nmpMinPly || (abs(beta) < VALUE_KNOWN_WIN && depth < 13))
|
if (thisThread->nmpMinPly || (abs(beta) < VALUE_KNOWN_WIN && depth < 13))
|
||||||
@@ -885,15 +871,29 @@ namespace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
probcutBeta = beta + 176 - 49 * improving;
|
||||||
|
|
||||||
// Step 10. ProbCut (~10 Elo)
|
// Step 10. ProbCut (~10 Elo)
|
||||||
// If we have a good enough capture and a reduced search returns a value
|
// If we have a good enough capture and a reduced search returns a value
|
||||||
// much above beta, we can (almost) safely prune the previous move.
|
// much above beta, we can (almost) safely prune the previous move.
|
||||||
if ( !PvNode
|
if ( !PvNode
|
||||||
&& depth >= 5
|
&& depth > 4
|
||||||
&& abs(beta) < VALUE_MATE_IN_MAX_PLY)
|
&& abs(beta) < VALUE_TB_WIN_IN_MAX_PLY
|
||||||
|
&& !( ttHit
|
||||||
|
&& tte->depth() >= depth - 3
|
||||||
|
&& ttValue != VALUE_NONE
|
||||||
|
&& ttValue < probcutBeta))
|
||||||
{
|
{
|
||||||
Value raisedBeta = std::min(beta + 189 - 45 * improving, VALUE_INFINITE);
|
if ( ttHit
|
||||||
MovePicker mp(pos, ttMove, raisedBeta - ss->staticEval, &thisThread->captureHistory);
|
&& tte->depth() >= depth - 3
|
||||||
|
&& ttValue != VALUE_NONE
|
||||||
|
&& ttValue >= probcutBeta
|
||||||
|
&& ttMove
|
||||||
|
&& pos.capture_or_promotion(ttMove))
|
||||||
|
return probcutBeta;
|
||||||
|
|
||||||
|
assert(probcutBeta < VALUE_INFINITE);
|
||||||
|
MovePicker mp(pos, ttMove, probcutBeta - ss->staticEval, &captureHistory);
|
||||||
int probCutCount = 0;
|
int probCutCount = 0;
|
||||||
|
|
||||||
while ( (move = mp.next_move()) != MOVE_NONE
|
while ( (move = mp.next_move()) != MOVE_NONE
|
||||||
@@ -907,7 +907,7 @@ namespace {
|
|||||||
probCutCount++;
|
probCutCount++;
|
||||||
|
|
||||||
ss->currentMove = move;
|
ss->currentMove = move;
|
||||||
ss->continuationHistory = &thisThread->continuationHistory[inCheck]
|
ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck]
|
||||||
[captureOrPromotion]
|
[captureOrPromotion]
|
||||||
[pos.moved_piece(move)]
|
[pos.moved_piece(move)]
|
||||||
[to_sq(move)];
|
[to_sq(move)];
|
||||||
@@ -915,18 +915,26 @@ namespace {
|
|||||||
pos.do_move(move, st);
|
pos.do_move(move, st);
|
||||||
|
|
||||||
// Perform a preliminary qsearch to verify that the move holds
|
// Perform a preliminary qsearch to verify that the move holds
|
||||||
value = -qsearch<NonPV>(pos, ss+1, -raisedBeta, -raisedBeta+1);
|
value = -qsearch<NonPV>(pos, ss+1, -probcutBeta, -probcutBeta+1);
|
||||||
|
|
||||||
// If the qsearch held, perform the regular search
|
// If the qsearch held, perform the regular search
|
||||||
if (value >= raisedBeta)
|
if (value >= probcutBeta)
|
||||||
value = -search<NonPV>(pos, ss+1, -raisedBeta, -raisedBeta+1, depth - 4, !cutNode);
|
value = -search<NonPV>(pos, ss+1, -probcutBeta, -probcutBeta+1, depth - 4, !cutNode);
|
||||||
|
|
||||||
pos.undo_move(move);
|
pos.undo_move(move);
|
||||||
|
|
||||||
if (value >= raisedBeta)
|
if (value >= probcutBeta)
|
||||||
|
{
|
||||||
|
if ( !(ttHit
|
||||||
|
&& tte->depth() >= depth - 3
|
||||||
|
&& ttValue != VALUE_NONE))
|
||||||
|
tte->save(posKey, value_to_tt(value, ss->ply), ttPv,
|
||||||
|
BOUND_LOWER,
|
||||||
|
depth - 3, move, ss->staticEval);
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Step 11. Internal iterative deepening (~1 Elo)
|
// Step 11. Internal iterative deepening (~1 Elo)
|
||||||
if (depth >= 7 && !ttMove)
|
if (depth >= 7 && !ttMove)
|
||||||
@@ -947,13 +955,15 @@ moves_loop: // When in check, search starts from here
|
|||||||
Move countermove = thisThread->counterMoves[pos.piece_on(prevSq)][prevSq];
|
Move countermove = thisThread->counterMoves[pos.piece_on(prevSq)][prevSq];
|
||||||
|
|
||||||
MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory,
|
MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory,
|
||||||
&thisThread->captureHistory,
|
&thisThread->lowPlyHistory,
|
||||||
|
&captureHistory,
|
||||||
contHist,
|
contHist,
|
||||||
countermove,
|
countermove,
|
||||||
ss->killers);
|
ss->killers,
|
||||||
|
ss->ply);
|
||||||
|
|
||||||
value = bestValue;
|
value = bestValue;
|
||||||
singularLMR = moveCountPruning = false;
|
singularQuietLMR = moveCountPruning = false;
|
||||||
ttCapture = ttMove && pos.capture_or_promotion(ttMove);
|
ttCapture = ttMove && pos.capture_or_promotion(ttMove);
|
||||||
|
|
||||||
// Mark this node as being searched
|
// Mark this node as being searched
|
||||||
@@ -996,17 +1006,17 @@ moves_loop: // When in check, search starts from here
|
|||||||
// Step 13. Pruning at shallow depth (~200 Elo)
|
// Step 13. Pruning at shallow depth (~200 Elo)
|
||||||
if ( !rootNode
|
if ( !rootNode
|
||||||
&& pos.non_pawn_material(us)
|
&& pos.non_pawn_material(us)
|
||||||
&& bestValue > VALUE_MATED_IN_MAX_PLY)
|
&& bestValue > VALUE_TB_LOSS_IN_MAX_PLY)
|
||||||
{
|
{
|
||||||
// Skip quiet moves if movecount exceeds our FutilityMoveCount threshold
|
// Skip quiet moves if movecount exceeds our FutilityMoveCount threshold
|
||||||
moveCountPruning = moveCount >= futility_move_count(improving, depth);
|
moveCountPruning = moveCount >= futility_move_count(improving, depth);
|
||||||
|
|
||||||
if ( !captureOrPromotion
|
|
||||||
&& !givesCheck)
|
|
||||||
{
|
|
||||||
// Reduced depth of the next LMR search
|
// Reduced depth of the next LMR search
|
||||||
int lmrDepth = std::max(newDepth - reduction(improving, depth, moveCount), 0);
|
int lmrDepth = std::max(newDepth - reduction(improving, depth, moveCount), 0);
|
||||||
|
|
||||||
|
if ( !captureOrPromotion
|
||||||
|
&& !givesCheck)
|
||||||
|
{
|
||||||
// Countermoves based pruning (~20 Elo)
|
// Countermoves based pruning (~20 Elo)
|
||||||
if ( lmrDepth < 4 + ((ss-1)->statScore > 0 || (ss-1)->moveCount == 1)
|
if ( lmrDepth < 4 + ((ss-1)->statScore > 0 || (ss-1)->moveCount == 1)
|
||||||
&& (*contHist[0])[movedPiece][to_sq(move)] < CounterMovePruneThreshold
|
&& (*contHist[0])[movedPiece][to_sq(move)] < CounterMovePruneThreshold
|
||||||
@@ -1015,20 +1025,40 @@ moves_loop: // When in check, search starts from here
|
|||||||
|
|
||||||
// Futility pruning: parent node (~5 Elo)
|
// Futility pruning: parent node (~5 Elo)
|
||||||
if ( lmrDepth < 6
|
if ( lmrDepth < 6
|
||||||
&& !inCheck
|
&& !ss->inCheck
|
||||||
&& ss->staticEval + 235 + 172 * lmrDepth <= alpha
|
&& ss->staticEval + 284 + 188 * lmrDepth <= alpha
|
||||||
&& thisThread->mainHistory[us][from_to(move)]
|
&& (*contHist[0])[movedPiece][to_sq(move)]
|
||||||
+ (*contHist[0])[movedPiece][to_sq(move)]
|
|
||||||
+ (*contHist[1])[movedPiece][to_sq(move)]
|
+ (*contHist[1])[movedPiece][to_sq(move)]
|
||||||
+ (*contHist[3])[movedPiece][to_sq(move)] < 25000)
|
+ (*contHist[3])[movedPiece][to_sq(move)]
|
||||||
|
+ (*contHist[5])[movedPiece][to_sq(move)] / 2 < 28388)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Prune moves with negative SEE (~20 Elo)
|
// Prune moves with negative SEE (~20 Elo)
|
||||||
if (!pos.see_ge(move, Value(-(32 - std::min(lmrDepth, 18)) * lmrDepth * lmrDepth)))
|
if (!pos.see_ge(move, Value(-(29 - std::min(lmrDepth, 17)) * lmrDepth * lmrDepth)))
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else if (!pos.see_ge(move, Value(-194) * depth)) // (~25 Elo)
|
else
|
||||||
|
{
|
||||||
|
// Capture history based pruning when the move doesn't give check
|
||||||
|
if ( !givesCheck
|
||||||
|
&& lmrDepth < 1
|
||||||
|
&& captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] < 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
// Futility pruning for captures
|
||||||
|
if ( !givesCheck
|
||||||
|
&& lmrDepth < 6
|
||||||
|
&& !(PvNode && abs(bestValue) < 2)
|
||||||
|
&& PieceValue[MG][type_of(movedPiece)] >= PieceValue[MG][type_of(pos.piece_on(to_sq(move)))]
|
||||||
|
&& !ss->inCheck
|
||||||
|
&& ss->staticEval + 267 + 391 * lmrDepth
|
||||||
|
+ PieceValue[MG][type_of(pos.piece_on(to_sq(move)))] <= alpha)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// See based pruning
|
||||||
|
if (!pos.see_ge(move, Value(-202) * depth)) // (~25 Elo)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 14. Extensions (~75 Elo)
|
// Step 14. Extensions (~75 Elo)
|
||||||
@@ -1037,7 +1067,7 @@ moves_loop: // When in check, search starts from here
|
|||||||
// search of (alpha-s, beta-s), and just one fails high on (alpha, beta),
|
// search of (alpha-s, beta-s), and just one fails high on (alpha, beta),
|
||||||
// then that move is singular and should be extended. To verify this we do
|
// 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
|
// 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.
|
// result is lower than ttValue minus a margin, then we will extend the ttMove.
|
||||||
if ( depth >= 6
|
if ( depth >= 6
|
||||||
&& move == ttMove
|
&& move == ttMove
|
||||||
&& !rootNode
|
&& !rootNode
|
||||||
@@ -1048,16 +1078,16 @@ moves_loop: // When in check, search starts from here
|
|||||||
&& tte->depth() >= depth - 3
|
&& tte->depth() >= depth - 3
|
||||||
&& pos.legal(move))
|
&& pos.legal(move))
|
||||||
{
|
{
|
||||||
Value singularBeta = ttValue - 2 * depth;
|
Value singularBeta = ttValue - ((formerPv + 4) * depth) / 2;
|
||||||
Depth halfDepth = depth / 2;
|
Depth singularDepth = (depth - 1 + 3 * formerPv) / 2;
|
||||||
ss->excludedMove = move;
|
ss->excludedMove = move;
|
||||||
value = search<NonPV>(pos, ss, singularBeta - 1, singularBeta, halfDepth, cutNode);
|
value = search<NonPV>(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode);
|
||||||
ss->excludedMove = MOVE_NONE;
|
ss->excludedMove = MOVE_NONE;
|
||||||
|
|
||||||
if (value < singularBeta)
|
if (value < singularBeta)
|
||||||
{
|
{
|
||||||
extension = 1;
|
extension = 1;
|
||||||
singularLMR = true;
|
singularQuietLMR = !ttCapture;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multi-cut pruning
|
// Multi-cut pruning
|
||||||
@@ -1067,6 +1097,18 @@ moves_loop: // When in check, search starts from here
|
|||||||
// a soft bound.
|
// a soft bound.
|
||||||
else if (singularBeta >= beta)
|
else if (singularBeta >= beta)
|
||||||
return singularBeta;
|
return singularBeta;
|
||||||
|
|
||||||
|
// If the eval of ttMove is greater than beta we try also if there is another
|
||||||
|
// move that pushes it over beta, if so also produce a cutoff.
|
||||||
|
else if (ttValue >= beta)
|
||||||
|
{
|
||||||
|
ss->excludedMove = move;
|
||||||
|
value = search<NonPV>(pos, ss, beta - 1, beta, (depth + 3) / 2, cutNode);
|
||||||
|
ss->excludedMove = MOVE_NONE;
|
||||||
|
|
||||||
|
if (value >= beta)
|
||||||
|
return beta;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check extension (~2 Elo)
|
// Check extension (~2 Elo)
|
||||||
@@ -1104,7 +1146,7 @@ moves_loop: // When in check, search starts from here
|
|||||||
|
|
||||||
// Update the current move (this must be done after singular extension search)
|
// Update the current move (this must be done after singular extension search)
|
||||||
ss->currentMove = move;
|
ss->currentMove = move;
|
||||||
ss->continuationHistory = &thisThread->continuationHistory[inCheck]
|
ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck]
|
||||||
[captureOrPromotion]
|
[captureOrPromotion]
|
||||||
[movedPiece]
|
[movedPiece]
|
||||||
[to_sq(move)];
|
[to_sq(move)];
|
||||||
@@ -1115,21 +1157,28 @@ moves_loop: // When in check, search starts from here
|
|||||||
// Step 16. Reduced depth search (LMR, ~200 Elo). If the move fails high it will be
|
// Step 16. Reduced depth search (LMR, ~200 Elo). If the move fails high it will be
|
||||||
// re-searched at full depth.
|
// re-searched at full depth.
|
||||||
if ( depth >= 3
|
if ( depth >= 3
|
||||||
&& moveCount > 1 + rootNode + (rootNode && bestValue < alpha)
|
&& moveCount > 1 + 2 * rootNode
|
||||||
&& (!rootNode || thisThread->best_move_count(move) == 0)
|
&& (!rootNode || thisThread->best_move_count(move) == 0)
|
||||||
&& ( !captureOrPromotion
|
&& ( !captureOrPromotion
|
||||||
|| moveCountPruning
|
|| moveCountPruning
|
||||||
|| ss->staticEval + PieceValue[EG][pos.captured_piece()] <= alpha
|
|| ss->staticEval + PieceValue[EG][pos.captured_piece()] <= alpha
|
||||||
|| cutNode
|
|| cutNode
|
||||||
|| thisThread->ttHitAverage < 375 * ttHitAverageResolution * ttHitAverageWindow / 1024))
|
|| thisThread->ttHitAverage < 415 * TtHitAverageResolution * TtHitAverageWindow / 1024))
|
||||||
{
|
{
|
||||||
Depth r = reduction(improving, depth, moveCount);
|
Depth r = reduction(improving, depth, moveCount);
|
||||||
|
|
||||||
// Decrease reduction if the ttHit running average is large
|
// Decrease reduction at non-check cut nodes for second move at low depths
|
||||||
if (thisThread->ttHitAverage > 500 * ttHitAverageResolution * ttHitAverageWindow / 1024)
|
if ( cutNode
|
||||||
|
&& depth <= 10
|
||||||
|
&& moveCount <= 2
|
||||||
|
&& !ss->inCheck)
|
||||||
r--;
|
r--;
|
||||||
|
|
||||||
// Reduction if other threads are searching this position.
|
// Decrease reduction if the ttHit running average is large
|
||||||
|
if (thisThread->ttHitAverage > 473 * TtHitAverageResolution * TtHitAverageWindow / 1024)
|
||||||
|
r--;
|
||||||
|
|
||||||
|
// Reduction if other threads are searching this position
|
||||||
if (th.marked())
|
if (th.marked())
|
||||||
r++;
|
r++;
|
||||||
|
|
||||||
@@ -1137,13 +1186,16 @@ moves_loop: // When in check, search starts from here
|
|||||||
if (ttPv)
|
if (ttPv)
|
||||||
r -= 2;
|
r -= 2;
|
||||||
|
|
||||||
|
if (moveCountPruning && !formerPv)
|
||||||
|
r++;
|
||||||
|
|
||||||
// Decrease reduction if opponent's move count is high (~5 Elo)
|
// Decrease reduction if opponent's move count is high (~5 Elo)
|
||||||
if ((ss-1)->moveCount > 14)
|
if ((ss-1)->moveCount > 13)
|
||||||
r--;
|
r--;
|
||||||
|
|
||||||
// Decrease reduction if ttMove has been singularly extended (~3 Elo)
|
// Decrease reduction if ttMove has been singularly extended (~3 Elo)
|
||||||
if (singularLMR)
|
if (singularQuietLMR)
|
||||||
r -= 2;
|
r -= 1 + formerPv;
|
||||||
|
|
||||||
if (!captureOrPromotion)
|
if (!captureOrPromotion)
|
||||||
{
|
{
|
||||||
@@ -1160,44 +1212,50 @@ moves_loop: // When in check, search starts from here
|
|||||||
// hence break make_move(). (~2 Elo)
|
// hence break make_move(). (~2 Elo)
|
||||||
else if ( type_of(move) == NORMAL
|
else if ( type_of(move) == NORMAL
|
||||||
&& !pos.see_ge(reverse_move(move)))
|
&& !pos.see_ge(reverse_move(move)))
|
||||||
r -= 2;
|
r -= 2 + ttPv - (type_of(movedPiece) == PAWN);
|
||||||
|
|
||||||
ss->statScore = thisThread->mainHistory[us][from_to(move)]
|
ss->statScore = thisThread->mainHistory[us][from_to(move)]
|
||||||
+ (*contHist[0])[movedPiece][to_sq(move)]
|
+ (*contHist[0])[movedPiece][to_sq(move)]
|
||||||
+ (*contHist[1])[movedPiece][to_sq(move)]
|
+ (*contHist[1])[movedPiece][to_sq(move)]
|
||||||
+ (*contHist[3])[movedPiece][to_sq(move)]
|
+ (*contHist[3])[movedPiece][to_sq(move)]
|
||||||
- 4926;
|
- 4826;
|
||||||
|
|
||||||
// Reset statScore to zero if negative and most stats shows >= 0
|
|
||||||
if ( ss->statScore < 0
|
|
||||||
&& (*contHist[0])[movedPiece][to_sq(move)] >= 0
|
|
||||||
&& (*contHist[1])[movedPiece][to_sq(move)] >= 0
|
|
||||||
&& thisThread->mainHistory[us][from_to(move)] >= 0)
|
|
||||||
ss->statScore = 0;
|
|
||||||
|
|
||||||
// Decrease/increase reduction by comparing opponent's stat score (~10 Elo)
|
// Decrease/increase reduction by comparing opponent's stat score (~10 Elo)
|
||||||
if (ss->statScore >= -102 && (ss-1)->statScore < -114)
|
if (ss->statScore >= -100 && (ss-1)->statScore < -112)
|
||||||
r--;
|
r--;
|
||||||
|
|
||||||
else if ((ss-1)->statScore >= -116 && ss->statScore < -154)
|
else if ((ss-1)->statScore >= -125 && ss->statScore < -138)
|
||||||
r++;
|
r++;
|
||||||
|
|
||||||
// Decrease/increase reduction for moves with a good/bad history (~30 Elo)
|
// Decrease/increase reduction for moves with a good/bad history (~30 Elo)
|
||||||
r -= ss->statScore / 16384;
|
r -= ss->statScore / 14615;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
// Increase reduction for captures/promotions if late move and at low depth
|
// Increase reduction for captures/promotions if late move and at low depth
|
||||||
else if (depth < 8 && moveCount > 2)
|
if (depth < 8 && moveCount > 2)
|
||||||
r++;
|
r++;
|
||||||
|
|
||||||
Depth d = clamp(newDepth - r, 1, newDepth);
|
// Unless giving check, this capture is likely bad
|
||||||
|
if ( !givesCheck
|
||||||
|
&& ss->staticEval + PieceValue[EG][pos.captured_piece()] + 211 * depth <= alpha)
|
||||||
|
r++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Depth d = Utility::clamp(newDepth - r, 1, newDepth);
|
||||||
|
|
||||||
value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, d, true);
|
value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, d, true);
|
||||||
|
|
||||||
doFullDepthSearch = (value > alpha && d != newDepth), didLMR = true;
|
doFullDepthSearch = value > alpha && d != newDepth;
|
||||||
|
|
||||||
|
didLMR = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
doFullDepthSearch = !PvNode || moveCount > 1, didLMR = false;
|
{
|
||||||
|
doFullDepthSearch = !PvNode || moveCount > 1;
|
||||||
|
|
||||||
|
didLMR = false;
|
||||||
|
}
|
||||||
|
|
||||||
// Step 17. Full depth search when LMR is skipped or fails high
|
// Step 17. Full depth search when LMR is skipped or fails high
|
||||||
if (doFullDepthSearch)
|
if (doFullDepthSearch)
|
||||||
@@ -1257,7 +1315,7 @@ moves_loop: // When in check, search starts from here
|
|||||||
rm.pv.push_back(*m);
|
rm.pv.push_back(*m);
|
||||||
|
|
||||||
// We record how often the best move has been changed in each
|
// We record how often the best move has been changed in each
|
||||||
// iteration. This information is used for time management: When
|
// iteration. This information is used for time management: when
|
||||||
// the best move changes frequently, we allocate some more time.
|
// the best move changes frequently, we allocate some more time.
|
||||||
if (moveCount > 1)
|
if (moveCount > 1)
|
||||||
++thisThread->bestMoveChanges;
|
++thisThread->bestMoveChanges;
|
||||||
@@ -1314,11 +1372,11 @@ moves_loop: // When in check, search starts from here
|
|||||||
// must be a mate or a stalemate. If we are in a singular extension search then
|
// must be a mate or a stalemate. If we are in a singular extension search then
|
||||||
// return a fail low score.
|
// return a fail low score.
|
||||||
|
|
||||||
assert(moveCount || !inCheck || excludedMove || !MoveList<LEGAL>(pos).size());
|
assert(moveCount || !ss->inCheck || excludedMove || !MoveList<LEGAL>(pos).size());
|
||||||
|
|
||||||
if (!moveCount)
|
if (!moveCount)
|
||||||
bestValue = excludedMove ? alpha
|
bestValue = excludedMove ? alpha
|
||||||
: inCheck ? mated_in(ss->ply) : VALUE_DRAW;
|
: ss->inCheck ? mated_in(ss->ply) : VALUE_DRAW;
|
||||||
|
|
||||||
else if (bestMove)
|
else if (bestMove)
|
||||||
update_all_stats(pos, ss, bestMove, bestValue, beta, prevSq,
|
update_all_stats(pos, ss, bestMove, bestValue, beta, prevSq,
|
||||||
@@ -1332,7 +1390,7 @@ moves_loop: // When in check, search starts from here
|
|||||||
if (PvNode)
|
if (PvNode)
|
||||||
bestValue = std::min(bestValue, maxValue);
|
bestValue = std::min(bestValue, maxValue);
|
||||||
|
|
||||||
if (!excludedMove)
|
if (!excludedMove && !(rootNode && thisThread->pvIdx))
|
||||||
tte->save(posKey, value_to_tt(bestValue, ss->ply), ttPv,
|
tte->save(posKey, value_to_tt(bestValue, ss->ply), ttPv,
|
||||||
bestValue >= beta ? BOUND_LOWER :
|
bestValue >= beta ? BOUND_LOWER :
|
||||||
PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER,
|
PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER,
|
||||||
@@ -1362,7 +1420,7 @@ moves_loop: // When in check, search starts from here
|
|||||||
Move ttMove, move, bestMove;
|
Move ttMove, move, bestMove;
|
||||||
Depth ttDepth;
|
Depth ttDepth;
|
||||||
Value bestValue, value, ttValue, futilityValue, futilityBase, oldAlpha;
|
Value bestValue, value, ttValue, futilityValue, futilityBase, oldAlpha;
|
||||||
bool ttHit, pvHit, inCheck, givesCheck, captureOrPromotion, evasionPrunable;
|
bool ttHit, pvHit, givesCheck, captureOrPromotion;
|
||||||
int moveCount;
|
int moveCount;
|
||||||
|
|
||||||
if (PvNode)
|
if (PvNode)
|
||||||
@@ -1375,20 +1433,20 @@ moves_loop: // When in check, search starts from here
|
|||||||
Thread* thisThread = pos.this_thread();
|
Thread* thisThread = pos.this_thread();
|
||||||
(ss+1)->ply = ss->ply + 1;
|
(ss+1)->ply = ss->ply + 1;
|
||||||
bestMove = MOVE_NONE;
|
bestMove = MOVE_NONE;
|
||||||
inCheck = pos.checkers();
|
ss->inCheck = pos.checkers();
|
||||||
moveCount = 0;
|
moveCount = 0;
|
||||||
|
|
||||||
// Check for an immediate draw or maximum ply reached
|
// Check for an immediate draw or maximum ply reached
|
||||||
if ( pos.is_draw(ss->ply)
|
if ( pos.is_draw(ss->ply)
|
||||||
|| ss->ply >= MAX_PLY)
|
|| ss->ply >= MAX_PLY)
|
||||||
return (ss->ply >= MAX_PLY && !inCheck) ? evaluate(pos) : VALUE_DRAW;
|
return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) : VALUE_DRAW;
|
||||||
|
|
||||||
assert(0 <= ss->ply && ss->ply < MAX_PLY);
|
assert(0 <= ss->ply && ss->ply < MAX_PLY);
|
||||||
|
|
||||||
// Decide whether or not to include checks: this fixes also the type of
|
// Decide whether or not to include checks: this fixes also the type of
|
||||||
// TT entry depth that we are going to use. Note that in qsearch we use
|
// TT entry depth that we are going to use. Note that in qsearch we use
|
||||||
// only two types of depth in TT: DEPTH_QS_CHECKS or DEPTH_QS_NO_CHECKS.
|
// only two types of depth in TT: DEPTH_QS_CHECKS or DEPTH_QS_NO_CHECKS.
|
||||||
ttDepth = inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS
|
ttDepth = ss->inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS
|
||||||
: DEPTH_QS_NO_CHECKS;
|
: DEPTH_QS_NO_CHECKS;
|
||||||
// Transposition table lookup
|
// Transposition table lookup
|
||||||
posKey = pos.key();
|
posKey = pos.key();
|
||||||
@@ -1406,7 +1464,7 @@ moves_loop: // When in check, search starts from here
|
|||||||
return ttValue;
|
return ttValue;
|
||||||
|
|
||||||
// Evaluate the position statically
|
// Evaluate the position statically
|
||||||
if (inCheck)
|
if (ss->inCheck)
|
||||||
{
|
{
|
||||||
ss->staticEval = VALUE_NONE;
|
ss->staticEval = VALUE_NONE;
|
||||||
bestValue = futilityBase = -VALUE_INFINITE;
|
bestValue = futilityBase = -VALUE_INFINITE;
|
||||||
@@ -1427,13 +1485,13 @@ moves_loop: // When in check, search starts from here
|
|||||||
else
|
else
|
||||||
ss->staticEval = bestValue =
|
ss->staticEval = bestValue =
|
||||||
(ss-1)->currentMove != MOVE_NULL ? evaluate(pos)
|
(ss-1)->currentMove != MOVE_NULL ? evaluate(pos)
|
||||||
: -(ss-1)->staticEval + 2 * Eval::Tempo;
|
: -(ss-1)->staticEval + 2 * Tempo;
|
||||||
|
|
||||||
// Stand pat. Return immediately if static value is at least beta
|
// Stand pat. Return immediately if static value is at least beta
|
||||||
if (bestValue >= beta)
|
if (bestValue >= beta)
|
||||||
{
|
{
|
||||||
if (!ttHit)
|
if (!ttHit)
|
||||||
tte->save(posKey, value_to_tt(bestValue, ss->ply), pvHit, BOUND_LOWER,
|
tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER,
|
||||||
DEPTH_NONE, MOVE_NONE, ss->staticEval);
|
DEPTH_NONE, MOVE_NONE, ss->staticEval);
|
||||||
|
|
||||||
return bestValue;
|
return bestValue;
|
||||||
@@ -1442,7 +1500,7 @@ moves_loop: // When in check, search starts from here
|
|||||||
if (PvNode && bestValue > alpha)
|
if (PvNode && bestValue > alpha)
|
||||||
alpha = bestValue;
|
alpha = bestValue;
|
||||||
|
|
||||||
futilityBase = bestValue + 154;
|
futilityBase = bestValue + 141;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory,
|
const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory,
|
||||||
@@ -1451,8 +1509,8 @@ moves_loop: // When in check, search starts from here
|
|||||||
|
|
||||||
// Initialize a MovePicker object for the current position, and prepare
|
// Initialize a MovePicker object for the current position, and prepare
|
||||||
// to search the moves. Because the depth is <= 0 here, only captures,
|
// to search the moves. Because the depth is <= 0 here, only captures,
|
||||||
// queen promotions and checks (only if depth >= DEPTH_QS_CHECKS) will
|
// queen and checking knight promotions, and other checks(only if depth >= DEPTH_QS_CHECKS)
|
||||||
// be generated.
|
// will be generated.
|
||||||
MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory,
|
MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory,
|
||||||
&thisThread->captureHistory,
|
&thisThread->captureHistory,
|
||||||
contHist,
|
contHist,
|
||||||
@@ -1469,7 +1527,7 @@ moves_loop: // When in check, search starts from here
|
|||||||
moveCount++;
|
moveCount++;
|
||||||
|
|
||||||
// Futility pruning
|
// Futility pruning
|
||||||
if ( !inCheck
|
if ( !ss->inCheck
|
||||||
&& !givesCheck
|
&& !givesCheck
|
||||||
&& futilityBase > -VALUE_KNOWN_WIN
|
&& futilityBase > -VALUE_KNOWN_WIN
|
||||||
&& !pos.advanced_pawn_push(move))
|
&& !pos.advanced_pawn_push(move))
|
||||||
@@ -1491,14 +1549,8 @@ moves_loop: // When in check, search starts from here
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect non-capture evasions that are candidates to be pruned
|
// Do not search moves with negative SEE values
|
||||||
evasionPrunable = inCheck
|
if ( !ss->inCheck && !pos.see_ge(move))
|
||||||
&& (depth != 0 || moveCount > 2)
|
|
||||||
&& bestValue > VALUE_MATED_IN_MAX_PLY
|
|
||||||
&& !pos.capture(move);
|
|
||||||
|
|
||||||
// Don't search moves with negative SEE values
|
|
||||||
if ( (!inCheck || evasionPrunable) && !pos.see_ge(move))
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Speculative prefetch as early as possible
|
// Speculative prefetch as early as possible
|
||||||
@@ -1512,7 +1564,7 @@ moves_loop: // When in check, search starts from here
|
|||||||
}
|
}
|
||||||
|
|
||||||
ss->currentMove = move;
|
ss->currentMove = move;
|
||||||
ss->continuationHistory = &thisThread->continuationHistory[inCheck]
|
ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck]
|
||||||
[captureOrPromotion]
|
[captureOrPromotion]
|
||||||
[pos.moved_piece(move)]
|
[pos.moved_piece(move)]
|
||||||
[to_sq(move)];
|
[to_sq(move)];
|
||||||
@@ -1544,9 +1596,9 @@ moves_loop: // When in check, search starts from here
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// All legal moves have been searched. A special case: If we're in check
|
// All legal moves have been searched. A special case: if we're in check
|
||||||
// and no legal moves were found, it is checkmate.
|
// and no legal moves were found, it is checkmate.
|
||||||
if (inCheck && bestValue == -VALUE_INFINITE)
|
if (ss->inCheck && bestValue == -VALUE_INFINITE)
|
||||||
return mated_in(ss->ply); // Plies to mate from the root
|
return mated_in(ss->ply); // Plies to mate from the root
|
||||||
|
|
||||||
tte->save(posKey, value_to_tt(bestValue, ss->ply), pvHit,
|
tte->save(posKey, value_to_tt(bestValue, ss->ply), pvHit,
|
||||||
@@ -1560,28 +1612,47 @@ moves_loop: // When in check, search starts from here
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// value_to_tt() adjusts a mate score from "plies to mate from the root" to
|
// value_to_tt() adjusts a mate or TB score from "plies to mate from the root" to
|
||||||
// "plies to mate from the current position". Non-mate scores are unchanged.
|
// "plies to mate from the current position". Standard scores are unchanged.
|
||||||
// The function is called before storing a value in the transposition table.
|
// The function is called before storing a value in the transposition table.
|
||||||
|
|
||||||
Value value_to_tt(Value v, int ply) {
|
Value value_to_tt(Value v, int ply) {
|
||||||
|
|
||||||
assert(v != VALUE_NONE);
|
assert(v != VALUE_NONE);
|
||||||
|
|
||||||
return v >= VALUE_MATE_IN_MAX_PLY ? v + ply
|
return v >= VALUE_TB_WIN_IN_MAX_PLY ? v + ply
|
||||||
: v <= VALUE_MATED_IN_MAX_PLY ? v - ply : v;
|
: v <= VALUE_TB_LOSS_IN_MAX_PLY ? v - ply : v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// value_from_tt() is the inverse of value_to_tt(): It adjusts a mate score
|
// value_from_tt() is the inverse of value_to_tt(): it adjusts a mate or TB score
|
||||||
// from the transposition table (which refers to the plies to mate/be mated
|
// from the transposition table (which refers to the plies to mate/be mated from
|
||||||
// from current position) to "plies to mate/be mated from the root".
|
// current position) to "plies to mate/be mated (TB win/loss) from the root". However,
|
||||||
|
// for mate scores, to avoid potentially false mate scores related to the 50 moves rule
|
||||||
|
// and the graph history interaction, we return an optimal TB score instead.
|
||||||
|
|
||||||
Value value_from_tt(Value v, int ply, int r50c) {
|
Value value_from_tt(Value v, int ply, int r50c) {
|
||||||
|
|
||||||
return v == VALUE_NONE ? VALUE_NONE
|
if (v == VALUE_NONE)
|
||||||
: v >= VALUE_MATE_IN_MAX_PLY ? VALUE_MATE - v > 99 - r50c ? VALUE_MATE_IN_MAX_PLY : v - ply
|
return VALUE_NONE;
|
||||||
: v <= VALUE_MATED_IN_MAX_PLY ? VALUE_MATE + v > 99 - r50c ? VALUE_MATED_IN_MAX_PLY : v + ply : v;
|
|
||||||
|
if (v >= VALUE_TB_WIN_IN_MAX_PLY) // TB win or better
|
||||||
|
{
|
||||||
|
if (v >= VALUE_MATE_IN_MAX_PLY && VALUE_MATE - v > 99 - r50c)
|
||||||
|
return VALUE_MATE_IN_MAX_PLY - 1; // do not return a potentially false mate score
|
||||||
|
|
||||||
|
return v - ply;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v <= VALUE_TB_LOSS_IN_MAX_PLY) // TB loss or worse
|
||||||
|
{
|
||||||
|
if (v <= VALUE_MATED_IN_MAX_PLY && VALUE_MATE + v > 99 - r50c)
|
||||||
|
return VALUE_MATED_IN_MAX_PLY + 1; // do not return a potentially false mate score
|
||||||
|
|
||||||
|
return v + ply;
|
||||||
|
}
|
||||||
|
|
||||||
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1613,7 +1684,7 @@ moves_loop: // When in check, search starts from here
|
|||||||
|
|
||||||
if (!pos.capture_or_promotion(bestMove))
|
if (!pos.capture_or_promotion(bestMove))
|
||||||
{
|
{
|
||||||
update_quiet_stats(pos, ss, bestMove, bonus2);
|
update_quiet_stats(pos, ss, bestMove, bonus2, depth);
|
||||||
|
|
||||||
// Decrease all the non-best quiet moves
|
// Decrease all the non-best quiet moves
|
||||||
for (int i = 0; i < quietCount; ++i)
|
for (int i = 0; i < quietCount; ++i)
|
||||||
@@ -1641,19 +1712,23 @@ moves_loop: // When in check, search starts from here
|
|||||||
|
|
||||||
|
|
||||||
// update_continuation_histories() updates histories of the move pairs formed
|
// update_continuation_histories() updates histories of the move pairs formed
|
||||||
// by moves at ply -1, -2, and -4 with current move.
|
// by moves at ply -1, -2, -4, and -6 with current move.
|
||||||
|
|
||||||
void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) {
|
void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) {
|
||||||
|
|
||||||
for (int i : {1, 2, 4, 6})
|
for (int i : {1, 2, 4, 6})
|
||||||
|
{
|
||||||
|
if (ss->inCheck && i > 2)
|
||||||
|
break;
|
||||||
if (is_ok((ss-i)->currentMove))
|
if (is_ok((ss-i)->currentMove))
|
||||||
(*(ss-i)->continuationHistory)[pc][to] << bonus;
|
(*(ss-i)->continuationHistory)[pc][to] << bonus;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// update_quiet_stats() updates move sorting heuristics
|
// update_quiet_stats() updates move sorting heuristics
|
||||||
|
|
||||||
void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus) {
|
void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus, int depth) {
|
||||||
|
|
||||||
if (ss->killers[0] != move)
|
if (ss->killers[0] != move)
|
||||||
{
|
{
|
||||||
@@ -1674,6 +1749,9 @@ moves_loop: // When in check, search starts from here
|
|||||||
Square prevSq = to_sq((ss-1)->currentMove);
|
Square prevSq = to_sq((ss-1)->currentMove);
|
||||||
thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] = move;
|
thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] = move;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (depth > 11 && ss->ply < MAX_LPH)
|
||||||
|
thisThread->lowPlyHistory[ss->ply][from_to(move)] << stat_bonus(depth - 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
// When playing with strength handicap, choose best move among a set of RootMoves
|
// When playing with strength handicap, choose best move among a set of RootMoves
|
||||||
@@ -1711,6 +1789,7 @@ moves_loop: // When in check, search starts from here
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
|
||||||
/// MainThread::check_time() is used to print debug info and, more importantly,
|
/// MainThread::check_time() is used to print debug info and, more importantly,
|
||||||
/// to detect when we are out of available time and thus stop the search.
|
/// to detect when we are out of available time and thus stop the search.
|
||||||
|
|
||||||
@@ -1767,7 +1846,7 @@ string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) {
|
|||||||
Depth d = updated ? depth : depth - 1;
|
Depth d = updated ? depth : depth - 1;
|
||||||
Value v = updated ? rootMoves[i].score : rootMoves[i].previousScore;
|
Value v = updated ? rootMoves[i].score : rootMoves[i].previousScore;
|
||||||
|
|
||||||
bool tb = TB::RootInTB && abs(v) < VALUE_MATE - MAX_PLY;
|
bool tb = TB::RootInTB && abs(v) < VALUE_MATE_IN_MAX_PLY;
|
||||||
v = tb ? rootMoves[i].tbScore : v;
|
v = tb ? rootMoves[i].tbScore : v;
|
||||||
|
|
||||||
if (ss.rdbuf()->in_avail()) // Not at first line
|
if (ss.rdbuf()->in_avail()) // Not at first line
|
||||||
@@ -1779,6 +1858,9 @@ string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) {
|
|||||||
<< " multipv " << i + 1
|
<< " multipv " << i + 1
|
||||||
<< " score " << UCI::value(v);
|
<< " score " << UCI::value(v);
|
||||||
|
|
||||||
|
if (Options["UCI_ShowWDL"])
|
||||||
|
ss << UCI::wdl(v, pos.game_ply());
|
||||||
|
|
||||||
if (!tb && i == pvIdx)
|
if (!tb && i == pvIdx)
|
||||||
ss << (v >= beta ? " lowerbound" : v <= alpha ? " upperbound" : "");
|
ss << (v >= beta ? " lowerbound" : v <= alpha ? " upperbound" : "");
|
||||||
|
|
||||||
|
|||||||
+3
-4
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -49,6 +47,7 @@ struct Stack {
|
|||||||
Value staticEval;
|
Value staticEval;
|
||||||
int statScore;
|
int statScore;
|
||||||
int moveCount;
|
int moveCount;
|
||||||
|
bool inCheck;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -90,7 +89,7 @@ struct LimitsType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool use_time_management() const {
|
bool use_time_management() const {
|
||||||
return !(mate | movetime | depth | nodes | perft | infinite);
|
return time[WHITE] || time[BLACK];
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Move> searchmoves;
|
std::vector<Move> searchmoves;
|
||||||
|
|||||||
+30
-22
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (c) 2013 Ronald de Man
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2016-2020 Marco Costalba, Lucas Braesch
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -60,13 +59,12 @@ namespace {
|
|||||||
constexpr int TBPIECES = 7; // Max number of supported pieces
|
constexpr int TBPIECES = 7; // Max number of supported pieces
|
||||||
|
|
||||||
enum { BigEndian, LittleEndian };
|
enum { BigEndian, LittleEndian };
|
||||||
enum TBType { KEY, WDL, DTZ }; // Used as template parameter
|
enum TBType { WDL, DTZ }; // Used as template parameter
|
||||||
|
|
||||||
// Each table has a set of flags: all of them refer to DTZ tables, the last one to WDL tables
|
// Each table has a set of flags: all of them refer to DTZ tables, the last one to WDL tables
|
||||||
enum TBFlag { STM = 1, Mapped = 2, WinPlies = 4, LossPlies = 8, Wide = 16, SingleValue = 128 };
|
enum TBFlag { STM = 1, Mapped = 2, WinPlies = 4, LossPlies = 8, Wide = 16, SingleValue = 128 };
|
||||||
|
|
||||||
inline WDLScore operator-(WDLScore d) { return WDLScore(-int(d)); }
|
inline WDLScore operator-(WDLScore d) { return WDLScore(-int(d)); }
|
||||||
inline Square operator^=(Square& s, int i) { return s = Square(int(s) ^ i); }
|
|
||||||
inline Square operator^(Square s, int i) { return Square(int(s) ^ i); }
|
inline Square operator^(Square s, int i) { return Square(int(s) ^ i); }
|
||||||
|
|
||||||
const std::string PieceToChar = " PNBRQK pnbrqk";
|
const std::string PieceToChar = " PNBRQK pnbrqk";
|
||||||
@@ -404,7 +402,17 @@ TBTable<DTZ>::TBTable(const TBTable<WDL>& wdl) : TBTable() {
|
|||||||
// at init time, accessed at probe time.
|
// at init time, accessed at probe time.
|
||||||
class TBTables {
|
class TBTables {
|
||||||
|
|
||||||
typedef std::tuple<Key, TBTable<WDL>*, TBTable<DTZ>*> Entry;
|
struct Entry
|
||||||
|
{
|
||||||
|
Key key;
|
||||||
|
TBTable<WDL>* wdl;
|
||||||
|
TBTable<DTZ>* dtz;
|
||||||
|
|
||||||
|
template <TBType Type>
|
||||||
|
TBTable<Type>* get() const {
|
||||||
|
return (TBTable<Type>*)(Type == WDL ? (void*)wdl : (void*)dtz);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
static constexpr int Size = 1 << 12; // 4K table, indexed by key's 12 lsb
|
static constexpr int Size = 1 << 12; // 4K table, indexed by key's 12 lsb
|
||||||
static constexpr int Overflow = 1; // Number of elements allowed to map to the last bucket
|
static constexpr int Overflow = 1; // Number of elements allowed to map to the last bucket
|
||||||
@@ -416,12 +424,12 @@ class TBTables {
|
|||||||
|
|
||||||
void insert(Key key, TBTable<WDL>* wdl, TBTable<DTZ>* dtz) {
|
void insert(Key key, TBTable<WDL>* wdl, TBTable<DTZ>* dtz) {
|
||||||
uint32_t homeBucket = (uint32_t)key & (Size - 1);
|
uint32_t homeBucket = (uint32_t)key & (Size - 1);
|
||||||
Entry entry = std::make_tuple(key, wdl, dtz);
|
Entry entry{ key, wdl, dtz };
|
||||||
|
|
||||||
// Ensure last element is empty to avoid overflow when looking up
|
// Ensure last element is empty to avoid overflow when looking up
|
||||||
for (uint32_t bucket = homeBucket; bucket < Size + Overflow - 1; ++bucket) {
|
for (uint32_t bucket = homeBucket; bucket < Size + Overflow - 1; ++bucket) {
|
||||||
Key otherKey = std::get<KEY>(hashTable[bucket]);
|
Key otherKey = hashTable[bucket].key;
|
||||||
if (otherKey == key || !std::get<WDL>(hashTable[bucket])) {
|
if (otherKey == key || !hashTable[bucket].get<WDL>()) {
|
||||||
hashTable[bucket] = entry;
|
hashTable[bucket] = entry;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -430,7 +438,7 @@ class TBTables {
|
|||||||
// insert here and search for a new spot for the other element instead.
|
// insert here and search for a new spot for the other element instead.
|
||||||
uint32_t otherHomeBucket = (uint32_t)otherKey & (Size - 1);
|
uint32_t otherHomeBucket = (uint32_t)otherKey & (Size - 1);
|
||||||
if (otherHomeBucket > homeBucket) {
|
if (otherHomeBucket > homeBucket) {
|
||||||
swap(entry, hashTable[bucket]);
|
std::swap(entry, hashTable[bucket]);
|
||||||
key = otherKey;
|
key = otherKey;
|
||||||
homeBucket = otherHomeBucket;
|
homeBucket = otherHomeBucket;
|
||||||
}
|
}
|
||||||
@@ -443,8 +451,8 @@ public:
|
|||||||
template<TBType Type>
|
template<TBType Type>
|
||||||
TBTable<Type>* get(Key key) {
|
TBTable<Type>* get(Key key) {
|
||||||
for (const Entry* entry = &hashTable[(uint32_t)key & (Size - 1)]; ; ++entry) {
|
for (const Entry* entry = &hashTable[(uint32_t)key & (Size - 1)]; ; ++entry) {
|
||||||
if (std::get<KEY>(*entry) == key || !std::get<Type>(*entry))
|
if (entry->key == key || !entry->get<Type>())
|
||||||
return std::get<Type>(*entry);
|
return entry->get<Type>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -521,7 +529,7 @@ int decompress_pairs(PairsData* d, uint64_t idx) {
|
|||||||
// I(k) = k * d->span + d->span / 2 (1)
|
// I(k) = k * d->span + d->span / 2 (1)
|
||||||
|
|
||||||
// First step is to get the 'k' of the I(k) nearest to our idx, using definition (1)
|
// First step is to get the 'k' of the I(k) nearest to our idx, using definition (1)
|
||||||
uint32_t k = idx / d->span;
|
uint32_t k = uint32_t(idx / d->span);
|
||||||
|
|
||||||
// Then we read the corresponding SparseIndex[] entry
|
// Then we read the corresponding SparseIndex[] entry
|
||||||
uint32_t block = number<uint32_t, LittleEndian>(&d->sparseIndex[k].block);
|
uint32_t block = number<uint32_t, LittleEndian>(&d->sparseIndex[k].block);
|
||||||
@@ -567,7 +575,7 @@ int decompress_pairs(PairsData* d, uint64_t idx) {
|
|||||||
// All the symbols of a given length are consecutive integers (numerical
|
// All the symbols of a given length are consecutive integers (numerical
|
||||||
// sequence property), so we can compute the offset of our symbol of
|
// sequence property), so we can compute the offset of our symbol of
|
||||||
// length len, stored at the beginning of buf64.
|
// length len, stored at the beginning of buf64.
|
||||||
sym = (buf64 - d->base64[len]) >> (64 - len - d->minSymLen);
|
sym = Sym((buf64 - d->base64[len]) >> (64 - len - d->minSymLen));
|
||||||
|
|
||||||
// Now add the value of the lowest symbol of length len to get our symbol
|
// Now add the value of the lowest symbol of length len to get our symbol
|
||||||
sym += number<Sym, LittleEndian>(&d->lowestSym[len]);
|
sym += number<Sym, LittleEndian>(&d->lowestSym[len]);
|
||||||
@@ -706,7 +714,7 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu
|
|||||||
|
|
||||||
std::swap(squares[0], *std::max_element(squares, squares + leadPawnsCnt, pawns_comp));
|
std::swap(squares[0], *std::max_element(squares, squares + leadPawnsCnt, pawns_comp));
|
||||||
|
|
||||||
tbFile = map_to_queenside(file_of(squares[0]));
|
tbFile = File(edge_distance(file_of(squares[0])));
|
||||||
}
|
}
|
||||||
|
|
||||||
// DTZ tables are one-sided, i.e. they store positions only for white to
|
// DTZ tables are one-sided, i.e. they store positions only for white to
|
||||||
@@ -743,7 +751,7 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu
|
|||||||
// the triangle A1-D1-D4.
|
// the triangle A1-D1-D4.
|
||||||
if (file_of(squares[0]) > FILE_D)
|
if (file_of(squares[0]) > FILE_D)
|
||||||
for (int i = 0; i < size; ++i)
|
for (int i = 0; i < size; ++i)
|
||||||
squares[i] ^= 7; // Horizontal flip: SQ_H1 -> SQ_A1
|
squares[i] = flip_file(squares[i]);
|
||||||
|
|
||||||
// Encode leading pawns starting with the one with minimum MapPawns[] and
|
// Encode leading pawns starting with the one with minimum MapPawns[] and
|
||||||
// proceeding in ascending order.
|
// proceeding in ascending order.
|
||||||
@@ -762,7 +770,7 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu
|
|||||||
// piece is below RANK_5.
|
// piece is below RANK_5.
|
||||||
if (rank_of(squares[0]) > RANK_4)
|
if (rank_of(squares[0]) > RANK_4)
|
||||||
for (int i = 0; i < size; ++i)
|
for (int i = 0; i < size; ++i)
|
||||||
squares[i] ^= SQ_A8; // Vertical flip: SQ_A8 -> SQ_A1
|
squares[i] = flip_rank(squares[i]);
|
||||||
|
|
||||||
// Look for the first piece of the leading group not on the A1-D4 diagonal
|
// Look for the first piece of the leading group not on the A1-D4 diagonal
|
||||||
// and ensure it is mapped below the diagonal.
|
// and ensure it is mapped below the diagonal.
|
||||||
@@ -770,7 +778,7 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu
|
|||||||
if (!off_A1H8(squares[i]))
|
if (!off_A1H8(squares[i]))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (off_A1H8(squares[i]) > 0) // A1-H8 diagonal flip: SQ_A3 -> SQ_C3
|
if (off_A1H8(squares[i]) > 0) // A1-H8 diagonal flip: SQ_A3 -> SQ_C1
|
||||||
for (int j = i; j < size; ++j)
|
for (int j = i; j < size; ++j)
|
||||||
squares[j] = Square(((squares[j] >> 3) | (squares[j] << 3)) & 63);
|
squares[j] = Square(((squares[j] >> 3) | (squares[j] << 3)) & 63);
|
||||||
break;
|
break;
|
||||||
@@ -975,7 +983,7 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) {
|
|||||||
|
|
||||||
d->sizeofBlock = 1ULL << *data++;
|
d->sizeofBlock = 1ULL << *data++;
|
||||||
d->span = 1ULL << *data++;
|
d->span = 1ULL << *data++;
|
||||||
d->sparseIndexSize = (tbSize + d->span - 1) / d->span; // Round up
|
d->sparseIndexSize = size_t((tbSize + d->span - 1) / d->span); // Round up
|
||||||
auto padding = number<uint8_t, LittleEndian>(data++);
|
auto padding = number<uint8_t, LittleEndian>(data++);
|
||||||
d->blocksNum = number<uint32_t, LittleEndian>(data); data += sizeof(uint32_t);
|
d->blocksNum = number<uint32_t, LittleEndian>(data); data += sizeof(uint32_t);
|
||||||
d->blockLengthSize = d->blocksNum + padding; // Padded to ensure SparseIndex[]
|
d->blockLengthSize = d->blocksNum + padding; // Padded to ensure SparseIndex[]
|
||||||
@@ -1191,7 +1199,7 @@ WDLScore search(Position& pos, ProbeState* result) {
|
|||||||
auto moveList = MoveList<LEGAL>(pos);
|
auto moveList = MoveList<LEGAL>(pos);
|
||||||
size_t totalCount = moveList.size(), moveCount = 0;
|
size_t totalCount = moveList.size(), moveCount = 0;
|
||||||
|
|
||||||
for (const Move& move : moveList)
|
for (const Move move : moveList)
|
||||||
{
|
{
|
||||||
if ( !pos.capture(move)
|
if ( !pos.capture(move)
|
||||||
&& (!CheckZeroingMoves || type_of(pos.moved_piece(move)) != PAWN))
|
&& (!CheckZeroingMoves || type_of(pos.moved_piece(move)) != PAWN))
|
||||||
@@ -1344,7 +1352,7 @@ void Tablebases::init(const std::string& paths) {
|
|||||||
if (leadPawnsCnt == 1)
|
if (leadPawnsCnt == 1)
|
||||||
{
|
{
|
||||||
MapPawns[sq] = availableSquares--;
|
MapPawns[sq] = availableSquares--;
|
||||||
MapPawns[sq ^ 7] = availableSquares--; // Horizontal flip
|
MapPawns[flip_file(sq)] = availableSquares--;
|
||||||
}
|
}
|
||||||
LeadPawnIdx[leadPawnsCnt][sq] = idx;
|
LeadPawnIdx[leadPawnsCnt][sq] = idx;
|
||||||
idx += Binomial[leadPawnsCnt - 1][MapPawns[sq]];
|
idx += Binomial[leadPawnsCnt - 1][MapPawns[sq]];
|
||||||
@@ -1353,7 +1361,7 @@ void Tablebases::init(const std::string& paths) {
|
|||||||
LeadPawnsSize[leadPawnsCnt][f] = idx;
|
LeadPawnsSize[leadPawnsCnt][f] = idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add entries in TB tables if the corresponding ".rtbw" file exsists
|
// Add entries in TB tables if the corresponding ".rtbw" file exists
|
||||||
for (PieceType p1 = PAWN; p1 < KING; ++p1) {
|
for (PieceType p1 = PAWN; p1 < KING; ++p1) {
|
||||||
TBTables.add({KING, p1, KING});
|
TBTables.add({KING, p1, KING});
|
||||||
|
|
||||||
@@ -1460,7 +1468,7 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) {
|
|||||||
StateInfo st;
|
StateInfo st;
|
||||||
int minDTZ = 0xFFFF;
|
int minDTZ = 0xFFFF;
|
||||||
|
|
||||||
for (const Move& move : MoveList<LEGAL>(pos))
|
for (const Move move : MoveList<LEGAL>(pos))
|
||||||
{
|
{
|
||||||
bool zeroing = pos.capture(move) || type_of(pos.moved_piece(move)) == PAWN;
|
bool zeroing = pos.capture(move) || type_of(pos.moved_piece(move)) == PAWN;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (c) 2013 Ronald de Man
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2016-2020 Marco Costalba, Lucas Braesch
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|||||||
+65
-11
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -52,9 +50,10 @@ Thread::~Thread() {
|
|||||||
stdThread.join();
|
stdThread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Thread::bestMoveCount(Move move) return best move counter for the given root move
|
/// Thread::bestMoveCount(Move move) return best move counter for the given root move
|
||||||
|
|
||||||
int Thread::best_move_count(Move move) {
|
int Thread::best_move_count(Move move) const {
|
||||||
|
|
||||||
auto rm = std::find(rootMoves.begin() + pvIdx,
|
auto rm = std::find(rootMoves.begin() + pvIdx,
|
||||||
rootMoves.begin() + pvLast, move);
|
rootMoves.begin() + pvLast, move);
|
||||||
@@ -62,25 +61,27 @@ int Thread::best_move_count(Move move) {
|
|||||||
return rm != rootMoves.begin() + pvLast ? rm->bestMoveCount : 0;
|
return rm != rootMoves.begin() + pvLast ? rm->bestMoveCount : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Thread::clear() reset histories, usually before a new game
|
/// Thread::clear() reset histories, usually before a new game
|
||||||
|
|
||||||
void Thread::clear() {
|
void Thread::clear() {
|
||||||
|
|
||||||
counterMoves.fill(MOVE_NONE);
|
counterMoves.fill(MOVE_NONE);
|
||||||
mainHistory.fill(0);
|
mainHistory.fill(0);
|
||||||
|
lowPlyHistory.fill(0);
|
||||||
captureHistory.fill(0);
|
captureHistory.fill(0);
|
||||||
|
|
||||||
for (bool inCheck : { false, true })
|
for (bool inCheck : { false, true })
|
||||||
for (StatsType c : { NoCaptures, Captures })
|
for (StatsType c : { NoCaptures, Captures })
|
||||||
|
{
|
||||||
for (auto& to : continuationHistory[inCheck][c])
|
for (auto& to : continuationHistory[inCheck][c])
|
||||||
for (auto& h : to)
|
for (auto& h : to)
|
||||||
h->fill(0);
|
h->fill(0);
|
||||||
|
|
||||||
for (bool inCheck : { false, true })
|
|
||||||
for (StatsType c : { NoCaptures, Captures })
|
|
||||||
continuationHistory[inCheck][c][NO_PIECE][0]->fill(Search::CounterMovePruneThreshold - 1);
|
continuationHistory[inCheck][c][NO_PIECE][0]->fill(Search::CounterMovePruneThreshold - 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Thread::start_searching() wakes up the thread that will start the search
|
/// Thread::start_searching() wakes up the thread that will start the search
|
||||||
|
|
||||||
void Thread::start_searching() {
|
void Thread::start_searching() {
|
||||||
@@ -151,14 +152,15 @@ void ThreadPool::set(size_t requested) {
|
|||||||
clear();
|
clear();
|
||||||
|
|
||||||
// Reallocate the hash with the new threadpool size
|
// Reallocate the hash with the new threadpool size
|
||||||
TT.resize(Options["Hash"]);
|
TT.resize(size_t(Options["Hash"]));
|
||||||
|
|
||||||
// Init thread number dependent search params.
|
// Init thread number dependent search params.
|
||||||
Search::init();
|
Search::init();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ThreadPool::clear() sets threadPool data to initial values.
|
|
||||||
|
/// ThreadPool::clear() sets threadPool data to initial values
|
||||||
|
|
||||||
void ThreadPool::clear() {
|
void ThreadPool::clear() {
|
||||||
|
|
||||||
@@ -166,10 +168,11 @@ void ThreadPool::clear() {
|
|||||||
th->clear();
|
th->clear();
|
||||||
|
|
||||||
main()->callsCnt = 0;
|
main()->callsCnt = 0;
|
||||||
main()->previousScore = VALUE_INFINITE;
|
main()->bestPreviousScore = VALUE_INFINITE;
|
||||||
main()->previousTimeReduction = 1.0;
|
main()->previousTimeReduction = 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// ThreadPool::start_thinking() wakes up main thread waiting in idle_loop() and
|
/// ThreadPool::start_thinking() wakes up main thread waiting in idle_loop() and
|
||||||
/// returns immediately. Main thread will wake up other threads and start the search.
|
/// returns immediately. Main thread will wake up other threads and start the search.
|
||||||
|
|
||||||
@@ -208,7 +211,7 @@ void ThreadPool::start_thinking(Position& pos, StateListPtr& states,
|
|||||||
|
|
||||||
for (Thread* th : *this)
|
for (Thread* th : *this)
|
||||||
{
|
{
|
||||||
th->nodes = th->tbHits = th->nmpMinPly = 0;
|
th->nodes = th->tbHits = th->nmpMinPly = th->bestMoveChanges = 0;
|
||||||
th->rootDepth = th->completedDepth = 0;
|
th->rootDepth = th->completedDepth = 0;
|
||||||
th->rootMoves = rootMoves;
|
th->rootMoves = rootMoves;
|
||||||
th->rootPos.set(pos.fen(), pos.is_chess960(), &setupStates->back(), th);
|
th->rootPos.set(pos.fen(), pos.is_chess960(), &setupStates->back(), th);
|
||||||
@@ -218,3 +221,54 @@ void ThreadPool::start_thinking(Position& pos, StateListPtr& states,
|
|||||||
|
|
||||||
main()->start_searching();
|
main()->start_searching();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Thread* ThreadPool::get_best_thread() const {
|
||||||
|
|
||||||
|
Thread* bestThread = front();
|
||||||
|
std::map<Move, int64_t> votes;
|
||||||
|
Value minScore = VALUE_NONE;
|
||||||
|
|
||||||
|
// Find minimum score of all threads
|
||||||
|
for (Thread* th: *this)
|
||||||
|
minScore = std::min(minScore, th->rootMoves[0].score);
|
||||||
|
|
||||||
|
// Vote according to score and depth, and select the best thread
|
||||||
|
for (Thread* th : *this)
|
||||||
|
{
|
||||||
|
votes[th->rootMoves[0].pv[0]] +=
|
||||||
|
(th->rootMoves[0].score - minScore + 14) * int(th->completedDepth);
|
||||||
|
|
||||||
|
if (abs(bestThread->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY)
|
||||||
|
{
|
||||||
|
// Make sure we pick the shortest mate / TB conversion or stave off mate the longest
|
||||||
|
if (th->rootMoves[0].score > bestThread->rootMoves[0].score)
|
||||||
|
bestThread = th;
|
||||||
|
}
|
||||||
|
else if ( th->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY
|
||||||
|
|| ( th->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY
|
||||||
|
&& votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]]))
|
||||||
|
bestThread = th;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bestThread;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Start non-main threads
|
||||||
|
|
||||||
|
void ThreadPool::start_searching() {
|
||||||
|
|
||||||
|
for (Thread* th : *this)
|
||||||
|
if (th != front())
|
||||||
|
th->start_searching();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Wait for non-main threads
|
||||||
|
|
||||||
|
void ThreadPool::wait_for_search_finished() const {
|
||||||
|
|
||||||
|
for (Thread* th : *this)
|
||||||
|
if (th != front())
|
||||||
|
th->wait_for_search_finished();
|
||||||
|
}
|
||||||
|
|||||||
+7
-5
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -56,7 +54,7 @@ public:
|
|||||||
void idle_loop();
|
void idle_loop();
|
||||||
void start_searching();
|
void start_searching();
|
||||||
void wait_for_search_finished();
|
void wait_for_search_finished();
|
||||||
int best_move_count(Move move);
|
int best_move_count(Move move) const;
|
||||||
|
|
||||||
Pawns::Table pawnsTable;
|
Pawns::Table pawnsTable;
|
||||||
Material::Table materialTable;
|
Material::Table materialTable;
|
||||||
@@ -71,6 +69,7 @@ public:
|
|||||||
Depth rootDepth, completedDepth;
|
Depth rootDepth, completedDepth;
|
||||||
CounterMoveHistory counterMoves;
|
CounterMoveHistory counterMoves;
|
||||||
ButterflyHistory mainHistory;
|
ButterflyHistory mainHistory;
|
||||||
|
LowPlyHistory lowPlyHistory;
|
||||||
CapturePieceToHistory captureHistory;
|
CapturePieceToHistory captureHistory;
|
||||||
ContinuationHistory continuationHistory[2][2];
|
ContinuationHistory continuationHistory[2][2];
|
||||||
Score contempt;
|
Score contempt;
|
||||||
@@ -87,7 +86,7 @@ struct MainThread : public Thread {
|
|||||||
void check_time();
|
void check_time();
|
||||||
|
|
||||||
double previousTimeReduction;
|
double previousTimeReduction;
|
||||||
Value previousScore;
|
Value bestPreviousScore;
|
||||||
Value iterValue[4];
|
Value iterValue[4];
|
||||||
int callsCnt;
|
int callsCnt;
|
||||||
bool stopOnPonderhit;
|
bool stopOnPonderhit;
|
||||||
@@ -108,6 +107,9 @@ struct ThreadPool : public std::vector<Thread*> {
|
|||||||
MainThread* main() const { return static_cast<MainThread*>(front()); }
|
MainThread* main() const { return static_cast<MainThread*>(front()); }
|
||||||
uint64_t nodes_searched() const { return accumulate(&Thread::nodes); }
|
uint64_t nodes_searched() const { return accumulate(&Thread::nodes); }
|
||||||
uint64_t tb_hits() const { return accumulate(&Thread::tbHits); }
|
uint64_t tb_hits() const { return accumulate(&Thread::tbHits); }
|
||||||
|
Thread* get_best_thread() const;
|
||||||
|
void start_searching();
|
||||||
|
void wait_for_search_finished() const;
|
||||||
|
|
||||||
std::atomic_bool stop, increaseDepth;
|
std::atomic_bool stop, increaseDepth;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|||||||
+41
-77
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -28,66 +26,21 @@
|
|||||||
|
|
||||||
TimeManagement Time; // Our global time management object
|
TimeManagement Time; // Our global time management object
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
enum TimeType { OptimumTime, MaxTime };
|
/// TimeManagement::init() is called at the beginning of the search and calculates
|
||||||
|
/// the bounds of time allowed for the current game ply. We currently support:
|
||||||
constexpr int MoveHorizon = 50; // Plan time management at most this many moves ahead
|
// 1) x basetime (+ z increment)
|
||||||
constexpr double MaxRatio = 7.3; // When in trouble, we can step over reserved time with this ratio
|
// 2) x moves in y seconds (+ z increment)
|
||||||
constexpr double StealRatio = 0.34; // However we must not steal time from remaining moves over this ratio
|
|
||||||
|
|
||||||
|
|
||||||
// move_importance() is a skew-logistic function based on naive statistical
|
|
||||||
// analysis of "how many games are still undecided after n half-moves". Game
|
|
||||||
// is considered "undecided" as long as neither side has >275cp advantage.
|
|
||||||
// Data was extracted from the CCRL game database with some simple filtering criteria.
|
|
||||||
|
|
||||||
double move_importance(int ply) {
|
|
||||||
|
|
||||||
constexpr double XScale = 6.85;
|
|
||||||
constexpr double XShift = 64.5;
|
|
||||||
constexpr double Skew = 0.171;
|
|
||||||
|
|
||||||
return pow((1 + exp((ply - XShift) / XScale)), -Skew) + DBL_MIN; // Ensure non-zero
|
|
||||||
}
|
|
||||||
|
|
||||||
template<TimeType T>
|
|
||||||
TimePoint remaining(TimePoint myTime, int movesToGo, int ply, TimePoint slowMover) {
|
|
||||||
|
|
||||||
constexpr double TMaxRatio = (T == OptimumTime ? 1.0 : MaxRatio);
|
|
||||||
constexpr double TStealRatio = (T == OptimumTime ? 0.0 : StealRatio);
|
|
||||||
|
|
||||||
double moveImportance = (move_importance(ply) * slowMover) / 100.0;
|
|
||||||
double otherMovesImportance = 0.0;
|
|
||||||
|
|
||||||
for (int i = 1; i < movesToGo; ++i)
|
|
||||||
otherMovesImportance += move_importance(ply + 2 * i);
|
|
||||||
|
|
||||||
double ratio1 = (TMaxRatio * moveImportance) / (TMaxRatio * moveImportance + otherMovesImportance);
|
|
||||||
double ratio2 = (moveImportance + TStealRatio * otherMovesImportance) / (moveImportance + otherMovesImportance);
|
|
||||||
|
|
||||||
return TimePoint(myTime * std::min(ratio1, ratio2)); // Intel C++ asks for an explicit cast
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
|
|
||||||
/// init() is called at the beginning of the search and calculates the allowed
|
|
||||||
/// thinking time out of the time control and current game ply. We support four
|
|
||||||
/// different kinds of time controls, passed in 'limits':
|
|
||||||
///
|
|
||||||
/// inc == 0 && movestogo == 0 means: x basetime [sudden death!]
|
|
||||||
/// inc == 0 && movestogo != 0 means: x moves in y minutes
|
|
||||||
/// inc > 0 && movestogo == 0 means: x basetime + z increment
|
|
||||||
/// inc > 0 && movestogo != 0 means: x moves in y minutes + z increment
|
|
||||||
|
|
||||||
void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
|
void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
|
||||||
|
|
||||||
TimePoint minThinkingTime = Options["Minimum Thinking Time"];
|
TimePoint moveOverhead = TimePoint(Options["Move Overhead"]);
|
||||||
TimePoint moveOverhead = Options["Move Overhead"];
|
TimePoint slowMover = TimePoint(Options["Slow Mover"]);
|
||||||
TimePoint slowMover = Options["Slow Mover"];
|
TimePoint npmsec = TimePoint(Options["nodestime"]);
|
||||||
TimePoint npmsec = Options["nodestime"];
|
|
||||||
TimePoint hypMyTime;
|
// opt_scale is a percentage of available time to use for the current move.
|
||||||
|
// max_scale is a multiplier applied to optimumTime.
|
||||||
|
double opt_scale, max_scale;
|
||||||
|
|
||||||
// If we have to play in 'nodes as time' mode, then convert from time
|
// If we have to play in 'nodes as time' mode, then convert from time
|
||||||
// to nodes, and use resulting values in time management formulas.
|
// to nodes, and use resulting values in time management formulas.
|
||||||
@@ -105,29 +58,40 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
startTime = limits.startTime;
|
startTime = limits.startTime;
|
||||||
optimumTime = maximumTime = std::max(limits.time[us], minThinkingTime);
|
|
||||||
|
|
||||||
const int maxMTG = limits.movestogo ? std::min(limits.movestogo, MoveHorizon) : MoveHorizon;
|
// Maximum move horizon of 50 moves
|
||||||
|
int mtg = limits.movestogo ? std::min(limits.movestogo, 50) : 50;
|
||||||
|
|
||||||
// We calculate optimum time usage for different hypothetical "moves to go" values
|
// Make sure timeLeft is > 0 since we may use it as a divisor
|
||||||
// and choose the minimum of calculated search time values. Usually the greatest
|
TimePoint timeLeft = std::max(TimePoint(1),
|
||||||
// hypMTG gives the minimum values.
|
limits.time[us] + limits.inc[us] * (mtg - 1) - moveOverhead * (2 + mtg));
|
||||||
for (int hypMTG = 1; hypMTG <= maxMTG; ++hypMTG)
|
|
||||||
|
// A user may scale time usage by setting UCI option "Slow Mover"
|
||||||
|
// Default is 100 and changing this value will probably lose elo.
|
||||||
|
timeLeft = slowMover * timeLeft / 100;
|
||||||
|
|
||||||
|
// x basetime (+ z increment)
|
||||||
|
// If there is a healthy increment, timeLeft can exceed actual available
|
||||||
|
// game time for the current move, so also cap to 20% of available game time.
|
||||||
|
if (limits.movestogo == 0)
|
||||||
{
|
{
|
||||||
// Calculate thinking time for hypothetical "moves to go"-value
|
opt_scale = std::min(0.008 + std::pow(ply + 3.0, 0.5) / 250.0,
|
||||||
hypMyTime = limits.time[us]
|
0.2 * limits.time[us] / double(timeLeft));
|
||||||
+ limits.inc[us] * (hypMTG - 1)
|
max_scale = std::min(7.0, 4.0 + ply / 12.0);
|
||||||
- moveOverhead * (2 + std::min(hypMTG, 40));
|
|
||||||
|
|
||||||
hypMyTime = std::max(hypMyTime, TimePoint(0));
|
|
||||||
|
|
||||||
TimePoint t1 = minThinkingTime + remaining<OptimumTime>(hypMyTime, hypMTG, ply, slowMover);
|
|
||||||
TimePoint t2 = minThinkingTime + remaining<MaxTime >(hypMyTime, hypMTG, ply, slowMover);
|
|
||||||
|
|
||||||
optimumTime = std::min(t1, optimumTime);
|
|
||||||
maximumTime = std::min(t2, maximumTime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// x moves in y seconds (+ z increment)
|
||||||
|
else
|
||||||
|
{
|
||||||
|
opt_scale = std::min((0.8 + ply / 128.0) / mtg,
|
||||||
|
0.8 * limits.time[us] / double(timeLeft));
|
||||||
|
max_scale = std::min(6.3, 1.5 + 0.11 * mtg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Never use more than 80% of the available time for this move
|
||||||
|
optimumTime = TimePoint(opt_scale * timeLeft);
|
||||||
|
maximumTime = TimePoint(std::min(0.8 * limits.time[us] - moveOverhead, max_scale * optimumTime));
|
||||||
|
|
||||||
if (Options["Ponder"])
|
if (Options["Ponder"])
|
||||||
optimumTime += optimumTime / 4;
|
optimumTime += optimumTime / 4;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-3
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|||||||
+15
-18
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -30,23 +28,23 @@
|
|||||||
|
|
||||||
TranspositionTable TT; // Our global transposition table
|
TranspositionTable TT; // Our global transposition table
|
||||||
|
|
||||||
/// TTEntry::save populates the TTEntry with a new node's data, possibly
|
/// TTEntry::save() populates the TTEntry with a new node's data, possibly
|
||||||
/// overwriting an old position. Update is not atomic and can be racy.
|
/// overwriting an old position. Update is not atomic and can be racy.
|
||||||
|
|
||||||
void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) {
|
void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) {
|
||||||
|
|
||||||
// Preserve any existing move for the same position
|
// Preserve any existing move for the same position
|
||||||
if (m || (k >> 48) != key16)
|
if (m || (uint16_t)k != key16)
|
||||||
move16 = (uint16_t)m;
|
move16 = (uint16_t)m;
|
||||||
|
|
||||||
// Overwrite less valuable entries
|
// Overwrite less valuable entries
|
||||||
if ( (k >> 48) != key16
|
if ((uint16_t)k != key16
|
||||||
|| d - DEPTH_OFFSET > depth8 - 4
|
|| d - DEPTH_OFFSET > depth8 - 4
|
||||||
|| b == BOUND_EXACT)
|
|| b == BOUND_EXACT)
|
||||||
{
|
{
|
||||||
assert(d >= DEPTH_OFFSET);
|
assert(d >= DEPTH_OFFSET);
|
||||||
|
|
||||||
key16 = (uint16_t)(k >> 48);
|
key16 = (uint16_t)k;
|
||||||
value16 = (int16_t)v;
|
value16 = (int16_t)v;
|
||||||
eval16 = (int16_t)ev;
|
eval16 = (int16_t)ev;
|
||||||
genBound8 = (uint8_t)(TT.generation8 | uint8_t(pv) << 2 | b);
|
genBound8 = (uint8_t)(TT.generation8 | uint8_t(pv) << 2 | b);
|
||||||
@@ -63,11 +61,10 @@ void TranspositionTable::resize(size_t mbSize) {
|
|||||||
|
|
||||||
Threads.main()->wait_for_search_finished();
|
Threads.main()->wait_for_search_finished();
|
||||||
|
|
||||||
|
aligned_ttmem_free(mem);
|
||||||
|
|
||||||
clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster);
|
clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster);
|
||||||
|
table = static_cast<Cluster*>(aligned_ttmem_alloc(clusterCount * sizeof(Cluster), mem));
|
||||||
free(mem);
|
|
||||||
mem = malloc(clusterCount * sizeof(Cluster) + CacheLineSize - 1);
|
|
||||||
|
|
||||||
if (!mem)
|
if (!mem)
|
||||||
{
|
{
|
||||||
std::cerr << "Failed to allocate " << mbSize
|
std::cerr << "Failed to allocate " << mbSize
|
||||||
@@ -75,7 +72,6 @@ void TranspositionTable::resize(size_t mbSize) {
|
|||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
table = (Cluster*)((uintptr_t(mem) + CacheLineSize - 1) & ~(CacheLineSize - 1));
|
|
||||||
clear();
|
clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,8 +92,8 @@ void TranspositionTable::clear() {
|
|||||||
WinProcGroup::bindThisThread(idx);
|
WinProcGroup::bindThisThread(idx);
|
||||||
|
|
||||||
// Each thread will zero its part of the hash table
|
// Each thread will zero its part of the hash table
|
||||||
const size_t stride = clusterCount / Options["Threads"],
|
const size_t stride = size_t(clusterCount / Options["Threads"]),
|
||||||
start = stride * idx,
|
start = size_t(stride * idx),
|
||||||
len = idx != Options["Threads"] - 1 ?
|
len = idx != Options["Threads"] - 1 ?
|
||||||
stride : clusterCount - start;
|
stride : clusterCount - start;
|
||||||
|
|
||||||
@@ -105,10 +101,11 @@ void TranspositionTable::clear() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (std::thread& th: threads)
|
for (std::thread& th : threads)
|
||||||
th.join();
|
th.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// TranspositionTable::probe() looks up the current position in the transposition
|
/// TranspositionTable::probe() looks up the current position in the transposition
|
||||||
/// table. It returns true and a pointer to the TTEntry if the position is found.
|
/// table. It returns true and a pointer to the TTEntry if the position is found.
|
||||||
/// Otherwise, it returns false and a pointer to an empty or least valuable TTEntry
|
/// Otherwise, it returns false and a pointer to an empty or least valuable TTEntry
|
||||||
@@ -119,7 +116,7 @@ void TranspositionTable::clear() {
|
|||||||
TTEntry* TranspositionTable::probe(const Key key, bool& found) const {
|
TTEntry* TranspositionTable::probe(const Key key, bool& found) const {
|
||||||
|
|
||||||
TTEntry* const tte = first_entry(key);
|
TTEntry* const tte = first_entry(key);
|
||||||
const uint16_t key16 = key >> 48; // Use the high 16 bits as key inside the cluster
|
const uint16_t key16 = (uint16_t)key; // Use the low 16 bits as key inside the cluster
|
||||||
|
|
||||||
for (int i = 0; i < ClusterSize; ++i)
|
for (int i = 0; i < ClusterSize; ++i)
|
||||||
if (!tte[i].key16 || tte[i].key16 == key16)
|
if (!tte[i].key16 || tte[i].key16 == key16)
|
||||||
@@ -150,9 +147,9 @@ TTEntry* TranspositionTable::probe(const Key key, bool& found) const {
|
|||||||
int TranspositionTable::hashfull() const {
|
int TranspositionTable::hashfull() const {
|
||||||
|
|
||||||
int cnt = 0;
|
int cnt = 0;
|
||||||
for (int i = 0; i < 1000 / ClusterSize; ++i)
|
for (int i = 0; i < 1000; ++i)
|
||||||
for (int j = 0; j < ClusterSize; ++j)
|
for (int j = 0; j < ClusterSize; ++j)
|
||||||
cnt += (table[i].entry[j].genBound8 & 0xF8) == generation8;
|
cnt += (table[i].entry[j].genBound8 & 0xF8) == generation8;
|
||||||
|
|
||||||
return cnt * 1000 / (ClusterSize * (1000 / ClusterSize));
|
return cnt / ClusterSize;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -57,36 +55,33 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/// A TranspositionTable consists of a power of 2 number of clusters and each
|
/// A TranspositionTable is an array of Cluster, of size clusterCount. Each
|
||||||
/// cluster consists of ClusterSize number of TTEntry. Each non-empty entry
|
/// cluster consists of ClusterSize number of TTEntry. Each non-empty TTEntry
|
||||||
/// contains information of exactly one position. The size of a cluster should
|
/// contains information on exactly one position. The size of a Cluster should
|
||||||
/// divide the size of a cache line size, to ensure that clusters never cross
|
/// divide the size of a cache line for best performance, as the cacheline is
|
||||||
/// cache lines. This ensures best cache performance, as the cacheline is
|
/// prefetched when possible.
|
||||||
/// prefetched, as soon as possible.
|
|
||||||
|
|
||||||
class TranspositionTable {
|
class TranspositionTable {
|
||||||
|
|
||||||
static constexpr int CacheLineSize = 64;
|
|
||||||
static constexpr int ClusterSize = 3;
|
static constexpr int ClusterSize = 3;
|
||||||
|
|
||||||
struct Cluster {
|
struct Cluster {
|
||||||
TTEntry entry[ClusterSize];
|
TTEntry entry[ClusterSize];
|
||||||
char padding[2]; // Align to a divisor of the cache line size
|
char padding[2]; // Pad to 32 bytes
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(CacheLineSize % sizeof(Cluster) == 0, "Cluster size incorrect");
|
static_assert(sizeof(Cluster) == 32, "Unexpected Cluster size");
|
||||||
|
|
||||||
public:
|
public:
|
||||||
~TranspositionTable() { free(mem); }
|
~TranspositionTable() { aligned_ttmem_free(mem); }
|
||||||
void new_search() { generation8 += 8; } // Lower 3 bits are used by PV flag and Bound
|
void new_search() { generation8 += 8; } // Lower 3 bits are used by PV flag and Bound
|
||||||
TTEntry* probe(const Key key, bool& found) const;
|
TTEntry* probe(const Key key, bool& found) const;
|
||||||
int hashfull() const;
|
int hashfull() const;
|
||||||
void resize(size_t mbSize);
|
void resize(size_t mbSize);
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
// The 32 lowest order bits of the key are used to get the index of the cluster
|
|
||||||
TTEntry* first_entry(const Key key) const {
|
TTEntry* first_entry(const Key key) const {
|
||||||
return &table[(uint32_t(key) * uint64_t(clusterCount)) >> 32].entry[0];
|
return &table[mul_hi64(key, clusterCount)].entry[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
+144
@@ -0,0 +1,144 @@
|
|||||||
|
/*
|
||||||
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
|
Copyright (C) 2004-2020 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 <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
#include "misc.h"
|
||||||
|
#include "uci.h"
|
||||||
|
|
||||||
|
using std::string;
|
||||||
|
|
||||||
|
bool Tune::update_on_last;
|
||||||
|
const UCI::Option* LastOption = nullptr;
|
||||||
|
BoolConditions Conditions;
|
||||||
|
static std::map<std::string, int> TuneResults;
|
||||||
|
|
||||||
|
string Tune::next(string& names, bool pop) {
|
||||||
|
|
||||||
|
string name;
|
||||||
|
|
||||||
|
do {
|
||||||
|
string token = names.substr(0, names.find(','));
|
||||||
|
|
||||||
|
if (pop)
|
||||||
|
names.erase(0, token.size() + 1);
|
||||||
|
|
||||||
|
std::stringstream ws(token);
|
||||||
|
name += (ws >> token, token); // Remove trailing whitespace
|
||||||
|
|
||||||
|
} while ( std::count(name.begin(), name.end(), '(')
|
||||||
|
- std::count(name.begin(), name.end(), ')'));
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_tune(const UCI::Option& o) {
|
||||||
|
|
||||||
|
if (!Tune::update_on_last || LastOption == &o)
|
||||||
|
Tune::read_options();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void make_option(const string& n, int v, const SetRange& r) {
|
||||||
|
|
||||||
|
// Do not generate option when there is nothing to tune (ie. min = max)
|
||||||
|
if (r(v).first == r(v).second)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (TuneResults.count(n))
|
||||||
|
v = TuneResults[n];
|
||||||
|
|
||||||
|
Options[n] << UCI::Option(v, r(v).first, r(v).second, on_tune);
|
||||||
|
LastOption = &Options[n];
|
||||||
|
|
||||||
|
// Print formatted parameters, ready to be copy-pasted in Fishtest
|
||||||
|
std::cout << n << ","
|
||||||
|
<< v << ","
|
||||||
|
<< r(v).first << "," << r(v).second << ","
|
||||||
|
<< (r(v).second - r(v).first) / 20.0 << ","
|
||||||
|
<< "0.0020"
|
||||||
|
<< std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<> void Tune::Entry<int>::init_option() { make_option(name, value, range); }
|
||||||
|
|
||||||
|
template<> void Tune::Entry<int>::read_option() {
|
||||||
|
if (Options.count(name))
|
||||||
|
value = int(Options[name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<> void Tune::Entry<Value>::init_option() { make_option(name, value, range); }
|
||||||
|
|
||||||
|
template<> void Tune::Entry<Value>::read_option() {
|
||||||
|
if (Options.count(name))
|
||||||
|
value = Value(int(Options[name]));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<> void Tune::Entry<Score>::init_option() {
|
||||||
|
make_option("m" + name, mg_value(value), range);
|
||||||
|
make_option("e" + name, eg_value(value), range);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<> void Tune::Entry<Score>::read_option() {
|
||||||
|
if (Options.count("m" + name))
|
||||||
|
value = make_score(int(Options["m" + name]), eg_value(value));
|
||||||
|
|
||||||
|
if (Options.count("e" + name))
|
||||||
|
value = make_score(mg_value(value), int(Options["e" + name]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instead of a variable here we have a PostUpdate function: just call it
|
||||||
|
template<> void Tune::Entry<Tune::PostUpdate>::init_option() {}
|
||||||
|
template<> void Tune::Entry<Tune::PostUpdate>::read_option() { value(); }
|
||||||
|
|
||||||
|
|
||||||
|
// Set binary conditions according to a probability that depends
|
||||||
|
// on the corresponding parameter value.
|
||||||
|
|
||||||
|
void BoolConditions::set() {
|
||||||
|
|
||||||
|
static PRNG rng(now());
|
||||||
|
static bool startup = true; // To workaround fishtest bench
|
||||||
|
|
||||||
|
for (size_t i = 0; i < binary.size(); i++)
|
||||||
|
binary[i] = !startup && (values[i] + int(rng.rand<unsigned>() % variance) > threshold);
|
||||||
|
|
||||||
|
startup = false;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < binary.size(); i++)
|
||||||
|
sync_cout << binary[i] << sync_endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Init options with tuning session results instead of default values. Useful to
|
||||||
|
// get correct bench signature after a tuning session or to test tuned values.
|
||||||
|
// Just copy fishtest tuning results in a result.txt file and extract the
|
||||||
|
// values with:
|
||||||
|
//
|
||||||
|
// cat results.txt | sed 's/^param: \([^,]*\), best: \([^,]*\).*/ TuneResults["\1"] = int(round(\2));/'
|
||||||
|
//
|
||||||
|
// Then paste the output below, as the function body
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
void Tune::read_results() {
|
||||||
|
|
||||||
|
/* ...insert your values here... */
|
||||||
|
}
|
||||||
+193
@@ -0,0 +1,193 @@
|
|||||||
|
/*
|
||||||
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
|
|
||||||
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Stockfish is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TUNE_H_INCLUDED
|
||||||
|
#define TUNE_H_INCLUDED
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
typedef std::pair<int, int> Range; // Option's min-max values
|
||||||
|
typedef Range (RangeFun) (int);
|
||||||
|
|
||||||
|
// Default Range function, to calculate Option's min-max values
|
||||||
|
inline Range default_range(int v) {
|
||||||
|
return v > 0 ? Range(0, 2 * v) : Range(2 * v, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SetRange {
|
||||||
|
explicit SetRange(RangeFun f) : fun(f) {}
|
||||||
|
SetRange(int min, int max) : fun(nullptr), range(min, max) {}
|
||||||
|
Range operator()(int v) const { return fun ? fun(v) : range; }
|
||||||
|
|
||||||
|
RangeFun* fun;
|
||||||
|
Range range;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SetDefaultRange SetRange(default_range)
|
||||||
|
|
||||||
|
|
||||||
|
/// BoolConditions struct is used to tune boolean conditions in the
|
||||||
|
/// code by toggling them on/off according to a probability that
|
||||||
|
/// depends on the value of a tuned integer parameter: for high
|
||||||
|
/// values of the parameter condition is always disabled, for low
|
||||||
|
/// values is always enabled, otherwise it is enabled with a given
|
||||||
|
/// probability that depnends on the parameter under tuning.
|
||||||
|
|
||||||
|
struct BoolConditions {
|
||||||
|
void init(size_t size) { values.resize(size, defaultValue), binary.resize(size, 0); }
|
||||||
|
void set();
|
||||||
|
|
||||||
|
std::vector<int> binary, values;
|
||||||
|
int defaultValue = 465, variance = 40, threshold = 500;
|
||||||
|
SetRange range = SetRange(0, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
extern BoolConditions Conditions;
|
||||||
|
|
||||||
|
inline void set_conditions() { Conditions.set(); }
|
||||||
|
|
||||||
|
|
||||||
|
/// Tune class implements the 'magic' code that makes the setup of a fishtest
|
||||||
|
/// tuning session as easy as it can be. Mainly you have just to remove const
|
||||||
|
/// qualifiers from the variables you want to tune and flag them for tuning, so
|
||||||
|
/// if you have:
|
||||||
|
///
|
||||||
|
/// const Score myScore = S(10, 15);
|
||||||
|
/// const Value myValue[][2] = { { V(100), V(20) }, { V(7), V(78) } };
|
||||||
|
///
|
||||||
|
/// If you have a my_post_update() function to run after values have been updated,
|
||||||
|
/// and a my_range() function to set custom Option's min-max values, then you just
|
||||||
|
/// remove the 'const' qualifiers and write somewhere below in the file:
|
||||||
|
///
|
||||||
|
/// TUNE(SetRange(my_range), myScore, myValue, my_post_update);
|
||||||
|
///
|
||||||
|
/// You can also set the range directly, and restore the default at the end
|
||||||
|
///
|
||||||
|
/// TUNE(SetRange(-100, 100), myScore, SetDefaultRange);
|
||||||
|
///
|
||||||
|
/// In case update function is slow and you have many parameters, you can add:
|
||||||
|
///
|
||||||
|
/// UPDATE_ON_LAST();
|
||||||
|
///
|
||||||
|
/// And the values update, including post update function call, will be done only
|
||||||
|
/// once, after the engine receives the last UCI option, that is the one defined
|
||||||
|
/// and created as the last one, so the GUI should send the options in the same
|
||||||
|
/// order in which have been defined.
|
||||||
|
|
||||||
|
class Tune {
|
||||||
|
|
||||||
|
typedef void (PostUpdate) (); // Post-update function
|
||||||
|
|
||||||
|
Tune() { read_results(); }
|
||||||
|
Tune(const Tune&) = delete;
|
||||||
|
void operator=(const Tune&) = delete;
|
||||||
|
void read_results();
|
||||||
|
|
||||||
|
static Tune& instance() { static Tune t; return t; } // Singleton
|
||||||
|
|
||||||
|
// Use polymorphism to accomodate Entry of different types in the same vector
|
||||||
|
struct EntryBase {
|
||||||
|
virtual ~EntryBase() = default;
|
||||||
|
virtual void init_option() = 0;
|
||||||
|
virtual void read_option() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct Entry : public EntryBase {
|
||||||
|
|
||||||
|
static_assert(!std::is_const<T>::value, "Parameter cannot be const!");
|
||||||
|
|
||||||
|
static_assert( std::is_same<T, int>::value
|
||||||
|
|| std::is_same<T, Value>::value
|
||||||
|
|| std::is_same<T, Score>::value
|
||||||
|
|| std::is_same<T, PostUpdate>::value, "Parameter type not supported!");
|
||||||
|
|
||||||
|
Entry(const std::string& n, T& v, const SetRange& r) : name(n), value(v), range(r) {}
|
||||||
|
void operator=(const Entry&) = delete; // Because 'value' is a reference
|
||||||
|
void init_option() override;
|
||||||
|
void read_option() override;
|
||||||
|
|
||||||
|
std::string name;
|
||||||
|
T& value;
|
||||||
|
SetRange range;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Our facilty to fill the container, each Entry corresponds to a parameter to tune.
|
||||||
|
// We use variadic templates to deal with an unspecified number of entries, each one
|
||||||
|
// of a possible different type.
|
||||||
|
static std::string next(std::string& names, bool pop = true);
|
||||||
|
|
||||||
|
int add(const SetRange&, std::string&&) { return 0; }
|
||||||
|
|
||||||
|
template<typename T, typename... Args>
|
||||||
|
int add(const SetRange& range, std::string&& names, T& value, Args&&... args) {
|
||||||
|
list.push_back(std::unique_ptr<EntryBase>(new Entry<T>(next(names), value, range)));
|
||||||
|
return add(range, std::move(names), args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Template specialization for arrays: recursively handle multi-dimensional arrays
|
||||||
|
template<typename T, size_t N, typename... Args>
|
||||||
|
int add(const SetRange& range, std::string&& names, T (&value)[N], Args&&... args) {
|
||||||
|
for (size_t i = 0; i < N; i++)
|
||||||
|
add(range, next(names, i == N - 1) + "[" + std::to_string(i) + "]", value[i]);
|
||||||
|
return add(range, std::move(names), args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Template specialization for SetRange
|
||||||
|
template<typename... Args>
|
||||||
|
int add(const SetRange&, std::string&& names, SetRange& value, Args&&... args) {
|
||||||
|
return add(value, (next(names), std::move(names)), args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Template specialization for BoolConditions
|
||||||
|
template<typename... Args>
|
||||||
|
int add(const SetRange& range, std::string&& names, BoolConditions& cond, Args&&... args) {
|
||||||
|
for (size_t size = cond.values.size(), i = 0; i < size; i++)
|
||||||
|
add(cond.range, next(names, i == size - 1) + "_" + std::to_string(i), cond.values[i]);
|
||||||
|
return add(range, std::move(names), args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<EntryBase>> list;
|
||||||
|
|
||||||
|
public:
|
||||||
|
template<typename... Args>
|
||||||
|
static int add(const std::string& names, Args&&... args) {
|
||||||
|
return instance().add(SetDefaultRange, names.substr(1, names.size() - 2), args...); // Remove trailing parenthesis
|
||||||
|
}
|
||||||
|
static void init() { for (auto& e : instance().list) e->init_option(); read_options(); } // Deferred, due to UCI::Options access
|
||||||
|
static void read_options() { for (auto& e : instance().list) e->read_option(); }
|
||||||
|
static bool update_on_last;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Some macro magic :-) we define a dummy int variable that compiler initializes calling Tune::add()
|
||||||
|
#define STRINGIFY(x) #x
|
||||||
|
#define UNIQUE2(x, y) x ## y
|
||||||
|
#define UNIQUE(x, y) UNIQUE2(x, y) // Two indirection levels to expand __LINE__
|
||||||
|
#define TUNE(...) int UNIQUE(p, __LINE__) = Tune::add(STRINGIFY((__VA_ARGS__)), __VA_ARGS__)
|
||||||
|
|
||||||
|
#define UPDATE_ON_LAST() bool UNIQUE(p, __LINE__) = Tune::update_on_last = true
|
||||||
|
|
||||||
|
// Some macro to tune toggling of boolean conditions
|
||||||
|
#define CONDITION(x) (Conditions.binary[__COUNTER__] || (x))
|
||||||
|
#define TUNE_CONDITIONS() int UNIQUE(c, __LINE__) = (Conditions.init(__COUNTER__), 0); \
|
||||||
|
TUNE(Conditions, set_conditions)
|
||||||
|
|
||||||
|
#endif // #ifndef TUNE_H_INCLUDED
|
||||||
+150
-29
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -40,7 +38,6 @@
|
|||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <climits>
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -176,14 +173,17 @@ enum Value : int {
|
|||||||
VALUE_INFINITE = 32001,
|
VALUE_INFINITE = 32001,
|
||||||
VALUE_NONE = 32002,
|
VALUE_NONE = 32002,
|
||||||
|
|
||||||
VALUE_MATE_IN_MAX_PLY = VALUE_MATE - 2 * MAX_PLY,
|
VALUE_TB_WIN_IN_MAX_PLY = VALUE_MATE - 2 * MAX_PLY,
|
||||||
VALUE_MATED_IN_MAX_PLY = -VALUE_MATE + 2 * MAX_PLY,
|
VALUE_TB_LOSS_IN_MAX_PLY = -VALUE_TB_WIN_IN_MAX_PLY,
|
||||||
|
VALUE_MATE_IN_MAX_PLY = VALUE_MATE - MAX_PLY,
|
||||||
|
VALUE_MATED_IN_MAX_PLY = -VALUE_MATE_IN_MAX_PLY,
|
||||||
|
|
||||||
PawnValueMg = 128, PawnValueEg = 213,
|
PawnValueMg = 124, PawnValueEg = 206,
|
||||||
KnightValueMg = 781, KnightValueEg = 854,
|
KnightValueMg = 781, KnightValueEg = 854,
|
||||||
BishopValueMg = 825, BishopValueEg = 915,
|
BishopValueMg = 825, BishopValueEg = 915,
|
||||||
RookValueMg = 1276, RookValueEg = 1380,
|
RookValueMg = 1276, RookValueEg = 1380,
|
||||||
QueenValueMg = 2538, QueenValueEg = 2682,
|
QueenValueMg = 2538, QueenValueEg = 2682,
|
||||||
|
Tempo = 28,
|
||||||
|
|
||||||
MidgameLimit = 15258, EndgameLimit = 3915
|
MidgameLimit = 15258, EndgameLimit = 3915
|
||||||
};
|
};
|
||||||
@@ -201,18 +201,38 @@ enum Piece {
|
|||||||
PIECE_NB = 16
|
PIECE_NB = 16
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Value PieceValue[PHASE_NB][PIECE_NB];
|
// An ID used to track the pieces. Max. 32 pieces on board.
|
||||||
|
enum PieceId {
|
||||||
|
PIECE_ID_ZERO = 0,
|
||||||
|
PIECE_ID_KING = 30,
|
||||||
|
PIECE_ID_WKING = 30,
|
||||||
|
PIECE_ID_BKING = 31,
|
||||||
|
PIECE_ID_NONE = 32
|
||||||
|
};
|
||||||
|
|
||||||
|
inline PieceId operator++(PieceId& d, int) {
|
||||||
|
|
||||||
|
PieceId x = d;
|
||||||
|
d = PieceId(int(d) + 1);
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr Value PieceValue[PHASE_NB][PIECE_NB] = {
|
||||||
|
{ VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg, VALUE_ZERO, VALUE_ZERO,
|
||||||
|
VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg, VALUE_ZERO, VALUE_ZERO },
|
||||||
|
{ VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg, VALUE_ZERO, VALUE_ZERO,
|
||||||
|
VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg, VALUE_ZERO, VALUE_ZERO }
|
||||||
|
};
|
||||||
|
|
||||||
typedef int Depth;
|
typedef int Depth;
|
||||||
|
|
||||||
enum : int {
|
enum : int {
|
||||||
|
|
||||||
DEPTH_QS_CHECKS = 0,
|
DEPTH_QS_CHECKS = 0,
|
||||||
DEPTH_QS_NO_CHECKS = -1,
|
DEPTH_QS_NO_CHECKS = -1,
|
||||||
DEPTH_QS_RECAPTURES = -5,
|
DEPTH_QS_RECAPTURES = -5,
|
||||||
|
|
||||||
DEPTH_NONE = -6,
|
DEPTH_NONE = -6,
|
||||||
DEPTH_OFFSET = DEPTH_NONE,
|
DEPTH_OFFSET = DEPTH_NONE
|
||||||
};
|
};
|
||||||
|
|
||||||
enum Square : int {
|
enum Square : int {
|
||||||
@@ -226,6 +246,7 @@ enum Square : int {
|
|||||||
SQ_A8, SQ_B8, SQ_C8, SQ_D8, SQ_E8, SQ_F8, SQ_G8, SQ_H8,
|
SQ_A8, SQ_B8, SQ_C8, SQ_D8, SQ_E8, SQ_F8, SQ_G8, SQ_H8,
|
||||||
SQ_NONE,
|
SQ_NONE,
|
||||||
|
|
||||||
|
SQUARE_ZERO = 0,
|
||||||
SQUARE_NB = 64
|
SQUARE_NB = 64
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -249,6 +270,94 @@ enum Rank : int {
|
|||||||
RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, RANK_NB
|
RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, RANK_NB
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// unique number for each piece type on each square
|
||||||
|
enum PieceSquare : uint32_t {
|
||||||
|
PS_NONE = 0,
|
||||||
|
PS_W_PAWN = 1,
|
||||||
|
PS_B_PAWN = 1 * SQUARE_NB + 1,
|
||||||
|
PS_W_KNIGHT = 2 * SQUARE_NB + 1,
|
||||||
|
PS_B_KNIGHT = 3 * SQUARE_NB + 1,
|
||||||
|
PS_W_BISHOP = 4 * SQUARE_NB + 1,
|
||||||
|
PS_B_BISHOP = 5 * SQUARE_NB + 1,
|
||||||
|
PS_W_ROOK = 6 * SQUARE_NB + 1,
|
||||||
|
PS_B_ROOK = 7 * SQUARE_NB + 1,
|
||||||
|
PS_W_QUEEN = 8 * SQUARE_NB + 1,
|
||||||
|
PS_B_QUEEN = 9 * SQUARE_NB + 1,
|
||||||
|
PS_W_KING = 10 * SQUARE_NB + 1,
|
||||||
|
PS_END = PS_W_KING, // pieces without kings (pawns included)
|
||||||
|
PS_B_KING = 11 * SQUARE_NB + 1,
|
||||||
|
PS_END2 = 12 * SQUARE_NB + 1
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ExtPieceSquare {
|
||||||
|
PieceSquare from[COLOR_NB];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Array for finding the PieceSquare corresponding to the piece on the board
|
||||||
|
extern ExtPieceSquare kpp_board_index[PIECE_NB];
|
||||||
|
|
||||||
|
constexpr bool is_ok(PieceId pid);
|
||||||
|
constexpr Square rotate180(Square sq);
|
||||||
|
|
||||||
|
// Structure holding which tracked piece (PieceId) is where (PieceSquare)
|
||||||
|
class EvalList {
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Max. number of pieces without kings is 30 but must be a multiple of 4 in AVX2
|
||||||
|
static const int MAX_LENGTH = 32;
|
||||||
|
|
||||||
|
// Array that holds the piece id for the pieces on the board
|
||||||
|
PieceId piece_id_list[SQUARE_NB];
|
||||||
|
|
||||||
|
// List of pieces, separate from White and Black POV
|
||||||
|
PieceSquare* piece_list_fw() const { return const_cast<PieceSquare*>(pieceListFw); }
|
||||||
|
PieceSquare* piece_list_fb() const { return const_cast<PieceSquare*>(pieceListFb); }
|
||||||
|
|
||||||
|
// Place the piece pc with piece_id on the square sq on the board
|
||||||
|
void put_piece(PieceId piece_id, Square sq, Piece pc)
|
||||||
|
{
|
||||||
|
assert(is_ok(piece_id));
|
||||||
|
if (pc != NO_PIECE)
|
||||||
|
{
|
||||||
|
pieceListFw[piece_id] = PieceSquare(kpp_board_index[pc].from[WHITE] + sq);
|
||||||
|
pieceListFb[piece_id] = PieceSquare(kpp_board_index[pc].from[BLACK] + rotate180(sq));
|
||||||
|
piece_id_list[sq] = piece_id;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pieceListFw[piece_id] = PS_NONE;
|
||||||
|
pieceListFb[piece_id] = PS_NONE;
|
||||||
|
piece_id_list[sq] = piece_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the specified piece_id piece to ExtPieceSquare type and return it
|
||||||
|
ExtPieceSquare piece_with_id(PieceId piece_id) const
|
||||||
|
{
|
||||||
|
ExtPieceSquare eps;
|
||||||
|
eps.from[WHITE] = pieceListFw[piece_id];
|
||||||
|
eps.from[BLACK] = pieceListFb[piece_id];
|
||||||
|
return eps;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
PieceSquare pieceListFw[MAX_LENGTH];
|
||||||
|
PieceSquare pieceListFb[MAX_LENGTH];
|
||||||
|
};
|
||||||
|
|
||||||
|
// For differential evaluation of pieces that changed since last turn
|
||||||
|
struct DirtyPiece {
|
||||||
|
|
||||||
|
// Number of changed pieces
|
||||||
|
int dirty_num;
|
||||||
|
|
||||||
|
// The ids of changed pieces, max. 2 pieces can change in one move
|
||||||
|
PieceId pieceId[2];
|
||||||
|
|
||||||
|
// What changed from the piece with that piece number
|
||||||
|
ExtPieceSquare old_piece[2];
|
||||||
|
ExtPieceSquare new_piece[2];
|
||||||
|
};
|
||||||
|
|
||||||
/// Score enum stores a middlegame and an endgame value in a single integer (enum).
|
/// Score enum stores a middlegame and an endgame value in a single integer (enum).
|
||||||
/// The least significant 16 bits are used to store the middlegame value and the
|
/// The least significant 16 bits are used to store the middlegame value and the
|
||||||
@@ -274,11 +383,11 @@ inline Value mg_value(Score s) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#define ENABLE_BASE_OPERATORS_ON(T) \
|
#define ENABLE_BASE_OPERATORS_ON(T) \
|
||||||
constexpr T operator+(T d1, T d2) { return T(int(d1) + int(d2)); } \
|
constexpr T operator+(T d1, int d2) { return T(int(d1) + d2); } \
|
||||||
constexpr T operator-(T d1, T d2) { return T(int(d1) - int(d2)); } \
|
constexpr T operator-(T d1, int d2) { return T(int(d1) - d2); } \
|
||||||
constexpr T operator-(T d) { return T(-int(d)); } \
|
constexpr T operator-(T d) { return T(-int(d)); } \
|
||||||
inline T& operator+=(T& d1, T d2) { return d1 = d1 + d2; } \
|
inline T& operator+=(T& d1, int d2) { return d1 = d1 + d2; } \
|
||||||
inline T& operator-=(T& d1, T d2) { return d1 = d1 - d2; }
|
inline T& operator-=(T& d1, int d2) { return d1 = d1 - d2; }
|
||||||
|
|
||||||
#define ENABLE_INCR_OPERATORS_ON(T) \
|
#define ENABLE_INCR_OPERATORS_ON(T) \
|
||||||
inline T& operator++(T& d) { return d = T(int(d) + 1); } \
|
inline T& operator++(T& d) { return d = T(int(d) + 1); } \
|
||||||
@@ -296,8 +405,10 @@ inline T& operator/=(T& d, int i) { return d = T(int(d) / i); }
|
|||||||
ENABLE_FULL_OPERATORS_ON(Value)
|
ENABLE_FULL_OPERATORS_ON(Value)
|
||||||
ENABLE_FULL_OPERATORS_ON(Direction)
|
ENABLE_FULL_OPERATORS_ON(Direction)
|
||||||
|
|
||||||
ENABLE_INCR_OPERATORS_ON(PieceType)
|
|
||||||
ENABLE_INCR_OPERATORS_ON(Piece)
|
ENABLE_INCR_OPERATORS_ON(Piece)
|
||||||
|
ENABLE_INCR_OPERATORS_ON(PieceSquare)
|
||||||
|
ENABLE_INCR_OPERATORS_ON(PieceId)
|
||||||
|
ENABLE_INCR_OPERATORS_ON(PieceType)
|
||||||
ENABLE_INCR_OPERATORS_ON(Square)
|
ENABLE_INCR_OPERATORS_ON(Square)
|
||||||
ENABLE_INCR_OPERATORS_ON(File)
|
ENABLE_INCR_OPERATORS_ON(File)
|
||||||
ENABLE_INCR_OPERATORS_ON(Rank)
|
ENABLE_INCR_OPERATORS_ON(Rank)
|
||||||
@@ -308,12 +419,6 @@ ENABLE_BASE_OPERATORS_ON(Score)
|
|||||||
#undef ENABLE_INCR_OPERATORS_ON
|
#undef ENABLE_INCR_OPERATORS_ON
|
||||||
#undef ENABLE_BASE_OPERATORS_ON
|
#undef ENABLE_BASE_OPERATORS_ON
|
||||||
|
|
||||||
/// Additional operators to add integers to a Value
|
|
||||||
constexpr Value operator+(Value v, int i) { return Value(int(v) + i); }
|
|
||||||
constexpr Value operator-(Value v, int i) { return Value(int(v) - i); }
|
|
||||||
inline Value& operator+=(Value& v, int i) { return v = v + i; }
|
|
||||||
inline Value& operator-=(Value& v, int i) { return v = v - i; }
|
|
||||||
|
|
||||||
/// Additional operators to add a Direction to a Square
|
/// Additional operators to add a Direction to a Square
|
||||||
constexpr Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); }
|
constexpr Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); }
|
||||||
constexpr Square operator-(Square s, Direction d) { return Square(int(s) - int(d)); }
|
constexpr Square operator-(Square s, Direction d) { return Square(int(s) - int(d)); }
|
||||||
@@ -343,23 +448,23 @@ inline Score operator*(Score s, int i) {
|
|||||||
|
|
||||||
/// Multiplication of a Score by a boolean
|
/// Multiplication of a Score by a boolean
|
||||||
inline Score operator*(Score s, bool b) {
|
inline Score operator*(Score s, bool b) {
|
||||||
return Score(int(s) * int(b));
|
return b ? s : SCORE_ZERO;
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr Color operator~(Color c) {
|
constexpr Color operator~(Color c) {
|
||||||
return Color(c ^ BLACK); // Toggle color
|
return Color(c ^ BLACK); // Toggle color
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr Square operator~(Square s) {
|
constexpr Square flip_rank(Square s) { // Swap A1 <-> A8
|
||||||
return Square(s ^ SQ_A8); // Vertical flip SQ_A1 -> SQ_A8
|
return Square(s ^ SQ_A8);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr Square flip_file(Square s) { // Swap A1 <-> H1
|
||||||
|
return Square(s ^ SQ_H1);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr Piece operator~(Piece pc) {
|
constexpr Piece operator~(Piece pc) {
|
||||||
return Piece(pc ^ 8); // Swap color of piece B_KNIGHT -> W_KNIGHT
|
return Piece(pc ^ 8); // Swap color of piece B_KNIGHT <-> W_KNIGHT
|
||||||
}
|
|
||||||
|
|
||||||
inline File map_to_queenside(File f) {
|
|
||||||
return std::min(f, File(FILE_H - f)); // Map files ABCDEFGH to files ABCDDCBA
|
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr CastlingRights operator&(Color c, CastlingRights cr) {
|
constexpr CastlingRights operator&(Color c, CastlingRights cr) {
|
||||||
@@ -391,6 +496,10 @@ inline Color color_of(Piece pc) {
|
|||||||
return Color(pc >> 3);
|
return Color(pc >> 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr bool is_ok(PieceId pid) {
|
||||||
|
return pid < PIECE_ID_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
constexpr bool is_ok(Square s) {
|
constexpr bool is_ok(Square s) {
|
||||||
return s >= SQ_A1 && s <= SQ_H8;
|
return s >= SQ_A1 && s <= SQ_H8;
|
||||||
}
|
}
|
||||||
@@ -427,6 +536,11 @@ constexpr Square to_sq(Move m) {
|
|||||||
return Square(m & 0x3F);
|
return Square(m & 0x3F);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return relative square when turning the board 180 degrees
|
||||||
|
constexpr Square rotate180(Square sq) {
|
||||||
|
return (Square)(sq ^ 0x3F);
|
||||||
|
}
|
||||||
|
|
||||||
constexpr int from_to(Move m) {
|
constexpr int from_to(Move m) {
|
||||||
return m & 0xFFF;
|
return m & 0xFFF;
|
||||||
}
|
}
|
||||||
@@ -456,4 +570,11 @@ constexpr bool is_ok(Move m) {
|
|||||||
return from_sq(m) != to_sq(m); // Catch MOVE_NULL and MOVE_NONE
|
return from_sq(m) != to_sq(m); // Catch MOVE_NULL and MOVE_NONE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Based on a congruential pseudo random number generator
|
||||||
|
constexpr Key make_key(uint64_t seed) {
|
||||||
|
return seed * 6364136223846793005ULL + 1442695040888963407ULL;
|
||||||
|
}
|
||||||
|
|
||||||
#endif // #ifndef TYPES_H_INCLUDED
|
#endif // #ifndef TYPES_H_INCLUDED
|
||||||
|
|
||||||
|
#include "tune.h" // Global visibility to tuning setup
|
||||||
|
|||||||
+58
-7
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,6 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <cmath>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -77,6 +76,20 @@ namespace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// trace_eval() prints the evaluation for the current position, consistent with the UCI
|
||||||
|
// options set so far.
|
||||||
|
|
||||||
|
void trace_eval(Position& pos) {
|
||||||
|
|
||||||
|
StateListPtr states(new std::deque<StateInfo>(1));
|
||||||
|
Position p;
|
||||||
|
p.set(pos.fen(), Options["UCI_Chess960"], &states->back(), Threads.main());
|
||||||
|
|
||||||
|
Eval::verify_NNUE();
|
||||||
|
|
||||||
|
sync_cout << "\n" << Eval::trace(p) << sync_endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// setoption() is called when engine receives the "setoption" UCI command. The
|
// setoption() is called when engine receives the "setoption" UCI command. The
|
||||||
// function updates the UCI option ("name") to the given value ("value").
|
// function updates the UCI option ("name") to the given value ("value").
|
||||||
@@ -115,7 +128,7 @@ namespace {
|
|||||||
limits.startTime = now(); // As early as possible!
|
limits.startTime = now(); // As early as possible!
|
||||||
|
|
||||||
while (is >> token)
|
while (is >> token)
|
||||||
if (token == "searchmoves")
|
if (token == "searchmoves") // Needs to be the last command on the line
|
||||||
while (is >> token)
|
while (is >> token)
|
||||||
limits.searchmoves.push_back(UCI::to_move(pos, token));
|
limits.searchmoves.push_back(UCI::to_move(pos, token));
|
||||||
|
|
||||||
@@ -165,7 +178,7 @@ namespace {
|
|||||||
nodes += Threads.nodes_searched();
|
nodes += Threads.nodes_searched();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
sync_cout << "\n" << Eval::trace(pos) << sync_endl;
|
trace_eval(pos);
|
||||||
}
|
}
|
||||||
else if (token == "setoption") setoption(is);
|
else if (token == "setoption") setoption(is);
|
||||||
else if (token == "position") position(pos, is, states);
|
else if (token == "position") position(pos, is, states);
|
||||||
@@ -182,6 +195,28 @@ namespace {
|
|||||||
<< "\nNodes/second : " << 1000 * nodes / elapsed << endl;
|
<< "\nNodes/second : " << 1000 * nodes / elapsed << endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The win rate model returns the probability (per mille) of winning given an eval
|
||||||
|
// and a game-ply. The model fits rather accurately the LTC fishtest statistics.
|
||||||
|
int win_rate_model(Value v, int ply) {
|
||||||
|
|
||||||
|
// The model captures only up to 240 plies, so limit input (and rescale)
|
||||||
|
double m = std::min(240, ply) / 64.0;
|
||||||
|
|
||||||
|
// Coefficients of a 3rd order polynomial fit based on fishtest data
|
||||||
|
// for two parameters needed to transform eval to the argument of a
|
||||||
|
// logistic function.
|
||||||
|
double as[] = {-8.24404295, 64.23892342, -95.73056462, 153.86478679};
|
||||||
|
double bs[] = {-3.37154371, 28.44489198, -56.67657741, 72.05858751};
|
||||||
|
double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3];
|
||||||
|
double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3];
|
||||||
|
|
||||||
|
// Transform eval to centipawns with limited range
|
||||||
|
double x = Utility::clamp(double(100 * v) / PawnValueEg, -1000.0, 1000.0);
|
||||||
|
|
||||||
|
// Return win rate in per mille (rounded to nearest)
|
||||||
|
return int(0.5 + 1000 / (1 + std::exp((a - x) / b)));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
|
||||||
@@ -238,7 +273,7 @@ void UCI::loop(int argc, char* argv[]) {
|
|||||||
else if (token == "flip") pos.flip();
|
else if (token == "flip") pos.flip();
|
||||||
else if (token == "bench") bench(pos, is, states);
|
else if (token == "bench") bench(pos, is, states);
|
||||||
else if (token == "d") sync_cout << pos << sync_endl;
|
else if (token == "d") sync_cout << pos << sync_endl;
|
||||||
else if (token == "eval") sync_cout << Eval::trace(pos) << sync_endl;
|
else if (token == "eval") trace_eval(pos);
|
||||||
else if (token == "compiler") sync_cout << compiler_info() << sync_endl;
|
else if (token == "compiler") sync_cout << compiler_info() << sync_endl;
|
||||||
else
|
else
|
||||||
sync_cout << "Unknown command: " << cmd << sync_endl;
|
sync_cout << "Unknown command: " << cmd << sync_endl;
|
||||||
@@ -260,7 +295,7 @@ string UCI::value(Value v) {
|
|||||||
|
|
||||||
stringstream ss;
|
stringstream ss;
|
||||||
|
|
||||||
if (abs(v) < VALUE_MATE - MAX_PLY)
|
if (abs(v) < VALUE_MATE_IN_MAX_PLY)
|
||||||
ss << "cp " << v * 100 / PawnValueEg;
|
ss << "cp " << v * 100 / PawnValueEg;
|
||||||
else
|
else
|
||||||
ss << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2;
|
ss << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2;
|
||||||
@@ -269,6 +304,22 @@ string UCI::value(Value v) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// UCI::wdl() report WDL statistics given an evaluation and a game ply, based on
|
||||||
|
/// data gathered for fishtest LTC games.
|
||||||
|
|
||||||
|
string UCI::wdl(Value v, int ply) {
|
||||||
|
|
||||||
|
stringstream ss;
|
||||||
|
|
||||||
|
int wdl_w = win_rate_model( v, ply);
|
||||||
|
int wdl_l = win_rate_model(-v, ply);
|
||||||
|
int wdl_d = 1000 - wdl_w - wdl_l;
|
||||||
|
ss << " wdl " << wdl_w << " " << wdl_d << " " << wdl_l;
|
||||||
|
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// UCI::square() converts a Square to a string in algebraic notation (g1, a7, etc.)
|
/// UCI::square() converts a Square to a string in algebraic notation (g1, a7, etc.)
|
||||||
|
|
||||||
std::string UCI::square(Square s) {
|
std::string UCI::square(Square s) {
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -73,6 +71,7 @@ std::string value(Value v);
|
|||||||
std::string square(Square s);
|
std::string square(Square s);
|
||||||
std::string move(Move m, bool chess960);
|
std::string move(Move m, bool chess960);
|
||||||
std::string pv(const Position& pos, Depth depth, Value alpha, Value beta);
|
std::string pv(const Position& pos, Depth depth, Value alpha, Value beta);
|
||||||
|
std::string wdl(Value v, int ply);
|
||||||
Move to_move(const Position& pos, std::string& str);
|
Move to_move(const Position& pos, std::string& str);
|
||||||
|
|
||||||
} // namespace UCI
|
} // namespace UCI
|
||||||
|
|||||||
+12
-12
@@ -1,8 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
|
Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
|
||||||
Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
|
|
||||||
Copyright (C) 2015-2020 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
|
|
||||||
|
|
||||||
Stockfish is free software: you can redistribute it and/or modify
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -38,11 +36,12 @@ namespace UCI {
|
|||||||
|
|
||||||
/// 'On change' actions, triggered by an option's value change
|
/// 'On change' actions, triggered by an option's value change
|
||||||
void on_clear_hash(const Option&) { Search::clear(); }
|
void on_clear_hash(const Option&) { Search::clear(); }
|
||||||
void on_hash_size(const Option& o) { TT.resize(o); }
|
void on_hash_size(const Option& o) { TT.resize(size_t(o)); }
|
||||||
void on_logger(const Option& o) { start_logger(o); }
|
void on_logger(const Option& o) { start_logger(o); }
|
||||||
void on_threads(const Option& o) { Threads.set(o); }
|
void on_threads(const Option& o) { Threads.set(size_t(o)); }
|
||||||
void on_tb_path(const Option& o) { Tablebases::init(o); }
|
void on_tb_path(const Option& o) { Tablebases::init(o); }
|
||||||
|
void on_use_NNUE(const Option& ) { Eval::init_NNUE(); }
|
||||||
|
void on_eval_file(const Option& ) { Eval::init_NNUE(); }
|
||||||
|
|
||||||
/// Our case insensitive less() function as required by UCI protocol
|
/// Our case insensitive less() function as required by UCI protocol
|
||||||
bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const {
|
bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const {
|
||||||
@@ -52,12 +51,11 @@ bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// init() initializes the UCI options to their hard-coded default values
|
/// UCI::init() initializes the UCI options to their hard-coded default values
|
||||||
|
|
||||||
void init(OptionsMap& o) {
|
void init(OptionsMap& o) {
|
||||||
|
|
||||||
// at most 2^32 clusters.
|
constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048;
|
||||||
constexpr int MaxHashMB = Is64Bit ? 131072 : 2048;
|
|
||||||
|
|
||||||
o["Debug Log File"] << Option("", on_logger);
|
o["Debug Log File"] << Option("", on_logger);
|
||||||
o["Contempt"] << Option(24, -100, 100);
|
o["Contempt"] << Option(24, -100, 100);
|
||||||
@@ -68,18 +66,20 @@ void init(OptionsMap& o) {
|
|||||||
o["Ponder"] << Option(false);
|
o["Ponder"] << Option(false);
|
||||||
o["MultiPV"] << Option(1, 1, 500);
|
o["MultiPV"] << Option(1, 1, 500);
|
||||||
o["Skill Level"] << Option(20, 0, 20);
|
o["Skill Level"] << Option(20, 0, 20);
|
||||||
o["Move Overhead"] << Option(30, 0, 5000);
|
o["Move Overhead"] << Option(10, 0, 5000);
|
||||||
o["Minimum Thinking Time"] << Option(20, 0, 5000);
|
o["Slow Mover"] << Option(100, 10, 1000);
|
||||||
o["Slow Mover"] << Option(84, 10, 1000);
|
|
||||||
o["nodestime"] << Option(0, 0, 10000);
|
o["nodestime"] << Option(0, 0, 10000);
|
||||||
o["UCI_Chess960"] << Option(false);
|
o["UCI_Chess960"] << Option(false);
|
||||||
o["UCI_AnalyseMode"] << Option(false);
|
o["UCI_AnalyseMode"] << Option(false);
|
||||||
o["UCI_LimitStrength"] << Option(false);
|
o["UCI_LimitStrength"] << Option(false);
|
||||||
o["UCI_Elo"] << Option(1350, 1350, 2850);
|
o["UCI_Elo"] << Option(1350, 1350, 2850);
|
||||||
|
o["UCI_ShowWDL"] << Option(false);
|
||||||
o["SyzygyPath"] << Option("<empty>", on_tb_path);
|
o["SyzygyPath"] << Option("<empty>", on_tb_path);
|
||||||
o["SyzygyProbeDepth"] << Option(1, 1, 100);
|
o["SyzygyProbeDepth"] << Option(1, 1, 100);
|
||||||
o["Syzygy50MoveRule"] << Option(true);
|
o["Syzygy50MoveRule"] << Option(true);
|
||||||
o["SyzygyProbeLimit"] << Option(7, 0, 7);
|
o["SyzygyProbeLimit"] << Option(7, 0, 7);
|
||||||
|
o["Use NNUE"] << Option(false, on_use_NNUE);
|
||||||
|
o["EvalFile"] << Option("nn-97f742aaefcd.nnue", on_eval_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user