stockfish-16.orig/ 0000755 0001750 0001750 00000000000 14523756712 012374 5 ustar pdm pdm stockfish-16.orig/tests/ 0000755 0001750 0001750 00000000000 14447216752 013536 5 ustar pdm pdm stockfish-16.orig/tests/perft.sh 0000755 0001750 0001750 00000002054 14447216752 015216 0 ustar pdm pdm #!/bin/bash
# verify perft numbers (positions from www.chessprogramming.org/Perft_Results)
error()
{
echo "perft testing failed on line $1"
exit 1
}
trap 'error ${LINENO}' ERR
echo "perft testing started"
cat << EOF > perft.exp
set timeout 10
lassign \$argv pos depth result
spawn ./stockfish
send "position \$pos\\ngo perft \$depth\\n"
expect "Nodes searched? \$result" {} timeout {exit 1}
send "quit\\n"
expect eof
EOF
expect perft.exp startpos 5 4865609 > /dev/null
expect perft.exp "fen r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq -" 5 193690690 > /dev/null
expect perft.exp "fen 8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - -" 6 11030083 > /dev/null
expect perft.exp "fen r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1" 5 15833292 > /dev/null
expect perft.exp "fen rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8" 5 89941194 > /dev/null
expect perft.exp "fen r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10" 5 164075551 > /dev/null
rm perft.exp
echo "perft testing OK"
stockfish-16.orig/tests/reprosearch.sh 0000755 0001750 0001750 00000002374 14447216752 016420 0 ustar pdm pdm #!/bin/bash
# verify reproducible search
error()
{
echo "reprosearch testing failed on line $1"
exit 1
}
trap 'error ${LINENO}' ERR
echo "reprosearch testing started"
# repeat two short games, separated by ucinewgame.
# with go nodes $nodes they should result in exactly
# the same node count for each iteration.
cat << EOF > repeat.exp
set timeout 10
spawn ./stockfish
lassign \$argv nodes
send "uci\n"
expect "uciok"
send "ucinewgame\n"
send "position startpos\n"
send "go nodes \$nodes\n"
expect "bestmove"
send "position startpos moves e2e4 e7e6\n"
send "go nodes \$nodes\n"
expect "bestmove"
send "ucinewgame\n"
send "position startpos\n"
send "go nodes \$nodes\n"
expect "bestmove"
send "position startpos moves e2e4 e7e6\n"
send "go nodes \$nodes\n"
expect "bestmove"
send "quit\n"
expect eof
EOF
# to increase the likelihood of finding a non-reproducible case,
# the allowed number of nodes are varied systematically
for i in `seq 1 20`
do
nodes=$((100*3**i/2**i))
echo "reprosearch testing with $nodes nodes"
# each line should appear exactly an even number of times
expect repeat.exp $nodes 2>&1 | grep -o "nodes [0-9]*" | sort | uniq -c | awk '{if ($1%2!=0) exit(1)}'
done
rm repeat.exp
echo "reprosearch testing OK"
stockfish-16.orig/tests/signature.sh 0000755 0001750 0001750 00000001376 14447216752 016105 0 ustar pdm pdm #!/bin/bash
# obtain and optionally verify Bench / signature
# if no reference is given, the output is deliberately limited to just the signature
error()
{
echo "running bench for signature failed on line $1"
exit 1
}
trap 'error ${LINENO}' ERR
# obtain
signature=`./stockfish bench 2>&1 | grep "Nodes searched : " | awk '{print $4}'`
if [ $# -gt 0 ]; then
# compare to given reference
if [ "$1" != "$signature" ]; then
if [ -z "$signature" ]; then
echo "No signature obtained from bench. Code crashed or assert triggered ?"
else
echo "signature mismatch: reference $1 obtained: $signature ."
fi
exit 1
else
echo "signature OK: $signature"
fi
else
# just report signature
echo $signature
fi
stockfish-16.orig/tests/instrumented.sh 0000755 0001750 0001750 00000006755 14447216752 016633 0 ustar pdm pdm #!/bin/bash
# check for errors under valgrind or sanitizers.
error()
{
echo "instrumented testing failed on line $1"
exit 1
}
trap 'error ${LINENO}' ERR
# define suitable post and prefixes for testing options
case $1 in
--valgrind)
echo "valgrind testing started"
prefix=''
exeprefix='valgrind --error-exitcode=42 --errors-for-leak-kinds=all --leak-check=full'
postfix='1>/dev/null'
threads="1"
;;
--valgrind-thread)
echo "valgrind-thread testing started"
prefix=''
exeprefix='valgrind --fair-sched=try --error-exitcode=42'
postfix='1>/dev/null'
threads="2"
;;
--sanitizer-undefined)
echo "sanitizer-undefined testing started"
prefix='!'
exeprefix=''
postfix='2>&1 | grep -A50 "runtime error:"'
threads="1"
;;
--sanitizer-thread)
echo "sanitizer-thread testing started"
prefix='!'
exeprefix=''
postfix='2>&1 | grep -A50 "WARNING: ThreadSanitizer:"'
threads="2"
cat << EOF > tsan.supp
race:Stockfish::TTEntry::move
race:Stockfish::TTEntry::depth
race:Stockfish::TTEntry::bound
race:Stockfish::TTEntry::save
race:Stockfish::TTEntry::value
race:Stockfish::TTEntry::eval
race:Stockfish::TTEntry::is_pv
race:Stockfish::TranspositionTable::probe
race:Stockfish::TranspositionTable::hashfull
EOF
export TSAN_OPTIONS="suppressions=./tsan.supp"
;;
*)
echo "unknown testing started"
prefix=''
exeprefix=''
postfix=''
threads="1"
;;
esac
# simple command line testing
for args in "eval" \
"go nodes 1000" \
"go depth 10" \
"go movetime 1000" \
"go wtime 8000 btime 8000 winc 500 binc 500" \
"bench 128 $threads 8 default depth" \
"export_net verify.nnue"
do
echo "$prefix $exeprefix ./stockfish $args $postfix"
eval "$prefix $exeprefix ./stockfish $args $postfix"
done
# verify the generated net equals the base net
network=`./stockfish uci | grep 'option name EvalFile type string default' | awk '{print $NF}'`
echo "Comparing $network to the written verify.nnue"
diff $network verify.nnue
# more general testing, following an uci protocol exchange
cat << EOF > game.exp
set timeout 240
spawn $exeprefix ./stockfish
send "uci\n"
expect "uciok"
send "setoption name Threads value $threads\n"
send "ucinewgame\n"
send "position startpos\n"
send "go nodes 1000\n"
expect "bestmove"
send "position startpos moves e2e4 e7e6\n"
send "go nodes 1000\n"
expect "bestmove"
send "position fen 5rk1/1K4p1/8/8/3B4/8/8/8 b - - 0 1\n"
send "go depth 10\n"
expect "bestmove"
send "quit\n"
expect eof
# return error code of the spawned program, useful for valgrind
lassign [wait] pid spawnid os_error_flag value
exit \$value
EOF
#download TB as needed
if [ ! -d ../tests/syzygy ]; then
curl -sL https://api.github.com/repos/niklasf/python-chess/tarball/9b9aa13f9f36d08aadfabff872882f4ab1494e95 | tar -xzf -
mv niklasf-python-chess-9b9aa13 ../tests/syzygy
fi
cat << EOF > syzygy.exp
set timeout 240
spawn $exeprefix ./stockfish
send "uci\n"
send "setoption name SyzygyPath value ../tests/syzygy/\n"
expect "info string Found 35 tablebases" {} timeout {exit 1}
send "bench 128 1 8 default depth\n"
send "quit\n"
expect eof
# return error code of the spawned program, useful for valgrind
lassign [wait] pid spawnid os_error_flag value
exit \$value
EOF
for exp in game.exp syzygy.exp
do
echo "$prefix expect $exp $postfix"
eval "$prefix expect $exp $postfix"
rm $exp
done
rm -f tsan.supp
echo "instrumented testing OK"
stockfish-16.orig/Top CPU Contributors.txt 0000644 0001750 0001750 00000042547 14447216752 017001 0 ustar pdm pdm Contributors to Fishtest with >10,000 CPU hours, as of 2023-06-20.
Thank you!
Username CPU Hours Games played
------------------------------------------------------------------
noobpwnftw 37457426 2850540907
technologov 14135647 742892808
linrock 4423514 303254809
mlang 3026000 200065824
dew 1689162 100033738
okrout 1578136 148855886
pemo 1508508 48814305
grandphish2 1461406 91540343
TueRens 1194790 70400852
JojoM 947612 61773190
tvijlbrief 796125 51897690
sebastronomy 742434 38218524
mibere 703840 46867607
gvreuls 651026 42988582
oz 543438 39314736
cw 517858 34869755
fastgm 503862 30260818
leszek 467278 33514883
CSU_Dynasty 464940 31177118
ctoks 434416 28506889
crunchy 427035 27344275
maximmasiutin 424795 26577722
bcross 415722 29060963
olafm 395922 32268020
rpngn 348378 24560289
velislav 342567 22138992
Fisherman 327231 21829379
mgrabiak 300612 20608380
Dantist 296386 18031762
nordlandia 246201 16189678
robal 241300 15656382
marrco 234581 17714473
ncfish1 227517 15233777
glinscott 208125 13277240
drabel 204167 13930674
mhoram 202894 12601997
bking_US 198894 11876016
Thanar 179852 12365359
vdv 175544 9904472
spams 157128 10319326
sqrt2 147963 9724586
DesolatedDodo 146350 9536172
Calis007 143165 9478764
vdbergh 138650 9064413
CoffeeOne 137100 5024116
armo9494 136191 9460264
malala 136182 8002293
xoto 133759 9159372
davar 129023 8376525
DMBK 122960 8980062
dsmith 122059 7570238
amicic 119661 7938029
Data 113305 8220352
BrunoBanani 112960 7436849
CypressChess 108331 7759788
skiminki 107583 7218170
jcAEie 105675 8238962
MaZePallas 102823 6633619
sterni1971 100532 5880772
sunu 100167 7040199
zeryl 99331 6221261
thirdlife 99124 2242380
ElbertoOne 99028 7023771
cuistot 98853 6069816
bigpen0r 94809 6529203
brabos 92118 6186135
Wolfgang 91939 6105872
psk 89957 5984901
sschnee 88235 5268000
racerschmacer 85805 6122790
Fifis 85722 5709729
Dubslow 84986 6042456
Vizvezdenec 83761 5344740
0x3C33 82614 5271253
BRAVONE 81239 5054681
nssy 76497 5259388
jromang 76106 5236025
teddybaer 75125 5407666
tolkki963 74762 5149662
megaman7de 74351 4940352
Wencey 74181 4711488
Pking_cda 73776 5293873
yurikvelo 73150 5004382
markkulix 72607 5304642
Bobo1239 70579 4794999
solarlight 70517 5028306
dv8silencer 70287 3883992
manap 66273 4121774
tinker 64333 4268790
qurashee 61208 3429862
Mineta 59357 4418202
Spprtr 58723 3911011
AGI 58147 4325994
robnjr 57262 4053117
Freja 56938 3733019
MaxKlaxxMiner 56879 3423958
MarcusTullius 56746 3762951
ttruscott 56010 3680085
rkl 55132 4164467
renouve 53811 3501516
javran 53785 4627608
finfish 51360 3370515
eva42 51272 3599691
eastorwest 51117 3454811
rap 49985 3219146
pb00067 49733 3298934
OuaisBla 48626 3445134
ronaldjerum 47654 3240695
biffhero 46564 3111352
VoyagerOne 45476 3452465
jmdana 44893 3065205
maposora 44597 4039578
oryx 44570 3454238
speedycpu 43842 3003273
jbwiebe 43305 2805433
GPUex 42378 3133332
Antihistamine 41788 2761312
mhunt 41735 2691355
homyur 39893 2850481
gri 39871 2515779
Garf 37741 2999686
SC 37299 2731694
csnodgrass 36207 2688994
strelock 34716 2074055
szupaw 34102 2880346
EthanOConnor 33370 2090311
slakovv 32915 2021889
Gelma 31771 1551204
gopeto 31671 2060990
kdave 31157 2198362
manapbk 30987 1810399
Prcuvu 30377 2170122
anst 30301 2190091
jkiiski 30136 1904470
spcc 29925 1901692
hyperbolic.tom 29840 2017394
chuckstablers 29659 2093438
Pyafue 29650 1902349
belzedar94 28846 1811530
chriswk 26902 1868317
xwziegtm 26897 2124586
achambord 26582 1767323
Patrick_G 26276 1801617
yorkman 26193 1992080
Ulysses 25288 1689730
SFTUser 25182 1675689
nabildanial 24942 1519409
Sharaf_DG 24765 1786697
Maxim 24705 1502062
rodneyc 24376 1416402
agg177 23890 1395014
Goatminola 23763 1956036
Ente 23639 1671638
Jopo12321 23467 1483172
JanErik 23408 1703875
Isidor 23388 1680691
Norabor 23371 1603244
cisco2015 22920 1763301
jsys14 22824 1591906
Zirie 22542 1472937
team-oh 22272 1636708
Roady 22220 1465606
MazeOfGalious 21978 1629593
sg4032 21947 1643353
ianh2105 21725 1632562
xor12 21628 1680365
dex 21612 1467203
nesoneg 21494 1463031
user213718 21454 1404128
sphinx 21211 1384728
AndreasKrug 21097 1634811
jjoshua2 21001 1423089
Zake9298 20938 1565848
horst.prack 20878 1465656
0xB00B1ES 20590 1208666
j3corre 20405 941444
Adrian.Schmidt123 20316 1281436
wei 19973 1745989
notchris 19958 1800128
Serpensin 19840 1697528
Gaster319 19712 1677310
fishtester 19617 1257388
rstoesser 19569 1293588
eudhan 19274 1283717
votoanthuan 19108 1609992
vulcan 18871 1729392
Karpovbot 18766 1053178
qoo_charly_cai 18543 1284937
jundery 18445 1115855
ville 17883 1384026
chris 17698 1487385
purplefishies 17595 1092533
dju 17414 981289
iisiraider 17275 1049015
DragonLord 17014 1162790
redstone59 16842 1461780
Alb11747 16787 1213926
IgorLeMasson 16064 1147232
Karby 15982 979610
scuzzi 15757 968735
ako027ako 15671 1173203
Nikolay.IT 15154 1068349
Andrew Grant 15114 895539
Naven94 15054 834762
OssumOpossum 14857 1007129
ZacHFX 14783 1021842
enedene 14476 905279
bpfliegel 14233 882523
mpx86 14019 759568
jpulman 13982 870599
Skiff84 13826 721996
crocogoat 13803 1117422
Nesa92 13786 1114691
joster 13710 946160
mbeier 13650 1044928
Hjax 13535 915487
Nullvalue 13468 1140498
Dark_wizzie 13422 1007152
Rudolphous 13244 883140
pirt 13100 1009897
Machariel 13010 863104
infinigon 12991 943216
mabichito 12903 749391
thijsk 12886 722107
AdrianSA 12860 804972
Flopzee 12698 894821
korposzczur 12606 838168
fatmurphy 12547 853210
SapphireBrand 12416 969604
Oakwen 12399 844109
deflectooor 12386 579392
modolief 12386 896470
Farseer 12249 694108
Jackfish 12213 805008
pgontarz 12151 848794
dbernier 12103 860824
getraideBFF 12072 1024966
stocky 11954 699440
mschmidt 11941 803401
MooTheCow 11870 773598
FormazChar 11766 885707
whelanh 11557 245188
3cho 11494 1031076
infinity 11470 727027
aga 11412 695127
torbjo 11395 729145
Thomas A. Anderson 11372 732094
savage84 11358 670860
d64 11263 789184
ali-al-zhrani 11245 779246
snicolet 11106 869170
dapper 11032 771402
ols 10947 624903
Karmatron 10828 677458
basepi 10637 744851
Cubox 10621 826448
michaelrpg 10509 739239
OIVAS7572 10420 995586
jojo2357 10419 929708
WoodMan777 10380 873720
Garruk 10365 706465
dzjp 10343 732529
stockfish-16.orig/Copying.txt 0000644 0001750 0001750 00000105755 14447216752 014562 0 ustar pdm pdm GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Copyright (C)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
Copyright (C)
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
.
stockfish-16.orig/.gitignore 0000644 0001750 0001750 00000000222 14447216752 014360 0 ustar pdm pdm # Files from build
**/*.o
**/*.s
src/.depend
# Built binary
src/stockfish*
src/-lstdc++.res
# Neural network for the NNUE evaluation
**/*.nnue
stockfish-16.orig/src/ 0000755 0001750 0001750 00000000000 14523757657 013174 5 ustar pdm pdm stockfish-16.orig/src/material.cpp 0000644 0001750 0001750 00000021314 14447216752 015466 0 ustar pdm pdm /*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include
#include // For std::memset
#include "material.h"
#include "thread.h"
using namespace std;
namespace Stockfish {
namespace {
#define S(mg, eg) make_score(mg, eg)
// Polynomial material imbalance parameters
// One Score parameter for each pair (our piece, another of our pieces)
constexpr Score QuadraticOurs[][PIECE_TYPE_NB] = {
// OUR PIECE 2
// bishop pair pawn knight bishop rook queen
{S(1419, 1455) }, // Bishop pair
{S( 101, 28), S( 37, 39) }, // Pawn
{S( 57, 64), S(249, 187), S(-49, -62) }, // Knight OUR PIECE 1
{S( 0, 0), S(118, 137), S( 10, 27), S( 0, 0) }, // Bishop
{S( -63, -68), S( -5, 3), S(100, 81), S(132, 118), S(-246, -244) }, // Rook
{S(-210, -211), S( 37, 14), S(147, 141), S(161, 105), S(-158, -174), S(-9,-31) } // Queen
};
// One Score parameter for each pair (our piece, their piece)
constexpr Score QuadraticTheirs[][PIECE_TYPE_NB] = {
// THEIR PIECE
// bishop pair pawn knight bishop rook queen
{ }, // Bishop pair
{S( 33, 30) }, // Pawn
{S( 46, 18), S(106, 84) }, // Knight OUR PIECE
{S( 75, 35), S( 59, 44), S( 60, 15) }, // Bishop
{S( 26, 35), S( 6, 22), S( 38, 39), S(-12, -2) }, // Rook
{S( 97, 93), S(100, 163), S(-58, -91), S(112, 192), S(276, 225) } // Queen
};
#undef S
// Endgame evaluation and scaling functions are accessed directly and not through
// the function maps because they correspond to more than one material hash key.
Endgame EvaluateKXK[] = { Endgame(WHITE), Endgame(BLACK) };
Endgame ScaleKBPsK[] = { Endgame(WHITE), Endgame(BLACK) };
Endgame ScaleKQKRPs[] = { Endgame(WHITE), Endgame(BLACK) };
Endgame ScaleKPsK[] = { Endgame(WHITE), Endgame(BLACK) };
Endgame ScaleKPKP[] = { Endgame(WHITE), Endgame(BLACK) };
// Helper used to detect a given material distribution
bool is_KXK(const Position& pos, Color us) {
return !more_than_one(pos.pieces(~us))
&& pos.non_pawn_material(us) >= RookValueMg;
}
bool is_KBPsK(const Position& pos, Color us) {
return pos.non_pawn_material(us) == BishopValueMg
&& pos.count(us) >= 1;
}
bool is_KQKRPs(const Position& pos, Color us) {
return !pos.count(us)
&& pos.non_pawn_material(us) == QueenValueMg
&& pos.count(~us) == 1
&& pos.count(~us) >= 1;
}
/// imbalance() calculates the imbalance by comparing the piece count of each
/// piece type for both colors.
template
Score imbalance(const int pieceCount[][PIECE_TYPE_NB]) {
constexpr Color Them = ~Us;
Score bonus = SCORE_ZERO;
// Second-degree polynomial material imbalance, by Tord Romstad
for (int pt1 = NO_PIECE_TYPE; pt1 <= QUEEN; ++pt1)
{
if (!pieceCount[Us][pt1])
continue;
int v = QuadraticOurs[pt1][pt1] * pieceCount[Us][pt1];
for (int pt2 = NO_PIECE_TYPE; pt2 < pt1; ++pt2)
v += QuadraticOurs[pt1][pt2] * pieceCount[Us][pt2]
+ QuadraticTheirs[pt1][pt2] * pieceCount[Them][pt2];
bonus += pieceCount[Us][pt1] * v;
}
return bonus;
}
} // namespace
namespace Material {
/// Material::probe() looks up the current position's material configuration in
/// the material hash table. It returns a pointer to the Entry if the position
/// is found. Otherwise a new Entry is computed and stored there, so we don't
/// have to recompute all when the same material configuration occurs again.
Entry* probe(const Position& pos) {
Key key = pos.material_key();
Entry* e = pos.this_thread()->materialTable[key];
if (e->key == key)
return e;
std::memset(e, 0, sizeof(Entry));
e->key = key;
e->factor[WHITE] = e->factor[BLACK] = (uint8_t)SCALE_FACTOR_NORMAL;
Value npm_w = pos.non_pawn_material(WHITE);
Value npm_b = pos.non_pawn_material(BLACK);
Value npm = std::clamp(npm_w + npm_b, EndgameLimit, MidgameLimit);
// Map total non-pawn material into [PHASE_ENDGAME, PHASE_MIDGAME]
e->gamePhase = Phase(((npm - EndgameLimit) * PHASE_MIDGAME) / (MidgameLimit - EndgameLimit));
// Let's look if we have a specialized evaluation function for this particular
// material configuration. Firstly we look for a fixed configuration one, then
// for a generic one if the previous search failed.
if ((e->evaluationFunction = Endgames::probe(key)) != nullptr)
return e;
for (Color c : { WHITE, BLACK })
if (is_KXK(pos, c))
{
e->evaluationFunction = &EvaluateKXK[c];
return e;
}
// OK, we didn't find any special evaluation function for the current material
// configuration. Is there a suitable specialized scaling function?
const auto* sf = Endgames::probe(key);
if (sf)
{
e->scalingFunction[sf->strongSide] = sf; // Only strong color assigned
return e;
}
// We didn't find any specialized scaling function, so fall back on generic
// ones that refer to more than one material distribution. Note that in this
// case we don't return after setting the function.
for (Color c : { WHITE, BLACK })
{
if (is_KBPsK(pos, c))
e->scalingFunction[c] = &ScaleKBPsK[c];
else if (is_KQKRPs(pos, c))
e->scalingFunction[c] = &ScaleKQKRPs[c];
}
if (npm_w + npm_b == VALUE_ZERO && pos.pieces(PAWN)) // Only pawns on the board
{
if (!pos.count(BLACK))
{
assert(pos.count(WHITE) >= 2);
e->scalingFunction[WHITE] = &ScaleKPsK[WHITE];
}
else if (!pos.count(WHITE))
{
assert(pos.count(BLACK) >= 2);
e->scalingFunction[BLACK] = &ScaleKPsK[BLACK];
}
else if (pos.count(WHITE) == 1 && pos.count(BLACK) == 1)
{
// This is a special case because we set scaling functions
// for both colors instead of only one.
e->scalingFunction[WHITE] = &ScaleKPKP[WHITE];
e->scalingFunction[BLACK] = &ScaleKPKP[BLACK];
}
}
// Zero or just one pawn makes it difficult to win, even with a small material
// advantage. This catches some trivial draws like KK, KBK and KNK and gives a
// drawish scale factor for cases such as KRKBP and KmmKm (except for KBBKN).
if (!pos.count(WHITE) && npm_w - npm_b <= BishopValueMg)
e->factor[WHITE] = uint8_t(npm_w < RookValueMg ? SCALE_FACTOR_DRAW :
npm_b <= BishopValueMg ? 4 : 14);
if (!pos.count(BLACK) && npm_b - npm_w <= BishopValueMg)
e->factor[BLACK] = uint8_t(npm_b < RookValueMg ? SCALE_FACTOR_DRAW :
npm_w <= BishopValueMg ? 4 : 14);
// Evaluate the material imbalance. We use PIECE_TYPE_NONE as a place holder
// for the bishop pair "extended piece", which allows us to be more flexible
// in defining bishop pair bonuses.
const int pieceCount[COLOR_NB][PIECE_TYPE_NB] = {
{ pos.count(WHITE) > 1, pos.count(WHITE), pos.count(WHITE),
pos.count(WHITE) , pos.count(WHITE), pos.count(WHITE) },
{ pos.count(BLACK) > 1, pos.count(BLACK), pos.count(BLACK),
pos.count(BLACK) , pos.count(BLACK), pos.count(BLACK) } };
e->score = (imbalance(pieceCount) - imbalance(pieceCount)) / 16;
return e;
}
} // namespace Material
} // namespace Stockfish
stockfish-16.orig/src/psqt.h 0000644 0001750 0001750 00000002026 14447216752 014323 0 ustar pdm pdm /*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#ifndef PSQT_H_INCLUDED
#define PSQT_H_INCLUDED
#include "types.h"
namespace Stockfish::PSQT
{
extern Score psq[PIECE_NB][SQUARE_NB];
// Fill psqt array from a set of internally linked parameters
void init();
} // namespace Stockfish::PSQT
#endif // PSQT_H_INCLUDED
stockfish-16.orig/src/misc.h 0000644 0001750 0001750 00000012740 14447216752 014273 0 ustar pdm pdm /*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#ifndef MISC_H_INCLUDED
#define MISC_H_INCLUDED
#include
#include
#include
#include
#include
#include
#include "types.h"
#define stringify2(x) #x
#define stringify(x) stringify2(x)
namespace Stockfish {
std::string engine_info(bool to_uci = false);
std::string compiler_info();
void prefetch(void* addr);
void start_logger(const std::string& fname);
void* std_aligned_alloc(size_t alignment, size_t size);
void std_aligned_free(void* ptr);
void* aligned_large_pages_alloc(size_t size); // memory aligned by page size, min alignment: 4096 bytes
void aligned_large_pages_free(void* mem); // nop if mem == nullptr
void dbg_hit_on(bool cond, int slot = 0);
void dbg_mean_of(int64_t value, int slot = 0);
void dbg_stdev_of(int64_t value, int slot = 0);
void dbg_correl_of(int64_t value1, int64_t value2, int slot = 0);
void dbg_print();
using TimePoint = std::chrono::milliseconds::rep; // A value in milliseconds
static_assert(sizeof(TimePoint) == sizeof(int64_t), "TimePoint should be 64 bits");
inline TimePoint now() {
return std::chrono::duration_cast
(std::chrono::steady_clock::now().time_since_epoch()).count();
}
template
struct HashTable {
Entry* operator[](Key key) { return &table[(uint32_t)key & (Size - 1)]; }
private:
std::vector table = std::vector(Size); // Allocate on the heap
};
enum SyncCout { IO_LOCK, IO_UNLOCK };
std::ostream& operator<<(std::ostream&, SyncCout);
#define sync_cout std::cout << IO_LOCK
#define sync_endl std::endl << IO_UNLOCK
// align_ptr_up() : get the first aligned element of an array.
// ptr must point to an array of size at least `sizeof(T) * N + alignment` bytes,
// where N is the number of elements in the array.
template
T* align_ptr_up(T* ptr)
{
static_assert(alignof(T) < Alignment);
const uintptr_t ptrint = reinterpret_cast(reinterpret_cast(ptr));
return reinterpret_cast(reinterpret_cast((ptrint + (Alignment - 1)) / Alignment * Alignment));
}
// IsLittleEndian : true if and only if the binary is compiled on a little endian machine
static inline const union { uint32_t i; char c[4]; } Le = { 0x01020304 };
static inline const bool IsLittleEndian = (Le.c[0] == 4);
template
class ValueList {
public:
std::size_t size() const { return size_; }
void push_back(const T& value) { values_[size_++] = value; }
const T* begin() const { return values_; }
const T* end() const { return values_ + size_; }
private:
T values_[MaxSize];
std::size_t size_ = 0;
};
/// xorshift64star Pseudo-Random Number Generator
/// This class is based on original code written and dedicated
/// to the public domain by Sebastiano Vigna (2014).
/// It has the following characteristics:
///
/// - Outputs 64-bit numbers
/// - Passes Dieharder and SmallCrush test batteries
/// - Does not require warm-up, no zeroland to escape
/// - Internal state is a single 64-bit integer
/// - Period is 2^64 - 1
/// - Speed: 1.60 ns/call (Core i7 @3.40GHz)
///
/// For further analysis see
///
class PRNG {
uint64_t s;
uint64_t rand64() {
s ^= s >> 12, s ^= s << 25, s ^= s >> 27;
return s * 2685821657736338717LL;
}
public:
PRNG(uint64_t seed) : s(seed) { assert(seed); }
template T rand() { return T(rand64()); }
/// Special generator used to fast init magic numbers.
/// Output values only have 1/8th of their bits set on average.
template T sparse_rand()
{ return T(rand64() & rand64() & rand64()); }
};
inline uint64_t mul_hi64(uint64_t a, uint64_t b) {
#if defined(__GNUC__) && defined(IS_64BIT)
__extension__ using uint128 = unsigned __int128;
return ((uint128)a * (uint128)b) >> 64;
#else
uint64_t aL = (uint32_t)a, aH = a >> 32;
uint64_t bL = (uint32_t)b, bH = b >> 32;
uint64_t c1 = (aL * bL) >> 32;
uint64_t c2 = aH * bL + c1;
uint64_t c3 = aL * bH + (uint32_t)c2;
return aH * bH + (c2 >> 32) + (c3 >> 32);
#endif
}
/// Under Windows it is not possible for a process to run on more than one
/// logical processor group. This usually means to be limited to use max 64
/// cores. To overcome this, some special platform specific API should be
/// called to set group affinity for each thread. Original code from Texel by
/// Peter Österlund.
namespace WinProcGroup {
void bindThisThread(size_t idx);
}
namespace CommandLine {
void init(int argc, char* argv[]);
extern std::string binaryDirectory; // path of the executable directory
extern std::string workingDirectory; // path of the working directory
}
} // namespace Stockfish
#endif // #ifndef MISC_H_INCLUDED
stockfish-16.orig/src/misc.cpp 0000644 0001750 0001750 00000053361 14447216752 014632 0 ustar pdm pdm /*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#ifdef _WIN32
#if _WIN32_WINNT < 0x0601
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x0601 // Force to include needed API prototypes
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include
// The needed Windows API for processor groups could be missed from old Windows
// versions, so instead of calling them directly (forcing the linker to resolve
// the calls at compile time), try to load them at runtime. To do this we need
// first to define the corresponding function pointers.
extern "C" {
using fun1_t = bool(*)(LOGICAL_PROCESSOR_RELATIONSHIP,
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, PDWORD);
using fun2_t = bool(*)(USHORT, PGROUP_AFFINITY);
using fun3_t = bool(*)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY);
using fun4_t = bool(*)(USHORT, PGROUP_AFFINITY, USHORT, PUSHORT);
using fun5_t = WORD(*)();
using fun6_t = bool(*)(HANDLE, DWORD, PHANDLE);
using fun7_t = bool(*)(LPCSTR, LPCSTR, PLUID);
using fun8_t = bool(*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, PDWORD);
}
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#if defined(__linux__) && !defined(__ANDROID__)
#include
#include
#endif
#if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__) || (defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC) && !defined(_WIN32)) || defined(__e2k__)
#define POSIXALIGNEDALLOC
#include
#endif
#include "misc.h"
#include "thread.h"
using namespace std;
namespace Stockfish {
namespace {
/// Version number or dev.
constexpr string_view version = "16";
/// Our fancy logging facility. The trick here is to replace cin.rdbuf() and
/// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We
/// can toggle the logging of std::cout and std:cin at runtime whilst preserving
/// usual I/O functionality, all without changing a single line of code!
/// Idea from http://groups.google.com/group/comp.lang.c++/msg/1d941c0f26ea0d81
struct Tie: public streambuf { // MSVC requires split streambuf for cin and cout
Tie(streambuf* b, streambuf* l) : buf(b), logBuf(l) {}
int sync() override { return logBuf->pubsync(), buf->pubsync(); }
int overflow(int c) override { return log(buf->sputc((char)c), "<< "); }
int underflow() override { return buf->sgetc(); }
int uflow() override { return log(buf->sbumpc(), ">> "); }
streambuf *buf, *logBuf;
int log(int c, const char* prefix) {
static int last = '\n'; // Single log file
if (last == '\n')
logBuf->sputn(prefix, 3);
return last = logBuf->sputc((char)c);
}
};
class Logger {
Logger() : in(cin.rdbuf(), file.rdbuf()), out(cout.rdbuf(), file.rdbuf()) {}
~Logger() { start(""); }
ofstream file;
Tie in, out;
public:
static void start(const std::string& fname) {
static Logger l;
if (l.file.is_open())
{
cout.rdbuf(l.out.buf);
cin.rdbuf(l.in.buf);
l.file.close();
}
if (!fname.empty())
{
l.file.open(fname, ifstream::out);
if (!l.file.is_open())
{
cerr << "Unable to open debug log file " << fname << endl;
exit(EXIT_FAILURE);
}
cin.rdbuf(&l.in);
cout.rdbuf(&l.out);
}
}
};
} // namespace
/// engine_info() returns the full name of the current Stockfish version.
/// For local dev compiles we try to append the commit sha and commit date
/// from git if that fails only the local compilation date is set and "nogit" is specified:
/// Stockfish dev-YYYYMMDD-SHA
/// or
/// Stockfish dev-YYYYMMDD-nogit
///
/// For releases (non dev builds) we only include the version number:
/// Stockfish version
string engine_info(bool to_uci) {
stringstream ss;
ss << "Stockfish " << version << setfill('0');
if constexpr (version == "dev")
{
ss << "-";
#ifdef GIT_DATE
ss << stringify(GIT_DATE);
#else
constexpr string_view months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec");
string month, day, year;
stringstream date(__DATE__); // From compiler, format is "Sep 21 2008"
date >> month >> day >> year;
ss << year << setw(2) << setfill('0') << (1 + months.find(month) / 4) << setw(2) << setfill('0') << day;
#endif
ss << "-";
#ifdef GIT_SHA
ss << stringify(GIT_SHA);
#else
ss << "nogit";
#endif
}
ss << (to_uci ? "\nid author ": " by ")
<< "the Stockfish developers (see AUTHORS file)";
return ss.str();
}
/// compiler_info() returns a string trying to describe the compiler we use
std::string compiler_info() {
#define make_version_string(major, minor, patch) stringify(major) "." stringify(minor) "." stringify(patch)
/// Predefined macros hell:
///
/// __GNUC__ Compiler is gcc, Clang or Intel on Linux
/// __INTEL_COMPILER Compiler is Intel
/// _MSC_VER Compiler is MSVC or Intel on Windows
/// _WIN32 Building on Windows (any)
/// _WIN64 Building on Windows 64 bit
std::string compiler = "\nCompiled by ";
#ifdef __clang__
compiler += "clang++ ";
compiler += make_version_string(__clang_major__, __clang_minor__, __clang_patchlevel__);
#elif __INTEL_COMPILER
compiler += "Intel compiler ";
compiler += "(version ";
compiler += stringify(__INTEL_COMPILER) " update " stringify(__INTEL_COMPILER_UPDATE);
compiler += ")";
#elif _MSC_VER
compiler += "MSVC ";
compiler += "(version ";
compiler += stringify(_MSC_FULL_VER) "." stringify(_MSC_BUILD);
compiler += ")";
#elif defined(__e2k__) && defined(__LCC__)
#define dot_ver2(n) \
compiler += (char)'.'; \
compiler += (char)('0' + (n) / 10); \
compiler += (char)('0' + (n) % 10);
compiler += "MCST LCC ";
compiler += "(version ";
compiler += std::to_string(__LCC__ / 100);
dot_ver2(__LCC__ % 100)
dot_ver2(__LCC_MINOR__)
compiler += ")";
#elif __GNUC__
compiler += "g++ (GNUC) ";
compiler += make_version_string(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__);
#else
compiler += "Unknown compiler ";
compiler += "(unknown version)";
#endif
#if defined(__APPLE__)
compiler += " on Apple";
#elif defined(__CYGWIN__)
compiler += " on Cygwin";
#elif defined(__MINGW64__)
compiler += " on MinGW64";
#elif defined(__MINGW32__)
compiler += " on MinGW32";
#elif defined(__ANDROID__)
compiler += " on Android";
#elif defined(__linux__)
compiler += " on Linux";
#elif defined(_WIN64)
compiler += " on Microsoft Windows 64-bit";
#elif defined(_WIN32)
compiler += " on Microsoft Windows 32-bit";
#else
compiler += " on unknown system";
#endif
compiler += "\nCompilation settings include: ";
compiler += (Is64Bit ? " 64bit" : " 32bit");
#if defined(USE_VNNI)
compiler += " VNNI";
#endif
#if defined(USE_AVX512)
compiler += " AVX512";
#endif
compiler += (HasPext ? " BMI2" : "");
#if defined(USE_AVX2)
compiler += " AVX2";
#endif
#if defined(USE_SSE41)
compiler += " SSE41";
#endif
#if defined(USE_SSSE3)
compiler += " SSSE3";
#endif
#if defined(USE_SSE2)
compiler += " SSE2";
#endif
compiler += (HasPopCnt ? " POPCNT" : "");
#if defined(USE_MMX)
compiler += " MMX";
#endif
#if defined(USE_NEON)
compiler += " NEON";
#endif
#if !defined(NDEBUG)
compiler += " DEBUG";
#endif
compiler += "\n__VERSION__ macro expands to: ";
#ifdef __VERSION__
compiler += __VERSION__;
#else
compiler += "(undefined macro)";
#endif
compiler += "\n";
return compiler;
}
/// Debug functions used mainly to collect run-time statistics
constexpr int MaxDebugSlots = 32;
namespace {
template
struct DebugInfo {
std::atomic data[N] = { 0 };
constexpr inline std::atomic& operator[](int index) { return data[index]; }
};
DebugInfo<2> hit[MaxDebugSlots];
DebugInfo<2> mean[MaxDebugSlots];
DebugInfo<3> stdev[MaxDebugSlots];
DebugInfo<6> correl[MaxDebugSlots];
} // namespace
void dbg_hit_on(bool cond, int slot) {
++hit[slot][0];
if (cond)
++hit[slot][1];
}
void dbg_mean_of(int64_t value, int slot) {
++mean[slot][0];
mean[slot][1] += value;
}
void dbg_stdev_of(int64_t value, int slot) {
++stdev[slot][0];
stdev[slot][1] += value;
stdev[slot][2] += value * value;
}
void dbg_correl_of(int64_t value1, int64_t value2, int slot) {
++correl[slot][0];
correl[slot][1] += value1;
correl[slot][2] += value1 * value1;
correl[slot][3] += value2;
correl[slot][4] += value2 * value2;
correl[slot][5] += value1 * value2;
}
void dbg_print() {
int64_t n;
auto E = [&n](int64_t x) { return double(x) / n; };
auto sqr = [](double x) { return x * x; };
for (int i = 0; i < MaxDebugSlots; ++i)
if ((n = hit[i][0]))
std::cerr << "Hit #" << i
<< ": Total " << n << " Hits " << hit[i][1]
<< " Hit Rate (%) " << 100.0 * E(hit[i][1])
<< std::endl;
for (int i = 0; i < MaxDebugSlots; ++i)
if ((n = mean[i][0]))
{
std::cerr << "Mean #" << i
<< ": Total " << n << " Mean " << E(mean[i][1])
<< std::endl;
}
for (int i = 0; i < MaxDebugSlots; ++i)
if ((n = stdev[i][0]))
{
double r = sqrtl(E(stdev[i][2]) - sqr(E(stdev[i][1])));
std::cerr << "Stdev #" << i
<< ": Total " << n << " Stdev " << r
<< std::endl;
}
for (int i = 0; i < MaxDebugSlots; ++i)
if ((n = correl[i][0]))
{
double r = (E(correl[i][5]) - E(correl[i][1]) * E(correl[i][3]))
/ ( sqrtl(E(correl[i][2]) - sqr(E(correl[i][1])))
* sqrtl(E(correl[i][4]) - sqr(E(correl[i][3]))));
std::cerr << "Correl. #" << i
<< ": Total " << n << " Coefficient " << r
<< std::endl;
}
}
/// Used to serialize access to std::cout to avoid multiple threads writing at
/// the same time.
std::ostream& operator<<(std::ostream& os, SyncCout sc) {
static std::mutex m;
if (sc == IO_LOCK)
m.lock();
if (sc == IO_UNLOCK)
m.unlock();
return os;
}
/// Trampoline helper to avoid moving Logger to misc.h
void start_logger(const std::string& fname) { Logger::start(fname); }
/// prefetch() preloads the given address in L1/L2 cache. This is a non-blocking
/// function that doesn't stall the CPU waiting for data to be loaded from memory,
/// which can be quite slow.
#ifdef NO_PREFETCH
void prefetch(void*) {}
#else
void prefetch(void* addr) {
# if defined(__INTEL_COMPILER)
// This hack prevents prefetches from being optimized away by
// Intel compiler. Both MSVC and gcc seem not be affected by this.
__asm__ ("");
# endif
# if defined(__INTEL_COMPILER) || defined(_MSC_VER)
_mm_prefetch((char*)addr, _MM_HINT_T0);
# else
__builtin_prefetch(addr);
# endif
}
#endif
/// std_aligned_alloc() is our wrapper for systems where the c++17 implementation
/// does not guarantee the availability of aligned_alloc(). Memory allocated with
/// std_aligned_alloc() must be freed with std_aligned_free().
void* std_aligned_alloc(size_t alignment, size_t size) {
#if defined(POSIXALIGNEDALLOC)
void *mem;
return posix_memalign(&mem, alignment, size) ? nullptr : mem;
#elif defined(_WIN32) && !defined(_M_ARM) && !defined(_M_ARM64)
return _mm_malloc(size, alignment);
#elif defined(_WIN32)
return _aligned_malloc(size, alignment);
#else
return std::aligned_alloc(alignment, size);
#endif
}
void std_aligned_free(void* ptr) {
#if defined(POSIXALIGNEDALLOC)
free(ptr);
#elif defined(_WIN32) && !defined(_M_ARM) && !defined(_M_ARM64)
_mm_free(ptr);
#elif defined(_WIN32)
_aligned_free(ptr);
#else
free(ptr);
#endif
}
/// aligned_large_pages_alloc() will return suitably aligned memory, if possible using large pages.
#if defined(_WIN32)
static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize) {
#if !defined(_WIN64)
return nullptr;
#else
HANDLE hProcessToken { };
LUID luid { };
void* mem = nullptr;
const size_t largePageSize = GetLargePageMinimum();
if (!largePageSize)
return nullptr;
// Dynamically link OpenProcessToken, LookupPrivilegeValue and AdjustTokenPrivileges
HMODULE hAdvapi32 = GetModuleHandle(TEXT("advapi32.dll"));
if (!hAdvapi32)
hAdvapi32 = LoadLibrary(TEXT("advapi32.dll"));
auto fun6 = (fun6_t)(void(*)())GetProcAddress(hAdvapi32, "OpenProcessToken");
if (!fun6)
return nullptr;
auto fun7 = (fun7_t)(void(*)())GetProcAddress(hAdvapi32, "LookupPrivilegeValueA");
if (!fun7)
return nullptr;
auto fun8 = (fun8_t)(void(*)())GetProcAddress(hAdvapi32, "AdjustTokenPrivileges");
if (!fun8)
return nullptr;
// We need SeLockMemoryPrivilege, so try to enable it for the process
if (!fun6( // OpenProcessToken()
GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken))
return nullptr;
if (fun7( // LookupPrivilegeValue(nullptr, SE_LOCK_MEMORY_NAME, &luid)
nullptr, "SeLockMemoryPrivilege", &luid))
{
TOKEN_PRIVILEGES tp { };
TOKEN_PRIVILEGES prevTp { };
DWORD prevTpLen = 0;
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
// Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() succeeds,
// we still need to query GetLastError() to ensure that the privileges were actually obtained.
if (fun8( // AdjustTokenPrivileges()
hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, &prevTpLen) &&
GetLastError() == ERROR_SUCCESS)
{
// Round up size to full pages and allocate
allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1);
mem = VirtualAlloc(
nullptr, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE);
// Privilege no longer needed, restore previous state
fun8( // AdjustTokenPrivileges ()
hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr);
}
}
CloseHandle(hProcessToken);
return mem;
#endif
}
void* aligned_large_pages_alloc(size_t allocSize) {
// Try to allocate large pages
void* mem = aligned_large_pages_alloc_windows(allocSize);
// Fall back to regular, page aligned, allocation if necessary
if (!mem)
mem = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
return mem;
}
#else
void* aligned_large_pages_alloc(size_t allocSize) {
#if defined(__linux__)
constexpr size_t alignment = 2 * 1024 * 1024; // assumed 2MB page size
#else
constexpr size_t alignment = 4096; // assumed small page size
#endif
// round up to multiples of alignment
size_t size = ((allocSize + alignment - 1) / alignment) * alignment;
void *mem = std_aligned_alloc(alignment, size);
#if defined(MADV_HUGEPAGE)
madvise(mem, size, MADV_HUGEPAGE);
#endif
return mem;
}
#endif
/// aligned_large_pages_free() will free the previously allocated ttmem
#if defined(_WIN32)
void aligned_large_pages_free(void* mem) {
if (mem && !VirtualFree(mem, 0, MEM_RELEASE))
{
DWORD err = GetLastError();
std::cerr << "Failed to free large page memory. Error code: 0x"
<< std::hex << err
<< std::dec << std::endl;
exit(EXIT_FAILURE);
}
}
#else
void aligned_large_pages_free(void *mem) {
std_aligned_free(mem);
}
#endif
namespace WinProcGroup {
#ifndef _WIN32
void bindThisThread(size_t) {}
#else
/// best_node() retrieves logical processor information using Windows specific
/// API and returns the best node id for the thread with index idx. Original
/// code from Texel by Peter Österlund.
static int best_node(size_t idx) {
int threads = 0;
int nodes = 0;
int cores = 0;
DWORD returnLength = 0;
DWORD byteOffset = 0;
// Early exit if the needed API is not available at runtime
HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll"));
auto fun1 = (fun1_t)(void(*)())GetProcAddress(k32, "GetLogicalProcessorInformationEx");
if (!fun1)
return -1;
// First call to GetLogicalProcessorInformationEx() to get returnLength.
// We expect the call to fail due to null buffer.
if (fun1(RelationAll, nullptr, &returnLength))
return -1;
// Once we know returnLength, allocate the buffer
SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *buffer, *ptr;
ptr = buffer = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)malloc(returnLength);
// Second call to GetLogicalProcessorInformationEx(), now we expect to succeed
if (!fun1(RelationAll, buffer, &returnLength))
{
free(buffer);
return -1;
}
while (byteOffset < returnLength)
{
if (ptr->Relationship == RelationNumaNode)
nodes++;
else if (ptr->Relationship == RelationProcessorCore)
{
cores++;
threads += (ptr->Processor.Flags == LTP_PC_SMT) ? 2 : 1;
}
assert(ptr->Size);
byteOffset += ptr->Size;
ptr = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)(((char*)ptr) + ptr->Size);
}
free(buffer);
std::vector groups;
// Run as many threads as possible on the same node until core limit is
// reached, then move on filling the next node.
for (int n = 0; n < nodes; n++)
for (int i = 0; i < cores / nodes; i++)
groups.push_back(n);
// In case a core has more than one logical processor (we assume 2) and we
// have still threads to allocate, then spread them evenly across available
// nodes.
for (int t = 0; t < threads - cores; t++)
groups.push_back(t % nodes);
// If we still have more threads than the total number of logical processors
// then return -1 and let the OS to decide what to do.
return idx < groups.size() ? groups[idx] : -1;
}
/// bindThisThread() set the group affinity of the current thread
void bindThisThread(size_t idx) {
// Use only local variables to be thread-safe
int node = best_node(idx);
if (node == -1)
return;
// Early exit if the needed API are not available at runtime
HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll"));
auto fun2 = (fun2_t)(void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMaskEx");
auto fun3 = (fun3_t)(void(*)())GetProcAddress(k32, "SetThreadGroupAffinity");
auto fun4 = (fun4_t)(void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMask2");
auto fun5 = (fun5_t)(void(*)())GetProcAddress(k32, "GetMaximumProcessorGroupCount");
if (!fun2 || !fun3)
return;
if (!fun4 || !fun5)
{
GROUP_AFFINITY affinity;
if (fun2(node, &affinity)) // GetNumaNodeProcessorMaskEx
fun3(GetCurrentThread(), &affinity, nullptr); // SetThreadGroupAffinity
}
else
{
// If a numa node has more than one processor group, we assume they are
// sized equal and we spread threads evenly across the groups.
USHORT elements, returnedElements;
elements = fun5(); // GetMaximumProcessorGroupCount
GROUP_AFFINITY *affinity = (GROUP_AFFINITY*)malloc(elements * sizeof(GROUP_AFFINITY));
if (fun4(node, affinity, elements, &returnedElements)) // GetNumaNodeProcessorMask2
fun3(GetCurrentThread(), &affinity[idx % returnedElements], nullptr); // SetThreadGroupAffinity
free(affinity);
}
}
#endif
} // namespace WinProcGroup
#ifdef _WIN32
#include
#define GETCWD _getcwd
#else
#include
#define GETCWD getcwd
#endif
namespace CommandLine {
string argv0; // path+name of the executable binary, as given by argv[0]
string binaryDirectory; // path of the executable directory
string workingDirectory; // path of the working directory
void init([[maybe_unused]] int argc, char* argv[]) {
string pathSeparator;
// extract the path+name of the executable binary
argv0 = argv[0];
#ifdef _WIN32
pathSeparator = "\\";
#ifdef _MSC_VER
// Under windows argv[0] may not have the extension. Also _get_pgmptr() had
// issues in some windows 10 versions, so check returned values carefully.
char* pgmptr = nullptr;
if (!_get_pgmptr(&pgmptr) && pgmptr != nullptr && *pgmptr)
argv0 = pgmptr;
#endif
#else
pathSeparator = "/";
#endif
// extract the working directory
workingDirectory = "";
char buff[40000];
char* cwd = GETCWD(buff, 40000);
if (cwd)
workingDirectory = cwd;
// extract the binary directory path from argv0
binaryDirectory = argv0;
size_t pos = binaryDirectory.find_last_of("\\/");
if (pos == std::string::npos)
binaryDirectory = "." + pathSeparator;
else
binaryDirectory.resize(pos + 1);
// pattern replacement: "./" at the start of path is replaced by the working directory
if (binaryDirectory.find("." + pathSeparator) == 0)
binaryDirectory.replace(0, 1, workingDirectory);
}
} // namespace CommandLine
} // namespace Stockfish
stockfish-16.orig/src/thread.h 0000644 0001750 0001750 00000007667 14447216752 014623 0 ustar pdm pdm /*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#ifndef THREAD_H_INCLUDED
#define THREAD_H_INCLUDED
#include
#include
#include
#include
#include
#include "material.h"
#include "movepick.h"
#include "pawns.h"
#include "position.h"
#include "search.h"
#include "thread_win32_osx.h"
namespace Stockfish {
/// Thread class keeps together all the thread-related stuff. We use
/// per-thread pawn and material hash tables so that once we get a
/// pointer to an entry its life time is unlimited and we don't have
/// to care about someone changing the entry under our feet.
class Thread {
std::mutex mutex;
std::condition_variable cv;
size_t idx;
bool exit = false, searching = true; // Set before starting std::thread
NativeThread stdThread;
public:
explicit Thread(size_t);
virtual ~Thread();
virtual void search();
void clear();
void idle_loop();
void start_searching();
void wait_for_search_finished();
size_t id() const { return idx; }
Pawns::Table pawnsTable;
Material::Table materialTable;
size_t pvIdx, pvLast;
std::atomic nodes, tbHits, bestMoveChanges;
int selDepth, nmpMinPly;
Value bestValue, optimism[COLOR_NB];
Position rootPos;
StateInfo rootState;
Search::RootMoves rootMoves;
Depth rootDepth, completedDepth;
Value rootDelta;
CounterMoveHistory counterMoves;
ButterflyHistory mainHistory;
CapturePieceToHistory captureHistory;
ContinuationHistory continuationHistory[2][2];
};
/// MainThread is a derived class specific for main thread
struct MainThread : public Thread {
using Thread::Thread;
void search() override;
void check_time();
double previousTimeReduction;
Value bestPreviousScore;
Value bestPreviousAverageScore;
Value iterValue[4];
int callsCnt;
bool stopOnPonderhit;
std::atomic_bool ponder;
};
/// ThreadPool struct handles all the threads-related stuff like init, starting,
/// parking and, most importantly, launching a thread. All the access to threads
/// is done through this class.
struct ThreadPool {
void start_thinking(Position&, StateListPtr&, const Search::LimitsType&, bool = false);
void clear();
void set(size_t);
MainThread* main() const { return static_cast(threads.front()); }
uint64_t nodes_searched() const { return accumulate(&Thread::nodes); }
uint64_t tb_hits() const { return accumulate(&Thread::tbHits); }
Thread* get_best_thread() const;
void start_searching();
void wait_for_search_finished() const;
std::atomic_bool stop, increaseDepth;
auto cbegin() const noexcept { return threads.cbegin(); }
auto begin() noexcept { return threads.begin(); }
auto end() noexcept { return threads.end(); }
auto cend() const noexcept { return threads.cend(); }
auto size() const noexcept { return threads.size(); }
auto empty() const noexcept { return threads.empty(); }
private:
StateListPtr setupStates;
std::vector threads;
uint64_t accumulate(std::atomic Thread::* member) const {
uint64_t sum = 0;
for (Thread* th : threads)
sum += (th->*member).load(std::memory_order_relaxed);
return sum;
}
};
extern ThreadPool Threads;
} // namespace Stockfish
#endif // #ifndef THREAD_H_INCLUDED
stockfish-16.orig/src/benchmark.cpp 0000644 0001750 0001750 00000015210 14447216752 015620 0 ustar pdm pdm /*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include "benchmark.h"
#include
#include
#include
#include
#include "position.h"
using namespace std;
namespace {
const vector Defaults = {
"setoption name UCI_Chess960 value false",
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
"r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 10",
"8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 11",
"4rrk1/pp1n3p/3q2pQ/2p1pb2/2PP4/2P3N1/P2B2PP/4RRK1 b - - 7 19",
"rq3rk1/ppp2ppp/1bnpb3/3N2B1/3NP3/7P/PPPQ1PP1/2KR3R w - - 7 14 moves d4e6",
"r1bq1r1k/1pp1n1pp/1p1p4/4p2Q/4Pp2/1BNP4/PPP2PPP/3R1RK1 w - - 2 14 moves g2g4",
"r3r1k1/2p2ppp/p1p1bn2/8/1q2P3/2NPQN2/PPP3PP/R4RK1 b - - 2 15",
"r1bbk1nr/pp3p1p/2n5/1N4p1/2Np1B2/8/PPP2PPP/2KR1B1R w kq - 0 13",
"r1bq1rk1/ppp1nppp/4n3/3p3Q/3P4/1BP1B3/PP1N2PP/R4RK1 w - - 1 16",
"4r1k1/r1q2ppp/ppp2n2/4P3/5Rb1/1N1BQ3/PPP3PP/R5K1 w - - 1 17",
"2rqkb1r/ppp2p2/2npb1p1/1N1Nn2p/2P1PP2/8/PP2B1PP/R1BQK2R b KQ - 0 11",
"r1bq1r1k/b1p1npp1/p2p3p/1p6/3PP3/1B2NN2/PP3PPP/R2Q1RK1 w - - 1 16",
"3r1rk1/p5pp/bpp1pp2/8/q1PP1P2/b3P3/P2NQRPP/1R2B1K1 b - - 6 22",
"r1q2rk1/2p1bppp/2Pp4/p6b/Q1PNp3/4B3/PP1R1PPP/2K4R w - - 2 18",
"4k2r/1pb2ppp/1p2p3/1R1p4/3P4/2r1PN2/P4PPP/1R4K1 b - - 3 22",
"3q2k1/pb3p1p/4pbp1/2r5/PpN2N2/1P2P2P/5PP1/Q2R2K1 b - - 4 26",
"6k1/6p1/6Pp/ppp5/3pn2P/1P3K2/1PP2P2/3N4 b - - 0 1",
"3b4/5kp1/1p1p1p1p/pP1PpP1P/P1P1P3/3KN3/8/8 w - - 0 1",
"2K5/p7/7P/5pR1/8/5k2/r7/8 w - - 0 1 moves g5g6 f3e3 g6g5 e3f3",
"8/6pk/1p6/8/PP3p1p/5P2/4KP1q/3Q4 w - - 0 1",
"7k/3p2pp/4q3/8/4Q3/5Kp1/P6b/8 w - - 0 1",
"8/2p5/8/2kPKp1p/2p4P/2P5/3P4/8 w - - 0 1",
"8/1p3pp1/7p/5P1P/2k3P1/8/2K2P2/8 w - - 0 1",
"8/pp2r1k1/2p1p3/3pP2p/1P1P1P1P/P5KR/8/8 w - - 0 1",
"8/3p4/p1bk3p/Pp6/1Kp1PpPp/2P2P1P/2P5/5B2 b - - 0 1",
"5k2/7R/4P2p/5K2/p1r2P1p/8/8/8 b - - 0 1",
"6k1/6p1/P6p/r1N5/5p2/7P/1b3PP1/4R1K1 w - - 0 1",
"1r3k2/4q3/2Pp3b/3Bp3/2Q2p2/1p1P2P1/1P2KP2/3N4 w - - 0 1",
"6k1/4pp1p/3p2p1/P1pPb3/R7/1r2P1PP/3B1P2/6K1 w - - 0 1",
"8/3p3B/5p2/5P2/p7/PP5b/k7/6K1 w - - 0 1",
"5rk1/q6p/2p3bR/1pPp1rP1/1P1Pp3/P3B1Q1/1K3P2/R7 w - - 93 90",
"4rrk1/1p1nq3/p7/2p1P1pp/3P2bp/3Q1Bn1/PPPB4/1K2R1NR w - - 40 21",
"r3k2r/3nnpbp/q2pp1p1/p7/Pp1PPPP1/4BNN1/1P5P/R2Q1RK1 w kq - 0 16",
"3Qb1k1/1r2ppb1/pN1n2q1/Pp1Pp1Pr/4P2p/4BP2/4B1R1/1R5K b - - 11 40",
"4k3/3q1r2/1N2r1b1/3ppN2/2nPP3/1B1R2n1/2R1Q3/3K4 w - - 5 1",
// 5-man positions
"8/8/8/8/5kp1/P7/8/1K1N4 w - - 0 1", // Kc2 - mate
"8/8/8/5N2/8/p7/8/2NK3k w - - 0 1", // Na2 - mate
"8/3k4/8/8/8/4B3/4KB2/2B5 w - - 0 1", // draw
// 6-man positions
"8/8/1P6/5pr1/8/4R3/7k/2K5 w - - 0 1", // Re5 - mate
"8/2p4P/8/kr6/6R1/8/8/1K6 w - - 0 1", // Ka2 - mate
"8/8/3P3k/8/1p6/8/1P6/1K3n2 b - - 0 1", // Nd2 - draw
// 7-man positions
"8/R7/2q5/8/6k1/8/1P5p/K6R w - - 0 124", // Draw
// Mate and stalemate positions
"6k1/3b3r/1p1p4/p1n2p2/1PPNpP1q/P3Q1p1/1R1RB1P1/5K2 b - - 0 1",
"r2r1n2/pp2bk2/2p1p2p/3q4/3PN1QP/2P3R1/P4PP1/5RK1 w - - 0 1",
"8/8/8/8/8/6k1/6p1/6K1 w - -",
"7k/7P/6K1/8/3B4/8/8/8 b - -",
// Chess 960
"setoption name UCI_Chess960 value true",
"bbqnnrkr/pppppppp/8/8/8/8/PPPPPPPP/BBQNNRKR w HFhf - 0 1 moves g2g3 d7d5 d2d4 c8h3 c1g5 e8d6 g5e7 f7f6",
"nqbnrkrb/pppppppp/8/8/8/8/PPPPPPPP/NQBNRKRB w KQkq - 0 1",
"setoption name UCI_Chess960 value false"
};
} // namespace
namespace Stockfish {
/// setup_bench() builds a list of UCI commands to be run by bench. There
/// are five parameters: TT size in MB, number of search threads that
/// should be used, the limit value spent for each position, a file name
/// where to look for positions in FEN format, the type of the limit:
/// depth, perft, nodes and movetime (in millisecs), and evaluation type
/// mixed (default), classical, NNUE.
///
/// bench -> search default positions up to depth 13
/// bench 64 1 15 -> search default positions up to depth 15 (TT = 64MB)
/// bench 64 4 5000 current movetime -> search current position with 4 threads for 5 sec
/// bench 64 1 100000 default nodes -> search default positions for 100K nodes each
/// bench 16 1 5 default perft -> run a perft 5 on default positions
vector setup_bench(const Position& current, istream& is) {
vector fens, list;
string go, token;
// Assign default values to missing arguments
string ttSize = (is >> token) ? token : "16";
string threads = (is >> token) ? token : "1";
string limit = (is >> token) ? token : "13";
string fenFile = (is >> token) ? token : "default";
string limitType = (is >> token) ? token : "depth";
string evalType = (is >> token) ? token : "mixed";
go = limitType == "eval" ? "eval" : "go " + limitType + " " + limit;
if (fenFile == "default")
fens = Defaults;
else if (fenFile == "current")
fens.push_back(current.fen());
else
{
string fen;
ifstream file(fenFile);
if (!file.is_open())
{
cerr << "Unable to open file " << fenFile << endl;
exit(EXIT_FAILURE);
}
while (getline(file, fen))
if (!fen.empty())
fens.push_back(fen);
file.close();
}
list.emplace_back("setoption name Threads value " + threads);
list.emplace_back("setoption name Hash value " + ttSize);
list.emplace_back("ucinewgame");
size_t posCounter = 0;
for (const string& fen : fens)
if (fen.find("setoption") != string::npos)
list.emplace_back(fen);
else
{
if (evalType == "classical" || (evalType == "mixed" && posCounter % 2 == 0))
list.emplace_back("setoption name Use NNUE value false");
else if (evalType == "NNUE" || (evalType == "mixed" && posCounter % 2 != 0))
list.emplace_back("setoption name Use NNUE value true");
list.emplace_back("position fen " + fen);
list.emplace_back(go);
++posCounter;
}
list.emplace_back("setoption name Use NNUE value true");
return list;
}
} // namespace Stockfish
stockfish-16.orig/src/syzygy/ 0000755 0001750 0001750 00000000000 14447216752 014541 5 ustar pdm pdm stockfish-16.orig/src/syzygy/tbprobe.cpp 0000644 0001750 0001750 00000170017 14447216752 016710 0 ustar pdm pdm /*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include
#include
#include
#include // For std::memset and std::memcpy
#include
#include
#include
#include
#include
#include
#include
#include
#include "../bitboard.h"
#include "../movegen.h"
#include "../position.h"
#include "../search.h"
#include "../types.h"
#include "../uci.h"
#include "tbprobe.h"
#ifndef _WIN32
#include
#include
#include
#include
#else
#define WIN32_LEAN_AND_MEAN
#ifndef NOMINMAX
# define NOMINMAX // Disable macros min() and max()
#endif
#include
#endif
using namespace Stockfish::Tablebases;
int Stockfish::Tablebases::MaxCardinality;
namespace Stockfish {
namespace {
constexpr int TBPIECES = 7; // Max number of supported pieces
constexpr int MAX_DTZ = 1 << 18; // Max DTZ supported, large enough to deal with the syzygy TB limit.
enum { BigEndian, LittleEndian };
enum TBType { WDL, DTZ }; // Used as template parameter
// Each table has a set of flags: all of them refer to DTZ tables, the last one to WDL tables
enum TBFlag { STM = 1, Mapped = 2, WinPlies = 4, LossPlies = 8, Wide = 16, SingleValue = 128 };
inline WDLScore operator-(WDLScore d) { return WDLScore(-int(d)); }
inline Square operator^(Square s, int i) { return Square(int(s) ^ i); }
constexpr std::string_view PieceToChar = " PNBRQK pnbrqk";
int MapPawns[SQUARE_NB];
int MapB1H1H7[SQUARE_NB];
int MapA1D1D4[SQUARE_NB];
int MapKK[10][SQUARE_NB]; // [MapA1D1D4][SQUARE_NB]
int Binomial[6][SQUARE_NB]; // [k][n] k elements from a set of n elements
int LeadPawnIdx[6][SQUARE_NB]; // [leadPawnsCnt][SQUARE_NB]
int LeadPawnsSize[6][4]; // [leadPawnsCnt][FILE_A..FILE_D]
// Comparison function to sort leading pawns in ascending MapPawns[] order
bool pawns_comp(Square i, Square j) { return MapPawns[i] < MapPawns[j]; }
int off_A1H8(Square sq) { return int(rank_of(sq)) - file_of(sq); }
constexpr Value WDL_to_value[] = {
-VALUE_MATE + MAX_PLY + 1,
VALUE_DRAW - 2,
VALUE_DRAW,
VALUE_DRAW + 2,
VALUE_MATE - MAX_PLY - 1
};
template
inline void swap_endian(T& x)
{
static_assert(std::is_unsigned::value, "Argument of swap_endian not unsigned");
uint8_t tmp, *c = (uint8_t*)&x;
for (int i = 0; i < Half; ++i)
tmp = c[i], c[i] = c[End - i], c[End - i] = tmp;
}
template<> inline void swap_endian(uint8_t&) {}
template T number(void* addr)
{
T v;
if ((uintptr_t)addr & (alignof(T) - 1)) // Unaligned pointer (very rare)
std::memcpy(&v, addr, sizeof(T));
else
v = *((T*)addr);
if (LE != IsLittleEndian)
swap_endian(v);
return v;
}
// DTZ tables don't store valid scores for moves that reset the rule50 counter
// like captures and pawn moves but we can easily recover the correct dtz of the
// previous move if we know the position's WDL score.
int dtz_before_zeroing(WDLScore wdl) {
return wdl == WDLWin ? 1 :
wdl == WDLCursedWin ? 101 :
wdl == WDLBlessedLoss ? -101 :
wdl == WDLLoss ? -1 : 0;
}
// Return the sign of a number (-1, 0, 1)
template int sign_of(T val) {
return (T(0) < val) - (val < T(0));
}
// Numbers in little endian used by sparseIndex[] to point into blockLength[]
struct SparseEntry {
char block[4]; // Number of block
char offset[2]; // Offset within the block
};
static_assert(sizeof(SparseEntry) == 6, "SparseEntry must be 6 bytes");
using Sym = uint16_t; // Huffman symbol
struct LR {
enum Side { Left, Right };
uint8_t lr[3]; // The first 12 bits is the left-hand symbol, the second 12
// bits is the right-hand symbol. If symbol has length 1,
// then the left-hand symbol is the stored value.
template
Sym get() {
return S == Left ? ((lr[1] & 0xF) << 8) | lr[0] :
S == Right ? (lr[2] << 4) | (lr[1] >> 4) : (assert(false), Sym(-1));
}
};
static_assert(sizeof(LR) == 3, "LR tree entry must be 3 bytes");
// Tablebases data layout is structured as following:
//
// TBFile: memory maps/unmaps the physical .rtbw and .rtbz files
// TBTable: one object for each file with corresponding indexing information
// TBTables: has ownership of TBTable objects, keeping a list and a hash
// class TBFile memory maps/unmaps the single .rtbw and .rtbz files. Files are
// memory mapped for best performance. Files are mapped at first access: at init
// time only existence of the file is checked.
class TBFile : public std::ifstream {
std::string fname;
public:
// Look for and open the file among the Paths directories where the .rtbw
// and .rtbz files can be found. Multiple directories are separated by ";"
// on Windows and by ":" on Unix-based operating systems.
//
// Example:
// C:\tb\wdl345;C:\tb\wdl6;D:\tb\dtz345;D:\tb\dtz6
static std::string Paths;
TBFile(const std::string& f) {
#ifndef _WIN32
constexpr char SepChar = ':';
#else
constexpr char SepChar = ';';
#endif
std::stringstream ss(Paths);
std::string path;
while (std::getline(ss, path, SepChar))
{
fname = path + "/" + f;
std::ifstream::open(fname);
if (is_open())
return;
}
}
// Memory map the file and check it.
uint8_t* map(void** baseAddress, uint64_t* mapping, TBType type) {
if (is_open())
close(); // Need to re-open to get native file descriptor
#ifndef _WIN32
struct stat statbuf;
int fd = ::open(fname.c_str(), O_RDONLY);
if (fd == -1)
return *baseAddress = nullptr, nullptr;
fstat(fd, &statbuf);
if (statbuf.st_size % 64 != 16)
{
std::cerr << "Corrupt tablebase file " << fname << std::endl;
exit(EXIT_FAILURE);
}
*mapping = statbuf.st_size;
*baseAddress = mmap(nullptr, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0);
#if defined(MADV_RANDOM)
madvise(*baseAddress, statbuf.st_size, MADV_RANDOM);
#endif
::close(fd);
if (*baseAddress == MAP_FAILED)
{
std::cerr << "Could not mmap() " << fname << std::endl;
exit(EXIT_FAILURE);
}
#else
// Note FILE_FLAG_RANDOM_ACCESS is only a hint to Windows and as such may get ignored.
HANDLE fd = CreateFileA(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, nullptr);
if (fd == INVALID_HANDLE_VALUE)
return *baseAddress = nullptr, nullptr;
DWORD size_high;
DWORD size_low = GetFileSize(fd, &size_high);
if (size_low % 64 != 16)
{
std::cerr << "Corrupt tablebase file " << fname << std::endl;
exit(EXIT_FAILURE);
}
HANDLE mmap = CreateFileMapping(fd, nullptr, PAGE_READONLY, size_high, size_low, nullptr);
CloseHandle(fd);
if (!mmap)
{
std::cerr << "CreateFileMapping() failed" << std::endl;
exit(EXIT_FAILURE);
}
*mapping = (uint64_t)mmap;
*baseAddress = MapViewOfFile(mmap, FILE_MAP_READ, 0, 0, 0);
if (!*baseAddress)
{
std::cerr << "MapViewOfFile() failed, name = " << fname
<< ", error = " << GetLastError() << std::endl;
exit(EXIT_FAILURE);
}
#endif
uint8_t* data = (uint8_t*)*baseAddress;
constexpr uint8_t Magics[][4] = { { 0xD7, 0x66, 0x0C, 0xA5 },
{ 0x71, 0xE8, 0x23, 0x5D } };
if (memcmp(data, Magics[type == WDL], 4))
{
std::cerr << "Corrupted table in file " << fname << std::endl;
unmap(*baseAddress, *mapping);
return *baseAddress = nullptr, nullptr;
}
return data + 4; // Skip Magics's header
}
static void unmap(void* baseAddress, uint64_t mapping) {
#ifndef _WIN32
munmap(baseAddress, mapping);
#else
UnmapViewOfFile(baseAddress);
CloseHandle((HANDLE)mapping);
#endif
}
};
std::string TBFile::Paths;
// struct PairsData contains low level indexing information to access TB data.
// There are 8, 4 or 2 PairsData records for each TBTable, according to type of
// table and if positions have pawns or not. It is populated at first access.
struct PairsData {
uint8_t flags; // Table flags, see enum TBFlag
uint8_t maxSymLen; // Maximum length in bits of the Huffman symbols
uint8_t minSymLen; // Minimum length in bits of the Huffman symbols
uint32_t blocksNum; // Number of blocks in the TB file
size_t sizeofBlock; // Block size in bytes
size_t span; // About every span values there is a SparseIndex[] entry
Sym* lowestSym; // lowestSym[l] is the symbol of length l with the lowest value
LR* btree; // btree[sym] stores the left and right symbols that expand sym
uint16_t* blockLength; // Number of stored positions (minus one) for each block: 1..65536
uint32_t blockLengthSize; // Size of blockLength[] table: padded so it's bigger than blocksNum
SparseEntry* sparseIndex; // Partial indices into blockLength[]
size_t sparseIndexSize; // Size of SparseIndex[] table
uint8_t* data; // Start of Huffman compressed data
std::vector base64; // base64[l - min_sym_len] is the 64bit-padded lowest symbol of length l
std::vector symlen; // Number of values (-1) represented by a given Huffman symbol: 1..256
Piece pieces[TBPIECES]; // Position pieces: the order of pieces defines the groups
uint64_t groupIdx[TBPIECES+1]; // Start index used for the encoding of the group's pieces
int groupLen[TBPIECES+1]; // Number of pieces in a given group: KRKN -> (3, 1)
uint16_t map_idx[4]; // WDLWin, WDLLoss, WDLCursedWin, WDLBlessedLoss (used in DTZ)
};
// struct TBTable contains indexing information to access the corresponding TBFile.
// There are 2 types of TBTable, corresponding to a WDL or a DTZ file. TBTable
// is populated at init time but the nested PairsData records are populated at
// first access, when the corresponding file is memory mapped.
template
struct TBTable {
using Ret = typename std::conditional::type;
static constexpr int Sides = Type == WDL ? 2 : 1;
std::atomic_bool ready;
void* baseAddress;
uint8_t* map;
uint64_t mapping;
Key key;
Key key2;
int pieceCount;
bool hasPawns;
bool hasUniquePieces;
uint8_t pawnCount[2]; // [Lead color / other color]
PairsData items[Sides][4]; // [wtm / btm][FILE_A..FILE_D or 0]
PairsData* get(int stm, int f) {
return &items[stm % Sides][hasPawns ? f : 0];
}
TBTable() : ready(false), baseAddress(nullptr) {}
explicit TBTable(const std::string& code);
explicit TBTable(const TBTable& wdl);
~TBTable() {
if (baseAddress)
TBFile::unmap(baseAddress, mapping);
}
};
template<>
TBTable::TBTable(const std::string& code) : TBTable() {
StateInfo st;
Position pos;
key = pos.set(code, WHITE, &st).material_key();
pieceCount = pos.count();
hasPawns = pos.pieces(PAWN);
hasUniquePieces = false;
for (Color c : { WHITE, BLACK })
for (PieceType pt = PAWN; pt < KING; ++pt)
if (popcount(pos.pieces(c, pt)) == 1)
hasUniquePieces = true;
// Set the leading color. In case both sides have pawns the leading color
// is the side with less pawns because this leads to better compression.
bool c = !pos.count(BLACK)
|| ( pos.count(WHITE)
&& pos.count(BLACK) >= pos.count(WHITE));
pawnCount[0] = pos.count(c ? WHITE : BLACK);
pawnCount[1] = pos.count(c ? BLACK : WHITE);
key2 = pos.set(code, BLACK, &st).material_key();
}
template<>
TBTable::TBTable(const TBTable& wdl) : TBTable() {
// Use the corresponding WDL table to avoid recalculating all from scratch
key = wdl.key;
key2 = wdl.key2;
pieceCount = wdl.pieceCount;
hasPawns = wdl.hasPawns;
hasUniquePieces = wdl.hasUniquePieces;
pawnCount[0] = wdl.pawnCount[0];
pawnCount[1] = wdl.pawnCount[1];
}
// class TBTables creates and keeps ownership of the TBTable objects, one for
// each TB file found. It supports a fast, hash based, table lookup. Populated
// at init time, accessed at probe time.
class TBTables {
struct Entry
{
Key key;
TBTable* wdl;
TBTable* dtz;
template
TBTable* get() const {
return (TBTable*)(Type == WDL ? (void*)wdl : (void*)dtz);
}
};
static constexpr int Size = 1 << 12; // 4K table, indexed by key's 12 lsb
static constexpr int Overflow = 1; // Number of elements allowed to map to the last bucket
Entry hashTable[Size + Overflow];
std::deque> wdlTable;
std::deque> dtzTable;
void insert(Key key, TBTable* wdl, TBTable* dtz) {
uint32_t homeBucket = (uint32_t)key & (Size - 1);
Entry entry{ key, wdl, dtz };
// Ensure last element is empty to avoid overflow when looking up
for (uint32_t bucket = homeBucket; bucket < Size + Overflow - 1; ++bucket) {
Key otherKey = hashTable[bucket].key;
if (otherKey == key || !hashTable[bucket].get()) {
hashTable[bucket] = entry;
return;
}
// Robin Hood hashing: If we've probed for longer than this element,
// insert here and search for a new spot for the other element instead.
uint32_t otherHomeBucket = (uint32_t)otherKey & (Size - 1);
if (otherHomeBucket > homeBucket) {
std::swap(entry, hashTable[bucket]);
key = otherKey;
homeBucket = otherHomeBucket;
}
}
std::cerr << "TB hash table size too low!" << std::endl;
exit(EXIT_FAILURE);
}
public:
template
TBTable* get(Key key) {
for (const Entry* entry = &hashTable[(uint32_t)key & (Size - 1)]; ; ++entry) {
if (entry->key == key || !entry->get())
return entry->get();
}
}
void clear() {
memset(hashTable, 0, sizeof(hashTable));
wdlTable.clear();
dtzTable.clear();
}
size_t size() const { return wdlTable.size(); }
void add(const std::vector& pieces);
};
TBTables TBTables;
// If the corresponding file exists two new objects TBTable and TBTable
// are created and added to the lists and hash table. Called at init time.
void TBTables::add(const std::vector& pieces) {
std::string code;
for (PieceType pt : pieces)
code += PieceToChar[pt];
TBFile file(code.insert(code.find('K', 1), "v") + ".rtbw"); // KRK -> KRvK
if (!file.is_open()) // Only WDL file is checked
return;
file.close();
MaxCardinality = std::max((int)pieces.size(), MaxCardinality);
wdlTable.emplace_back(code);
dtzTable.emplace_back(wdlTable.back());
// Insert into the hash keys for both colors: KRvK with KR white and black
insert(wdlTable.back().key , &wdlTable.back(), &dtzTable.back());
insert(wdlTable.back().key2, &wdlTable.back(), &dtzTable.back());
}
// TB tables are compressed with canonical Huffman code. The compressed data is divided into
// blocks of size d->sizeofBlock, and each block stores a variable number of symbols.
// Each symbol represents either a WDL or a (remapped) DTZ value, or a pair of other symbols
// (recursively). If you keep expanding the symbols in a block, you end up with up to 65536
// WDL or DTZ values. Each symbol represents up to 256 values and will correspond after
// Huffman coding to at least 1 bit. So a block of 32 bytes corresponds to at most
// 32 x 8 x 256 = 65536 values. This maximum is only reached for tables that consist mostly
// of draws or mostly of wins, but such tables are actually quite common. In principle, the
// blocks in WDL tables are 64 bytes long (and will be aligned on cache lines). But for
// mostly-draw or mostly-win tables this can leave many 64-byte blocks only half-filled, so
// in such cases blocks are 32 bytes long. The blocks of DTZ tables are up to 1024 bytes long.
// The generator picks the size that leads to the smallest table. The "book" of symbols and
// Huffman codes is the same for all blocks in the table. A non-symmetric pawnless TB file
// will have one table for wtm and one for btm, a TB file with pawns will have tables per
// file a,b,c,d also in this case one set for wtm and one for btm.
int decompress_pairs(PairsData* d, uint64_t idx) {
// Special case where all table positions store the same value
if (d->flags & TBFlag::SingleValue)
return d->minSymLen;
// First we need to locate the right block that stores the value at index "idx".
// Because each block n stores blockLength[n] + 1 values, the index i of the block
// that contains the value at position idx is:
//
// for (i = -1, sum = 0; sum <= idx; i++)
// sum += blockLength[i + 1] + 1;
//
// This can be slow, so we use SparseIndex[] populated with a set of SparseEntry that
// point to known indices into blockLength[]. Namely SparseIndex[k] is a SparseEntry
// that stores the blockLength[] index and the offset within that block of the value
// with index I(k), where:
//
// I(k) = k * d->span + d->span / 2 (1)
// First step is to get the 'k' of the I(k) nearest to our idx, using definition (1)
uint32_t k = uint32_t(idx / d->span);
// Then we read the corresponding SparseIndex[] entry
uint32_t block = number(&d->sparseIndex[k].block);
int offset = number(&d->sparseIndex[k].offset);
// Now compute the difference idx - I(k). From definition of k we know that
//
// idx = k * d->span + idx % d->span (2)
//
// So from (1) and (2) we can compute idx - I(K):
int diff = idx % d->span - d->span / 2;
// Sum the above to offset to find the offset corresponding to our idx
offset += diff;
// Move to previous/next block, until we reach the correct block that contains idx,
// that is when 0 <= offset <= d->blockLength[block]
while (offset < 0)
offset += d->blockLength[--block] + 1;
while (offset > d->blockLength[block])
offset -= d->blockLength[block++] + 1;
// Finally, we find the start address of our block of canonical Huffman symbols
uint32_t* ptr = (uint32_t*)(d->data + ((uint64_t)block * d->sizeofBlock));
// Read the first 64 bits in our block, this is a (truncated) sequence of
// unknown number of symbols of unknown length but we know the first one
// is at the beginning of this 64 bits sequence.
uint64_t buf64 = number(ptr); ptr += 2;
int buf64Size = 64;
Sym sym;
while (true)
{
int len = 0; // This is the symbol length - d->min_sym_len
// Now get the symbol length. For any symbol s64 of length l right-padded
// to 64 bits we know that d->base64[l-1] >= s64 >= d->base64[l] so we
// can find the symbol length iterating through base64[].
while (buf64 < d->base64[len])
++len;
// All the symbols of a given length are consecutive integers (numerical
// sequence property), so we can compute the offset of our symbol of
// length len, stored at the beginning of buf64.
sym = Sym((buf64 - d->base64[len]) >> (64 - len - d->minSymLen));
// Now add the value of the lowest symbol of length len to get our symbol
sym += number(&d->lowestSym[len]);
// If our offset is within the number of values represented by symbol sym
// we are done...
if (offset < d->symlen[sym] + 1)
break;
// ...otherwise update the offset and continue to iterate
offset -= d->symlen[sym] + 1;
len += d->minSymLen; // Get the real length
buf64 <<= len; // Consume the just processed symbol
buf64Size -= len;
if (buf64Size <= 32) { // Refill the buffer
buf64Size += 32;
buf64 |= (uint64_t)number(ptr++) << (64 - buf64Size);
}
}
// Ok, now we have our symbol that expands into d->symlen[sym] + 1 symbols.
// We binary-search for our value recursively expanding into the left and
// right child symbols until we reach a leaf node where symlen[sym] + 1 == 1
// that will store the value we need.
while (d->symlen[sym])
{
Sym left = d->btree[sym].get();
// If a symbol contains 36 sub-symbols (d->symlen[sym] + 1 = 36) and
// expands in a pair (d->symlen[left] = 23, d->symlen[right] = 11), then
// we know that, for instance the ten-th value (offset = 10) will be on
// the left side because in Recursive Pairing child symbols are adjacent.
if (offset < d->symlen[left] + 1)
sym = left;
else {
offset -= d->symlen[left] + 1;
sym = d->btree[sym].get();
}
}
return d->btree[sym].get();
}
bool check_dtz_stm(TBTable*, int, File) { return true; }
bool check_dtz_stm(TBTable* entry, int stm, File f) {
auto flags = entry->get(stm, f)->flags;
return (flags & TBFlag::STM) == stm
|| ((entry->key == entry->key2) && !entry->hasPawns);
}
// DTZ scores are sorted by frequency of occurrence and then assigned the
// values 0, 1, 2, ... in order of decreasing frequency. This is done for each
// of the four WDLScore values. The mapping information necessary to reconstruct
// the original values is stored in the TB file and read during map[] init.
WDLScore map_score(TBTable*, File, int value, WDLScore) { return WDLScore(value - 2); }
int map_score(TBTable* entry, File f, int value, WDLScore wdl) {
constexpr int WDLMap[] = { 1, 3, 0, 2, 0 };
auto flags = entry->get(0, f)->flags;
uint8_t* map = entry->map;
uint16_t* idx = entry->get(0, f)->map_idx;
if (flags & TBFlag::Mapped) {
if (flags & TBFlag::Wide)
value = ((uint16_t *)map)[idx[WDLMap[wdl + 2]] + value];
else
value = map[idx[WDLMap[wdl + 2]] + value];
}
// DTZ tables store distance to zero in number of moves or plies. We
// want to return plies, so we have convert to plies when needed.
if ( (wdl == WDLWin && !(flags & TBFlag::WinPlies))
|| (wdl == WDLLoss && !(flags & TBFlag::LossPlies))
|| wdl == WDLCursedWin
|| wdl == WDLBlessedLoss)
value *= 2;
return value + 1;
}
// Compute a unique index out of a position and use it to probe the TB file. To
// encode k pieces of same type and color, first sort the pieces by square in
// ascending order s1 <= s2 <= ... <= sk then compute the unique index as:
//
// idx = Binomial[1][s1] + Binomial[2][s2] + ... + Binomial[k][sk]
//
template
Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result) {
Square squares[TBPIECES];
Piece pieces[TBPIECES];
uint64_t idx;
int next = 0, size = 0, leadPawnsCnt = 0;
PairsData* d;
Bitboard b, leadPawns = 0;
File tbFile = FILE_A;
// A given TB entry like KRK has associated two material keys: KRvk and Kvkr.
// If both sides have the same pieces keys are equal. In this case TB tables
// only store the 'white to move' case, so if the position to lookup has black
// to move, we need to switch the color and flip the squares before to lookup.
bool symmetricBlackToMove = (entry->key == entry->key2 && pos.side_to_move());
// TB files are calculated for white as stronger side. For instance we have
// KRvK, not KvKR. A position where stronger side is white will have its
// material key == entry->key, otherwise we have to switch the color and
// flip the squares before to lookup.
bool blackStronger = (pos.material_key() != entry->key);
int flipColor = (symmetricBlackToMove || blackStronger) * 8;
int flipSquares = (symmetricBlackToMove || blackStronger) * 56;
int stm = (symmetricBlackToMove || blackStronger) ^ pos.side_to_move();
// For pawns, TB files store 4 separate tables according if leading pawn is on
// file a, b, c or d after reordering. The leading pawn is the one with maximum
// MapPawns[] value, that is the one most toward the edges and with lowest rank.
if (entry->hasPawns) {
// In all the 4 tables, pawns are at the beginning of the piece sequence and
// their color is the reference one. So we just pick the first one.
Piece pc = Piece(entry->get(0, 0)->pieces[0] ^ flipColor);
assert(type_of(pc) == PAWN);
leadPawns = b = pos.pieces(color_of(pc), PAWN);
do
squares[size++] = pop_lsb(b) ^ flipSquares;
while (b);
leadPawnsCnt = size;
std::swap(squares[0], *std::max_element(squares, squares + leadPawnsCnt, pawns_comp));
tbFile = File(edge_distance(file_of(squares[0])));
}
// DTZ tables are one-sided, i.e. they store positions only for white to
// move or only for black to move, so check for side to move to be stm,
// early exit otherwise.
if (!check_dtz_stm(entry, stm, tbFile))
return *result = CHANGE_STM, Ret();
// Now we are ready to get all the position pieces (but the lead pawns) and
// directly map them to the correct color and square.
b = pos.pieces() ^ leadPawns;
do {
Square s = pop_lsb(b);
squares[size] = s ^ flipSquares;
pieces[size++] = Piece(pos.piece_on(s) ^ flipColor);
} while (b);
assert(size >= 2);
d = entry->get(stm, tbFile);
// Then we reorder the pieces to have the same sequence as the one stored
// in pieces[i]: the sequence that ensures the best compression.
for (int i = leadPawnsCnt; i < size - 1; ++i)
for (int j = i + 1; j < size; ++j)
if (d->pieces[i] == pieces[j])
{
std::swap(pieces[i], pieces[j]);
std::swap(squares[i], squares[j]);
break;
}
// Now we map again the squares so that the square of the lead piece is in
// the triangle A1-D1-D4.
if (file_of(squares[0]) > FILE_D)
for (int i = 0; i < size; ++i)
squares[i] = flip_file(squares[i]);
// Encode leading pawns starting with the one with minimum MapPawns[] and
// proceeding in ascending order.
if (entry->hasPawns) {
idx = LeadPawnIdx[leadPawnsCnt][squares[0]];
std::stable_sort(squares + 1, squares + leadPawnsCnt, pawns_comp);
for (int i = 1; i < leadPawnsCnt; ++i)
idx += Binomial[i][MapPawns[squares[i]]];
goto encode_remaining; // With pawns we have finished special treatments
}
// In positions without pawns, we further flip the squares to ensure leading
// piece is below RANK_5.
if (rank_of(squares[0]) > RANK_4)
for (int i = 0; i < size; ++i)
squares[i] = flip_rank(squares[i]);
// Look for the first piece of the leading group not on the A1-D4 diagonal
// and ensure it is mapped below the diagonal.
for (int i = 0; i < d->groupLen[0]; ++i) {
if (!off_A1H8(squares[i]))
continue;
if (off_A1H8(squares[i]) > 0) // A1-H8 diagonal flip: SQ_A3 -> SQ_C1
for (int j = i; j < size; ++j)
squares[j] = Square(((squares[j] >> 3) | (squares[j] << 3)) & 63);
break;
}
// Encode the leading group.
//
// Suppose we have KRvK. Let's say the pieces are on square numbers wK, wR
// and bK (each 0...63). The simplest way to map this position to an index
// is like this:
//
// index = wK * 64 * 64 + wR * 64 + bK;
//
// But this way the TB is going to have 64*64*64 = 262144 positions, with
// lots of positions being equivalent (because they are mirrors of each
// other) and lots of positions being invalid (two pieces on one square,
// adjacent kings, etc.).
// Usually the first step is to take the wK and bK together. There are just
// 462 ways legal and not-mirrored ways to place the wK and bK on the board.
// Once we have placed the wK and bK, there are 62 squares left for the wR
// Mapping its square from 0..63 to available squares 0..61 can be done like:
//
// wR -= (wR > wK) + (wR > bK);
//
// In words: if wR "comes later" than wK, we deduct 1, and the same if wR
// "comes later" than bK. In case of two same pieces like KRRvK we want to
// place the two Rs "together". If we have 62 squares left, we can place two
// Rs "together" in 62 * 61 / 2 ways (we divide by 2 because rooks can be
// swapped and still get the same position.)
//
// In case we have at least 3 unique pieces (included kings) we encode them
// together.
if (entry->hasUniquePieces) {
int adjust1 = squares[1] > squares[0];
int adjust2 = (squares[2] > squares[0]) + (squares[2] > squares[1]);
// First piece is below a1-h8 diagonal. MapA1D1D4[] maps the b1-d1-d3
// triangle to 0...5. There are 63 squares for second piece and and 62
// (mapped to 0...61) for the third.
if (off_A1H8(squares[0]))
idx = ( MapA1D1D4[squares[0]] * 63
+ (squares[1] - adjust1)) * 62
+ squares[2] - adjust2;
// First piece is on a1-h8 diagonal, second below: map this occurrence to
// 6 to differentiate from the above case, rank_of() maps a1-d4 diagonal
// to 0...3 and finally MapB1H1H7[] maps the b1-h1-h7 triangle to 0..27.
else if (off_A1H8(squares[1]))
idx = ( 6 * 63 + rank_of(squares[0]) * 28
+ MapB1H1H7[squares[1]]) * 62
+ squares[2] - adjust2;
// First two pieces are on a1-h8 diagonal, third below
else if (off_A1H8(squares[2]))
idx = 6 * 63 * 62 + 4 * 28 * 62
+ rank_of(squares[0]) * 7 * 28
+ (rank_of(squares[1]) - adjust1) * 28
+ MapB1H1H7[squares[2]];
// All 3 pieces on the diagonal a1-h8
else
idx = 6 * 63 * 62 + 4 * 28 * 62 + 4 * 7 * 28
+ rank_of(squares[0]) * 7 * 6
+ (rank_of(squares[1]) - adjust1) * 6
+ (rank_of(squares[2]) - adjust2);
} else
// We don't have at least 3 unique pieces, like in KRRvKBB, just map
// the kings.
idx = MapKK[MapA1D1D4[squares[0]]][squares[1]];
encode_remaining:
idx *= d->groupIdx[0];
Square* groupSq = squares + d->groupLen[0];
// Encode remaining pawns then pieces according to square, in ascending order
bool remainingPawns = entry->hasPawns && entry->pawnCount[1];
while (d->groupLen[++next])
{
std::stable_sort(groupSq, groupSq + d->groupLen[next]);
uint64_t n = 0;
// Map down a square if "comes later" than a square in the previous
// groups (similar to what done earlier for leading group pieces).
for (int i = 0; i < d->groupLen[next]; ++i)
{
auto f = [&](Square s) { return groupSq[i] > s; };
auto adjust = std::count_if(squares, groupSq, f);
n += Binomial[i + 1][groupSq[i] - adjust - 8 * remainingPawns];
}
remainingPawns = false;
idx += n * d->groupIdx[next];
groupSq += d->groupLen[next];
}
// Now that we have the index, decompress the pair and get the score
return map_score(entry, tbFile, decompress_pairs(d, idx), wdl);
}
// Group together pieces that will be encoded together. The general rule is that
// a group contains pieces of same type and color. The exception is the leading
// group that, in case of positions without pawns, can be formed by 3 different
// pieces (default) or by the king pair when there is not a unique piece apart
// from the kings. When there are pawns, pawns are always first in pieces[].
//
// As example KRKN -> KRK + N, KNNK -> KK + NN, KPPKP -> P + PP + K + K
//
// The actual grouping depends on the TB generator and can be inferred from the
// sequence of pieces in piece[] array.
template
void set_groups(T& e, PairsData* d, int order[], File f) {
int n = 0, firstLen = e.hasPawns ? 0 : e.hasUniquePieces ? 3 : 2;
d->groupLen[n] = 1;
// Number of pieces per group is stored in groupLen[], for instance in KRKN
// the encoder will default on '111', so groupLen[] will be (3, 1).
for (int i = 1; i < e.pieceCount; ++i)
if (--firstLen > 0 || d->pieces[i] == d->pieces[i - 1])
d->groupLen[n]++;
else
d->groupLen[++n] = 1;
d->groupLen[++n] = 0; // Zero-terminated
// The sequence in pieces[] defines the groups, but not the order in which
// they are encoded. If the pieces in a group g can be combined on the board
// in N(g) different ways, then the position encoding will be of the form:
//
// g1 * N(g2) * N(g3) + g2 * N(g3) + g3
//
// This ensures unique encoding for the whole position. The order of the
// groups is a per-table parameter and could not follow the canonical leading
// pawns/pieces -> remaining pawns -> remaining pieces. In particular the
// first group is at order[0] position and the remaining pawns, when present,
// are at order[1] position.
bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides
int next = pp ? 2 : 1;
int freeSquares = 64 - d->groupLen[0] - (pp ? d->groupLen[1] : 0);
uint64_t idx = 1;
for (int k = 0; next < n || k == order[0] || k == order[1]; ++k)
if (k == order[0]) // Leading pawns or pieces
{
d->groupIdx[0] = idx;
idx *= e.hasPawns ? LeadPawnsSize[d->groupLen[0]][f]
: e.hasUniquePieces ? 31332 : 462;
}
else if (k == order[1]) // Remaining pawns
{
d->groupIdx[1] = idx;
idx *= Binomial[d->groupLen[1]][48 - d->groupLen[0]];
}
else // Remaining pieces
{
d->groupIdx[next] = idx;
idx *= Binomial[d->groupLen[next]][freeSquares];
freeSquares -= d->groupLen[next++];
}
d->groupIdx[n] = idx;
}
// In Recursive Pairing each symbol represents a pair of children symbols. So
// read d->btree[] symbols data and expand each one in his left and right child
// symbol until reaching the leafs that represent the symbol value.
uint8_t set_symlen(PairsData* d, Sym s, std::vector& visited) {
visited[s] = true; // We can set it now because tree is acyclic
Sym sr = d->btree[s].get();
if (sr == 0xFFF)
return 0;
Sym sl = d->btree[s].get();
if (!visited[sl])
d->symlen[sl] = set_symlen(d, sl, visited);
if (!visited[sr])
d->symlen[sr] = set_symlen(d, sr, visited);
return d->symlen[sl] + d->symlen[sr] + 1;
}
uint8_t* set_sizes(PairsData* d, uint8_t* data) {
d->flags = *data++;
if (d->flags & TBFlag::SingleValue) {
d->blocksNum = d->blockLengthSize = 0;
d->span = d->sparseIndexSize = 0; // Broken MSVC zero-init
d->minSymLen = *data++; // Here we store the single value
return data;
}
// groupLen[] is a zero-terminated list of group lengths, the last groupIdx[]
// element stores the biggest index that is the tb size.
uint64_t tbSize = d->groupIdx[std::find(d->groupLen, d->groupLen + 7, 0) - d->groupLen];
d->sizeofBlock = 1ULL << *data++;
d->span = 1ULL << *data++;
d->sparseIndexSize = size_t((tbSize + d->span - 1) / d->span); // Round up
auto padding = number(data++);
d->blocksNum = number(data); data += sizeof(uint32_t);
d->blockLengthSize = d->blocksNum + padding; // Padded to ensure SparseIndex[]
// does not point out of range.
d->maxSymLen = *data++;
d->minSymLen = *data++;
d->lowestSym = (Sym*)data;
d->base64.resize(d->maxSymLen - d->minSymLen + 1);
// The canonical code is ordered such that longer symbols (in terms of
// the number of bits of their Huffman code) have lower numeric value,
// so that d->lowestSym[i] >= d->lowestSym[i+1] (when read as LittleEndian).
// Starting from this we compute a base64[] table indexed by symbol length
// and containing 64 bit values so that d->base64[i] >= d->base64[i+1].
// See https://en.wikipedia.org/wiki/Huffman_coding
for (int i = d->base64.size() - 2; i >= 0; --i) {
d->base64[i] = (d->base64[i + 1] + number(&d->lowestSym[i])
- number(&d->lowestSym[i + 1])) / 2;
assert(d->base64[i] * 2 >= d->base64[i+1]);
}
// Now left-shift by an amount so that d->base64[i] gets shifted 1 bit more
// than d->base64[i+1] and given the above assert condition, we ensure that
// d->base64[i] >= d->base64[i+1]. Moreover for any symbol s64 of length i
// and right-padded to 64 bits holds d->base64[i-1] >= s64 >= d->base64[i].
for (size_t i = 0; i < d->base64.size(); ++i)
d->base64[i] <<= 64 - i - d->minSymLen; // Right-padding to 64 bits
data += d->base64.size() * sizeof(Sym);
d->symlen.resize(number(data)); data += sizeof(uint16_t);
d->btree = (LR*)data;
// The compression scheme used is "Recursive Pairing", that replaces the most
// frequent adjacent pair of symbols in the source message by a new symbol,
// reevaluating the frequencies of all of the symbol pairs with respect to
// the extended alphabet, and then repeating the process.
// See http://www.larsson.dogma.net/dcc99.pdf
std::vector visited(d->symlen.size());
for (Sym sym = 0; sym < d->symlen.size(); ++sym)
if (!visited[sym])
d->symlen[sym] = set_symlen(d, sym, visited);
return data + d->symlen.size() * sizeof(LR) + (d->symlen.size() & 1);
}
uint8_t* set_dtz_map(TBTable&, uint8_t* data, File) { return data; }
uint8_t* set_dtz_map(TBTable& e, uint8_t* data, File maxFile) {
e.map = data;
for (File f = FILE_A; f <= maxFile; ++f) {
auto flags = e.get(0, f)->flags;
if (flags & TBFlag::Mapped) {
if (flags & TBFlag::Wide) {
data += (uintptr_t)data & 1; // Word alignment, we may have a mixed table
for (int i = 0; i < 4; ++i) { // Sequence like 3,x,x,x,1,x,0,2,x,x
e.get(0, f)->map_idx[i] = (uint16_t)((uint16_t *)data - (uint16_t *)e.map + 1);
data += 2 * number(data) + 2;
}
}
else {
for (int i = 0; i < 4; ++i) {
e.get(0, f)->map_idx[i] = (uint16_t)(data - e.map + 1);
data += *data + 1;
}
}
}
}
return data += (uintptr_t)data & 1; // Word alignment
}
// Populate entry's PairsData records with data from the just memory mapped file.
// Called at first access.
template
void set(T& e, uint8_t* data) {
PairsData* d;
enum { Split = 1, HasPawns = 2 };
assert(e.hasPawns == bool(*data & HasPawns));
assert((e.key != e.key2) == bool(*data & Split));
data++; // First byte stores flags
const int sides = T::Sides == 2 && (e.key != e.key2) ? 2 : 1;
const File maxFile = e.hasPawns ? FILE_D : FILE_A;
bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides
assert(!pp || e.pawnCount[0]);
for (File f = FILE_A; f <= maxFile; ++f) {
for (int i = 0; i < sides; i++)
*e.get(i, f) = PairsData();
int order[][2] = { { *data & 0xF, pp ? *(data + 1) & 0xF : 0xF },
{ *data >> 4, pp ? *(data + 1) >> 4 : 0xF } };
data += 1 + pp;
for (int k = 0; k < e.pieceCount; ++k, ++data)
for (int i = 0; i < sides; i++)
e.get(i, f)->pieces[k] = Piece(i ? *data >> 4 : *data & 0xF);
for (int i = 0; i < sides; ++i)
set_groups(e, e.get(i, f), order[i], f);
}
data += (uintptr_t)data & 1; // Word alignment
for (File f = FILE_A; f <= maxFile; ++f)
for (int i = 0; i < sides; i++)
data = set_sizes(e.get(i, f), data);
data = set_dtz_map(e, data, maxFile);
for (File f = FILE_A; f <= maxFile; ++f)
for (int i = 0; i < sides; i++) {
(d = e.get(i, f))->sparseIndex = (SparseEntry*)data;
data += d->sparseIndexSize * sizeof(SparseEntry);
}
for (File f = FILE_A; f <= maxFile; ++f)
for (int i = 0; i < sides; i++) {
(d = e.get(i, f))->blockLength = (uint16_t*)data;
data += d->blockLengthSize * sizeof(uint16_t);
}
for (File f = FILE_A; f <= maxFile; ++f)
for (int i = 0; i < sides; i++) {
data = (uint8_t*)(((uintptr_t)data + 0x3F) & ~0x3F); // 64 byte alignment
(d = e.get(i, f))->data = data;
data += d->blocksNum * d->sizeofBlock;
}
}
// If the TB file corresponding to the given position is already memory mapped
// then return its base address, otherwise try to memory map and init it. Called
// at every probe, memory map and init only at first access. Function is thread
// safe and can be called concurrently.
template
void* mapped(TBTable& e, const Position& pos) {
static std::mutex mutex;
// Use 'acquire' to avoid a thread reading 'ready' == true while
// another is still working. (compiler reordering may cause this).
if (e.ready.load(std::memory_order_acquire))
return e.baseAddress; // Could be nullptr if file does not exist
std::scoped_lock lk(mutex);
if (e.ready.load(std::memory_order_relaxed)) // Recheck under lock
return e.baseAddress;
// Pieces strings in decreasing order for each color, like ("KPP","KR")
std::string fname, w, b;
for (PieceType pt = KING; pt >= PAWN; --pt) {
w += std::string(popcount(pos.pieces(WHITE, pt)), PieceToChar[pt]);
b += std::string(popcount(pos.pieces(BLACK, pt)), PieceToChar[pt]);
}
fname = (e.key == pos.material_key() ? w + 'v' + b : b + 'v' + w)
+ (Type == WDL ? ".rtbw" : ".rtbz");
uint8_t* data = TBFile(fname).map(&e.baseAddress, &e.mapping, Type);
if (data)
set(e, data);
e.ready.store(true, std::memory_order_release);
return e.baseAddress;
}
template::Ret>
Ret probe_table(const Position& pos, ProbeState* result, WDLScore wdl = WDLDraw) {
if (pos.count() == 2) // KvK
return Ret(WDLDraw);
TBTable* entry = TBTables.get(pos.material_key());
if (!entry || !mapped(*entry, pos))
return *result = FAIL, Ret();
return do_probe_table(pos, entry, wdl, result);
}
// For a position where the side to move has a winning capture it is not necessary
// to store a winning value so the generator treats such positions as "don't cares"
// and tries to assign to it a value that improves the compression ratio. Similarly,
// if the side to move has a drawing capture, then the position is at least drawn.
// If the position is won, then the TB needs to store a win value. But if the
// position is drawn, the TB may store a loss value if that is better for compression.
// All of this means that during probing, the engine must look at captures and probe
// their results and must probe the position itself. The "best" result of these
// probes is the correct result for the position.
// DTZ tables do not store values when a following move is a zeroing winning move
// (winning capture or winning pawn move). Also DTZ store wrong values for positions
// where the best move is an ep-move (even if losing). So in all these cases set
// the state to ZEROING_BEST_MOVE.
template
WDLScore search(Position& pos, ProbeState* result) {
WDLScore value, bestValue = WDLLoss;
StateInfo st;
auto moveList = MoveList(pos);
size_t totalCount = moveList.size(), moveCount = 0;
for (const Move move : moveList)
{
if ( !pos.capture(move)
&& (!CheckZeroingMoves || type_of(pos.moved_piece(move)) != PAWN))
continue;
moveCount++;
pos.do_move(move, st);
value = -search(pos, result);
pos.undo_move(move);
if (*result == FAIL)
return WDLDraw;
if (value > bestValue)
{
bestValue = value;
if (value >= WDLWin)
{
*result = ZEROING_BEST_MOVE; // Winning DTZ-zeroing move
return value;
}
}
}
// In case we have already searched all the legal moves we don't have to probe
// the TB because the stored score could be wrong. For instance TB tables
// do not contain information on position with ep rights, so in this case
// the result of probe_wdl_table is wrong. Also in case of only capture
// moves, for instance here 4K3/4q3/6p1/2k5/6p1/8/8/8 w - - 0 7, we have to
// return with ZEROING_BEST_MOVE set.
bool noMoreMoves = (moveCount && moveCount == totalCount);
if (noMoreMoves)
value = bestValue;
else
{
value = probe_table(pos, result);
if (*result == FAIL)
return WDLDraw;
}
// DTZ stores a "don't care" value if bestValue is a win
if (bestValue >= value)
return *result = ( bestValue > WDLDraw
|| noMoreMoves ? ZEROING_BEST_MOVE : OK), bestValue;
return *result = OK, value;
}
} // namespace
/// Tablebases::init() is called at startup and after every change to
/// "SyzygyPath" UCI option to (re)create the various tables. It is not thread
/// safe, nor it needs to be.
void Tablebases::init(const std::string& paths) {
TBTables.clear();
MaxCardinality = 0;
TBFile::Paths = paths;
if (paths.empty() || paths == "")
return;
// MapB1H1H7[] encodes a square below a1-h8 diagonal to 0..27
int code = 0;
for (Square s = SQ_A1; s <= SQ_H8; ++s)
if (off_A1H8(s) < 0)
MapB1H1H7[s] = code++;
// MapA1D1D4[] encodes a square in the a1-d1-d4 triangle to 0..9
std::vector diagonal;
code = 0;
for (Square s = SQ_A1; s <= SQ_D4; ++s)
if (off_A1H8(s) < 0 && file_of(s) <= FILE_D)
MapA1D1D4[s] = code++;
else if (!off_A1H8(s) && file_of(s) <= FILE_D)
diagonal.push_back(s);
// Diagonal squares are encoded as last ones
for (auto s : diagonal)
MapA1D1D4[s] = code++;
// MapKK[] encodes all the 462 possible legal positions of two kings where
// the first is in the a1-d1-d4 triangle. If the first king is on the a1-d4
// diagonal, the other one shall not to be above the a1-h8 diagonal.
std::vector> bothOnDiagonal;
code = 0;
for (int idx = 0; idx < 10; idx++)
for (Square s1 = SQ_A1; s1 <= SQ_D4; ++s1)
if (MapA1D1D4[s1] == idx && (idx || s1 == SQ_B1)) // SQ_B1 is mapped to 0
{
for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
if ((PseudoAttacks[KING][s1] | s1) & s2)
continue; // Illegal position
else if (!off_A1H8(s1) && off_A1H8(s2) > 0)
continue; // First on diagonal, second above
else if (!off_A1H8(s1) && !off_A1H8(s2))
bothOnDiagonal.emplace_back(idx, s2);
else
MapKK[idx][s2] = code++;
}
// Legal positions with both kings on diagonal are encoded as last ones
for (auto p : bothOnDiagonal)
MapKK[p.first][p.second] = code++;
// Binomial[] stores the Binomial Coefficients using Pascal rule. There
// are Binomial[k][n] ways to choose k elements from a set of n elements.
Binomial[0][0] = 1;
for (int n = 1; n < 64; n++) // Squares
for (int k = 0; k < 6 && k <= n; ++k) // Pieces
Binomial[k][n] = (k > 0 ? Binomial[k - 1][n - 1] : 0)
+ (k < n ? Binomial[k ][n - 1] : 0);
// MapPawns[s] encodes squares a2-h7 to 0..47. This is the number of possible
// available squares when the leading one is in 's'. Moreover the pawn with
// highest MapPawns[] is the leading pawn, the one nearest the edge and,
// among pawns with same file, the one with lowest rank.
int availableSquares = 47; // Available squares when lead pawn is in a2
// Init the tables for the encoding of leading pawns group: with 7-men TB we
// can have up to 5 leading pawns (KPPPPPK).
for (int leadPawnsCnt = 1; leadPawnsCnt <= 5; ++leadPawnsCnt)
for (File f = FILE_A; f <= FILE_D; ++f)
{
// Restart the index at every file because TB table is split
// by file, so we can reuse the same index for different files.
int idx = 0;
// Sum all possible combinations for a given file, starting with
// the leading pawn on rank 2 and increasing the rank.
for (Rank r = RANK_2; r <= RANK_7; ++r)
{
Square sq = make_square(f, r);
// Compute MapPawns[] at first pass.
// If sq is the leading pawn square, any other pawn cannot be
// below or more toward the edge of sq. There are 47 available
// squares when sq = a2 and reduced by 2 for any rank increase
// due to mirroring: sq == a3 -> no a2, h2, so MapPawns[a3] = 45
if (leadPawnsCnt == 1)
{
MapPawns[sq] = availableSquares--;
MapPawns[flip_file(sq)] = availableSquares--;
}
LeadPawnIdx[leadPawnsCnt][sq] = idx;
idx += Binomial[leadPawnsCnt - 1][MapPawns[sq]];
}
// After a file is traversed, store the cumulated per-file index
LeadPawnsSize[leadPawnsCnt][f] = idx;
}
// Add entries in TB tables if the corresponding ".rtbw" file exists
for (PieceType p1 = PAWN; p1 < KING; ++p1) {
TBTables.add({KING, p1, KING});
for (PieceType p2 = PAWN; p2 <= p1; ++p2) {
TBTables.add({KING, p1, p2, KING});
TBTables.add({KING, p1, KING, p2});
for (PieceType p3 = PAWN; p3 < KING; ++p3)
TBTables.add({KING, p1, p2, KING, p3});
for (PieceType p3 = PAWN; p3 <= p2; ++p3) {
TBTables.add({KING, p1, p2, p3, KING});
for (PieceType p4 = PAWN; p4 <= p3; ++p4) {
TBTables.add({KING, p1, p2, p3, p4, KING});
for (PieceType p5 = PAWN; p5 <= p4; ++p5)
TBTables.add({KING, p1, p2, p3, p4, p5, KING});
for (PieceType p5 = PAWN; p5 < KING; ++p5)
TBTables.add({KING, p1, p2, p3, p4, KING, p5});
}
for (PieceType p4 = PAWN; p4 < KING; ++p4) {
TBTables.add({KING, p1, p2, p3, KING, p4});
for (PieceType p5 = PAWN; p5 <= p4; ++p5)
TBTables.add({KING, p1, p2, p3, KING, p4, p5});
}
}
for (PieceType p3 = PAWN; p3 <= p1; ++p3)
for (PieceType p4 = PAWN; p4 <= (p1 == p3 ? p2 : p3); ++p4)
TBTables.add({KING, p1, p2, KING, p3, p4});
}
}
sync_cout << "info string Found " << TBTables.size() << " tablebases" << sync_endl;
}
// Probe the WDL table for a particular position.
// If *result != FAIL, the probe was successful.
// The return value is from the point of view of the side to move:
// -2 : loss
// -1 : loss, but draw under 50-move rule
// 0 : draw
// 1 : win, but draw under 50-move rule
// 2 : win
WDLScore Tablebases::probe_wdl(Position& pos, ProbeState* result) {
*result = OK;
return search(pos, result);
}
// Probe the DTZ table for a particular position.
// If *result != FAIL, the probe was successful.
// The return value is from the point of view of the side to move:
// n < -100 : loss, but draw under 50-move rule
// -100 <= n < -1 : loss in n ply (assuming 50-move counter == 0)
// -1 : loss, the side to move is mated
// 0 : draw
// 1 < n <= 100 : win in n ply (assuming 50-move counter == 0)
// 100 < n : win, but draw under 50-move rule
//
// The return value n can be off by 1: a return value -n can mean a loss
// in n+1 ply and a return value +n can mean a win in n+1 ply. This
// cannot happen for tables with positions exactly on the "edge" of
// the 50-move rule.
//
// This implies that if dtz > 0 is returned, the position is certainly
// a win if dtz + 50-move-counter <= 99. Care must be taken that the engine
// picks moves that preserve dtz + 50-move-counter <= 99.
//
// If n = 100 immediately after a capture or pawn move, then the position
// is also certainly a win, and during the whole phase until the next
// capture or pawn move, the inequality to be preserved is
// dtz + 50-move-counter <= 100.
//
// In short, if a move is available resulting in dtz + 50-move-counter <= 99,
// then do not accept moves leading to dtz + 50-move-counter == 100.
int Tablebases::probe_dtz(Position& pos, ProbeState* result) {
*result = OK;
WDLScore wdl = search(pos, result);
if (*result == FAIL || wdl == WDLDraw) // DTZ tables don't store draws
return 0;
// DTZ stores a 'don't care' value in this case, or even a plain wrong
// one as in case the best move is a losing ep, so it cannot be probed.
if (*result == ZEROING_BEST_MOVE)
return dtz_before_zeroing(wdl);
int dtz = probe_table(pos, result, wdl);
if (*result == FAIL)
return 0;
if (*result != CHANGE_STM)
return (dtz + 100 * (wdl == WDLBlessedLoss || wdl == WDLCursedWin)) * sign_of(wdl);
// DTZ stores results for the other side, so we need to do a 1-ply search and
// find the winning move that minimizes DTZ.
StateInfo st;
int minDTZ = 0xFFFF;
for (const Move move : MoveList(pos))
{
bool zeroing = pos.capture(move) || type_of(pos.moved_piece(move)) == PAWN;
pos.do_move(move, st);
// For zeroing moves we want the dtz of the move _before_ doing it,
// otherwise we will get the dtz of the next move sequence. Search the
// position after the move to get the score sign (because even in a
// winning position we could make a losing capture or going for a draw).
dtz = zeroing ? -dtz_before_zeroing(search(pos, result))
: -probe_dtz(pos, result);
// If the move mates, force minDTZ to 1
if (dtz == 1 && pos.checkers() && MoveList(pos).size() == 0)
minDTZ = 1;
// Convert result from 1-ply search. Zeroing moves are already accounted
// by dtz_before_zeroing() that returns the DTZ of the previous move.
if (!zeroing)
dtz += sign_of(dtz);
// Skip the draws and if we are winning only pick positive dtz
if (dtz < minDTZ && sign_of(dtz) == sign_of(wdl))
minDTZ = dtz;
pos.undo_move(move);
if (*result == FAIL)
return 0;
}
// When there are no legal moves, the position is mate: we return -1
return minDTZ == 0xFFFF ? -1 : minDTZ;
}
// Use the DTZ tables to rank root moves.
//
// A return value false indicates that not all probes were successful.
bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) {
ProbeState result = OK;
StateInfo st;
// Obtain 50-move counter for the root position
int cnt50 = pos.rule50_count();
// Check whether a position was repeated since the last zeroing move.
bool rep = pos.has_repeated();
int dtz, bound = Options["Syzygy50MoveRule"] ? (MAX_DTZ - 100) : 1;
// Probe and rank each move
for (auto& m : rootMoves)
{
pos.do_move(m.pv[0], st);
// Calculate dtz for the current move counting from the root position
if (pos.rule50_count() == 0)
{
// In case of a zeroing move, dtz is one of -101/-1/0/1/101
WDLScore wdl = -probe_wdl(pos, &result);
dtz = dtz_before_zeroing(wdl);
}
else if (pos.is_draw(1))
{
// In case a root move leads to a draw by repetition or
// 50-move rule, we set dtz to zero. Note: since we are
// only 1 ply from the root, this must be a true 3-fold
// repetition inside the game history.
dtz = 0;
}
else
{
// Otherwise, take dtz for the new position and correct by 1 ply
dtz = -probe_dtz(pos, &result);
dtz = dtz > 0 ? dtz + 1
: dtz < 0 ? dtz - 1 : dtz;
}
// Make sure that a mating move is assigned a dtz value of 1
if ( pos.checkers()
&& dtz == 2
&& MoveList(pos).size() == 0)
dtz = 1;
pos.undo_move(m.pv[0]);
if (result == FAIL)
return false;
// Better moves are ranked higher. Certain wins are ranked equally.
// Losing moves are ranked equally unless a 50-move draw is in sight.
int r = dtz > 0 ? (dtz + cnt50 <= 99 && !rep ? MAX_DTZ : MAX_DTZ - (dtz + cnt50))
: dtz < 0 ? (-dtz * 2 + cnt50 < 100 ? -MAX_DTZ : -MAX_DTZ + (-dtz + cnt50))
: 0;
m.tbRank = r;
// Determine the score to be displayed for this move. Assign at least
// 1 cp to cursed wins and let it grow to 49 cp as the positions gets
// closer to a real win.
m.tbScore = r >= bound ? VALUE_MATE - MAX_PLY - 1
: r > 0 ? Value((std::max( 3, r - (MAX_DTZ - 200)) * int(PawnValueEg)) / 200)
: r == 0 ? VALUE_DRAW
: r > -bound ? Value((std::min(-3, r + (MAX_DTZ - 200)) * int(PawnValueEg)) / 200)
: -VALUE_MATE + MAX_PLY + 1;
}
return true;
}
// Use the WDL tables to rank root moves.
// This is a fallback for the case that some or all DTZ tables are missing.
//
// A return value false indicates that not all probes were successful.
bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) {
static const int WDL_to_rank[] = { -MAX_DTZ, -MAX_DTZ + 101, 0, MAX_DTZ - 101, MAX_DTZ };
ProbeState result = OK;
StateInfo st;
WDLScore wdl;
bool rule50 = Options["Syzygy50MoveRule"];
// Probe and rank each move
for (auto& m : rootMoves)
{
pos.do_move(m.pv[0], st);
if (pos.is_draw(1))
wdl = WDLDraw;
else
wdl = -probe_wdl(pos, &result);
pos.undo_move(m.pv[0]);
if (result == FAIL)
return false;
m.tbRank = WDL_to_rank[wdl + 2];
if (!rule50)
wdl = wdl > WDLDraw ? WDLWin
: wdl < WDLDraw ? WDLLoss : WDLDraw;
m.tbScore = WDL_to_value[wdl + 2];
}
return true;
}
} // namespace Stockfish
stockfish-16.orig/src/syzygy/tbprobe.h 0000644 0001750 0001750 00000004700 14447216752 016350 0 ustar pdm pdm /*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#ifndef TBPROBE_H
#define TBPROBE_H
#include
#include "../search.h"
namespace Stockfish::Tablebases {
enum WDLScore {
WDLLoss = -2, // Loss
WDLBlessedLoss = -1, // Loss, but draw under 50-move rule
WDLDraw = 0, // Draw
WDLCursedWin = 1, // Win, but draw under 50-move rule
WDLWin = 2, // Win
};
// Possible states after a probing operation
enum ProbeState {
FAIL = 0, // Probe failed (missing file table)
OK = 1, // Probe successful
CHANGE_STM = -1, // DTZ should check the other side
ZEROING_BEST_MOVE = 2 // Best move zeroes DTZ (capture or pawn move)
};
extern int MaxCardinality;
void init(const std::string& paths);
WDLScore probe_wdl(Position& pos, ProbeState* result);
int probe_dtz(Position& pos, ProbeState* result);
bool root_probe(Position& pos, Search::RootMoves& rootMoves);
bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves);
void rank_root_moves(Position& pos, Search::RootMoves& rootMoves);
inline std::ostream& operator<<(std::ostream& os, const WDLScore v) {
os << (v == WDLLoss ? "Loss" :
v == WDLBlessedLoss ? "Blessed loss" :
v == WDLDraw ? "Draw" :
v == WDLCursedWin ? "Cursed win" :
v == WDLWin ? "Win" : "None");
return os;
}
inline std::ostream& operator<<(std::ostream& os, const ProbeState v) {
os << (v == FAIL ? "Failed" :
v == OK ? "Success" :
v == CHANGE_STM ? "Probed opponent side" :
v == ZEROING_BEST_MOVE ? "Best move zeroes DTZ" : "None");
return os;
}
} // namespace Stockfish::Tablebases
#endif
stockfish-16.orig/src/endgame.h 0000644 0001750 0001750 00000006606 14447216752 014744 0 ustar pdm pdm /*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2023 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#ifndef ENDGAME_H_INCLUDED
#define ENDGAME_H_INCLUDED
#include
#include
#include
#include
#include
#include "position.h"
#include "types.h"
namespace Stockfish {
/// EndgameCode lists all supported endgame functions by corresponding codes
enum EndgameCode {
EVALUATION_FUNCTIONS,
KNNK, // KNN vs K
KNNKP, // KNN vs KP
KXK, // Generic "mate lone king" eval
KBNK, // KBN vs K
KPK, // KP vs K
KRKP, // KR vs KP
KRKB, // KR vs KB
KRKN, // KR vs KN
KQKP, // KQ vs KP
KQKR, // KQ vs KR
SCALING_FUNCTIONS,
KBPsK, // KB and pawns vs K
KQKRPs, // KQ vs KR and pawns
KRPKR, // KRP vs KR
KRPKB, // KRP vs KB
KRPPKRP, // KRPP vs KRP
KPsK, // K and pawns vs K
KBPKB, // KBP vs KB
KBPPKB, // KBPP vs KB
KBPKN, // KBP vs KN
KPKP // KP vs KP
};
/// Endgame functions can be of two types depending on whether they return a
/// Value or a ScaleFactor.
template using
eg_type = typename std::conditional<(E < SCALING_FUNCTIONS), Value, ScaleFactor>::type;
/// Base and derived functors for endgame evaluation and scaling functions
template
struct EndgameBase {
explicit EndgameBase(Color c) : strongSide(c), weakSide(~c) {}
virtual ~EndgameBase() = default;
virtual T operator()(const Position&) const = 0;
const Color strongSide, weakSide;
};
template>
struct Endgame : public EndgameBase {
explicit Endgame(Color c) : EndgameBase(c) {}
T operator()(const Position&) const override;
};
/// The Endgames namespace handles the pointers to endgame evaluation and scaling
/// base objects in two std::map. We use polymorphism to invoke the actual
/// endgame function by calling its virtual operator().
namespace Endgames {
template using Ptr = std::unique_ptr>;
template using Map = std::unordered_map>;
extern std::pair