Import stockfish_12.orig.tar.gz
authorMilan Zamazal <pdm@debian.org>
Wed, 6 Jan 2021 19:35:55 +0000 (19:35 +0000)
committerMilan Zamazal <pdm@debian.org>
Wed, 6 Jan 2021 19:35:55 +0000 (19:35 +0000)
[dgit import orig stockfish_12.orig.tar.gz]

68 files changed:
.travis.yml [new file with mode: 0644]
AUTHORS [new file with mode: 0644]
Copying.txt [new file with mode: 0644]
README.md [new file with mode: 0644]
Top CPU Contributors.txt [new file with mode: 0644]
appveyor.yml [new file with mode: 0644]
src/Makefile [new file with mode: 0644]
src/benchmark.cpp [new file with mode: 0644]
src/bitbase.cpp [new file with mode: 0644]
src/bitboard.cpp [new file with mode: 0644]
src/bitboard.h [new file with mode: 0644]
src/endgame.cpp [new file with mode: 0644]
src/endgame.h [new file with mode: 0644]
src/evaluate.cpp [new file with mode: 0644]
src/evaluate.h [new file with mode: 0644]
src/incbin/UNLICENCE [new file with mode: 0644]
src/incbin/incbin.h [new file with mode: 0755]
src/main.cpp [new file with mode: 0644]
src/material.cpp [new file with mode: 0644]
src/material.h [new file with mode: 0644]
src/misc.cpp [new file with mode: 0644]
src/misc.h [new file with mode: 0644]
src/movegen.cpp [new file with mode: 0644]
src/movegen.h [new file with mode: 0644]
src/movepick.cpp [new file with mode: 0644]
src/movepick.h [new file with mode: 0644]
src/nn-82215d0fd0df.nnue [new file with mode: 0644]
src/nnue/architectures/halfkp_256x2-32-32.h [new file with mode: 0644]
src/nnue/evaluate_nnue.cpp [new file with mode: 0644]
src/nnue/evaluate_nnue.h [new file with mode: 0644]
src/nnue/features/feature_set.h [new file with mode: 0644]
src/nnue/features/features_common.h [new file with mode: 0644]
src/nnue/features/half_kp.cpp [new file with mode: 0644]
src/nnue/features/half_kp.h [new file with mode: 0644]
src/nnue/features/index_list.h [new file with mode: 0644]
src/nnue/layers/affine_transform.h [new file with mode: 0644]
src/nnue/layers/clipped_relu.h [new file with mode: 0644]
src/nnue/layers/input_slice.h [new file with mode: 0644]
src/nnue/nnue_accumulator.h [new file with mode: 0644]
src/nnue/nnue_architecture.h [new file with mode: 0644]
src/nnue/nnue_common.h [new file with mode: 0644]
src/nnue/nnue_feature_transformer.h [new file with mode: 0644]
src/pawns.cpp [new file with mode: 0644]
src/pawns.h [new file with mode: 0644]
src/position.cpp [new file with mode: 0644]
src/position.h [new file with mode: 0644]
src/psqt.cpp [new file with mode: 0644]
src/search.cpp [new file with mode: 0644]
src/search.h [new file with mode: 0644]
src/syzygy/tbprobe.cpp [new file with mode: 0644]
src/syzygy/tbprobe.h [new file with mode: 0644]
src/thread.cpp [new file with mode: 0644]
src/thread.h [new file with mode: 0644]
src/thread_win32_osx.h [new file with mode: 0644]
src/timeman.cpp [new file with mode: 0644]
src/timeman.h [new file with mode: 0644]
src/tt.cpp [new file with mode: 0644]
src/tt.h [new file with mode: 0644]
src/tune.cpp [new file with mode: 0644]
src/tune.h [new file with mode: 0644]
src/types.h [new file with mode: 0644]
src/uci.cpp [new file with mode: 0644]
src/uci.h [new file with mode: 0644]
src/ucioption.cpp [new file with mode: 0644]
tests/instrumented.sh [new file with mode: 0755]
tests/perft.sh [new file with mode: 0755]
tests/reprosearch.sh [new file with mode: 0755]
tests/signature.sh [new file with mode: 0755]

diff --git a/.travis.yml b/.travis.yml
new file mode 100644 (file)
index 0000000..092c7f5
--- /dev/null
@@ -0,0 +1,101 @@
+language: cpp
+dist: bionic
+
+matrix:
+  include:
+    - os: linux
+      compiler: gcc
+      addons:
+        apt:
+          packages: ['g++-8', 'g++-8-multilib', 'g++-multilib', 'valgrind', 'expect', 'curl']
+      env:
+        - COMPILER=g++-8
+        - COMP=gcc
+
+    - os: linux
+      compiler: clang
+      addons:
+        apt:
+          packages: ['clang-10', 'llvm-10-dev', 'g++-multilib', 'valgrind', 'expect', 'curl']
+      env:
+        - COMPILER=clang++-10
+        - COMP=clang
+
+    - os: osx
+      osx_image: xcode12
+      compiler: gcc
+      env:
+        - COMPILER=g++
+        - COMP=gcc
+
+    - os: osx
+      osx_image: xcode12
+      compiler: clang
+      env:
+        - COMPILER=clang++
+        - COMP=clang
+
+branches:
+  only:
+   - master
+
+before_script:
+  - cd src
+
+script:
+  # Download net
+  - make net
+
+  # Obtain bench reference from git log
+  - git log HEAD | grep "\b[Bb]ench[ :]\+[0-9]\{7\}" | head -n 1 | sed "s/[^0-9]*\([0-9]*\).*/\1/g" > git_sig
+  - export benchref=$(cat git_sig)
+  - echo "Reference bench:" $benchref
+
+  # Compiler version string
+  - $COMPILER -v
+
+  # test help target
+  - make help
+
+  # Verify bench number against various builds
+  - export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG"
+  - make clean && make -j2 ARCH=x86-64-modern optimize=no debug=yes build && ../tests/signature.sh $benchref
+  - export CXXFLAGS="-Werror"
+  - make clean && make -j2 ARCH=x86-64-modern build && ../tests/signature.sh $benchref
+  - make clean && make -j2 ARCH=x86-64-ssse3 build && ../tests/signature.sh $benchref
+  - make clean && make -j2 ARCH=x86-64-sse3-popcnt build && ../tests/signature.sh $benchref
+  - make clean && make -j2 ARCH=x86-64 build && ../tests/signature.sh $benchref
+  - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then make clean && make -j2 ARCH=general-64 build && ../tests/signature.sh $benchref; fi
+  - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then make clean && make -j2 ARCH=x86-32 optimize=no debug=yes build && ../tests/signature.sh $benchref; fi
+  - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then make clean && make -j2 ARCH=x86-32-sse41-popcnt build && ../tests/signature.sh $benchref; fi
+  - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then make clean && make -j2 ARCH=x86-32-sse2 build && ../tests/signature.sh $benchref; fi
+  - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then make clean && make -j2 ARCH=x86-32 build && ../tests/signature.sh $benchref; fi
+  - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then make clean && make -j2 ARCH=general-32 build && ../tests/signature.sh $benchref; fi
+  # workaround: exclude a custom version of llvm+clang, which doesn't find llvm-profdata on ubuntu
+  - if [[ "$TRAVIS_OS_NAME" != "linux" || "$COMP" == "gcc" ]]; then make clean && make -j2 ARCH=x86-64-modern profile-build && ../tests/signature.sh $benchref; fi
+
+  # compile only for some more advanced architectures (might not run in travis)
+  - make clean && make -j2 ARCH=x86-64-avx2 build
+  - make clean && make -j2 ARCH=x86-64-bmi2 build
+  - make clean && make -j2 ARCH=x86-64-avx512 build
+  - make clean && make -j2 ARCH=x86-64-vnni512 build
+  - make clean && make -j2 ARCH=x86-64-vnni256 build
+
+  #
+  # Check perft and reproducible search
+  - make clean && make -j2 ARCH=x86-64-modern build
+  - ../tests/perft.sh
+  - ../tests/reprosearch.sh
+
+  #
+  # Valgrind
+  #
+  - export CXXFLAGS="-O1 -fno-inline"
+  - if [ -x "$(command -v valgrind )" ]; then make clean && make -j2 ARCH=x86-64-modern debug=yes optimize=no build > /dev/null && ../tests/instrumented.sh --valgrind; fi
+  - if [ -x "$(command -v valgrind )" ]; then ../tests/instrumented.sh --valgrind-thread; fi
+
+  #
+  # Sanitizer
+  #
+  - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then make clean && make -j2 ARCH=x86-64-modern sanitize=undefined optimize=no debug=yes build > /dev/null && ../tests/instrumented.sh --sanitizer-undefined; fi
+  - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then make clean && make -j2 ARCH=x86-64-modern sanitize=thread    optimize=no debug=yes build > /dev/null && ../tests/instrumented.sh --sanitizer-thread; fi
diff --git a/AUTHORS b/AUTHORS
new file mode 100644 (file)
index 0000000..c00ab65
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,185 @@
+# List of authors for Stockfish, as of August 4, 2020
+
+# Founders of the Stockfish project and fishtest infrastructure
+Tord Romstad (romstad)
+Marco Costalba (mcostalba)
+Joona Kiiski (zamar)
+Gary Linscott (glinscott)
+
+# Authors and inventors of NNUE, training, NNUE port
+Yu Nasu (ynasu87)
+Motohiro Isozaki (yaneurao)
+Hisayori Noda (nodchip)
+
+# all other authors of the code in alphabetical order
+Aditya (absimaldata)
+Adrian Petrescu (apetresc)
+Ajith Chandy Jose (ajithcj)
+Alain Savard (Rocky640)
+Alayan Feh (Alayan-stk-2)
+Alexander Kure
+Alexander Pagel (Lolligerhans)
+Ali AlZhrani (Cooffe)
+Andrew Grant (AndyGrant)
+Andrey Neporada (nepal)
+Andy Duplain
+Aram Tumanian (atumanian)
+Arjun Temurnikar
+Auguste Pop
+Balint Pfliegel
+Ben Koshy (BKSpurgeon)
+Bill Henry (VoyagerOne)
+Bojun Guo (noobpwnftw, Nooby)
+braich
+Brian Sheppard (SapphireBrand, briansheppard-toast)
+Bryan Cross (crossbr)
+candirufish
+Chess13234
+Chris Cain (ceebo)
+Dale Weiler (graphitemaster)
+Dan Schmidt (dfannius)
+Daniel Axtens (daxtens)
+Daniel Dugovic (ddugovic)
+Dariusz Orzechowski (dorzechowski)
+David Zar
+Daylen Yang (daylen)
+DiscanX
+Dominik Schlösser (domschl)
+double-beep
+Eduardo Cáceres (eduherminio)
+Eelco de Groot (KingDefender)
+Elvin Liu (solarlight2)
+erbsenzaehler
+Ernesto Gatti
+Linmiao Xu (linrock)
+Fabian Beuke (madnight)
+Fabian Fichter (ianfab)
+Fanael Linithien (Fanael)
+fanon
+Fauzi Akram Dabat (FauziAkram)
+Felix Wittmann
+gamander
+Gary Heckman (gheckman)
+George Sobala (gsobala)
+gguliash
+Gian-Carlo Pascutto (gcp)
+Gontran Lemaire (gonlem)
+Goodkov Vasiliy Aleksandrovich (goodkov)
+Gregor Cramer
+GuardianRM
+Günther Demetz (pb00067, pb00068)
+Guy Vreuls (gvreuls)
+Henri Wiechers
+Hiraoka Takuya (HiraokaTakuya)
+homoSapiensSapiens
+Hongzhi Cheng
+Ivan Ivec (IIvec)
+Jacques B. (Timshel)
+Jan Ondruš (hxim)
+Jared Kish (Kurtbusch)
+Jarrod Torriero (DU-jdto)
+Jean Gauthier (OuaisBla)
+Jean-Francois Romang (jromang)
+Jekaa
+Jerry Donald Watson (jerrydonaldwatson)
+jjoshua2
+Jonathan Calovski (Mysseno)
+Jonathan Dumale (SFisGOD)
+Joost VandeVondele (vondele)
+Jörg Oster (joergoster)
+Joseph Ellis (jhellis3)
+Joseph R. Prostko
+jundery
+Justin Blanchard (UncombedCoconut)
+Kelly Wilson
+Ken Takusagawa
+kinderchocolate
+Kiran Panditrao (Krgp)
+Kojirion
+Leonardo Ljubičić (ICCF World Champion)
+Leonid Pechenik (lp--)
+Linus Arver (listx)
+loco-loco
+Lub van den Berg (ElbertoOne)
+Luca Brivio (lucabrivio)
+Lucas Braesch (lucasart)
+Lyudmil Antonov (lantonov)
+Maciej Żenczykowski (zenczykowski)
+Malcolm Campbell (xoto10)
+Mark Tenzer (31m059)
+marotear
+Matthew Lai (matthewlai)
+Matthew Sullivan (Matt14916)
+Michael An (man)
+Michael Byrne (MichaelB7)
+Michael Chaly (Vizvezdenec)
+Michael Stembera (mstembera)
+Michael Whiteley (protonspring)
+Michel Van den Bergh (vdbergh)
+Miguel Lahoz (miguel-l)
+Mikael Bäckman (mbootsector)
+Mira
+Miroslav Fontán (Hexik)
+Moez Jellouli (MJZ1977)
+Mohammed Li (tthsqe12)
+Nathan Rugg (nmrugg)
+Nick Pelling (nickpelling)
+Nicklas Persson (NicklasPersson)
+Niklas Fiekas (niklasf)
+Nikolay Kostov (NikolayIT)
+Nguyen Pham (nguyenpham)
+Norman Schmidt (FireFather)
+notruck
+Ondrej Mosnáček (WOnder93)
+Oskar Werkelin Ahlin
+Pablo Vazquez
+Panthee
+Pascal Romaret
+Pasquale Pigazzini (ppigazzini)
+Patrick Jansen (mibere)
+pellanda
+Peter Zsifkovits (CoffeeOne)
+Praveen Kumar Tummala (praveentml)
+Rahul Dsilva (silversolver1)
+Ralph Stößer (Ralph Stoesser)
+Raminder Singh
+renouve
+Reuven Peleg
+Richard Lloyd
+Rodrigo Exterckötter Tjäder
+Ron Britvich (Britvich)
+Ronald de Man (syzygy1, syzygy)
+rqs
+Ryan Schmitt
+Ryan Takker
+Sami Kiminki (skiminki)
+Sebastian Buchwald (UniQP)
+Sergei Antonov (saproj)
+Sergei Ivanov (svivanov72)
+Sergio Vieri (sergiovieri)
+sf-x
+Shane Booth (shane31)
+Shawn Varghese (xXH4CKST3RXx)
+Stefan Geschwentner (locutus2)
+Stefano Cardanobile (Stefano80)
+Steinar Gunderson (sesse)
+Stéphane Nicolet (snicolet)
+Thanar2
+thaspel
+theo77186
+Tom Truscott
+Tom Vijlbrief (tomtor)
+Tomasz Sobczyk (Sopel97)
+Torsten Franz (torfranz, tfranzer)
+Tracey Emery (basepr1me)
+tttak
+Unai Corzo (unaiic)
+Uri Blass (uriblass)
+Vince Negri (cuddlestmonkey)
+zz4032
+
+
+# Additionally, we acknowledge the authors and maintainers of fishtest,
+# an amazing and essential framework for the development of Stockfish!
+#
+# https://github.com/glinscott/fishtest/blob/master/AUTHORS
diff --git a/Copying.txt b/Copying.txt
new file mode 100644 (file)
index 0000000..818433e
--- /dev/null
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE\r
+                       Version 3, 29 June 2007\r
+\r
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\r
+ Everyone is permitted to copy and distribute verbatim copies\r
+ of this license document, but changing it is not allowed.\r
+\r
+                            Preamble\r
+\r
+  The GNU General Public License is a free, copyleft license for\r
+software and other kinds of works.\r
+\r
+  The licenses for most software and other practical works are designed\r
+to take away your freedom to share and change the works.  By contrast,\r
+the GNU General Public License is intended to guarantee your freedom to\r
+share and change all versions of a program--to make sure it remains free\r
+software for all its users.  We, the Free Software Foundation, use the\r
+GNU General Public License for most of our software; it applies also to\r
+any other work released this way by its authors.  You can apply it to\r
+your programs, too.\r
+\r
+  When we speak of free software, we are referring to freedom, not\r
+price.  Our General Public Licenses are designed to make sure that you\r
+have the freedom to distribute copies of free software (and charge for\r
+them if you wish), that you receive source code or can get it if you\r
+want it, that you can change the software or use pieces of it in new\r
+free programs, and that you know you can do these things.\r
+\r
+  To protect your rights, we need to prevent others from denying you\r
+these rights or asking you to surrender the rights.  Therefore, you have\r
+certain responsibilities if you distribute copies of the software, or if\r
+you modify it: responsibilities to respect the freedom of others.\r
+\r
+  For example, if you distribute copies of such a program, whether\r
+gratis or for a fee, you must pass on to the recipients the same\r
+freedoms that you received.  You must make sure that they, too, receive\r
+or can get the source code.  And you must show them these terms so they\r
+know their rights.\r
+\r
+  Developers that use the GNU GPL protect your rights with two steps:\r
+(1) assert copyright on the software, and (2) offer you this License\r
+giving you legal permission to copy, distribute and/or modify it.\r
+\r
+  For the developers' and authors' protection, the GPL clearly explains\r
+that there is no warranty for this free software.  For both users' and\r
+authors' sake, the GPL requires that modified versions be marked as\r
+changed, so that their problems will not be attributed erroneously to\r
+authors of previous versions.\r
+\r
+  Some devices are designed to deny users access to install or run\r
+modified versions of the software inside them, although the manufacturer\r
+can do so.  This is fundamentally incompatible with the aim of\r
+protecting users' freedom to change the software.  The systematic\r
+pattern of such abuse occurs in the area of products for individuals to\r
+use, which is precisely where it is most unacceptable.  Therefore, we\r
+have designed this version of the GPL to prohibit the practice for those\r
+products.  If such problems arise substantially in other domains, we\r
+stand ready to extend this provision to those domains in future versions\r
+of the GPL, as needed to protect the freedom of users.\r
+\r
+  Finally, every program is threatened constantly by software patents.\r
+States should not allow patents to restrict development and use of\r
+software on general-purpose computers, but in those that do, we wish to\r
+avoid the special danger that patents applied to a free program could\r
+make it effectively proprietary.  To prevent this, the GPL assures that\r
+patents cannot be used to render the program non-free.\r
+\r
+  The precise terms and conditions for copying, distribution and\r
+modification follow.\r
+\r
+                       TERMS AND CONDITIONS\r
+\r
+  0. Definitions.\r
+\r
+  "This License" refers to version 3 of the GNU General Public License.\r
+\r
+  "Copyright" also means copyright-like laws that apply to other kinds of\r
+works, such as semiconductor masks.\r
+\r
+  "The Program" refers to any copyrightable work licensed under this\r
+License.  Each licensee is addressed as "you".  "Licensees" and\r
+"recipients" may be individuals or organizations.\r
+\r
+  To "modify" a work means to copy from or adapt all or part of the work\r
+in a fashion requiring copyright permission, other than the making of an\r
+exact copy.  The resulting work is called a "modified version" of the\r
+earlier work or a work "based on" the earlier work.\r
+\r
+  A "covered work" means either the unmodified Program or a work based\r
+on the Program.\r
+\r
+  To "propagate" a work means to do anything with it that, without\r
+permission, would make you directly or secondarily liable for\r
+infringement under applicable copyright law, except executing it on a\r
+computer or modifying a private copy.  Propagation includes copying,\r
+distribution (with or without modification), making available to the\r
+public, and in some countries other activities as well.\r
+\r
+  To "convey" a work means any kind of propagation that enables other\r
+parties to make or receive copies.  Mere interaction with a user through\r
+a computer network, with no transfer of a copy, is not conveying.\r
+\r
+  An interactive user interface displays "Appropriate Legal Notices"\r
+to the extent that it includes a convenient and prominently visible\r
+feature that (1) displays an appropriate copyright notice, and (2)\r
+tells the user that there is no warranty for the work (except to the\r
+extent that warranties are provided), that licensees may convey the\r
+work under this License, and how to view a copy of this License.  If\r
+the interface presents a list of user commands or options, such as a\r
+menu, a prominent item in the list meets this criterion.\r
+\r
+  1. Source Code.\r
+\r
+  The "source code" for a work means the preferred form of the work\r
+for making modifications to it.  "Object code" means any non-source\r
+form of a work.\r
+\r
+  A "Standard Interface" means an interface that either is an official\r
+standard defined by a recognized standards body, or, in the case of\r
+interfaces specified for a particular programming language, one that\r
+is widely used among developers working in that language.\r
+\r
+  The "System Libraries" of an executable work include anything, other\r
+than the work as a whole, that (a) is included in the normal form of\r
+packaging a Major Component, but which is not part of that Major\r
+Component, and (b) serves only to enable use of the work with that\r
+Major Component, or to implement a Standard Interface for which an\r
+implementation is available to the public in source code form.  A\r
+"Major Component", in this context, means a major essential component\r
+(kernel, window system, and so on) of the specific operating system\r
+(if any) on which the executable work runs, or a compiler used to\r
+produce the work, or an object code interpreter used to run it.\r
+\r
+  The "Corresponding Source" for a work in object code form means all\r
+the source code needed to generate, install, and (for an executable\r
+work) run the object code and to modify the work, including scripts to\r
+control those activities.  However, it does not include the work's\r
+System Libraries, or general-purpose tools or generally available free\r
+programs which are used unmodified in performing those activities but\r
+which are not part of the work.  For example, Corresponding Source\r
+includes interface definition files associated with source files for\r
+the work, and the source code for shared libraries and dynamically\r
+linked subprograms that the work is specifically designed to require,\r
+such as by intimate data communication or control flow between those\r
+subprograms and other parts of the work.\r
+\r
+  The Corresponding Source need not include anything that users\r
+can regenerate automatically from other parts of the Corresponding\r
+Source.\r
+\r
+  The Corresponding Source for a work in source code form is that\r
+same work.\r
+\r
+  2. Basic Permissions.\r
+\r
+  All rights granted under this License are granted for the term of\r
+copyright on the Program, and are irrevocable provided the stated\r
+conditions are met.  This License explicitly affirms your unlimited\r
+permission to run the unmodified Program.  The output from running a\r
+covered work is covered by this License only if the output, given its\r
+content, constitutes a covered work.  This License acknowledges your\r
+rights of fair use or other equivalent, as provided by copyright law.\r
+\r
+  You may make, run and propagate covered works that you do not\r
+convey, without conditions so long as your license otherwise remains\r
+in force.  You may convey covered works to others for the sole purpose\r
+of having them make modifications exclusively for you, or provide you\r
+with facilities for running those works, provided that you comply with\r
+the terms of this License in conveying all material for which you do\r
+not control copyright.  Those thus making or running the covered works\r
+for you must do so exclusively on your behalf, under your direction\r
+and control, on terms that prohibit them from making any copies of\r
+your copyrighted material outside their relationship with you.\r
+\r
+  Conveying under any other circumstances is permitted solely under\r
+the conditions stated below.  Sublicensing is not allowed; section 10\r
+makes it unnecessary.\r
+\r
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\r
+\r
+  No covered work shall be deemed part of an effective technological\r
+measure under any applicable law fulfilling obligations under article\r
+11 of the WIPO copyright treaty adopted on 20 December 1996, or\r
+similar laws prohibiting or restricting circumvention of such\r
+measures.\r
+\r
+  When you convey a covered work, you waive any legal power to forbid\r
+circumvention of technological measures to the extent such circumvention\r
+is effected by exercising rights under this License with respect to\r
+the covered work, and you disclaim any intention to limit operation or\r
+modification of the work as a means of enforcing, against the work's\r
+users, your or third parties' legal rights to forbid circumvention of\r
+technological measures.\r
+\r
+  4. Conveying Verbatim Copies.\r
+\r
+  You may convey verbatim copies of the Program's source code as you\r
+receive it, in any medium, provided that you conspicuously and\r
+appropriately publish on each copy an appropriate copyright notice;\r
+keep intact all notices stating that this License and any\r
+non-permissive terms added in accord with section 7 apply to the code;\r
+keep intact all notices of the absence of any warranty; and give all\r
+recipients a copy of this License along with the Program.\r
+\r
+  You may charge any price or no price for each copy that you convey,\r
+and you may offer support or warranty protection for a fee.\r
+\r
+  5. Conveying Modified Source Versions.\r
+\r
+  You may convey a work based on the Program, or the modifications to\r
+produce it from the Program, in the form of source code under the\r
+terms of section 4, provided that you also meet all of these conditions:\r
+\r
+    a) The work must carry prominent notices stating that you modified\r
+    it, and giving a relevant date.\r
+\r
+    b) The work must carry prominent notices stating that it is\r
+    released under this License and any conditions added under section\r
+    7.  This requirement modifies the requirement in section 4 to\r
+    "keep intact all notices".\r
+\r
+    c) You must license the entire work, as a whole, under this\r
+    License to anyone who comes into possession of a copy.  This\r
+    License will therefore apply, along with any applicable section 7\r
+    additional terms, to the whole of the work, and all its parts,\r
+    regardless of how they are packaged.  This License gives no\r
+    permission to license the work in any other way, but it does not\r
+    invalidate such permission if you have separately received it.\r
+\r
+    d) If the work has interactive user interfaces, each must display\r
+    Appropriate Legal Notices; however, if the Program has interactive\r
+    interfaces that do not display Appropriate Legal Notices, your\r
+    work need not make them do so.\r
+\r
+  A compilation of a covered work with other separate and independent\r
+works, which are not by their nature extensions of the covered work,\r
+and which are not combined with it such as to form a larger program,\r
+in or on a volume of a storage or distribution medium, is called an\r
+"aggregate" if the compilation and its resulting copyright are not\r
+used to limit the access or legal rights of the compilation's users\r
+beyond what the individual works permit.  Inclusion of a covered work\r
+in an aggregate does not cause this License to apply to the other\r
+parts of the aggregate.\r
+\r
+  6. Conveying Non-Source Forms.\r
+\r
+  You may convey a covered work in object code form under the terms\r
+of sections 4 and 5, provided that you also convey the\r
+machine-readable Corresponding Source under the terms of this License,\r
+in one of these ways:\r
+\r
+    a) Convey the object code in, or embodied in, a physical product\r
+    (including a physical distribution medium), accompanied by the\r
+    Corresponding Source fixed on a durable physical medium\r
+    customarily used for software interchange.\r
+\r
+    b) Convey the object code in, or embodied in, a physical product\r
+    (including a physical distribution medium), accompanied by a\r
+    written offer, valid for at least three years and valid for as\r
+    long as you offer spare parts or customer support for that product\r
+    model, to give anyone who possesses the object code either (1) a\r
+    copy of the Corresponding Source for all the software in the\r
+    product that is covered by this License, on a durable physical\r
+    medium customarily used for software interchange, for a price no\r
+    more than your reasonable cost of physically performing this\r
+    conveying of source, or (2) access to copy the\r
+    Corresponding Source from a network server at no charge.\r
+\r
+    c) Convey individual copies of the object code with a copy of the\r
+    written offer to provide the Corresponding Source.  This\r
+    alternative is allowed only occasionally and noncommercially, and\r
+    only if you received the object code with such an offer, in accord\r
+    with subsection 6b.\r
+\r
+    d) Convey the object code by offering access from a designated\r
+    place (gratis or for a charge), and offer equivalent access to the\r
+    Corresponding Source in the same way through the same place at no\r
+    further charge.  You need not require recipients to copy the\r
+    Corresponding Source along with the object code.  If the place to\r
+    copy the object code is a network server, the Corresponding Source\r
+    may be on a different server (operated by you or a third party)\r
+    that supports equivalent copying facilities, provided you maintain\r
+    clear directions next to the object code saying where to find the\r
+    Corresponding Source.  Regardless of what server hosts the\r
+    Corresponding Source, you remain obligated to ensure that it is\r
+    available for as long as needed to satisfy these requirements.\r
+\r
+    e) Convey the object code using peer-to-peer transmission, provided\r
+    you inform other peers where the object code and Corresponding\r
+    Source of the work are being offered to the general public at no\r
+    charge under subsection 6d.\r
+\r
+  A separable portion of the object code, whose source code is excluded\r
+from the Corresponding Source as a System Library, need not be\r
+included in conveying the object code work.\r
+\r
+  A "User Product" is either (1) a "consumer product", which means any\r
+tangible personal property which is normally used for personal, family,\r
+or household purposes, or (2) anything designed or sold for incorporation\r
+into a dwelling.  In determining whether a product is a consumer product,\r
+doubtful cases shall be resolved in favor of coverage.  For a particular\r
+product received by a particular user, "normally used" refers to a\r
+typical or common use of that class of product, regardless of the status\r
+of the particular user or of the way in which the particular user\r
+actually uses, or expects or is expected to use, the product.  A product\r
+is a consumer product regardless of whether the product has substantial\r
+commercial, industrial or non-consumer uses, unless such uses represent\r
+the only significant mode of use of the product.\r
+\r
+  "Installation Information" for a User Product means any methods,\r
+procedures, authorization keys, or other information required to install\r
+and execute modified versions of a covered work in that User Product from\r
+a modified version of its Corresponding Source.  The information must\r
+suffice to ensure that the continued functioning of the modified object\r
+code is in no case prevented or interfered with solely because\r
+modification has been made.\r
+\r
+  If you convey an object code work under this section in, or with, or\r
+specifically for use in, a User Product, and the conveying occurs as\r
+part of a transaction in which the right of possession and use of the\r
+User Product is transferred to the recipient in perpetuity or for a\r
+fixed term (regardless of how the transaction is characterized), the\r
+Corresponding Source conveyed under this section must be accompanied\r
+by the Installation Information.  But this requirement does not apply\r
+if neither you nor any third party retains the ability to install\r
+modified object code on the User Product (for example, the work has\r
+been installed in ROM).\r
+\r
+  The requirement to provide Installation Information does not include a\r
+requirement to continue to provide support service, warranty, or updates\r
+for a work that has been modified or installed by the recipient, or for\r
+the User Product in which it has been modified or installed.  Access to a\r
+network may be denied when the modification itself materially and\r
+adversely affects the operation of the network or violates the rules and\r
+protocols for communication across the network.\r
+\r
+  Corresponding Source conveyed, and Installation Information provided,\r
+in accord with this section must be in a format that is publicly\r
+documented (and with an implementation available to the public in\r
+source code form), and must require no special password or key for\r
+unpacking, reading or copying.\r
+\r
+  7. Additional Terms.\r
+\r
+  "Additional permissions" are terms that supplement the terms of this\r
+License by making exceptions from one or more of its conditions.\r
+Additional permissions that are applicable to the entire Program shall\r
+be treated as though they were included in this License, to the extent\r
+that they are valid under applicable law.  If additional permissions\r
+apply only to part of the Program, that part may be used separately\r
+under those permissions, but the entire Program remains governed by\r
+this License without regard to the additional permissions.\r
+\r
+  When you convey a copy of a covered work, you may at your option\r
+remove any additional permissions from that copy, or from any part of\r
+it.  (Additional permissions may be written to require their own\r
+removal in certain cases when you modify the work.)  You may place\r
+additional permissions on material, added by you to a covered work,\r
+for which you have or can give appropriate copyright permission.\r
+\r
+  Notwithstanding any other provision of this License, for material you\r
+add to a covered work, you may (if authorized by the copyright holders of\r
+that material) supplement the terms of this License with terms:\r
+\r
+    a) Disclaiming warranty or limiting liability differently from the\r
+    terms of sections 15 and 16 of this License; or\r
+\r
+    b) Requiring preservation of specified reasonable legal notices or\r
+    author attributions in that material or in the Appropriate Legal\r
+    Notices displayed by works containing it; or\r
+\r
+    c) Prohibiting misrepresentation of the origin of that material, or\r
+    requiring that modified versions of such material be marked in\r
+    reasonable ways as different from the original version; or\r
+\r
+    d) Limiting the use for publicity purposes of names of licensors or\r
+    authors of the material; or\r
+\r
+    e) Declining to grant rights under trademark law for use of some\r
+    trade names, trademarks, or service marks; or\r
+\r
+    f) Requiring indemnification of licensors and authors of that\r
+    material by anyone who conveys the material (or modified versions of\r
+    it) with contractual assumptions of liability to the recipient, for\r
+    any liability that these contractual assumptions directly impose on\r
+    those licensors and authors.\r
+\r
+  All other non-permissive additional terms are considered "further\r
+restrictions" within the meaning of section 10.  If the Program as you\r
+received it, or any part of it, contains a notice stating that it is\r
+governed by this License along with a term that is a further\r
+restriction, you may remove that term.  If a license document contains\r
+a further restriction but permits relicensing or conveying under this\r
+License, you may add to a covered work material governed by the terms\r
+of that license document, provided that the further restriction does\r
+not survive such relicensing or conveying.\r
+\r
+  If you add terms to a covered work in accord with this section, you\r
+must place, in the relevant source files, a statement of the\r
+additional terms that apply to those files, or a notice indicating\r
+where to find the applicable terms.\r
+\r
+  Additional terms, permissive or non-permissive, may be stated in the\r
+form of a separately written license, or stated as exceptions;\r
+the above requirements apply either way.\r
+\r
+  8. Termination.\r
+\r
+  You may not propagate or modify a covered work except as expressly\r
+provided under this License.  Any attempt otherwise to propagate or\r
+modify it is void, and will automatically terminate your rights under\r
+this License (including any patent licenses granted under the third\r
+paragraph of section 11).\r
+\r
+  However, if you cease all violation of this License, then your\r
+license from a particular copyright holder is reinstated (a)\r
+provisionally, unless and until the copyright holder explicitly and\r
+finally terminates your license, and (b) permanently, if the copyright\r
+holder fails to notify you of the violation by some reasonable means\r
+prior to 60 days after the cessation.\r
+\r
+  Moreover, your license from a particular copyright holder is\r
+reinstated permanently if the copyright holder notifies you of the\r
+violation by some reasonable means, this is the first time you have\r
+received notice of violation of this License (for any work) from that\r
+copyright holder, and you cure the violation prior to 30 days after\r
+your receipt of the notice.\r
+\r
+  Termination of your rights under this section does not terminate the\r
+licenses of parties who have received copies or rights from you under\r
+this License.  If your rights have been terminated and not permanently\r
+reinstated, you do not qualify to receive new licenses for the same\r
+material under section 10.\r
+\r
+  9. Acceptance Not Required for Having Copies.\r
+\r
+  You are not required to accept this License in order to receive or\r
+run a copy of the Program.  Ancillary propagation of a covered work\r
+occurring solely as a consequence of using peer-to-peer transmission\r
+to receive a copy likewise does not require acceptance.  However,\r
+nothing other than this License grants you permission to propagate or\r
+modify any covered work.  These actions infringe copyright if you do\r
+not accept this License.  Therefore, by modifying or propagating a\r
+covered work, you indicate your acceptance of this License to do so.\r
+\r
+  10. Automatic Licensing of Downstream Recipients.\r
+\r
+  Each time you convey a covered work, the recipient automatically\r
+receives a license from the original licensors, to run, modify and\r
+propagate that work, subject to this License.  You are not responsible\r
+for enforcing compliance by third parties with this License.\r
+\r
+  An "entity transaction" is a transaction transferring control of an\r
+organization, or substantially all assets of one, or subdividing an\r
+organization, or merging organizations.  If propagation of a covered\r
+work results from an entity transaction, each party to that\r
+transaction who receives a copy of the work also receives whatever\r
+licenses to the work the party's predecessor in interest had or could\r
+give under the previous paragraph, plus a right to possession of the\r
+Corresponding Source of the work from the predecessor in interest, if\r
+the predecessor has it or can get it with reasonable efforts.\r
+\r
+  You may not impose any further restrictions on the exercise of the\r
+rights granted or affirmed under this License.  For example, you may\r
+not impose a license fee, royalty, or other charge for exercise of\r
+rights granted under this License, and you may not initiate litigation\r
+(including a cross-claim or counterclaim in a lawsuit) alleging that\r
+any patent claim is infringed by making, using, selling, offering for\r
+sale, or importing the Program or any portion of it.\r
+\r
+  11. Patents.\r
+\r
+  A "contributor" is a copyright holder who authorizes use under this\r
+License of the Program or a work on which the Program is based.  The\r
+work thus licensed is called the contributor's "contributor version".\r
+\r
+  A contributor's "essential patent claims" are all patent claims\r
+owned or controlled by the contributor, whether already acquired or\r
+hereafter acquired, that would be infringed by some manner, permitted\r
+by this License, of making, using, or selling its contributor version,\r
+but do not include claims that would be infringed only as a\r
+consequence of further modification of the contributor version.  For\r
+purposes of this definition, "control" includes the right to grant\r
+patent sublicenses in a manner consistent with the requirements of\r
+this License.\r
+\r
+  Each contributor grants you a non-exclusive, worldwide, royalty-free\r
+patent license under the contributor's essential patent claims, to\r
+make, use, sell, offer for sale, import and otherwise run, modify and\r
+propagate the contents of its contributor version.\r
+\r
+  In the following three paragraphs, a "patent license" is any express\r
+agreement or commitment, however denominated, not to enforce a patent\r
+(such as an express permission to practice a patent or covenant not to\r
+sue for patent infringement).  To "grant" such a patent license to a\r
+party means to make such an agreement or commitment not to enforce a\r
+patent against the party.\r
+\r
+  If you convey a covered work, knowingly relying on a patent license,\r
+and the Corresponding Source of the work is not available for anyone\r
+to copy, free of charge and under the terms of this License, through a\r
+publicly available network server or other readily accessible means,\r
+then you must either (1) cause the Corresponding Source to be so\r
+available, or (2) arrange to deprive yourself of the benefit of the\r
+patent license for this particular work, or (3) arrange, in a manner\r
+consistent with the requirements of this License, to extend the patent\r
+license to downstream recipients.  "Knowingly relying" means you have\r
+actual knowledge that, but for the patent license, your conveying the\r
+covered work in a country, or your recipient's use of the covered work\r
+in a country, would infringe one or more identifiable patents in that\r
+country that you have reason to believe are valid.\r
+\r
+  If, pursuant to or in connection with a single transaction or\r
+arrangement, you convey, or propagate by procuring conveyance of, a\r
+covered work, and grant a patent license to some of the parties\r
+receiving the covered work authorizing them to use, propagate, modify\r
+or convey a specific copy of the covered work, then the patent license\r
+you grant is automatically extended to all recipients of the covered\r
+work and works based on it.\r
+\r
+  A patent license is "discriminatory" if it does not include within\r
+the scope of its coverage, prohibits the exercise of, or is\r
+conditioned on the non-exercise of one or more of the rights that are\r
+specifically granted under this License.  You may not convey a covered\r
+work if you are a party to an arrangement with a third party that is\r
+in the business of distributing software, under which you make payment\r
+to the third party based on the extent of your activity of conveying\r
+the work, and under which the third party grants, to any of the\r
+parties who would receive the covered work from you, a discriminatory\r
+patent license (a) in connection with copies of the covered work\r
+conveyed by you (or copies made from those copies), or (b) primarily\r
+for and in connection with specific products or compilations that\r
+contain the covered work, unless you entered into that arrangement,\r
+or that patent license was granted, prior to 28 March 2007.\r
+\r
+  Nothing in this License shall be construed as excluding or limiting\r
+any implied license or other defenses to infringement that may\r
+otherwise be available to you under applicable patent law.\r
+\r
+  12. No Surrender of Others' Freedom.\r
+\r
+  If conditions are imposed on you (whether by court order, agreement or\r
+otherwise) that contradict the conditions of this License, they do not\r
+excuse you from the conditions of this License.  If you cannot convey a\r
+covered work so as to satisfy simultaneously your obligations under this\r
+License and any other pertinent obligations, then as a consequence you may\r
+not convey it at all.  For example, if you agree to terms that obligate you\r
+to collect a royalty for further conveying from those to whom you convey\r
+the Program, the only way you could satisfy both those terms and this\r
+License would be to refrain entirely from conveying the Program.\r
+\r
+  13. Use with the GNU Affero General Public License.\r
+\r
+  Notwithstanding any other provision of this License, you have\r
+permission to link or combine any covered work with a work licensed\r
+under version 3 of the GNU Affero General Public License into a single\r
+combined work, and to convey the resulting work.  The terms of this\r
+License will continue to apply to the part which is the covered work,\r
+but the special requirements of the GNU Affero General Public License,\r
+section 13, concerning interaction through a network will apply to the\r
+combination as such.\r
+\r
+  14. Revised Versions of this License.\r
+\r
+  The Free Software Foundation may publish revised and/or new versions of\r
+the GNU General Public License from time to time.  Such new versions will\r
+be similar in spirit to the present version, but may differ in detail to\r
+address new problems or concerns.\r
+\r
+  Each version is given a distinguishing version number.  If the\r
+Program specifies that a certain numbered version of the GNU General\r
+Public License "or any later version" applies to it, you have the\r
+option of following the terms and conditions either of that numbered\r
+version or of any later version published by the Free Software\r
+Foundation.  If the Program does not specify a version number of the\r
+GNU General Public License, you may choose any version ever published\r
+by the Free Software Foundation.\r
+\r
+  If the Program specifies that a proxy can decide which future\r
+versions of the GNU General Public License can be used, that proxy's\r
+public statement of acceptance of a version permanently authorizes you\r
+to choose that version for the Program.\r
+\r
+  Later license versions may give you additional or different\r
+permissions.  However, no additional obligations are imposed on any\r
+author or copyright holder as a result of your choosing to follow a\r
+later version.\r
+\r
+  15. Disclaimer of Warranty.\r
+\r
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\r
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\r
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY\r
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\r
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\r
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\r
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.\r
+\r
+  16. Limitation of Liability.\r
+\r
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\r
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\r
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\r
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\r
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\r
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\r
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\r
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\r
+SUCH DAMAGES.\r
+\r
+  17. Interpretation of Sections 15 and 16.\r
+\r
+  If the disclaimer of warranty and limitation of liability provided\r
+above cannot be given local legal effect according to their terms,\r
+reviewing courts shall apply local law that most closely approximates\r
+an absolute waiver of all civil liability in connection with the\r
+Program, unless a warranty or assumption of liability accompanies a\r
+copy of the Program in return for a fee.\r
+\r
+                     END OF TERMS AND CONDITIONS\r
+\r
+            How to Apply These Terms to Your New Programs\r
+\r
+  If you develop a new program, and you want it to be of the greatest\r
+possible use to the public, the best way to achieve this is to make it\r
+free software which everyone can redistribute and change under these terms.\r
+\r
+  To do so, attach the following notices to the program.  It is safest\r
+to attach them to the start of each source file to most effectively\r
+state the exclusion of warranty; and each file should have at least\r
+the "copyright" line and a pointer to where the full notice is found.\r
+\r
+    <one line to give the program's name and a brief idea of what it does.>\r
+    Copyright (C) <year>  <name of author>\r
+\r
+    This program is free software: you can redistribute it and/or modify\r
+    it under the terms of the GNU General Public License as published by\r
+    the Free Software Foundation, either version 3 of the License, or\r
+    (at your option) any later version.\r
+\r
+    This program is distributed in the hope that it will be useful,\r
+    but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+    GNU General Public License for more details.\r
+\r
+    You should have received a copy of the GNU General Public License\r
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.\r
+\r
+Also add information on how to contact you by electronic and paper mail.\r
+\r
+  If the program does terminal interaction, make it output a short\r
+notice like this when it starts in an interactive mode:\r
+\r
+    <program>  Copyright (C) <year>  <name of author>\r
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\r
+    This is free software, and you are welcome to redistribute it\r
+    under certain conditions; type `show c' for details.\r
+\r
+The hypothetical commands `show w' and `show c' should show the appropriate\r
+parts of the General Public License.  Of course, your program's commands\r
+might be different; for a GUI interface, you would use an "about box".\r
+\r
+  You should also get your employer (if you work as a programmer) or school,\r
+if any, to sign a "copyright disclaimer" for the program, if necessary.\r
+For more information on this, and how to apply and follow the GNU GPL, see\r
+<http://www.gnu.org/licenses/>.\r
+\r
+  The GNU General Public License does not permit incorporating your program\r
+into proprietary programs.  If your program is a subroutine library, you\r
+may consider it more useful to permit linking proprietary applications with\r
+the library.  If this is what you want to do, use the GNU Lesser General\r
+Public License instead of this License.  But first, please read\r
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.\r
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..96a495a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,296 @@
+## Overview
+
+[![Build Status](https://travis-ci.org/official-stockfish/Stockfish.svg?branch=master)](https://travis-ci.org/official-stockfish/Stockfish)
+[![Build Status](https://ci.appveyor.com/api/projects/status/github/official-stockfish/Stockfish?branch=master&svg=true)](https://ci.appveyor.com/project/mcostalba/stockfish/branch/master)
+
+[Stockfish](https://stockfishchess.org) is a free, powerful UCI chess engine
+derived from Glaurung 2.1. Stockfish is not a complete chess program and requires a
+UCI-compatible graphical user interface (GUI) (e.g. XBoard with PolyGlot, Scid,
+Cute Chess, eboard, Arena, Sigma Chess, Shredder, Chess Partner or Fritz) in order
+to be used comfortably. Read the documentation for your GUI of choice for information
+about how to use Stockfish with it.
+
+The Stockfish engine features two evaluation functions for chess, the classical
+evaluation based on handcrafted terms, and the NNUE evaluation based on efficiently
+updateable neural networks. The classical evaluation runs efficiently on almost all
+CPU architectures, while the NNUE evaluation benefits from the vector
+intrinsics available on most CPUs (sse2, avx2, neon, or similar).
+
+
+## Files
+
+This distribution of Stockfish consists of the following files:
+
+  * Readme.md, the file you are currently reading.
+
+  * Copying.txt, a text file containing the GNU General Public License version 3.
+
+  * src, a subdirectory containing the full source code, including a Makefile
+    that can be used to compile Stockfish on Unix-like systems.
+
+  * a file with the .nnue extension, storing the neural network for the NNUE 
+    evaluation. Binary distributions will have this file embedded.
+
+Note: to use the NNUE evaluation, the additional data file with neural network parameters
+needs to be available. Normally, this file is already embedded in the binary or it can be downloaded.
+The filename for the default (recommended) net can be found as the default
+value of the `EvalFile` UCI option, with the format `nn-[SHA256 first 12 digits].nnue`
+(for instance, `nn-c157e0a5755b.nnue`). This file can be downloaded from
+```
+https://tests.stockfishchess.org/api/nn/[filename]
+```
+replacing `[filename]` as needed.
+
+
+## UCI options
+
+Currently, Stockfish has the following UCI options:
+
+  * #### Threads
+    The number of CPU threads used for searching a position. For best performance, set
+    this equal to the number of CPU cores available.
+
+  * #### Hash
+    The size of the hash table in MB. It is recommended to set Hash after setting Threads.
+
+  * #### Ponder
+    Let Stockfish ponder its next move while the opponent is thinking.
+
+  * #### MultiPV
+    Output the N best lines (principal variations, PVs) when searching.
+    Leave at 1 for best performance.
+
+  * #### Use NNUE
+    Toggle between the NNUE and classical evaluation functions. If set to "true",
+    the network parameters must be available to load from file (see also EvalFile),
+    if they are not embedded in the binary.
+
+  * #### EvalFile
+    The name of the file of the NNUE evaluation parameters. Depending on the GUI the
+    filename might have to include the full path to the folder/directory that contains the file.
+    Other locations, such as the directory that contains the binary and the working directory,
+    are also searched.
+
+  * #### UCI_AnalyseMode
+    An option handled by your GUI.
+
+  * #### UCI_Chess960
+    An option handled by your GUI. If true, Stockfish will play Chess960.
+
+  * #### UCI_ShowWDL
+    If enabled, show approximate WDL statistics as part of the engine output.
+    These WDL numbers model expected game outcomes for a given evaluation and
+    game ply for engine self-play at fishtest LTC conditions (60+0.6s per game).
+
+  * #### UCI_LimitStrength
+    Enable weaker play aiming for an Elo rating as set by UCI_Elo. This option overrides Skill Level.
+
+  * #### UCI_Elo
+    If enabled by UCI_LimitStrength, aim for an engine strength of the given Elo.
+    This Elo rating has been calibrated at a time control of 60s+0.6s and anchored to CCRL 40/4.
+
+  * #### Skill Level
+    Lower the Skill Level in order to make Stockfish play weaker (see also UCI_LimitStrength).
+    Internally, MultiPV is enabled, and with a certain probability depending on the Skill Level a
+    weaker move will be played.
+
+  * #### SyzygyPath
+    Path to the folders/directories storing the Syzygy tablebase files. Multiple
+    directories are to be separated by ";" on Windows and by ":" on Unix-based
+    operating systems. Do not use spaces around the ";" or ":".
+
+    Example: `C:\tablebases\wdl345;C:\tablebases\wdl6;D:\tablebases\dtz345;D:\tablebases\dtz6`
+
+    It is recommended to store .rtbw files on an SSD. There is no loss in storing
+    the .rtbz files on a regular HD. It is recommended to verify all md5 checksums
+    of the downloaded tablebase files (`md5sum -c checksum.md5`) as corruption will
+    lead to engine crashes.
+
+  * #### SyzygyProbeDepth
+    Minimum remaining search depth for which a position is probed. Set this option
+    to a higher value to probe less agressively if you experience too much slowdown
+    (in terms of nps) due to TB probing.
+
+  * #### Syzygy50MoveRule
+    Disable to let fifty-move rule draws detected by Syzygy tablebase probes count
+    as wins or losses. This is useful for ICCF correspondence games.
+
+  * #### SyzygyProbeLimit
+    Limit Syzygy tablebase probing to positions with at most this many pieces left
+    (including kings and pawns).
+
+  * #### Contempt
+    A positive value for contempt favors middle game positions and avoids draws,
+    effective for the classical evaluation only.
+
+  * #### Analysis Contempt
+    By default, contempt is set to prefer the side to move. Set this option to "White"
+    or "Black" to analyse with contempt for that side, or "Off" to disable contempt.
+
+  * #### Move Overhead
+    Assume a time delay of x ms due to network and GUI overheads. This is useful to
+    avoid losses on time in those cases.
+
+  * #### Slow Mover
+    Lower values will make Stockfish take less time in games, higher values will
+    make it think longer.
+
+  * #### nodestime
+    Tells the engine to use nodes searched instead of wall time to account for
+    elapsed time. Useful for engine testing.
+
+  * #### Clear Hash
+    Clear the hash table.
+
+  * #### Debug Log File
+    Write all communication to and from the engine into a text file.
+
+## A note on classical and NNUE evaluation
+
+Both approaches assign a value to a position that is used in alpha-beta (PVS) search
+to find the best move. The classical evaluation computes this value as a function
+of various chess concepts, handcrafted by experts, tested and tuned using fishtest.
+The NNUE evaluation computes this value with a neural network based on basic
+inputs (e.g. piece positions only). The network is optimized and trained
+on the evalutions of millions of positions at moderate search depth.
+
+The NNUE evaluation was first introduced in shogi, and ported to Stockfish afterward.
+It can be evaluated efficiently on CPUs, and exploits the fact that only parts
+of the neural network need to be updated after a typical chess move.
+[The nodchip repository](https://github.com/nodchip/Stockfish) provides additional
+tools to train and develop the NNUE networks.
+
+On CPUs supporting modern vector instructions (avx2 and similar), the NNUE evaluation
+results in stronger playing strength, even if the nodes per second computed by the engine
+is somewhat lower (roughly 60% of nps is typical).
+
+Note that the NNUE evaluation depends on the Stockfish binary and the network parameter
+file (see EvalFile). Not every parameter file is compatible with a given Stockfish binary.
+The default value of the EvalFile UCI option is the name of a network that is guaranteed
+to be compatible with that binary.
+
+## What to expect from Syzygybases?
+
+If the engine is searching a position that is not in the tablebases (e.g.
+a position with 8 pieces), it will access the tablebases during the search.
+If the engine reports a very large score (typically 153.xx), this means
+that it has found a winning line into a tablebase position.
+
+If the engine is given a position to search that is in the tablebases, it
+will use the tablebases at the beginning of the search to preselect all
+good moves, i.e. all moves that preserve the win or preserve the draw while
+taking into account the 50-move rule.
+It will then perform a search only on those moves. **The engine will not move
+immediately**, unless there is only a single good move. **The engine likely
+will not report a mate score even if the position is known to be won.**
+
+It is therefore clear that this behaviour is not identical to what one might
+be used to with Nalimov tablebases. There are technical reasons for this
+difference, the main technical reason being that Nalimov tablebases use the
+DTM metric (distance-to-mate), while Syzygybases use a variation of the
+DTZ metric (distance-to-zero, zero meaning any move that resets the 50-move
+counter). This special metric is one of the reasons that Syzygybases are
+more compact than Nalimov tablebases, while still storing all information
+needed for optimal play and in addition being able to take into account
+the 50-move rule.
+
+## Large Pages
+
+Stockfish supports large pages on Linux and Windows. Large pages make
+the hash access more efficient, improving the engine speed, especially
+on large hash sizes. Typical increases are 5..10% in terms of nps, but
+speed increases up to 30% have been measured. The support is
+automatic. Stockfish attempts to use large pages when available and
+will fall back to regular memory allocation when this is not the case.
+
+### Support on Linux
+
+Large page support on Linux is obtained by the Linux kernel
+transparent huge pages functionality. Typically, transparent huge pages
+are already enabled and no configuration is needed.
+
+### Support on Windows
+
+The use of large pages requires "Lock Pages in Memory" privilege. See
+[Enable the Lock Pages in Memory Option (Windows)](https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/enable-the-lock-pages-in-memory-option-windows)
+on how to enable this privilege. Logout/login may be needed
+afterwards. Due to memory fragmentation, it may not always be
+possible to allocate large pages even when enabled. A reboot
+might alleviate this problem. To determine whether large pages
+are in use, see the engine log.
+
+## Compiling Stockfish yourself from the sources
+
+Stockfish has support for 32 or 64-bit CPUs, certain hardware
+instructions, big-endian machines such as Power PC, and other platforms.
+
+On Unix-like systems, it should be easy to compile Stockfish
+directly from the source code with the included Makefile in the folder
+`src`. In general it is recommended to run `make help` to see a list of make
+targets with corresponding descriptions.
+
+```
+    cd src
+    make help
+    make build ARCH=x86-64-modern
+    make net
+```
+
+When not using the Makefile to compile (for instance with Microsoft MSVC) you
+need to manually set/unset some switches in the compiler command line; see
+file *types.h* for a quick reference.
+
+When reporting an issue or a bug, please tell us which version and
+compiler you used to create your executable. These informations can
+be found by typing the following commands in a console:
+
+```
+    ./stockfish compiler
+```
+
+## Understanding the code base and participating in the project
+
+Stockfish's improvement over the last couple of years has been a great
+community effort. There are a few ways to help contribute to its growth.
+
+### Donating hardware
+
+Improving Stockfish requires a massive amount of testing. You can donate
+your hardware resources by installing the [Fishtest Worker](https://github.com/glinscott/fishtest/wiki/Running-the-worker:-overview)
+and view the current tests on [Fishtest](https://tests.stockfishchess.org/tests).
+
+### Improving the code
+
+If you want to help improve the code, there are several valuable resources:
+
+* [In this wiki,](https://www.chessprogramming.org) many techniques used in
+Stockfish are explained with a lot of background information.
+
+* [The section on Stockfish](https://www.chessprogramming.org/Stockfish)
+describes many features and techniques used by Stockfish. However, it is
+generic rather than being focused on Stockfish's precise implementation.
+Nevertheless, a helpful resource.
+
+* The latest source can always be found on [GitHub](https://github.com/official-stockfish/Stockfish).
+Discussions about Stockfish take place in the [FishCooking](https://groups.google.com/forum/#!forum/fishcooking)
+group and engine testing is done on [Fishtest](https://tests.stockfishchess.org/tests).
+If you want to help improve Stockfish, please read this [guideline](https://github.com/glinscott/fishtest/wiki/Creating-my-first-test)
+first, where the basics of Stockfish development are explained.
+
+
+## Terms of use
+
+Stockfish is free, and distributed under the **GNU General Public License version 3**
+(GPL v3). Essentially, this means that you are free to do almost exactly
+what you want with the program, including distributing it among your
+friends, making it available for download from your web site, selling
+it (either by itself or as part of some bigger software package), or
+using it as the starting point for a software project of your own.
+
+The only real limitation is that whenever you distribute Stockfish in
+some way, you must always include the full source code, or a pointer
+to where the source code can be found. If you make any changes to the
+source code, these changes must also be made available under the GPL.
+
+For full details, read the copy of the GPL v3 found in the file named
+*Copying.txt*.
diff --git a/Top CPU Contributors.txt b/Top CPU Contributors.txt
new file mode 100644 (file)
index 0000000..482e900
--- /dev/null
@@ -0,0 +1,173 @@
+Contributors with >10,000 CPU hours as of Sept 2, 2020
+Thank you!
+
+Username                  CPU Hours   Games played
+--------------------------------------------------
+noobpwnftw                 19352969     1231459677
+mlang                        957168       61657446
+dew                          949885       56893432
+mibere                       703817       46865007
+crunchy                      427035       27344275
+cw                           416006       27521077
+JojoM                        415904       24479564
+fastgm                       404873       23953472
+CSU_Dynasty                  335774       22850550
+tvijlbrief                   335199       21871270
+Fisherman                    325053       21786603
+gvreuls                      311480       20751516
+ctoks                        275877       18710423
+velislav                     241267       15596372
+glinscott                    217799       13780820
+nordlandia                   211692       13484886
+bcross                       206213       14934233
+bking_US                     198894       11876016
+leszek                       189170       11446821
+mgrabiak                     183896       11778092
+drabel                       181408       12489478
+TueRens                      181349       12192000
+Thanar                       179852       12365359
+vdv                          175171        9881246
+robal                        166948       10702862
+spams                        157128       10319326
+marrco                       149947        9376421
+sqrt2                        147963        9724586
+vdbergh                      137041        8926915
+CoffeeOne                    136294        5004100
+malala                       136182        8002293
+mhoram                       128934        8177193
+davar                        122092        7960001
+dsmith                       122059        7570238
+xoto                         119696        8222144
+grandphish2                  116481        7582197
+Data                         113305        8220352
+BrunoBanani                  112960        7436849
+ElbertoOne                    99028        7023771
+MaZePallas                    98571        6362619
+brabos                        92118        6186135
+psk                           89957        5984901
+sunu                          88463        6007033
+sterni1971                    86948        5613788
+Vizvezdenec                   83752        5343724
+BRAVONE                       81239        5054681
+nssy                          76497        5259388
+teddybaer                     75125        5407666
+Pking_cda                     73776        5293873
+jromang                       70695        4940891
+solarlight                    70517        5028306
+dv8silencer                   70287        3883992
+Bobo1239                      68515        4652287
+racerschmacer                 67468        4935996
+manap                         66273        4121774
+tinker                        63458        4213726
+linrock                       59082        4516053
+robnjr                        57262        4053117
+Freja                         56938        3733019
+ttruscott                     56005        3679485
+renouve                       53811        3501516
+cuistot                       52532        3014920
+finfish                       51360        3370515
+eva42                         51272        3599691
+rkl                           50759        3840947
+rap                           49985        3219146
+pb00067                       49727        3298270
+ronaldjerum                   47654        3240695
+bigpen0r                      47278        3291647
+biffhero                      46564        3111352
+VoyagerOne                    45386        3445881
+speedycpu                     43842        3003273
+jbwiebe                       43305        2805433
+Antihistamine                 41788        2761312
+mhunt                         41735        2691355
+eastorwest                    40387        2812173
+homyur                        39893        2850481
+gri                           39871        2515779
+oryx                          38228        2941656
+0x3C33                        37773        2529097
+SC                            37290        2731014
+csnodgrass                    36207        2688994
+jmdana                        36108        2205261
+strelock                      34716        2074055
+Garf                          33800        2747562
+EthanOConnor                  33370        2090311
+slakovv                       32915        2021889
+Spprtr                        32591        2139601
+Prcuvu                        30377        2170122
+anst                          30301        2190091
+jkiiski                       30136        1904470
+hyperbolic.tom                29840        2017394
+Pyafue                        29650        1902349
+OuaisBla                      27629        1578000
+chriswk                       26902        1868317
+achambord                     26582        1767323
+Patrick_G                     26276        1801617
+yorkman                       26193        1992080
+SFTUser                       25182        1675689
+nabildanial                   24942        1519409
+Sharaf_DG                     24765        1786697
+ncfish1                       24411        1520927
+agg177                        23890        1395014
+JanErik                       23408        1703875
+Isidor                        23388        1680691
+Norabor                       22976        1587862
+cisco2015                     22880        1759669
+Zirie                         22542        1472937
+team-oh                       22272        1636708
+MazeOfGalious                 21978        1629593
+sg4032                        21945        1643065
+ianh2105                      21725        1632562
+xor12                         21628        1680365
+dex                           21612        1467203
+nesoneg                       21494        1463031
+horst.prack                   20878        1465656
+0xB00B1ES                     20590        1208666
+j3corre                       20405         941444
+Adrian.Schmidt123             20316        1281436
+wei                           19973        1745989
+rstoesser                     19569        1293588
+eudhan                        19274        1283717
+Ente                          19070        1373058
+jundery                       18445        1115855
+iisiraider                    18247        1101015
+ville                         17883        1384026
+chris                         17698        1487385
+purplefishies                 17595        1092533
+DragonLord                    17014        1162790
+dju                           16515         929427
+IgorLeMasson                  16064        1147232
+ako027ako                     15671        1173203
+Nikolay.IT                    15154        1068349
+Andrew Grant                  15114         895539
+yurikvelo                     15027        1165616
+OssumOpossum                  14857        1007129
+enedene                       14476         905279
+bpfliegel                     14298         884523
+jpulman                       13982         870599
+joster                        13794         950160
+Nesa92                        13786        1114691
+Dark_wizzie                   13422        1007152
+Hjax                          13350         900887
+Fifis                         13313         965473
+mabichito                     12903         749391
+thijsk                        12886         722107
+crocogoat                     12876        1048802
+AdrianSA                      12860         804972
+Flopzee                       12698         894821
+fatmurphy                     12547         853210
+SapphireBrand                 12416         969604
+modolief                      12386         896470
+scuzzi                        12362         833465
+pgontarz                      12151         848794
+stocky                        11954         699440
+mschmidt                      11941         803401
+infinity                      11470         727027
+torbjo                        11387         728873
+Thomas A. Anderson            11372         732094
+snicolet                      11106         869170
+amicic                        10779         733593
+rpngn                         10712         688203
+d64                           10680         771144
+basepi                        10637         744851
+jjoshua2                      10559         670905
+dzjp                          10343         732529
+ols                           10259         570669
+lbraesch                      10252         647825
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644 (file)
index 0000000..ab60840
--- /dev/null
@@ -0,0 +1,88 @@
+version: 1.0.{build}
+clone_depth: 50
+
+branches:
+  only:
+    - master
+
+# Operating system (build VM template)
+os: Visual Studio 2019
+
+# Build platform, i.e. x86, x64, AnyCPU. This setting is optional.
+platform:
+  - x86
+  - x64
+
+# build Configuration, i.e. Debug, Release, etc.
+configuration:
+  - Debug
+  - Release
+
+matrix:
+  # The build fail immediately once one of the job fails
+  fast_finish: true
+
+# Scripts that are called at very beginning, before repo cloning
+init:
+  - cmake --version
+  - msbuild /version
+
+before_build:
+  - ps: |
+      # Get sources
+      $src = get-childitem -Path *.cpp -Recurse | select -ExpandProperty FullName
+      $src = $src -join ' '
+      $src = $src.Replace("\", "/")
+
+      # Build CMakeLists.txt
+      $t = 'cmake_minimum_required(VERSION 3.17)',
+           'project(Stockfish)',
+           'set(CMAKE_CXX_STANDARD 17)',
+           'set(CMAKE_CXX_STANDARD_REQUIRED ON)',
+           'set (CMAKE_CXX_EXTENSIONS OFF)',
+           'set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/src)',
+           'set(source_files', $src, ')',
+           'add_executable(stockfish ${source_files})'
+
+      # Write CMakeLists.txt withouth BOM
+      $MyPath = (Get-Item -Path "." -Verbose).FullName + '\CMakeLists.txt'
+      $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
+      [System.IO.File]::WriteAllLines($MyPath, $t, $Utf8NoBomEncoding)
+
+      # Obtain bench reference from git log
+      $b = git log HEAD | sls "\b[Bb]ench[ :]+[0-9]{7}" | select -first 1
+      $bench = $b -match '\D+(\d+)' | % { $matches[1] }
+      Write-Host "Reference bench:" $bench
+      $g = "Visual Studio 16 2019"
+      If (${env:PLATFORM} -eq 'x64') { $a = "x64" }
+      If (${env:PLATFORM} -eq 'x86') { $a = "Win32" }
+      cmake -G "${g}" -A ${a} .
+      Write-Host "Generated files for: " $g $a
+
+build_script:
+  - cmake --build . --config %CONFIGURATION% -- /verbosity:minimal
+  - ps: |
+      # Download default NNUE net from fishtest
+      $nnuenet = Get-Content -Path src\evaluate.h | Select-String -CaseSensitive -Pattern "EvalFileDefaultName" | Select-String -CaseSensitive -Pattern "nn-[a-z0-9]{12}.nnue"
+      $dummy = $nnuenet -match "(?<nnuenet>nn-[a-z0-9]{12}.nnue)"
+      $nnuenet = $Matches.nnuenet
+      Write-Host "Default net:" $nnuenet
+      $nnuedownloadurl = "https://tests.stockfishchess.org/api/nn/$nnuenet"
+      $nnuefilepath = "src\${env:CONFIGURATION}\$nnuenet"
+      if (Test-Path -Path $nnuefilepath) {
+            Write-Host "Already available."
+      } else {
+            Write-Host "Downloading $nnuedownloadurl to $nnuefilepath"
+            Invoke-WebRequest -Uri $nnuedownloadurl -OutFile $nnuefilepath
+      }
+
+before_test:
+  - cd src/%CONFIGURATION%
+  - stockfish bench 2> out.txt >NUL
+  - ps: |
+      # Verify bench number
+      $s = (gc "./out.txt" | out-string)
+      $r = ($s -match 'Nodes searched \D+(\d+)' | % { $matches[1] })
+      Write-Host "Engine bench:" $r
+      Write-Host "Reference bench:" $bench
+      If ($r -ne $bench) { exit 1 }
diff --git a/src/Makefile b/src/Makefile
new file mode 100644 (file)
index 0000000..340b300
--- /dev/null
@@ -0,0 +1,890 @@
+# Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+# Copyright (C) 2004-2008 Tord Romstad (Glaurung author)
+# Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad
+# Copyright (C) 2015-2019 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad
+#
+# Stockfish is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Stockfish is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+
+### ==========================================================================
+### Section 1. General Configuration
+### ==========================================================================
+
+### Executable name
+ifeq ($(COMP),mingw)
+EXE = stockfish.exe
+else
+EXE = stockfish
+endif
+
+### Installation dir definitions
+PREFIX = /usr/local
+BINDIR = $(PREFIX)/bin
+
+### Built-in benchmark for pgo-builds
+PGOBENCH = ./$(EXE) bench
+
+### Source and object files
+SRCS = benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp main.cpp \
+       material.cpp misc.cpp movegen.cpp movepick.cpp pawns.cpp position.cpp psqt.cpp \
+       search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \
+       nnue/evaluate_nnue.cpp nnue/features/half_kp.cpp
+
+OBJS = $(notdir $(SRCS:.cpp=.o))
+
+VPATH = syzygy:nnue:nnue/features
+
+### Establish the operating system name
+KERNEL = $(shell uname -s)
+ifeq ($(KERNEL),Linux)
+       OS = $(shell uname -o)
+endif
+
+### ==========================================================================
+### Section 2. High-level Configuration
+### ==========================================================================
+#
+# flag                --- Comp switch      --- Description
+# ----------------------------------------------------------------------------
+#
+# debug = yes/no      --- -DNDEBUG         --- Enable/Disable debug mode
+# sanitize = undefined/thread/no (-fsanitize )
+#                     --- ( undefined )    --- enable undefined behavior checks
+#                     --- ( thread    )    --- enable threading error  checks
+# optimize = yes/no   --- (-O3/-fast etc.) --- Enable/Disable optimizations
+# arch = (name)       --- (-arch)          --- Target architecture
+# bits = 64/32        --- -DIS_64BIT       --- 64-/32-bit operating system
+# prefetch = yes/no   --- -DUSE_PREFETCH   --- Use prefetch asm-instruction
+# popcnt = yes/no     --- -DUSE_POPCNT     --- Use popcnt asm-instruction
+# pext = yes/no       --- -DUSE_PEXT       --- Use pext x86_64 asm-instruction
+# sse = yes/no        --- -msse            --- Use Intel Streaming SIMD Extensions
+# mmx = yes/no        --- -mmmx            --- Use Intel MMX instructions
+# sse2 = yes/no       --- -msse2           --- Use Intel Streaming SIMD Extensions 2
+# ssse3 = yes/no      --- -mssse3          --- Use Intel Supplemental Streaming SIMD Extensions 3
+# sse41 = yes/no      --- -msse4.1         --- Use Intel Streaming SIMD Extensions 4.1
+# avx2 = yes/no       --- -mavx2           --- Use Intel Advanced Vector Extensions 2
+# avx512 = yes/no     --- -mavx512bw       --- Use Intel Advanced Vector Extensions 512
+# vnni256 = yes/no    --- -mavx512vnni     --- Use Intel Vector Neural Network Instructions 256
+# vnni512 = yes/no    --- -mavx512vnni     --- Use Intel Vector Neural Network Instructions 512
+# neon = yes/no       --- -DUSE_NEON       --- Use ARM SIMD architecture
+#
+# Note that Makefile is space sensitive, so when adding new architectures
+# or modifying existing flags, you have to make sure there are no extra spaces
+# at the end of the line for flag values.
+
+### 2.1. General and architecture defaults
+
+ifeq ($(ARCH),)
+   ARCH = x86-64-modern
+   help_skip_sanity = yes
+endif
+# explicitly check for the list of supported architectures (as listed with make help),
+# the user can override with `make ARCH=x86-32-vnni256 SUPPORTED_ARCH=true`
+ifeq ($(ARCH), $(filter $(ARCH), \
+                 x86-64-vnni512 x86-64-vnni256 x86-64-avx512 x86-64-bmi2 x86-64-avx2 \
+                 x86-64-sse41-popcnt x86-64-modern x86-64-ssse3 x86-64-sse3-popcnt \
+                 x86-64 x86-32-sse41-popcnt x86-32-sse2 x86-32 ppc-64 ppc-32 \
+                 armv7 armv7-neon armv8 apple-silicon general-64 general-32))
+   SUPPORTED_ARCH=true
+else
+   SUPPORTED_ARCH=false
+endif
+
+optimize = yes
+debug = no
+sanitize = no
+bits = 64
+prefetch = no
+popcnt = no
+pext = no
+sse = no
+mmx = no
+sse2 = no
+ssse3 = no
+sse41 = no
+avx2 = no
+avx512 = no
+vnni256 = no
+vnni512 = no
+neon = no
+STRIP = strip
+
+### 2.2 Architecture specific
+
+ifeq ($(findstring x86,$(ARCH)),x86)
+
+# x86-32/64
+
+ifeq ($(findstring x86-32,$(ARCH)),x86-32)
+       arch = i386
+       bits = 32
+       sse = yes
+       mmx = yes
+else
+       arch = x86_64
+       sse = yes
+       sse2 = yes
+endif
+
+ifeq ($(findstring -sse,$(ARCH)),-sse)
+       sse = yes
+endif
+
+ifeq ($(findstring -popcnt,$(ARCH)),-popcnt)
+       popcnt = yes
+endif
+
+ifeq ($(findstring -mmx,$(ARCH)),-mmx)
+       mmx = yes
+endif
+
+ifeq ($(findstring -sse2,$(ARCH)),-sse2)
+       sse = yes
+       sse2 = yes
+endif
+
+ifeq ($(findstring -ssse3,$(ARCH)),-ssse3)
+       sse = yes
+       sse2 = yes
+       ssse3 = yes
+endif
+
+ifeq ($(findstring -sse41,$(ARCH)),-sse41)
+       sse = yes
+       sse2 = yes
+       ssse3 = yes
+       sse41 = yes
+endif
+
+ifeq ($(findstring -modern,$(ARCH)),-modern)
+       popcnt = yes
+       sse = yes
+       sse2 = yes
+       ssse3 = yes
+       sse41 = yes
+endif
+
+ifeq ($(findstring -avx2,$(ARCH)),-avx2)
+       popcnt = yes
+       sse = yes
+       sse2 = yes
+       ssse3 = yes
+       sse41 = yes
+       avx2 = yes
+endif
+
+ifeq ($(findstring -bmi2,$(ARCH)),-bmi2)
+       popcnt = yes
+       sse = yes
+       sse2 = yes
+       ssse3 = yes
+       sse41 = yes
+       avx2 = yes
+       pext = yes
+endif
+
+ifeq ($(findstring -avx512,$(ARCH)),-avx512)
+       popcnt = yes
+       sse = yes
+       sse2 = yes
+       ssse3 = yes
+       sse41 = yes
+       avx2 = yes
+       pext = yes
+       avx512 = yes
+endif
+
+ifeq ($(findstring -vnni256,$(ARCH)),-vnni256)
+       popcnt = yes
+       sse = yes
+       sse2 = yes
+       ssse3 = yes
+       sse41 = yes
+       avx2 = yes
+       pext = yes
+       vnni256 = yes
+endif
+
+ifeq ($(findstring -vnni512,$(ARCH)),-vnni512)
+       popcnt = yes
+       sse = yes
+       sse2 = yes
+       ssse3 = yes
+       sse41 = yes
+       avx2 = yes
+       pext = yes
+       avx512 = yes
+       vnni512 = yes
+endif
+
+ifeq ($(sse),yes)
+       prefetch = yes
+endif
+
+# 64-bit pext is not available on x86-32
+ifeq ($(bits),32)
+       pext = no
+endif
+
+else
+
+# all other architectures
+
+ifeq ($(ARCH),general-32)
+       arch = any
+       bits = 32
+endif
+
+ifeq ($(ARCH),general-64)
+       arch = any
+endif
+
+ifeq ($(ARCH),armv7)
+       arch = armv7
+       prefetch = yes
+       bits = 32
+endif
+
+ifeq ($(ARCH),armv7-neon)
+       arch = armv7
+       prefetch = yes
+       popcnt = yes
+       neon = yes
+       bits = 32
+endif
+
+ifeq ($(ARCH),armv8)
+       arch = armv8
+       prefetch = yes
+       popcnt = yes
+       neon = yes
+endif
+
+ifeq ($(ARCH),apple-silicon)
+       arch = arm64
+       prefetch = yes
+       popcnt = yes
+       neon = yes
+endif
+
+ifeq ($(ARCH),ppc-32)
+       arch = ppc
+       bits = 32
+endif
+
+ifeq ($(ARCH),ppc-64)
+       arch = ppc64
+       popcnt = yes
+       prefetch = yes
+endif
+
+endif
+
+### ==========================================================================
+### Section 3. Low-level Configuration
+### ==========================================================================
+
+### 3.1 Selecting compiler (default = gcc)
+CXXFLAGS += -Wall -Wcast-qual -fno-exceptions -std=c++17 $(EXTRACXXFLAGS)
+DEPENDFLAGS += -std=c++17
+LDFLAGS += $(EXTRALDFLAGS)
+
+ifeq ($(COMP),)
+       COMP=gcc
+endif
+
+ifeq ($(COMP),gcc)
+       comp=gcc
+       CXX=g++
+       CXXFLAGS += -pedantic -Wextra -Wshadow
+
+       ifeq ($(arch),$(filter $(arch),armv7 armv8))
+               ifeq ($(OS),Android)
+                       CXXFLAGS += -m$(bits)
+                       LDFLAGS += -m$(bits)
+               endif
+       else
+               CXXFLAGS += -m$(bits)
+               LDFLAGS += -m$(bits)
+       endif
+
+       ifeq ($(arch),$(filter $(arch),armv7))
+               LDFLAGS += -latomic
+       endif
+
+       ifneq ($(KERNEL),Darwin)
+          LDFLAGS += -Wl,--no-as-needed
+       endif
+endif
+
+ifeq ($(COMP),mingw)
+       comp=mingw
+
+       ifeq ($(KERNEL),Linux)
+               ifeq ($(bits),64)
+                       ifeq ($(shell which x86_64-w64-mingw32-c++-posix),)
+                               CXX=x86_64-w64-mingw32-c++
+                       else
+                               CXX=x86_64-w64-mingw32-c++-posix
+                       endif
+               else
+                       ifeq ($(shell which i686-w64-mingw32-c++-posix),)
+                               CXX=i686-w64-mingw32-c++
+                       else
+                               CXX=i686-w64-mingw32-c++-posix
+                       endif
+               endif
+       else
+               CXX=g++
+       endif
+
+       CXXFLAGS += -Wextra -Wshadow
+       LDFLAGS += -static
+endif
+
+ifeq ($(COMP),icc)
+       comp=icc
+       CXX=icpc
+       CXXFLAGS += -diag-disable 1476,10120 -Wcheck -Wabi -Wdeprecated -strict-ansi
+endif
+
+ifeq ($(COMP),clang)
+       comp=clang
+       CXX=clang++
+       CXXFLAGS += -pedantic -Wextra -Wshadow
+
+       ifneq ($(KERNEL),Darwin)
+       ifneq ($(KERNEL),OpenBSD)
+               LDFLAGS += -latomic
+       endif
+       endif
+
+       ifeq ($(arch),$(filter $(arch),armv7 armv8))
+               ifeq ($(OS),Android)
+                       CXXFLAGS += -m$(bits)
+                       LDFLAGS += -m$(bits)
+               endif
+       else
+               CXXFLAGS += -m$(bits)
+               LDFLAGS += -m$(bits)
+       endif
+endif
+
+ifeq ($(comp),icc)
+       profile_make = icc-profile-make
+       profile_use = icc-profile-use
+else
+ifeq ($(comp),clang)
+       profile_make = clang-profile-make
+       profile_use = clang-profile-use
+else
+       profile_make = gcc-profile-make
+       profile_use = gcc-profile-use
+endif
+endif
+
+ifeq ($(KERNEL),Darwin)
+       CXXFLAGS += -arch $(arch) -mmacosx-version-min=10.14
+       LDFLAGS += -arch $(arch) -mmacosx-version-min=10.14
+       XCRUN = xcrun
+endif
+
+# To cross-compile for Android, NDK version r21 or later is recommended.
+# In earlier NDK versions, you'll need to pass -fno-addrsig if using GNU binutils.
+# Currently we don't know how to make PGO builds with the NDK yet.
+ifeq ($(COMP),ndk)
+       CXXFLAGS += -stdlib=libc++ -fPIE
+       ifeq ($(arch),armv7)
+               comp=armv7a-linux-androideabi16-clang
+               CXX=armv7a-linux-androideabi16-clang++
+               CXXFLAGS += -mthumb -march=armv7-a -mfloat-abi=softfp -mfpu=neon
+               STRIP=arm-linux-androideabi-strip
+       endif
+       ifeq ($(arch),armv8)
+               comp=aarch64-linux-android21-clang
+               CXX=aarch64-linux-android21-clang++
+               STRIP=aarch64-linux-android-strip
+       endif
+       LDFLAGS += -static-libstdc++ -pie -lm -latomic
+endif
+
+### Travis CI script uses COMPILER to overwrite CXX
+ifdef COMPILER
+       COMPCXX=$(COMPILER)
+endif
+
+### Allow overwriting CXX from command line
+ifdef COMPCXX
+       CXX=$(COMPCXX)
+endif
+
+### Sometimes gcc is really clang
+ifeq ($(COMP),gcc)
+       gccversion = $(shell $(CXX) --version)
+       gccisclang = $(findstring clang,$(gccversion))
+       ifneq ($(gccisclang),)
+               profile_make = clang-profile-make
+               profile_use = clang-profile-use
+       endif
+endif
+
+### On mingw use Windows threads, otherwise POSIX
+ifneq ($(comp),mingw)
+       CXXFLAGS += -DUSE_PTHREADS
+       # On Android Bionic's C library comes with its own pthread implementation bundled in
+       ifneq ($(OS),Android)
+               # Haiku has pthreads in its libroot, so only link it in on other platforms
+               ifneq ($(KERNEL),Haiku)
+                       ifneq ($(COMP),ndk)
+                               LDFLAGS += -lpthread
+                       endif
+               endif
+       endif
+endif
+
+### 3.2.1 Debugging
+ifeq ($(debug),no)
+       CXXFLAGS += -DNDEBUG
+else
+       CXXFLAGS += -g
+endif
+
+### 3.2.2 Debugging with undefined behavior sanitizers
+ifneq ($(sanitize),no)
+        CXXFLAGS += -g3 -fsanitize=$(sanitize)
+        LDFLAGS += -fsanitize=$(sanitize)
+endif
+
+### 3.3 Optimization
+ifeq ($(optimize),yes)
+
+       CXXFLAGS += -O3
+
+       ifeq ($(comp),gcc)
+               ifeq ($(OS), Android)
+                       CXXFLAGS += -fno-gcse -mthumb -march=armv7-a -mfloat-abi=softfp
+               endif
+       endif
+
+       ifeq ($(comp),$(filter $(comp),gcc clang icc))
+               ifeq ($(KERNEL),Darwin)
+                       CXXFLAGS += -mdynamic-no-pic
+               endif
+       endif
+endif
+
+### 3.4 Bits
+ifeq ($(bits),64)
+       CXXFLAGS += -DIS_64BIT
+endif
+
+### 3.5 prefetch
+ifeq ($(prefetch),yes)
+       ifeq ($(sse),yes)
+               CXXFLAGS += -msse
+       endif
+else
+       CXXFLAGS += -DNO_PREFETCH
+endif
+
+### 3.6 popcnt
+ifeq ($(popcnt),yes)
+       ifeq ($(arch),$(filter $(arch),ppc64 armv7 armv8 arm64))
+               CXXFLAGS += -DUSE_POPCNT
+       else ifeq ($(comp),icc)
+               CXXFLAGS += -msse3 -DUSE_POPCNT
+       else
+               CXXFLAGS += -msse3 -mpopcnt -DUSE_POPCNT
+       endif
+endif
+
+
+ifeq ($(avx2),yes)
+       CXXFLAGS += -DUSE_AVX2
+       ifeq ($(comp),$(filter $(comp),gcc clang mingw))
+               CXXFLAGS += -mavx2
+       endif
+endif
+
+ifeq ($(avx512),yes)
+       CXXFLAGS += -DUSE_AVX512
+       ifeq ($(comp),$(filter $(comp),gcc clang mingw))
+               CXXFLAGS += -mavx512f -mavx512bw
+       endif
+endif
+
+ifeq ($(vnni256),yes)
+       CXXFLAGS += -DUSE_VNNI
+       ifeq ($(comp),$(filter $(comp),gcc clang mingw))
+               CXXFLAGS += -mavx512f -mavx512bw -mavx512vnni -mavx512dq -mavx512vl -mprefer-vector-width=256
+       endif
+endif
+
+ifeq ($(vnni512),yes)
+       CXXFLAGS += -DUSE_VNNI
+       ifeq ($(comp),$(filter $(comp),gcc clang mingw))
+               CXXFLAGS += -mavx512vnni -mavx512dq -mavx512vl
+       endif
+endif
+
+ifeq ($(sse41),yes)
+       CXXFLAGS += -DUSE_SSE41
+       ifeq ($(comp),$(filter $(comp),gcc clang mingw))
+               CXXFLAGS += -msse4.1
+       endif
+endif
+
+ifeq ($(ssse3),yes)
+       CXXFLAGS += -DUSE_SSSE3
+       ifeq ($(comp),$(filter $(comp),gcc clang mingw))
+               CXXFLAGS += -mssse3
+       endif
+endif
+
+ifeq ($(sse2),yes)
+       CXXFLAGS += -DUSE_SSE2
+       ifeq ($(comp),$(filter $(comp),gcc clang mingw))
+               CXXFLAGS += -msse2
+       endif
+endif
+
+ifeq ($(mmx),yes)
+       CXXFLAGS += -DUSE_MMX
+       ifeq ($(comp),$(filter $(comp),gcc clang mingw))
+               CXXFLAGS += -mmmx
+       endif
+endif
+
+ifeq ($(neon),yes)
+       CXXFLAGS += -DUSE_NEON
+       ifeq ($(KERNEL),Linux)
+       ifneq ($(COMP),ndk)
+       ifneq ($(arch),armv8)
+               CXXFLAGS += -mfpu=neon
+       endif
+       endif
+       endif
+endif
+
+### 3.7 pext
+ifeq ($(pext),yes)
+       CXXFLAGS += -DUSE_PEXT
+       ifeq ($(comp),$(filter $(comp),gcc clang mingw))
+               CXXFLAGS += -mbmi2
+       endif
+endif
+
+### 3.8 Link Time Optimization
+### This is a mix of compile and link time options because the lto link phase
+### needs access to the optimization flags.
+ifeq ($(optimize),yes)
+ifeq ($(debug), no)
+       ifeq ($(COMP),ndk)
+               CXXFLAGS += -flto=thin
+               LDFLAGS += $(CXXFLAGS)
+       else ifeq ($(comp),clang)
+               CXXFLAGS += -flto=thin
+               ifneq ($(findstring MINGW,$(KERNEL)),)
+                       CXXFLAGS += -fuse-ld=lld
+               else ifneq ($(findstring MSYS,$(KERNEL)),)
+                       CXXFLAGS += -fuse-ld=lld
+               endif
+               LDFLAGS += $(CXXFLAGS)
+
+# GCC and CLANG use different methods for parallelizing LTO and CLANG pretends to be
+# GCC on some systems.
+       else ifeq ($(comp),gcc)
+       ifeq ($(gccisclang),)
+               CXXFLAGS += -flto
+               LDFLAGS += $(CXXFLAGS) -flto=jobserver
+               ifneq ($(findstring MINGW,$(KERNEL)),)
+                       LDFLAGS += -save-temps
+               else ifneq ($(findstring MSYS,$(KERNEL)),)
+                       LDFLAGS += -save-temps
+               endif
+       else
+               CXXFLAGS += -flto=thin
+               LDFLAGS += $(CXXFLAGS)
+       endif
+
+# To use LTO and static linking on windows, the tool chain requires a recent gcc:
+# gcc version 10.1 in msys2 or TDM-GCC version 9.2 are known to work, older might not.
+# So, only enable it for a cross from Linux by default.
+       else ifeq ($(comp),mingw)
+       ifeq ($(KERNEL),Linux)
+       ifneq ($(arch),i386)
+               CXXFLAGS += -flto
+               LDFLAGS += $(CXXFLAGS) -flto=jobserver
+       endif
+       endif
+       endif
+endif
+endif
+
+### 3.9 Android 5 can only run position independent executables. Note that this
+### breaks Android 4.0 and earlier.
+ifeq ($(OS), Android)
+       CXXFLAGS += -fPIE
+       LDFLAGS += -fPIE -pie
+endif
+
+### ==========================================================================
+### Section 4. Public Targets
+### ==========================================================================
+
+
+help:
+       @echo ""
+       @echo "To compile stockfish, type: "
+       @echo ""
+       @echo "make target ARCH=arch [COMP=compiler] [COMPCXX=cxx]"
+       @echo ""
+       @echo "Supported targets:"
+       @echo ""
+       @echo "help                    > Display architecture details"
+       @echo "build                   > Standard build"
+       @echo "net                     > Download the default nnue net"
+       @echo "profile-build           > Faster build (with profile-guided optimization)"
+       @echo "strip                   > Strip executable"
+       @echo "install                 > Install executable"
+       @echo "clean                   > Clean up"
+       @echo ""
+       @echo "Supported archs:"
+       @echo ""
+       @echo "x86-64-vnni512          > x86 64-bit with vnni support 512bit wide"
+       @echo "x86-64-vnni256          > x86 64-bit with vnni support 256bit wide"
+       @echo "x86-64-avx512           > x86 64-bit with avx512 support"
+       @echo "x86-64-bmi2             > x86 64-bit with bmi2 support"
+       @echo "x86-64-avx2             > x86 64-bit with avx2 support"
+       @echo "x86-64-sse41-popcnt     > x86 64-bit with sse41 and popcnt support"
+       @echo "x86-64-modern           > common modern CPU, currently x86-64-sse41-popcnt"
+       @echo "x86-64-ssse3            > x86 64-bit with ssse3 support"
+       @echo "x86-64-sse3-popcnt      > x86 64-bit with sse3 and popcnt support"
+       @echo "x86-64                  > x86 64-bit generic (with sse2 support)"
+       @echo "x86-32-sse41-popcnt     > x86 32-bit with sse41 and popcnt support"
+       @echo "x86-32-sse2             > x86 32-bit with sse2 support"
+       @echo "x86-32                  > x86 32-bit generic (with mmx and sse support)"
+       @echo "ppc-64                  > PPC 64-bit"
+       @echo "ppc-32                  > PPC 32-bit"
+       @echo "armv7                   > ARMv7 32-bit"
+       @echo "armv7-neon              > ARMv7 32-bit with popcnt and neon"
+       @echo "armv8                   > ARMv8 64-bit with popcnt and neon"
+       @echo "apple-silicon           > Apple silicon ARM64"
+       @echo "general-64              > unspecified 64-bit"
+       @echo "general-32              > unspecified 32-bit"
+       @echo ""
+       @echo "Supported compilers:"
+       @echo ""
+       @echo "gcc                     > Gnu compiler (default)"
+       @echo "mingw                   > Gnu compiler with MinGW under Windows"
+       @echo "clang                   > LLVM Clang compiler"
+       @echo "icc                     > Intel compiler"
+       @echo "ndk                     > Google NDK to cross-compile for Android"
+       @echo ""
+       @echo "Simple examples. If you don't know what to do, you likely want to run: "
+       @echo ""
+       @echo "make -j build ARCH=x86-64  (A portable, slow compile for 64-bit systems)"
+       @echo "make -j build ARCH=x86-32  (A portable, slow compile for 32-bit systems)"
+       @echo ""
+       @echo "Advanced examples, for experienced users looking for performance: "
+       @echo ""
+       @echo "make    help  ARCH=x86-64-bmi2"
+       @echo "make -j profile-build ARCH=x86-64-bmi2 COMP=gcc COMPCXX=g++-9.0"
+       @echo "make -j build ARCH=x86-64-ssse3 COMP=clang"
+       @echo ""
+       @echo "-------------------------------"
+ifeq ($(SUPPORTED_ARCH)$(help_skip_sanity), true)
+       @echo "The selected architecture $(ARCH) will enable the following configuration: "
+       @$(MAKE) ARCH=$(ARCH) COMP=$(COMP) config-sanity
+else
+       @echo "Specify a supported architecture with the ARCH option for more details"
+       @echo ""
+endif
+
+
+.PHONY: help build profile-build strip install clean net objclean profileclean \
+        config-sanity icc-profile-use icc-profile-make gcc-profile-use gcc-profile-make \
+        clang-profile-use clang-profile-make
+
+build: config-sanity net
+       $(MAKE) ARCH=$(ARCH) COMP=$(COMP) all
+
+profile-build: net config-sanity objclean profileclean
+       @echo ""
+       @echo "Step 1/4. Building instrumented executable ..."
+       $(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_make)
+       @echo ""
+       @echo "Step 2/4. Running benchmark for pgo-build ..."
+       $(PGOBENCH) > /dev/null
+       @echo ""
+       @echo "Step 3/4. Building optimized executable ..."
+       $(MAKE) ARCH=$(ARCH) COMP=$(COMP) objclean
+       $(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_use)
+       @echo ""
+       @echo "Step 4/4. Deleting profile data ..."
+       $(MAKE) ARCH=$(ARCH) COMP=$(COMP) profileclean
+
+strip:
+       $(STRIP) $(EXE)
+
+install:
+       -mkdir -p -m 755 $(BINDIR)
+       -cp $(EXE) $(BINDIR)
+       -strip $(BINDIR)/$(EXE)
+
+# clean all
+clean: objclean profileclean
+       @rm -f .depend *~ core
+
+# evaluation network (nnue)
+net:
+       $(eval nnuenet := $(shell grep EvalFileDefaultName evaluate.h | grep define | sed 's/.*\(nn-[a-z0-9]\{12\}.nnue\).*/\1/'))
+       @echo "Default net: $(nnuenet)"
+       $(eval nnuedownloadurl := https://tests.stockfishchess.org/api/nn/$(nnuenet))
+       $(eval curl_or_wget := $(shell if hash curl 2>/dev/null; then echo "curl -skL"; elif hash wget 2>/dev/null; then echo "wget -qO-"; fi))
+       @if test -f "$(nnuenet)"; then \
+            echo "Already available."; \
+         else \
+            if [ "x$(curl_or_wget)" = "x" ]; then \
+               echo "Automatic download failed: neither curl nor wget is installed. Install one of these tools or download the net manually"; exit 1; \
+            else \
+               echo "Downloading $(nnuedownloadurl)"; $(curl_or_wget) $(nnuedownloadurl) > $(nnuenet);\
+            fi; \
+        fi;
+       $(eval shasum_command := $(shell if hash shasum 2>/dev/null; then echo "shasum -a 256 "; elif hash sha256sum 2>/dev/null; then echo "sha256sum "; fi))
+       @if [ "x$(shasum_command)" != "x" ]; then \
+           if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \
+                echo "Failed download or $(nnuenet) corrupted, please delete!"; exit 1; \
+            fi \
+         else \
+            echo "shasum / sha256sum not found, skipping net validation"; \
+        fi
+
+# clean binaries and objects
+objclean:
+       @rm -f $(EXE) *.o ./syzygy/*.o ./nnue/*.o ./nnue/features/*.o
+
+# clean auxiliary profiling files
+profileclean:
+       @rm -rf profdir
+       @rm -f bench.txt *.gcda *.gcno ./syzygy/*.gcda ./nnue/*.gcda ./nnue/features/*.gcda *.s
+       @rm -f stockfish.profdata *.profraw
+
+default:
+       help
+
+### ==========================================================================
+### Section 5. Private Targets
+### ==========================================================================
+
+all: $(EXE) .depend
+
+config-sanity:
+       @echo ""
+       @echo "Config:"
+       @echo "debug: '$(debug)'"
+       @echo "sanitize: '$(sanitize)'"
+       @echo "optimize: '$(optimize)'"
+       @echo "arch: '$(arch)'"
+       @echo "bits: '$(bits)'"
+       @echo "kernel: '$(KERNEL)'"
+       @echo "os: '$(OS)'"
+       @echo "prefetch: '$(prefetch)'"
+       @echo "popcnt: '$(popcnt)'"
+       @echo "pext: '$(pext)'"
+       @echo "sse: '$(sse)'"
+       @echo "mmx: '$(mmx)'"
+       @echo "sse2: '$(sse2)'"
+       @echo "ssse3: '$(ssse3)'"
+       @echo "sse41: '$(sse41)'"
+       @echo "avx2: '$(avx2)'"
+       @echo "avx512: '$(avx512)'"
+       @echo "vnni256: '$(vnni256)'"
+       @echo "vnni512: '$(vnni512)'"
+       @echo "neon: '$(neon)'"
+       @echo ""
+       @echo "Flags:"
+       @echo "CXX: $(CXX)"
+       @echo "CXXFLAGS: $(CXXFLAGS)"
+       @echo "LDFLAGS: $(LDFLAGS)"
+       @echo ""
+       @echo "Testing config sanity. If this fails, try 'make help' ..."
+       @echo ""
+       @test "$(debug)" = "yes" || test "$(debug)" = "no"
+       @test "$(sanitize)" = "undefined" || test "$(sanitize)" = "thread" || test "$(sanitize)" = "address" || test "$(sanitize)" = "no"
+       @test "$(optimize)" = "yes" || test "$(optimize)" = "no"
+       @test "$(SUPPORTED_ARCH)" = "true"
+       @test "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \
+        test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || \
+        test "$(arch)" = "armv7" || test "$(arch)" = "armv8" || test "$(arch)" = "arm64"
+       @test "$(bits)" = "32" || test "$(bits)" = "64"
+       @test "$(prefetch)" = "yes" || test "$(prefetch)" = "no"
+       @test "$(popcnt)" = "yes" || test "$(popcnt)" = "no"
+       @test "$(pext)" = "yes" || test "$(pext)" = "no"
+       @test "$(sse)" = "yes" || test "$(sse)" = "no"
+       @test "$(mmx)" = "yes" || test "$(mmx)" = "no"
+       @test "$(sse2)" = "yes" || test "$(sse2)" = "no"
+       @test "$(ssse3)" = "yes" || test "$(ssse3)" = "no"
+       @test "$(sse41)" = "yes" || test "$(sse41)" = "no"
+       @test "$(avx2)" = "yes" || test "$(avx2)" = "no"
+       @test "$(avx512)" = "yes" || test "$(avx512)" = "no"
+       @test "$(vnni256)" = "yes" || test "$(vnni256)" = "no"
+       @test "$(vnni512)" = "yes" || test "$(vnni512)" = "no"
+       @test "$(neon)" = "yes" || test "$(neon)" = "no"
+       @test "$(comp)" = "gcc" || test "$(comp)" = "icc" || test "$(comp)" = "mingw" || test "$(comp)" = "clang" \
+       || test "$(comp)" = "armv7a-linux-androideabi16-clang"  || test "$(comp)" = "aarch64-linux-android21-clang"
+
+$(EXE): $(OBJS)
+       +$(CXX) -o $@ $(OBJS) $(LDFLAGS)
+
+clang-profile-make:
+       $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
+       EXTRACXXFLAGS='-fprofile-instr-generate ' \
+       EXTRALDFLAGS=' -fprofile-instr-generate' \
+       all
+
+clang-profile-use:
+       $(XCRUN) llvm-profdata merge -output=stockfish.profdata *.profraw
+       $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
+       EXTRACXXFLAGS='-fprofile-instr-use=stockfish.profdata' \
+       EXTRALDFLAGS='-fprofile-use ' \
+       all
+
+gcc-profile-make:
+       $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
+       EXTRACXXFLAGS='-fprofile-generate' \
+       EXTRALDFLAGS='-lgcov' \
+       all
+
+gcc-profile-use:
+       $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
+       EXTRACXXFLAGS='-fprofile-use -fno-peel-loops -fno-tracer' \
+       EXTRALDFLAGS='-lgcov' \
+       all
+
+icc-profile-make:
+       @mkdir -p profdir
+       $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
+       EXTRACXXFLAGS='-prof-gen=srcpos -prof_dir ./profdir' \
+       all
+
+icc-profile-use:
+       $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
+       EXTRACXXFLAGS='-prof_use -prof_dir ./profdir' \
+       all
+
+.depend:
+       -@$(CXX) $(DEPENDFLAGS) -MM $(SRCS) > $@ 2> /dev/null
+
+-include .depend
diff --git a/src/benchmark.cpp b/src/benchmark.cpp
new file mode 100644 (file)
index 0000000..806e984
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <fstream>
+#include <iostream>
+#include <istream>
+#include <vector>
+
+#include "position.h"
+
+using namespace std;
+
+namespace {
+
+const vector<string> Defaults = {
+  "setoption name UCI_Chess960 value false",
+  "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
+  "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 10",
+  "8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 11",
+  "4rrk1/pp1n3p/3q2pQ/2p1pb2/2PP4/2P3N1/P2B2PP/4RRK1 b - - 7 19",
+  "rq3rk1/ppp2ppp/1bnpb3/3N2B1/3NP3/7P/PPPQ1PP1/2KR3R w - - 7 14 moves d4e6",
+  "r1bq1r1k/1pp1n1pp/1p1p4/4p2Q/4Pp2/1BNP4/PPP2PPP/3R1RK1 w - - 2 14 moves g2g4",
+  "r3r1k1/2p2ppp/p1p1bn2/8/1q2P3/2NPQN2/PPP3PP/R4RK1 b - - 2 15",
+  "r1bbk1nr/pp3p1p/2n5/1N4p1/2Np1B2/8/PPP2PPP/2KR1B1R w kq - 0 13",
+  "r1bq1rk1/ppp1nppp/4n3/3p3Q/3P4/1BP1B3/PP1N2PP/R4RK1 w - - 1 16",
+  "4r1k1/r1q2ppp/ppp2n2/4P3/5Rb1/1N1BQ3/PPP3PP/R5K1 w - - 1 17",
+  "2rqkb1r/ppp2p2/2npb1p1/1N1Nn2p/2P1PP2/8/PP2B1PP/R1BQK2R b KQ - 0 11",
+  "r1bq1r1k/b1p1npp1/p2p3p/1p6/3PP3/1B2NN2/PP3PPP/R2Q1RK1 w - - 1 16",
+  "3r1rk1/p5pp/bpp1pp2/8/q1PP1P2/b3P3/P2NQRPP/1R2B1K1 b - - 6 22",
+  "r1q2rk1/2p1bppp/2Pp4/p6b/Q1PNp3/4B3/PP1R1PPP/2K4R w - - 2 18",
+  "4k2r/1pb2ppp/1p2p3/1R1p4/3P4/2r1PN2/P4PPP/1R4K1 b - - 3 22",
+  "3q2k1/pb3p1p/4pbp1/2r5/PpN2N2/1P2P2P/5PP1/Q2R2K1 b - - 4 26",
+  "6k1/6p1/6Pp/ppp5/3pn2P/1P3K2/1PP2P2/3N4 b - - 0 1",
+  "3b4/5kp1/1p1p1p1p/pP1PpP1P/P1P1P3/3KN3/8/8 w - - 0 1",
+  "2K5/p7/7P/5pR1/8/5k2/r7/8 w - - 0 1 moves g5g6 f3e3 g6g5 e3f3",
+  "8/6pk/1p6/8/PP3p1p/5P2/4KP1q/3Q4 w - - 0 1",
+  "7k/3p2pp/4q3/8/4Q3/5Kp1/P6b/8 w - - 0 1",
+  "8/2p5/8/2kPKp1p/2p4P/2P5/3P4/8 w - - 0 1",
+  "8/1p3pp1/7p/5P1P/2k3P1/8/2K2P2/8 w - - 0 1",
+  "8/pp2r1k1/2p1p3/3pP2p/1P1P1P1P/P5KR/8/8 w - - 0 1",
+  "8/3p4/p1bk3p/Pp6/1Kp1PpPp/2P2P1P/2P5/5B2 b - - 0 1",
+  "5k2/7R/4P2p/5K2/p1r2P1p/8/8/8 b - - 0 1",
+  "6k1/6p1/P6p/r1N5/5p2/7P/1b3PP1/4R1K1 w - - 0 1",
+  "1r3k2/4q3/2Pp3b/3Bp3/2Q2p2/1p1P2P1/1P2KP2/3N4 w - - 0 1",
+  "6k1/4pp1p/3p2p1/P1pPb3/R7/1r2P1PP/3B1P2/6K1 w - - 0 1",
+  "8/3p3B/5p2/5P2/p7/PP5b/k7/6K1 w - - 0 1",
+  "5rk1/q6p/2p3bR/1pPp1rP1/1P1Pp3/P3B1Q1/1K3P2/R7 w - - 93 90",
+  "4rrk1/1p1nq3/p7/2p1P1pp/3P2bp/3Q1Bn1/PPPB4/1K2R1NR w - - 40 21",
+  "r3k2r/3nnpbp/q2pp1p1/p7/Pp1PPPP1/4BNN1/1P5P/R2Q1RK1 w kq - 0 16",
+  "3Qb1k1/1r2ppb1/pN1n2q1/Pp1Pp1Pr/4P2p/4BP2/4B1R1/1R5K b - - 11 40",
+  "4k3/3q1r2/1N2r1b1/3ppN2/2nPP3/1B1R2n1/2R1Q3/3K4 w - - 5 1",
+
+  // 5-man positions
+  "8/8/8/8/5kp1/P7/8/1K1N4 w - - 0 1",     // Kc2 - mate
+  "8/8/8/5N2/8/p7/8/2NK3k w - - 0 1",      // Na2 - mate
+  "8/3k4/8/8/8/4B3/4KB2/2B5 w - - 0 1",    // draw
+
+  // 6-man positions
+  "8/8/1P6/5pr1/8/4R3/7k/2K5 w - - 0 1",   // Re5 - mate
+  "8/2p4P/8/kr6/6R1/8/8/1K6 w - - 0 1",    // Ka2 - mate
+  "8/8/3P3k/8/1p6/8/1P6/1K3n2 b - - 0 1",  // Nd2 - draw
+
+  // 7-man positions
+  "8/R7/2q5/8/6k1/8/1P5p/K6R w - - 0 124", // Draw
+
+  // Mate and stalemate positions
+  "6k1/3b3r/1p1p4/p1n2p2/1PPNpP1q/P3Q1p1/1R1RB1P1/5K2 b - - 0 1",
+  "r2r1n2/pp2bk2/2p1p2p/3q4/3PN1QP/2P3R1/P4PP1/5RK1 w - - 0 1",
+  "8/8/8/8/8/6k1/6p1/6K1 w - -",
+  "7k/7P/6K1/8/3B4/8/8/8 b - -",
+
+  // Chess 960
+  "setoption name UCI_Chess960 value true",
+  "bbqnnrkr/pppppppp/8/8/8/8/PPPPPPPP/BBQNNRKR w HFhf - 0 1 moves g2g3 d7d5 d2d4 c8h3 c1g5 e8d6 g5e7 f7f6",
+  "setoption name UCI_Chess960 value false"
+};
+
+} // namespace
+
+/// 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<string> setup_bench(const Position& current, istream& is) {
+
+  vector<string> fens, list;
+  string go, token;
+
+  // Assign default values to missing arguments
+  string ttSize    = (is >> token) ? token : "16";
+  string threads   = (is >> token) ? token : "1";
+  string limit     = (is >> token) ? token : "13";
+  string fenFile   = (is >> token) ? token : "default";
+  string limitType = (is >> token) ? token : "depth";
+  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;
+      }
+
+  return list;
+}
diff --git a/src/bitbase.cpp b/src/bitbase.cpp
new file mode 100644 (file)
index 0000000..bbe8e9a
--- /dev/null
@@ -0,0 +1,170 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <cassert>
+#include <vector>
+#include <bitset>
+
+#include "bitboard.h"
+#include "types.h"
+
+namespace {
+
+  // There are 24 possible pawn squares: files A to D and ranks from 2 to 7.
+  // Positions with the pawn on files E to H will be mirrored before probing.
+  constexpr unsigned MAX_INDEX = 2*24*64*64; // stm * psq * wksq * bksq = 196608
+
+  std::bitset<MAX_INDEX> KPKBitbase;
+
+  // A KPK bitbase index is an integer in [0, IndexMax] range
+  //
+  // Information is mapped in a way that minimizes the number of iterations:
+  //
+  // bit  0- 5: white king square (from SQ_A1 to SQ_H8)
+  // bit  6-11: black king square (from SQ_A1 to SQ_H8)
+  // bit    12: side to move (WHITE or BLACK)
+  // bit 13-14: white pawn file (from FILE_A to FILE_D)
+  // bit 15-17: white pawn RANK_7 - rank (from RANK_7 - RANK_7 to RANK_7 - RANK_2)
+  unsigned index(Color stm, Square bksq, Square wksq, Square psq) {
+    return int(wksq) | (bksq << 6) | (stm << 12) | (file_of(psq) << 13) | ((RANK_7 - rank_of(psq)) << 15);
+  }
+
+  enum Result {
+    INVALID = 0,
+    UNKNOWN = 1,
+    DRAW    = 2,
+    WIN     = 4
+  };
+
+  Result& operator|=(Result& r, Result v) { return r = Result(r | v); }
+
+  struct KPKPosition {
+    KPKPosition() = default;
+    explicit KPKPosition(unsigned idx);
+    operator Result() const { return result; }
+    Result classify(const std::vector<KPKPosition>& db);
+
+    Color stm;
+    Square ksq[COLOR_NB], psq;
+    Result result;
+  };
+
+} // namespace
+
+
+bool Bitbases::probe(Square wksq, Square wpsq, Square bksq, Color stm) {
+
+  assert(file_of(wpsq) <= FILE_D);
+
+  return KPKBitbase[index(stm, bksq, wksq, wpsq)];
+}
+
+
+void Bitbases::init() {
+
+  std::vector<KPKPosition> db(MAX_INDEX);
+  unsigned idx, repeat = 1;
+
+  // Initialize db with known win / draw positions
+  for (idx = 0; idx < MAX_INDEX; ++idx)
+      db[idx] = KPKPosition(idx);
+
+  // Iterate through the positions until none of the unknown positions can be
+  // changed to either wins or draws (15 cycles needed).
+  while (repeat)
+      for (repeat = idx = 0; idx < MAX_INDEX; ++idx)
+          repeat |= (db[idx] == UNKNOWN && db[idx].classify(db) != UNKNOWN);
+
+  // Fill the bitbase with the decisive results
+  for (idx = 0; idx < MAX_INDEX; ++idx)
+      if (db[idx] == WIN)
+          KPKBitbase.set(idx);
+}
+
+
+namespace {
+
+  KPKPosition::KPKPosition(unsigned idx) {
+
+    ksq[WHITE] = Square((idx >>  0) & 0x3F);
+    ksq[BLACK] = Square((idx >>  6) & 0x3F);
+    stm        = Color ((idx >> 12) & 0x01);
+    psq        = make_square(File((idx >> 13) & 0x3), Rank(RANK_7 - ((idx >> 15) & 0x7)));
+
+    // Invalid if two pieces are on the same square or if a king can be captured
+    if (   distance(ksq[WHITE], ksq[BLACK]) <= 1
+        || ksq[WHITE] == psq
+        || ksq[BLACK] == psq
+        || (stm == WHITE && (pawn_attacks_bb(WHITE, psq) & ksq[BLACK])))
+        result = INVALID;
+
+    // Win if the pawn can be promoted without getting captured
+    else if (   stm == WHITE
+             && rank_of(psq) == RANK_7
+             && ksq[WHITE] != psq + NORTH
+             && (    distance(ksq[BLACK], psq + NORTH) > 1
+                 || (distance(ksq[WHITE], psq + NORTH) == 1)))
+        result = WIN;
+
+    // Draw if it is stalemate or the black king can capture the pawn
+    else if (   stm == BLACK
+             && (  !(attacks_bb<KING>(ksq[BLACK]) & ~(attacks_bb<KING>(ksq[WHITE]) | pawn_attacks_bb(WHITE, psq)))
+                 || (attacks_bb<KING>(ksq[BLACK]) & ~attacks_bb<KING>(ksq[WHITE]) & psq)))
+        result = DRAW;
+
+    // Position will be classified later
+    else
+        result = UNKNOWN;
+  }
+
+  Result KPKPosition::classify(const std::vector<KPKPosition>& db) {
+
+    // White to move: If one move leads to a position classified as WIN, the result
+    // of the current position is WIN. If all moves lead to positions classified
+    // as DRAW, the current position is classified as DRAW, otherwise the current
+    // position is classified as UNKNOWN.
+    //
+    // Black to move: If one move leads to a position classified as DRAW, the result
+    // of the current position is DRAW. If all moves lead to positions classified
+    // as WIN, the position is classified as WIN, otherwise the current position is
+    // classified as UNKNOWN.
+    const Result Good = (stm == WHITE ? WIN   : DRAW);
+    const Result Bad  = (stm == WHITE ? DRAW  : WIN);
+
+    Result r = INVALID;
+    Bitboard b = attacks_bb<KING>(ksq[stm]);
+
+    while (b)
+        r |= stm == WHITE ? db[index(BLACK, ksq[BLACK] , pop_lsb(&b), psq)]
+                          : db[index(WHITE, pop_lsb(&b),  ksq[WHITE], psq)];
+
+    if (stm == WHITE)
+    {
+        if (rank_of(psq) < RANK_7)      // Single push
+            r |= db[index(BLACK, ksq[BLACK], ksq[WHITE], psq + NORTH)];
+
+        if (   rank_of(psq) == RANK_2   // Double push
+            && psq + NORTH != ksq[WHITE]
+            && psq + NORTH != ksq[BLACK])
+            r |= db[index(BLACK, ksq[BLACK], ksq[WHITE], psq + NORTH + NORTH)];
+    }
+
+    return result = r & Good  ? Good  : r & UNKNOWN ? UNKNOWN : Bad;
+  }
+
+} // namespace
diff --git a/src/bitboard.cpp b/src/bitboard.cpp
new file mode 100644 (file)
index 0000000..80206b5
--- /dev/null
@@ -0,0 +1,213 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <algorithm>
+#include <bitset>
+
+#include "bitboard.h"
+#include "misc.h"
+
+uint8_t PopCnt16[1 << 16];
+uint8_t SquareDistance[SQUARE_NB][SQUARE_NB];
+
+Bitboard SquareBB[SQUARE_NB];
+Bitboard LineBB[SQUARE_NB][SQUARE_NB];
+Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB];
+Bitboard PawnAttacks[COLOR_NB][SQUARE_NB];
+
+Magic RookMagics[SQUARE_NB];
+Magic BishopMagics[SQUARE_NB];
+
+namespace {
+
+  Bitboard RookTable[0x19000];  // To store rook attacks
+  Bitboard BishopTable[0x1480]; // To store bishop attacks
+
+  void init_magics(PieceType pt, Bitboard table[], Magic magics[]);
+
+}
+
+
+/// safe_destination() returns the bitboard of target square for the given step
+/// from the given square. If the step is off the board, returns empty bitboard.
+
+inline Bitboard safe_destination(Square s, int step) {
+    Square to = Square(s + step);
+    return is_ok(to) && distance(s, to) <= 2 ? square_bb(to) : Bitboard(0);
+}
+
+
+/// Bitboards::pretty() returns an ASCII representation of a bitboard suitable
+/// to be printed to standard output. Useful for debugging.
+
+const std::string Bitboards::pretty(Bitboard b) {
+
+  std::string s = "+---+---+---+---+---+---+---+---+\n";
+
+  for (Rank r = RANK_8; r >= RANK_1; --r)
+  {
+      for (File f = FILE_A; f <= FILE_H; ++f)
+          s += b & make_square(f, r) ? "| X " : "|   ";
+
+      s += "| " + std::to_string(1 + r) + "\n+---+---+---+---+---+---+---+---+\n";
+  }
+  s += "  a   b   c   d   e   f   g   h\n";
+
+  return s;
+}
+
+
+/// Bitboards::init() initializes various bitboard tables. It is called at
+/// startup and relies on global objects to be already zero-initialized.
+
+void Bitboards::init() {
+
+  for (unsigned i = 0; i < (1 << 16); ++i)
+      PopCnt16[i] = uint8_t(std::bitset<16>(i).count());
+
+  for (Square s = SQ_A1; s <= SQ_H8; ++s)
+      SquareBB[s] = (1ULL << s);
+
+  for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
+      for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
+          SquareDistance[s1][s2] = std::max(distance<File>(s1, s2), distance<Rank>(s1, s2));
+
+  init_magics(ROOK, RookTable, RookMagics);
+  init_magics(BISHOP, BishopTable, BishopMagics);
+
+  for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
+  {
+      PawnAttacks[WHITE][s1] = pawn_attacks_bb<WHITE>(square_bb(s1));
+      PawnAttacks[BLACK][s1] = pawn_attacks_bb<BLACK>(square_bb(s1));
+
+      for (int step : {-9, -8, -7, -1, 1, 7, 8, 9} )
+         PseudoAttacks[KING][s1] |= safe_destination(s1, step);
+
+      for (int step : {-17, -15, -10, -6, 6, 10, 15, 17} )
+         PseudoAttacks[KNIGHT][s1] |= safe_destination(s1, step);
+
+      PseudoAttacks[QUEEN][s1]  = PseudoAttacks[BISHOP][s1] = attacks_bb<BISHOP>(s1, 0);
+      PseudoAttacks[QUEEN][s1] |= PseudoAttacks[  ROOK][s1] = attacks_bb<  ROOK>(s1, 0);
+
+      for (PieceType pt : { BISHOP, ROOK })
+          for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
+              if (PseudoAttacks[pt][s1] & s2)
+                  LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2;
+  }
+}
+
+
+namespace {
+
+  Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) {
+
+    Bitboard attacks = 0;
+    Direction   RookDirections[4] = {NORTH, SOUTH, EAST, WEST};
+    Direction BishopDirections[4] = {NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST};
+
+    for (Direction d : (pt == ROOK ? RookDirections : BishopDirections))
+    {
+        Square s = sq;
+        while(safe_destination(s, d) && !(occupied & s))
+            attacks |= (s += d);
+    }
+
+    return attacks;
+  }
+
+
+  // init_magics() computes all rook and bishop attacks at startup. Magic
+  // bitboards are used to look up attacks of sliding pieces. As a reference see
+  // www.chessprogramming.org/Magic_Bitboards. In particular, here we use the so
+  // called "fancy" approach.
+
+  void init_magics(PieceType pt, Bitboard table[], Magic magics[]) {
+
+    // Optimal PRNG seeds to pick the correct magics in the shortest time
+    int seeds[][RANK_NB] = { { 8977, 44560, 54343, 38998,  5731, 95205, 104912, 17020 },
+                             {  728, 10316, 55013, 32803, 12281, 15100,  16645,   255 } };
+
+    Bitboard occupancy[4096], reference[4096], edges, b;
+    int epoch[4096] = {}, cnt = 0, size = 0;
+
+    for (Square s = SQ_A1; s <= SQ_H8; ++s)
+    {
+        // Board edges are not considered in the relevant occupancies
+        edges = ((Rank1BB | Rank8BB) & ~rank_bb(s)) | ((FileABB | FileHBB) & ~file_bb(s));
+
+        // Given a square 's', the mask is the bitboard of sliding attacks from
+        // 's' computed on an empty board. The index must be big enough to contain
+        // all the attacks for each possible subset of the mask and so is 2 power
+        // the number of 1s of the mask. Hence we deduce the size of the shift to
+        // apply to the 64 or 32 bits word to get the index.
+        Magic& m = magics[s];
+        m.mask  = sliding_attack(pt, s, 0) & ~edges;
+        m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask);
+
+        // Set the offset for the attacks table of the square. We have individual
+        // table sizes for each square with "Fancy Magic Bitboards".
+        m.attacks = s == SQ_A1 ? table : magics[s - 1].attacks + size;
+
+        // Use Carry-Rippler trick to enumerate all subsets of masks[s] and
+        // store the corresponding sliding attack bitboard in reference[].
+        b = size = 0;
+        do {
+            occupancy[size] = b;
+            reference[size] = sliding_attack(pt, s, b);
+
+            if (HasPext)
+                m.attacks[pext(b, m.mask)] = reference[size];
+
+            size++;
+            b = (b - m.mask) & m.mask;
+        } while (b);
+
+        if (HasPext)
+            continue;
+
+        PRNG rng(seeds[Is64Bit][rank_of(s)]);
+
+        // Find a magic for square 's' picking up an (almost) random number
+        // until we find the one that passes the verification test.
+        for (int i = 0; i < size; )
+        {
+            for (m.magic = 0; popcount((m.magic * m.mask) >> 56) < 6; )
+                m.magic = rng.sparse_rand<Bitboard>();
+
+            // A good magic must map every possible occupancy to an index that
+            // looks up the correct sliding attack in the attacks[s] database.
+            // Note that we build up the database for square 's' as a side
+            // effect of verifying the magic. Keep track of the attempt count
+            // and save it in epoch[], little speed-up trick to avoid resetting
+            // m.attacks[] after every failed attempt.
+            for (++cnt, i = 0; i < size; ++i)
+            {
+                unsigned idx = m.index(occupancy[i]);
+
+                if (epoch[idx] < cnt)
+                {
+                    epoch[idx] = cnt;
+                    m.attacks[idx] = reference[i];
+                }
+                else if (m.attacks[idx] != reference[i])
+                    break;
+            }
+        }
+    }
+  }
+}
diff --git a/src/bitboard.h b/src/bitboard.h
new file mode 100644 (file)
index 0000000..29d8f66
--- /dev/null
@@ -0,0 +1,433 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef BITBOARD_H_INCLUDED
+#define BITBOARD_H_INCLUDED
+
+#include <string>
+
+#include "types.h"
+
+namespace Bitbases {
+
+void init();
+bool probe(Square wksq, Square wpsq, Square bksq, Color us);
+
+}
+
+namespace Bitboards {
+
+void init();
+const std::string pretty(Bitboard b);
+
+}
+
+constexpr Bitboard AllSquares = ~Bitboard(0);
+constexpr Bitboard DarkSquares = 0xAA55AA55AA55AA55ULL;
+
+constexpr Bitboard FileABB = 0x0101010101010101ULL;
+constexpr Bitboard FileBBB = FileABB << 1;
+constexpr Bitboard FileCBB = FileABB << 2;
+constexpr Bitboard FileDBB = FileABB << 3;
+constexpr Bitboard FileEBB = FileABB << 4;
+constexpr Bitboard FileFBB = FileABB << 5;
+constexpr Bitboard FileGBB = FileABB << 6;
+constexpr Bitboard FileHBB = FileABB << 7;
+
+constexpr Bitboard Rank1BB = 0xFF;
+constexpr Bitboard Rank2BB = Rank1BB << (8 * 1);
+constexpr Bitboard Rank3BB = Rank1BB << (8 * 2);
+constexpr Bitboard Rank4BB = Rank1BB << (8 * 3);
+constexpr Bitboard Rank5BB = Rank1BB << (8 * 4);
+constexpr Bitboard Rank6BB = Rank1BB << (8 * 5);
+constexpr Bitboard Rank7BB = Rank1BB << (8 * 6);
+constexpr Bitboard Rank8BB = Rank1BB << (8 * 7);
+
+constexpr Bitboard QueenSide   = FileABB | FileBBB | FileCBB | FileDBB;
+constexpr Bitboard CenterFiles = FileCBB | FileDBB | FileEBB | FileFBB;
+constexpr Bitboard KingSide    = FileEBB | FileFBB | FileGBB | FileHBB;
+constexpr Bitboard Center      = (FileDBB | FileEBB) & (Rank4BB | Rank5BB);
+
+constexpr Bitboard KingFlank[FILE_NB] = {
+  QueenSide ^ FileDBB, QueenSide, QueenSide,
+  CenterFiles, CenterFiles,
+  KingSide, KingSide, KingSide ^ FileEBB
+};
+
+extern uint8_t PopCnt16[1 << 16];
+extern uint8_t SquareDistance[SQUARE_NB][SQUARE_NB];
+
+extern Bitboard SquareBB[SQUARE_NB];
+extern Bitboard LineBB[SQUARE_NB][SQUARE_NB];
+extern Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB];
+extern Bitboard PawnAttacks[COLOR_NB][SQUARE_NB];
+
+
+/// Magic holds all magic bitboards relevant data for a single square
+struct Magic {
+  Bitboard  mask;
+  Bitboard  magic;
+  Bitboard* attacks;
+  unsigned  shift;
+
+  // Compute the attack's index using the 'magic bitboards' approach
+  unsigned index(Bitboard occupied) const {
+
+    if (HasPext)
+        return unsigned(pext(occupied, mask));
+
+    if (Is64Bit)
+        return unsigned(((occupied & mask) * magic) >> shift);
+
+    unsigned lo = unsigned(occupied) & unsigned(mask);
+    unsigned hi = unsigned(occupied >> 32) & unsigned(mask >> 32);
+    return (lo * unsigned(magic) ^ hi * unsigned(magic >> 32)) >> shift;
+  }
+};
+
+extern Magic RookMagics[SQUARE_NB];
+extern Magic BishopMagics[SQUARE_NB];
+
+inline Bitboard square_bb(Square s) {
+  assert(is_ok(s));
+  return SquareBB[s];
+}
+
+
+/// Overloads of bitwise operators between a Bitboard and a Square for testing
+/// whether a given bit is set in a bitboard, and for setting and clearing bits.
+
+inline Bitboard  operator&( Bitboard  b, Square s) { return b &  square_bb(s); }
+inline Bitboard  operator|( Bitboard  b, Square s) { return b |  square_bb(s); }
+inline Bitboard  operator^( Bitboard  b, Square s) { return b ^  square_bb(s); }
+inline Bitboard& operator|=(Bitboard& b, Square s) { return b |= square_bb(s); }
+inline Bitboard& operator^=(Bitboard& b, Square s) { return b ^= square_bb(s); }
+
+inline Bitboard  operator&(Square s, Bitboard b) { return b & s; }
+inline Bitboard  operator|(Square s, Bitboard b) { return b | s; }
+inline Bitboard  operator^(Square s, Bitboard b) { return b ^ s; }
+
+inline Bitboard  operator|(Square s1, Square s2) { return square_bb(s1) | s2; }
+
+constexpr bool more_than_one(Bitboard b) {
+  return b & (b - 1);
+}
+
+
+constexpr bool opposite_colors(Square s1, Square s2) {
+  return (s1 + rank_of(s1) + s2 + rank_of(s2)) & 1;
+}
+
+
+/// rank_bb() and file_bb() return a bitboard representing all the squares on
+/// the given file or rank.
+
+constexpr Bitboard rank_bb(Rank r) {
+  return Rank1BB << (8 * r);
+}
+
+constexpr Bitboard rank_bb(Square s) {
+  return rank_bb(rank_of(s));
+}
+
+constexpr Bitboard file_bb(File f) {
+  return FileABB << f;
+}
+
+constexpr Bitboard file_bb(Square s) {
+  return file_bb(file_of(s));
+}
+
+
+/// shift() moves a bitboard one or two steps as specified by the direction D
+
+template<Direction D>
+constexpr Bitboard shift(Bitboard b) {
+  return  D == NORTH      ?  b             << 8 : D == SOUTH      ?  b             >> 8
+        : D == NORTH+NORTH?  b             <<16 : D == SOUTH+SOUTH?  b             >>16
+        : D == EAST       ? (b & ~FileHBB) << 1 : D == WEST       ? (b & ~FileABB) >> 1
+        : D == NORTH_EAST ? (b & ~FileHBB) << 9 : D == NORTH_WEST ? (b & ~FileABB) << 7
+        : D == SOUTH_EAST ? (b & ~FileHBB) >> 7 : D == SOUTH_WEST ? (b & ~FileABB) >> 9
+        : 0;
+}
+
+
+/// pawn_attacks_bb() returns the squares attacked by pawns of the given color
+/// from the squares in the given bitboard.
+
+template<Color C>
+constexpr Bitboard pawn_attacks_bb(Bitboard b) {
+  return C == WHITE ? shift<NORTH_WEST>(b) | shift<NORTH_EAST>(b)
+                    : shift<SOUTH_WEST>(b) | shift<SOUTH_EAST>(b);
+}
+
+inline Bitboard pawn_attacks_bb(Color c, Square s) {
+
+  assert(is_ok(s));
+  return PawnAttacks[c][s];
+}
+
+
+/// pawn_double_attacks_bb() returns the squares doubly attacked by pawns of the
+/// given color from the squares in the given bitboard.
+
+template<Color C>
+constexpr Bitboard pawn_double_attacks_bb(Bitboard b) {
+  return C == WHITE ? shift<NORTH_WEST>(b) & shift<NORTH_EAST>(b)
+                    : shift<SOUTH_WEST>(b) & shift<SOUTH_EAST>(b);
+}
+
+
+/// adjacent_files_bb() returns a bitboard representing all the squares on the
+/// adjacent files of a given square.
+
+constexpr Bitboard adjacent_files_bb(Square s) {
+  return shift<EAST>(file_bb(s)) | shift<WEST>(file_bb(s));
+}
+
+
+/// line_bb() returns a bitboard representing an entire line (from board edge
+/// to board edge) that intersects the two given squares. If the given squares
+/// are not on a same file/rank/diagonal, the function returns 0. For instance,
+/// line_bb(SQ_C4, SQ_F7) will return a bitboard with the A2-G8 diagonal.
+
+inline Bitboard line_bb(Square s1, Square s2) {
+
+  assert(is_ok(s1) && is_ok(s2));
+  return LineBB[s1][s2];
+}
+
+
+/// between_bb() returns a bitboard representing squares that are linearly
+/// between the two given squares (excluding the given squares). If the given
+/// squares are not on a same file/rank/diagonal, we return 0. For instance,
+/// between_bb(SQ_C4, SQ_F7) will return a bitboard with squares D5 and E6.
+
+inline Bitboard between_bb(Square s1, Square s2) {
+  Bitboard b = line_bb(s1, s2) & ((AllSquares << s1) ^ (AllSquares << s2));
+  return b & (b - 1); //exclude lsb
+}
+
+
+/// forward_ranks_bb() returns a bitboard representing the squares on the ranks
+/// in front of the given one, from the point of view of the given color. For instance,
+/// forward_ranks_bb(BLACK, SQ_D3) will return the 16 squares on ranks 1 and 2.
+
+constexpr Bitboard forward_ranks_bb(Color c, Square s) {
+  return c == WHITE ? ~Rank1BB << 8 * relative_rank(WHITE, s)
+                    : ~Rank8BB >> 8 * relative_rank(BLACK, s);
+}
+
+
+/// forward_file_bb() returns a bitboard representing all the squares along the
+/// line in front of the given one, from the point of view of the given color.
+
+constexpr Bitboard forward_file_bb(Color c, Square s) {
+  return forward_ranks_bb(c, s) & file_bb(s);
+}
+
+
+/// pawn_attack_span() returns a bitboard representing all the squares that can
+/// be attacked by a pawn of the given color when it moves along its file, starting
+/// from the given square.
+
+constexpr Bitboard pawn_attack_span(Color c, Square s) {
+  return forward_ranks_bb(c, s) & adjacent_files_bb(s);
+}
+
+
+/// passed_pawn_span() returns a bitboard which can be used to test if a pawn of
+/// the given color and on the given square is a passed pawn.
+
+constexpr Bitboard passed_pawn_span(Color c, Square s) {
+  return pawn_attack_span(c, s) | forward_file_bb(c, s);
+}
+
+
+/// aligned() returns true if the squares s1, s2 and s3 are aligned either on a
+/// straight or on a diagonal line.
+
+inline bool aligned(Square s1, Square s2, Square s3) {
+  return line_bb(s1, s2) & s3;
+}
+
+
+/// distance() functions return the distance between x and y, defined as the
+/// number of steps for a king in x to reach y.
+
+template<typename T1 = Square> inline int distance(Square x, Square y);
+template<> inline int distance<File>(Square x, Square y) { return std::abs(file_of(x) - file_of(y)); }
+template<> inline int distance<Rank>(Square x, Square y) { return std::abs(rank_of(x) - rank_of(y)); }
+template<> inline int distance<Square>(Square x, Square y) { return SquareDistance[x][y]; }
+
+inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); }
+inline int edge_distance(Rank r) { return std::min(r, Rank(RANK_8 - r)); }
+
+
+/// attacks_bb(Square) returns the pseudo attacks of the give piece type
+/// assuming an empty board.
+
+template<PieceType Pt>
+inline Bitboard attacks_bb(Square s) {
+
+  assert((Pt != PAWN) && (is_ok(s)));
+
+  return PseudoAttacks[Pt][s];
+}
+
+
+/// attacks_bb(Square, Bitboard) returns the attacks by the given piece
+/// assuming the board is occupied according to the passed Bitboard.
+/// Sliding piece attacks do not continue passed an occupied square.
+
+template<PieceType Pt>
+inline Bitboard attacks_bb(Square s, Bitboard occupied) {
+
+  assert((Pt != PAWN) && (is_ok(s)));
+
+  switch (Pt)
+  {
+  case BISHOP: return BishopMagics[s].attacks[BishopMagics[s].index(occupied)];
+  case ROOK  : return   RookMagics[s].attacks[  RookMagics[s].index(occupied)];
+  case QUEEN : return attacks_bb<BISHOP>(s, occupied) | attacks_bb<ROOK>(s, occupied);
+  default    : return PseudoAttacks[Pt][s];
+  }
+}
+
+inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) {
+
+  assert((pt != PAWN) && (is_ok(s)));
+
+  switch (pt)
+  {
+  case BISHOP: return attacks_bb<BISHOP>(s, occupied);
+  case ROOK  : return attacks_bb<  ROOK>(s, occupied);
+  case QUEEN : return attacks_bb<BISHOP>(s, occupied) | attacks_bb<ROOK>(s, occupied);
+  default    : return PseudoAttacks[pt][s];
+  }
+}
+
+
+/// popcount() counts the number of non-zero bits in a bitboard
+
+inline int popcount(Bitboard b) {
+
+#ifndef USE_POPCNT
+
+  union { Bitboard bb; uint16_t u[4]; } v = { b };
+  return PopCnt16[v.u[0]] + PopCnt16[v.u[1]] + PopCnt16[v.u[2]] + PopCnt16[v.u[3]];
+
+#elif defined(_MSC_VER) || defined(__INTEL_COMPILER)
+
+  return (int)_mm_popcnt_u64(b);
+
+#else // Assumed gcc or compatible compiler
+
+  return __builtin_popcountll(b);
+
+#endif
+}
+
+
+/// lsb() and msb() return the least/most significant bit in a non-zero bitboard
+
+#if defined(__GNUC__)  // GCC, Clang, ICC
+
+inline Square lsb(Bitboard b) {
+  assert(b);
+  return Square(__builtin_ctzll(b));
+}
+
+inline Square msb(Bitboard b) {
+  assert(b);
+  return Square(63 ^ __builtin_clzll(b));
+}
+
+#elif defined(_MSC_VER)  // MSVC
+
+#ifdef _WIN64  // MSVC, WIN64
+
+inline Square lsb(Bitboard b) {
+  assert(b);
+  unsigned long idx;
+  _BitScanForward64(&idx, b);
+  return (Square) idx;
+}
+
+inline Square msb(Bitboard b) {
+  assert(b);
+  unsigned long idx;
+  _BitScanReverse64(&idx, b);
+  return (Square) idx;
+}
+
+#else  // MSVC, WIN32
+
+inline Square lsb(Bitboard b) {
+  assert(b);
+  unsigned long idx;
+
+  if (b & 0xffffffff) {
+      _BitScanForward(&idx, int32_t(b));
+      return Square(idx);
+  } else {
+      _BitScanForward(&idx, int32_t(b >> 32));
+      return Square(idx + 32);
+  }
+}
+
+inline Square msb(Bitboard b) {
+  assert(b);
+  unsigned long idx;
+
+  if (b >> 32) {
+      _BitScanReverse(&idx, int32_t(b >> 32));
+      return Square(idx + 32);
+  } else {
+      _BitScanReverse(&idx, int32_t(b));
+      return Square(idx);
+  }
+}
+
+#endif
+
+#else  // Compiler is neither GCC nor MSVC compatible
+
+#error "Compiler not supported."
+
+#endif
+
+
+/// pop_lsb() finds and clears the least significant bit in a non-zero bitboard
+
+inline Square pop_lsb(Bitboard* b) {
+  assert(*b);
+  const Square s = lsb(*b);
+  *b &= *b - 1;
+  return s;
+}
+
+
+/// frontmost_sq() returns the most advanced square for the given color,
+/// requires a non-zero bitboard.
+inline Square frontmost_sq(Color c, Bitboard b) {
+  assert(b);
+  return c == WHITE ? msb(b) : lsb(b);
+}
+
+#endif // #ifndef BITBOARD_H_INCLUDED
diff --git a/src/endgame.cpp b/src/endgame.cpp
new file mode 100644 (file)
index 0000000..c8be219
--- /dev/null
@@ -0,0 +1,743 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <cassert>
+
+#include "bitboard.h"
+#include "endgame.h"
+#include "movegen.h"
+
+namespace {
+
+  // Used to drive the king towards the edge of the board
+  // in KX vs K and KQ vs KR endgames.
+  // Values range from 27 (center squares) to 90 (in the corners)
+  inline int push_to_edge(Square s) {
+      int rd = edge_distance(rank_of(s)), fd = edge_distance(file_of(s));
+      return 90 - (7 * fd * fd / 2 + 7 * rd * rd / 2);
+  }
+
+  // Used to drive the king towards A1H8 corners in KBN vs K endgames.
+  // Values range from 0 on A8H1 diagonal to 7 in A1H8 corners
+  inline int push_to_corner(Square s) {
+      return abs(7 - rank_of(s) - file_of(s));
+  }
+
+  // Drive a piece close to or away from another piece
+  inline int push_close(Square s1, Square s2) { return 140 - 20 * distance(s1, s2); }
+  inline int push_away(Square s1, Square s2) { return 120 - push_close(s1, s2); }
+
+#ifndef NDEBUG
+  bool verify_material(const Position& pos, Color c, Value npm, int pawnsCnt) {
+    return pos.non_pawn_material(c) == npm && pos.count<PAWN>(c) == pawnsCnt;
+  }
+#endif
+
+  // Map the square as if strongSide is white and strongSide's only pawn
+  // is on the left half of the board.
+  Square normalize(const Position& pos, Color strongSide, Square sq) {
+
+    assert(pos.count<PAWN>(strongSide) == 1);
+
+    if (file_of(pos.square<PAWN>(strongSide)) >= FILE_E)
+        sq = flip_file(sq);
+
+    return strongSide == WHITE ? sq : flip_rank(sq);
+  }
+
+} // namespace
+
+
+namespace Endgames {
+
+  std::pair<Map<Value>, Map<ScaleFactor>> maps;
+
+  void init() {
+
+    add<KPK>("KPK");
+    add<KNNK>("KNNK");
+    add<KBNK>("KBNK");
+    add<KRKP>("KRKP");
+    add<KRKB>("KRKB");
+    add<KRKN>("KRKN");
+    add<KQKP>("KQKP");
+    add<KQKR>("KQKR");
+    add<KNNKP>("KNNKP");
+
+    add<KRPKR>("KRPKR");
+    add<KRPKB>("KRPKB");
+    add<KBPKB>("KBPKB");
+    add<KBPKN>("KBPKN");
+    add<KBPPKB>("KBPPKB");
+    add<KRPPKRP>("KRPPKRP");
+  }
+}
+
+
+/// Mate with KX vs K. This function is used to evaluate positions with
+/// king and plenty of material vs a lone king. It simply gives the
+/// attacking side a bonus for driving the defending king towards the edge
+/// of the board, and for keeping the distance between the two kings small.
+template<>
+Value Endgame<KXK>::operator()(const Position& pos) const {
+
+  assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
+  assert(!pos.checkers()); // Eval is never called when in check
+
+  // Stalemate detection with lone king
+  if (pos.side_to_move() == weakSide && !MoveList<LEGAL>(pos).size())
+      return VALUE_DRAW;
+
+  Square strongKing = pos.square<KING>(strongSide);
+  Square weakKing   = pos.square<KING>(weakSide);
+
+  Value result =  pos.non_pawn_material(strongSide)
+                + pos.count<PAWN>(strongSide) * PawnValueEg
+                + push_to_edge(weakKing)
+                + push_close(strongKing, weakKing);
+
+  if (   pos.count<QUEEN>(strongSide)
+      || pos.count<ROOK>(strongSide)
+      ||(pos.count<BISHOP>(strongSide) && pos.count<KNIGHT>(strongSide))
+      || (   (pos.pieces(strongSide, BISHOP) & ~DarkSquares)
+          && (pos.pieces(strongSide, BISHOP) &  DarkSquares)))
+      result = std::min(result + VALUE_KNOWN_WIN, VALUE_TB_WIN_IN_MAX_PLY - 1);
+
+  return strongSide == pos.side_to_move() ? result : -result;
+}
+
+
+/// Mate with KBN vs K. This is similar to KX vs K, but we have to drive the
+/// defending king towards a corner square that our bishop attacks.
+template<>
+Value Endgame<KBNK>::operator()(const Position& pos) const {
+
+  assert(verify_material(pos, strongSide, KnightValueMg + BishopValueMg, 0));
+  assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
+
+  Square strongKing   = pos.square<KING>(strongSide);
+  Square strongBishop = pos.square<BISHOP>(strongSide);
+  Square weakKing     = pos.square<KING>(weakSide);
+
+  // If our bishop does not attack A1/H8, we flip the enemy king square
+  // to drive to opposite corners (A8/H1).
+
+  Value result =  (VALUE_KNOWN_WIN + 3520)
+                + push_close(strongKing, weakKing)
+                + 420 * push_to_corner(opposite_colors(strongBishop, SQ_A1) ? flip_file(weakKing) : weakKing);
+
+  assert(abs(result) < VALUE_TB_WIN_IN_MAX_PLY);
+  return strongSide == pos.side_to_move() ? result : -result;
+}
+
+
+/// KP vs K. This endgame is evaluated with the help of a bitbase
+template<>
+Value Endgame<KPK>::operator()(const Position& pos) const {
+
+  assert(verify_material(pos, strongSide, VALUE_ZERO, 1));
+  assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
+
+  // Assume strongSide is white and the pawn is on files A-D
+  Square strongKing = normalize(pos, strongSide, pos.square<KING>(strongSide));
+  Square strongPawn = normalize(pos, strongSide, pos.square<PAWN>(strongSide));
+  Square weakKing   = normalize(pos, strongSide, pos.square<KING>(weakSide));
+
+  Color us = strongSide == pos.side_to_move() ? WHITE : BLACK;
+
+  if (!Bitbases::probe(strongKing, strongPawn, weakKing, us))
+      return VALUE_DRAW;
+
+  Value result = VALUE_KNOWN_WIN + PawnValueEg + Value(rank_of(strongPawn));
+
+  return strongSide == pos.side_to_move() ? result : -result;
+}
+
+
+/// KR vs KP. This is a somewhat tricky endgame to evaluate precisely without
+/// a bitbase. The function below returns drawish scores when the pawn is
+/// far advanced with support of the king, while the attacking king is far
+/// away.
+template<>
+Value Endgame<KRKP>::operator()(const Position& pos) const {
+
+  assert(verify_material(pos, strongSide, RookValueMg, 0));
+  assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
+
+  Square strongKing = pos.square<KING>(strongSide);
+  Square weakKing   = pos.square<KING>(weakSide);
+  Square strongRook = pos.square<ROOK>(strongSide);
+  Square weakPawn   = pos.square<PAWN>(weakSide);
+  Square queeningSquare = make_square(file_of(weakPawn), relative_rank(weakSide, RANK_8));
+  Value result;
+
+  // If the stronger side's king is in front of the pawn, it's a win
+  if (forward_file_bb(strongSide, strongKing) & weakPawn)
+      result = RookValueEg - distance(strongKing, weakPawn);
+
+  // If the weaker side's king is too far from the pawn and the rook,
+  // it's a win.
+  else if (   distance(weakKing, weakPawn) >= 3 + (pos.side_to_move() == weakSide)
+           && distance(weakKing, strongRook) >= 3)
+      result = RookValueEg - distance(strongKing, weakPawn);
+
+  // If the pawn is far advanced and supported by the defending king,
+  // the position is drawish
+  else if (   relative_rank(strongSide, weakKing) <= RANK_3
+           && distance(weakKing, weakPawn) == 1
+           && relative_rank(strongSide, strongKing) >= RANK_4
+           && distance(strongKing, weakPawn) > 2 + (pos.side_to_move() == strongSide))
+      result = Value(80) - 8 * distance(strongKing, weakPawn);
+
+  else
+      result =  Value(200) - 8 * (  distance(strongKing, weakPawn + pawn_push(weakSide))
+                                  - distance(weakKing, weakPawn + pawn_push(weakSide))
+                                  - distance(weakPawn, queeningSquare));
+
+  return strongSide == pos.side_to_move() ? result : -result;
+}
+
+
+/// KR vs KB. This is very simple, and always returns drawish scores. The
+/// score is slightly bigger when the defending king is close to the edge.
+template<>
+Value Endgame<KRKB>::operator()(const Position& pos) const {
+
+  assert(verify_material(pos, strongSide, RookValueMg, 0));
+  assert(verify_material(pos, weakSide, BishopValueMg, 0));
+
+  Value result = Value(push_to_edge(pos.square<KING>(weakSide)));
+  return strongSide == pos.side_to_move() ? result : -result;
+}
+
+
+/// KR vs KN. The attacking side has slightly better winning chances than
+/// in KR vs KB, particularly if the king and the knight are far apart.
+template<>
+Value Endgame<KRKN>::operator()(const Position& pos) const {
+
+  assert(verify_material(pos, strongSide, RookValueMg, 0));
+  assert(verify_material(pos, weakSide, KnightValueMg, 0));
+
+  Square weakKing   = pos.square<KING>(weakSide);
+  Square weakKnight = pos.square<KNIGHT>(weakSide);
+  Value result = Value(push_to_edge(weakKing) + push_away(weakKing, weakKnight));
+  return strongSide == pos.side_to_move() ? result : -result;
+}
+
+
+/// KQ vs KP. In general, this is a win for the stronger side, but there are a
+/// few important exceptions. A pawn on 7th rank and on the A,C,F or H files
+/// with a king positioned next to it can be a draw, so in that case, we only
+/// use the distance between the kings.
+template<>
+Value Endgame<KQKP>::operator()(const Position& pos) const {
+
+  assert(verify_material(pos, strongSide, QueenValueMg, 0));
+  assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
+
+  Square strongKing = pos.square<KING>(strongSide);
+  Square weakKing   = pos.square<KING>(weakSide);
+  Square weakPawn   = pos.square<PAWN>(weakSide);
+
+  Value result = Value(push_close(strongKing, weakKing));
+
+  if (   relative_rank(weakSide, weakPawn) != RANK_7
+      || distance(weakKing, weakPawn) != 1
+      || ((FileBBB | FileDBB | FileEBB | FileGBB) & weakPawn))
+      result += QueenValueEg - PawnValueEg;
+
+  return strongSide == pos.side_to_move() ? result : -result;
+}
+
+
+/// KQ vs KR. This is almost identical to KX vs K: we give the attacking
+/// king a bonus for having the kings close together, and for forcing the
+/// defending king towards the edge. If we also take care to avoid null move for
+/// the defending side in the search, this is usually sufficient to win KQ vs KR.
+template<>
+Value Endgame<KQKR>::operator()(const Position& pos) const {
+
+  assert(verify_material(pos, strongSide, QueenValueMg, 0));
+  assert(verify_material(pos, weakSide, RookValueMg, 0));
+
+  Square strongKing = pos.square<KING>(strongSide);
+  Square weakKing   = pos.square<KING>(weakSide);
+
+  Value result =  QueenValueEg
+                - RookValueEg
+                + push_to_edge(weakKing)
+                + push_close(strongKing, weakKing);
+
+  return strongSide == pos.side_to_move() ? result : -result;
+}
+
+
+/// KNN vs KP. Very drawish, but there are some mate opportunities if we can
+/// press the weakSide King to a corner before the pawn advances too much.
+template<>
+Value Endgame<KNNKP>::operator()(const Position& pos) const {
+
+  assert(verify_material(pos, strongSide, 2 * KnightValueMg, 0));
+  assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
+
+  Square weakKing = pos.square<KING>(weakSide);
+  Square weakPawn = pos.square<PAWN>(weakSide);
+
+  Value result =      PawnValueEg
+               +  2 * push_to_edge(weakKing)
+               - 10 * relative_rank(weakSide, weakPawn);
+
+  return strongSide == pos.side_to_move() ? result : -result;
+}
+
+
+/// Some cases of trivial draws
+template<> Value Endgame<KNNK>::operator()(const Position&) const { return VALUE_DRAW; }
+
+
+/// KB and one or more pawns vs K. It checks for draws with rook pawns and
+/// a bishop of the wrong color. If such a draw is detected, SCALE_FACTOR_DRAW
+/// is returned. If not, the return value is SCALE_FACTOR_NONE, i.e. no scaling
+/// will be used.
+template<>
+ScaleFactor Endgame<KBPsK>::operator()(const Position& pos) const {
+
+  assert(pos.non_pawn_material(strongSide) == BishopValueMg);
+  assert(pos.count<PAWN>(strongSide) >= 1);
+
+  // No assertions about the material of weakSide, because we want draws to
+  // be detected even when the weaker side has some pawns.
+
+  Bitboard strongPawns = pos.pieces(strongSide, PAWN);
+  Bitboard allPawns = pos.pieces(PAWN);
+
+  Square strongBishop = pos.square<BISHOP>(strongSide);
+  Square weakKing = pos.square<KING>(weakSide);
+  Square strongKing = pos.square<KING>(strongSide);
+
+  // All strongSide pawns are on a single rook file?
+  if (!(strongPawns & ~FileABB) || !(strongPawns & ~FileHBB))
+  {
+      Square queeningSquare = relative_square(strongSide, make_square(file_of(lsb(strongPawns)), RANK_8));
+
+      if (   opposite_colors(queeningSquare, strongBishop)
+          && distance(queeningSquare, weakKing) <= 1)
+          return SCALE_FACTOR_DRAW;
+  }
+
+  // If all the pawns are on the same B or G file, then it's potentially a draw
+  if ((!(allPawns & ~FileBBB) || !(allPawns & ~FileGBB))
+      && pos.non_pawn_material(weakSide) == 0
+      && pos.count<PAWN>(weakSide) >= 1)
+  {
+      // Get the least advanced weakSide pawn
+      Square weakPawn = frontmost_sq(strongSide, pos.pieces(weakSide, PAWN));
+
+      // There's potential for a draw if our pawn is blocked on the 7th rank,
+      // the bishop cannot attack it or they only have one pawn left.
+      if (   relative_rank(strongSide, weakPawn) == RANK_7
+          && (strongPawns & (weakPawn + pawn_push(weakSide)))
+          && (opposite_colors(strongBishop, weakPawn) || !more_than_one(strongPawns)))
+      {
+          int strongKingDist = distance(weakPawn, strongKing);
+          int weakKingDist = distance(weakPawn, weakKing);
+
+          // It's a draw if the weak king is on its back two ranks, within 2
+          // squares of the blocking pawn and the strong king is not
+          // closer. (I think this rule only fails in practically
+          // unreachable positions such as 5k1K/6p1/6P1/8/8/3B4/8/8 w
+          // and positions where qsearch will immediately correct the
+          // problem such as 8/4k1p1/6P1/1K6/3B4/8/8/8 w).
+          if (   relative_rank(strongSide, weakKing) >= RANK_7
+              && weakKingDist <= 2
+              && weakKingDist <= strongKingDist)
+              return SCALE_FACTOR_DRAW;
+      }
+  }
+
+  return SCALE_FACTOR_NONE;
+}
+
+
+/// KQ vs KR and one or more pawns. It tests for fortress draws with a rook on
+/// the third rank defended by a pawn.
+template<>
+ScaleFactor Endgame<KQKRPs>::operator()(const Position& pos) const {
+
+  assert(verify_material(pos, strongSide, QueenValueMg, 0));
+  assert(pos.count<ROOK>(weakSide) == 1);
+  assert(pos.count<PAWN>(weakSide) >= 1);
+
+  Square strongKing = pos.square<KING>(strongSide);
+  Square weakKing   = pos.square<KING>(weakSide);
+  Square weakRook   = pos.square<ROOK>(weakSide);
+
+  if (    relative_rank(weakSide,   weakKing) <= RANK_2
+      &&  relative_rank(weakSide, strongKing) >= RANK_4
+      &&  relative_rank(weakSide,   weakRook) == RANK_3
+      && (  pos.pieces(weakSide, PAWN)
+          & attacks_bb<KING>(weakKing)
+          & pawn_attacks_bb(strongSide, weakRook)))
+          return SCALE_FACTOR_DRAW;
+
+  return SCALE_FACTOR_NONE;
+}
+
+
+/// KRP vs KR. This function knows a handful of the most important classes of
+/// drawn positions, but is far from perfect. It would probably be a good idea
+/// to add more knowledge in the future.
+///
+/// It would also be nice to rewrite the actual code for this function,
+/// which is mostly copied from Glaurung 1.x, and isn't very pretty.
+template<>
+ScaleFactor Endgame<KRPKR>::operator()(const Position& pos) const {
+
+  assert(verify_material(pos, strongSide, RookValueMg, 1));
+  assert(verify_material(pos, weakSide,   RookValueMg, 0));
+
+  // Assume strongSide is white and the pawn is on files A-D
+  Square strongKing = normalize(pos, strongSide, pos.square<KING>(strongSide));
+  Square strongRook = normalize(pos, strongSide, pos.square<ROOK>(strongSide));
+  Square strongPawn = normalize(pos, strongSide, pos.square<PAWN>(strongSide));
+  Square weakKing = normalize(pos, strongSide, pos.square<KING>(weakSide));
+  Square weakRook = normalize(pos, strongSide, pos.square<ROOK>(weakSide));
+
+  File pawnFile = file_of(strongPawn);
+  Rank pawnRank = rank_of(strongPawn);
+  Square queeningSquare = make_square(pawnFile, RANK_8);
+  int tempo = (pos.side_to_move() == strongSide);
+
+  // If the pawn is not too far advanced and the defending king defends the
+  // queening square, use the third-rank defence.
+  if (   pawnRank <= RANK_5
+      && distance(weakKing, queeningSquare) <= 1
+      && strongKing <= SQ_H5
+      && (rank_of(weakRook) == RANK_6 || (pawnRank <= RANK_3 && rank_of(strongRook) != RANK_6)))
+      return SCALE_FACTOR_DRAW;
+
+  // The defending side saves a draw by checking from behind in case the pawn
+  // has advanced to the 6th rank with the king behind.
+  if (   pawnRank == RANK_6
+      && distance(weakKing, queeningSquare) <= 1
+      && rank_of(strongKing) + tempo <= RANK_6
+      && (rank_of(weakRook) == RANK_1 || (!tempo && distance<File>(weakRook, strongPawn) >= 3)))
+      return SCALE_FACTOR_DRAW;
+
+  if (   pawnRank >= RANK_6
+      && weakKing == queeningSquare
+      && rank_of(weakRook) == RANK_1
+      && (!tempo || distance(strongKing, strongPawn) >= 2))
+      return SCALE_FACTOR_DRAW;
+
+  // White pawn on a7 and rook on a8 is a draw if black's king is on g7 or h7
+  // and the black rook is behind the pawn.
+  if (   strongPawn == SQ_A7
+      && strongRook == SQ_A8
+      && (weakKing == SQ_H7 || weakKing == SQ_G7)
+      && file_of(weakRook) == FILE_A
+      && (rank_of(weakRook) <= RANK_3 || file_of(strongKing) >= FILE_D || rank_of(strongKing) <= RANK_5))
+      return SCALE_FACTOR_DRAW;
+
+  // If the defending king blocks the pawn and the attacking king is too far
+  // away, it's a draw.
+  if (   pawnRank <= RANK_5
+      && weakKing == strongPawn + NORTH
+      && distance(strongKing, strongPawn) - tempo >= 2
+      && distance(strongKing, weakRook) - tempo >= 2)
+      return SCALE_FACTOR_DRAW;
+
+  // Pawn on the 7th rank supported by the rook from behind usually wins if the
+  // attacking king is closer to the queening square than the defending king,
+  // and the defending king cannot gain tempi by threatening the attacking rook.
+  if (   pawnRank == RANK_7
+      && pawnFile != FILE_A
+      && file_of(strongRook) == pawnFile
+      && strongRook != queeningSquare
+      && (distance(strongKing, queeningSquare) < distance(weakKing, queeningSquare) - 2 + tempo)
+      && (distance(strongKing, queeningSquare) < distance(weakKing, strongRook) + tempo))
+      return ScaleFactor(SCALE_FACTOR_MAX - 2 * distance(strongKing, queeningSquare));
+
+  // Similar to the above, but with the pawn further back
+  if (   pawnFile != FILE_A
+      && file_of(strongRook) == pawnFile
+      && strongRook < strongPawn
+      && (distance(strongKing, queeningSquare) < distance(weakKing, queeningSquare) - 2 + tempo)
+      && (distance(strongKing, strongPawn + NORTH) < distance(weakKing, strongPawn + NORTH) - 2 + tempo)
+      && (  distance(weakKing, strongRook) + tempo >= 3
+          || (    distance(strongKing, queeningSquare) < distance(weakKing, strongRook) + tempo
+              && (distance(strongKing, strongPawn + NORTH) < distance(weakKing, strongPawn) + tempo))))
+      return ScaleFactor(  SCALE_FACTOR_MAX
+                         - 8 * distance(strongPawn, queeningSquare)
+                         - 2 * distance(strongKing, queeningSquare));
+
+  // If the pawn is not far advanced and the defending king is somewhere in
+  // the pawn's path, it's probably a draw.
+  if (pawnRank <= RANK_4 && weakKing > strongPawn)
+  {
+      if (file_of(weakKing) == file_of(strongPawn))
+          return ScaleFactor(10);
+      if (   distance<File>(weakKing, strongPawn) == 1
+          && distance(strongKing, weakKing) > 2)
+          return ScaleFactor(24 - 2 * distance(strongKing, weakKing));
+  }
+  return SCALE_FACTOR_NONE;
+}
+
+template<>
+ScaleFactor Endgame<KRPKB>::operator()(const Position& pos) const {
+
+  assert(verify_material(pos, strongSide, RookValueMg, 1));
+  assert(verify_material(pos, weakSide, BishopValueMg, 0));
+
+  // Test for a rook pawn
+  if (pos.pieces(PAWN) & (FileABB | FileHBB))
+  {
+      Square weakKing = pos.square<KING>(weakSide);
+      Square weakBishop = pos.square<BISHOP>(weakSide);
+      Square strongKing = pos.square<KING>(strongSide);
+      Square strongPawn = pos.square<PAWN>(strongSide);
+      Rank pawnRank = relative_rank(strongSide, strongPawn);
+      Direction push = pawn_push(strongSide);
+
+      // If the pawn is on the 5th rank and the pawn (currently) is on
+      // the same color square as the bishop then there is a chance of
+      // a fortress. Depending on the king position give a moderate
+      // reduction or a stronger one if the defending king is near the
+      // corner but not trapped there.
+      if (pawnRank == RANK_5 && !opposite_colors(weakBishop, strongPawn))
+      {
+          int d = distance(strongPawn + 3 * push, weakKing);
+
+          if (d <= 2 && !(d == 0 && weakKing == strongKing + 2 * push))
+              return ScaleFactor(24);
+          else
+              return ScaleFactor(48);
+      }
+
+      // When the pawn has moved to the 6th rank we can be fairly sure
+      // it's drawn if the bishop attacks the square in front of the
+      // pawn from a reasonable distance and the defending king is near
+      // the corner
+      if (   pawnRank == RANK_6
+          && distance(strongPawn + 2 * push, weakKing) <= 1
+          && (attacks_bb<BISHOP>(weakBishop) & (strongPawn + push))
+          && distance<File>(weakBishop, strongPawn) >= 2)
+          return ScaleFactor(8);
+  }
+
+  return SCALE_FACTOR_NONE;
+}
+
+/// KRPP vs KRP. There is just a single rule: if the stronger side has no passed
+/// pawns and the defending king is actively placed, the position is drawish.
+template<>
+ScaleFactor Endgame<KRPPKRP>::operator()(const Position& pos) const {
+
+  assert(verify_material(pos, strongSide, RookValueMg, 2));
+  assert(verify_material(pos, weakSide,   RookValueMg, 1));
+
+  Square strongPawn1 = pos.squares<PAWN>(strongSide)[0];
+  Square strongPawn2 = pos.squares<PAWN>(strongSide)[1];
+  Square weakKing = pos.square<KING>(weakSide);
+
+  // Does the stronger side have a passed pawn?
+  if (pos.pawn_passed(strongSide, strongPawn1) || pos.pawn_passed(strongSide, strongPawn2))
+      return SCALE_FACTOR_NONE;
+
+  Rank pawnRank = std::max(relative_rank(strongSide, strongPawn1), relative_rank(strongSide, strongPawn2));
+
+  if (   distance<File>(weakKing, strongPawn1) <= 1
+      && distance<File>(weakKing, strongPawn2) <= 1
+      && relative_rank(strongSide, weakKing) > pawnRank)
+  {
+      assert(pawnRank > RANK_1 && pawnRank < RANK_7);
+      return ScaleFactor(7 * pawnRank);
+  }
+  return SCALE_FACTOR_NONE;
+}
+
+
+/// K and two or more pawns vs K. There is just a single rule here: if all pawns
+/// are on the same rook file and are blocked by the defending king, it's a draw.
+template<>
+ScaleFactor Endgame<KPsK>::operator()(const Position& pos) const {
+
+  assert(pos.non_pawn_material(strongSide) == VALUE_ZERO);
+  assert(pos.count<PAWN>(strongSide) >= 2);
+  assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
+
+  Square weakKing = pos.square<KING>(weakSide);
+  Bitboard strongPawns = pos.pieces(strongSide, PAWN);
+
+  // If all pawns are ahead of the king on a single rook file, it's a draw.
+  if (   !(strongPawns & ~(FileABB | FileHBB))
+      && !(strongPawns & ~passed_pawn_span(weakSide, weakKing)))
+      return SCALE_FACTOR_DRAW;
+
+  return SCALE_FACTOR_NONE;
+}
+
+
+/// KBP vs KB. There are two rules: if the defending king is somewhere along the
+/// path of the pawn, and the square of the king is not of the same color as the
+/// stronger side's bishop, it's a draw. If the two bishops have opposite color,
+/// it's almost always a draw.
+template<>
+ScaleFactor Endgame<KBPKB>::operator()(const Position& pos) const {
+
+  assert(verify_material(pos, strongSide, BishopValueMg, 1));
+  assert(verify_material(pos, weakSide,   BishopValueMg, 0));
+
+  Square strongPawn = pos.square<PAWN>(strongSide);
+  Square strongBishop = pos.square<BISHOP>(strongSide);
+  Square weakBishop = pos.square<BISHOP>(weakSide);
+  Square weakKing = pos.square<KING>(weakSide);
+
+  // Case 1: Defending king blocks the pawn, and cannot be driven away
+  if (   (forward_file_bb(strongSide, strongPawn) & weakKing)
+      && (   opposite_colors(weakKing, strongBishop)
+          || relative_rank(strongSide, weakKing) <= RANK_6))
+      return SCALE_FACTOR_DRAW;
+
+  // Case 2: Opposite colored bishops
+  if (opposite_colors(strongBishop, weakBishop))
+      return SCALE_FACTOR_DRAW;
+
+  return SCALE_FACTOR_NONE;
+}
+
+
+/// KBPP vs KB. It detects a few basic draws with opposite-colored bishops
+template<>
+ScaleFactor Endgame<KBPPKB>::operator()(const Position& pos) const {
+
+  assert(verify_material(pos, strongSide, BishopValueMg, 2));
+  assert(verify_material(pos, weakSide,   BishopValueMg, 0));
+
+  Square strongBishop = pos.square<BISHOP>(strongSide);
+  Square weakBishop   = pos.square<BISHOP>(weakSide);
+
+  if (!opposite_colors(strongBishop, weakBishop))
+      return SCALE_FACTOR_NONE;
+
+  Square weakKing = pos.square<KING>(weakSide);
+  Square strongPawn1 = pos.squares<PAWN>(strongSide)[0];
+  Square strongPawn2 = pos.squares<PAWN>(strongSide)[1];
+  Square blockSq1, blockSq2;
+
+  if (relative_rank(strongSide, strongPawn1) > relative_rank(strongSide, strongPawn2))
+  {
+      blockSq1 = strongPawn1 + pawn_push(strongSide);
+      blockSq2 = make_square(file_of(strongPawn2), rank_of(strongPawn1));
+  }
+  else
+  {
+      blockSq1 = strongPawn2 + pawn_push(strongSide);
+      blockSq2 = make_square(file_of(strongPawn1), rank_of(strongPawn2));
+  }
+
+  switch (distance<File>(strongPawn1, strongPawn2))
+  {
+  case 0:
+    // Both pawns are on the same file. It's an easy draw if the defender firmly
+    // controls some square in the frontmost pawn's path.
+    if (   file_of(weakKing) == file_of(blockSq1)
+        && relative_rank(strongSide, weakKing) >= relative_rank(strongSide, blockSq1)
+        && opposite_colors(weakKing, strongBishop))
+        return SCALE_FACTOR_DRAW;
+    else
+        return SCALE_FACTOR_NONE;
+
+  case 1:
+    // Pawns on adjacent files. It's a draw if the defender firmly controls the
+    // square in front of the frontmost pawn's path, and the square diagonally
+    // behind this square on the file of the other pawn.
+    if (   weakKing == blockSq1
+        && opposite_colors(weakKing, strongBishop)
+        && (   weakBishop == blockSq2
+            || (attacks_bb<BISHOP>(blockSq2, pos.pieces()) & pos.pieces(weakSide, BISHOP))
+            || distance<Rank>(strongPawn1, strongPawn2) >= 2))
+        return SCALE_FACTOR_DRAW;
+
+    else if (   weakKing == blockSq2
+             && opposite_colors(weakKing, strongBishop)
+             && (   weakBishop == blockSq1
+                 || (attacks_bb<BISHOP>(blockSq1, pos.pieces()) & pos.pieces(weakSide, BISHOP))))
+        return SCALE_FACTOR_DRAW;
+    else
+        return SCALE_FACTOR_NONE;
+
+  default:
+    // The pawns are not on the same file or adjacent files. No scaling.
+    return SCALE_FACTOR_NONE;
+  }
+}
+
+
+/// KBP vs KN. There is a single rule: if the defending king is somewhere along
+/// the path of the pawn, and the square of the king is not of the same color as
+/// the stronger side's bishop, it's a draw.
+template<>
+ScaleFactor Endgame<KBPKN>::operator()(const Position& pos) const {
+
+  assert(verify_material(pos, strongSide, BishopValueMg, 1));
+  assert(verify_material(pos, weakSide, KnightValueMg, 0));
+
+  Square strongPawn = pos.square<PAWN>(strongSide);
+  Square strongBishop = pos.square<BISHOP>(strongSide);
+  Square weakKing = pos.square<KING>(weakSide);
+
+  if (   file_of(weakKing) == file_of(strongPawn)
+      && relative_rank(strongSide, strongPawn) < relative_rank(strongSide, weakKing)
+      && (   opposite_colors(weakKing, strongBishop)
+          || relative_rank(strongSide, weakKing) <= RANK_6))
+      return SCALE_FACTOR_DRAW;
+
+  return SCALE_FACTOR_NONE;
+}
+
+
+/// KP vs KP. This is done by removing the weakest side's pawn and probing the
+/// KP vs K bitbase: if the weakest side has a draw without the pawn, it probably
+/// has at least a draw with the pawn as well. The exception is when the stronger
+/// side's pawn is far advanced and not on a rook file; in this case it is often
+/// possible to win (e.g. 8/4k3/3p4/3P4/6K1/8/8/8 w - - 0 1).
+template<>
+ScaleFactor Endgame<KPKP>::operator()(const Position& pos) const {
+
+  assert(verify_material(pos, strongSide, VALUE_ZERO, 1));
+  assert(verify_material(pos, weakSide,   VALUE_ZERO, 1));
+
+  // Assume strongSide is white and the pawn is on files A-D
+  Square strongKing = normalize(pos, strongSide, pos.square<KING>(strongSide));
+  Square weakKing   = normalize(pos, strongSide, pos.square<KING>(weakSide));
+  Square strongPawn = normalize(pos, strongSide, pos.square<PAWN>(strongSide));
+
+  Color us = strongSide == pos.side_to_move() ? WHITE : BLACK;
+
+  // If the pawn has advanced to the fifth rank or further, and is not a
+  // rook pawn, it's too dangerous to assume that it's at least a draw.
+  if (rank_of(strongPawn) >= RANK_5 && file_of(strongPawn) != FILE_A)
+      return SCALE_FACTOR_NONE;
+
+  // Probe the KPK bitbase with the weakest side's pawn removed. If it's a draw,
+  // it's probably at least a draw even with the pawn.
+  return Bitbases::probe(strongKing, strongPawn, weakKing, us) ? SCALE_FACTOR_NONE : SCALE_FACTOR_DRAW;
+}
diff --git a/src/endgame.h b/src/endgame.h
new file mode 100644 (file)
index 0000000..1351d88
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef ENDGAME_H_INCLUDED
+#define ENDGAME_H_INCLUDED
+
+#include <memory>
+#include <string>
+#include <type_traits>
+#include <unordered_map>
+#include <utility>
+
+#include "position.h"
+#include "types.h"
+
+
+/// EndgameCode lists all supported endgame functions by corresponding codes
+
+enum EndgameCode {
+
+  EVALUATION_FUNCTIONS,
+  KNNK,  // KNN vs K
+  KNNKP, // KNN vs KP
+  KXK,   // Generic "mate lone king" eval
+  KBNK,  // KBN vs K
+  KPK,   // KP vs K
+  KRKP,  // KR vs KP
+  KRKB,  // KR vs KB
+  KRKN,  // KR vs KN
+  KQKP,  // KQ vs KP
+  KQKR,  // KQ vs KR
+
+  SCALING_FUNCTIONS,
+  KBPsK,   // KB and pawns vs K
+  KQKRPs,  // KQ vs KR and pawns
+  KRPKR,   // KRP vs KR
+  KRPKB,   // KRP vs KB
+  KRPPKRP, // KRPP vs KRP
+  KPsK,    // K and pawns vs K
+  KBPKB,   // KBP vs KB
+  KBPPKB,  // KBPP vs KB
+  KBPKN,   // KBP vs KN
+  KPKP     // KP vs KP
+};
+
+
+/// Endgame functions can be of two types depending on whether they return a
+/// Value or a ScaleFactor.
+
+template<EndgameCode E> using
+eg_type = typename std::conditional<(E < SCALING_FUNCTIONS), Value, ScaleFactor>::type;
+
+
+/// Base and derived functors for endgame evaluation and scaling functions
+
+template<typename T>
+struct EndgameBase {
+
+  explicit EndgameBase(Color c) : strongSide(c), weakSide(~c) {}
+  virtual ~EndgameBase() = default;
+  virtual T operator()(const Position&) const = 0;
+
+  const Color strongSide, weakSide;
+};
+
+
+template<EndgameCode E, typename T = eg_type<E>>
+struct Endgame : public EndgameBase<T> {
+
+  explicit Endgame(Color c) : EndgameBase<T>(c) {}
+  T operator()(const Position&) const override;
+};
+
+
+/// The Endgames namespace handles the pointers to endgame evaluation and scaling
+/// base objects in two std::map. We use polymorphism to invoke the actual
+/// endgame function by calling its virtual operator().
+
+namespace Endgames {
+
+  template<typename T> using Ptr = std::unique_ptr<EndgameBase<T>>;
+  template<typename T> using Map = std::unordered_map<Key, Ptr<T>>;
+
+  extern std::pair<Map<Value>, Map<ScaleFactor>> maps;
+
+  void init();
+
+  template<typename T>
+  Map<T>& map() {
+    return std::get<std::is_same<T, ScaleFactor>::value>(maps);
+  }
+
+  template<EndgameCode E, typename T = eg_type<E>>
+  void add(const std::string& code) {
+
+    StateInfo st;
+    map<T>()[Position().set(code, WHITE, &st).material_key()] = Ptr<T>(new Endgame<E>(WHITE));
+    map<T>()[Position().set(code, BLACK, &st).material_key()] = Ptr<T>(new Endgame<E>(BLACK));
+  }
+
+  template<typename T>
+  const EndgameBase<T>* probe(Key key) {
+    auto it = map<T>().find(key);
+    return it != map<T>().end() ? it->second.get() : nullptr;
+  }
+}
+
+#endif // #ifndef ENDGAME_H_INCLUDED
diff --git a/src/evaluate.cpp b/src/evaluate.cpp
new file mode 100644 (file)
index 0000000..09f3651
--- /dev/null
@@ -0,0 +1,1092 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <algorithm>
+#include <cassert>
+#include <cstdlib>
+#include <cstring>   // For std::memset
+#include <fstream>
+#include <iomanip>
+#include <sstream>
+#include <iostream>
+#include <streambuf>
+#include <vector>
+
+#include "bitboard.h"
+#include "evaluate.h"
+#include "material.h"
+#include "misc.h"
+#include "pawns.h"
+#include "thread.h"
+#include "uci.h"
+#include "incbin/incbin.h"
+
+
+// Macro to embed the default NNUE file data in the engine binary (using incbin.h, by Dale Weiler).
+// This macro invocation will declare the following three variables
+//     const unsigned char        gEmbeddedNNUEData[];  // a pointer to the embedded data
+//     const unsigned char *const gEmbeddedNNUEEnd;     // a marker to the end
+//     const unsigned int         gEmbeddedNNUESize;    // the size of the embedded file
+// Note that this does not work in Microsof Visual Studio.
+#if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF)
+  INCBIN(EmbeddedNNUE, EvalFileDefaultName);
+#else
+  const unsigned char        gEmbeddedNNUEData[1] = {0x0};
+  const unsigned char *const gEmbeddedNNUEEnd = &gEmbeddedNNUEData[1];
+  const unsigned int         gEmbeddedNNUESize = 1;
+#endif
+
+
+using namespace std;
+using namespace Eval::NNUE;
+
+namespace Eval {
+
+  bool useNNUE;
+  string eval_file_loaded = "None";
+
+  /// init_NNUE() tries to load a nnue network at startup time, or when the engine
+  /// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue"
+  /// The name of the nnue network is always retrieved from the EvalFile option.
+  /// We search the given network in three locations: internally (the default
+  /// network may be embedded in the binary), in the active working directory and
+  /// in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY
+  /// variable to have the engine search in a special directory in their distro.
+
+  void init_NNUE() {
+
+    useNNUE = Options["Use NNUE"];
+    if (!useNNUE)
+        return;
+
+    string eval_file = string(Options["EvalFile"]);
+
+    #if defined(DEFAULT_NNUE_DIRECTORY)
+    #define stringify2(x) #x
+    #define stringify(x) stringify2(x)
+    vector<string> dirs = { "<internal>" , "" , CommandLine::binaryDirectory , stringify(DEFAULT_NNUE_DIRECTORY) };
+    #else
+    vector<string> dirs = { "<internal>" , "" , CommandLine::binaryDirectory };
+    #endif
+
+    for (string directory : dirs)
+        if (eval_file_loaded != eval_file)
+        {
+            if (directory != "<internal>")
+            {
+                ifstream stream(directory + eval_file, ios::binary);
+                if (load_eval(eval_file, stream))
+                    eval_file_loaded = eval_file;
+            }
+
+            if (directory == "<internal>" && eval_file == EvalFileDefaultName)
+            {
+                // C++ way to prepare a buffer for a memory stream
+                class MemoryBuffer : public basic_streambuf<char> {
+                    public: MemoryBuffer(char* p, size_t n) { setg(p, p, p + n); setp(p, p + n); }
+                };
+
+                MemoryBuffer buffer(const_cast<char*>(reinterpret_cast<const char*>(gEmbeddedNNUEData)),
+                                    size_t(gEmbeddedNNUESize));
+
+                istream stream(&buffer);
+                if (load_eval(eval_file, stream))
+                    eval_file_loaded = eval_file;
+            }
+        }
+  }
+
+  /// verify_NNUE() verifies that the last net used was loaded successfully
+  void verify_NNUE() {
+
+    string eval_file = string(Options["EvalFile"]);
+
+    if (useNNUE && eval_file_loaded != eval_file)
+    {
+        UCI::OptionsMap defaults;
+        UCI::init(defaults);
+
+        string msg1 = "If the UCI option \"Use NNUE\" is set to true, network evaluation parameters compatible with the engine must be available.";
+        string msg2 = "The option is set to true, but the network file " + eval_file + " was not loaded successfully.";
+        string msg3 = "The UCI option EvalFile might need to specify the full path, including the directory name, to the network file.";
+        string msg4 = "The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/" + string(defaults["EvalFile"]);
+        string msg5 = "The engine will be terminated now.";
+
+        sync_cout << "info string ERROR: " << msg1 << sync_endl;
+        sync_cout << "info string ERROR: " << msg2 << sync_endl;
+        sync_cout << "info string ERROR: " << msg3 << sync_endl;
+        sync_cout << "info string ERROR: " << msg4 << sync_endl;
+        sync_cout << "info string ERROR: " << msg5 << sync_endl;
+
+        exit(EXIT_FAILURE);
+    }
+
+    if (useNNUE)
+        sync_cout << "info string NNUE evaluation using " << eval_file << " enabled" << sync_endl;
+    else
+        sync_cout << "info string classical evaluation enabled" << sync_endl;
+  }
+}
+
+namespace Trace {
+
+  enum Tracing { NO_TRACE, TRACE };
+
+  enum Term { // The first 8 entries are reserved for PieceType
+    MATERIAL = 8, IMBALANCE, MOBILITY, THREAT, PASSED, SPACE, WINNABLE, TOTAL, TERM_NB
+  };
+
+  Score scores[TERM_NB][COLOR_NB];
+
+  double to_cp(Value v) { return double(v) / PawnValueEg; }
+
+  void add(int idx, Color c, Score s) {
+    scores[idx][c] = s;
+  }
+
+  void add(int idx, Score w, Score b = SCORE_ZERO) {
+    scores[idx][WHITE] = w;
+    scores[idx][BLACK] = b;
+  }
+
+  std::ostream& operator<<(std::ostream& os, Score s) {
+    os << std::setw(5) << to_cp(mg_value(s)) << " "
+       << std::setw(5) << to_cp(eg_value(s));
+    return os;
+  }
+
+  std::ostream& operator<<(std::ostream& os, Term t) {
+
+    if (t == MATERIAL || t == IMBALANCE || t == WINNABLE || t == TOTAL)
+        os << " ----  ----"    << " | " << " ----  ----";
+    else
+        os << scores[t][WHITE] << " | " << scores[t][BLACK];
+
+    os << " | " << scores[t][WHITE] - scores[t][BLACK] << "\n";
+    return os;
+  }
+}
+
+using namespace Trace;
+
+namespace {
+
+  // Threshold for lazy and space evaluation
+  constexpr Value LazyThreshold1 =  Value(1400);
+  constexpr Value LazyThreshold2 =  Value(1300);
+  constexpr Value SpaceThreshold = Value(12222);
+  constexpr Value NNUEThreshold1 =   Value(550);
+  constexpr Value NNUEThreshold2 =   Value(150);
+
+  // KingAttackWeights[PieceType] contains king attack weights by piece type
+  constexpr int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 81, 52, 44, 10 };
+
+  // SafeCheck[PieceType][single/multiple] contains safe check bonus by piece type,
+  // higher if multiple safe checks are possible for that piece type.
+  constexpr int SafeCheck[][2] = {
+      {}, {}, {792, 1283}, {645, 967}, {1084, 1897}, {772, 1119}
+  };
+
+#define S(mg, eg) make_score(mg, eg)
+
+  // MobilityBonus[PieceType-2][attacked] contains bonuses for middle and end game,
+  // indexed by piece type and number of attacked squares in the mobility area.
+  constexpr Score MobilityBonus[][32] = {
+    { S(-62,-81), S(-53,-56), S(-12,-31), S( -4,-16), S(  3,  5), S( 13, 11), // Knight
+      S( 22, 17), S( 28, 20), S( 33, 25) },
+    { S(-48,-59), S(-20,-23), S( 16, -3), S( 26, 13), S( 38, 24), S( 51, 42), // Bishop
+      S( 55, 54), S( 63, 57), S( 63, 65), S( 68, 73), S( 81, 78), S( 81, 86),
+      S( 91, 88), S( 98, 97) },
+    { S(-60,-78), S(-20,-17), S(  2, 23), S(  3, 39), S(  3, 70), S( 11, 99), // Rook
+      S( 22,103), S( 31,121), S( 40,134), S( 40,139), S( 41,158), S( 48,164),
+      S( 57,168), S( 57,169), S( 62,172) },
+    { S(-30,-48), S(-12,-30), S( -8, -7), S( -9, 19), S( 20, 40), S( 23, 55), // Queen
+      S( 23, 59), S( 35, 75), S( 38, 78), S( 53, 96), S( 64, 96), S( 65,100),
+      S( 65,121), S( 66,127), S( 67,131), S( 67,133), S( 72,136), S( 72,141),
+      S( 77,147), S( 79,150), S( 93,151), S(108,168), S(108,168), S(108,171),
+      S(110,182), S(114,182), S(114,192), S(116,219) }
+  };
+
+  // KingProtector[knight/bishop] contains penalty for each distance unit to own king
+  constexpr Score KingProtector[] = { S(8, 9), S(6, 9) };
+
+  // Outpost[knight/bishop] contains bonuses for each knight or bishop occupying a
+  // pawn protected square on rank 4 to 6 which is also safe from a pawn attack.
+  constexpr Score Outpost[] = { S(56, 34), S(31, 23) };
+
+  // PassedRank[Rank] contains a bonus according to the rank of a passed pawn
+  constexpr Score PassedRank[RANK_NB] = {
+    S(0, 0), S(9, 28), S(15, 31), S(17, 39), S(64, 70), S(171, 177), S(277, 260)
+  };
+
+  // RookOnFile[semiopen/open] contains bonuses for each rook when there is
+  // no (friendly) pawn on the rook file.
+  constexpr Score RookOnFile[] = { S(19, 7), S(48, 27) };
+
+  // ThreatByMinor/ByRook[attacked PieceType] contains bonuses according to
+  // which piece type attacks which one. Attacks on lesser pieces which are
+  // pawn-defended are not considered.
+  constexpr Score ThreatByMinor[PIECE_TYPE_NB] = {
+    S(0, 0), S(5, 32), S(55, 41), S(77, 56), S(89, 119), S(79, 162)
+  };
+
+  constexpr Score ThreatByRook[PIECE_TYPE_NB] = {
+    S(0, 0), S(3, 44), S(37, 68), S(42, 60), S(0, 39), S(58, 43)
+  };
+
+  // Assorted bonuses and penalties
+  constexpr Score BadOutpost          = S( -7, 36);
+  constexpr Score BishopOnKingRing    = S( 24,  0);
+  constexpr Score BishopPawns         = S(  3,  7);
+  constexpr Score BishopXRayPawns     = S(  4,  5);
+  constexpr Score CorneredBishop      = S( 50, 50);
+  constexpr Score FlankAttacks        = S(  8,  0);
+  constexpr Score Hanging             = S( 69, 36);
+  constexpr Score KnightOnQueen       = S( 16, 11);
+  constexpr Score LongDiagonalBishop  = S( 45,  0);
+  constexpr Score MinorBehindPawn     = S( 18,  3);
+  constexpr Score PassedFile          = S( 11,  8);
+  constexpr Score PawnlessFlank       = S( 17, 95);
+  constexpr Score ReachableOutpost    = S( 31, 22);
+  constexpr Score RestrictedPiece     = S(  7,  7);
+  constexpr Score RookOnKingRing      = S( 16,  0);
+  constexpr Score RookOnQueenFile     = S(  6, 11);
+  constexpr Score SliderOnQueen       = S( 60, 18);
+  constexpr Score ThreatByKing        = S( 24, 89);
+  constexpr Score ThreatByPawnPush    = S( 48, 39);
+  constexpr Score ThreatBySafePawn    = S(173, 94);
+  constexpr Score TrappedRook         = S( 55, 13);
+  constexpr Score WeakQueenProtection = S( 14,  0);
+  constexpr Score WeakQueen           = S( 56, 15);
+
+
+#undef S
+
+  // Evaluation class computes and stores attacks tables and other working data
+  template<Tracing T>
+  class Evaluation {
+
+  public:
+    Evaluation() = delete;
+    explicit Evaluation(const Position& p) : pos(p) {}
+    Evaluation& operator=(const Evaluation&) = delete;
+    Value value();
+
+  private:
+    template<Color Us> void initialize();
+    template<Color Us, PieceType Pt> Score pieces();
+    template<Color Us> Score king() const;
+    template<Color Us> Score threats() const;
+    template<Color Us> Score passed() const;
+    template<Color Us> Score space() const;
+    Value winnable(Score score) const;
+
+    const Position& pos;
+    Material::Entry* me;
+    Pawns::Entry* pe;
+    Bitboard mobilityArea[COLOR_NB];
+    Score mobility[COLOR_NB] = { SCORE_ZERO, SCORE_ZERO };
+
+    // attackedBy[color][piece type] is a bitboard representing all squares
+    // attacked by a given color and piece type. Special "piece types" which
+    // is also calculated is ALL_PIECES.
+    Bitboard attackedBy[COLOR_NB][PIECE_TYPE_NB];
+
+    // attackedBy2[color] are the squares attacked by at least 2 units of a given
+    // color, including x-rays. But diagonal x-rays through pawns are not computed.
+    Bitboard attackedBy2[COLOR_NB];
+
+    // kingRing[color] are the squares adjacent to the king plus some other
+    // very near squares, depending on king position.
+    Bitboard kingRing[COLOR_NB];
+
+    // kingAttackersCount[color] is the number of pieces of the given color
+    // which attack a square in the kingRing of the enemy king.
+    int kingAttackersCount[COLOR_NB];
+
+    // kingAttackersWeight[color] is the sum of the "weights" of the pieces of
+    // the given color which attack a square in the kingRing of the enemy king.
+    // The weights of the individual piece types are given by the elements in
+    // the KingAttackWeights array.
+    int kingAttackersWeight[COLOR_NB];
+
+    // kingAttacksCount[color] is the number of attacks by the given color to
+    // squares directly adjacent to the enemy king. Pieces which attack more
+    // than one square are counted multiple times. For instance, if there is
+    // a white knight on g5 and black's king is on g8, this white knight adds 2
+    // to kingAttacksCount[WHITE].
+    int kingAttacksCount[COLOR_NB];
+  };
+
+
+  // Evaluation::initialize() computes king and pawn attacks, and the king ring
+  // bitboard for a given color. This is done at the beginning of the evaluation.
+
+  template<Tracing T> template<Color Us>
+  void Evaluation<T>::initialize() {
+
+    constexpr Color     Them = ~Us;
+    constexpr Direction Up   = pawn_push(Us);
+    constexpr Direction Down = -Up;
+    constexpr Bitboard LowRanks = (Us == WHITE ? Rank2BB | Rank3BB : Rank7BB | Rank6BB);
+
+    const Square ksq = pos.square<KING>(Us);
+
+    Bitboard dblAttackByPawn = pawn_double_attacks_bb<Us>(pos.pieces(Us, PAWN));
+
+    // Find our pawns that are blocked or on the first two ranks
+    Bitboard b = pos.pieces(Us, PAWN) & (shift<Down>(pos.pieces()) | LowRanks);
+
+    // Squares occupied by those pawns, by our king or queen, by blockers to attacks on our king
+    // or controlled by enemy pawns are excluded from the mobility area.
+    mobilityArea[Us] = ~(b | pos.pieces(Us, KING, QUEEN) | pos.blockers_for_king(Us) | pe->pawn_attacks(Them));
+
+    // Initialize attackedBy[] for king and pawns
+    attackedBy[Us][KING] = attacks_bb<KING>(ksq);
+    attackedBy[Us][PAWN] = pe->pawn_attacks(Us);
+    attackedBy[Us][ALL_PIECES] = attackedBy[Us][KING] | attackedBy[Us][PAWN];
+    attackedBy2[Us] = dblAttackByPawn | (attackedBy[Us][KING] & attackedBy[Us][PAWN]);
+
+    // Init our king safety tables
+    Square s = make_square(std::clamp(file_of(ksq), FILE_B, FILE_G),
+                           std::clamp(rank_of(ksq), RANK_2, RANK_7));
+    kingRing[Us] = attacks_bb<KING>(s) | s;
+
+    kingAttackersCount[Them] = popcount(kingRing[Us] & pe->pawn_attacks(Them));
+    kingAttacksCount[Them] = kingAttackersWeight[Them] = 0;
+
+    // Remove from kingRing[] the squares defended by two pawns
+    kingRing[Us] &= ~dblAttackByPawn;
+  }
+
+
+  // Evaluation::pieces() scores pieces of a given color and type
+
+  template<Tracing T> template<Color Us, PieceType Pt>
+  Score Evaluation<T>::pieces() {
+
+    constexpr Color     Them = ~Us;
+    constexpr Direction Down = -pawn_push(Us);
+    constexpr Bitboard OutpostRanks = (Us == WHITE ? Rank4BB | Rank5BB | Rank6BB
+                                                   : Rank5BB | Rank4BB | Rank3BB);
+    const Square* pl = pos.squares<Pt>(Us);
+
+    Bitboard b, bb;
+    Score score = SCORE_ZERO;
+
+    attackedBy[Us][Pt] = 0;
+
+    for (Square s = *pl; s != SQ_NONE; s = *++pl)
+    {
+        // Find attacked squares, including x-ray attacks for bishops and rooks
+        b = Pt == BISHOP ? attacks_bb<BISHOP>(s, pos.pieces() ^ pos.pieces(QUEEN))
+          : Pt ==   ROOK ? attacks_bb<  ROOK>(s, pos.pieces() ^ pos.pieces(QUEEN) ^ pos.pieces(Us, ROOK))
+                         : attacks_bb<Pt>(s, pos.pieces());
+
+        if (pos.blockers_for_king(Us) & s)
+            b &= line_bb(pos.square<KING>(Us), s);
+
+        attackedBy2[Us] |= attackedBy[Us][ALL_PIECES] & b;
+        attackedBy[Us][Pt] |= b;
+        attackedBy[Us][ALL_PIECES] |= b;
+
+        if (b & kingRing[Them])
+        {
+            kingAttackersCount[Us]++;
+            kingAttackersWeight[Us] += KingAttackWeights[Pt];
+            kingAttacksCount[Us] += popcount(b & attackedBy[Them][KING]);
+        }
+
+        else if (Pt == ROOK && (file_bb(s) & kingRing[Them]))
+            score += RookOnKingRing;
+
+        else if (Pt == BISHOP && (attacks_bb<BISHOP>(s, pos.pieces(PAWN)) & kingRing[Them]))
+            score += BishopOnKingRing;
+
+        int mob = popcount(b & mobilityArea[Us]);
+
+        mobility[Us] += MobilityBonus[Pt - 2][mob];
+
+        if (Pt == BISHOP || Pt == KNIGHT)
+        {
+            // Bonus if the piece is on an outpost square or can reach one
+            // Reduced bonus for knights (BadOutpost) if few relevant targets
+            bb = OutpostRanks & (attackedBy[Us][PAWN] | shift<Down>(pos.pieces(PAWN)))
+                              & ~pe->pawn_attacks_span(Them);
+            Bitboard targets = pos.pieces(Them) & ~pos.pieces(PAWN);
+
+            if (   Pt == KNIGHT
+                && bb & s & ~CenterFiles // on a side outpost
+                && !(b & targets)        // no relevant attacks
+                && (!more_than_one(targets & (s & QueenSide ? QueenSide : KingSide))))
+                score += BadOutpost;
+            else if (bb & s)
+                score += Outpost[Pt == BISHOP];
+            else if (Pt == KNIGHT && bb & b & ~pos.pieces(Us))
+                score += ReachableOutpost;
+
+            // Bonus for a knight or bishop shielded by pawn
+            if (shift<Down>(pos.pieces(PAWN)) & s)
+                score += MinorBehindPawn;
+
+            // Penalty if the piece is far from the king
+            score -= KingProtector[Pt == BISHOP] * distance(pos.square<KING>(Us), s);
+
+            if (Pt == BISHOP)
+            {
+                // Penalty according to the number of our pawns on the same color square as the
+                // bishop, bigger when the center files are blocked with pawns and smaller
+                // when the bishop is outside the pawn chain.
+                Bitboard blocked = pos.pieces(Us, PAWN) & shift<Down>(pos.pieces());
+
+                score -= BishopPawns * pos.pawns_on_same_color_squares(Us, s)
+                                     * (!(attackedBy[Us][PAWN] & s) + popcount(blocked & CenterFiles));
+
+                // Penalty for all enemy pawns x-rayed
+                score -= BishopXRayPawns * popcount(attacks_bb<BISHOP>(s) & pos.pieces(Them, PAWN));
+
+                // Bonus for bishop on a long diagonal which can "see" both center squares
+                if (more_than_one(attacks_bb<BISHOP>(s, pos.pieces(PAWN)) & Center))
+                    score += LongDiagonalBishop;
+
+                // An important Chess960 pattern: a cornered bishop blocked by a friendly
+                // pawn diagonally in front of it is a very serious problem, especially
+                // when that pawn is also blocked.
+                if (   pos.is_chess960()
+                    && (s == relative_square(Us, SQ_A1) || s == relative_square(Us, SQ_H1)))
+                {
+                    Direction d = pawn_push(Us) + (file_of(s) == FILE_A ? EAST : WEST);
+                    if (pos.piece_on(s + d) == make_piece(Us, PAWN))
+                        score -= !pos.empty(s + d + pawn_push(Us))                ? CorneredBishop * 4
+                                : pos.piece_on(s + d + d) == make_piece(Us, PAWN) ? CorneredBishop * 2
+                                                                                  : CorneredBishop;
+                }
+            }
+        }
+
+        if (Pt == ROOK)
+        {
+            // Bonus for rook on the same file as a queen
+            if (file_bb(s) & pos.pieces(QUEEN))
+                score += RookOnQueenFile;
+
+            // Bonus for rook on an open or semi-open file
+            if (pos.is_on_semiopen_file(Us, s))
+                score += RookOnFile[pos.is_on_semiopen_file(Them, s)];
+
+            // Penalty when trapped by the king, even more if the king cannot castle
+            else if (mob <= 3)
+            {
+                File kf = file_of(pos.square<KING>(Us));
+                if ((kf < FILE_E) == (file_of(s) < kf))
+                    score -= TrappedRook * (1 + !pos.castling_rights(Us));
+            }
+        }
+
+        if (Pt == QUEEN)
+        {
+            // Penalty if any relative pin or discovered attack against the queen
+            Bitboard queenPinners;
+            if (pos.slider_blockers(pos.pieces(Them, ROOK, BISHOP), s, queenPinners))
+                score -= WeakQueen;
+        }
+    }
+    if (T)
+        Trace::add(Pt, Us, score);
+
+    return score;
+  }
+
+
+  // Evaluation::king() assigns bonuses and penalties to a king of a given color
+
+  template<Tracing T> template<Color Us>
+  Score Evaluation<T>::king() const {
+
+    constexpr Color    Them = ~Us;
+    constexpr Bitboard Camp = (Us == WHITE ? AllSquares ^ Rank6BB ^ Rank7BB ^ Rank8BB
+                                           : AllSquares ^ Rank1BB ^ Rank2BB ^ Rank3BB);
+
+    Bitboard weak, b1, b2, b3, safe, unsafeChecks = 0;
+    Bitboard rookChecks, queenChecks, bishopChecks, knightChecks;
+    int kingDanger = 0;
+    const Square ksq = pos.square<KING>(Us);
+
+    // Init the score with king shelter and enemy pawns storm
+    Score score = pe->king_safety<Us>(pos);
+
+    // Attacked squares defended at most once by our queen or king
+    weak =  attackedBy[Them][ALL_PIECES]
+          & ~attackedBy2[Us]
+          & (~attackedBy[Us][ALL_PIECES] | attackedBy[Us][KING] | attackedBy[Us][QUEEN]);
+
+    // Analyse the safe enemy's checks which are possible on next move
+    safe  = ~pos.pieces(Them);
+    safe &= ~attackedBy[Us][ALL_PIECES] | (weak & attackedBy2[Them]);
+
+    b1 = attacks_bb<ROOK  >(ksq, pos.pieces() ^ pos.pieces(Us, QUEEN));
+    b2 = attacks_bb<BISHOP>(ksq, pos.pieces() ^ pos.pieces(Us, QUEEN));
+
+    // Enemy rooks checks
+    rookChecks = b1 & attackedBy[Them][ROOK] & safe;
+    if (rookChecks)
+        kingDanger += SafeCheck[ROOK][more_than_one(rookChecks)];
+    else
+        unsafeChecks |= b1 & attackedBy[Them][ROOK];
+
+    // Enemy queen safe checks: count them only if the checks are from squares from
+    // which opponent cannot give a rook check, because rook checks are more valuable.
+    queenChecks =  (b1 | b2) & attackedBy[Them][QUEEN] & safe
+                 & ~(attackedBy[Us][QUEEN] | rookChecks);
+    if (queenChecks)
+        kingDanger += SafeCheck[QUEEN][more_than_one(queenChecks)];
+
+    // Enemy bishops checks: count them only if they are from squares from which
+    // opponent cannot give a queen check, because queen checks are more valuable.
+    bishopChecks =  b2 & attackedBy[Them][BISHOP] & safe
+                  & ~queenChecks;
+    if (bishopChecks)
+        kingDanger += SafeCheck[BISHOP][more_than_one(bishopChecks)];
+
+    else
+        unsafeChecks |= b2 & attackedBy[Them][BISHOP];
+
+    // Enemy knights checks
+    knightChecks = attacks_bb<KNIGHT>(ksq) & attackedBy[Them][KNIGHT];
+    if (knightChecks & safe)
+        kingDanger += SafeCheck[KNIGHT][more_than_one(knightChecks & safe)];
+    else
+        unsafeChecks |= knightChecks;
+
+    // Find the squares that opponent attacks in our king flank, the squares
+    // which they attack twice in that flank, and the squares that we defend.
+    b1 = attackedBy[Them][ALL_PIECES] & KingFlank[file_of(ksq)] & Camp;
+    b2 = b1 & attackedBy2[Them];
+    b3 = attackedBy[Us][ALL_PIECES] & KingFlank[file_of(ksq)] & Camp;
+
+    int kingFlankAttack  = popcount(b1) + popcount(b2);
+    int kingFlankDefense = popcount(b3);
+
+    kingDanger +=        kingAttackersCount[Them] * kingAttackersWeight[Them]
+                 + 185 * popcount(kingRing[Us] & weak)
+                 + 148 * popcount(unsafeChecks)
+                 +  98 * popcount(pos.blockers_for_king(Us))
+                 +  69 * kingAttacksCount[Them]
+                 +   3 * kingFlankAttack * kingFlankAttack / 8
+                 +       mg_value(mobility[Them] - mobility[Us])
+                 - 873 * !pos.count<QUEEN>(Them)
+                 - 100 * bool(attackedBy[Us][KNIGHT] & attackedBy[Us][KING])
+                 -   6 * mg_value(score) / 8
+                 -   4 * kingFlankDefense
+                 +  37;
+
+    // Transform the kingDanger units into a Score, and subtract it from the evaluation
+    if (kingDanger > 100)
+        score -= make_score(kingDanger * kingDanger / 4096, kingDanger / 16);
+
+    // Penalty when our king is on a pawnless flank
+    if (!(pos.pieces(PAWN) & KingFlank[file_of(ksq)]))
+        score -= PawnlessFlank;
+
+    // Penalty if king flank is under attack, potentially moving toward the king
+    score -= FlankAttacks * kingFlankAttack;
+
+    if (T)
+        Trace::add(KING, Us, score);
+
+    return score;
+  }
+
+
+  // Evaluation::threats() assigns bonuses according to the types of the
+  // attacking and the attacked pieces.
+
+  template<Tracing T> template<Color Us>
+  Score Evaluation<T>::threats() const {
+
+    constexpr Color     Them     = ~Us;
+    constexpr Direction Up       = pawn_push(Us);
+    constexpr Bitboard  TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB);
+
+    Bitboard b, weak, defended, nonPawnEnemies, stronglyProtected, safe;
+    Score score = SCORE_ZERO;
+
+    // Non-pawn enemies
+    nonPawnEnemies = pos.pieces(Them) & ~pos.pieces(PAWN);
+
+    // Squares strongly protected by the enemy, either because they defend the
+    // square with a pawn, or because they defend the square twice and we don't.
+    stronglyProtected =  attackedBy[Them][PAWN]
+                       | (attackedBy2[Them] & ~attackedBy2[Us]);
+
+    // Non-pawn enemies, strongly protected
+    defended = nonPawnEnemies & stronglyProtected;
+
+    // Enemies not strongly protected and under our attack
+    weak = pos.pieces(Them) & ~stronglyProtected & attackedBy[Us][ALL_PIECES];
+
+    // Bonus according to the kind of attacking pieces
+    if (defended | weak)
+    {
+        b = (defended | weak) & (attackedBy[Us][KNIGHT] | attackedBy[Us][BISHOP]);
+        while (b)
+            score += ThreatByMinor[type_of(pos.piece_on(pop_lsb(&b)))];
+
+        b = weak & attackedBy[Us][ROOK];
+        while (b)
+            score += ThreatByRook[type_of(pos.piece_on(pop_lsb(&b)))];
+
+        if (weak & attackedBy[Us][KING])
+            score += ThreatByKing;
+
+        b =  ~attackedBy[Them][ALL_PIECES]
+           | (nonPawnEnemies & attackedBy2[Us]);
+        score += Hanging * popcount(weak & b);
+
+        // Additional bonus if weak piece is only protected by a queen
+        score += WeakQueenProtection * popcount(weak & attackedBy[Them][QUEEN]);
+    }
+
+    // Bonus for restricting their piece moves
+    b =   attackedBy[Them][ALL_PIECES]
+       & ~stronglyProtected
+       &  attackedBy[Us][ALL_PIECES];
+    score += RestrictedPiece * popcount(b);
+
+    // Protected or unattacked squares
+    safe = ~attackedBy[Them][ALL_PIECES] | attackedBy[Us][ALL_PIECES];
+
+    // Bonus for attacking enemy pieces with our relatively safe pawns
+    b = pos.pieces(Us, PAWN) & safe;
+    b = pawn_attacks_bb<Us>(b) & nonPawnEnemies;
+    score += ThreatBySafePawn * popcount(b);
+
+    // Find squares where our pawns can push on the next move
+    b  = shift<Up>(pos.pieces(Us, PAWN)) & ~pos.pieces();
+    b |= shift<Up>(b & TRank3BB) & ~pos.pieces();
+
+    // Keep only the squares which are relatively safe
+    b &= ~attackedBy[Them][PAWN] & safe;
+
+    // Bonus for safe pawn threats on the next move
+    b = pawn_attacks_bb<Us>(b) & nonPawnEnemies;
+    score += ThreatByPawnPush * popcount(b);
+
+    // Bonus for threats on the next moves against enemy queen
+    if (pos.count<QUEEN>(Them) == 1)
+    {
+        bool queenImbalance = pos.count<QUEEN>() == 1;
+
+        Square s = pos.square<QUEEN>(Them);
+        safe =   mobilityArea[Us]
+              & ~pos.pieces(Us, PAWN)
+              & ~stronglyProtected;
+
+        b = attackedBy[Us][KNIGHT] & attacks_bb<KNIGHT>(s);
+
+        score += KnightOnQueen * popcount(b & safe) * (1 + queenImbalance);
+
+        b =  (attackedBy[Us][BISHOP] & attacks_bb<BISHOP>(s, pos.pieces()))
+           | (attackedBy[Us][ROOK  ] & attacks_bb<ROOK  >(s, pos.pieces()));
+
+        score += SliderOnQueen * popcount(b & safe & attackedBy2[Us]) * (1 + queenImbalance);
+    }
+
+    if (T)
+        Trace::add(THREAT, Us, score);
+
+    return score;
+  }
+
+  // Evaluation::passed() evaluates the passed pawns and candidate passed
+  // pawns of the given color.
+
+  template<Tracing T> template<Color Us>
+  Score Evaluation<T>::passed() const {
+
+    constexpr Color     Them = ~Us;
+    constexpr Direction Up   = pawn_push(Us);
+    constexpr Direction Down = -Up;
+
+    auto king_proximity = [&](Color c, Square s) {
+      return std::min(distance(pos.square<KING>(c), s), 5);
+    };
+
+    Bitboard b, bb, squaresToQueen, unsafeSquares, blockedPassers, helpers;
+    Score score = SCORE_ZERO;
+
+    b = pe->passed_pawns(Us);
+
+    blockedPassers = b & shift<Down>(pos.pieces(Them, PAWN));
+    if (blockedPassers)
+    {
+        helpers =  shift<Up>(pos.pieces(Us, PAWN))
+                 & ~pos.pieces(Them)
+                 & (~attackedBy2[Them] | attackedBy[Us][ALL_PIECES]);
+
+        // Remove blocked candidate passers that don't have help to pass
+        b &=  ~blockedPassers
+            | shift<WEST>(helpers)
+            | shift<EAST>(helpers);
+    }
+
+    while (b)
+    {
+        Square s = pop_lsb(&b);
+
+        assert(!(pos.pieces(Them, PAWN) & forward_file_bb(Us, s + Up)));
+
+        int r = relative_rank(Us, s);
+
+        Score bonus = PassedRank[r];
+
+        if (r > RANK_3)
+        {
+            int w = 5 * r - 13;
+            Square blockSq = s + Up;
+
+            // Adjust bonus based on the king's proximity
+            bonus += make_score(0, (  king_proximity(Them, blockSq) * 19 / 4
+                                    - king_proximity(Us,   blockSq) *  2) * w);
+
+            // If blockSq is not the queening square then consider also a second push
+            if (r != RANK_7)
+                bonus -= make_score(0, king_proximity(Us, blockSq + Up) * w);
+
+            // If the pawn is free to advance, then increase the bonus
+            if (pos.empty(blockSq))
+            {
+                squaresToQueen = forward_file_bb(Us, s);
+                unsafeSquares = passed_pawn_span(Us, s);
+
+                bb = forward_file_bb(Them, s) & pos.pieces(ROOK, QUEEN);
+
+                if (!(pos.pieces(Them) & bb))
+                    unsafeSquares &= attackedBy[Them][ALL_PIECES];
+
+                // If there are no enemy attacks on passed pawn span, assign a big bonus.
+                // Otherwise assign a smaller bonus if the path to queen is not attacked
+                // and even smaller bonus if it is attacked but block square is not.
+                int k = !unsafeSquares                    ? 35 :
+                        !(unsafeSquares & squaresToQueen) ? 20 :
+                        !(unsafeSquares & blockSq)        ?  9 :
+                                                             0 ;
+
+                // Assign a larger bonus if the block square is defended
+                if ((pos.pieces(Us) & bb) || (attackedBy[Us][ALL_PIECES] & blockSq))
+                    k += 5;
+
+                bonus += make_score(k * w, k * w);
+            }
+        } // r > RANK_3
+
+        score += bonus - PassedFile * edge_distance(file_of(s));
+    }
+
+    if (T)
+        Trace::add(PASSED, Us, score);
+
+    return score;
+  }
+
+
+  // Evaluation::space() computes a space evaluation for a given side, aiming to improve game
+  // play in the opening. It is based on the number of safe squares on the four central files
+  // on ranks 2 to 4. Completely safe squares behind a friendly pawn are counted twice.
+  // Finally, the space bonus is multiplied by a weight which decreases according to occupancy.
+
+  template<Tracing T> template<Color Us>
+  Score Evaluation<T>::space() const {
+
+    // Early exit if, for example, both queens or 6 minor pieces have been exchanged
+    if (pos.non_pawn_material() < SpaceThreshold)
+        return SCORE_ZERO;
+
+    constexpr Color Them     = ~Us;
+    constexpr Direction Down = -pawn_push(Us);
+    constexpr Bitboard SpaceMask =
+      Us == WHITE ? CenterFiles & (Rank2BB | Rank3BB | Rank4BB)
+                  : CenterFiles & (Rank7BB | Rank6BB | Rank5BB);
+
+    // Find the available squares for our pieces inside the area defined by SpaceMask
+    Bitboard safe =   SpaceMask
+                   & ~pos.pieces(Us, PAWN)
+                   & ~attackedBy[Them][PAWN];
+
+    // Find all squares which are at most three squares behind some friendly pawn
+    Bitboard behind = pos.pieces(Us, PAWN);
+    behind |= shift<Down>(behind);
+    behind |= shift<Down+Down>(behind);
+
+    int bonus = popcount(safe) + popcount(behind & safe & ~attackedBy[Them][ALL_PIECES]);
+    int weight = pos.count<ALL_PIECES>(Us) - 3 + std::min(pe->blocked_count(), 9);
+    Score score = make_score(bonus * weight * weight / 16, 0);
+
+    if (T)
+        Trace::add(SPACE, Us, score);
+
+    return score;
+  }
+
+
+  // Evaluation::winnable() adjusts the midgame and endgame score components, based on
+  // the known attacking/defending status of the players. The final value is derived
+  // by interpolation from the midgame and endgame values.
+
+  template<Tracing T>
+  Value Evaluation<T>::winnable(Score score) const {
+
+    int outflanking =  distance<File>(pos.square<KING>(WHITE), pos.square<KING>(BLACK))
+                     - distance<Rank>(pos.square<KING>(WHITE), pos.square<KING>(BLACK));
+
+    bool pawnsOnBothFlanks =   (pos.pieces(PAWN) & QueenSide)
+                            && (pos.pieces(PAWN) & KingSide);
+
+    bool almostUnwinnable =   outflanking < 0
+                           && !pawnsOnBothFlanks;
+
+    bool infiltration =   rank_of(pos.square<KING>(WHITE)) > RANK_4
+                       || rank_of(pos.square<KING>(BLACK)) < RANK_5;
+
+    // Compute the initiative bonus for the attacking side
+    int complexity =   9 * pe->passed_count()
+                    + 12 * pos.count<PAWN>()
+                    +  9 * outflanking
+                    + 21 * pawnsOnBothFlanks
+                    + 24 * infiltration
+                    + 51 * !pos.non_pawn_material()
+                    - 43 * almostUnwinnable
+                    -110 ;
+
+    Value mg = mg_value(score);
+    Value eg = eg_value(score);
+
+    // Now apply the bonus: note that we find the attacking side by extracting the
+    // sign of the midgame or endgame values, and that we carefully cap the bonus
+    // so that the midgame and endgame scores do not change sign after the bonus.
+    int u = ((mg > 0) - (mg < 0)) * std::clamp(complexity + 50, -abs(mg), 0);
+    int v = ((eg > 0) - (eg < 0)) * std::max(complexity, -abs(eg));
+
+    mg += u;
+    eg += v;
+
+    // Compute the scale factor for the winning side
+    Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK;
+    int sf = me->scale_factor(pos, strongSide);
+
+    // If scale factor is not already specific, scale down via general heuristics
+    if (sf == SCALE_FACTOR_NORMAL)
+    {
+        if (pos.opposite_bishops())
+        {
+            if (   pos.non_pawn_material(WHITE) == BishopValueMg
+                && pos.non_pawn_material(BLACK) == BishopValueMg)
+                sf = 18 + 4 * popcount(pe->passed_pawns(strongSide));
+            else
+                sf = 22 + 3 * pos.count<ALL_PIECES>(strongSide);
+        }
+        else if (  pos.non_pawn_material(WHITE) == RookValueMg
+                && pos.non_pawn_material(BLACK) == RookValueMg
+                && pos.count<PAWN>(strongSide) - pos.count<PAWN>(~strongSide) <= 1
+                && bool(KingSide & pos.pieces(strongSide, PAWN)) != bool(QueenSide & pos.pieces(strongSide, PAWN))
+                && (attacks_bb<KING>(pos.square<KING>(~strongSide)) & pos.pieces(~strongSide, PAWN)))
+            sf = 36;
+        else if (pos.count<QUEEN>() == 1)
+            sf = 37 + 3 * (pos.count<QUEEN>(WHITE) == 1 ? pos.count<BISHOP>(BLACK) + pos.count<KNIGHT>(BLACK)
+                                                        : pos.count<BISHOP>(WHITE) + pos.count<KNIGHT>(WHITE));
+        else
+            sf = std::min(sf, 36 + 7 * pos.count<PAWN>(strongSide));
+    }
+
+    // Interpolate between the middlegame and (scaled by 'sf') endgame score
+    v =  mg * int(me->game_phase())
+       + eg * int(PHASE_MIDGAME - me->game_phase()) * ScaleFactor(sf) / SCALE_FACTOR_NORMAL;
+    v /= PHASE_MIDGAME;
+
+    if (T)
+    {
+        Trace::add(WINNABLE, make_score(u, eg * ScaleFactor(sf) / SCALE_FACTOR_NORMAL - eg_value(score)));
+        Trace::add(TOTAL, make_score(mg, eg * ScaleFactor(sf) / SCALE_FACTOR_NORMAL));
+    }
+
+    return Value(v);
+  }
+
+
+  // Evaluation::value() is the main function of the class. It computes the various
+  // parts of the evaluation and returns the value of the position from the point
+  // of view of the side to move.
+
+  template<Tracing T>
+  Value Evaluation<T>::value() {
+
+    assert(!pos.checkers());
+
+    // Probe the material hash table
+    me = Material::probe(pos);
+
+    // If we have a specialized evaluation function for the current material
+    // configuration, call it and return.
+    if (me->specialized_eval_exists())
+        return me->evaluate(pos);
+
+    // Initialize score by reading the incrementally updated scores included in
+    // the position object (material + piece square tables) and the material
+    // imbalance. Score is computed internally from the white point of view.
+    Score score = pos.psq_score() + me->imbalance() + pos.this_thread()->contempt;
+
+    // Probe the pawn hash table
+    pe = Pawns::probe(pos);
+    score += pe->pawn_score(WHITE) - pe->pawn_score(BLACK);
+
+    // Early exit if score is high
+    auto lazy_skip = [&](Value lazyThreshold) {
+        return abs(mg_value(score) + eg_value(score)) / 2 > lazyThreshold + pos.non_pawn_material() / 64;
+    };
+
+    if (lazy_skip(LazyThreshold1))
+        goto make_v;
+
+    // Main evaluation begins here
+    initialize<WHITE>();
+    initialize<BLACK>();
+
+    // Pieces evaluated first (also populates attackedBy, attackedBy2).
+    // Note that the order of evaluation of the terms is left unspecified.
+    score +=  pieces<WHITE, KNIGHT>() - pieces<BLACK, KNIGHT>()
+            + pieces<WHITE, BISHOP>() - pieces<BLACK, BISHOP>()
+            + pieces<WHITE, ROOK  >() - pieces<BLACK, ROOK  >()
+            + pieces<WHITE, QUEEN >() - pieces<BLACK, QUEEN >();
+
+    score += mobility[WHITE] - mobility[BLACK];
+
+    // More complex interactions that require fully populated attack bitboards
+    score +=  king<   WHITE>() - king<   BLACK>()
+            + passed< WHITE>() - passed< BLACK>();
+
+    if (lazy_skip(LazyThreshold2))
+        goto make_v;
+
+    score +=  threats<WHITE>() - threats<BLACK>()
+            + space<  WHITE>() - space<  BLACK>();
+
+make_v:
+    // Derive single value from mg and eg parts of score
+    Value v = winnable(score);
+
+    // In case of tracing add all remaining individual evaluation terms
+    if (T)
+    {
+        Trace::add(MATERIAL, pos.psq_score());
+        Trace::add(IMBALANCE, me->imbalance());
+        Trace::add(PAWN, pe->pawn_score(WHITE), pe->pawn_score(BLACK));
+        Trace::add(MOBILITY, mobility[WHITE], mobility[BLACK]);
+    }
+
+    // Evaluation grain
+    v = (v / 16) * 16;
+
+    // Side to move point of view
+    v = (pos.side_to_move() == WHITE ? v : -v) + Tempo;
+
+    return v;
+  }
+
+} // namespace
+
+
+/// evaluate() is the evaluator for the outer world. It returns a static
+/// evaluation of the position from the point of view of the side to move.
+
+Value Eval::evaluate(const Position& pos) {
+
+  bool classical = !Eval::useNNUE
+                ||  abs(eg_value(pos.psq_score())) * 16 > NNUEThreshold1 * (16 + pos.rule50_count());
+  Value v = classical ? Evaluation<NO_TRACE>(pos).value()
+                      : NNUE::evaluate(pos) * 5 / 4 + Tempo;
+
+  if (classical && Eval::useNNUE && abs(v) * 16 < NNUEThreshold2 * (16 + pos.rule50_count()))
+      v = NNUE::evaluate(pos) * 5 / 4 + Tempo;
+
+  // Damp down the evaluation linearly when shuffling
+  v = v * (100 - pos.rule50_count()) / 100;
+
+  // Guarantee evaluation does not hit the tablebase range
+  v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1);
+
+  return v;
+}
+
+/// trace() is like evaluate(), but instead of returning a value, it returns
+/// a string (suitable for outputting to stdout) that contains the detailed
+/// descriptions and values of each evaluation term. Useful for debugging.
+/// Trace scores are from white's point of view
+
+std::string Eval::trace(const Position& pos) {
+
+  if (pos.checkers())
+      return "Final evaluation: none (in check)";
+
+  std::stringstream ss;
+  ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2);
+
+  Value v;
+
+  std::memset(scores, 0, sizeof(scores));
+
+  pos.this_thread()->contempt = SCORE_ZERO; // Reset any dynamic contempt
+
+  v = Evaluation<TRACE>(pos).value();
+
+  ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2)
+     << "     Term    |    White    |    Black    |    Total   \n"
+     << "             |   MG    EG  |   MG    EG  |   MG    EG \n"
+     << " ------------+-------------+-------------+------------\n"
+     << "    Material | " << Term(MATERIAL)
+     << "   Imbalance | " << Term(IMBALANCE)
+     << "       Pawns | " << Term(PAWN)
+     << "     Knights | " << Term(KNIGHT)
+     << "     Bishops | " << Term(BISHOP)
+     << "       Rooks | " << Term(ROOK)
+     << "      Queens | " << Term(QUEEN)
+     << "    Mobility | " << Term(MOBILITY)
+     << " King safety | " << Term(KING)
+     << "     Threats | " << Term(THREAT)
+     << "      Passed | " << Term(PASSED)
+     << "       Space | " << Term(SPACE)
+     << "    Winnable | " << Term(WINNABLE)
+     << " ------------+-------------+-------------+------------\n"
+     << "       Total | " << Term(TOTAL);
+
+  v = pos.side_to_move() == WHITE ? v : -v;
+
+  ss << "\nClassical evaluation: " << to_cp(v) << " (white side)\n";
+
+  if (Eval::useNNUE)
+  {
+      v = NNUE::evaluate(pos);
+      v = pos.side_to_move() == WHITE ? v : -v;
+      ss << "\nNNUE evaluation:      " << to_cp(v) << " (white side)\n";
+  }
+
+  v = evaluate(pos);
+  v = pos.side_to_move() == WHITE ? v : -v;
+  ss << "\nFinal evaluation:     " << to_cp(v) << " (white side)\n";
+
+  return ss.str();
+}
diff --git a/src/evaluate.h b/src/evaluate.h
new file mode 100644 (file)
index 0000000..d701f5a
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef EVALUATE_H_INCLUDED
+#define EVALUATE_H_INCLUDED
+
+#include <string>
+
+#include "types.h"
+
+class Position;
+
+namespace Eval {
+
+  std::string trace(const Position& pos);
+  Value evaluate(const Position& pos);
+
+  extern bool useNNUE;
+  extern std::string eval_file_loaded;
+  void init_NNUE();
+  void verify_NNUE();
+
+  // The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue
+  // for the build process (profile-build and fishtest) to work. Do not change the
+  // name of the macro, as it is used in the Makefile.
+  #define EvalFileDefaultName   "nn-82215d0fd0df.nnue"
+
+  namespace NNUE {
+
+    Value evaluate(const Position& pos);
+    Value compute_eval(const Position& pos);
+    void  update_eval(const Position& pos);
+    bool  load_eval(std::string streamName, std::istream& stream);
+
+  } // namespace NNUE
+
+} // namespace Eval
+
+#endif // #ifndef EVALUATE_H_INCLUDED
diff --git a/src/incbin/UNLICENCE b/src/incbin/UNLICENCE
new file mode 100644 (file)
index 0000000..32484ab
--- /dev/null
@@ -0,0 +1,26 @@
+The file "incbin.h" is free and unencumbered software released into
+the public domain by Dale Weiler, see:
+   <https://github.com/graphitemaster/incbin>
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <http://unlicense.org/>
diff --git a/src/incbin/incbin.h b/src/incbin/incbin.h
new file mode 100755 (executable)
index 0000000..c19684d
--- /dev/null
@@ -0,0 +1,368 @@
+/**
+ * @file incbin.h
+ * @author Dale Weiler
+ * @brief Utility for including binary files
+ *
+ * Facilities for including binary files into the current translation unit and
+ * making use from them externally in other translation units.
+ */
+#ifndef INCBIN_HDR
+#define INCBIN_HDR
+#include <limits.h>
+#if   defined(__AVX512BW__) || \
+      defined(__AVX512CD__) || \
+      defined(__AVX512DQ__) || \
+      defined(__AVX512ER__) || \
+      defined(__AVX512PF__) || \
+      defined(__AVX512VL__) || \
+      defined(__AVX512F__)
+# define INCBIN_ALIGNMENT_INDEX 6
+#elif defined(__AVX__)      || \
+      defined(__AVX2__)
+# define INCBIN_ALIGNMENT_INDEX 5
+#elif defined(__SSE__)      || \
+      defined(__SSE2__)     || \
+      defined(__SSE3__)     || \
+      defined(__SSSE3__)    || \
+      defined(__SSE4_1__)   || \
+      defined(__SSE4_2__)   || \
+      defined(__neon__)
+# define INCBIN_ALIGNMENT_INDEX 4
+#elif ULONG_MAX != 0xffffffffu
+# define INCBIN_ALIGNMENT_INDEX 3
+# else
+# define INCBIN_ALIGNMENT_INDEX 2
+#endif
+
+/* Lookup table of (1 << n) where `n' is `INCBIN_ALIGNMENT_INDEX' */
+#define INCBIN_ALIGN_SHIFT_0 1
+#define INCBIN_ALIGN_SHIFT_1 2
+#define INCBIN_ALIGN_SHIFT_2 4
+#define INCBIN_ALIGN_SHIFT_3 8
+#define INCBIN_ALIGN_SHIFT_4 16
+#define INCBIN_ALIGN_SHIFT_5 32
+#define INCBIN_ALIGN_SHIFT_6 64
+
+/* Actual alignment value */
+#define INCBIN_ALIGNMENT \
+    INCBIN_CONCATENATE( \
+        INCBIN_CONCATENATE(INCBIN_ALIGN_SHIFT, _), \
+        INCBIN_ALIGNMENT_INDEX)
+
+/* Stringize */
+#define INCBIN_STR(X) \
+    #X
+#define INCBIN_STRINGIZE(X) \
+    INCBIN_STR(X)
+/* Concatenate */
+#define INCBIN_CAT(X, Y) \
+    X ## Y
+#define INCBIN_CONCATENATE(X, Y) \
+    INCBIN_CAT(X, Y)
+/* Deferred macro expansion */
+#define INCBIN_EVAL(X) \
+    X
+#define INCBIN_INVOKE(N, ...) \
+    INCBIN_EVAL(N(__VA_ARGS__))
+
+/* Green Hills uses a different directive for including binary data */
+#if defined(__ghs__)
+#  if (__ghs_asm == 2)
+#    define INCBIN_MACRO ".file"
+/* Or consider the ".myrawdata" entry in the ld file */
+#  else
+#    define INCBIN_MACRO "\tINCBIN"
+#  endif
+#else
+#  define INCBIN_MACRO ".incbin"
+#endif
+
+#ifndef _MSC_VER
+#  define INCBIN_ALIGN \
+    __attribute__((aligned(INCBIN_ALIGNMENT)))
+#else
+#  define INCBIN_ALIGN __declspec(align(INCBIN_ALIGNMENT))
+#endif
+
+#if defined(__arm__) || /* GNU C and RealView */ \
+    defined(__arm) || /* Diab */ \
+    defined(_ARM) /* ImageCraft */
+#  define INCBIN_ARM
+#endif
+
+#ifdef __GNUC__
+/* Utilize .balign where supported */
+#  define INCBIN_ALIGN_HOST ".balign " INCBIN_STRINGIZE(INCBIN_ALIGNMENT) "\n"
+#  define INCBIN_ALIGN_BYTE ".balign 1\n"
+#elif defined(INCBIN_ARM)
+/*
+ * On arm assemblers, the alignment value is calculated as (1 << n) where `n' is
+ * the shift count. This is the value passed to `.align'
+ */
+#  define INCBIN_ALIGN_HOST ".align " INCBIN_STRINGIZE(INCBIN_ALIGNMENT_INDEX) "\n"
+#  define INCBIN_ALIGN_BYTE ".align 0\n"
+#else
+/* We assume other inline assembler's treat `.align' as `.balign' */
+#  define INCBIN_ALIGN_HOST ".align " INCBIN_STRINGIZE(INCBIN_ALIGNMENT) "\n"
+#  define INCBIN_ALIGN_BYTE ".align 1\n"
+#endif
+
+/* INCBIN_CONST is used by incbin.c generated files */
+#if defined(__cplusplus)
+#  define INCBIN_EXTERNAL extern "C"
+#  define INCBIN_CONST    extern const
+#else
+#  define INCBIN_EXTERNAL extern
+#  define INCBIN_CONST    const
+#endif
+
+/**
+ * @brief Optionally override the linker section into which data is emitted.
+ *
+ * @warning If you use this facility, you'll have to deal with platform-specific linker output
+ * section naming on your own
+ *
+ * Overriding the default linker output section, e.g for esp8266/Arduino:
+ * @code
+ * #define INCBIN_OUTPUT_SECTION ".irom.text"
+ * #include "incbin.h"
+ * INCBIN(Foo, "foo.txt");
+ * // Data is emitted into program memory that never gets copied to RAM
+ * @endcode
+ */
+#if !defined(INCBIN_OUTPUT_SECTION)
+#  if defined(__APPLE__)
+#    define INCBIN_OUTPUT_SECTION         ".const_data"
+#  else
+#    define INCBIN_OUTPUT_SECTION         ".rodata"
+#  endif
+#endif
+
+#if defined(__APPLE__)
+/* The directives are different for Apple branded compilers */
+#  define INCBIN_SECTION         INCBIN_OUTPUT_SECTION "\n"
+#  define INCBIN_GLOBAL(NAME)    ".globl " INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME "\n"
+#  define INCBIN_INT             ".long "
+#  define INCBIN_MANGLE          "_"
+#  define INCBIN_BYTE            ".byte "
+#  define INCBIN_TYPE(...)
+#else
+#  define INCBIN_SECTION         ".section " INCBIN_OUTPUT_SECTION "\n"
+#  define INCBIN_GLOBAL(NAME)    ".global " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME "\n"
+#  if defined(__ghs__)
+#    define INCBIN_INT           ".word "
+#  else
+#    define INCBIN_INT           ".int "
+#  endif
+#  if defined(__USER_LABEL_PREFIX__)
+#    define INCBIN_MANGLE        INCBIN_STRINGIZE(__USER_LABEL_PREFIX__)
+#  else
+#    define INCBIN_MANGLE        ""
+#  endif
+#  if defined(INCBIN_ARM)
+/* On arm assemblers, `@' is used as a line comment token */
+#    define INCBIN_TYPE(NAME)    ".type " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME ", %object\n"
+#  elif defined(__MINGW32__) || defined(__MINGW64__)
+/* Mingw doesn't support this directive either */
+#    define INCBIN_TYPE(NAME)
+#  else
+/* It's safe to use `@' on other architectures */
+#    define INCBIN_TYPE(NAME)    ".type " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME ", @object\n"
+#  endif
+#  define INCBIN_BYTE            ".byte "
+#endif
+
+/* List of style types used for symbol names */
+#define INCBIN_STYLE_CAMEL 0
+#define INCBIN_STYLE_SNAKE 1
+
+/**
+ * @brief Specify the prefix to use for symbol names.
+ *
+ * By default this is `g', producing symbols of the form:
+ * @code
+ * #include "incbin.h"
+ * INCBIN(Foo, "foo.txt");
+ *
+ * // Now you have the following symbols:
+ * // const unsigned char gFooData[];
+ * // const unsigned char *const gFooEnd;
+ * // const unsigned int gFooSize;
+ * @endcode
+ *
+ * If however you specify a prefix before including: e.g:
+ * @code
+ * #define INCBIN_PREFIX incbin
+ * #include "incbin.h"
+ * INCBIN(Foo, "foo.txt");
+ *
+ * // Now you have the following symbols instead:
+ * // const unsigned char incbinFooData[];
+ * // const unsigned char *const incbinFooEnd;
+ * // const unsigned int incbinFooSize;
+ * @endcode
+ */
+#if !defined(INCBIN_PREFIX)
+#  define INCBIN_PREFIX g
+#endif
+
+/**
+ * @brief Specify the style used for symbol names.
+ *
+ * Possible options are
+ * - INCBIN_STYLE_CAMEL "CamelCase"
+ * - INCBIN_STYLE_SNAKE "snake_case"
+ *
+ * Default option is *INCBIN_STYLE_CAMEL* producing symbols of the form:
+ * @code
+ * #include "incbin.h"
+ * INCBIN(Foo, "foo.txt");
+ *
+ * // Now you have the following symbols:
+ * // const unsigned char <prefix>FooData[];
+ * // const unsigned char *const <prefix>FooEnd;
+ * // const unsigned int <prefix>FooSize;
+ * @endcode
+ *
+ * If however you specify a style before including: e.g:
+ * @code
+ * #define INCBIN_STYLE INCBIN_STYLE_SNAKE
+ * #include "incbin.h"
+ * INCBIN(foo, "foo.txt");
+ *
+ * // Now you have the following symbols:
+ * // const unsigned char <prefix>foo_data[];
+ * // const unsigned char *const <prefix>foo_end;
+ * // const unsigned int <prefix>foo_size;
+ * @endcode
+ */
+#if !defined(INCBIN_STYLE)
+#  define INCBIN_STYLE INCBIN_STYLE_CAMEL
+#endif
+
+/* Style lookup tables */
+#define INCBIN_STYLE_0_DATA Data
+#define INCBIN_STYLE_0_END End
+#define INCBIN_STYLE_0_SIZE Size
+#define INCBIN_STYLE_1_DATA _data
+#define INCBIN_STYLE_1_END _end
+#define INCBIN_STYLE_1_SIZE _size
+
+/* Style lookup: returning identifier */
+#define INCBIN_STYLE_IDENT(TYPE) \
+    INCBIN_CONCATENATE( \
+        INCBIN_STYLE_, \
+        INCBIN_CONCATENATE( \
+            INCBIN_EVAL(INCBIN_STYLE), \
+            INCBIN_CONCATENATE(_, TYPE)))
+
+/* Style lookup: returning string literal */
+#define INCBIN_STYLE_STRING(TYPE) \
+    INCBIN_STRINGIZE( \
+        INCBIN_STYLE_IDENT(TYPE)) \
+
+/* Generate the global labels by indirectly invoking the macro with our style
+ * type and concatenating the name against them. */
+#define INCBIN_GLOBAL_LABELS(NAME, TYPE) \
+    INCBIN_INVOKE( \
+        INCBIN_GLOBAL, \
+        INCBIN_CONCATENATE( \
+            NAME, \
+            INCBIN_INVOKE( \
+                INCBIN_STYLE_IDENT, \
+                TYPE))) \
+    INCBIN_INVOKE( \
+        INCBIN_TYPE, \
+        INCBIN_CONCATENATE( \
+            NAME, \
+            INCBIN_INVOKE( \
+                INCBIN_STYLE_IDENT, \
+                TYPE)))
+
+/**
+ * @brief Externally reference binary data included in another translation unit.
+ *
+ * Produces three external symbols that reference the binary data included in
+ * another translation unit.
+ *
+ * The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with
+ * "Data", as well as "End" and "Size" after. An example is provided below.
+ *
+ * @param NAME The name given for the binary data
+ *
+ * @code
+ * INCBIN_EXTERN(Foo);
+ *
+ * // Now you have the following symbols:
+ * // extern const unsigned char <prefix>FooData[];
+ * // extern const unsigned char *const <prefix>FooEnd;
+ * // extern const unsigned int <prefix>FooSize;
+ * @endcode
+ */
+#define INCBIN_EXTERN(NAME) \
+    INCBIN_EXTERNAL const INCBIN_ALIGN unsigned char \
+        INCBIN_CONCATENATE( \
+            INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \
+            INCBIN_STYLE_IDENT(DATA))[]; \
+    INCBIN_EXTERNAL const INCBIN_ALIGN unsigned char *const \
+    INCBIN_CONCATENATE( \
+        INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \
+        INCBIN_STYLE_IDENT(END)); \
+    INCBIN_EXTERNAL const unsigned int \
+        INCBIN_CONCATENATE( \
+            INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \
+            INCBIN_STYLE_IDENT(SIZE))
+
+/**
+ * @brief Include a binary file into the current translation unit.
+ *
+ * Includes a binary file into the current translation unit, producing three symbols
+ * for objects that encode the data and size respectively.
+ *
+ * The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with
+ * "Data", as well as "End" and "Size" after. An example is provided below.
+ *
+ * @param NAME The name to associate with this binary data (as an identifier.)
+ * @param FILENAME The file to include (as a string literal.)
+ *
+ * @code
+ * INCBIN(Icon, "icon.png");
+ *
+ * // Now you have the following symbols:
+ * // const unsigned char <prefix>IconData[];
+ * // const unsigned char *const <prefix>IconEnd;
+ * // const unsigned int <prefix>IconSize;
+ * @endcode
+ *
+ * @warning This must be used in global scope
+ * @warning The identifiers may be different if INCBIN_STYLE is not default
+ *
+ * To externally reference the data included by this in another translation unit
+ * please @see INCBIN_EXTERN.
+ */
+#ifdef _MSC_VER
+#define INCBIN(NAME, FILENAME) \
+    INCBIN_EXTERN(NAME)
+#else
+#define INCBIN(NAME, FILENAME) \
+    __asm__(INCBIN_SECTION \
+            INCBIN_GLOBAL_LABELS(NAME, DATA) \
+            INCBIN_ALIGN_HOST \
+            INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(DATA) ":\n" \
+            INCBIN_MACRO " \"" FILENAME "\"\n" \
+            INCBIN_GLOBAL_LABELS(NAME, END) \
+            INCBIN_ALIGN_BYTE \
+            INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(END) ":\n" \
+                INCBIN_BYTE "1\n" \
+            INCBIN_GLOBAL_LABELS(NAME, SIZE) \
+            INCBIN_ALIGN_HOST \
+            INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(SIZE) ":\n" \
+                INCBIN_INT INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(END) " - " \
+                           INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(DATA) "\n" \
+            INCBIN_ALIGN_HOST \
+            ".text\n" \
+    ); \
+    INCBIN_EXTERN(NAME)
+
+#endif
+#endif
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644 (file)
index 0000000..f95db1c
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <iostream>
+
+#include "bitboard.h"
+#include "endgame.h"
+#include "position.h"
+#include "search.h"
+#include "thread.h"
+#include "tt.h"
+#include "uci.h"
+#include "syzygy/tbprobe.h"
+
+namespace PSQT {
+  void init();
+}
+
+int main(int argc, char* argv[]) {
+
+  std::cout << engine_info() << std::endl;
+
+  CommandLine::init(argc, argv);
+  UCI::init(Options);
+  Tune::init();
+  PSQT::init();
+  Bitboards::init();
+  Position::init();
+  Bitbases::init();
+  Endgames::init();
+  Threads.set(size_t(Options["Threads"]));
+  Search::clear(); // After threads are up
+  Eval::init_NNUE();
+
+  UCI::loop(argc, argv);
+
+  Threads.set(0);
+  return 0;
+}
diff --git a/src/material.cpp b/src/material.cpp
new file mode 100644 (file)
index 0000000..870a5e1
--- /dev/null
@@ -0,0 +1,220 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <cassert>
+#include <cstring>   // For std::memset
+
+#include "material.h"
+#include "thread.h"
+
+using namespace std;
+
+namespace {
+
+  // Polynomial material imbalance parameters
+
+  constexpr int QuadraticOurs[][PIECE_TYPE_NB] = {
+    //            OUR PIECES
+    // pair pawn knight bishop rook queen
+    {1438                               }, // Bishop pair
+    {  40,   38                         }, // Pawn
+    {  32,  255, -62                    }, // Knight      OUR PIECES
+    {   0,  104,   4,    0              }, // Bishop
+    { -26,   -2,  47,   105,  -208      }, // Rook
+    {-189,   24, 117,   133,  -134, -6  }  // Queen
+  };
+
+  constexpr int QuadraticTheirs[][PIECE_TYPE_NB] = {
+    //           THEIR PIECES
+    // pair pawn knight bishop rook queen
+    {                                   }, // Bishop pair
+    {  36,                              }, // Pawn
+    {   9,   63,                        }, // Knight      OUR PIECES
+    {  59,   65,  42,                   }, // Bishop
+    {  46,   39,  24,   -24,            }, // Rook
+    {  97,  100, -42,   137,  268,      }  // Queen
+  };
+
+  // Endgame evaluation and scaling functions are accessed directly and not through
+  // the function maps because they correspond to more than one material hash key.
+  Endgame<KXK>    EvaluateKXK[] = { Endgame<KXK>(WHITE),    Endgame<KXK>(BLACK) };
+
+  Endgame<KBPsK>  ScaleKBPsK[]  = { Endgame<KBPsK>(WHITE),  Endgame<KBPsK>(BLACK) };
+  Endgame<KQKRPs> ScaleKQKRPs[] = { Endgame<KQKRPs>(WHITE), Endgame<KQKRPs>(BLACK) };
+  Endgame<KPsK>   ScaleKPsK[]   = { Endgame<KPsK>(WHITE),   Endgame<KPsK>(BLACK) };
+  Endgame<KPKP>   ScaleKPKP[]   = { Endgame<KPKP>(WHITE),   Endgame<KPKP>(BLACK) };
+
+  // Helper used to detect a given material distribution
+  bool is_KXK(const Position& pos, Color us) {
+    return  !more_than_one(pos.pieces(~us))
+          && pos.non_pawn_material(us) >= RookValueMg;
+  }
+
+  bool is_KBPsK(const Position& pos, Color us) {
+    return   pos.non_pawn_material(us) == BishopValueMg
+          && pos.count<PAWN  >(us) >= 1;
+  }
+
+  bool is_KQKRPs(const Position& pos, Color us) {
+    return  !pos.count<PAWN>(us)
+          && pos.non_pawn_material(us) == QueenValueMg
+          && pos.count<ROOK>(~us) == 1
+          && pos.count<PAWN>(~us) >= 1;
+  }
+
+
+  /// imbalance() calculates the imbalance by comparing the piece count of each
+  /// piece type for both colors.
+
+  template<Color Us>
+  int imbalance(const int pieceCount[][PIECE_TYPE_NB]) {
+
+    constexpr Color Them = ~Us;
+
+    int bonus = 0;
+
+    // Second-degree polynomial material imbalance, by Tord Romstad
+    for (int pt1 = NO_PIECE_TYPE; pt1 <= QUEEN; ++pt1)
+    {
+        if (!pieceCount[Us][pt1])
+            continue;
+
+        int v = QuadraticOurs[pt1][pt1] * pieceCount[Us][pt1];
+
+        for (int pt2 = NO_PIECE_TYPE; pt2 < pt1; ++pt2)
+            v +=  QuadraticOurs[pt1][pt2] * pieceCount[Us][pt2]
+                + QuadraticTheirs[pt1][pt2] * pieceCount[Them][pt2];
+
+        bonus += pieceCount[Us][pt1] * v;
+    }
+
+    return bonus;
+  }
+
+} // namespace
+
+namespace Material {
+
+
+/// Material::probe() looks up the current position's material configuration in
+/// the material hash table. It returns a pointer to the Entry if the position
+/// is found. Otherwise a new Entry is computed and stored there, so we don't
+/// have to recompute all when the same material configuration occurs again.
+
+Entry* probe(const Position& pos) {
+
+  Key key = pos.material_key();
+  Entry* e = pos.this_thread()->materialTable[key];
+
+  if (e->key == key)
+      return e;
+
+  std::memset(e, 0, sizeof(Entry));
+  e->key = key;
+  e->factor[WHITE] = e->factor[BLACK] = (uint8_t)SCALE_FACTOR_NORMAL;
+
+  Value npm_w = pos.non_pawn_material(WHITE);
+  Value npm_b = pos.non_pawn_material(BLACK);
+  Value npm   = std::clamp(npm_w + npm_b, EndgameLimit, MidgameLimit);
+
+  // Map total non-pawn material into [PHASE_ENDGAME, PHASE_MIDGAME]
+  e->gamePhase = Phase(((npm - EndgameLimit) * PHASE_MIDGAME) / (MidgameLimit - EndgameLimit));
+
+  // Let's look if we have a specialized evaluation function for this particular
+  // material configuration. Firstly we look for a fixed configuration one, then
+  // for a generic one if the previous search failed.
+  if ((e->evaluationFunction = Endgames::probe<Value>(key)) != nullptr)
+      return e;
+
+  for (Color c : { WHITE, BLACK })
+      if (is_KXK(pos, c))
+      {
+          e->evaluationFunction = &EvaluateKXK[c];
+          return e;
+      }
+
+  // OK, we didn't find any special evaluation function for the current material
+  // configuration. Is there a suitable specialized scaling function?
+  const auto* sf = Endgames::probe<ScaleFactor>(key);
+
+  if (sf)
+  {
+      e->scalingFunction[sf->strongSide] = sf; // Only strong color assigned
+      return e;
+  }
+
+  // We didn't find any specialized scaling function, so fall back on generic
+  // ones that refer to more than one material distribution. Note that in this
+  // case we don't return after setting the function.
+  for (Color c : { WHITE, BLACK })
+  {
+    if (is_KBPsK(pos, c))
+        e->scalingFunction[c] = &ScaleKBPsK[c];
+
+    else if (is_KQKRPs(pos, c))
+        e->scalingFunction[c] = &ScaleKQKRPs[c];
+  }
+
+  if (npm_w + npm_b == VALUE_ZERO && pos.pieces(PAWN)) // Only pawns on the board
+  {
+      if (!pos.count<PAWN>(BLACK))
+      {
+          assert(pos.count<PAWN>(WHITE) >= 2);
+
+          e->scalingFunction[WHITE] = &ScaleKPsK[WHITE];
+      }
+      else if (!pos.count<PAWN>(WHITE))
+      {
+          assert(pos.count<PAWN>(BLACK) >= 2);
+
+          e->scalingFunction[BLACK] = &ScaleKPsK[BLACK];
+      }
+      else if (pos.count<PAWN>(WHITE) == 1 && pos.count<PAWN>(BLACK) == 1)
+      {
+          // This is a special case because we set scaling functions
+          // for both colors instead of only one.
+          e->scalingFunction[WHITE] = &ScaleKPKP[WHITE];
+          e->scalingFunction[BLACK] = &ScaleKPKP[BLACK];
+      }
+  }
+
+  // Zero or just one pawn makes it difficult to win, even with a small material
+  // advantage. This catches some trivial draws like KK, KBK and KNK and gives a
+  // drawish scale factor for cases such as KRKBP and KmmKm (except for KBBKN).
+  if (!pos.count<PAWN>(WHITE) && npm_w - npm_b <= BishopValueMg)
+      e->factor[WHITE] = uint8_t(npm_w <  RookValueMg   ? SCALE_FACTOR_DRAW :
+                                 npm_b <= BishopValueMg ? 4 : 14);
+
+  if (!pos.count<PAWN>(BLACK) && npm_b - npm_w <= BishopValueMg)
+      e->factor[BLACK] = uint8_t(npm_b <  RookValueMg   ? SCALE_FACTOR_DRAW :
+                                 npm_w <= BishopValueMg ? 4 : 14);
+
+  // Evaluate the material imbalance. We use PIECE_TYPE_NONE as a place holder
+  // for the bishop pair "extended piece", which allows us to be more flexible
+  // in defining bishop pair bonuses.
+  const int pieceCount[COLOR_NB][PIECE_TYPE_NB] = {
+  { pos.count<BISHOP>(WHITE) > 1, pos.count<PAWN>(WHITE), pos.count<KNIGHT>(WHITE),
+    pos.count<BISHOP>(WHITE)    , pos.count<ROOK>(WHITE), pos.count<QUEEN >(WHITE) },
+  { pos.count<BISHOP>(BLACK) > 1, pos.count<PAWN>(BLACK), pos.count<KNIGHT>(BLACK),
+    pos.count<BISHOP>(BLACK)    , pos.count<ROOK>(BLACK), pos.count<QUEEN >(BLACK) } };
+
+  e->value = int16_t((imbalance<WHITE>(pieceCount) - imbalance<BLACK>(pieceCount)) / 16);
+  return e;
+}
+
+} // namespace Material
diff --git a/src/material.h b/src/material.h
new file mode 100644 (file)
index 0000000..80d0165
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef MATERIAL_H_INCLUDED
+#define MATERIAL_H_INCLUDED
+
+#include "endgame.h"
+#include "misc.h"
+#include "position.h"
+#include "types.h"
+
+namespace Material {
+
+/// Material::Entry contains various information about a material configuration.
+/// It contains a material imbalance evaluation, a function pointer to a special
+/// endgame evaluation function (which in most cases is NULL, meaning that the
+/// standard evaluation function will be used), and scale factors.
+///
+/// The scale factors are used to scale the evaluation score up or down. For
+/// instance, in KRB vs KR endgames, the score is scaled down by a factor of 4,
+/// which will result in scores of absolute value less than one pawn.
+
+struct Entry {
+
+  Score imbalance() const { return make_score(value, value); }
+  Phase game_phase() const { return gamePhase; }
+  bool specialized_eval_exists() const { return evaluationFunction != nullptr; }
+  Value evaluate(const Position& pos) const { return (*evaluationFunction)(pos); }
+
+  // scale_factor() takes a position and a color as input and returns a scale factor
+  // for the given color. We have to provide the position in addition to the color
+  // because the scale factor may also be a function which should be applied to
+  // the position. For instance, in KBP vs K endgames, the scaling function looks
+  // for rook pawns and wrong-colored bishops.
+  ScaleFactor scale_factor(const Position& pos, Color c) const {
+    ScaleFactor sf = scalingFunction[c] ? (*scalingFunction[c])(pos)
+                                        :  SCALE_FACTOR_NONE;
+    return sf != SCALE_FACTOR_NONE ? sf : ScaleFactor(factor[c]);
+  }
+
+  Key key;
+  const EndgameBase<Value>* evaluationFunction;
+  const EndgameBase<ScaleFactor>* scalingFunction[COLOR_NB]; // Could be one for each
+                                                             // side (e.g. KPKP, KBPsK)
+  int16_t value;
+  uint8_t factor[COLOR_NB];
+  Phase gamePhase;
+};
+
+typedef HashTable<Entry, 8192> Table;
+
+Entry* probe(const Position& pos);
+
+} // namespace Material
+
+#endif // #ifndef MATERIAL_H_INCLUDED
diff --git a/src/misc.cpp b/src/misc.cpp
new file mode 100644 (file)
index 0000000..22070f0
--- /dev/null
@@ -0,0 +1,650 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#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 <windows.h>
+// The needed Windows API for processor groups could be missed from old Windows
+// versions, so instead of calling them directly (forcing the linker to resolve
+// the calls at compile time), try to load them at runtime. To do this we need
+// first to define the corresponding function pointers.
+extern "C" {
+typedef bool(*fun1_t)(LOGICAL_PROCESSOR_RELATIONSHIP,
+                      PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, PDWORD);
+typedef bool(*fun2_t)(USHORT, PGROUP_AFFINITY);
+typedef bool(*fun3_t)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY);
+}
+#endif
+
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+#include <sstream>
+#include <vector>
+#include <cstdlib>
+
+#if defined(__linux__) && !defined(__ANDROID__)
+#include <stdlib.h>
+#include <sys/mman.h>
+#endif
+
+#if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__) || (defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC) && !defined(_WIN32))
+#define POSIXALIGNEDALLOC
+#include <stdlib.h>
+#endif
+
+#include "misc.h"
+#include "thread.h"
+
+using namespace std;
+
+namespace {
+
+/// Version number. If Version is left empty, then compile date in the format
+/// DD-MM-YY and show in engine_info.
+const string Version = "12";
+
+/// Our fancy logging facility. The trick here is to replace cin.rdbuf() and
+/// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We
+/// can toggle the logging of std::cout and std:cin at runtime whilst preserving
+/// usual I/O functionality, all without changing a single line of code!
+/// Idea from http://groups.google.com/group/comp.lang.c++/msg/1d941c0f26ea0d81
+
+struct Tie: public streambuf { // MSVC requires split streambuf for cin and cout
+
+  Tie(streambuf* b, streambuf* l) : buf(b), logBuf(l) {}
+
+  int sync() override { return logBuf->pubsync(), buf->pubsync(); }
+  int overflow(int c) override { return log(buf->sputc((char)c), "<< "); }
+  int underflow() override { return buf->sgetc(); }
+  int uflow() override { return log(buf->sbumpc(), ">> "); }
+
+  streambuf *buf, *logBuf;
+
+  int log(int c, const char* prefix) {
+
+    static int last = '\n'; // Single log file
+
+    if (last == '\n')
+        logBuf->sputn(prefix, 3);
+
+    return last = logBuf->sputc((char)c);
+  }
+};
+
+class Logger {
+
+  Logger() : in(cin.rdbuf(), file.rdbuf()), out(cout.rdbuf(), file.rdbuf()) {}
+ ~Logger() { start(""); }
+
+  ofstream file;
+  Tie in, out;
+
+public:
+  static void start(const std::string& fname) {
+
+    static Logger l;
+
+    if (!fname.empty() && !l.file.is_open())
+    {
+        l.file.open(fname, ifstream::out);
+
+        if (!l.file.is_open())
+        {
+            cerr << "Unable to open debug log file " << fname << endl;
+            exit(EXIT_FAILURE);
+        }
+
+        cin.rdbuf(&l.in);
+        cout.rdbuf(&l.out);
+    }
+    else if (fname.empty() && l.file.is_open())
+    {
+        cout.rdbuf(l.out.buf);
+        cin.rdbuf(l.in.buf);
+        l.file.close();
+    }
+  }
+};
+
+} // namespace
+
+
+/// engine_info() returns the full name of the current Stockfish version. This
+/// will be either "Stockfish <Tag> DD-MM-YY" (where DD-MM-YY is the date when
+/// the program was compiled) or "Stockfish <Version>", depending on whether
+/// Version is empty.
+
+const string engine_info(bool to_uci) {
+
+  const string months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec");
+  string month, day, year;
+  stringstream ss, date(__DATE__); // From compiler, format is "Sep 21 2008"
+
+  ss << "Stockfish " << Version << setfill('0');
+
+  if (Version.empty())
+  {
+      date >> month >> day >> year;
+      ss << setw(2) << day << setw(2) << (1 + months.find(month) / 4) << year.substr(2);
+  }
+
+  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
+
+const std::string compiler_info() {
+
+  #define stringify2(x) #x
+  #define stringify(x) stringify2(x)
+  #define make_version_string(major, minor, patch) stringify(major) "." stringify(minor) "." stringify(patch)
+
+/// Predefined macros hell:
+///
+/// __GNUC__           Compiler is gcc, Clang or Intel on Linux
+/// __INTEL_COMPILER   Compiler is Intel
+/// _MSC_VER           Compiler is MSVC or Intel on Windows
+/// _WIN32             Building on Windows (any)
+/// _WIN64             Building on Windows 64 bit
+
+  std::string compiler = "\nCompiled by ";
+
+  #ifdef __clang__
+     compiler += "clang++ ";
+     compiler += make_version_string(__clang_major__, __clang_minor__, __clang_patchlevel__);
+  #elif __INTEL_COMPILER
+     compiler += "Intel compiler ";
+     compiler += "(version ";
+     compiler += stringify(__INTEL_COMPILER) " update " stringify(__INTEL_COMPILER_UPDATE);
+     compiler += ")";
+  #elif _MSC_VER
+     compiler += "MSVC ";
+     compiler += "(version ";
+     compiler += stringify(_MSC_FULL_VER) "." stringify(_MSC_BUILD);
+     compiler += ")";
+  #elif __GNUC__
+     compiler += "g++ (GNUC) ";
+     compiler += make_version_string(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__);
+  #else
+     compiler += "Unknown compiler ";
+     compiler += "(unknown version)";
+  #endif
+
+  #if defined(__APPLE__)
+     compiler += " on Apple";
+  #elif defined(__CYGWIN__)
+     compiler += " on Cygwin";
+  #elif defined(__MINGW64__)
+     compiler += " on MinGW64";
+  #elif defined(__MINGW32__)
+     compiler += " on MinGW32";
+  #elif defined(__ANDROID__)
+     compiler += " on Android";
+  #elif defined(__linux__)
+     compiler += " on Linux";
+  #elif defined(_WIN64)
+     compiler += " on Microsoft Windows 64-bit";
+  #elif defined(_WIN32)
+     compiler += " on Microsoft Windows 32-bit";
+  #else
+     compiler += " on unknown system";
+  #endif
+
+  compiler += "\nCompilation settings include: ";
+  compiler += (Is64Bit ? " 64bit" : " 32bit");
+  #if defined(USE_VNNI)
+    compiler += " VNNI";
+  #endif
+  #if defined(USE_AVX512)
+    compiler += " AVX512";
+  #endif
+  compiler += (HasPext ? " BMI2" : "");
+  #if defined(USE_AVX2)
+    compiler += " AVX2";
+  #endif
+  #if defined(USE_SSE41)
+    compiler += " SSE41";
+  #endif
+  #if defined(USE_SSSE3)
+    compiler += " SSSE3";
+  #endif
+  #if defined(USE_SSE2)
+    compiler += " SSE2";
+  #endif
+  compiler += (HasPopCnt ? " POPCNT" : "");
+  #if defined(USE_MMX)
+    compiler += " MMX";
+  #endif
+  #if defined(USE_NEON)
+    compiler += " NEON";
+  #endif
+
+  #if !defined(NDEBUG)
+    compiler += " DEBUG";
+  #endif
+
+  compiler += "\n__VERSION__ macro expands to: ";
+  #ifdef __VERSION__
+     compiler += __VERSION__;
+  #else
+     compiler += "(undefined macro)";
+  #endif
+  compiler += "\n";
+
+  return compiler;
+}
+
+
+/// Debug functions used mainly to collect run-time statistics
+static std::atomic<int64_t> hits[2], means[2];
+
+void dbg_hit_on(bool b) { ++hits[0]; if (b) ++hits[1]; }
+void dbg_hit_on(bool c, bool b) { if (c) dbg_hit_on(b); }
+void dbg_mean_of(int v) { ++means[0]; means[1] += v; }
+
+void dbg_print() {
+
+  if (hits[0])
+      cerr << "Total " << hits[0] << " Hits " << hits[1]
+           << " hit rate (%) " << 100 * hits[1] / hits[0] << endl;
+
+  if (means[0])
+      cerr << "Total " << means[0] << " Mean "
+           << (double)means[1] / means[0] << endl;
+}
+
+
+/// Used to serialize access to std::cout to avoid multiple threads writing at
+/// the same time.
+
+std::ostream& operator<<(std::ostream& os, SyncCout sc) {
+
+  static std::mutex m;
+
+  if (sc == IO_LOCK)
+      m.lock();
+
+  if (sc == IO_UNLOCK)
+      m.unlock();
+
+  return os;
+}
+
+
+/// Trampoline helper to avoid moving Logger to misc.h
+void start_logger(const std::string& fname) { Logger::start(fname); }
+
+
+/// prefetch() preloads the given address in L1/L2 cache. This is a non-blocking
+/// function that doesn't stall the CPU waiting for data to be loaded from memory,
+/// which can be quite slow.
+#ifdef NO_PREFETCH
+
+void prefetch(void*) {}
+
+#else
+
+void prefetch(void* addr) {
+
+#  if defined(__INTEL_COMPILER)
+   // This hack prevents prefetches from being optimized away by
+   // Intel compiler. Both MSVC and gcc seem not be affected by this.
+   __asm__ ("");
+#  endif
+
+#  if defined(__INTEL_COMPILER) || defined(_MSC_VER)
+  _mm_prefetch((char*)addr, _MM_HINT_T0);
+#  else
+  __builtin_prefetch(addr);
+#  endif
+}
+
+#endif
+
+
+/// std_aligned_alloc() is our wrapper for systems where the c++17 implementation
+/// does not guarantee the availability of aligned_alloc(). Memory allocated with
+/// std_aligned_alloc() must be freed with std_aligned_free().
+
+void* std_aligned_alloc(size_t alignment, size_t size) {
+
+#if defined(POSIXALIGNEDALLOC)
+  void *mem;
+  return posix_memalign(&mem, alignment, size) ? nullptr : mem;
+#elif defined(_WIN32)
+  return _mm_malloc(size, alignment);
+#else
+  return std::aligned_alloc(alignment, size);
+#endif
+}
+
+void std_aligned_free(void* ptr) {
+
+#if defined(POSIXALIGNEDALLOC)
+  free(ptr);
+#elif defined(_WIN32)
+  _mm_free(ptr);
+#else
+  free(ptr);
+#endif
+}
+
+/// aligned_ttmem_alloc() will return suitably aligned memory, if possible using large pages.
+/// The returned pointer is the aligned one, while the mem argument is the one that needs
+/// to be passed to free. With c++17 some of this functionality could be simplified.
+
+#if defined(__linux__) && !defined(__ANDROID__)
+
+void* aligned_ttmem_alloc(size_t allocSize, void*& mem) {
+
+  constexpr size_t alignment = 2 * 1024 * 1024; // assumed 2MB page sizes
+  size_t size = ((allocSize + alignment - 1) / alignment) * alignment; // multiple of alignment
+  if (posix_memalign(&mem, alignment, size))
+     mem = nullptr;
+#if defined(MADV_HUGEPAGE)
+  madvise(mem, allocSize, MADV_HUGEPAGE);
+#endif
+  return mem;
+}
+
+#elif defined(_WIN64)
+
+static void* aligned_ttmem_alloc_large_pages(size_t allocSize) {
+
+  HANDLE hProcessToken { };
+  LUID luid { };
+  void* mem = nullptr;
+
+  const size_t largePageSize = GetLargePageMinimum();
+  if (!largePageSize)
+      return nullptr;
+
+  // We need SeLockMemoryPrivilege, so try to enable it for the process
+  if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken))
+      return nullptr;
+
+  if (LookupPrivilegeValue(NULL, SE_LOCK_MEMORY_NAME, &luid))
+  {
+      TOKEN_PRIVILEGES tp { };
+      TOKEN_PRIVILEGES prevTp { };
+      DWORD prevTpLen = 0;
+
+      tp.PrivilegeCount = 1;
+      tp.Privileges[0].Luid = luid;
+      tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
+
+      // Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() succeeds,
+      // we still need to query GetLastError() to ensure that the privileges were actually obtained.
+      if (AdjustTokenPrivileges(
+              hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, &prevTpLen) &&
+          GetLastError() == ERROR_SUCCESS)
+      {
+          // Round up size to full pages and allocate
+          allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1);
+          mem = VirtualAlloc(
+              NULL, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE);
+
+          // Privilege no longer needed, restore previous state
+          AdjustTokenPrivileges(hProcessToken, FALSE, &prevTp, 0, NULL, NULL);
+      }
+  }
+
+  CloseHandle(hProcessToken);
+
+  return mem;
+}
+
+void* aligned_ttmem_alloc(size_t allocSize, void*& mem) {
+
+  static bool firstCall = true;
+
+  // Try to allocate large pages
+  mem = aligned_ttmem_alloc_large_pages(allocSize);
+
+  // Suppress info strings on the first call. The first call occurs before 'uci'
+  // is received and in that case this output confuses some GUIs.
+  if (!firstCall)
+  {
+      if (mem)
+          sync_cout << "info string Hash table allocation: Windows large pages used." << sync_endl;
+      else
+          sync_cout << "info string Hash table allocation: Windows large pages not used." << sync_endl;
+  }
+  firstCall = false;
+
+  // Fall back to regular, page aligned, allocation if necessary
+  if (!mem)
+      mem = VirtualAlloc(NULL, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
+
+  return mem;
+}
+
+#else
+
+void* aligned_ttmem_alloc(size_t allocSize, void*& mem) {
+
+  constexpr size_t alignment = 64; // assumed cache line size
+  size_t size = allocSize + alignment - 1; // allocate some extra space
+  mem = malloc(size);
+  void* ret = reinterpret_cast<void*>((uintptr_t(mem) + alignment - 1) & ~uintptr_t(alignment - 1));
+  return ret;
+}
+
+#endif
+
+
+/// aligned_ttmem_free() will free the previously allocated ttmem
+
+#if defined(_WIN64)
+
+void aligned_ttmem_free(void* mem) {
+
+  if (mem && !VirtualFree(mem, 0, MEM_RELEASE))
+  {
+      DWORD err = GetLastError();
+      std::cerr << "Failed to free transposition table. Error code: 0x" <<
+          std::hex << err << std::dec << std::endl;
+      exit(EXIT_FAILURE);
+  }
+}
+
+#else
+
+void aligned_ttmem_free(void *mem) {
+  free(mem);
+}
+
+#endif
+
+
+namespace WinProcGroup {
+
+#ifndef _WIN32
+
+void bindThisThread(size_t) {}
+
+#else
+
+/// best_group() retrieves logical processor information using Windows specific
+/// API and returns the best group id for the thread with index idx. Original
+/// code from Texel by Peter Österlund.
+
+int best_group(size_t idx) {
+
+  int threads = 0;
+  int nodes = 0;
+  int cores = 0;
+  DWORD returnLength = 0;
+  DWORD byteOffset = 0;
+
+  // Early exit if the needed API is not available at runtime
+  HMODULE k32 = GetModuleHandle("Kernel32.dll");
+  auto fun1 = (fun1_t)(void(*)())GetProcAddress(k32, "GetLogicalProcessorInformationEx");
+  if (!fun1)
+      return -1;
+
+  // First call to get returnLength. We expect it to fail due to null buffer
+  if (fun1(RelationAll, nullptr, &returnLength))
+      return -1;
+
+  // Once we know returnLength, allocate the buffer
+  SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *buffer, *ptr;
+  ptr = buffer = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)malloc(returnLength);
+
+  // Second call, now we expect to succeed
+  if (!fun1(RelationAll, buffer, &returnLength))
+  {
+      free(buffer);
+      return -1;
+  }
+
+  while (byteOffset < returnLength)
+  {
+      if (ptr->Relationship == RelationNumaNode)
+          nodes++;
+
+      else if (ptr->Relationship == RelationProcessorCore)
+      {
+          cores++;
+          threads += (ptr->Processor.Flags == LTP_PC_SMT) ? 2 : 1;
+      }
+
+      assert(ptr->Size);
+      byteOffset += ptr->Size;
+      ptr = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)(((char*)ptr) + ptr->Size);
+  }
+
+  free(buffer);
+
+  std::vector<int> groups;
+
+  // Run as many threads as possible on the same node until core limit is
+  // reached, then move on filling the next node.
+  for (int n = 0; n < nodes; n++)
+      for (int i = 0; i < cores / nodes; i++)
+          groups.push_back(n);
+
+  // In case a core has more than one logical processor (we assume 2) and we
+  // have still threads to allocate, then spread them evenly across available
+  // nodes.
+  for (int t = 0; t < threads - cores; t++)
+      groups.push_back(t % nodes);
+
+  // If we still have more threads than the total number of logical processors
+  // then return -1 and let the OS to decide what to do.
+  return idx < groups.size() ? groups[idx] : -1;
+}
+
+
+/// bindThisThread() set the group affinity of the current thread
+
+void bindThisThread(size_t idx) {
+
+  // Use only local variables to be thread-safe
+  int group = best_group(idx);
+
+  if (group == -1)
+      return;
+
+  // Early exit if the needed API are not available at runtime
+  HMODULE k32 = GetModuleHandle("Kernel32.dll");
+  auto fun2 = (fun2_t)(void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMaskEx");
+  auto fun3 = (fun3_t)(void(*)())GetProcAddress(k32, "SetThreadGroupAffinity");
+
+  if (!fun2 || !fun3)
+      return;
+
+  GROUP_AFFINITY affinity;
+  if (fun2(group, &affinity))
+      fun3(GetCurrentThread(), &affinity, nullptr);
+}
+
+#endif
+
+} // namespace WinProcGroup
+
+#ifdef _WIN32
+#include <direct.h>
+#define GETCWD _getcwd
+#else
+#include <unistd.h>
+#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
+string pathSeparator;    // Separator for our current OS
+
+void init(int argc, char* argv[]) {
+    (void)argc;
+    string separator;
+
+    // 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
diff --git a/src/misc.h b/src/misc.h
new file mode 100644 (file)
index 0000000..68b9c88
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef MISC_H_INCLUDED
+#define MISC_H_INCLUDED
+
+#include <cassert>
+#include <chrono>
+#include <ostream>
+#include <string>
+#include <vector>
+
+#include "types.h"
+
+const std::string engine_info(bool to_uci = false);
+const 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_ttmem_alloc(size_t size, void*& mem);
+void aligned_ttmem_free(void* mem); // nop if mem == nullptr
+
+void dbg_hit_on(bool b);
+void dbg_hit_on(bool c, bool b);
+void dbg_mean_of(int v);
+void dbg_print();
+
+typedef std::chrono::milliseconds::rep TimePoint; // A value in milliseconds
+static_assert(sizeof(TimePoint) == sizeof(int64_t), "TimePoint should be 64 bits");
+inline TimePoint now() {
+  return std::chrono::duration_cast<std::chrono::milliseconds>
+        (std::chrono::steady_clock::now().time_since_epoch()).count();
+}
+
+template<class Entry, int Size>
+struct HashTable {
+  Entry* operator[](Key key) { return &table[(uint32_t)key & (Size - 1)]; }
+
+private:
+  std::vector<Entry> table = std::vector<Entry>(Size); // Allocate on the heap
+};
+
+
+enum SyncCout { IO_LOCK, IO_UNLOCK };
+std::ostream& operator<<(std::ostream&, SyncCout);
+
+#define sync_cout std::cout << IO_LOCK
+#define sync_endl std::endl << IO_UNLOCK
+
+
+/// xorshift64star Pseudo-Random Number Generator
+/// This class is based on original code written and dedicated
+/// to the public domain by Sebastiano Vigna (2014).
+/// It has the following characteristics:
+///
+///  -  Outputs 64-bit numbers
+///  -  Passes Dieharder and SmallCrush test batteries
+///  -  Does not require warm-up, no zeroland to escape
+///  -  Internal state is a single 64-bit integer
+///  -  Period is 2^64 - 1
+///  -  Speed: 1.60 ns/call (Core i7 @3.40GHz)
+///
+/// For further analysis see
+///   <http://vigna.di.unimi.it/ftp/papers/xorshift.pdf>
+
+class PRNG {
+
+  uint64_t s;
+
+  uint64_t rand64() {
+
+    s ^= s >> 12, s ^= s << 25, s ^= s >> 27;
+    return s * 2685821657736338717LL;
+  }
+
+public:
+  PRNG(uint64_t seed) : s(seed) { assert(seed); }
+
+  template<typename T> T rand() { return T(rand64()); }
+
+  /// Special generator used to fast init magic numbers.
+  /// Output values only have 1/8th of their bits set on average.
+  template<typename T> T sparse_rand()
+  { return T(rand64() & rand64() & rand64()); }
+};
+
+inline uint64_t mul_hi64(uint64_t a, uint64_t b) {
+#if defined(__GNUC__) && defined(IS_64BIT)
+    __extension__ typedef unsigned __int128 uint128;
+    return ((uint128)a * (uint128)b) >> 64;
+#else
+    uint64_t aL = (uint32_t)a, aH = a >> 32;
+    uint64_t bL = (uint32_t)b, bH = b >> 32;
+    uint64_t c1 = (aL * bL) >> 32;
+    uint64_t c2 = aH * bL + c1;
+    uint64_t c3 = aL * bH + (uint32_t)c2;
+    return aH * bH + (c2 >> 32) + (c3 >> 32);
+#endif
+}
+
+/// Under Windows it is not possible for a process to run on more than one
+/// logical processor group. This usually means to be limited to use max 64
+/// cores. To overcome this, some special platform specific API should be
+/// called to set group affinity for each thread. Original code from Texel by
+/// Peter Österlund.
+
+namespace WinProcGroup {
+  void bindThisThread(size_t idx);
+}
+
+namespace CommandLine {
+  void init(int argc, char* argv[]);
+
+  extern std::string binaryDirectory;  // path of the executable directory
+  extern std::string workingDirectory; // path of the working directory
+}
+
+#endif // #ifndef MISC_H_INCLUDED
diff --git a/src/movegen.cpp b/src/movegen.cpp
new file mode 100644 (file)
index 0000000..3340f65
--- /dev/null
@@ -0,0 +1,367 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <cassert>
+
+#include "movegen.h"
+#include "position.h"
+
+namespace {
+
+  template<GenType Type, Direction D>
+  ExtMove* make_promotions(ExtMove* moveList, Square to, Square ksq) {
+
+    if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
+    {
+        *moveList++ = make<PROMOTION>(to - D, to, QUEEN);
+        if (attacks_bb<KNIGHT>(to) & ksq)
+            *moveList++ = make<PROMOTION>(to - D, to, KNIGHT);
+    }
+
+    if (Type == QUIETS || Type == EVASIONS || Type == NON_EVASIONS)
+    {
+        *moveList++ = make<PROMOTION>(to - D, to, ROOK);
+        *moveList++ = make<PROMOTION>(to - D, to, BISHOP);
+        if (!(attacks_bb<KNIGHT>(to) & ksq))
+            *moveList++ = make<PROMOTION>(to - D, to, KNIGHT);
+    }
+
+    return moveList;
+  }
+
+
+  template<Color Us, GenType Type>
+  ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard target) {
+
+    constexpr Color     Them     = ~Us;
+    constexpr Bitboard  TRank7BB = (Us == WHITE ? Rank7BB    : Rank2BB);
+    constexpr Bitboard  TRank3BB = (Us == WHITE ? Rank3BB    : Rank6BB);
+    constexpr Direction Up       = pawn_push(Us);
+    constexpr Direction UpRight  = (Us == WHITE ? NORTH_EAST : SOUTH_WEST);
+    constexpr Direction UpLeft   = (Us == WHITE ? NORTH_WEST : SOUTH_EAST);
+
+    const Square ksq = pos.square<KING>(Them);
+    Bitboard emptySquares;
+
+    Bitboard pawnsOn7    = pos.pieces(Us, PAWN) &  TRank7BB;
+    Bitboard pawnsNotOn7 = pos.pieces(Us, PAWN) & ~TRank7BB;
+
+    Bitboard enemies = (Type == EVASIONS ? pos.pieces(Them) & target:
+                        Type == CAPTURES ? target : pos.pieces(Them));
+
+    // Single and double pawn pushes, no promotions
+    if (Type != CAPTURES)
+    {
+        emptySquares = (Type == QUIETS || Type == QUIET_CHECKS ? target : ~pos.pieces());
+
+        Bitboard b1 = shift<Up>(pawnsNotOn7)   & emptySquares;
+        Bitboard b2 = shift<Up>(b1 & TRank3BB) & emptySquares;
+
+        if (Type == EVASIONS) // Consider only blocking squares
+        {
+            b1 &= target;
+            b2 &= target;
+        }
+
+        if (Type == QUIET_CHECKS)
+        {
+            b1 &= pawn_attacks_bb(Them, ksq);
+            b2 &= pawn_attacks_bb(Them, ksq);
+
+            // Add pawn pushes which give discovered check. This is possible only
+            // if the pawn is not on the same file as the enemy king, because we
+            // don't generate captures. Note that a possible discovery check
+            // promotion has been already generated amongst the captures.
+            Bitboard dcCandidateQuiets = pos.blockers_for_king(Them) & pawnsNotOn7;
+            if (dcCandidateQuiets)
+            {
+                Bitboard dc1 = shift<Up>(dcCandidateQuiets) & emptySquares & ~file_bb(ksq);
+                Bitboard dc2 = shift<Up>(dc1 & TRank3BB) & emptySquares;
+
+                b1 |= dc1;
+                b2 |= dc2;
+            }
+        }
+
+        while (b1)
+        {
+            Square to = pop_lsb(&b1);
+            *moveList++ = make_move(to - Up, to);
+        }
+
+        while (b2)
+        {
+            Square to = pop_lsb(&b2);
+            *moveList++ = make_move(to - Up - Up, to);
+        }
+    }
+
+    // Promotions and underpromotions
+    if (pawnsOn7)
+    {
+        if (Type == CAPTURES)
+            emptySquares = ~pos.pieces();
+
+        if (Type == EVASIONS)
+            emptySquares &= target;
+
+        Bitboard b1 = shift<UpRight>(pawnsOn7) & enemies;
+        Bitboard b2 = shift<UpLeft >(pawnsOn7) & enemies;
+        Bitboard b3 = shift<Up     >(pawnsOn7) & emptySquares;
+
+        while (b1)
+            moveList = make_promotions<Type, UpRight>(moveList, pop_lsb(&b1), ksq);
+
+        while (b2)
+            moveList = make_promotions<Type, UpLeft >(moveList, pop_lsb(&b2), ksq);
+
+        while (b3)
+            moveList = make_promotions<Type, Up     >(moveList, pop_lsb(&b3), ksq);
+    }
+
+    // Standard and en-passant captures
+    if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
+    {
+        Bitboard b1 = shift<UpRight>(pawnsNotOn7) & enemies;
+        Bitboard b2 = shift<UpLeft >(pawnsNotOn7) & enemies;
+
+        while (b1)
+        {
+            Square to = pop_lsb(&b1);
+            *moveList++ = make_move(to - UpRight, to);
+        }
+
+        while (b2)
+        {
+            Square to = pop_lsb(&b2);
+            *moveList++ = make_move(to - UpLeft, to);
+        }
+
+        if (pos.ep_square() != SQ_NONE)
+        {
+            assert(rank_of(pos.ep_square()) == relative_rank(Us, RANK_6));
+
+            // An en passant capture can be an evasion only if the checking piece
+            // is the double pushed pawn and so is in the target. Otherwise this
+            // is a discovery check and we are forced to do otherwise.
+            if (Type == EVASIONS && !(target & (pos.ep_square() - Up)))
+                return moveList;
+
+            b1 = pawnsNotOn7 & pawn_attacks_bb(Them, pos.ep_square());
+
+            assert(b1);
+
+            while (b1)
+                *moveList++ = make<ENPASSANT>(pop_lsb(&b1), pos.ep_square());
+        }
+    }
+
+    return moveList;
+  }
+
+
+  template<Color Us, PieceType Pt, bool Checks>
+  ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target) {
+
+    static_assert(Pt != KING && Pt != PAWN, "Unsupported piece type in generate_moves()");
+
+    const Square* pl = pos.squares<Pt>(Us);
+
+    for (Square from = *pl; from != SQ_NONE; from = *++pl)
+    {
+        if (Checks)
+        {
+            if (    (Pt == BISHOP || Pt == ROOK || Pt == QUEEN)
+                && !(attacks_bb<Pt>(from) & target & pos.check_squares(Pt)))
+                continue;
+
+            if (pos.blockers_for_king(~Us) & from)
+                continue;
+        }
+
+        Bitboard b = attacks_bb<Pt>(from, pos.pieces()) & target;
+
+        if (Checks)
+            b &= pos.check_squares(Pt);
+
+        while (b)
+            *moveList++ = make_move(from, pop_lsb(&b));
+    }
+
+    return moveList;
+  }
+
+
+  template<Color Us, GenType Type>
+  ExtMove* generate_all(const Position& pos, ExtMove* moveList) {
+    constexpr bool Checks = Type == QUIET_CHECKS; // Reduce template instantations
+    Bitboard target;
+
+    switch (Type)
+    {
+        case CAPTURES:
+            target =  pos.pieces(~Us);
+            break;
+        case QUIETS:
+        case QUIET_CHECKS:
+            target = ~pos.pieces();
+            break;
+        case EVASIONS:
+        {
+            Square checksq = lsb(pos.checkers());
+            target = between_bb(pos.square<KING>(Us), checksq) | checksq;
+            break;
+        }
+        case NON_EVASIONS:
+            target = ~pos.pieces(Us);
+            break;
+        default:
+            static_assert(true, "Unsupported type in generate_all()");
+    }
+
+    moveList = generate_pawn_moves<Us, Type>(pos, moveList, target);
+    moveList = generate_moves<Us, KNIGHT, Checks>(pos, moveList, target);
+    moveList = generate_moves<Us, BISHOP, Checks>(pos, moveList, target);
+    moveList = generate_moves<Us,   ROOK, Checks>(pos, moveList, target);
+    moveList = generate_moves<Us,  QUEEN, Checks>(pos, moveList, target);
+
+    if (Type != QUIET_CHECKS && Type != EVASIONS)
+    {
+        Square ksq = pos.square<KING>(Us);
+        Bitboard b = attacks_bb<KING>(ksq) & target;
+        while (b)
+            *moveList++ = make_move(ksq, pop_lsb(&b));
+
+        if ((Type != CAPTURES) && pos.can_castle(Us & ANY_CASTLING))
+            for (CastlingRights cr : { Us & KING_SIDE, Us & QUEEN_SIDE } )
+                if (!pos.castling_impeded(cr) && pos.can_castle(cr))
+                    *moveList++ = make<CASTLING>(ksq, pos.castling_rook_square(cr));
+    }
+
+    return moveList;
+  }
+
+} // namespace
+
+
+/// <CAPTURES>     Generates all pseudo-legal captures plus queen and checking knight promotions
+/// <QUIETS>       Generates all pseudo-legal non-captures and underpromotions(except checking knight)
+/// <NON_EVASIONS> Generates all pseudo-legal captures and non-captures
+///
+/// Returns a pointer to the end of the move list.
+
+template<GenType Type>
+ExtMove* generate(const Position& pos, ExtMove* moveList) {
+
+  static_assert(Type == CAPTURES || Type == QUIETS || Type == NON_EVASIONS, "Unsupported type in generate()");
+  assert(!pos.checkers());
+
+  Color us = pos.side_to_move();
+
+  return us == WHITE ? generate_all<WHITE, Type>(pos, moveList)
+                     : generate_all<BLACK, Type>(pos, moveList);
+}
+
+// Explicit template instantiations
+template ExtMove* generate<CAPTURES>(const Position&, ExtMove*);
+template ExtMove* generate<QUIETS>(const Position&, ExtMove*);
+template ExtMove* generate<NON_EVASIONS>(const Position&, ExtMove*);
+
+
+/// generate<QUIET_CHECKS> generates all pseudo-legal non-captures.
+/// Returns a pointer to the end of the move list.
+template<>
+ExtMove* generate<QUIET_CHECKS>(const Position& pos, ExtMove* moveList) {
+
+  assert(!pos.checkers());
+
+  Color us = pos.side_to_move();
+  Bitboard dc = pos.blockers_for_king(~us) & pos.pieces(us) & ~pos.pieces(PAWN);
+
+  while (dc)
+  {
+     Square from = pop_lsb(&dc);
+     PieceType pt = type_of(pos.piece_on(from));
+
+     Bitboard b = attacks_bb(pt, from, pos.pieces()) & ~pos.pieces();
+
+     if (pt == KING)
+         b &= ~attacks_bb<QUEEN>(pos.square<KING>(~us));
+
+     while (b)
+         *moveList++ = make_move(from, pop_lsb(&b));
+  }
+
+  return us == WHITE ? generate_all<WHITE, QUIET_CHECKS>(pos, moveList)
+                     : generate_all<BLACK, QUIET_CHECKS>(pos, moveList);
+}
+
+
+/// generate<EVASIONS> generates all pseudo-legal check evasions when the side
+/// to move is in check. Returns a pointer to the end of the move list.
+template<>
+ExtMove* generate<EVASIONS>(const Position& pos, ExtMove* moveList) {
+
+  assert(pos.checkers());
+
+  Color us = pos.side_to_move();
+  Square ksq = pos.square<KING>(us);
+  Bitboard sliderAttacks = 0;
+  Bitboard sliders = pos.checkers() & ~pos.pieces(KNIGHT, PAWN);
+
+  // Find all the squares attacked by slider checkers. We will remove them from
+  // the king evasions in order to skip known illegal moves, which avoids any
+  // useless legality checks later on.
+  while (sliders)
+      sliderAttacks |= line_bb(ksq, pop_lsb(&sliders)) & ~pos.checkers();
+
+  // Generate evasions for king, capture and non capture moves
+  Bitboard b = attacks_bb<KING>(ksq) & ~pos.pieces(us) & ~sliderAttacks;
+  while (b)
+      *moveList++ = make_move(ksq, pop_lsb(&b));
+
+  if (more_than_one(pos.checkers()))
+      return moveList; // Double check, only a king move can save the day
+
+  // Generate blocking evasions or captures of the checking piece
+  return us == WHITE ? generate_all<WHITE, EVASIONS>(pos, moveList)
+                     : generate_all<BLACK, EVASIONS>(pos, moveList);
+}
+
+
+/// generate<LEGAL> generates all the legal moves in the given position
+
+template<>
+ExtMove* generate<LEGAL>(const Position& pos, ExtMove* moveList) {
+
+  Color us = pos.side_to_move();
+  Bitboard pinned = pos.blockers_for_king(us) & pos.pieces(us);
+  Square ksq = pos.square<KING>(us);
+  ExtMove* cur = moveList;
+
+  moveList = pos.checkers() ? generate<EVASIONS    >(pos, moveList)
+                            : generate<NON_EVASIONS>(pos, moveList);
+  while (cur != moveList)
+      if (   (pinned || from_sq(*cur) == ksq || type_of(*cur) == ENPASSANT)
+          && !pos.legal(*cur))
+          *cur = (--moveList)->move;
+      else
+          ++cur;
+
+  return moveList;
+}
diff --git a/src/movegen.h b/src/movegen.h
new file mode 100644 (file)
index 0000000..fb616d0
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef MOVEGEN_H_INCLUDED
+#define MOVEGEN_H_INCLUDED
+
+#include <algorithm>
+
+#include "types.h"
+
+class Position;
+
+enum GenType {
+  CAPTURES,
+  QUIETS,
+  QUIET_CHECKS,
+  EVASIONS,
+  NON_EVASIONS,
+  LEGAL
+};
+
+struct ExtMove {
+  Move move;
+  int value;
+
+  operator Move() const { return move; }
+  void operator=(Move m) { move = m; }
+
+  // Inhibit unwanted implicit conversions to Move
+  // with an ambiguity that yields to a compile error.
+  operator float() const = delete;
+};
+
+inline bool operator<(const ExtMove& f, const ExtMove& s) {
+  return f.value < s.value;
+}
+
+template<GenType>
+ExtMove* generate(const Position& pos, ExtMove* moveList);
+
+/// The MoveList struct is a simple wrapper around generate(). It sometimes comes
+/// in handy to use this class instead of the low level generate() function.
+template<GenType T>
+struct MoveList {
+
+  explicit MoveList(const Position& pos) : last(generate<T>(pos, moveList)) {}
+  const ExtMove* begin() const { return moveList; }
+  const ExtMove* end() const { return last; }
+  size_t size() const { return last - moveList; }
+  bool contains(Move move) const {
+    return std::find(begin(), end(), move) != end();
+  }
+
+private:
+  ExtMove moveList[MAX_MOVES], *last;
+};
+
+#endif // #ifndef MOVEGEN_H_INCLUDED
diff --git a/src/movepick.cpp b/src/movepick.cpp
new file mode 100644 (file)
index 0000000..153d323
--- /dev/null
@@ -0,0 +1,264 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <cassert>
+
+#include "movepick.h"
+
+namespace {
+
+  enum Stages {
+    MAIN_TT, CAPTURE_INIT, GOOD_CAPTURE, REFUTATION, QUIET_INIT, QUIET, BAD_CAPTURE,
+    EVASION_TT, EVASION_INIT, EVASION,
+    PROBCUT_TT, PROBCUT_INIT, PROBCUT,
+    QSEARCH_TT, QCAPTURE_INIT, QCAPTURE, QCHECK_INIT, QCHECK
+  };
+
+  // partial_insertion_sort() sorts moves in descending order up to and including
+  // a given limit. The order of moves smaller than the limit is left unspecified.
+  void partial_insertion_sort(ExtMove* begin, ExtMove* end, int limit) {
+
+    for (ExtMove *sortedEnd = begin, *p = begin + 1; p < end; ++p)
+        if (p->value >= limit)
+        {
+            ExtMove tmp = *p, *q;
+            *p = *++sortedEnd;
+            for (q = sortedEnd; q != begin && *(q - 1) < tmp; --q)
+                *q = *(q - 1);
+            *q = tmp;
+        }
+  }
+
+} // namespace
+
+
+/// Constructors of the MovePicker class. As arguments we pass information
+/// to help it to return the (presumably) good moves first, to decide which
+/// moves to return (in the quiescence search, for instance, we only want to
+/// search captures, promotions, and some checks) and how important good move
+/// ordering is at the current node.
+
+/// MovePicker constructor for the main search
+MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, const LowPlyHistory* lp,
+                       const CapturePieceToHistory* cph, const PieceToHistory** ch, Move cm, const Move* killers, int pl)
+           : pos(p), mainHistory(mh), lowPlyHistory(lp), captureHistory(cph), continuationHistory(ch),
+             ttMove(ttm), refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}}, depth(d), ply(pl) {
+
+  assert(d > 0);
+
+  stage = (pos.checkers() ? EVASION_TT : MAIN_TT) +
+          !(ttm && pos.pseudo_legal(ttm));
+}
+
+/// MovePicker constructor for quiescence search
+MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh,
+                       const CapturePieceToHistory* cph, const PieceToHistory** ch, Square rs)
+           : pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch), ttMove(ttm), recaptureSquare(rs), depth(d) {
+
+  assert(d <= 0);
+
+  stage = (pos.checkers() ? EVASION_TT : QSEARCH_TT) +
+           !(ttm && (depth > DEPTH_QS_RECAPTURES || to_sq(ttm) == recaptureSquare)
+                 && pos.pseudo_legal(ttm));
+}
+
+/// MovePicker constructor for ProbCut: we generate captures with SEE greater
+/// than or equal to the given threshold.
+MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph)
+           : pos(p), captureHistory(cph), ttMove(ttm), threshold(th) {
+
+  assert(!pos.checkers());
+
+  stage = PROBCUT_TT + !(ttm && pos.capture(ttm)
+                             && pos.pseudo_legal(ttm)
+                             && pos.see_ge(ttm, threshold));
+}
+
+/// MovePicker::score() assigns a numerical value to each move in a list, used
+/// for sorting. Captures are ordered by Most Valuable Victim (MVV), preferring
+/// captures with a good history. Quiets moves are ordered using the histories.
+template<GenType Type>
+void MovePicker::score() {
+
+  static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type");
+
+  for (auto& m : *this)
+      if (Type == CAPTURES)
+          m.value =  int(PieceValue[MG][pos.piece_on(to_sq(m))]) * 6
+                   + (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))];
+
+      else if (Type == QUIETS)
+          m.value =      (*mainHistory)[pos.side_to_move()][from_to(m)]
+                   + 2 * (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)]
+                   + 2 * (*continuationHistory[1])[pos.moved_piece(m)][to_sq(m)]
+                   + 2 * (*continuationHistory[3])[pos.moved_piece(m)][to_sq(m)]
+                   +     (*continuationHistory[5])[pos.moved_piece(m)][to_sq(m)]
+                   + (ply < MAX_LPH ? std::min(4, depth / 3) * (*lowPlyHistory)[ply][from_to(m)] : 0);
+
+      else // Type == EVASIONS
+      {
+          if (pos.capture(m))
+              m.value =  PieceValue[MG][pos.piece_on(to_sq(m))]
+                       - Value(type_of(pos.moved_piece(m)));
+          else
+              m.value =  (*mainHistory)[pos.side_to_move()][from_to(m)]
+                       + (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)]
+                       - (1 << 28);
+      }
+}
+
+/// MovePicker::select() returns the next move satisfying a predicate function.
+/// It never returns the TT move.
+template<MovePicker::PickType T, typename Pred>
+Move MovePicker::select(Pred filter) {
+
+  while (cur < endMoves)
+  {
+      if (T == Best)
+          std::swap(*cur, *std::max_element(cur, endMoves));
+
+      if (*cur != ttMove && filter())
+          return *cur++;
+
+      cur++;
+  }
+  return MOVE_NONE;
+}
+
+/// MovePicker::next_move() is the most important method of the MovePicker class. It
+/// returns a new pseudo legal move every time it is called until there are no more
+/// moves left, picking the move with the highest score from a list of generated moves.
+Move MovePicker::next_move(bool skipQuiets) {
+
+top:
+  switch (stage) {
+
+  case MAIN_TT:
+  case EVASION_TT:
+  case QSEARCH_TT:
+  case PROBCUT_TT:
+      ++stage;
+      return ttMove;
+
+  case CAPTURE_INIT:
+  case PROBCUT_INIT:
+  case QCAPTURE_INIT:
+      cur = endBadCaptures = moves;
+      endMoves = generate<CAPTURES>(pos, cur);
+
+      score<CAPTURES>();
+      ++stage;
+      goto top;
+
+  case GOOD_CAPTURE:
+      if (select<Best>([&](){
+                       return pos.see_ge(*cur, Value(-69 * cur->value / 1024)) ?
+                              // Move losing capture to endBadCaptures to be tried later
+                              true : (*endBadCaptures++ = *cur, false); }))
+          return *(cur - 1);
+
+      // Prepare the pointers to loop over the refutations array
+      cur = std::begin(refutations);
+      endMoves = std::end(refutations);
+
+      // If the countermove is the same as a killer, skip it
+      if (   refutations[0].move == refutations[2].move
+          || refutations[1].move == refutations[2].move)
+          --endMoves;
+
+      ++stage;
+      [[fallthrough]];
+
+  case REFUTATION:
+      if (select<Next>([&](){ return    *cur != MOVE_NONE
+                                    && !pos.capture(*cur)
+                                    &&  pos.pseudo_legal(*cur); }))
+          return *(cur - 1);
+      ++stage;
+      [[fallthrough]];
+
+  case QUIET_INIT:
+      if (!skipQuiets)
+      {
+          cur = endBadCaptures;
+          endMoves = generate<QUIETS>(pos, cur);
+
+          score<QUIETS>();
+          partial_insertion_sort(cur, endMoves, -3000 * depth);
+      }
+
+      ++stage;
+      [[fallthrough]];
+
+  case QUIET:
+      if (   !skipQuiets
+          && select<Next>([&](){return   *cur != refutations[0].move
+                                      && *cur != refutations[1].move
+                                      && *cur != refutations[2].move;}))
+          return *(cur - 1);
+
+      // Prepare the pointers to loop over the bad captures
+      cur = moves;
+      endMoves = endBadCaptures;
+
+      ++stage;
+      [[fallthrough]];
+
+  case BAD_CAPTURE:
+      return select<Next>([](){ return true; });
+
+  case EVASION_INIT:
+      cur = moves;
+      endMoves = generate<EVASIONS>(pos, cur);
+
+      score<EVASIONS>();
+      ++stage;
+      [[fallthrough]];
+
+  case EVASION:
+      return select<Best>([](){ return true; });
+
+  case PROBCUT:
+      return select<Best>([&](){ return pos.see_ge(*cur, threshold); });
+
+  case QCAPTURE:
+      if (select<Best>([&](){ return   depth > DEPTH_QS_RECAPTURES
+                                    || to_sq(*cur) == recaptureSquare; }))
+          return *(cur - 1);
+
+      // If we did not find any move and we do not try checks, we have finished
+      if (depth != DEPTH_QS_CHECKS)
+          return MOVE_NONE;
+
+      ++stage;
+      [[fallthrough]];
+
+  case QCHECK_INIT:
+      cur = moves;
+      endMoves = generate<QUIET_CHECKS>(pos, cur);
+
+      ++stage;
+      [[fallthrough]];
+
+  case QCHECK:
+      return select<Next>([](){ return true; });
+  }
+
+  assert(false);
+  return MOVE_NONE; // Silence warning
+}
diff --git a/src/movepick.h b/src/movepick.h
new file mode 100644 (file)
index 0000000..4c0ad55
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef MOVEPICK_H_INCLUDED
+#define MOVEPICK_H_INCLUDED
+
+#include <array>
+#include <limits>
+#include <type_traits>
+
+#include "movegen.h"
+#include "position.h"
+#include "types.h"
+
+/// StatsEntry stores the stat table value. It is usually a number but could
+/// be a move or even a nested history. We use a class instead of naked value
+/// to directly call history update operator<<() on the entry so to use stats
+/// tables at caller sites as simple multi-dim arrays.
+template<typename T, int D>
+class StatsEntry {
+
+  T entry;
+
+public:
+  void operator=(const T& v) { entry = v; }
+  T* operator&() { return &entry; }
+  T* operator->() { return &entry; }
+  operator const T&() const { return entry; }
+
+  void operator<<(int bonus) {
+    assert(abs(bonus) <= D); // Ensure range is [-D, D]
+    static_assert(D <= std::numeric_limits<T>::max(), "D overflows T");
+
+    entry += bonus - entry * abs(bonus) / D;
+
+    assert(abs(entry) <= D);
+  }
+};
+
+/// Stats is a generic N-dimensional array used to store various statistics.
+/// The first template parameter T is the base type of the array, the second
+/// template parameter D limits the range of updates in [-D, D] when we update
+/// values with the << operator, while the last parameters (Size and Sizes)
+/// encode the dimensions of the array.
+template <typename T, int D, int Size, int... Sizes>
+struct Stats : public std::array<Stats<T, D, Sizes...>, Size>
+{
+  typedef Stats<T, D, Size, Sizes...> stats;
+
+  void fill(const T& v) {
+
+    // For standard-layout 'this' points to first struct member
+    assert(std::is_standard_layout<stats>::value);
+
+    typedef StatsEntry<T, D> entry;
+    entry* p = reinterpret_cast<entry*>(this);
+    std::fill(p, p + sizeof(*this) / sizeof(entry), v);
+  }
+};
+
+template <typename T, int D, int Size>
+struct Stats<T, D, Size> : public std::array<StatsEntry<T, D>, Size> {};
+
+/// In stats table, D=0 means that the template parameter is not used
+enum StatsParams { NOT_USED = 0 };
+enum StatsType { NoCaptures, Captures };
+
+/// ButterflyHistory records how often quiet moves have been successful or
+/// unsuccessful during the current search, and is used for reduction and move
+/// ordering decisions. It uses 2 tables (one for each color) indexed by
+/// the move's from and to squares, see www.chessprogramming.org/Butterfly_Boards
+typedef Stats<int16_t, 10692, COLOR_NB, int(SQUARE_NB) * int(SQUARE_NB)> ButterflyHistory;
+
+/// At higher depths LowPlyHistory records successful quiet moves near the root
+/// and quiet moves which are/were in the PV (ttPv). It is cleared with each new
+/// search and filled during iterative deepening.
+constexpr int MAX_LPH = 4;
+typedef Stats<int16_t, 10692, MAX_LPH, int(SQUARE_NB) * int(SQUARE_NB)> LowPlyHistory;
+
+/// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous
+/// move, see www.chessprogramming.org/Countermove_Heuristic
+typedef Stats<Move, NOT_USED, PIECE_NB, SQUARE_NB> CounterMoveHistory;
+
+/// CapturePieceToHistory is addressed by a move's [piece][to][captured piece type]
+typedef Stats<int16_t, 10692, PIECE_NB, SQUARE_NB, PIECE_TYPE_NB> CapturePieceToHistory;
+
+/// PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to]
+typedef Stats<int16_t, 29952, PIECE_NB, SQUARE_NB> PieceToHistory;
+
+/// ContinuationHistory is the combined history of a given pair of moves, usually
+/// the current one given a previous one. The nested history table is based on
+/// PieceToHistory instead of ButterflyBoards.
+typedef Stats<PieceToHistory, NOT_USED, PIECE_NB, SQUARE_NB> ContinuationHistory;
+
+
+/// MovePicker class is used to pick one pseudo legal move at a time from the
+/// current position. The most important method is next_move(), which returns a
+/// new pseudo legal move each time it is called, until there are no moves left,
+/// when MOVE_NONE is returned. In order to improve the efficiency of the alpha
+/// beta algorithm, MovePicker attempts to return the moves which are most likely
+/// to get a cut-off first.
+class MovePicker {
+
+  enum PickType { Next, Best };
+
+public:
+  MovePicker(const MovePicker&) = delete;
+  MovePicker& operator=(const MovePicker&) = delete;
+  MovePicker(const Position&, Move, Value, const CapturePieceToHistory*);
+  MovePicker(const Position&, Move, Depth, const ButterflyHistory*,
+                                           const CapturePieceToHistory*,
+                                           const PieceToHistory**,
+                                           Square);
+  MovePicker(const Position&, Move, Depth, const ButterflyHistory*,
+                                           const LowPlyHistory*,
+                                           const CapturePieceToHistory*,
+                                           const PieceToHistory**,
+                                           Move,
+                                           const Move*,
+                                           int);
+  Move next_move(bool skipQuiets = false);
+
+private:
+  template<PickType T, typename Pred> Move select(Pred);
+  template<GenType> void score();
+  ExtMove* begin() { return cur; }
+  ExtMove* end() { return endMoves; }
+
+  const Position& pos;
+  const ButterflyHistory* mainHistory;
+  const LowPlyHistory* lowPlyHistory;
+  const CapturePieceToHistory* captureHistory;
+  const PieceToHistory** continuationHistory;
+  Move ttMove;
+  ExtMove refutations[3], *cur, *endMoves, *endBadCaptures;
+  int stage;
+  Square recaptureSquare;
+  Value threshold;
+  Depth depth;
+  int ply;
+  ExtMove moves[MAX_MOVES];
+};
+
+#endif // #ifndef MOVEPICK_H_INCLUDED
diff --git a/src/nn-82215d0fd0df.nnue b/src/nn-82215d0fd0df.nnue
new file mode 100644 (file)
index 0000000..d010366
Binary files /dev/null and b/src/nn-82215d0fd0df.nnue differ
diff --git a/src/nnue/architectures/halfkp_256x2-32-32.h b/src/nnue/architectures/halfkp_256x2-32-32.h
new file mode 100644 (file)
index 0000000..9216bd4
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// Definition of input features and network structure used in NNUE evaluation function
+
+#ifndef NNUE_HALFKP_256X2_32_32_H_INCLUDED
+#define NNUE_HALFKP_256X2_32_32_H_INCLUDED
+
+#include "../features/feature_set.h"
+#include "../features/half_kp.h"
+
+#include "../layers/input_slice.h"
+#include "../layers/affine_transform.h"
+#include "../layers/clipped_relu.h"
+
+namespace Eval::NNUE {
+
+// Input features used in evaluation function
+using RawFeatures = Features::FeatureSet<
+    Features::HalfKP<Features::Side::kFriend>>;
+
+// Number of input feature dimensions after conversion
+constexpr IndexType kTransformedFeatureDimensions = 256;
+
+namespace Layers {
+
+// Define network structure
+using InputLayer = InputSlice<kTransformedFeatureDimensions * 2>;
+using HiddenLayer1 = ClippedReLU<AffineTransform<InputLayer, 32>>;
+using HiddenLayer2 = ClippedReLU<AffineTransform<HiddenLayer1, 32>>;
+using OutputLayer = AffineTransform<HiddenLayer2, 1>;
+
+}  // namespace Layers
+
+using Network = Layers::OutputLayer;
+
+}  // namespace Eval::NNUE
+
+#endif // #ifndef NNUE_HALFKP_256X2_32_32_H_INCLUDED
diff --git a/src/nnue/evaluate_nnue.cpp b/src/nnue/evaluate_nnue.cpp
new file mode 100644 (file)
index 0000000..d6ac989
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// Code for calculating NNUE evaluation function
+
+#include <iostream>
+#include <set>
+
+#include "../evaluate.h"
+#include "../position.h"
+#include "../misc.h"
+#include "../uci.h"
+
+#include "evaluate_nnue.h"
+
+namespace Eval::NNUE {
+
+  uint32_t kpp_board_index[PIECE_NB][COLOR_NB] = {
+   // convention: W - us, B - them
+   // viewed from other side, W and B are reversed
+      { PS_NONE,     PS_NONE     },
+      { PS_W_PAWN,   PS_B_PAWN   },
+      { PS_W_KNIGHT, PS_B_KNIGHT },
+      { PS_W_BISHOP, PS_B_BISHOP },
+      { PS_W_ROOK,   PS_B_ROOK   },
+      { PS_W_QUEEN,  PS_B_QUEEN  },
+      { PS_W_KING,   PS_B_KING   },
+      { PS_NONE,     PS_NONE     },
+      { PS_NONE,     PS_NONE     },
+      { PS_B_PAWN,   PS_W_PAWN   },
+      { PS_B_KNIGHT, PS_W_KNIGHT },
+      { PS_B_BISHOP, PS_W_BISHOP },
+      { PS_B_ROOK,   PS_W_ROOK   },
+      { PS_B_QUEEN,  PS_W_QUEEN  },
+      { PS_B_KING,   PS_W_KING   },
+      { PS_NONE,     PS_NONE     }
+  };
+
+  // Input feature converter
+  AlignedPtr<FeatureTransformer> feature_transformer;
+
+  // Evaluation function
+  AlignedPtr<Network> network;
+
+  // Evaluation function file name
+  std::string fileName;
+
+  namespace Detail {
+
+  // Initialize the evaluation function parameters
+  template <typename T>
+  void Initialize(AlignedPtr<T>& pointer) {
+
+    pointer.reset(reinterpret_cast<T*>(std_aligned_alloc(alignof(T), sizeof(T))));
+    std::memset(pointer.get(), 0, sizeof(T));
+  }
+
+  // Read evaluation function parameters
+  template <typename T>
+  bool ReadParameters(std::istream& stream, const AlignedPtr<T>& pointer) {
+
+    std::uint32_t header;
+    header = read_little_endian<std::uint32_t>(stream);
+    if (!stream || header != T::GetHashValue()) return false;
+    return pointer->ReadParameters(stream);
+  }
+
+  }  // namespace Detail
+
+  // Initialize the evaluation function parameters
+  void Initialize() {
+
+    Detail::Initialize(feature_transformer);
+    Detail::Initialize(network);
+  }
+
+  // Read network header
+  bool ReadHeader(std::istream& stream, std::uint32_t* hash_value, std::string* architecture)
+  {
+    std::uint32_t version, size;
+
+    version     = read_little_endian<std::uint32_t>(stream);
+    *hash_value = read_little_endian<std::uint32_t>(stream);
+    size        = read_little_endian<std::uint32_t>(stream);
+    if (!stream || version != kVersion) return false;
+    architecture->resize(size);
+    stream.read(&(*architecture)[0], size);
+    return !stream.fail();
+  }
+
+  // Read network parameters
+  bool ReadParameters(std::istream& stream) {
+
+    std::uint32_t hash_value;
+    std::string architecture;
+    if (!ReadHeader(stream, &hash_value, &architecture)) return false;
+    if (hash_value != kHashValue) return false;
+    if (!Detail::ReadParameters(stream, feature_transformer)) return false;
+    if (!Detail::ReadParameters(stream, network)) return false;
+    return stream && stream.peek() == std::ios::traits_type::eof();
+  }
+
+  // Proceed with the difference calculation if possible
+  static void UpdateAccumulatorIfPossible(const Position& pos) {
+
+    feature_transformer->UpdateAccumulatorIfPossible(pos);
+  }
+
+  // Calculate the evaluation value
+  static Value ComputeScore(const Position& pos, bool refresh) {
+
+    auto& accumulator = pos.state()->accumulator;
+    if (!refresh && accumulator.computed_score) {
+      return accumulator.score;
+    }
+
+    alignas(kCacheLineSize) TransformedFeatureType
+        transformed_features[FeatureTransformer::kBufferSize];
+    feature_transformer->Transform(pos, transformed_features, refresh);
+    alignas(kCacheLineSize) char buffer[Network::kBufferSize];
+    const auto output = network->Propagate(transformed_features, buffer);
+
+    auto score = static_cast<Value>(output[0] / FV_SCALE);
+
+    accumulator.score = score;
+    accumulator.computed_score = true;
+    return accumulator.score;
+  }
+
+  // Load eval, from a file stream or a memory stream
+  bool load_eval(std::string streamName, std::istream& stream) {
+
+    Initialize();
+    fileName = streamName;
+    return ReadParameters(stream);
+  }
+
+  // Evaluation function. Perform differential calculation.
+  Value evaluate(const Position& pos) {
+    return ComputeScore(pos, false);
+  }
+
+  // Evaluation function. Perform full calculation.
+  Value compute_eval(const Position& pos) {
+    return ComputeScore(pos, true);
+  }
+
+  // Proceed with the difference calculation if possible
+  void update_eval(const Position& pos) {
+    UpdateAccumulatorIfPossible(pos);
+  }
+
+} // namespace Eval::NNUE
diff --git a/src/nnue/evaluate_nnue.h b/src/nnue/evaluate_nnue.h
new file mode 100644 (file)
index 0000000..5f0d185
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// header used in NNUE evaluation function
+
+#ifndef NNUE_EVALUATE_NNUE_H_INCLUDED
+#define NNUE_EVALUATE_NNUE_H_INCLUDED
+
+#include "nnue_feature_transformer.h"
+
+#include <memory>
+
+namespace Eval::NNUE {
+
+  // Hash value of evaluation function structure
+  constexpr std::uint32_t kHashValue =
+      FeatureTransformer::GetHashValue() ^ Network::GetHashValue();
+
+  // Deleter for automating release of memory area
+  template <typename T>
+  struct AlignedDeleter {
+    void operator()(T* ptr) const {
+      ptr->~T();
+      std_aligned_free(ptr);
+    }
+  };
+
+  template <typename T>
+  using AlignedPtr = std::unique_ptr<T, AlignedDeleter<T>>;
+
+}  // namespace Eval::NNUE
+
+#endif // #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED
diff --git a/src/nnue/features/feature_set.h b/src/nnue/features/feature_set.h
new file mode 100644 (file)
index 0000000..558a6b2
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// A class template that represents the input feature set of the NNUE evaluation function
+
+#ifndef NNUE_FEATURE_SET_H_INCLUDED
+#define NNUE_FEATURE_SET_H_INCLUDED
+
+#include "features_common.h"
+#include <array>
+
+namespace Eval::NNUE::Features {
+
+  // Class template that represents a list of values
+  template <typename T, T... Values>
+  struct CompileTimeList;
+
+  template <typename T, T First, T... Remaining>
+  struct CompileTimeList<T, First, Remaining...> {
+    static constexpr bool Contains(T value) {
+      return value == First || CompileTimeList<T, Remaining...>::Contains(value);
+    }
+    static constexpr std::array<T, sizeof...(Remaining) + 1>
+        kValues = {{First, Remaining...}};
+  };
+
+  // Base class of feature set
+  template <typename Derived>
+  class FeatureSetBase {
+
+   public:
+    // Get a list of indices for active features
+    template <typename IndexListType>
+    static void AppendActiveIndices(
+        const Position& pos, TriggerEvent trigger, IndexListType active[2]) {
+
+      for (Color perspective : { WHITE, BLACK }) {
+        Derived::CollectActiveIndices(
+            pos, trigger, perspective, &active[perspective]);
+      }
+    }
+
+    // Get a list of indices for recently changed features
+    template <typename PositionType, typename IndexListType>
+    static void AppendChangedIndices(
+        const PositionType& pos, TriggerEvent trigger,
+        IndexListType removed[2], IndexListType added[2], bool reset[2]) {
+
+      const auto& dp = pos.state()->dirtyPiece;
+      if (dp.dirty_num == 0) return;
+
+      for (Color perspective : { WHITE, BLACK }) {
+        reset[perspective] = false;
+        switch (trigger) {
+          case TriggerEvent::kFriendKingMoved:
+            reset[perspective] = dp.piece[0] == make_piece(perspective, KING);
+            break;
+          default:
+            assert(false);
+            break;
+        }
+        if (reset[perspective]) {
+          Derived::CollectActiveIndices(
+              pos, trigger, perspective, &added[perspective]);
+        } else {
+          Derived::CollectChangedIndices(
+              pos, trigger, perspective,
+              &removed[perspective], &added[perspective]);
+        }
+      }
+    }
+  };
+
+  // Class template that represents the feature set
+  template <typename FeatureType>
+  class FeatureSet<FeatureType> : public FeatureSetBase<FeatureSet<FeatureType>> {
+
+   public:
+    // Hash value embedded in the evaluation file
+    static constexpr std::uint32_t kHashValue = FeatureType::kHashValue;
+    // Number of feature dimensions
+    static constexpr IndexType kDimensions = FeatureType::kDimensions;
+    // Maximum number of simultaneously active features
+    static constexpr IndexType kMaxActiveDimensions =
+        FeatureType::kMaxActiveDimensions;
+    // Trigger for full calculation instead of difference calculation
+    using SortedTriggerSet =
+        CompileTimeList<TriggerEvent, FeatureType::kRefreshTrigger>;
+    static constexpr auto kRefreshTriggers = SortedTriggerSet::kValues;
+
+   private:
+    // Get a list of indices for active features
+    static void CollectActiveIndices(
+        const Position& pos, const TriggerEvent trigger, const Color perspective,
+        IndexList* const active) {
+      if (FeatureType::kRefreshTrigger == trigger) {
+        FeatureType::AppendActiveIndices(pos, perspective, active);
+      }
+    }
+
+    // Get a list of indices for recently changed features
+    static void CollectChangedIndices(
+        const Position& pos, const TriggerEvent trigger, const Color perspective,
+        IndexList* const removed, IndexList* const added) {
+
+      if (FeatureType::kRefreshTrigger == trigger) {
+        FeatureType::AppendChangedIndices(pos, perspective, removed, added);
+      }
+    }
+
+    // Make the base class and the class template that recursively uses itself a friend
+    friend class FeatureSetBase<FeatureSet>;
+    template <typename... FeatureTypes>
+    friend class FeatureSet;
+  };
+
+}  // namespace Eval::NNUE::Features
+
+#endif // #ifndef NNUE_FEATURE_SET_H_INCLUDED
diff --git a/src/nnue/features/features_common.h b/src/nnue/features/features_common.h
new file mode 100644 (file)
index 0000000..d00a35d
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+//Common header of input features of NNUE evaluation function
+
+#ifndef NNUE_FEATURES_COMMON_H_INCLUDED
+#define NNUE_FEATURES_COMMON_H_INCLUDED
+
+#include "../../evaluate.h"
+#include "../nnue_common.h"
+
+namespace Eval::NNUE::Features {
+
+  class IndexList;
+
+  template <typename... FeatureTypes>
+  class FeatureSet;
+
+  // Trigger to perform full calculations instead of difference only
+  enum class TriggerEvent {
+    kFriendKingMoved // calculate full evaluation when own king moves
+  };
+
+  enum class Side {
+    kFriend // side to move
+  };
+
+}  // namespace Eval::NNUE::Features
+
+#endif // #ifndef NNUE_FEATURES_COMMON_H_INCLUDED
diff --git a/src/nnue/features/half_kp.cpp b/src/nnue/features/half_kp.cpp
new file mode 100644 (file)
index 0000000..88e384a
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+//Definition of input features HalfKP of NNUE evaluation function
+
+#include "half_kp.h"
+#include "index_list.h"
+
+namespace Eval::NNUE::Features {
+
+  // Orient a square according to perspective (rotates by 180 for black)
+  inline Square orient(Color perspective, Square s) {
+    return Square(int(s) ^ (bool(perspective) * 63));
+  }
+
+  // Find the index of the feature quantity from the king position and PieceSquare
+  template <Side AssociatedKing>
+  inline IndexType HalfKP<AssociatedKing>::MakeIndex(
+      Color perspective, Square s, Piece pc, Square ksq) {
+
+    return IndexType(orient(perspective, s) + kpp_board_index[pc][perspective] + PS_END * ksq);
+  }
+
+  // Get a list of indices for active features
+  template <Side AssociatedKing>
+  void HalfKP<AssociatedKing>::AppendActiveIndices(
+      const Position& pos, Color perspective, IndexList* active) {
+
+    Square ksq = orient(perspective, pos.square<KING>(perspective));
+    Bitboard bb = pos.pieces() & ~pos.pieces(KING);
+    while (bb) {
+      Square s = pop_lsb(&bb);
+      active->push_back(MakeIndex(perspective, s, pos.piece_on(s), ksq));
+    }
+  }
+
+  // Get a list of indices for recently changed features
+  template <Side AssociatedKing>
+  void HalfKP<AssociatedKing>::AppendChangedIndices(
+      const Position& pos, Color perspective,
+      IndexList* removed, IndexList* added) {
+
+    Square ksq = orient(perspective, pos.square<KING>(perspective));
+    const auto& dp = pos.state()->dirtyPiece;
+    for (int i = 0; i < dp.dirty_num; ++i) {
+      Piece pc = dp.piece[i];
+      if (type_of(pc) == KING) continue;
+      if (dp.from[i] != SQ_NONE)
+        removed->push_back(MakeIndex(perspective, dp.from[i], pc, ksq));
+      if (dp.to[i] != SQ_NONE)
+        added->push_back(MakeIndex(perspective, dp.to[i], pc, ksq));
+    }
+  }
+
+  template class HalfKP<Side::kFriend>;
+
+}  // namespace Eval::NNUE::Features
diff --git a/src/nnue/features/half_kp.h b/src/nnue/features/half_kp.h
new file mode 100644 (file)
index 0000000..ee6a8df
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+//Definition of input features HalfKP of NNUE evaluation function
+
+#ifndef NNUE_FEATURES_HALF_KP_H_INCLUDED
+#define NNUE_FEATURES_HALF_KP_H_INCLUDED
+
+#include "../../evaluate.h"
+#include "features_common.h"
+
+namespace Eval::NNUE::Features {
+
+  // Feature HalfKP: Combination of the position of own king
+  // and the position of pieces other than kings
+  template <Side AssociatedKing>
+  class HalfKP {
+
+   public:
+    // Feature name
+    static constexpr const char* kName = "HalfKP(Friend)";
+    // Hash value embedded in the evaluation file
+    static constexpr std::uint32_t kHashValue =
+        0x5D69D5B9u ^ (AssociatedKing == Side::kFriend);
+    // Number of feature dimensions
+    static constexpr IndexType kDimensions =
+        static_cast<IndexType>(SQUARE_NB) * static_cast<IndexType>(PS_END);
+    // Maximum number of simultaneously active features
+    static constexpr IndexType kMaxActiveDimensions = 30; // Kings don't count
+    // Trigger for full calculation instead of difference calculation
+    static constexpr TriggerEvent kRefreshTrigger = TriggerEvent::kFriendKingMoved;
+
+    // Get a list of indices for active features
+    static void AppendActiveIndices(const Position& pos, Color perspective,
+                                    IndexList* active);
+
+    // Get a list of indices for recently changed features
+    static void AppendChangedIndices(const Position& pos, Color perspective,
+                                     IndexList* removed, IndexList* added);
+
+   private:
+    // Index of a feature for a given king position and another piece on some square
+    static IndexType MakeIndex(Color perspective, Square s, Piece pc, Square sq_k);
+  };
+
+}  // namespace Eval::NNUE::Features
+
+#endif // #ifndef NNUE_FEATURES_HALF_KP_H_INCLUDED
diff --git a/src/nnue/features/index_list.h b/src/nnue/features/index_list.h
new file mode 100644 (file)
index 0000000..d9ad680
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// Definition of index list of input features
+
+#ifndef NNUE_FEATURES_INDEX_LIST_H_INCLUDED
+#define NNUE_FEATURES_INDEX_LIST_H_INCLUDED
+
+#include "../../position.h"
+#include "../nnue_architecture.h"
+
+namespace Eval::NNUE::Features {
+
+  // Class template used for feature index list
+  template <typename T, std::size_t MaxSize>
+  class ValueList {
+
+   public:
+    std::size_t size() const { return size_; }
+    void resize(std::size_t size) { size_ = size; }
+    void push_back(const T& value) { values_[size_++] = value; }
+    T& operator[](std::size_t index) { return values_[index]; }
+    T* begin() { return values_; }
+    T* end() { return values_ + size_; }
+    const T& operator[](std::size_t index) const { return values_[index]; }
+    const T* begin() const { return values_; }
+    const T* end() const { return values_ + size_; }
+
+    void swap(ValueList& other) {
+      const std::size_t max_size = std::max(size_, other.size_);
+      for (std::size_t i = 0; i < max_size; ++i) {
+        std::swap(values_[i], other.values_[i]);
+      }
+      std::swap(size_, other.size_);
+    }
+
+   private:
+    T values_[MaxSize];
+    std::size_t size_ = 0;
+  };
+
+  //Type of feature index list
+  class IndexList
+      : public ValueList<IndexType, RawFeatures::kMaxActiveDimensions> {
+  };
+
+}  // namespace Eval::NNUE::Features
+
+#endif // NNUE_FEATURES_INDEX_LIST_H_INCLUDED
diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h
new file mode 100644 (file)
index 0000000..94d0b5a
--- /dev/null
@@ -0,0 +1,266 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// Definition of layer AffineTransform of NNUE evaluation function
+
+#ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED
+#define NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED
+
+#include <iostream>
+#include "../nnue_common.h"
+
+namespace Eval::NNUE::Layers {
+
+  // Affine transformation layer
+  template <typename PreviousLayer, IndexType OutputDimensions>
+  class AffineTransform {
+   public:
+    // Input/output type
+    using InputType = typename PreviousLayer::OutputType;
+    using OutputType = std::int32_t;
+    static_assert(std::is_same<InputType, std::uint8_t>::value, "");
+
+    // Number of input/output dimensions
+    static constexpr IndexType kInputDimensions =
+        PreviousLayer::kOutputDimensions;
+    static constexpr IndexType kOutputDimensions = OutputDimensions;
+    static constexpr IndexType kPaddedInputDimensions =
+        CeilToMultiple<IndexType>(kInputDimensions, kMaxSimdWidth);
+
+    // Size of forward propagation buffer used in this layer
+    static constexpr std::size_t kSelfBufferSize =
+        CeilToMultiple(kOutputDimensions * sizeof(OutputType), kCacheLineSize);
+
+    // Size of the forward propagation buffer used from the input layer to this layer
+    static constexpr std::size_t kBufferSize =
+        PreviousLayer::kBufferSize + kSelfBufferSize;
+
+    // Hash value embedded in the evaluation file
+    static constexpr std::uint32_t GetHashValue() {
+      std::uint32_t hash_value = 0xCC03DAE4u;
+      hash_value += kOutputDimensions;
+      hash_value ^= PreviousLayer::GetHashValue() >> 1;
+      hash_value ^= PreviousLayer::GetHashValue() << 31;
+      return hash_value;
+    }
+
+   // Read network parameters
+    bool ReadParameters(std::istream& stream) {
+      if (!previous_layer_.ReadParameters(stream)) return false;
+      for (std::size_t i = 0; i < kOutputDimensions; ++i)
+        biases_[i] = read_little_endian<BiasType>(stream);
+      for (std::size_t i = 0; i < kOutputDimensions * kPaddedInputDimensions; ++i)
+        weights_[i] = read_little_endian<WeightType>(stream);
+      return !stream.fail();
+    }
+
+    // Forward propagation
+    const OutputType* Propagate(
+        const TransformedFeatureType* transformed_features, char* buffer) const {
+      const auto input = previous_layer_.Propagate(
+          transformed_features, buffer + kSelfBufferSize);
+      const auto output = reinterpret_cast<OutputType*>(buffer);
+
+  #if defined(USE_AVX512)
+      constexpr IndexType kNumChunks = kPaddedInputDimensions / (kSimdWidth * 2);
+      const auto input_vector = reinterpret_cast<const __m512i*>(input);
+  #if !defined(USE_VNNI)
+      const __m512i kOnes = _mm512_set1_epi16(1);
+  #endif
+
+  #elif defined(USE_AVX2)
+      constexpr IndexType kNumChunks = kPaddedInputDimensions / kSimdWidth;
+      const auto input_vector = reinterpret_cast<const __m256i*>(input);
+  #if !defined(USE_VNNI)
+      const __m256i kOnes = _mm256_set1_epi16(1);
+  #endif
+
+  #elif defined(USE_SSE2)
+      constexpr IndexType kNumChunks = kPaddedInputDimensions / kSimdWidth;
+  #ifndef USE_SSSE3
+      const __m128i kZeros = _mm_setzero_si128();
+  #else
+      const __m128i kOnes = _mm_set1_epi16(1);
+  #endif
+      const auto input_vector = reinterpret_cast<const __m128i*>(input);
+
+  #elif defined(USE_MMX)
+      constexpr IndexType kNumChunks = kPaddedInputDimensions / kSimdWidth;
+      const __m64 kZeros = _mm_setzero_si64();
+      const auto input_vector = reinterpret_cast<const __m64*>(input);
+
+  #elif defined(USE_NEON)
+      constexpr IndexType kNumChunks = kPaddedInputDimensions / kSimdWidth;
+      const auto input_vector = reinterpret_cast<const int8x8_t*>(input);
+  #endif
+
+      for (IndexType i = 0; i < kOutputDimensions; ++i) {
+        const IndexType offset = i * kPaddedInputDimensions;
+
+  #if defined(USE_AVX512)
+        __m512i sum = _mm512_setzero_si512();
+        const auto row = reinterpret_cast<const __m512i*>(&weights_[offset]);
+        for (IndexType j = 0; j < kNumChunks; ++j) {
+  #if defined(USE_VNNI)
+            sum = _mm512_dpbusd_epi32(sum, _mm512_loadA_si512(&input_vector[j]), _mm512_load_si512(&row[j]));
+  #else
+            __m512i product = _mm512_maddubs_epi16(_mm512_loadA_si512(&input_vector[j]), _mm512_load_si512(&row[j]));
+            product = _mm512_madd_epi16(product, kOnes);
+            sum = _mm512_add_epi32(sum, product);
+  #endif
+        }
+
+        // Note: Changing kMaxSimdWidth from 32 to 64 breaks loading existing networks.
+        // As a result kPaddedInputDimensions may not be an even multiple of 64(512bit)
+        // and we have to do one more 256bit chunk.
+        if (kPaddedInputDimensions != kNumChunks * kSimdWidth * 2)
+        {
+            const auto iv256  = reinterpret_cast<const __m256i*>(&input_vector[kNumChunks]);
+            const auto row256 = reinterpret_cast<const __m256i*>(&row[kNumChunks]);
+  #if defined(USE_VNNI)
+            __m256i product256 = _mm256_dpbusd_epi32(
+                _mm512_castsi512_si256(sum), _mm256_loadA_si256(&iv256[0]), _mm256_load_si256(&row256[0]));
+            sum = _mm512_inserti32x8(sum, product256, 0);
+  #else
+            __m256i product256 = _mm256_maddubs_epi16(_mm256_loadA_si256(&iv256[0]), _mm256_load_si256(&row256[0]));
+            sum = _mm512_add_epi32(sum, _mm512_cvtepi16_epi32(product256));
+  #endif
+        }
+        output[i] = _mm512_reduce_add_epi32(sum) + biases_[i];
+
+  #elif defined(USE_AVX2)
+        __m256i sum = _mm256_setzero_si256();
+        const auto row = reinterpret_cast<const __m256i*>(&weights_[offset]);
+        for (IndexType j = 0; j < kNumChunks; ++j) {
+  #if defined(USE_VNNI)
+          sum = _mm256_dpbusd_epi32(sum, _mm256_loadA_si256(&input_vector[j]), _mm256_load_si256(&row[j]));
+  #else
+          __m256i product = _mm256_maddubs_epi16(_mm256_loadA_si256(&input_vector[j]), _mm256_load_si256(&row[j]));
+          product = _mm256_madd_epi16(product, kOnes);
+          sum = _mm256_add_epi32(sum, product);
+  #endif
+        }
+        __m128i sum128 = _mm_add_epi32(_mm256_castsi256_si128(sum), _mm256_extracti128_si256(sum, 1));
+        sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_BADC));
+        sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_CDAB));
+        output[i] = _mm_cvtsi128_si32(sum128) + biases_[i];
+
+  #elif defined(USE_SSSE3)
+        __m128i sum = _mm_setzero_si128();
+        const auto row = reinterpret_cast<const __m128i*>(&weights_[offset]);
+        for (int j = 0; j < (int)kNumChunks - 1; j += 2) {
+          __m128i product0 = _mm_maddubs_epi16(_mm_load_si128(&input_vector[j]), _mm_load_si128(&row[j]));
+          product0 = _mm_madd_epi16(product0, kOnes);
+          sum = _mm_add_epi32(sum, product0);
+          __m128i product1 = _mm_maddubs_epi16(_mm_load_si128(&input_vector[j+1]), _mm_load_si128(&row[j+1]));
+          product1 = _mm_madd_epi16(product1, kOnes);
+          sum = _mm_add_epi32(sum, product1);
+        }
+        if (kNumChunks & 0x1) {
+          __m128i product = _mm_maddubs_epi16(_mm_load_si128(&input_vector[kNumChunks-1]), _mm_load_si128(&row[kNumChunks-1]));
+          product = _mm_madd_epi16(product, kOnes);
+          sum = _mm_add_epi32(sum, product);
+        }
+        sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0x4E)); //_MM_PERM_BADC
+        sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0xB1)); //_MM_PERM_CDAB
+        output[i] = _mm_cvtsi128_si32(sum) + biases_[i];
+
+  #elif defined(USE_SSE2)
+        __m128i sum_lo = _mm_cvtsi32_si128(biases_[i]);
+        __m128i sum_hi = kZeros;
+        const auto row = reinterpret_cast<const __m128i*>(&weights_[offset]);
+        for (IndexType j = 0; j < kNumChunks; ++j) {
+          __m128i row_j = _mm_load_si128(&row[j]);
+          __m128i input_j = _mm_load_si128(&input_vector[j]);
+          __m128i row_signs = _mm_cmpgt_epi8(kZeros, row_j);
+          __m128i extended_row_lo = _mm_unpacklo_epi8(row_j, row_signs);
+          __m128i extended_row_hi = _mm_unpackhi_epi8(row_j, row_signs);
+          __m128i extended_input_lo = _mm_unpacklo_epi8(input_j, kZeros);
+          __m128i extended_input_hi = _mm_unpackhi_epi8(input_j, kZeros);
+          __m128i product_lo = _mm_madd_epi16(extended_row_lo, extended_input_lo);
+          __m128i product_hi = _mm_madd_epi16(extended_row_hi, extended_input_hi);
+          sum_lo = _mm_add_epi32(sum_lo, product_lo);
+          sum_hi = _mm_add_epi32(sum_hi, product_hi);
+        }
+        __m128i sum = _mm_add_epi32(sum_lo, sum_hi);
+        __m128i sum_high_64 = _mm_shuffle_epi32(sum, _MM_SHUFFLE(1, 0, 3, 2));
+        sum = _mm_add_epi32(sum, sum_high_64);
+        __m128i sum_second_32 = _mm_shufflelo_epi16(sum, _MM_SHUFFLE(1, 0, 3, 2));
+        sum = _mm_add_epi32(sum, sum_second_32);
+        output[i] = _mm_cvtsi128_si32(sum);
+
+  #elif defined(USE_MMX)
+        __m64 sum_lo = _mm_cvtsi32_si64(biases_[i]);
+        __m64 sum_hi = kZeros;
+        const auto row = reinterpret_cast<const __m64*>(&weights_[offset]);
+        for (IndexType j = 0; j < kNumChunks; ++j) {
+          __m64 row_j = row[j];
+          __m64 input_j = input_vector[j];
+          __m64 row_signs = _mm_cmpgt_pi8(kZeros, row_j);
+          __m64 extended_row_lo = _mm_unpacklo_pi8(row_j, row_signs);
+          __m64 extended_row_hi = _mm_unpackhi_pi8(row_j, row_signs);
+          __m64 extended_input_lo = _mm_unpacklo_pi8(input_j, kZeros);
+          __m64 extended_input_hi = _mm_unpackhi_pi8(input_j, kZeros);
+          __m64 product_lo = _mm_madd_pi16(extended_row_lo, extended_input_lo);
+          __m64 product_hi = _mm_madd_pi16(extended_row_hi, extended_input_hi);
+          sum_lo = _mm_add_pi32(sum_lo, product_lo);
+          sum_hi = _mm_add_pi32(sum_hi, product_hi);
+        }
+        __m64 sum = _mm_add_pi32(sum_lo, sum_hi);
+        sum = _mm_add_pi32(sum, _mm_unpackhi_pi32(sum, sum));
+        output[i] = _mm_cvtsi64_si32(sum);
+
+  #elif defined(USE_NEON)
+        int32x4_t sum = {biases_[i]};
+        const auto row = reinterpret_cast<const int8x8_t*>(&weights_[offset]);
+        for (IndexType j = 0; j < kNumChunks; ++j) {
+          int16x8_t product = vmull_s8(input_vector[j * 2], row[j * 2]);
+          product = vmlal_s8(product, input_vector[j * 2 + 1], row[j * 2 + 1]);
+          sum = vpadalq_s16(sum, product);
+        }
+        output[i] = sum[0] + sum[1] + sum[2] + sum[3];
+
+  #else
+        OutputType sum = biases_[i];
+        for (IndexType j = 0; j < kInputDimensions; ++j) {
+          sum += weights_[offset + j] * input[j];
+        }
+        output[i] = sum;
+  #endif
+
+      }
+  #if defined(USE_MMX)
+      _mm_empty();
+  #endif
+      return output;
+    }
+
+   private:
+    using BiasType = OutputType;
+    using WeightType = std::int8_t;
+
+    PreviousLayer previous_layer_;
+
+    alignas(kCacheLineSize) BiasType biases_[kOutputDimensions];
+    alignas(kCacheLineSize)
+        WeightType weights_[kOutputDimensions * kPaddedInputDimensions];
+  };
+
+}  // namespace Eval::NNUE::Layers
+
+#endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED
diff --git a/src/nnue/layers/clipped_relu.h b/src/nnue/layers/clipped_relu.h
new file mode 100644 (file)
index 0000000..44d8a7d
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// Definition of layer ClippedReLU of NNUE evaluation function
+
+#ifndef NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED
+#define NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED
+
+#include "../nnue_common.h"
+
+namespace Eval::NNUE::Layers {
+
+  // Clipped ReLU
+  template <typename PreviousLayer>
+  class ClippedReLU {
+   public:
+    // Input/output type
+    using InputType = typename PreviousLayer::OutputType;
+    using OutputType = std::uint8_t;
+    static_assert(std::is_same<InputType, std::int32_t>::value, "");
+
+    // Number of input/output dimensions
+    static constexpr IndexType kInputDimensions =
+        PreviousLayer::kOutputDimensions;
+    static constexpr IndexType kOutputDimensions = kInputDimensions;
+
+    // Size of forward propagation buffer used in this layer
+    static constexpr std::size_t kSelfBufferSize =
+        CeilToMultiple(kOutputDimensions * sizeof(OutputType), kCacheLineSize);
+
+    // Size of the forward propagation buffer used from the input layer to this layer
+    static constexpr std::size_t kBufferSize =
+        PreviousLayer::kBufferSize + kSelfBufferSize;
+
+    // Hash value embedded in the evaluation file
+    static constexpr std::uint32_t GetHashValue() {
+      std::uint32_t hash_value = 0x538D24C7u;
+      hash_value += PreviousLayer::GetHashValue();
+      return hash_value;
+    }
+
+    // Read network parameters
+    bool ReadParameters(std::istream& stream) {
+      return previous_layer_.ReadParameters(stream);
+    }
+
+    // Forward propagation
+    const OutputType* Propagate(
+        const TransformedFeatureType* transformed_features, char* buffer) const {
+      const auto input = previous_layer_.Propagate(
+          transformed_features, buffer + kSelfBufferSize);
+      const auto output = reinterpret_cast<OutputType*>(buffer);
+
+  #if defined(USE_AVX2)
+      constexpr IndexType kNumChunks = kInputDimensions / kSimdWidth;
+      const __m256i kZero = _mm256_setzero_si256();
+      const __m256i kOffsets = _mm256_set_epi32(7, 3, 6, 2, 5, 1, 4, 0);
+      const auto in = reinterpret_cast<const __m256i*>(input);
+      const auto out = reinterpret_cast<__m256i*>(output);
+      for (IndexType i = 0; i < kNumChunks; ++i) {
+        const __m256i words0 = _mm256_srai_epi16(_mm256_packs_epi32(
+            _mm256_loadA_si256(&in[i * 4 + 0]),
+            _mm256_loadA_si256(&in[i * 4 + 1])), kWeightScaleBits);
+        const __m256i words1 = _mm256_srai_epi16(_mm256_packs_epi32(
+            _mm256_loadA_si256(&in[i * 4 + 2]),
+            _mm256_loadA_si256(&in[i * 4 + 3])), kWeightScaleBits);
+        _mm256_storeA_si256(&out[i], _mm256_permutevar8x32_epi32(_mm256_max_epi8(
+            _mm256_packs_epi16(words0, words1), kZero), kOffsets));
+      }
+      constexpr IndexType kStart = kNumChunks * kSimdWidth;
+
+  #elif defined(USE_SSE2)
+      constexpr IndexType kNumChunks = kInputDimensions / kSimdWidth;
+
+  #ifdef USE_SSE41
+      const __m128i kZero = _mm_setzero_si128();
+  #else
+      const __m128i k0x80s = _mm_set1_epi8(-128);
+  #endif
+
+      const auto in = reinterpret_cast<const __m128i*>(input);
+      const auto out = reinterpret_cast<__m128i*>(output);
+      for (IndexType i = 0; i < kNumChunks; ++i) {
+        const __m128i words0 = _mm_srai_epi16(_mm_packs_epi32(
+            _mm_load_si128(&in[i * 4 + 0]),
+            _mm_load_si128(&in[i * 4 + 1])), kWeightScaleBits);
+        const __m128i words1 = _mm_srai_epi16(_mm_packs_epi32(
+            _mm_load_si128(&in[i * 4 + 2]),
+            _mm_load_si128(&in[i * 4 + 3])), kWeightScaleBits);
+        const __m128i packedbytes = _mm_packs_epi16(words0, words1);
+        _mm_store_si128(&out[i],
+
+  #ifdef USE_SSE41
+          _mm_max_epi8(packedbytes, kZero)
+  #else
+          _mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s)
+  #endif
+
+        );
+      }
+      constexpr IndexType kStart = kNumChunks * kSimdWidth;
+
+  #elif defined(USE_MMX)
+      constexpr IndexType kNumChunks = kInputDimensions / kSimdWidth;
+      const __m64 k0x80s = _mm_set1_pi8(-128);
+      const auto in = reinterpret_cast<const __m64*>(input);
+      const auto out = reinterpret_cast<__m64*>(output);
+      for (IndexType i = 0; i < kNumChunks; ++i) {
+        const __m64 words0 = _mm_srai_pi16(
+            _mm_packs_pi32(in[i * 4 + 0], in[i * 4 + 1]),
+            kWeightScaleBits);
+        const __m64 words1 = _mm_srai_pi16(
+            _mm_packs_pi32(in[i * 4 + 2], in[i * 4 + 3]),
+            kWeightScaleBits);
+        const __m64 packedbytes = _mm_packs_pi16(words0, words1);
+        out[i] = _mm_subs_pi8(_mm_adds_pi8(packedbytes, k0x80s), k0x80s);
+      }
+      _mm_empty();
+      constexpr IndexType kStart = kNumChunks * kSimdWidth;
+
+  #elif defined(USE_NEON)
+      constexpr IndexType kNumChunks = kInputDimensions / (kSimdWidth / 2);
+      const int8x8_t kZero = {0};
+      const auto in = reinterpret_cast<const int32x4_t*>(input);
+      const auto out = reinterpret_cast<int8x8_t*>(output);
+      for (IndexType i = 0; i < kNumChunks; ++i) {
+        int16x8_t shifted;
+        const auto pack = reinterpret_cast<int16x4_t*>(&shifted);
+        pack[0] = vqshrn_n_s32(in[i * 2 + 0], kWeightScaleBits);
+        pack[1] = vqshrn_n_s32(in[i * 2 + 1], kWeightScaleBits);
+        out[i] = vmax_s8(vqmovn_s16(shifted), kZero);
+      }
+      constexpr IndexType kStart = kNumChunks * (kSimdWidth / 2);
+  #else
+      constexpr IndexType kStart = 0;
+  #endif
+
+      for (IndexType i = kStart; i < kInputDimensions; ++i) {
+        output[i] = static_cast<OutputType>(
+            std::max(0, std::min(127, input[i] >> kWeightScaleBits)));
+      }
+      return output;
+    }
+
+   private:
+    PreviousLayer previous_layer_;
+  };
+
+}  // namespace Eval::NNUE::Layers
+
+#endif // NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED
diff --git a/src/nnue/layers/input_slice.h b/src/nnue/layers/input_slice.h
new file mode 100644 (file)
index 0000000..afca14c
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// NNUE evaluation function layer InputSlice definition
+
+#ifndef NNUE_LAYERS_INPUT_SLICE_H_INCLUDED
+#define NNUE_LAYERS_INPUT_SLICE_H_INCLUDED
+
+#include "../nnue_common.h"
+
+namespace Eval::NNUE::Layers {
+
+// Input layer
+template <IndexType OutputDimensions, IndexType Offset = 0>
+class InputSlice {
+ public:
+  // Need to maintain alignment
+  static_assert(Offset % kMaxSimdWidth == 0, "");
+
+  // Output type
+  using OutputType = TransformedFeatureType;
+
+  // Output dimensionality
+  static constexpr IndexType kOutputDimensions = OutputDimensions;
+
+  // Size of forward propagation buffer used from the input layer to this layer
+  static constexpr std::size_t kBufferSize = 0;
+
+  // Hash value embedded in the evaluation file
+  static constexpr std::uint32_t GetHashValue() {
+    std::uint32_t hash_value = 0xEC42E90Du;
+    hash_value ^= kOutputDimensions ^ (Offset << 10);
+    return hash_value;
+  }
+
+  // Read network parameters
+  bool ReadParameters(std::istream& /*stream*/) {
+    return true;
+  }
+
+  // Forward propagation
+  const OutputType* Propagate(
+      const TransformedFeatureType* transformed_features,
+      char* /*buffer*/) const {
+    return transformed_features + Offset;
+  }
+
+ private:
+};
+
+}  // namespace Layers
+
+#endif // #ifndef NNUE_LAYERS_INPUT_SLICE_H_INCLUDED
diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h
new file mode 100644 (file)
index 0000000..69dfaad
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// Class for difference calculation of NNUE evaluation function
+
+#ifndef NNUE_ACCUMULATOR_H_INCLUDED
+#define NNUE_ACCUMULATOR_H_INCLUDED
+
+#include "nnue_architecture.h"
+
+namespace Eval::NNUE {
+
+  // Class that holds the result of affine transformation of input features
+  struct alignas(kCacheLineSize) Accumulator {
+    std::int16_t
+        accumulation[2][kRefreshTriggers.size()][kTransformedFeatureDimensions];
+    Value score;
+    bool computed_accumulation;
+    bool computed_score;
+  };
+
+}  // namespace Eval::NNUE
+
+#endif // NNUE_ACCUMULATOR_H_INCLUDED
diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h
new file mode 100644 (file)
index 0000000..91cdc4b
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// Input features and network structure used in NNUE evaluation function
+
+#ifndef NNUE_ARCHITECTURE_H_INCLUDED
+#define NNUE_ARCHITECTURE_H_INCLUDED
+
+// Defines the network structure
+#include "architectures/halfkp_256x2-32-32.h"
+
+namespace Eval::NNUE {
+
+  static_assert(kTransformedFeatureDimensions % kMaxSimdWidth == 0, "");
+  static_assert(Network::kOutputDimensions == 1, "");
+  static_assert(std::is_same<Network::OutputType, std::int32_t>::value, "");
+
+  // Trigger for full calculation instead of difference calculation
+  constexpr auto kRefreshTriggers = RawFeatures::kRefreshTriggers;
+
+}  // namespace Eval::NNUE
+
+#endif // #ifndef NNUE_ARCHITECTURE_H_INCLUDED
diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h
new file mode 100644 (file)
index 0000000..7bc905d
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// Constants used in NNUE evaluation function
+
+#ifndef NNUE_COMMON_H_INCLUDED
+#define NNUE_COMMON_H_INCLUDED
+
+#include <cstring>
+#include <iostream>
+
+#if defined(USE_AVX2)
+#include <immintrin.h>
+
+#elif defined(USE_SSE41)
+#include <smmintrin.h>
+
+#elif defined(USE_SSSE3)
+#include <tmmintrin.h>
+
+#elif defined(USE_SSE2)
+#include <emmintrin.h>
+
+#elif defined(USE_MMX)
+#include <mmintrin.h>
+
+#elif defined(USE_NEON)
+#include <arm_neon.h>
+#endif
+
+// HACK: Use _mm256_loadu_si256() instead of _mm256_load_si256. Otherwise a binary
+//       compiled with older g++ crashes because the output memory is not aligned
+//       even though alignas is specified.
+#if defined(USE_AVX2)
+#if defined(__GNUC__ ) && (__GNUC__ < 9) && defined(_WIN32) && !defined(__clang__)
+#define _mm256_loadA_si256  _mm256_loadu_si256
+#define _mm256_storeA_si256 _mm256_storeu_si256
+#else
+#define _mm256_loadA_si256  _mm256_load_si256
+#define _mm256_storeA_si256 _mm256_store_si256
+#endif
+#endif
+
+#if defined(USE_AVX512)
+#if defined(__GNUC__ ) && (__GNUC__ < 9) && defined(_WIN32) && !defined(__clang__)
+#define _mm512_loadA_si512   _mm512_loadu_si512
+#define _mm512_storeA_si512  _mm512_storeu_si512
+#else
+#define _mm512_loadA_si512   _mm512_load_si512
+#define _mm512_storeA_si512  _mm512_store_si512
+#endif
+#endif
+
+namespace Eval::NNUE {
+
+  // Version of the evaluation file
+  constexpr std::uint32_t kVersion = 0x7AF32F16u;
+
+  // Constant used in evaluation value calculation
+  constexpr int FV_SCALE = 16;
+  constexpr int kWeightScaleBits = 6;
+
+  // Size of cache line (in bytes)
+  constexpr std::size_t kCacheLineSize = 64;
+
+  // SIMD width (in bytes)
+  #if defined(USE_AVX2)
+  constexpr std::size_t kSimdWidth = 32;
+
+  #elif defined(USE_SSE2)
+  constexpr std::size_t kSimdWidth = 16;
+
+  #elif defined(USE_MMX)
+  constexpr std::size_t kSimdWidth = 8;
+
+  #elif defined(USE_NEON)
+  constexpr std::size_t kSimdWidth = 16;
+  #endif
+
+  constexpr std::size_t kMaxSimdWidth = 32;
+
+  // unique number for each piece type on each square
+  enum {
+    PS_NONE     =  0,
+    PS_W_PAWN   =  1,
+    PS_B_PAWN   =  1 * SQUARE_NB + 1,
+    PS_W_KNIGHT =  2 * SQUARE_NB + 1,
+    PS_B_KNIGHT =  3 * SQUARE_NB + 1,
+    PS_W_BISHOP =  4 * SQUARE_NB + 1,
+    PS_B_BISHOP =  5 * SQUARE_NB + 1,
+    PS_W_ROOK   =  6 * SQUARE_NB + 1,
+    PS_B_ROOK   =  7 * SQUARE_NB + 1,
+    PS_W_QUEEN  =  8 * SQUARE_NB + 1,
+    PS_B_QUEEN  =  9 * SQUARE_NB + 1,
+    PS_W_KING   = 10 * SQUARE_NB + 1,
+    PS_END      = PS_W_KING, // pieces without kings (pawns included)
+    PS_B_KING   = 11 * SQUARE_NB + 1,
+    PS_END2     = 12 * SQUARE_NB + 1
+  };
+
+  extern uint32_t kpp_board_index[PIECE_NB][COLOR_NB];
+
+  // Type of input feature after conversion
+  using TransformedFeatureType = std::uint8_t;
+  using IndexType = std::uint32_t;
+
+  // Round n up to be a multiple of base
+  template <typename IntType>
+  constexpr IntType CeilToMultiple(IntType n, IntType base) {
+      return (n + base - 1) / base * base;
+  }
+
+  // read_little_endian() is our utility to read an integer (signed or unsigned, any size)
+  // from a stream in little-endian order. We swap the byte order after the read if
+  // necessary to return a result with the byte ordering of the compiling machine.
+  template <typename IntType>
+  inline IntType read_little_endian(std::istream& stream) {
+
+      IntType result;
+      std::uint8_t u[sizeof(IntType)];
+      typename std::make_unsigned<IntType>::type v = 0;
+
+      stream.read(reinterpret_cast<char*>(u), sizeof(IntType));
+      for (std::size_t i = 0; i < sizeof(IntType); ++i)
+          v = (v << 8) | u[sizeof(IntType) - i - 1];
+
+      std::memcpy(&result, &v, sizeof(IntType));
+      return result;
+  }
+
+}  // namespace Eval::NNUE
+
+#endif // #ifndef NNUE_COMMON_H_INCLUDED
diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h
new file mode 100644 (file)
index 0000000..4370761
--- /dev/null
@@ -0,0 +1,378 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// A class that converts the input features of the NNUE evaluation function
+
+#ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED
+#define NNUE_FEATURE_TRANSFORMER_H_INCLUDED
+
+#include "nnue_common.h"
+#include "nnue_architecture.h"
+#include "features/index_list.h"
+
+#include <cstring> // std::memset()
+
+namespace Eval::NNUE {
+
+  // Input feature converter
+  class FeatureTransformer {
+
+   private:
+    // Number of output dimensions for one side
+    static constexpr IndexType kHalfDimensions = kTransformedFeatureDimensions;
+
+   public:
+    // Output type
+    using OutputType = TransformedFeatureType;
+
+    // Number of input/output dimensions
+    static constexpr IndexType kInputDimensions = RawFeatures::kDimensions;
+    static constexpr IndexType kOutputDimensions = kHalfDimensions * 2;
+
+    // Size of forward propagation buffer
+    static constexpr std::size_t kBufferSize =
+        kOutputDimensions * sizeof(OutputType);
+
+    // Hash value embedded in the evaluation file
+    static constexpr std::uint32_t GetHashValue() {
+      return RawFeatures::kHashValue ^ kOutputDimensions;
+    }
+
+    // Read network parameters
+    bool ReadParameters(std::istream& stream) {
+      for (std::size_t i = 0; i < kHalfDimensions; ++i)
+        biases_[i] = read_little_endian<BiasType>(stream);
+      for (std::size_t i = 0; i < kHalfDimensions * kInputDimensions; ++i)
+        weights_[i] = read_little_endian<WeightType>(stream);
+      return !stream.fail();
+    }
+
+    // Proceed with the difference calculation if possible
+    bool UpdateAccumulatorIfPossible(const Position& pos) const {
+      const auto now = pos.state();
+      if (now->accumulator.computed_accumulation) {
+        return true;
+      }
+      const auto prev = now->previous;
+      if (prev && prev->accumulator.computed_accumulation) {
+        UpdateAccumulator(pos);
+        return true;
+      }
+      return false;
+    }
+
+    // Convert input features
+    void Transform(const Position& pos, OutputType* output, bool refresh) const {
+      if (refresh || !UpdateAccumulatorIfPossible(pos)) {
+        RefreshAccumulator(pos);
+      }
+      const auto& accumulation = pos.state()->accumulator.accumulation;
+
+  #if defined(USE_AVX2)
+      constexpr IndexType kNumChunks = kHalfDimensions / kSimdWidth;
+      constexpr int kControl = 0b11011000;
+      const __m256i kZero = _mm256_setzero_si256();
+
+  #elif defined(USE_SSE2)
+      constexpr IndexType kNumChunks = kHalfDimensions / kSimdWidth;
+
+  #ifdef USE_SSE41
+      const __m128i kZero = _mm_setzero_si128();
+  #else
+      const __m128i k0x80s = _mm_set1_epi8(-128);
+  #endif
+
+  #elif defined(USE_MMX)
+      constexpr IndexType kNumChunks = kHalfDimensions / kSimdWidth;
+      const __m64 k0x80s = _mm_set1_pi8(-128);
+
+  #elif defined(USE_NEON)
+      constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2);
+      const int8x8_t kZero = {0};
+  #endif
+
+      const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()};
+      for (IndexType p = 0; p < 2; ++p) {
+        const IndexType offset = kHalfDimensions * p;
+
+  #if defined(USE_AVX2)
+        auto out = reinterpret_cast<__m256i*>(&output[offset]);
+        for (IndexType j = 0; j < kNumChunks; ++j) {
+          __m256i sum0 = _mm256_loadA_si256(
+              &reinterpret_cast<const __m256i*>(accumulation[perspectives[p]][0])[j * 2 + 0]);
+          __m256i sum1 = _mm256_loadA_si256(
+            &reinterpret_cast<const __m256i*>(accumulation[perspectives[p]][0])[j * 2 + 1]);
+          _mm256_storeA_si256(&out[j], _mm256_permute4x64_epi64(_mm256_max_epi8(
+              _mm256_packs_epi16(sum0, sum1), kZero), kControl));
+        }
+
+  #elif defined(USE_SSE2)
+        auto out = reinterpret_cast<__m128i*>(&output[offset]);
+        for (IndexType j = 0; j < kNumChunks; ++j) {
+          __m128i sum0 = _mm_load_si128(&reinterpret_cast<const __m128i*>(
+              accumulation[perspectives[p]][0])[j * 2 + 0]);
+          __m128i sum1 = _mm_load_si128(&reinterpret_cast<const __m128i*>(
+              accumulation[perspectives[p]][0])[j * 2 + 1]);
+      const __m128i packedbytes = _mm_packs_epi16(sum0, sum1);
+
+          _mm_store_si128(&out[j],
+
+  #ifdef USE_SSE41
+            _mm_max_epi8(packedbytes, kZero)
+  #else
+            _mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s)
+  #endif
+
+          );
+        }
+
+  #elif defined(USE_MMX)
+        auto out = reinterpret_cast<__m64*>(&output[offset]);
+        for (IndexType j = 0; j < kNumChunks; ++j) {
+          __m64 sum0 = *(&reinterpret_cast<const __m64*>(
+              accumulation[perspectives[p]][0])[j * 2 + 0]);
+          __m64 sum1 = *(&reinterpret_cast<const __m64*>(
+              accumulation[perspectives[p]][0])[j * 2 + 1]);
+          const __m64 packedbytes = _mm_packs_pi16(sum0, sum1);
+          out[j] = _mm_subs_pi8(_mm_adds_pi8(packedbytes, k0x80s), k0x80s);
+        }
+
+  #elif defined(USE_NEON)
+        const auto out = reinterpret_cast<int8x8_t*>(&output[offset]);
+        for (IndexType j = 0; j < kNumChunks; ++j) {
+          int16x8_t sum = reinterpret_cast<const int16x8_t*>(
+              accumulation[perspectives[p]][0])[j];
+          out[j] = vmax_s8(vqmovn_s16(sum), kZero);
+        }
+
+  #else
+        for (IndexType j = 0; j < kHalfDimensions; ++j) {
+          BiasType sum = accumulation[static_cast<int>(perspectives[p])][0][j];
+          output[offset + j] = static_cast<OutputType>(
+              std::max<int>(0, std::min<int>(127, sum)));
+        }
+  #endif
+
+      }
+  #if defined(USE_MMX)
+      _mm_empty();
+  #endif
+    }
+
+   private:
+    // Calculate cumulative value without using difference calculation
+    void RefreshAccumulator(const Position& pos) const {
+      auto& accumulator = pos.state()->accumulator;
+      IndexType i = 0;
+      Features::IndexList active_indices[2];
+      RawFeatures::AppendActiveIndices(pos, kRefreshTriggers[i],
+                                       active_indices);
+      for (Color perspective : { WHITE, BLACK }) {
+        std::memcpy(accumulator.accumulation[perspective][i], biases_,
+                   kHalfDimensions * sizeof(BiasType));
+        for (const auto index : active_indices[perspective]) {
+          const IndexType offset = kHalfDimensions * index;
+  #if defined(USE_AVX512)
+          auto accumulation = reinterpret_cast<__m512i*>(
+              &accumulator.accumulation[perspective][i][0]);
+          auto column = reinterpret_cast<const __m512i*>(&weights_[offset]);
+          constexpr IndexType kNumChunks = kHalfDimensions / kSimdWidth;
+          for (IndexType j = 0; j < kNumChunks; ++j)
+            _mm512_storeA_si512(&accumulation[j], _mm512_add_epi16(_mm512_loadA_si512(&accumulation[j]), column[j]));
+
+  #elif defined(USE_AVX2)
+          auto accumulation = reinterpret_cast<__m256i*>(
+              &accumulator.accumulation[perspective][i][0]);
+          auto column = reinterpret_cast<const __m256i*>(&weights_[offset]);
+          constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2);
+          for (IndexType j = 0; j < kNumChunks; ++j)
+            _mm256_storeA_si256(&accumulation[j], _mm256_add_epi16(_mm256_loadA_si256(&accumulation[j]), column[j]));
+
+  #elif defined(USE_SSE2)
+          auto accumulation = reinterpret_cast<__m128i*>(
+              &accumulator.accumulation[perspective][i][0]);
+          auto column = reinterpret_cast<const __m128i*>(&weights_[offset]);
+          constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2);
+          for (IndexType j = 0; j < kNumChunks; ++j)
+            accumulation[j] = _mm_add_epi16(accumulation[j], column[j]);
+
+  #elif defined(USE_MMX)
+          auto accumulation = reinterpret_cast<__m64*>(
+              &accumulator.accumulation[perspective][i][0]);
+          auto column = reinterpret_cast<const __m64*>(&weights_[offset]);
+          constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2);
+          for (IndexType j = 0; j < kNumChunks; ++j) {
+            accumulation[j] = _mm_add_pi16(accumulation[j], column[j]);
+          }
+
+  #elif defined(USE_NEON)
+          auto accumulation = reinterpret_cast<int16x8_t*>(
+              &accumulator.accumulation[perspective][i][0]);
+          auto column = reinterpret_cast<const int16x8_t*>(&weights_[offset]);
+          constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2);
+          for (IndexType j = 0; j < kNumChunks; ++j)
+            accumulation[j] = vaddq_s16(accumulation[j], column[j]);
+
+  #else
+          for (IndexType j = 0; j < kHalfDimensions; ++j)
+            accumulator.accumulation[perspective][i][j] += weights_[offset + j];
+  #endif
+
+        }
+      }
+  #if defined(USE_MMX)
+      _mm_empty();
+  #endif
+
+      accumulator.computed_accumulation = true;
+      accumulator.computed_score = false;
+    }
+
+    // Calculate cumulative value using difference calculation
+    void UpdateAccumulator(const Position& pos) const {
+      const auto prev_accumulator = pos.state()->previous->accumulator;
+      auto& accumulator = pos.state()->accumulator;
+      IndexType i = 0;
+      Features::IndexList removed_indices[2], added_indices[2];
+      bool reset[2];
+      RawFeatures::AppendChangedIndices(pos, kRefreshTriggers[i],
+                                        removed_indices, added_indices, reset);
+      for (Color perspective : { WHITE, BLACK }) {
+
+  #if defined(USE_AVX2)
+        constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2);
+        auto accumulation = reinterpret_cast<__m256i*>(
+            &accumulator.accumulation[perspective][i][0]);
+
+  #elif defined(USE_SSE2)
+        constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2);
+        auto accumulation = reinterpret_cast<__m128i*>(
+            &accumulator.accumulation[perspective][i][0]);
+
+  #elif defined(USE_MMX)
+        constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2);
+        auto accumulation = reinterpret_cast<__m64*>(
+            &accumulator.accumulation[perspective][i][0]);
+
+  #elif defined(USE_NEON)
+        constexpr IndexType kNumChunks = kHalfDimensions / (kSimdWidth / 2);
+        auto accumulation = reinterpret_cast<int16x8_t*>(
+            &accumulator.accumulation[perspective][i][0]);
+  #endif
+
+        if (reset[perspective]) {
+          std::memcpy(accumulator.accumulation[perspective][i], biases_,
+                      kHalfDimensions * sizeof(BiasType));
+        } else {
+          std::memcpy(accumulator.accumulation[perspective][i],
+                      prev_accumulator.accumulation[perspective][i],
+                      kHalfDimensions * sizeof(BiasType));
+          // Difference calculation for the deactivated features
+          for (const auto index : removed_indices[perspective]) {
+            const IndexType offset = kHalfDimensions * index;
+
+  #if defined(USE_AVX2)
+            auto column = reinterpret_cast<const __m256i*>(&weights_[offset]);
+            for (IndexType j = 0; j < kNumChunks; ++j) {
+              accumulation[j] = _mm256_sub_epi16(accumulation[j], column[j]);
+            }
+
+  #elif defined(USE_SSE2)
+            auto column = reinterpret_cast<const __m128i*>(&weights_[offset]);
+            for (IndexType j = 0; j < kNumChunks; ++j) {
+              accumulation[j] = _mm_sub_epi16(accumulation[j], column[j]);
+            }
+
+  #elif defined(USE_MMX)
+            auto column = reinterpret_cast<const __m64*>(&weights_[offset]);
+            for (IndexType j = 0; j < kNumChunks; ++j) {
+              accumulation[j] = _mm_sub_pi16(accumulation[j], column[j]);
+            }
+
+  #elif defined(USE_NEON)
+            auto column = reinterpret_cast<const int16x8_t*>(&weights_[offset]);
+            for (IndexType j = 0; j < kNumChunks; ++j) {
+              accumulation[j] = vsubq_s16(accumulation[j], column[j]);
+            }
+
+  #else
+            for (IndexType j = 0; j < kHalfDimensions; ++j) {
+              accumulator.accumulation[perspective][i][j] -=
+                  weights_[offset + j];
+            }
+  #endif
+
+          }
+        }
+        { // Difference calculation for the activated features
+          for (const auto index : added_indices[perspective]) {
+            const IndexType offset = kHalfDimensions * index;
+
+  #if defined(USE_AVX2)
+            auto column = reinterpret_cast<const __m256i*>(&weights_[offset]);
+            for (IndexType j = 0; j < kNumChunks; ++j) {
+              accumulation[j] = _mm256_add_epi16(accumulation[j], column[j]);
+            }
+
+  #elif defined(USE_SSE2)
+            auto column = reinterpret_cast<const __m128i*>(&weights_[offset]);
+            for (IndexType j = 0; j < kNumChunks; ++j) {
+              accumulation[j] = _mm_add_epi16(accumulation[j], column[j]);
+            }
+
+  #elif defined(USE_MMX)
+            auto column = reinterpret_cast<const __m64*>(&weights_[offset]);
+            for (IndexType j = 0; j < kNumChunks; ++j) {
+              accumulation[j] = _mm_add_pi16(accumulation[j], column[j]);
+            }
+
+  #elif defined(USE_NEON)
+            auto column = reinterpret_cast<const int16x8_t*>(&weights_[offset]);
+            for (IndexType j = 0; j < kNumChunks; ++j) {
+              accumulation[j] = vaddq_s16(accumulation[j], column[j]);
+            }
+
+  #else
+            for (IndexType j = 0; j < kHalfDimensions; ++j) {
+              accumulator.accumulation[perspective][i][j] +=
+                  weights_[offset + j];
+            }
+  #endif
+
+          }
+        }
+      }
+  #if defined(USE_MMX)
+      _mm_empty();
+  #endif
+
+      accumulator.computed_accumulation = true;
+      accumulator.computed_score = false;
+    }
+
+    using BiasType = std::int16_t;
+    using WeightType = std::int16_t;
+
+    alignas(kCacheLineSize) BiasType biases_[kHalfDimensions];
+    alignas(kCacheLineSize)
+        WeightType weights_[kHalfDimensions * kInputDimensions];
+  };
+
+}  // namespace Eval::NNUE
+
+#endif // #ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED
diff --git a/src/pawns.cpp b/src/pawns.cpp
new file mode 100644 (file)
index 0000000..af0f661
--- /dev/null
@@ -0,0 +1,281 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <algorithm>
+#include <cassert>
+
+#include "bitboard.h"
+#include "pawns.h"
+#include "position.h"
+#include "thread.h"
+
+namespace {
+
+  #define V Value
+  #define S(mg, eg) make_score(mg, eg)
+
+  // Pawn penalties
+  constexpr Score Backward      = S( 8, 27);
+  constexpr Score Doubled       = S(11, 55);
+  constexpr Score Isolated      = S( 5, 17);
+  constexpr Score WeakLever     = S( 2, 54);
+  constexpr Score WeakUnopposed = S(15, 25);
+
+  // Bonus for blocked pawns at 5th or 6th rank
+  constexpr Score BlockedPawn[2] = { S(-13, -4), S(-4, 3) };
+
+  constexpr Score BlockedStorm[RANK_NB] = {
+    S(0, 0), S(0, 0), S(76, 78), S(-10, 15), S(-7, 10), S(-4, 6), S(-1, 2)
+  };
+
+  // Connected pawn bonus
+  constexpr int Connected[RANK_NB] = { 0, 7, 8, 11, 24, 45, 85 };
+
+  // Strength of pawn shelter for our king by [distance from edge][rank].
+  // RANK_1 = 0 is used for files where we have no pawn, or pawn is behind our king.
+  constexpr Value ShelterStrength[int(FILE_NB) / 2][RANK_NB] = {
+    { V( -6), V( 81), V( 93), V( 58), V( 39), V( 18), V(  25) },
+    { V(-43), V( 61), V( 35), V(-49), V(-29), V(-11), V( -63) },
+    { V(-10), V( 75), V( 23), V( -2), V( 32), V(  3), V( -45) },
+    { V(-39), V(-13), V(-29), V(-52), V(-48), V(-67), V(-166) }
+  };
+
+  // Danger of enemy pawns moving toward our king by [distance from edge][rank].
+  // RANK_1 = 0 is used for files where the enemy has no pawn, or their pawn
+  // is behind our king. Note that UnblockedStorm[0][1-2] accommodate opponent pawn
+  // on edge, likely blocked by our king.
+  constexpr Value UnblockedStorm[int(FILE_NB) / 2][RANK_NB] = {
+    { V( 85), V(-289), V(-166), V(97), V(50), V( 45), V( 50) },
+    { V( 46), V( -25), V( 122), V(45), V(37), V(-10), V( 20) },
+    { V( -6), V(  51), V( 168), V(34), V(-2), V(-22), V(-14) },
+    { V(-15), V( -11), V( 101), V( 4), V(11), V(-15), V(-29) }
+  };
+
+  #undef S
+  #undef V
+
+
+  /// evaluate() calculates a score for the static pawn structure of the given position.
+  /// We cannot use the location of pieces or king in this function, as the evaluation
+  /// of the pawn structure will be stored in a small cache for speed reasons, and will
+  /// be re-used even when the pieces have moved.
+
+  template<Color Us>
+  Score evaluate(const Position& pos, Pawns::Entry* e) {
+
+    constexpr Color     Them = ~Us;
+    constexpr Direction Up   = pawn_push(Us);
+
+    Bitboard neighbours, stoppers, support, phalanx, opposed;
+    Bitboard lever, leverPush, blocked;
+    Square s;
+    bool backward, passed, doubled;
+    Score score = SCORE_ZERO;
+    const Square* pl = pos.squares<PAWN>(Us);
+
+    Bitboard ourPawns   = pos.pieces(  Us, PAWN);
+    Bitboard theirPawns = pos.pieces(Them, PAWN);
+
+    Bitboard doubleAttackThem = pawn_double_attacks_bb<Them>(theirPawns);
+
+    e->passedPawns[Us] = 0;
+    e->kingSquares[Us] = SQ_NONE;
+    e->pawnAttacks[Us] = e->pawnAttacksSpan[Us] = pawn_attacks_bb<Us>(ourPawns);
+    e->blockedCount += popcount(shift<Up>(ourPawns) & (theirPawns | doubleAttackThem));
+
+    // Loop through all pawns of the current color and score each pawn
+    while ((s = *pl++) != SQ_NONE)
+    {
+        assert(pos.piece_on(s) == make_piece(Us, PAWN));
+
+        Rank r = relative_rank(Us, s);
+
+        // Flag the pawn
+        opposed    = theirPawns & forward_file_bb(Us, s);
+        blocked    = theirPawns & (s + Up);
+        stoppers   = theirPawns & passed_pawn_span(Us, s);
+        lever      = theirPawns & pawn_attacks_bb(Us, s);
+        leverPush  = theirPawns & pawn_attacks_bb(Us, s + Up);
+        doubled    = ourPawns   & (s - Up);
+        neighbours = ourPawns   & adjacent_files_bb(s);
+        phalanx    = neighbours & rank_bb(s);
+        support    = neighbours & rank_bb(s - Up);
+
+        // A pawn is backward when it is behind all pawns of the same color on
+        // the adjacent files and cannot safely advance.
+        backward =  !(neighbours & forward_ranks_bb(Them, s + Up))
+                  && (leverPush | blocked);
+
+        // Compute additional span if pawn is not backward nor blocked
+        if (!backward && !blocked)
+            e->pawnAttacksSpan[Us] |= pawn_attack_span(Us, s);
+
+        // A pawn is passed if one of the three following conditions is true:
+        // (a) there is no stoppers except some levers
+        // (b) the only stoppers are the leverPush, but we outnumber them
+        // (c) there is only one front stopper which can be levered.
+        //     (Refined in Evaluation::passed)
+        passed =   !(stoppers ^ lever)
+                || (   !(stoppers ^ leverPush)
+                    && popcount(phalanx) >= popcount(leverPush))
+                || (   stoppers == blocked && r >= RANK_5
+                    && (shift<Up>(support) & ~(theirPawns | doubleAttackThem)));
+
+        passed &= !(forward_file_bb(Us, s) & ourPawns);
+
+        // Passed pawns will be properly scored later in evaluation when we have
+        // full attack info.
+        if (passed)
+            e->passedPawns[Us] |= s;
+
+        // Score this pawn
+        if (support | phalanx)
+        {
+            int v =  Connected[r] * (2 + bool(phalanx) - bool(opposed))
+                   + 21 * popcount(support);
+
+            score += make_score(v, v * (r - 2) / 4);
+        }
+
+        else if (!neighbours)
+        {
+            if (     opposed
+                &&  (ourPawns & forward_file_bb(Them, s))
+                && !(theirPawns & adjacent_files_bb(s)))
+                score -= Doubled;
+            else
+                score -=  Isolated
+                        + WeakUnopposed * !opposed;
+        }
+
+        else if (backward)
+            score -=  Backward
+                    + WeakUnopposed * !opposed;
+
+        if (!support)
+            score -=  Doubled * doubled
+                    + WeakLever * more_than_one(lever);
+
+        if (blocked && r > RANK_4)
+            score += BlockedPawn[r-4];
+    }
+
+    return score;
+  }
+
+} // namespace
+
+namespace Pawns {
+
+
+/// Pawns::probe() looks up the current position's pawns configuration in
+/// the pawns hash table. It returns a pointer to the Entry if the position
+/// is found. Otherwise a new Entry is computed and stored there, so we don't
+/// have to recompute all when the same pawns configuration occurs again.
+
+Entry* probe(const Position& pos) {
+
+  Key key = pos.pawn_key();
+  Entry* e = pos.this_thread()->pawnsTable[key];
+
+  if (e->key == key)
+      return e;
+
+  e->key = key;
+  e->blockedCount = 0;
+  e->scores[WHITE] = evaluate<WHITE>(pos, e);
+  e->scores[BLACK] = evaluate<BLACK>(pos, e);
+
+  return e;
+}
+
+
+/// Entry::evaluate_shelter() calculates the shelter bonus and the storm
+/// penalty for a king, looking at the king file and the two closest files.
+
+template<Color Us>
+Score Entry::evaluate_shelter(const Position& pos, Square ksq) const {
+
+  constexpr Color Them = ~Us;
+
+  Bitboard b = pos.pieces(PAWN) & ~forward_ranks_bb(Them, ksq);
+  Bitboard ourPawns = b & pos.pieces(Us) & ~pawnAttacks[Them];
+  Bitboard theirPawns = b & pos.pieces(Them);
+
+  Score bonus = make_score(5, 5);
+
+  File center = std::clamp(file_of(ksq), FILE_B, FILE_G);
+  for (File f = File(center - 1); f <= File(center + 1); ++f)
+  {
+      b = ourPawns & file_bb(f);
+      int ourRank = b ? relative_rank(Us, frontmost_sq(Them, b)) : 0;
+
+      b = theirPawns & file_bb(f);
+      int theirRank = b ? relative_rank(Us, frontmost_sq(Them, b)) : 0;
+
+      int d = edge_distance(f);
+      bonus += make_score(ShelterStrength[d][ourRank], 0);
+
+      if (ourRank && (ourRank == theirRank - 1))
+          bonus -= BlockedStorm[theirRank];
+      else
+          bonus -= make_score(UnblockedStorm[d][theirRank], 0);
+  }
+
+  return bonus;
+}
+
+
+/// Entry::do_king_safety() calculates a bonus for king safety. It is called only
+/// when king square changes, which is about 20% of total king_safety() calls.
+
+template<Color Us>
+Score Entry::do_king_safety(const Position& pos) {
+
+  Square ksq = pos.square<KING>(Us);
+  kingSquares[Us] = ksq;
+  castlingRights[Us] = pos.castling_rights(Us);
+  auto compare = [](Score a, Score b) { return mg_value(a) < mg_value(b); };
+
+  Score shelter = evaluate_shelter<Us>(pos, ksq);
+
+  // If we can castle use the bonus after castling if it is bigger
+
+  if (pos.can_castle(Us & KING_SIDE))
+      shelter = std::max(shelter, evaluate_shelter<Us>(pos, relative_square(Us, SQ_G1)), compare);
+
+  if (pos.can_castle(Us & QUEEN_SIDE))
+      shelter = std::max(shelter, evaluate_shelter<Us>(pos, relative_square(Us, SQ_C1)), compare);
+
+  // In endgame we like to bring our king near our closest pawn
+  Bitboard pawns = pos.pieces(Us, PAWN);
+  int minPawnDist = 6;
+
+  if (pawns & attacks_bb<KING>(ksq))
+      minPawnDist = 1;
+  else while (pawns)
+      minPawnDist = std::min(minPawnDist, distance(ksq, pop_lsb(&pawns)));
+
+  return shelter - make_score(0, 16 * minPawnDist);
+}
+
+// Explicit template instantiation
+template Score Entry::do_king_safety<WHITE>(const Position& pos);
+template Score Entry::do_king_safety<BLACK>(const Position& pos);
+
+} // namespace Pawns
diff --git a/src/pawns.h b/src/pawns.h
new file mode 100644 (file)
index 0000000..5499826
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef PAWNS_H_INCLUDED
+#define PAWNS_H_INCLUDED
+
+#include "misc.h"
+#include "position.h"
+#include "types.h"
+
+namespace Pawns {
+
+/// Pawns::Entry contains various information about a pawn structure. A lookup
+/// to the pawn hash table (performed by calling the probe function) returns a
+/// pointer to an Entry object.
+
+struct Entry {
+
+  Score pawn_score(Color c) const { return scores[c]; }
+  Bitboard pawn_attacks(Color c) const { return pawnAttacks[c]; }
+  Bitboard passed_pawns(Color c) const { return passedPawns[c]; }
+  Bitboard pawn_attacks_span(Color c) const { return pawnAttacksSpan[c]; }
+  int passed_count() const { return popcount(passedPawns[WHITE] | passedPawns[BLACK]); }
+  int blocked_count() const { return blockedCount; }
+
+  template<Color Us>
+  Score king_safety(const Position& pos) {
+    return  kingSquares[Us] == pos.square<KING>(Us) && castlingRights[Us] == pos.castling_rights(Us)
+          ? kingSafety[Us] : (kingSafety[Us] = do_king_safety<Us>(pos));
+  }
+
+  template<Color Us>
+  Score do_king_safety(const Position& pos);
+
+  template<Color Us>
+  Score evaluate_shelter(const Position& pos, Square ksq) const;
+
+  Key key;
+  Score scores[COLOR_NB];
+  Bitboard passedPawns[COLOR_NB];
+  Bitboard pawnAttacks[COLOR_NB];
+  Bitboard pawnAttacksSpan[COLOR_NB];
+  Square kingSquares[COLOR_NB];
+  Score kingSafety[COLOR_NB];
+  int castlingRights[COLOR_NB];
+  int blockedCount;
+};
+
+typedef HashTable<Entry, 131072> Table;
+
+Entry* probe(const Position& pos);
+
+} // namespace Pawns
+
+#endif // #ifndef PAWNS_H_INCLUDED
diff --git a/src/position.cpp b/src/position.cpp
new file mode 100644 (file)
index 0000000..fe89b75
--- /dev/null
@@ -0,0 +1,1348 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <algorithm>
+#include <cassert>
+#include <cstddef> // For offsetof()
+#include <cstring> // For std::memset, std::memcmp
+#include <iomanip>
+#include <sstream>
+
+#include "bitboard.h"
+#include "misc.h"
+#include "movegen.h"
+#include "position.h"
+#include "thread.h"
+#include "tt.h"
+#include "uci.h"
+#include "syzygy/tbprobe.h"
+
+using std::string;
+
+namespace Zobrist {
+
+  Key psq[PIECE_NB][SQUARE_NB];
+  Key enpassant[FILE_NB];
+  Key castling[CASTLING_RIGHT_NB];
+  Key side, noPawns;
+}
+
+namespace {
+
+const string PieceToChar(" PNBRQK  pnbrqk");
+
+constexpr Piece Pieces[] = { W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING,
+                             B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING };
+} // namespace
+
+
+/// operator<<(Position) returns an ASCII representation of the position
+
+std::ostream& operator<<(std::ostream& os, const Position& pos) {
+
+  os << "\n +---+---+---+---+---+---+---+---+\n";
+
+  for (Rank r = RANK_8; r >= RANK_1; --r)
+  {
+      for (File f = FILE_A; f <= FILE_H; ++f)
+          os << " | " << PieceToChar[pos.piece_on(make_square(f, r))];
+
+      os << " | " << (1 + r) << "\n +---+---+---+---+---+---+---+---+\n";
+  }
+
+  os << "   a   b   c   d   e   f   g   h\n"
+     << "\nFen: " << pos.fen() << "\nKey: " << std::hex << std::uppercase
+     << std::setfill('0') << std::setw(16) << pos.key()
+     << std::setfill(' ') << std::dec << "\nCheckers: ";
+
+  for (Bitboard b = pos.checkers(); b; )
+      os << UCI::square(pop_lsb(&b)) << " ";
+
+  if (    int(Tablebases::MaxCardinality) >= popcount(pos.pieces())
+      && !pos.can_castle(ANY_CASTLING))
+  {
+      StateInfo st;
+      Position p;
+      p.set(pos.fen(), pos.is_chess960(), &st, pos.this_thread());
+      Tablebases::ProbeState s1, s2;
+      Tablebases::WDLScore wdl = Tablebases::probe_wdl(p, &s1);
+      int dtz = Tablebases::probe_dtz(p, &s2);
+      os << "\nTablebases WDL: " << std::setw(4) << wdl << " (" << s1 << ")"
+         << "\nTablebases DTZ: " << std::setw(4) << dtz << " (" << s2 << ")";
+  }
+
+  return os;
+}
+
+
+// Marcel van Kervinck's cuckoo algorithm for fast detection of "upcoming repetition"
+// situations. Description of the algorithm in the following paper:
+// https://marcelk.net/2013-04-06/paper/upcoming-rep-v2.pdf
+
+// First and second hash functions for indexing the cuckoo tables
+inline int H1(Key h) { return h & 0x1fff; }
+inline int H2(Key h) { return (h >> 16) & 0x1fff; }
+
+// Cuckoo tables with Zobrist hashes of valid reversible moves, and the moves themselves
+Key cuckoo[8192];
+Move cuckooMove[8192];
+
+
+/// Position::init() initializes at startup the various arrays used to compute hash keys
+
+void Position::init() {
+
+  PRNG rng(1070372);
+
+  for (Piece pc : Pieces)
+      for (Square s = SQ_A1; s <= SQ_H8; ++s)
+          Zobrist::psq[pc][s] = rng.rand<Key>();
+
+  for (File f = FILE_A; f <= FILE_H; ++f)
+      Zobrist::enpassant[f] = rng.rand<Key>();
+
+  for (int cr = NO_CASTLING; cr <= ANY_CASTLING; ++cr)
+      Zobrist::castling[cr] = rng.rand<Key>();
+
+  Zobrist::side = rng.rand<Key>();
+  Zobrist::noPawns = rng.rand<Key>();
+
+  // Prepare the cuckoo tables
+  std::memset(cuckoo, 0, sizeof(cuckoo));
+  std::memset(cuckooMove, 0, sizeof(cuckooMove));
+  int count = 0;
+  for (Piece pc : Pieces)
+      for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
+          for (Square s2 = Square(s1 + 1); s2 <= SQ_H8; ++s2)
+              if ((type_of(pc) != PAWN) && (attacks_bb(type_of(pc), s1, 0) & s2))
+              {
+                  Move move = make_move(s1, s2);
+                  Key key = Zobrist::psq[pc][s1] ^ Zobrist::psq[pc][s2] ^ Zobrist::side;
+                  int i = H1(key);
+                  while (true)
+                  {
+                      std::swap(cuckoo[i], key);
+                      std::swap(cuckooMove[i], move);
+                      if (move == MOVE_NONE) // Arrived at empty slot?
+                          break;
+                      i = (i == H1(key)) ? H2(key) : H1(key); // Push victim to alternative slot
+                  }
+                  count++;
+             }
+  assert(count == 3668);
+}
+
+
+/// Position::set() initializes the position object with the given FEN string.
+/// This function is not very robust - make sure that input FENs are correct,
+/// this is assumed to be the responsibility of the GUI.
+
+Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Thread* th) {
+/*
+   A FEN string defines a particular position using only the ASCII character set.
+
+   A FEN string contains six fields separated by a space. The fields are:
+
+   1) Piece placement (from white's perspective). Each rank is described, starting
+      with rank 8 and ending with rank 1. Within each rank, the contents of each
+      square are described from file A through file H. Following the Standard
+      Algebraic Notation (SAN), each piece is identified by a single letter taken
+      from the standard English names. White pieces are designated using upper-case
+      letters ("PNBRQK") whilst Black uses lowercase ("pnbrqk"). Blank squares are
+      noted using digits 1 through 8 (the number of blank squares), and "/"
+      separates ranks.
+
+   2) Active color. "w" means white moves next, "b" means black.
+
+   3) Castling availability. If neither side can castle, this is "-". Otherwise,
+      this has one or more letters: "K" (White can castle kingside), "Q" (White
+      can castle queenside), "k" (Black can castle kingside), and/or "q" (Black
+      can castle queenside).
+
+   4) En passant target square (in algebraic notation). If there's no en passant
+      target square, this is "-". If a pawn has just made a 2-square move, this
+      is the position "behind" the pawn. Following X-FEN standard, this is recorded only
+      if there is a pawn in position to make an en passant capture, and if there really
+      is a pawn that might have advanced two squares.
+
+   5) Halfmove clock. This is the number of halfmoves since the last pawn advance
+      or capture. This is used to determine if a draw can be claimed under the
+      fifty-move rule.
+
+   6) Fullmove number. The number of the full move. It starts at 1, and is
+      incremented after Black's move.
+*/
+
+  unsigned char col, row, token;
+  size_t idx;
+  Square sq = SQ_A8;
+  std::istringstream ss(fenStr);
+
+  std::memset(this, 0, sizeof(Position));
+  std::memset(si, 0, sizeof(StateInfo));
+  std::fill_n(&pieceList[0][0], sizeof(pieceList) / sizeof(Square), SQ_NONE);
+  st = si;
+
+  ss >> std::noskipws;
+
+  // 1. Piece placement
+  while ((ss >> token) && !isspace(token))
+  {
+      if (isdigit(token))
+          sq += (token - '0') * EAST; // Advance the given number of files
+
+      else if (token == '/')
+          sq += 2 * SOUTH;
+
+      else if ((idx = PieceToChar.find(token)) != string::npos) {
+          put_piece(Piece(idx), sq);
+          ++sq;
+      }
+  }
+
+  // 2. Active color
+  ss >> token;
+  sideToMove = (token == 'w' ? WHITE : BLACK);
+  ss >> token;
+
+  // 3. Castling availability. Compatible with 3 standards: Normal FEN standard,
+  // Shredder-FEN that uses the letters of the columns on which the rooks began
+  // the game instead of KQkq and also X-FEN standard that, in case of Chess960,
+  // if an inner rook is associated with the castling right, the castling tag is
+  // replaced by the file letter of the involved rook, as for the Shredder-FEN.
+  while ((ss >> token) && !isspace(token))
+  {
+      Square rsq;
+      Color c = islower(token) ? BLACK : WHITE;
+      Piece rook = make_piece(c, ROOK);
+
+      token = char(toupper(token));
+
+      if (token == 'K')
+          for (rsq = relative_square(c, SQ_H1); piece_on(rsq) != rook; --rsq) {}
+
+      else if (token == 'Q')
+          for (rsq = relative_square(c, SQ_A1); piece_on(rsq) != rook; ++rsq) {}
+
+      else if (token >= 'A' && token <= 'H')
+          rsq = make_square(File(token - 'A'), relative_rank(c, RANK_1));
+
+      else
+          continue;
+
+      set_castling_right(c, rsq);
+  }
+
+  // 4. En passant square.
+  // Ignore if square is invalid or not on side to move relative rank 6.
+  bool enpassant = false;
+
+  if (   ((ss >> col) && (col >= 'a' && col <= 'h'))
+      && ((ss >> row) && (row == (sideToMove == WHITE ? '6' : '3'))))
+  {
+      st->epSquare = make_square(File(col - 'a'), Rank(row - '1'));
+
+      // En passant square will be considered only if
+      // a) side to move have a pawn threatening epSquare
+      // b) there is an enemy pawn in front of epSquare
+      // c) there is no piece on epSquare or behind epSquare
+      enpassant = pawn_attacks_bb(~sideToMove, st->epSquare) & pieces(sideToMove, PAWN)
+               && (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove)))
+               && !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove))));
+  }
+
+  if (!enpassant)
+      st->epSquare = SQ_NONE;
+
+  // 5-6. Halfmove clock and fullmove number
+  ss >> std::skipws >> st->rule50 >> gamePly;
+
+  // Convert from fullmove starting from 1 to gamePly starting from 0,
+  // handle also common incorrect FEN with fullmove = 0.
+  gamePly = std::max(2 * (gamePly - 1), 0) + (sideToMove == BLACK);
+
+  chess960 = isChess960;
+  thisThread = th;
+  set_state(st);
+
+  assert(pos_is_ok());
+
+  return *this;
+}
+
+
+/// Position::set_castling_right() is a helper function used to set castling
+/// rights given the corresponding color and the rook starting square.
+
+void Position::set_castling_right(Color c, Square rfrom) {
+
+  Square kfrom = square<KING>(c);
+  CastlingRights cr = c & (kfrom < rfrom ? KING_SIDE: QUEEN_SIDE);
+
+  st->castlingRights |= cr;
+  castlingRightsMask[kfrom] |= cr;
+  castlingRightsMask[rfrom] |= cr;
+  castlingRookSquare[cr] = rfrom;
+
+  Square kto = relative_square(c, cr & KING_SIDE ? SQ_G1 : SQ_C1);
+  Square rto = relative_square(c, cr & KING_SIDE ? SQ_F1 : SQ_D1);
+
+  castlingPath[cr] =   (between_bb(rfrom, rto) | between_bb(kfrom, kto) | rto | kto)
+                    & ~(kfrom | rfrom);
+}
+
+
+/// Position::set_check_info() sets king attacks to detect if a move gives check
+
+void Position::set_check_info(StateInfo* si) const {
+
+  si->blockersForKing[WHITE] = slider_blockers(pieces(BLACK), square<KING>(WHITE), si->pinners[BLACK]);
+  si->blockersForKing[BLACK] = slider_blockers(pieces(WHITE), square<KING>(BLACK), si->pinners[WHITE]);
+
+  Square ksq = square<KING>(~sideToMove);
+
+  si->checkSquares[PAWN]   = pawn_attacks_bb(~sideToMove, ksq);
+  si->checkSquares[KNIGHT] = attacks_bb<KNIGHT>(ksq);
+  si->checkSquares[BISHOP] = attacks_bb<BISHOP>(ksq, pieces());
+  si->checkSquares[ROOK]   = attacks_bb<ROOK>(ksq, pieces());
+  si->checkSquares[QUEEN]  = si->checkSquares[BISHOP] | si->checkSquares[ROOK];
+  si->checkSquares[KING]   = 0;
+}
+
+
+/// Position::set_state() computes the hash keys of the position, and other
+/// data that once computed is updated incrementally as moves are made.
+/// The function is only used when a new position is set up, and to verify
+/// the correctness of the StateInfo data when running in debug mode.
+
+void Position::set_state(StateInfo* si) const {
+
+  si->key = si->materialKey = 0;
+  si->pawnKey = Zobrist::noPawns;
+  si->nonPawnMaterial[WHITE] = si->nonPawnMaterial[BLACK] = VALUE_ZERO;
+  si->checkersBB = attackers_to(square<KING>(sideToMove)) & pieces(~sideToMove);
+
+  set_check_info(si);
+
+  for (Bitboard b = pieces(); b; )
+  {
+      Square s = pop_lsb(&b);
+      Piece pc = piece_on(s);
+      si->key ^= Zobrist::psq[pc][s];
+
+      if (type_of(pc) == PAWN)
+          si->pawnKey ^= Zobrist::psq[pc][s];
+
+      else if (type_of(pc) != KING)
+          si->nonPawnMaterial[color_of(pc)] += PieceValue[MG][pc];
+  }
+
+  if (si->epSquare != SQ_NONE)
+      si->key ^= Zobrist::enpassant[file_of(si->epSquare)];
+
+  if (sideToMove == BLACK)
+      si->key ^= Zobrist::side;
+
+  si->key ^= Zobrist::castling[si->castlingRights];
+
+  for (Piece pc : Pieces)
+      for (int cnt = 0; cnt < pieceCount[pc]; ++cnt)
+          si->materialKey ^= Zobrist::psq[pc][cnt];
+}
+
+
+/// Position::set() is an overload to initialize the position object with
+/// the given endgame code string like "KBPKN". It is mainly a helper to
+/// get the material key out of an endgame code.
+
+Position& Position::set(const string& code, Color c, StateInfo* si) {
+
+  assert(code[0] == 'K');
+
+  string sides[] = { code.substr(code.find('K', 1)),      // Weak
+                     code.substr(0, std::min(code.find('v'), code.find('K', 1))) }; // Strong
+
+  assert(sides[0].length() > 0 && sides[0].length() < 8);
+  assert(sides[1].length() > 0 && sides[1].length() < 8);
+
+  std::transform(sides[c].begin(), sides[c].end(), sides[c].begin(), tolower);
+
+  string fenStr = "8/" + sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/"
+                       + sides[1] + char(8 - sides[1].length() + '0') + "/8 w - - 0 10";
+
+  return set(fenStr, false, si, nullptr);
+}
+
+
+/// Position::fen() returns a FEN representation of the position. In case of
+/// Chess960 the Shredder-FEN notation is used. This is mainly a debugging function.
+
+const string Position::fen() const {
+
+  int emptyCnt;
+  std::ostringstream ss;
+
+  for (Rank r = RANK_8; r >= RANK_1; --r)
+  {
+      for (File f = FILE_A; f <= FILE_H; ++f)
+      {
+          for (emptyCnt = 0; f <= FILE_H && empty(make_square(f, r)); ++f)
+              ++emptyCnt;
+
+          if (emptyCnt)
+              ss << emptyCnt;
+
+          if (f <= FILE_H)
+              ss << PieceToChar[piece_on(make_square(f, r))];
+      }
+
+      if (r > RANK_1)
+          ss << '/';
+  }
+
+  ss << (sideToMove == WHITE ? " w " : " b ");
+
+  if (can_castle(WHITE_OO))
+      ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OO ))) : 'K');
+
+  if (can_castle(WHITE_OOO))
+      ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OOO))) : 'Q');
+
+  if (can_castle(BLACK_OO))
+      ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OO ))) : 'k');
+
+  if (can_castle(BLACK_OOO))
+      ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OOO))) : 'q');
+
+  if (!can_castle(ANY_CASTLING))
+      ss << '-';
+
+  ss << (ep_square() == SQ_NONE ? " - " : " " + UCI::square(ep_square()) + " ")
+     << st->rule50 << " " << 1 + (gamePly - (sideToMove == BLACK)) / 2;
+
+  return ss.str();
+}
+
+
+/// Position::slider_blockers() returns a bitboard of all the pieces (both colors)
+/// that are blocking attacks on the square 's' from 'sliders'. A piece blocks a
+/// slider if removing that piece from the board would result in a position where
+/// square 's' is attacked. For example, a king-attack blocking piece can be either
+/// a pinned or a discovered check piece, according if its color is the opposite
+/// or the same of the color of the slider.
+
+Bitboard Position::slider_blockers(Bitboard sliders, Square s, Bitboard& pinners) const {
+
+  Bitboard blockers = 0;
+  pinners = 0;
+
+  // Snipers are sliders that attack 's' when a piece and other snipers are removed
+  Bitboard snipers = (  (attacks_bb<  ROOK>(s) & pieces(QUEEN, ROOK))
+                      | (attacks_bb<BISHOP>(s) & pieces(QUEEN, BISHOP))) & sliders;
+  Bitboard occupancy = pieces() ^ snipers;
+
+  while (snipers)
+  {
+    Square sniperSq = pop_lsb(&snipers);
+    Bitboard b = between_bb(s, sniperSq) & occupancy;
+
+    if (b && !more_than_one(b))
+    {
+        blockers |= b;
+        if (b & pieces(color_of(piece_on(s))))
+            pinners |= sniperSq;
+    }
+  }
+  return blockers;
+}
+
+
+/// Position::attackers_to() computes a bitboard of all pieces which attack a
+/// given square. Slider attacks use the occupied bitboard to indicate occupancy.
+
+Bitboard Position::attackers_to(Square s, Bitboard occupied) const {
+
+  return  (pawn_attacks_bb(BLACK, s)       & pieces(WHITE, PAWN))
+        | (pawn_attacks_bb(WHITE, s)       & pieces(BLACK, PAWN))
+        | (attacks_bb<KNIGHT>(s)           & pieces(KNIGHT))
+        | (attacks_bb<  ROOK>(s, occupied) & pieces(  ROOK, QUEEN))
+        | (attacks_bb<BISHOP>(s, occupied) & pieces(BISHOP, QUEEN))
+        | (attacks_bb<KING>(s)             & pieces(KING));
+}
+
+
+/// Position::legal() tests whether a pseudo-legal move is legal
+
+bool Position::legal(Move m) const {
+
+  assert(is_ok(m));
+
+  Color us = sideToMove;
+  Square from = from_sq(m);
+  Square to = to_sq(m);
+
+  assert(color_of(moved_piece(m)) == us);
+  assert(piece_on(square<KING>(us)) == make_piece(us, KING));
+
+  // En passant captures are a tricky special case. Because they are rather
+  // uncommon, we do it simply by testing whether the king is attacked after
+  // the move is made.
+  if (type_of(m) == ENPASSANT)
+  {
+      Square ksq = square<KING>(us);
+      Square capsq = to - pawn_push(us);
+      Bitboard occupied = (pieces() ^ from ^ capsq) | to;
+
+      assert(to == ep_square());
+      assert(moved_piece(m) == make_piece(us, PAWN));
+      assert(piece_on(capsq) == make_piece(~us, PAWN));
+      assert(piece_on(to) == NO_PIECE);
+
+      return   !(attacks_bb<  ROOK>(ksq, occupied) & pieces(~us, QUEEN, ROOK))
+            && !(attacks_bb<BISHOP>(ksq, occupied) & pieces(~us, QUEEN, BISHOP));
+  }
+
+  // Castling moves generation does not check if the castling path is clear of
+  // enemy attacks, it is delayed at a later time: now!
+  if (type_of(m) == CASTLING)
+  {
+      // After castling, the rook and king final positions are the same in
+      // Chess960 as they would be in standard chess.
+      to = relative_square(us, to > from ? SQ_G1 : SQ_C1);
+      Direction step = to > from ? WEST : EAST;
+
+      for (Square s = to; s != from; s += step)
+          if (attackers_to(s) & pieces(~us))
+              return false;
+
+      // In case of Chess960, verify that when moving the castling rook we do
+      // not discover some hidden checker.
+      // For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1.
+      return   !chess960
+            || !(attacks_bb<ROOK>(to, pieces() ^ to_sq(m)) & pieces(~us, ROOK, QUEEN));
+  }
+
+  // If the moving piece is a king, check whether the destination square is
+  // attacked by the opponent.
+  if (type_of(piece_on(from)) == KING)
+      return !(attackers_to(to) & pieces(~us));
+
+  // A non-king move is legal if and only if it is not pinned or it
+  // is moving along the ray towards or away from the king.
+  return   !(blockers_for_king(us) & from)
+        ||  aligned(from, to, square<KING>(us));
+}
+
+
+/// Position::pseudo_legal() takes a random move and tests whether the move is
+/// pseudo legal. It is used to validate moves from TT that can be corrupted
+/// due to SMP concurrent access or hash position key aliasing.
+
+bool Position::pseudo_legal(const Move m) const {
+
+  Color us = sideToMove;
+  Square from = from_sq(m);
+  Square to = to_sq(m);
+  Piece pc = moved_piece(m);
+
+  // Use a slower but simpler function for uncommon cases
+  if (type_of(m) != NORMAL)
+      return MoveList<LEGAL>(*this).contains(m);
+
+  // Is not a promotion, so promotion piece must be empty
+  if (promotion_type(m) - KNIGHT != NO_PIECE_TYPE)
+      return false;
+
+  // If the 'from' square is not occupied by a piece belonging to the side to
+  // move, the move is obviously not legal.
+  if (pc == NO_PIECE || color_of(pc) != us)
+      return false;
+
+  // The destination square cannot be occupied by a friendly piece
+  if (pieces(us) & to)
+      return false;
+
+  // Handle the special case of a pawn move
+  if (type_of(pc) == PAWN)
+  {
+      // We have already handled promotion moves, so destination
+      // cannot be on the 8th/1st rank.
+      if ((Rank8BB | Rank1BB) & to)
+          return false;
+
+      if (   !(pawn_attacks_bb(us, from) & pieces(~us) & to) // Not a capture
+          && !((from + pawn_push(us) == to) && empty(to))       // Not a single push
+          && !(   (from + 2 * pawn_push(us) == to)              // Not a double push
+               && (relative_rank(us, from) == RANK_2)
+               && empty(to)
+               && empty(to - pawn_push(us))))
+          return false;
+  }
+  else if (!(attacks_bb(type_of(pc), from, pieces()) & to))
+      return false;
+
+  // Evasions generator already takes care to avoid some kind of illegal moves
+  // and legal() relies on this. We therefore have to take care that the same
+  // kind of moves are filtered out here.
+  if (checkers())
+  {
+      if (type_of(pc) != KING)
+      {
+          // Double check? In this case a king move is required
+          if (more_than_one(checkers()))
+              return false;
+
+          // Our move must be a blocking evasion or a capture of the checking piece
+          if (!((between_bb(lsb(checkers()), square<KING>(us)) | checkers()) & to))
+              return false;
+      }
+      // In case of king moves under check we have to remove king so as to catch
+      // invalid moves like b1a1 when opposite queen is on c1.
+      else if (attackers_to(to, pieces() ^ from) & pieces(~us))
+          return false;
+  }
+
+  return true;
+}
+
+
+/// Position::gives_check() tests whether a pseudo-legal move gives a check
+
+bool Position::gives_check(Move m) const {
+
+  assert(is_ok(m));
+  assert(color_of(moved_piece(m)) == sideToMove);
+
+  Square from = from_sq(m);
+  Square to = to_sq(m);
+
+  // Is there a direct check?
+  if (check_squares(type_of(piece_on(from))) & to)
+      return true;
+
+  // Is there a discovered check?
+  if (   (blockers_for_king(~sideToMove) & from)
+      && !aligned(from, to, square<KING>(~sideToMove)))
+      return true;
+
+  switch (type_of(m))
+  {
+  case NORMAL:
+      return false;
+
+  case PROMOTION:
+      return attacks_bb(promotion_type(m), to, pieces() ^ from) & square<KING>(~sideToMove);
+
+  // En passant capture with check? We have already handled the case
+  // of direct checks and ordinary discovered check, so the only case we
+  // need to handle is the unusual case of a discovered check through
+  // the captured pawn.
+  case ENPASSANT:
+  {
+      Square capsq = make_square(file_of(to), rank_of(from));
+      Bitboard b = (pieces() ^ from ^ capsq) | to;
+
+      return  (attacks_bb<  ROOK>(square<KING>(~sideToMove), b) & pieces(sideToMove, QUEEN, ROOK))
+            | (attacks_bb<BISHOP>(square<KING>(~sideToMove), b) & pieces(sideToMove, QUEEN, BISHOP));
+  }
+  case CASTLING:
+  {
+      Square kfrom = from;
+      Square rfrom = to; // Castling is encoded as 'king captures the rook'
+      Square kto = relative_square(sideToMove, rfrom > kfrom ? SQ_G1 : SQ_C1);
+      Square rto = relative_square(sideToMove, rfrom > kfrom ? SQ_F1 : SQ_D1);
+
+      return   (attacks_bb<ROOK>(rto) & square<KING>(~sideToMove))
+            && (attacks_bb<ROOK>(rto, (pieces() ^ kfrom ^ rfrom) | rto | kto) & square<KING>(~sideToMove));
+  }
+  default:
+      assert(false);
+      return false;
+  }
+}
+
+
+/// Position::do_move() makes a move, and saves all information necessary
+/// to a StateInfo object. The move is assumed to be legal. Pseudo-legal
+/// moves should be filtered out before this function is called.
+
+void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
+
+  assert(is_ok(m));
+  assert(&newSt != st);
+
+  thisThread->nodes.fetch_add(1, std::memory_order_relaxed);
+  Key k = st->key ^ Zobrist::side;
+
+  // Copy some fields of the old state to our new StateInfo object except the
+  // ones which are going to be recalculated from scratch anyway and then switch
+  // our state pointer to point to the new (ready to be updated) state.
+  std::memcpy(&newSt, st, offsetof(StateInfo, key));
+  newSt.previous = st;
+  st = &newSt;
+
+  // Increment ply counters. In particular, rule50 will be reset to zero later on
+  // in case of a capture or a pawn move.
+  ++gamePly;
+  ++st->rule50;
+  ++st->pliesFromNull;
+
+  // Used by NNUE
+  st->accumulator.computed_accumulation = false;
+  st->accumulator.computed_score = false;
+  auto& dp = st->dirtyPiece;
+  dp.dirty_num = 1;
+
+  Color us = sideToMove;
+  Color them = ~us;
+  Square from = from_sq(m);
+  Square to = to_sq(m);
+  Piece pc = piece_on(from);
+  Piece captured = type_of(m) == ENPASSANT ? make_piece(them, PAWN) : piece_on(to);
+
+  assert(color_of(pc) == us);
+  assert(captured == NO_PIECE || color_of(captured) == (type_of(m) != CASTLING ? them : us));
+  assert(type_of(captured) != KING);
+
+  if (type_of(m) == CASTLING)
+  {
+      assert(pc == make_piece(us, KING));
+      assert(captured == make_piece(us, ROOK));
+
+      Square rfrom, rto;
+      do_castling<true>(us, from, to, rfrom, rto);
+
+      k ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto];
+      captured = NO_PIECE;
+  }
+
+  if (captured)
+  {
+      Square capsq = to;
+
+      // If the captured piece is a pawn, update pawn hash key, otherwise
+      // update non-pawn material.
+      if (type_of(captured) == PAWN)
+      {
+          if (type_of(m) == ENPASSANT)
+          {
+              capsq -= pawn_push(us);
+
+              assert(pc == make_piece(us, PAWN));
+              assert(to == st->epSquare);
+              assert(relative_rank(us, to) == RANK_6);
+              assert(piece_on(to) == NO_PIECE);
+              assert(piece_on(capsq) == make_piece(them, PAWN));
+          }
+
+          st->pawnKey ^= Zobrist::psq[captured][capsq];
+      }
+      else
+          st->nonPawnMaterial[them] -= PieceValue[MG][captured];
+
+      if (Eval::useNNUE)
+      {
+          dp.dirty_num = 2;  // 1 piece moved, 1 piece captured
+          dp.piece[1] = captured;
+          dp.from[1] = capsq;
+          dp.to[1] = SQ_NONE;
+      }
+
+      // Update board and piece lists
+      remove_piece(capsq);
+
+      if (type_of(m) == ENPASSANT)
+          board[capsq] = NO_PIECE;
+
+      // Update material hash key and prefetch access to materialTable
+      k ^= Zobrist::psq[captured][capsq];
+      st->materialKey ^= Zobrist::psq[captured][pieceCount[captured]];
+      prefetch(thisThread->materialTable[st->materialKey]);
+
+      // Reset rule 50 counter
+      st->rule50 = 0;
+  }
+
+  // Update hash key
+  k ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to];
+
+  // Reset en passant square
+  if (st->epSquare != SQ_NONE)
+  {
+      k ^= Zobrist::enpassant[file_of(st->epSquare)];
+      st->epSquare = SQ_NONE;
+  }
+
+  // Update castling rights if needed
+  if (st->castlingRights && (castlingRightsMask[from] | castlingRightsMask[to]))
+  {
+      k ^= Zobrist::castling[st->castlingRights];
+      st->castlingRights &= ~(castlingRightsMask[from] | castlingRightsMask[to]);
+      k ^= Zobrist::castling[st->castlingRights];
+  }
+
+  // Move the piece. The tricky Chess960 castling is handled earlier
+  if (type_of(m) != CASTLING)
+  {
+      if (Eval::useNNUE)
+      {
+          dp.piece[0] = pc;
+          dp.from[0] = from;
+          dp.to[0] = to;
+      }
+
+      move_piece(from, to);
+  }
+
+  // If the moving piece is a pawn do some special extra work
+  if (type_of(pc) == PAWN)
+  {
+      // Set en-passant square if the moved pawn can be captured
+      if (   (int(to) ^ int(from)) == 16
+          && (pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN)))
+      {
+          st->epSquare = to - pawn_push(us);
+          k ^= Zobrist::enpassant[file_of(st->epSquare)];
+      }
+
+      else if (type_of(m) == PROMOTION)
+      {
+          Piece promotion = make_piece(us, promotion_type(m));
+
+          assert(relative_rank(us, to) == RANK_8);
+          assert(type_of(promotion) >= KNIGHT && type_of(promotion) <= QUEEN);
+
+          remove_piece(to);
+          put_piece(promotion, to);
+
+          if (Eval::useNNUE)
+          {
+              // Promoting pawn to SQ_NONE, promoted piece from SQ_NONE
+              dp.to[0] = SQ_NONE;
+              dp.piece[dp.dirty_num] = promotion;
+              dp.from[dp.dirty_num] = SQ_NONE;
+              dp.to[dp.dirty_num] = to;
+              dp.dirty_num++;
+          }
+
+          // Update hash keys
+          k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[promotion][to];
+          st->pawnKey ^= Zobrist::psq[pc][to];
+          st->materialKey ^=  Zobrist::psq[promotion][pieceCount[promotion]-1]
+                            ^ Zobrist::psq[pc][pieceCount[pc]];
+
+          // Update material
+          st->nonPawnMaterial[us] += PieceValue[MG][promotion];
+      }
+
+      // Update pawn hash key
+      st->pawnKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to];
+
+      // Reset rule 50 draw counter
+      st->rule50 = 0;
+  }
+
+  // Set capture piece
+  st->capturedPiece = captured;
+
+  // Update the key with the final value
+  st->key = k;
+
+  // Calculate checkers bitboard (if move gives check)
+  st->checkersBB = givesCheck ? attackers_to(square<KING>(them)) & pieces(us) : 0;
+
+  sideToMove = ~sideToMove;
+
+  // Update king attacks used for fast check detection
+  set_check_info(st);
+
+  // Calculate the repetition info. It is the ply distance from the previous
+  // occurrence of the same position, negative in the 3-fold case, or zero
+  // if the position was not repeated.
+  st->repetition = 0;
+  int end = std::min(st->rule50, st->pliesFromNull);
+  if (end >= 4)
+  {
+      StateInfo* stp = st->previous->previous;
+      for (int i = 4; i <= end; i += 2)
+      {
+          stp = stp->previous->previous;
+          if (stp->key == st->key)
+          {
+              st->repetition = stp->repetition ? -i : i;
+              break;
+          }
+      }
+  }
+
+  assert(pos_is_ok());
+}
+
+
+/// Position::undo_move() unmakes a move. When it returns, the position should
+/// be restored to exactly the same state as before the move was made.
+
+void Position::undo_move(Move m) {
+
+  assert(is_ok(m));
+
+  sideToMove = ~sideToMove;
+
+  Color us = sideToMove;
+  Square from = from_sq(m);
+  Square to = to_sq(m);
+  Piece pc = piece_on(to);
+
+  assert(empty(from) || type_of(m) == CASTLING);
+  assert(type_of(st->capturedPiece) != KING);
+
+  if (type_of(m) == PROMOTION)
+  {
+      assert(relative_rank(us, to) == RANK_8);
+      assert(type_of(pc) == promotion_type(m));
+      assert(type_of(pc) >= KNIGHT && type_of(pc) <= QUEEN);
+
+      remove_piece(to);
+      pc = make_piece(us, PAWN);
+      put_piece(pc, to);
+  }
+
+  if (type_of(m) == CASTLING)
+  {
+      Square rfrom, rto;
+      do_castling<false>(us, from, to, rfrom, rto);
+  }
+  else
+  {
+      move_piece(to, from); // Put the piece back at the source square
+
+      if (st->capturedPiece)
+      {
+          Square capsq = to;
+
+          if (type_of(m) == ENPASSANT)
+          {
+              capsq -= pawn_push(us);
+
+              assert(type_of(pc) == PAWN);
+              assert(to == st->previous->epSquare);
+              assert(relative_rank(us, to) == RANK_6);
+              assert(piece_on(capsq) == NO_PIECE);
+              assert(st->capturedPiece == make_piece(~us, PAWN));
+          }
+
+          put_piece(st->capturedPiece, capsq); // Restore the captured piece
+      }
+  }
+
+  // Finally point our state pointer back to the previous state
+  st = st->previous;
+  --gamePly;
+
+  assert(pos_is_ok());
+}
+
+
+/// Position::do_castling() is a helper used to do/undo a castling move. This
+/// is a bit tricky in Chess960 where from/to squares can overlap.
+template<bool Do>
+void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto) {
+
+  bool kingSide = to > from;
+  rfrom = to; // Castling is encoded as "king captures friendly rook"
+  rto = relative_square(us, kingSide ? SQ_F1 : SQ_D1);
+  to = relative_square(us, kingSide ? SQ_G1 : SQ_C1);
+
+  if (Do && Eval::useNNUE)
+  {
+      auto& dp = st->dirtyPiece;
+      dp.piece[0] = make_piece(us, KING);
+      dp.from[0] = from;
+      dp.to[0] = to;
+      dp.piece[1] = make_piece(us, ROOK);
+      dp.from[1] = rfrom;
+      dp.to[1] = rto;
+      dp.dirty_num = 2;
+  }
+
+  // Remove both pieces first since squares could overlap in Chess960
+  remove_piece(Do ? from : to);
+  remove_piece(Do ? rfrom : rto);
+  board[Do ? from : to] = board[Do ? rfrom : rto] = NO_PIECE; // Since remove_piece doesn't do this for us
+  put_piece(make_piece(us, KING), Do ? to : from);
+  put_piece(make_piece(us, ROOK), Do ? rto : rfrom);
+}
+
+
+/// Position::do(undo)_null_move() is used to do(undo) a "null move": it flips
+/// the side to move without executing any move on the board.
+
+void Position::do_null_move(StateInfo& newSt) {
+
+  assert(!checkers());
+  assert(&newSt != st);
+
+  if (Eval::useNNUE)
+  {
+      std::memcpy(&newSt, st, sizeof(StateInfo));
+      st->accumulator.computed_score = false;
+  }
+  else
+      std::memcpy(&newSt, st, offsetof(StateInfo, accumulator));
+
+  newSt.previous = st;
+  st = &newSt;
+
+  if (st->epSquare != SQ_NONE)
+  {
+      st->key ^= Zobrist::enpassant[file_of(st->epSquare)];
+      st->epSquare = SQ_NONE;
+  }
+
+  st->key ^= Zobrist::side;
+  prefetch(TT.first_entry(st->key));
+
+  ++st->rule50;
+  st->pliesFromNull = 0;
+
+  sideToMove = ~sideToMove;
+
+  set_check_info(st);
+
+  st->repetition = 0;
+
+  assert(pos_is_ok());
+}
+
+void Position::undo_null_move() {
+
+  assert(!checkers());
+
+  st = st->previous;
+  sideToMove = ~sideToMove;
+}
+
+
+/// Position::key_after() computes the new hash key after the given move. Needed
+/// for speculative prefetch. It doesn't recognize special moves like castling,
+/// en-passant and promotions.
+
+Key Position::key_after(Move m) const {
+
+  Square from = from_sq(m);
+  Square to = to_sq(m);
+  Piece pc = piece_on(from);
+  Piece captured = piece_on(to);
+  Key k = st->key ^ Zobrist::side;
+
+  if (captured)
+      k ^= Zobrist::psq[captured][to];
+
+  return k ^ Zobrist::psq[pc][to] ^ Zobrist::psq[pc][from];
+}
+
+
+/// Position::see_ge (Static Exchange Evaluation Greater or Equal) tests if the
+/// SEE value of move is greater or equal to the given threshold. We'll use an
+/// algorithm similar to alpha-beta pruning with a null window.
+
+bool Position::see_ge(Move m, Value threshold) const {
+
+  assert(is_ok(m));
+
+  // Only deal with normal moves, assume others pass a simple see
+  if (type_of(m) != NORMAL)
+      return VALUE_ZERO >= threshold;
+
+  Square from = from_sq(m), to = to_sq(m);
+
+  int swap = PieceValue[MG][piece_on(to)] - threshold;
+  if (swap < 0)
+      return false;
+
+  swap = PieceValue[MG][piece_on(from)] - swap;
+  if (swap <= 0)
+      return true;
+
+  Bitboard occupied = pieces() ^ from ^ to;
+  Color stm = color_of(piece_on(from));
+  Bitboard attackers = attackers_to(to, occupied);
+  Bitboard stmAttackers, bb;
+  int res = 1;
+
+  while (true)
+  {
+      stm = ~stm;
+      attackers &= occupied;
+
+      // If stm has no more attackers then give up: stm loses
+      if (!(stmAttackers = attackers & pieces(stm)))
+          break;
+
+      // Don't allow pinned pieces to attack (except the king) as long as
+      // there are pinners on their original square.
+      if (pinners(~stm) & occupied)
+          stmAttackers &= ~blockers_for_king(stm);
+
+      if (!stmAttackers)
+          break;
+
+      res ^= 1;
+
+      // Locate and remove the next least valuable attacker, and add to
+      // the bitboard 'attackers' any X-ray attackers behind it.
+      if ((bb = stmAttackers & pieces(PAWN)))
+      {
+          if ((swap = PawnValueMg - swap) < res)
+              break;
+
+          occupied ^= lsb(bb);
+          attackers |= attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN);
+      }
+
+      else if ((bb = stmAttackers & pieces(KNIGHT)))
+      {
+          if ((swap = KnightValueMg - swap) < res)
+              break;
+
+          occupied ^= lsb(bb);
+      }
+
+      else if ((bb = stmAttackers & pieces(BISHOP)))
+      {
+          if ((swap = BishopValueMg - swap) < res)
+              break;
+
+          occupied ^= lsb(bb);
+          attackers |= attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN);
+      }
+
+      else if ((bb = stmAttackers & pieces(ROOK)))
+      {
+          if ((swap = RookValueMg - swap) < res)
+              break;
+
+          occupied ^= lsb(bb);
+          attackers |= attacks_bb<ROOK>(to, occupied) & pieces(ROOK, QUEEN);
+      }
+
+      else if ((bb = stmAttackers & pieces(QUEEN)))
+      {
+          if ((swap = QueenValueMg - swap) < res)
+              break;
+
+          occupied ^= lsb(bb);
+          attackers |=  (attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN))
+                      | (attacks_bb<ROOK  >(to, occupied) & pieces(ROOK  , QUEEN));
+      }
+
+      else // KING
+           // If we "capture" with the king but opponent still has attackers,
+           // reverse the result.
+          return (attackers & ~pieces(stm)) ? res ^ 1 : res;
+  }
+
+  return bool(res);
+}
+
+
+/// Position::is_draw() tests whether the position is drawn by 50-move rule
+/// or by repetition. It does not detect stalemates.
+
+bool Position::is_draw(int ply) const {
+
+  if (st->rule50 > 99 && (!checkers() || MoveList<LEGAL>(*this).size()))
+      return true;
+
+  // Return a draw score if a position repeats once earlier but strictly
+  // after the root, or repeats twice before or at the root.
+  return st->repetition && st->repetition < ply;
+}
+
+
+// Position::has_repeated() tests whether there has been at least one repetition
+// of positions since the last capture or pawn move.
+
+bool Position::has_repeated() const {
+
+    StateInfo* stc = st;
+    int end = std::min(st->rule50, st->pliesFromNull);
+    while (end-- >= 4)
+    {
+        if (stc->repetition)
+            return true;
+
+        stc = stc->previous;
+    }
+    return false;
+}
+
+
+/// Position::has_game_cycle() tests if the position has a move which draws by repetition,
+/// or an earlier position has a move that directly reaches the current position.
+
+bool Position::has_game_cycle(int ply) const {
+
+  int j;
+
+  int end = std::min(st->rule50, st->pliesFromNull);
+
+  if (end < 3)
+    return false;
+
+  Key originalKey = st->key;
+  StateInfo* stp = st->previous;
+
+  for (int i = 3; i <= end; i += 2)
+  {
+      stp = stp->previous->previous;
+
+      Key moveKey = originalKey ^ stp->key;
+      if (   (j = H1(moveKey), cuckoo[j] == moveKey)
+          || (j = H2(moveKey), cuckoo[j] == moveKey))
+      {
+          Move move = cuckooMove[j];
+          Square s1 = from_sq(move);
+          Square s2 = to_sq(move);
+
+          if (!(between_bb(s1, s2) & pieces()))
+          {
+              if (ply > i)
+                  return true;
+
+              // For nodes before or at the root, check that the move is a
+              // repetition rather than a move to the current position.
+              // In the cuckoo table, both moves Rc1c5 and Rc5c1 are stored in
+              // the same location, so we have to select which square to check.
+              if (color_of(piece_on(empty(s1) ? s2 : s1)) != side_to_move())
+                  continue;
+
+              // For repetitions before or at the root, require one more
+              if (stp->repetition)
+                  return true;
+          }
+      }
+  }
+  return false;
+}
+
+
+/// Position::flip() flips position with the white and black sides reversed. This
+/// is only useful for debugging e.g. for finding evaluation symmetry bugs.
+
+void Position::flip() {
+
+  string f, token;
+  std::stringstream ss(fen());
+
+  for (Rank r = RANK_8; r >= RANK_1; --r) // Piece placement
+  {
+      std::getline(ss, token, r > RANK_1 ? '/' : ' ');
+      f.insert(0, token + (f.empty() ? " " : "/"));
+  }
+
+  ss >> token; // Active color
+  f += (token == "w" ? "B " : "W "); // Will be lowercased later
+
+  ss >> token; // Castling availability
+  f += token + " ";
+
+  std::transform(f.begin(), f.end(), f.begin(),
+                 [](char c) { return char(islower(c) ? toupper(c) : tolower(c)); });
+
+  ss >> token; // En passant square
+  f += (token == "-" ? token : token.replace(1, 1, token[1] == '3' ? "6" : "3"));
+
+  std::getline(ss, token); // Half and full moves
+  f += token;
+
+  set(f, is_chess960(), st, this_thread());
+
+  assert(pos_is_ok());
+}
+
+
+/// Position::pos_is_ok() performs some consistency checks for the
+/// position object and raises an asserts if something wrong is detected.
+/// This is meant to be helpful when debugging.
+
+bool Position::pos_is_ok() const {
+
+  constexpr bool Fast = true; // Quick (default) or full check?
+
+  if (   (sideToMove != WHITE && sideToMove != BLACK)
+      || piece_on(square<KING>(WHITE)) != W_KING
+      || piece_on(square<KING>(BLACK)) != B_KING
+      || (   ep_square() != SQ_NONE
+          && relative_rank(sideToMove, ep_square()) != RANK_6))
+      assert(0 && "pos_is_ok: Default");
+
+  if (Fast)
+      return true;
+
+  if (   pieceCount[W_KING] != 1
+      || pieceCount[B_KING] != 1
+      || attackers_to(square<KING>(~sideToMove)) & pieces(sideToMove))
+      assert(0 && "pos_is_ok: Kings");
+
+  if (   (pieces(PAWN) & (Rank1BB | Rank8BB))
+      || pieceCount[W_PAWN] > 8
+      || pieceCount[B_PAWN] > 8)
+      assert(0 && "pos_is_ok: Pawns");
+
+  if (   (pieces(WHITE) & pieces(BLACK))
+      || (pieces(WHITE) | pieces(BLACK)) != pieces()
+      || popcount(pieces(WHITE)) > 16
+      || popcount(pieces(BLACK)) > 16)
+      assert(0 && "pos_is_ok: Bitboards");
+
+  for (PieceType p1 = PAWN; p1 <= KING; ++p1)
+      for (PieceType p2 = PAWN; p2 <= KING; ++p2)
+          if (p1 != p2 && (pieces(p1) & pieces(p2)))
+              assert(0 && "pos_is_ok: Bitboards");
+
+  StateInfo si = *st;
+  set_state(&si);
+  if (std::memcmp(&si, st, sizeof(StateInfo)))
+      assert(0 && "pos_is_ok: State");
+
+  for (Piece pc : Pieces)
+  {
+      if (   pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc)))
+          || pieceCount[pc] != std::count(board, board + SQUARE_NB, pc))
+          assert(0 && "pos_is_ok: Pieces");
+
+      for (int i = 0; i < pieceCount[pc]; ++i)
+          if (board[pieceList[pc][i]] != pc || index[pieceList[pc][i]] != i)
+              assert(0 && "pos_is_ok: Index");
+  }
+
+  for (Color c : { WHITE, BLACK })
+      for (CastlingRights cr : {c & KING_SIDE, c & QUEEN_SIDE})
+      {
+          if (!can_castle(cr))
+              continue;
+
+          if (   piece_on(castlingRookSquare[cr]) != make_piece(c, ROOK)
+              || castlingRightsMask[castlingRookSquare[cr]] != cr
+              || (castlingRightsMask[square<KING>(c)] & cr) != cr)
+              assert(0 && "pos_is_ok: Castling");
+      }
+
+  return true;
+}
diff --git a/src/position.h b/src/position.h
new file mode 100644 (file)
index 0000000..d6f5c9f
--- /dev/null
@@ -0,0 +1,447 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef POSITION_H_INCLUDED
+#define POSITION_H_INCLUDED
+
+#include <cassert>
+#include <deque>
+#include <memory> // For std::unique_ptr
+#include <string>
+
+#include "bitboard.h"
+#include "evaluate.h"
+#include "types.h"
+
+#include "nnue/nnue_accumulator.h"
+
+
+/// StateInfo struct stores information needed to restore a Position object to
+/// its previous state when we retract a move. Whenever a move is made on the
+/// board (by calling Position::do_move), a StateInfo object must be passed.
+
+struct StateInfo {
+
+  // Copied when making a move
+  Key    pawnKey;
+  Key    materialKey;
+  Value  nonPawnMaterial[COLOR_NB];
+  int    castlingRights;
+  int    rule50;
+  int    pliesFromNull;
+  Square epSquare;
+
+  // Not copied when making a move (will be recomputed anyhow)
+  Key        key;
+  Bitboard   checkersBB;
+  Piece      capturedPiece;
+  StateInfo* previous;
+  Bitboard   blockersForKing[COLOR_NB];
+  Bitboard   pinners[COLOR_NB];
+  Bitboard   checkSquares[PIECE_TYPE_NB];
+  int        repetition;
+
+  // Used by NNUE
+  Eval::NNUE::Accumulator accumulator;
+  DirtyPiece dirtyPiece;
+};
+
+
+/// A list to keep track of the position states along the setup moves (from the
+/// start position to the position just before the search starts). Needed by
+/// 'draw by repetition' detection. Use a std::deque because pointers to
+/// elements are not invalidated upon list resizing.
+typedef std::unique_ptr<std::deque<StateInfo>> StateListPtr;
+
+
+/// Position class stores information regarding the board representation as
+/// pieces, side to move, hash keys, castling info, etc. Important methods are
+/// do_move() and undo_move(), used by the search to update node info when
+/// traversing the search tree.
+class Thread;
+
+class Position {
+public:
+  static void init();
+
+  Position() = default;
+  Position(const Position&) = delete;
+  Position& operator=(const Position&) = delete;
+
+  // FEN string input/output
+  Position& set(const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th);
+  Position& set(const std::string& code, Color c, StateInfo* si);
+  const std::string fen() const;
+
+  // Position representation
+  Bitboard pieces(PieceType pt) const;
+  Bitboard pieces(PieceType pt1, PieceType pt2) const;
+  Bitboard pieces(Color c) const;
+  Bitboard pieces(Color c, PieceType pt) const;
+  Bitboard pieces(Color c, PieceType pt1, PieceType pt2) const;
+  Piece piece_on(Square s) const;
+  Square ep_square() const;
+  bool empty(Square s) const;
+  template<PieceType Pt> int count(Color c) const;
+  template<PieceType Pt> int count() const;
+  template<PieceType Pt> const Square* squares(Color c) const;
+  template<PieceType Pt> Square square(Color c) const;
+  bool is_on_semiopen_file(Color c, Square s) const;
+
+  // Castling
+  CastlingRights castling_rights(Color c) const;
+  bool can_castle(CastlingRights cr) const;
+  bool castling_impeded(CastlingRights cr) const;
+  Square castling_rook_square(CastlingRights cr) const;
+
+  // Checking
+  Bitboard checkers() const;
+  Bitboard blockers_for_king(Color c) const;
+  Bitboard check_squares(PieceType pt) const;
+  Bitboard pinners(Color c) const;
+  bool is_discovery_check_on_king(Color c, Move m) const;
+
+  // Attacks to/from a given square
+  Bitboard attackers_to(Square s) const;
+  Bitboard attackers_to(Square s, Bitboard occupied) const;
+  Bitboard slider_blockers(Bitboard sliders, Square s, Bitboard& pinners) const;
+
+  // Properties of moves
+  bool legal(Move m) const;
+  bool pseudo_legal(const Move m) const;
+  bool capture(Move m) const;
+  bool capture_or_promotion(Move m) const;
+  bool gives_check(Move m) const;
+  bool advanced_pawn_push(Move m) const;
+  Piece moved_piece(Move m) const;
+  Piece captured_piece() const;
+
+  // Piece specific
+  bool pawn_passed(Color c, Square s) const;
+  bool opposite_bishops() const;
+  int  pawns_on_same_color_squares(Color c, Square s) const;
+
+  // Doing and undoing moves
+  void do_move(Move m, StateInfo& newSt);
+  void do_move(Move m, StateInfo& newSt, bool givesCheck);
+  void undo_move(Move m);
+  void do_null_move(StateInfo& newSt);
+  void undo_null_move();
+
+  // Static Exchange Evaluation
+  bool see_ge(Move m, Value threshold = VALUE_ZERO) const;
+
+  // Accessing hash keys
+  Key key() const;
+  Key key_after(Move m) const;
+  Key material_key() const;
+  Key pawn_key() const;
+
+  // Other properties of the position
+  Color side_to_move() const;
+  int game_ply() const;
+  bool is_chess960() const;
+  Thread* this_thread() const;
+  bool is_draw(int ply) const;
+  bool has_game_cycle(int ply) const;
+  bool has_repeated() const;
+  int rule50_count() const;
+  Score psq_score() const;
+  Value non_pawn_material(Color c) const;
+  Value non_pawn_material() const;
+
+  // Position consistency check, for debugging
+  bool pos_is_ok() const;
+  void flip();
+
+  // Used by NNUE
+  StateInfo* state() const;
+
+private:
+  // Initialization helpers (used while setting up a position)
+  void set_castling_right(Color c, Square rfrom);
+  void set_state(StateInfo* si) const;
+  void set_check_info(StateInfo* si) const;
+
+  // Other helpers
+  void put_piece(Piece pc, Square s);
+  void remove_piece(Square s);
+  void move_piece(Square from, Square to);
+  template<bool Do>
+  void do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto);
+
+  // Data members
+  Piece board[SQUARE_NB];
+  Bitboard byTypeBB[PIECE_TYPE_NB];
+  Bitboard byColorBB[COLOR_NB];
+  int pieceCount[PIECE_NB];
+  Square pieceList[PIECE_NB][16];
+  int index[SQUARE_NB];
+  int castlingRightsMask[SQUARE_NB];
+  Square castlingRookSquare[CASTLING_RIGHT_NB];
+  Bitboard castlingPath[CASTLING_RIGHT_NB];
+  int gamePly;
+  Color sideToMove;
+  Score psq;
+  Thread* thisThread;
+  StateInfo* st;
+  bool chess960;
+};
+
+namespace PSQT {
+  extern Score psq[PIECE_NB][SQUARE_NB];
+}
+
+extern std::ostream& operator<<(std::ostream& os, const Position& pos);
+
+inline Color Position::side_to_move() const {
+  return sideToMove;
+}
+
+inline Piece Position::piece_on(Square s) const {
+  assert(is_ok(s));
+  return board[s];
+}
+
+inline bool Position::empty(Square s) const {
+  return piece_on(s) == NO_PIECE;
+}
+
+inline Piece Position::moved_piece(Move m) const {
+  return piece_on(from_sq(m));
+}
+
+inline Bitboard Position::pieces(PieceType pt = ALL_PIECES) const {
+  return byTypeBB[pt];
+}
+
+inline Bitboard Position::pieces(PieceType pt1, PieceType pt2) const {
+  return pieces(pt1) | pieces(pt2);
+}
+
+inline Bitboard Position::pieces(Color c) const {
+  return byColorBB[c];
+}
+
+inline Bitboard Position::pieces(Color c, PieceType pt) const {
+  return pieces(c) & pieces(pt);
+}
+
+inline Bitboard Position::pieces(Color c, PieceType pt1, PieceType pt2) const {
+  return pieces(c) & (pieces(pt1) | pieces(pt2));
+}
+
+template<PieceType Pt> inline int Position::count(Color c) const {
+  return pieceCount[make_piece(c, Pt)];
+}
+
+template<PieceType Pt> inline int Position::count() const {
+  return count<Pt>(WHITE) + count<Pt>(BLACK);
+}
+
+template<PieceType Pt> inline const Square* Position::squares(Color c) const {
+  return pieceList[make_piece(c, Pt)];
+}
+
+template<PieceType Pt> inline Square Position::square(Color c) const {
+  assert(pieceCount[make_piece(c, Pt)] == 1);
+  return squares<Pt>(c)[0];
+}
+
+inline Square Position::ep_square() const {
+  return st->epSquare;
+}
+
+inline bool Position::is_on_semiopen_file(Color c, Square s) const {
+  return !(pieces(c, PAWN) & file_bb(s));
+}
+
+inline bool Position::can_castle(CastlingRights cr) const {
+  return st->castlingRights & cr;
+}
+
+inline CastlingRights Position::castling_rights(Color c) const {
+  return c & CastlingRights(st->castlingRights);
+}
+
+inline bool Position::castling_impeded(CastlingRights cr) const {
+  assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO);
+
+  return pieces() & castlingPath[cr];
+}
+
+inline Square Position::castling_rook_square(CastlingRights cr) const {
+  assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO);
+
+  return castlingRookSquare[cr];
+}
+
+inline Bitboard Position::attackers_to(Square s) const {
+  return attackers_to(s, pieces());
+}
+
+inline Bitboard Position::checkers() const {
+  return st->checkersBB;
+}
+
+inline Bitboard Position::blockers_for_king(Color c) const {
+  return st->blockersForKing[c];
+}
+
+inline Bitboard Position::pinners(Color c) const {
+  return st->pinners[c];
+}
+
+inline Bitboard Position::check_squares(PieceType pt) const {
+  return st->checkSquares[pt];
+}
+
+inline bool Position::is_discovery_check_on_king(Color c, Move m) const {
+  return st->blockersForKing[c] & from_sq(m);
+}
+
+inline bool Position::pawn_passed(Color c, Square s) const {
+  return !(pieces(~c, PAWN) & passed_pawn_span(c, s));
+}
+
+inline bool Position::advanced_pawn_push(Move m) const {
+  return   type_of(moved_piece(m)) == PAWN
+        && relative_rank(sideToMove, to_sq(m)) > RANK_5;
+}
+
+inline int Position::pawns_on_same_color_squares(Color c, Square s) const {
+  return popcount(pieces(c, PAWN) & ((DarkSquares & s) ? DarkSquares : ~DarkSquares));
+}
+
+inline Key Position::key() const {
+  return st->key;
+}
+
+inline Key Position::pawn_key() const {
+  return st->pawnKey;
+}
+
+inline Key Position::material_key() const {
+  return st->materialKey;
+}
+
+inline Score Position::psq_score() const {
+  return psq;
+}
+
+inline Value Position::non_pawn_material(Color c) const {
+  return st->nonPawnMaterial[c];
+}
+
+inline Value Position::non_pawn_material() const {
+  return non_pawn_material(WHITE) + non_pawn_material(BLACK);
+}
+
+inline int Position::game_ply() const {
+  return gamePly;
+}
+
+inline int Position::rule50_count() const {
+  return st->rule50;
+}
+
+inline bool Position::opposite_bishops() const {
+  return   count<BISHOP>(WHITE) == 1
+        && count<BISHOP>(BLACK) == 1
+        && opposite_colors(square<BISHOP>(WHITE), square<BISHOP>(BLACK));
+}
+
+inline bool Position::is_chess960() const {
+  return chess960;
+}
+
+inline bool Position::capture_or_promotion(Move m) const {
+  assert(is_ok(m));
+  return type_of(m) != NORMAL ? type_of(m) != CASTLING : !empty(to_sq(m));
+}
+
+inline bool Position::capture(Move m) const {
+  assert(is_ok(m));
+  // Castling is encoded as "king captures rook"
+  return (!empty(to_sq(m)) && type_of(m) != CASTLING) || type_of(m) == ENPASSANT;
+}
+
+inline Piece Position::captured_piece() const {
+  return st->capturedPiece;
+}
+
+inline Thread* Position::this_thread() const {
+  return thisThread;
+}
+
+inline void Position::put_piece(Piece pc, Square s) {
+
+  board[s] = pc;
+  byTypeBB[ALL_PIECES] |= byTypeBB[type_of(pc)] |= s;
+  byColorBB[color_of(pc)] |= s;
+  index[s] = pieceCount[pc]++;
+  pieceList[pc][index[s]] = s;
+  pieceCount[make_piece(color_of(pc), ALL_PIECES)]++;
+  psq += PSQT::psq[pc][s];
+}
+
+inline void Position::remove_piece(Square s) {
+
+  // WARNING: This is not a reversible operation. If we remove a piece in
+  // do_move() and then replace it in undo_move() we will put it at the end of
+  // the list and not in its original place, it means index[] and pieceList[]
+  // are not invariant to a do_move() + undo_move() sequence.
+  Piece pc = board[s];
+  byTypeBB[ALL_PIECES] ^= s;
+  byTypeBB[type_of(pc)] ^= s;
+  byColorBB[color_of(pc)] ^= s;
+  /* board[s] = NO_PIECE;  Not needed, overwritten by the capturing one */
+  Square lastSquare = pieceList[pc][--pieceCount[pc]];
+  index[lastSquare] = index[s];
+  pieceList[pc][index[lastSquare]] = lastSquare;
+  pieceList[pc][pieceCount[pc]] = SQ_NONE;
+  pieceCount[make_piece(color_of(pc), ALL_PIECES)]--;
+  psq -= PSQT::psq[pc][s];
+}
+
+inline void Position::move_piece(Square from, Square to) {
+
+  // index[from] is not updated and becomes stale. This works as long as index[]
+  // is accessed just by known occupied squares.
+  Piece pc = board[from];
+  Bitboard fromTo = from | to;
+  byTypeBB[ALL_PIECES] ^= fromTo;
+  byTypeBB[type_of(pc)] ^= fromTo;
+  byColorBB[color_of(pc)] ^= fromTo;
+  board[from] = NO_PIECE;
+  board[to] = pc;
+  index[to] = index[from];
+  pieceList[pc][index[to]] = to;
+  psq += PSQT::psq[pc][to] - PSQT::psq[pc][from];
+}
+
+inline void Position::do_move(Move m, StateInfo& newSt) {
+  do_move(m, newSt, gives_check(m));
+}
+
+inline StateInfo* Position::state() const {
+
+  return st;
+}
+
+#endif // #ifndef POSITION_H_INCLUDED
diff --git a/src/psqt.cpp b/src/psqt.cpp
new file mode 100644 (file)
index 0000000..eb36e75
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <algorithm>
+
+#include "types.h"
+#include "bitboard.h"
+
+namespace PSQT {
+
+#define S(mg, eg) make_score(mg, eg)
+
+// Bonus[PieceType][Square / 2] contains Piece-Square scores. For each piece
+// type on a given square a (middlegame, endgame) score pair is assigned. Table
+// is defined for files A..D and white side: it is symmetric for black side and
+// second half of the files.
+constexpr Score Bonus[][RANK_NB][int(FILE_NB) / 2] = {
+  { },
+  { },
+  { // Knight
+   { S(-175, -96), S(-92,-65), S(-74,-49), S(-73,-21) },
+   { S( -77, -67), S(-41,-54), S(-27,-18), S(-15,  8) },
+   { S( -61, -40), S(-17,-27), S(  6, -8), S( 12, 29) },
+   { S( -35, -35), S(  8, -2), S( 40, 13), S( 49, 28) },
+   { S( -34, -45), S( 13,-16), S( 44,  9), S( 51, 39) },
+   { S(  -9, -51), S( 22,-44), S( 58,-16), S( 53, 17) },
+   { S( -67, -69), S(-27,-50), S(  4,-51), S( 37, 12) },
+   { S(-201,-100), S(-83,-88), S(-56,-56), S(-26,-17) }
+  },
+  { // Bishop
+   { S(-53,-57), S( -5,-30), S( -8,-37), S(-23,-12) },
+   { S(-15,-37), S(  8,-13), S( 19,-17), S(  4,  1) },
+   { S( -7,-16), S( 21, -1), S( -5, -2), S( 17, 10) },
+   { S( -5,-20), S( 11, -6), S( 25,  0), S( 39, 17) },
+   { S(-12,-17), S( 29, -1), S( 22,-14), S( 31, 15) },
+   { S(-16,-30), S(  6,  6), S(  1,  4), S( 11,  6) },
+   { S(-17,-31), S(-14,-20), S(  5, -1), S(  0,  1) },
+   { S(-48,-46), S(  1,-42), S(-14,-37), S(-23,-24) }
+  },
+  { // Rook
+   { S(-31, -9), S(-20,-13), S(-14,-10), S(-5, -9) },
+   { S(-21,-12), S(-13, -9), S( -8, -1), S( 6, -2) },
+   { S(-25,  6), S(-11, -8), S( -1, -2), S( 3, -6) },
+   { S(-13, -6), S( -5,  1), S( -4, -9), S(-6,  7) },
+   { S(-27, -5), S(-15,  8), S( -4,  7), S( 3, -6) },
+   { S(-22,  6), S( -2,  1), S(  6, -7), S(12, 10) },
+   { S( -2,  4), S( 12,  5), S( 16, 20), S(18, -5) },
+   { S(-17, 18), S(-19,  0), S( -1, 19), S( 9, 13) }
+  },
+  { // Queen
+   { S( 3,-69), S(-5,-57), S(-5,-47), S( 4,-26) },
+   { S(-3,-55), S( 5,-31), S( 8,-22), S(12, -4) },
+   { S(-3,-39), S( 6,-18), S(13, -9), S( 7,  3) },
+   { S( 4,-23), S( 5, -3), S( 9, 13), S( 8, 24) },
+   { S( 0,-29), S(14, -6), S(12,  9), S( 5, 21) },
+   { S(-4,-38), S(10,-18), S( 6,-12), S( 8,  1) },
+   { S(-5,-50), S( 6,-27), S(10,-24), S( 8, -8) },
+   { S(-2,-75), S(-2,-52), S( 1,-43), S(-2,-36) }
+  },
+  { // King
+   { S(271,  1), S(327, 45), S(271, 85), S(198, 76) },
+   { S(278, 53), S(303,100), S(234,133), S(179,135) },
+   { S(195, 88), S(258,130), S(169,169), S(120,175) },
+   { S(164,103), S(190,156), S(138,172), S( 98,172) },
+   { S(154, 96), S(179,166), S(105,199), S( 70,199) },
+   { S(123, 92), S(145,172), S( 81,184), S( 31,191) },
+   { S( 88, 47), S(120,121), S( 65,116), S( 33,131) },
+   { S( 59, 11), S( 89, 59), S( 45, 73), S( -1, 78) }
+  }
+};
+
+constexpr Score PBonus[RANK_NB][FILE_NB] =
+  { // Pawn (asymmetric distribution)
+   { },
+   { S(  3,-10), S(  3, -6), S( 10, 10), S( 19,  0), S( 16, 14), S( 19,  7), S(  7, -5), S( -5,-19) },
+   { S( -9,-10), S(-15,-10), S( 11,-10), S( 15,  4), S( 32,  4), S( 22,  3), S(  5, -6), S(-22, -4) },
+   { S( -4,  6), S(-23, -2), S(  6, -8), S( 20, -4), S( 40,-13), S( 17,-12), S(  4,-10), S( -8, -9) },
+   { S( 13, 10), S(  0,  5), S(-13,  4), S(  1, -5), S( 11, -5), S( -2, -5), S(-13, 14), S(  5,  9) },
+   { S(  5, 28), S(-12, 20), S( -7, 21), S( 22, 28), S( -8, 30), S( -5,  7), S(-15,  6), S( -8, 13) },
+   { S( -7,  0), S(  7,-11), S( -3, 12), S(-13, 21), S(  5, 25), S(-16, 19), S( 10,  4), S( -8,  7) }
+  };
+
+#undef S
+
+Score psq[PIECE_NB][SQUARE_NB];
+
+
+// PSQT::init() initializes piece-square tables: the white halves of the tables are
+// copied from Bonus[] and PBonus[], adding the piece value, then the black halves of
+// the tables are initialized by flipping and changing the sign of the white scores.
+void init() {
+
+  for (Piece pc : {W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING})
+  {
+      Score score = make_score(PieceValue[MG][pc], PieceValue[EG][pc]);
+
+      for (Square s = SQ_A1; s <= SQ_H8; ++s)
+      {
+          File f = File(edge_distance(file_of(s)));
+          psq[ pc][s] = score + (type_of(pc) == PAWN ? PBonus[rank_of(s)][file_of(s)]
+                                                     : Bonus[pc][rank_of(s)][f]);
+          psq[~pc][flip_rank(s)] = -psq[pc][s];
+      }
+  }
+}
+
+} // namespace PSQT
diff --git a/src/search.cpp b/src/search.cpp
new file mode 100644 (file)
index 0000000..b79fa6b
--- /dev/null
@@ -0,0 +1,1982 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <cstring>   // For std::memset
+#include <iostream>
+#include <sstream>
+
+#include "evaluate.h"
+#include "misc.h"
+#include "movegen.h"
+#include "movepick.h"
+#include "position.h"
+#include "search.h"
+#include "thread.h"
+#include "timeman.h"
+#include "tt.h"
+#include "uci.h"
+#include "syzygy/tbprobe.h"
+
+namespace Search {
+
+  LimitsType Limits;
+}
+
+namespace Tablebases {
+
+  int Cardinality;
+  bool RootInTB;
+  bool UseRule50;
+  Depth ProbeDepth;
+}
+
+namespace TB = Tablebases;
+
+using std::string;
+using Eval::evaluate;
+using namespace Search;
+
+namespace {
+
+  // Different node types, used as a template parameter
+  enum NodeType { NonPV, PV };
+
+  constexpr uint64_t TtHitAverageWindow     = 4096;
+  constexpr uint64_t TtHitAverageResolution = 1024;
+
+  // Razor and futility margins
+  constexpr int RazorMargin = 510;
+  Value futility_margin(Depth d, bool improving) {
+    return Value(223 * (d - improving));
+  }
+
+  // Reductions lookup table, initialized at startup
+  int Reductions[MAX_MOVES]; // [depth or moveNumber]
+
+  Depth reduction(bool i, Depth d, int mn) {
+    int r = Reductions[d] * Reductions[mn];
+    return (r + 509) / 1024 + (!i && r > 894);
+  }
+
+  constexpr int futility_move_count(bool improving, Depth depth) {
+    return (3 + depth * depth) / (2 - improving);
+  }
+
+  // History and stats update bonus, based on depth
+  int stat_bonus(Depth d) {
+    return d > 13 ? 29 : 17 * d * d + 134 * d - 134;
+  }
+
+  // Add a small random component to draw evaluations to avoid 3fold-blindness
+  Value value_draw(Thread* thisThread) {
+    return VALUE_DRAW + Value(2 * (thisThread->nodes & 1) - 1);
+  }
+
+  // Skill structure is used to implement strength limit
+  struct Skill {
+    explicit Skill(int l) : level(l) {}
+    bool enabled() const { return level < 20; }
+    bool time_to_pick(Depth depth) const { return depth == 1 + level; }
+    Move pick_best(size_t multiPV);
+
+    int level;
+    Move best = MOVE_NONE;
+  };
+
+  // Breadcrumbs are used to mark nodes as being searched by a given thread
+  struct Breadcrumb {
+    std::atomic<Thread*> thread;
+    std::atomic<Key> key;
+  };
+  std::array<Breadcrumb, 1024> breadcrumbs;
+
+  // ThreadHolding structure keeps track of which thread left breadcrumbs at the given
+  // node for potential reductions. A free node will be marked upon entering the moves
+  // loop by the constructor, and unmarked upon leaving that loop by the destructor.
+  struct ThreadHolding {
+    explicit ThreadHolding(Thread* thisThread, Key posKey, int ply) {
+       location = ply < 8 ? &breadcrumbs[posKey & (breadcrumbs.size() - 1)] : nullptr;
+       otherThread = false;
+       owning = false;
+       if (location)
+       {
+          // See if another already marked this location, if not, mark it ourselves
+          Thread* tmp = (*location).thread.load(std::memory_order_relaxed);
+          if (tmp == nullptr)
+          {
+              (*location).thread.store(thisThread, std::memory_order_relaxed);
+              (*location).key.store(posKey, std::memory_order_relaxed);
+              owning = true;
+          }
+          else if (   tmp != thisThread
+                   && (*location).key.load(std::memory_order_relaxed) == posKey)
+              otherThread = true;
+       }
+    }
+
+    ~ThreadHolding() {
+       if (owning) // Free the marked location
+           (*location).thread.store(nullptr, std::memory_order_relaxed);
+    }
+
+    bool marked() { return otherThread; }
+
+    private:
+    Breadcrumb* location;
+    bool otherThread, owning;
+  };
+
+  template <NodeType NT>
+  Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode);
+
+  template <NodeType NT>
+  Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = 0);
+
+  Value value_to_tt(Value v, int ply);
+  Value value_from_tt(Value v, int ply, int r50c);
+  void update_pv(Move* pv, Move move, Move* childPv);
+  void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus);
+  void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus, int depth);
+  void update_all_stats(const Position& pos, Stack* ss, Move bestMove, Value bestValue, Value beta, Square prevSq,
+                        Move* quietsSearched, int quietCount, Move* capturesSearched, int captureCount, Depth depth);
+
+  // perft() is our utility to verify move generation. All the leaf nodes up
+  // to the given depth are generated and counted, and the sum is returned.
+  template<bool Root>
+  uint64_t perft(Position& pos, Depth depth) {
+
+    StateInfo st;
+    uint64_t cnt, nodes = 0;
+    const bool leaf = (depth == 2);
+
+    for (const auto& m : MoveList<LEGAL>(pos))
+    {
+        if (Root && depth <= 1)
+            cnt = 1, nodes++;
+        else
+        {
+            pos.do_move(m, st);
+            cnt = leaf ? MoveList<LEGAL>(pos).size() : perft<false>(pos, depth - 1);
+            nodes += cnt;
+            pos.undo_move(m);
+        }
+        if (Root)
+            sync_cout << UCI::move(m, pos.is_chess960()) << ": " << cnt << sync_endl;
+    }
+    return nodes;
+  }
+
+} // namespace
+
+
+/// Search::init() is called at startup to initialize various lookup tables
+
+void Search::init() {
+
+  for (int i = 1; i < MAX_MOVES; ++i)
+      Reductions[i] = int((22.0 + std::log(Threads.size())) * std::log(i));
+}
+
+
+/// Search::clear() resets search state to its initial value
+
+void Search::clear() {
+
+  Threads.main()->wait_for_search_finished();
+
+  Time.availableNodes = 0;
+  TT.clear();
+  Threads.clear();
+  Tablebases::init(Options["SyzygyPath"]); // Free mapped files
+}
+
+
+/// MainThread::search() is started when the program receives the UCI 'go'
+/// command. It searches from the root position and outputs the "bestmove".
+
+void MainThread::search() {
+
+  if (Limits.perft)
+  {
+      nodes = perft<true>(rootPos, Limits.perft);
+      sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl;
+      return;
+  }
+
+  Color us = rootPos.side_to_move();
+  Time.init(Limits, us, rootPos.game_ply());
+  TT.new_search();
+
+  Eval::verify_NNUE();
+
+  if (rootMoves.empty())
+  {
+      rootMoves.emplace_back(MOVE_NONE);
+      sync_cout << "info depth 0 score "
+                << UCI::value(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW)
+                << sync_endl;
+  }
+  else
+  {
+      Threads.start_searching(); // start non-main threads
+      Thread::search();          // main thread start searching
+  }
+
+  // When we reach the maximum depth, we can arrive here without a raise of
+  // Threads.stop. However, if we are pondering or in an infinite search,
+  // the UCI protocol states that we shouldn't print the best move before the
+  // GUI sends a "stop" or "ponderhit" command. We therefore simply wait here
+  // until the GUI sends one of those commands.
+
+  while (!Threads.stop && (ponder || Limits.infinite))
+  {} // Busy wait for a stop or a ponder reset
+
+  // Stop the threads if not already stopped (also raise the stop if
+  // "ponderhit" just reset Threads.ponder).
+  Threads.stop = true;
+
+  // Wait until all threads have finished
+  Threads.wait_for_search_finished();
+
+  // When playing in 'nodes as time' mode, subtract the searched nodes from
+  // the available ones before exiting.
+  if (Limits.npmsec)
+      Time.availableNodes += Limits.inc[us] - Threads.nodes_searched();
+
+  Thread* bestThread = this;
+
+  if (   int(Options["MultiPV"]) == 1
+      && !Limits.depth
+      && !(Skill(Options["Skill Level"]).enabled() || int(Options["UCI_LimitStrength"]))
+      && rootMoves[0].pv[0] != MOVE_NONE)
+      bestThread = Threads.get_best_thread();
+
+  bestPreviousScore = bestThread->rootMoves[0].score;
+
+  // Send again PV info if we have a new best thread
+  if (bestThread != this)
+      sync_cout << UCI::pv(bestThread->rootPos, bestThread->completedDepth, -VALUE_INFINITE, VALUE_INFINITE) << sync_endl;
+
+  sync_cout << "bestmove " << UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960());
+
+  if (bestThread->rootMoves[0].pv.size() > 1 || bestThread->rootMoves[0].extract_ponder_from_tt(rootPos))
+      std::cout << " ponder " << UCI::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960());
+
+  std::cout << sync_endl;
+}
+
+
+/// Thread::search() is the main iterative deepening loop. It calls search()
+/// repeatedly with increasing depth until the allocated thinking time has been
+/// consumed, the user stops the search, or the maximum search depth is reached.
+
+void Thread::search() {
+
+  // To allow access to (ss-7) up to (ss+2), the stack must be oversized.
+  // The former is needed to allow update_continuation_histories(ss-1, ...),
+  // which accesses its argument at ss-6, also near the root.
+  // The latter is needed for statScores and killer initialization.
+  Stack stack[MAX_PLY+10], *ss = stack+7;
+  Move  pv[MAX_PLY+1];
+  Value bestValue, alpha, beta, delta;
+  Move  lastBestMove = MOVE_NONE;
+  Depth lastBestMoveDepth = 0;
+  MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr);
+  double timeReduction = 1, totBestMoveChanges = 0;
+  Color us = rootPos.side_to_move();
+  int iterIdx = 0;
+
+  std::memset(ss-7, 0, 10 * sizeof(Stack));
+  for (int i = 7; i > 0; i--)
+      (ss-i)->continuationHistory = &this->continuationHistory[0][0][NO_PIECE][0]; // Use as a sentinel
+
+  ss->pv = pv;
+
+  bestValue = delta = alpha = -VALUE_INFINITE;
+  beta = VALUE_INFINITE;
+
+  if (mainThread)
+  {
+      if (mainThread->bestPreviousScore == VALUE_INFINITE)
+          for (int i = 0; i < 4; ++i)
+              mainThread->iterValue[i] = VALUE_ZERO;
+      else
+          for (int i = 0; i < 4; ++i)
+              mainThread->iterValue[i] = mainThread->bestPreviousScore;
+  }
+
+  std::copy(&lowPlyHistory[2][0], &lowPlyHistory.back().back() + 1, &lowPlyHistory[0][0]);
+  std::fill(&lowPlyHistory[MAX_LPH - 2][0], &lowPlyHistory.back().back() + 1, 0);
+
+  size_t multiPV = size_t(Options["MultiPV"]);
+
+  // Pick integer skill levels, but non-deterministically round up or down
+  // such that the average integer skill corresponds to the input floating point one.
+  // UCI_Elo is converted to a suitable fractional skill level, using anchoring
+  // to CCRL Elo (goldfish 1.13 = 2000) and a fit through Ordo derived Elo
+  // for match (TC 60+0.6) results spanning a wide range of k values.
+  PRNG rng(now());
+  double floatLevel = Options["UCI_LimitStrength"] ?
+                      std::clamp(std::pow((Options["UCI_Elo"] - 1346.6) / 143.4, 1 / 0.806), 0.0, 20.0) :
+                        double(Options["Skill Level"]);
+  int intLevel = int(floatLevel) +
+                 ((floatLevel - int(floatLevel)) * 1024 > rng.rand<unsigned>() % 1024  ? 1 : 0);
+  Skill skill(intLevel);
+
+  // When playing with strength handicap enable MultiPV search that we will
+  // use behind the scenes to retrieve a set of possible moves.
+  if (skill.enabled())
+      multiPV = std::max(multiPV, (size_t)4);
+
+  multiPV = std::min(multiPV, rootMoves.size());
+  ttHitAverage = TtHitAverageWindow * TtHitAverageResolution / 2;
+
+  int ct = int(Options["Contempt"]) * PawnValueEg / 100; // From centipawns
+
+  // In analysis mode, adjust contempt in accordance with user preference
+  if (Limits.infinite || Options["UCI_AnalyseMode"])
+      ct =  Options["Analysis Contempt"] == "Off"  ? 0
+          : Options["Analysis Contempt"] == "Both" ? ct
+          : Options["Analysis Contempt"] == "White" && us == BLACK ? -ct
+          : Options["Analysis Contempt"] == "Black" && us == WHITE ? -ct
+          : ct;
+
+  // Evaluation score is from the white point of view
+  contempt = (us == WHITE ?  make_score(ct, ct / 2)
+                          : -make_score(ct, ct / 2));
+
+  int searchAgainCounter = 0;
+
+  // Iterative deepening loop until requested to stop or the target depth is reached
+  while (   ++rootDepth < MAX_PLY
+         && !Threads.stop
+         && !(Limits.depth && mainThread && rootDepth > Limits.depth))
+  {
+      // Age out PV variability metric
+      if (mainThread)
+          totBestMoveChanges /= 2;
+
+      // Save the last iteration's scores before first PV line is searched and
+      // all the move scores except the (new) PV are set to -VALUE_INFINITE.
+      for (RootMove& rm : rootMoves)
+          rm.previousScore = rm.score;
+
+      size_t pvFirst = 0;
+      pvLast = 0;
+
+      if (!Threads.increaseDepth)
+         searchAgainCounter++;
+
+      // MultiPV loop. We perform a full root search for each PV line
+      for (pvIdx = 0; pvIdx < multiPV && !Threads.stop; ++pvIdx)
+      {
+          if (pvIdx == pvLast)
+          {
+              pvFirst = pvLast;
+              for (pvLast++; pvLast < rootMoves.size(); pvLast++)
+                  if (rootMoves[pvLast].tbRank != rootMoves[pvFirst].tbRank)
+                      break;
+          }
+
+          // Reset UCI info selDepth for each depth and each PV line
+          selDepth = 0;
+
+          // Reset aspiration window starting size
+          if (rootDepth >= 4)
+          {
+              Value prev = rootMoves[pvIdx].previousScore;
+              delta = Value(17);
+              alpha = std::max(prev - delta,-VALUE_INFINITE);
+              beta  = std::min(prev + delta, VALUE_INFINITE);
+
+              // Adjust contempt based on root move's previousScore (dynamic contempt)
+              int dct = ct + (105 - ct / 2) * prev / (abs(prev) + 149);
+
+              contempt = (us == WHITE ?  make_score(dct, dct / 2)
+                                      : -make_score(dct, dct / 2));
+          }
+
+          // Start with a small aspiration window and, in the case of a fail
+          // high/low, re-search with a bigger window until we don't fail
+          // high/low anymore.
+          int failedHighCnt = 0;
+          while (true)
+          {
+              Depth adjustedDepth = std::max(1, rootDepth - failedHighCnt - searchAgainCounter);
+              bestValue = ::search<PV>(rootPos, ss, alpha, beta, adjustedDepth, false);
+
+              // Bring the best move to the front. It is critical that sorting
+              // is done with a stable algorithm because all the values but the
+              // first and eventually the new best one are set to -VALUE_INFINITE
+              // and we want to keep the same order for all the moves except the
+              // new PV that goes to the front. Note that in case of MultiPV
+              // search the already searched PV lines are preserved.
+              std::stable_sort(rootMoves.begin() + pvIdx, rootMoves.begin() + pvLast);
+
+              // If search has been stopped, we break immediately. Sorting is
+              // safe because RootMoves is still valid, although it refers to
+              // the previous iteration.
+              if (Threads.stop)
+                  break;
+
+              // When failing high/low give some update (without cluttering
+              // the UI) before a re-search.
+              if (   mainThread
+                  && multiPV == 1
+                  && (bestValue <= alpha || bestValue >= beta)
+                  && Time.elapsed() > 3000)
+                  sync_cout << UCI::pv(rootPos, rootDepth, alpha, beta) << sync_endl;
+
+              // In case of failing low/high increase aspiration window and
+              // re-search, otherwise exit the loop.
+              if (bestValue <= alpha)
+              {
+                  beta = (alpha + beta) / 2;
+                  alpha = std::max(bestValue - delta, -VALUE_INFINITE);
+
+                  failedHighCnt = 0;
+                  if (mainThread)
+                      mainThread->stopOnPonderhit = false;
+              }
+              else if (bestValue >= beta)
+              {
+                  beta = std::min(bestValue + delta, VALUE_INFINITE);
+                  ++failedHighCnt;
+              }
+              else
+              {
+                  ++rootMoves[pvIdx].bestMoveCount;
+                  break;
+              }
+
+              delta += delta / 4 + 5;
+
+              assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE);
+          }
+
+          // Sort the PV lines searched so far and update the GUI
+          std::stable_sort(rootMoves.begin() + pvFirst, rootMoves.begin() + pvIdx + 1);
+
+          if (    mainThread
+              && (Threads.stop || pvIdx + 1 == multiPV || Time.elapsed() > 3000))
+              sync_cout << UCI::pv(rootPos, rootDepth, alpha, beta) << sync_endl;
+      }
+
+      if (!Threads.stop)
+          completedDepth = rootDepth;
+
+      if (rootMoves[0].pv[0] != lastBestMove) {
+         lastBestMove = rootMoves[0].pv[0];
+         lastBestMoveDepth = rootDepth;
+      }
+
+      // Have we found a "mate in x"?
+      if (   Limits.mate
+          && bestValue >= VALUE_MATE_IN_MAX_PLY
+          && VALUE_MATE - bestValue <= 2 * Limits.mate)
+          Threads.stop = true;
+
+      if (!mainThread)
+          continue;
+
+      // If skill level is enabled and time is up, pick a sub-optimal best move
+      if (skill.enabled() && skill.time_to_pick(rootDepth))
+          skill.pick_best(multiPV);
+
+      // Do we have time for the next iteration? Can we stop searching now?
+      if (    Limits.use_time_management()
+          && !Threads.stop
+          && !mainThread->stopOnPonderhit)
+      {
+          double fallingEval = (318 + 6 * (mainThread->bestPreviousScore - bestValue)
+                                    + 6 * (mainThread->iterValue[iterIdx] - bestValue)) / 825.0;
+          fallingEval = std::clamp(fallingEval, 0.5, 1.5);
+
+          // If the bestMove is stable over several iterations, reduce time accordingly
+          timeReduction = lastBestMoveDepth + 9 < completedDepth ? 1.92 : 0.95;
+          double reduction = (1.47 + mainThread->previousTimeReduction) / (2.32 * timeReduction);
+
+          // Use part of the gained time from a previous stable move for the current move
+          for (Thread* th : Threads)
+          {
+              totBestMoveChanges += th->bestMoveChanges;
+              th->bestMoveChanges = 0;
+          }
+          double bestMoveInstability = 1 + totBestMoveChanges / Threads.size();
+
+          double totalTime = rootMoves.size() == 1 ? 0 :
+                             Time.optimum() * fallingEval * reduction * bestMoveInstability;
+
+          // Stop the search if we have exceeded the totalTime, at least 1ms search
+          if (Time.elapsed() > totalTime)
+          {
+              // If we are allowed to ponder do not stop the search now but
+              // keep pondering until the GUI sends "ponderhit" or "stop".
+              if (mainThread->ponder)
+                  mainThread->stopOnPonderhit = true;
+              else
+                  Threads.stop = true;
+          }
+          else if (   Threads.increaseDepth
+                   && !mainThread->ponder
+                   && Time.elapsed() > totalTime * 0.58)
+                   Threads.increaseDepth = false;
+          else
+                   Threads.increaseDepth = true;
+      }
+
+      mainThread->iterValue[iterIdx] = bestValue;
+      iterIdx = (iterIdx + 1) & 3;
+  }
+
+  if (!mainThread)
+      return;
+
+  mainThread->previousTimeReduction = timeReduction;
+
+  // If skill level is enabled, swap best PV line with the sub-optimal one
+  if (skill.enabled())
+      std::swap(rootMoves[0], *std::find(rootMoves.begin(), rootMoves.end(),
+                skill.best ? skill.best : skill.pick_best(multiPV)));
+}
+
+
+namespace {
+
+  // search<>() is the main search function for both PV and non-PV nodes
+
+  template <NodeType NT>
+  Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) {
+
+    constexpr bool PvNode = NT == PV;
+    const bool rootNode = PvNode && ss->ply == 0;
+
+    // Check if we have an upcoming move which draws by repetition, or
+    // if the opponent had an alternative move earlier to this position.
+    if (   pos.rule50_count() >= 3
+        && alpha < VALUE_DRAW
+        && !rootNode
+        && pos.has_game_cycle(ss->ply))
+    {
+        alpha = value_draw(pos.this_thread());
+        if (alpha >= beta)
+            return alpha;
+    }
+
+    // Dive into quiescence search when the depth reaches zero
+    if (depth <= 0)
+        return qsearch<NT>(pos, ss, alpha, beta);
+
+    assert(-VALUE_INFINITE <= alpha && alpha < beta && beta <= VALUE_INFINITE);
+    assert(PvNode || (alpha == beta - 1));
+    assert(0 < depth && depth < MAX_PLY);
+    assert(!(PvNode && cutNode));
+
+    Move pv[MAX_PLY+1], capturesSearched[32], quietsSearched[64];
+    StateInfo st;
+    TTEntry* tte;
+    Key posKey;
+    Move ttMove, move, excludedMove, bestMove;
+    Depth extension, newDepth;
+    Value bestValue, value, ttValue, eval, maxValue, probCutBeta;
+    bool ttHit, formerPv, givesCheck, improving, didLMR, priorCapture;
+    bool captureOrPromotion, doFullDepthSearch, moveCountPruning,
+         ttCapture, singularQuietLMR;
+    Piece movedPiece;
+    int moveCount, captureCount, quietCount;
+
+    // Step 1. Initialize node
+    Thread* thisThread = pos.this_thread();
+    ss->inCheck = pos.checkers();
+    priorCapture = pos.captured_piece();
+    Color us = pos.side_to_move();
+    moveCount = captureCount = quietCount = ss->moveCount = 0;
+    bestValue = -VALUE_INFINITE;
+    maxValue = VALUE_INFINITE;
+
+    // Check for the available remaining time
+    if (thisThread == Threads.main())
+        static_cast<MainThread*>(thisThread)->check_time();
+
+    // Used to send selDepth info to GUI (selDepth counts from 1, ply from 0)
+    if (PvNode && thisThread->selDepth < ss->ply + 1)
+        thisThread->selDepth = ss->ply + 1;
+
+    if (!rootNode)
+    {
+        // Step 2. Check for aborted search and immediate draw
+        if (   Threads.stop.load(std::memory_order_relaxed)
+            || pos.is_draw(ss->ply)
+            || ss->ply >= MAX_PLY)
+            return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos)
+                                                        : value_draw(pos.this_thread());
+
+        // Step 3. Mate distance pruning. Even if we mate at the next move our score
+        // would be at best mate_in(ss->ply+1), but if alpha is already bigger because
+        // a shorter mate was found upward in the tree then there is no need to search
+        // because we will never beat the current alpha. Same logic but with reversed
+        // signs applies also in the opposite condition of being mated instead of giving
+        // mate. In this case return a fail-high score.
+        alpha = std::max(mated_in(ss->ply), alpha);
+        beta = std::min(mate_in(ss->ply+1), beta);
+        if (alpha >= beta)
+            return alpha;
+    }
+
+    assert(0 <= ss->ply && ss->ply < MAX_PLY);
+
+    (ss+1)->ply = ss->ply + 1;
+    (ss+1)->ttPv = false;
+    (ss+1)->excludedMove = bestMove = MOVE_NONE;
+    (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE;
+    Square prevSq = to_sq((ss-1)->currentMove);
+
+    // Initialize statScore to zero for the grandchildren of the current position.
+    // So statScore is shared between all grandchildren and only the first grandchild
+    // starts with statScore = 0. Later grandchildren start with the last calculated
+    // statScore of the previous grandchild. This influences the reduction rules in
+    // LMR which are based on the statScore of parent position.
+    if (rootNode)
+        (ss+4)->statScore = 0;
+    else
+        (ss+2)->statScore = 0;
+
+    // Step 4. Transposition table lookup. We don't want the score of a partial
+    // search to overwrite a previous full search TT value, so we use a different
+    // position key in case of an excluded move.
+    excludedMove = ss->excludedMove;
+    posKey = excludedMove == MOVE_NONE ? pos.key() : pos.key() ^ make_key(excludedMove);
+    tte = TT.probe(posKey, ttHit);
+    ttValue = ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE;
+    ttMove =  rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0]
+            : ttHit    ? tte->move() : MOVE_NONE;
+    if (!excludedMove)
+        ss->ttPv = PvNode || (ttHit && tte->is_pv());
+    formerPv = ss->ttPv && !PvNode;
+
+    if (   ss->ttPv
+        && depth > 12
+        && ss->ply - 1 < MAX_LPH
+        && !priorCapture
+        && is_ok((ss-1)->currentMove))
+        thisThread->lowPlyHistory[ss->ply - 1][from_to((ss-1)->currentMove)] << stat_bonus(depth - 5);
+
+    // thisThread->ttHitAverage can be used to approximate the running average of ttHit
+    thisThread->ttHitAverage =   (TtHitAverageWindow - 1) * thisThread->ttHitAverage / TtHitAverageWindow
+                                + TtHitAverageResolution * ttHit;
+
+    // At non-PV nodes we check for an early TT cutoff
+    if (  !PvNode
+        && ttHit
+        && tte->depth() >= depth
+        && ttValue != VALUE_NONE // Possible in case of TT access race
+        && (ttValue >= beta ? (tte->bound() & BOUND_LOWER)
+                            : (tte->bound() & BOUND_UPPER)))
+    {
+        // If ttMove is quiet, update move sorting heuristics on TT hit
+        if (ttMove)
+        {
+            if (ttValue >= beta)
+            {
+                if (!pos.capture_or_promotion(ttMove))
+                    update_quiet_stats(pos, ss, ttMove, stat_bonus(depth), depth);
+
+                // Extra penalty for early quiet moves of the previous ply
+                if ((ss-1)->moveCount <= 2 && !priorCapture)
+                    update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + 1));
+            }
+            // Penalty for a quiet ttMove that fails low
+            else if (!pos.capture_or_promotion(ttMove))
+            {
+                int penalty = -stat_bonus(depth);
+                thisThread->mainHistory[us][from_to(ttMove)] << penalty;
+                update_continuation_histories(ss, pos.moved_piece(ttMove), to_sq(ttMove), penalty);
+            }
+        }
+
+        if (pos.rule50_count() < 90)
+            return ttValue;
+    }
+
+    // Step 5. Tablebases probe
+    if (!rootNode && TB::Cardinality)
+    {
+        int piecesCount = pos.count<ALL_PIECES>();
+
+        if (    piecesCount <= TB::Cardinality
+            && (piecesCount <  TB::Cardinality || depth >= TB::ProbeDepth)
+            &&  pos.rule50_count() == 0
+            && !pos.can_castle(ANY_CASTLING))
+        {
+            TB::ProbeState err;
+            TB::WDLScore wdl = Tablebases::probe_wdl(pos, &err);
+
+            // Force check of time on the next occasion
+            if (thisThread == Threads.main())
+                static_cast<MainThread*>(thisThread)->callsCnt = 0;
+
+            if (err != TB::ProbeState::FAIL)
+            {
+                thisThread->tbHits.fetch_add(1, std::memory_order_relaxed);
+
+                int drawScore = TB::UseRule50 ? 1 : 0;
+
+                // use the range VALUE_MATE_IN_MAX_PLY to VALUE_TB_WIN_IN_MAX_PLY to score
+                value =  wdl < -drawScore ? VALUE_MATED_IN_MAX_PLY + ss->ply + 1
+                       : wdl >  drawScore ? VALUE_MATE_IN_MAX_PLY - ss->ply - 1
+                                          : VALUE_DRAW + 2 * wdl * drawScore;
+
+                Bound b =  wdl < -drawScore ? BOUND_UPPER
+                         : wdl >  drawScore ? BOUND_LOWER : BOUND_EXACT;
+
+                if (    b == BOUND_EXACT
+                    || (b == BOUND_LOWER ? value >= beta : value <= alpha))
+                {
+                    tte->save(posKey, value_to_tt(value, ss->ply), ss->ttPv, b,
+                              std::min(MAX_PLY - 1, depth + 6),
+                              MOVE_NONE, VALUE_NONE);
+
+                    return value;
+                }
+
+                if (PvNode)
+                {
+                    if (b == BOUND_LOWER)
+                        bestValue = value, alpha = std::max(alpha, bestValue);
+                    else
+                        maxValue = value;
+                }
+            }
+        }
+    }
+
+    CapturePieceToHistory& captureHistory = thisThread->captureHistory;
+
+    // Step 6. Static evaluation of the position
+    if (ss->inCheck)
+    {
+        // Skip early pruning when in check
+        ss->staticEval = eval = VALUE_NONE;
+        improving = false;
+        goto moves_loop;
+    }
+    else if (ttHit)
+    {
+        // Never assume anything about values stored in TT
+        ss->staticEval = eval = tte->eval();
+        if (eval == VALUE_NONE)
+            ss->staticEval = eval = evaluate(pos);
+
+        if (eval == VALUE_DRAW)
+            eval = value_draw(thisThread);
+
+        // Can ttValue be used as a better position evaluation?
+        if (    ttValue != VALUE_NONE
+            && (tte->bound() & (ttValue > eval ? BOUND_LOWER : BOUND_UPPER)))
+            eval = ttValue;
+    }
+    else
+    {
+        if ((ss-1)->currentMove != MOVE_NULL)
+            ss->staticEval = eval = evaluate(pos);
+        else
+            ss->staticEval = eval = -(ss-1)->staticEval + 2 * Tempo;
+
+        tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval);
+    }
+
+    // Step 7. Razoring (~1 Elo)
+    if (   !rootNode // The required rootNode PV handling is not available in qsearch
+        &&  depth == 1
+        &&  eval <= alpha - RazorMargin)
+        return qsearch<NT>(pos, ss, alpha, beta);
+
+    improving =  (ss-2)->staticEval == VALUE_NONE
+               ? ss->staticEval > (ss-4)->staticEval || (ss-4)->staticEval == VALUE_NONE
+               : ss->staticEval > (ss-2)->staticEval;
+
+    // Step 8. Futility pruning: child node (~50 Elo)
+    if (   !PvNode
+        &&  depth < 8
+        &&  eval - futility_margin(depth, improving) >= beta
+        &&  eval < VALUE_KNOWN_WIN) // Do not return unproven wins
+        return eval;
+
+    // Step 9. Null move search with verification search (~40 Elo)
+    if (   !PvNode
+        && (ss-1)->currentMove != MOVE_NULL
+        && (ss-1)->statScore < 22977
+        &&  eval >= beta
+        &&  eval >= ss->staticEval
+        &&  ss->staticEval >= beta - 30 * depth - 28 * improving + 84 * ss->ttPv + 182
+        && !excludedMove
+        &&  pos.non_pawn_material(us)
+        && (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor))
+    {
+        assert(eval - beta >= 0);
+
+        // Null move dynamic reduction based on depth and value
+        Depth R = (817 + 71 * depth) / 213 + std::min(int(eval - beta) / 192, 3);
+
+        ss->currentMove = MOVE_NULL;
+        ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0];
+
+        pos.do_null_move(st);
+
+        Value nullValue = -search<NonPV>(pos, ss+1, -beta, -beta+1, depth-R, !cutNode);
+
+        pos.undo_null_move();
+
+        if (nullValue >= beta)
+        {
+            // Do not return unproven mate or TB scores
+            if (nullValue >= VALUE_TB_WIN_IN_MAX_PLY)
+                nullValue = beta;
+
+            if (thisThread->nmpMinPly || (abs(beta) < VALUE_KNOWN_WIN && depth < 13))
+                return nullValue;
+
+            assert(!thisThread->nmpMinPly); // Recursive verification is not allowed
+
+            // Do verification search at high depths, with null move pruning disabled
+            // for us, until ply exceeds nmpMinPly.
+            thisThread->nmpMinPly = ss->ply + 3 * (depth-R) / 4;
+            thisThread->nmpColor = us;
+
+            Value v = search<NonPV>(pos, ss, beta-1, beta, depth-R, false);
+
+            thisThread->nmpMinPly = 0;
+
+            if (v >= beta)
+                return nullValue;
+        }
+    }
+
+    probCutBeta = beta + 176 - 49 * improving;
+
+    // Step 10. ProbCut (~10 Elo)
+    // If we have a good enough capture and a reduced search returns a value
+    // much above beta, we can (almost) safely prune the previous move.
+    if (   !PvNode
+        &&  depth > 4
+        &&  abs(beta) < VALUE_TB_WIN_IN_MAX_PLY
+        // if value from transposition table is lower than probCutBeta, don't attempt probCut
+        // there and in further interactions with transposition table cutoff depth is set to depth - 3
+        // because probCut search has depth set to depth - 4 but we also do a move before it
+        // so effective depth is equal to depth - 3
+        && !(   ttHit
+             && tte->depth() >= depth - 3
+             && ttValue != VALUE_NONE
+             && ttValue < probCutBeta))
+    {
+        // if ttMove is a capture and value from transposition table is good enough produce probCut
+        // cutoff without digging into actual probCut search
+        if (   ttHit
+            && tte->depth() >= depth - 3
+            && ttValue != VALUE_NONE
+            && ttValue >= probCutBeta
+            && ttMove
+            && pos.capture_or_promotion(ttMove))
+            return probCutBeta;
+
+        assert(probCutBeta < VALUE_INFINITE);
+        MovePicker mp(pos, ttMove, probCutBeta - ss->staticEval, &captureHistory);
+        int probCutCount = 0;
+        bool ttPv = ss->ttPv;
+        ss->ttPv = false;
+
+        while (   (move = mp.next_move()) != MOVE_NONE
+               && probCutCount < 2 + 2 * cutNode)
+            if (move != excludedMove && pos.legal(move))
+            {
+                assert(pos.capture_or_promotion(move));
+                assert(depth >= 5);
+
+                captureOrPromotion = true;
+                probCutCount++;
+
+                ss->currentMove = move;
+                ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck]
+                                                                          [captureOrPromotion]
+                                                                          [pos.moved_piece(move)]
+                                                                          [to_sq(move)];
+
+                pos.do_move(move, st);
+
+                // Perform a preliminary qsearch to verify that the move holds
+                value = -qsearch<NonPV>(pos, ss+1, -probCutBeta, -probCutBeta+1);
+
+                // If the qsearch held, perform the regular search
+                if (value >= probCutBeta)
+                    value = -search<NonPV>(pos, ss+1, -probCutBeta, -probCutBeta+1, depth - 4, !cutNode);
+
+                pos.undo_move(move);
+
+                if (value >= probCutBeta)
+                {
+                    // if transposition table doesn't have equal or more deep info write probCut data into it
+                    if ( !(ttHit
+                       && tte->depth() >= depth - 3
+                       && ttValue != VALUE_NONE))
+                        tte->save(posKey, value_to_tt(value, ss->ply), ttPv,
+                            BOUND_LOWER,
+                            depth - 3, move, ss->staticEval);
+                    return value;
+                }
+            }
+         ss->ttPv = ttPv;
+    }
+
+    // Step 11. If the position is not in TT, decrease depth by 2
+    if (   PvNode
+        && depth >= 6
+        && !ttMove)
+        depth -= 2;
+
+moves_loop: // When in check, search starts from here
+
+    const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory,
+                                          nullptr                   , (ss-4)->continuationHistory,
+                                          nullptr                   , (ss-6)->continuationHistory };
+
+    Move countermove = thisThread->counterMoves[pos.piece_on(prevSq)][prevSq];
+
+    MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory,
+                                      &thisThread->lowPlyHistory,
+                                      &captureHistory,
+                                      contHist,
+                                      countermove,
+                                      ss->killers,
+                                      ss->ply);
+
+    value = bestValue;
+    singularQuietLMR = moveCountPruning = false;
+    ttCapture = ttMove && pos.capture_or_promotion(ttMove);
+
+    // Mark this node as being searched
+    ThreadHolding th(thisThread, posKey, ss->ply);
+
+    // Step 12. Loop through all pseudo-legal moves until no moves remain
+    // or a beta cutoff occurs.
+    while ((move = mp.next_move(moveCountPruning)) != MOVE_NONE)
+    {
+      assert(is_ok(move));
+
+      if (move == excludedMove)
+          continue;
+
+      // At root obey the "searchmoves" option and skip moves not listed in Root
+      // Move List. As a consequence any illegal move is also skipped. In MultiPV
+      // mode we also skip PV moves which have been already searched and those
+      // of lower "TB rank" if we are in a TB root position.
+      if (rootNode && !std::count(thisThread->rootMoves.begin() + thisThread->pvIdx,
+                                  thisThread->rootMoves.begin() + thisThread->pvLast, move))
+          continue;
+
+      // Check for legality
+      if (!rootNode && !pos.legal(move))
+          continue;
+
+      ss->moveCount = ++moveCount;
+
+      if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000)
+          sync_cout << "info depth " << depth
+                    << " currmove " << UCI::move(move, pos.is_chess960())
+                    << " currmovenumber " << moveCount + thisThread->pvIdx << sync_endl;
+      if (PvNode)
+          (ss+1)->pv = nullptr;
+
+      extension = 0;
+      captureOrPromotion = pos.capture_or_promotion(move);
+      movedPiece = pos.moved_piece(move);
+      givesCheck = pos.gives_check(move);
+
+      // Calculate new depth for this move
+      newDepth = depth - 1;
+
+      // Step 13. Pruning at shallow depth (~200 Elo)
+      if (  !rootNode
+          && pos.non_pawn_material(us)
+          && bestValue > VALUE_TB_LOSS_IN_MAX_PLY)
+      {
+          // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold
+          moveCountPruning = moveCount >= futility_move_count(improving, depth);
+
+          // Reduced depth of the next LMR search
+          int lmrDepth = std::max(newDepth - reduction(improving, depth, moveCount), 0);
+
+          if (   !captureOrPromotion
+              && !givesCheck)
+          {
+              // Countermoves based pruning (~20 Elo)
+              if (   lmrDepth < 4 + ((ss-1)->statScore > 0 || (ss-1)->moveCount == 1)
+                  && (*contHist[0])[movedPiece][to_sq(move)] < CounterMovePruneThreshold
+                  && (*contHist[1])[movedPiece][to_sq(move)] < CounterMovePruneThreshold)
+                  continue;
+
+              // Futility pruning: parent node (~5 Elo)
+              if (   lmrDepth < 7
+                  && !ss->inCheck
+                  && ss->staticEval + 283 + 170 * lmrDepth <= alpha
+                  &&  (*contHist[0])[movedPiece][to_sq(move)]
+                    + (*contHist[1])[movedPiece][to_sq(move)]
+                    + (*contHist[3])[movedPiece][to_sq(move)]
+                    + (*contHist[5])[movedPiece][to_sq(move)] / 2 < 27376)
+                  continue;
+
+              // Prune moves with negative SEE (~20 Elo)
+              if (!pos.see_ge(move, Value(-(29 - std::min(lmrDepth, 18)) * lmrDepth * lmrDepth)))
+                  continue;
+          }
+          else
+          {
+              // Capture history based pruning when the move doesn't give check
+              if (   !givesCheck
+                  && lmrDepth < 1
+                  && captureHistory[movedPiece][to_sq(move)][type_of(pos.piece_on(to_sq(move)))] < 0)
+                  continue;
+
+              // Futility pruning for captures
+              if (   !givesCheck
+                  && lmrDepth < 6
+                  && !(PvNode && abs(bestValue) < 2)
+                  && PieceValue[MG][type_of(movedPiece)] >= PieceValue[MG][type_of(pos.piece_on(to_sq(move)))]
+                  && !ss->inCheck
+                  && ss->staticEval + 169 + 244 * lmrDepth
+                     + PieceValue[MG][type_of(pos.piece_on(to_sq(move)))] <= alpha)
+                  continue;
+
+              // See based pruning
+              if (!pos.see_ge(move, Value(-221) * depth)) // (~25 Elo)
+                  continue;
+          }
+      }
+
+      // Step 14. Extensions (~75 Elo)
+
+      // Singular extension search (~70 Elo). If all moves but one fail low on a
+      // search of (alpha-s, beta-s), and just one fails high on (alpha, beta),
+      // then that move is singular and should be extended. To verify this we do
+      // a reduced search on all the other moves but the ttMove and if the
+      // result is lower than ttValue minus a margin, then we will extend the ttMove.
+      if (    depth >= 7
+          &&  move == ttMove
+          && !rootNode
+          && !excludedMove // Avoid recursive singular search
+       /* &&  ttValue != VALUE_NONE Already implicit in the next condition */
+          &&  abs(ttValue) < VALUE_KNOWN_WIN
+          && (tte->bound() & BOUND_LOWER)
+          &&  tte->depth() >= depth - 3)
+      {
+          Value singularBeta = ttValue - ((formerPv + 4) * depth) / 2;
+          Depth singularDepth = (depth - 1 + 3 * formerPv) / 2;
+          ss->excludedMove = move;
+          value = search<NonPV>(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode);
+          ss->excludedMove = MOVE_NONE;
+
+          if (value < singularBeta)
+          {
+              extension = 1;
+              singularQuietLMR = !ttCapture;
+          }
+
+          // Multi-cut pruning
+          // Our ttMove is assumed to fail high, and now we failed high also on a reduced
+          // search without the ttMove. So we assume this expected Cut-node is not singular,
+          // that multiple moves fail high, and we can prune the whole subtree by returning
+          // a soft bound.
+          else if (singularBeta >= beta)
+              return singularBeta;
+
+          // If the eval of ttMove is greater than beta we try also if there is another
+          // move that pushes it over beta, if so also produce a cutoff.
+          else if (ttValue >= beta)
+          {
+              ss->excludedMove = move;
+              value = search<NonPV>(pos, ss, beta - 1, beta, (depth + 3) / 2, cutNode);
+              ss->excludedMove = MOVE_NONE;
+
+              if (value >= beta)
+                  return beta;
+          }
+      }
+
+      // Check extension (~2 Elo)
+      else if (    givesCheck
+               && (pos.is_discovery_check_on_king(~us, move) || pos.see_ge(move)))
+          extension = 1;
+
+      // Last captures extension
+      else if (   PieceValue[EG][pos.captured_piece()] > PawnValueEg
+               && pos.non_pawn_material() <= 2 * RookValueMg)
+          extension = 1;
+
+      // Castling extension
+      if (   type_of(move) == CASTLING
+          && popcount(pos.pieces(us) & ~pos.pieces(PAWN) & (to_sq(move) & KingSide ? KingSide : QueenSide)) <= 2)
+          extension = 1;
+
+      // Late irreversible move extension
+      if (   move == ttMove
+          && pos.rule50_count() > 80
+          && (captureOrPromotion || type_of(movedPiece) == PAWN))
+          extension = 2;
+
+      // Add extension to new depth
+      newDepth += extension;
+
+      // Speculative prefetch as early as possible
+      prefetch(TT.first_entry(pos.key_after(move)));
+
+      // Update the current move (this must be done after singular extension search)
+      ss->currentMove = move;
+      ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck]
+                                                                [captureOrPromotion]
+                                                                [movedPiece]
+                                                                [to_sq(move)];
+
+      // Step 15. Make the move
+      pos.do_move(move, st, givesCheck);
+
+      // Step 16. Reduced depth search (LMR, ~200 Elo). If the move fails high it will be
+      // re-searched at full depth.
+      if (    depth >= 3
+          &&  moveCount > 1 + 2 * rootNode + 2 * (PvNode && abs(bestValue) < 2)
+          && (  !captureOrPromotion
+              || moveCountPruning
+              || ss->staticEval + PieceValue[EG][pos.captured_piece()] <= alpha
+              || cutNode
+              || thisThread->ttHitAverage < 427 * TtHitAverageResolution * TtHitAverageWindow / 1024))
+      {
+          Depth r = reduction(improving, depth, moveCount);
+
+          // Decrease reduction at non-check cut nodes for second move at low depths
+          if (   cutNode
+              && depth <= 10
+              && moveCount <= 2
+              && !ss->inCheck)
+              r--;
+
+          // Decrease reduction if the ttHit running average is large
+          if (thisThread->ttHitAverage > 509 * TtHitAverageResolution * TtHitAverageWindow / 1024)
+              r--;
+
+          // Reduction if other threads are searching this position
+          if (th.marked())
+              r++;
+
+          // Decrease reduction if position is or has been on the PV (~10 Elo)
+          if (ss->ttPv)
+              r -= 2;
+
+          if (moveCountPruning && !formerPv)
+              r++;
+
+          // Decrease reduction if opponent's move count is high (~5 Elo)
+          if ((ss-1)->moveCount > 13)
+              r--;
+
+          // Decrease reduction if ttMove has been singularly extended (~3 Elo)
+          if (singularQuietLMR)
+              r -= 1 + formerPv;
+
+          if (!captureOrPromotion)
+          {
+              // Increase reduction if ttMove is a capture (~5 Elo)
+              if (ttCapture)
+                  r++;
+
+              // Increase reduction for cut nodes (~10 Elo)
+              if (cutNode)
+                  r += 2;
+
+              // Decrease reduction for moves that escape a capture. Filter out
+              // castling moves, because they are coded as "king captures rook" and
+              // hence break make_move(). (~2 Elo)
+              else if (    type_of(move) == NORMAL
+                       && !pos.see_ge(reverse_move(move)))
+                  r -= 2 + ss->ttPv - (type_of(movedPiece) == PAWN);
+
+              ss->statScore =  thisThread->mainHistory[us][from_to(move)]
+                             + (*contHist[0])[movedPiece][to_sq(move)]
+                             + (*contHist[1])[movedPiece][to_sq(move)]
+                             + (*contHist[3])[movedPiece][to_sq(move)]
+                             - 5287;
+
+              // Decrease/increase reduction by comparing opponent's stat score (~10 Elo)
+              if (ss->statScore >= -106 && (ss-1)->statScore < -104)
+                  r--;
+
+              else if ((ss-1)->statScore >= -119 && ss->statScore < -140)
+                  r++;
+
+              // Decrease/increase reduction for moves with a good/bad history (~30 Elo)
+              r -= ss->statScore / 14884;
+          }
+          else
+          {
+            // Increase reduction for captures/promotions if late move and at low depth
+            if (depth < 8 && moveCount > 2)
+                r++;
+
+            // Unless giving check, this capture is likely bad
+            if (   !givesCheck
+                && ss->staticEval + PieceValue[EG][pos.captured_piece()] + 213 * depth <= alpha)
+                r++;
+          }
+
+          Depth d = std::clamp(newDepth - r, 1, newDepth);
+
+          value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, d, true);
+
+          doFullDepthSearch = value > alpha && d != newDepth;
+
+          didLMR = true;
+      }
+      else
+      {
+          doFullDepthSearch = !PvNode || moveCount > 1;
+
+          didLMR = false;
+      }
+
+      // Step 17. Full depth search when LMR is skipped or fails high
+      if (doFullDepthSearch)
+      {
+          value = -search<NonPV>(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode);
+
+          if (didLMR && !captureOrPromotion)
+          {
+              int bonus = value > alpha ?  stat_bonus(newDepth)
+                                        : -stat_bonus(newDepth);
+
+              if (move == ss->killers[0])
+                  bonus += bonus / 4;
+
+              update_continuation_histories(ss, movedPiece, to_sq(move), bonus);
+          }
+      }
+
+      // For PV nodes only, do a full PV search on the first move or after a fail
+      // high (in the latter case search only if value < beta), otherwise let the
+      // parent node fail low with value <= alpha and try another move.
+      if (PvNode && (moveCount == 1 || (value > alpha && (rootNode || value < beta))))
+      {
+          (ss+1)->pv = pv;
+          (ss+1)->pv[0] = MOVE_NONE;
+
+          value = -search<PV>(pos, ss+1, -beta, -alpha, newDepth, false);
+      }
+
+      // Step 18. Undo move
+      pos.undo_move(move);
+
+      assert(value > -VALUE_INFINITE && value < VALUE_INFINITE);
+
+      // Step 19. Check for a new best move
+      // Finished searching the move. If a stop occurred, the return value of
+      // the search cannot be trusted, and we return immediately without
+      // updating best move, PV and TT.
+      if (Threads.stop.load(std::memory_order_relaxed))
+          return VALUE_ZERO;
+
+      if (rootNode)
+      {
+          RootMove& rm = *std::find(thisThread->rootMoves.begin(),
+                                    thisThread->rootMoves.end(), move);
+
+          // PV move or new best move?
+          if (moveCount == 1 || value > alpha)
+          {
+              rm.score = value;
+              rm.selDepth = thisThread->selDepth;
+              rm.pv.resize(1);
+
+              assert((ss+1)->pv);
+
+              for (Move* m = (ss+1)->pv; *m != MOVE_NONE; ++m)
+                  rm.pv.push_back(*m);
+
+              // We record how often the best move has been changed in each
+              // iteration. This information is used for time management: when
+              // the best move changes frequently, we allocate some more time.
+              if (moveCount > 1)
+                  ++thisThread->bestMoveChanges;
+          }
+          else
+              // All other moves but the PV are set to the lowest value: this
+              // is not a problem when sorting because the sort is stable and the
+              // move position in the list is preserved - just the PV is pushed up.
+              rm.score = -VALUE_INFINITE;
+      }
+
+      if (value > bestValue)
+      {
+          bestValue = value;
+
+          if (value > alpha)
+          {
+              bestMove = move;
+
+              if (PvNode && !rootNode) // Update pv even in fail-high case
+                  update_pv(ss->pv, move, (ss+1)->pv);
+
+              if (PvNode && value < beta) // Update alpha! Always alpha < beta
+                  alpha = value;
+              else
+              {
+                  assert(value >= beta); // Fail high
+                  ss->statScore = 0;
+                  break;
+              }
+          }
+      }
+
+      if (move != bestMove)
+      {
+          if (captureOrPromotion && captureCount < 32)
+              capturesSearched[captureCount++] = move;
+
+          else if (!captureOrPromotion && quietCount < 64)
+              quietsSearched[quietCount++] = move;
+      }
+    }
+
+    // The following condition would detect a stop only after move loop has been
+    // completed. But in this case bestValue is valid because we have fully
+    // searched our subtree, and we can anyhow save the result in TT.
+    /*
+       if (Threads.stop)
+        return VALUE_DRAW;
+    */
+
+    // Step 20. Check for mate and stalemate
+    // All legal moves have been searched and if there are no legal moves, it
+    // must be a mate or a stalemate. If we are in a singular extension search then
+    // return a fail low score.
+
+    assert(moveCount || !ss->inCheck || excludedMove || !MoveList<LEGAL>(pos).size());
+
+    if (!moveCount)
+        bestValue = excludedMove ? alpha
+                   :     ss->inCheck ? mated_in(ss->ply) : VALUE_DRAW;
+
+    else if (bestMove)
+        update_all_stats(pos, ss, bestMove, bestValue, beta, prevSq,
+                         quietsSearched, quietCount, capturesSearched, captureCount, depth);
+
+    // Bonus for prior countermove that caused the fail low
+    else if (   (depth >= 3 || PvNode)
+             && !priorCapture)
+        update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth));
+
+    if (PvNode)
+        bestValue = std::min(bestValue, maxValue);
+
+    // If no good move is found and the previous position was ttPv, then the previous
+    // opponent move is probably good and the new position is added to the search tree.
+    if (bestValue <= alpha)
+        ss->ttPv = ss->ttPv || ((ss-1)->ttPv && depth > 3);
+    // Otherwise, a counter move has been found and if the position is the last leaf
+    // in the search tree, remove the position from the search tree.
+    else if (depth > 3)
+        ss->ttPv = ss->ttPv && (ss+1)->ttPv;
+
+    if (!excludedMove && !(rootNode && thisThread->pvIdx))
+        tte->save(posKey, value_to_tt(bestValue, ss->ply), ss->ttPv,
+                  bestValue >= beta ? BOUND_LOWER :
+                  PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER,
+                  depth, bestMove, ss->staticEval);
+
+    assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE);
+
+    return bestValue;
+  }
+
+
+  // qsearch() is the quiescence search function, which is called by the main search
+  // function with zero depth, or recursively with further decreasing depth per call.
+  template <NodeType NT>
+  Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) {
+
+    constexpr bool PvNode = NT == PV;
+
+    assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE);
+    assert(PvNode || (alpha == beta - 1));
+    assert(depth <= 0);
+
+    Move pv[MAX_PLY+1];
+    StateInfo st;
+    TTEntry* tte;
+    Key posKey;
+    Move ttMove, move, bestMove;
+    Depth ttDepth;
+    Value bestValue, value, ttValue, futilityValue, futilityBase, oldAlpha;
+    bool ttHit, pvHit, givesCheck, captureOrPromotion;
+    int moveCount;
+
+    if (PvNode)
+    {
+        oldAlpha = alpha; // To flag BOUND_EXACT when eval above alpha and no available moves
+        (ss+1)->pv = pv;
+        ss->pv[0] = MOVE_NONE;
+    }
+
+    Thread* thisThread = pos.this_thread();
+    (ss+1)->ply = ss->ply + 1;
+    bestMove = MOVE_NONE;
+    ss->inCheck = pos.checkers();
+    moveCount = 0;
+
+    // Check for an immediate draw or maximum ply reached
+    if (   pos.is_draw(ss->ply)
+        || ss->ply >= MAX_PLY)
+        return (ss->ply >= MAX_PLY && !ss->inCheck) ? evaluate(pos) : VALUE_DRAW;
+
+    assert(0 <= ss->ply && ss->ply < MAX_PLY);
+
+    // Decide whether or not to include checks: this fixes also the type of
+    // TT entry depth that we are going to use. Note that in qsearch we use
+    // only two types of depth in TT: DEPTH_QS_CHECKS or DEPTH_QS_NO_CHECKS.
+    ttDepth = ss->inCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS
+                                                  : DEPTH_QS_NO_CHECKS;
+    // Transposition table lookup
+    posKey = pos.key();
+    tte = TT.probe(posKey, ttHit);
+    ttValue = ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE;
+    ttMove = ttHit ? tte->move() : MOVE_NONE;
+    pvHit = ttHit && tte->is_pv();
+
+    if (  !PvNode
+        && ttHit
+        && tte->depth() >= ttDepth
+        && ttValue != VALUE_NONE // Only in case of TT access race
+        && (ttValue >= beta ? (tte->bound() & BOUND_LOWER)
+                            : (tte->bound() & BOUND_UPPER)))
+        return ttValue;
+
+    // Evaluate the position statically
+    if (ss->inCheck)
+    {
+        ss->staticEval = VALUE_NONE;
+        bestValue = futilityBase = -VALUE_INFINITE;
+    }
+    else
+    {
+        if (ttHit)
+        {
+            // Never assume anything about values stored in TT
+            if ((ss->staticEval = bestValue = tte->eval()) == VALUE_NONE)
+                ss->staticEval = bestValue = evaluate(pos);
+
+            // Can ttValue be used as a better position evaluation?
+            if (    ttValue != VALUE_NONE
+                && (tte->bound() & (ttValue > bestValue ? BOUND_LOWER : BOUND_UPPER)))
+                bestValue = ttValue;
+        }
+        else
+            ss->staticEval = bestValue =
+            (ss-1)->currentMove != MOVE_NULL ? evaluate(pos)
+                                             : -(ss-1)->staticEval + 2 * Tempo;
+
+        // Stand pat. Return immediately if static value is at least beta
+        if (bestValue >= beta)
+        {
+            if (!ttHit)
+                tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER,
+                          DEPTH_NONE, MOVE_NONE, ss->staticEval);
+
+            return bestValue;
+        }
+
+        if (PvNode && bestValue > alpha)
+            alpha = bestValue;
+
+        futilityBase = bestValue + 145;
+    }
+
+    const PieceToHistory* contHist[] = { (ss-1)->continuationHistory, (ss-2)->continuationHistory,
+                                          nullptr                   , (ss-4)->continuationHistory,
+                                          nullptr                   , (ss-6)->continuationHistory };
+
+    // Initialize a MovePicker object for the current position, and prepare
+    // to search the moves. Because the depth is <= 0 here, only captures,
+    // queen and checking knight promotions, and other checks(only if depth >= DEPTH_QS_CHECKS)
+    // will be generated.
+    MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory,
+                                      &thisThread->captureHistory,
+                                      contHist,
+                                      to_sq((ss-1)->currentMove));
+
+    // Loop through the moves until no moves remain or a beta cutoff occurs
+    while ((move = mp.next_move()) != MOVE_NONE)
+    {
+      assert(is_ok(move));
+
+      givesCheck = pos.gives_check(move);
+      captureOrPromotion = pos.capture_or_promotion(move);
+
+      moveCount++;
+
+      // Futility pruning
+      if (   !ss->inCheck
+          && !givesCheck
+          &&  futilityBase > -VALUE_KNOWN_WIN
+          && !pos.advanced_pawn_push(move))
+      {
+          assert(type_of(move) != ENPASSANT); // Due to !pos.advanced_pawn_push
+
+          // moveCount pruning
+          if (moveCount > 2)
+              continue;
+
+          futilityValue = futilityBase + PieceValue[EG][pos.piece_on(to_sq(move))];
+
+          if (futilityValue <= alpha)
+          {
+              bestValue = std::max(bestValue, futilityValue);
+              continue;
+          }
+
+          if (futilityBase <= alpha && !pos.see_ge(move, VALUE_ZERO + 1))
+          {
+              bestValue = std::max(bestValue, futilityBase);
+              continue;
+          }
+      }
+
+      // Do not search moves with negative SEE values
+      if (!ss->inCheck && !pos.see_ge(move))
+          continue;
+
+      // Speculative prefetch as early as possible
+      prefetch(TT.first_entry(pos.key_after(move)));
+
+      // Check for legality just before making the move
+      if (!pos.legal(move))
+      {
+          moveCount--;
+          continue;
+      }
+
+      ss->currentMove = move;
+      ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck]
+                                                                [captureOrPromotion]
+                                                                [pos.moved_piece(move)]
+                                                                [to_sq(move)];
+
+      if (  !captureOrPromotion
+          && moveCount
+          && (*contHist[0])[pos.moved_piece(move)][to_sq(move)] < CounterMovePruneThreshold
+          && (*contHist[1])[pos.moved_piece(move)][to_sq(move)] < CounterMovePruneThreshold)
+          continue;
+
+      // Make and search the move
+      pos.do_move(move, st, givesCheck);
+      value = -qsearch<NT>(pos, ss+1, -beta, -alpha, depth - 1);
+      pos.undo_move(move);
+
+      assert(value > -VALUE_INFINITE && value < VALUE_INFINITE);
+
+      // Check for a new best move
+      if (value > bestValue)
+      {
+          bestValue = value;
+
+          if (value > alpha)
+          {
+              bestMove = move;
+
+              if (PvNode) // Update pv even in fail-high case
+                  update_pv(ss->pv, move, (ss+1)->pv);
+
+              if (PvNode && value < beta) // Update alpha here!
+                  alpha = value;
+              else
+                  break; // Fail high
+          }
+       }
+    }
+
+    // All legal moves have been searched. A special case: if we're in check
+    // and no legal moves were found, it is checkmate.
+    if (ss->inCheck && bestValue == -VALUE_INFINITE)
+        return mated_in(ss->ply); // Plies to mate from the root
+
+    tte->save(posKey, value_to_tt(bestValue, ss->ply), pvHit,
+              bestValue >= beta ? BOUND_LOWER :
+              PvNode && bestValue > oldAlpha  ? BOUND_EXACT : BOUND_UPPER,
+              ttDepth, bestMove, ss->staticEval);
+
+    assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE);
+
+    return bestValue;
+  }
+
+
+  // value_to_tt() adjusts a mate or TB score from "plies to mate from the root" to
+  // "plies to mate from the current position". Standard scores are unchanged.
+  // The function is called before storing a value in the transposition table.
+
+  Value value_to_tt(Value v, int ply) {
+
+    assert(v != VALUE_NONE);
+
+    return  v >= VALUE_TB_WIN_IN_MAX_PLY  ? v + ply
+          : v <= VALUE_TB_LOSS_IN_MAX_PLY ? v - ply : v;
+  }
+
+
+  // value_from_tt() is the inverse of value_to_tt(): it adjusts a mate or TB score
+  // from the transposition table (which refers to the plies to mate/be mated from
+  // current position) to "plies to mate/be mated (TB win/loss) from the root". However,
+  // for mate scores, to avoid potentially false mate scores related to the 50 moves rule
+  // and the graph history interaction, we return an optimal TB score instead.
+
+  Value value_from_tt(Value v, int ply, int r50c) {
+
+    if (v == VALUE_NONE)
+        return VALUE_NONE;
+
+    if (v >= VALUE_TB_WIN_IN_MAX_PLY)  // TB win or better
+    {
+        if (v >= VALUE_MATE_IN_MAX_PLY && VALUE_MATE - v > 99 - r50c)
+            return VALUE_MATE_IN_MAX_PLY - 1; // do not return a potentially false mate score
+
+        return v - ply;
+    }
+
+    if (v <= VALUE_TB_LOSS_IN_MAX_PLY) // TB loss or worse
+    {
+        if (v <= VALUE_MATED_IN_MAX_PLY && VALUE_MATE + v > 99 - r50c)
+            return VALUE_MATED_IN_MAX_PLY + 1; // do not return a potentially false mate score
+
+        return v + ply;
+    }
+
+    return v;
+  }
+
+
+  // update_pv() adds current move and appends child pv[]
+
+  void update_pv(Move* pv, Move move, Move* childPv) {
+
+    for (*pv++ = move; childPv && *childPv != MOVE_NONE; )
+        *pv++ = *childPv++;
+    *pv = MOVE_NONE;
+  }
+
+
+  // update_all_stats() updates stats at the end of search() when a bestMove is found
+
+  void update_all_stats(const Position& pos, Stack* ss, Move bestMove, Value bestValue, Value beta, Square prevSq,
+                        Move* quietsSearched, int quietCount, Move* capturesSearched, int captureCount, Depth depth) {
+
+    int bonus1, bonus2;
+    Color us = pos.side_to_move();
+    Thread* thisThread = pos.this_thread();
+    CapturePieceToHistory& captureHistory = thisThread->captureHistory;
+    Piece moved_piece = pos.moved_piece(bestMove);
+    PieceType captured = type_of(pos.piece_on(to_sq(bestMove)));
+
+    bonus1 = stat_bonus(depth + 1);
+    bonus2 = bestValue > beta + PawnValueMg ? bonus1               // larger bonus
+                                            : stat_bonus(depth);   // smaller bonus
+
+    if (!pos.capture_or_promotion(bestMove))
+    {
+        update_quiet_stats(pos, ss, bestMove, bonus2, depth);
+
+        // Decrease all the non-best quiet moves
+        for (int i = 0; i < quietCount; ++i)
+        {
+            thisThread->mainHistory[us][from_to(quietsSearched[i])] << -bonus2;
+            update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), to_sq(quietsSearched[i]), -bonus2);
+        }
+    }
+    else
+        captureHistory[moved_piece][to_sq(bestMove)][captured] << bonus1;
+
+    // Extra penalty for a quiet TT or main killer move in previous ply when it gets refuted
+    if (   ((ss-1)->moveCount == 1 || ((ss-1)->currentMove == (ss-1)->killers[0]))
+        && !pos.captured_piece())
+            update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -bonus1);
+
+    // Decrease all the non-best capture moves
+    for (int i = 0; i < captureCount; ++i)
+    {
+        moved_piece = pos.moved_piece(capturesSearched[i]);
+        captured = type_of(pos.piece_on(to_sq(capturesSearched[i])));
+        captureHistory[moved_piece][to_sq(capturesSearched[i])][captured] << -bonus1;
+    }
+  }
+
+
+  // update_continuation_histories() updates histories of the move pairs formed
+  // by moves at ply -1, -2, -4, and -6 with current move.
+
+  void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) {
+
+    for (int i : {1, 2, 4, 6})
+    {
+        if (ss->inCheck && i > 2)
+            break;
+        if (is_ok((ss-i)->currentMove))
+            (*(ss-i)->continuationHistory)[pc][to] << bonus;
+    }
+  }
+
+
+  // update_quiet_stats() updates move sorting heuristics
+
+  void update_quiet_stats(const Position& pos, Stack* ss, Move move, int bonus, int depth) {
+
+    if (ss->killers[0] != move)
+    {
+        ss->killers[1] = ss->killers[0];
+        ss->killers[0] = move;
+    }
+
+    Color us = pos.side_to_move();
+    Thread* thisThread = pos.this_thread();
+    thisThread->mainHistory[us][from_to(move)] << bonus;
+    update_continuation_histories(ss, pos.moved_piece(move), to_sq(move), bonus);
+
+    if (type_of(pos.moved_piece(move)) != PAWN)
+        thisThread->mainHistory[us][from_to(reverse_move(move))] << -bonus;
+
+    if (is_ok((ss-1)->currentMove))
+    {
+        Square prevSq = to_sq((ss-1)->currentMove);
+        thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] = move;
+    }
+
+    if (depth > 11 && ss->ply < MAX_LPH)
+        thisThread->lowPlyHistory[ss->ply][from_to(move)] << stat_bonus(depth - 7);
+  }
+
+  // When playing with strength handicap, choose best move among a set of RootMoves
+  // using a statistical rule dependent on 'level'. Idea by Heinz van Saanen.
+
+  Move Skill::pick_best(size_t multiPV) {
+
+    const RootMoves& rootMoves = Threads.main()->rootMoves;
+    static PRNG rng(now()); // PRNG sequence should be non-deterministic
+
+    // RootMoves are already sorted by score in descending order
+    Value topScore = rootMoves[0].score;
+    int delta = std::min(topScore - rootMoves[multiPV - 1].score, PawnValueMg);
+    int weakness = 120 - 2 * level;
+    int maxScore = -VALUE_INFINITE;
+
+    // Choose best move. For each move score we add two terms, both dependent on
+    // weakness. One is deterministic and bigger for weaker levels, and one is
+    // random. Then we choose the move with the resulting highest score.
+    for (size_t i = 0; i < multiPV; ++i)
+    {
+        // This is our magic formula
+        int push = (  weakness * int(topScore - rootMoves[i].score)
+                    + delta * (rng.rand<unsigned>() % weakness)) / 128;
+
+        if (rootMoves[i].score + push >= maxScore)
+        {
+            maxScore = rootMoves[i].score + push;
+            best = rootMoves[i].pv[0];
+        }
+    }
+
+    return best;
+  }
+
+} // namespace
+
+
+/// MainThread::check_time() is used to print debug info and, more importantly,
+/// to detect when we are out of available time and thus stop the search.
+
+void MainThread::check_time() {
+
+  if (--callsCnt > 0)
+      return;
+
+  // When using nodes, ensure checking rate is not lower than 0.1% of nodes
+  callsCnt = Limits.nodes ? std::min(1024, int(Limits.nodes / 1024)) : 1024;
+
+  static TimePoint lastInfoTime = now();
+
+  TimePoint elapsed = Time.elapsed();
+  TimePoint tick = Limits.startTime + elapsed;
+
+  if (tick - lastInfoTime >= 1000)
+  {
+      lastInfoTime = tick;
+      dbg_print();
+  }
+
+  // We should not stop pondering until told so by the GUI
+  if (ponder)
+      return;
+
+  if (   (Limits.use_time_management() && (elapsed > Time.maximum() - 10 || stopOnPonderhit))
+      || (Limits.movetime && elapsed >= Limits.movetime)
+      || (Limits.nodes && Threads.nodes_searched() >= (uint64_t)Limits.nodes))
+      Threads.stop = true;
+}
+
+
+/// UCI::pv() formats PV information according to the UCI protocol. UCI requires
+/// that all (if any) unsearched PV lines are sent using a previous search score.
+
+string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) {
+
+  std::stringstream ss;
+  TimePoint elapsed = Time.elapsed() + 1;
+  const RootMoves& rootMoves = pos.this_thread()->rootMoves;
+  size_t pvIdx = pos.this_thread()->pvIdx;
+  size_t multiPV = std::min((size_t)Options["MultiPV"], rootMoves.size());
+  uint64_t nodesSearched = Threads.nodes_searched();
+  uint64_t tbHits = Threads.tb_hits() + (TB::RootInTB ? rootMoves.size() : 0);
+
+  for (size_t i = 0; i < multiPV; ++i)
+  {
+      bool updated = rootMoves[i].score != -VALUE_INFINITE;
+
+      if (depth == 1 && !updated && i > 0)
+          continue;
+
+      Depth d = updated ? depth : std::max(1, depth - 1);
+      Value v = updated ? rootMoves[i].score : rootMoves[i].previousScore;
+
+      if (v == -VALUE_INFINITE)
+          v = VALUE_ZERO;
+
+      bool tb = TB::RootInTB && abs(v) < VALUE_MATE_IN_MAX_PLY;
+      v = tb ? rootMoves[i].tbScore : v;
+
+      if (ss.rdbuf()->in_avail()) // Not at first line
+          ss << "\n";
+
+      ss << "info"
+         << " depth "    << d
+         << " seldepth " << rootMoves[i].selDepth
+         << " multipv "  << i + 1
+         << " score "    << UCI::value(v);
+
+      if (Options["UCI_ShowWDL"])
+          ss << UCI::wdl(v, pos.game_ply());
+
+      if (!tb && i == pvIdx)
+          ss << (v >= beta ? " lowerbound" : v <= alpha ? " upperbound" : "");
+
+      ss << " nodes "    << nodesSearched
+         << " nps "      << nodesSearched * 1000 / elapsed;
+
+      if (elapsed > 1000) // Earlier makes little sense
+          ss << " hashfull " << TT.hashfull();
+
+      ss << " tbhits "   << tbHits
+         << " time "     << elapsed
+         << " pv";
+
+      for (Move m : rootMoves[i].pv)
+          ss << " " << UCI::move(m, pos.is_chess960());
+  }
+
+  return ss.str();
+}
+
+
+/// RootMove::extract_ponder_from_tt() is called in case we have no ponder move
+/// before exiting the search, for instance, in case we stop the search during a
+/// fail high at root. We try hard to have a ponder move to return to the GUI,
+/// otherwise in case of 'ponder on' we have nothing to think on.
+
+bool RootMove::extract_ponder_from_tt(Position& pos) {
+
+    StateInfo st;
+    bool ttHit;
+
+    assert(pv.size() == 1);
+
+    if (pv[0] == MOVE_NONE)
+        return false;
+
+    pos.do_move(pv[0], st);
+    TTEntry* tte = TT.probe(pos.key(), ttHit);
+
+    if (ttHit)
+    {
+        Move m = tte->move(); // Local copy to be SMP safe
+        if (MoveList<LEGAL>(pos).contains(m))
+            pv.push_back(m);
+    }
+
+    pos.undo_move(pv[0]);
+    return pv.size() > 1;
+}
+
+void Tablebases::rank_root_moves(Position& pos, Search::RootMoves& rootMoves) {
+
+    RootInTB = false;
+    UseRule50 = bool(Options["Syzygy50MoveRule"]);
+    ProbeDepth = int(Options["SyzygyProbeDepth"]);
+    Cardinality = int(Options["SyzygyProbeLimit"]);
+    bool dtz_available = true;
+
+    // Tables with fewer pieces than SyzygyProbeLimit are searched with
+    // ProbeDepth == DEPTH_ZERO
+    if (Cardinality > MaxCardinality)
+    {
+        Cardinality = MaxCardinality;
+        ProbeDepth = 0;
+    }
+
+    if (Cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING))
+    {
+        // Rank moves using DTZ tables
+        RootInTB = root_probe(pos, rootMoves);
+
+        if (!RootInTB)
+        {
+            // DTZ tables are missing; try to rank moves using WDL tables
+            dtz_available = false;
+            RootInTB = root_probe_wdl(pos, rootMoves);
+        }
+    }
+
+    if (RootInTB)
+    {
+        // Sort moves according to TB rank
+        std::stable_sort(rootMoves.begin(), rootMoves.end(),
+                  [](const RootMove &a, const RootMove &b) { return a.tbRank > b.tbRank; } );
+
+        // Probe during search only if DTZ is not available and we are winning
+        if (dtz_available || rootMoves[0].tbScore <= VALUE_DRAW)
+            Cardinality = 0;
+    }
+    else
+    {
+        // Clean up if root_probe() and root_probe_wdl() have failed
+        for (auto& m : rootMoves)
+            m.tbRank = 0;
+    }
+}
diff --git a/src/search.h b/src/search.h
new file mode 100644 (file)
index 0000000..7908518
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef SEARCH_H_INCLUDED
+#define SEARCH_H_INCLUDED
+
+#include <vector>
+
+#include "misc.h"
+#include "movepick.h"
+#include "types.h"
+
+class Position;
+
+namespace Search {
+
+/// Threshold used for countermoves based pruning
+constexpr int CounterMovePruneThreshold = 0;
+
+
+/// Stack struct keeps track of the information we need to remember from nodes
+/// shallower and deeper in the tree during the search. Each search thread has
+/// its own array of Stack objects, indexed by the current ply.
+
+struct Stack {
+  Move* pv;
+  PieceToHistory* continuationHistory;
+  int ply;
+  Move currentMove;
+  Move excludedMove;
+  Move killers[2];
+  Value staticEval;
+  int statScore;
+  int moveCount;
+  bool inCheck;
+  bool ttPv;
+};
+
+
+/// RootMove struct is used for moves at the root of the tree. For each root move
+/// we store a score and a PV (really a refutation in the case of moves which
+/// fail low). Score is normally set at -VALUE_INFINITE for all non-pv moves.
+
+struct RootMove {
+
+  explicit RootMove(Move m) : pv(1, m) {}
+  bool extract_ponder_from_tt(Position& pos);
+  bool operator==(const Move& m) const { return pv[0] == m; }
+  bool operator<(const RootMove& m) const { // Sort in descending order
+    return m.score != score ? m.score < score
+                            : m.previousScore < previousScore;
+  }
+
+  Value score = -VALUE_INFINITE;
+  Value previousScore = -VALUE_INFINITE;
+  int selDepth = 0;
+  int tbRank = 0;
+  int bestMoveCount = 0;
+  Value tbScore;
+  std::vector<Move> pv;
+};
+
+typedef std::vector<RootMove> RootMoves;
+
+
+/// LimitsType struct stores information sent by GUI about available time to
+/// search the current move, maximum depth/time, or if we are in analysis mode.
+
+struct LimitsType {
+
+  LimitsType() { // Init explicitly due to broken value-initialization of non POD in MSVC
+    time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = npmsec = movetime = TimePoint(0);
+    movestogo = depth = mate = perft = infinite = 0;
+    nodes = 0;
+  }
+
+  bool use_time_management() const {
+    return time[WHITE] || time[BLACK];
+  }
+
+  std::vector<Move> searchmoves;
+  TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime;
+  int movestogo, depth, mate, perft, infinite;
+  int64_t nodes;
+};
+
+extern LimitsType Limits;
+
+void init();
+void clear();
+
+} // namespace Search
+
+#endif // #ifndef SEARCH_H_INCLUDED
diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp
new file mode 100644 (file)
index 0000000..4d682f1
--- /dev/null
@@ -0,0 +1,1612 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <algorithm>
+#include <atomic>
+#include <cstdint>
+#include <cstring>   // For std::memset and std::memcpy
+#include <deque>
+#include <fstream>
+#include <iostream>
+#include <list>
+#include <sstream>
+#include <type_traits>
+#include <mutex>
+
+#include "../bitboard.h"
+#include "../movegen.h"
+#include "../position.h"
+#include "../search.h"
+#include "../types.h"
+#include "../uci.h"
+
+#include "tbprobe.h"
+
+#ifndef _WIN32
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#else
+#define WIN32_LEAN_AND_MEAN
+#ifndef NOMINMAX
+#  define NOMINMAX // Disable macros min() and max()
+#endif
+#include <windows.h>
+#endif
+
+using namespace Tablebases;
+
+int Tablebases::MaxCardinality;
+
+namespace {
+
+constexpr int TBPIECES = 7; // Max number of supported pieces
+
+enum { BigEndian, LittleEndian };
+enum TBType { WDL, DTZ }; // Used as template parameter
+
+// Each table has a set of flags: all of them refer to DTZ tables, the last one to WDL tables
+enum TBFlag { STM = 1, Mapped = 2, WinPlies = 4, LossPlies = 8, Wide = 16, SingleValue = 128 };
+
+inline WDLScore operator-(WDLScore d) { return WDLScore(-int(d)); }
+inline Square operator^(Square s, int i) { return Square(int(s) ^ i); }
+
+const std::string PieceToChar = " 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<typename T, int Half = sizeof(T) / 2, int End = sizeof(T) - 1>
+inline void swap_endian(T& x)
+{
+    static_assert(std::is_unsigned<T>::value, "Argument of swap_endian not unsigned");
+
+    uint8_t tmp, *c = (uint8_t*)&x;
+    for (int i = 0; i < Half; ++i)
+        tmp = c[i], c[i] = c[End - i], c[End - i] = tmp;
+}
+template<> inline void swap_endian<uint8_t>(uint8_t&) {}
+
+template<typename T, int LE> T number(void* addr)
+{
+    static const union { uint32_t i; char c[4]; } Le = { 0x01020304 };
+    static const bool IsLittleEndian = (Le.c[0] == 4);
+
+    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 <typename T> int sign_of(T val) {
+    return (T(0) < val) - (val < T(0));
+}
+
+// Numbers in little endian used by sparseIndex[] to point into blockLength[]
+struct SparseEntry {
+    char block[4];   // Number of block
+    char offset[2];  // Offset within the block
+};
+
+static_assert(sizeof(SparseEntry) == 6, "SparseEntry must be 6 bytes");
+
+typedef uint16_t Sym; // Huffman symbol
+
+struct LR {
+    enum Side { Left, Right };
+
+    uint8_t lr[3]; // The first 12 bits is the left-hand symbol, the second 12
+                   // bits is the right-hand symbol. If symbol has length 1,
+                   // then the left-hand symbol is the stored value.
+    template<Side S>
+    Sym get() {
+        return S == Left  ? ((lr[1] & 0xF) << 8) | lr[0] :
+               S == Right ?  (lr[2] << 4) | (lr[1] >> 4) : (assert(false), Sym(-1));
+    }
+};
+
+static_assert(sizeof(LR) == 3, "LR tree entry must be 3 bytes");
+
+// Tablebases data layout is structured as following:
+//
+//  TBFile:   memory maps/unmaps the physical .rtbw and .rtbz files
+//  TBTable:  one object for each file with corresponding indexing information
+//  TBTables: has ownership of TBTable objects, keeping a list and a hash
+
+// class TBFile memory maps/unmaps the single .rtbw and .rtbz files. Files are
+// memory mapped for best performance. Files are mapped at first access: at init
+// time only existence of the file is checked.
+class TBFile : public std::ifstream {
+
+    std::string fname;
+
+public:
+    // Look for and open the file among the Paths directories where the .rtbw
+    // and .rtbz files can be found. Multiple directories are separated by ";"
+    // on Windows and by ":" on Unix-based operating systems.
+    //
+    // Example:
+    // C:\tb\wdl345;C:\tb\wdl6;D:\tb\dtz345;D:\tb\dtz6
+    static std::string Paths;
+
+    TBFile(const std::string& f) {
+
+#ifndef _WIN32
+        constexpr char SepChar = ':';
+#else
+        constexpr char SepChar = ';';
+#endif
+        std::stringstream ss(Paths);
+        std::string path;
+
+        while (std::getline(ss, path, SepChar)) {
+            fname = path + "/" + f;
+            std::ifstream::open(fname);
+            if (is_open())
+                return;
+        }
+    }
+
+    // Memory map the file and check it. File should be already open and will be
+    // closed after mapping.
+    uint8_t* map(void** baseAddress, uint64_t* mapping, TBType type) {
+
+        assert(is_open());
+
+        close(); // Need to re-open to get native file descriptor
+
+#ifndef _WIN32
+        struct stat statbuf;
+        int fd = ::open(fname.c_str(), O_RDONLY);
+
+        if (fd == -1)
+            return *baseAddress = nullptr, nullptr;
+
+        fstat(fd, &statbuf);
+
+        if (statbuf.st_size % 64 != 16)
+        {
+            std::cerr << "Corrupt tablebase file " << fname << std::endl;
+            exit(EXIT_FAILURE);
+        }
+
+        *mapping = statbuf.st_size;
+        *baseAddress = mmap(nullptr, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0);
+#if defined(MADV_RANDOM)
+        madvise(*baseAddress, statbuf.st_size, MADV_RANDOM);
+#endif
+        ::close(fd);
+
+        if (*baseAddress == MAP_FAILED)
+        {
+            std::cerr << "Could not mmap() " << fname << std::endl;
+            exit(EXIT_FAILURE);
+        }
+#else
+        // Note FILE_FLAG_RANDOM_ACCESS is only a hint to Windows and as such may get ignored.
+        HANDLE fd = CreateFile(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
+                               OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, nullptr);
+
+        if (fd == INVALID_HANDLE_VALUE)
+            return *baseAddress = nullptr, nullptr;
+
+        DWORD size_high;
+        DWORD size_low = GetFileSize(fd, &size_high);
+
+        if (size_low % 64 != 16)
+        {
+            std::cerr << "Corrupt tablebase file " << fname << std::endl;
+            exit(EXIT_FAILURE);
+        }
+
+        HANDLE mmap = CreateFileMapping(fd, nullptr, PAGE_READONLY, size_high, size_low, nullptr);
+        CloseHandle(fd);
+
+        if (!mmap)
+        {
+            std::cerr << "CreateFileMapping() failed" << std::endl;
+            exit(EXIT_FAILURE);
+        }
+
+        *mapping = (uint64_t)mmap;
+        *baseAddress = MapViewOfFile(mmap, FILE_MAP_READ, 0, 0, 0);
+
+        if (!*baseAddress)
+        {
+            std::cerr << "MapViewOfFile() failed, name = " << fname
+                      << ", error = " << GetLastError() << std::endl;
+            exit(EXIT_FAILURE);
+        }
+#endif
+        uint8_t* data = (uint8_t*)*baseAddress;
+
+        constexpr uint8_t Magics[][4] = { { 0xD7, 0x66, 0x0C, 0xA5 },
+                                          { 0x71, 0xE8, 0x23, 0x5D } };
+
+        if (memcmp(data, Magics[type == WDL], 4))
+        {
+            std::cerr << "Corrupted table in file " << fname << std::endl;
+            unmap(*baseAddress, *mapping);
+            return *baseAddress = nullptr, nullptr;
+        }
+
+        return data + 4; // Skip Magics's header
+    }
+
+    static void unmap(void* baseAddress, uint64_t mapping) {
+
+#ifndef _WIN32
+        munmap(baseAddress, mapping);
+#else
+        UnmapViewOfFile(baseAddress);
+        CloseHandle((HANDLE)mapping);
+#endif
+    }
+};
+
+std::string TBFile::Paths;
+
+// struct PairsData contains low level indexing information to access TB data.
+// There are 8, 4 or 2 PairsData records for each TBTable, according to type of
+// table and if positions have pawns or not. It is populated at first access.
+struct PairsData {
+    uint8_t flags;                 // Table flags, see enum TBFlag
+    uint8_t maxSymLen;             // Maximum length in bits of the Huffman symbols
+    uint8_t minSymLen;             // Minimum length in bits of the Huffman symbols
+    uint32_t blocksNum;            // Number of blocks in the TB file
+    size_t sizeofBlock;            // Block size in bytes
+    size_t span;                   // About every span values there is a SparseIndex[] entry
+    Sym* lowestSym;                // lowestSym[l] is the symbol of length l with the lowest value
+    LR* btree;                     // btree[sym] stores the left and right symbols that expand sym
+    uint16_t* blockLength;         // Number of stored positions (minus one) for each block: 1..65536
+    uint32_t blockLengthSize;      // Size of blockLength[] table: padded so it's bigger than blocksNum
+    SparseEntry* sparseIndex;      // Partial indices into blockLength[]
+    size_t sparseIndexSize;        // Size of SparseIndex[] table
+    uint8_t* data;                 // Start of Huffman compressed data
+    std::vector<uint64_t> base64;  // base64[l - min_sym_len] is the 64bit-padded lowest symbol of length l
+    std::vector<uint8_t> symlen;   // Number of values (-1) represented by a given Huffman symbol: 1..256
+    Piece pieces[TBPIECES];        // Position pieces: the order of pieces defines the groups
+    uint64_t groupIdx[TBPIECES+1]; // Start index used for the encoding of the group's pieces
+    int groupLen[TBPIECES+1];      // Number of pieces in a given group: KRKN -> (3, 1)
+    uint16_t map_idx[4];           // WDLWin, WDLLoss, WDLCursedWin, WDLBlessedLoss (used in DTZ)
+};
+
+// struct TBTable contains indexing information to access the corresponding TBFile.
+// 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<TBType Type>
+struct TBTable {
+    typedef typename std::conditional<Type == WDL, WDLScore, int>::type Ret;
+
+    static constexpr int Sides = Type == WDL ? 2 : 1;
+
+    std::atomic_bool ready;
+    void* baseAddress;
+    uint8_t* map;
+    uint64_t mapping;
+    Key key;
+    Key key2;
+    int pieceCount;
+    bool hasPawns;
+    bool hasUniquePieces;
+    uint8_t pawnCount[2]; // [Lead color / other color]
+    PairsData items[Sides][4]; // [wtm / btm][FILE_A..FILE_D or 0]
+
+    PairsData* get(int stm, int f) {
+        return &items[stm % Sides][hasPawns ? f : 0];
+    }
+
+    TBTable() : ready(false), baseAddress(nullptr) {}
+    explicit TBTable(const std::string& code);
+    explicit TBTable(const TBTable<WDL>& wdl);
+
+    ~TBTable() {
+        if (baseAddress)
+            TBFile::unmap(baseAddress, mapping);
+    }
+};
+
+template<>
+TBTable<WDL>::TBTable(const std::string& code) : TBTable() {
+
+    StateInfo st;
+    Position pos;
+
+    key = pos.set(code, WHITE, &st).material_key();
+    pieceCount = pos.count<ALL_PIECES>();
+    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<PAWN>(BLACK)
+            || (   pos.count<PAWN>(WHITE)
+                && pos.count<PAWN>(BLACK) >= pos.count<PAWN>(WHITE));
+
+    pawnCount[0] = pos.count<PAWN>(c ? WHITE : BLACK);
+    pawnCount[1] = pos.count<PAWN>(c ? BLACK : WHITE);
+
+    key2 = pos.set(code, BLACK, &st).material_key();
+}
+
+template<>
+TBTable<DTZ>::TBTable(const TBTable<WDL>& wdl) : TBTable() {
+
+    // Use the corresponding WDL table to avoid recalculating all from scratch
+    key = wdl.key;
+    key2 = wdl.key2;
+    pieceCount = wdl.pieceCount;
+    hasPawns = wdl.hasPawns;
+    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>* wdl;
+        TBTable<DTZ>* dtz;
+
+        template <TBType Type>
+        TBTable<Type>* get() const {
+            return (TBTable<Type>*)(Type == WDL ? (void*)wdl : (void*)dtz);
+        }
+    };
+
+    static constexpr int Size = 1 << 12; // 4K table, indexed by key's 12 lsb
+    static constexpr int Overflow = 1;  // Number of elements allowed to map to the last bucket
+
+    Entry hashTable[Size + Overflow];
+
+    std::deque<TBTable<WDL>> wdlTable;
+    std::deque<TBTable<DTZ>> dtzTable;
+
+    void insert(Key key, TBTable<WDL>* wdl, TBTable<DTZ>* dtz) {
+        uint32_t homeBucket = (uint32_t)key & (Size - 1);
+        Entry entry{ 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<WDL>()) {
+                hashTable[bucket] = entry;
+                return;
+            }
+
+            // Robin Hood hashing: If we've probed for longer than this element,
+            // insert here and search for a new spot for the other element instead.
+            uint32_t otherHomeBucket = (uint32_t)otherKey & (Size - 1);
+            if (otherHomeBucket > homeBucket) {
+                std::swap(entry, hashTable[bucket]);
+                key = otherKey;
+                homeBucket = otherHomeBucket;
+            }
+        }
+        std::cerr << "TB hash table size too low!" << std::endl;
+        exit(EXIT_FAILURE);
+    }
+
+public:
+    template<TBType Type>
+    TBTable<Type>* get(Key key) {
+        for (const Entry* entry = &hashTable[(uint32_t)key & (Size - 1)]; ; ++entry) {
+            if (entry->key == key || !entry->get<Type>())
+                return entry->get<Type>();
+        }
+    }
+
+    void clear() {
+        memset(hashTable, 0, sizeof(hashTable));
+        wdlTable.clear();
+        dtzTable.clear();
+    }
+    size_t size() const { return wdlTable.size(); }
+    void add(const std::vector<PieceType>& pieces);
+};
+
+TBTables TBTables;
+
+// If the corresponding file exists two new objects TBTable<WDL> and TBTable<DTZ>
+// are created and added to the lists and hash table. Called at init time.
+void TBTables::add(const std::vector<PieceType>& 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<uint32_t, LittleEndian>(&d->sparseIndex[k].block);
+    int offset     = number<uint16_t, LittleEndian>(&d->sparseIndex[k].offset);
+
+    // Now compute the difference idx - I(k). From definition of k we know that
+    //
+    //       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<uint64_t, BigEndian>(ptr); ptr += 2;
+    int buf64Size = 64;
+    Sym sym;
+
+    while (true) {
+        int len = 0; // This is the symbol length - d->min_sym_len
+
+        // 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<Sym, LittleEndian>(&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<uint32_t, BigEndian>(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<LR::Left>();
+
+        // If a symbol contains 36 sub-symbols (d->symlen[sym] + 1 = 36) and
+        // expands in a pair (d->symlen[left] = 23, d->symlen[right] = 11), then
+        // we know that, for instance the ten-th value (offset = 10) will be on
+        // 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<LR::Right>();
+        }
+    }
+
+    return d->btree[sym].get<LR::Left>();
+}
+
+bool check_dtz_stm(TBTable<WDL>*, int, File) { return true; }
+
+bool check_dtz_stm(TBTable<DTZ>* entry, int stm, File f) {
+
+    auto flags = entry->get(stm, f)->flags;
+    return   (flags & TBFlag::STM) == stm
+          || ((entry->key == entry->key2) && !entry->hasPawns);
+}
+
+// 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<WDL>*, File, int value, WDLScore) { return WDLScore(value - 2); }
+
+int map_score(TBTable<DTZ>* entry, File f, int value, WDLScore wdl) {
+
+    constexpr int WDLMap[] = { 1, 3, 0, 2, 0 };
+
+    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<typename T, typename Ret = typename T::Ret>
+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 withouth 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 (inlcuded 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 occurence 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 remainig 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 withouth 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<typename T>
+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 -> remainig 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 // Remainig 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 childern 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<bool>& visited) {
+
+    visited[s] = true; // We can set it now because tree is acyclic
+    Sym sr = d->btree[s].get<LR::Right>();
+
+    if (sr == 0xFFF)
+        return 0;
+
+    Sym sl = d->btree[s].get<LR::Left>();
+
+    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<uint8_t, LittleEndian>(data++);
+    d->blocksNum = number<uint32_t, LittleEndian>(data); data += sizeof(uint32_t);
+    d->blockLengthSize = d->blocksNum + padding; // Padded to ensure SparseIndex[]
+                                                 // does not point out of range.
+    d->maxSymLen = *data++;
+    d->minSymLen = *data++;
+    d->lowestSym = (Sym*)data;
+    d->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 http://www.eecs.harvard.edu/~michaelm/E210/huffman.pdf
+    for (int i = d->base64.size() - 2; i >= 0; --i) {
+        d->base64[i] = (d->base64[i + 1] + number<Sym, LittleEndian>(&d->lowestSym[i])
+                                         - number<Sym, LittleEndian>(&d->lowestSym[i + 1])) / 2;
+
+        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<uint16_t, LittleEndian>(data)); data += sizeof(uint16_t);
+    d->btree = (LR*)data;
+
+    // The compression scheme used is "Recursive Pairing", that replaces the most
+    // frequent adjacent pair of symbols in the source message by a new symbol,
+    // reevaluating the frequencies of all of the symbol pairs with respect to
+    // the extended alphabet, and then repeating the process.
+    // See http://www.larsson.dogma.net/dcc99.pdf
+    std::vector<bool> 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<WDL>&, uint8_t* data, File) { return data; }
+
+uint8_t* set_dtz_map(TBTable<DTZ>& 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<uint16_t, LittleEndian>(data) + 2;
+                }
+            }
+            else {
+                for (int i = 0; i < 4; ++i) {
+                    e.get(0, f)->map_idx[i] = (uint16_t)(data - e.map + 1);
+                    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<typename T>
+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<TBType Type>
+void* mapped(TBTable<Type>& 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::unique_lock<std::mutex> 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<TBType Type, typename Ret = typename TBTable<Type>::Ret>
+Ret probe_table(const Position& pos, ProbeState* result, WDLScore wdl = WDLDraw) {
+
+    if (pos.count<ALL_PIECES>() == 2) // KvK
+        return Ret(WDLDraw);
+
+    TBTable<Type>* entry = TBTables.get<Type>(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<bool CheckZeroingMoves>
+WDLScore search(Position& pos, ProbeState* result) {
+
+    WDLScore value, bestValue = WDLLoss;
+    StateInfo st;
+
+    auto moveList = MoveList<LEGAL>(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<false>(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<WDL>(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 == "<empty>")
+        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<Square> diagonal;
+    code = 0;
+    for (Square s = SQ_A1; s <= SQ_D4; ++s)
+        if (off_A1H8(s) < 0 && file_of(s) <= FILE_D)
+            MapA1D1D4[s] = code++;
+
+        else if (!off_A1H8(s) && file_of(s) <= FILE_D)
+            diagonal.push_back(s);
+
+    // Diagonal squares are encoded as last ones
+    for (auto s : diagonal)
+        MapA1D1D4[s] = code++;
+
+    // MapKK[] encodes all the 461 possible legal positions of two kings where
+    // the first is in the a1-d1-d4 triangle. If the first king is on the a1-d4
+    // diagonal, the other one shall not to be above the a1-h8 diagonal.
+    std::vector<std::pair<int, Square>> bothOnDiagonal;
+    code = 0;
+    for (int idx = 0; idx < 10; idx++)
+        for (Square s1 = SQ_A1; s1 <= SQ_D4; ++s1)
+            if (MapA1D1D4[s1] == idx && (idx || s1 == SQ_B1)) // SQ_B1 is mapped to 0
+            {
+                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 Coefficents using Pascal rule. There
+    // are Binomial[k][n] ways to choose k elements from a set of n elements.
+    Binomial[0][0] = 1;
+
+    for (int n = 1; n < 64; n++) // Squares
+        for (int k = 0; k < 6 && k <= n; ++k) // Pieces
+            Binomial[k][n] =  (k > 0 ? Binomial[k - 1][n - 1] : 0)
+                            + (k < n ? Binomial[k    ][n - 1] : 0);
+
+    // MapPawns[s] encodes squares a2-h7 to 0..47. This is the number of possible
+    // available squares when the leading one is in 's'. Moreover the pawn with
+    // highest MapPawns[] is the leading pawn, the one nearest the edge and,
+    // among pawns with same file, the one with lowest rank.
+    int availableSquares = 47; // Available squares when lead pawn is in a2
+
+    // Init the tables for the encoding of leading pawns group: with 7-men TB we
+    // can have up to 5 leading pawns (KPPPPPK).
+    for (int leadPawnsCnt = 1; leadPawnsCnt <= 5; ++leadPawnsCnt)
+        for (File f = FILE_A; f <= FILE_D; ++f)
+        {
+            // Restart the index at every file because TB table is splitted
+            // by file, so we can reuse the same index for different files.
+            int idx = 0;
+
+            // Sum all possible combinations for a given file, starting with
+            // the leading pawn on rank 2 and increasing the rank.
+            for (Rank r = RANK_2; r <= RANK_7; ++r)
+            {
+                Square sq = make_square(f, r);
+
+                // Compute MapPawns[] at first pass.
+                // If sq is the leading pawn square, any other pawn cannot be
+                // below or more toward the edge of sq. There are 47 available
+                // squares when sq = a2 and reduced by 2 for any rank increase
+                // due to mirroring: sq == a3 -> no a2, h2, so MapPawns[a3] = 45
+                if (leadPawnsCnt == 1)
+                {
+                    MapPawns[sq] = availableSquares--;
+                    MapPawns[flip_file(sq)] = availableSquares--;
+                }
+                LeadPawnIdx[leadPawnsCnt][sq] = idx;
+                idx += Binomial[leadPawnsCnt - 1][MapPawns[sq]];
+            }
+            // After a file is traversed, store the cumulated per-file index
+            LeadPawnsSize[leadPawnsCnt][f] = idx;
+        }
+
+    // Add entries in TB tables if the corresponding ".rtbw" file exists
+    for (PieceType p1 = PAWN; p1 < 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<false>(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-movecounter <= 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<true>(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<DTZ>(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<LEGAL>(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<false>(pos, result))
+                      : -probe_dtz(pos, result);
+
+        // If the move mates, force minDTZ to 1
+        if (dtz == 1 && pos.checkers() && MoveList<LEGAL>(pos).size() == 0)
+            minDTZ = 1;
+
+        // Convert result from 1-ply search. Zeroing moves are already accounted
+        // by dtz_before_zeroing() that returns the DTZ of the previous move.
+        if (!zeroing)
+            dtz += sign_of(dtz);
+
+        // Skip the draws and if we are winning only pick positive dtz
+        if (dtz < minDTZ && sign_of(dtz) == sign_of(wdl))
+            minDTZ = dtz;
+
+        pos.undo_move(move);
+
+        if (*result == FAIL)
+            return 0;
+    }
+
+    // When there are no legal moves, the position is mate: we return -1
+    return minDTZ == 0xFFFF ? -1 : minDTZ;
+}
+
+
+// Use the DTZ tables to rank root moves.
+//
+// A return value false indicates that not all probes were successful.
+bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) {
+
+    ProbeState result;
+    StateInfo st;
+
+    // Obtain 50-move counter for the root position
+    int cnt50 = pos.rule50_count();
+
+    // Check whether a position was repeated since the last zeroing move.
+    bool rep = pos.has_repeated();
+
+    int dtz, bound = Options["Syzygy50MoveRule"] ? 900 : 1;
+
+    // Probe and rank each move
+    for (auto& m : rootMoves)
+    {
+        pos.do_move(m.pv[0], st);
+
+        // Calculate dtz for the current move counting from the root position
+        if (pos.rule50_count() == 0)
+        {
+            // In case of a zeroing move, dtz is one of -101/-1/0/1/101
+            WDLScore wdl = -probe_wdl(pos, &result);
+            dtz = dtz_before_zeroing(wdl);
+        }
+        else
+        {
+            // 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<LEGAL>(pos).size() == 0)
+            dtz = 1;
+
+        pos.undo_move(m.pv[0]);
+
+        if (result == FAIL)
+            return false;
+
+        // Better moves are ranked higher. Certain wins are ranked equally.
+        // Losing moves are ranked equally unless a 50-move draw is in sight.
+        int r =  dtz > 0 ? (dtz + cnt50 <= 99 && !rep ? 1000 : 1000 - (dtz + cnt50))
+               : dtz < 0 ? (-dtz * 2 + cnt50 < 100 ? -1000 : -1000 + (-dtz + cnt50))
+               : 0;
+        m.tbRank = r;
+
+        // Determine the score to be displayed for this move. Assign at least
+        // 1 cp to cursed wins and let it grow to 49 cp as the positions gets
+        // closer to a real win.
+        m.tbScore =  r >= bound ? VALUE_MATE - MAX_PLY - 1
+                   : r >  0     ? Value((std::max( 3, r - 800) * int(PawnValueEg)) / 200)
+                   : r == 0     ? VALUE_DRAW
+                   : r > -bound ? Value((std::min(-3, r + 800) * int(PawnValueEg)) / 200)
+                   :             -VALUE_MATE + MAX_PLY + 1;
+    }
+
+    return true;
+}
+
+
+// Use the WDL tables to rank root moves.
+// This is a fallback for the case that some or all DTZ tables are missing.
+//
+// A return value false indicates that not all probes were successful.
+bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) {
+
+    static const int WDL_to_rank[] = { -1000, -899, 0, 899, 1000 };
+
+    ProbeState result;
+    StateInfo st;
+
+    bool rule50 = Options["Syzygy50MoveRule"];
+
+    // Probe and rank each move
+    for (auto& m : rootMoves)
+    {
+        pos.do_move(m.pv[0], st);
+
+        WDLScore 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;
+}
diff --git a/src/syzygy/tbprobe.h b/src/syzygy/tbprobe.h
new file mode 100644 (file)
index 0000000..b998989
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef TBPROBE_H
+#define TBPROBE_H
+
+#include <ostream>
+
+#include "../search.h"
+
+namespace Tablebases {
+
+enum WDLScore {
+    WDLLoss        = -2, // Loss
+    WDLBlessedLoss = -1, // Loss, but draw under 50-move rule
+    WDLDraw        =  0, // Draw
+    WDLCursedWin   =  1, // Win, but draw under 50-move rule
+    WDLWin         =  2, // Win
+
+    WDLScoreNone  = -1000
+};
+
+// Possible states after a probing operation
+enum ProbeState {
+    FAIL              =  0, // Probe failed (missing file table)
+    OK                =  1, // Probe succesful
+    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;
+}
+
+}
+
+#endif
diff --git a/src/thread.cpp b/src/thread.cpp
new file mode 100644 (file)
index 0000000..b46fce5
--- /dev/null
@@ -0,0 +1,260 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <cassert>
+
+#include <algorithm> // For std::count
+#include "movegen.h"
+#include "search.h"
+#include "thread.h"
+#include "uci.h"
+#include "syzygy/tbprobe.h"
+#include "tt.h"
+
+ThreadPool Threads; // Global object
+
+
+/// Thread constructor launches the thread and waits until it goes to sleep
+/// in idle_loop(). Note that 'searching' and 'exit' should be already set.
+
+Thread::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this) {
+
+  wait_for_search_finished();
+}
+
+
+/// Thread destructor wakes up the thread in idle_loop() and waits
+/// for its termination. Thread should be already waiting.
+
+Thread::~Thread() {
+
+  assert(!searching);
+
+  exit = true;
+  start_searching();
+  stdThread.join();
+}
+
+
+/// Thread::clear() reset histories, usually before a new game
+
+void Thread::clear() {
+
+  counterMoves.fill(MOVE_NONE);
+  mainHistory.fill(0);
+  lowPlyHistory.fill(0);
+  captureHistory.fill(0);
+
+  for (bool inCheck : { false, true })
+      for (StatsType c : { NoCaptures, Captures })
+      {
+          for (auto& to : continuationHistory[inCheck][c])
+                for (auto& h : to)
+                      h->fill(0);
+          continuationHistory[inCheck][c][NO_PIECE][0]->fill(Search::CounterMovePruneThreshold - 1);
+      }
+}
+
+
+/// Thread::start_searching() wakes up the thread that will start the search
+
+void Thread::start_searching() {
+
+  std::lock_guard<std::mutex> lk(mutex);
+  searching = true;
+  cv.notify_one(); // Wake up the thread in idle_loop()
+}
+
+
+/// Thread::wait_for_search_finished() blocks on the condition variable
+/// until the thread has finished searching.
+
+void Thread::wait_for_search_finished() {
+
+  std::unique_lock<std::mutex> lk(mutex);
+  cv.wait(lk, [&]{ return !searching; });
+}
+
+
+/// Thread::idle_loop() is where the thread is parked, blocked on the
+/// condition variable, when it has no work to do.
+
+void Thread::idle_loop() {
+
+  // If OS already scheduled us on a different group than 0 then don't overwrite
+  // the choice, eventually we are one of many one-threaded processes running on
+  // some Windows NUMA hardware, for instance in fishtest. To make it simple,
+  // just check if running threads are below a threshold, in this case all this
+  // NUMA machinery is not needed.
+  if (Options["Threads"] > 8)
+      WinProcGroup::bindThisThread(idx);
+
+  while (true)
+  {
+      std::unique_lock<std::mutex> lk(mutex);
+      searching = false;
+      cv.notify_one(); // Wake up anyone waiting for search finished
+      cv.wait(lk, [&]{ return searching; });
+
+      if (exit)
+          return;
+
+      lk.unlock();
+
+      search();
+  }
+}
+
+/// ThreadPool::set() creates/destroys threads to match the requested number.
+/// Created and launched threads will immediately go to sleep in idle_loop.
+/// Upon resizing, threads are recreated to allow for binding if necessary.
+
+void ThreadPool::set(size_t requested) {
+
+  if (size() > 0) { // destroy any existing thread(s)
+      main()->wait_for_search_finished();
+
+      while (size() > 0)
+          delete back(), pop_back();
+  }
+
+  if (requested > 0) { // create new thread(s)
+      push_back(new MainThread(0));
+
+      while (size() < requested)
+          push_back(new Thread(size()));
+      clear();
+
+      // Reallocate the hash with the new threadpool size
+      TT.resize(size_t(Options["Hash"]));
+
+      // Init thread number dependent search params.
+      Search::init();
+  }
+}
+
+
+/// ThreadPool::clear() sets threadPool data to initial values
+
+void ThreadPool::clear() {
+
+  for (Thread* th : *this)
+      th->clear();
+
+  main()->callsCnt = 0;
+  main()->bestPreviousScore = VALUE_INFINITE;
+  main()->previousTimeReduction = 1.0;
+}
+
+
+/// ThreadPool::start_thinking() wakes up main thread waiting in idle_loop() and
+/// returns immediately. Main thread will wake up other threads and start the search.
+
+void ThreadPool::start_thinking(Position& pos, StateListPtr& states,
+                                const Search::LimitsType& limits, bool ponderMode) {
+
+  main()->wait_for_search_finished();
+
+  main()->stopOnPonderhit = stop = false;
+  increaseDepth = true;
+  main()->ponder = ponderMode;
+  Search::Limits = limits;
+  Search::RootMoves rootMoves;
+
+  for (const auto& m : MoveList<LEGAL>(pos))
+      if (   limits.searchmoves.empty()
+          || std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m))
+          rootMoves.emplace_back(m);
+
+  if (!rootMoves.empty())
+      Tablebases::rank_root_moves(pos, rootMoves);
+
+  // After ownership transfer 'states' becomes empty, so if we stop the search
+  // and call 'go' again without setting a new position states.get() == NULL.
+  assert(states.get() || setupStates.get());
+
+  if (states.get())
+      setupStates = std::move(states); // Ownership transfer, states is now empty
+
+  // We use Position::set() to set root position across threads. But there are
+  // some StateInfo fields (previous, pliesFromNull, capturedPiece) that cannot
+  // be deduced from a fen string, so set() clears them and they are set from
+  // setupStates->back() later. The rootState is per thread, earlier states are shared
+  // since they are read-only.
+  for (Thread* th : *this)
+  {
+      th->nodes = th->tbHits = th->nmpMinPly = th->bestMoveChanges = 0;
+      th->rootDepth = th->completedDepth = 0;
+      th->rootMoves = rootMoves;
+      th->rootPos.set(pos.fen(), pos.is_chess960(), &th->rootState, th);
+      th->rootState = setupStates->back();
+  }
+
+  main()->start_searching();
+}
+
+Thread* ThreadPool::get_best_thread() const {
+
+    Thread* bestThread = front();
+    std::map<Move, int64_t> votes;
+    Value minScore = VALUE_NONE;
+
+    // Find minimum score of all threads
+    for (Thread* th: *this)
+        minScore = std::min(minScore, th->rootMoves[0].score);
+
+    // Vote according to score and depth, and select the best thread
+    for (Thread* th : *this)
+    {
+        votes[th->rootMoves[0].pv[0]] +=
+            (th->rootMoves[0].score - minScore + 14) * int(th->completedDepth);
+
+          if (abs(bestThread->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY)
+          {
+              // Make sure we pick the shortest mate / TB conversion or stave off mate the longest
+              if (th->rootMoves[0].score > bestThread->rootMoves[0].score)
+                  bestThread = th;
+          }
+          else if (   th->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY
+                   || (   th->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY
+                       && votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]]))
+              bestThread = th;
+    }
+
+    return bestThread;
+}
+
+
+/// Start non-main threads
+
+void ThreadPool::start_searching() {
+
+    for (Thread* th : *this)
+        if (th != front())
+            th->start_searching();
+}
+
+
+/// Wait for non-main threads
+
+void ThreadPool::wait_for_search_finished() const {
+
+    for (Thread* th : *this)
+        if (th != front())
+            th->wait_for_search_finished();
+}
diff --git a/src/thread.h b/src/thread.h
new file mode 100644 (file)
index 0000000..34b9901
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef THREAD_H_INCLUDED
+#define THREAD_H_INCLUDED
+
+#include <atomic>
+#include <condition_variable>
+#include <mutex>
+#include <thread>
+#include <vector>
+
+#include "material.h"
+#include "movepick.h"
+#include "pawns.h"
+#include "position.h"
+#include "search.h"
+#include "thread_win32_osx.h"
+
+
+/// 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();
+
+  Pawns::Table pawnsTable;
+  Material::Table materialTable;
+  size_t pvIdx, pvLast;
+  uint64_t ttHitAverage;
+  int selDepth, nmpMinPly;
+  Color nmpColor;
+  std::atomic<uint64_t> nodes, tbHits, bestMoveChanges;
+
+  Position rootPos;
+  StateInfo rootState;
+  Search::RootMoves rootMoves;
+  Depth rootDepth, completedDepth;
+  CounterMoveHistory counterMoves;
+  ButterflyHistory mainHistory;
+  LowPlyHistory lowPlyHistory;
+  CapturePieceToHistory captureHistory;
+  ContinuationHistory continuationHistory[2][2];
+  Score contempt;
+};
+
+
+/// MainThread is a derived class specific for main thread
+
+struct MainThread : public Thread {
+
+  using Thread::Thread;
+
+  void search() override;
+  void check_time();
+
+  double previousTimeReduction;
+  Value bestPreviousScore;
+  Value iterValue[4];
+  int callsCnt;
+  bool stopOnPonderhit;
+  std::atomic_bool ponder;
+};
+
+
+/// ThreadPool struct handles all the threads-related stuff like init, starting,
+/// parking and, most importantly, launching a thread. All the access to threads
+/// is done through this class.
+
+struct ThreadPool : public std::vector<Thread*> {
+
+  void start_thinking(Position&, StateListPtr&, const Search::LimitsType&, bool = false);
+  void clear();
+  void set(size_t);
+
+  MainThread* main()        const { return static_cast<MainThread*>(front()); }
+  uint64_t nodes_searched() const { return accumulate(&Thread::nodes); }
+  uint64_t tb_hits()        const { return accumulate(&Thread::tbHits); }
+  Thread* get_best_thread() const;
+  void start_searching();
+  void wait_for_search_finished() const;
+
+  std::atomic_bool stop, increaseDepth;
+
+private:
+  StateListPtr setupStates;
+
+  uint64_t accumulate(std::atomic<uint64_t> Thread::* member) const {
+
+    uint64_t sum = 0;
+    for (Thread* th : *this)
+        sum += (th->*member).load(std::memory_order_relaxed);
+    return sum;
+  }
+};
+
+extern ThreadPool Threads;
+
+#endif // #ifndef THREAD_H_INCLUDED
diff --git a/src/thread_win32_osx.h b/src/thread_win32_osx.h
new file mode 100644 (file)
index 0000000..75ef5d9
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef THREAD_WIN32_OSX_H_INCLUDED
+#define THREAD_WIN32_OSX_H_INCLUDED
+
+#include <thread>
+
+/// On OSX threads other than the main thread are created with a reduced stack
+/// size of 512KB by default, this is too low for deep searches, which require
+/// somewhat more than 1MB stack, so adjust it to TH_STACK_SIZE.
+/// The implementation calls pthread_create() with the stack size parameter
+/// equal to the linux 8MB default, on platforms that support it.
+
+#if defined(__APPLE__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(USE_PTHREADS)
+
+#include <pthread.h>
+
+static const size_t TH_STACK_SIZE = 8 * 1024 * 1024;
+
+template <class T, class P = std::pair<T*, void(T::*)()>>
+void* start_routine(void* ptr)
+{
+   P* p = reinterpret_cast<P*>(ptr);
+   (p->first->*(p->second))(); // Call member function pointer
+   delete p;
+   return NULL;
+}
+
+class NativeThread {
+
+   pthread_t thread;
+
+public:
+  template<class T, class P = std::pair<T*, void(T::*)()>>
+  explicit NativeThread(void(T::*fun)(), T* obj) {
+    pthread_attr_t attr_storage, *attr = &attr_storage;
+    pthread_attr_init(attr);
+    pthread_attr_setstacksize(attr, TH_STACK_SIZE);
+    pthread_create(&thread, attr, start_routine<T>, new P(obj, fun));
+  }
+  void join() { pthread_join(thread, NULL); }
+};
+
+#else // Default case: use STL classes
+
+typedef std::thread NativeThread;
+
+#endif
+
+#endif // #ifndef THREAD_WIN32_OSX_H_INCLUDED
diff --git a/src/timeman.cpp b/src/timeman.cpp
new file mode 100644 (file)
index 0000000..6d9c95e
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <algorithm>
+#include <cfloat>
+#include <cmath>
+
+#include "search.h"
+#include "timeman.h"
+#include "uci.h"
+
+TimeManagement Time; // Our global time management object
+
+
+/// TimeManagement::init() is called at the beginning of the search and calculates
+/// the bounds of time allowed for the current game ply. We currently support:
+//      1) x basetime (+ z increment)
+//      2) x moves in y seconds (+ z increment)
+
+void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
+
+  TimePoint moveOverhead    = TimePoint(Options["Move Overhead"]);
+  TimePoint slowMover       = TimePoint(Options["Slow Mover"]);
+  TimePoint npmsec          = TimePoint(Options["nodestime"]);
+
+  // optScale is a percentage of available time to use for the current move.
+  // maxScale is a multiplier applied to optimumTime.
+  double optScale, maxScale;
+
+  // If we have to play in 'nodes as time' mode, then convert from time
+  // to nodes, and use resulting values in time management formulas.
+  // WARNING: to avoid time losses, the given npmsec (nodes per millisecond)
+  // must be much lower than the real engine speed.
+  if (npmsec)
+  {
+      if (!availableNodes) // Only once at game start
+          availableNodes = npmsec * limits.time[us]; // Time is in msec
+
+      // Convert from milliseconds to nodes
+      limits.time[us] = TimePoint(availableNodes);
+      limits.inc[us] *= npmsec;
+      limits.npmsec = npmsec;
+  }
+
+  startTime = limits.startTime;
+
+  // Maximum move horizon of 50 moves
+  int mtg = limits.movestogo ? std::min(limits.movestogo, 50) : 50;
+
+  // Make sure timeLeft is > 0 since we may use it as a divisor
+  TimePoint timeLeft =  std::max(TimePoint(1),
+      limits.time[us] + limits.inc[us] * (mtg - 1) - moveOverhead * (2 + mtg));
+
+  // A user may scale time usage by setting UCI option "Slow Mover"
+  // Default is 100 and changing this value will probably lose elo.
+  timeLeft = slowMover * timeLeft / 100;
+
+  // x basetime (+ z increment)
+  // If there is a healthy increment, timeLeft can exceed actual available
+  // game time for the current move, so also cap to 20% of available game time.
+  if (limits.movestogo == 0)
+  {
+      optScale = std::min(0.008 + std::pow(ply + 3.0, 0.5) / 250.0,
+                           0.2 * limits.time[us] / double(timeLeft));
+      maxScale = std::min(7.0, 4.0 + ply / 12.0);
+  }
+
+  // x moves in y seconds (+ z increment)
+  else
+  {
+      optScale = std::min((0.8 + ply / 128.0) / mtg,
+                            0.8 * limits.time[us] / double(timeLeft));
+      maxScale = std::min(6.3, 1.5 + 0.11 * mtg);
+  }
+
+  // Never use more than 80% of the available time for this move
+  optimumTime = TimePoint(optScale * timeLeft);
+  maximumTime = TimePoint(std::min(0.8 * limits.time[us] - moveOverhead, maxScale * optimumTime));
+
+  if (Options["Ponder"])
+      optimumTime += optimumTime / 4;
+}
diff --git a/src/timeman.h b/src/timeman.h
new file mode 100644 (file)
index 0000000..5ad72b3
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef TIMEMAN_H_INCLUDED
+#define TIMEMAN_H_INCLUDED
+
+#include "misc.h"
+#include "search.h"
+#include "thread.h"
+
+/// The TimeManagement class computes the optimal time to think depending on
+/// the maximum available time, the game move number and other parameters.
+
+class TimeManagement {
+public:
+  void init(Search::LimitsType& limits, Color us, int ply);
+  TimePoint optimum() const { return optimumTime; }
+  TimePoint maximum() const { return maximumTime; }
+  TimePoint elapsed() const { return Search::Limits.npmsec ?
+                                     TimePoint(Threads.nodes_searched()) : now() - startTime; }
+
+  int64_t availableNodes; // When in 'nodes as time' mode
+
+private:
+  TimePoint startTime;
+  TimePoint optimumTime;
+  TimePoint maximumTime;
+};
+
+extern TimeManagement Time;
+
+#endif // #ifndef TIMEMAN_H_INCLUDED
diff --git a/src/tt.cpp b/src/tt.cpp
new file mode 100644 (file)
index 0000000..60a3a5f
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <cstring>   // For std::memset
+#include <iostream>
+#include <thread>
+
+#include "bitboard.h"
+#include "misc.h"
+#include "thread.h"
+#include "tt.h"
+#include "uci.h"
+
+TranspositionTable TT; // Our global transposition table
+
+/// TTEntry::save() populates the TTEntry with a new node's data, possibly
+/// overwriting an old position. Update is not atomic and can be racy.
+
+void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) {
+
+  // Preserve any existing move for the same position
+  if (m || (uint16_t)k != key16)
+      move16 = (uint16_t)m;
+
+  // Overwrite less valuable entries (cheapest checks first)
+  if (b == BOUND_EXACT
+      || (uint16_t)k != key16
+      || d - DEPTH_OFFSET > depth8 - 4)
+  {
+      assert(d > DEPTH_OFFSET);
+      assert(d < 256 + DEPTH_OFFSET);
+
+      key16     = (uint16_t)k;
+      depth8    = (uint8_t)(d - DEPTH_OFFSET);
+      genBound8 = (uint8_t)(TT.generation8 | uint8_t(pv) << 2 | b);
+      value16   = (int16_t)v;
+      eval16    = (int16_t)ev;
+  }
+}
+
+
+/// TranspositionTable::resize() sets the size of the transposition table,
+/// measured in megabytes. Transposition table consists of a power of 2 number
+/// of clusters and each cluster consists of ClusterSize number of TTEntry.
+
+void TranspositionTable::resize(size_t mbSize) {
+
+  Threads.main()->wait_for_search_finished();
+
+  aligned_ttmem_free(mem);
+
+  clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster);
+  table = static_cast<Cluster*>(aligned_ttmem_alloc(clusterCount * sizeof(Cluster), mem));
+  if (!mem)
+  {
+      std::cerr << "Failed to allocate " << mbSize
+                << "MB for transposition table." << std::endl;
+      exit(EXIT_FAILURE);
+  }
+
+  clear();
+}
+
+
+/// TranspositionTable::clear() initializes the entire transposition table to zero,
+//  in a multi-threaded way.
+
+void TranspositionTable::clear() {
+
+  std::vector<std::thread> threads;
+
+  for (size_t idx = 0; idx < Options["Threads"]; ++idx)
+  {
+      threads.emplace_back([this, idx]() {
+
+          // Thread binding gives faster search on systems with a first-touch policy
+          if (Options["Threads"] > 8)
+              WinProcGroup::bindThisThread(idx);
+
+          // Each thread will zero its part of the hash table
+          const size_t stride = size_t(clusterCount / Options["Threads"]),
+                       start  = size_t(stride * idx),
+                       len    = idx != Options["Threads"] - 1 ?
+                                stride : clusterCount - start;
+
+          std::memset(&table[start], 0, len * sizeof(Cluster));
+      });
+  }
+
+  for (std::thread& th : threads)
+      th.join();
+}
+
+
+/// TranspositionTable::probe() looks up the current position in the transposition
+/// table. It returns true and a pointer to the TTEntry if the position is found.
+/// Otherwise, it returns false and a pointer to an empty or least valuable TTEntry
+/// to be replaced later. The replace value of an entry is calculated as its depth
+/// minus 8 times its relative age. TTEntry t1 is considered more valuable than
+/// TTEntry t2 if its replace value is greater than that of t2.
+
+TTEntry* TranspositionTable::probe(const Key key, bool& found) const {
+
+  TTEntry* const tte = first_entry(key);
+  const uint16_t key16 = (uint16_t)key;  // Use the low 16 bits as key inside the cluster
+
+  for (int i = 0; i < ClusterSize; ++i)
+      if (tte[i].key16 == key16 || !tte[i].depth8)
+      {
+          tte[i].genBound8 = uint8_t(generation8 | (tte[i].genBound8 & 0x7)); // Refresh
+
+          return found = (bool)tte[i].depth8, &tte[i];
+      }
+
+  // Find an entry to be replaced according to the replacement strategy
+  TTEntry* replace = tte;
+  for (int i = 1; i < ClusterSize; ++i)
+      // Due to our packed storage format for generation and its cyclic
+      // nature we add 263 (256 is the modulus plus 7 to keep the unrelated
+      // lowest three bits from affecting the result) to calculate the entry
+      // age correctly even after generation8 overflows into the next cycle.
+      if (  replace->depth8 - ((263 + generation8 - replace->genBound8) & 0xF8)
+          >   tte[i].depth8 - ((263 + generation8 -   tte[i].genBound8) & 0xF8))
+          replace = &tte[i];
+
+  return found = false, replace;
+}
+
+
+/// TranspositionTable::hashfull() returns an approximation of the hashtable
+/// occupation during a search. The hash is x permill full, as per UCI protocol.
+
+int TranspositionTable::hashfull() const {
+
+  int cnt = 0;
+  for (int i = 0; i < 1000; ++i)
+      for (int j = 0; j < ClusterSize; ++j)
+          cnt += table[i].entry[j].depth8 && (table[i].entry[j].genBound8 & 0xF8) == generation8;
+
+  return cnt / ClusterSize;
+}
diff --git a/src/tt.h b/src/tt.h
new file mode 100644 (file)
index 0000000..fdfd676
--- /dev/null
+++ b/src/tt.h
@@ -0,0 +1,98 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef TT_H_INCLUDED
+#define TT_H_INCLUDED
+
+#include "misc.h"
+#include "types.h"
+
+/// TTEntry struct is the 10 bytes transposition table entry, defined as below:
+///
+/// key        16 bit
+/// depth       8 bit
+/// generation  5 bit
+/// pv node     1 bit
+/// bound type  2 bit
+/// move       16 bit
+/// value      16 bit
+/// eval value 16 bit
+
+struct TTEntry {
+
+  Move  move()  const { return (Move )move16; }
+  Value value() const { return (Value)value16; }
+  Value eval()  const { return (Value)eval16; }
+  Depth depth() const { return (Depth)depth8 + DEPTH_OFFSET; }
+  bool is_pv()  const { return (bool)(genBound8 & 0x4); }
+  Bound bound() const { return (Bound)(genBound8 & 0x3); }
+  void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev);
+
+private:
+  friend class TranspositionTable;
+
+  uint16_t key16;
+  uint8_t  depth8;
+  uint8_t  genBound8;
+  uint16_t move16;
+  int16_t  value16;
+  int16_t  eval16;
+};
+
+
+/// A TranspositionTable is an array of Cluster, of size clusterCount. Each
+/// cluster consists of ClusterSize number of TTEntry. Each non-empty TTEntry
+/// contains information on exactly one position. The size of a Cluster should
+/// divide the size of a cache line for best performance, as the cacheline is
+/// prefetched when possible.
+
+class TranspositionTable {
+
+  static constexpr int ClusterSize = 3;
+
+  struct Cluster {
+    TTEntry entry[ClusterSize];
+    char padding[2]; // Pad to 32 bytes
+  };
+
+  static_assert(sizeof(Cluster) == 32, "Unexpected Cluster size");
+
+public:
+ ~TranspositionTable() { aligned_ttmem_free(mem); }
+  void new_search() { generation8 += 8; } // Lower 3 bits are used by PV flag and Bound
+  TTEntry* probe(const Key key, bool& found) const;
+  int hashfull() const;
+  void resize(size_t mbSize);
+  void clear();
+
+  TTEntry* first_entry(const Key key) const {
+    return &table[mul_hi64(key, clusterCount)].entry[0];
+  }
+
+private:
+  friend struct TTEntry;
+
+  size_t clusterCount;
+  Cluster* table;
+  void* mem;
+  uint8_t generation8; // Size must be not bigger than TTEntry::genBound8
+};
+
+extern TranspositionTable TT;
+
+#endif // #ifndef TT_H_INCLUDED
diff --git a/src/tune.cpp b/src/tune.cpp
new file mode 100644 (file)
index 0000000..e94f67f
--- /dev/null
@@ -0,0 +1,144 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <algorithm>
+#include <iostream>
+#include <sstream>
+
+#include "types.h"
+#include "misc.h"
+#include "uci.h"
+
+using std::string;
+
+bool Tune::update_on_last;
+const UCI::Option* LastOption = nullptr;
+BoolConditions Conditions;
+static std::map<std::string, int> TuneResults;
+
+string Tune::next(string& names, bool pop) {
+
+  string name;
+
+  do {
+      string token = names.substr(0, names.find(','));
+
+      if (pop)
+          names.erase(0, token.size() + 1);
+
+      std::stringstream ws(token);
+      name += (ws >> token, token); // Remove trailing whitespace
+
+  } while (  std::count(name.begin(), name.end(), '(')
+           - std::count(name.begin(), name.end(), ')'));
+
+  return name;
+}
+
+static void on_tune(const UCI::Option& o) {
+
+  if (!Tune::update_on_last || LastOption == &o)
+      Tune::read_options();
+}
+
+static void make_option(const string& n, int v, const SetRange& r) {
+
+  // Do not generate option when there is nothing to tune (ie. min = max)
+  if (r(v).first == r(v).second)
+      return;
+
+  if (TuneResults.count(n))
+      v = TuneResults[n];
+
+  Options[n] << UCI::Option(v, r(v).first, r(v).second, on_tune);
+  LastOption = &Options[n];
+
+  // Print formatted parameters, ready to be copy-pasted in Fishtest
+  std::cout << n << ","
+            << v << ","
+            << r(v).first << "," << r(v).second << ","
+            << (r(v).second - r(v).first) / 20.0 << ","
+            << "0.0020"
+            << std::endl;
+}
+
+template<> void Tune::Entry<int>::init_option() { make_option(name, value, range); }
+
+template<> void Tune::Entry<int>::read_option() {
+  if (Options.count(name))
+      value = int(Options[name]);
+}
+
+template<> void Tune::Entry<Value>::init_option() { make_option(name, value, range); }
+
+template<> void Tune::Entry<Value>::read_option() {
+  if (Options.count(name))
+      value = Value(int(Options[name]));
+}
+
+template<> void Tune::Entry<Score>::init_option() {
+  make_option("m" + name, mg_value(value), range);
+  make_option("e" + name, eg_value(value), range);
+}
+
+template<> void Tune::Entry<Score>::read_option() {
+  if (Options.count("m" + name))
+      value = make_score(int(Options["m" + name]), eg_value(value));
+
+  if (Options.count("e" + name))
+      value = make_score(mg_value(value), int(Options["e" + name]));
+}
+
+// Instead of a variable here we have a PostUpdate function: just call it
+template<> void Tune::Entry<Tune::PostUpdate>::init_option() {}
+template<> void Tune::Entry<Tune::PostUpdate>::read_option() { value(); }
+
+
+// Set binary conditions according to a probability that depends
+// on the corresponding parameter value.
+
+void BoolConditions::set() {
+
+  static PRNG rng(now());
+  static bool startup = true; // To workaround fishtest bench
+
+  for (size_t i = 0; i < binary.size(); i++)
+      binary[i] = !startup && (values[i] + int(rng.rand<unsigned>() % variance) > threshold);
+
+  startup = false;
+
+  for (size_t i = 0; i < binary.size(); i++)
+      sync_cout << binary[i] << sync_endl;
+}
+
+
+// Init options with tuning session results instead of default values. Useful to
+// get correct bench signature after a tuning session or to test tuned values.
+// Just copy fishtest tuning results in a result.txt file and extract the
+// values with:
+//
+// cat results.txt | sed 's/^param: \([^,]*\), best: \([^,]*\).*/  TuneResults["\1"] = int(round(\2));/'
+//
+// Then paste the output below, as the function body
+
+#include <cmath>
+
+void Tune::read_results() {
+
+  /* ...insert your values here... */
+}
diff --git a/src/tune.h b/src/tune.h
new file mode 100644 (file)
index 0000000..1489fa3
--- /dev/null
@@ -0,0 +1,193 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef TUNE_H_INCLUDED
+#define TUNE_H_INCLUDED
+
+#include <memory>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+typedef std::pair<int, int> Range; // Option's min-max values
+typedef Range (RangeFun) (int);
+
+// Default Range function, to calculate Option's min-max values
+inline Range default_range(int v) {
+  return v > 0 ? Range(0, 2 * v) : Range(2 * v, 0);
+}
+
+struct SetRange {
+  explicit SetRange(RangeFun f) : fun(f) {}
+  SetRange(int min, int max) : fun(nullptr), range(min, max) {}
+  Range operator()(int v) const { return fun ? fun(v) : range; }
+
+  RangeFun* fun;
+  Range range;
+};
+
+#define SetDefaultRange SetRange(default_range)
+
+
+/// BoolConditions struct is used to tune boolean conditions in the
+/// code by toggling them on/off according to a probability that
+/// depends on the value of a tuned integer parameter: for high
+/// values of the parameter condition is always disabled, for low
+/// values is always enabled, otherwise it is enabled with a given
+/// probability that depnends on the parameter under tuning.
+
+struct BoolConditions {
+  void init(size_t size) { values.resize(size, defaultValue), binary.resize(size, 0); }
+  void set();
+
+  std::vector<int> binary, values;
+  int defaultValue = 465, variance = 40, threshold = 500;
+  SetRange range = SetRange(0, 1000);
+};
+
+extern BoolConditions Conditions;
+
+inline void set_conditions() { Conditions.set(); }
+
+
+/// Tune class implements the 'magic' code that makes the setup of a fishtest
+/// tuning session as easy as it can be. Mainly you have just to remove const
+/// qualifiers from the variables you want to tune and flag them for tuning, so
+/// if you have:
+///
+///   const Score myScore = S(10, 15);
+///   const Value myValue[][2] = { { V(100), V(20) }, { V(7), V(78) } };
+///
+/// If you have a my_post_update() function to run after values have been updated,
+/// and a my_range() function to set custom Option's min-max values, then you just
+/// remove the 'const' qualifiers and write somewhere below in the file:
+///
+///   TUNE(SetRange(my_range), myScore, myValue, my_post_update);
+///
+/// You can also set the range directly, and restore the default at the end
+///
+///   TUNE(SetRange(-100, 100), myScore, SetDefaultRange);
+///
+/// In case update function is slow and you have many parameters, you can add:
+///
+///   UPDATE_ON_LAST();
+///
+/// And the values update, including post update function call, will be done only
+/// once, after the engine receives the last UCI option, that is the one defined
+/// and created as the last one, so the GUI should send the options in the same
+/// order in which have been defined.
+
+class Tune {
+
+  typedef void (PostUpdate) (); // Post-update function
+
+  Tune() { read_results(); }
+  Tune(const Tune&) = delete;
+  void operator=(const Tune&) = delete;
+  void read_results();
+
+  static Tune& instance() { static Tune t; return t; } // Singleton
+
+  // Use polymorphism to accomodate Entry of different types in the same vector
+  struct EntryBase {
+    virtual ~EntryBase() = default;
+    virtual void init_option() = 0;
+    virtual void read_option() = 0;
+  };
+
+  template<typename T>
+  struct Entry : public EntryBase {
+
+    static_assert(!std::is_const<T>::value, "Parameter cannot be const!");
+
+    static_assert(   std::is_same<T,   int>::value
+                  || std::is_same<T, Value>::value
+                  || std::is_same<T, Score>::value
+                  || std::is_same<T, PostUpdate>::value, "Parameter type not supported!");
+
+    Entry(const std::string& n, T& v, const SetRange& r) : name(n), value(v), range(r) {}
+    void operator=(const Entry&) = delete; // Because 'value' is a reference
+    void init_option() override;
+    void read_option() override;
+
+    std::string name;
+    T& value;
+    SetRange range;
+  };
+
+  // Our facilty to fill the container, each Entry corresponds to a parameter to tune.
+  // We use variadic templates to deal with an unspecified number of entries, each one
+  // of a possible different type.
+  static std::string next(std::string& names, bool pop = true);
+
+  int add(const SetRange&, std::string&&) { return 0; }
+
+  template<typename T, typename... Args>
+  int add(const SetRange& range, std::string&& names, T& value, Args&&... args) {
+    list.push_back(std::unique_ptr<EntryBase>(new Entry<T>(next(names), value, range)));
+    return add(range, std::move(names), args...);
+  }
+
+  // Template specialization for arrays: recursively handle multi-dimensional arrays
+  template<typename T, size_t N, typename... Args>
+  int add(const SetRange& range, std::string&& names, T (&value)[N], Args&&... args) {
+    for (size_t i = 0; i < N; i++)
+        add(range, next(names, i == N - 1) + "[" + std::to_string(i) + "]", value[i]);
+    return add(range, std::move(names), args...);
+  }
+
+  // Template specialization for SetRange
+  template<typename... Args>
+  int add(const SetRange&, std::string&& names, SetRange& value, Args&&... args) {
+    return add(value, (next(names), std::move(names)), args...);
+  }
+
+  // Template specialization for BoolConditions
+  template<typename... Args>
+  int add(const SetRange& range, std::string&& names, BoolConditions& cond, Args&&... args) {
+    for (size_t size = cond.values.size(), i = 0; i < size; i++)
+        add(cond.range, next(names, i == size - 1) + "_" + std::to_string(i), cond.values[i]);
+    return add(range, std::move(names), args...);
+  }
+
+  std::vector<std::unique_ptr<EntryBase>> list;
+
+public:
+  template<typename... Args>
+  static int add(const std::string& names, Args&&... args) {
+    return instance().add(SetDefaultRange, names.substr(1, names.size() - 2), args...); // Remove trailing parenthesis
+  }
+  static void init() { for (auto& e : instance().list) e->init_option(); read_options(); } // Deferred, due to UCI::Options access
+  static void read_options() { for (auto& e : instance().list) e->read_option(); }
+  static bool update_on_last;
+};
+
+// Some macro magic :-) we define a dummy int variable that compiler initializes calling Tune::add()
+#define STRINGIFY(x) #x
+#define UNIQUE2(x, y) x ## y
+#define UNIQUE(x, y) UNIQUE2(x, y) // Two indirection levels to expand __LINE__
+#define TUNE(...) int UNIQUE(p, __LINE__) = Tune::add(STRINGIFY((__VA_ARGS__)), __VA_ARGS__)
+
+#define UPDATE_ON_LAST() bool UNIQUE(p, __LINE__) = Tune::update_on_last = true
+
+// Some macro to tune toggling of boolean conditions
+#define CONDITION(x) (Conditions.binary[__COUNTER__] || (x))
+#define TUNE_CONDITIONS() int UNIQUE(c, __LINE__) = (Conditions.init(__COUNTER__), 0); \
+                          TUNE(Conditions, set_conditions)
+
+#endif // #ifndef TUNE_H_INCLUDED
diff --git a/src/types.h b/src/types.h
new file mode 100644 (file)
index 0000000..5873c69
--- /dev/null
@@ -0,0 +1,481 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef TYPES_H_INCLUDED
+#define TYPES_H_INCLUDED
+
+/// When compiling with provided Makefile (e.g. for Linux and OSX), configuration
+/// is done automatically. To get started type 'make help'.
+///
+/// When Makefile is not used (e.g. with Microsoft Visual Studio) some switches
+/// need to be set manually:
+///
+/// -DNDEBUG      | Disable debugging mode. Always use this for release.
+///
+/// -DNO_PREFETCH | Disable use of prefetch asm-instruction. You may need this to
+///               | run on some very old machines.
+///
+/// -DUSE_POPCNT  | Add runtime support for use of popcnt asm-instruction. Works
+///               | only in 64-bit mode and requires hardware with popcnt support.
+///
+/// -DUSE_PEXT    | Add runtime support for use of pext asm-instruction. Works
+///               | only in 64-bit mode and requires hardware with pext support.
+
+#include <cassert>
+#include <cctype>
+#include <cstdint>
+#include <cstdlib>
+#include <algorithm>
+
+#if defined(_MSC_VER)
+// Disable some silly and noisy warning from MSVC compiler
+#pragma warning(disable: 4127) // Conditional expression is constant
+#pragma warning(disable: 4146) // Unary minus operator applied to unsigned type
+#pragma warning(disable: 4800) // Forcing value to bool 'true' or 'false'
+#endif
+
+/// Predefined macros hell:
+///
+/// __GNUC__           Compiler is gcc, Clang or Intel on Linux
+/// __INTEL_COMPILER   Compiler is Intel
+/// _MSC_VER           Compiler is MSVC or Intel on Windows
+/// _WIN32             Building on Windows (any)
+/// _WIN64             Building on Windows 64 bit
+
+#if defined(_WIN64) && defined(_MSC_VER) // No Makefile used
+#  include <intrin.h> // Microsoft header for _BitScanForward64()
+#  define IS_64BIT
+#endif
+
+#if defined(USE_POPCNT) && (defined(__INTEL_COMPILER) || defined(_MSC_VER))
+#  include <nmmintrin.h> // Intel and Microsoft header for _mm_popcnt_u64()
+#endif
+
+#if !defined(NO_PREFETCH) && (defined(__INTEL_COMPILER) || defined(_MSC_VER))
+#  include <xmmintrin.h> // Intel and Microsoft header for _mm_prefetch()
+#endif
+
+#if defined(USE_PEXT)
+#  include <immintrin.h> // Header for _pext_u64() intrinsic
+#  define pext(b, m) _pext_u64(b, m)
+#else
+#  define pext(b, m) 0
+#endif
+
+#ifdef USE_POPCNT
+constexpr bool HasPopCnt = true;
+#else
+constexpr bool HasPopCnt = false;
+#endif
+
+#ifdef USE_PEXT
+constexpr bool HasPext = true;
+#else
+constexpr bool HasPext = false;
+#endif
+
+#ifdef IS_64BIT
+constexpr bool Is64Bit = true;
+#else
+constexpr bool Is64Bit = false;
+#endif
+
+typedef uint64_t Key;
+typedef uint64_t Bitboard;
+
+constexpr int MAX_MOVES = 256;
+constexpr int MAX_PLY   = 246;
+
+/// A move needs 16 bits to be stored
+///
+/// bit  0- 5: destination square (from 0 to 63)
+/// bit  6-11: origin square (from 0 to 63)
+/// bit 12-13: promotion piece type - 2 (from KNIGHT-2 to QUEEN-2)
+/// bit 14-15: special move flag: promotion (1), en passant (2), castling (3)
+/// NOTE: EN-PASSANT bit is set only when a pawn can be captured
+///
+/// Special cases are MOVE_NONE and MOVE_NULL. We can sneak these in because in
+/// any normal move destination square is always different from origin square
+/// while MOVE_NONE and MOVE_NULL have the same origin and destination square.
+
+enum Move : int {
+  MOVE_NONE,
+  MOVE_NULL = 65
+};
+
+enum MoveType {
+  NORMAL,
+  PROMOTION = 1 << 14,
+  ENPASSANT = 2 << 14,
+  CASTLING  = 3 << 14
+};
+
+enum Color {
+  WHITE, BLACK, COLOR_NB = 2
+};
+
+enum CastlingRights {
+  NO_CASTLING,
+  WHITE_OO,
+  WHITE_OOO = WHITE_OO << 1,
+  BLACK_OO  = WHITE_OO << 2,
+  BLACK_OOO = WHITE_OO << 3,
+
+  KING_SIDE      = WHITE_OO  | BLACK_OO,
+  QUEEN_SIDE     = WHITE_OOO | BLACK_OOO,
+  WHITE_CASTLING = WHITE_OO  | WHITE_OOO,
+  BLACK_CASTLING = BLACK_OO  | BLACK_OOO,
+  ANY_CASTLING   = WHITE_CASTLING | BLACK_CASTLING,
+
+  CASTLING_RIGHT_NB = 16
+};
+
+enum Phase {
+  PHASE_ENDGAME,
+  PHASE_MIDGAME = 128,
+  MG = 0, EG = 1, PHASE_NB = 2
+};
+
+enum ScaleFactor {
+  SCALE_FACTOR_DRAW    = 0,
+  SCALE_FACTOR_NORMAL  = 64,
+  SCALE_FACTOR_MAX     = 128,
+  SCALE_FACTOR_NONE    = 255
+};
+
+enum Bound {
+  BOUND_NONE,
+  BOUND_UPPER,
+  BOUND_LOWER,
+  BOUND_EXACT = BOUND_UPPER | BOUND_LOWER
+};
+
+enum Value : int {
+  VALUE_ZERO      = 0,
+  VALUE_DRAW      = 0,
+  VALUE_KNOWN_WIN = 10000,
+  VALUE_MATE      = 32000,
+  VALUE_INFINITE  = 32001,
+  VALUE_NONE      = 32002,
+
+  VALUE_TB_WIN_IN_MAX_PLY  =  VALUE_MATE - 2 * MAX_PLY,
+  VALUE_TB_LOSS_IN_MAX_PLY = -VALUE_TB_WIN_IN_MAX_PLY,
+  VALUE_MATE_IN_MAX_PLY  =  VALUE_MATE - MAX_PLY,
+  VALUE_MATED_IN_MAX_PLY = -VALUE_MATE_IN_MAX_PLY,
+
+  PawnValueMg   = 126,   PawnValueEg   = 208,
+  KnightValueMg = 781,   KnightValueEg = 854,
+  BishopValueMg = 825,   BishopValueEg = 915,
+  RookValueMg   = 1276,  RookValueEg   = 1380,
+  QueenValueMg  = 2538,  QueenValueEg  = 2682,
+  Tempo = 28,
+
+  MidgameLimit  = 15258, EndgameLimit  = 3915
+};
+
+enum PieceType {
+  NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING,
+  ALL_PIECES = 0,
+  PIECE_TYPE_NB = 8
+};
+
+enum Piece {
+  NO_PIECE,
+  W_PAWN = 1, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING,
+  B_PAWN = 9, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING,
+  PIECE_NB = 16
+};
+
+constexpr Value PieceValue[PHASE_NB][PIECE_NB] = {
+  { VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg, VALUE_ZERO, VALUE_ZERO,
+    VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg, VALUE_ZERO, VALUE_ZERO },
+  { VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg, VALUE_ZERO, VALUE_ZERO,
+    VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg, VALUE_ZERO, VALUE_ZERO }
+};
+
+typedef int Depth;
+
+enum : int {
+  DEPTH_QS_CHECKS     =  0,
+  DEPTH_QS_NO_CHECKS  = -1,
+  DEPTH_QS_RECAPTURES = -5,
+
+  DEPTH_NONE   = -6,
+
+  DEPTH_OFFSET = -7 // value used only for TT entry occupancy check
+};
+
+enum Square : int {
+  SQ_A1, SQ_B1, SQ_C1, SQ_D1, SQ_E1, SQ_F1, SQ_G1, SQ_H1,
+  SQ_A2, SQ_B2, SQ_C2, SQ_D2, SQ_E2, SQ_F2, SQ_G2, SQ_H2,
+  SQ_A3, SQ_B3, SQ_C3, SQ_D3, SQ_E3, SQ_F3, SQ_G3, SQ_H3,
+  SQ_A4, SQ_B4, SQ_C4, SQ_D4, SQ_E4, SQ_F4, SQ_G4, SQ_H4,
+  SQ_A5, SQ_B5, SQ_C5, SQ_D5, SQ_E5, SQ_F5, SQ_G5, SQ_H5,
+  SQ_A6, SQ_B6, SQ_C6, SQ_D6, SQ_E6, SQ_F6, SQ_G6, SQ_H6,
+  SQ_A7, SQ_B7, SQ_C7, SQ_D7, SQ_E7, SQ_F7, SQ_G7, SQ_H7,
+  SQ_A8, SQ_B8, SQ_C8, SQ_D8, SQ_E8, SQ_F8, SQ_G8, SQ_H8,
+  SQ_NONE,
+
+  SQUARE_ZERO = 0,
+  SQUARE_NB   = 64
+};
+
+enum Direction : int {
+  NORTH =  8,
+  EAST  =  1,
+  SOUTH = -NORTH,
+  WEST  = -EAST,
+
+  NORTH_EAST = NORTH + EAST,
+  SOUTH_EAST = SOUTH + EAST,
+  SOUTH_WEST = SOUTH + WEST,
+  NORTH_WEST = NORTH + WEST
+};
+
+enum File : int {
+  FILE_A, FILE_B, FILE_C, FILE_D, FILE_E, FILE_F, FILE_G, FILE_H, FILE_NB
+};
+
+enum Rank : int {
+  RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, RANK_NB
+};
+
+// Keep track of what a move changes on the board (used by NNUE)
+struct DirtyPiece {
+
+  // Number of changed pieces
+  int dirty_num;
+
+  // Max 3 pieces can change in one move. A promotion with capture moves
+  // both the pawn and the captured piece to SQ_NONE and the piece promoted
+  // to from SQ_NONE to the capture square.
+  Piece piece[3];
+
+  // From and to squares, which may be SQ_NONE
+  Square from[3];
+  Square to[3];
+};
+
+/// Score enum stores a middlegame and an endgame value in a single integer (enum).
+/// The least significant 16 bits are used to store the middlegame value and the
+/// upper 16 bits are used to store the endgame value. We have to take care to
+/// avoid left-shifting a signed int to avoid undefined behavior.
+enum Score : int { SCORE_ZERO };
+
+constexpr Score make_score(int mg, int eg) {
+  return Score((int)((unsigned int)eg << 16) + mg);
+}
+
+/// Extracting the signed lower and upper 16 bits is not so trivial because
+/// according to the standard a simple cast to short is implementation defined
+/// and so is a right shift of a signed integer.
+inline Value eg_value(Score s) {
+  union { uint16_t u; int16_t s; } eg = { uint16_t(unsigned(s + 0x8000) >> 16) };
+  return Value(eg.s);
+}
+
+inline Value mg_value(Score s) {
+  union { uint16_t u; int16_t s; } mg = { uint16_t(unsigned(s)) };
+  return Value(mg.s);
+}
+
+#define ENABLE_BASE_OPERATORS_ON(T)                                \
+constexpr T operator+(T d1, int d2) { return T(int(d1) + d2); }    \
+constexpr T operator-(T d1, int d2) { return T(int(d1) - d2); }    \
+constexpr T operator-(T d) { return T(-int(d)); }                  \
+inline T& operator+=(T& d1, int d2) { return d1 = d1 + d2; }       \
+inline T& operator-=(T& d1, int d2) { return d1 = d1 - d2; }
+
+#define ENABLE_INCR_OPERATORS_ON(T)                                \
+inline T& operator++(T& d) { return d = T(int(d) + 1); }           \
+inline T& operator--(T& d) { return d = T(int(d) - 1); }
+
+#define ENABLE_FULL_OPERATORS_ON(T)                                \
+ENABLE_BASE_OPERATORS_ON(T)                                        \
+constexpr T operator*(int i, T d) { return T(i * int(d)); }        \
+constexpr T operator*(T d, int i) { return T(int(d) * i); }        \
+constexpr T operator/(T d, int i) { return T(int(d) / i); }        \
+constexpr int operator/(T d1, T d2) { return int(d1) / int(d2); }  \
+inline T& operator*=(T& d, int i) { return d = T(int(d) * i); }    \
+inline T& operator/=(T& d, int i) { return d = T(int(d) / i); }
+
+ENABLE_FULL_OPERATORS_ON(Value)
+ENABLE_FULL_OPERATORS_ON(Direction)
+
+ENABLE_INCR_OPERATORS_ON(Piece)
+ENABLE_INCR_OPERATORS_ON(PieceType)
+ENABLE_INCR_OPERATORS_ON(Square)
+ENABLE_INCR_OPERATORS_ON(File)
+ENABLE_INCR_OPERATORS_ON(Rank)
+
+ENABLE_BASE_OPERATORS_ON(Score)
+
+#undef ENABLE_FULL_OPERATORS_ON
+#undef ENABLE_INCR_OPERATORS_ON
+#undef ENABLE_BASE_OPERATORS_ON
+
+/// Additional operators to add a Direction to a Square
+constexpr Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); }
+constexpr Square operator-(Square s, Direction d) { return Square(int(s) - int(d)); }
+inline Square& operator+=(Square& s, Direction d) { return s = s + d; }
+inline Square& operator-=(Square& s, Direction d) { return s = s - d; }
+
+/// Only declared but not defined. We don't want to multiply two scores due to
+/// a very high risk of overflow. So user should explicitly convert to integer.
+Score operator*(Score, Score) = delete;
+
+/// Division of a Score must be handled separately for each term
+inline Score operator/(Score s, int i) {
+  return make_score(mg_value(s) / i, eg_value(s) / i);
+}
+
+/// Multiplication of a Score by an integer. We check for overflow in debug mode.
+inline Score operator*(Score s, int i) {
+
+  Score result = Score(int(s) * i);
+
+  assert(eg_value(result) == (i * eg_value(s)));
+  assert(mg_value(result) == (i * mg_value(s)));
+  assert((i == 0) || (result / i) == s);
+
+  return result;
+}
+
+/// Multiplication of a Score by a boolean
+inline Score operator*(Score s, bool b) {
+  return b ? s : SCORE_ZERO;
+}
+
+constexpr Color operator~(Color c) {
+  return Color(c ^ BLACK); // Toggle color
+}
+
+constexpr Square flip_rank(Square s) { // Swap A1 <-> A8
+  return Square(s ^ SQ_A8);
+}
+
+constexpr Square flip_file(Square s) { // Swap A1 <-> H1
+  return Square(s ^ SQ_H1);
+}
+
+constexpr Piece operator~(Piece pc) {
+  return Piece(pc ^ 8); // Swap color of piece B_KNIGHT <-> W_KNIGHT
+}
+
+constexpr CastlingRights operator&(Color c, CastlingRights cr) {
+  return CastlingRights((c == WHITE ? WHITE_CASTLING : BLACK_CASTLING) & cr);
+}
+
+constexpr Value mate_in(int ply) {
+  return VALUE_MATE - ply;
+}
+
+constexpr Value mated_in(int ply) {
+  return -VALUE_MATE + ply;
+}
+
+constexpr Square make_square(File f, Rank r) {
+  return Square((r << 3) + f);
+}
+
+constexpr Piece make_piece(Color c, PieceType pt) {
+  return Piece((c << 3) + pt);
+}
+
+constexpr PieceType type_of(Piece pc) {
+  return PieceType(pc & 7);
+}
+
+inline Color color_of(Piece pc) {
+  assert(pc != NO_PIECE);
+  return Color(pc >> 3);
+}
+
+constexpr bool is_ok(Square s) {
+  return s >= SQ_A1 && s <= SQ_H8;
+}
+
+constexpr File file_of(Square s) {
+  return File(s & 7);
+}
+
+constexpr Rank rank_of(Square s) {
+  return Rank(s >> 3);
+}
+
+constexpr Square relative_square(Color c, Square s) {
+  return Square(s ^ (c * 56));
+}
+
+constexpr Rank relative_rank(Color c, Rank r) {
+  return Rank(r ^ (c * 7));
+}
+
+constexpr Rank relative_rank(Color c, Square s) {
+  return relative_rank(c, rank_of(s));
+}
+
+constexpr Direction pawn_push(Color c) {
+  return c == WHITE ? NORTH : SOUTH;
+}
+
+constexpr Square from_sq(Move m) {
+  return Square((m >> 6) & 0x3F);
+}
+
+constexpr Square to_sq(Move m) {
+  return Square(m & 0x3F);
+}
+
+constexpr int from_to(Move m) {
+ return m & 0xFFF;
+}
+
+constexpr MoveType type_of(Move m) {
+  return MoveType(m & (3 << 14));
+}
+
+constexpr PieceType promotion_type(Move m) {
+  return PieceType(((m >> 12) & 3) + KNIGHT);
+}
+
+constexpr Move make_move(Square from, Square to) {
+  return Move((from << 6) + to);
+}
+
+constexpr Move reverse_move(Move m) {
+  return make_move(to_sq(m), from_sq(m));
+}
+
+template<MoveType T>
+constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) {
+  return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to);
+}
+
+constexpr bool is_ok(Move m) {
+  return from_sq(m) != to_sq(m); // Catch MOVE_NULL and MOVE_NONE
+}
+
+/// Based on a congruential pseudo random number generator
+constexpr Key make_key(uint64_t seed) {
+  return seed * 6364136223846793005ULL + 1442695040888963407ULL;
+}
+
+#endif // #ifndef TYPES_H_INCLUDED
+
+#include "tune.h" // Global visibility to tuning setup
diff --git a/src/uci.cpp b/src/uci.cpp
new file mode 100644 (file)
index 0000000..bc0ee0a
--- /dev/null
@@ -0,0 +1,371 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <cassert>
+#include <cmath>
+#include <iostream>
+#include <sstream>
+#include <string>
+
+#include "evaluate.h"
+#include "movegen.h"
+#include "position.h"
+#include "search.h"
+#include "thread.h"
+#include "timeman.h"
+#include "tt.h"
+#include "uci.h"
+#include "syzygy/tbprobe.h"
+
+using namespace std;
+
+extern vector<string> setup_bench(const Position&, istream&);
+
+namespace {
+
+  // FEN string of the initial position, normal chess
+  const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
+
+
+  // position() is called when engine receives the "position" UCI command.
+  // The function sets up the position described in the given FEN string ("fen")
+  // or the starting position ("startpos") and then makes the moves given in the
+  // following move list ("moves").
+
+  void position(Position& pos, istringstream& is, StateListPtr& states) {
+
+    Move m;
+    string token, fen;
+
+    is >> token;
+
+    if (token == "startpos")
+    {
+        fen = StartFEN;
+        is >> token; // Consume "moves" token if any
+    }
+    else if (token == "fen")
+        while (is >> token && token != "moves")
+            fen += token + " ";
+    else
+        return;
+
+    states = StateListPtr(new std::deque<StateInfo>(1)); // Drop old and create a new one
+    pos.set(fen, Options["UCI_Chess960"], &states->back(), Threads.main());
+
+    // Parse move list (if any)
+    while (is >> token && (m = UCI::to_move(pos, token)) != MOVE_NONE)
+    {
+        states->emplace_back();
+        pos.do_move(m, states->back());
+    }
+  }
+
+  // trace_eval() prints the evaluation for the current position, consistent with the UCI
+  // options set so far.
+
+  void trace_eval(Position& pos) {
+
+    StateListPtr states(new std::deque<StateInfo>(1));
+    Position p;
+    p.set(pos.fen(), Options["UCI_Chess960"], &states->back(), Threads.main());
+
+    Eval::verify_NNUE();
+
+    sync_cout << "\n" << Eval::trace(p) << sync_endl;
+  }
+
+
+  // setoption() is called when engine receives the "setoption" UCI command. The
+  // function updates the UCI option ("name") to the given value ("value").
+
+  void setoption(istringstream& is) {
+
+    string token, name, value;
+
+    is >> token; // Consume "name" token
+
+    // Read option name (can contain spaces)
+    while (is >> token && token != "value")
+        name += (name.empty() ? "" : " ") + token;
+
+    // Read option value (can contain spaces)
+    while (is >> token)
+        value += (value.empty() ? "" : " ") + token;
+
+    if (Options.count(name))
+        Options[name] = value;
+    else
+        sync_cout << "No such option: " << name << sync_endl;
+  }
+
+
+  // go() is called when engine receives the "go" UCI command. The function sets
+  // the thinking time and other parameters from the input string, then starts
+  // the search.
+
+  void go(Position& pos, istringstream& is, StateListPtr& states) {
+
+    Search::LimitsType limits;
+    string token;
+    bool ponderMode = false;
+
+    limits.startTime = now(); // As early as possible!
+
+    while (is >> token)
+        if (token == "searchmoves") // Needs to be the last command on the line
+            while (is >> token)
+                limits.searchmoves.push_back(UCI::to_move(pos, token));
+
+        else if (token == "wtime")     is >> limits.time[WHITE];
+        else if (token == "btime")     is >> limits.time[BLACK];
+        else if (token == "winc")      is >> limits.inc[WHITE];
+        else if (token == "binc")      is >> limits.inc[BLACK];
+        else if (token == "movestogo") is >> limits.movestogo;
+        else if (token == "depth")     is >> limits.depth;
+        else if (token == "nodes")     is >> limits.nodes;
+        else if (token == "movetime")  is >> limits.movetime;
+        else if (token == "mate")      is >> limits.mate;
+        else if (token == "perft")     is >> limits.perft;
+        else if (token == "infinite")  limits.infinite = 1;
+        else if (token == "ponder")    ponderMode = true;
+
+    Threads.start_thinking(pos, states, limits, ponderMode);
+  }
+
+
+  // bench() is called when engine receives the "bench" command. Firstly
+  // a list of UCI commands is setup according to bench parameters, then
+  // it is run one by one printing a summary at the end.
+
+  void bench(Position& pos, istream& args, StateListPtr& states) {
+
+    string token;
+    uint64_t num, nodes = 0, cnt = 1;
+
+    vector<string> list = setup_bench(pos, args);
+    num = count_if(list.begin(), list.end(), [](string s) { return s.find("go ") == 0 || s.find("eval") == 0; });
+
+    TimePoint elapsed = now();
+
+    for (const auto& cmd : list)
+    {
+        istringstream is(cmd);
+        is >> skipws >> token;
+
+        if (token == "go" || token == "eval")
+        {
+            cerr << "\nPosition: " << cnt++ << '/' << num << endl;
+            if (token == "go")
+            {
+               go(pos, is, states);
+               Threads.main()->wait_for_search_finished();
+               nodes += Threads.nodes_searched();
+            }
+            else
+               trace_eval(pos);
+        }
+        else if (token == "setoption")  setoption(is);
+        else if (token == "position")   position(pos, is, states);
+        else if (token == "ucinewgame") { Search::clear(); elapsed = now(); } // Search::clear() may take some while
+    }
+
+    elapsed = now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero'
+
+    dbg_print(); // Just before exiting
+
+    cerr << "\n==========================="
+         << "\nTotal time (ms) : " << elapsed
+         << "\nNodes searched  : " << nodes
+         << "\nNodes/second    : " << 1000 * nodes / elapsed << endl;
+  }
+
+  // The win rate model returns the probability (per mille) of winning given an eval
+  // and a game-ply. The model fits rather accurately the LTC fishtest statistics.
+  int win_rate_model(Value v, int ply) {
+
+     // The model captures only up to 240 plies, so limit input (and rescale)
+     double m = std::min(240, ply) / 64.0;
+
+     // Coefficients of a 3rd order polynomial fit based on fishtest data
+     // for two parameters needed to transform eval to the argument of a
+     // logistic function.
+     double as[] = {-8.24404295, 64.23892342, -95.73056462, 153.86478679};
+     double bs[] = {-3.37154371, 28.44489198, -56.67657741,  72.05858751};
+     double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3];
+     double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3];
+
+     // Transform eval to centipawns with limited range
+     double x = std::clamp(double(100 * v) / PawnValueEg, -1000.0, 1000.0);
+
+     // Return win rate in per mille (rounded to nearest)
+     return int(0.5 + 1000 / (1 + std::exp((a - x) / b)));
+  }
+
+} // namespace
+
+
+/// UCI::loop() waits for a command from stdin, parses it and calls the appropriate
+/// function. Also intercepts EOF from stdin to ensure gracefully exiting if the
+/// GUI dies unexpectedly. When called with some command line arguments, e.g. to
+/// run 'bench', once the command is executed the function returns immediately.
+/// In addition to the UCI ones, also some additional debug commands are supported.
+
+void UCI::loop(int argc, char* argv[]) {
+
+  Position pos;
+  string token, cmd;
+  StateListPtr states(new std::deque<StateInfo>(1));
+
+  pos.set(StartFEN, false, &states->back(), Threads.main());
+
+  for (int i = 1; i < argc; ++i)
+      cmd += std::string(argv[i]) + " ";
+
+  do {
+      if (argc == 1 && !getline(cin, cmd)) // Block here waiting for input or EOF
+          cmd = "quit";
+
+      istringstream is(cmd);
+
+      token.clear(); // Avoid a stale if getline() returns empty or blank line
+      is >> skipws >> token;
+
+      if (    token == "quit"
+          ||  token == "stop")
+          Threads.stop = true;
+
+      // The GUI sends 'ponderhit' to tell us the user has played the expected move.
+      // So 'ponderhit' will be sent if we were told to ponder on the same move the
+      // user has played. We should continue searching but switch from pondering to
+      // normal search.
+      else if (token == "ponderhit")
+          Threads.main()->ponder = false; // Switch to normal search
+
+      else if (token == "uci")
+          sync_cout << "id name " << engine_info(true)
+                    << "\n"       << Options
+                    << "\nuciok"  << sync_endl;
+
+      else if (token == "setoption")  setoption(is);
+      else if (token == "go")         go(pos, is, states);
+      else if (token == "position")   position(pos, is, states);
+      else if (token == "ucinewgame") Search::clear();
+      else if (token == "isready")    sync_cout << "readyok" << sync_endl;
+
+      // Additional custom non-UCI commands, mainly for debugging.
+      // Do not use these commands during a search!
+      else if (token == "flip")     pos.flip();
+      else if (token == "bench")    bench(pos, is, states);
+      else if (token == "d")        sync_cout << pos << sync_endl;
+      else if (token == "eval")     trace_eval(pos);
+      else if (token == "compiler") sync_cout << compiler_info() << sync_endl;
+      else
+          sync_cout << "Unknown command: " << cmd << sync_endl;
+
+  } while (token != "quit" && argc == 1); // Command line args are one-shot
+}
+
+
+/// UCI::value() converts a Value to a string suitable for use with the UCI
+/// protocol specification:
+///
+/// cp <x>    The score from the engine's point of view in centipawns.
+/// mate <y>  Mate in y moves, not plies. If the engine is getting mated
+///           use negative values for y.
+
+string UCI::value(Value v) {
+
+  assert(-VALUE_INFINITE < v && v < VALUE_INFINITE);
+
+  stringstream ss;
+
+  if (abs(v) < VALUE_MATE_IN_MAX_PLY)
+      ss << "cp " << v * 100 / PawnValueEg;
+  else
+      ss << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2;
+
+  return ss.str();
+}
+
+
+/// UCI::wdl() report WDL statistics given an evaluation and a game ply, based on
+/// data gathered for fishtest LTC games.
+
+string UCI::wdl(Value v, int ply) {
+
+  stringstream ss;
+
+  int wdl_w = win_rate_model( v, ply);
+  int wdl_l = win_rate_model(-v, ply);
+  int wdl_d = 1000 - wdl_w - wdl_l;
+  ss << " wdl " << wdl_w << " " << wdl_d << " " << wdl_l;
+
+  return ss.str();
+}
+
+
+/// UCI::square() converts a Square to a string in algebraic notation (g1, a7, etc.)
+
+std::string UCI::square(Square s) {
+  return std::string{ char('a' + file_of(s)), char('1' + rank_of(s)) };
+}
+
+
+/// UCI::move() converts a Move to a string in coordinate notation (g1f3, a7a8q).
+/// The only special case is castling, where we print in the e1g1 notation in
+/// normal chess mode, and in e1h1 notation in chess960 mode. Internally all
+/// castling moves are always encoded as 'king captures rook'.
+
+string UCI::move(Move m, bool chess960) {
+
+  Square from = from_sq(m);
+  Square to = to_sq(m);
+
+  if (m == MOVE_NONE)
+      return "(none)";
+
+  if (m == MOVE_NULL)
+      return "0000";
+
+  if (type_of(m) == CASTLING && !chess960)
+      to = make_square(to > from ? FILE_G : FILE_C, rank_of(from));
+
+  string move = UCI::square(from) + UCI::square(to);
+
+  if (type_of(m) == PROMOTION)
+      move += " pnbrqk"[promotion_type(m)];
+
+  return move;
+}
+
+
+/// UCI::to_move() converts a string representing a move in coordinate notation
+/// (g1f3, a7a8q) to the corresponding legal Move, if any.
+
+Move UCI::to_move(const Position& pos, string& str) {
+
+  if (str.length() == 5) // Junior could send promotion piece in uppercase
+      str[4] = char(tolower(str[4]));
+
+  for (const auto& m : MoveList<LEGAL>(pos))
+      if (str == UCI::move(m, pos.is_chess960()))
+          return m;
+
+  return MOVE_NONE;
+}
diff --git a/src/uci.h b/src/uci.h
new file mode 100644 (file)
index 0000000..eb0b390
--- /dev/null
+++ b/src/uci.h
@@ -0,0 +1,81 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef UCI_H_INCLUDED
+#define UCI_H_INCLUDED
+
+#include <map>
+#include <string>
+
+#include "types.h"
+
+class Position;
+
+namespace UCI {
+
+class Option;
+
+/// Custom comparator because UCI options should be case insensitive
+struct CaseInsensitiveLess {
+  bool operator() (const std::string&, const std::string&) const;
+};
+
+/// Our options container is actually a std::map
+typedef std::map<std::string, Option, CaseInsensitiveLess> OptionsMap;
+
+/// Option class implements an option as defined by UCI protocol
+class Option {
+
+  typedef void (*OnChange)(const Option&);
+
+public:
+  Option(OnChange = nullptr);
+  Option(bool v, OnChange = nullptr);
+  Option(const char* v, OnChange = nullptr);
+  Option(double v, int minv, int maxv, OnChange = nullptr);
+  Option(const char* v, const char* cur, OnChange = nullptr);
+
+  Option& operator=(const std::string&);
+  void operator<<(const Option&);
+  operator double() const;
+  operator std::string() const;
+  bool operator==(const char*) const;
+
+private:
+  friend std::ostream& operator<<(std::ostream&, const OptionsMap&);
+
+  std::string defaultValue, currentValue, type;
+  int min, max;
+  size_t idx;
+  OnChange on_change;
+};
+
+void init(OptionsMap&);
+void loop(int argc, char* argv[]);
+std::string value(Value v);
+std::string square(Square s);
+std::string move(Move m, bool chess960);
+std::string pv(const Position& pos, Depth depth, Value alpha, Value beta);
+std::string wdl(Value v, int ply);
+Move to_move(const Position& pos, std::string& str);
+
+} // namespace UCI
+
+extern UCI::OptionsMap Options;
+
+#endif // #ifndef UCI_H_INCLUDED
diff --git a/src/ucioption.cpp b/src/ucioption.cpp
new file mode 100644 (file)
index 0000000..5e747a7
--- /dev/null
@@ -0,0 +1,192 @@
+/*
+  Stockfish, a UCI chess playing engine derived from Glaurung 2.1
+  Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file)
+
+  Stockfish is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  Stockfish is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <algorithm>
+#include <cassert>
+#include <ostream>
+#include <sstream>
+
+#include "evaluate.h"
+#include "misc.h"
+#include "search.h"
+#include "thread.h"
+#include "tt.h"
+#include "uci.h"
+#include "syzygy/tbprobe.h"
+
+using std::string;
+
+UCI::OptionsMap Options; // Global object
+
+namespace UCI {
+
+/// 'On change' actions, triggered by an option's value change
+void on_clear_hash(const Option&) { Search::clear(); }
+void on_hash_size(const Option& o) { TT.resize(size_t(o)); }
+void on_logger(const Option& o) { start_logger(o); }
+void on_threads(const Option& o) { Threads.set(size_t(o)); }
+void on_tb_path(const Option& o) { Tablebases::init(o); }
+void on_use_NNUE(const Option& ) { Eval::init_NNUE(); }
+void on_eval_file(const Option& ) { Eval::init_NNUE(); }
+
+/// Our case insensitive less() function as required by UCI protocol
+bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const {
+
+  return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(),
+         [](char c1, char c2) { return tolower(c1) < tolower(c2); });
+}
+
+
+/// UCI::init() initializes the UCI options to their hard-coded default values
+
+void init(OptionsMap& o) {
+
+  constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048;
+
+  o["Debug Log File"]        << Option("", on_logger);
+  o["Contempt"]              << Option(24, -100, 100);
+  o["Analysis Contempt"]     << Option("Both var Off var White var Black var Both", "Both");
+  o["Threads"]               << Option(1, 1, 512, on_threads);
+  o["Hash"]                  << Option(16, 1, MaxHashMB, on_hash_size);
+  o["Clear Hash"]            << Option(on_clear_hash);
+  o["Ponder"]                << Option(false);
+  o["MultiPV"]               << Option(1, 1, 500);
+  o["Skill Level"]           << Option(20, 0, 20);
+  o["Move Overhead"]         << Option(10, 0, 5000);
+  o["Slow Mover"]            << Option(100, 10, 1000);
+  o["nodestime"]             << Option(0, 0, 10000);
+  o["UCI_Chess960"]          << Option(false);
+  o["UCI_AnalyseMode"]       << Option(false);
+  o["UCI_LimitStrength"]     << Option(false);
+  o["UCI_Elo"]               << Option(1350, 1350, 2850);
+  o["UCI_ShowWDL"]           << Option(false);
+  o["SyzygyPath"]            << Option("<empty>", on_tb_path);
+  o["SyzygyProbeDepth"]      << Option(1, 1, 100);
+  o["Syzygy50MoveRule"]      << Option(true);
+  o["SyzygyProbeLimit"]      << Option(7, 0, 7);
+  o["Use NNUE"]              << Option(true, on_use_NNUE);
+  o["EvalFile"]              << Option(EvalFileDefaultName, on_eval_file);
+}
+
+
+/// operator<<() is used to print all the options default values in chronological
+/// insertion order (the idx field) and in the format defined by the UCI protocol.
+
+std::ostream& operator<<(std::ostream& os, const OptionsMap& om) {
+
+  for (size_t idx = 0; idx < om.size(); ++idx)
+      for (const auto& it : om)
+          if (it.second.idx == idx)
+          {
+              const Option& o = it.second;
+              os << "\noption name " << it.first << " type " << o.type;
+
+              if (o.type == "string" || o.type == "check" || o.type == "combo")
+                  os << " default " << o.defaultValue;
+
+              if (o.type == "spin")
+                  os << " default " << int(stof(o.defaultValue))
+                     << " min "     << o.min
+                     << " max "     << o.max;
+
+              break;
+          }
+
+  return os;
+}
+
+
+/// Option class constructors and conversion operators
+
+Option::Option(const char* v, OnChange f) : type("string"), min(0), max(0), on_change(f)
+{ defaultValue = currentValue = v; }
+
+Option::Option(bool v, OnChange f) : type("check"), min(0), max(0), on_change(f)
+{ defaultValue = currentValue = (v ? "true" : "false"); }
+
+Option::Option(OnChange f) : type("button"), min(0), max(0), on_change(f)
+{}
+
+Option::Option(double v, int minv, int maxv, OnChange f) : type("spin"), min(minv), max(maxv), on_change(f)
+{ defaultValue = currentValue = std::to_string(v); }
+
+Option::Option(const char* v, const char* cur, OnChange f) : type("combo"), min(0), max(0), on_change(f)
+{ defaultValue = v; currentValue = cur; }
+
+Option::operator double() const {
+  assert(type == "check" || type == "spin");
+  return (type == "spin" ? stof(currentValue) : currentValue == "true");
+}
+
+Option::operator std::string() const {
+  assert(type == "string");
+  return currentValue;
+}
+
+bool Option::operator==(const char* s) const {
+  assert(type == "combo");
+  return   !CaseInsensitiveLess()(currentValue, s)
+        && !CaseInsensitiveLess()(s, currentValue);
+}
+
+
+/// operator<<() inits options and assigns idx in the correct printing order
+
+void Option::operator<<(const Option& o) {
+
+  static size_t insert_order = 0;
+
+  *this = o;
+  idx = insert_order++;
+}
+
+
+/// operator=() updates currentValue and triggers on_change() action. It's up to
+/// the GUI to check for option's limits, but we could receive the new value
+/// from the user by console window, so let's check the bounds anyway.
+
+Option& Option::operator=(const string& v) {
+
+  assert(!type.empty());
+
+  if (   (type != "button" && v.empty())
+      || (type == "check" && v != "true" && v != "false")
+      || (type == "spin" && (stof(v) < min || stof(v) > max)))
+      return *this;
+
+  if (type == "combo")
+  {
+      OptionsMap comboMap; // To have case insensitive compare
+      string token;
+      std::istringstream ss(defaultValue);
+      while (ss >> token)
+          comboMap[token] << Option();
+      if (!comboMap.count(v) || v == "var")
+          return *this;
+  }
+
+  if (type != "button")
+      currentValue = v;
+
+  if (on_change)
+      on_change(*this);
+
+  return *this;
+}
+
+} // namespace UCI
diff --git a/tests/instrumented.sh b/tests/instrumented.sh
new file mode 100755 (executable)
index 0000000..03ded74
--- /dev/null
@@ -0,0 +1,145 @@
+#!/bin/bash
+# check for errors under valgrind or sanitizers.
+
+error()
+{
+  echo "instrumented testing failed on line $1"
+  exit 1
+}
+trap 'error ${LINENO}' ERR
+
+# define suitable post and prefixes for testing options
+case $1 in
+  --valgrind)
+    echo "valgrind testing started"
+    prefix=''
+    exeprefix='valgrind --error-exitcode=42'
+    postfix='1>/dev/null'
+    threads="1"
+  ;;
+  --valgrind-thread)
+    echo "valgrind-thread testing started"
+    prefix=''
+    exeprefix='valgrind --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:TTEntry::move
+race:TTEntry::depth
+race:TTEntry::bound
+race:TTEntry::save
+race:TTEntry::value
+race:TTEntry::eval
+race:TTEntry::is_pv
+
+race:TranspositionTable::probe
+race:TranspositionTable::hashfull
+
+EOF
+
+    export TSAN_OPTIONS="suppressions=./tsan.supp"
+
+  ;;
+  *)
+    echo "unknown testing started"
+    prefix=''
+    exeprefix=''
+    postfix=''
+    threads="1"
+  ;;
+esac
+
+# simple command line testing
+for args in "eval" \
+            "go nodes 1000" \
+            "go depth 10" \
+            "go movetime 1000" \
+            "go wtime 8000 btime 8000 winc 500 binc 500" \
+            "bench 128 $threads 8 default depth"
+do
+
+   echo "$prefix $exeprefix ./stockfish $args $postfix"
+   eval "$prefix $exeprefix ./stockfish $args $postfix"
+
+done
+
+# more general testing, following an uci protocol exchange
+cat << EOF > game.exp
+ set timeout 240
+ spawn $exeprefix ./stockfish
+
+ send "uci\n"
+ expect "uciok"
+
+ send "setoption name Threads value $threads\n"
+
+ send "ucinewgame\n"
+ send "position startpos\n"
+ send "go nodes 1000\n"
+ expect "bestmove"
+
+ send "position startpos moves e2e4 e7e6\n"
+ send "go nodes 1000\n"
+ expect "bestmove"
+
+ send "position fen 5rk1/1K4p1/8/8/3B4/8/8/8 b - - 0 1\n"
+ send "go depth 20\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"
diff --git a/tests/perft.sh b/tests/perft.sh
new file mode 100755 (executable)
index 0000000..545e750
--- /dev/null
@@ -0,0 +1,32 @@
+#!/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"
diff --git a/tests/reprosearch.sh b/tests/reprosearch.sh
new file mode 100755 (executable)
index 0000000..9fd847f
--- /dev/null
@@ -0,0 +1,61 @@
+#!/bin/bash
+# verify reproducible search
+
+error()
+{
+  echo "reprosearch testing failed on line $1"
+  exit 1
+}
+trap 'error ${LINENO}' ERR
+
+echo "reprosearch testing started"
+
+# repeat two short games, separated by ucinewgame. 
+# with go nodes $nodes they should result in exactly
+# the same node count for each iteration.
+cat << EOF > repeat.exp
+ set timeout 10
+ spawn ./stockfish
+ lassign \$argv nodes
+
+ send "uci\n"
+ expect "uciok"
+
+ send "ucinewgame\n"
+ send "position startpos\n"
+ send "go nodes \$nodes\n"
+ expect "bestmove"
+
+ send "position startpos moves e2e4 e7e6\n"
+ send "go nodes \$nodes\n"
+ expect "bestmove"
+
+ send "ucinewgame\n"
+ send "position startpos\n"
+ send "go nodes \$nodes\n"
+ expect "bestmove"
+
+ send "position startpos moves e2e4 e7e6\n"
+ send "go nodes \$nodes\n"
+ expect "bestmove"
+
+ send "quit\n"
+ expect eof
+EOF
+
+# to increase the likelyhood of finding a non-reproducible case,
+# the allowed number of nodes are varied systematically
+for i in `seq 1 20`
+do
+
+  nodes=$((100*3**i/2**i))
+  echo "reprosearch testing with $nodes nodes"
+
+  # each line should appear exactly an even number of times
+  expect repeat.exp $nodes 2>&1 | grep -o "nodes [0-9]*" | sort | uniq -c | awk '{if ($1%2!=0) exit(1)}'
+
+done
+
+rm repeat.exp
+
+echo "reprosearch testing OK"
diff --git a/tests/signature.sh b/tests/signature.sh
new file mode 100755 (executable)
index 0000000..2e5c183
--- /dev/null
@@ -0,0 +1,31 @@
+#!/bin/bash
+# obtain and optionally verify Bench / signature
+# if no reference is given, the output is deliberately limited to just the signature
+
+error()
+{
+  echo "running bench for signature failed on line $1"
+  exit 1
+}
+trap 'error ${LINENO}' ERR
+
+# obtain
+
+signature=`./stockfish bench 2>&1 | grep "Nodes searched  : " | awk '{print $4}'`
+
+if [ $# -gt 0 ]; then
+   # compare to given reference
+   if [ "$1" != "$signature" ]; then
+      if [ -z "$signature" ]; then
+         echo "No signature obtained from bench. Code crashed or assert triggered ?"
+      else
+         echo "signature mismatch: reference $1 obtained: $signature ."
+      fi
+      exit 1
+   else
+      echo "signature OK: $signature"
+   fi
+else
+   # just report signature
+   echo $signature
+fi