pax_global_header00006660000000000000000000000064141457123310014513gustar00rootroot0000000000000052 comment=433d4115a31ebcf0d9c0e4238b12915b70a0b7c1 Fairy-Stockfish-fairy_sf_14_0_1_xq/000077500000000000000000000000001414571233100173375ustar00rootroot00000000000000Fairy-Stockfish-fairy_sf_14_0_1_xq/.github/000077500000000000000000000000001414571233100206775ustar00rootroot00000000000000Fairy-Stockfish-fairy_sf_14_0_1_xq/.github/FUNDING.yml000066400000000000000000000013631414571233100225170ustar00rootroot00000000000000# These are supported funding model platforms github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: ianfab # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username custom: ['https://paypal.me/FairyStockfish'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] Fairy-Stockfish-fairy_sf_14_0_1_xq/.github/workflows/000077500000000000000000000000001414571233100227345ustar00rootroot00000000000000Fairy-Stockfish-fairy_sf_14_0_1_xq/.github/workflows/build.yml000066400000000000000000000031371414571233100245620ustar00rootroot00000000000000name: build on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - name: install run: sudo apt install mingw-w64 - name: make x86-64 run: cd src && make clean && make -j build COMP=mingw ARCH=x86-64 EXE=fairy-stockfish_x86-64.exe && strip fairy-stockfish_x86-64.exe - name: make x86-64-modern run: cd src && make clean && make -j build COMP=mingw ARCH=x86-64-modern EXE=fairy-stockfish_x86-64-modern.exe && strip fairy-stockfish_x86-64-modern.exe - name: make x86-64-bmi2 run: cd src && make clean && make -j build COMP=mingw ARCH=x86-64-bmi2 EXE=fairy-stockfish_x86-64-bmi2.exe && strip fairy-stockfish_x86-64-bmi2.exe - name: make largeboards x86-64 run: cd src && make clean && make -j build COMP=mingw ARCH=x86-64 EXE=fairy-stockfish-largeboards_x86-64.exe largeboards=yes && strip fairy-stockfish-largeboards_x86-64.exe - name: make largeboards x86-64-modern run: cd src && make clean && make -j build COMP=mingw ARCH=x86-64-modern EXE=fairy-stockfish-largeboards_x86-64-modern.exe largeboards=yes && strip fairy-stockfish-largeboards_x86-64-modern.exe - name: make largeboards x86-64-bmi2 run: cd src && make clean && make -j build COMP=mingw ARCH=x86-64-bmi2 EXE=fairy-stockfish-largeboards_x86-64-bmi2.exe largeboards=yes && strip fairy-stockfish-largeboards_x86-64-bmi2.exe - uses: actions/upload-artifact@v2 with: name: fairy-stockfish path: src/fairy-stockfish*.exe Fairy-Stockfish-fairy_sf_14_0_1_xq/.github/workflows/fairy.yml000066400000000000000000000044311414571233100245730ustar00rootroot00000000000000name: fairy on: push: branches: - master pull_request: branches: - master jobs: fairy: name: ${{ matrix.config.name }} runs-on: ${{ matrix.config.os }} env: COMPILER: ${{ matrix.config.compiler }} COMP: ${{ matrix.config.comp }} CXXFLAGS: "-Werror" strategy: matrix: config: - { name: "Ubuntu 20.04 GCC", os: ubuntu-20.04, compiler: g++, comp: gcc, run_expensive_tests: true } - { name: "Ubuntu 20.04 Clang", os: ubuntu-20.04, compiler: clang++, comp: clang, run_expensive_tests: false } defaults: run: working-directory: src steps: - uses: actions/checkout@v2 with: fetch-depth: 0 - name: Download required packages run: | sudo apt update sudo apt install expect valgrind g++-multilib - name: Download the used network from the fishtest framework run: | make net - name: Test NNUE run: | make clean make -j2 ARCH=x86-64 nnue=yes debug=yes build ./stockfish bench - name: Test NNUE largeboards run: | make clean make -j2 ARCH=x86-64 largeboards=yes nnue=yes debug=yes build ./stockfish bench - name: Build all variants run: | make clean make -j2 ARCH=x86-64 largeboards=yes all=yes debug=yes build - name: Test protocols run: | ../tests/protocol.sh - name: Test variants.ini run: | ! ./stockfish check variants.ini 2>&1 >/dev/null | grep -v "Parsing variant" - name: Test variant perft run: | ../tests/perft.sh all - name: Test variant bench run: | ./stockfish bench xiangqi ./stockfish bench shogi ./stockfish bench capablanca ./stockfish bench sittuyin - name: Test 32bit largeboards run: | if [[ "$COMP" == "gcc" ]]; then export EXTRACXXFLAGS=-Wno-class-memaccess; fi make clean make -j2 ARCH=x86-32 largeboards=yes build ../tests/perft.sh largeboard Fairy-Stockfish-fairy_sf_14_0_1_xq/.github/workflows/ffishjs.yml000066400000000000000000000021141414571233100251110ustar00rootroot00000000000000name: ffishjs on: push: branches: [ master ] pull_request: branches: [ master ] env: EM_VERSION: 1.39.16 EM_CACHE_FOLDER: 'emsdk-cache' jobs: test: runs-on: ubuntu-20.04 strategy: matrix: node-version: [12.x] steps: - uses: actions/checkout@v2 - name: Setup cache id: cache-system-libraries uses: actions/cache@v2 with: path: ${{env.EM_CACHE_FOLDER}} key: emsdk-${{env.EM_VERSION}}-${{ runner.os }} - uses: mymindstorm/setup-emsdk@v7 with: version: ${{env.EM_VERSION}} actions-cache-folder: ${{env.EM_CACHE_FOLDER}} - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - name: Build ffishjs working-directory: src run: make -f Makefile_js build debug=yes - name: Install dependencies working-directory: tests/js run: npm install - name: Run unit tests working-directory: tests/js run: npm test Fairy-Stockfish-fairy_sf_14_0_1_xq/.github/workflows/stockfish.yml000066400000000000000000000125561414571233100254650ustar00rootroot00000000000000name: Stockfish on: push: branches: - master - tools - github_ci pull_request: branches: - master - tools jobs: Stockfish: name: ${{ matrix.config.name }} runs-on: ${{ matrix.config.os }} env: COMPILER: ${{ matrix.config.compiler }} COMP: ${{ matrix.config.comp }} CXXFLAGS: "-Werror" strategy: matrix: config: - { name: "Ubuntu 20.04 GCC", os: ubuntu-20.04, compiler: g++, comp: gcc, run_expensive_tests: true } - { name: "Ubuntu 20.04 Clang", os: ubuntu-20.04, compiler: clang++, comp: clang, run_expensive_tests: false } defaults: run: working-directory: src steps: - uses: actions/checkout@v2 with: fetch-depth: 0 - name: Download required packages run: | sudo apt update sudo apt install expect valgrind g++-multilib - name: Download the used network from the fishtest framework run: | make net - name: Extract the bench number from the commit history run: | git log HEAD | grep "\b[Bb]ench[ :]\+[0-9]\{7\}" | head -n 1 | sed "s/[^0-9]*\([0-9]*\).*/\1/g" > git_sig [ -s git_sig ] && echo "benchref=$(cat git_sig)" >> $GITHUB_ENV && echo "Reference bench:" $(cat git_sig) || echo "No bench found" - name: Check compiler run: | $COMPILER -v - name: Test help target run: | make help # x86-32 tests - name: Test debug x86-32 build run: | export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG" make clean make -j2 ARCH=x86-32 optimize=no debug=yes build ../tests/signature.sh $benchref - name: Test x86-32 build run: | make clean make -j2 ARCH=x86-32 build ../tests/signature.sh $benchref - name: Test x86-32-sse41-popcnt build run: | make clean make -j2 ARCH=x86-32-sse41-popcnt build ../tests/signature.sh $benchref - name: Test x86-32-sse2 build run: | make clean make -j2 ARCH=x86-32-sse2 build ../tests/signature.sh $benchref - name: Test general-32 build run: | make clean make -j2 ARCH=general-32 build ../tests/signature.sh $benchref # x86-64 tests - name: Test debug x86-64-modern build run: | export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG" make clean make -j2 ARCH=x86-64-modern optimize=no debug=yes build ../tests/signature.sh $benchref - name: Test x86-64-modern build run: | make clean make -j2 ARCH=x86-64-modern build ../tests/signature.sh $benchref - name: Test x86-64-ssse3 build run: | make clean make -j2 ARCH=x86-64-ssse3 build ../tests/signature.sh $benchref - name: Test x86-64-sse3-popcnt build run: | make clean make -j2 ARCH=x86-64-sse3-popcnt build ../tests/signature.sh $benchref - name: Test x86-64 build run: | make clean make -j2 ARCH=x86-64 build ../tests/signature.sh $benchref - name: Test general-64 build run: | make clean make -j2 ARCH=general-64 build ../tests/signature.sh $benchref # x86-64 with newer extensions tests - name: Compile x86-64-avx2 build run: | make clean make -j2 ARCH=x86-64-avx2 build - name: Compile x86-64-bmi2 build run: | make clean make -j2 ARCH=x86-64-bmi2 build - name: Compile x86-64-avx512 build run: | make clean make -j2 ARCH=x86-64-avx512 build - name: Compile x86-64-vnni512 build run: | make clean make -j2 ARCH=x86-64-vnni512 build - name: Compile x86-64-vnni256 build run: | make clean make -j2 ARCH=x86-64-vnni256 build # Other tests - name: Check perft and search reproducibility run: | make clean make -j2 ARCH=x86-64-modern build ../tests/perft.sh ../tests/reprosearch.sh # Sanitizers - name: Run under valgrind if: ${{ matrix.config.run_expensive_tests }} run: | export CXXFLAGS="-O1 -fno-inline" make clean make -j2 ARCH=x86-64-modern debug=yes optimize=no build > /dev/null ../tests/instrumented.sh --valgrind ../tests/instrumented.sh --valgrind-thread - name: Run with UB sanitizer if: ${{ matrix.config.run_expensive_tests }} run: | export CXXFLAGS="-O1 -fno-inline" make clean make -j2 ARCH=x86-64-modern sanitize=undefined optimize=no debug=yes build > /dev/null ../tests/instrumented.sh --sanitizer-undefined - name: Run with thread sanitizer if: ${{ matrix.config.run_expensive_tests }} run: | export CXXFLAGS="-O1 -fno-inline" make clean make -j2 ARCH=x86-64-modern sanitize=thread optimize=no debug=yes build > /dev/null ../tests/instrumented.sh --sanitizer-thread Fairy-Stockfish-fairy_sf_14_0_1_xq/.gitignore000066400000000000000000000004511414571233100213270ustar00rootroot00000000000000# Files from build **/*.o **/*.s src/.depend # Built binary src/stockfish* src/-lstdc++.res # Neural network for the NNUE evaluation **/*.nnue # Build/test artifacts *.exe tests/syzygy # ffishjs tests/js/node_modules ffish.js *.wasm # pyffish venv *.so pyffish.egg-info # IDEs .vscode .ideaFairy-Stockfish-fairy_sf_14_0_1_xq/AUTHORS000066400000000000000000000113431414571233100204110ustar00rootroot00000000000000# Fairy-Stockfish authors # Main author Fabian Fichter (ianfab) # Contributors in alphabetical order alwey Belzedar94 CouchTomato87 Fulmene gbtami majormink mtaktikos QueensGambit thearst3rd tttak yoav-rozin ydirson # List of authors for Stockfish, as of June 14, 2021 # 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) Alfredo Menezes (lonfom169) Ali AlZhrani (Cooffe) Andrew Grant (AndyGrant) Andrey Neporada (nepal) Andy Duplain Antoine Champion (antoinechampion) Aram Tumanian (atumanian) Arjun Temurnikar Artem Solopiy (EntityFX) Auguste Pop Balint Pfliegel Ben Koshy (BKSpurgeon) Bill Henry (VoyagerOne) Bojun Guo (noobpwnftw, Nooby) braich Brian Sheppard (SapphireBrand, briansheppard-toast) Bruno de Melo Costa (BM123499) 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) Deshawn Mohan-Smith (GoldenRare) Dieter Dobbelaere (ddobbelaere) DiscanX Dominik Schlösser (domschl) double-beep Douglas Matos Gomes (dsmsgms) 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 Buladas Dumale (SFisGOD) Joost VandeVondele (vondele) Jörg Oster (joergoster) Joseph Ellis (jhellis3) Joseph R. Prostko Julian Willemer (NightlyKing) jundery Justin Blanchard (UncombedCoconut) Kelly Wilson Ken Takusagawa kinderchocolate Kiran Panditrao (Krgp) Kojirion Krystian Kuzniarek (kuzkry) 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 Matt Ginsberg (mattginsberg) Matthew Lai (matthewlai) Matthew Sullivan (Matt14916) Maxim Molchanov (Maxim) 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) Siad Daboul (Topologist) Stefan Geschwentner (locutus2) Stefano Cardanobile (Stefano80) Steinar Gunderson (sesse) Stéphane Nicolet (snicolet) Prokop Randáček (ProkopRandacek) 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 Fairy-Stockfish-fairy_sf_14_0_1_xq/Copying.txt000066400000000000000000001057551414571233100215250ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . Fairy-Stockfish-fairy_sf_14_0_1_xq/MANIFEST.in000066400000000000000000000001161414571233100210730ustar00rootroot00000000000000include AUTHORS Copying.txt Readme.md test.py recursive-include src *.h *.cpp Fairy-Stockfish-fairy_sf_14_0_1_xq/README.md000066400000000000000000000637451414571233100206350ustar00rootroot00000000000000# Fairy-Stockfish ## Overview [![Build Status](https://github.com/ianfab/Fairy-Stockfish/workflows/build/badge.svg?branch=master)](https://github.com/ianfab/Fairy-Stockfish/actions?query=workflow%3Abuild) [![Build Status](https://github.com/ianfab/Fairy-Stockfish/workflows/fairy/badge.svg?branch=master)](https://github.com/ianfab/Fairy-Stockfish/actions?query=workflow%3Afairy) [![Build Status](https://ci.appveyor.com/api/projects/status/github/ianfab/Fairy-Stockfish?branch=master&svg=true)](https://ci.appveyor.com/project/ianfab/Fairy-Stockfish/branch/master) [![PyPI version](https://badge.fury.io/py/pyffish.svg)](https://badge.fury.io/py/pyffish) [![NPM version](https://img.shields.io/npm/v/ffish.svg?sanitize=true)](https://www.npmjs.com/package/ffish) Fairy-Stockfish is a chess variant engine derived from [Stockfish](https://github.com/official-stockfish/Stockfish/) designed for the support of fairy chess variants and easy extensibility with more games. It can play various regional, historical, and modern chess variants as well as [games with user-defined rules](https://github.com/ianfab/Fairy-Stockfish/wiki/Variant-configuration). For [compatibility with graphical user interfaces](https://github.com/ianfab/Fairy-Stockfish/wiki/Graphical-user-interfaces) it supports the UCI, UCCI, USI, UCI-cyclone, and CECP/XBoard protocols. The goal of the project is to create an engine supporting a large variety of chess-like games, equipped with the powerful search of Stockfish. Despite its generality the [playing strength](https://github.com/ianfab/Fairy-Stockfish/wiki/Playing-strength) is on a very high level in almost all supported variants. Due to its multi-protocol support Fairy-Stockfish works with almost any chess variant GUI. ## Installation You can download the [Windows executable](https://github.com/ianfab/Fairy-Stockfish/releases/latest/download/fairy-stockfish-largeboard_x86-64.exe) or [Linux binary](https://github.com/ianfab/Fairy-Stockfish/releases/latest/download/fairy-stockfish-largeboard_x86-64) from the [latest release](https://github.com/ianfab/Fairy-Stockfish/releases/latest) or [compile the program from source](https://github.com/ianfab/Fairy-Stockfish#compiling-stockfish-yourself-from-the-sources). The program comes without a graphical user interface, so you perhaps want to use it together with a [compatible GUI](https://github.com/ianfab/Fairy-Stockfish/wiki/Graphical-user-interfaces), or [play against it online](https://github.com/ianfab/Fairy-Stockfish/wiki/Online) at [pychess](https://www.pychess.org/), [lishogi](https://lishogi.org/@/Fairy-Stockfish), [xichess](http://www.xichess.com/), or [lichess](https://lichess.org/@/Fairy-Stockfish). Read more about [how to use](https://github.com/ianfab/Fairy-Stockfish/wiki/Usage) Fairy-Stockfish in the wiki. Optional NNUE evaluation parameter files to improve playing strength for many variants can be obtained via [patreon](https://www.patreon.com/ianfab), also see the [variant NNUE overview](https://docs.google.com/spreadsheets/d/1fgGvBKleUOI1wZZbiNhpT98qhQ7mYuQ5kar1lTQ9g3w/edit?usp=sharing). See the [wiki](https://github.com/ianfab/Fairy-Stockfish/wiki/NNUE) for more details on NNUE. ## Contributing If you like this project, please support its development via [patreon](https://www.patreon.com/ianfab) or [paypal](https://paypal.me/FairyStockfish), by [contributing CPU time](https://github.com/ianfab/fishtest/wiki) to the framework for testing of code improvements, or by [contributing to the code](https://github.com/ianfab/Fairy-Stockfish/wiki/Contributing) or documentation. An [introduction to the code base](https://github.com/ianfab/Fairy-Stockfish/wiki/Understanding-the-code) can be found in the wiki. ## Supported games The games currently supported besides chess are listed below. Fairy-Stockfish can also play user-defined variants loaded via a variant configuration file, see the file [`src/variants.ini`](https://github.com/ianfab/Fairy-Stockfish/blob/master/src/variants.ini) and the [wiki](https://github.com/ianfab/Fairy-Stockfish/wiki/Variant-configuration). ### Regional and historical games - [Xiangqi](https://en.wikipedia.org/wiki/Xiangqi), [Manchu](https://en.wikipedia.org/wiki/Manchu_chess), [Minixiangqi](http://mlwi.magix.net/bg/minixiangqi.htm), [Supply chess](https://en.wikipedia.org/wiki/Xiangqi#Variations) - [Shogi](https://en.wikipedia.org/wiki/Shogi), [Shogi variants](https://github.com/ianfab/Fairy-Stockfish#shogi-variants) - [Janggi](https://en.wikipedia.org/wiki/Janggi) - [Makruk](https://en.wikipedia.org/wiki/Makruk), [ASEAN](http://hgm.nubati.net/rules/ASEAN.html), Makpong, Ai-Wok - [Ouk Chatrang](https://en.wikipedia.org/wiki/Makruk#Cambodian_chess), [Kar Ouk](https://en.wikipedia.org/wiki/Makruk#Cambodian_chess) - [Sittuyin](https://en.wikipedia.org/wiki/Sittuyin) - [Shatar](https://en.wikipedia.org/wiki/Shatar), [Jeson Mor](https://en.wikipedia.org/wiki/Jeson_Mor) - [Shatranj](https://en.wikipedia.org/wiki/Shatranj), [Courier](https://en.wikipedia.org/wiki/Courier_chess) ### Chess variants - [Capablanca](https://en.wikipedia.org/wiki/Capablanca_Chess), [Janus](https://en.wikipedia.org/wiki/Janus_Chess), [Modern](https://en.wikipedia.org/wiki/Modern_Chess_(chess_variant)), [Chancellor](https://en.wikipedia.org/wiki/Chancellor_Chess), [Embassy](https://en.wikipedia.org/wiki/Embassy_Chess), [Gothic](https://www.chessvariants.com/large.dir/gothicchess.html), [Capablanca random chess](https://en.wikipedia.org/wiki/Capablanca_Random_Chess) - [Grand](https://en.wikipedia.org/wiki/Grand_Chess), [Shako](https://www.chessvariants.com/large.dir/shako.html), [Centaur](https://www.chessvariants.com/large.dir/contest/royalcourt.html), [Tencubed](https://www.chessvariants.com/contests/10/tencubedchess.html), [Opulent](https://www.chessvariants.com/rules/opulent-chess) - [Chess960](https://en.wikipedia.org/wiki/Chess960), [Placement/Pre-Chess](https://www.chessvariants.com/link/placement-chess) - [Crazyhouse](https://en.wikipedia.org/wiki/Crazyhouse), [Loop](https://en.wikipedia.org/wiki/Crazyhouse#Variations), [Chessgi](https://en.wikipedia.org/wiki/Crazyhouse#Variations), [Pocket Knight](http://www.chessvariants.com/other.dir/pocket.html), Capablanca-Crazyhouse - [Bughouse](https://en.wikipedia.org/wiki/Bughouse_chess), [Koedem](http://schachclub-oetigheim.de/wp-content/uploads/2016/04/Koedem-rules.pdf) - [Seirawan](https://en.wikipedia.org/wiki/Seirawan_chess), Seirawan-Crazyhouse - [Amazon](https://www.chessvariants.com/diffmove.dir/amazone.html), [Chigorin](https://www.chessvariants.com/diffsetup.dir/chigorin.html), [Almost chess](https://en.wikipedia.org/wiki/Almost_Chess) - [Hoppel-Poppel](http://www.chessvariants.com/diffmove.dir/hoppel-poppel.html), New Zealand - [Antichess](https://lichess.org/variant/antichess), [Giveaway](http://www.chessvariants.com/diffobjective.dir/giveaway.old.html), [Suicide](https://www.freechess.org/Help/HelpFiles/suicide_chess.html), [Losers](https://www.chessclub.com/help/Wild17), [Codrus](http://www.binnewirtz.com/Schlagschach1.htm) - [Extinction](https://en.wikipedia.org/wiki/Extinction_chess), [Kinglet](https://en.wikipedia.org/wiki/V._R._Parton#Kinglet_chess), Three Kings, [Coregal](https://www.chessvariants.com/winning.dir/coregal.html) - [King of the Hill](https://en.wikipedia.org/wiki/King_of_the_Hill_(chess)), [Racing Kings](https://en.wikipedia.org/wiki/V._R._Parton#Racing_Kings) - [Three-check](https://en.wikipedia.org/wiki/Three-check_chess), Five-check - [Los Alamos](https://en.wikipedia.org/wiki/Los_Alamos_chess), [Gardner's Minichess](https://en.wikipedia.org/wiki/Minichess#5%C3%975_chess) - [Atomic](https://en.wikipedia.org/wiki/Atomic_chess) - [Horde](https://en.wikipedia.org/wiki/Dunsany%27s_Chess#Horde_Chess), [Maharajah and the Sepoys](https://en.wikipedia.org/wiki/Maharajah_and_the_Sepoys) - [Knightmate](https://www.chessvariants.com/diffobjective.dir/knightmate.html), [Nightrider](https://en.wikipedia.org/wiki/Nightrider_(chess)), [Grasshopper](https://en.wikipedia.org/wiki/Grasshopper_chess) ### Shogi variants - [Minishogi](https://en.wikipedia.org/wiki/Minishogi), [EuroShogi](https://en.wikipedia.org/wiki/EuroShogi), [Judkins shogi](https://en.wikipedia.org/wiki/Judkins_shogi) - [Kyoto shogi](https://en.wikipedia.org/wiki/Kyoto_shogi), [Microshogi](https://en.wikipedia.org/wiki/Micro_shogi) - [Dobutsu shogi](https://en.wikipedia.org/wiki/Dōbutsu_shōgi), [Goro goro shogi](https://en.wikipedia.org/wiki/D%C5%8Dbutsu_sh%C5%8Dgi#Variation) - [Tori shogi](https://en.wikipedia.org/wiki/Tori_shogi) - [Yari shogi](https://en.wikipedia.org/wiki/Yari_shogi) - [Okisaki shogi](https://en.wikipedia.org/wiki/Okisaki_shogi) - [Sho shogi](https://en.wikipedia.org/wiki/Sho_shogi) ### Related games - [Amazons](https://en.wikipedia.org/wiki/Game_of_the_Amazons) - [Ataxx](https://en.wikipedia.org/wiki/Ataxx) - [Breakthrough](https://en.wikipedia.org/wiki/Breakthrough_(board_game)) - [Clobber](https://en.wikipedia.org/wiki/Clobber) - [Cfour](https://en.wikipedia.org/wiki/Connect_Four), [Tic-tac-toe](https://en.wikipedia.org/wiki/Tic-tac-toe) - [Flipersi](https://en.wikipedia.org/wiki/Reversi), [Flipello](https://en.wikipedia.org/wiki/Reversi#Othello) ## Help See the [Fairy-Stockfish Wiki](https://github.com/ianfab/Fairy-Stockfish/wiki) for more info, or if the required information is not available, open an [issue](https://github.com/ianfab/Fairy-Stockfish/issues) or join our [discord server](https://discord.gg/FYUGgmCFB4). ## Bindings Besides the C++ engine, this project also includes bindings for other programming languages in order to be able to use it as a library for chess variants. They support move, SAN, and FEN generation, as well as checking of game end conditions for all variants supported by Fairy-Stockfish. Since the bindings are using the C++ code, they are very performant compared to libraries directly written in the respective target language. ### Python The python binding [pyffish](https://pypi.org/project/pyffish/) contributed by [@gbtami](https://github.com/gbtami) is implemented in [pyffish.cpp](https://github.com/ianfab/Fairy-Stockfish/blob/master/src/pyffish.cpp). It is e.g. used in the backend for the [pychess server](https://github.com/gbtami/pychess-variants). ### Javascript The javascript binding ffish.js contributed by [@QueensGambit](https://github.com/QueensGambit) is implemented in [ffishjs.cpp](https://github.com/ianfab/Fairy-Stockfish/blob/master/src/ffishjs.cpp). The compilation/binding to javascript is done using emscripten, see the [readme](https://github.com/ianfab/Fairy-Stockfish/tree/master/tests/js). ## Ports ### WASM A port of Fairy-Stockfish to WebAssembly is maintained at https://github.com/ianfab/stockfish.wasm. # Stockfish ## Overview [![Build Status](https://github.com/official-stockfish/Stockfish/actions/workflows/stockfish.yml/badge.svg)](https://github.com/official-stockfish/Stockfish/actions) [![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 updatable 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](https://github.com/official-stockfish/Stockfish/blob/master/README.md), the file you are currently reading. * [Copying.txt](https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt), a text file containing the GNU General Public License version 3. * [AUTHORS](https://github.com/official-stockfish/Stockfish/blob/master/AUTHORS), a text file with the list of authors for the project * [src](https://github.com/official-stockfish/Stockfish/tree/master/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. ## The UCI protocol and available options The Universal Chess Interface (UCI) is a standard protocol used to communicate with a chess engine, and is the recommended way to do so for typical graphical user interfaces (GUI) or chess tools. Stockfish implements the majority of it options as described in [the UCI protocol](https://www.shredderchess.com/download/div/uci.zip). Developers can see the default values for UCI options available in Stockfish by typing `./stockfish uci` in a terminal, but the majority of users will typically see them and change them via a chess GUI. This is a list of available UCI options in Stockfish: * #### 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. * #### Clear Hash Clear the hash table. * #### Ponder Let Stockfish ponder its next move while the opponent is thinking. * #### MultiPV Output the N best lines (principal variations, PVs) when searching. Leave at 1 for best performance. * #### 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 aggressively if you experience too much slowdown (in terms of nps) due to tablebase 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). * #### 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. * #### Debug Log File Write all communication to and from the engine into a text file. For developers the following non-standard commands might be of interest, mainly useful for debugging: * #### bench *ttSize threads limit fenFile limitType evalType* Performs a standard benchmark using various options. The signature of a version (standard node count) is obtained using all defaults. `bench` is currently `bench 16 1 13 default depth mixed`. * #### compiler Give information about the compiler and environment used for building a binary. * #### d Display the current position, with ascii art and fen. * #### eval Return the evaluation of the current position. * #### export_net [filename] Exports the currently loaded network to a file. If the currently loaded network is the embedded network and the filename is not specified then the network is saved to the file matching the name of the embedded network, as defined in evaluate.h. If the currently loaded network is not the embedded network (some net set through the UCI setoption) then the filename parameter is required and the network is saved into that file. * #### flip Flips the side to move. ## A note on classical evaluation versus 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 evaluations 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 much stronger playing strength, even if the nodes per second computed by the engine is somewhat lower (roughly 80% of nps is typical). Notes: 1) the NNUE evaluation depends on the Stockfish binary and the network parameter file (see the EvalFile UCI option). Not every parameter file is compatible with a given Stockfish binary, but the default value of the EvalFile UCI option is the name of a network that is guaranteed to be compatible with that binary. 2) 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. ## What to expect from the Syzygy tablebases? 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 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 the Syzygy tablebases 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 the Syzygy tablebases 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 nodes per second, 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, then run [RAMMap](https://docs.microsoft.com/en-us/sysinternals/downloads/rammap) to double-check that large pages are used. We suggest that you reboot your computer after you have enabled large pages, because long Windows sessions suffer from memory fragmentation, which may prevent Stockfish from getting large pages: a fresh session is better in this regard. ## 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 net make build ARCH=x86-64-modern ``` 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 Stockfish version and which compiler you used to create your executable. This information can be found by typing the following command in a console: ``` ./stockfish compiler ``` ## Understanding the code base and participating in the project Stockfish's improvement over the last decade 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 these days mainly in the [FishCooking](https://groups.google.com/forum/#!forum/fishcooking) group and on the [Stockfish Discord channel](https://discord.gg/nv8gDtt). The 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 you are free to do almost exactly what you want with the program, including distributing it among your friends, making it available for download from your website, selling it (either by itself or as part of some bigger software package), or using it as the starting point for a software project of your own. The only real limitation is that whenever you distribute Stockfish in some way, you MUST always include the full source code, or a pointer to where the source code can be found, to generate the exact binary you are distributing. If you make any changes to the source code, these changes must also be made available under the GPL. For full details, read the copy of the GPL v3 found in the file named [*Copying.txt*](https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt). Fairy-Stockfish-fairy_sf_14_0_1_xq/Top CPU Contributors.txt000066400000000000000000000253551414571233100237420ustar00rootroot00000000000000Contributors to Fishtest with >10,000 CPU hours, as of Jun 29, 2021. Thank you! Username CPU Hours Games played ----------------------------------------------------- noobpwnftw 27649494 1834734733 mlang 1426107 89454622 dew 1380910 82831648 mibere 703840 46867607 grandphish2 692707 41737913 tvijlbrief 669642 42371594 JojoM 597778 35297180 TueRens 519226 31823562 cw 458421 30307421 fastgm 439667 25950040 gvreuls 436599 28177460 crunchy 427035 27344275 CSU_Dynasty 374765 25106278 Fisherman 326901 21822979 ctoks 325477 21767943 velislav 295343 18844324 linrock 292789 10624427 bcross 278584 19488961 okrout 262818 13803272 pemo 245982 11376085 glinscott 217799 13780820 leszek 212346 12959025 nordlandia 211692 13484886 bking_US 198894 11876016 drabel 196463 13450602 robal 195473 12375650 mgrabiak 187226 12016564 Dantist 183202 10990484 Thanar 179852 12365359 vdv 175274 9889046 spams 157128 10319326 marrco 150295 9402141 sqrt2 147963 9724586 mhoram 141278 8901241 CoffeeOne 137100 5024116 vdbergh 137041 8926915 malala 136182 8002293 xoto 133702 9156676 davar 122092 7960001 dsmith 122059 7570238 Data 113305 8220352 BrunoBanani 112960 7436849 MaZePallas 102823 6633619 sterni1971 100532 5880772 ElbertoOne 99028 7023771 brabos 92118 6186135 oz 92100 6486640 psk 89957 5984901 amicic 89156 5392305 sunu 88851 6028873 Vizvezdenec 83761 5344740 0x3C33 82614 5271253 BRAVONE 81239 5054681 racerschmacer 80899 5759262 cuistot 80300 4606144 nssy 76497 5259388 teddybaer 75125 5407666 Pking_cda 73776 5293873 jromang 72192 5057715 solarlight 70517 5028306 dv8silencer 70287 3883992 Bobo1239 68515 4652287 manap 66273 4121774 skiminki 65088 4023328 tinker 64333 4268790 sschnee 60767 3500800 qurashee 57344 3168264 robnjr 57262 4053117 Freja 56938 3733019 ttruscott 56010 3680085 rkl 55132 4164467 renouve 53811 3501516 finfish 51360 3370515 eva42 51272 3599691 rap 49985 3219146 pb00067 49727 3298270 ronaldjerum 47654 3240695 bigpen0r 47653 3335327 eastorwest 47585 3221629 biffhero 46564 3111352 VoyagerOne 45476 3452465 yurikvelo 44834 3034550 speedycpu 43842 3003273 jbwiebe 43305 2805433 Spprtr 42279 2680153 DesolatedDodo 42007 2447516 Antihistamine 41788 2761312 mhunt 41735 2691355 homyur 39893 2850481 gri 39871 2515779 Fifis 38776 2529121 oryx 38724 2966648 SC 37290 2731014 csnodgrass 36207 2688994 jmdana 36157 2210661 strelock 34716 2074055 rpngn 33951 2057395 Garf 33922 2751802 EthanOConnor 33370 2090311 slakovv 32915 2021889 manapbk 30987 1810399 Prcuvu 30377 2170122 anst 30301 2190091 jkiiski 30136 1904470 hyperbolic.tom 29840 2017394 Pyafue 29650 1902349 Wolfgang 29260 1658936 zeryl 28156 1579911 OuaisBla 27636 1578800 DMBK 27051 1999456 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 rodneyc 24227 1409514 agg177 23890 1395014 JanErik 23408 1703875 Isidor 23388 1680691 Norabor 23164 1591830 cisco2015 22897 1762669 Zirie 22542 1472937 team-oh 22272 1636708 MazeOfGalious 21978 1629593 sg4032 21947 1643265 ianh2105 21725 1632562 xor12 21628 1680365 dex 21612 1467203 nesoneg 21494 1463031 sphinx 21211 1384728 jjoshua2 21001 1423089 horst.prack 20878 1465656 Ente 20865 1477066 0xB00B1ES 20590 1208666 j3corre 20405 941444 Adrian.Schmidt123 20316 1281436 wei 19973 1745989 MaxKlaxxMiner 19850 1009176 rstoesser 19569 1293588 gopeto 19491 1174952 eudhan 19274 1283717 jundery 18445 1115855 megaman7de 18377 1067540 iisiraider 18247 1101015 ville 17883 1384026 chris 17698 1487385 purplefishies 17595 1092533 dju 17353 978595 DragonLord 17014 1162790 IgorLeMasson 16064 1147232 ako027ako 15671 1173203 chuckstablers 15289 891576 Nikolay.IT 15154 1068349 Andrew Grant 15114 895539 OssumOpossum 14857 1007129 Karby 14808 867120 enedene 14476 905279 bpfliegel 14298 884523 mpx86 14019 759568 jpulman 13982 870599 crocogoat 13803 1117422 joster 13794 950160 Nesa92 13786 1114691 Hjax 13535 915487 jsys14 13459 785000 Dark_wizzie 13422 1007152 mabichito 12903 749391 thijsk 12886 722107 AdrianSA 12860 804972 Flopzee 12698 894821 fatmurphy 12547 853210 Rudolphous 12520 832340 scuzzi 12511 845761 SapphireBrand 12416 969604 modolief 12386 896470 Machariel 12335 810784 pgontarz 12151 848794 stocky 11954 699440 mschmidt 11941 803401 Maxim 11543 836024 infinity 11470 727027 torbjo 11395 729145 Thomas A. Anderson 11372 732094 savage84 11358 670860 d64 11263 789184 MooTheCow 11237 720174 snicolet 11106 869170 ali-al-zhrani 11086 767926 AndreasKrug 10875 887457 pirt 10806 836519 basepi 10637 744851 michaelrpg 10508 739039 dzjp 10343 732529 aga 10302 622975 ols 10259 570669 lbraesch 10252 647825 FormazChar 10059 757283 Fairy-Stockfish-fairy_sf_14_0_1_xq/appveyor.yml000066400000000000000000000060651414571233100217360ustar00rootroot00000000000000version: 1.0.{build} clone_depth: 50 branches: only: - master - merge - appveyor # 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 -Exclude pyffish.cpp,ffishjs.cpp | 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_compile_definitions(NNUE_EMBEDDING_OFF)', '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 "(?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 } Fairy-Stockfish-fairy_sf_14_0_1_xq/appveyor_python.yml000066400000000000000000000033641414571233100233360ustar00rootroot00000000000000image: - Visual Studio 2019 environment: matrix: - PYTHON: "C:\\Python36-x64" - PYTHON: "C:\\Python37-x64" - PYTHON: "C:\\Python38-x64" - PYTHON: "C:\\Python39-x64" init: - "ECHO %PYTHON% %PYTHON_ARCH% %MSVC_VERSION%" install: - ps: | if (-not (Test-Path $env:PYTHON)) { curl -o install_python.ps1 https://raw.githubusercontent.com/matthew-brett/multibuild/11a389d78892cf90addac8f69433d5e22bfa422a/install_python.ps1 .\install_python.ps1 } - ps: if (-not (Test-Path $env:PYTHON)) { throw "No $env:PYTHON" } - "set PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - python --version # We need wheel installed to build wheels - "%PYTHON%\\python.exe -m pip install wheel" build: off test_script: # Put your test command here. # If you don't need to build C extensions on 64-bit Python 3.3 or 3.4, # you can remove "build.cmd" from the front of the command, as it's # only needed to support those cases. # Note that you must use the environment variable %PYTHON% to refer to # the interpreter you're using - Appveyor does not do anything special # to put the Python version you want to use on PATH. - "%PYTHON%\\python.exe setup.py test" after_test: # This step builds your wheels. # Again, you only need build.cmd if you're building C extensions for # 64-bit Python 3.3/3.4. And you need to use %PYTHON% to get the correct # interpreter - "%PYTHON%\\python.exe setup.py bdist_wheel" artifacts: # bdist_wheel puts your built wheel in the dist directory - path: dist\* #on_success: # You can use this step to upload your artifacts to a public website. # See Appveyor's documentation for more details. Or you can simply # access your wheels from the Appveyor "artifacts" tab for your build. Fairy-Stockfish-fairy_sf_14_0_1_xq/setup.py000066400000000000000000000031061414571233100210510ustar00rootroot00000000000000# -*- coding: utf-8 -*- from setuptools import setup, Extension from glob import glob import platform import io import os if platform.python_compiler().startswith("MSC"): args = ["/std:c++17"] else: args = ["-std=c++17", "-flto", "-Wno-date-time"] args.extend(["-DLARGEBOARDS", "-DPRECOMPUTED_MAGICS", "-DNNUE_EMBEDDING_OFF"]) if "64bit" in platform.architecture(): args.append("-DIS_64BIT") CLASSIFIERS = [ "Development Status :: 3 - Alpha", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "Programming Language :: Python :: 3", "Operating System :: OS Independent", ] with io.open("README.md", "r", encoding="utf8") as fh: long_description = fh.read().strip() sources = glob("src/*.cpp") + glob("src/syzygy/*.cpp") + glob("src/nnue/*.cpp") + glob("src/nnue/features/*.cpp") ffish_source_file = os.path.normcase("src/ffishjs.cpp") try: sources.remove(ffish_source_file) except ValueError: print(f"ffish_source_file {ffish_source_file} was not found in sources {sources}.") pyffish_module = Extension( "pyffish", sources=sources, extra_compile_args=args) setup(name="pyffish", version="0.0.66", description="Fairy-Stockfish Python wrapper", long_description=long_description, long_description_content_type="text/markdown", author="Bajusz Tamás", author_email="gbtami@gmail.com", license="GPL3", classifiers=CLASSIFIERS, url="https://github.com/gbtami/Fairy-Stockfish", python_requires=">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*", ext_modules=[pyffish_module] ) Fairy-Stockfish-fairy_sf_14_0_1_xq/src/000077500000000000000000000000001414571233100201265ustar00rootroot00000000000000Fairy-Stockfish-fairy_sf_14_0_1_xq/src/Makefile000066400000000000000000000633011414571233100215710ustar00rootroot00000000000000# Stockfish, a UCI chess playing engine derived from Glaurung 2.1 # Copyright (C) 2004-2021 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 . ### ========================================================================== ### 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 ifeq ($(SDE_PATH),) PGOBENCH = ./$(EXE) bench else PGOBENCH = $(SDE_PATH) -- ./$(EXE) bench endif ### Source and object files SRCS = benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp main.cpp \ material.cpp misc.cpp movegen.cpp movepick.cpp pawns.cpp position.cpp psqt.cpp \ search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \ nnue/evaluate_nnue.cpp nnue/features/half_ka_v2.cpp \ partner.cpp parser.cpp piece.cpp variant.cpp xboard.cpp \ nnue/features/half_ka_v2_variants.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 = none/ ... (-fsanitize ) # --- ( undefined ) --- enable undefined behavior checks # --- ( thread ) --- enable threading error checks # --- ( address ) --- enable memory access checks # --- ...etc... --- see compiler documentation for supported sanitizers # optimize = yes/no --- (-O3/-fast etc.) --- Enable/Disable optimizations # arch = (name) --- (-arch) --- Target architecture # bits = 64/32 --- -DIS_64BIT --- 64-/32-bit operating system # prefetch = yes/no --- -DUSE_PREFETCH --- Use prefetch asm-instruction # popcnt = yes/no --- -DUSE_POPCNT --- Use popcnt asm-instruction # pext = yes/no --- -DUSE_PEXT --- Use pext x86_64 asm-instruction # sse = yes/no --- -msse --- Use Intel Streaming SIMD Extensions # mmx = yes/no --- -mmmx --- Use Intel MMX instructions # sse2 = yes/no --- -msse2 --- Use Intel Streaming SIMD Extensions 2 # ssse3 = yes/no --- -mssse3 --- Use Intel Supplemental Streaming SIMD Extensions 3 # sse41 = yes/no --- -msse4.1 --- Use Intel Streaming SIMD Extensions 4.1 # avx2 = yes/no --- -mavx2 --- Use Intel Advanced Vector Extensions 2 # 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. # # Example of use for these flags: # make build ARCH=x86-64-avx512 debug=on sanitize="address undefined" ### 2.1. General and architecture defaults largeboards = no all = no precomputedmagics = yes nnue = no load_net = $(if $(filter $(nnue),yes),net) 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 e2k \ armv7 armv7-neon armv8 apple-silicon general-64 general-32)) SUPPORTED_ARCH=true else SUPPORTED_ARCH=false endif optimize = yes debug = no sanitize = none 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 ifeq ($(findstring e2k,$(ARCH)),e2k) arch = e2k mmx = yes bits = 64 sse = yes sse2 = yes ssse3 = yes sse41 = yes popcnt = yes endif endif # Disable precomputed magics when 64-bit PEXT is available ifeq ($(pext),yes) precomputedmagics = no 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) # Ignore warning due to missing profile information from unused NNUE features CXXFLAGS += -Wno-profile-instr-out-of-date # Compile version with support for large board variants # Use precomputed magics by default if pext is not available ifneq ($(largeboards),no) CXXFLAGS += -DLARGEBOARDS ifeq ($(precomputedmagics),yes) CXXFLAGS += -DPRECOMPUTED_MAGICS endif endif # Embed and enable NNUE ifeq ($(nnue),no) CXXFLAGS += -DNNUE_EMBEDDING_OFF endif # Enable all variants, even heavyweight ones like amazons ifneq ($(all),no) CXXFLAGS += -DALLVARS endif ifeq ($(COMP),) COMP=gcc endif ifeq ($(COMP),gcc) comp=gcc CXX=g++ CXXFLAGS += -Wextra -Wshadow ifeq ($(largeboards),no) CXXFLAGS += -pedantic 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 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) ifneq ($(KERNEL),FreeBSD) LDFLAGS += -latomic endif 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 ($(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 comp=clang ifeq ($(arch),armv7) CXX=armv7a-linux-androideabi16-clang++ CXXFLAGS += -mthumb -march=armv7-a -mfloat-abi=softfp -mfpu=neon STRIP=arm-linux-androideabi-strip endif ifeq ($(arch),armv8) CXX=aarch64-linux-android21-clang++ STRIP=aarch64-linux-android-strip endif LDFLAGS += -static-libstdc++ -pie -lm -latomic 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 ### 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),none) CXXFLAGS += -g3 $(addprefix -fsanitize=,$(sanitize)) LDFLAGS += $(addprefix -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 ifeq ($(comp),clang) CXXFLAGS += -fexperimental-new-pass-manager 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),clang) CXXFLAGS += -flto 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 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 "e2k > Elbrus 2000" @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 "" @echo "-------------------------------" @echo "Embed and enable NNUE by default: " @echo "" @echo "make build ARCH=x86-64 nnue=yes" @echo "" @echo "-------------------------------" @echo "Version for large boards: " @echo "" @echo "make build ARCH=x86-64 COMP=gcc largeboards=yes" @echo "" @echo "Include all variants (adds Game of the Amazons): " @echo "" @echo "make build ARCH=x86-64 largeboards=yes all=yes" @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: $(load_net) config-sanity $(MAKE) ARCH=$(ARCH) COMP=$(COMP) all profile-build: $(load_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 @rm -f stockfish.exe.lto_wrapper_args @rm -f stockfish.exe.ltrans.out @rm -f ./-lstdc++.res default: help ### ========================================================================== ### Section 5. Private Targets ### ========================================================================== all: $(EXE) .depend config-sanity: $(load_net) @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 "Fairy-Stockfish specific:" @echo "largeboards: '$(largeboards)'" @echo "all: '$(all)'" @echo "precomputedmagics: '$(precomputedmagics)'" @echo "nnue: '$(nnue)'" @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 "$(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)" = "e2k" || \ 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: @mkdir -p profdir $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ EXTRACXXFLAGS='-fprofile-generate=profdir' \ EXTRALDFLAGS='-lgcov' \ all gcc-profile-use: $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ EXTRACXXFLAGS='-fprofile-use=profdir -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 Fairy-Stockfish-fairy_sf_14_0_1_xq/src/Makefile_js000066400000000000000000000052341414571233100222660ustar00rootroot00000000000000# ffish.js, a JavaScript chess variant library derived from Fairy-Stockfish # Copyright (C) 2021 Fabian Fichter, Johannes Czech # # ffish.js 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. # # ffish.js 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 . EXE = ../tests/js/ffish.js SRCS = ffishjs.cpp benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.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_ka_v2.cpp \ partner.cpp parser.cpp piece.cpp variant.cpp xboard.cpp \ nnue/features/half_ka_v2_variants.cpp CXX=emcc CXXFLAGS += --bind -DNNUE_EMBEDDING_OFF -DNO_THREADS -std=c++17 -Wall largeboards = yes optimize = yes debug = no ### Debugging ifeq ($(debug),no) CXXFLAGS += -DNDEBUG -s ASSERTIONS=0 -s SAFE_HEAP=0 else CXXFLAGS += -g -s ASSERTIONS=1 -s SAFE_HEAP=1 endif ### Optimization ifeq ($(optimize),yes) CXXFLAGS += -O3 endif # Compile version with support for large board variants # Use precomputed magics by default ifneq ($(largeboards),no) CXXFLAGS += -DLARGEBOARDS -DPRECOMPUTED_MAGICS -s TOTAL_MEMORY=32MB -s ALLOW_MEMORY_GROWTH=1 -s WASM_MEM_MAX=1GB endif ### Compile as ES6/ES2015 module ifeq ($(es6),yes) CXXFLAGS += -s ENVIRONMENT='web,worker' -s EXPORT_ES6=1 -s MODULARIZE=1 -s USE_ES6_IMPORT_META=0 endif .PHONY: help objclean clean build deps test serve help: @echo "" @echo "To compile ffishjs, type: " @echo "" @echo "make -f Makefile_js build" @echo "" @echo "Supported targets:" @echo "" @echo "help > Display this help" @echo "build > Standard build" @echo "clean > Clean up" @echo "deps > Install runtime dependencies using npm" @echo "test > Run tests" @echo "serve > Run example server" @echo "" objclean: @rm -f $(EXE) *.o ./syzygy/*.o ./nnue/*.o ./nnue/features/*.o clean: objclean build: $(CXX) $(CXXFLAGS) $(SRCS) -o $(EXE) deps: cd ../tests/js && npm install test: deps cd ../tests/js && npm test serve: deps cd ../tests/js && node index.js Fairy-Stockfish-fairy_sf_14_0_1_xq/src/apiutil.h000066400000000000000000001065601414571233100217560ustar00rootroot00000000000000/* Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish Copyright (C) 2018-2021 Fabian Fichter Fairy-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. Fairy-Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef APIUTIL_H_INCLUDED #define APIUTIL_H_INCLUDED #include #include #include #include #include #include #include #include "types.h" #include "position.h" #include "variant.h" namespace Stockfish { enum Notation { NOTATION_DEFAULT, // https://en.wikipedia.org/wiki/Algebraic_notation_(chess) NOTATION_SAN, NOTATION_LAN, // https://en.wikipedia.org/wiki/Shogi_notation#Western_notation NOTATION_SHOGI_HOSKING, // Examples: P76, S’34 NOTATION_SHOGI_HODGES, // Examples: P-7f, S*3d NOTATION_SHOGI_HODGES_NUMBER, // Examples: P-76, S*34 // http://www.janggi.pl/janggi-notation/ NOTATION_JANGGI, // https://en.wikipedia.org/wiki/Xiangqi#Notation NOTATION_XIANGQI_WXF, }; inline Notation default_notation(const Variant* v) { if (v->variantTemplate == "shogi") return NOTATION_SHOGI_HODGES_NUMBER; return NOTATION_SAN; } enum Termination { ONGOING, CHECKMATE, STALEMATE, INSUFFICIENT_MATERIAL, N_MOVE_RULE, N_FOLD_REPETITION, VARIANT_END, }; namespace SAN { enum Disambiguation { NO_DISAMBIGUATION, FILE_DISAMBIGUATION, RANK_DISAMBIGUATION, SQUARE_DISAMBIGUATION, }; inline bool is_shogi(Notation n) { return n == NOTATION_SHOGI_HOSKING || n == NOTATION_SHOGI_HODGES || n == NOTATION_SHOGI_HODGES_NUMBER; } // is there more than one file with a pair of pieces? inline bool multi_tandem(Bitboard b) { int tandems = 0; for (File f = FILE_A; f <= FILE_MAX; ++f) if (more_than_one(b & file_bb(f))) tandems++; return tandems >= 2; } inline std::string piece(const Position& pos, Move m, Notation n) { Color us = pos.side_to_move(); Square from = from_sq(m); Piece pc = pos.moved_piece(m); PieceType pt = type_of(pc); // Quiet pawn moves if ((n == NOTATION_SAN || n == NOTATION_LAN) && type_of(pc) == PAWN && type_of(m) != DROP) return ""; // Tandem pawns else if (n == NOTATION_XIANGQI_WXF && popcount(pos.pieces(us, pt) & file_bb(from)) >= 3 - multi_tandem(pos.pieces(us, pt))) return std::to_string(popcount(forward_file_bb(us, from) & pos.pieces(us, pt)) + 1); // Moves of promoted pieces else if (is_shogi(n) && type_of(m) != DROP && pos.unpromoted_piece_on(from)) return "+" + std::string(1, toupper(pos.piece_to_char()[pos.unpromoted_piece_on(from)])); // Promoted drops else if (is_shogi(n) && type_of(m) == DROP && dropped_piece_type(m) != in_hand_piece_type(m)) return "+" + std::string(1, toupper(pos.piece_to_char()[in_hand_piece_type(m)])); else if (pos.piece_to_char_synonyms()[pc] != ' ') return std::string(1, toupper(pos.piece_to_char_synonyms()[pc])); else return std::string(1, toupper(pos.piece_to_char()[pc])); } inline std::string file(const Position& pos, Square s, Notation n) { switch (n) { case NOTATION_SHOGI_HOSKING: case NOTATION_SHOGI_HODGES: case NOTATION_SHOGI_HODGES_NUMBER: return std::to_string(pos.max_file() - file_of(s) + 1); case NOTATION_JANGGI: return std::to_string(file_of(s) + 1); case NOTATION_XIANGQI_WXF: return std::to_string((pos.side_to_move() == WHITE ? pos.max_file() - file_of(s) : file_of(s)) + 1); default: return std::string(1, char('a' + file_of(s))); } } inline std::string rank(const Position& pos, Square s, Notation n) { switch (n) { case NOTATION_SHOGI_HOSKING: case NOTATION_SHOGI_HODGES_NUMBER: return std::to_string(pos.max_rank() - rank_of(s) + 1); case NOTATION_SHOGI_HODGES: return std::string(1, char('a' + pos.max_rank() - rank_of(s))); case NOTATION_JANGGI: return std::to_string((pos.max_rank() - rank_of(s) + 1) % 10); case NOTATION_XIANGQI_WXF: { if (pos.empty(s)) // Handle piece drops return std::to_string(relative_rank(pos.side_to_move(), s, pos.max_rank()) + 1); else if (pos.pieces(pos.side_to_move(), type_of(pos.piece_on(s))) & forward_file_bb(pos.side_to_move(), s)) return "-"; else return "+"; } default: return std::to_string(rank_of(s) + 1); } } inline std::string square(const Position& pos, Square s, Notation n) { switch (n) { case NOTATION_JANGGI: return rank(pos, s, n) + file(pos, s, n); default: return file(pos, s, n) + rank(pos, s, n); } } inline Disambiguation disambiguation_level(const Position& pos, Move m, Notation n) { // Drops never need disambiguation if (type_of(m) == DROP) return NO_DISAMBIGUATION; // NOTATION_LAN and Janggi always use disambiguation if (n == NOTATION_LAN || n == NOTATION_JANGGI) return SQUARE_DISAMBIGUATION; Color us = pos.side_to_move(); Square from = from_sq(m); Square to = to_sq(m); Piece pc = pos.moved_piece(m); PieceType pt = type_of(pc); // Xiangqi uses either file disambiguation or +/- if two pieces on file if (n == NOTATION_XIANGQI_WXF) { // Disambiguate by rank (+/-) if target square of other piece is valid if (popcount(pos.pieces(us, pt) & file_bb(from)) == 2 && !multi_tandem(pos.pieces(us, pt))) { Square otherFrom = lsb((pos.pieces(us, pt) & file_bb(from)) ^ from); Square otherTo = otherFrom + Direction(to) - Direction(from); if (is_ok(otherTo) && (pos.board_bb(us, pt) & otherTo)) return RANK_DISAMBIGUATION; } return FILE_DISAMBIGUATION; } // Pawn captures always use disambiguation if (n == NOTATION_SAN && pt == PAWN) { if (pos.capture(m)) return FILE_DISAMBIGUATION; if (type_of(m) == PROMOTION && from != to && pos.sittuyin_promotion()) return SQUARE_DISAMBIGUATION; } // A disambiguation occurs if we have more then one piece of type 'pt' // that can reach 'to' with a legal move. Bitboard b = pos.pieces(us, pt) ^ from; Bitboard others = 0; while (b) { Square s = pop_lsb(b); if ( pos.pseudo_legal(make_move(s, to)) && pos.legal(make_move(s, to)) && !(is_shogi(n) && pos.unpromoted_piece_on(s) != pos.unpromoted_piece_on(from))) others |= s; } if (!others) return NO_DISAMBIGUATION; else if (is_shogi(n)) return SQUARE_DISAMBIGUATION; else if (!(others & file_bb(from))) return FILE_DISAMBIGUATION; else if (!(others & rank_bb(from))) return RANK_DISAMBIGUATION; else return SQUARE_DISAMBIGUATION; } inline std::string disambiguation(const Position& pos, Square s, Notation n, Disambiguation d) { switch (d) { case FILE_DISAMBIGUATION: return file(pos, s, n); case RANK_DISAMBIGUATION: return rank(pos, s, n); case SQUARE_DISAMBIGUATION: return square(pos, s, n); default: assert(d == NO_DISAMBIGUATION); return ""; } } inline const std::string move_to_san(Position& pos, Move m, Notation n) { std::string san = ""; Color us = pos.side_to_move(); Square from = from_sq(m); Square to = to_sq(m); if (type_of(m) == CASTLING) { san = to > from ? "O-O" : "O-O-O"; if (is_gating(m)) { san += std::string("/") + pos.piece_to_char()[make_piece(WHITE, gating_type(m))]; san += square(pos, gating_square(m), n); } } else { // Piece san += piece(pos, m, n); // Origin square, disambiguation Disambiguation d = disambiguation_level(pos, m, n); san += disambiguation(pos, from, n, d); // Separator/Operator if (type_of(m) == DROP) san += n == NOTATION_SHOGI_HOSKING ? '\'' : is_shogi(n) ? '*' : '@'; else if (n == NOTATION_XIANGQI_WXF) { if (rank_of(from) == rank_of(to)) san += '='; else if (relative_rank(us, to, pos.max_rank()) > relative_rank(us, from, pos.max_rank())) san += '+'; else san += '-'; } else if (pos.capture(m)) san += 'x'; else if (n == NOTATION_LAN || (is_shogi(n) && (n != NOTATION_SHOGI_HOSKING || d == SQUARE_DISAMBIGUATION)) || n == NOTATION_JANGGI) san += '-'; // Destination square if (n == NOTATION_XIANGQI_WXF && type_of(m) != DROP) san += file_of(to) == file_of(from) ? std::to_string(std::abs(rank_of(to) - rank_of(from))) : file(pos, to, n); else san += square(pos, to, n); // Suffix if (type_of(m) == PROMOTION) san += std::string("=") + pos.piece_to_char()[make_piece(WHITE, promotion_type(m))]; else if (type_of(m) == PIECE_PROMOTION) san += is_shogi(n) ? std::string("+") : std::string("=") + pos.piece_to_char()[make_piece(WHITE, pos.promoted_piece_type(type_of(pos.moved_piece(m))))]; else if (type_of(m) == PIECE_DEMOTION) san += is_shogi(n) ? std::string("-") : std::string("=") + std::string(1, pos.piece_to_char()[pos.unpromoted_piece_on(from)]); else if (type_of(m) == NORMAL && is_shogi(n) && pos.pseudo_legal(make(from, to))) san += std::string("="); if (is_gating(m)) san += std::string("/") + pos.piece_to_char()[make_piece(WHITE, gating_type(m))]; } // Check and checkmate if (pos.gives_check(m) && !is_shogi(n) && n != NOTATION_XIANGQI_WXF) { StateInfo st; pos.do_move(m, st); san += MoveList(pos).size() ? "+" : "#"; pos.undo_move(m); } return san; } } // namespace SAN inline bool has_insufficient_material(Color c, const Position& pos) { // Other win rules if ( pos.captures_to_hand() || pos.count_in_hand(c, ALL_PIECES) || (pos.extinction_value() != VALUE_NONE && !pos.extinction_pseudo_royal()) || (pos.capture_the_flag_piece() && pos.count(c, pos.capture_the_flag_piece()))) return false; // Restricted pieces Bitboard restricted = pos.pieces(~c, KING); // Atomic kings can not help checkmating if (pos.extinction_pseudo_royal() && pos.blast_on_capture() && pos.extinction_piece_types().find(COMMONER) != pos.extinction_piece_types().end()) restricted |= pos.pieces(c, COMMONER); for (PieceType pt : pos.piece_types()) if (pt == KING || !(pos.board_bb(c, pt) & pos.board_bb(~c, KING))) restricted |= pos.pieces(c, pt); else if (is_custom(pt) && pos.count(c, pt) > 0) // to be conservative, assume any custom piece has mating potential return false; // Mating pieces for (PieceType pt : { ROOK, QUEEN, ARCHBISHOP, CHANCELLOR, SILVER, GOLD, COMMONER, CENTAUR }) if ((pos.pieces(c, pt) & ~restricted) || (pos.count(c, PAWN) && pos.promotion_piece_types().find(pt) != pos.promotion_piece_types().end())) return false; // Color-bound pieces Bitboard colorbound = 0, unbound; for (PieceType pt : { BISHOP, FERS, FERS_ALFIL, ALFIL, ELEPHANT }) colorbound |= pos.pieces(pt) & ~restricted; unbound = pos.pieces() ^ restricted ^ colorbound; if ((colorbound & pos.pieces(c)) && (((DarkSquares & colorbound) && (~DarkSquares & colorbound)) || unbound || pos.stalemate_value() != VALUE_DRAW || pos.check_counting() || pos.makpong())) return false; // Unbound pieces require one helper piece of either color if ((pos.pieces(c) & unbound) && (popcount(pos.pieces() ^ restricted) >= 2 || pos.stalemate_value() != VALUE_DRAW || pos.check_counting() || pos.makpong())) return false; return true; } namespace FEN { enum FenValidation : int { FEN_INVALID_COUNTING_RULE = -14, FEN_INVALID_CHECK_COUNT = -13, FEN_INVALID_NB_PARTS = -11, FEN_INVALID_CHAR = -10, FEN_TOUCHING_KINGS = -9, FEN_INVALID_BOARD_GEOMETRY = -8, FEN_INVALID_POCKET_INFO = -7, FEN_INVALID_SIDE_TO_MOVE = -6, FEN_INVALID_CASTLING_INFO = -5, FEN_INVALID_EN_PASSANT_SQ = -4, FEN_INVALID_NUMBER_OF_KINGS = -3, FEN_INVALID_HALF_MOVE_COUNTER = -2, FEN_INVALID_MOVE_COUNTER = -1, FEN_EMPTY = 0, FEN_OK = 1 }; enum Validation : int { NOK, OK }; struct CharSquare { int rowIdx; int fileIdx; CharSquare() : rowIdx(-1), fileIdx(-1) {} CharSquare(int row, int file) : rowIdx(row), fileIdx(file) {} }; inline bool operator==(const CharSquare& s1, const CharSquare& s2) { return s1.rowIdx == s2.rowIdx && s1.fileIdx == s2.fileIdx; } inline bool operator!=(const CharSquare& s1, const CharSquare& s2) { return !(s1 == s2); } inline int non_root_euclidian_distance(const CharSquare& s1, const CharSquare& s2) { return pow(s1.rowIdx - s2.rowIdx, 2) + pow(s1.fileIdx - s2.fileIdx, 2); } class CharBoard { private: int nbRanks; int nbFiles; std::vector board; // fill an array where the pieces are for later geometry checks public: CharBoard(int ranks, int files) : nbRanks(ranks), nbFiles(files) { assert(nbFiles > 0 && nbRanks > 0); board = std::vector(nbRanks * nbFiles, ' '); } void set_piece(int rankIdx, int fileIdx, char c) { board[rankIdx * nbFiles + fileIdx] = c; } char get_piece(int rowIdx, int fileIdx) const { return board[rowIdx * nbFiles + fileIdx]; } int get_nb_ranks() const { return nbRanks; } int get_nb_files() const { return nbFiles; } /// Returns the square of a given character CharSquare get_square_for_piece(char piece) const { CharSquare s; for (int r = 0; r < nbRanks; ++r) { for (int c = 0; c < nbFiles; ++c) { if (get_piece(r, c) == piece) { s.rowIdx = r; s.fileIdx = c; return s; } } } return s; } /// Returns all square positions for a given piece std::vector get_squares_for_piece(char piece) const { std::vector squares; for (int r = 0; r < nbRanks; ++r) for (int c = 0; c < nbFiles; ++c) if (get_piece(r, c) == piece) squares.emplace_back(CharSquare(r, c)); return squares; } friend std::ostream& operator<<(std::ostream& os, const CharBoard& board); }; inline std::ostream& operator<<(std::ostream& os, const CharBoard& board) { for (int r = 0; r < board.nbRanks; ++r) { for (int c = 0; c < board.nbFiles; ++c) os << "[" << board.get_piece(r, c) << "] "; os << std::endl; } return os; } inline Validation check_for_valid_characters(const std::string& firstFenPart, const std::string& validSpecialCharacters, const Variant* v) { for (char c : firstFenPart) { if (!isdigit(c) && v->pieceToChar.find(c) == std::string::npos && v->pieceToCharSynonyms.find(c) == std::string::npos && validSpecialCharacters.find(c) == std::string::npos) { std::cerr << "Invalid piece character: '" << c << "'." << std::endl; return NOK; } } return OK; } inline std::vector get_fen_parts(const std::string& fullFen, char delim) { std::vector fenParts; std::string curPart; std::stringstream ss(fullFen); while (std::getline(ss, curPart, delim)) fenParts.emplace_back(curPart); return fenParts; } /// fills the character board according to a given FEN string inline Validation fill_char_board(CharBoard& board, const std::string& fenBoard, const std::string& validSpecialCharacters, const Variant* v) { int rankIdx = 0; int fileIdx = 0; char prevChar = '?'; for (char c : fenBoard) { if (c == ' ' || c == '[') break; if (isdigit(c)) { fileIdx += c - '0'; // if we have multiple digits attached we can add multiples of 9 to compute the resulting number (e.g. -> 21 = 2 + 2 * 9 + 1) if (isdigit(prevChar)) fileIdx += 9 * (prevChar - '0'); } else if (c == '/') { ++rankIdx; if (fileIdx != board.get_nb_files()) { std::cerr << "curRankWidth != nbFiles: " << fileIdx << " != " << board.get_nb_files() << std::endl; return NOK; } if (rankIdx == board.get_nb_ranks()) break; fileIdx = 0; } else if (validSpecialCharacters.find(c) == std::string::npos) { // normal piece if (fileIdx == board.get_nb_files()) { std::cerr << "File index: " << fileIdx << " for piece '" << c << "' exceeds maximum of allowed number of files: " << board.get_nb_files() << "." << std::endl; return NOK; } board.set_piece(v->maxRank-rankIdx, fileIdx, c); // we mirror the rank index because the black pieces are given first in the FEN ++fileIdx; } prevChar = c; } if (v->pieceDrops) { // pockets can either be defined by [] or / if (rankIdx+1 != board.get_nb_ranks() && rankIdx != board.get_nb_ranks()) { std::cerr << "Invalid number of ranks. Expected: " << board.get_nb_ranks() << " Actual: " << rankIdx+1 << std::endl; return NOK; } } else { if (rankIdx+1 != board.get_nb_ranks()) { std::cerr << "Invalid number of ranks. Expected: " << board.get_nb_ranks() << " Actual: " << rankIdx+1 << std::endl; return NOK; } } return OK; } inline Validation check_touching_kings(const CharBoard& board, const std::array& kingPositions) { if (non_root_euclidian_distance(kingPositions[WHITE], kingPositions[BLACK]) <= 2) { std::cerr << "King pieces are next to each other." << std::endl; std::cerr << board << std::endl; return NOK; } return OK; } inline Validation fill_castling_info_splitted(const std::string& castlingInfo, std::array& castlingInfoSplitted) { for (char c : castlingInfo) { if (c != '-') { if (!isalpha(c)) { std::cerr << "Invalid castling specification: '" << c << "'." << std::endl; return NOK; } else if (isupper(c)) castlingInfoSplitted[WHITE] += tolower(c); else castlingInfoSplitted[BLACK] += c; } } return OK; } inline std::string color_to_string(Color c) { switch (c) { case WHITE: return "WHITE"; case BLACK: return "BLACK"; case COLOR_NB: return "COLOR_NB"; default: return "INVALID_COLOR"; } } inline std::string castling_rights_to_string(CastlingRights castlingRights) { switch (castlingRights) { case KING_SIDE: return "KING_SIDE"; case QUEEN_SIDE: return "QUEENS_SIDE"; case WHITE_OO: return "WHITE_OO"; case WHITE_OOO: return "WHITE_OOO"; case BLACK_OO: return "BLACK_OO"; case BLACK_OOO: return "BLACK_OOO"; case WHITE_CASTLING: return "WHITE_CASTLING"; case BLACK_CASTLING: return "BLACK_CASTLING"; case ANY_CASTLING: return "ANY_CASTLING"; case CASTLING_RIGHT_NB: return "CASTLING_RIGHT_NB"; default: return "INVALID_CASTLING_RIGHTS"; } } inline Validation check_castling_rank(const std::array& castlingInfoSplitted, const CharBoard& board, const std::array& kingPositions, const Variant* v) { for (Color c : {WHITE, BLACK}) { const Rank castlingRank = relative_rank(c, v->castlingRank, v->maxRank); char rookChar = v->pieceToChar[make_piece(c, v->castlingRookPiece)]; for (char castlingFlag : castlingInfoSplitted[c]) { if (tolower(castlingFlag) == 'k' || tolower(castlingFlag) == 'q') { if (kingPositions[c].rowIdx != castlingRank) { std::cerr << "The " << color_to_string(c) << " king must be on rank " << castlingRank << " if castling is enabled for " << color_to_string(c) << "." << std::endl; return NOK; } bool kingside = tolower(castlingFlag) == 'k'; bool castlingRook = false; for (int f = kingside ? board.get_nb_files() - 1 : 0; f != kingPositions[c].fileIdx; kingside ? f-- : f++) if (board.get_piece(castlingRank, f) == rookChar) { castlingRook = true; break; } if (!castlingRook) { std::cerr << "No castling rook for flag " << castlingFlag << std::endl; return NOK; } } else if (board.get_piece(castlingRank, tolower(castlingFlag) - 'a') == ' ') { std::cerr << "No gating piece for flag " << castlingFlag << std::endl; return NOK; } } } return OK; } inline Validation check_standard_castling(std::array& castlingInfoSplitted, const CharBoard& board, const std::array& kingPositions, const std::array& kingPositionsStart, const std::array, 2>& rookPositionsStart, const Variant* v) { for (Color c : {WHITE, BLACK}) { if (castlingInfoSplitted[c].size() == 0) continue; if (kingPositions[c] != kingPositionsStart[c]) { std::cerr << "The " << color_to_string(c) << " KING has moved. Castling is no longer valid for " << color_to_string(c) << "." << std::endl; return NOK; } for (CastlingRights castling: {KING_SIDE, QUEEN_SIDE}) { CharSquare rookStartingSquare = castling == QUEEN_SIDE ? rookPositionsStart[c][0] : rookPositionsStart[c][1]; char targetChar = castling == QUEEN_SIDE ? 'q' : 'k'; char rookChar = v->pieceToChar[make_piece(c, v->castlingRookPiece)]; if (castlingInfoSplitted[c].find(targetChar) != std::string::npos) { if (board.get_piece(rookStartingSquare.rowIdx, rookStartingSquare.fileIdx) != rookChar) { std::cerr << "The " << color_to_string(c) << " ROOK on the "<< castling_rights_to_string(castling) << " has moved. " << castling_rights_to_string(castling) << " castling is no longer valid for " << color_to_string(c) << "." << std::endl; return NOK; } } } } return OK; } inline Validation check_pocket_info(const std::string& fenBoard, int nbRanks, const Variant* v, std::string& pocket) { char stopChar; int offset = 0; if (std::count(fenBoard.begin(), fenBoard.end(), '/') == nbRanks) { // look for last '/' stopChar = '/'; } else if (std::count(fenBoard.begin(), fenBoard.end(), '[') == 1) { // pocket is defined as [ and ] stopChar = '['; offset = 1; if (*(fenBoard.end()-1) != ']') { std::cerr << "Pocket specification does not end with ']'." << std::endl; return NOK; } } else // allow to skip pocket return OK; // look for last '/' for (auto it = fenBoard.rbegin()+offset; it != fenBoard.rend(); ++it) { const char c = *it; if (c == stopChar) return OK; if (c != '-') { if (v->pieceToChar.find(c) == std::string::npos && v->pieceToCharSynonyms.find(c) == std::string::npos) { std::cerr << "Invalid pocket piece: '" << c << "'." << std::endl; return NOK; } else pocket += c; } } std::cerr << "Pocket piece closing character '" << stopChar << "' was not found." << std::endl; return NOK; } inline int piece_count(const std::string& fenBoard, Color c, PieceType pt, const Variant* v) { return std::count(fenBoard.begin(), fenBoard.end(), v->pieceToChar[make_piece(c, pt)]); } inline Validation check_number_of_kings(const std::string& fenBoard, const std::string& startFenBoard, const Variant* v) { int nbWhiteKings = piece_count(fenBoard, WHITE, KING, v); int nbBlackKings = piece_count(fenBoard, BLACK, KING, v); int nbWhiteKingsStart = piece_count(startFenBoard, WHITE, KING, v); int nbBlackKingsStart = piece_count(startFenBoard, BLACK, KING, v); if (nbWhiteKings != nbWhiteKingsStart) { std::cerr << "Invalid number of white kings. Expected: " << nbWhiteKingsStart << ". Given: " << nbWhiteKings << std::endl; return NOK; } if (nbBlackKings != nbBlackKingsStart) { std::cerr << "Invalid number of black kings. Expected: " << nbBlackKingsStart << ". Given: " << nbBlackKings << std::endl; return NOK; } return OK; } inline Validation check_en_passant_square(const std::string& enPassantInfo) { if (enPassantInfo.size() != 1 || enPassantInfo[0] != '-') { if (enPassantInfo.size() != 2) { std::cerr << "Invalid en-passant square '" << enPassantInfo << "'. Expects 2 characters. Actual: " << enPassantInfo.size() << " character(s)." << std::endl; return NOK; } if (!isalpha(enPassantInfo[0])) { std::cerr << "Invalid en-passant square '" << enPassantInfo << "'. Expects 1st character to be a letter." << std::endl; return NOK; } if (!isdigit(enPassantInfo[1])) { std::cerr << "Invalid en-passant square '" << enPassantInfo << "'. Expects 2nd character to be a digit." << std::endl; return NOK; } } return OK; } inline Validation check_check_count(const std::string& checkCountInfo) { if (checkCountInfo.size() != 3) { std::cerr << "Invalid check count '" << checkCountInfo << "'. Expects 3 characters. Actual: " << checkCountInfo.size() << " character(s)." << std::endl; return NOK; } if (!isdigit(checkCountInfo[0])) { std::cerr << "Invalid check count '" << checkCountInfo << "'. Expects 1st character to be a digit." << std::endl; return NOK; } if (!isdigit(checkCountInfo[2])) { std::cerr << "Invalid check count '" << checkCountInfo << "'. Expects 3rd character to be a digit." << std::endl; return NOK; } return OK; } inline Validation check_lichess_check_count(const std::string& checkCountInfo) { if (checkCountInfo.size() != 4) { std::cerr << "Invalid check count '" << checkCountInfo << "'. Expects 4 characters. Actual: " << checkCountInfo.size() << " character(s)." << std::endl; return NOK; } if (!isdigit(checkCountInfo[1]) || checkCountInfo[1] - '0' > 3) { std::cerr << "Invalid check count '" << checkCountInfo << "'. Expects 2nd character to be a digit up to 3." << std::endl; return NOK; } if (!isdigit(checkCountInfo[3]) || checkCountInfo[3] - '0' > 3) { std::cerr << "Invalid check count '" << checkCountInfo << "'. Expects 4th character to be a digit up to 3." << std::endl; return NOK; } return OK; } inline Validation check_digit_field(const std::string& field) { if (field.size() == 1 && field[0] == '-') return OK; for (char c : field) if (!isdigit(c)) return NOK; return OK; } inline FenValidation validate_fen(const std::string& fen, const Variant* v, bool chess960 = false) { const std::string validSpecialCharacters = "/+~[]-"; // 0) Layout // check for empty fen if (fen.size() == 0) { std::cerr << "Fen is empty." << std::endl; return FEN_EMPTY; } std::vector fenParts = get_fen_parts(fen, ' '); std::vector starFenParts = get_fen_parts(v->startFen, ' '); // check for number of parts const unsigned int maxNumberFenParts = 6 + v->checkCounting; if (fenParts.size() < 1 || fenParts.size() > maxNumberFenParts) { std::cerr << "Invalid number of fen parts. Expected: >= 1 and <= " << maxNumberFenParts << " Actual: " << fenParts.size() << std::endl; return FEN_INVALID_NB_PARTS; } // 1) Part // check for valid characters if (check_for_valid_characters(fenParts[0], validSpecialCharacters, v) == NOK) return FEN_INVALID_CHAR; // check for number of ranks const int nbRanks = v->maxRank + 1; // check for number of files const int nbFiles = v->maxFile + 1; CharBoard board(nbRanks, nbFiles); // create a 2D character board for later geometry checks if (fill_char_board(board, fenParts[0], validSpecialCharacters, v) == NOK) return FEN_INVALID_BOARD_GEOMETRY; // check for pocket std::string pocket = ""; if (v->pieceDrops || v->seirawanGating || v->arrowGating) { if (check_pocket_info(fenParts[0], nbRanks, v, pocket) == NOK) return FEN_INVALID_POCKET_INFO; } // check for number of kings if (v->pieceTypes.find(KING) != v->pieceTypes.end()) { // we have a royal king in this variant, // ensure that each side has exactly as many kings as in the starting position // (variants like giveaway use the COMMONER piece type instead) if (check_number_of_kings(fenParts[0], starFenParts[0], v) == NOK) return FEN_INVALID_NUMBER_OF_KINGS; // check for touching kings if there are exactly two royal kings on the board (excluding pocket) if ( v->kingType == KING && piece_count(fenParts[0], WHITE, KING, v) - piece_count(pocket, WHITE, KING, v) == 1 && piece_count(fenParts[0], BLACK, KING, v) - piece_count(pocket, BLACK, KING, v) == 1) { std::array kingPositions; kingPositions[WHITE] = board.get_square_for_piece(v->pieceToChar[make_piece(WHITE, KING)]); kingPositions[BLACK] = board.get_square_for_piece(v->pieceToChar[make_piece(BLACK, KING)]); if (check_touching_kings(board, kingPositions) == NOK) return FEN_TOUCHING_KINGS; } } // 2) Part // check side to move char if (fenParts.size() >= 2 && fenParts[1][0] != 'w' && fenParts[1][0] != 'b') { std::cerr << "Invalid side to move specification: '" << fenParts[1][0] << "'." << std::endl; return FEN_INVALID_SIDE_TO_MOVE; } // Castling and en passant can be skipped bool skipCastlingAndEp = fenParts.size() >= 4 && fenParts.size() <= 5 && isdigit(fenParts[2][0]); // 3) Part // check castling rights if (fenParts.size() >= 3 && !skipCastlingAndEp && v->castling) { std::array castlingInfoSplitted; if (fill_castling_info_splitted(fenParts[2], castlingInfoSplitted) == NOK) return FEN_INVALID_CASTLING_INFO; if (castlingInfoSplitted[WHITE].size() != 0 || castlingInfoSplitted[BLACK].size() != 0) { std::array kingPositions; kingPositions[WHITE] = board.get_square_for_piece(toupper(v->pieceToChar[v->castlingKingPiece])); kingPositions[BLACK] = board.get_square_for_piece(tolower(v->pieceToChar[v->castlingKingPiece])); CharBoard startBoard(board.get_nb_ranks(), board.get_nb_files()); fill_char_board(startBoard, v->startFen, validSpecialCharacters, v); // Check pieces present on castling rank against castling/gating rights if (check_castling_rank(castlingInfoSplitted, board, kingPositions, v) == NOK) return FEN_INVALID_CASTLING_INFO; // only check exact squares if starting position of castling pieces is known if (!v->chess960 && !v->castlingDroppedPiece && !chess960) { std::array kingPositionsStart; kingPositionsStart[WHITE] = startBoard.get_square_for_piece(v->pieceToChar[make_piece(WHITE, v->castlingKingPiece)]); kingPositionsStart[BLACK] = startBoard.get_square_for_piece(v->pieceToChar[make_piece(BLACK, v->castlingKingPiece)]); std::array, 2> rookPositionsStart; rookPositionsStart[WHITE] = startBoard.get_squares_for_piece(v->pieceToChar[make_piece(WHITE, v->castlingRookPiece)]); rookPositionsStart[BLACK] = startBoard.get_squares_for_piece(v->pieceToChar[make_piece(BLACK, v->castlingRookPiece)]); if (check_standard_castling(castlingInfoSplitted, board, kingPositions, kingPositionsStart, rookPositionsStart, v) == NOK) return FEN_INVALID_CASTLING_INFO; } } } // 4) Part // check en-passant square if (fenParts.size() >= 4 && !skipCastlingAndEp) { if (v->doubleStep && v->pieceTypes.find(PAWN) != v->pieceTypes.end()) { if (check_en_passant_square(fenParts[3]) == NOK) return FEN_INVALID_EN_PASSANT_SQ; } else if (v->countingRule && !check_digit_field(fenParts[3])) return FEN_INVALID_COUNTING_RULE; } // 5) Part // check check count unsigned int optionalInbetweenFields = 2 * !skipCastlingAndEp; unsigned int optionalTrailingFields = 0; if (fenParts.size() >= 3 + optionalInbetweenFields && v->checkCounting && fenParts.size() % 2) { if (check_check_count(fenParts[2 + optionalInbetweenFields]) == NOK) { // allow valid lichess style check as alternative if (fenParts.size() < 5 + optionalInbetweenFields || check_lichess_check_count(fenParts[fenParts.size() - 1]) == NOK) return FEN_INVALID_CHECK_COUNT; else optionalTrailingFields++; } else optionalInbetweenFields++; } // 6) Part // check half move counter if (fenParts.size() >= 3 + optionalInbetweenFields && !check_digit_field(fenParts[fenParts.size() - 2 - optionalTrailingFields])) { std::cerr << "Invalid half move counter: '" << fenParts[fenParts.size()-2] << "'." << std::endl; return FEN_INVALID_HALF_MOVE_COUNTER; } // 7) Part // check move counter if (fenParts.size() >= 4 + optionalInbetweenFields && !check_digit_field(fenParts[fenParts.size() - 1 - optionalTrailingFields])) { std::cerr << "Invalid move counter: '" << fenParts[fenParts.size()-1] << "'." << std::endl; return FEN_INVALID_MOVE_COUNTER; } return FEN_OK; } } // namespace FEN } // namespace Stockfish #endif // #ifndef APIUTIL_H_INCLUDED Fairy-Stockfish-fairy_sf_14_0_1_xq/src/benchmark.cpp000066400000000000000000000161151414571233100225700ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include "position.h" #include "uci.h" using namespace std; namespace { const vector Defaults = { "setoption name UCI_Chess960 value false", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 10", "8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 11", "4rrk1/pp1n3p/3q2pQ/2p1pb2/2PP4/2P3N1/P2B2PP/4RRK1 b - - 7 19", "rq3rk1/ppp2ppp/1bnpb3/3N2B1/3NP3/7P/PPPQ1PP1/2KR3R w - - 7 14 moves d4e6", "r1bq1r1k/1pp1n1pp/1p1p4/4p2Q/4Pp2/1BNP4/PPP2PPP/3R1RK1 w - - 2 14 moves g2g4", "r3r1k1/2p2ppp/p1p1bn2/8/1q2P3/2NPQN2/PPP3PP/R4RK1 b - - 2 15", "r1bbk1nr/pp3p1p/2n5/1N4p1/2Np1B2/8/PPP2PPP/2KR1B1R w kq - 0 13", "r1bq1rk1/ppp1nppp/4n3/3p3Q/3P4/1BP1B3/PP1N2PP/R4RK1 w - - 1 16", "4r1k1/r1q2ppp/ppp2n2/4P3/5Rb1/1N1BQ3/PPP3PP/R5K1 w - - 1 17", "2rqkb1r/ppp2p2/2npb1p1/1N1Nn2p/2P1PP2/8/PP2B1PP/R1BQK2R b KQ - 0 11", "r1bq1r1k/b1p1npp1/p2p3p/1p6/3PP3/1B2NN2/PP3PPP/R2Q1RK1 w - - 1 16", "3r1rk1/p5pp/bpp1pp2/8/q1PP1P2/b3P3/P2NQRPP/1R2B1K1 b - - 6 22", "r1q2rk1/2p1bppp/2Pp4/p6b/Q1PNp3/4B3/PP1R1PPP/2K4R w - - 2 18", "4k2r/1pb2ppp/1p2p3/1R1p4/3P4/2r1PN2/P4PPP/1R4K1 b - - 3 22", "3q2k1/pb3p1p/4pbp1/2r5/PpN2N2/1P2P2P/5PP1/Q2R2K1 b - - 4 26", "6k1/6p1/6Pp/ppp5/3pn2P/1P3K2/1PP2P2/3N4 b - - 0 1", "3b4/5kp1/1p1p1p1p/pP1PpP1P/P1P1P3/3KN3/8/8 w - - 0 1", "2K5/p7/7P/5pR1/8/5k2/r7/8 w - - 0 1 moves g5g6 f3e3 g6g5 e3f3", "8/6pk/1p6/8/PP3p1p/5P2/4KP1q/3Q4 w - - 0 1", "7k/3p2pp/4q3/8/4Q3/5Kp1/P6b/8 w - - 0 1", "8/2p5/8/2kPKp1p/2p4P/2P5/3P4/8 w - - 0 1", "8/1p3pp1/7p/5P1P/2k3P1/8/2K2P2/8 w - - 0 1", "8/pp2r1k1/2p1p3/3pP2p/1P1P1P1P/P5KR/8/8 w - - 0 1", "8/3p4/p1bk3p/Pp6/1Kp1PpPp/2P2P1P/2P5/5B2 b - - 0 1", "5k2/7R/4P2p/5K2/p1r2P1p/8/8/8 b - - 0 1", "6k1/6p1/P6p/r1N5/5p2/7P/1b3PP1/4R1K1 w - - 0 1", "1r3k2/4q3/2Pp3b/3Bp3/2Q2p2/1p1P2P1/1P2KP2/3N4 w - - 0 1", "6k1/4pp1p/3p2p1/P1pPb3/R7/1r2P1PP/3B1P2/6K1 w - - 0 1", "8/3p3B/5p2/5P2/p7/PP5b/k7/6K1 w - - 0 1", "5rk1/q6p/2p3bR/1pPp1rP1/1P1Pp3/P3B1Q1/1K3P2/R7 w - - 93 90", "4rrk1/1p1nq3/p7/2p1P1pp/3P2bp/3Q1Bn1/PPPB4/1K2R1NR w - - 40 21", "r3k2r/3nnpbp/q2pp1p1/p7/Pp1PPPP1/4BNN1/1P5P/R2Q1RK1 w kq - 0 16", "3Qb1k1/1r2ppb1/pN1n2q1/Pp1Pp1Pr/4P2p/4BP2/4B1R1/1R5K b - - 11 40", "4k3/3q1r2/1N2r1b1/3ppN2/2nPP3/1B1R2n1/2R1Q3/3K4 w - - 5 1", // 5-man positions "8/8/8/8/5kp1/P7/8/1K1N4 w - - 0 1", // Kc2 - mate "8/8/8/5N2/8/p7/8/2NK3k w - - 0 1", // Na2 - mate "8/3k4/8/8/8/4B3/4KB2/2B5 w - - 0 1", // draw // 6-man positions "8/8/1P6/5pr1/8/4R3/7k/2K5 w - - 0 1", // Re5 - mate "8/2p4P/8/kr6/6R1/8/8/1K6 w - - 0 1", // Ka2 - mate "8/8/3P3k/8/1p6/8/1P6/1K3n2 b - - 0 1", // Nd2 - draw // 7-man positions "8/R7/2q5/8/6k1/8/1P5p/K6R w - - 0 124", // Draw // Mate and stalemate positions "6k1/3b3r/1p1p4/p1n2p2/1PPNpP1q/P3Q1p1/1R1RB1P1/5K2 b - - 0 1", "r2r1n2/pp2bk2/2p1p2p/3q4/3PN1QP/2P3R1/P4PP1/5RK1 w - - 0 1", "8/8/8/8/8/6k1/6p1/6K1 w - -", "7k/7P/6K1/8/3B4/8/8/8 b - -", // Chess 960 "setoption name UCI_Chess960 value true", "bbqnnrkr/pppppppp/8/8/8/8/PPPPPPPP/BBQNNRKR w HFhf - 0 1 moves g2g3 d7d5 d2d4 c8h3 c1g5 e8d6 g5e7 f7f6", "setoption name UCI_Chess960 value false" }; } // namespace namespace Stockfish { /// setup_bench() builds a list of UCI commands to be run by bench. There /// are five parameters: TT size in MB, number of search threads that /// should be used, the limit value spent for each position, a file name /// where to look for positions in FEN format, the type of the limit: /// depth, perft, nodes and movetime (in millisecs), and evaluation type /// mixed (default), classical, NNUE. /// /// bench -> search default positions up to depth 13 /// bench 64 1 15 -> search default positions up to depth 15 (TT = 64MB) /// bench 64 4 5000 current movetime -> search current position with 4 threads for 5 sec /// bench 64 1 100000 default nodes -> search default positions for 100K nodes each /// bench 16 1 5 default perft -> run a perft 5 on default positions vector setup_bench(const Position& current, istream& is) { vector fens, list; string go, token, varname; streampos args = is.tellg(); // Check whether the next token is a variant name if ((is >> token) && variants.find(token) != variants.end()) { args = is.tellg(); varname = token; } else { is.seekg(args); varname = string(Options["UCI_Variant"]); } const Variant* variant = variants.find(varname)->second; // 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") { if (varname != "chess") fens.push_back(variant->startFen); else 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("setoption name UCI_Variant value " + varname); list.emplace_back("ucinewgame"); size_t posCounter = 0; for (const string& fen : fens) if (fen.find("setoption") != string::npos) list.emplace_back(fen); else { if (evalType == "classical" || (evalType == "mixed" && posCounter % 2 == 0)) list.emplace_back("setoption name Use NNUE value false"); else if (evalType == "NNUE" || (evalType == "mixed" && posCounter % 2 != 0)) list.emplace_back("setoption name Use NNUE value true"); list.emplace_back("position fen " + fen); list.emplace_back(go); ++posCounter; } list.emplace_back("setoption name Use NNUE value true"); return list; } } // namespace Stockfish Fairy-Stockfish-fairy_sf_14_0_1_xq/src/bitbase.cpp000066400000000000000000000135001414571233100222420ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include "bitboard.h" #include "types.h" namespace Stockfish { namespace { // There are 24 possible pawn squares: files A to D and ranks from 2 to 7. // Positions with the pawn on files E to H will be mirrored before probing. constexpr unsigned MAX_INDEX = 2*24*64*64; // stm * psq * wksq * bksq = 196608 std::bitset 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& 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() { #ifdef LARGEBOARDS // Bitbases are not working for large-board version return; #endif std::vector 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(ksq[BLACK]) & ~(attacks_bb(ksq[WHITE]) | pawn_attacks_bb(WHITE, psq))) || (attacks_bb(ksq[BLACK]) & ~attacks_bb(ksq[WHITE]) & psq))) result = DRAW; // Position will be classified later else result = UNKNOWN; } Result KPKPosition::classify(const std::vector& 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(ksq[stm]); while (b) r |= stm == WHITE ? db[index(BLACK, ksq[BLACK], pop_lsb(b), psq)] : db[index(WHITE, pop_lsb(b), ksq[WHITE], psq)]; if (stm == WHITE) { if (rank_of(psq) < RANK_7) // Single push r |= db[index(BLACK, ksq[BLACK], ksq[WHITE], psq + NORTH)]; if ( rank_of(psq) == RANK_2 // Double push && psq + NORTH != ksq[WHITE] && psq + NORTH != ksq[BLACK]) r |= db[index(BLACK, ksq[BLACK], ksq[WHITE], psq + NORTH + NORTH)]; } return result = r & Good ? Good : r & UNKNOWN ? UNKNOWN : Bad; } } // namespace } // namespace Stockfish Fairy-Stockfish-fairy_sf_14_0_1_xq/src/bitboard.cpp000066400000000000000000000477421414571233100224360ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include "bitboard.h" #include "magic.h" #include "misc.h" #include "piece.h" namespace Stockfish { uint8_t PopCnt16[1 << 16]; uint8_t SquareDistance[SQUARE_NB][SQUARE_NB]; Bitboard SquareBB[SQUARE_NB]; Bitboard LineBB[SQUARE_NB][SQUARE_NB]; Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; Bitboard PseudoAttacks[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; Bitboard PseudoMoves[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; Bitboard LeaperAttacks[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; Bitboard LeaperMoves[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; Bitboard BoardSizeBB[FILE_NB][RANK_NB]; RiderType AttackRiderTypes[PIECE_TYPE_NB]; RiderType MoveRiderTypes[PIECE_TYPE_NB]; Magic RookMagicsH[SQUARE_NB]; Magic RookMagicsV[SQUARE_NB]; Magic BishopMagics[SQUARE_NB]; Magic CannonMagicsH[SQUARE_NB]; Magic CannonMagicsV[SQUARE_NB]; Magic HorseMagics[SQUARE_NB]; Magic ElephantMagics[SQUARE_NB]; Magic JanggiElephantMagics[SQUARE_NB]; Magic CannonDiagMagics[SQUARE_NB]; Magic NightriderMagics[SQUARE_NB]; Magic GrasshopperMagicsH[SQUARE_NB]; Magic GrasshopperMagicsV[SQUARE_NB]; Magic GrasshopperMagicsD[SQUARE_NB]; Magic* magics[] = {BishopMagics, RookMagicsH, RookMagicsV, CannonMagicsH, CannonMagicsV, HorseMagics, ElephantMagics, JanggiElephantMagics, CannonDiagMagics, NightriderMagics, GrasshopperMagicsH, GrasshopperMagicsV, GrasshopperMagicsD}; namespace { // Some magics need to be split in order to reduce memory consumption. // Otherwise on a 12x10 board they can be >100 MB. #ifdef LARGEBOARDS Bitboard RookTableH[0x11800]; // To store horizontalrook attacks Bitboard RookTableV[0x4800]; // To store vertical rook attacks Bitboard BishopTable[0x33C00]; // To store bishop attacks Bitboard CannonTableH[0x11800]; // To store horizontal cannon attacks Bitboard CannonTableV[0x4800]; // To store vertical cannon attacks Bitboard HorseTable[0x500]; // To store horse attacks Bitboard ElephantTable[0x400]; // To store elephant attacks Bitboard JanggiElephantTable[0x1C000]; // To store janggi elephant attacks Bitboard CannonDiagTable[0x33C00]; // To store diagonal cannon attacks Bitboard NightriderTable[0x70200]; // To store nightrider attacks Bitboard GrasshopperTableH[0x11800]; // To store horizontal grasshopper attacks Bitboard GrasshopperTableV[0x4800]; // To store vertical grasshopper attacks Bitboard GrasshopperTableD[0x33C00]; // To store diagonal grasshopper attacks #else Bitboard RookTableH[0xA00]; // To store horizontal rook attacks Bitboard RookTableV[0xA00]; // To store vertical rook attacks Bitboard BishopTable[0x1480]; // To store bishop attacks Bitboard CannonTableH[0xA00]; // To store horizontal cannon attacks Bitboard CannonTableV[0xA00]; // To store vertical cannon attacks Bitboard HorseTable[0x240]; // To store horse attacks Bitboard ElephantTable[0x1A0]; // To store elephant attacks Bitboard JanggiElephantTable[0x5C00]; // To store janggi elephant attacks Bitboard CannonDiagTable[0x1480]; // To store diagonal cannon attacks Bitboard NightriderTable[0x1840]; // To store nightrider attacks Bitboard GrasshopperTableH[0xA00]; // To store horizontal grasshopper attacks Bitboard GrasshopperTableV[0xA00]; // To store vertical grasshopper attacks Bitboard GrasshopperTableD[0x1480]; // To store diagonal grasshopper attacks #endif // Rider directions const std::map RookDirectionsV { {NORTH, 0}, {SOUTH, 0}}; const std::map RookDirectionsH { {EAST, 0}, {WEST, 0} }; const std::map BishopDirections { {NORTH_EAST, 0}, {SOUTH_EAST, 0}, {SOUTH_WEST, 0}, {NORTH_WEST, 0} }; const std::map HorseDirections { {2 * SOUTH + WEST, 0}, {2 * SOUTH + EAST, 0}, {SOUTH + 2 * WEST, 0}, {SOUTH + 2 * EAST, 0}, {NORTH + 2 * WEST, 0}, {NORTH + 2 * EAST, 0}, {2 * NORTH + WEST, 0}, {2 * NORTH + EAST, 0} }; const std::map ElephantDirections { {2 * NORTH_EAST, 0}, {2 * SOUTH_EAST, 0}, {2 * SOUTH_WEST, 0}, {2 * NORTH_WEST, 0} }; const std::map JanggiElephantDirections { {NORTH + 2 * NORTH_EAST, 0}, {EAST + 2 * NORTH_EAST, 0}, {EAST + 2 * SOUTH_EAST, 0}, {SOUTH + 2 * SOUTH_EAST, 0}, {SOUTH + 2 * SOUTH_WEST, 0}, {WEST + 2 * SOUTH_WEST, 0}, {WEST + 2 * NORTH_WEST, 0}, {NORTH + 2 * NORTH_WEST, 0} }; const std::map GrasshopperDirectionsV { {NORTH, 1}, {SOUTH, 1}}; const std::map GrasshopperDirectionsH { {EAST, 1}, {WEST, 1} }; const std::map GrasshopperDirectionsD { {NORTH_EAST, 1}, {SOUTH_EAST, 1}, {SOUTH_WEST, 1}, {NORTH_WEST, 1} }; enum MovementType { RIDER, HOPPER, LAME_LEAPER, UNLIMITED_RIDER }; template #ifdef PRECOMPUTED_MAGICS void init_magics(Bitboard table[], Magic magics[], std::map directions, const Bitboard magicsInit[]); #else void init_magics(Bitboard table[], Magic magics[], std::map directions); #endif template Bitboard sliding_attack(std::map directions, Square sq, Bitboard occupied, Color c = WHITE) { assert(MT != LAME_LEAPER); Bitboard attack = 0; for (auto const& [d, limit] : directions) { int count = 0; bool hurdle = false; for (Square s = sq + (c == WHITE ? d : -d); is_ok(s) && distance(s, s - (c == WHITE ? d : -d)) <= 2; s += (c == WHITE ? d : -d)) { if (MT != HOPPER || hurdle) { attack |= s; if (limit && MT != UNLIMITED_RIDER && ++count >= limit) break; } if (occupied & s) { if (MT == HOPPER && !hurdle) hurdle = true; else break; } } } return attack; } Bitboard lame_leaper_path(Direction d, Square s) { Direction dr = d > 0 ? NORTH : SOUTH; Direction df = (std::abs(d % NORTH) < NORTH / 2 ? d % NORTH : -(d % NORTH)) < 0 ? WEST : EAST; Square to = s + d; Bitboard b = 0; if (!is_ok(to) || distance(s, to) >= 4) return b; while (s != to) { int diff = std::abs(file_of(to) - file_of(s)) - std::abs(rank_of(to) - rank_of(s)); if (diff > 0) s += df; else if (diff < 0) s += dr; else s += df + dr; if (s != to) b |= s; } return b; } Bitboard lame_leaper_path(std::map directions, Square s) { Bitboard b = 0; for (const auto& i : directions) b |= lame_leaper_path(i.first, s); return b; } Bitboard lame_leaper_attack(std::map directions, Square s, Bitboard occupied) { Bitboard b = 0; for (const auto& i : directions) { Square to = s + i.first; if (is_ok(to) && distance(s, to) < 4 && !(lame_leaper_path(i.first, s) & occupied)) b |= to; } return b; } } /// 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) <= 3 ? square_bb(to) : Bitboard(0); } /// Bitboards::pretty() returns an ASCII representation of a bitboard suitable /// to be printed to standard output. Useful for debugging. std::string Bitboards::pretty(Bitboard b) { std::string s = "+---+---+---+---+---+---+---+---+---+---+---+---+\n"; for (Rank r = RANK_MAX; r >= RANK_1; --r) { for (File f = FILE_A; f <= FILE_MAX; ++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 i j k\n"; return s; } /// Bitboards::init_pieces() initializes piece move/attack bitboards and rider types void Bitboards::init_pieces() { for (PieceType pt = PAWN; pt <= KING; ++pt) { const PieceInfo* pi = pieceMap.find(pt)->second; // Detect rider types for (auto modality : {MODALITY_QUIET, MODALITY_CAPTURE}) { auto& riderTypes = modality == MODALITY_CAPTURE ? AttackRiderTypes[pt] : MoveRiderTypes[pt]; riderTypes = NO_RIDER; for (auto const& [d, limit] : pi->steps[modality]) { if (limit && HorseDirections.find(d) != HorseDirections.end()) riderTypes |= RIDER_HORSE; if (limit && ElephantDirections.find(d) != ElephantDirections.end()) riderTypes |= RIDER_ELEPHANT; if (limit && JanggiElephantDirections.find(d) != JanggiElephantDirections.end()) riderTypes |= RIDER_JANGGI_ELEPHANT; } for (auto const& [d, limit] : pi->slider[modality]) { if (BishopDirections.find(d) != BishopDirections.end()) riderTypes |= RIDER_BISHOP; if (RookDirectionsH.find(d) != RookDirectionsH.end()) riderTypes |= RIDER_ROOK_H; if (RookDirectionsV.find(d) != RookDirectionsV.end()) riderTypes |= RIDER_ROOK_V; if (HorseDirections.find(d) != HorseDirections.end()) riderTypes |= RIDER_NIGHTRIDER; } for (auto const& [d, limit] : pi->hopper[modality]) { if (RookDirectionsH.find(d) != RookDirectionsH.end()) riderTypes |= limit == 1 ? RIDER_GRASSHOPPER_H : RIDER_CANNON_H; if (RookDirectionsV.find(d) != RookDirectionsV.end()) riderTypes |= limit == 1 ? RIDER_GRASSHOPPER_V : RIDER_CANNON_V; if (BishopDirections.find(d) != BishopDirections.end()) riderTypes |= limit == 1 ? RIDER_GRASSHOPPER_D : RIDER_CANNON_DIAG; } } // Initialize move/attack bitboards for (Color c : { WHITE, BLACK }) { for (Square s = SQ_A1; s <= SQ_MAX; ++s) { for (auto modality : {MODALITY_QUIET, MODALITY_CAPTURE}) { auto& pseudo = modality == MODALITY_CAPTURE ? PseudoAttacks[c][pt][s] : PseudoMoves[c][pt][s]; auto& leaper = modality == MODALITY_CAPTURE ? LeaperAttacks[c][pt][s] : LeaperMoves[c][pt][s]; pseudo = 0; leaper = 0; for (auto const& [d, limit] : pi->steps[modality]) { pseudo |= safe_destination(s, c == WHITE ? d : -d); if (!limit) leaper |= safe_destination(s, c == WHITE ? d : -d); } pseudo |= sliding_attack(pi->slider[modality], s, 0, c); pseudo |= sliding_attack(pi->hopper[modality], s, 0, c); } } } } } /// 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_MAX; ++s) SquareBB[s] = make_bitboard(s); for (File f = FILE_A; f <= FILE_MAX; ++f) for (Rank r = RANK_1; r <= RANK_MAX; ++r) BoardSizeBB[f][r] = forward_file_bb(BLACK, make_square(f, r)) | SquareBB[make_square(f, r)] | (f > FILE_A ? BoardSizeBB[f - 1][r] : Bitboard(0)); for (Square s1 = SQ_A1; s1 <= SQ_MAX; ++s1) for (Square s2 = SQ_A1; s2 <= SQ_MAX; ++s2) SquareDistance[s1][s2] = std::max(distance(s1, s2), distance(s1, s2)); #ifdef PRECOMPUTED_MAGICS init_magics(RookTableH, RookMagicsH, RookDirectionsH, RookMagicHInit); init_magics(RookTableV, RookMagicsV, RookDirectionsV, RookMagicVInit); init_magics(BishopTable, BishopMagics, BishopDirections, BishopMagicInit); init_magics(CannonTableH, CannonMagicsH, RookDirectionsH, CannonMagicHInit); init_magics(CannonTableV, CannonMagicsV, RookDirectionsV, CannonMagicVInit); init_magics(HorseTable, HorseMagics, HorseDirections, HorseMagicInit); init_magics(ElephantTable, ElephantMagics, ElephantDirections, ElephantMagicInit); init_magics(JanggiElephantTable, JanggiElephantMagics, JanggiElephantDirections, JanggiElephantMagicInit); init_magics(CannonDiagTable, CannonDiagMagics, BishopDirections, CannonDiagMagicInit); init_magics(NightriderTable, NightriderMagics, HorseDirections, NightriderMagicInit); init_magics(GrasshopperTableH, GrasshopperMagicsH, GrasshopperDirectionsH, GrasshopperMagicHInit); init_magics(GrasshopperTableV, GrasshopperMagicsV, GrasshopperDirectionsV, GrasshopperMagicVInit); init_magics(GrasshopperTableD, GrasshopperMagicsD, GrasshopperDirectionsD, GrasshopperMagicDInit); #else init_magics(RookTableH, RookMagicsH, RookDirectionsH); init_magics(RookTableV, RookMagicsV, RookDirectionsV); init_magics(BishopTable, BishopMagics, BishopDirections); init_magics(CannonTableH, CannonMagicsH, RookDirectionsH); init_magics(CannonTableV, CannonMagicsV, RookDirectionsV); init_magics(HorseTable, HorseMagics, HorseDirections); init_magics(ElephantTable, ElephantMagics, ElephantDirections); init_magics(JanggiElephantTable, JanggiElephantMagics, JanggiElephantDirections); init_magics(CannonDiagTable, CannonDiagMagics, BishopDirections); init_magics(NightriderTable, NightriderMagics, HorseDirections); init_magics(GrasshopperTableH, GrasshopperMagicsH, GrasshopperDirectionsH); init_magics(GrasshopperTableV, GrasshopperMagicsV, GrasshopperDirectionsV); init_magics(GrasshopperTableD, GrasshopperMagicsD, GrasshopperDirectionsD); #endif init_pieces(); for (Square s1 = SQ_A1; s1 <= SQ_MAX; ++s1) { for (PieceType pt : { BISHOP, ROOK }) for (Square s2 = SQ_A1; s2 <= SQ_MAX; ++s2) { if (PseudoAttacks[WHITE][pt][s1] & s2) { LineBB[s1][s2] = (attacks_bb(WHITE, pt, s1, 0) & attacks_bb(WHITE, pt, s2, 0)) | s1 | s2; BetweenBB[s1][s2] = (attacks_bb(WHITE, pt, s1, square_bb(s2)) & attacks_bb(WHITE, pt, s2, square_bb(s1))); } BetweenBB[s1][s2] |= s2; } } } namespace { // 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. template #ifdef PRECOMPUTED_MAGICS void init_magics(Bitboard table[], Magic magics[], std::map directions, const Bitboard magicsInit[]) { #else void init_magics(Bitboard table[], Magic magics[], std::map directions) { #endif // Optimal PRNG seeds to pick the correct magics in the shortest time #ifndef PRECOMPUTED_MAGICS #ifdef LARGEBOARDS int seeds[][RANK_NB] = { { 734, 10316, 55013, 32803, 12281, 15100, 16645, 255, 346, 89123 }, { 734, 10316, 55013, 32803, 12281, 15100, 16645, 255, 346, 89123 } }; #else int seeds[][RANK_NB] = { { 8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020 }, { 728, 10316, 55013, 32803, 12281, 15100, 16645, 255 } }; #endif #endif Bitboard* occupancy = new Bitboard[1 << (FILE_NB + RANK_NB - 4)]; Bitboard* reference = new Bitboard[1 << (FILE_NB + RANK_NB - 4)]; Bitboard edges, b; int* epoch = new int[1 << (FILE_NB + RANK_NB - 4)](); int cnt = 0, size = 0; for (Square s = SQ_A1; s <= SQ_MAX; ++s) { // Board edges are not considered in the relevant occupancies edges = ((Rank1BB | rank_bb(RANK_MAX)) & ~rank_bb(s)) | ((FileABB | file_bb(FILE_MAX)) & ~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]; // The mask for hoppers is unlimited distance, even if the hopper is limited distance (e.g., grasshopper) m.mask = (MT == LAME_LEAPER ? lame_leaper_path(directions, s) : sliding_attack(directions, s, 0)) & ~edges; #ifdef LARGEBOARDS m.shift = 128 - popcount(m.mask); #else m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask); #endif // 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] = MT == LAME_LEAPER ? lame_leaper_attack(directions, s, b) : sliding_attack(directions, s, b); if (HasPext) m.attacks[pext(b, m.mask)] = reference[size]; size++; b = (b - m.mask) & m.mask; } while (b); if (HasPext) continue; #ifndef PRECOMPUTED_MAGICS PRNG rng(seeds[Is64Bit][rank_of(s)]); #endif // 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) >> (SQUARE_NB - FILE_NB)) < FILE_NB - 2; ) { #ifdef LARGEBOARDS #ifdef PRECOMPUTED_MAGICS m.magic = magicsInit[s]; #else m.magic = (rng.sparse_rand() << 64) ^ rng.sparse_rand(); #endif #else m.magic = rng.sparse_rand(); #endif } // 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; } } } delete[] occupancy; delete[] reference; delete[] epoch; } } } // namespace Stockfish Fairy-Stockfish-fairy_sf_14_0_1_xq/src/bitboard.h000066400000000000000000000515711414571233100220760ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef BITBOARD_H_INCLUDED #define BITBOARD_H_INCLUDED #include #include "types.h" namespace Stockfish { namespace Bitbases { void init(); bool probe(Square wksq, Square wpsq, Square bksq, Color us); } // namespace Stockfish::Bitbases namespace Bitboards { void init_pieces(); void init(); std::string pretty(Bitboard b); } // namespace Stockfish::Bitboards #ifdef LARGEBOARDS constexpr Bitboard AllSquares = ((~Bitboard(0)) >> 8); #else constexpr Bitboard AllSquares = ~Bitboard(0); #endif #ifdef LARGEBOARDS constexpr Bitboard DarkSquares = (Bitboard(0xAAA555AAA555AAULL) << 64) ^ Bitboard(0xA555AAA555AAA555ULL); #else constexpr Bitboard DarkSquares = 0xAA55AA55AA55AA55ULL; #endif #ifdef LARGEBOARDS constexpr Bitboard FileABB = (Bitboard(0x00100100100100ULL) << 64) ^ Bitboard(0x1001001001001001ULL); #else constexpr Bitboard FileABB = 0x0101010101010101ULL; #endif 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; #ifdef LARGEBOARDS constexpr Bitboard FileIBB = FileABB << 8; constexpr Bitboard FileJBB = FileABB << 9; constexpr Bitboard FileKBB = FileABB << 10; constexpr Bitboard FileLBB = FileABB << 11; #endif #ifdef LARGEBOARDS constexpr Bitboard Rank1BB = 0xFFF; #else constexpr Bitboard Rank1BB = 0xFF; #endif constexpr Bitboard Rank2BB = Rank1BB << (FILE_NB * 1); constexpr Bitboard Rank3BB = Rank1BB << (FILE_NB * 2); constexpr Bitboard Rank4BB = Rank1BB << (FILE_NB * 3); constexpr Bitboard Rank5BB = Rank1BB << (FILE_NB * 4); constexpr Bitboard Rank6BB = Rank1BB << (FILE_NB * 5); constexpr Bitboard Rank7BB = Rank1BB << (FILE_NB * 6); constexpr Bitboard Rank8BB = Rank1BB << (FILE_NB * 7); #ifdef LARGEBOARDS constexpr Bitboard Rank9BB = Rank1BB << (FILE_NB * 8); constexpr Bitboard Rank10BB = Rank1BB << (FILE_NB * 9); #endif constexpr Bitboard QueenSide = FileABB | FileBBB | FileCBB | FileDBB; constexpr Bitboard CenterFiles = FileCBB | FileDBB | FileEBB | FileFBB; constexpr Bitboard KingSide = FileEBB | FileFBB | FileGBB | FileHBB; constexpr Bitboard Center = (FileDBB | FileEBB) & (Rank4BB | Rank5BB); constexpr Bitboard KingFlank[FILE_NB] = { QueenSide ^ FileDBB, QueenSide, QueenSide, CenterFiles, CenterFiles, KingSide, KingSide, KingSide ^ FileEBB }; extern uint8_t PopCnt16[1 << 16]; extern uint8_t SquareDistance[SQUARE_NB][SQUARE_NB]; extern Bitboard SquareBB[SQUARE_NB]; extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; extern Bitboard LineBB[SQUARE_NB][SQUARE_NB]; extern Bitboard PseudoAttacks[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; extern Bitboard PseudoMoves[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; extern Bitboard LeaperAttacks[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; extern Bitboard LeaperMoves[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; extern Bitboard SquareBB[SQUARE_NB]; extern Bitboard BoardSizeBB[FILE_NB][RANK_NB]; extern RiderType AttackRiderTypes[PIECE_TYPE_NB]; extern RiderType MoveRiderTypes[PIECE_TYPE_NB]; #ifdef LARGEBOARDS int popcount(Bitboard b); // required for 128 bit pext #endif /// 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)); #ifdef LARGEBOARDS return unsigned(((occupied & mask) * magic) >> shift); #else if (Is64Bit) return unsigned(((occupied & mask) * magic) >> shift); #endif 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 RookMagicsH[SQUARE_NB]; extern Magic RookMagicsV[SQUARE_NB]; extern Magic BishopMagics[SQUARE_NB]; extern Magic CannonMagicsH[SQUARE_NB]; extern Magic CannonMagicsV[SQUARE_NB]; extern Magic HorseMagics[SQUARE_NB]; extern Magic ElephantMagics[SQUARE_NB]; extern Magic JanggiElephantMagics[SQUARE_NB]; extern Magic CannonDiagMagics[SQUARE_NB]; extern Magic NightriderMagics[SQUARE_NB]; extern Magic GrasshopperMagicsH[SQUARE_NB]; extern Magic GrasshopperMagicsV[SQUARE_NB]; extern Magic GrasshopperMagicsD[SQUARE_NB]; extern Magic* magics[]; constexpr Bitboard make_bitboard() { return 0; } template constexpr Bitboard make_bitboard(Square s, Squares... squares) { return (Bitboard(1) << s) | make_bitboard(squares...); } 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-( 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); } /// board_size_bb() returns a bitboard representing all the squares /// on a board with given size. inline Bitboard board_size_bb(File f, Rank r) { return BoardSizeBB[f][r]; } 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 << (FILE_NB * 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 constexpr Bitboard shift(Bitboard b) { return D == NORTH ? b << NORTH : D == SOUTH ? b >> NORTH : D == NORTH+NORTH? b <<(2 * NORTH) : D == SOUTH+SOUTH? b >> (2 * NORTH) : D == EAST ? (b & ~file_bb(FILE_MAX)) << EAST : D == WEST ? (b & ~FileABB) >> EAST : D == NORTH_EAST ? (b & ~file_bb(FILE_MAX)) << NORTH_EAST : D == NORTH_WEST ? (b & ~FileABB) << NORTH_WEST : D == SOUTH_EAST ? (b & ~file_bb(FILE_MAX)) >> NORTH_WEST : D == SOUTH_WEST ? (b & ~FileABB) >> NORTH_EAST : Bitboard(0); } /// shift() moves a bitboard one step along direction D (mainly for pawns) constexpr Bitboard shift(Direction D, Bitboard b) { return D == NORTH ? b << NORTH : D == SOUTH ? b >> NORTH : D == NORTH+NORTH? b <<(2 * NORTH) : D == SOUTH+SOUTH? b >> (2 * NORTH) : D == EAST ? (b & ~file_bb(FILE_MAX)) << EAST : D == WEST ? (b & ~FileABB) >> EAST : D == NORTH_EAST ? (b & ~file_bb(FILE_MAX)) << NORTH_EAST : D == NORTH_WEST ? (b & ~FileABB) << NORTH_WEST : D == SOUTH_EAST ? (b & ~file_bb(FILE_MAX)) >> NORTH_WEST : D == SOUTH_WEST ? (b & ~FileABB) >> NORTH_EAST : Bitboard(0); } /// pawn_attacks_bb() returns the squares attacked by pawns of the given color /// from the squares in the given bitboard. template constexpr Bitboard pawn_attacks_bb(Bitboard b) { return C == WHITE ? shift(b) | shift(b) : shift(b) | shift(b); } inline Bitboard pawn_attacks_bb(Color c, Square s) { assert(is_ok(s)); return PseudoAttacks[c][PAWN][s]; } /// pawn_double_attacks_bb() returns the squares doubly attacked by pawns of the /// given color from the squares in the given bitboard. template constexpr Bitboard pawn_double_attacks_bb(Bitboard b) { return C == WHITE ? shift(b) & shift(b) : shift(b) & shift(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(file_bb(s)) | shift(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(s1, s2) returns a bitboard representing the squares in the semi-open /// segment between the squares s1 and s2 (excluding s1 but including s2). If the /// given squares are not on a same file/rank/diagonal, it returns s2. For instance, /// between_bb(SQ_C4, SQ_F7) will return a bitboard with squares D5, E6 and F7, but /// between_bb(SQ_E6, SQ_F8) will return a bitboard with the square F8. This trick /// allows to generate non-king evasion moves faster: the defending piece must either /// interpose itself to cover the check or capture the checking piece. inline Bitboard between_bb(Square s1, Square s2) { assert(is_ok(s1) && is_ok(s2)); return BetweenBB[s1][s2]; } inline Bitboard between_bb(Square s1, Square s2, PieceType pt) { if (pt == HORSE) return PseudoAttacks[WHITE][WAZIR][s2] & PseudoAttacks[WHITE][FERS][s1]; else if (pt == JANGGI_ELEPHANT) return (PseudoAttacks[WHITE][WAZIR][s2] & PseudoAttacks[WHITE][ALFIL][s1]) | (PseudoAttacks[WHITE][KNIGHT][s2] & PseudoAttacks[WHITE][FERS][s1]); else return between_bb(s1, s2); } /// forward_ranks_bb() returns a bitboard representing the squares on the ranks in /// front of the given one, from the point of view of the given color. For instance, /// forward_ranks_bb(BLACK, SQ_D3) will return the 16 squares on ranks 1 and 2. constexpr Bitboard forward_ranks_bb(Color c, Square s) { return c == WHITE ? (AllSquares ^ Rank1BB) << FILE_NB * relative_rank(WHITE, s, RANK_MAX) : (AllSquares ^ rank_bb(RANK_MAX)) >> FILE_NB * relative_rank(BLACK, s, RANK_MAX); } constexpr Bitboard forward_ranks_bb(Color c, Rank r) { return c == WHITE ? (AllSquares ^ Rank1BB) << FILE_NB * (r - RANK_1) : (AllSquares ^ rank_bb(RANK_MAX)) >> FILE_NB * (RANK_MAX - r); } /// zone_bb() returns a bitboard representing the squares on all the ranks /// in front of and on the given relative rank, from the point of view of the given color. /// For instance, zone_bb(BLACK, RANK_7) will return the 16 squares on ranks 1 and 2. inline Bitboard zone_bb(Color c, Rank r, Rank maxRank) { return forward_ranks_bb(c, relative_rank(c, r, maxRank)) | rank_bb(relative_rank(c, r, maxRank)); } /// 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 inline int distance(Square x, Square y); template<> inline int distance(Square x, Square y) { return std::abs(file_of(x) - file_of(y)); } template<> inline int distance(Square x, Square y) { return std::abs(rank_of(x) - rank_of(y)); } template<> inline int distance(Square x, Square y) { return SquareDistance[x][y]; } inline int edge_distance(File f, File maxFile = FILE_H) { return std::min(f, File(maxFile - f)); } inline int edge_distance(Rank r, Rank maxRank = RANK_8) { return std::min(r, Rank(maxRank - r)); } template inline Bitboard rider_attacks_bb(Square s, Bitboard occupied) { static_assert(R != NO_RIDER && !(R & (R - 1))); // exactly one bit const Magic& m = R == RIDER_ROOK_H ? RookMagicsH[s] : R == RIDER_ROOK_V ? RookMagicsV[s] : R == RIDER_CANNON_H ? CannonMagicsH[s] : R == RIDER_CANNON_V ? CannonMagicsV[s] : R == RIDER_HORSE ? HorseMagics[s] : R == RIDER_ELEPHANT ? ElephantMagics[s] : R == RIDER_JANGGI_ELEPHANT ? JanggiElephantMagics[s] : R == RIDER_CANNON_DIAG ? CannonDiagMagics[s] : R == RIDER_NIGHTRIDER ? NightriderMagics[s] : R == RIDER_GRASSHOPPER_H ? GrasshopperMagicsH[s] : R == RIDER_GRASSHOPPER_V ? GrasshopperMagicsV[s] : R == RIDER_GRASSHOPPER_D ? GrasshopperMagicsD[s] : BishopMagics[s]; return m.attacks[m.index(occupied)]; } inline Square lsb(Bitboard b); inline Bitboard rider_attacks_bb(RiderType R, Square s, Bitboard occupied) { assert(R != NO_RIDER && !(R & (R - 1))); // exactly one bit const Magic& m = magics[lsb(R)][s]; // re-use Bitboard lsb for riders return m.attacks[m.index(occupied)]; } /// attacks_bb(Square) returns the pseudo attacks of the give piece type /// assuming an empty board. template inline Bitboard attacks_bb(Square s) { assert((Pt != PAWN) && (is_ok(s))); return PseudoAttacks[WHITE][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 inline Bitboard attacks_bb(Square s, Bitboard occupied) { assert((Pt != PAWN) && (is_ok(s))); switch (Pt) { case BISHOP: return rider_attacks_bb(s, occupied); case ROOK : return rider_attacks_bb(s, occupied) | rider_attacks_bb(s, occupied); case QUEEN : return attacks_bb(s, occupied) | attacks_bb(s, occupied); default : return PseudoAttacks[WHITE][Pt][s]; } } /// pop_rider() finds and clears a rider in a (hybrid) rider type inline RiderType pop_rider(RiderType* r) { assert(*r); const RiderType r2 = *r & ~(*r - 1); *r &= *r - 1; return r2; } inline Bitboard attacks_bb(Color c, PieceType pt, Square s, Bitboard occupied) { Bitboard b = LeaperAttacks[c][pt][s]; RiderType r = AttackRiderTypes[pt]; while (r) b |= rider_attacks_bb(pop_rider(&r), s, occupied); return b & PseudoAttacks[c][pt][s]; } inline Bitboard moves_bb(Color c, PieceType pt, Square s, Bitboard occupied) { Bitboard b = LeaperMoves[c][pt][s]; RiderType r = MoveRiderTypes[pt]; while (r) b |= rider_attacks_bb(pop_rider(&r), s, occupied); return b & PseudoMoves[c][pt][s]; } /// popcount() counts the number of non-zero bits in a bitboard inline int popcount(Bitboard b) { #ifndef USE_POPCNT #ifdef LARGEBOARDS union { Bitboard bb; uint16_t u[8]; } v = { b }; return PopCnt16[v.u[0]] + PopCnt16[v.u[1]] + PopCnt16[v.u[2]] + PopCnt16[v.u[3]] + PopCnt16[v.u[4]] + PopCnt16[v.u[5]] + PopCnt16[v.u[6]] + PopCnt16[v.u[7]]; #else 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]]; #endif #elif defined(_MSC_VER) || defined(__INTEL_COMPILER) #ifdef LARGEBOARDS return (int)_mm_popcnt_u64(uint64_t(b >> 64)) + (int)_mm_popcnt_u64(uint64_t(b)); #else return (int)_mm_popcnt_u64(b); #endif #else // Assumed gcc or compatible compiler #ifdef LARGEBOARDS return __builtin_popcountll(b >> 64) + __builtin_popcountll(b); #else return __builtin_popcountll(b); #endif #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); #ifdef LARGEBOARDS if (!(b << 64)) return Square(__builtin_ctzll(b >> 64) + 64); #endif return Square(__builtin_ctzll(b)); } inline Square msb(Bitboard b) { assert(b); #ifdef LARGEBOARDS if (b >> 64) return Square(int(SQUARE_BIT_MASK) ^ __builtin_clzll(b >> 64)); return Square(int(SQUARE_BIT_MASK) ^ (__builtin_clzll(b) + 64)); #else return Square(int(SQUARE_BIT_MASK) ^ __builtin_clzll(b)); #endif } #elif defined(_MSC_VER) // MSVC #ifdef _WIN64 // MSVC, WIN64 inline Square lsb(Bitboard b) { assert(b); unsigned long idx; #ifdef LARGEBOARDS if (uint64_t(b)) { _BitScanForward64(&idx, uint64_t(b)); return Square(idx); } else { _BitScanForward64(&idx, uint64_t(b >> 64)); return Square(idx + 64); } #else _BitScanForward64(&idx, b); return (Square) idx; #endif } inline Square msb(Bitboard b) { assert(b); unsigned long idx; #ifdef LARGEBOARDS if (b >> 64) { _BitScanReverse64(&idx, uint64_t(b >> 64)); return Square(idx + 64); } else { _BitScanReverse64(&idx, uint64_t(b)); return Square(idx); } #else _BitScanReverse64(&idx, b); return (Square) idx; #endif } #else // MSVC, WIN32 inline Square lsb(Bitboard b) { assert(b); unsigned long idx; #ifdef LARGEBOARDS if (b << 96) { _BitScanForward(&idx, uint32_t(b)); return Square(idx); } else if (b << 64) { _BitScanForward(&idx, uint32_t(b >> 32)); return Square(idx + 32); } else if (b << 32) { _BitScanForward(&idx, uint32_t(b >> 64)); return Square(idx + 64); } else { _BitScanForward(&idx, uint32_t(b >> 96)); return Square(idx + 96); } #else if (b & 0xffffffff) { _BitScanForward(&idx, uint32_t(b)); return Square(idx); } else { _BitScanForward(&idx, uint32_t(b >> 32)); return Square(idx + 32); } #endif } inline Square msb(Bitboard b) { assert(b); unsigned long idx; #ifdef LARGEBOARDS if (b >> 96) { _BitScanReverse(&idx, uint32_t(b >> 96)); return Square(idx + 96); } else if (b >> 64) { _BitScanReverse(&idx, uint32_t(b >> 64)); return Square(idx + 64); } else #endif if (b >> 32) { _BitScanReverse(&idx, uint32_t(b >> 32)); return Square(idx + 32); } else { _BitScanReverse(&idx, uint32_t(b)); return Square(idx); } } #endif #else // Compiler is neither GCC nor MSVC compatible #error "Compiler not supported." #endif /// least_significant_square_bb() returns the bitboard of the least significant /// square of a non-zero bitboard. It is equivalent to square_bb(lsb(bb)). inline Bitboard least_significant_square_bb(Bitboard b) { assert(b); return b & -b; } /// 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); } } // namespace Stockfish #endif // #ifndef BITBOARD_H_INCLUDED Fairy-Stockfish-fairy_sf_14_0_1_xq/src/endgame.cpp000066400000000000000000001070251414571233100222370ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include "bitboard.h" #include "endgame.h" #include "movegen.h" namespace Stockfish { namespace { // Used to drive the king towards the edge of the board // in KX vs K and KQ vs KR endgames. // Values range from 27 (center squares) to 90 (in the corners) inline int push_to_edge(Square s, const Position& pos) { int rd = edge_distance(rank_of(s), pos.max_rank()), fd = edge_distance(file_of(s), pos.max_file()); 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, const Position& pos) { return abs((pos.max_file() + pos.max_rank()) / 2 - rank_of(s) - file_of(s)); } // Used to drive the king towards the edge of the board in KSF vs K. inline int push_to_opposing_edge(Square s, const Position& pos) { int rd = rank_of(s), fd = edge_distance(file_of(s), pos.max_file()); return 20 - (7 * fd * fd / 2 - 7 * rd * rd / 4); } // 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(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(strongSide) == 1); if (file_of(pos.square(strongSide)) > pos.max_file() / 2) sq = flip_file(sq, pos.max_file()); return strongSide == WHITE ? sq : flip_rank(sq, pos.max_rank()); } } // namespace namespace Endgames { std::pair, Map> maps; void init() { add("KPK"); add("KNNK"); add("KBNK"); add("KRKP"); add("KRKB"); add("KRKN"); add("KQKP"); add("KQKR"); add("KNNKP"); // Fairy piece endgames add("KNSK"); add("KNFK"); add("KNSFKR"); add("KSFK"); add("KSFKF"); add("KRKS"); add("KRPKB"); add("KBPKB"); add("KBPKN"); add("KBPPKB"); add("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::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(pos).size()) return VALUE_DRAW; Square strongKing = pos.square(strongSide); Square weakKing = pos.square(weakSide); Value result = pos.non_pawn_material(strongSide) + pos.count(strongSide) * PawnValueEg + push_to_edge(weakKing, pos) + push_close(strongKing, weakKing); if ( pos.count(strongSide) || pos.count(strongSide) ||(pos.count(strongSide) && pos.count(strongSide)) || ( (pos.pieces(strongSide, BISHOP) & ~DarkSquares) && (pos.pieces(strongSide, BISHOP) & DarkSquares)) || pos.count(strongSide) >= 2 ||(pos.count(strongSide) && pos.count(strongSide)) ||(pos.count(strongSide) && pos.count(strongSide)) ||(pos.count(strongSide) && pos.count(strongSide) >= 2) ||(pos.count(strongSide) >= 3 && ( DarkSquares & pos.pieces(strongSide, FERS)) && (~DarkSquares & pos.pieces(strongSide, FERS)))) 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::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(strongSide); Square strongBishop = pos.square(strongSide); Square weakKing = pos.square(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, pos.max_file()) : weakKing, pos); 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::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(strongSide)); Square strongPawn = normalize(pos, strongSide, pos.square(strongSide)); Square weakKing = normalize(pos, strongSide, pos.square(weakSide)); Color us = strongSide == pos.side_to_move() ? WHITE : BLACK; // Non-standard promotion, evaluation unclear if ( pos.promotion_rank() != RANK_8 || RANK_MAX != RANK_8 || pos.promotion_piece_types().find(QUEEN) == pos.promotion_piece_types().end()) { Value result = PawnValueEg + Value(rank_of(strongPawn)); return strongSide == pos.side_to_move() ? result : -result; } 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::operator()(const Position& pos) const { assert(verify_material(pos, strongSide, RookValueMg, 0)); assert(verify_material(pos, weakSide, VALUE_ZERO, 1)); Square strongKing = pos.square(strongSide); Square weakKing = pos.square(weakSide); Square strongRook = pos.square(strongSide); Square weakPawn = pos.square(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::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(weakSide), pos)); 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::operator()(const Position& pos) const { assert(verify_material(pos, strongSide, RookValueMg, 0)); assert(verify_material(pos, weakSide, KnightValueMg, 0)); Square weakKing = pos.square(weakSide); Square weakKnight = pos.square(weakSide); Value result = Value(push_to_edge(weakKing, pos) + 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::operator()(const Position& pos) const { assert(verify_material(pos, strongSide, QueenValueMg, 0)); assert(verify_material(pos, weakSide, VALUE_ZERO, 1)); Square strongKing = pos.square(strongSide); Square weakKing = pos.square(weakSide); Square weakPawn = pos.square(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::operator()(const Position& pos) const { assert(verify_material(pos, strongSide, QueenValueMg, 0)); assert(verify_material(pos, weakSide, RookValueMg, 0)); Square strongKing = pos.square(strongSide); Square weakKing = pos.square(weakSide); Value result = QueenValueEg - RookValueEg + push_to_edge(weakKing, pos) + 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::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(weakSide); Square weakPawn = pos.square(weakSide); Value result = PawnValueEg + 2 * push_to_edge(weakKing, pos) - 10 * relative_rank(weakSide, weakPawn); return strongSide == pos.side_to_move() ? result : -result; } /// Some cases of trivial draws template<> Value Endgame::operator()(const Position&) const { return VALUE_DRAW; } /// KFsPs vs K. template<> Value Endgame::operator()(const Position& pos) const { assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); Square winnerKSq = pos.square(strongSide); Square loserKSq = pos.square(weakSide); Value result = pos.non_pawn_material(strongSide) + pos.count(strongSide) * PawnValueEg + push_to_edge(loserKSq, pos) + push_close(winnerKSq, loserKSq); if ( pos.count(strongSide) >= 3 && ( DarkSquares & pos.pieces(strongSide, FERS)) && (~DarkSquares & pos.pieces(strongSide, FERS))) result = std::min(result + VALUE_KNOWN_WIN, VALUE_TB_WIN_IN_MAX_PLY - 1); else if (pos.count(strongSide) + pos.count(strongSide) < 3) return VALUE_DRAW; else { bool dark = DarkSquares & pos.pieces(strongSide, FERS); bool light = ~DarkSquares & pos.pieces(strongSide, FERS); // Determine the color of ferzes from promoting pawns Bitboard b = pos.pieces(strongSide, PAWN); while (b && (!dark || !light)) { if (file_of(pop_lsb(b)) % 2 != relative_rank(strongSide, pos.promotion_rank(), pos.max_rank()) % 2) light = true; else dark = true; } if (!dark || !light) return VALUE_DRAW; // we can not checkmate with same colored ferzes } return strongSide == pos.side_to_move() ? result : -result; } /// Mate with KNS vs K. template<> Value Endgame::operator()(const Position& pos) const { assert(verify_material(pos, strongSide, KnightValueMg + SilverValueMg, 0)); assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); Square winnerKSq = pos.square(strongSide); Square loserKSq = pos.square(weakSide); Value result = VALUE_KNOWN_WIN + push_close(winnerKSq, loserKSq) + push_to_opposing_edge(relative_square(strongSide, loserKSq, pos.max_rank()), pos); return strongSide == pos.side_to_move() ? result : -result; } /// KNF vs K. Can only be won if the weaker side's king /// is close to a corner of the same color as the fers. template<> Value Endgame::operator()(const Position& pos) const { assert(verify_material(pos, strongSide, KnightValueMg + FersValueMg, 0)); assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); Square winnerKSq = pos.square(strongSide); Square loserKSq = pos.square(weakSide); Square fersSq = pos.square(strongSide); // tries to drive toward corners A1 or H8. If we have a // fers that cannot reach the above squares, we flip the kings in order // to drive the enemy toward corners A8 or H1. if (opposite_colors(fersSq, SQ_A1)) { winnerKSq = relative_square(BLACK, winnerKSq, pos.max_rank()); loserKSq = relative_square(BLACK, loserKSq, pos.max_rank()); } Value result = Value(push_close(winnerKSq, loserKSq)) + 50 * push_to_corner(loserKSq, pos); return strongSide == pos.side_to_move() ? result : -result; } /// KNSFKR vs K. template<> Value Endgame::operator()(const Position& pos) const { assert(verify_material(pos, strongSide, KnightValueMg + SilverValueMg + FersValueMg, 0)); assert(verify_material(pos, weakSide, RookValueMg, 0)); Square winnerKSq = pos.square(strongSide); Square loserKSq = pos.square(weakSide); Value result = KnightValueEg + SilverValueEg + FersValueEg - RookValueEg + push_close(winnerKSq, loserKSq) + push_to_opposing_edge(relative_square(strongSide, loserKSq, pos.max_rank()), pos); return strongSide == pos.side_to_move() ? result : -result; } /// Mate with KSF vs K. template<> Value Endgame::operator()(const Position& pos) const { assert(verify_material(pos, strongSide, SilverValueMg + FersValueMg, 0)); assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); Square winnerKSq = pos.square(strongSide); Square loserKSq = pos.square(weakSide); Value result = VALUE_KNOWN_WIN + push_close(winnerKSq, loserKSq) + push_to_opposing_edge(relative_square(strongSide, loserKSq, pos.max_rank()), pos); return strongSide == pos.side_to_move() ? result : -result; } /// Mate with KSF vs KF. template<> Value Endgame::operator()(const Position& pos) const { assert(verify_material(pos, strongSide, SilverValueMg + FersValueMg, 0)); assert(verify_material(pos, weakSide, FersValueMg, 0)); Square winnerKSq = pos.square(strongSide); Square loserKSq = pos.square(weakSide); Square fersSq = pos.square(weakSide); Value result = SilverValueEg + push_close(winnerKSq, loserKSq) + push_away(fersSq, loserKSq) + push_to_opposing_edge(relative_square(strongSide, loserKSq, pos.max_rank()), pos); return strongSide == pos.side_to_move() ? result : -result; } /// KR vs KS template<> Value Endgame::operator()(const Position& pos) const { assert(verify_material(pos, strongSide, RookValueMg, 0)); assert(verify_material(pos, weakSide, SilverValueMg, 0)); Square winnerKSq = pos.square(strongSide); Square loserKSq = pos.square(weakSide); Value result = RookValueEg - SilverValueEg + push_to_edge(loserKSq, pos) + push_close(winnerKSq, loserKSq); return strongSide == pos.side_to_move() ? result : -result; } /// 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::operator()(const Position& pos) const { assert(pos.non_pawn_material(strongSide) == BishopValueMg); assert(pos.count(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(strongSide); Square weakKing = pos.square(weakSide); Square strongKing = pos.square(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(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::operator()(const Position& pos) const { assert(verify_material(pos, strongSide, QueenValueMg, 0)); assert(pos.count(weakSide) == 1); assert(pos.count(weakSide) >= 1); Square strongKing = pos.square(strongSide); Square weakKing = pos.square(weakSide); Square weakRook = pos.square(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(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::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(strongSide)); Square strongRook = normalize(pos, strongSide, pos.square(strongSide)); Square strongPawn = normalize(pos, strongSide, pos.square(strongSide)); Square weakKing = normalize(pos, strongSide, pos.square(weakSide)); Square weakRook = normalize(pos, strongSide, pos.square(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(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(weakKing, strongPawn) == 1 && distance(strongKing, weakKing) > 2) return ScaleFactor(24 - 2 * distance(strongKing, weakKing)); } return SCALE_FACTOR_NONE; } template<> ScaleFactor Endgame::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(weakSide); Square weakBishop = pos.square(weakSide); Square strongKing = pos.square(strongSide); Square strongPawn = pos.square(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(weakBishop) & (strongPawn + push)) && distance(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::operator()(const Position& pos) const { assert(verify_material(pos, strongSide, RookValueMg, 2)); assert(verify_material(pos, weakSide, RookValueMg, 1)); Square strongPawn1 = lsb(pos.pieces(strongSide, PAWN)); Square strongPawn2 = msb(pos.pieces(strongSide, PAWN)); Square weakKing = pos.square(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(weakKing, strongPawn1) <= 1 && distance(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::operator()(const Position& pos) const { assert(pos.non_pawn_material(strongSide) == VALUE_ZERO); assert(pos.count(strongSide) >= 2); assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); Square weakKing = pos.square(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::operator()(const Position& pos) const { assert(verify_material(pos, strongSide, BishopValueMg, 1)); assert(verify_material(pos, weakSide, BishopValueMg, 0)); Square strongPawn = pos.square(strongSide); Square strongBishop = pos.square(strongSide); Square weakBishop = pos.square(weakSide); Square weakKing = pos.square(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::operator()(const Position& pos) const { assert(verify_material(pos, strongSide, BishopValueMg, 2)); assert(verify_material(pos, weakSide, BishopValueMg, 0)); Square strongBishop = pos.square(strongSide); Square weakBishop = pos.square(weakSide); if (!opposite_colors(strongBishop, weakBishop)) return SCALE_FACTOR_NONE; Square weakKing = pos.square(weakSide); Square strongPawn1 = lsb(pos.pieces(strongSide, PAWN)); Square strongPawn2 = msb(pos.pieces(strongSide, PAWN)); Square blockSq1, blockSq2; if (relative_rank(strongSide, strongPawn1) > relative_rank(strongSide, strongPawn2)) { blockSq1 = strongPawn1 + pawn_push(strongSide); blockSq2 = make_square(file_of(strongPawn2), rank_of(strongPawn1)); } else { blockSq1 = strongPawn2 + pawn_push(strongSide); blockSq2 = make_square(file_of(strongPawn1), rank_of(strongPawn2)); } switch (distance(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(blockSq2, pos.pieces()) & pos.pieces(weakSide, BISHOP)) || distance(strongPawn1, strongPawn2) >= 2)) return SCALE_FACTOR_DRAW; else if ( weakKing == blockSq2 && opposite_colors(weakKing, strongBishop) && ( weakBishop == blockSq1 || (attacks_bb(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::operator()(const Position& pos) const { assert(verify_material(pos, strongSide, BishopValueMg, 1)); assert(verify_material(pos, weakSide, KnightValueMg, 0)); Square strongPawn = pos.square(strongSide); Square strongBishop = pos.square(strongSide); Square weakKing = pos.square(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::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(strongSide)); Square weakKing = normalize(pos, strongSide, pos.square(weakSide)); Square strongPawn = normalize(pos, strongSide, pos.square(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. if ( pos.promotion_rank() != RANK_8 || RANK_MAX != RANK_8 || pos.promotion_piece_types().find(QUEEN) == pos.promotion_piece_types().end()) return SCALE_FACTOR_NONE; return Bitbases::probe(strongKing, strongPawn, weakKing, us) ? SCALE_FACTOR_NONE : SCALE_FACTOR_DRAW; } } // namespace Stockfish Fairy-Stockfish-fairy_sf_14_0_1_xq/src/endgame.h000066400000000000000000000070771414571233100217120ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef ENDGAME_H_INCLUDED #define ENDGAME_H_INCLUDED #include #include #include #include #include #include "position.h" #include "types.h" namespace Stockfish { /// EndgameCode lists all supported endgame functions by corresponding codes enum EndgameCode { EVALUATION_FUNCTIONS, KNNK, // KNN vs K KNNKP, // KNN vs KP KXK, // Generic "mate lone king" eval KBNK, // KBN vs K KPK, // KP vs K KRKP, // KR vs KP KRKB, // KR vs KB KRKN, // KR vs KN KQKP, // KQ vs KP KQKR, // KQ vs KR // Fairy piece endgames KFsPsK, // KFsPsK vs K KNSK, // KNS vs K KNFK, // KNF vs K KNSFKR, // KNSFKR vs K KSFK, // KSF vs K KSFKF, // KSF vs KF KRKS, // KR vs KS SCALING_FUNCTIONS, KBPsK, // KB and pawns vs K KQKRPs, // KQ vs KR and pawns KRPKR, // KRP vs KR KRPKB, // KRP vs KB KRPPKRP, // KRPP vs KRP KPsK, // K and pawns vs K KBPKB, // KBP vs KB KBPPKB, // KBPP vs KB KBPKN, // KBP vs KN KPKP // KP vs KP }; /// Endgame functions can be of two types depending on whether they return a /// Value or a ScaleFactor. template using eg_type = typename std::conditional<(E < SCALING_FUNCTIONS), Value, ScaleFactor>::type; /// Base and derived functors for endgame evaluation and scaling functions template struct EndgameBase { explicit EndgameBase(Color c) : strongSide(c), weakSide(~c) {} virtual ~EndgameBase() = default; virtual T operator()(const Position&) const = 0; const Color strongSide, weakSide; }; template> struct Endgame : public EndgameBase { explicit Endgame(Color c) : EndgameBase(c) {} T operator()(const Position&) const override; }; /// The Endgames namespace handles the pointers to endgame evaluation and scaling /// base objects in two std::map. We use polymorphism to invoke the actual /// endgame function by calling its virtual operator(). namespace Endgames { template using Ptr = std::unique_ptr>; template using Map = std::unordered_map>; extern std::pair, Map> maps; void init(); template Map& map() { return std::get::value>(maps); } template> void add(const std::string& code) { StateInfo st; map()[Position().set(code, WHITE, &st).material_key()] = Ptr(new Endgame(WHITE)); map()[Position().set(code, BLACK, &st).material_key()] = Ptr(new Endgame(BLACK)); } template const EndgameBase* probe(Key key) { auto it = map().find(key); return it != map().end() ? it->second.get() : nullptr; } } } // namespace Stockfish #endif // #ifndef ENDGAME_H_INCLUDED Fairy-Stockfish-fairy_sf_14_0_1_xq/src/evaluate.cpp000066400000000000000000002114051414571233100224430ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include // For std::memset #include #include #include #include #include #include #include "bitboard.h" #include "evaluate.h" #include "material.h" #include "misc.h" #include "pawns.h" #include "thread.h" #include "timeman.h" #include "uci.h" #include "incbin/incbin.h" // Macro to embed the default efficiently updatable neural network (NNUE) file // data in the engine binary (using incbin.h, by Dale Weiler). // This macro invocation will declare the following three variables // const unsigned char gEmbeddedNNUEData[]; // a pointer to the embedded data // const unsigned char *const gEmbeddedNNUEEnd; // a marker to the end // const unsigned int gEmbeddedNNUESize; // the size of the embedded file // Note that this does not work in Microsoft Visual Studio. #if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF) INCBIN(EmbeddedNNUE, EvalFileDefaultName); INCBIN(EmbeddedNNUE2, EvalFile2DefaultName); #else const unsigned char gEmbeddedNNUEData[1] = {0x0}; [[maybe_unused]] const unsigned char *const gEmbeddedNNUEEnd = &gEmbeddedNNUEData[1]; const unsigned int gEmbeddedNNUESize = 1; const unsigned char gEmbeddedNNUE2Data[1] = {0x0}; [[maybe_unused]] const unsigned char *const gEmbeddedNNUE2End = &gEmbeddedNNUE2Data[1]; const unsigned int gEmbeddedNNUE2Size = 1; #endif using namespace std; namespace Stockfish { const Variant* currentNnueVariant; namespace Eval { bool useNNUE; string eval_file_loaded = "None"; /// NNUE::init() tries to load a NNUE network at startup time, or when the engine /// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue" /// The name of the NNUE network is always retrieved from the EvalFile option. /// We search the given network in three locations: internally (the default /// network may be embedded in the binary), in the active working directory and /// in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY /// variable to have the engine search in a special directory in their distro. void NNUE::init() { useNNUE = Options["Use NNUE"]; if (!useNNUE) return; string eval_file = string(Options["EvalFile"]); // Restrict NNUE usage to corresponding variant // Support multiple variant networks separated by semicolon(Windows)/colon(Unix) stringstream ss(eval_file); string variant = string(Options["UCI_Variant"]); useNNUE = false; while (getline(ss, eval_file, UCI::SepChar)) { string basename = eval_file.substr(eval_file.find_last_of("\\/") + 1); string nnueAlias = variants.find(variant)->second->nnueAlias; if (basename.rfind(variant, 0) != string::npos || (!nnueAlias.empty() && basename.rfind(nnueAlias, 0) != string::npos)) { useNNUE = true; break; } } if (!useNNUE) return; currentNnueVariant = variants.find(variant)->second; #if defined(DEFAULT_NNUE_DIRECTORY) #define stringify2(x) #x #define stringify(x) stringify2(x) vector dirs = { "" , "" , CommandLine::binaryDirectory , stringify(DEFAULT_NNUE_DIRECTORY) }; #else vector dirs = { "" , "" , CommandLine::binaryDirectory }; #endif for (string directory : dirs) if (eval_file_loaded != eval_file) { if (directory != "") { ifstream stream(directory + eval_file, ios::binary); if (load_eval(eval_file, stream)) eval_file_loaded = eval_file; } if (directory == "" && (eval_file == EvalFileDefaultName || eval_file == EvalFile2DefaultName)) { // C++ way to prepare a buffer for a memory stream class MemoryBuffer : public basic_streambuf { public: MemoryBuffer(char* p, size_t n) { setg(p, p, p + n); setp(p, p + n); } }; bool first = eval_file == EvalFileDefaultName; MemoryBuffer buffer(const_cast(reinterpret_cast(first ? gEmbeddedNNUEData : gEmbeddedNNUE2Data)), size_t(first ? gEmbeddedNNUESize : gEmbeddedNNUE2Size)); istream stream(&buffer); if (load_eval(eval_file, stream)) eval_file_loaded = eval_file; } } } /// NNUE::verify() verifies that the last net used was loaded successfully void NNUE::verify() { string eval_file = string(Options["EvalFile"]); if (useNNUE && eval_file.find(eval_file_loaded) == string::npos) { 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_loaded << " enabled" << sync_endl; else sync_cout << "info string classical evaluation enabled" << sync_endl; } } namespace Trace { enum Tracing { NO_TRACE, TRACE }; enum Term { // The first PIECE_TYPE_NB entries are reserved for PieceType MATERIAL = PIECE_TYPE_NB, IMBALANCE, MOBILITY, THREAT, PASSED, SPACE, VARIANT, 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(1565); constexpr Value LazyThreshold2 = Value(1102); constexpr Value SpaceThreshold = Value(11551); // KingAttackWeights[PieceType] contains king attack weights by piece type constexpr int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 81, 52, 44, 10, 40 }; // 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] = { {}, {600, 600}, {803, 1292}, {639, 974}, {1087, 1878}, {759, 1132}, {600, 900} }; #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[][4 * RANK_NB] = { { S(-62,-79), S(-53,-57), S(-12,-31), S( -3,-17), S( 3, 7), S( 12, 13), // Knight S( 21, 16), S( 28, 21), S( 37, 26) }, { S(-47,-59), S(-20,-25), S( 14, -8), S( 29, 12), S( 39, 21), S( 53, 40), // Bishop S( 53, 56), S( 60, 58), S( 62, 65), S( 69, 72), S( 78, 78), S( 83, 87), S( 91, 88), S( 96, 98) }, { S(-60,-82), S(-24,-15), S( 0, 17) ,S( 3, 43), S( 4, 72), S( 14,100), // Rook S( 20,102), S( 30,122), S( 41,133), S(41 ,139), S( 41,153), S( 45,160), S( 57,165), S( 58,170), S( 67,175) }, { S(-29,-49), S(-16,-29), S( -8, -8), S( -8, 17), S( 18, 39), S( 25, 54), // Queen S( 23, 59), S( 37, 73), S( 41, 76), S( 54, 95), S( 65, 95) ,S( 68,101), S( 69,124), S( 70,128), S( 70,132), S( 70,133) ,S( 71,136), S( 72,140), S( 74,147), S( 76,149), S( 90,153), S(104,169), S(105,171), S(106,171), S(112,178), S(114,185), S(114,187), S(119,221) } }; constexpr Score MaxMobility = S(150, 200); constexpr Score DropMobility = S(10, 10); // BishopPawns[distance from edge] contains a file-dependent penalty for pawns on // squares of the same color as our bishop. constexpr Score BishopPawns[int(FILE_NB) / 2] = { S(3, 8), S(3, 9), S(2, 8), S(3, 8) }; // 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(57, 38), S(31, 24) }; // PassedRank[Rank] contains a bonus according to the rank of a passed pawn constexpr Score PassedRank[RANK_NB] = { S(0, 0), S(7, 27), S(16, 32), S(17, 40), S(64, 71), S(170, 174), S(278, 262) }; constexpr Score RookOnClosedFile = S(10, 5); constexpr Score RookOnOpenFile[] = { S(19, 6), S(47, 26) }; // ThreatByMinor/ByRook[attacked PieceType] contains bonuses according to // which piece type attacks which one. Attacks on lesser pieces which are // pawn-defended are not considered. constexpr Score ThreatByMinor[PIECE_TYPE_NB] = { S(0, 0), S(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) }; constexpr Value CorneredBishop = Value(50); // Assorted bonuses and penalties constexpr Score UncontestedOutpost = S( 1, 10); constexpr Score BishopOnKingRing = S( 24, 0); constexpr Score BishopXRayPawns = S( 4, 5); 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 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); // Variant and fairy piece bonuses constexpr Score KingProximity = S(2, 6); constexpr Score EndgameKingProximity = S(0, 10); constexpr Score ConnectedSoldier = S(20, 20); constexpr int VirtualCheck = 600; #undef S // Evaluation class computes and stores attacks tables and other working data template class Evaluation { public: Evaluation() = delete; explicit Evaluation(const Position& p) : pos(p) {} Evaluation& operator=(const Evaluation&) = delete; Value value(); private: template void initialize(); template Score pieces(PieceType Pt); template Score hand(PieceType pt); template Score king() const; template Score threats() const; template Score passed() const; template Score space() const; template Score variant() 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]; int kingAttackersCountInHand[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]; int kingAttackersWeightInHand[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 template void Evaluation::initialize() { constexpr Color Them = ~Us; constexpr Direction Up = pawn_push(Us); constexpr Direction Down = -Up; Bitboard LowRanks = rank_bb(relative_rank(Us, RANK_2, pos.max_rank())) | rank_bb(relative_rank(Us, RANK_3, pos.max_rank())); const Square ksq = pos.count(Us) ? pos.square(Us) : SQ_NONE; Bitboard dblAttackByPawn = pawn_double_attacks_bb(pos.pieces(Us, PAWN)); // Find our pawns that are blocked or on the first two ranks Bitboard b = pos.pieces(Us, PAWN) & (shift(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. if (pos.must_capture()) mobilityArea[Us] = AllSquares; else mobilityArea[Us] = ~(b | pos.pieces(Us, KING, QUEEN) | pos.blockers_for_king(Us) | pe->pawn_attacks(Them) | (pos.pieces(Us, SHOGI_PAWN) & shift(pos.pieces(Us))) | shift(pos.pieces(Them, SHOGI_PAWN, SOLDIER)) | shift(pos.promoted_soldiers(Them)) | shift(pos.promoted_soldiers(Them))); // Initialize attackedBy[] for king and pawns attackedBy[Us][KING] = pos.count(Us) ? pos.attacks_from(Us, KING, ksq) : Bitboard(0); attackedBy[Us][PAWN] = pe->pawn_attacks(Us); attackedBy[Us][SHOGI_PAWN] = shift(pos.pieces(Us, SHOGI_PAWN)); attackedBy[Us][ALL_PIECES] = attackedBy[Us][KING] | attackedBy[Us][PAWN] | attackedBy[Us][SHOGI_PAWN]; attackedBy2[Us] = (attackedBy[Us][KING] & attackedBy[Us][PAWN]) | (attackedBy[Us][KING] & attackedBy[Us][SHOGI_PAWN]) | (attackedBy[Us][PAWN] & attackedBy[Us][SHOGI_PAWN]) | dblAttackByPawn; // Init our king safety tables if (!pos.count(Us)) kingRing[Us] = Bitboard(0); else { Square s = make_square(std::clamp(file_of(ksq), FILE_B, File(pos.max_file() - 1)), std::clamp(rank_of(ksq), RANK_2, Rank(pos.max_rank() - 1))); kingRing[Us] = attacks_bb(s) | s; } kingAttackersCount[Them] = popcount(kingRing[Us] & (pe->pawn_attacks(Them) | shift(pos.pieces(Them, SHOGI_PAWN)))); kingAttacksCount[Them] = kingAttackersWeight[Them] = 0; kingAttackersCountInHand[Them] = kingAttackersWeightInHand[Them] = 0; // Remove from kingRing[] the squares defended by two pawns kingRing[Us] &= ~dblAttackByPawn; kingRing[Us] &= pos.board_bb(); } // Evaluation::pieces() scores pieces of a given color and type template template Score Evaluation::pieces(PieceType Pt) { constexpr Color Them = ~Us; constexpr Direction Down = -pawn_push(Us); constexpr Bitboard OutpostRanks = (Us == WHITE ? Rank4BB | Rank5BB | Rank6BB : Rank5BB | Rank4BB | Rank3BB); Bitboard b1 = pos.pieces(Us, Pt); Bitboard b, bb; Score score = SCORE_ZERO; attackedBy[Us][Pt] = 0; while (b1) { Square s = pop_lsb(b1); // Find attacked squares, including x-ray attacks for bishops and rooks b = Pt == BISHOP ? attacks_bb(s, pos.pieces() ^ pos.pieces(QUEEN)) : Pt == ROOK && !pos.diagonal_lines() ? attacks_bb< ROOK>(s, pos.pieces() ^ pos.pieces(QUEEN) ^ pos.pieces(Us, ROOK)) : pos.attacks_from(Us, Pt, s); // Restrict mobility to actual squares of board b &= pos.board_bb(Us, Pt); if (pos.blockers_for_king(Us) & s) b &= line_bb(pos.square(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[std::min(Pt, FAIRY_PIECES)]; kingAttacksCount[Us] += popcount(b & attackedBy[Them][KING]); } else if (Pt == ROOK && (file_bb(s) & kingRing[Them])) score += RookOnKingRing; else if (Pt == BISHOP && (attacks_bb(s, pos.pieces(PAWN)) & kingRing[Them])) score += BishopOnKingRing; if (Pt > QUEEN) b = (b & pos.pieces()) | (pos.moves_from(Us, Pt, s) & ~pos.pieces() & pos.board_bb()); int mob = popcount(b & mobilityArea[Us]); if (Pt <= QUEEN) mobility[Us] += MobilityBonus[Pt - 2][mob]; else mobility[Us] += MaxMobility * (mob - 2) / (8 + mob); // Piece promotion bonus if (pos.promoted_piece_type(Pt) != NO_PIECE_TYPE) { Bitboard zone = zone_bb(Us, pos.promotion_rank(), pos.max_rank()); if (zone & (b | s)) score += make_score(PieceValue[MG][pos.promoted_piece_type(Pt)] - PieceValue[MG][Pt], PieceValue[EG][pos.promoted_piece_type(Pt)] - PieceValue[EG][Pt]) / (zone & s && b ? 6 : 12); } else if (pos.piece_demotion() && pos.unpromoted_piece_on(s)) score -= make_score(PieceValue[MG][Pt] - PieceValue[MG][pos.unpromoted_piece_on(s)], PieceValue[EG][Pt] - PieceValue[EG][pos.unpromoted_piece_on(s)]) / 4; else if (pos.captures_to_hand() && pos.unpromoted_piece_on(s)) score += make_score(PieceValue[MG][Pt] - PieceValue[MG][pos.unpromoted_piece_on(s)], PieceValue[EG][Pt] - PieceValue[EG][pos.unpromoted_piece_on(s)]) / 8; // Penalty if the piece is far from the kings in drop variants if ((pos.captures_to_hand() || pos.two_boards()) && pos.count(Them) && pos.count(Us)) { if (!(b & (kingRing[Us] | kingRing[Them]))) score -= KingProximity * distance(s, pos.square(Us)) * distance(s, pos.square(Them)); } else if (pos.count(Us) && (Pt == FERS || Pt == SILVER)) score -= EndgameKingProximity * (distance(s, pos.square(Us)) - 2); if (Pt == SOLDIER && (pos.pieces(Us, SOLDIER) & rank_bb(s) & adjacent_files_bb(s))) score += ConnectedSoldier; if (Pt == BISHOP || Pt == KNIGHT) { // Bonus if the piece is on an outpost square or can reach one // Bonus for knights (UncontestedOutpost) if few relevant targets bb = OutpostRanks & (attackedBy[Us][PAWN] | shift(pos.pieces(PAWN))) & ~pe->pawn_attacks_span(Them); Bitboard targets = pos.pieces(Them) & ~pos.pieces(PAWN); if ( Pt == KNIGHT && bb & s & ~CenterFiles // on a side outpost && !(b & targets) // no relevant attacks && (!more_than_one(targets & (s & QueenSide ? QueenSide : KingSide)))) score += UncontestedOutpost * popcount(pos.pieces(PAWN) & (s & QueenSide ? QueenSide : KingSide)); else if (bb & s) score += Outpost[Pt == BISHOP]; else if (Pt == KNIGHT && bb & b & ~pos.pieces(Us)) score += ReachableOutpost; // Bonus for a knight or bishop shielded by pawn if (shift(pos.pieces(PAWN)) & s) score += MinorBehindPawn; // Penalty if the piece is far from the king if (pos.count(Us)) score -= KingProtector[Pt == BISHOP] * distance(pos.square(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(pos.pieces()); score -= BishopPawns[edge_distance(file_of(s), pos.max_file())] * 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(s) & pos.pieces(Them, PAWN)); // Bonus for bishop on a long diagonal which can "see" both center squares if (more_than_one(attacks_bb(s, pos.pieces(PAWN)) & Center)) score += LongDiagonalBishop; // An important Chess960 pattern: a cornered bishop blocked by a friendly // pawn diagonally in front of it is a very serious problem, especially // when that pawn is also blocked. if ( pos.is_chess960() && (s == relative_square(Us, SQ_A1) || s == relative_square(Us, SQ_H1))) { Direction d = pawn_push(Us) + (file_of(s) == FILE_A ? EAST : WEST); if (pos.piece_on(s + d) == make_piece(Us, PAWN)) score -= !pos.empty(s + d + pawn_push(Us)) ? 4 * make_score(CorneredBishop, CorneredBishop) : 3 * make_score(CorneredBishop, CorneredBishop); } } } if (Pt == ROOK) { // Bonuses for rook on a (semi-)open or closed file if (pos.is_on_semiopen_file(Us, s)) { score += RookOnOpenFile[pos.is_on_semiopen_file(Them, s)]; } else { // If our pawn on this file is blocked, increase penalty if ( pos.pieces(Us, PAWN) & shift(pos.pieces()) & file_bb(s)) { score -= RookOnClosedFile; } // Penalty when trapped by the king, even more if the king cannot castle if (mob <= 3 && pos.count(Us)) { File kf = file_of(pos.square(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, Them)) score -= WeakQueen; } } if constexpr (T) Trace::add(Pt, Us, score); return score; } // Evaluation::hand() scores pieces of a given color and type in hand template template Score Evaluation::hand(PieceType pt) { constexpr Color Them = ~Us; Score score = SCORE_ZERO; if (pos.count_in_hand(Us, pt) > 0 && pt != KING) { Bitboard b = pos.drop_region(Us, pt) & ~pos.pieces() & (~attackedBy2[Them] | attackedBy[Us][ALL_PIECES]); if ((b & kingRing[Them]) && pt != SHOGI_PAWN) { kingAttackersCountInHand[Us] += pos.count_in_hand(Us, pt); kingAttackersWeightInHand[Us] += KingAttackWeights[std::min(pt, FAIRY_PIECES)] * pos.count_in_hand(Us, pt); kingAttacksCount[Us] += popcount(b & attackedBy[Them][KING]); } Bitboard theirHalf = pos.board_bb() & ~forward_ranks_bb(Them, relative_rank(Them, Rank((pos.max_rank() - 1) / 2), pos.max_rank())); mobility[Us] += DropMobility * popcount(b & theirHalf & ~attackedBy[Them][ALL_PIECES]); // Bonus for Kyoto shogi style drops of promoted pieces if (pos.promoted_piece_type(pt) != NO_PIECE_TYPE && pos.drop_promoted()) score += make_score(std::max(PieceValue[MG][pos.promoted_piece_type(pt)] - PieceValue[MG][pt], VALUE_ZERO), std::max(PieceValue[EG][pos.promoted_piece_type(pt)] - PieceValue[EG][pt], VALUE_ZERO)) / 4 * pos.count_in_hand(Us, pt); // Mobility bonus for reversi variants if (pos.enclosing_drop()) mobility[Us] += make_score(500, 500) * popcount(b); // Reduce score if there is a deficit of gates if (pos.seirawan_gating() && !pos.piece_drops() && pos.count_in_hand(Us, ALL_PIECES) > popcount(pos.gates(Us))) score -= make_score(200, 900) / pos.count_in_hand(Us, ALL_PIECES) * (pos.count_in_hand(Us, ALL_PIECES) - popcount(pos.gates(Us))); // Redundant pieces that can not be doubled per file (e.g., shogi pawns) if (pt == pos.drop_no_doubled()) score -= make_score(50, 20) * std::max(pos.count_with_hand(Us, pt) - pos.max_file() - 1, 0); } return score; } // Evaluation::king() assigns bonuses and penalties to a king of a given color template template Score Evaluation::king() const { constexpr Color Them = ~Us; Rank r = relative_rank(Us, std::min(Rank((pos.max_rank() - 1) / 2 + 1), pos.max_rank()), pos.max_rank()); Bitboard Camp = pos.board_bb() & ~forward_ranks_bb(Us, r); if (!pos.count(Us) || !pos.checking_permitted() || pos.checkmate_value() != -VALUE_MATE) return SCORE_ZERO; Bitboard weak, b1, b2, b3, safe, unsafeChecks = 0; Bitboard queenChecks, knightChecks, pawnChecks, otherChecks; int kingDanger = 0; const Square ksq = pos.square(Us); // Init the score with king shelter and enemy pawns storm Score score = pe->king_safety(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); if (!pos.check_counting() || pos.checks_remaining(Them) > 1) safe &= ~attackedBy[Us][ALL_PIECES] | (weak & attackedBy2[Them]); b1 = attacks_bb(ksq, pos.pieces() ^ pos.pieces(Us, QUEEN)); b2 = attacks_bb(ksq, pos.pieces() ^ pos.pieces(Us, QUEEN)); std::function get_attacks = [this](Color c, PieceType pt) { return attackedBy[c][pt] | (pos.piece_drops() && pos.count_in_hand(c, pt) > 0 ? pos.drop_region(c, pt) & ~pos.pieces() : Bitboard(0)); }; for (PieceType pt : pos.piece_types()) { switch (pt) { case QUEEN: // Enemy queen safe checks: we count them only if they are from squares from // which we can't give a rook check, because rook checks are more valuable. queenChecks = (b1 | b2) & get_attacks(Them, QUEEN) & pos.board_bb() & safe & ~attackedBy[Us][QUEEN] & ~(b1 & attackedBy[Them][ROOK]); if (queenChecks) kingDanger += SafeCheck[QUEEN][more_than_one(queenChecks)]; break; case ROOK: case BISHOP: case KNIGHT: knightChecks = attacks_bb(Us, pt, ksq, pos.pieces() ^ pos.pieces(Us, QUEEN)) & get_attacks(Them, pt) & pos.board_bb(); if (knightChecks & safe) kingDanger += SafeCheck[pt][more_than_one(knightChecks & safe)]; else unsafeChecks |= knightChecks; break; case PAWN: if (pos.piece_drops() && pos.count_in_hand(Them, pt) > 0) { pawnChecks = attacks_bb(Us, pt, ksq, pos.pieces()) & ~pos.pieces() & pos.board_bb(); if (pawnChecks & safe) kingDanger += SafeCheck[PAWN][more_than_one(pawnChecks & safe)]; else unsafeChecks |= pawnChecks; } break; case SHOGI_PAWN: if (pos.promoted_piece_type(pt)) { otherChecks = attacks_bb(Us, pos.promoted_piece_type(pt), ksq, pos.pieces()) & attackedBy[Them][pt] & zone_bb(Them, pos.promotion_rank(), pos.max_rank()) & pos.board_bb(); if (otherChecks & safe) kingDanger += SafeCheck[FAIRY_PIECES][more_than_one(otherChecks & safe)]; else unsafeChecks |= otherChecks; } break; case KING: break; default: otherChecks = attacks_bb(Us, pt, ksq, pos.pieces()) & get_attacks(Them, pt) & pos.board_bb(); if (otherChecks & safe) kingDanger += SafeCheck[FAIRY_PIECES][more_than_one(otherChecks & safe)]; else unsafeChecks |= otherChecks; } } // Virtual piece drops if (pos.two_boards() && pos.piece_drops()) { for (PieceType pt : pos.piece_types()) if (pos.count_in_hand(Them, pt) <= 0 && (attacks_bb(Us, pt, ksq, pos.pieces()) & safe & pos.drop_region(Them, pt) & ~pos.pieces())) { kingDanger += VirtualCheck * 500 / (500 + PieceValue[MG][pt]); // Presumably a mate threat if (!(attackedBy[Us][KING] & ~(attackedBy[Them][ALL_PIECES] | pos.pieces(Us)))) kingDanger += 2000; } } if (pos.check_counting()) kingDanger += kingDanger * 7 / (3 + pos.checks_remaining(Them)); Square s = file_of(ksq) == FILE_A ? ksq + EAST : file_of(ksq) == pos.max_file() ? ksq + WEST : ksq; Bitboard kingFlank = pos.max_file() == FILE_H ? KingFlank[file_of(ksq)] : file_bb(s) | adjacent_files_bb(s); // 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 & Camp; b2 = b1 & attackedBy2[Them]; b3 = attackedBy[Us][ALL_PIECES] & kingFlank & Camp; int kingFlankAttack = popcount(b1) + popcount(b2); int kingFlankDefense = popcount(b3); kingDanger += kingAttackersCount[Them] * kingAttackersWeight[Them] + kingAttackersCountInHand[Them] * kingAttackersWeight[Them] + kingAttackersCount[Them] * kingAttackersWeightInHand[Them] + 183 * popcount(kingRing[Us] & (weak | ~pos.board_bb(Us, KING))) * (1 + pos.captures_to_hand() + pos.check_counting()) + 148 * popcount(unsafeChecks) * (1 + pos.check_counting()) + 98 * popcount(pos.blockers_for_king(Us)) + 69 * kingAttacksCount[Them] * (2 + 8 * pos.check_counting() + pos.captures_to_hand()) / 2 + 3 * kingFlankAttack * kingFlankAttack / 8 + mg_value(mobility[Them] - mobility[Us]) * int(!pos.captures_to_hand()) - 873 * !(pos.major_pieces(Them) || pos.captures_to_hand()) * 2 / (2 + 2 * pos.check_counting() + 2 * pos.two_boards() + 2 * pos.makpong() + (pos.king_type() != KING) * (pos.diagonal_lines() ? 1 : 2)) - 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(std::min(kingDanger, 3500) * kingDanger / 4096, kingDanger / 16); // Penalty when our king is on a pawnless flank if (!(pos.pieces(PAWN) & kingFlank)) score -= PawnlessFlank; // Penalty if king flank is under attack, potentially moving toward the king score -= FlankAttacks * kingFlankAttack * (1 + 5 * pos.captures_to_hand() + pos.check_counting()); if (pos.check_counting()) score += make_score(0, mg_value(score) * 2 / (2 + pos.checks_remaining(Them))); if (pos.king_type() == WAZIR) score += make_score(0, mg_value(score) / 2); // For drop games, king danger is independent of game phase, but dependent on material density if (pos.captures_to_hand() || pos.two_boards()) score = make_score(mg_value(score) * me->material_density() / 11000, mg_value(score) * me->material_density() / 11000); if constexpr (T) Trace::add(KING, Us, score); return score; } // Evaluation::threats() assigns bonuses according to the types of the // attacking and the attacked pieces. template template Score Evaluation::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; // Bonuses for variants with mandatory captures if (pos.must_capture()) { // Penalties for possible captures Bitboard captures = attackedBy[Us][ALL_PIECES] & pos.pieces(Them); if (captures) score -= make_score(2000, 2000) / (1 + popcount(captures & attackedBy[Them][ALL_PIECES] & ~attackedBy2[Us])); // Bonus if we threaten to force captures Bitboard moves = 0, piecebb = pos.pieces(Us); while (piecebb) { Square s = pop_lsb(piecebb); if (type_of(pos.piece_on(s)) != KING) moves |= pos.moves_from(Us, type_of(pos.piece_on(s)), s); } score += make_score(200, 200) * popcount(attackedBy[Them][ALL_PIECES] & moves & ~pos.pieces()); score += make_score(200, 220) * popcount(attackedBy[Them][ALL_PIECES] & moves & ~pos.pieces() & ~attackedBy2[Us]); } // Extinction threats if (pos.extinction_value() == -VALUE_MATE) { Bitboard bExt = attackedBy[Us][ALL_PIECES] & pos.pieces(Them); for (PieceType pt : pos.extinction_piece_types()) { if (pt == ALL_PIECES) continue; int denom = std::max(pos.count_with_hand(Them, pt) - pos.extinction_piece_count(), 1); // Explosion threats if (pos.blast_on_capture()) { int evasions = popcount(((attackedBy[Them][pt] & ~pos.pieces(Them)) | pos.pieces(Them, pt)) & ~attackedBy[Us][ALL_PIECES]) * denom; int attacks = popcount((attackedBy[Them][pt] | pos.pieces(Them, pt)) & attackedBy[Us][ALL_PIECES]); int explosions = 0; Bitboard bExtBlast = bExt & (attackedBy2[Us] | ~attackedBy[Us][pt]); while (bExtBlast) { Square s = pop_lsb(bExtBlast); if (((attacks_bb(s) | s) & pos.pieces(Them, pt)) && !(attacks_bb(s) & pos.pieces(Us, pt))) explosions++; } int danger = 20 * attacks / (evasions + 1) + 40 * explosions; score += make_score(danger * (100 + danger), 0); } else // Direct extinction threats score += make_score(1000, 1000) / (denom * denom) * popcount(bExt & pos.pieces(Them, pt)); } } // Non-pawn enemies nonPawnEnemies = pos.pieces(Them) & ~pos.pieces(PAWN, SHOGI_PAWN) & ~pos.pieces(SOLDIER); // 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] | attackedBy[Them][SHOGI_PAWN] | attackedBy[Them][SOLDIER]) | (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(b) & nonPawnEnemies; score += ThreatBySafePawn * popcount(b); // Find squares where our pawns can push on the next move b = shift(pos.pieces(Us, PAWN)) & ~pos.pieces(); b |= shift(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(b) | shift(shift(pos.pieces(Us, SHOGI_PAWN, SOLDIER)))) & nonPawnEnemies; score += ThreatByPawnPush * popcount(b); // Bonus for threats on the next moves against enemy queen if (pos.count(Them) == 1) { bool queenImbalance = pos.count() == 1; Square s = pos.square(Them); safe = mobilityArea[Us] & ~pos.pieces(Us, PAWN) & ~stronglyProtected; b = attackedBy[Us][KNIGHT] & attacks_bb(s); score += KnightOnQueen * popcount(b & safe) * (1 + queenImbalance); b = (attackedBy[Us][BISHOP] & attacks_bb(s, pos.pieces())) | (attackedBy[Us][ROOK ] & attacks_bb(s, pos.pieces())); score += SliderOnQueen * popcount(b & safe & attackedBy2[Us]) * (1 + queenImbalance); } if constexpr (T) Trace::add(THREAT, Us, score); return score; } // Evaluation::passed() evaluates the passed pawns and candidate passed // pawns of the given color. template template Score Evaluation::passed() const { constexpr Color Them = ~Us; constexpr Direction Up = pawn_push(Us); constexpr Direction Down = -Up; auto king_proximity = [&](Color c, Square s) { return pos.extinction_value() == VALUE_MATE ? 0 : pos.count(c) ? std::min(distance(pos.square(c), s), 5) : 5; }; Bitboard b, bb, squaresToQueen, unsafeSquares, blockedPassers, helpers; Score score = SCORE_ZERO; b = pe->passed_pawns(Us); blockedPassers = b & shift(pos.pieces(Them, PAWN)); if (blockedPassers) { helpers = shift(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(helpers) | shift(helpers); } while (b) { Square s = pop_lsb(b); assert(!(pos.pieces(Them, PAWN) & forward_file_bb(Us, s + Up))); int r = std::max(RANK_8 - std::max(pos.promotion_rank() - relative_rank(Us, s, pos.max_rank()), 0), 0); Score bonus = PassedRank[r]; if (r > RANK_3) { int w = 5 * r - 13; Square blockSq = s + Up; // Adjust bonus based on the king's proximity bonus += make_score(0, ( king_proximity(Them, blockSq) * 19 / 4 - king_proximity(Us, blockSq) * 2) * w); // If blockSq is not the queening square then consider also a second push if (r != RANK_7) bonus -= make_score(0, king_proximity(Us, blockSq + Up) * w); // If the pawn is free to advance, then increase the bonus if (pos.empty(blockSq)) { squaresToQueen = forward_file_bb(Us, s); unsafeSquares = passed_pawn_span(Us, s); bb = forward_file_bb(Them, s) & pos.pieces(ROOK, QUEEN); if (!(pos.pieces(Them) & bb)) unsafeSquares &= attackedBy[Them][ALL_PIECES] | pos.pieces(Them); // If there are no enemy pieces or attacks on passed pawn span, assign a big bonus. // Or if there is some, but they are all attacked by our pawns, assign a bit smaller bonus. // Otherwise assign a smaller bonus if the path to queen is not attacked // and even smaller bonus if it is attacked but block square is not. int k = !unsafeSquares ? 36 : !(unsafeSquares & ~attackedBy[Us][PAWN]) ? 30 : !(unsafeSquares & squaresToQueen) ? 17 : !(unsafeSquares & blockSq) ? 7 : 0 ; // Assign a larger bonus if the block square is defended if ((pos.pieces(Us) & bb) || (attackedBy[Us][ALL_PIECES] & blockSq)) k += 5; bonus += make_score(k * w, k * w); } } // r > RANK_3 score += bonus - PassedFile * edge_distance(file_of(s), pos.max_file()); } // Scale by maximum promotion piece value Value maxMg = VALUE_ZERO, maxEg = VALUE_ZERO; for (PieceType pt : pos.promotion_piece_types()) { maxMg = std::max(maxMg, PieceValue[MG][pt]); maxEg = std::max(maxEg, PieceValue[EG][pt]); } score = make_score(mg_value(score) * int(maxMg - PawnValueMg) / (QueenValueMg - PawnValueMg), eg_value(score) * int(maxEg - PawnValueEg) / (QueenValueEg - PawnValueEg)); // Score passed shogi pawns PieceType pt = pos.promoted_piece_type(SHOGI_PAWN); if (pt != NO_PIECE_TYPE) { b = pos.pieces(Us, SHOGI_PAWN); while (b) { Square s = pop_lsb(b); if ((pos.pieces(Them, SHOGI_PAWN) & forward_file_bb(Us, s)) || relative_rank(Us, s, pos.max_rank()) == pos.max_rank()) continue; Square blockSq = s + Up; int d = 2 * std::max(pos.promotion_rank() - relative_rank(Us, s, pos.max_rank()), 1); d += !!(attackedBy[Them][ALL_PIECES] & ~attackedBy2[Us] & blockSq); score += make_score(PieceValue[MG][pt], PieceValue[EG][pt]) / (d * d); } } if constexpr (T) Trace::add(PASSED, Us, score); return score; } // Evaluation::space() computes a space evaluation for a given side, aiming to improve game // play in the opening. It is based on the number of safe squares on the four central files // on ranks 2 to 4. Completely safe squares behind a friendly pawn are counted twice. // Finally, the space bonus is multiplied by a weight which decreases according to occupancy. template template Score Evaluation::space() const { bool pawnsOnly = !(pos.pieces(Us) ^ pos.pieces(Us, PAWN)); // Early exit if, for example, both queens or 6 minor pieces have been exchanged if (pos.non_pawn_material() < SpaceThreshold && !pawnsOnly && pos.double_step_enabled()) 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(behind); behind |= shift(behind); if (pawnsOnly) { safe = pos.board_bb() & ((attackedBy2[Us] & ~attackedBy2[Them]) | (attackedBy[Us][PAWN] & ~pos.pieces(Us, PAWN))); behind = 0; } // Compute space score based on the number of safe squares and number of our pieces // increased with number of total blocked pawns in position. int bonus = popcount(safe) + popcount(behind & safe & ~attackedBy[Them][ALL_PIECES]); int weight = pos.count(Us) - 3 + std::min(pe->blocked_count(), 9); Score score = make_score(bonus * weight * weight / 16, 0); if (pos.capture_the_flag(Us)) score += make_score(200, 200) * popcount(behind & safe & pos.capture_the_flag(Us)); if constexpr (T) Trace::add(SPACE, Us, score); return score; } // Evaluation::variant() computes variant-specific evaluation bonuses for a given side. template template Score Evaluation::variant() const { constexpr Color Them = ~Us; constexpr Direction Down = pawn_push(Them); Score score = SCORE_ZERO; // Capture the flag if (pos.capture_the_flag(Us)) { PieceType ptCtf = pos.capture_the_flag_piece(); Bitboard ctfPieces = pos.pieces(Us, ptCtf); Bitboard ctfTargets = pos.capture_the_flag(Us) & pos.board_bb(); Bitboard onHold = 0; Bitboard onHold2 = 0; Bitboard processed = 0; Bitboard blocked = pos.pieces(Us, PAWN) | attackedBy[Them][ALL_PIECES]; Bitboard doubleBlocked = attackedBy2[Them] | (pos.pieces(Us, PAWN) & (shift(pos.pieces()) | attackedBy[Them][ALL_PIECES])) | (pos.pieces(Them) & pe->pawn_attacks(Them)) | (pawn_attacks_bb(pos.pieces(Them, PAWN) & pe->pawn_attacks(Them))); Bitboard inaccessible = pos.pieces(Us, PAWN) & shift(pos.pieces(Them, PAWN)); // Traverse all paths of the CTF pieces to the CTF targets. // Put squares that are attacked or occupied on hold for one iteration. // This reflects that likely a move will be needed to block or capture the attack. for (int dist = 0; (ctfPieces || onHold || onHold2) && (ctfTargets & ~processed); dist++) { int wins = popcount(ctfTargets & ctfPieces); if (wins) score += make_score(4000, 4000) * wins / (wins + dist * dist); Bitboard current = ctfPieces & ~ctfTargets; processed |= ctfPieces; ctfPieces = onHold & ~processed; onHold = onHold2 & ~processed; onHold2 = 0; while (current) { Square s = pop_lsb(current); Bitboard attacks = ( (PseudoAttacks[Us][ptCtf][s] & pos.pieces()) | (PseudoMoves[Us][ptCtf][s] & ~pos.pieces())) & ~processed & pos.board_bb(); ctfPieces |= attacks & ~blocked; onHold |= attacks & ~doubleBlocked; onHold2 |= attacks & ~inaccessible; } } } // nCheck if (pos.check_counting()) { int remainingChecks = pos.checks_remaining(Us); assert(remainingChecks > 0); score += make_score(3600, 1000) / (remainingChecks * remainingChecks); } // Extinction if (pos.extinction_value() != VALUE_NONE) { for (PieceType pt : pos.extinction_piece_types()) if (pt != ALL_PIECES) { // Single piece type extinction bonus int denom = std::max(pos.count(Us, pt) - pos.extinction_piece_count(), 1); if (pos.count(Them, pt) >= pos.extinction_opponent_piece_count() || pos.two_boards()) score += make_score(1000000 / (500 + PieceValue[MG][pt]), 1000000 / (500 + PieceValue[EG][pt])) / (denom * denom) * (pos.extinction_value() / VALUE_MATE); } else if (pos.extinction_value() == VALUE_MATE) { // Losing chess variant bonus score += make_score(pos.non_pawn_material(Us), pos.non_pawn_material(Us)) / std::max(pos.count(Us), 1); } else if (pos.count(Us) == pos.count(Us)) { // Pawns easy to stop/capture int l = 0, m = 0, r = popcount(pos.pieces(Us, PAWN) & file_bb(FILE_A)); for (File f = FILE_A; f <= pos.max_file(); ++f) { l = m; m = r; r = popcount(pos.pieces(Us, PAWN) & shift(file_bb(f))); score -= make_score(80 - 10 * (edge_distance(f, pos.max_file()) % 2), 80 - 15 * (edge_distance(f, pos.max_file()) % 2)) * m / (1 + l * r); } } else if (pos.count(Them) == pos.count(Them)) { // Add a bonus according to how close we are to breaking through the pawn wall int dist = 8; Bitboard breakthroughs = attackedBy[Us][ALL_PIECES] & rank_bb(relative_rank(Us, pos.max_rank(), pos.max_rank())); if (breakthroughs) dist = attackedBy[Us][QUEEN] & breakthroughs ? 0 : 1; else for (File f = FILE_A; f <= pos.max_file(); ++f) dist = std::min(dist, popcount(pos.pieces(PAWN) & file_bb(f))); score += make_score(70, 70) * pos.count(Them) / (1 + dist * dist) / (pos.pieces(Us, QUEEN) ? 2 : 4); } } // Connect-n if (pos.connect_n() > 0) { for (Direction d : {NORTH, NORTH_EAST, EAST, SOUTH_EAST}) { // Find sufficiently large gaps Bitboard b = pos.board_bb() & ~pos.pieces(Them); for (int i = 1; i < pos.connect_n(); i++) b &= shift(d, b); // Count number of pieces per gap while (b) { Square s = pop_lsb(b); int c = 0; for (int j = 0; j < pos.connect_n(); j++) if (pos.pieces(Us) & (s - j * d)) c++; score += make_score(200, 200) * c / (pos.connect_n() - c) / (pos.connect_n() - c); } } } // Potential piece flips (Reversi) if (pos.flip_enclosed_pieces()) { // Stable pieces if (pos.flip_enclosed_pieces() == REVERSI) { Bitboard edges = (FileABB | file_bb(pos.max_file()) | Rank1BB | rank_bb(pos.max_rank())) & pos.board_bb(); Bitboard edgePieces = pos.pieces(Us) & edges; while (edgePieces) { Bitboard connectedEdge = attacks_bb(Us, ROOK, pop_lsb(edgePieces), ~(pos.pieces(Us) & edges)) & edges; if (!more_than_one(connectedEdge & ~pos.pieces(Us))) score += make_score(300, 300); else if (!(connectedEdge & ~pos.pieces())) score += make_score(200, 200); } } // Unstable Bitboard unstable = 0; Bitboard drops = pos.drop_region(Them, IMMOBILE_PIECE); while (drops) { Square s = pop_lsb(drops); if (pos.flip_enclosed_pieces() == REVERSI) { Bitboard b = attacks_bb(Them, QUEEN, s, ~pos.pieces(Us)) & ~PseudoAttacks[Them][KING][s] & pos.pieces(Them); while(b) unstable |= between_bb(s, pop_lsb(b)); } else unstable |= PseudoAttacks[Them][KING][s] & pos.pieces(Us); } score -= make_score(200, 200) * popcount(unstable); } if (T) Trace::add(VARIANT, 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 Value Evaluation::winnable(Score score) const { // No initiative bonus for extinction variants int complexity = 0; bool pawnsOnBothFlanks = true; if (pos.extinction_value() == VALUE_NONE && !pos.captures_to_hand() && !pos.connect_n() && !pos.material_counting()) { int outflanking = !pos.count(WHITE) || !pos.count(BLACK) ? 0 : distance(pos.square(WHITE), pos.square(BLACK)) + int(rank_of(pos.square(WHITE)) - rank_of(pos.square(BLACK))); pawnsOnBothFlanks = (pos.pieces(PAWN) & QueenSide) && (pos.pieces(PAWN) & KingSide); bool almostUnwinnable = outflanking < 0 && pos.stalemate_value() == VALUE_DRAW && !pawnsOnBothFlanks; bool infiltration = (pos.count(WHITE) && rank_of(pos.square(WHITE)) > RANK_4) || (pos.count(BLACK) && rank_of(pos.square(BLACK)) < RANK_5); // Compute the initiative bonus for the attacking side complexity = 9 * pe->passed_count() + 12 * pos.count() + 15 * pos.count() + 9 * outflanking + 21 * pawnsOnBothFlanks + 24 * infiltration + 51 * !pos.non_pawn_material() - 43 * almostUnwinnable -110 ; } Value mg = mg_value(score); Value eg = eg_value(score); // Now apply the bonus: note that we find the attacking side by extracting the // sign of the midgame or endgame values, and that we carefully cap the bonus // so that the midgame and endgame scores do not change sign after the bonus. int u = ((mg > 0) - (mg < 0)) * std::clamp(complexity + 50, -abs(mg), 0); int v = ((eg > 0) - (eg < 0)) * std::max(complexity, -abs(eg)); mg += u; eg += v; // Compute the scale factor for the winning side Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK; int sf = me->scale_factor(pos, strongSide); // If scale factor is not already specific, scale up/down via general heuristics if (sf == SCALE_FACTOR_NORMAL && !pos.captures_to_hand() && !pos.material_counting()) { if (pos.opposite_bishops()) { // For pure opposite colored bishops endgames use scale factor // based on the number of passed pawns of the strong side. if ( pos.non_pawn_material(WHITE) == BishopValueMg && pos.non_pawn_material(BLACK) == BishopValueMg) sf = 18 + 4 * popcount(pe->passed_pawns(strongSide)); // For every other opposite colored bishops endgames use scale factor // based on the number of all pieces of the strong side. else sf = 22 + 3 * pos.count(strongSide); } // For rook endgames with strong side not having overwhelming pawn number advantage // and its pawns being on one flank and weak side protecting its pieces with a king // use lower scale factor. else if ( pos.non_pawn_material(WHITE) == RookValueMg && pos.non_pawn_material(BLACK) == RookValueMg && pos.count(strongSide) - pos.count(~strongSide) <= 1 && bool(KingSide & pos.pieces(strongSide, PAWN)) != bool(QueenSide & pos.pieces(strongSide, PAWN)) && pos.count(~strongSide) && (attacks_bb(pos.square(~strongSide)) & pos.pieces(~strongSide, PAWN))) sf = 36; // For queen vs no queen endgames use scale factor // based on number of minors of side that doesn't have queen. else if (pos.count() == 1) sf = 37 + 3 * (pos.count(WHITE) == 1 ? pos.count(BLACK) + pos.count(BLACK) : pos.count(WHITE) + pos.count(WHITE)); // In every other case use scale factor based on // the number of pawns of the strong side reduced if pawns are on a single flank. else sf = std::min(sf, 36 + 7 * (pos.count(strongSide) + pos.count(strongSide))) - 4 * !pawnsOnBothFlanks; // Reduce scale factor in case of pawns being on a single flank sf -= 4 * !pawnsOnBothFlanks; } // Interpolate between the middlegame and (scaled by 'sf') endgame score v = mg * int(me->game_phase()) + eg * int(PHASE_MIDGAME - me->game_phase()) * ScaleFactor(sf) / SCALE_FACTOR_NORMAL; v /= PHASE_MIDGAME; if constexpr (T) { Trace::add(WINNABLE, make_score(u, eg * ScaleFactor(sf) / SCALE_FACTOR_NORMAL - eg_value(score))); Trace::add(TOTAL, make_score(mg, eg * ScaleFactor(sf) / SCALE_FACTOR_NORMAL)); } return Value(v); } // Evaluation::value() is the main function of the class. It computes the various // parts of the evaluation and returns the value of the position from the point // of view of the side to move. template Value Evaluation::value() { assert(!pos.checkers()); assert(!pos.is_immediate_game_end()); // 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(); if (T) Trace::add(MATERIAL, score); score += me->imbalance() + pos.this_thread()->trend; // 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) && Options["UCI_Variant"] == "chess") goto make_v; // Main evaluation begins here std::memset(attackedBy, 0, sizeof(attackedBy)); initialize(); initialize(); // Pieces evaluated first (also populates attackedBy, attackedBy2). // For unused piece types, we still need to set attack bitboard to zero. for (PieceType pt : pos.piece_types()) if (pt != SHOGI_PAWN && pt != PAWN && pt != KING) score += pieces(pt) - pieces(pt); // Evaluate pieces in hand once attack tables are complete if (pos.piece_drops() || pos.seirawan_gating()) for (PieceType pt : pos.piece_types()) score += hand(pt) - hand(pt); score += (mobility[WHITE] - mobility[BLACK]) * (1 + pos.captures_to_hand() + pos.must_capture() + pos.check_counting()); // More complex interactions that require fully populated attack bitboards score += king< WHITE>() - king< BLACK>() + passed< WHITE>() - passed< BLACK>() + variant() - variant(); if (lazy_skip(LazyThreshold2) && Options["UCI_Variant"] == "chess") goto make_v; score += threats() - threats() + space< WHITE>() - space< BLACK>(); make_v: // Derive single value from mg and eg parts of score Value v = winnable(score); // In case of tracing add all remaining individual evaluation terms if constexpr (T) { Trace::add(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) + 80 * pos.captures_to_hand(); return v; } /// Fisher Random Chess: correction for cornered bishops, to fix chess960 play with NNUE Value fix_FRC(const Position& pos) { constexpr Bitboard Corners = Bitboard(1ULL) << SQ_A1 | Bitboard(1ULL) << SQ_H1 | Bitboard(1ULL) << SQ_A8 | Bitboard(1ULL) << SQ_H8; if (!(pos.pieces(BISHOP) & Corners)) return VALUE_ZERO; int correction = 0; if ( pos.piece_on(SQ_A1) == W_BISHOP && pos.piece_on(SQ_B2) == W_PAWN) correction += !pos.empty(SQ_B3) ? -CorneredBishop * 4 : -CorneredBishop * 3; if ( pos.piece_on(SQ_H1) == W_BISHOP && pos.piece_on(SQ_G2) == W_PAWN) correction += !pos.empty(SQ_G3) ? -CorneredBishop * 4 : -CorneredBishop * 3; if ( pos.piece_on(SQ_A8) == B_BISHOP && pos.piece_on(SQ_B7) == B_PAWN) correction += !pos.empty(SQ_B6) ? CorneredBishop * 4 : CorneredBishop * 3; if ( pos.piece_on(SQ_H8) == B_BISHOP && pos.piece_on(SQ_G7) == B_PAWN) correction += !pos.empty(SQ_G6) ? CorneredBishop * 4 : CorneredBishop * 3; return pos.side_to_move() == WHITE ? Value(correction) : -Value(correction); } } // namespace Eval /// evaluate() is the evaluator for the outer world. It returns a static /// evaluation of the position from the point of view of the side to move. Value Eval::evaluate(const Position& pos) { Value v; if (!Eval::useNNUE || !pos.nnue_applicable()) v = Evaluation(pos).value(); else { // Scale and shift NNUE for compatibility with search and classical evaluation auto adjusted_NNUE = [&]() { int scale = 903 + 32 * pos.count() + 32 * pos.non_pawn_material() / 1024; Value nnue = NNUE::evaluate(pos, true) * scale / 1024; if (pos.is_chess960()) nnue += fix_FRC(pos); if (pos.check_counting()) { Color us = pos.side_to_move(); nnue += 6 * scale / (5 * pos.checks_remaining( us)) - 6 * scale / (5 * pos.checks_remaining(~us)); } return nnue; }; // If there is PSQ imbalance we use the classical eval, but we switch to // NNUE eval faster when shuffling or if the material on the board is high. int r50 = pos.rule50_count(); Value psq = Value(abs(eg_value(pos.psq_score()))); bool pure = !pos.check_counting(); bool classical = psq * 5 > (750 + pos.non_pawn_material() / 64) * (5 + r50) && !pure; v = classical ? Evaluation(pos).value() // classical : adjusted_NNUE(); // NNUE } // Damp down the evaluation linearly when shuffling if (pos.n_move_rule()) { v = v * (2 * pos.n_move_rule() - pos.rule50_count()) / (2 * pos.n_move_rule()); if (pos.material_counting()) v += pos.material_counting_result() / (10 * std::max(2 * pos.n_move_rule() - pos.rule50_count(), 1)); } // Guarantee evaluation does not hit the virtual win/loss range if (pos.two_boards() && std::abs(v) >= VALUE_VIRTUAL_MATE_IN_MAX_PLY) v += v > VALUE_ZERO ? MAX_PLY + 1 : -MAX_PLY - 1; // 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(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()->trend = SCORE_ZERO; // Reset any dynamic contempt v = Evaluation(pos).value(); ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2) << " Contributing terms for the classical eval:\n" << "+------------+-------------+-------------+-------------+\n" << "| Term | White | Black | Total |\n" << "| | MG EG | MG EG | MG EG |\n" << "+------------+-------------+-------------+-------------+\n" << "| Material | " << Term(MATERIAL) << "| Imbalance | " << Term(IMBALANCE) << "| Pawns | " << Term(PAWN) << "| Knights | " << Term(KNIGHT) << "| Bishops | " << Term(BISHOP) << "| Rooks | " << Term(ROOK) << "| Queens | " << Term(QUEEN) << "| Mobility | " << Term(MOBILITY) << "|King safety | " << Term(KING) << "| Threats | " << Term(THREAT) << "| Passed | " << Term(PASSED) << "| Space | " << Term(SPACE) << "| Variant | " << Term(VARIANT) << "| Winnable | " << Term(WINNABLE) << "+------------+-------------+-------------+-------------+\n" << "| Total | " << Term(TOTAL) << "+------------+-------------+-------------+-------------+\n"; if (Eval::useNNUE && pos.nnue_applicable()) ss << '\n' << NNUE::trace(pos) << '\n'; ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); v = pos.side_to_move() == WHITE ? v : -v; ss << "\nClassical evaluation " << to_cp(v) << " (white side)\n"; if (Eval::useNNUE && pos.nnue_applicable()) { v = NNUE::evaluate(pos, false); v = pos.side_to_move() == WHITE ? v : -v; ss << "NNUE evaluation " << to_cp(v) << " (white side)\n"; } v = evaluate(pos); v = pos.side_to_move() == WHITE ? v : -v; ss << "Final evaluation " << to_cp(v) << " (white side)"; if (Eval::useNNUE && pos.nnue_applicable()) ss << " [with scaled NNUE, hybrid, ...]"; ss << "\n"; return ss.str(); } } // namespace Stockfish Fairy-Stockfish-fairy_sf_14_0_1_xq/src/evaluate.h000066400000000000000000000036031414571233100221070ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef EVALUATE_H_INCLUDED #define EVALUATE_H_INCLUDED #include #include #include "types.h" #include "variant.h" namespace Stockfish { class Position; namespace Eval { std::string trace(Position& pos); Value evaluate(const Position& pos); extern bool useNNUE; extern std::string eval_file_loaded; // 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 "xiangqi-83f16c17fe26.nnue" #define EvalFile2DefaultName "janggi-85de3dae670a.nnue" namespace NNUE { std::string trace(Position& pos); Value evaluate(const Position& pos, bool adjusted = false); void init(); void verify(); bool load_eval(std::string name, std::istream& stream); bool save_eval(std::ostream& stream); bool save_eval(const std::optional& filename); } // namespace NNUE } // namespace Eval extern const Variant* currentNnueVariant; } // namespace Stockfish #endif // #ifndef EVALUATE_H_INCLUDED Fairy-Stockfish-fairy_sf_14_0_1_xq/src/ffishjs.cpp000066400000000000000000000530371414571233100222760ustar00rootroot00000000000000/* ffish.js, a JavaScript chess variant library derived from Fairy-Stockfish Copyright (C) 2021 Fabian Fichter, Johannes Czech ffish.js 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. ffish.js is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include "misc.h" #include "types.h" #include "bitboard.h" #include "evaluate.h" #include "position.h" #include "search.h" #include "syzygy/tbprobe.h" #include "thread.h" #include "tt.h" #include "uci.h" #include "piece.h" #include "variant.h" #include "movegen.h" #include "apiutil.h" using namespace emscripten; using namespace Stockfish; void initialize_stockfish() { pieceMap.init(); variants.init(); UCI::init(Options); Bitboards::init(); Position::init(); Bitbases::init(); } #define DELIM " " inline void save_pop_back(std::string& s) { if (s.size() != 0) { s.pop_back(); } } const Variant* get_variant(const std::string& uciVariant) { if (uciVariant.size() == 0 || uciVariant == "Standard" || uciVariant == "standard") return variants.find("chess")->second; return variants.find(uciVariant)->second; } template inline bool is_move_none(Move move, const std::string& strMove, const Position& pos) { if (move == MOVE_NONE) { std::cerr << "The given "; isUCI ? std::cerr << "uciMove" : std::cerr << "sanMove"; std::cerr << " '" << strMove << "' for position '" << pos.fen() << "' is invalid." << std::endl; return true; } return false; } class Board { // note: we can't use references for strings here due to conversion to JavaScript private: const Variant* v; StateListPtr states; Position pos; Thread* thread; std::vector moveStack; bool is960; public: static bool sfInitialized; Board(): Board("chess", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" , false) { } Board(std::string uciVariant): Board(uciVariant, "", false) { } Board(std::string uciVariant, std::string fen): Board(uciVariant, fen, false) { } Board(std::string uciVariant, std::string fen, bool is960) { init(uciVariant, fen, is960); } std::string legal_moves() { std::string moves; for (const ExtMove& move : MoveList(this->pos)) { moves += UCI::move(this->pos, move); moves += DELIM; } save_pop_back(moves); return moves; } std::string legal_moves_san() { std::string movesSan; for (const ExtMove& move : MoveList(this->pos)) { movesSan += SAN::move_to_san(this->pos, move, NOTATION_SAN); movesSan += DELIM; } save_pop_back(movesSan); return movesSan; } int number_legal_moves() const { return MoveList(pos).size(); } bool push(std::string uciMove) { const Move move = UCI::to_move(this->pos, uciMove); if (is_move_none(move, uciMove, pos)) return false; do_move(move); return true; } bool push_san(std::string sanMove) { return push_san(sanMove, NOTATION_SAN); } // TODO: This is a naive implementation which compares all legal SAN moves with the requested string. // If the SAN move wasn't found the position remains unchanged. Alternatively, implement a direct conversion. bool push_san(std::string sanMove, Notation notation) { Move foundMove = MOVE_NONE; for (const ExtMove& move : MoveList(pos)) { if (sanMove == SAN::move_to_san(this->pos, move, notation)) { foundMove = move; break; } } if (is_move_none(foundMove, sanMove, pos)) return false; do_move(foundMove); return true; } void pop() { pos.undo_move(this->moveStack.back()); moveStack.pop_back(); states->pop_back(); } void reset() { set_fen(v->startFen); } bool is_960() const { return is960; } std::string fen() const { return this->pos.fen(); } std::string fen(bool showPromoted, int countStarted) const { return this->pos.fen(false, showPromoted, countStarted); } void set_fen(std::string fen) { resetStates(); moveStack.clear(); pos.set(v, fen, is960, &states->back(), thread); } // note: const identifier for pos not possible due to SAN::move_to_san() std::string san_move(std::string uciMove) { return san_move(uciMove, NOTATION_SAN); } std::string san_move(std::string uciMove, Notation notation) { const Move move = UCI::to_move(this->pos, uciMove); if (is_move_none(move, uciMove, pos)) return ""; return SAN::move_to_san(this->pos, UCI::to_move(this->pos, uciMove), notation); } std::string variation_san(std::string uciMoves) { return variation_san(uciMoves, NOTATION_SAN, true); } std::string variation_san(std::string uciMoves, Notation notation) { return variation_san(uciMoves, notation, true); } std::string variation_san(std::string uciMoves, Notation notation, bool moveNumbers) { std::stringstream ss(uciMoves); StateListPtr tempStates; std::vector moves; std::string variationSan = ""; std::string uciMove; bool first = true; while (std::getline(ss, uciMove, ' ')) { const Move move = UCI::to_move(this->pos, uciMove); if (is_move_none(move, uciMove, pos)) return ""; moves.emplace_back(UCI::to_move(this->pos, uciMove)); if (first) { first = false; if (moveNumbers) { variationSan = std::to_string(fullmove_number()); if (pos.side_to_move() == WHITE) variationSan += ". "; else variationSan += "..."; } variationSan += SAN::move_to_san(this->pos, moves.back(), Notation(notation)); } else { if (moveNumbers && pos.side_to_move() == WHITE) { variationSan += DELIM; variationSan += std::to_string(fullmove_number()); variationSan += "."; } variationSan += DELIM; variationSan += SAN::move_to_san(this->pos, moves.back(), Notation(notation)); } states->emplace_back(); pos.do_move(moves.back(), states->back()); } // recover initial state for(auto rIt = std::rbegin(moves); rIt != std::rend(moves); ++rIt) { pos.undo_move(*rIt); } return variationSan; } // returns true for WHITE and false for BLACK bool turn() const { return !pos.side_to_move(); } int fullmove_number() const { return pos.game_ply() / 2 + 1; } int halfmove_clock() const { return pos.rule50_count(); } int game_ply() const { return pos.game_ply(); } bool has_insufficient_material(bool turn) const { return Stockfish::has_insufficient_material(turn ? WHITE : BLACK, pos); } bool is_insufficient_material() const { return Stockfish::has_insufficient_material(WHITE, pos) && Stockfish::has_insufficient_material(BLACK, pos); } bool is_game_over() const { return is_game_over(false); } bool is_game_over(bool claim_draw) const { if (is_insufficient_material()) return true; if (claim_draw && pos.is_optional_game_end()) return true; return MoveList(pos).size() == 0; } std::string result() const { return result(false); } std::string result(bool claim_draw) const { Value result; bool gameEnd = pos.is_immediate_game_end(result); if (!gameEnd) { if (is_insufficient_material()) { gameEnd = true; result = VALUE_DRAW; } } if (!gameEnd && MoveList(pos).size() == 0) { gameEnd = true; result = pos.checkers() ? pos.checkmate_value() : pos.stalemate_value(); } if (!gameEnd && claim_draw) gameEnd = pos.is_optional_game_end(result); if (!gameEnd) return "*"; if (result == 0) { if (pos.material_counting()) result = pos.material_counting_result(); if (result == 0) return "1/2-1/2"; } if (pos.side_to_move() == BLACK) result = -result; if (result > 0) return "1-0"; else return "0-1"; } bool is_check() const { return pos.checkers(); } bool is_bikjang() const { return pos.bikjang(); } std::string move_stack() const { std::string moves; for(auto it = std::begin(moveStack); it != std::end(moveStack); ++it) { moves += UCI::move(pos, *it); moves += DELIM; } save_pop_back(moves); return moves; } void push_moves(std::string uciMoves) { std::stringstream ss(uciMoves); std::string uciMove; while (std::getline(ss, uciMove, ' ')) { push(uciMove); } } void push_san_moves(std::string sanMoves) { return push_san_moves(sanMoves, NOTATION_SAN); } void push_san_moves(std::string sanMoves, Notation notation) { std::stringstream ss(sanMoves); std::string sanMove; while (std::getline(ss, sanMove, ' ')) push_san(sanMove, notation); } std::string pocket(bool color) { const Color c = Color(!color); std::string pocket; for (PieceType pt = KING; pt >= PAWN; --pt) { for (int i = 0; i < pos.count_in_hand(c, pt); ++i) { // only create BLACK pieces in order to convert to lower case pocket += std::string(1, pos.piece_to_char()[make_piece(BLACK, pt)]); } } return pocket; } std::string to_string() { std::string stringBoard; for (Rank r = pos.max_rank(); r >= RANK_1; --r) { for (File f = FILE_A; f <= pos.max_file(); ++f) { if (f != FILE_A) stringBoard += " "; const Piece p = pos.piece_on(make_square(f, r)); switch(p) { case NO_PIECE: stringBoard += '.'; break; default: stringBoard += pos.piece_to_char()[p]; } } if (r != RANK_1) stringBoard += "\n"; } return stringBoard; } std::string to_verbose_string() { std::stringstream ss; operator<<(ss, pos); return ss.str(); } std::string variant() { // Iterate through the variants map for (auto it = variants.begin(); it != variants.end(); ++it) if (it->second == v) return it->first; std::cerr << "Current variant is not registered." << std::endl; return "unknown"; } private: void resetStates() { this->states = StateListPtr(new std::deque(1)); } void do_move(Move move) { states->emplace_back(); this->pos.do_move(move, states->back()); this->moveStack.emplace_back(move); } void init(std::string uciVariant, std::string fen, bool is960) { if (!Board::sfInitialized) { initialize_stockfish(); Board::sfInitialized = true; } v = get_variant(uciVariant); UCI::init_variant(v); this->resetStates(); if (fen == "") fen = v->startFen; this->pos.set(this->v, fen, is960, &this->states->back(), this->thread); this->is960 = is960; } }; bool Board::sfInitialized = false; namespace ffish { // returns the version of the Fairy-Stockfish binary std::string info() { return engine_info(); } template void set_option(std::string name, T value) { Options[name] = value; Board::sfInitialized = false; } std::string available_variants() { std::string availableVariants; for (std::string variant : variants.get_keys()) { availableVariants += variant; availableVariants += DELIM; } save_pop_back(availableVariants); return availableVariants; } void load_variant_config(std::string variantInitContent) { std::stringstream ss(variantInitContent); if (!Board::sfInitialized) initialize_stockfish(); variants.parse_istream(ss); Options["UCI_Variant"].set_combo(variants.get_keys()); Board::sfInitialized = true; } std::string starting_fen(std::string uciVariant) { const Variant* v = get_variant(uciVariant); return v->startFen; } int validate_fen(std::string fen, std::string uciVariant, bool chess960) { const Variant* v = get_variant(uciVariant); return FEN::validate_fen(fen, v, chess960); } int validate_fen(std::string fen, std::string uciVariant) { return validate_fen(fen, uciVariant, false); } int validate_fen(std::string fen) { return validate_fen(fen, "chess"); } } class Game { private: std::unordered_map header; std::unique_ptr board; std::string variant = "chess"; std::string fen = ""; // start pos bool is960 = false; bool parsedGame = false; public: std::string header_keys() { std::string keys; for (auto it = header.begin(); it != header.end(); ++it) { keys += it->first; keys += DELIM; } save_pop_back(keys); return keys; } std::string headers(std::string item) { auto it = header.find(item); if (it == header.end()) return ""; return it->second; } std::string mainline_moves() { if (!parsedGame) return ""; return board->move_stack(); } friend Game read_game_pgn(std::string); }; bool skip_comment(const std::string& pgn, size_t& curIdx, size_t& lineEnd) { curIdx = pgn.find('}', curIdx); if (curIdx == std::string::npos) { std::cerr << "Missing '}' for move comment while reading pgn." << std::endl; return false; } if (curIdx > lineEnd) lineEnd = pgn.find('\n', curIdx); return true; } Game read_game_pgn(std::string pgn) { Game game; size_t lineStart = 0; bool headersParsed = false; while(true) { size_t lineEnd = pgn.find('\n', lineStart); if (lineEnd == std::string::npos) lineEnd = pgn.size(); if (!headersParsed && pgn[lineStart] == '[') { // parse header // look for item size_t headerKeyStart = lineStart+1; size_t headerKeyEnd = pgn.find(' ', lineStart); size_t headerItemStart = pgn.find('"', headerKeyEnd)+1; size_t headerItemEnd = pgn.find('"', headerItemStart); // put item into list game.header[pgn.substr(headerKeyStart, headerKeyEnd-headerKeyStart)] = pgn.substr(headerItemStart, headerItemEnd-headerItemStart); } else { if (!headersParsed) { headersParsed = true; auto it = game.header.find("Variant"); if (it != game.header.end()) { game.variant = it->second; std::transform(game.variant.begin(), game.variant.end(), game.variant.begin(), [](unsigned char c){ return std::tolower(c); }); game.is960 = it->second.find("960") != std::string::npos; } it = game.header.find("FEN"); if (it != game.header.end()) game.fen = it->second; game.board = std::make_unique(game.variant, game.fen, game.is960); game.parsedGame = true; } // game line size_t curIdx = lineStart; while (curIdx <= lineEnd) { if (pgn[curIdx] == '*') return game; if (pgn[curIdx] == '{') { if (!skip_comment(pgn, curIdx, lineEnd)) return game; ++curIdx; } // Movetext RAV (Recursive Annotation Variation) size_t openedRAV = 0; if (pgn[curIdx] == '(') { openedRAV = 1; ++curIdx; } while (openedRAV != 0) { switch (pgn[curIdx]) { case '(': ++openedRAV; break; case ')': --openedRAV; break; case '{': if (!skip_comment(pgn, curIdx, lineEnd)) return game; default: ; // pass } ++curIdx; if (curIdx > lineEnd) lineEnd = pgn.find('\n', curIdx); } if (pgn[curIdx] == '$') { // we are at a glyph curIdx = pgn.find(' ', curIdx); } if (pgn[curIdx] >= '0' && pgn[curIdx] <= '9') { // we are at a move number -> look for next point curIdx = pgn.find('.', curIdx); if (curIdx == std::string::npos) break; ++curIdx; // increment if we're at a space while (curIdx < pgn.size() && pgn[curIdx] == ' ') ++curIdx; // increment if we're at a point while (curIdx < pgn.size() && pgn[curIdx] == '.') ++curIdx; } // extract sanMove size_t sanMoveEnd = std::min(pgn.find(' ', curIdx), lineEnd); if (sanMoveEnd > curIdx) { std::string sanMove = pgn.substr(curIdx, sanMoveEnd-curIdx); // clean possible ? and ! from string size_t annotationChar1 = sanMove.find('?'); size_t annotationChar2 = sanMove.find('!'); if (annotationChar1 != std::string::npos || annotationChar2 != std::string::npos) sanMove = sanMove.substr(0, std::min(annotationChar1, annotationChar2)); std::cout << sanMove << " "; game.board->push_san(sanMove); } curIdx = sanMoveEnd+1; } } lineStart = lineEnd+1; if (lineStart >= pgn.size()) return game; } return game; } // binding code EMSCRIPTEN_BINDINGS(ffish_js) { class_("Board") .constructor<>() .constructor() .constructor() .constructor() .function("legalMoves", &Board::legal_moves) .function("legalMovesSan", &Board::legal_moves_san) .function("numberLegalMoves", &Board::number_legal_moves) .function("push", &Board::push) .function("pushSan", select_overload(&Board::push_san)) .function("pushSan", select_overload(&Board::push_san)) .function("pop", &Board::pop) .function("reset", &Board::reset) .function("is960", &Board::is_960) .function("fen", select_overload(&Board::fen)) .function("fen", select_overload(&Board::fen)) .function("setFen", &Board::set_fen) .function("sanMove", select_overload(&Board::san_move)) .function("sanMove", select_overload(&Board::san_move)) .function("variationSan", select_overload(&Board::variation_san)) .function("variationSan", select_overload(&Board::variation_san)) .function("variationSan", select_overload(&Board::variation_san)) .function("turn", &Board::turn) .function("fullmoveNumber", &Board::fullmove_number) .function("halfmoveClock", &Board::halfmove_clock) .function("gamePly", &Board::game_ply) .function("hasInsufficientMaterial", &Board::has_insufficient_material) .function("isInsufficientMaterial", &Board::is_insufficient_material) .function("isGameOver", select_overload(&Board::is_game_over)) .function("isGameOver", select_overload(&Board::is_game_over)) .function("result", select_overload(&Board::result)) .function("result", select_overload(&Board::result)) .function("isCheck", &Board::is_check) .function("isBikjang", &Board::is_bikjang) .function("moveStack", &Board::move_stack) .function("pushMoves", &Board::push_moves) .function("pushSanMoves", select_overload(&Board::push_san_moves)) .function("pushSanMoves", select_overload(&Board::push_san_moves)) .function("pocket", &Board::pocket) .function("toString", &Board::to_string) .function("toVerboseString", &Board::to_verbose_string) .function("variant", &Board::variant); class_("Game") .function("headerKeys", &Game::header_keys) .function("headers", &Game::headers) .function("mainlineMoves", &Game::mainline_moves); // usage: e.g. ffish.Notation.DEFAULT enum_("Notation") .value("DEFAULT", NOTATION_DEFAULT) .value("SAN", NOTATION_SAN) .value("LAN", NOTATION_LAN) .value("SHOGI_HOSKING", NOTATION_SHOGI_HOSKING) .value("SHOGI_HODGES", NOTATION_SHOGI_HODGES) .value("SHOGI_HODGES_NUMBER", NOTATION_SHOGI_HODGES_NUMBER) .value("JANGGI", NOTATION_JANGGI) .value("XIANGQI_WXF", NOTATION_XIANGQI_WXF); // usage: e.g. ffish.Termination.CHECKMATE enum_("Termination") .value("ONGOING", ONGOING) .value("CHECKMATE", CHECKMATE) .value("STALEMATE", STALEMATE) .value("INSUFFICIENT_MATERIAL", INSUFFICIENT_MATERIAL) .value("N_MOVE_RULE", N_MOVE_RULE) .value("N_FOLD_REPETITION", N_FOLD_REPETITION) .value("VARIANT_END", VARIANT_END); function("info", &ffish::info); function("setOption", &ffish::set_option); function("setOptionInt", &ffish::set_option); function("setOptionBool", &ffish::set_option); function("readGamePGN", &read_game_pgn); function("variants", &ffish::available_variants); function("loadVariantConfig", &ffish::load_variant_config); function("startingFen", &ffish::starting_fen); function("validateFen", select_overload(&ffish::validate_fen)); function("validateFen", select_overload(&ffish::validate_fen)); function("validateFen", select_overload(&ffish::validate_fen)); // TODO: enable to string conversion method // .class_function("getStringFromInstance", &Board::get_string_from_instance); } Fairy-Stockfish-fairy_sf_14_0_1_xq/src/incbin/000077500000000000000000000000001414571233100213705ustar00rootroot00000000000000Fairy-Stockfish-fairy_sf_14_0_1_xq/src/incbin/UNLICENCE000066400000000000000000000024141414571233100226210ustar00rootroot00000000000000The file "incbin.h" is free and unencumbered software released into the public domain by Dale Weiler, see: 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 Fairy-Stockfish-fairy_sf_14_0_1_xq/src/incbin/incbin.h000077500000000000000000000265421414571233100230170ustar00rootroot00000000000000/** * @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 #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 FooData[]; * // const unsigned char *const FooEnd; * // const unsigned int 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 foo_data[]; * // const unsigned char *const foo_end; * // const unsigned int 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 FooData[]; * // extern const unsigned char *const FooEnd; * // extern const unsigned int 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 IconData[]; * // const unsigned char *const IconEnd; * // const unsigned int 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 Fairy-Stockfish-fairy_sf_14_0_1_xq/src/magic.h000066400000000000000000002217441414571233100213710ustar00rootroot00000000000000/* Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish Copyright (C) 2018-2021 Fabian Fichter Fairy-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. Fairy-Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef MAGIC_H_INCLUDED #define MAGIC_H_INCLUDED namespace Stockfish { #ifdef PRECOMPUTED_MAGICS #define B(a, b) (Bitboard(a) << 64) ^ Bitboard(b) // Use precomputed magics if pext is not available, // since the magics generation is very slow. constexpr Bitboard RookMagicHInit[SQUARE_NB] = { B(0x120000880110000, 0x1008000000020020), B(0x24200C080840A052, 0x2004004000010008), B(0xC030024000228800, 0x4000010400000020), B(0x1A0020802008802, 0x206010208000), B(0x12002000D001024, 0x80100800090138), B(0x4220010000241010, 0x3098000602001500), B(0x401010004801040, 0x8000280480100000), B(0x820082024921836, 0x220028000), B(0x100400502411400, 0x220402120240D14), B(0x880202020010404, 0xA80202510000), B(0x140002801000018, 0x1000346490040), B(0x120000880110000, 0x1008000000020020), B(0xD01004008030400, 0x104000408104420), B(0x8420060100020000, 0x800280400000120), B(0x4010020018010, 0x40A00001100000), B(0x40006A0004000200, 0x40000000110), B(0xD01004008030400, 0x104000408104420), B(0x8908A20028110011, 0x800080000001A114), B(0x200042000080F009, 0x20001000004000), B(0x2820008820100, 0x10002400058000B9), B(0x6083100420008050, 0x4040012600280080), B(0x216020000000446, 0x4080204000000211), B(0x340140003002089, 0x2402008000000911), B(0xD01004008030400, 0x104000408104420), B(0x1404040B20001000, 0x8000824010800011), B(0x8C0488120024214, 0x8414880202291), B(0x1010000060050000, 0x4000004050002602), B(0x4022983A0060000, 0x80000040010400), B(0x1404040B20001000, 0x8000824010800011), B(0x6020101802002840, 0x31000003000004), B(0x9000420008840, 0x4881300000000210), B(0xA200808865, 0x41C0048023000128), B(0x31801100400000, 0x8802DC001221240), B(0x884000080200920, 0x1004002410401001), B(0x2400040000884, 0x421006208040C0), B(0x1404040B20001000, 0x8000824010800011), B(0x24100400060009, 0x112008025042410), B(0x1800040009040200, 0x180000A1004E408A), B(0x24100400060009, 0x112008025042410), B(0x4060402008080, 0xC240080000110000), B(0x20080100920020, 0x2002248010242052), B(0x10001010802050, 0x880000001C98420), B(0x4000800100420022, 0x502022010A00D0), B(0x4C18104500200885, 0x400880800), B(0x8080810081020090, 0x8000000000000), B(0x8000062812080201, 0x8004C8300800), B(0xC010220920198, 0x85000A08000), B(0x24100400060009, 0x112008025042410), B(0x80102204040, 0x1000000900000000), B(0x2080000004202804, 0x120880003461), B(0x102004090A4030, 0x801020589240), B(0x20001100814000A0, 0x420202000820004), B(0x100800000A000120, 0x208000800010000), B(0x1008205000040802, 0x80002000400040), B(0x1480000098008401, 0xA0010000581010), B(0x30C0008200100820, 0x102800080904834), B(0x4810821884000500, 0x4400000200000212), B(0x1811D00128A0180, 0x2500848803000000), B(0x41618A0300040040, 0x21200200A421801), B(0x80102204040, 0x1000000900000000), B(0xA1808E0100108000, 0x2008000505000002), B(0x8C890020410000A0, 0xA010000048000400), B(0x40006002210044, 0x600008000408000), B(0x1200447220090042, 0x80001000160012), B(0x48410010AB000000, 0x9200600000000100), B(0x2040000000240003, 0x8020080288000600), B(0x9080000088848088, 0x4010210500000041), B(0xA1808E0100108000, 0x2008000505000002), B(0x480100400024, 0x1004800018200000), B(0x808403080080200, 0x802601000000500), B(0x8C890020410000A0, 0xA010000048000400), B(0xA1808E0100108000, 0x2008000505000002), B(0x100A40000004008, 0x2800200400200480), B(0x100A40000004008, 0x2800200400200480), B(0x400014006000000, 0x10006000810001F5), B(0xC410062001414, 0x820080041B01044), B(0x20000800310, 0x430040000201000), B(0xA40010008000008, 0x4002200028000040), B(0xC00102000008021C, 0x10C2000A010E024), B(0x80004200104008, 0x50A00800C400020), B(0x20200080012542, 0x910F0040000402C0), B(0xB040100504000300, 0x24802002000040), B(0x800001000014008, 0x400031004000), B(0x100A40000004008, 0x2800200400200480), B(0x84008002041081C0, 0x8080500200000000), B(0x440090001012001, 0x4020004010), B(0x100A0028088020, 0x80040E00010020), B(0x2180808000810, 0xB018040A00040000), B(0x40C80920304C4001, 0x42800B200800000), B(0x85000425001000, 0x4810048020001100), B(0x600C000801000004, 0x8015084010200020), B(0x20020050000240C0, 0x100202008600800), B(0x38000050001220, 0x9200010200145900), B(0x1042108040005, 0x1402A0802201001), B(0x824240000C20400, 0x1000000400080010), B(0x84008002041081C0, 0x8080500200000000), B(0x400804A1000008, 0x1024104A0200010), B(0x8000402308483, 0x20006020100100), B(0x80880120000080, 0x8000240100084), B(0x5840020004882001, 0x1004528000A00010), B(0x8001018800300002, 0x84010040804), B(0x180D10004000A008, 0xA001080008020004), B(0x400080B, 0x10A0000004010000), B(0x8080000200000, 0x2001000082004E0), B(0x40040001000C2000, 0x2024800001004008), B(0x400804A1000008, 0x1024104A0200010), B(0x8000402308483, 0x20006020100100), B(0x400804A1000008, 0x1024104A0200010), B(0x2000200000, 0x1201011000802), B(0x100100000000C4, 0x208004084048201), B(0x400084000044, 0x100810140300), B(0x29040C0C01010, 0x300204010820080), B(0x1A808000020200, 0x1000000005210040), B(0x20000400150000, 0x85008020), B(0x40C040008184014, 0x8002AA00024010), B(0x202000081B00804, 0x10001002008), B(0x40011000210060, 0x6080C40000021004), B(0x2000200000, 0x1201011000802), B(0x4100480203840, 0x300080100804), B(0x2000200000, 0x1201011000802), }; constexpr Bitboard RookMagicVInit[SQUARE_NB] = { B(0x202000812104400, 0x24800B01C0000303), B(0x340020400010D, 0x88060150C00400), B(0x400802040609, 0x49010200501A0002), B(0x8002680301000208, 0x628006C0C020200), B(0x20400209001C0804, 0xA044000800143110), B(0xC400082060010202, 0x4000480401014000), B(0x22500200144040, 0x8204820084704C00), B(0x8C1204009030020, 0x328400805000000), B(0x84800800D0001640, 0x200080040060108), B(0x804810208020040, 0x140010108020000), B(0x1102010B008004, 0x300208006220020), B(0x140080404A0A2428, 0x6308010100080), B(0x20444002120408, 0xA080010508010001), B(0x82011044000D02, 0x4112028620110809), B(0x81010831000C02, 0x408802085000000), B(0x81010831000C02, 0x408802085000000), B(0x920008920600040, 0x8053801004000028), B(0x81140283208300, 0x10040C004200420), B(0x103080022201, 0xC01000081312620), B(0x2200221100008, 0x1000408104000A4), B(0x4402088080042008, 0x210401501040340), B(0x898400202170001, 0x80040404208000), B(0x20080004051012, 0x5100048200081800), B(0x2320020000401018, 0x108501021040210), B(0x21080410A422021, 0x83040180008800), B(0x44E8100000408224, 0x20010008040400), B(0x1800240002810405, 0x23004820000020), B(0x80A0100400110, 0x80104020100C4028), B(0x1002050001222C0, 0x5100818004024020), B(0x104000200040, 0xC010A09800102000), B(0x1020003A058120, 0x450900809000302), B(0x40040045008B1, 0x202800400383010), B(0x4640200220034, 0x8800485420304000), B(0x5001042100084288, 0x110820001240080A), B(0x2002C04004010120, 0xA15008020880001), B(0x2800004080C4190, 0x890808280020080), B(0x40C0401000104000, 0x2020880008002580), B(0x40020C002400802, 0x801104010000000), B(0x44842000040080, 0x2050011084000400), B(0x4110040800000401, 0x2023810029008000), B(0x20884000840, 0x8017102004008000), B(0x10411104000480, 0x1414042000201001), B(0x220040000008, 0x800306021000000), B(0x41400A0008080, 0x501000298ACAD10), B(0x800240012831810, 0x80120004468050E), B(0x800005020801008, 0x20102400240000), B(0x20C00040C114C010, 0x88080820200C00), B(0x1044010100820081, 0x20080841004000), B(0x8041048400022, 0x8020836040005002), B(0x2001004010205, 0x8001002884042009), B(0x128088400087, 0x20008002201002), B(0x8084108040402000, 0x80809000A080400), B(0x408081840880, 0x201002088000040), B(0xA40180010280, 0x241004006000010), B(0x4204100080048140, 0x2002C4F104202020), B(0x100140A10204, 0x980200800840060), B(0x1005140010202048, 0x1442280800202815), B(0x2000082025008600, 0x1108400040600003), B(0x1005050648000, 0x200020240008002), B(0x202010208044000, 0x8210404060008), B(0x8011040402000210, 0xC840180408016004), B(0x404098801028, 0x80020A0001000400), B(0x404098801028, 0x80020A0001000400), B(0x80101002180140, 0x40C2080820000C0), B(0x208202081260800, 0x14090E4C04000050), B(0x4221201084004C2, 0x110480A011060), B(0x8000008421090204, 0x1C01010800024), B(0x8000008421090204, 0x1C01010800024), B(0x200180C840088A0, 0x401100400820000), B(0x10084043A021070, 0x202041600080200), B(0x210E6202001040C, 0x10100800080B0), B(0x848008021204002, 0x801004308100BAD), B(0xC082C0390A000601, 0x4040080189008), B(0x431200240210402D, 0x58102820000), B(0x202020100A0019B0, 0x4010C0D018000000), B(0x800800908402203, 0x102948C84C184), B(0x26801100080845, 0x4009702022A00820), B(0x8880520010401040, 0x1060084832052000), B(0x100100022042081, 0x10000600008C121), B(0x46020384100040, 0x800200320882021), B(0xC0002010148, 0x4200800800040003), B(0x2002208020090040, 0x40820210021410), B(0x9000A41160002004, 0x2A09000100080043), B(0x800004010008001, 0x1108002020104600), B(0x800540C000A4E041, 0x18021180000401), B(0x808200900A900202, 0x8364202140012005), B(0x1DBA52000081010, 0x4008000023000010), B(0x4100110204401481, 0x800040091020001C), B(0x4100110204401481, 0x800040091020001C), B(0x4101100020400482, 0x2000402302100120), B(0x100408000A020212, 0xA000400111000020), B(0x2000010488080104, 0x3000404410208100), B(0x2684220180008DD0, 0x422040200004000A), B(0x2021200C0424, 0x1010100000080200), B(0x8908020020801006, 0x3010800020C2000), B(0x4000030008062044, 0x244010202688000), B(0x242101200408009, 0x8150040000200015), B(0x42004C02180204, 0x210208014241040), B(0x4E1A01C208410804, 0x8890041000012004), B(0x2080200401000080, 0x8001098429008004), B(0xA01400121804104, 0x280200C400000500), B(0xD0080408040420, 0x1006040100224000), B(0x28400205000800C9, 0x6021101401040075), B(0x4000900040020104, 0x88129801100D0C), B(0x8000004002180410, 0x400380200400204), B(0x4002A430043008, 0x400200340100020), B(0x401960004140A42, 0x100880710000464), B(0x58014090102, 0xB8D30004010080), B(0xA004C08000244000, 0x11280100E0000040), B(0x2102008089208804, 0x110001004080040), B(0x700010084E003004, 0x8080864112000D40), B(0x4080881000200C20, 0x30324040880E0600), B(0x2024A40401810820, 0x3000888002000000), B(0x8200100400014, 0x4400340800252844), B(0x24A00804288281, 0x410103002201140), B(0x4080005022A08, 0x1000402200100264), B(0x200080032244040, 0x200502189010001), B(0x28108110404001, 0x400600120008412), B(0xA00002102810020, 0xB1080240015408), B(0x810080200806, 0x410440804080046) }; constexpr Bitboard BishopMagicInit[SQUARE_NB] = { B(0x2001040305000010, 0x830200040400082), B(0x1042400080E01200, 0x2004904010811400), B(0x400010120200, 0x880080D080018000), B(0x240190C00100040, 0x100A020140044404), B(0x1018010404010004, 0x1001010018081E0), B(0x41200A804C0904, 0x40000322000008), B(0x4001180A004, 0x8000001106000000), B(0x6006020020030600, 0x1840002100004841), B(0x4200200100, 0x4001041808002000), B(0x4100020050124600, 0x1001802902400CA0), B(0x448C0081440161, 0x200206010008000), B(0x400008008008408, 0x1000080210100080), B(0x200280C01008200, 0x210200813000080), B(0x1A000204400, 0x222200401023000), B(0x10081040640A00, 0x8410021881400000), B(0x1840400318080008, 0x800800840080000), B(0x4204050C040, 0x6500600200140000), B(0x1012100040204, 0x402404444400000), B(0x6000012680008240, 0x410140000004220), B(0x1000020810040008, 0x2D0011000060000), B(0x1020020400, 0x400108059001001), B(0x400020001100808, 0x480204800200000B), B(0x10000010030084, 0x2042000848900022), B(0x10000010030084, 0x2042000848900022), B(0x100D801402400, 0x1512404009000400), B(0x8000208005112400, 0xA02040401000000), B(0x1000420002800200, 0x4CA000183020000), B(0x800811480020, 0x408801010224001), B(0xC805200810900100, 0x9000084204004020), B(0x8200160204100004, 0x8040004004002022), B(0x104514013080080, 0x146410040001000), B(0x140844000080002, 0x1008102020040001), B(0x4040400041A2002, 0x8040000A8802510), B(0x801014041008002, 0x80068008025200), B(0xA00540A414040, 0x4101040010A0000), B(0x6484008010810002, 0x1100506884024000), B(0x2800401008006000, 0x1005420884029020), B(0x6822091010004421, 0x2000458080480), B(0x40101000200101, 0x10020100001C4E0), B(0x100400008C42, 0x4000100009008000), B(0x851220018800400, 0x1681800040080080), B(0x64200002010, 0x900020200040002), B(0x20800080000022, 0x80040810002010), B(0xA88408000802080, 0x20808001000000), B(0x200000400C005040, 0x100140020290108), B(0x224100000800408, 0x4204802004400020), B(0x80080620010210, 0x91080088804040), B(0x4008002100010, 0x80AC201001000001), B(0x10008200902C046, 0x8080D03004000010), B(0x3002100081000180, 0x2210002121528408), B(0x8C101800804420, 0x1019880200043008), B(0x200022000920D0, 0x8000800081300020), B(0x1D40800880000, 0x400040001400050), B(0x2020004100040, 0x200008040008008), B(0x4840800040100001, 0x100100040203040), B(0x40084001105, 0x8800080088000089), B(0x4000128008020008, 0x4004200200440020), B(0x210040008520000, 0x820219001080022), B(0x1494040018002116, 0x400101047020008), B(0x510008001910C224, 0x80200148118000), B(0xC0301002301000, 0x4211A08004801), B(0x50008E0C01001080, 0x100C004102845100), B(0x400600020060400, 0x88024100250050), B(0x8202920002002040, 0x810012000003), B(0x800004208800200, 0x18AA00201000048), B(0x402100800100002, 0x411000081000400), B(0x101000022004044, 0x9000100040000), B(0x41068001001, 0xC00400010001), B(0x310210001040, 0x1A1200020010000), B(0xA082409200004048, 0x490040800124101), B(0x18844820E0040212, 0x1000404420D10000), B(0x802908A40003348, 0x20200040104140), B(0x1800404028205003, 0xC020010401089020), B(0x802100044D01000, 0x8C41888000800040), B(0x1D0161011410081, 0x10008000100200), B(0x401000480040100, 0x286800404002212), B(0x821030000100009, 0x2000090200A00000), B(0x200020800200800, 0x2000480900841012), B(0x80A000048030080, 0x200000120200008), B(0x40B1400008020020, 0x148000200008004), B(0xA021700002002010, 0x3040E400040100), B(0x400242C200200640, 0x20440210200281), B(0x80AC140040206240, 0x120000102801401), B(0x2020340040832040, 0x10402100A44000), B(0x420100400040220, 0x80014C8004000106), B(0x504300822421120, 0x8004004008400100), B(0x2001100008040, 0x2020104302000000), B(0xA500802000A, 0x2008008000114100), B(0x8A0020000200, 0x9C00101001002408), B(0x104000001001008, 0x9001000204040060), B(0x1000820080108200, 0xA401000008100001), B(0x2008600009000480, 0x9008020001400000), B(0x4000800200040200, 0xA00030400308082), B(0x4004300202004709, 0x1000100180010020), B(0xC014800100440010, 0x402020280002C010), B(0x220208010884680, 0x1040280000042110), B(0x40B0018019202801, 0x1008408000100040), B(0x8269010206080044, 0x8001810000000040), B(0x4000020880081040, 0x208A44000028000), B(0x4004004E9004220A, 0x2104004001400024), B(0x8035006008C0904, 0x402002001080120), B(0x1800884002, 0x404400820000000), B(0x8088000004008910, 0x8024100401000000), B(0x142200086000100, 0x28021040020002E), B(0x1000409141004018, 0x100410820080040A), B(0x1800801800140, 0x810801060C0801), B(0x1000C00100402220, 0x808023420000000), B(0x8A0A202414305008, 0x100040200000021), B(0xC0208024050, 0x8003088008020401), B(0x8044004201440101, 0x400820080C024022), B(0x406018884120099, 0xB00088018002000), B(0x2000800010403010, 0xC5A002002010010), B(0x800020040840, 0x201800202800200), B(0x201280120020008D, 0x258809001000040), B(0x9100002020181, 0x80400082204000), B(0x104010080201001, 0x40080080181080), B(0x8440248092000430, 0xA200804900100000), B(0x2031010C01000C20, 0x200310A560082008), B(0x400202081811400, 0x40081802050000C), B(0x1011002100821300, 0x2400825040804100) }; constexpr Bitboard CannonMagicHInit[SQUARE_NB] = { B(0x120000880110000, 0x1008000000020020), B(0x24200C080840A052, 0x2004004000010008), B(0xC030024000228800, 0x4000010400000020), B(0x1A0020802008802, 0x206010208000), B(0x12002000D001024, 0x80100800090138), B(0x4220010000241010, 0x3098000602001500), B(0x401010004801040, 0x8000280480100000), B(0x820082024921836, 0x220028000), B(0x100400502411400, 0x220402120240D14), B(0x880202020010404, 0xA80202510000), B(0x140002801000018, 0x1000346490040), B(0x120000880110000, 0x1008000000020020), B(0xD01004008030400, 0x104000408104420), B(0x8420060100020000, 0x800280400000120), B(0x4010020018010, 0x40A00001100000), B(0x40006A0004000200, 0x40000000110), B(0xD01004008030400, 0x104000408104420), B(0x8908A20028110011, 0x800080000001A114), B(0x200042000080F009, 0x20001000004000), B(0x2820008820100, 0x10002400058000B9), B(0x6083100420008050, 0x4040012600280080), B(0x216020000000446, 0x4080204000000211), B(0x340140003002089, 0x2402008000000911), B(0xD01004008030400, 0x104000408104420), B(0x1404040B20001000, 0x8000824010800011), B(0x8C0488120024214, 0x8414880202291), B(0x1010000060050000, 0x4000004050002602), B(0x4022983A0060000, 0x80000040010400), B(0x1404040B20001000, 0x8000824010800011), B(0x6020101802002840, 0x31000003000004), B(0x9000420008840, 0x4881300000000210), B(0xA200808865, 0x41C0048023000128), B(0x31801100400000, 0x8802DC001221240), B(0x884000080200920, 0x1004002410401001), B(0x2400040000884, 0x421006208040C0), B(0x1404040B20001000, 0x8000824010800011), B(0x24100400060009, 0x112008025042410), B(0x1800040009040200, 0x180000A1004E408A), B(0x24100400060009, 0x112008025042410), B(0x4060402008080, 0xC240080000110000), B(0x20080100920020, 0x2002248010242052), B(0x10001010802050, 0x880000001C98420), B(0x4000800100420022, 0x502022010A00D0), B(0x4C18104500200885, 0x400880800), B(0x8080810081020090, 0x8000000000000), B(0x8000062812080201, 0x8004C8300800), B(0x1800040009040200, 0x180000A1004E408A), B(0x24100400060009, 0x112008025042410), B(0x80102204040, 0x1000000900000000), B(0x2080000004202804, 0x120880003461), B(0x102004090A4030, 0x801020589240), B(0x20001100814000A0, 0x420202000820004), B(0x100800000A000120, 0x208000800010000), B(0x1008205000040802, 0x80002000400040), B(0x1480000098008401, 0xA0010000581010), B(0x30C0008200100820, 0x102800080904834), B(0x4810821884000500, 0x4400000200000212), B(0x1811D00128A0180, 0x2500848803000000), B(0x41618A0300040040, 0x21200200A421801), B(0x80102204040, 0x1000000900000000), B(0xA1808E0100108000, 0x2008000505000002), B(0x8C890020410000A0, 0xA010000048000400), B(0x40006002210044, 0x600008000408000), B(0x1200447220090042, 0x80001000160012), B(0x48410010AB000000, 0x9200600000000100), B(0x2040000000240003, 0x8020080288000600), B(0x9080000088848088, 0x4010210500000041), B(0xA1808E0100108000, 0x2008000505000002), B(0x480100400024, 0x1004800018200000), B(0x808403080080200, 0x802601000000500), B(0x8C890020410000A0, 0xA010000048000400), B(0xA1808E0100108000, 0x2008000505000002), B(0x100A40000004008, 0x2800200400200480), B(0x100A40000004008, 0x2800200400200480), B(0x400014006000000, 0x10006000810001F5), B(0xC410062001414, 0x820080041B01044), B(0x20000800310, 0x430040000201000), B(0xA40010008000008, 0x4002200028000040), B(0xC00102000008021C, 0x10C2000A010E024), B(0x80004200104008, 0x50A00800C400020), B(0x20200080012542, 0x910F0040000402C0), B(0xB040100504000300, 0x24802002000040), B(0x800001000014008, 0x400031004000), B(0x100A40000004008, 0x2800200400200480), B(0x84008002041081C0, 0x8080500200000000), B(0x440090001012001, 0x4020004010), B(0x100A0028088020, 0x80040E00010020), B(0x2180808000810, 0xB018040A00040000), B(0x40C80920304C4001, 0x42800B200800000), B(0x85000425001000, 0x4810048020001100), B(0x600C000801000004, 0x8015084010200020), B(0x20020050000240C0, 0x100202008600800), B(0x38000050001220, 0x9200010200145900), B(0x1042108040005, 0x1402A0802201001), B(0x824240000C20400, 0x1000000400080010), B(0x84008002041081C0, 0x8080500200000000), B(0x400804A1000008, 0x1024104A0200010), B(0x8000402308483, 0x20006020100100), B(0x80880120000080, 0x8000240100084), B(0x5840020004882001, 0x1004528000A00010), B(0x8001018800300002, 0x84010040804), B(0x180D10004000A008, 0xA001080008020004), B(0x400080B, 0x10A0000004010000), B(0x8080000200000, 0x2001000082004E0), B(0x40040001000C2000, 0x2024800001004008), B(0x400804A1000008, 0x1024104A0200010), B(0x8000402308483, 0x20006020100100), B(0x400804A1000008, 0x1024104A0200010), B(0x2000200000, 0x1201011000802), B(0x100100000000C4, 0x208004084048201), B(0x400084000044, 0x100810140300), B(0x29040C0C01010, 0x300204010820080), B(0x1A808000020200, 0x1000000005210040), B(0x20000400150000, 0x85008020), B(0x40C040008184014, 0x8002AA00024010), B(0x202000081B00804, 0x10001002008), B(0x40011000210060, 0x6080C40000021004), B(0x2000200000, 0x1201011000802), B(0x4100480203840, 0x300080100804), B(0x2000200000, 0x1201011000802), }; constexpr Bitboard CannonMagicVInit[SQUARE_NB] = { B(0x202000812104400, 0x24800B01C0000303), B(0x340020400010D, 0x88060150C00400), B(0x400802040609, 0x49010200501A0002), B(0x8002680301000208, 0x628006C0C020200), B(0x20400209001C0804, 0xA044000800143110), B(0xC400082060010202, 0x4000480401014000), B(0x22500200144040, 0x8204820084704C00), B(0x8C1204009030020, 0x328400805000000), B(0x84800800D0001640, 0x200080040060108), B(0x804810208020040, 0x140010108020000), B(0x1102010B008004, 0x300208006220020), B(0x140080404A0A2428, 0x6308010100080), B(0x20444002120408, 0xA080010508010001), B(0x82011044000D02, 0x4112028620110809), B(0x81010831000C02, 0x408802085000000), B(0x81010831000C02, 0x408802085000000), B(0x920008920600040, 0x8053801004000028), B(0x81140283208300, 0x10040C004200420), B(0x103080022201, 0xC01000081312620), B(0x2200221100008, 0x1000408104000A4), B(0x4402088080042008, 0x210401501040340), B(0x898400202170001, 0x80040404208000), B(0x20080004051012, 0x5100048200081800), B(0x2320020000401018, 0x108501021040210), B(0x21080410A422021, 0x83040180008800), B(0x44E8100000408224, 0x20010008040400), B(0x1800240002810405, 0x23004820000020), B(0x80A0100400110, 0x80104020100C4028), B(0x1002050001222C0, 0x5100818004024020), B(0x104000200040, 0xC010A09800102000), B(0x1020003A058120, 0x450900809000302), B(0x40040045008B1, 0x202800400383010), B(0x4640200220034, 0x8800485420304000), B(0x5001042100084288, 0x110820001240080A), B(0x2002C04004010120, 0xA15008020880001), B(0x2800004080C4190, 0x890808280020080), B(0x40C0401000104000, 0x2020880008002580), B(0x40020C002400802, 0x801104010000000), B(0x44842000040080, 0x2050011084000400), B(0x4110040800000401, 0x2023810029008000), B(0x20884000840, 0x8017102004008000), B(0x10411104000480, 0x1414042000201001), B(0x220040000008, 0x800306021000000), B(0x41400A0008080, 0x501000298ACAD10), B(0x800240012831810, 0x80120004468050E), B(0x800005020801008, 0x20102400240000), B(0x20C00040C114C010, 0x88080820200C00), B(0x1044010100820081, 0x20080841004000), B(0x8041048400022, 0x8020836040005002), B(0x2001004010205, 0x8001002884042009), B(0x128088400087, 0x20008002201002), B(0x8084108040402000, 0x80809000A080400), B(0x408081840880, 0x201002088000040), B(0xA40180010280, 0x241004006000010), B(0x4204100080048140, 0x2002C4F104202020), B(0x100140A10204, 0x980200800840060), B(0x1005140010202048, 0x1442280800202815), B(0x2000082025008600, 0x1108400040600003), B(0x1005050648000, 0x200020240008002), B(0x202010208044000, 0x8210404060008), B(0x8011040402000210, 0xC840180408016004), B(0x404098801028, 0x80020A0001000400), B(0x404098801028, 0x80020A0001000400), B(0x80101002180140, 0x40C2080820000C0), B(0x208202081260800, 0x14090E4C04000050), B(0x4221201084004C2, 0x110480A011060), B(0x8000008421090204, 0x1C01010800024), B(0x8000008421090204, 0x1C01010800024), B(0x200180C840088A0, 0x401100400820000), B(0x10084043A021070, 0x202041600080200), B(0x210E6202001040C, 0x10100800080B0), B(0x848008021204002, 0x801004308100BAD), B(0xC082C0390A000601, 0x4040080189008), B(0x431200240210402D, 0x58102820000), B(0x202020100A0019B0, 0x4010C0D018000000), B(0x800800908402203, 0x102948C84C184), B(0x26801100080845, 0x4009702022A00820), B(0x8880520010401040, 0x1060084832052000), B(0x100100022042081, 0x10000600008C121), B(0x46020384100040, 0x800200320882021), B(0xC0002010148, 0x4200800800040003), B(0x2002208020090040, 0x40820210021410), B(0x9000A41160002004, 0x2A09000100080043), B(0x800004010008001, 0x1108002020104600), B(0x800540C000A4E041, 0x18021180000401), B(0x808200900A900202, 0x8364202140012005), B(0x1DBA52000081010, 0x4008000023000010), B(0x4100110204401481, 0x800040091020001C), B(0x4100110204401481, 0x800040091020001C), B(0x4101100020400482, 0x2000402302100120), B(0x100408000A020212, 0xA000400111000020), B(0x2000010488080104, 0x3000404410208100), B(0x2684220180008DD0, 0x422040200004000A), B(0x2021200C0424, 0x1010100000080200), B(0x8908020020801006, 0x3010800020C2000), B(0x4000030008062044, 0x244010202688000), B(0x242101200408009, 0x8150040000200015), B(0x42004C02180204, 0x210208014241040), B(0x4E1A01C208410804, 0x8890041000012004), B(0x2080200401000080, 0x8001098429008004), B(0xA01400121804104, 0x280200C400000500), B(0xD0080408040420, 0x1006040100224000), B(0x28400205000800C9, 0x6021101401040075), B(0x4000900040020104, 0x88129801100D0C), B(0x8000004002180410, 0x400380200400204), B(0x4002A430043008, 0x400200340100020), B(0x401960004140A42, 0x100880710000464), B(0x58014090102, 0xB8D30004010080), B(0xA004C08000244000, 0x11280100E0000040), B(0x2102008089208804, 0x110001004080040), B(0x700010084E003004, 0x8080864112000D40), B(0x4080881000200C20, 0x30324040880E0600), B(0x2024A40401810820, 0x3000888002000000), B(0x8200100400014, 0x4400340800252844), B(0x24A00804288281, 0x410103002201140), B(0x4080005022A08, 0x1000402200100264), B(0x200080032244040, 0x200502189010001), B(0x28108110404001, 0x400600120008412), B(0xA00002102810020, 0xB1080240015408), B(0x810080200806, 0x410440804080046), }; constexpr Bitboard HorseMagicInit[SQUARE_NB] = { B(0x3C080482A592000C, 0x540104000020000), B(0x2802C40008000420, 0x4A00000001818009), B(0x1083040280804000, 0x120004C20100880), B(0x6840940880000892, 0x2014A01080800C2), B(0x8401489004000180, 0x2000800000400000), B(0x820161C800000110, 0x8000100000204020), B(0x610011A122000109, 0x1000004020008004), B(0x83282004023000, 0xE000020004848446), B(0x6840940880000892, 0x2014A01080800C2), B(0x4020120800800002, 0x88008000010020), B(0x30025B140A1000, 0x3141801401000040), B(0x41104D1810100050, 0x8141002010910), B(0x4200828A298400, 0x400340001040C000), B(0x8016A4900110040, 0x844812001068020), B(0x2250035820400A2, 0x8012010080900), B(0x820080083A009000, 0x880404091080110), B(0x80401500AF0020, 0x240000082201A04), B(0x668020020C081005, 0x4008001004100021), B(0x240100910000000, 0x82000A0030454000), B(0xA24091400008, 0x200014880004A921), B(0x840110042200410, 0x100080000A400000), B(0x40024024102000, 0x1000000002180404), B(0x92828423000530, 0x118800020110), B(0x1122404A1C90A8, 0x822040280020D00), B(0x41201A40900A000, 0x80C0480040605100), B(0x2504A85005488280, 0x3028112120022800), B(0x210180080626B048, 0x8000401000014000), B(0x1000410401040200, 0x41014000050C0106), B(0x1040650210802200, 0x80C0041000000), B(0x4020C10110900002, 0x2140C2001050009), B(0x191180092200022, 0x6010008400400800), B(0x8010821088080202, 0xCA240011008208), B(0x8C0488120024214, 0x8414880202291), B(0x8C0488120024214, 0x8414880202291), B(0x22080C8A0161401, 0x200C10004C002002), B(0x8430818023034080, 0x210090800000801), B(0x4845087008200, 0x40661480000), B(0x1202804428812050, 0x100022038020000), B(0x400016001201080, 0x24002200402060), B(0x680E041300800800, 0xE00130080004000), B(0x3409080200, 0x282840210000000), B(0x803310108400, 0x85200000080100A0), B(0xE180008A04162104, 0x9088240412404), B(0x20080100920020, 0x2002248010242052), B(0x8A000400C2410, 0x1000024086014300), B(0x1821040024663, 0x100000100010009), B(0x4000822310611, 0x120280406014008), B(0x1004008010818D08, 0x800000141892000), B(0x8010800004024042, 0x44B106008800896), B(0xA0063423444, 0x41002C15811008), B(0x2040012381001282, 0x4804080104A4000), B(0x10840101820880, 0xA800008000020020), B(0x10840101820880, 0xA800008000020020), B(0x60201D8300408190, 0x2010020920200000), B(0x4048100200090090, 0x2008090100000900), B(0x24200000280210, 0xD440050008004000), B(0x1280001000580020, 0x2200040089000A4), B(0x10208018C1020A20, 0x84C0432240610014), B(0x10208018C1020A20, 0x84C0432240610014), B(0x4108000010209089, 0x913000000024840), B(0x410C208008008E02, 0xE8000000000001), B(0x802208004005, 0x94206000022080), B(0xC00290018902002, 0x4204100000000000), B(0x2102801400093816, 0x9810004001000202), B(0x8008304000015800, 0x4A5C000000020000), B(0x1020108380800514, 0x1144210000000080), B(0xC0001000008090, 0x2812060000204000), B(0x1001100200003100, 0x246240060A004004), B(0xA00020A008002030, 0x2440C40000110B00), B(0x80502104000C008, 0x8222200042100010), B(0xC020200088014, 0x422094000000480), B(0x1029002000001030, 0x8105841120000210), B(0x49040D, 0x2310808A14042C0), B(0x200040200080A02C, 0xB890290400080000), B(0x2240180C0800002, 0x4151050280000100), B(0x2240180C0800002, 0x4151050280000100), B(0x8220224180420006, 0x4024501212011000), B(0x1806810A0881000, 0x802002048400080), B(0x400400A080842, 0x9305000401180000), B(0x10008001444110, 0x4420401040041833), B(0x2000002C02010E00, 0x400408D08009804), B(0x69D008200020100, 0x100842240049021), B(0x42C24450020000, 0xD38400880090884), B(0x485800800100001, 0x2484086522018840), B(0x900200020820042, 0x22302421400040C0), B(0x50B0413001818000, 0x452014040800C40), B(0x8004040021008, 0x20088A08000290), B(0x600C000801000004, 0x8015084010200020), B(0x208000C00, 0xE004804021100100), B(0x20001000040204, 0x948110C0B2081), B(0x268502400100021, 0x80A201840802080), B(0x408C000008, 0x8822102408014), B(0x1182080410100000, 0x608002046A0100), B(0x100820A083C00002, 0x3100100410A00), B(0x8401040000400124, 0x2000081288202200), B(0xB014040003000800, 0x11960D1101210), B(0x10040001900C000, 0x85603C1001280), B(0x2000844000000100, 0x2000024C60800800), B(0x120004234800900, 0x210010841040), B(0x8010300040000002, 0x4200008222104100), B(0x1000120402200100, 0x209080CC040108B4), B(0x110049A00000800, 0x80000420022180A8), B(0x80001C00080384, 0x1400101111081001), B(0x8011200008100428, 0x2020000880800922), B(0x10001000000204C8, 0x280C11104240), B(0x50100C82C000500, 0x28000280618DD1), B(0x8800498020000, 0x20500A0200320128), B(0x20010104000860, 0x8021720186008), B(0x4000000000100080, 0x35040084270C04), B(0x4500080000800, 0x280100002482C842), B(0x10400000000000, 0x20080051100130C2), B(0x10400000000000, 0x20080051100130C2), B(0x2000002110202014, 0x121004004004681), B(0x400202001006D40, 0x82240082202424), B(0x4500080000800, 0x280100002482C842), B(0xC6000000D00804, 0x1050020C0081090C), B(0x200080000000042, 0x10800661), B(0x2000001011200200, 0x2A420000802A0222), B(0x802020001202412, 0x2400404148426), B(0x8000440801040002, 0x444002800010052A), }; constexpr Bitboard ElephantMagicInit[SQUARE_NB] = { B(0x64D2990200008, 0x4401880001C000), B(0x29BAA00010020, 0x200000400800600), B(0x3024240000000, 0x4100400010080), B(0xA490A00480020, 0x20084001041010A4), B(0x328C021008042, 0x100000000C10204), B(0x1964090001018, 0x7002040148001205), B(0x800302098404080, 0x4983020000000001), B(0x8812244630A02080, 0x8200006204003C08), B(0x41120231008000, 0x240441401020), B(0x840091030C00040, 0x1400008200023400), B(0x8001040E77030200, 0x100040090022000), B(0x602022139D835040, 0x101002010025900), B(0x405707C48400, 0x40010000008001), B(0x982003456A82050, 0x60800820040030), B(0x204184849200088, 0x101800004006), B(0x300222470949200, 0x2A0800200200800), B(0x400001211914000, 0x8200001407001), B(0x2000008614831020, 0x4000020001404000), B(0x84000024A2048048, 0x1200102000042), B(0x424010A58422008, 0x88440242212A0110), B(0x20020812C0C4408, 0x4121400000080010), B(0x680200062042420, 0x2001100000800000), B(0x200010060AEC855, 0x8083002040200000), B(0x4000008BAA85810, 0x82000805C0200A90), B(0x81450B200A025400, 0x4400101050000040), B(0x820A2241409010, 0x888420030000), B(0x909203000028, 0xC000004C00200041), B(0x8021400A84880240, 0x100180002010020), B(0x8001A20061410000, 0x14008499A000000), B(0x8201444800A00080, 0x402010040588120), B(0x100C06280020, 0x60010104840130), B(0x520040800080044, 0x8220000080001402), B(0x102021410040202, 0x2004400410006000), B(0x5401832090020400, 0x300010020001), B(0x180003105A84C108, 0x1012008800081000), B(0x480C10210026904, 0xA006000004200418), B(0x48050820210843A6, 0x108001004000C00), B(0x1030101182206324, 0x4401008921502002), B(0x40281060800800, 0x406000201260022), B(0xC29002440040C820, 0x400001002008020), B(0x40000400800241, 0xC220000000400280), B(0x40880126014208, 0x2A8004C008940000), B(0x121028100114080, 0x5010280481100082), B(0x4000088280442, 0x908420140008041), B(0x808C42400C0020, 0x3028100840801000), B(0x4000000410078488, 0x501000000620000), B(0x90080001421020A4, 0x4118400101060406), B(0x280420004855, 0xD200100400820000), B(0xA0063423444, 0x41002C15811008), B(0x200061201808102, 0x4286969000200002), B(0x10208018C1020A20, 0x84C0432240610014), B(0x4001A04880402000, 0x8100824080000001), B(0x60201D8300408190, 0x2010020920200000), B(0x20018C04908019, 0x2010884002002040), B(0x800000000C40810, 0x680100081150000D), B(0x2002002000040040, 0x8810049000010600), B(0x41618A0300040040, 0x21200200A421801), B(0x10208018C1020A20, 0x84C0432240610014), B(0x10208018C1020A20, 0x84C0432240610014), B(0x5A04001400412854, 0x8A44006000010002), B(0x13000C0810072432, 0x50049001021104), B(0x400048801142130, 0x4C1204100226010C), B(0x80001048, 0x408800104000080), B(0x8104868204040412, 0x22244202000081), B(0x8104868204040412, 0x22244202000081), B(0x4140001000240440, 0x80209004410004E), B(0x800800000100, 0xB111820100000002), B(0x404240004220, 0x2110402802050080), B(0x284010400004040, 0x100245002502020), B(0x14880A100114010, 0x400208080010024), B(0x4100004040440648, 0x10030D838041A80), B(0x32004000210, 0x4010225C88014000), B(0x2240180C0800002, 0x4151050280000100), B(0x2010A12002000042, 0x189051442010000), B(0x4060050080121883, 0x8250C10001000141), B(0x10000000044100, 0x8401084010261009), B(0xA00028040000, 0x2003224000002000), B(0x2060009001000020, 0x1000432022020228), B(0x404200000883080, 0x1080800848245000), B(0x240000402080, 0xCA0820814210502), B(0x200040200080A02C, 0xB890290400080000), B(0x800000000300482, 0x9203008100100013), B(0x8000210202042000, 0x22642104004C2400), B(0x1040400805000401, 0x2A0300102C80010), B(0x8010A01088020000, 0x122106105A06A030), B(0x8000C00001010494, 0x130A1A20404120), B(0x4B084010844290, 0x10A08008900840), B(0x1180001802460000, 0xB08000034C82004), B(0x4001880060028029, 0x204040002401000), B(0x8021A0001308002A, 0x97001822040040), B(0xC00000009A020AC1, 0x1000080900400), B(0x60010110001990, 0x4000880900400000), B(0x10290402401200, 0x230080402C08), B(0x4220000219012000, 0x140204804100008), B(0x1400200100002, 0x8E62200414128), B(0x402808502004403, 0x20049100C0284520), B(0xB30041004280280, 0x10020464DB200308), B(0x440010800808, 0xA0102E295812100), B(0x10008000B000, 0x2000058583220200), B(0x2000844000000100, 0x2000024C60800800), B(0x110000400100028, 0x24052304508004), B(0x8458000000840004, 0x118006463400001), B(0x804008000040050, 0x41044890228000), B(0x20000050000400, 0x80A101824A00086), B(0x600080404000020, 0x100007322480005), B(0xD082200020020008, 0x642000630120001), B(0x10000100040230, 0x8048114733320002), B(0x20200442002A880A, 0x8200002CB4B8052), B(0x290080000000, 0xA41297838F40D), B(0x800205000080, 0xF221232039874400), B(0x1444002004880C20, 0xC4100049144200), B(0x4500080000800, 0x280100002482C842), B(0x281240881008, 0x204084004C101900), B(0x1444002004880C20, 0xC4100049144200), B(0x4500080000800, 0x280100002482C842), B(0xC0010928430540, 0x92041902180), B(0x1051001208A, 0x4900064800C20640), B(0x882020418C00000, 0x30004040092A821), B(0x224404002004268C, 0x202500204C7D254), B(0x290080000000, 0xA41297838F40D), }; constexpr Bitboard JanggiElephantMagicInit[SQUARE_NB] = { B(0xC502282200061400, 0x2D07081241D90200), B(0xC502282200061400, 0x2D07081241D90200), B(0x8084810022440C2, 0x81402202004), B(0x80204010A800500, 0x5000021001740218), B(0x8048100401208000, 0x2001000390000044), B(0x202080020000000, 0x4010800010090424), B(0x4081A0480073200, 0x100000A010406000), B(0x4081A0480073200, 0x100000A010406000), B(0x2040450004000C40, 0x8400000006302), B(0x84010410018201, 0xA00A00000100000), B(0x840091030C00040, 0x1400008200023400), B(0x801058C0A0022, 0xC1920480010034), B(0x80B4004800840800, 0x4080210A42040010), B(0x400402221000445, 0x80321200408040), B(0x4028142401012A00, 0x4005009000104448), B(0x1440102040800220, 0x82800010A082000), B(0x4100040300C00200, 0x800805100120000), B(0x8200080061100, 0x2000101400000), B(0x2000100410070001, 0x40818200B0900410), B(0x400088020080000, 0x4A000402000CA0), B(0x1402040410004000, 0x9840044504040), B(0x20800088A00A0400, 0x1000020100180), B(0x2001820520308201, 0x2008003404349000), B(0x4004808022100, 0x8001000008081080), B(0x102041041100425, 0x840400180B100104), B(0x8806446000800214, 0x404402100010000), B(0x8200141409C04101, 0x209030004A00D00), B(0x8806004800880080, 0x1560004201000A01), B(0x4200050600200090, 0x1CD0000000000421), B(0x4820100022408100, 0x101404080320), B(0x2A000A0A08080080, 0x1C02808000C2C0), B(0x8808425040040014, 0x2021000100020), B(0x5282104044A0020, 0x6B402104200008), B(0x4001091040068120, 0x202000004003031), B(0x4001091040068120, 0x202000004003031), B(0x98040200A0214344, 0xA00300840010), B(0x82508040A40808A, 0x40010000110042), B(0x4400100101023, 0x450C8480040022), B(0x210588880010800, 0x800A000108018102), B(0x9400010144400, 0xC00010100018000), B(0x20A0400100040004, 0x1242000101002040), B(0x8022900040001001, 0x100000014000260), B(0x51004124000A080, 0x40098400000002), B(0x2158040001080022, 0x80009238401222), B(0xA0103A0000802220, 0x20000200400010), B(0x1101001208240, 0x100000800001064), B(0x821020002090081, 0x5840D0010290280), B(0x821020002090081, 0x5840D0010290280), B(0x10400C1042000400, 0x4005000000440200), B(0x844022008804820, 0x1000800100118000), B(0x10802A9800800139, 0x4802840100842200), B(0x4000A008200081, 0x4001100200402000), B(0x200000008108400, 0x1000C00008080020), B(0x120C11500100081, 0x440300308041100), B(0x8080040080060100, 0xC00101B0040028), B(0x901420A00110000, 0x8200010044700280), B(0x140080080410000, 0x808040000C001001), B(0x80210C0200A0008, 0x88088004600201), B(0x8000004202020301, 0x2100142104002000), B(0x1101011210004880, 0x8500840400000000), B(0x40208802004800, 0x8080806009011240), B(0x800000140408880, 0xC001018004060040), B(0xC008080420500, 0x8024A10000000000), B(0x2800000000400010, 0x44001C00400408), B(0xA804008001200408, 0x202000020001000), B(0xC08288805004080, 0x200042000800004), B(0xA40A01000080012, 0x8800080042408), B(0x2200100000100810, 0x800200010000100), B(0x9881800004040001, 0x8058100100884004), B(0x820000044020014, 0x4AA00010245012), B(0x820000044020014, 0x4AA00010245012), B(0x4000080240000808, 0x10100022054000), B(0x5002000840101, 0x202020004000A00), B(0x1188008200008402, 0x8088100020A2204), B(0x304012004044080, 0x8028108818006010), B(0x102210000008400, 0x1008000200380002), B(0x51410E114200, 0x100C00084000000), B(0x5001242320218, 0x800025000040040), B(0x4008000200008190, 0x400020021000000), B(0x10910022F0040, 0x450084400040001), B(0x180010810000040, 0x4004100040040), B(0x1088801424062010, 0x400084010030401), B(0x3000120408000040, 0x10802001080A4051), B(0x200008420, 0x40C0100020008804), B(0x1048C000004000, 0x4220120804004000), B(0x404A180000000E, 0x4C30412008110102), B(0x400000404202005, 0x800808550EC40044), B(0x282000200212010, 0x8001C0C102000210), B(0x9012240000008100, 0x280CA04010040000), B(0x2000C04001020C00, 0x2002010101042000), B(0x1010000204408408, 0x8008004800E0C4A), B(0x800286801000025, 0x8402401040050088), B(0x40002000A0880000, 0x8400300108082086), B(0x2080004404011, 0x20C080400100001), B(0xB0010218100800, 0x8040200482C14103), B(0x8011035000000C20, 0x4200044043200040), B(0x804008000040050, 0x41044890228000), B(0x80000400A0020020, 0x5308022021000000), B(0x2118200000008004, 0x4141014004423D00), B(0x90C0000200008040, 0x41041062000082), B(0x1D000100941204, 0x12402001200420), B(0x8C0040400400065, 0x22300B408100000), B(0x8C0040400400065, 0x22300B408100000), B(0x802802044600000, 0x1210100401030082), B(0x9400488010000000, 0x8005404902040000), B(0x2214020200001, 0x40102100820200), B(0x2022000000800000, 0x6400440108480), B(0x110000400100028, 0x24052304508004), B(0x848820140010000, 0x201012500A000), B(0x848820140010000, 0x201012500A000), B(0x100100000000C4, 0x208004084048201), B(0x100500000000290, 0x10102818208000), B(0x2800414000C000, 0x20004005001301), B(0x698180005101241, 0x10002014800210), B(0x20000080000009, 0x440340C040), B(0x1C0220200290020, 0x42100004004011C0), B(0x200E620018320208, 0x440410402), B(0xD04101010004024, 0x20000121104010A4), B(0x220400000A80040, 0x806080020810010C), B(0xA000200000000080, 0x1040801A0081208), }; constexpr Bitboard CannonDiagMagicInit[SQUARE_NB] = { B(0x811801000400, 0x312260280280202), B(0x44A000402022680, 0x1020224880420005), B(0x8000C80800200880, 0x2000810060080C0), B(0x2010300240428040, 0x40240002C004E30), B(0x1018010404010004, 0x1001010018081E0), B(0x2042040010080090, 0x100000008410300), B(0x400080020102000, 0x4500005300000000), B(0x2D00C80420010200, 0x804003280020008), B(0x8038820024420, 0x6010010080012040), B(0x1202028004200088, 0x50018100004000C6), B(0xA02010F0410081, 0x20013001000009A), B(0x4013002041030588, 0x4802004110000004), B(0x110020802000081, 0x202001800908002), B(0x22010404103, 0x2020882080491200), B(0x60000220400580, 0x85902800100100), B(0x100080800050100, 0x200010220021088), B(0x8088840404200080, 0x140011040104000), B(0x4008508080082015, 0x8010100200580048), B(0x4010400420201001, 0x260002080A80808), B(0xC2002004A0008008, 0x8020082000110840), B(0xA000A0820042400, 0x810408082100420), B(0x80231808100004, 0x204002000800400), B(0x8296144044004900, 0x4A1003008001840), B(0x80A0020A0011008, 0x800104846080810), B(0x803800801041000, 0x1030500102000404), B(0x240C00900800850, 0x1804000108810000), B(0x800400000088800, 0x800021801020000), B(0x84800409300082, 0x1002D40680044000), B(0xA110C0000200010, 0x401010001200260), B(0x8200160204100004, 0x8040004004002022), B(0x10001000000100C0, 0x84002811000200), B(0x2000080020014001, 0x42002020000102), B(0x109040044020018, 0x2020400202001000), B(0x620000CD0108, 0x40040201008000), B(0xA1402200A0020, 0x81400400300912), B(0x20020CF100018020, 0x801A14086404000), B(0x800801844001, 0x11621488425000), B(0x10201004A8080, 0x100A000801000010), B(0x2800411001000800, 0x80224084900020), B(0x40400024028100, 0x501000400230060), B(0x404808010080, 0x1201000400100004), B(0x80802005200, 0x2000200008A0000), B(0x20800080000022, 0x80040810002010), B(0x40016004808240, 0x400114000801100), B(0x8410004204240, 0x20011000604050), B(0x8000C1009008268, 0x201004000209000), B(0x10240C000920, 0xE000A5C14003002), B(0x10184024280008, 0x90240802000000), B(0x40889081081000, 0x8010050008800000), B(0x100008C089000019, 0x802032014020010), B(0x401C1804C00, 0x402501002002020), B(0x200022000920D0, 0x8000800081300020), B(0x801000400011, 0x400100044010226), B(0x4A04010100100000, 0x500400080400000), B(0xA000050200080000, 0x8500090001010000), B(0x40400040001812, 0x4403000400100A0), B(0x20C2250203020004, 0x210001C000080000), B(0x21000408C6020001, 0x4200830012D1001), B(0x840082016080A210, 0x2400080801081008), B(0x40001020000, 0x4041240200083120), B(0x2C04030010C0818, 0xA670002000818100), B(0x4704A07085000510, 0x914001000040), B(0x900210304100100, 0x1010004000281840), B(0x8202920002002040, 0x810012000003), B(0x4001400100050, 0x1144000408002000), B(0x5900200020008100, 0x40200020002004), B(0x301020002000480, 0x202000C0004), B(0x20D000201104040, 0x34840100020010), B(0x800004200080408, 0x40184200100240), B(0x8430080100404020, 0x90042100244500), B(0x3800100010220062, 0x50404030200218), B(0x42E20008002020, 0x2000008200200300), B(0xE488008280A004, 0x200001010CC80000), B(0x6018010041109810, 0x800002000242041A), B(0x40A8002438980, 0x8000810008208009), B(0x401000480040100, 0x286800404002212), B(0x821030000100009, 0x2000090200A00000), B(0x20000100C0008028, 0x5000000100400082), B(0x80A000048030080, 0x200000120200008), B(0x6300280800204003, 0x48000105C0040100), B(0x83008802420C0200, 0x2008020200080100), B(0x1050C3102200042, 0x20103900010008), B(0x8040902021408180, 0x12000021806200A4), B(0x3008204008C10004, 0x680110100010401), B(0x204321100421000, 0x400E204820494000), B(0x8000044022404048, 0x4024010090024021), B(0x140201424050, 0x280A000130008000), B(0x900340808004002, 0x21026008000380), B(0x82808000300444, 0x20002000A2001141), B(0x140180100406002, 0x4004480001000004), B(0x4808420800841900, 0x14008C0041000000), B(0x2008600009000480, 0x9008020001400000), B(0x2000100800100002, 0x2004100820210020), B(0x2062010401A8100, 0x12200108420090), B(0x1403188200032, 0x40048166105000), B(0x410020020140041, 0x4400348102940040), B(0x414040209208041, 0x4402400028B004), B(0x8008010100421202, 0x401418002008800), B(0x4000020010062200, 0xA02009148048000), B(0x4443080082008B, 0x104014022801010), B(0x42B440A0C000800, 0x9001009016111020), B(0x400000214002, 0x8008080209020009), B(0x480C414A001900, 0x3400100400210200), B(0x1006008800604, 0x20240004030A050), B(0x4C022401002A8300, 0x405008400000600), B(0x3104000800A1042, 0x2004800204406200), B(0xA09010280008200C, 0x4004000208C4168), B(0x2800401120C20120, 0x4A00450200022030), B(0x88001800304C0200, 0x204288102080000), B(0x8044004201440101, 0x400820080C024022), B(0xA000100C080, 0x4B40341004008081), B(0x94802001300810, 0x140206008000800), B(0x40002020202820, 0x280680404000040), B(0xA820800004200, 0x80E1401012000491), B(0x804000010020C000, 0x9403020200802000), B(0x8C0001284201400, 0xC000100C01620800), B(0x4010004002200414, 0x403080080200000), B(0x140400A100800101, 0x10054C031080400), B(0x20012C2400880082, 0x7000880020C03200), B(0x204040300004, 0x840800041101002), }; constexpr Bitboard NightriderMagicInit[SQUARE_NB] = { B(0x8008100800020052, 0x8001440000000000), B(0x24028400210090, 0x4200000000021), B(0x22002020A0200800, 0x100820120000082), B(0x424009020002200, 0x84810D4100002A), B(0x404008020000600, 0x1000202000000081), B(0x21020001000680, 0x2140200000000000), B(0x2060021100000608, 0x8080020040000C8), B(0x910C20408001200, 0x80A4030800000500), B(0x2C40100010400009, 0x40400000200000C), B(0x4090210034400004, 0x2104008000000), B(0x1082008040840180, 0x140082080000000F), B(0x8041001002101002, 0x8C002000000), B(0x102205086800080, 0x84400030020D600), B(0x2120080A32044E0, 0x400A02000016000), B(0x6040003021040A0, 0x8000200206040040), B(0x8001000008104082, 0x4002104202000114), B(0x100400081080000, 0x201020020000020), B(0x203000090010040, 0x140040010000000C), B(0x9140000108100040, 0x4004004C08450010), B(0x4402A12120020000, 0x450C002C000040C), B(0x2028000200120800, 0x100100401001000), B(0x800890081000, 0x10700A080180048), B(0x41040C4008400, 0x8080001000080020), B(0x2404000001060401, 0x10800116C0009110), B(0x2001810020400110, 0xA0100800000480E2), B(0x4081400120200218, 0x8800410800000000), B(0x4C020020300E00, 0x810800000000808), B(0xC8002200100108, 0x4860040380000100), B(0x8000E0000804080, 0x4C800000000000), B(0x211880010934041, 0x31000040000188), B(0x4800801000003140, 0x80000000028), B(0x61240410840004, 0x230100050000002), B(0x61240410840004, 0x230100050000002), B(0x4020008000C04C42, 0x3000021000000804), B(0x829808040040840, 0x288010000002200), B(0xA0420002040084, 0x1008604002000048), B(0x10200004980, 0x2010410028000000), B(0x4900200420100010, 0x1000A00401001898), B(0x20000801600134, 0xC0002400000021C), B(0x200040021010001, 0x8800000400000088), B(0x88000824, 0x8800000040010000), B(0x40000000200000D0, 0x8200004000AA84), B(0x1002000000812002, 0x4000001C1008480), B(0xC00010420003, 0x400002C00000004), B(0x208400008008040, 0x111004280000C0), B(0x60004000E800241, 0x202001000010040), B(0x200004800061, 0x1109100480045800), B(0x52880000200001, 0x608204100012DA0), B(0x1000429008100800, 0x2008001000006018), B(0x8490024200123001, 0x1041400080020), B(0x810001020200014, 0x2C50500220000020), B(0x20234B8000A0080, 0x4091200200000020), B(0x400401010012600, 0xE00200000000020), B(0x8040C00804004000, 0x8208014000000083), B(0x102000410001044, 0x8200000000000), B(0x102000410001044, 0x8200000000000), B(0x242E00508040001, 0x80028000000000), B(0x40810368002080, 0x4008080000224), B(0x48200010208800, 0x1020800C000022), B(0x220C4018008000, 0x282100130000211), B(0x8008004400100, 0xC128208200020400), B(0x44C004100020, 0x4008004000011000), B(0x1008000000600008, 0x41C00180480183), B(0x10080000, 0x404090018800020A), B(0x4500000000004200, 0x45200200A0140), B(0x2040000000240003, 0x8020080288000600), B(0x2040000000240003, 0x8020080288000600), B(0x2040000000240003, 0x8020080288000600), B(0x48000000020A0080, 0x1000020200088), B(0xA00000008404200, 0x4204010101001004), B(0x10008030082, 0x2002800200100104), B(0x44080002460010, 0x8007804100082D00), B(0x98400040001802, 0x913002002020301), B(0xA0400501040028, 0x8040481824002024), B(0x4C0A0600100080A0, 0x8005000080008200), B(0x802124000860008, 0x1009081800100080), B(0x4000110040011000, 0x4080422200001081), B(0x422008008000080, 0x4001020C00000202), B(0x8000202008002101, 0xA008010000040), B(0x804200020000020, 0xE0B0008010000001), B(0x14040200002, 0x2000000006000004), B(0x45000002010020, 0xA4040008400000), B(0x800040020204184A, 0x10000402000006), B(0x908000080400004, 0x5040100038400050), B(0x3100002000040014, 0x804008008120000), B(0x6002200000020008, 0xC011409081008881), B(0xC02004060280001, 0x2003004010220254), B(0x16410101, 0x100040108600000), B(0x100000000200000, 0x201200010800200), B(0xC400000004090002, 0x808808010224000A), B(0xC400000004090002, 0x808808010224000A), B(0x1000000000890001, 0x4050002001040), B(0x618000000008484, 0x40000A0150000D4), B(0x500000400009004, 0x1100200204100A1), B(0x6006000008080480, 0x100254800C00904), B(0x6006000008080480, 0x100254800C00904), B(0x404808040044040, 0x4202020040000200), B(0x2000080440810, 0x9044008844920000), B(0x40204418040, 0x412000204080010), B(0x88400820040248, 0x408040010C040800), B(0x8110084000803, 0x102000000000), B(0x4000040140001100, 0x80808102002), B(0x100480044020008, 0x4601000100084488), B(0x6200204420840, 0x41802043B0000), B(0x540000010091010, 0x409004800A01208), B(0x300040008000008, 0x2030080800010000), B(0x408804010400090, 0x4008100800004000), B(0x401600A0000080, 0x8400200201002200), B(0x44000000100000, 0x1140040210000001), B(0xC6000000D00804, 0x1050020C0081090C), B(0x4098002, 0x900000C045008300), B(0x245190008020, 0x2000C00100041000), B(0x1000000A100046, 0x88000084800004), B(0x1000000220240002, 0x400000401001088), B(0x1000000220240002, 0x400000401001088), B(0x820000118808000, 0x8000048161004A), B(0x1100000000071A02, 0x500146102000048), B(0x610000, 0x10010080040641), B(0x2000010441A0044, 0x500800502020188), B(0xA80000000180000, 0x234402012110080), }; constexpr Bitboard GrasshopperMagicHInit[SQUARE_NB] = { B(0x120000880110000, 0x1008000000020020), B(0x24200C080840A052, 0x2004004000010008), B(0xC030024000228800, 0x4000010400000020), B(0x1A0020802008802, 0x206010208000), B(0x12002000D001024, 0x80100800090138), B(0x4220010000241010, 0x3098000602001500), B(0x401010004801040, 0x8000280480100000), B(0x820082024921836, 0x220028000), B(0x100400502411400, 0x220402120240D14), B(0x880202020010404, 0xA80202510000), B(0x140002801000018, 0x1000346490040), B(0x120000880110000, 0x1008000000020020), B(0xD01004008030400, 0x104000408104420), B(0x8420060100020000, 0x800280400000120), B(0x4010020018010, 0x40A00001100000), B(0x40006A0004000200, 0x40000000110), B(0xD01004008030400, 0x104000408104420), B(0x8908A20028110011, 0x800080000001A114), B(0x200042000080F009, 0x20001000004000), B(0x2820008820100, 0x10002400058000B9), B(0x6083100420008050, 0x4040012600280080), B(0x216020000000446, 0x4080204000000211), B(0x340140003002089, 0x2402008000000911), B(0xD01004008030400, 0x104000408104420), B(0x1404040B20001000, 0x8000824010800011), B(0x8C0488120024214, 0x8414880202291), B(0x1010000060050000, 0x4000004050002602), B(0x4022983A0060000, 0x80000040010400), B(0x1404040B20001000, 0x8000824010800011), B(0x6020101802002840, 0x31000003000004), B(0x9000420008840, 0x4881300000000210), B(0xA200808865, 0x41C0048023000128), B(0x31801100400000, 0x8802DC001221240), B(0x884000080200920, 0x1004002410401001), B(0x2400040000884, 0x421006208040C0), B(0x1404040B20001000, 0x8000824010800011), B(0x24100400060009, 0x112008025042410), B(0x1800040009040200, 0x180000A1004E408A), B(0x24100400060009, 0x112008025042410), B(0x4060402008080, 0xC240080000110000), B(0x20080100920020, 0x2002248010242052), B(0x10001010802050, 0x880000001C98420), B(0x4000800100420022, 0x502022010A00D0), B(0x4C18104500200885, 0x400880800), B(0x8080810081020090, 0x8000000000000), B(0x8000062812080201, 0x8004C8300800), B(0xC010220920198, 0x85000A08000), B(0x24100400060009, 0x112008025042410), B(0x80102204040, 0x1000000900000000), B(0x2080000004202804, 0x120880003461), B(0x102004090A4030, 0x801020589240), B(0x20001100814000A0, 0x420202000820004), B(0x100800000A000120, 0x208000800010000), B(0x1008205000040802, 0x80002000400040), B(0x1480000098008401, 0xA0010000581010), B(0x30C0008200100820, 0x102800080904834), B(0x4810821884000500, 0x4400000200000212), B(0x1811D00128A0180, 0x2500848803000000), B(0x41618A0300040040, 0x21200200A421801), B(0x80102204040, 0x1000000900000000), B(0xA1808E0100108000, 0x2008000505000002), B(0x8C890020410000A0, 0xA010000048000400), B(0x40006002210044, 0x600008000408000), B(0x1200447220090042, 0x80001000160012), B(0x48410010AB000000, 0x9200600000000100), B(0x2040000000240003, 0x8020080288000600), B(0x9080000088848088, 0x4010210500000041), B(0xA1808E0100108000, 0x2008000505000002), B(0x480100400024, 0x1004800018200000), B(0x808403080080200, 0x802601000000500), B(0x8C890020410000A0, 0xA010000048000400), B(0xA1808E0100108000, 0x2008000505000002), B(0x100A40000004008, 0x2800200400200480), B(0x100A40000004008, 0x2800200400200480), B(0x400014006000000, 0x10006000810001F5), B(0xC410062001414, 0x820080041B01044), B(0x20000800310, 0x430040000201000), B(0xA40010008000008, 0x4002200028000040), B(0xC00102000008021C, 0x10C2000A010E024), B(0x80004200104008, 0x50A00800C400020), B(0x20200080012542, 0x910F0040000402C0), B(0xB040100504000300, 0x24802002000040), B(0x800001000014008, 0x400031004000), B(0x100A40000004008, 0x2800200400200480), B(0x84008002041081C0, 0x8080500200000000), B(0x440090001012001, 0x4020004010), B(0x100A0028088020, 0x80040E00010020), B(0x2180808000810, 0xB018040A00040000), B(0x40C80920304C4001, 0x42800B200800000), B(0x85000425001000, 0x4810048020001100), B(0x600C000801000004, 0x8015084010200020), B(0x20020050000240C0, 0x100202008600800), B(0x38000050001220, 0x9200010200145900), B(0x1042108040005, 0x1402A0802201001), B(0x824240000C20400, 0x1000000400080010), B(0x84008002041081C0, 0x8080500200000000), B(0x400804A1000008, 0x1024104A0200010), B(0x8000402308483, 0x20006020100100), B(0x80880120000080, 0x8000240100084), B(0x5840020004882001, 0x1004528000A00010), B(0x8001018800300002, 0x84010040804), B(0x180D10004000A008, 0xA001080008020004), B(0x400080B, 0x10A0000004010000), B(0x8080000200000, 0x2001000082004E0), B(0x40040001000C2000, 0x2024800001004008), B(0x400804A1000008, 0x1024104A0200010), B(0x8000402308483, 0x20006020100100), B(0x400804A1000008, 0x1024104A0200010), B(0x2000200000, 0x1201011000802), B(0x100100000000C4, 0x208004084048201), B(0x400084000044, 0x100810140300), B(0x29040C0C01010, 0x300204010820080), B(0x1A808000020200, 0x1000000005210040), B(0x20000400150000, 0x85008020), B(0x40C040008184014, 0x8002AA00024010), B(0x202000081B00804, 0x10001002008), B(0x40011000210060, 0x6080C40000021004), B(0x2000200000, 0x1201011000802), B(0x4100480203840, 0x300080100804), B(0x2000200000, 0x1201011000802), }; constexpr Bitboard GrasshopperMagicVInit[SQUARE_NB] = { B(0x202000812104400, 0x24800B01C0000303), B(0x340020400010D, 0x88060150C00400), B(0x400802040609, 0x49010200501A0002), B(0x8002680301000208, 0x628006C0C020200), B(0x20400209001C0804, 0xA044000800143110), B(0xC400082060010202, 0x4000480401014000), B(0x22500200144040, 0x8204820084704C00), B(0x8C1204009030020, 0x328400805000000), B(0x84800800D0001640, 0x200080040060108), B(0x804810208020040, 0x140010108020000), B(0x1102010B008004, 0x300208006220020), B(0x140080404A0A2428, 0x6308010100080), B(0x20444002120408, 0xA080010508010001), B(0x82011044000D02, 0x4112028620110809), B(0x81010831000C02, 0x408802085000000), B(0x81010831000C02, 0x408802085000000), B(0x920008920600040, 0x8053801004000028), B(0x81140283208300, 0x10040C004200420), B(0x103080022201, 0xC01000081312620), B(0x2200221100008, 0x1000408104000A4), B(0x4402088080042008, 0x210401501040340), B(0x898400202170001, 0x80040404208000), B(0x20080004051012, 0x5100048200081800), B(0x2320020000401018, 0x108501021040210), B(0x21080410A422021, 0x83040180008800), B(0x44E8100000408224, 0x20010008040400), B(0x1800240002810405, 0x23004820000020), B(0x80A0100400110, 0x80104020100C4028), B(0x1002050001222C0, 0x5100818004024020), B(0x104000200040, 0xC010A09800102000), B(0x1020003A058120, 0x450900809000302), B(0x40040045008B1, 0x202800400383010), B(0x4640200220034, 0x8800485420304000), B(0x5001042100084288, 0x110820001240080A), B(0x2002C04004010120, 0xA15008020880001), B(0x2800004080C4190, 0x890808280020080), B(0x40C0401000104000, 0x2020880008002580), B(0x40020C002400802, 0x801104010000000), B(0x44842000040080, 0x2050011084000400), B(0x4110040800000401, 0x2023810029008000), B(0x20884000840, 0x8017102004008000), B(0x10411104000480, 0x1414042000201001), B(0x220040000008, 0x800306021000000), B(0x41400A0008080, 0x501000298ACAD10), B(0x800240012831810, 0x80120004468050E), B(0x800005020801008, 0x20102400240000), B(0x20C00040C114C010, 0x88080820200C00), B(0x1044010100820081, 0x20080841004000), B(0x8041048400022, 0x8020836040005002), B(0x2001004010205, 0x8001002884042009), B(0x128088400087, 0x20008002201002), B(0x8084108040402000, 0x80809000A080400), B(0x408081840880, 0x201002088000040), B(0xA40180010280, 0x241004006000010), B(0x4204100080048140, 0x2002C4F104202020), B(0x100140A10204, 0x980200800840060), B(0x1005140010202048, 0x1442280800202815), B(0x2000082025008600, 0x1108400040600003), B(0x1005050648000, 0x200020240008002), B(0x202010208044000, 0x8210404060008), B(0x8011040402000210, 0xC840180408016004), B(0x404098801028, 0x80020A0001000400), B(0x404098801028, 0x80020A0001000400), B(0x80101002180140, 0x40C2080820000C0), B(0x208202081260800, 0x14090E4C04000050), B(0x4221201084004C2, 0x110480A011060), B(0x8000008421090204, 0x1C01010800024), B(0x8000008421090204, 0x1C01010800024), B(0x200180C840088A0, 0x401100400820000), B(0x10084043A021070, 0x202041600080200), B(0x210E6202001040C, 0x10100800080B0), B(0x848008021204002, 0x801004308100BAD), B(0xC082C0390A000601, 0x4040080189008), B(0x431200240210402D, 0x58102820000), B(0x202020100A0019B0, 0x4010C0D018000000), B(0x800800908402203, 0x102948C84C184), B(0x26801100080845, 0x4009702022A00820), B(0x8880520010401040, 0x1060084832052000), B(0x100100022042081, 0x10000600008C121), B(0x46020384100040, 0x800200320882021), B(0xC0002010148, 0x4200800800040003), B(0x2002208020090040, 0x40820210021410), B(0x9000A41160002004, 0x2A09000100080043), B(0x800004010008001, 0x1108002020104600), B(0x800540C000A4E041, 0x18021180000401), B(0x808200900A900202, 0x8364202140012005), B(0x1DBA52000081010, 0x4008000023000010), B(0x4100110204401481, 0x800040091020001C), B(0x4100110204401481, 0x800040091020001C), B(0x4101100020400482, 0x2000402302100120), B(0x100408000A020212, 0xA000400111000020), B(0x2000010488080104, 0x3000404410208100), B(0x2684220180008DD0, 0x422040200004000A), B(0x2021200C0424, 0x1010100000080200), B(0x8908020020801006, 0x3010800020C2000), B(0x4000030008062044, 0x244010202688000), B(0x242101200408009, 0x8150040000200015), B(0x42004C02180204, 0x210208014241040), B(0x4E1A01C208410804, 0x8890041000012004), B(0x2080200401000080, 0x8001098429008004), B(0xA01400121804104, 0x280200C400000500), B(0xD0080408040420, 0x1006040100224000), B(0x28400205000800C9, 0x6021101401040075), B(0x4000900040020104, 0x88129801100D0C), B(0x8000004002180410, 0x400380200400204), B(0x4002A430043008, 0x400200340100020), B(0x401960004140A42, 0x100880710000464), B(0x58014090102, 0xB8D30004010080), B(0xA004C08000244000, 0x11280100E0000040), B(0x2102008089208804, 0x110001004080040), B(0x700010084E003004, 0x8080864112000D40), B(0x4080881000200C20, 0x30324040880E0600), B(0x2024A40401810820, 0x3000888002000000), B(0x8200100400014, 0x4400340800252844), B(0x24A00804288281, 0x410103002201140), B(0x4080005022A08, 0x1000402200100264), B(0x200080032244040, 0x200502189010001), B(0x28108110404001, 0x400600120008412), B(0xA00002102810020, 0xB1080240015408), B(0x810080200806, 0x410440804080046), }; constexpr Bitboard GrasshopperMagicDInit[SQUARE_NB] = { B(0x811801000400, 0x312260280280202), B(0x44A000402022680, 0x1020224880420005), B(0x8000C80800200880, 0x2000810060080C0), B(0x2010300240428040, 0x40240002C004E30), B(0x1018010404010004, 0x1001010018081E0), B(0x2042040010080090, 0x100000008410300), B(0x400080020102000, 0x4500005300000000), B(0x2D00C80420010200, 0x804003280020008), B(0x8038820024420, 0x6010010080012040), B(0x1202028004200088, 0x50018100004000C6), B(0xA02010F0410081, 0x20013001000009A), B(0x4013002041030588, 0x4802004110000004), B(0x110020802000081, 0x202001800908002), B(0x22010404103, 0x2020882080491200), B(0x60000220400580, 0x85902800100100), B(0x100080800050100, 0x200010220021088), B(0x8088840404200080, 0x140011040104000), B(0x4008508080082015, 0x8010100200580048), B(0x4010400420201001, 0x260002080A80808), B(0xC2002004A0008008, 0x8020082000110840), B(0xA000A0820042400, 0x810408082100420), B(0x80231808100004, 0x204002000800400), B(0x8296144044004900, 0x4A1003008001840), B(0x80A0020A0011008, 0x800104846080810), B(0x803800801041000, 0x1030500102000404), B(0x240C00900800850, 0x1804000108810000), B(0x800400000088800, 0x800021801020000), B(0x84800409300082, 0x1002D40680044000), B(0xA110C0000200010, 0x401010001200260), B(0x8200160204100004, 0x8040004004002022), B(0x10001000000100C0, 0x84002811000200), B(0x2000080020014001, 0x42002020000102), B(0x109040044020018, 0x2020400202001000), B(0x620000CD0108, 0x40040201008000), B(0xA1402200A0020, 0x81400400300912), B(0x20020CF100018020, 0x801A14086404000), B(0x800801844001, 0x11621488425000), B(0x10201004A8080, 0x100A000801000010), B(0x2800411001000800, 0x80224084900020), B(0x40400024028100, 0x501000400230060), B(0x404808010080, 0x1201000400100004), B(0x80802005200, 0x2000200008A0000), B(0x20800080000022, 0x80040810002010), B(0x40016004808240, 0x400114000801100), B(0x8410004204240, 0x20011000604050), B(0x8000C1009008268, 0x201004000209000), B(0x10240C000920, 0xE000A5C14003002), B(0x10184024280008, 0x90240802000000), B(0x40889081081000, 0x8010050008800000), B(0x100008C089000019, 0x802032014020010), B(0x401C1804C00, 0x402501002002020), B(0x200022000920D0, 0x8000800081300020), B(0x801000400011, 0x400100044010226), B(0x4A04010100100000, 0x500400080400000), B(0xA000050200080000, 0x8500090001010000), B(0x40400040001812, 0x4403000400100A0), B(0x20C2250203020004, 0x210001C000080000), B(0x21000408C6020001, 0x4200830012D1001), B(0x840082016080A210, 0x2400080801081008), B(0x40001020000, 0x4041240200083120), B(0x2C04030010C0818, 0xA670002000818100), B(0x4704A07085000510, 0x914001000040), B(0x900210304100100, 0x1010004000281840), B(0x8202920002002040, 0x810012000003), B(0x4001400100050, 0x1144000408002000), B(0x5900200020008100, 0x40200020002004), B(0x301020002000480, 0x202000C0004), B(0x20D000201104040, 0x34840100020010), B(0x800004200080408, 0x40184200100240), B(0x8430080100404020, 0x90042100244500), B(0x3800100010220062, 0x50404030200218), B(0x42E20008002020, 0x2000008200200300), B(0xE488008280A004, 0x200001010CC80000), B(0x6018010041109810, 0x800002000242041A), B(0x40A8002438980, 0x8000810008208009), B(0x401000480040100, 0x286800404002212), B(0x821030000100009, 0x2000090200A00000), B(0x20000100C0008028, 0x5000000100400082), B(0x80A000048030080, 0x200000120200008), B(0x6300280800204003, 0x48000105C0040100), B(0x83008802420C0200, 0x2008020200080100), B(0x1050C3102200042, 0x20103900010008), B(0x8040902021408180, 0x12000021806200A4), B(0x3008204008C10004, 0x680110100010401), B(0x204321100421000, 0x400E204820494000), B(0x8000044022404048, 0x4024010090024021), B(0x140201424050, 0x280A000130008000), B(0x900340808004002, 0x21026008000380), B(0x82808000300444, 0x20002000A2001141), B(0x140180100406002, 0x4004480001000004), B(0x4808420800841900, 0x14008C0041000000), B(0x2008600009000480, 0x9008020001400000), B(0x2000100800100002, 0x2004100820210020), B(0x2062010401A8100, 0x12200108420090), B(0x1403188200032, 0x40048166105000), B(0x410020020140041, 0x4400348102940040), B(0x414040209208041, 0x4402400028B004), B(0x8008010100421202, 0x401418002008800), B(0x4000020010062200, 0xA02009148048000), B(0x4443080082008B, 0x104014022801010), B(0x42B440A0C000800, 0x9001009016111020), B(0x400000214002, 0x8008080209020009), B(0x480C414A001900, 0x3400100400210200), B(0x1006008800604, 0x20240004030A050), B(0x4C022401002A8300, 0x405008400000600), B(0x3104000800A1042, 0x2004800204406200), B(0xA09010280008200C, 0x4004000208C4168), B(0x2800401120C20120, 0x4A00450200022030), B(0x88001800304C0200, 0x204288102080000), B(0x8044004201440101, 0x400820080C024022), B(0xA000100C080, 0x4B40341004008081), B(0x94802001300810, 0x140206008000800), B(0x40002020202820, 0x280680404000040), B(0xA820800004200, 0x80E1401012000491), B(0x804000010020C000, 0x9403020200802000), B(0x8C0001284201400, 0xC000100C01620800), B(0x4010004002200414, 0x403080080200000), B(0x140400A100800101, 0x10054C031080400), B(0x20012C2400880082, 0x7000880020C03200), B(0x204040300004, 0x840800041101002), }; #undef B #endif } // namespace Stockfish #endif // #ifndef MAGIC_H_INCLUDEDFairy-Stockfish-fairy_sf_14_0_1_xq/src/main.cpp000066400000000000000000000031531414571233100215600ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include "bitboard.h" #include "endgame.h" #include "position.h" #include "psqt.h" #include "search.h" #include "syzygy/tbprobe.h" #include "thread.h" #include "tt.h" #include "uci.h" #include "piece.h" #include "variant.h" #include "xboard.h" using namespace Stockfish; int main(int argc, char* argv[]) { std::cout << engine_info() << std::endl; pieceMap.init(); variants.init(); CommandLine::init(argc, argv); UCI::init(Options); Tune::init(); PSQT::init(variants.find(Options["UCI_Variant"])->second); Bitboards::init(); Position::init(); Bitbases::init(); Endgames::init(); Threads.set(size_t(Options["Threads"])); Search::clear(); // After threads are up Eval::NNUE::init(); UCI::loop(argc, argv); Threads.set(0); variants.clear_all(); pieceMap.clear_all(); delete XBoard::stateMachine; return 0; } Fairy-Stockfish-fairy_sf_14_0_1_xq/src/material.cpp000066400000000000000000000256411414571233100224400ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include // For std::memset #include "material.h" #include "thread.h" using namespace std; namespace Stockfish { namespace { #define S(mg, eg) make_score(mg, eg) // Polynomial material imbalance parameters // One Score parameter for each pair (our piece, another of our pieces) constexpr Score QuadraticOurs[][PIECE_TYPE_NB] = { // OUR PIECE 2 // bishop pair pawn knight bishop rook queen {S(1419, 1455) }, // Bishop pair {S( 101, 28), S( 37, 39) }, // Pawn {S( 57, 64), S(249, 187), S(-49, -62) }, // Knight OUR PIECE 1 {S( 0, 0), S(118, 137), S( 10, 27), S( 0, 0) }, // Bishop {S( -63, -68), S( -5, 3), S(100, 81), S(132, 118), S(-246, -244) }, // Rook {S(-210, -211), S( 37, 14), S(147, 141), S(161, 105), S(-158, -174), S(-9,-31) } // Queen }; // One Score parameter for each pair (our piece, their piece) constexpr Score QuadraticTheirs[][PIECE_TYPE_NB] = { // THEIR PIECE // bishop pair pawn knight bishop rook queen { }, // Bishop pair {S( 33, 30) }, // Pawn {S( 46, 18), S(106, 84) }, // Knight OUR PIECE {S( 75, 35), S( 59, 44), S( 60, 15) }, // Bishop {S( 26, 35), S( 6, 22), S( 38, 39), S(-12, -2) }, // Rook {S( 97, 93), S(100, 163), S(-58, -91), S(112, 192), S(276, 225) } // Queen }; #undef S // Endgame evaluation and scaling functions are accessed directly and not through // the function maps because they correspond to more than one material hash key. Endgame EvaluateKFsPsK[] = { Endgame(WHITE), Endgame(BLACK) }; Endgame EvaluateKXK[] = { Endgame(WHITE), Endgame(BLACK) }; Endgame ScaleKBPsK[] = { Endgame(WHITE), Endgame(BLACK) }; Endgame ScaleKQKRPs[] = { Endgame(WHITE), Endgame(BLACK) }; Endgame ScaleKPsK[] = { Endgame(WHITE), Endgame(BLACK) }; Endgame ScaleKPKP[] = { Endgame(WHITE), Endgame(BLACK) }; // Helper used to detect a given material distribution bool is_KFsPsK(const Position& pos, Color us) { return pos.promotion_piece_types().size() == 1 && pos.promotion_piece_types().find(FERS) != pos.promotion_piece_types().end() && !more_than_one(pos.pieces(~us)) && (pos.count(us) || pos.count(us)) && !(pos.count(us) - pos.count(us) - pos.count(us) - pos.count(us)); } bool is_KXK(const Position& pos, Color us) { return !more_than_one(pos.pieces(~us)) && pos.non_pawn_material(us) >= std::min(RookValueMg, 2 * SilverValueMg); } bool is_KBPsK(const Position& pos, Color us) { return pos.non_pawn_material(us) == BishopValueMg && pos.count(us) >= 1; } bool is_KQKRPs(const Position& pos, Color us) { return !pos.count(us) && pos.non_pawn_material(us) == QueenValueMg && pos.count(~us) == 1 && pos.count(~us) >= 1; } /// imbalance() calculates the imbalance by comparing the piece count of each /// piece type for both colors. template Score imbalance(const Position& pos, const int pieceCount[][PIECE_TYPE_NB]) { constexpr Color Them = ~Us; Score bonus = SCORE_ZERO; // Second-degree polynomial material imbalance, by Tord Romstad for (int pt1 = NO_PIECE_TYPE; pt1 <= QUEEN; ++pt1) { if (!pieceCount[Us][pt1]) continue; int v = QuadraticOurs[pt1][pt1] * pieceCount[Us][pt1]; for (int pt2 = NO_PIECE_TYPE; pt2 < pt1; ++pt2) v += QuadraticOurs[pt1][pt2] * pieceCount[Us][pt2] + QuadraticTheirs[pt1][pt2] * pieceCount[Them][pt2]; bonus += pieceCount[Us][pt1] * v; } if (pos.must_capture()) bonus += (make_score(mg_value(QuadraticOurs[PAWN][PAWN]), 0) * pieceCount[Us][PAWN] + 3 * QuadraticOurs[KNIGHT][PAWN] * pieceCount[Us][KNIGHT] + QuadraticOurs[QUEEN][PAWN] * pieceCount[Us][QUEEN]) * pieceCount[Us][PAWN] + make_score( mg_value(QuadraticOurs[KNIGHT][KNIGHT]), -eg_value(QuadraticOurs[KNIGHT][KNIGHT])) * pieceCount[Us][KNIGHT] * pieceCount[Us][KNIGHT]; else if (pos.blast_on_capture()) bonus -= make_score(mg_value(QuadraticOurs[KNIGHT][PAWN]) * pieceCount[Us][KNIGHT] * pieceCount[Us][PAWN] / 2, 0); else if (pos.check_counting()) bonus -= 2 * QuadraticOurs[PAWN][PAWN] * pieceCount[Us][PAWN] * pieceCount[Us][PAWN]; else if (pos.captures_to_hand()) bonus -= bonus / 10; 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] if (pos.captures_to_hand() || pos.two_boards()) { Value npm2 = VALUE_ZERO; for (PieceType pt : pos.piece_types()) npm2 += pos.count_in_hand(pt) * PieceValue[MG][make_piece(WHITE, pt)]; e->gamePhase = Phase(PHASE_MIDGAME * npm / std::max(int(npm + npm2), 1)); int countAll = pos.count_with_hand(WHITE, ALL_PIECES) + pos.count_with_hand(BLACK, ALL_PIECES); e->materialDensity = (npm + npm2 + pos.count() * PawnValueMg) * countAll / (pos.files() * pos.ranks()); } else e->gamePhase = Phase(((npm - EndgameLimit) * PHASE_MIDGAME) / (MidgameLimit - EndgameLimit)); if (pos.endgame_eval()) { // Let's look if we have a specialized evaluation function for this particular // material configuration. Firstly we look for a fixed configuration one, then // for a generic one if the previous search failed. if ((e->evaluationFunction = Endgames::probe(key)) != nullptr) return e; for (Color c : { WHITE, BLACK }) if (is_KFsPsK(pos, c)) { e->evaluationFunction = &EvaluateKFsPsK[c]; return e; } for (Color c : { WHITE, BLACK }) if (is_KXK(pos, c)) { e->evaluationFunction = &EvaluateKXK[c]; return e; } // OK, we didn't find any special evaluation function for the current material // configuration. Is there a suitable specialized scaling function? const auto* sf = Endgames::probe(key); if (sf) { e->scalingFunction[sf->strongSide] = sf; // Only strong color assigned return e; } // We didn't find any specialized scaling function, so fall back on generic // ones that refer to more than one material distribution. Note that in this // case we don't return after setting the function. for (Color c : { WHITE, BLACK }) { if (is_KBPsK(pos, c)) e->scalingFunction[c] = &ScaleKBPsK[c]; else if (is_KQKRPs(pos, c)) e->scalingFunction[c] = &ScaleKQKRPs[c]; } if (npm_w + npm_b == VALUE_ZERO && pos.pieces(PAWN)) // Only pawns on the board { if (!pos.count(BLACK)) { assert(pos.count(WHITE) >= 2); e->scalingFunction[WHITE] = &ScaleKPsK[WHITE]; } else if (!pos.count(WHITE)) { assert(pos.count(BLACK) >= 2); e->scalingFunction[BLACK] = &ScaleKPsK[BLACK]; } else if (pos.count(WHITE) == 1 && pos.count(BLACK) == 1) { // This is a special case because we set scaling functions // for both colors instead of only one. e->scalingFunction[WHITE] = &ScaleKPKP[WHITE]; e->scalingFunction[BLACK] = &ScaleKPKP[BLACK]; } } // Zero or just one pawn makes it difficult to win, even with a small material // advantage. This catches some trivial draws like KK, KBK and KNK and gives a // drawish scale factor for cases such as KRKBP and KmmKm (except for KBBKN). if (!pos.count(WHITE) && npm_w - npm_b <= BishopValueMg) e->factor[WHITE] = uint8_t(npm_w < RookValueMg && pos.count(WHITE) <= 2 ? SCALE_FACTOR_DRAW : npm_b <= BishopValueMg && pos.count(WHITE) <= 3 ? 4 : 14); if (!pos.count(BLACK) && npm_b - npm_w <= BishopValueMg) e->factor[BLACK] = uint8_t(npm_b < RookValueMg && pos.count(BLACK) <= 2 ? SCALE_FACTOR_DRAW : npm_w <= BishopValueMg && pos.count(BLACK) <= 3 ? 4 : 14); } // Evaluate the material imbalance. We use PIECE_TYPE_NONE as a place holder // for the bishop pair "extended piece", which allows us to be more flexible // in defining bishop pair bonuses. const int pieceCount[COLOR_NB][PIECE_TYPE_NB] = { { pos.count(WHITE) > 1, pos.count(WHITE), pos.count(WHITE), pos.count(WHITE) , pos.count(WHITE), pos.count(WHITE) }, { pos.count(BLACK) > 1, pos.count(BLACK), pos.count(BLACK), pos.count(BLACK) , pos.count(BLACK), pos.count(BLACK) } }; e->score = (imbalance(pos, pieceCount) - imbalance(pos, pieceCount)) / 16; return e; } } // namespace Material } // namespace Stockfish Fairy-Stockfish-fairy_sf_14_0_1_xq/src/material.h000066400000000000000000000055741414571233100221100ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef MATERIAL_H_INCLUDED #define MATERIAL_H_INCLUDED #include "endgame.h" #include "misc.h" #include "position.h" #include "types.h" namespace Stockfish::Material { /// Material::Entry contains various information about a material configuration. /// It contains a material imbalance evaluation, a function pointer to a special /// endgame evaluation function (which in most cases is 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 score; } Phase game_phase() const { return (Phase)gamePhase; } bool specialized_eval_exists() const { return evaluationFunction != nullptr; } Value evaluate(const Position& pos) const { return (*evaluationFunction)(pos); } int material_density() const { return materialDensity; } // 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* evaluationFunction; const EndgameBase* scalingFunction[COLOR_NB]; // Could be one for each // side (e.g. KPKP, KBPsK) Score score; int16_t gamePhase; uint8_t factor[COLOR_NB]; int materialDensity; }; typedef HashTable Table; Entry* probe(const Position& pos); } // namespace Stockfish::Material #endif // #ifndef MATERIAL_H_INCLUDED Fairy-Stockfish-fairy_sf_14_0_1_xq/src/misc.cpp000066400000000000000000000426321414571233100215740ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifdef _WIN32 #if _WIN32_WINNT < 0x0601 #undef _WIN32_WINNT #define _WIN32_WINNT 0x0601 // Force to include needed API prototypes #endif #ifndef NOMINMAX #define NOMINMAX #endif #include // The needed Windows API for processor groups could be missed from old Windows // versions, so instead of calling them directly (forcing the linker to resolve // the calls at compile time), try to load them at runtime. To do this we need // first to define the corresponding function pointers. extern "C" { 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 #include #include #include #include #include #if defined(__linux__) && !defined(__ANDROID__) #include #include #endif #if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__) || (defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC) && !defined(_WIN32)) || defined(__e2k__) #define POSIXALIGNEDALLOC #include #endif #include "misc.h" #include "thread.h" using namespace std; namespace Stockfish { namespace { /// Version number. If Version is left empty, then compile date in the format /// DD-MM-YY and show in engine_info. const string Version = "14.0.1"; /// 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 DD-MM-YY" (where DD-MM-YY is the date when /// the program was compiled) or "Stockfish ", depending on whether /// Version is empty. string engine_info(bool to_uci, bool to_xboard) { 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 << "Fairy-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); } #ifdef LARGEBOARDS ss << " XQ"; #endif if (!to_xboard) ss << (to_uci ? "\nid author ": " by ") << "Fabian Fichter"; return ss.str(); } /// compiler_info() returns a string trying to describe the compiler we use 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 defined(__e2k__) && defined(__LCC__) #define dot_ver2(n) \ compiler += (char)'.'; \ compiler += (char)('0' + (n) / 10); \ compiler += (char)('0' + (n) % 10); compiler += "MCST LCC "; compiler += "(version "; compiler += std::to_string(__LCC__ / 100); dot_ver2(__LCC__ % 100) dot_ver2(__LCC_MINOR__) compiler += ")"; #elif __GNUC__ compiler += "g++ (GNUC) "; compiler += make_version_string(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__); #else compiler += "Unknown compiler "; compiler += "(unknown version)"; #endif #if defined(__APPLE__) compiler += " on Apple"; #elif defined(__CYGWIN__) compiler += " on Cygwin"; #elif defined(__MINGW64__) compiler += " on MinGW64"; #elif defined(__MINGW32__) compiler += " on MinGW32"; #elif defined(__ANDROID__) compiler += " on Android"; #elif defined(__linux__) compiler += " on Linux"; #elif defined(_WIN64) compiler += " on Microsoft Windows 64-bit"; #elif defined(_WIN32) compiler += " on Microsoft Windows 32-bit"; #else compiler += " on unknown system"; #endif compiler += "\nCompilation settings include: "; compiler += (Is64Bit ? " 64bit" : " 32bit"); #if defined(USE_VNNI) compiler += " VNNI"; #endif #if defined(USE_AVX512) compiler += " AVX512"; #endif compiler += (HasPext ? " BMI2" : ""); #if defined(USE_AVX2) compiler += " AVX2"; #endif #if defined(USE_SSE41) compiler += " SSE41"; #endif #if defined(USE_SSSE3) compiler += " SSSE3"; #endif #if defined(USE_SSE2) compiler += " SSE2"; #endif compiler += (HasPopCnt ? " POPCNT" : ""); #if defined(USE_MMX) compiler += " MMX"; #endif #if defined(USE_NEON) compiler += " NEON"; #endif #if !defined(NDEBUG) compiler += " DEBUG"; #endif compiler += "\n__VERSION__ macro expands to: "; #ifdef __VERSION__ compiler += __VERSION__; #else compiler += "(undefined macro)"; #endif compiler += "\n"; return compiler; } /// Debug functions used mainly to collect run-time statistics static std::atomic 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 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_large_pages_alloc() will return suitably aligned memory, if possible using large pages. #if defined(_WIN32) static void* aligned_large_pages_alloc_windows(size_t allocSize) { #if !defined(_WIN64) return nullptr; #else HANDLE hProcessToken { }; LUID luid { }; void* mem = nullptr; const size_t largePageSize = GetLargePageMinimum(); if (!largePageSize) return nullptr; // 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; #endif } void* aligned_large_pages_alloc(size_t allocSize) { // Try to allocate large pages void* mem = aligned_large_pages_alloc_windows(allocSize); // Fall back to regular, page aligned, allocation if necessary if (!mem) mem = VirtualAlloc(NULL, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); return mem; } #else void* aligned_large_pages_alloc(size_t allocSize) { #if defined(__linux__) constexpr size_t alignment = 2 * 1024 * 1024; // assumed 2MB page size #else constexpr size_t alignment = 4096; // assumed small page size #endif // round up to multiples of alignment size_t size = ((allocSize + alignment - 1) / alignment) * alignment; void *mem = std_aligned_alloc(alignment, size); #if defined(MADV_HUGEPAGE) madvise(mem, size, MADV_HUGEPAGE); #endif return mem; } #endif /// aligned_large_pages_free() will free the previously allocated ttmem #if defined(_WIN32) void aligned_large_pages_free(void* mem) { if (mem && !VirtualFree(mem, 0, MEM_RELEASE)) { DWORD err = GetLastError(); std::cerr << "Failed to free large page memory. Error code: 0x" << std::hex << err << std::dec << std::endl; exit(EXIT_FAILURE); } } #else void aligned_large_pages_free(void *mem) { std_aligned_free(mem); } #endif namespace WinProcGroup { #ifndef _WIN32 void bindThisThread(size_t) {} #else /// best_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 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 #define GETCWD _getcwd #else #include #define GETCWD getcwd #endif namespace CommandLine { string argv0; // path+name of the executable binary, as given by argv[0] string binaryDirectory; // path of the executable directory string workingDirectory; // path of the working directory void init(int argc, char* argv[]) { (void)argc; string pathSeparator; // extract the path+name of the executable binary argv0 = argv[0]; #ifdef _WIN32 pathSeparator = "\\"; #ifdef _MSC_VER // Under windows argv[0] may not have the extension. Also _get_pgmptr() had // issues in some windows 10 versions, so check returned values carefully. char* pgmptr = nullptr; if (!_get_pgmptr(&pgmptr) && pgmptr != nullptr && *pgmptr) argv0 = pgmptr; #endif #else pathSeparator = "/"; #endif // extract the working directory workingDirectory = ""; char buff[40000]; char* cwd = GETCWD(buff, 40000); if (cwd) workingDirectory = cwd; // extract the binary directory path from argv0 binaryDirectory = argv0; size_t pos = binaryDirectory.find_last_of("\\/"); if (pos == std::string::npos) binaryDirectory = "." + pathSeparator; else binaryDirectory.resize(pos + 1); // pattern replacement: "./" at the start of path is replaced by the working directory if (binaryDirectory.find("." + pathSeparator) == 0) binaryDirectory.replace(0, 1, workingDirectory); } } // namespace CommandLine } // namespace Stockfish Fairy-Stockfish-fairy_sf_14_0_1_xq/src/misc.h000066400000000000000000000141751414571233100212420ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef MISC_H_INCLUDED #define MISC_H_INCLUDED #include #include #include #include #include #include #include "types.h" namespace Stockfish { std::string engine_info(bool to_uci = false, bool to_xboard = false); std::string compiler_info(); void prefetch(void* addr); void start_logger(const std::string& fname); void* std_aligned_alloc(size_t alignment, size_t size); void std_aligned_free(void* ptr); void* aligned_large_pages_alloc(size_t size); // memory aligned by page size, min alignment: 4096 bytes void aligned_large_pages_free(void* mem); // nop if mem == nullptr void dbg_hit_on(bool 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::steady_clock::now().time_since_epoch()).count(); } template struct HashTable { Entry* operator[](Key key) { return &table[(uint32_t)key & (Size - 1)]; } private: std::vector table = std::vector(Size); // Allocate on the heap }; enum SyncCout { IO_LOCK, IO_UNLOCK }; std::ostream& operator<<(std::ostream&, SyncCout); #define sync_cout std::cout << IO_LOCK #define sync_endl std::endl << IO_UNLOCK // align_ptr_up() : get the first aligned element of an array. // ptr must point to an array of size at least `sizeof(T) * N + alignment` bytes, // where N is the number of elements in the array. template T* align_ptr_up(T* ptr) { static_assert(alignof(T) < Alignment); const uintptr_t ptrint = reinterpret_cast(reinterpret_cast(ptr)); return reinterpret_cast(reinterpret_cast((ptrint + (Alignment - 1)) / Alignment * Alignment)); } // IsLittleEndian : true if and only if the binary is compiled on a little endian machine static inline const union { uint32_t i; char c[4]; } Le = { 0x01020304 }; static inline const bool IsLittleEndian = (Le.c[0] == 4); template class ValueListInserter { public: ValueListInserter(T* v, std::size_t& s) : values(v), size(&s) { } void push_back(const T& value) { values[(*size)++] = value; } private: T* values; std::size_t* size; }; template class ValueList { public: std::size_t size() const { return size_; } void resize(std::size_t newSize) { size_ = newSize; } 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_; } operator ValueListInserter() { return ValueListInserter(values_, size_); } void swap(ValueList& other) { const std::size_t maxSize = std::max(size_, other.size_); for (std::size_t i = 0; i < maxSize; ++i) { std::swap(values_[i], other.values_[i]); } std::swap(size_, other.size_); } private: T values_[MaxSize]; std::size_t size_ = 0; }; /// xorshift64star Pseudo-Random Number Generator /// This class is based on original code written and dedicated /// to the public domain by Sebastiano Vigna (2014). /// It has the following characteristics: /// /// - Outputs 64-bit numbers /// - Passes Dieharder and SmallCrush test batteries /// - Does not require warm-up, no zeroland to escape /// - Internal state is a single 64-bit integer /// - Period is 2^64 - 1 /// - Speed: 1.60 ns/call (Core i7 @3.40GHz) /// /// For further analysis see /// class PRNG { uint64_t s; uint64_t rand64() { s ^= s >> 12, s ^= s << 25, s ^= s >> 27; return s * 2685821657736338717LL; } public: PRNG(uint64_t seed) : s(seed) { assert(seed); } template T rand() { return T(rand64()); } /// Special generator used to fast init magic numbers. /// Output values only have 1/8th of their bits set on average. template T sparse_rand() { return T(rand64() & rand64() & rand64()); } }; inline uint64_t mul_hi64(uint64_t a, uint64_t b) { #if defined(__GNUC__) && defined(IS_64BIT) __extension__ 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 } } // namespace Stockfish #endif // #ifndef MISC_H_INCLUDED Fairy-Stockfish-fairy_sf_14_0_1_xq/src/movegen.cpp000066400000000000000000000427771414571233100223130ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include "movegen.h" #include "position.h" namespace Stockfish { namespace { template ExtMove* make_move_and_gating(const Position& pos, ExtMove* moveList, Color us, Square from, Square to) { // Arrow gating moves if (pos.arrow_gating()) { for (PieceType pt_gating : pos.piece_types()) if (pos.count_in_hand(us, pt_gating) > 0) { Bitboard b = pos.drop_region(us, pt_gating) & moves_bb(us, type_of(pos.piece_on(from)), to, pos.pieces() ^ from) & ~(pos.pieces() ^ from); while (b) *moveList++ = make_gating(from, to, pt_gating, pop_lsb(b)); } return moveList; } *moveList++ = make(from, to); // Gating moves if (pos.seirawan_gating() && (pos.gates(us) & from)) for (PieceType pt_gating : pos.piece_types()) if (pos.count_in_hand(us, pt_gating) > 0 && (pos.drop_region(us, pt_gating) & from)) *moveList++ = make_gating(from, to, pt_gating, from); if (pos.seirawan_gating() && T == CASTLING && (pos.gates(us) & to)) for (PieceType pt_gating : pos.piece_types()) if (pos.count_in_hand(us, pt_gating) > 0 && (pos.drop_region(us, pt_gating) & to)) *moveList++ = make_gating(from, to, pt_gating, to); return moveList; } template ExtMove* make_promotions(const Position& pos, ExtMove* moveList, Square to) { if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) { for (PieceType pt : pos.promotion_piece_types()) if (!pos.promotion_limit(pt) || pos.promotion_limit(pt) > pos.count(c, pt)) *moveList++ = make(to - D, to, pt); PieceType pt = pos.promoted_piece_type(PAWN); if (pt && !(pos.piece_promotion_on_capture() && pos.empty(to))) *moveList++ = make(to - D, to); } return moveList; } template ExtMove* generate_drops(const Position& pos, ExtMove* moveList, PieceType pt, Bitboard b) { assert(Type != CAPTURES); // Do not generate virtual drops for perft and at root if (pos.count_in_hand(Us, pt) > 0 || (Type != NON_EVASIONS && pos.two_boards() && pos.allow_virtual_drop(Us, pt))) { // Restrict to valid target b &= pos.drop_region(Us, pt); // Add to move list if (pos.drop_promoted() && pos.promoted_piece_type(pt)) { Bitboard b2 = b; if (Type == QUIET_CHECKS) b2 &= pos.check_squares(pos.promoted_piece_type(pt)); while (b2) *moveList++ = make_drop(pop_lsb(b2), pt, pos.promoted_piece_type(pt)); } if (Type == QUIET_CHECKS || pos.count_in_hand(Us, pt) <= 0) b &= pos.check_squares(pt); while (b) *moveList++ = make_drop(pop_lsb(b), pt, pt); } return moveList; } template ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard target) { constexpr Color Them = ~Us; constexpr Direction Up = pawn_push(Us); constexpr Direction Down = -pawn_push(Us); constexpr Direction UpRight = (Us == WHITE ? NORTH_EAST : SOUTH_WEST); constexpr Direction UpLeft = (Us == WHITE ? NORTH_WEST : SOUTH_EAST); Bitboard TRank8BB = pos.mandatory_pawn_promotion() ? rank_bb(relative_rank(Us, pos.promotion_rank(), pos.max_rank())) : zone_bb(Us, pos.promotion_rank(), pos.max_rank()); Bitboard TRank7BB = shift(TRank8BB); // Define squares a pawn can pass during a double step Bitboard TRank3BB = forward_ranks_bb(Us, relative_rank(Us, pos.double_step_rank_min(), pos.max_rank())) & ~shift(forward_ranks_bb(Us, relative_rank(Us, pos.double_step_rank_max(), pos.max_rank()))); const Bitboard emptySquares = Type == QUIETS || Type == QUIET_CHECKS ? target : ~pos.pieces() & pos.board_bb(); const Bitboard enemies = Type == EVASIONS ? (pos.checkers() & pos.non_sliding_riders() ? pos.pieces(Them) : pos.checkers()) : Type == CAPTURES ? target : pos.pieces(Them); Bitboard pawnsOn7 = pos.pieces(Us, PAWN) & TRank7BB; Bitboard pawnsNotOn7 = pos.pieces(Us, PAWN) & (pos.mandatory_pawn_promotion() ? ~TRank7BB : AllSquares); // Single and double pawn pushes, no promotions if (Type != CAPTURES) { Bitboard b1 = shift(pawnsNotOn7) & emptySquares; Bitboard b2 = pos.double_step_enabled() ? shift(b1 & TRank3BB) & emptySquares : Bitboard(0); if (Type == EVASIONS) // Consider only blocking squares { b1 &= target; b2 &= target; } if (Type == QUIET_CHECKS && pos.count(Them)) { // To make a quiet check, you either make a direct check by pushing a pawn // or push a blocker pawn that is not on the same file as the enemy king. // Discovered check promotion has been already generated amongst the captures. Square ksq = pos.square(Them); Bitboard dcCandidatePawns = pos.blockers_for_king(Them) & ~file_bb(ksq); b1 &= pawn_attacks_bb(Them, ksq) | shift< Up>(dcCandidatePawns); b2 &= pawn_attacks_bb(Them, ksq) | shift(dcCandidatePawns); } 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) { Bitboard b1 = shift(pawnsOn7) & enemies; Bitboard b2 = shift(pawnsOn7) & enemies; Bitboard b3 = shift(pawnsOn7) & emptySquares; if (Type == EVASIONS) b3 &= target; while (b1) moveList = make_promotions(pos, moveList, pop_lsb(b1)); while (b2) moveList = make_promotions(pos, moveList, pop_lsb(b2)); while (b3) moveList = make_promotions(pos, moveList, pop_lsb(b3)); } // Sittuyin promotions if (pos.sittuyin_promotion() && (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)) { Bitboard pawns = pos.pieces(Us, PAWN); // Pawns need to be on diagonals on opponent's half if there is more than one pawn if (pos.count(Us) > 1) pawns &= ( PseudoAttacks[Us][BISHOP][make_square(FILE_A, relative_rank(Us, RANK_1, pos.max_rank()))] | PseudoAttacks[Us][BISHOP][make_square(pos.max_file(), relative_rank(Us, RANK_1, pos.max_rank()))]) & forward_ranks_bb(Us, relative_rank(Us, Rank((pos.max_rank() - 1) / 2), pos.max_rank())); while (pawns) { Square from = pop_lsb(pawns); for (PieceType pt : pos.promotion_piece_types()) { if (pos.promotion_limit(pt) && pos.promotion_limit(pt) <= pos.count(Us, pt)) continue; Bitboard b = (pos.attacks_from(Us, pt, from) & ~pos.pieces()) | from; if (Type == EVASIONS) b &= target; while (b) { Square to = pop_lsb(b); if (!(attacks_bb(Us, pt, to, pos.pieces() ^ from) & pos.pieces(Them))) *moveList++ = make(from, to, pt); } } } } // Standard and en passant captures if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) { Bitboard b1 = shift(pawnsNotOn7) & enemies; Bitboard b2 = shift(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(relative_rank(Them, rank_of(pos.ep_square()), pos.max_rank()) <= Rank(pos.double_step_rank_max() + 1)); // An en passant capture cannot resolve a discovered check 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(pop_lsb(b1), pos.ep_square()); } } return moveList; } template ExtMove* generate_moves(const Position& pos, ExtMove* moveList, PieceType Pt, Bitboard target) { assert(Pt != KING && Pt != PAWN); Bitboard bb = pos.pieces(Us, Pt); while (bb) { Square from = pop_lsb(bb); Bitboard b1 = ( (pos.attacks_from(Us, Pt, from) & pos.pieces()) | (pos.moves_from(Us, Pt, from) & ~pos.pieces())) & target; PieceType promPt = pos.promoted_piece_type(Pt); Bitboard b2 = promPt && (!pos.promotion_limit(promPt) || pos.promotion_limit(promPt) > pos.count(Us, promPt)) ? b1 : Bitboard(0); Bitboard b3 = pos.piece_demotion() && pos.is_promoted(from) ? b1 : Bitboard(0); if (Checks) { b1 &= pos.check_squares(Pt); if (b2) b2 &= pos.check_squares(pos.promoted_piece_type(Pt)); if (b3) b3 &= pos.check_squares(type_of(pos.unpromoted_piece_on(from))); } // Restrict target squares considering promotion zone if (b2 | b3) { Bitboard promotion_zone = zone_bb(Us, pos.promotion_rank(), pos.max_rank()); if (pos.mandatory_piece_promotion()) b1 &= (promotion_zone & from ? Bitboard(0) : ~promotion_zone) | (pos.piece_promotion_on_capture() ? ~pos.pieces() : Bitboard(0)); // Exclude quiet promotions/demotions if (pos.piece_promotion_on_capture()) { b2 &= pos.pieces(); b3 &= pos.pieces(); } // Consider promotions/demotions into promotion zone if (!(promotion_zone & from)) { b2 &= promotion_zone; b3 &= promotion_zone; } } while (b1) moveList = make_move_and_gating(pos, moveList, Us, from, pop_lsb(b1)); // Shogi-style piece promotions while (b2) *moveList++ = make(from, pop_lsb(b2)); // Piece demotions while (b3) *moveList++ = make(from, pop_lsb(b3)); } return moveList; } template ExtMove* generate_all(const Position& pos, ExtMove* moveList) { static_assert(Type != LEGAL, "Unsupported type in generate_all()"); constexpr bool Checks = Type == QUIET_CHECKS; // Reduce template instantiations const Square ksq = pos.count(Us) ? pos.square(Us) : SQ_NONE; Bitboard target; // Skip generating non-king moves when in double check if (Type != EVASIONS || !more_than_one(pos.checkers() & ~pos.non_sliding_riders())) { target = Type == EVASIONS ? between_bb(ksq, lsb(pos.checkers())) : Type == NON_EVASIONS ? ~pos.pieces( Us) : Type == CAPTURES ? pos.pieces(~Us) : ~pos.pieces( ); // QUIETS || QUIET_CHECKS if (Type == EVASIONS) { if (pos.checkers() & pos.non_sliding_riders()) target = ~pos.pieces(Us); // Leaper attacks can not be blocked Square checksq = lsb(pos.checkers()); if (LeaperAttacks[~Us][type_of(pos.piece_on(checksq))][checksq] & pos.square(Us)) target = pos.checkers(); } target &= pos.board_bb(); moveList = generate_pawn_moves(pos, moveList, target); for (PieceType pt : pos.piece_types()) if (pt != PAWN && pt != KING) moveList = generate_moves(pos, moveList, pt, target); // generate drops if (pos.piece_drops() && Type != CAPTURES && (pos.count_in_hand(Us, ALL_PIECES) > 0 || pos.two_boards())) for (PieceType pt : pos.piece_types()) moveList = generate_drops(pos, moveList, pt, target & ~pos.pieces(~Us)); // Castling with non-king piece if (!pos.count(Us) && Type != CAPTURES && pos.can_castle(Us & ANY_CASTLING)) { Square from = pos.castling_king_square(Us); for(CastlingRights cr : { Us & KING_SIDE, Us & QUEEN_SIDE } ) if (!pos.castling_impeded(cr) && pos.can_castle(cr)) moveList = make_move_and_gating(pos, moveList, Us, from, pos.castling_rook_square(cr)); } // Special moves if (pos.cambodian_moves() && pos.gates(Us)) { if (Type != CAPTURES && Type != EVASIONS && (pos.pieces(Us, KING) & pos.gates(Us))) { Square from = pos.square(Us); Bitboard b = PseudoAttacks[WHITE][KNIGHT][from] & rank_bb(rank_of(from + (Us == WHITE ? NORTH : SOUTH))) & target & ~pos.pieces(); while (b) moveList = make_move_and_gating(pos, moveList, Us, from, pop_lsb(b)); } Bitboard b = pos.pieces(Us, FERS) & pos.gates(Us); while (b) { Square from = pop_lsb(b); Square to = from + 2 * (Us == WHITE ? NORTH : SOUTH); if (is_ok(to) && (target & to)) moveList = make_move_and_gating(pos, moveList, Us, from, to); } } // Workaround for passing: Execute a non-move with any piece if (pos.pass() && !pos.count(Us) && pos.pieces(Us)) *moveList++ = make(lsb(pos.pieces(Us)), lsb(pos.pieces(Us))); } // King moves if (pos.count(Us) && (!Checks || pos.blockers_for_king(~Us) & ksq)) { Bitboard b = ( (pos.attacks_from(Us, KING, ksq) & pos.pieces()) | (pos.moves_from(Us, KING, ksq) & ~pos.pieces())) & (Type == EVASIONS ? ~pos.pieces(Us) : target); while (b) moveList = make_move_and_gating(pos, moveList, Us, ksq, pop_lsb(b)); // Passing move by king if (pos.pass()) *moveList++ = make(ksq, ksq); if ((Type == QUIETS || Type == NON_EVASIONS) && pos.can_castle(Us & ANY_CASTLING)) for (CastlingRights cr : { Us & KING_SIDE, Us & QUEEN_SIDE } ) if (!pos.castling_impeded(cr) && pos.can_castle(cr)) moveList = make_move_and_gating(pos, moveList, Us,ksq, pos.castling_rook_square(cr)); } return moveList; } } // namespace /// Generates all pseudo-legal captures plus queen promotions /// Generates all pseudo-legal non-captures and underpromotions /// Generates all pseudo-legal check evasions when the side to move is in check /// Generates all pseudo-legal non-captures giving check, except castling and promotions /// Generates all pseudo-legal captures and non-captures /// /// Returns a pointer to the end of the move list. template ExtMove* generate(const Position& pos, ExtMove* moveList) { static_assert(Type != LEGAL, "Unsupported type in generate()"); assert((Type == EVASIONS) == (bool)pos.checkers()); Color us = pos.side_to_move(); return us == WHITE ? generate_all(pos, moveList) : generate_all(pos, moveList); } // Explicit template instantiations template ExtMove* generate(const Position&, ExtMove*); template ExtMove* generate(const Position&, ExtMove*); template ExtMove* generate(const Position&, ExtMove*); template ExtMove* generate(const Position&, ExtMove*); template ExtMove* generate(const Position&, ExtMove*); /// generate generates all the legal moves in the given position template<> ExtMove* generate(const Position& pos, ExtMove* moveList) { if (pos.is_immediate_game_end()) return moveList; ExtMove* cur = moveList; moveList = pos.checkers() ? generate(pos, moveList) : generate(pos, moveList); while (cur != moveList) if (!pos.legal(*cur) || pos.virtual_drop(*cur)) *cur = (--moveList)->move; else ++cur; return moveList; } } // namespace Stockfish Fairy-Stockfish-fairy_sf_14_0_1_xq/src/movegen.h000066400000000000000000000040271414571233100217420ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef MOVEGEN_H_INCLUDED #define MOVEGEN_H_INCLUDED #include #include "types.h" namespace Stockfish { 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 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 struct MoveList { explicit MoveList(const Position& pos) : last(generate(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; }; } // namespace Stockfish #endif // #ifndef MOVEGEN_H_INCLUDED Fairy-Stockfish-fairy_sf_14_0_1_xq/src/movepick.cpp000066400000000000000000000227501414571233100224550ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include "movepick.h" namespace Stockfish { // Since continuation history grows quadratically with the number of piece types, // we need to reserve a limited number of slots and map piece types to these slots // in order to reduce memory consumption to a reasonable level. int history_slot(Piece pc) { return pc == NO_PIECE ? 0 : (type_of(pc) == KING ? PIECE_SLOTS - 1 : type_of(pc) % (PIECE_SLOTS - 1)) + color_of(pc) * PIECE_SLOTS; } 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 && (pos.checkers() || 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 void MovePicker::score() { static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type"); for (auto& m : *this) if constexpr (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 constexpr (Type == QUIETS) m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] + 2 * (*continuationHistory[0])[history_slot(pos.moved_piece(m))][to_sq(m)] + (*continuationHistory[1])[history_slot(pos.moved_piece(m))][to_sq(m)] + (*continuationHistory[3])[history_slot(pos.moved_piece(m))][to_sq(m)] + (*continuationHistory[5])[history_slot(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)] + 2 * (*continuationHistory[0])[history_slot(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 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; assert(pos.legal(ttMove) == MoveList(pos).contains(ttMove) || pos.virtual_drop(ttMove)); return ttMove; case CAPTURE_INIT: case PROBCUT_INIT: case QCAPTURE_INIT: cur = endBadCaptures = moves; endMoves = generate(pos, cur); score(); ++stage; goto top; case GOOD_CAPTURE: if (select([&](){ return pos.see_ge(*cur, Value(-69 * cur->value / 1024 - 500 * (pos.captures_to_hand() && pos.gives_check(*cur))))? // 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([&](){ return *cur != MOVE_NONE && !pos.capture(*cur) && pos.pseudo_legal(*cur); })) return *(cur - 1); ++stage; [[fallthrough]]; case QUIET_INIT: if (!skipQuiets && !(pos.must_capture() && pos.has_capture())) { cur = endBadCaptures; endMoves = generate(pos, cur); score(); partial_insertion_sort(cur, endMoves, -3000 * depth); } ++stage; [[fallthrough]]; case QUIET: if ( !skipQuiets && select([&](){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([](){ return true; }); case EVASION_INIT: cur = moves; endMoves = generate(pos, cur); score(); ++stage; [[fallthrough]]; case EVASION: return select([](){ return true; }); case PROBCUT: return select([&](){ return pos.see_ge(*cur, threshold); }); case QCAPTURE: if (select([&](){ 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(pos, cur); ++stage; [[fallthrough]]; case QCHECK: return select([](){ return true; }); } assert(false); return MOVE_NONE; // Silence warning } } // namespace Stockfish Fairy-Stockfish-fairy_sf_14_0_1_xq/src/movepick.h000066400000000000000000000144641414571233100221250ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef MOVEPICK_H_INCLUDED #define MOVEPICK_H_INCLUDED #include #include #include #include "movegen.h" #include "position.h" #include "types.h" namespace Stockfish { /// 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 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::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 struct Stats : public std::array, Size> { typedef Stats stats; void fill(const T& v) { // For standard-layout 'this' points to first struct member assert(std::is_standard_layout::value); typedef StatsEntry entry; entry* p = reinterpret_cast(this); std::fill(p, p + sizeof(*this) / sizeof(entry), v); } }; template struct Stats : public std::array, Size> {}; /// In stats table, D=0 means that the template parameter is not used enum StatsParams { NOT_USED = 0, PIECE_SLOTS = 8 }; 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 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 LowPlyHistory; /// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous /// move, see www.chessprogramming.org/Countermove_Heuristic typedef Stats CounterMoveHistory; /// CapturePieceToHistory is addressed by a move's [piece][to][captured piece type] typedef Stats CapturePieceToHistory; /// PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to] typedef Stats 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 ContinuationHistory; int history_slot(Piece pc); /// 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 Move select(Pred); template 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]; }; } // namespace Stockfish #endif // #ifndef MOVEPICK_H_INCLUDED Fairy-Stockfish-fairy_sf_14_0_1_xq/src/nnue/000077500000000000000000000000001414571233100210735ustar00rootroot00000000000000Fairy-Stockfish-fairy_sf_14_0_1_xq/src/nnue/evaluate_nnue.cpp000066400000000000000000000340201414571233100244310ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 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 . */ // Code for calculating NNUE evaluation function #include #include #include #include #include #include "../evaluate.h" #include "../position.h" #include "../misc.h" #include "../uci.h" #include "../types.h" #include "evaluate_nnue.h" namespace Stockfish::Eval::NNUE { // Input feature converter LargePagePtr featureTransformer; // Evaluation function AlignedPtr network[LayerStacks]; // Evaluation function file name std::string fileName; std::string netDescription; namespace Detail { // Initialize the evaluation function parameters template void initialize(AlignedPtr& pointer) { pointer.reset(reinterpret_cast(std_aligned_alloc(alignof(T), sizeof(T)))); std::memset(pointer.get(), 0, sizeof(T)); } template void initialize(LargePagePtr& pointer) { static_assert(alignof(T) <= 4096, "aligned_large_pages_alloc() may fail for such a big alignment requirement of T"); pointer.reset(reinterpret_cast(aligned_large_pages_alloc(sizeof(T)))); std::memset(pointer.get(), 0, sizeof(T)); } // Read evaluation function parameters template bool read_parameters(std::istream& stream, T& reference) { std::uint32_t header; header = read_little_endian(stream); if (!stream || header != T::get_hash_value()) return false; return reference.read_parameters(stream); } // Write evaluation function parameters template bool write_parameters(std::ostream& stream, const T& reference) { write_little_endian(stream, T::get_hash_value()); return reference.write_parameters(stream); } } // namespace Detail // Initialize the evaluation function parameters void initialize() { Detail::initialize(featureTransformer); for (std::size_t i = 0; i < LayerStacks; ++i) Detail::initialize(network[i]); } // Read network header bool read_header(std::istream& stream, std::uint32_t* hashValue, std::string* desc) { std::uint32_t version, size; version = read_little_endian(stream); *hashValue = read_little_endian(stream); size = read_little_endian(stream); if (!stream || version != Version) return false; desc->resize(size); stream.read(&(*desc)[0], size); return !stream.fail(); } // Write network header bool write_header(std::ostream& stream, std::uint32_t hashValue, const std::string& desc) { write_little_endian(stream, Version); write_little_endian(stream, hashValue); write_little_endian(stream, desc.size()); stream.write(&desc[0], desc.size()); return !stream.fail(); } // Read network parameters bool read_parameters(std::istream& stream) { std::uint32_t hashValue; if (!read_header(stream, &hashValue, &netDescription)) return false; if (hashValue != HashValue) return false; if (!Detail::read_parameters(stream, *featureTransformer)) return false; for (std::size_t i = 0; i < LayerStacks; ++i) if (!Detail::read_parameters(stream, *(network[i]))) return false; return stream && stream.peek() == std::ios::traits_type::eof(); } // Write network parameters bool write_parameters(std::ostream& stream) { if (!write_header(stream, HashValue, netDescription)) return false; if (!Detail::write_parameters(stream, *featureTransformer)) return false; for (std::size_t i = 0; i < LayerStacks; ++i) if (!Detail::write_parameters(stream, *(network[i]))) return false; return (bool)stream; } // Evaluation function. Perform differential calculation. Value evaluate(const Position& pos, bool adjusted) { // We manually align the arrays on the stack because with gcc < 9.3 // overaligning stack variables with alignas() doesn't work correctly. constexpr uint64_t alignment = CacheLineSize; #if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) TransformedFeatureType transformedFeaturesUnaligned[ FeatureTransformer::BufferSize + alignment / sizeof(TransformedFeatureType)]; char bufferUnaligned[Network::BufferSize + alignment]; auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); auto* buffer = align_ptr_up(&bufferUnaligned[0]); #else alignas(alignment) TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize]; alignas(alignment) char buffer[Network::BufferSize]; #endif ASSERT_ALIGNED(transformedFeatures, alignment); ASSERT_ALIGNED(buffer, alignment); const std::size_t bucket = std::min((pos.count() - 1) * 8 / currentNnueVariant->nnueMaxPieces, 7); const auto psqt = featureTransformer->transform(pos, transformedFeatures, bucket); const auto output = network[bucket]->propagate(transformedFeatures, buffer); int materialist = psqt; int positional = output[0]; int delta_npm = abs(pos.non_pawn_material(WHITE) - pos.non_pawn_material(BLACK)); int entertainment = (adjusted && delta_npm <= BishopValueMg - KnightValueMg ? 7 : 0); int A = 128 - entertainment; int B = 128 + entertainment; int sum = (A * materialist + B * positional) / 128; return static_cast( sum / OutputScale ); } struct NnueEvalTrace { static_assert(LayerStacks == PSQTBuckets); Value psqt[LayerStacks]; Value positional[LayerStacks]; std::size_t correctBucket; }; static NnueEvalTrace trace_evaluate(const Position& pos) { // We manually align the arrays on the stack because with gcc < 9.3 // overaligning stack variables with alignas() doesn't work correctly. constexpr uint64_t alignment = CacheLineSize; #if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN) TransformedFeatureType transformedFeaturesUnaligned[ FeatureTransformer::BufferSize + alignment / sizeof(TransformedFeatureType)]; char bufferUnaligned[Network::BufferSize + alignment]; auto* transformedFeatures = align_ptr_up(&transformedFeaturesUnaligned[0]); auto* buffer = align_ptr_up(&bufferUnaligned[0]); #else alignas(alignment) TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize]; alignas(alignment) char buffer[Network::BufferSize]; #endif ASSERT_ALIGNED(transformedFeatures, alignment); ASSERT_ALIGNED(buffer, alignment); NnueEvalTrace t{}; t.correctBucket = std::min((pos.count() - 1) * 8 / currentNnueVariant->nnueMaxPieces, 7); for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket) { const auto psqt = featureTransformer->transform(pos, transformedFeatures, bucket); const auto output = network[bucket]->propagate(transformedFeatures, buffer); int materialist = psqt; int positional = output[0]; t.psqt[bucket] = static_cast( materialist / OutputScale ); t.positional[bucket] = static_cast( positional / OutputScale ); } return t; } // Requires the buffer to have capacity for at least 5 values static void format_cp_compact(Value v, char* buffer) { buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' '); int cp = std::abs(100 * v / PawnValueEg); if (cp >= 10000) { buffer[1] = '0' + cp / 10000; cp %= 10000; buffer[2] = '0' + cp / 1000; cp %= 1000; buffer[3] = '0' + cp / 100; cp %= 100; buffer[4] = ' '; } else if (cp >= 1000) { buffer[1] = '0' + cp / 1000; cp %= 1000; buffer[2] = '0' + cp / 100; cp %= 100; buffer[3] = '.'; buffer[4] = '0' + cp / 10; } else { buffer[1] = '0' + cp / 100; cp %= 100; buffer[2] = '.'; buffer[3] = '0' + cp / 10; cp %= 10; buffer[4] = '0' + cp / 1; } } // Requires the buffer to have capacity for at least 7 values static void format_cp_aligned_dot(Value v, char* buffer) { buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' '); int cp = std::abs(100 * v / PawnValueEg); if (cp >= 10000) { buffer[1] = '0' + cp / 10000; cp %= 10000; buffer[2] = '0' + cp / 1000; cp %= 1000; buffer[3] = '0' + cp / 100; cp %= 100; buffer[4] = '.'; buffer[5] = '0' + cp / 10; cp %= 10; buffer[6] = '0' + cp; } else if (cp >= 1000) { buffer[1] = ' '; buffer[2] = '0' + cp / 1000; cp %= 1000; buffer[3] = '0' + cp / 100; cp %= 100; buffer[4] = '.'; buffer[5] = '0' + cp / 10; cp %= 10; buffer[6] = '0' + cp; } else { buffer[1] = ' '; buffer[2] = ' '; buffer[3] = '0' + cp / 100; cp %= 100; buffer[4] = '.'; buffer[5] = '0' + cp / 10; cp %= 10; buffer[6] = '0' + cp / 1; } } // trace() returns a string with the value of each piece on a board, // and a table for (PSQT, Layers) values bucket by bucket. std::string trace(Position& pos) { std::stringstream ss; char board[3*RANK_NB+1][8*FILE_NB+2]; std::memset(board, ' ', sizeof(board)); for (int row = 0; row < 3*pos.ranks()+1; ++row) board[row][8*FILE_NB+1] = '\0'; // A lambda to output one box of the board auto writeSquare = [&board, &pos](File file, Rank rank, Piece pc, Value value) { const int x = ((int)file) * 8; const int y = (pos.max_rank() - (int)rank) * 3; for (int i = 1; i < 8; ++i) board[y][x+i] = board[y+3][x+i] = '-'; for (int i = 1; i < 3; ++i) board[y+i][x] = board[y+i][x+8] = '|'; board[y][x] = board[y][x+8] = board[y+3][x+8] = board[y+3][x] = '+'; if (pc != NO_PIECE) board[y+1][x+4] = pos.piece_to_char()[pc]; if (value != VALUE_NONE) format_cp_compact(value, &board[y+2][x+2]); }; // We estimate the value of each piece by doing a differential evaluation from // the current base eval, simulating the removal of the piece from its square. Value base = evaluate(pos); base = pos.side_to_move() == WHITE ? base : -base; for (File f = FILE_A; f <= pos.max_file(); ++f) for (Rank r = RANK_1; r <= pos.max_rank(); ++r) { Square sq = make_square(f, r); Piece pc = pos.piece_on(sq); Piece unpromotedPc = pos.unpromoted_piece_on(sq); bool isPromoted = pos.is_promoted(sq); Value v = VALUE_NONE; if (pc != NO_PIECE && type_of(pc) != pos.nnue_king()) { auto st = pos.state(); pos.remove_piece(sq); st->accumulator.computed[WHITE] = false; st->accumulator.computed[BLACK] = false; Value eval = evaluate(pos); eval = pos.side_to_move() == WHITE ? eval : -eval; v = base - eval; pos.put_piece(pc, sq, isPromoted, unpromotedPc); st->accumulator.computed[WHITE] = false; st->accumulator.computed[BLACK] = false; } writeSquare(f, r, pc, v); } ss << " NNUE derived piece values:\n"; for (int row = 0; row < 3*pos.ranks()+1; ++row) ss << board[row] << '\n'; ss << '\n'; auto t = trace_evaluate(pos); ss << " NNUE network contributions " << (pos.side_to_move() == WHITE ? "(White to move)" : "(Black to move)") << std::endl << "+------------+------------+------------+------------+\n" << "| Bucket | Material | Positional | Total |\n" << "| | (PSQT) | (Layers) | |\n" << "+------------+------------+------------+------------+\n"; for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket) { char buffer[3][8]; std::memset(buffer, '\0', sizeof(buffer)); format_cp_aligned_dot(t.psqt[bucket], buffer[0]); format_cp_aligned_dot(t.positional[bucket], buffer[1]); format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], buffer[2]); ss << "| " << bucket << " " << " | " << buffer[0] << " " << " | " << buffer[1] << " " << " | " << buffer[2] << " " << " |"; if (bucket == t.correctBucket) ss << " <-- this bucket is used"; ss << '\n'; } ss << "+------------+------------+------------+------------+\n"; return ss.str(); } // Load eval, from a file stream or a memory stream bool load_eval(std::string name, std::istream& stream) { initialize(); fileName = name; return read_parameters(stream); } // Save eval, to a file stream or a memory stream bool save_eval(std::ostream& stream) { if (fileName.empty()) return false; return write_parameters(stream); } /// Save eval, to a file given by its name bool save_eval(const std::optional& filename) { std::string actualFilename; std::string msg; if (filename.has_value()) actualFilename = filename.value(); else { if (eval_file_loaded != EvalFileDefaultName) { msg = "Failed to export a net. A non-embedded net can only be saved if the filename is specified"; sync_cout << msg << sync_endl; return false; } actualFilename = EvalFileDefaultName; } std::ofstream stream(actualFilename, std::ios_base::binary); bool saved = save_eval(stream); msg = saved ? "Network saved successfully to " + actualFilename : "Failed to export a net"; sync_cout << msg << sync_endl; return saved; } } // namespace Stockfish::Eval::NNUE Fairy-Stockfish-fairy_sf_14_0_1_xq/src/nnue/evaluate_nnue.h000066400000000000000000000033321414571233100241000ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 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 . */ // header used in NNUE evaluation function #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED #define NNUE_EVALUATE_NNUE_H_INCLUDED #include "nnue_feature_transformer.h" #include namespace Stockfish::Eval::NNUE { // Hash value of evaluation function structure constexpr std::uint32_t HashValue = FeatureTransformer::get_hash_value() ^ Network::get_hash_value(); // Deleter for automating release of memory area template struct AlignedDeleter { void operator()(T* ptr) const { ptr->~T(); std_aligned_free(ptr); } }; template struct LargePageDeleter { void operator()(T* ptr) const { ptr->~T(); aligned_large_pages_free(ptr); } }; template using AlignedPtr = std::unique_ptr>; template using LargePagePtr = std::unique_ptr>; } // namespace Stockfish::Eval::NNUE #endif // #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED Fairy-Stockfish-fairy_sf_14_0_1_xq/src/nnue/features/000077500000000000000000000000001414571233100227115ustar00rootroot00000000000000Fairy-Stockfish-fairy_sf_14_0_1_xq/src/nnue/features/half_ka_v2.cpp000066400000000000000000000057401414571233100254170ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 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 . */ //Definition of input features HalfKAv2 of NNUE evaluation function #include "half_ka_v2.h" #include "../../position.h" namespace Stockfish::Eval::NNUE::Features { // Map square to numbering on 8x8 board constexpr Square to_chess_square(Square s) { return Square(s - rank_of(s) * (FILE_MAX - FILE_H)); } // Orient a square according to perspective (rotates by 180 for black) inline Square HalfKAv2::orient(Color perspective, Square s) { return Square(int(to_chess_square(s)) ^ (bool(perspective) * 56)); } // Index of a feature for a given king position and another piece on some square inline IndexType HalfKAv2::make_index(Color perspective, Square s, Piece pc, Square ksq) { return IndexType(orient(perspective, s) + PieceSquareIndex[perspective][pc] + PS_NB * ksq); } // Get a list of indices for active features void HalfKAv2::append_active_indices( const Position& pos, Color perspective, ValueListInserter active ) { Square ksq = orient(perspective, pos.square(perspective)); Bitboard bb = pos.pieces(); while (bb) { Square s = pop_lsb(bb); active.push_back(make_index(perspective, s, pos.piece_on(s), ksq)); } } // append_changed_indices() : get a list of indices for recently changed features void HalfKAv2::append_changed_indices( Square ksq, StateInfo* st, Color perspective, ValueListInserter removed, ValueListInserter added ) { const auto& dp = st->dirtyPiece; Square oriented_ksq = orient(perspective, ksq); for (int i = 0; i < dp.dirty_num; ++i) { Piece pc = dp.piece[i]; if (dp.from[i] != SQ_NONE) removed.push_back(make_index(perspective, dp.from[i], pc, oriented_ksq)); if (dp.to[i] != SQ_NONE) added.push_back(make_index(perspective, dp.to[i], pc, oriented_ksq)); } } int HalfKAv2::update_cost(StateInfo* st) { return st->dirtyPiece.dirty_num; } int HalfKAv2::refresh_cost(const Position& pos) { return pos.count(); } bool HalfKAv2::requires_refresh(StateInfo* st, Color perspective) { return st->dirtyPiece.piece[0] == make_piece(perspective, KING); } } // namespace Stockfish::Eval::NNUE::Features Fairy-Stockfish-fairy_sf_14_0_1_xq/src/nnue/features/half_ka_v2.h000066400000000000000000000150751414571233100250660ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 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 . */ //Definition of input features HalfKAv2 of NNUE evaluation function #ifndef NNUE_FEATURES_HALF_KA_V2_H_INCLUDED #define NNUE_FEATURES_HALF_KA_V2_H_INCLUDED #include "../nnue_common.h" #include "../../evaluate.h" #include "../../misc.h" namespace Stockfish { struct StateInfo; } namespace Stockfish::Eval::NNUE::Features { // Feature HalfKAv2: Combination of the position of own king // and the position of pieces class HalfKAv2 { // unique number for each piece type on each square enum { PS_NONE = 0, PS_W_PAWN = 0, PS_B_PAWN = 1 * SQUARE_NB_CHESS, PS_W_KNIGHT = 2 * SQUARE_NB_CHESS, PS_B_KNIGHT = 3 * SQUARE_NB_CHESS, PS_W_BISHOP = 4 * SQUARE_NB_CHESS, PS_B_BISHOP = 5 * SQUARE_NB_CHESS, PS_W_ROOK = 6 * SQUARE_NB_CHESS, PS_B_ROOK = 7 * SQUARE_NB_CHESS, PS_W_QUEEN = 8 * SQUARE_NB_CHESS, PS_B_QUEEN = 9 * SQUARE_NB_CHESS, PS_KING = 10 * SQUARE_NB_CHESS, PS_NB = 11 * SQUARE_NB_CHESS }; static constexpr uint32_t PieceSquareIndex[COLOR_NB][PIECE_NB] = { // convention: W - us, B - them // viewed from other side, W and B are reversed { PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_KING, PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_KING, }, { PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_KING, PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_NONE, PS_KING, } }; // Check that the fragile array definition is correct static_assert(PieceSquareIndex[WHITE][make_piece(WHITE, PAWN)] == PS_W_PAWN); static_assert(PieceSquareIndex[WHITE][make_piece(WHITE, KING)] == PS_KING); static_assert(PieceSquareIndex[WHITE][make_piece(BLACK, PAWN)] == PS_B_PAWN); static_assert(PieceSquareIndex[WHITE][make_piece(BLACK, KING)] == PS_KING); // Orient a square according to perspective (rotates by 180 for black) static Square orient(Color perspective, Square s); // Index of a feature for a given king position and another piece on some square static IndexType make_index(Color perspective, Square s, Piece pc, Square ksq); public: // Feature name static constexpr const char* Name = "HalfKAv2(Friend)"; // Hash value embedded in the evaluation file static constexpr std::uint32_t HashValue = 0x5f234cb8u; // Number of feature dimensions static constexpr IndexType Dimensions = static_cast(SQUARE_NB_CHESS) * static_cast(PS_NB); // Maximum number of simultaneously active features. static constexpr IndexType MaxActiveDimensions = 32; // Get a list of indices for active features static void append_active_indices( const Position& pos, Color perspective, ValueListInserter active); // Get a list of indices for recently changed features static void append_changed_indices( Square ksq, StateInfo* st, Color perspective, ValueListInserter removed, ValueListInserter added); // Returns the cost of updating one perspective, the most costly one. // Assumes no refresh needed. static int update_cost(StateInfo* st); static int refresh_cost(const Position& pos); // Returns whether the change stored in this StateInfo means that // a full accumulator refresh is required. static bool requires_refresh(StateInfo* st, Color perspective); }; } // namespace Stockfish::Eval::NNUE::Features #endif // #ifndef NNUE_FEATURES_HALF_KA_V2_H_INCLUDED Fairy-Stockfish-fairy_sf_14_0_1_xq/src/nnue/features/half_ka_v2_variants.cpp000066400000000000000000000105551414571233100273260ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 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 . */ //Definition of input features HalfKAv2 of NNUE evaluation function #include "half_ka_v2_variants.h" #include "../../position.h" namespace Stockfish::Eval::NNUE::Features { // Map square to numbering on variant board inline Square to_variant_square(Square s, const Position& pos) { return Square(s - rank_of(s) * (FILE_MAX - pos.max_file())); } // Orient a square according to perspective (rotates by 180 for black) // Missing kings map to index 0 (SQ_A1) inline Square HalfKAv2Variants::orient(Color perspective, Square s, const Position& pos) { return s != SQ_NONE ? to_variant_square( perspective == WHITE || (pos.capture_the_flag(BLACK) & Rank8BB) ? s : flip_rank(s, pos.max_rank()), pos) : SQ_A1; } // Index of a feature for a given king position and another piece on some square inline IndexType HalfKAv2Variants::make_index(Color perspective, Square s, Piece pc, Square ksq, const Position& pos) { return IndexType(orient(perspective, s, pos) + pos.variant()->pieceSquareIndex[perspective][pc] + pos.variant()->kingSquareIndex[ksq]); } // Index of a feature for a given king position and another piece on some square inline IndexType HalfKAv2Variants::make_index(Color perspective, int handCount, Piece pc, Square ksq, const Position& pos) { return IndexType(handCount + pos.variant()->pieceHandIndex[perspective][pc] + pos.variant()->kingSquareIndex[ksq]); } // Get a list of indices for active features void HalfKAv2Variants::append_active_indices( const Position& pos, Color perspective, ValueListInserter active ) { Square oriented_ksq = orient(perspective, pos.nnue_king_square(perspective), pos); Bitboard bb = pos.pieces(); while (bb) { Square s = pop_lsb(bb); active.push_back(make_index(perspective, s, pos.piece_on(s), oriented_ksq, pos)); } // Indices for pieces in hand if (pos.nnue_use_pockets()) for (Color c : {WHITE, BLACK}) for (PieceType pt : pos.piece_types()) for (int i = 0; i < pos.count_in_hand(c, pt); i++) active.push_back(make_index(perspective, i, make_piece(c, pt), oriented_ksq, pos)); } // append_changed_indices() : get a list of indices for recently changed features void HalfKAv2Variants::append_changed_indices( Square ksq, StateInfo* st, Color perspective, ValueListInserter removed, ValueListInserter added, const Position& pos ) { const auto& dp = st->dirtyPiece; Square oriented_ksq = orient(perspective, ksq, pos); for (int i = 0; i < dp.dirty_num; ++i) { Piece pc = dp.piece[i]; if (dp.from[i] != SQ_NONE) removed.push_back(make_index(perspective, dp.from[i], pc, oriented_ksq, pos)); else if (dp.handPiece[i] != NO_PIECE) removed.push_back(make_index(perspective, dp.handCount[i] - 1, dp.handPiece[i], oriented_ksq, pos)); if (dp.to[i] != SQ_NONE) added.push_back(make_index(perspective, dp.to[i], pc, oriented_ksq, pos)); else if (dp.handPiece[i] != NO_PIECE) added.push_back(make_index(perspective, dp.handCount[i] - 1, dp.handPiece[i], oriented_ksq, pos)); } } int HalfKAv2Variants::update_cost(StateInfo* st) { return st->dirtyPiece.dirty_num; } int HalfKAv2Variants::refresh_cost(const Position& pos) { return pos.count(); } bool HalfKAv2Variants::requires_refresh(StateInfo* st, Color perspective, const Position& pos) { return st->dirtyPiece.piece[0] == make_piece(perspective, pos.nnue_king()); } } // namespace Stockfish::Eval::NNUE::Features Fairy-Stockfish-fairy_sf_14_0_1_xq/src/nnue/features/half_ka_v2_variants.h000066400000000000000000000064361414571233100267760ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 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 . */ //Definition of input features HalfKAv2 of NNUE evaluation function #ifndef NNUE_FEATURES_HALF_KA_V2_VARIANTS_H_INCLUDED #define NNUE_FEATURES_HALF_KA_V2_VARIANTS_H_INCLUDED #include "../nnue_common.h" #include "../../evaluate.h" #include "../../misc.h" #include "half_ka_v2.h" namespace Stockfish { struct StateInfo; } namespace Stockfish::Eval::NNUE::Features { // Feature HalfKAv2: Combination of the position of own king // and the position of pieces class HalfKAv2Variants { // Orient a square according to perspective (rotates by 180 for black) static Square orient(Color perspective, Square s, const Position& pos); // Index of a feature for a given king position and another piece on some square static IndexType make_index(Color perspective, Square s, Piece pc, Square ksq, const Position& pos); // Index of a feature for a given king position and another piece in hand static IndexType make_index(Color perspective, int handCount, Piece pc, Square ksq, const Position& pos); public: // Feature name static constexpr const char* Name = "HalfKAv2(Friend)"; // Hash value embedded in the evaluation file static constexpr std::uint32_t HashValue = 0x5f234cb8u; // Number of feature dimensions static constexpr IndexType Dimensions = static_cast(SQUARE_NB) * static_cast(SQUARE_NB) * 19; static IndexType get_dimensions() { return currentNnueVariant->nnueDimensions; } // Maximum number of simultaneously active features. static constexpr IndexType MaxActiveDimensions = 64; // Get a list of indices for active features static void append_active_indices( const Position& pos, Color perspective, ValueListInserter active); // Get a list of indices for recently changed features static void append_changed_indices( Square ksq, StateInfo* st, Color perspective, ValueListInserter removed, ValueListInserter added, const Position& pos); // Returns the cost of updating one perspective, the most costly one. // Assumes no refresh needed. static int update_cost(StateInfo* st); static int refresh_cost(const Position& pos); // Returns whether the change stored in this StateInfo means that // a full accumulator refresh is required. static bool requires_refresh(StateInfo* st, Color perspective, const Position& pos); }; } // namespace Stockfish::Eval::NNUE::Features #endif // #ifndef NNUE_FEATURES_HALF_KA_V2_VARIANTS_H_INCLUDED Fairy-Stockfish-fairy_sf_14_0_1_xq/src/nnue/layers/000077500000000000000000000000001414571233100223725ustar00rootroot00000000000000Fairy-Stockfish-fairy_sf_14_0_1_xq/src/nnue/layers/affine_transform.h000066400000000000000000000427451414571233100261020ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 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 . */ // Definition of layer AffineTransform of NNUE evaluation function #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED #define NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED #include #include "../nnue_common.h" namespace Stockfish::Eval::NNUE::Layers { // Affine transformation layer template class AffineTransform { public: // Input/output type using InputType = typename PreviousLayer::OutputType; using OutputType = std::int32_t; static_assert(std::is_same::value, ""); // Number of input/output dimensions static constexpr IndexType InputDimensions = PreviousLayer::OutputDimensions; static constexpr IndexType OutputDimensions = OutDims; static constexpr IndexType PaddedInputDimensions = ceil_to_multiple(InputDimensions, MaxSimdWidth); #if defined (USE_AVX512) static constexpr const IndexType OutputSimdWidth = SimdWidth / 2; #elif defined (USE_SSSE3) static constexpr const IndexType OutputSimdWidth = SimdWidth / 4; #endif // Size of forward propagation buffer used in this layer static constexpr std::size_t SelfBufferSize = ceil_to_multiple(OutputDimensions * sizeof(OutputType), CacheLineSize); // Size of the forward propagation buffer used from the input layer to this layer static constexpr std::size_t BufferSize = PreviousLayer::BufferSize + SelfBufferSize; // Hash value embedded in the evaluation file static constexpr std::uint32_t get_hash_value() { std::uint32_t hashValue = 0xCC03DAE4u; hashValue += OutputDimensions; hashValue ^= PreviousLayer::get_hash_value() >> 1; hashValue ^= PreviousLayer::get_hash_value() << 31; return hashValue; } // Read network parameters bool read_parameters(std::istream& stream) { if (!previousLayer.read_parameters(stream)) return false; for (std::size_t i = 0; i < OutputDimensions; ++i) biases[i] = read_little_endian(stream); for (std::size_t i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) #if !defined (USE_SSSE3) weights[i] = read_little_endian(stream); #else weights[ (i / 4) % (PaddedInputDimensions / 4) * OutputDimensions * 4 + i / PaddedInputDimensions * 4 + i % 4 ] = read_little_endian(stream); #endif return !stream.fail(); } // Write network parameters bool write_parameters(std::ostream& stream) const { if (!previousLayer.write_parameters(stream)) return false; for (std::size_t i = 0; i < OutputDimensions; ++i) write_little_endian(stream, biases[i]); #if !defined (USE_SSSE3) for (std::size_t i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) write_little_endian(stream, weights[i]); #else std::unique_ptr unscrambledWeights = std::make_unique(OutputDimensions * PaddedInputDimensions); for (std::size_t i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) { unscrambledWeights[i] = weights[ (i / 4) % (PaddedInputDimensions / 4) * OutputDimensions * 4 + i / PaddedInputDimensions * 4 + i % 4 ]; } for (std::size_t i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) write_little_endian(stream, unscrambledWeights[i]); #endif return !stream.fail(); } // Forward propagation const OutputType* propagate( const TransformedFeatureType* transformedFeatures, char* buffer) const { const auto input = previousLayer.propagate( transformedFeatures, buffer + SelfBufferSize); #if defined (USE_AVX512) [[maybe_unused]] const __m512i Ones512 = _mm512_set1_epi16(1); [[maybe_unused]] auto m512_hadd = [](__m512i sum, int bias) -> int { return _mm512_reduce_add_epi32(sum) + bias; }; [[maybe_unused]] auto m512_add_dpbusd_epi32 = [=](__m512i& acc, __m512i a, __m512i b) { #if defined (USE_VNNI) acc = _mm512_dpbusd_epi32(acc, a, b); #else __m512i product0 = _mm512_maddubs_epi16(a, b); product0 = _mm512_madd_epi16(product0, Ones512); acc = _mm512_add_epi32(acc, product0); #endif }; [[maybe_unused]] auto m512_add_dpbusd_epi32x4 = [=](__m512i& acc, __m512i a0, __m512i b0, __m512i a1, __m512i b1, __m512i a2, __m512i b2, __m512i a3, __m512i b3) { #if defined (USE_VNNI) acc = _mm512_dpbusd_epi32(acc, a0, b0); acc = _mm512_dpbusd_epi32(acc, a1, b1); acc = _mm512_dpbusd_epi32(acc, a2, b2); acc = _mm512_dpbusd_epi32(acc, a3, b3); #else __m512i product0 = _mm512_maddubs_epi16(a0, b0); __m512i product1 = _mm512_maddubs_epi16(a1, b1); __m512i product2 = _mm512_maddubs_epi16(a2, b2); __m512i product3 = _mm512_maddubs_epi16(a3, b3); product0 = _mm512_adds_epi16(product0, product1); product0 = _mm512_madd_epi16(product0, Ones512); product2 = _mm512_adds_epi16(product2, product3); product2 = _mm512_madd_epi16(product2, Ones512); acc = _mm512_add_epi32(acc, _mm512_add_epi32(product0, product2)); #endif }; #endif #if defined (USE_AVX2) [[maybe_unused]] const __m256i Ones256 = _mm256_set1_epi16(1); [[maybe_unused]] auto m256_hadd = [](__m256i sum, int bias) -> int { __m128i sum128 = _mm_add_epi32(_mm256_castsi256_si128(sum), _mm256_extracti128_si256(sum, 1)); sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_BADC)); sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_CDAB)); return _mm_cvtsi128_si32(sum128) + bias; }; [[maybe_unused]] auto m256_add_dpbusd_epi32 = [=](__m256i& acc, __m256i a, __m256i b) { #if defined (USE_VNNI) acc = _mm256_dpbusd_epi32(acc, a, b); #else __m256i product0 = _mm256_maddubs_epi16(a, b); product0 = _mm256_madd_epi16(product0, Ones256); acc = _mm256_add_epi32(acc, product0); #endif }; [[maybe_unused]] auto m256_add_dpbusd_epi32x4 = [=](__m256i& acc, __m256i a0, __m256i b0, __m256i a1, __m256i b1, __m256i a2, __m256i b2, __m256i a3, __m256i b3) { #if defined (USE_VNNI) acc = _mm256_dpbusd_epi32(acc, a0, b0); acc = _mm256_dpbusd_epi32(acc, a1, b1); acc = _mm256_dpbusd_epi32(acc, a2, b2); acc = _mm256_dpbusd_epi32(acc, a3, b3); #else __m256i product0 = _mm256_maddubs_epi16(a0, b0); __m256i product1 = _mm256_maddubs_epi16(a1, b1); __m256i product2 = _mm256_maddubs_epi16(a2, b2); __m256i product3 = _mm256_maddubs_epi16(a3, b3); product0 = _mm256_adds_epi16(product0, product1); product0 = _mm256_madd_epi16(product0, Ones256); product2 = _mm256_adds_epi16(product2, product3); product2 = _mm256_madd_epi16(product2, Ones256); acc = _mm256_add_epi32(acc, _mm256_add_epi32(product0, product2)); #endif }; #endif #if defined (USE_SSSE3) [[maybe_unused]] const __m128i Ones128 = _mm_set1_epi16(1); [[maybe_unused]] auto m128_hadd = [](__m128i sum, int bias) -> int { sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0x4E)); //_MM_PERM_BADC sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0xB1)); //_MM_PERM_CDAB return _mm_cvtsi128_si32(sum) + bias; }; [[maybe_unused]] auto m128_add_dpbusd_epi32 = [=](__m128i& acc, __m128i a, __m128i b) { __m128i product0 = _mm_maddubs_epi16(a, b); product0 = _mm_madd_epi16(product0, Ones128); acc = _mm_add_epi32(acc, product0); }; [[maybe_unused]] auto m128_add_dpbusd_epi32x4 = [=](__m128i& acc, __m128i a0, __m128i b0, __m128i a1, __m128i b1, __m128i a2, __m128i b2, __m128i a3, __m128i b3) { __m128i product0 = _mm_maddubs_epi16(a0, b0); __m128i product1 = _mm_maddubs_epi16(a1, b1); __m128i product2 = _mm_maddubs_epi16(a2, b2); __m128i product3 = _mm_maddubs_epi16(a3, b3); product0 = _mm_adds_epi16(product0, product1); product0 = _mm_madd_epi16(product0, Ones128); product2 = _mm_adds_epi16(product2, product3); product2 = _mm_madd_epi16(product2, Ones128); acc = _mm_add_epi32(acc, _mm_add_epi32(product0, product2)); }; #endif #if defined (USE_AVX512) using vec_t = __m512i; #define vec_setzero _mm512_setzero_si512 #define vec_set_32 _mm512_set1_epi32 auto& vec_add_dpbusd_32 = m512_add_dpbusd_epi32; auto& vec_add_dpbusd_32x4 = m512_add_dpbusd_epi32x4; auto& vec_hadd = m512_hadd; #elif defined (USE_AVX2) using vec_t = __m256i; #define vec_setzero _mm256_setzero_si256 #define vec_set_32 _mm256_set1_epi32 auto& vec_add_dpbusd_32 = m256_add_dpbusd_epi32; auto& vec_add_dpbusd_32x4 = m256_add_dpbusd_epi32x4; auto& vec_hadd = m256_hadd; #elif defined (USE_SSSE3) using vec_t = __m128i; #define vec_setzero _mm_setzero_si128 #define vec_set_32 _mm_set1_epi32 auto& vec_add_dpbusd_32 = m128_add_dpbusd_epi32; auto& vec_add_dpbusd_32x4 = m128_add_dpbusd_epi32x4; auto& vec_hadd = m128_hadd; #endif #if defined (USE_SSSE3) // Different layout, we process 4 inputs at a time, always. static_assert(InputDimensions % 4 == 0); const auto output = reinterpret_cast(buffer); const auto inputVector = reinterpret_cast(input); static_assert(OutputDimensions % OutputSimdWidth == 0 || OutputDimensions == 1); // OutputDimensions is either 1 or a multiple of SimdWidth // because then it is also an input dimension. if constexpr (OutputDimensions % OutputSimdWidth == 0) { constexpr IndexType NumChunks = InputDimensions / 4; const auto input32 = reinterpret_cast(input); vec_t* outptr = reinterpret_cast(output); std::memcpy(output, biases, OutputDimensions * sizeof(OutputType)); for (int i = 0; i < (int)NumChunks - 3; i += 4) { const vec_t in0 = vec_set_32(input32[i + 0]); const vec_t in1 = vec_set_32(input32[i + 1]); const vec_t in2 = vec_set_32(input32[i + 2]); const vec_t in3 = vec_set_32(input32[i + 3]); const auto col0 = reinterpret_cast(&weights[(i + 0) * OutputDimensions * 4]); const auto col1 = reinterpret_cast(&weights[(i + 1) * OutputDimensions * 4]); const auto col2 = reinterpret_cast(&weights[(i + 2) * OutputDimensions * 4]); const auto col3 = reinterpret_cast(&weights[(i + 3) * OutputDimensions * 4]); for (int j = 0; j * OutputSimdWidth < OutputDimensions; ++j) vec_add_dpbusd_32x4(outptr[j], in0, col0[j], in1, col1[j], in2, col2[j], in3, col3[j]); } } else if constexpr (OutputDimensions == 1) { #if defined (USE_AVX512) if constexpr (PaddedInputDimensions % (SimdWidth * 2) != 0) { constexpr IndexType NumChunks = PaddedInputDimensions / SimdWidth; const auto inputVector256 = reinterpret_cast(input); __m256i sum0 = _mm256_setzero_si256(); const auto row0 = reinterpret_cast(&weights[0]); for (int j = 0; j < (int)NumChunks; ++j) { const __m256i in = inputVector256[j]; m256_add_dpbusd_epi32(sum0, in, row0[j]); } output[0] = m256_hadd(sum0, biases[0]); } else #endif { #if defined (USE_AVX512) constexpr IndexType NumChunks = PaddedInputDimensions / (SimdWidth * 2); #else constexpr IndexType NumChunks = PaddedInputDimensions / SimdWidth; #endif vec_t sum0 = vec_setzero(); const auto row0 = reinterpret_cast(&weights[0]); for (int j = 0; j < (int)NumChunks; ++j) { const vec_t in = inputVector[j]; vec_add_dpbusd_32(sum0, in, row0[j]); } output[0] = vec_hadd(sum0, biases[0]); } } #else // Use old implementation for the other architectures. auto output = reinterpret_cast(buffer); #if defined(USE_SSE2) // At least a multiple of 16, with SSE2. static_assert(InputDimensions % SimdWidth == 0); constexpr IndexType NumChunks = InputDimensions / SimdWidth; const __m128i Zeros = _mm_setzero_si128(); const auto inputVector = reinterpret_cast(input); #elif defined(USE_MMX) static_assert(InputDimensions % SimdWidth == 0); constexpr IndexType NumChunks = InputDimensions / SimdWidth; const __m64 Zeros = _mm_setzero_si64(); const auto inputVector = reinterpret_cast(input); #elif defined(USE_NEON) static_assert(InputDimensions % SimdWidth == 0); constexpr IndexType NumChunks = InputDimensions / SimdWidth; const auto inputVector = reinterpret_cast(input); #endif for (IndexType i = 0; i < OutputDimensions; ++i) { const IndexType offset = i * PaddedInputDimensions; #if defined(USE_SSE2) __m128i sumLo = _mm_cvtsi32_si128(biases[i]); __m128i sumHi = Zeros; const auto row = reinterpret_cast(&weights[offset]); for (IndexType j = 0; j < NumChunks; ++j) { __m128i row_j = _mm_load_si128(&row[j]); __m128i input_j = _mm_load_si128(&inputVector[j]); __m128i extendedRowLo = _mm_srai_epi16(_mm_unpacklo_epi8(row_j, row_j), 8); __m128i extendedRowHi = _mm_srai_epi16(_mm_unpackhi_epi8(row_j, row_j), 8); __m128i extendedInputLo = _mm_unpacklo_epi8(input_j, Zeros); __m128i extendedInputHi = _mm_unpackhi_epi8(input_j, Zeros); __m128i productLo = _mm_madd_epi16(extendedRowLo, extendedInputLo); __m128i productHi = _mm_madd_epi16(extendedRowHi, extendedInputHi); sumLo = _mm_add_epi32(sumLo, productLo); sumHi = _mm_add_epi32(sumHi, productHi); } __m128i sum = _mm_add_epi32(sumLo, sumHi); __m128i sumHigh_64 = _mm_shuffle_epi32(sum, _MM_SHUFFLE(1, 0, 3, 2)); sum = _mm_add_epi32(sum, sumHigh_64); __m128i sum_second_32 = _mm_shufflelo_epi16(sum, _MM_SHUFFLE(1, 0, 3, 2)); sum = _mm_add_epi32(sum, sum_second_32); output[i] = _mm_cvtsi128_si32(sum); #elif defined(USE_MMX) __m64 sumLo = _mm_cvtsi32_si64(biases[i]); __m64 sumHi = Zeros; const auto row = reinterpret_cast(&weights[offset]); for (IndexType j = 0; j < NumChunks; ++j) { __m64 row_j = row[j]; __m64 input_j = inputVector[j]; __m64 extendedRowLo = _mm_srai_pi16(_mm_unpacklo_pi8(row_j, row_j), 8); __m64 extendedRowHi = _mm_srai_pi16(_mm_unpackhi_pi8(row_j, row_j), 8); __m64 extendedInputLo = _mm_unpacklo_pi8(input_j, Zeros); __m64 extendedInputHi = _mm_unpackhi_pi8(input_j, Zeros); __m64 productLo = _mm_madd_pi16(extendedRowLo, extendedInputLo); __m64 productHi = _mm_madd_pi16(extendedRowHi, extendedInputHi); sumLo = _mm_add_pi32(sumLo, productLo); sumHi = _mm_add_pi32(sumHi, productHi); } __m64 sum = _mm_add_pi32(sumLo, sumHi); 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(&weights[offset]); for (IndexType j = 0; j < NumChunks; ++j) { int16x8_t product = vmull_s8(inputVector[j * 2], row[j * 2]); product = vmlal_s8(product, inputVector[j * 2 + 1], row[j * 2 + 1]); sum = vpadalq_s16(sum, product); } output[i] = sum[0] + sum[1] + sum[2] + sum[3]; #else OutputType sum = biases[i]; for (IndexType j = 0; j < InputDimensions; ++j) { sum += weights[offset + j] * input[j]; } output[i] = sum; #endif } #if defined(USE_MMX) _mm_empty(); #endif #endif return output; } private: using BiasType = OutputType; using WeightType = std::int8_t; PreviousLayer previousLayer; alignas(CacheLineSize) BiasType biases[OutputDimensions]; alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions]; }; } // namespace Stockfish::Eval::NNUE::Layers #endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED Fairy-Stockfish-fairy_sf_14_0_1_xq/src/nnue/layers/clipped_relu.h000066400000000000000000000166771414571233100252330ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 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 . */ // 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 Stockfish::Eval::NNUE::Layers { // Clipped ReLU template class ClippedReLU { public: // Input/output type using InputType = typename PreviousLayer::OutputType; using OutputType = std::uint8_t; static_assert(std::is_same::value, ""); // Number of input/output dimensions static constexpr IndexType InputDimensions = PreviousLayer::OutputDimensions; static constexpr IndexType OutputDimensions = InputDimensions; // Size of forward propagation buffer used in this layer static constexpr std::size_t SelfBufferSize = ceil_to_multiple(OutputDimensions * sizeof(OutputType), CacheLineSize); // Size of the forward propagation buffer used from the input layer to this layer static constexpr std::size_t BufferSize = PreviousLayer::BufferSize + SelfBufferSize; // Hash value embedded in the evaluation file static constexpr std::uint32_t get_hash_value() { std::uint32_t hashValue = 0x538D24C7u; hashValue += PreviousLayer::get_hash_value(); return hashValue; } // Read network parameters bool read_parameters(std::istream& stream) { return previousLayer.read_parameters(stream); } // Write network parameters bool write_parameters(std::ostream& stream) const { return previousLayer.write_parameters(stream); } // Forward propagation const OutputType* propagate( const TransformedFeatureType* transformedFeatures, char* buffer) const { const auto input = previousLayer.propagate( transformedFeatures, buffer + SelfBufferSize); const auto output = reinterpret_cast(buffer); #if defined(USE_AVX2) if constexpr (InputDimensions % SimdWidth == 0) { constexpr IndexType NumChunks = InputDimensions / SimdWidth; const __m256i Zero = _mm256_setzero_si256(); const __m256i Offsets = _mm256_set_epi32(7, 3, 6, 2, 5, 1, 4, 0); const auto in = reinterpret_cast(input); const auto out = reinterpret_cast<__m256i*>(output); for (IndexType i = 0; i < NumChunks; ++i) { const __m256i words0 = _mm256_srai_epi16(_mm256_packs_epi32( _mm256_load_si256(&in[i * 4 + 0]), _mm256_load_si256(&in[i * 4 + 1])), WeightScaleBits); const __m256i words1 = _mm256_srai_epi16(_mm256_packs_epi32( _mm256_load_si256(&in[i * 4 + 2]), _mm256_load_si256(&in[i * 4 + 3])), WeightScaleBits); _mm256_store_si256(&out[i], _mm256_permutevar8x32_epi32(_mm256_max_epi8( _mm256_packs_epi16(words0, words1), Zero), Offsets)); } } else { constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2); const __m128i Zero = _mm_setzero_si128(); const auto in = reinterpret_cast(input); const auto out = reinterpret_cast<__m128i*>(output); for (IndexType i = 0; i < NumChunks; ++i) { const __m128i words0 = _mm_srai_epi16(_mm_packs_epi32( _mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])), WeightScaleBits); const __m128i words1 = _mm_srai_epi16(_mm_packs_epi32( _mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])), WeightScaleBits); const __m128i packedbytes = _mm_packs_epi16(words0, words1); _mm_store_si128(&out[i], _mm_max_epi8(packedbytes, Zero)); } } constexpr IndexType Start = InputDimensions % SimdWidth == 0 ? InputDimensions / SimdWidth * SimdWidth : InputDimensions / (SimdWidth / 2) * (SimdWidth / 2); #elif defined(USE_SSE2) constexpr IndexType NumChunks = InputDimensions / SimdWidth; #ifdef USE_SSE41 const __m128i Zero = _mm_setzero_si128(); #else const __m128i k0x80s = _mm_set1_epi8(-128); #endif const auto in = reinterpret_cast(input); const auto out = reinterpret_cast<__m128i*>(output); for (IndexType i = 0; i < NumChunks; ++i) { const __m128i words0 = _mm_srai_epi16(_mm_packs_epi32( _mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])), WeightScaleBits); const __m128i words1 = _mm_srai_epi16(_mm_packs_epi32( _mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])), WeightScaleBits); const __m128i packedbytes = _mm_packs_epi16(words0, words1); _mm_store_si128(&out[i], #ifdef USE_SSE41 _mm_max_epi8(packedbytes, Zero) #else _mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s) #endif ); } constexpr IndexType Start = NumChunks * SimdWidth; #elif defined(USE_MMX) constexpr IndexType NumChunks = InputDimensions / SimdWidth; const __m64 k0x80s = _mm_set1_pi8(-128); const auto in = reinterpret_cast(input); const auto out = reinterpret_cast<__m64*>(output); for (IndexType i = 0; i < NumChunks; ++i) { const __m64 words0 = _mm_srai_pi16( _mm_packs_pi32(in[i * 4 + 0], in[i * 4 + 1]), WeightScaleBits); const __m64 words1 = _mm_srai_pi16( _mm_packs_pi32(in[i * 4 + 2], in[i * 4 + 3]), WeightScaleBits); const __m64 packedbytes = _mm_packs_pi16(words0, words1); out[i] = _mm_subs_pi8(_mm_adds_pi8(packedbytes, k0x80s), k0x80s); } _mm_empty(); constexpr IndexType Start = NumChunks * SimdWidth; #elif defined(USE_NEON) constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2); const int8x8_t Zero = {0}; const auto in = reinterpret_cast(input); const auto out = reinterpret_cast(output); for (IndexType i = 0; i < NumChunks; ++i) { int16x8_t shifted; const auto pack = reinterpret_cast(&shifted); pack[0] = vqshrn_n_s32(in[i * 2 + 0], WeightScaleBits); pack[1] = vqshrn_n_s32(in[i * 2 + 1], WeightScaleBits); out[i] = vmax_s8(vqmovn_s16(shifted), Zero); } constexpr IndexType Start = NumChunks * (SimdWidth / 2); #else constexpr IndexType Start = 0; #endif for (IndexType i = Start; i < InputDimensions; ++i) { output[i] = static_cast( std::max(0, std::min(127, input[i] >> WeightScaleBits))); } return output; } private: PreviousLayer previousLayer; }; } // namespace Stockfish::Eval::NNUE::Layers #endif // NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED Fairy-Stockfish-fairy_sf_14_0_1_xq/src/nnue/layers/input_slice.h000066400000000000000000000042011414571233100250560ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 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 . */ // 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 Stockfish::Eval::NNUE::Layers { // Input layer template class InputSlice { public: // Need to maintain alignment static_assert(Offset % MaxSimdWidth == 0, ""); // Output type using OutputType = TransformedFeatureType; // Output dimensionality static constexpr IndexType OutputDimensions = OutDims; // Size of forward propagation buffer used from the input layer to this layer static constexpr std::size_t BufferSize = 0; // Hash value embedded in the evaluation file static constexpr std::uint32_t get_hash_value() { std::uint32_t hashValue = 0xEC42E90Du; hashValue ^= OutputDimensions ^ (Offset << 10); return hashValue; } // Read network parameters bool read_parameters(std::istream& /*stream*/) { return true; } // Write network parameters bool write_parameters(std::ostream& /*stream*/) const { return true; } // Forward propagation const OutputType* propagate( const TransformedFeatureType* transformedFeatures, char* /*buffer*/) const { return transformedFeatures + Offset; } private: }; } // namespace Stockfish::Eval::NNUE::Layers #endif // #ifndef NNUE_LAYERS_INPUT_SLICE_H_INCLUDED Fairy-Stockfish-fairy_sf_14_0_1_xq/src/nnue/nnue_accumulator.h000066400000000000000000000024451414571233100246150ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 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 . */ // Class for difference calculation of NNUE evaluation function #ifndef NNUE_ACCUMULATOR_H_INCLUDED #define NNUE_ACCUMULATOR_H_INCLUDED #include "nnue_architecture.h" namespace Stockfish::Eval::NNUE { // Class that holds the result of affine transformation of input features struct alignas(CacheLineSize) Accumulator { std::int16_t accumulation[2][TransformedFeatureDimensions]; std::int32_t psqtAccumulation[2][PSQTBuckets]; bool computed[2]; }; } // namespace Stockfish::Eval::NNUE #endif // NNUE_ACCUMULATOR_H_INCLUDED Fairy-Stockfish-fairy_sf_14_0_1_xq/src/nnue/nnue_architecture.h000066400000000000000000000040621414571233100247550ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 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 . */ // Input features and network structure used in NNUE evaluation function #ifndef NNUE_ARCHITECTURE_H_INCLUDED #define NNUE_ARCHITECTURE_H_INCLUDED #include "nnue_common.h" #include "features/half_ka_v2_variants.h" #include "layers/input_slice.h" #include "layers/affine_transform.h" #include "layers/clipped_relu.h" namespace Stockfish::Eval::NNUE { // Input features used in evaluation function using FeatureSet = Features::HalfKAv2Variants; // Number of input feature dimensions after conversion constexpr IndexType TransformedFeatureDimensions = 512; constexpr IndexType PSQTBuckets = 8; constexpr IndexType LayerStacks = 8; namespace Layers { // Define network structure using InputLayer = InputSlice; using HiddenLayer1 = ClippedReLU>; using HiddenLayer2 = ClippedReLU>; using OutputLayer = AffineTransform; } // namespace Layers using Network = Layers::OutputLayer; static_assert(TransformedFeatureDimensions % MaxSimdWidth == 0, ""); static_assert(Network::OutputDimensions == 1, ""); static_assert(std::is_same::value, ""); } // namespace Stockfish::Eval::NNUE #endif // #ifndef NNUE_ARCHITECTURE_H_INCLUDED Fairy-Stockfish-fairy_sf_14_0_1_xq/src/nnue/nnue_common.h000066400000000000000000000122111414571233100235560ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 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 . */ // Constants used in NNUE evaluation function #ifndef NNUE_COMMON_H_INCLUDED #define NNUE_COMMON_H_INCLUDED #include #include #include "../misc.h" // for IsLittleEndian #if defined(USE_AVX2) #include #elif defined(USE_SSE41) #include #elif defined(USE_SSSE3) #include #elif defined(USE_SSE2) #include #elif defined(USE_MMX) #include #elif defined(USE_NEON) #include #endif namespace Stockfish::Eval::NNUE { // Version of the evaluation file constexpr std::uint32_t Version = 0x7AF32F20u; // Constant used in evaluation value calculation constexpr int OutputScale = 16; constexpr int WeightScaleBits = 6; // Size of cache line (in bytes) constexpr std::size_t CacheLineSize = 64; // SIMD width (in bytes) #if defined(USE_AVX2) constexpr std::size_t SimdWidth = 32; #elif defined(USE_SSE2) constexpr std::size_t SimdWidth = 16; #elif defined(USE_MMX) constexpr std::size_t SimdWidth = 8; #elif defined(USE_NEON) constexpr std::size_t SimdWidth = 16; #endif constexpr std::size_t MaxSimdWidth = 32; // Type of input feature after conversion using TransformedFeatureType = std::uint8_t; using IndexType = std::uint32_t; // Round n up to be a multiple of base template constexpr IntType ceil_to_multiple(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 inline IntType read_little_endian(std::istream& stream) { IntType result; if (IsLittleEndian) stream.read(reinterpret_cast(&result), sizeof(IntType)); else { std::uint8_t u[sizeof(IntType)]; typename std::make_unsigned::type v = 0; stream.read(reinterpret_cast(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; } // write_little_endian() is our utility to write an integer (signed or unsigned, any size) // to a stream in little-endian order. We swap the byte order before the write if // necessary to always write in little endian order, independantly of the byte // ordering of the compiling machine. template inline void write_little_endian(std::ostream& stream, IntType value) { if (IsLittleEndian) stream.write(reinterpret_cast(&value), sizeof(IntType)); else { std::uint8_t u[sizeof(IntType)]; typename std::make_unsigned::type v = value; std::size_t i = 0; // if constexpr to silence the warning about shift by 8 if constexpr (sizeof(IntType) > 1) { for (; i + 1 < sizeof(IntType); ++i) { u[i] = v; v >>= 8; } } u[i] = v; stream.write(reinterpret_cast(u), sizeof(IntType)); } } // read_little_endian(s, out, N) : read integers in bulk from a little indian stream. // This reads N integers from stream s and put them in array out. template inline void read_little_endian(std::istream& stream, IntType* out, std::size_t count) { if (IsLittleEndian) stream.read(reinterpret_cast(out), sizeof(IntType) * count); else for (std::size_t i = 0; i < count; ++i) out[i] = read_little_endian(stream); } // write_little_endian(s, values, N) : write integers in bulk to a little indian stream. // This takes N integers from array values and writes them on stream s. template inline void write_little_endian(std::ostream& stream, const IntType* values, std::size_t count) { if (IsLittleEndian) stream.write(reinterpret_cast(values), sizeof(IntType) * count); else for (std::size_t i = 0; i < count; ++i) write_little_endian(stream, values[i]); } } // namespace Stockfish::Eval::NNUE #endif // #ifndef NNUE_COMMON_H_INCLUDED Fairy-Stockfish-fairy_sf_14_0_1_xq/src/nnue/nnue_feature_transformer.h000066400000000000000000000557411414571233100263620ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 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 . */ // 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 // std::memset() namespace Stockfish::Eval::NNUE { using BiasType = std::int16_t; using WeightType = std::int16_t; using PSQTWeightType = std::int32_t; // If vector instructions are enabled, we update and refresh the // accumulator tile by tile such that each tile fits in the CPU's // vector registers. #define VECTOR static_assert(PSQTBuckets % 8 == 0, "Per feature PSQT values cannot be processed at granularity lower than 8 at a time."); #ifdef USE_AVX512 typedef __m512i vec_t; typedef __m256i psqt_vec_t; #define vec_load(a) _mm512_load_si512(a) #define vec_store(a,b) _mm512_store_si512(a,b) #define vec_add_16(a,b) _mm512_add_epi16(a,b) #define vec_sub_16(a,b) _mm512_sub_epi16(a,b) #define vec_load_psqt(a) _mm256_load_si256(a) #define vec_store_psqt(a,b) _mm256_store_si256(a,b) #define vec_add_psqt_32(a,b) _mm256_add_epi32(a,b) #define vec_sub_psqt_32(a,b) _mm256_sub_epi32(a,b) #define vec_zero_psqt() _mm256_setzero_si256() #define NumRegistersSIMD 32 #elif USE_AVX2 typedef __m256i vec_t; typedef __m256i psqt_vec_t; #define vec_load(a) _mm256_load_si256(a) #define vec_store(a,b) _mm256_store_si256(a,b) #define vec_add_16(a,b) _mm256_add_epi16(a,b) #define vec_sub_16(a,b) _mm256_sub_epi16(a,b) #define vec_load_psqt(a) _mm256_load_si256(a) #define vec_store_psqt(a,b) _mm256_store_si256(a,b) #define vec_add_psqt_32(a,b) _mm256_add_epi32(a,b) #define vec_sub_psqt_32(a,b) _mm256_sub_epi32(a,b) #define vec_zero_psqt() _mm256_setzero_si256() #define NumRegistersSIMD 16 #elif USE_SSE2 typedef __m128i vec_t; typedef __m128i psqt_vec_t; #define vec_load(a) (*(a)) #define vec_store(a,b) *(a)=(b) #define vec_add_16(a,b) _mm_add_epi16(a,b) #define vec_sub_16(a,b) _mm_sub_epi16(a,b) #define vec_load_psqt(a) (*(a)) #define vec_store_psqt(a,b) *(a)=(b) #define vec_add_psqt_32(a,b) _mm_add_epi32(a,b) #define vec_sub_psqt_32(a,b) _mm_sub_epi32(a,b) #define vec_zero_psqt() _mm_setzero_si128() #define NumRegistersSIMD (Is64Bit ? 16 : 8) #elif USE_MMX typedef __m64 vec_t; typedef __m64 psqt_vec_t; #define vec_load(a) (*(a)) #define vec_store(a,b) *(a)=(b) #define vec_add_16(a,b) _mm_add_pi16(a,b) #define vec_sub_16(a,b) _mm_sub_pi16(a,b) #define vec_load_psqt(a) (*(a)) #define vec_store_psqt(a,b) *(a)=(b) #define vec_add_psqt_32(a,b) _mm_add_pi32(a,b) #define vec_sub_psqt_32(a,b) _mm_sub_pi32(a,b) #define vec_zero_psqt() _mm_setzero_si64() #define NumRegistersSIMD 8 #elif USE_NEON typedef int16x8_t vec_t; typedef int32x4_t psqt_vec_t; #define vec_load(a) (*(a)) #define vec_store(a,b) *(a)=(b) #define vec_add_16(a,b) vaddq_s16(a,b) #define vec_sub_16(a,b) vsubq_s16(a,b) #define vec_load_psqt(a) (*(a)) #define vec_store_psqt(a,b) *(a)=(b) #define vec_add_psqt_32(a,b) vaddq_s32(a,b) #define vec_sub_psqt_32(a,b) vsubq_s32(a,b) #define vec_zero_psqt() psqt_vec_t{0} #define NumRegistersSIMD 16 #else #undef VECTOR #endif #ifdef VECTOR // Compute optimal SIMD register count for feature transformer accumulation. // We use __m* types as template arguments, which causes GCC to emit warnings // about losing some attribute information. This is irrelevant to us as we // only take their size, so the following pragma are harmless. #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wignored-attributes" template static constexpr int BestRegisterCount() { #define RegisterSize sizeof(SIMDRegisterType) #define LaneSize sizeof(LaneType) static_assert(RegisterSize >= LaneSize); static_assert(MaxRegisters <= NumRegistersSIMD); static_assert(MaxRegisters > 0); static_assert(NumRegistersSIMD > 0); static_assert(RegisterSize % LaneSize == 0); static_assert((NumLanes * LaneSize) % RegisterSize == 0); const int ideal = (NumLanes * LaneSize) / RegisterSize; if (ideal <= MaxRegisters) return ideal; // Look for the largest divisor of the ideal register count that is smaller than MaxRegisters for (int divisor = MaxRegisters; divisor > 1; --divisor) if (ideal % divisor == 0) return divisor; return 1; } static constexpr int NumRegs = BestRegisterCount(); static constexpr int NumPsqtRegs = BestRegisterCount(); #pragma GCC diagnostic pop #endif // Input feature converter class FeatureTransformer { private: // Number of output dimensions for one side static constexpr IndexType HalfDimensions = TransformedFeatureDimensions; #ifdef VECTOR static constexpr IndexType TileHeight = NumRegs * sizeof(vec_t) / 2; static constexpr IndexType PsqtTileHeight = NumPsqtRegs * sizeof(psqt_vec_t) / 4; static_assert(HalfDimensions % TileHeight == 0, "TileHeight must divide HalfDimensions"); static_assert(PSQTBuckets % PsqtTileHeight == 0, "PsqtTileHeight must divide PSQTBuckets"); #endif public: // Output type using OutputType = TransformedFeatureType; // Number of input/output dimensions static constexpr IndexType InputDimensions = FeatureSet::Dimensions; static constexpr IndexType OutputDimensions = HalfDimensions * 2; // Size of forward propagation buffer static constexpr std::size_t BufferSize = OutputDimensions * sizeof(OutputType); // Hash value embedded in the evaluation file static constexpr std::uint32_t get_hash_value() { return FeatureSet::HashValue ^ OutputDimensions; } // Read network parameters bool read_parameters(std::istream& stream) { read_little_endian(stream, biases , HalfDimensions ); read_little_endian(stream, weights , HalfDimensions * FeatureSet::get_dimensions()); read_little_endian(stream, psqtWeights, PSQTBuckets * FeatureSet::get_dimensions()); return !stream.fail(); } // Write network parameters bool write_parameters(std::ostream& stream) const { write_little_endian(stream, biases , HalfDimensions ); write_little_endian(stream, weights , HalfDimensions * FeatureSet::get_dimensions()); write_little_endian(stream, psqtWeights, PSQTBuckets * FeatureSet::get_dimensions()); return !stream.fail(); } // Convert input features std::int32_t transform(const Position& pos, OutputType* output, int bucket) const { update_accumulator(pos, WHITE); update_accumulator(pos, BLACK); const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()}; const auto& accumulation = pos.state()->accumulator.accumulation; const auto& psqtAccumulation = pos.state()->accumulator.psqtAccumulation; const auto psqt = ( psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket] ) / 2; #if defined(USE_AVX512) constexpr IndexType NumChunks = HalfDimensions / (SimdWidth * 2); static_assert(HalfDimensions % (SimdWidth * 2) == 0); const __m512i Control = _mm512_setr_epi64(0, 2, 4, 6, 1, 3, 5, 7); const __m512i Zero = _mm512_setzero_si512(); for (IndexType p = 0; p < 2; ++p) { const IndexType offset = HalfDimensions * p; auto out = reinterpret_cast<__m512i*>(&output[offset]); for (IndexType j = 0; j < NumChunks; ++j) { __m512i sum0 = _mm512_load_si512(&reinterpret_cast (accumulation[perspectives[p]])[j * 2 + 0]); __m512i sum1 = _mm512_load_si512(&reinterpret_cast (accumulation[perspectives[p]])[j * 2 + 1]); _mm512_store_si512(&out[j], _mm512_permutexvar_epi64(Control, _mm512_max_epi8(_mm512_packs_epi16(sum0, sum1), Zero))); } } return psqt; #elif defined(USE_AVX2) constexpr IndexType NumChunks = HalfDimensions / SimdWidth; constexpr int Control = 0b11011000; const __m256i Zero = _mm256_setzero_si256(); for (IndexType p = 0; p < 2; ++p) { const IndexType offset = HalfDimensions * p; auto out = reinterpret_cast<__m256i*>(&output[offset]); for (IndexType j = 0; j < NumChunks; ++j) { __m256i sum0 = _mm256_load_si256(&reinterpret_cast (accumulation[perspectives[p]])[j * 2 + 0]); __m256i sum1 = _mm256_load_si256(&reinterpret_cast (accumulation[perspectives[p]])[j * 2 + 1]); _mm256_store_si256(&out[j], _mm256_permute4x64_epi64( _mm256_max_epi8(_mm256_packs_epi16(sum0, sum1), Zero), Control)); } } return psqt; #elif defined(USE_SSE2) #ifdef USE_SSE41 constexpr IndexType NumChunks = HalfDimensions / SimdWidth; const __m128i Zero = _mm_setzero_si128(); #else constexpr IndexType NumChunks = HalfDimensions / SimdWidth; const __m128i k0x80s = _mm_set1_epi8(-128); #endif for (IndexType p = 0; p < 2; ++p) { const IndexType offset = HalfDimensions * p; auto out = reinterpret_cast<__m128i*>(&output[offset]); for (IndexType j = 0; j < NumChunks; ++j) { __m128i sum0 = _mm_load_si128(&reinterpret_cast (accumulation[perspectives[p]])[j * 2 + 0]); __m128i sum1 = _mm_load_si128(&reinterpret_cast (accumulation[perspectives[p]])[j * 2 + 1]); const __m128i packedbytes = _mm_packs_epi16(sum0, sum1); #ifdef USE_SSE41 _mm_store_si128(&out[j], _mm_max_epi8(packedbytes, Zero)); #else _mm_store_si128(&out[j], _mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s)); #endif } } return psqt; #elif defined(USE_MMX) constexpr IndexType NumChunks = HalfDimensions / SimdWidth; const __m64 k0x80s = _mm_set1_pi8(-128); for (IndexType p = 0; p < 2; ++p) { const IndexType offset = HalfDimensions * p; auto out = reinterpret_cast<__m64*>(&output[offset]); for (IndexType j = 0; j < NumChunks; ++j) { __m64 sum0 = *(&reinterpret_cast(accumulation[perspectives[p]])[j * 2 + 0]); __m64 sum1 = *(&reinterpret_cast(accumulation[perspectives[p]])[j * 2 + 1]); const __m64 packedbytes = _mm_packs_pi16(sum0, sum1); out[j] = _mm_subs_pi8(_mm_adds_pi8(packedbytes, k0x80s), k0x80s); } } _mm_empty(); return psqt; #elif defined(USE_NEON) constexpr IndexType NumChunks = HalfDimensions / (SimdWidth / 2); const int8x8_t Zero = {0}; for (IndexType p = 0; p < 2; ++p) { const IndexType offset = HalfDimensions * p; const auto out = reinterpret_cast(&output[offset]); for (IndexType j = 0; j < NumChunks; ++j) { int16x8_t sum = reinterpret_cast(accumulation[perspectives[p]])[j]; out[j] = vmax_s8(vqmovn_s16(sum), Zero); } } return psqt; #else for (IndexType p = 0; p < 2; ++p) { const IndexType offset = HalfDimensions * p; for (IndexType j = 0; j < HalfDimensions; ++j) { BiasType sum = accumulation[perspectives[p]][j]; output[offset + j] = static_cast(std::max(0, std::min(127, sum))); } } return psqt; #endif } // end of function transform() private: void update_accumulator(const Position& pos, const Color perspective) const { // The size must be enough to contain the largest possible update. // That might depend on the feature set and generally relies on the // feature set's update cost calculation to be correct and never // allow updates with more added/removed features than MaxActiveDimensions. using IndexList = ValueList; #ifdef VECTOR // Gcc-10.2 unnecessarily spills AVX2 registers if this array // is defined in the VECTOR code below, once in each branch vec_t acc[NumRegs]; psqt_vec_t psqt[NumPsqtRegs]; #endif // Look for a usable accumulator of an earlier position. We keep track // of the estimated gain in terms of features to be added/subtracted. StateInfo *st = pos.state(), *next = nullptr; int gain = FeatureSet::refresh_cost(pos); while (st->previous && !st->accumulator.computed[perspective]) { // This governs when a full feature refresh is needed and how many // updates are better than just one full refresh. if ( FeatureSet::requires_refresh(st, perspective, pos) || (gain -= FeatureSet::update_cost(st) + 1) < 0) break; next = st; st = st->previous; } if (st->accumulator.computed[perspective]) { if (next == nullptr) return; // Update incrementally in two steps. First, we update the "next" // accumulator. Then, we update the current accumulator (pos.state()). // Gather all features to be updated. const Square ksq = pos.nnue_king_square(perspective); IndexList removed[2], added[2]; FeatureSet::append_changed_indices( ksq, next, perspective, removed[0], added[0], pos); for (StateInfo *st2 = pos.state(); st2 != next; st2 = st2->previous) FeatureSet::append_changed_indices( ksq, st2, perspective, removed[1], added[1], pos); // Mark the accumulators as computed. next->accumulator.computed[perspective] = true; pos.state()->accumulator.computed[perspective] = true; // Now update the accumulators listed in states_to_update[], where the last element is a sentinel. StateInfo *states_to_update[3] = { next, next == pos.state() ? nullptr : pos.state(), nullptr }; #ifdef VECTOR for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) { // Load accumulator auto accTile = reinterpret_cast( &st->accumulator.accumulation[perspective][j * TileHeight]); for (IndexType k = 0; k < NumRegs; ++k) acc[k] = vec_load(&accTile[k]); for (IndexType i = 0; states_to_update[i]; ++i) { // Difference calculation for the deactivated features for (const auto index : removed[i]) { const IndexType offset = HalfDimensions * index + j * TileHeight; auto column = reinterpret_cast(&weights[offset]); for (IndexType k = 0; k < NumRegs; ++k) acc[k] = vec_sub_16(acc[k], column[k]); } // Difference calculation for the activated features for (const auto index : added[i]) { const IndexType offset = HalfDimensions * index + j * TileHeight; auto column = reinterpret_cast(&weights[offset]); for (IndexType k = 0; k < NumRegs; ++k) acc[k] = vec_add_16(acc[k], column[k]); } // Store accumulator accTile = reinterpret_cast( &states_to_update[i]->accumulator.accumulation[perspective][j * TileHeight]); for (IndexType k = 0; k < NumRegs; ++k) vec_store(&accTile[k], acc[k]); } } for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) { // Load accumulator auto accTilePsqt = reinterpret_cast( &st->accumulator.psqtAccumulation[perspective][j * PsqtTileHeight]); for (std::size_t k = 0; k < NumPsqtRegs; ++k) psqt[k] = vec_load_psqt(&accTilePsqt[k]); for (IndexType i = 0; states_to_update[i]; ++i) { // Difference calculation for the deactivated features for (const auto index : removed[i]) { const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); for (std::size_t k = 0; k < NumPsqtRegs; ++k) psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); } // Difference calculation for the activated features for (const auto index : added[i]) { const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); for (std::size_t k = 0; k < NumPsqtRegs; ++k) psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); } // Store accumulator accTilePsqt = reinterpret_cast( &states_to_update[i]->accumulator.psqtAccumulation[perspective][j * PsqtTileHeight]); for (std::size_t k = 0; k < NumPsqtRegs; ++k) vec_store_psqt(&accTilePsqt[k], psqt[k]); } } #else for (IndexType i = 0; states_to_update[i]; ++i) { std::memcpy(states_to_update[i]->accumulator.accumulation[perspective], st->accumulator.accumulation[perspective], HalfDimensions * sizeof(BiasType)); for (std::size_t k = 0; k < PSQTBuckets; ++k) states_to_update[i]->accumulator.psqtAccumulation[perspective][k] = st->accumulator.psqtAccumulation[perspective][k]; st = states_to_update[i]; // Difference calculation for the deactivated features for (const auto index : removed[i]) { const IndexType offset = HalfDimensions * index; for (IndexType j = 0; j < HalfDimensions; ++j) st->accumulator.accumulation[perspective][j] -= weights[offset + j]; for (std::size_t k = 0; k < PSQTBuckets; ++k) st->accumulator.psqtAccumulation[perspective][k] -= psqtWeights[index * PSQTBuckets + k]; } // Difference calculation for the activated features for (const auto index : added[i]) { const IndexType offset = HalfDimensions * index; for (IndexType j = 0; j < HalfDimensions; ++j) st->accumulator.accumulation[perspective][j] += weights[offset + j]; for (std::size_t k = 0; k < PSQTBuckets; ++k) st->accumulator.psqtAccumulation[perspective][k] += psqtWeights[index * PSQTBuckets + k]; } } #endif } else { // Refresh the accumulator auto& accumulator = pos.state()->accumulator; accumulator.computed[perspective] = true; IndexList active; FeatureSet::append_active_indices(pos, perspective, active); #ifdef VECTOR for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j) { auto biasesTile = reinterpret_cast( &biases[j * TileHeight]); for (IndexType k = 0; k < NumRegs; ++k) acc[k] = biasesTile[k]; for (const auto index : active) { const IndexType offset = HalfDimensions * index + j * TileHeight; auto column = reinterpret_cast(&weights[offset]); for (unsigned k = 0; k < NumRegs; ++k) acc[k] = vec_add_16(acc[k], column[k]); } auto accTile = reinterpret_cast( &accumulator.accumulation[perspective][j * TileHeight]); for (unsigned k = 0; k < NumRegs; k++) vec_store(&accTile[k], acc[k]); } for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j) { for (std::size_t k = 0; k < NumPsqtRegs; ++k) psqt[k] = vec_zero_psqt(); for (const auto index : active) { const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight; auto columnPsqt = reinterpret_cast(&psqtWeights[offset]); for (std::size_t k = 0; k < NumPsqtRegs; ++k) psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); } auto accTilePsqt = reinterpret_cast( &accumulator.psqtAccumulation[perspective][j * PsqtTileHeight]); for (std::size_t k = 0; k < NumPsqtRegs; ++k) vec_store_psqt(&accTilePsqt[k], psqt[k]); } #else std::memcpy(accumulator.accumulation[perspective], biases, HalfDimensions * sizeof(BiasType)); for (std::size_t k = 0; k < PSQTBuckets; ++k) accumulator.psqtAccumulation[perspective][k] = 0; for (const auto index : active) { const IndexType offset = HalfDimensions * index; for (IndexType j = 0; j < HalfDimensions; ++j) accumulator.accumulation[perspective][j] += weights[offset + j]; for (std::size_t k = 0; k < PSQTBuckets; ++k) accumulator.psqtAccumulation[perspective][k] += psqtWeights[index * PSQTBuckets + k]; } #endif } #if defined(USE_MMX) _mm_empty(); #endif } alignas(CacheLineSize) BiasType biases[HalfDimensions]; alignas(CacheLineSize) WeightType weights[HalfDimensions * InputDimensions]; alignas(CacheLineSize) PSQTWeightType psqtWeights[InputDimensions * PSQTBuckets]; }; } // namespace Stockfish::Eval::NNUE #endif // #ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED Fairy-Stockfish-fairy_sf_14_0_1_xq/src/parser.cpp000066400000000000000000000474151414571233100221410ustar00rootroot00000000000000/* Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish Copyright (C) 2018-2021 Fabian Fichter Fairy-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. Fairy-Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include "apiutil.h" #include "parser.h" #include "piece.h" #include "types.h" namespace Stockfish { namespace { template bool set(const std::string& value, T& target) { std::stringstream ss(value); ss >> target; return !ss.fail(); } template <> bool set(const std::string& value, Rank& target) { std::stringstream ss(value); int i; ss >> i; target = Rank(i - 1); return !ss.fail() && target >= RANK_1 && target <= RANK_MAX; } template <> bool set(const std::string& value, File& target) { std::stringstream ss(value); if (isdigit(ss.peek())) { int i; ss >> i; target = File(i - 1); } else { char c; ss >> c; target = File(c - 'a'); } return !ss.fail() && target >= FILE_A && target <= FILE_MAX; } template <> bool set(const std::string& value, std::string& target) { target = value; return true; } template <> bool set(const std::string& value, bool& target) { target = value == "true"; return value == "true" || value == "false"; } template <> bool set(const std::string& value, Value& target) { target = value == "win" ? VALUE_MATE : value == "loss" ? -VALUE_MATE : value == "draw" ? VALUE_DRAW : VALUE_NONE; return value == "win" || value == "loss" || value == "draw" || value == "none"; } template <> bool set(const std::string& value, MaterialCounting& target) { target = value == "janggi" ? JANGGI_MATERIAL : value == "unweighted" ? UNWEIGHTED_MATERIAL : value == "whitedrawodds" ? WHITE_DRAW_ODDS : value == "blackdrawodds" ? BLACK_DRAW_ODDS : NO_MATERIAL_COUNTING; return value == "janggi" || value == "unweighted" || value == "whitedrawodds" || value == "blackdrawodds" || value == "none"; } template <> bool set(const std::string& value, CountingRule& target) { target = value == "makruk" ? MAKRUK_COUNTING : value == "asean" ? ASEAN_COUNTING : NO_COUNTING; return value == "makruk" || value == "asean" || value == "none"; } template <> bool set(const std::string& value, EnclosingRule& target) { target = value == "reversi" ? REVERSI : value == "ataxx" ? ATAXX : NO_ENCLOSING; return value == "reversi" || value == "ataxx" || value == "none"; } template <> bool set(const std::string& value, Bitboard& target) { char file; int rank; std::stringstream ss(value); target = 0; while (!ss.eof() && ss >> file && ss >> rank) target |= file == '*' ? rank_bb(Rank(rank - 1)) : square_bb(make_square(File(tolower(file) - 'a'), Rank(rank - 1))); return !ss.fail(); } } // namespace template template void VariantParser::parse_attribute(const std::string& key, T& target) { const auto& it = config.find(key); if (it != config.end()) { bool valid = set(it->second, target); if (DoCheck && !valid) { std::string typeName = std::is_same() ? "int" : std::is_same() ? "Rank" : std::is_same() ? "File" : std::is_same() ? "bool" : std::is_same() ? "Value" : std::is_same() ? "MaterialCounting" : std::is_same() ? "CountingRule" : std::is_same() ? "Bitboard" : typeid(T).name(); std::cerr << key << " - Invalid value " << it->second << " for type " << typeName << std::endl; } } } template void VariantParser::parse_attribute(const std::string& key, PieceType& target, std::string pieceToChar) { const auto& it = config.find(key); if (it != config.end()) { char token; size_t idx; std::stringstream ss(it->second); if (ss >> token && (idx = token == '-' ? 0 : pieceToChar.find(toupper(token))) != std::string::npos) target = PieceType(idx); else if (DoCheck) std::cerr << key << " - Invalid piece type: " << token << std::endl; } } template Variant* VariantParser::parse() { Variant* v = new Variant(); v->reset_pieces(); v->promotionPieceTypes = {}; return parse(v); } template Variant* VariantParser::parse(Variant* v) { // piece types for (PieceType pt = PAWN; pt <= KING; ++pt) { if (pt == CUSTOM_PIECES_ROYAL) // reserved custom royal/king slot continue; // piece char std::string name = piece_name(pt); const auto& keyValue = config.find(name); if (keyValue != config.end() && !keyValue->second.empty()) { if (isalpha(keyValue->second.at(0))) v->add_piece(pt, keyValue->second.at(0)); else { if (DoCheck && keyValue->second.at(0) != '-') std::cerr << name << " - Invalid letter: " << keyValue->second.at(0) << std::endl; v->remove_piece(pt); } // betza if (is_custom(pt)) { if (keyValue->second.size() > 1) v->customPiece[pt - CUSTOM_PIECES] = keyValue->second.substr(2); else if (DoCheck) std::cerr << name << " - Missing Betza move notation" << std::endl; } else if (pt == KING) { if (keyValue->second.size() > 1) { // custom royal piece v->customPiece[CUSTOM_PIECES_ROYAL - CUSTOM_PIECES] = keyValue->second.substr(2); v->kingType = CUSTOM_PIECES_ROYAL; } else v->kingType = KING; } } // mobility region std::string capitalizedPiece = name; capitalizedPiece[0] = toupper(capitalizedPiece[0]); for (Color c : {WHITE, BLACK}) { std::string color = c == WHITE ? "White" : "Black"; parse_attribute("mobilityRegion" + color + capitalizedPiece, v->mobilityRegion[c][pt]); } } // piece values for (Phase phase : {MG, EG}) { const std::string optionName = phase == MG ? "pieceValueMg" : "pieceValueEg"; const auto& pv = config.find(optionName); if (pv != config.end()) { char token; size_t idx = 0; std::stringstream ss(pv->second); while (!ss.eof() && ss >> token && (idx = v->pieceToChar.find(toupper(token))) != std::string::npos && ss >> token && ss >> v->pieceValue[phase][idx]) {} if (DoCheck && idx == std::string::npos) std::cerr << optionName << " - Invalid piece type: " << token << std::endl; else if (DoCheck && !ss.eof()) std::cerr << optionName << " - Invalid piece value for type: " << v->pieceToChar[idx] << std::endl; } } parse_attribute("variantTemplate", v->variantTemplate); parse_attribute("pieceToCharTable", v->pieceToCharTable); parse_attribute("pocketSize", v->pocketSize); parse_attribute("maxRank", v->maxRank); parse_attribute("maxFile", v->maxFile); parse_attribute("chess960", v->chess960); parse_attribute("twoBoards", v->twoBoards); parse_attribute("startFen", v->startFen); parse_attribute("promotionRank", v->promotionRank); // promotion piece types const auto& it_prom = config.find("promotionPieceTypes"); if (it_prom != config.end()) { v->promotionPieceTypes = {}; char token; size_t idx = 0; std::stringstream ss(it_prom->second); while (ss >> token && ((idx = v->pieceToChar.find(toupper(token))) != std::string::npos)) v->promotionPieceTypes.insert(PieceType(idx)); if (DoCheck && idx == std::string::npos && token != '-') std::cerr << "promotionPieceTypes - Invalid piece type: " << token << std::endl; } parse_attribute("sittuyinPromotion", v->sittuyinPromotion); // promotion limit const auto& it_prom_limit = config.find("promotionLimit"); if (it_prom_limit != config.end()) { char token; size_t idx = 0; std::stringstream ss(it_prom_limit->second); while (!ss.eof() && ss >> token && (idx = v->pieceToChar.find(toupper(token))) != std::string::npos && ss >> token && ss >> v->promotionLimit[idx]) {} if (DoCheck && idx == std::string::npos) std::cerr << "promotionLimit - Invalid piece type: " << token << std::endl; else if (DoCheck && !ss.eof()) std::cerr << "promotionLimit - Invalid piece count for type: " << v->pieceToChar[idx] << std::endl; } // promoted piece types const auto& it_prom_pt = config.find("promotedPieceType"); if (it_prom_pt != config.end()) { char token; size_t idx = 0, idx2 = 0; std::stringstream ss(it_prom_pt->second); while ( ss >> token && (idx = v->pieceToChar.find(toupper(token))) != std::string::npos && ss >> token && ss >> token && (idx2 = (token == '-' ? 0 : v->pieceToChar.find(toupper(token)))) != std::string::npos) v->promotedPieceType[idx] = PieceType(idx2); if (DoCheck && (idx == std::string::npos || idx2 == std::string::npos)) std::cerr << "promotedPieceType - Invalid piece type: " << token << std::endl; } parse_attribute("piecePromotionOnCapture", v->piecePromotionOnCapture); parse_attribute("mandatoryPawnPromotion", v->mandatoryPawnPromotion); parse_attribute("mandatoryPiecePromotion", v->mandatoryPiecePromotion); parse_attribute("pieceDemotion", v->pieceDemotion); parse_attribute("blastOnCapture", v->blastOnCapture); parse_attribute("doubleStep", v->doubleStep); parse_attribute("doubleStepRank", v->doubleStepRank); parse_attribute("doubleStepRankMin", v->doubleStepRankMin); parse_attribute("enPassantRegion", v->enPassantRegion); parse_attribute("castling", v->castling); parse_attribute("castlingDroppedPiece", v->castlingDroppedPiece); parse_attribute("castlingKingsideFile", v->castlingKingsideFile); parse_attribute("castlingQueensideFile", v->castlingQueensideFile); parse_attribute("castlingRank", v->castlingRank); parse_attribute("castlingKingFile", v->castlingKingFile); parse_attribute("castlingKingPiece", v->castlingKingPiece, v->pieceToChar); parse_attribute("castlingRookPiece", v->castlingRookPiece, v->pieceToChar); parse_attribute("checking", v->checking); parse_attribute("dropChecks", v->dropChecks); parse_attribute("mustCapture", v->mustCapture); parse_attribute("mustDrop", v->mustDrop); parse_attribute("mustDropType", v->mustDropType, v->pieceToChar); parse_attribute("pieceDrops", v->pieceDrops); parse_attribute("dropLoop", v->dropLoop); parse_attribute("capturesToHand", v->capturesToHand); parse_attribute("firstRankPawnDrops", v->firstRankPawnDrops); parse_attribute("promotionZonePawnDrops", v->promotionZonePawnDrops); parse_attribute("dropOnTop", v->dropOnTop); parse_attribute("enclosingDrop", v->enclosingDrop); parse_attribute("enclosingDropStart", v->enclosingDropStart); parse_attribute("whiteDropRegion", v->whiteDropRegion); parse_attribute("blackDropRegion", v->blackDropRegion); parse_attribute("sittuyinRookDrop", v->sittuyinRookDrop); parse_attribute("dropOppositeColoredBishop", v->dropOppositeColoredBishop); parse_attribute("dropPromoted", v->dropPromoted); parse_attribute("dropNoDoubled", v->dropNoDoubled, v->pieceToChar); parse_attribute("dropNoDoubledCount", v->dropNoDoubledCount); parse_attribute("immobilityIllegal", v->immobilityIllegal); parse_attribute("gating", v->gating); parse_attribute("arrowGating", v->arrowGating); parse_attribute("seirawanGating", v->seirawanGating); parse_attribute("cambodianMoves", v->cambodianMoves); parse_attribute("diagonalLines", v->diagonalLines); parse_attribute("pass", v->pass); parse_attribute("passOnStalemate", v->passOnStalemate); parse_attribute("makpongRule", v->makpongRule); parse_attribute("flyingGeneral", v->flyingGeneral); parse_attribute("soldierPromotionRank", v->soldierPromotionRank); parse_attribute("flipEnclosedPieces", v->flipEnclosedPieces); // game end parse_attribute("nMoveRule", v->nMoveRule); parse_attribute("nFoldRule", v->nFoldRule); parse_attribute("nFoldValue", v->nFoldValue); parse_attribute("nFoldValueAbsolute", v->nFoldValueAbsolute); parse_attribute("perpetualCheckIllegal", v->perpetualCheckIllegal); parse_attribute("moveRepetitionIllegal", v->moveRepetitionIllegal); parse_attribute("stalemateValue", v->stalemateValue); parse_attribute("stalematePieceCount", v->stalematePieceCount); parse_attribute("checkmateValue", v->checkmateValue); parse_attribute("shogiPawnDropMateIllegal", v->shogiPawnDropMateIllegal); parse_attribute("shatarMateRule", v->shatarMateRule); parse_attribute("bikjangRule", v->bikjangRule); parse_attribute("extinctionValue", v->extinctionValue); parse_attribute("extinctionClaim", v->extinctionClaim); parse_attribute("extinctionPseudoRoyal", v->extinctionPseudoRoyal); // extinction piece types const auto& it_ext = config.find("extinctionPieceTypes"); if (it_ext != config.end()) { v->extinctionPieceTypes = {}; char token; size_t idx = 0; std::stringstream ss(it_ext->second); while (ss >> token && (idx = token == '*' ? size_t(ALL_PIECES) : v->pieceToChar.find(toupper(token))) != std::string::npos) v->extinctionPieceTypes.insert(PieceType(idx)); if (DoCheck && idx == std::string::npos) std::cerr << "extinctionPieceTypes - Invalid piece type: " << token << std::endl; } parse_attribute("extinctionPieceCount", v->extinctionPieceCount); parse_attribute("extinctionOpponentPieceCount", v->extinctionOpponentPieceCount); parse_attribute("flagPiece", v->flagPiece, v->pieceToChar); parse_attribute("whiteFlag", v->whiteFlag); parse_attribute("blackFlag", v->blackFlag); parse_attribute("flagMove", v->flagMove); parse_attribute("checkCounting", v->checkCounting); parse_attribute("connectN", v->connectN); parse_attribute("materialCounting", v->materialCounting); parse_attribute("countingRule", v->countingRule); // Report invalid options if (DoCheck) { const std::set& parsedKeys = config.get_comsumed_keys(); for (const auto& it : config) if (parsedKeys.find(it.first) == parsedKeys.end()) std::cerr << "Invalid option: " << it.first << std::endl; } // Check consistency if (DoCheck) { // pieces for (PieceType pt : v->pieceTypes) { for (Color c : {WHITE, BLACK}) if (std::count(v->pieceToChar.begin(), v->pieceToChar.end(), v->pieceToChar[make_piece(c, pt)]) != 1) std::cerr << piece_name(pt) << " - Ambiguous piece character: " << v->pieceToChar[make_piece(c, pt)] << std::endl; } // startFen if (FEN::validate_fen(v->startFen, v, v->chess960) != FEN::FEN_OK) std::cerr << "startFen - Invalid starting position: " << v->startFen << std::endl; // pieceToCharTable if (v->pieceToCharTable != "-") { const std::string fenBoard = v->startFen.substr(0, v->startFen.find(' ')); std::stringstream ss(v->pieceToCharTable); char token; while (ss >> token) if (isalpha(token) && v->pieceToChar.find(toupper(token)) == std::string::npos) std::cerr << "pieceToCharTable - Invalid piece type: " << token << std::endl; for (PieceType pt : v->pieceTypes) { char ptl = tolower(v->pieceToChar[pt]); if (v->pieceToCharTable.find(ptl) == std::string::npos && fenBoard.find(ptl) != std::string::npos) std::cerr << "pieceToCharTable - Missing piece type: " << ptl << std::endl; char ptu = toupper(v->pieceToChar[pt]); if (v->pieceToCharTable.find(ptu) == std::string::npos && fenBoard.find(ptu) != std::string::npos) std::cerr << "pieceToCharTable - Missing piece type: " << ptu << std::endl; } } // Contradictory options if (!v->checking && v->checkCounting) std::cerr << "checkCounting=true requires checking=true." << std::endl; if (v->doubleStep && v->doubleStepRankMin > v->doubleStepRank) std::cerr << "Inconsistent settings: doubleStepRankMin > doubleStepRank." << std::endl; if (v->castling && v->castlingRank > v->maxRank) std::cerr << "Inconsistent settings: castlingRank > maxRank." << std::endl; if (v->castling && v->castlingQueensideFile > v->castlingKingsideFile) std::cerr << "Inconsistent settings: castlingQueensideFile > castlingKingsideFile." << std::endl; // Check for limitations // Options incompatible with royal kings if (v->pieceTypes.find(KING) != v->pieceTypes.end()) { if (v->blastOnCapture) std::cerr << "Can not use kings with blastOnCapture." << std::endl; if (v->flipEnclosedPieces) std::cerr << "Can not use kings with flipEnclosedPieces." << std::endl; // We can not fully check support for custom king movements at this point, // since custom pieces are only initialized on loading of the variant. // We will assume this is valid, but it might cause problems later if it's not. if (!is_custom(v->kingType)) { const PieceInfo* pi = pieceMap.find(v->kingType)->second; if ( pi->hopper[MODALITY_QUIET].size() || pi->hopper[MODALITY_CAPTURE].size() || std::any_of(pi->steps[MODALITY_CAPTURE].begin(), pi->steps[MODALITY_CAPTURE].end(), [](const std::pair& d) { return d.second; })) std::cerr << piece_name(v->kingType) << " is not supported as kingType." << std::endl; } } } return v; } template Variant* VariantParser::parse(); template Variant* VariantParser::parse(); template Variant* VariantParser::parse(Variant* v); template Variant* VariantParser::parse(Variant* v); } // namespace Stockfish Fairy-Stockfish-fairy_sf_14_0_1_xq/src/parser.h000066400000000000000000000034241414571233100215760ustar00rootroot00000000000000/* Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish Copyright (C) 2018-2021 Fabian Fichter Fairy-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. Fairy-Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PARSER_H_INCLUDED #define PARSER_H_INCLUDED #include #include "variant.h" namespace Stockfish { class Config : public std::map { public: Config::iterator find (const std::string& s) { constexpr bool PrintOptions = false; // print config options? if (PrintOptions) std::cout << s << std::endl; consumedKeys.insert(s); return std::map::find(s); } const std::set& get_comsumed_keys() { return consumedKeys; } private: std::set consumedKeys = {}; }; template class VariantParser { public: VariantParser(const Config& c) : config (c) {}; Variant* parse(); Variant* parse(Variant* v); private: Config config; template void parse_attribute(const std::string& key, T& target); void parse_attribute(const std::string& key, PieceType& target, std::string pieceToChar); }; } // namespace Stockfish #endif // #ifndef PARSER_H_INCLUDEDFairy-Stockfish-fairy_sf_14_0_1_xq/src/partner.cpp000066400000000000000000000122311414571233100223040ustar00rootroot00000000000000/* Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish Copyright (C) 2018-2021 Fabian Fichter Fairy-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. Fairy-Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include "partner.h" #include "thread.h" #include "uci.h" namespace Stockfish { PartnerHandler Partner; // Global object void PartnerHandler::reset() { fast = sitRequested = partnerDead = weDead = weWin = weVirtualWin = weVirtualLoss = false; time = opptime = 0; } template void PartnerHandler::ptell(const std::string& message) { if (p == ALL_PARTNERS || (p == FAIRY && isFairy) || (p == HUMAN && !isFairy)) sync_cout << "tellics ptell " << message << sync_endl; } void PartnerHandler::parse_partner(std::istringstream& is) { std::string token; if (is >> token) // handshake to identify Fairy-Stockfish ptell("partner Fairy-Stockfish is an engine. Ask it 'help' for supported commands."); else isFairy = false; } void PartnerHandler::parse_ptell(std::istringstream& is, const Position& pos) { std::string token; is >> token; if (token == "partner") { // handshake to identify Fairy-Stockfish if (is >> token && token == "Fairy-Stockfish") isFairy = true; } else if (token == "help") { if (!(is >> token)) { ptell("I listen to the commands help, sit, go, move, fast, slow, dead, x, time, and otim."); ptell("Tell 'help sit', etc. for details."); } else if (token == "sit") ptell("After receiving 'sit', I stop moving. Also see 'go'."); else if (token == "go") ptell("After receiving 'go', I will no longer sit."); else if (token == "move") { ptell("After receiving 'move', I will move immediately." ); ptell("If you specify a valid move, e.g., 'move e2e4', I will play it."); } else if (token == "fast") ptell("After receiving 'go', I will play fast."); else if (token == "slow") ptell("After receiving 'slow', I will play at normal speed."); else if (token == "dead") ptell("After receiving 'dead', I assume you are dead and I play fast."); else if (token == "x") ptell("After receiving 'x', I assume I can play normally again."); else if (token == "time") { ptell("'time' together with your time in centiseconds allows me to consider your time."); ptell("E.g., 'time 1000' for 10 seconds."); } else if (token == "otim") ptell("'otim' together with your opponent's time in centiseconds allows me to consider his time."); } else if (!pos.two_boards()) return; else if (token == "sit") { // Avoid deadlocking sit if (!isFairy || !weWin) sitRequested = true; ptell("I sit, tell me 'go' to continue"); } else if (token == "go") { sitRequested = false; Threads.stop = true; } else if (token == "move") { if (is >> token) { // if the given move is valid and we can still abort the search, play it Move move = UCI::to_move(pos, token); if (move && !Threads.abort.exchange(true)) moveRequested = move; else ptell("sorry, not possible"); } else // Move immediately on request Threads.stop = true; } else if (token == "fast") { fast = true; ptell("I play fast, tell me 'slow' to play normally again"); } else if (token == "slow") { fast = false; ptell("I play at normal speed again."); } else if (token == "dead") { partnerDead = true; ptell("I play fast, tell me 'x' if you are no longer dead."); } else if (token == "x") { partnerDead = false; sitRequested = false; ptell("I play normally again"); } else if (token == "time") { int value; time = (is >> value) ? value * 10 : 0; } else if (token == "otim") { int value; opptime = (is >> value) ? value * 10 : 0; } } template void PartnerHandler::ptell(const std::string&); template void PartnerHandler::ptell(const std::string&); template void PartnerHandler::ptell(const std::string&); } // namespace Stockfish Fairy-Stockfish-fairy_sf_14_0_1_xq/src/partner.h000066400000000000000000000031411414571233100217510ustar00rootroot00000000000000/* Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish Copyright (C) 2018-2021 Fabian Fichter Fairy-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. Fairy-Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PARTNER_H_INCLUDED #define PARTNER_H_INCLUDED #include #include #include "misc.h" #include "position.h" namespace Stockfish { /// PartnerHandler manages the communication with the partner /// in games played on two boards, such as bughouse. enum PartnerType { HUMAN, FAIRY, ALL_PARTNERS }; struct PartnerHandler { void reset(); template void ptell(const std::string& message); void parse_partner(std::istringstream& is); void parse_ptell(std::istringstream& is, const Position& pos); std::atomic isFairy; std::atomic fast, sitRequested, partnerDead, weDead, weWin, weVirtualWin, weVirtualLoss; std::atomic time, opptime; Move moveRequested; }; extern PartnerHandler Partner; } // namespace Stockfish #endif // #ifndef PARTNER_H_INCLUDEDFairy-Stockfish-fairy_sf_14_0_1_xq/src/pawns.cpp000066400000000000000000000271451414571233100217730ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include "bitboard.h" #include "pawns.h" #include "position.h" #include "thread.h" namespace Stockfish { namespace { #define V Value #define S(mg, eg) make_score(mg, eg) // Pawn penalties constexpr Score Backward = S( 9, 22); constexpr Score Doubled = S(13, 51); constexpr Score DoubledEarly = S(20, 7); constexpr Score Isolated = S( 3, 15); constexpr Score WeakLever = S( 4, 58); constexpr Score WeakUnopposed = S(13, 24); // Bonus for blocked pawns at 5th or 6th rank constexpr Score BlockedPawn[RANK_NB - 5] = { S(-17, -6), S(-9, 2) }; constexpr Score BlockedStorm[RANK_NB] = { S(0, 0), S(0, 0), S(75, 78), S(-8, 16), S(-6, 10), S(-6, 6), S(0, 2) }; // Connected pawn bonus constexpr int Connected[RANK_NB] = { 0, 5, 7, 11, 23, 48, 87 }; // 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( -5), V( 82), V( 92), V( 54), V( 36), V( 22), V( 28) }, { V(-44), V( 63), V( 33), V(-50), V(-30), V(-12), V( -62) }, { V(-11), V( 77), V( 22), V( -6), V( 31), V( 8), V( -45) }, { V(-39), V(-12), V(-29), V(-50), V(-43), V(-68), V(-164) } }; // 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( 87), V(-288), V(-168), V( 96), V( 47), V( 44), V( 46) }, { V( 42), V( -25), V( 120), V( 45), V( 34), V( -9), V( 24) }, { V( -8), V( 51), V( 167), V( 35), V( -4), V(-16), V(-12) }, { V(-17), V( -13), V( 100), V( 4), V( 9), V(-16), V(-31) } }; // KingOnFile[semi-open Us][semi-open Them] contains bonuses/penalties // for king when the king is on a semi-open or open file. constexpr Score KingOnFile[2][2] = {{ S(-21,10), S(-7, 1) }, { S( 0,-3), S( 9,-4) }}; // Variant bonuses constexpr int HordeConnected[2][RANK_NB] = {{ 5, 10, 20, 55, 55, 100, 80 }, { -10, 5, -10, 5, 25, 40, 30 }}; #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 Score evaluate(const Position& pos, Pawns::Entry* e) { constexpr Color Them = ~Us; constexpr Direction Up = pawn_push(Us); constexpr Direction Down = -Up; Bitboard neighbours, stoppers, support, phalanx, opposed; Bitboard lever, leverPush, blocked; Square s; bool backward, passed, doubled; Score score = SCORE_ZERO; Bitboard b = pos.pieces(Us, PAWN); Bitboard ourPawns = pos.pieces( Us, PAWN); Bitboard theirPawns = pos.pieces(Them, PAWN); Bitboard doubleAttackThem = pawn_double_attacks_bb(theirPawns); e->passedPawns[Us] = 0; e->kingSquares[Us] = SQ_NONE; e->pawnAttacks[Us] = e->pawnAttacksSpan[Us] = pawn_attacks_bb(ourPawns); e->blockedCount += popcount(shift(ourPawns) & (theirPawns | doubleAttackThem)); // Loop through all pawns of the current color and score each pawn while (b) { s = pop_lsb(b); assert(pos.piece_on(s) == make_piece(Us, PAWN)); Rank r = relative_rank(Us, s, pos.max_rank()); // Flag the pawn opposed = theirPawns & forward_file_bb(Us, s); blocked = is_ok(s + Up) ? theirPawns & (s + Up) : Bitboard(0); stoppers = theirPawns & passed_pawn_span(Us, s); lever = theirPawns & pawn_attacks_bb(Us, s); leverPush = relative_rank(Them, s, pos.max_rank()) > RANK_1 ? theirPawns & pawn_attacks_bb(Us, s + Up) : Bitboard(0); doubled = r > RANK_1 ? ourPawns & (s - Up) : Bitboard(0); neighbours = ourPawns & adjacent_files_bb(s); phalanx = neighbours & rank_bb(s); support = r > RANK_1 ? neighbours & rank_bb(s - Up) : Bitboard(0); if (doubled) { // Additional doubled penalty if none of their pawns is fixed if (!(ourPawns & shift(theirPawns | pawn_attacks_bb(theirPawns)))) score -= DoubledEarly; } // A pawn is backward when it is behind all pawns of the same color on // the adjacent files and cannot safely advance. backward = is_ok(s + Up) && !(neighbours & forward_ranks_bb(Them, s + Up)) && (stoppers & 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(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 && is_ok(s + Up) && (r < pos.promotion_rank() || !pos.mandatory_pawn_promotion())) e->passedPawns[Us] |= s; // Score this pawn if ((support | phalanx) && (r < pos.promotion_rank() || !pos.mandatory_pawn_promotion())) { int v = Connected[r] * (2 + bool(phalanx) - bool(opposed)) * (r == RANK_2 && pos.captures_to_hand() ? 3 : 1) + 22 * popcount(support); if (pos.count(Us) > popcount(pos.board_bb()) / 4) v = popcount(support | phalanx) * HordeConnected[bool(opposed)][r]; 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 * (1 + 2 * pos.must_capture()); else score -= Isolated * (1 + 2 * pos.must_capture()) + WeakUnopposed * !opposed; } else if (backward) score -= Backward + WeakUnopposed * !opposed * bool(~(FileABB | FileHBB) & s); if (!support) score -= Doubled * doubled + WeakLever * more_than_one(lever); if (blocked && r >= RANK_5) score += BlockedPawn[r - RANK_5]; } // Double pawn evaluation if there are no non-pawn pieces if (pos.count(Us) == pos.count(Us)) score = score * 2; 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 && !pos.pieces(SHOGI_PAWN)) return e; e->key = key; e->blockedCount = 0; e->scores[WHITE] = evaluate(pos, e); e->scores[BLACK] = evaluate(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 Score Entry::evaluate_shelter(const Position& pos, Square ksq) const { constexpr Color Them = ~Us; Bitboard b = pos.pieces(PAWN, SHOGI_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(pos.max_file() - 1)); 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), pos.max_rank()) : 0; b = theirPawns & file_bb(f); int theirRank = b ? relative_rank(Us, frontmost_sq(Them, b), pos.max_rank()) : 0; int d = std::min(File(edge_distance(f, pos.max_file())), FILE_D); bonus += make_score(ShelterStrength[d][ourRank], 0) * (1 + (pos.captures_to_hand() && ourRank <= RANK_2) + (pos.check_counting() && d == 0 && ourRank == RANK_2)); if (ourRank && (ourRank == theirRank - 1)) bonus -= BlockedStorm[theirRank]; else bonus -= make_score(UnblockedStorm[d][theirRank], 0); } // King On File bonus -= KingOnFile[pos.is_on_semiopen_file(Us, ksq)][pos.is_on_semiopen_file(Them, ksq)]; return bonus; } /// Entry::do_king_safety() calculates a bonus for king safety. It is called only /// when king square changes, which is about 20% of total king_safety() calls. template Score Entry::do_king_safety(const Position& pos) { Square ksq = pos.square(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(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(pos, make_square(pos.castling_kingside_file(), pos.castling_rank(Us))), compare); if (pos.can_castle(Us & QUEEN_SIDE)) shelter = std::max(shelter, evaluate_shelter(pos, make_square(pos.castling_queenside_file(), pos.castling_rank(Us))), 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(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(const Position& pos); template Score Entry::do_king_safety(const Position& pos); } // namespace Pawns } // namespace Stockfish Fairy-Stockfish-fairy_sf_14_0_1_xq/src/pawns.h000066400000000000000000000044271414571233100214360ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PAWNS_H_INCLUDED #define PAWNS_H_INCLUDED #include "misc.h" #include "position.h" #include "types.h" namespace Stockfish::Pawns { /// Pawns::Entry contains various information about a pawn structure. A lookup /// to the pawn hash table (performed by calling the probe function) returns a /// pointer to an Entry object. struct Entry { Score pawn_score(Color c) const { return scores[c]; } Bitboard pawn_attacks(Color c) const { return pawnAttacks[c]; } Bitboard passed_pawns(Color c) const { return passedPawns[c]; } Bitboard pawn_attacks_span(Color c) const { return pawnAttacksSpan[c]; } int passed_count() const { return popcount(passedPawns[WHITE] | passedPawns[BLACK]); } int blocked_count() const { return blockedCount; } template Score king_safety(const Position& pos) { return kingSquares[Us] == pos.square(Us) && castlingRights[Us] == pos.castling_rights(Us) ? kingSafety[Us] : (kingSafety[Us] = do_king_safety(pos)); } template Score do_king_safety(const Position& pos); template 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 Table; Entry* probe(const Position& pos); } // namespace Stockfish::Pawns #endif // #ifndef PAWNS_H_INCLUDED Fairy-Stockfish-fairy_sf_14_0_1_xq/src/piece.cpp000066400000000000000000000244771414571233100217350ustar00rootroot00000000000000/* Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish Copyright (C) 2018-2021 Fabian Fichter Fairy-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. Fairy-Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include "types.h" #include "piece.h" namespace Stockfish { PieceMap pieceMap; // Global object namespace { std::map>> leaperAtoms = { {'W', {std::make_pair(1, 0)}}, {'F', {std::make_pair(1, 1)}}, {'D', {std::make_pair(2, 0)}}, {'N', {std::make_pair(2, 1)}}, {'A', {std::make_pair(2, 2)}}, {'H', {std::make_pair(3, 0)}}, {'L', {std::make_pair(3, 1)}}, {'C', {std::make_pair(3, 1)}}, {'J', {std::make_pair(3, 2)}}, {'Z', {std::make_pair(3, 2)}}, {'G', {std::make_pair(3, 3)}}, {'K', {std::make_pair(1, 0), std::make_pair(1, 1)}}, }; std::map>> riderAtoms = { {'R', {std::make_pair(1, 0)}}, {'B', {std::make_pair(1, 1)}}, {'Q', {std::make_pair(1, 0), std::make_pair(1, 1)}}, }; const std::string verticals = "fbvh"; const std::string horizontals = "rlsh"; // from_betza creates a piece by parsing Betza notation // https://en.wikipedia.org/wiki/Betza%27s_funny_notation PieceInfo* from_betza(const std::string& betza, const std::string& name) { PieceInfo* p = new PieceInfo(); p->name = name; p->betza = betza; std::vector moveModalities = {}; bool hopper = false; bool rider = false; bool lame = false; int distance = 0; std::vector prelimDirections = {}; for (std::string::size_type i = 0; i < betza.size(); i++) { char c = betza[i]; // Modality if (c == 'm' || c == 'c') moveModalities.push_back(c == 'c' ? MODALITY_CAPTURE : MODALITY_QUIET); // Hopper else if (c == 'p' || c == 'g') { hopper = true; // Grasshopper if (c == 'g') distance = 1; } // Lame leaper else if (c == 'n') lame = true; // Directional modifiers else if (verticals.find(c) != std::string::npos || horizontals.find(c) != std::string::npos) { if (i + 1 < betza.size()) { char c2 = betza[i+1]; // Can modifiers be combined? if ( c2 == c || (verticals.find(c) != std::string::npos && horizontals.find(c2) != std::string::npos) || (horizontals.find(c) != std::string::npos && verticals.find(c2) != std::string::npos)) { prelimDirections.push_back(std::string(1, c) + c2); i++; continue; } } prelimDirections.push_back(std::string(2, c)); } // Move atom else if (leaperAtoms.find(c) != leaperAtoms.end() || riderAtoms.find(c) != riderAtoms.end()) { const auto& atoms = riderAtoms.find(c) != riderAtoms.end() ? riderAtoms.find(c)->second : leaperAtoms.find(c)->second; // Check for rider if (riderAtoms.find(c) != riderAtoms.end()) rider = true; if (i + 1 < betza.size() && (isdigit(betza[i+1]) || betza[i+1] == c)) { rider = true; // limited distance riders if (isdigit(betza[i+1])) distance = betza[i+1] - '0'; i++; } if (!rider && lame) distance = -1; // No modality qualifier means m+c if (moveModalities.size() == 0) { moveModalities.push_back(MODALITY_QUIET); moveModalities.push_back(MODALITY_CAPTURE); } // Define moves for (const auto& atom : atoms) { std::vector directions = {}; // Split directions for orthogonal pieces // This is required e.g. to correctly interpret fsW for soldiers for (auto s : prelimDirections) if (atoms.size() == 1 && atom.second == 0 && s[0] != s[1]) { directions.push_back(std::string(2, s[0])); directions.push_back(std::string(2, s[1])); } else directions.push_back(s); // Add moves for (auto modality : moveModalities) { auto& v = hopper ? p->hopper[modality] : rider ? p->slider[modality] : p->steps[modality]; auto has_dir = [&](std::string s) { return std::find(directions.begin(), directions.end(), s) != directions.end(); }; if (directions.size() == 0 || has_dir("ff") || has_dir("vv") || has_dir("rf") || has_dir("rv") || has_dir("fh") || has_dir("rh") || has_dir("hr")) v[Direction(atom.first * FILE_NB + atom.second)] = distance; if (directions.size() == 0 || has_dir("bb") || has_dir("vv") || has_dir("lb") || has_dir("lv") || has_dir("bh") || has_dir("lh") || has_dir("hr")) v[Direction(-atom.first * FILE_NB - atom.second)] = distance; if (directions.size() == 0 || has_dir("rr") || has_dir("ss") || has_dir("br") || has_dir("bs") || has_dir("bh") || has_dir("lh") || has_dir("hr")) v[Direction(-atom.second * FILE_NB + atom.first)] = distance; if (directions.size() == 0 || has_dir("ll") || has_dir("ss") || has_dir("fl") || has_dir("fs") || has_dir("fh") || has_dir("rh") || has_dir("hr")) v[Direction(atom.second * FILE_NB - atom.first)] = distance; if (directions.size() == 0 || has_dir("rr") || has_dir("ss") || has_dir("fr") || has_dir("fs") || has_dir("fh") || has_dir("rh") || has_dir("hl")) v[Direction(atom.second * FILE_NB + atom.first)] = distance; if (directions.size() == 0 || has_dir("ll") || has_dir("ss") || has_dir("bl") || has_dir("bs") || has_dir("bh") || has_dir("lh") || has_dir("hl")) v[Direction(-atom.second * FILE_NB - atom.first)] = distance; if (directions.size() == 0 || has_dir("bb") || has_dir("vv") || has_dir("rb") || has_dir("rv") || has_dir("bh") || has_dir("rh") || has_dir("hl")) v[Direction(-atom.first * FILE_NB + atom.second)] = distance; if (directions.size() == 0 || has_dir("ff") || has_dir("vv") || has_dir("lf") || has_dir("lv") || has_dir("fh") || has_dir("lh") || has_dir("hl")) v[Direction(atom.first * FILE_NB - atom.second)] = distance; } } // Reset state moveModalities.clear(); prelimDirections.clear(); hopper = false; rider = false; } } return p; } // Special multi-leg betza description for Janggi elephant PieceInfo* janggi_elephant_piece() { PieceInfo* p = from_betza("nZ", "janggiElephant"); p->betza = "mafsmafW"; // for compatiblity with XBoard/Winboard return p; } } void PieceMap::init(const Variant* v) { clear_all(); add(PAWN, from_betza("fmWfceF", "pawn")); add(KNIGHT, from_betza("N", "knight")); add(BISHOP, from_betza("B", "bishop")); add(ROOK, from_betza("R", "rook")); add(QUEEN, from_betza("Q", "queen")); add(FERS, from_betza("F", "fers")); add(ALFIL, from_betza("A", "alfil")); add(FERS_ALFIL, from_betza("FA", "fersAlfil")); add(SILVER, from_betza("FfW", "silver")); add(AIWOK, from_betza("RNF", "aiwok")); add(BERS, from_betza("RF", "bers")); add(ARCHBISHOP, from_betza("BN", "archbishop")); add(CHANCELLOR, from_betza("RN", "chancellor")); add(AMAZON, from_betza("QN", "amazon")); add(KNIBIS, from_betza("mNcB", "knibis")); add(BISKNI, from_betza("mBcN", "biskni")); add(KNIROO, from_betza("mNcR", "kniroo")); add(ROOKNI, from_betza("mRcN", "rookni")); add(SHOGI_PAWN, from_betza("fW", "shogiPawn")); add(LANCE, from_betza("fR", "lance")); add(SHOGI_KNIGHT, from_betza("fN", "shogiKnight")); add(GOLD, from_betza("WfF", "gold")); add(DRAGON_HORSE, from_betza("BW", "dragonHorse")); add(CLOBBER_PIECE, from_betza("cW", "clobber")); add(BREAKTHROUGH_PIECE, from_betza("fmWfF", "breakthrough")); add(IMMOBILE_PIECE, from_betza("", "immobile")); add(CANNON, from_betza("mRcpR", "cannon")); add(JANGGI_CANNON, from_betza("pR", "janggiCannon")); add(SOLDIER, from_betza("fsW", "soldier")); add(HORSE, from_betza("nN", "horse")); add(ELEPHANT, from_betza("nA", "elephant")); add(JANGGI_ELEPHANT, janggi_elephant_piece()); add(BANNER, from_betza("RcpRnN", "banner")); add(WAZIR, from_betza("W", "wazir")); add(COMMONER, from_betza("K", "commoner")); add(CENTAUR, from_betza("KN", "centaur")); add(KING, from_betza("K", "king")); // Add custom pieces for (PieceType pt = CUSTOM_PIECES; pt <= CUSTOM_PIECES_END; ++pt) add(pt, from_betza(v != nullptr ? v->customPiece[pt - CUSTOM_PIECES] : "", "")); } void PieceMap::add(PieceType pt, const PieceInfo* p) { insert(std::pair(pt, p)); } void PieceMap::clear_all() { for (auto const& element : *this) delete element.second; clear(); } } // namespace Stockfish Fairy-Stockfish-fairy_sf_14_0_1_xq/src/piece.h000066400000000000000000000033301414571233100213630ustar00rootroot00000000000000/* Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish Copyright (C) 2018-2021 Fabian Fichter Fairy-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. Fairy-Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PIECE_H_INCLUDED #define PIECE_H_INCLUDED #include #include #include "types.h" #include "variant.h" namespace Stockfish { enum MoveModality {MODALITY_QUIET, MODALITY_CAPTURE, MOVE_MODALITY_NB}; /// PieceInfo struct stores information about the piece movements. struct PieceInfo { std::string name = ""; std::string betza = ""; std::map steps[MOVE_MODALITY_NB] = {}; std::map slider[MOVE_MODALITY_NB] = {}; std::map hopper[MOVE_MODALITY_NB] = {}; }; struct PieceMap : public std::map { void init(const Variant* v = nullptr); void add(PieceType pt, const PieceInfo* v); void clear_all(); }; extern PieceMap pieceMap; inline std::string piece_name(PieceType pt) { return is_custom(pt) ? "customPiece" + std::to_string(pt - CUSTOM_PIECES + 1) : pieceMap.find(pt)->second->name; } } // namespace Stockfish #endif // #ifndef PIECE_H_INCLUDED Fairy-Stockfish-fairy_sf_14_0_1_xq/src/position.cpp000066400000000000000000002737301414571233100225120ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include // For offsetof() #include // For std::memset, std::memcmp #include #include #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 Stockfish { namespace Zobrist { Key psq[PIECE_NB][SQUARE_NB]; Key enpassant[FILE_NB]; Key castling[CASTLING_RIGHT_NB]; Key side, noPawns; Key inHand[PIECE_NB][SQUARE_NB]; Key checks[COLOR_NB][CHECKS_NB]; } /// operator<<(Position) returns an ASCII representation of the position std::ostream& operator<<(std::ostream& os, const Position& pos) { os << "\n "; for (File f = FILE_A; f <= pos.max_file(); ++f) os << "+---"; os << "+\n"; for (Rank r = pos.max_rank(); r >= RANK_1; --r) { for (File f = FILE_A; f <= pos.max_file(); ++f) if (pos.unpromoted_piece_on(make_square(f, r))) os << " |+" << pos.piece_to_char()[pos.unpromoted_piece_on(make_square(f, r))]; else os << " | " << pos.piece_to_char()[pos.piece_on(make_square(f, r))]; os << " |" << (1 + r); if (r == pos.max_rank() || r == RANK_1) { Color c = r == RANK_1 ? WHITE : BLACK; if (c == pos.side_to_move()) os << " *"; else os << " "; if (pos.piece_drops() || pos.seirawan_gating() || pos.arrow_gating()) { os << " ["; for (PieceType pt = KING; pt >= PAWN; --pt) os << std::string(pos.count_in_hand(c, pt), pos.piece_to_char()[make_piece(c, pt)]); os << "]"; } } os << "\n "; for (File f = FILE_A; f <= pos.max_file(); ++f) os << "+---"; os << "+\n"; } for (File f = FILE_A; f <= pos.max_file(); ++f) os << " " << char('a' + f); os << "\n"; os << "\nFen: " << pos.fen() << "\nSfen: " << pos.fen(true) << "\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(pos, pop_lsb(b)) << " "; if ( int(Tablebases::MaxCardinality) >= popcount(pos.pieces()) && Options["UCI_Variant"] == "chess" && !pos.can_castle(ANY_CASTLING)) { StateInfo st; ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); Position p; p.set(pos.variant(), 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 #ifdef LARGEBOARDS inline int H1(Key h) { return h & 0x7fff; } inline int H2(Key h) { return (h >> 16) & 0x7fff; } #else inline int H1(Key h) { return h & 0x1fff; } inline int H2(Key h) { return (h >> 16) & 0x1fff; } #endif // Cuckoo tables with Zobrist hashes of valid reversible moves, and the moves themselves #ifdef LARGEBOARDS Key cuckoo[65536]; Move cuckooMove[65536]; #else Key cuckoo[8192]; Move cuckooMove[8192]; #endif /// Position::init() initializes at startup the various arrays used to compute hash keys void Position::init() { PRNG rng(1070372); for (Color c : {WHITE, BLACK}) for (PieceType pt = PAWN; pt <= KING; ++pt) for (Square s = SQ_A1; s <= SQ_MAX; ++s) Zobrist::psq[make_piece(c, pt)][s] = rng.rand(); for (File f = FILE_A; f <= FILE_MAX; ++f) Zobrist::enpassant[f] = rng.rand(); for (int cr = NO_CASTLING; cr <= ANY_CASTLING; ++cr) Zobrist::castling[cr] = rng.rand(); Zobrist::side = rng.rand(); Zobrist::noPawns = rng.rand(); for (Color c : {WHITE, BLACK}) for (int n = 0; n < CHECKS_NB; ++n) Zobrist::checks[c][n] = rng.rand(); for (Color c : {WHITE, BLACK}) for (PieceType pt = PAWN; pt <= KING; ++pt) for (int n = 0; n < SQUARE_NB; ++n) Zobrist::inHand[make_piece(c, pt)][n] = rng.rand(); // Prepare the cuckoo tables std::memset(cuckoo, 0, sizeof(cuckoo)); std::memset(cuckooMove, 0, sizeof(cuckooMove)); int count = 0; for (Color c : {WHITE, BLACK}) for (PieceType pt = KNIGHT; pt <= QUEEN || pt == KING; pt != QUEEN ? ++pt : pt = KING) { Piece pc = make_piece(c, pt); for (Square s1 = SQ_A1; s1 <= SQ_MAX; ++s1) for (Square s2 = Square(s1 + 1); s2 <= SQ_MAX; ++s2) if ((type_of(pc) != PAWN) && (attacks_bb(c, 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++; } } #ifdef LARGEBOARDS assert(count == 9344); #else assert(count == 3668); #endif } /// 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 Variant* v, const string& fenStr, bool isChess960, StateInfo* si, Thread* th, bool sfen) { /* 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; std::istringstream ss(fenStr); std::memset(this, 0, sizeof(Position)); std::memset(si, 0, sizeof(StateInfo)); st = si; var = v; ss >> std::noskipws; Square sq = SQ_A1 + max_rank() * NORTH; // 1. Piece placement while ((ss >> token) && !isspace(token)) { if (isdigit(token)) { #ifdef LARGEBOARDS if (isdigit(ss.peek())) { sq += 10 * (token - '0') * EAST; ss >> token; } #endif sq += (token - '0') * EAST; // Advance the given number of files } else if (token == '/') { sq += 2 * SOUTH + (FILE_MAX - max_file()) * EAST; if (!is_ok(sq)) break; } else if ((idx = piece_to_char().find(token)) != string::npos || (idx = piece_to_char_synonyms().find(token)) != string::npos) { if (ss.peek() == '~') ss >> token; put_piece(Piece(idx), sq, token == '~'); ++sq; } // Promoted shogi pieces else if (token == '+' && (idx = piece_to_char().find(ss.peek())) != string::npos) { ss >> token; put_piece(make_piece(color_of(Piece(idx)), promoted_piece_type(type_of(Piece(idx)))), sq, true, Piece(idx)); ++sq; } // Stop before pieces in hand else if (token == '[') break; } // Pieces in hand if (!isspace(token)) while ((ss >> token) && !isspace(token)) { if (token == ']') continue; else if ((idx = piece_to_char().find(token)) != string::npos) add_to_hand(Piece(idx)); } // 2. Active color ss >> token; sideToMove = (token != (sfen ? 'w' : 'b') ? WHITE : BLACK); // Invert colors for SFEN ss >> token; // 3-4. Skip parsing castling and en passant flags if not present st->epSquare = SQ_NONE; st->castlingKingSquare[WHITE] = st->castlingKingSquare[BLACK] = SQ_NONE; if (!isdigit(ss.peek()) && !sfen) { // 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, castling_rook_piece()); token = char(toupper(token)); if (token == 'K') for (rsq = make_square(FILE_MAX, castling_rank(c)); piece_on(rsq) != rook; --rsq) {} else if (token == 'Q') for (rsq = make_square(FILE_A, castling_rank(c)); piece_on(rsq) != rook; ++rsq) {} else if (token >= 'A' && token <= 'A' + max_file()) rsq = make_square(File(token - 'A'), castling_rank(c)); else continue; // Determine castling "king" position if (castling_enabled() && st->castlingKingSquare[c] == SQ_NONE) { Bitboard castlingKings = pieces(c, castling_king_piece()) & rank_bb(castling_rank(c)); // Ambiguity resolution for 960 variants with more than one "king" // e.g., EAH means that an e-file king can castle with a- and h-file rooks st->castlingKingSquare[c] = isChess960 && piece_on(rsq) == make_piece(c, castling_king_piece()) ? rsq : castlingKings && (!more_than_one(castlingKings) || isChess960) ? lsb(castlingKings) : make_square(castling_king_file(), castling_rank(c)); } // Set gates (and skip castling rights) if (gating()) { st->gatesBB[c] |= rsq; if (token == 'K' || token == 'Q') st->gatesBB[c] |= st->castlingKingSquare[c]; // Do not set castling rights for gates unless there are no pieces in hand, // which means that the file is referring to a chess960 castling right. else if (!seirawan_gating() || count_in_hand(c, ALL_PIECES) > 0 || captures_to_hand()) continue; } if (castling_enabled() && piece_on(rsq) == rook) set_castling_right(c, rsq); } // Set castling rights for 960 gating variants if (gating() && castling_enabled()) for (Color c : {WHITE, BLACK}) if ((gates(c) & pieces(castling_king_piece())) && !castling_rights(c) && (!seirawan_gating() || count_in_hand(c, ALL_PIECES) > 0 || captures_to_hand())) { Bitboard castling_rooks = gates(c) & pieces(castling_rook_piece()); while (castling_rooks) set_castling_right(c, pop_lsb(castling_rooks)); } // counting limit if (counting_rule() && isdigit(ss.peek())) ss >> st->countingLimit; // 4. En passant square. // Ignore if square is invalid or not on side to move relative rank 6. else if ( ((ss >> col) && (col >= 'a' && col <= 'a' + max_file())) && ((ss >> row) && (row >= '1' && row <= '1' + max_rank()))) { st->epSquare = make_square(File(col - 'a'), Rank(row - '1')); #ifdef LARGEBOARDS // Consider different rank numbering in CECP if (max_rank() == RANK_10 && Options["Protocol"] == "xboard") st->epSquare += NORTH; #endif // 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 bool enpassant; 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; } } // Check counter for nCheck ss >> std::skipws >> token >> std::noskipws; if (check_counting()) { if (ss.peek() == '+') { st->checksRemaining[WHITE] = CheckCount(std::max(token - '0', 0)); ss >> token >> token; st->checksRemaining[BLACK] = CheckCount(std::max(token - '0', 0)); } else { // If check count is not provided, assume that the next check wins st->checksRemaining[WHITE] = CheckCount(1); st->checksRemaining[BLACK] = CheckCount(1); ss.putback(token); } } else ss.putback(token); // 5-6. Halfmove clock and fullmove number if (sfen) { // Pieces in hand for SFEN int handCount = 1; while ((ss >> token) && !isspace(token)) { if (token == '-') continue; else if (isdigit(token)) { handCount = token - '0'; while (isdigit(ss.peek()) && ss >> token) handCount = 10 * handCount + (token - '0'); } else if ((idx = piece_to_char().find(token)) != string::npos) { for (int i = 0; i < handCount; i++) add_to_hand(Piece(idx)); handCount = 1; } } // Move count is in ply for SFEN ss >> std::skipws >> gamePly; gamePly = std::max(gamePly - 1, 0); } else { 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); } // counting rules if (st->countingLimit && st->rule50) { st->countingPly = st->rule50; st->rule50 = 0; } // Lichess-style counter for 3check if (check_counting()) { if (ss >> token && token == '+') { ss >> token; st->checksRemaining[WHITE] = CheckCount(std::max(3 - (token - '0'), 0)); ss >> token >> token; st->checksRemaining[BLACK] = CheckCount(std::max(3 - (token - '0'), 0)); } } chess960 = isChess960 || v->chess960; tsumeMode = Options["TsumeMode"]; 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) { assert(st->castlingKingSquare[c] != SQ_NONE); Square kfrom = st->castlingKingSquare[c]; CastlingRights cr = c & (kfrom < rfrom ? KING_SIDE: QUEEN_SIDE); st->castlingRights |= cr; castlingRightsMask[kfrom] |= cr; castlingRightsMask[rfrom] |= cr; castlingRookSquare[cr] = rfrom; Square kto = make_square(cr & KING_SIDE ? castling_kingside_file() : castling_queenside_file(), castling_rank(c)); Square rto = kto + (cr & KING_SIDE ? WEST : EAST); castlingPath[cr] = (between_bb(rfrom, rto) | between_bb(kfrom, 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), count(WHITE) ? square(WHITE) : SQ_NONE, si->pinners[BLACK], BLACK); si->blockersForKing[BLACK] = slider_blockers(pieces(WHITE), count(BLACK) ? square(BLACK) : SQ_NONE, si->pinners[WHITE], WHITE); Square ksq = count(~sideToMove) ? square(~sideToMove) : SQ_NONE; // For unused piece types, the check squares are left uninitialized si->nonSlidingRiders = 0; for (PieceType pt : piece_types()) { si->checkSquares[pt] = ksq != SQ_NONE ? attacks_bb(~sideToMove, pt, ksq, pieces()) : Bitboard(0); // Collect special piece types that require slower check and evasion detection if (AttackRiderTypes[pt] & NON_SLIDING_RIDERS) si->nonSlidingRiders |= pieces(pt); } si->checkSquares[KING] = 0; si->shak = si->checkersBB & (byTypeBB[KNIGHT] | byTypeBB[ROOK] | byTypeBB[BERS]); si->bikjang = var->bikjangRule && ksq != SQ_NONE ? bool(attacks_bb(sideToMove, ROOK, ksq, pieces()) & pieces(sideToMove, KING)) : false; si->legalCapture = NO_VALUE; if (var->extinctionPseudoRoyal) { si->pseudoRoyals = 0; for (PieceType pt : extinction_piece_types()) { if (count(sideToMove, pt) <= var->extinctionPieceCount + 1) si->pseudoRoyals |= pieces(sideToMove, pt); if (count(~sideToMove, pt) <= var->extinctionPieceCount + 1) si->pseudoRoyals |= pieces(~sideToMove, pt); } } } /// 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 = count(sideToMove) ? attackers_to(square(sideToMove), ~sideToMove) : Bitboard(0); 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 (Color c : {WHITE, BLACK}) for (PieceType pt = PAWN; pt <= KING; ++pt) { Piece pc = make_piece(c, pt); for (int cnt = 0; cnt < pieceCount[pc]; ++cnt) si->materialKey ^= Zobrist::psq[pc][cnt]; if (piece_drops() || seirawan_gating() || arrow_gating()) si->key ^= Zobrist::inHand[pc][pieceCountInHand[c][pt]]; } if (check_counting()) for (Color c : {WHITE, BLACK}) si->key ^= Zobrist::checks[c][si->checksRemaining[c]]; } /// 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 n = std::to_string(FILE_NB); string fenStr = n + "/" + sides[0] + char(FILE_NB - sides[0].length() + '0') + "/" + n + "/" + n + "/" + n + "/" + n + "/" + sides[1] + char(FILE_NB - sides[1].length() + '0') + "/" + n + " w - - 0 10"; return set(variants.find("fairy")->second, 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. string Position::fen(bool sfen, bool showPromoted, int countStarted, std::string holdings) const { int emptyCnt; std::ostringstream ss; for (Rank r = max_rank(); r >= RANK_1; --r) { for (File f = FILE_A; f <= max_file(); ++f) { for (emptyCnt = 0; f <= max_file() && empty(make_square(f, r)); ++f) ++emptyCnt; if (emptyCnt) ss << emptyCnt; if (f <= max_file()) { if (unpromoted_piece_on(make_square(f, r))) // Promoted shogi pieces, e.g., +r for dragon ss << "+" << piece_to_char()[unpromoted_piece_on(make_square(f, r))]; else { ss << piece_to_char()[piece_on(make_square(f, r))]; // Set promoted pieces if (((captures_to_hand() && !drop_loop()) || showPromoted) && is_promoted(make_square(f, r))) ss << "~"; } } } if (r > RANK_1) ss << '/'; } // SFEN if (sfen) { ss << (sideToMove == WHITE ? " b " : " w "); for (Color c : {WHITE, BLACK}) for (PieceType pt = KING; pt >= PAWN; --pt) if (pieceCountInHand[c][pt] > 0) { if (pieceCountInHand[c][pt] > 1) ss << pieceCountInHand[c][pt]; ss << piece_to_char()[make_piece(c, pt)]; } if (count_in_hand(ALL_PIECES) == 0) ss << '-'; ss << " " << gamePly + 1; return ss.str(); } // pieces in hand if (piece_drops() || seirawan_gating() || arrow_gating()) { ss << '['; if (holdings != "-") ss << holdings; else for (Color c : {WHITE, BLACK}) for (PieceType pt = KING; pt >= PAWN; --pt) { assert(pieceCountInHand[c][pt] >= 0); ss << std::string(pieceCountInHand[c][pt], piece_to_char()[make_piece(c, pt)]); } ss << ']'; } ss << (sideToMove == WHITE ? " w " : " b "); // Disambiguation for chess960 "king" square if (chess960 && can_castle(WHITE_CASTLING) && popcount(pieces(WHITE, castling_king_piece()) & rank_bb(castling_rank(WHITE))) > 1) ss << char('A' + castling_king_square(WHITE)); 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 (gating() && gates(WHITE) && (!seirawan_gating() || count_in_hand(WHITE, ALL_PIECES) > 0 || captures_to_hand())) for (File f = FILE_A; f <= max_file(); ++f) if ( (gates(WHITE) & file_bb(f)) // skip gating flags redundant with castling flags && !(!chess960 && can_castle(WHITE_CASTLING) && f == file_of(castling_king_square(WHITE))) && !(can_castle(WHITE_OO ) && f == file_of(castling_rook_square(WHITE_OO ))) && !(can_castle(WHITE_OOO) && f == file_of(castling_rook_square(WHITE_OOO)))) ss << char('A' + f); // Disambiguation for chess960 "king" square if (chess960 && can_castle(BLACK_CASTLING) && popcount(pieces(BLACK, castling_king_piece()) & rank_bb(castling_rank(BLACK))) > 1) ss << char('a' + castling_king_square(BLACK)); 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 (gating() && gates(BLACK) && (!seirawan_gating() || count_in_hand(BLACK, ALL_PIECES) > 0 || captures_to_hand())) for (File f = FILE_A; f <= max_file(); ++f) if ( (gates(BLACK) & file_bb(f)) // skip gating flags redundant with castling flags && !(!chess960 && can_castle(BLACK_CASTLING) && f == file_of(castling_king_square(BLACK))) && !(can_castle(BLACK_OO ) && f == file_of(castling_rook_square(BLACK_OO ))) && !(can_castle(BLACK_OOO) && f == file_of(castling_rook_square(BLACK_OOO)))) ss << char('a' + f); if (!can_castle(ANY_CASTLING) && !(gating() && (gates(WHITE) | gates(BLACK)))) ss << '-'; // Counting limit or ep-square if (st->countingLimit) ss << " " << st->countingLimit << " "; else ss << (ep_square() == SQ_NONE ? " - " : " " + UCI::square(*this, ep_square()) + " "); // Check count if (check_counting()) ss << st->checksRemaining[WHITE] << "+" << st->checksRemaining[BLACK] << " "; // Counting ply or 50-move rule counter if (st->countingLimit) ss << counting_ply(countStarted); else ss << st->rule50; ss << " " << 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, Color c) const { Bitboard blockers = 0; pinners = 0; if (s == SQ_NONE || !sliders) return blockers; // Snipers are sliders that attack 's' when a piece and other snipers are removed Bitboard snipers = 0; if (var->fastAttacks) snipers = ( (attacks_bb< ROOK>(s) & pieces(c, QUEEN, ROOK, CHANCELLOR)) | (attacks_bb(s) & pieces(c, QUEEN, BISHOP, ARCHBISHOP))) & sliders; else for (PieceType pt : piece_types()) { Bitboard b = sliders & (PseudoAttacks[~c][pt][s] ^ LeaperAttacks[~c][pt][s]) & pieces(c, pt); if (b) { // Consider asymmetrical moves (e.g., horse) if (AttackRiderTypes[pt] & ASYMMETRICAL_RIDERS) { Bitboard asymmetricals = PseudoAttacks[~c][pt][s] & pieces(c, pt); while (asymmetricals) { Square s2 = pop_lsb(asymmetricals); if (!(attacks_from(c, pt, s2) & s)) snipers |= s2; } } else snipers |= b & ~attacks_bb(~c, pt, s, pieces()); } } Bitboard occupancy = pieces() ^ snipers; while (snipers) { Square sniperSq = pop_lsb(snipers); Bitboard b = between_bb(s, sniperSq, type_of(piece_on(sniperSq))) & occupancy; if (b && (!more_than_one(b) || ((AttackRiderTypes[type_of(piece_on(sniperSq))] & HOPPING_RIDERS) && popcount(b) == 2))) { // Janggi cannons block each other if ((pieces(JANGGI_CANNON) & sniperSq) && (pieces(JANGGI_CANNON) & b)) b &= pieces(JANGGI_CANNON); 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, Color c, Bitboard janggiCannons) const { // Use a faster version for variants with moderate rule variations if (var->fastAttacks) { return (pawn_attacks_bb(~c, s) & pieces(c, PAWN)) | (attacks_bb(s) & pieces(c, KNIGHT, ARCHBISHOP, CHANCELLOR)) | (attacks_bb< ROOK>(s, occupied) & pieces(c, ROOK, QUEEN, CHANCELLOR)) | (attacks_bb(s, occupied) & pieces(c, BISHOP, QUEEN, ARCHBISHOP)) | (attacks_bb(s) & pieces(c, KING, COMMONER)); } // Use a faster version for selected fairy pieces if (var->fastAttacks2) { return (pawn_attacks_bb(~c, s) & pieces(c, PAWN, BREAKTHROUGH_PIECE, GOLD)) | (attacks_bb(s) & pieces(c, KNIGHT)) | (attacks_bb< ROOK>(s, occupied) & ( pieces(c, ROOK, QUEEN, DRAGON) | (pieces(c, LANCE) & PseudoAttacks[~c][LANCE][s]))) | (attacks_bb(s, occupied) & pieces(c, BISHOP, QUEEN, DRAGON_HORSE)) | (attacks_bb(s) & pieces(c, KING, COMMONER)) | (attacks_bb(s) & pieces(c, FERS, DRAGON, SILVER)) | (attacks_bb(s) & pieces(c, WAZIR, DRAGON_HORSE, GOLD)) | (LeaperAttacks[~c][SHOGI_KNIGHT][s] & pieces(c, SHOGI_KNIGHT)) | (LeaperAttacks[~c][SHOGI_PAWN][s] & pieces(c, SHOGI_PAWN, SILVER)); } Bitboard b = 0; for (PieceType pt : piece_types()) if (board_bb(c, pt) & s) { PieceType move_pt = pt == KING ? king_type() : pt; // Consider asymmetrical moves (e.g., horse) if (AttackRiderTypes[move_pt] & ASYMMETRICAL_RIDERS) { Bitboard asymmetricals = PseudoAttacks[~c][move_pt][s] & pieces(c, pt); while (asymmetricals) { Square s2 = pop_lsb(asymmetricals); if (attacks_bb(c, move_pt, s2, occupied) & s) b |= s2; } } else if (pt == JANGGI_CANNON) b |= attacks_bb(~c, move_pt, s, occupied) & attacks_bb(~c, move_pt, s, occupied & ~janggiCannons) & pieces(c, JANGGI_CANNON); else b |= attacks_bb(~c, move_pt, s, occupied) & pieces(c, pt); } // Consider special move of neang in cambodian chess if (cambodian_moves()) { Square fers_sq = s + 2 * (c == WHITE ? SOUTH : NORTH); if (is_ok(fers_sq)) b |= pieces(c, FERS) & gates(c) & fers_sq; } // Janggi palace moves if (diagonal_lines() & s) { Bitboard diags = 0; if (king_type() == WAZIR) diags |= attacks_bb(~c, FERS, s, occupied) & pieces(c, KING); diags |= attacks_bb(~c, FERS, s, occupied) & pieces(c, WAZIR); diags |= attacks_bb(~c, PAWN, s, occupied) & pieces(c, SOLDIER); diags |= rider_attacks_bb(s, occupied) & pieces(c, ROOK); diags |= rider_attacks_bb(s, occupied) & rider_attacks_bb(s, occupied & ~janggiCannons) & pieces(c, JANGGI_CANNON); b |= diags & diagonal_lines(); } // Unpromoted soldiers if (b & pieces(SOLDIER) && relative_rank(c, s, max_rank()) < var->soldierPromotionRank) b ^= b & pieces(SOLDIER) & ~PseudoAttacks[~c][SHOGI_PAWN][s]; return b; } Bitboard Position::attackers_to(Square s, Bitboard occupied) const { return attackers_to(s, occupied, WHITE) | attackers_to(s, occupied, BLACK); } /// Position::legal() tests whether a pseudo-legal move is legal bool Position::legal(Move m) const { assert(is_ok(m)); assert(type_of(m) != DROP || piece_drops()); Color us = sideToMove; Square from = from_sq(m); Square to = to_sq(m); assert(color_of(moved_piece(m)) == us); assert(!count(us) || piece_on(square(us)) == make_piece(us, KING)); assert(board_bb() & to); // Illegal checks if ((!checking_permitted() || (sittuyin_promotion() && type_of(m) == PROMOTION) || (!drop_checks() && type_of(m) == DROP)) && gives_check(m)) return false; // Illegal quiet moves if (must_capture() && !capture(m) && has_capture()) return false; // Illegal non-drop moves if (must_drop() && type_of(m) != DROP && count_in_hand(us, var->mustDropType) > 0) { if (checkers()) { for (const auto& mevasion : MoveList(*this)) if (type_of(mevasion) == DROP && legal(mevasion)) return false; } else { for (const auto& mquiet : MoveList(*this)) if (type_of(mquiet) == DROP && legal(mquiet)) return false; } } // Illegal drop move if (drop_opposite_colored_bishop() && type_of(m) == DROP) { if (type_of(moved_piece(m)) != BISHOP) { Bitboard remaining = drop_region(us, BISHOP) & ~pieces() & ~square_bb(to); // Are enough squares available to drop bishops on opposite colors? if ( popcount( DarkSquares & (pieces(us, BISHOP) | remaining)) < count_with_hand(us, BISHOP) / 2 || popcount(~DarkSquares & (pieces(us, BISHOP) | remaining)) < count_with_hand(us, BISHOP) / 2) return false; } else // Drop resulting in same-colored bishops if (popcount((DarkSquares & to ? DarkSquares : ~DarkSquares) & pieces(us, BISHOP)) + 1 > (count_with_hand(us, BISHOP) + 1) / 2) return false; } // No legal moves from target square if (immobility_illegal() && (type_of(m) == DROP || type_of(m) == NORMAL) && !(moves_bb(us, type_of(moved_piece(m)), to, 0) & board_bb())) return false; // Illegal king passing move if (pass_on_stalemate() && is_pass(m) && !checkers()) { for (const auto& move : MoveList(*this)) if (!is_pass(move) && legal(move)) return false; } // Check for attacks to pseudo-royal pieces if (var->extinctionPseudoRoyal) { Square kto = to; Bitboard occupied = (type_of(m) != DROP ? pieces() ^ from : pieces()) | kto; 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. kto = make_square(to > from ? castling_kingside_file() : castling_queenside_file(), castling_rank(us)); Direction step = kto > from ? EAST : WEST; Square rto = kto - step; // Pseudo-royal king if (st->pseudoRoyals & from) for (Square s = from; s != kto; s += step) if ( !(blast_on_capture() && (attacks_bb(s) & st->pseudoRoyals & pieces(~sideToMove))) && attackers_to(s, pieces() ^ from, ~us)) return false; occupied = (pieces() ^ from ^ to) | kto | rto; } if (type_of(m) == EN_PASSANT) occupied &= ~square_bb(kto - pawn_push(us)); if (capture(m) && blast_on_capture()) occupied &= ~((attacks_bb(kto) & (pieces() ^ pieces(PAWN))) | kto); Bitboard pseudoRoyals = st->pseudoRoyals & pieces(sideToMove); Bitboard pseudoRoyalsTheirs = st->pseudoRoyals & pieces(~sideToMove); if (is_ok(from) && (pseudoRoyals & from)) pseudoRoyals ^= square_bb(from) ^ kto; if (type_of(m) == PROMOTION && extinction_piece_types().find(promotion_type(m)) != extinction_piece_types().end()) pseudoRoyals |= kto; // Self-explosions are illegal if (pseudoRoyals & ~occupied) return false; // Check for legality unless we capture a pseudo-royal piece if (!(pseudoRoyalsTheirs & ~occupied)) while (pseudoRoyals) { Square sr = pop_lsb(pseudoRoyals); // Touching pseudo-royal pieces are immune if ( !(blast_on_capture() && (pseudoRoyalsTheirs & attacks_bb(sr))) && (attackers_to(sr, occupied, ~us) & (occupied & ~square_bb(kto)))) return false; } } // En passant captures are a tricky special case. Because they are rather // uncommon, we do it simply by testing whether the king is attacked after // the move is made. if (type_of(m) == EN_PASSANT && count(us)) { Square ksq = square(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 !(attackers_to(ksq, occupied, ~us) & occupied); } // 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 = make_square(to > from ? castling_kingside_file() : castling_queenside_file(), castling_rank(us)); Direction step = to > from ? WEST : EAST; // Will the gate be blocked by king or rook? Square rto = to + (to_sq(m) > from_sq(m) ? WEST : EAST); if (is_gating(m) && (gating_square(m) == to || gating_square(m) == rto)) return false; // Non-royal pieces can not be impeded from castling if (type_of(piece_on(from)) != KING) return true; for (Square s = to; s != from; s += step) if (attackers_to(s, ~us)) return false; // In case of Chess960, verify if the Rook blocks some checks // For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1. return !chess960 || !attackers_to(to, pieces() ^ to_sq(m), ~us); } Bitboard occupied = (type_of(m) != DROP ? pieces() ^ from : pieces()) | to; // Flying general rule and bikjang // In case of bikjang passing is always allowed, even when in check if (st->bikjang && is_pass(m)) return true; if ((var->flyingGeneral && count(us)) || st->bikjang) { Square s = type_of(moved_piece(m)) == KING ? to : square(us); if (attacks_bb(~us, ROOK, s, occupied) & pieces(~us, KING) & ~square_bb(to)) return false; } // Makpong rule if (var->makpongRule && checkers() && type_of(moved_piece(m)) == KING && (checkers() ^ to)) return false; // If the moving piece is a king, check whether the destination square is // attacked by the opponent. if (type_of(moved_piece(m)) == KING) return !attackers_to(to, occupied, ~us); // Return early when without king if (!count(us)) return true; Bitboard janggiCannons = pieces(JANGGI_CANNON); if (type_of(moved_piece(m)) == JANGGI_CANNON) janggiCannons = (type_of(m) == DROP ? janggiCannons : janggiCannons ^ from) | to; else if (janggiCannons & to) janggiCannons ^= to; // A non-king move is legal if the king is not under attack after the move. return !(attackers_to(square(us), occupied, ~us, janggiCannons) & ~SquareBB[to]); } /// 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); // Illegal moves to squares outside of board if (!(board_bb() & to)) return false; // Use a fast check for piece drops if (type_of(m) == DROP) return piece_drops() && pc != NO_PIECE && color_of(pc) == us && (count_in_hand(us, in_hand_piece_type(m)) > 0 || (two_boards() && allow_virtual_drop(us, type_of(pc)))) && (drop_region(us, type_of(pc)) & ~pieces() & to) && ( type_of(pc) == in_hand_piece_type(m) || (drop_promoted() && type_of(pc) == promoted_piece_type(in_hand_piece_type(m)))); // Use a slower but simpler function for uncommon cases // yet we skip the legality check of MoveList(). if (type_of(m) != NORMAL || is_gating(m) || arrow_gating()) return checkers() ? MoveList< EVASIONS>(*this).contains(m) : MoveList(*this).contains(m); // Handle the case where a mandatory piece promotion/demotion is not taken if ( mandatory_piece_promotion() && (is_promoted(from) ? piece_demotion() : promoted_piece_type(type_of(pc)) != NO_PIECE_TYPE) && (zone_bb(us, promotion_rank(), max_rank()) & (SquareBB[from] | to)) && (!piece_promotion_on_capture() || capture(m))) return false; // Is not a promotion, so promotion piece must be empty if (promotion_type(m) != 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 (mandatory_pawn_promotion() && rank_of(to) == relative_rank(us, promotion_rank(), max_rank())) 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, max_rank()) <= double_step_rank_max() && relative_rank(us, from, max_rank()) >= double_step_rank_min()) && empty(to) && empty(to - pawn_push(us)) && double_step_enabled())) return false; } else if (!((capture(m) ? attacks_from(us, type_of(pc), from) : moves_from(us, type_of(pc), from)) & to)) return false; // Janggi cannon if (type_of(pc) == JANGGI_CANNON && (pieces(JANGGI_CANNON) & (between_bb(from, to) | 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() && !(checkers() & non_sliding_riders())) { 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 Square checksq = lsb(checkers()); if ( !(between_bb(square(us), lsb(checkers())) & to) || ((LeaperAttacks[~us][type_of(piece_on(checksq))][checksq] & square(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, ~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); // No check possible without king if (!count(~sideToMove)) return false; // Is there a direct check? if (type_of(m) != PROMOTION && type_of(m) != PIECE_PROMOTION && type_of(m) != PIECE_DEMOTION) { PieceType pt = type_of(moved_piece(m)); if (AttackRiderTypes[pt] & (HOPPING_RIDERS | ASYMMETRICAL_RIDERS)) { Bitboard occupied = (type_of(m) != DROP ? pieces() ^ from : pieces()) | to; if (attacks_bb(sideToMove, pt, to, occupied) & square(~sideToMove)) return true; } else if (check_squares(pt) & to) return true; } Bitboard janggiCannons = pieces(JANGGI_CANNON); if (type_of(moved_piece(m)) == JANGGI_CANNON) janggiCannons = (type_of(m) == DROP ? janggiCannons : janggiCannons ^ from) | to; else if (janggiCannons & to) janggiCannons ^= to; // Is there a discovered check? if ( ((type_of(m) != DROP && (blockers_for_king(~sideToMove) & from)) || (non_sliding_riders() & pieces(sideToMove))) && attackers_to(square(~sideToMove), (type_of(m) == DROP ? pieces() : pieces() ^ from) | to, sideToMove, janggiCannons)) return true; // Is there a check by gated pieces? if ( is_gating(m) && attacks_bb(sideToMove, gating_type(m), gating_square(m), (pieces() ^ from) | to) & square(~sideToMove)) return true; // Is there a check by special diagonal moves? if (more_than_one(diagonal_lines() & (to | square(~sideToMove)))) { PieceType pt = type_of(moved_piece(m)); PieceType diagType = pt == WAZIR ? FERS : pt == SOLDIER ? PAWN : pt == ROOK ? BISHOP : NO_PIECE_TYPE; Bitboard occupied = type_of(m) == DROP ? pieces() : pieces() ^ from; if (diagType && (attacks_bb(sideToMove, diagType, to, occupied) & square(~sideToMove))) return true; else if (pt == JANGGI_CANNON && ( rider_attacks_bb(to, occupied) & rider_attacks_bb(to, occupied & ~janggiCannons) & square(~sideToMove))) return true; } switch (type_of(m)) { case NORMAL: case DROP: case SPECIAL: return false; case PROMOTION: return attacks_bb(sideToMove, promotion_type(m), to, pieces() ^ from) & square(~sideToMove); case PIECE_PROMOTION: return attacks_bb(sideToMove, promoted_piece_type(type_of(moved_piece(m))), to, pieces() ^ from) & square(~sideToMove); case PIECE_DEMOTION: return attacks_bb(sideToMove, type_of(unpromoted_piece_on(from)), to, pieces() ^ from) & square(~sideToMove); // En passant capture with check? We have already handled the case // of direct checks and ordinary discovered check, so the only case we // need to handle is the unusual case of a discovered check through // the captured pawn. case EN_PASSANT: { Square capsq = make_square(file_of(to), rank_of(from)); Bitboard b = (pieces() ^ from ^ capsq) | to; return attackers_to(square(~sideToMove), b) & pieces(sideToMove) & b; } default: //CASTLING { // Castling is encoded as 'king captures the rook' Square kfrom = from; Square rfrom = to; Square kto = make_square(rfrom > kfrom ? castling_kingside_file() : castling_queenside_file(), castling_rank(sideToMove)); Square rto = kto + (rfrom > kfrom ? WEST : EAST); return (PseudoAttacks[sideToMove][type_of(piece_on(rfrom))][rto] & square(~sideToMove)) && (attacks_bb(sideToMove, type_of(piece_on(rfrom)), rto, (pieces() ^ kfrom ^ rfrom) | rto | kto) & square(~sideToMove)); } } } /// 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); #ifndef NO_THREADS thisThread->nodes.fetch_add(1, std::memory_order_relaxed); #endif 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(static_cast(&newSt), static_cast(st), offsetof(StateInfo, key)); newSt.previous = st; st = &newSt; st->move = m; // 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; if (st->countingLimit) ++st->countingPly; // Used by NNUE st->accumulator.computed[WHITE] = false; st->accumulator.computed[BLACK] = false; auto& dp = st->dirtyPiece; dp.dirty_num = 1; Color us = sideToMove; Color them = ~us; Square from = from_sq(m); Square to = to_sq(m); Piece pc = moved_piece(m); Piece captured = type_of(m) == EN_PASSANT ? make_piece(them, PAWN) : piece_on(to); if (to == from) { assert((type_of(m) == PROMOTION && sittuyin_promotion()) || (is_pass(m) && pass())); captured = NO_PIECE; } st->capturedpromoted = is_promoted(to); st->unpromotedCapturedPiece = captured ? unpromoted_piece_on(to) : NO_PIECE; st->pass = is_pass(m); assert(color_of(pc) == us); assert(captured == NO_PIECE || color_of(captured) == (type_of(m) != CASTLING ? them : us)); assert(type_of(captured) != KING); if (check_counting() && givesCheck) k ^= Zobrist::checks[us][st->checksRemaining[us]] ^ Zobrist::checks[us][--(st->checksRemaining[us])]; if (type_of(m) == CASTLING) { assert(type_of(pc) != NO_PIECE_TYPE); assert(captured == make_piece(us, castling_rook_piece())); Square rfrom, rto; do_castling(us, from, to, rfrom, rto); k ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto]; captured = NO_PIECE; } if (captured) { Square capsq = to; // If the captured piece is a pawn, update pawn hash key, otherwise // update non-pawn material. if (type_of(captured) == PAWN) { if (type_of(m) == EN_PASSANT) { capsq -= pawn_push(us); assert(pc == make_piece(us, PAWN)); assert(to == st->epSquare); assert((var->enPassantRegion & to) && relative_rank(~us, to, max_rank()) <= Rank(double_step_rank_max() + 1) && relative_rank(~us, to, max_rank()) > double_step_rank_min()); 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 bool capturedPromoted = is_promoted(capsq); Piece unpromotedCaptured = unpromoted_piece_on(capsq); remove_piece(capsq); if (type_of(m) == EN_PASSANT) board[capsq] = NO_PIECE; if (captures_to_hand()) { Piece pieceToHand = !capturedPromoted || drop_loop() ? ~captured : unpromotedCaptured ? ~unpromotedCaptured : make_piece(~color_of(captured), PAWN); add_to_hand(pieceToHand); k ^= Zobrist::inHand[pieceToHand][pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)] - 1] ^ Zobrist::inHand[pieceToHand][pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)]]; if (Eval::useNNUE) { dp.handPiece[1] = pieceToHand; dp.handCount[1] = pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)]; } } else if (Eval::useNNUE) dp.handPiece[1] = NO_PIECE; // Update material hash key and prefetch access to materialTable k ^= Zobrist::psq[captured][capsq]; st->materialKey ^= Zobrist::psq[captured][pieceCount[captured]]; #ifndef NO_THREADS prefetch(thisThread->materialTable[st->materialKey]); #endif // Reset rule 50 counter st->rule50 = 0; } // Update hash key if (type_of(m) == DROP) { Piece pc_hand = make_piece(us, in_hand_piece_type(m)); k ^= Zobrist::psq[pc][to] ^ Zobrist::inHand[pc_hand][pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)] - 1] ^ Zobrist::inHand[pc_hand][pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)]]; } else 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 (type_of(m) != DROP && !is_pass(m) && st->castlingRights && (castlingRightsMask[from] | castlingRightsMask[to])) { k ^= Zobrist::castling[st->castlingRights]; st->castlingRights &= ~(castlingRightsMask[from] | castlingRightsMask[to]); k ^= Zobrist::castling[st->castlingRights]; } // Flip enclosed pieces st->flippedPieces = 0; if (flip_enclosed_pieces() && !is_pass(m)) { // Find end of rows to be flipped if (flip_enclosed_pieces() == REVERSI) { Bitboard b = attacks_bb(us, QUEEN, to, board_bb() & ~pieces(~us)) & ~PseudoAttacks[us][KING][to] & pieces(us); while(b) st->flippedPieces |= between_bb(to, pop_lsb(b)); } else { assert(flip_enclosed_pieces() == ATAXX); st->flippedPieces = PseudoAttacks[us][KING][to] & pieces(~us); } // Flip pieces Bitboard to_flip = st->flippedPieces; while(to_flip) { Square s = pop_lsb(to_flip); Piece flipped = piece_on(s); Piece resulting = ~flipped; // remove opponent's piece remove_piece(s); k ^= Zobrist::psq[flipped][s]; st->materialKey ^= Zobrist::psq[flipped][pieceCount[flipped]]; st->nonPawnMaterial[them] -= PieceValue[MG][flipped]; // add our piece put_piece(resulting, s); k ^= Zobrist::psq[resulting][s]; st->materialKey ^= Zobrist::psq[resulting][pieceCount[resulting]-1]; st->nonPawnMaterial[us] += PieceValue[MG][resulting]; } } // Move the piece. The tricky Chess960 castling is handled earlier if (type_of(m) == DROP) { if (Eval::useNNUE) { // Add drop piece dp.piece[0] = pc; dp.handPiece[0] = make_piece(us, in_hand_piece_type(m)); dp.handCount[0] = pieceCountInHand[us][in_hand_piece_type(m)]; dp.from[0] = SQ_NONE; dp.to[0] = to; } drop_piece(make_piece(us, in_hand_piece_type(m)), pc, to); st->materialKey ^= Zobrist::psq[pc][pieceCount[pc]-1]; if (type_of(pc) != PAWN) st->nonPawnMaterial[us] += PieceValue[MG][pc]; // Set castling rights for dropped king or rook if (castling_dropped_piece() && rank_of(to) == castling_rank(us)) { if (type_of(pc) == castling_king_piece() && file_of(to) == castling_king_file()) { st->castlingKingSquare[us] = to; Bitboard castling_rooks = pieces(us, castling_rook_piece()) & rank_bb(castling_rank(us)) & (file_bb(FILE_A) | file_bb(max_file())); while (castling_rooks) set_castling_right(us, pop_lsb(castling_rooks)); } else if (type_of(pc) == castling_rook_piece()) { if ( (file_of(to) == FILE_A || file_of(to) == max_file()) && piece_on(make_square(castling_king_file(), castling_rank(us))) == make_piece(us, castling_king_piece())) { st->castlingKingSquare[us] = make_square(castling_king_file(), castling_rank(us)); set_castling_right(us, to); } } } } else 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 ( type_of(m) != DROP && std::abs(int(to) - int(from)) == 2 * NORTH && (var->enPassantRegion & (to - pawn_push(us))) && (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 || type_of(m) == PIECE_PROMOTION) { Piece promotion = make_piece(us, type_of(m) == PROMOTION ? promotion_type(m) : promoted_piece_type(PAWN)); assert(relative_rank(us, to, max_rank()) >= promotion_rank() || sittuyin_promotion()); assert(type_of(promotion) >= KNIGHT && type_of(promotion) < KING); remove_piece(to); put_piece(promotion, to, true, type_of(m) == PIECE_PROMOTION ? pc : NO_PIECE); if (Eval::useNNUE) { // Promoting pawn to SQ_NONE, promoted piece from SQ_NONE dp.to[0] = SQ_NONE; dp.handPiece[0] = NO_PIECE; dp.piece[dp.dirty_num] = promotion; dp.handPiece[dp.dirty_num] = NO_PIECE; 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 ^= (type_of(m) != DROP ? Zobrist::psq[pc][from] : 0) ^ Zobrist::psq[pc][to]; // Reset rule 50 draw counter st->rule50 = 0; } else if (type_of(m) == PIECE_PROMOTION) { Piece promotion = make_piece(us, promoted_piece_type(type_of(pc))); remove_piece(to); put_piece(promotion, to, true, pc); if (Eval::useNNUE) { // Promoting piece to SQ_NONE, promoted piece from SQ_NONE dp.to[0] = SQ_NONE; dp.handPiece[0] = NO_PIECE; dp.piece[dp.dirty_num] = promotion; dp.handPiece[dp.dirty_num] = NO_PIECE; 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->materialKey ^= Zobrist::psq[promotion][pieceCount[promotion]-1] ^ Zobrist::psq[pc][pieceCount[pc]]; // Update material st->nonPawnMaterial[us] += PieceValue[MG][promotion] - PieceValue[MG][pc]; } else if (type_of(m) == PIECE_DEMOTION) { Piece demotion = unpromoted_piece_on(to); remove_piece(to); put_piece(demotion, to); if (Eval::useNNUE) { // Demoting piece to SQ_NONE, demoted piece from SQ_NONE dp.to[0] = SQ_NONE; dp.handPiece[0] = NO_PIECE; dp.piece[dp.dirty_num] = demotion; dp.handPiece[dp.dirty_num] = NO_PIECE; 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[demotion][to]; st->materialKey ^= Zobrist::psq[demotion][pieceCount[demotion]-1] ^ Zobrist::psq[pc][pieceCount[pc]]; // Update material st->nonPawnMaterial[us] += PieceValue[MG][demotion] - PieceValue[MG][pc]; } // Set capture piece st->capturedPiece = captured; // Add gating piece if (is_gating(m)) { Square gate = gating_square(m); Piece gating_piece = make_piece(us, gating_type(m)); if (Eval::useNNUE) { // Add gating piece dp.piece[dp.dirty_num] = gating_piece; dp.handPiece[dp.dirty_num] = gating_piece; dp.handCount[dp.dirty_num] = pieceCountInHand[us][gating_type(m)]; dp.from[dp.dirty_num] = SQ_NONE; dp.to[dp.dirty_num] = gate; dp.dirty_num++; } put_piece(gating_piece, gate); remove_from_hand(gating_piece); st->gatesBB[us] ^= gate; k ^= Zobrist::psq[gating_piece][gate]; st->materialKey ^= Zobrist::psq[gating_piece][pieceCount[gating_piece]]; st->nonPawnMaterial[us] += PieceValue[MG][gating_piece]; } // Remove gates if (gating()) { if (is_ok(from) && (gates(us) & from)) st->gatesBB[us] ^= from; if (type_of(m) == CASTLING && (gates(us) & to_sq(m))) st->gatesBB[us] ^= to_sq(m); if (gates(them) & to) st->gatesBB[them] ^= to; if (seirawan_gating() && count_in_hand(us, ALL_PIECES) == 0 && !captures_to_hand()) st->gatesBB[us] = 0; } // Remove the blast pieces if (captured && blast_on_capture()) { std::memset(st->unpromotedBycatch, 0, sizeof(st->unpromotedBycatch)); st->demotedBycatch = st->promotedBycatch = 0; Bitboard blast = (attacks_bb(to) & (pieces() ^ pieces(PAWN))) | to; while (blast) { Square bsq = pop_lsb(blast); Piece bpc = piece_on(bsq); Color bc = color_of(bpc); if (type_of(bpc) != PAWN) st->nonPawnMaterial[bc] -= PieceValue[MG][bpc]; if (Eval::useNNUE) { dp.piece[dp.dirty_num] = bpc; dp.handPiece[dp.dirty_num] = NO_PIECE; dp.from[dp.dirty_num] = bsq; dp.to[dp.dirty_num] = SQ_NONE; dp.dirty_num++; } // Update board and piece lists // In order to not have to store the values of both board and unpromotedBoard, // demote promoted pieces, but keep promoted pawns as promoted, // and store demotion/promotion bitboards to disambiguate the piece state bool capturedPromoted = is_promoted(bsq); Piece unpromotedCaptured = unpromoted_piece_on(bsq); st->unpromotedBycatch[bsq] = unpromotedCaptured ? unpromotedCaptured : bpc; if (unpromotedCaptured) st->demotedBycatch |= bsq; else if (capturedPromoted) st->promotedBycatch |= bsq; remove_piece(bsq); board[bsq] = NO_PIECE; if (captures_to_hand()) { Piece pieceToHand = !capturedPromoted || drop_loop() ? ~bpc : unpromotedCaptured ? ~unpromotedCaptured : make_piece(~color_of(bpc), PAWN); add_to_hand(pieceToHand); k ^= Zobrist::inHand[pieceToHand][pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)] - 1] ^ Zobrist::inHand[pieceToHand][pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)]]; if (Eval::useNNUE) { dp.handPiece[dp.dirty_num - 1] = pieceToHand; dp.handCount[dp.dirty_num - 1] = pieceCountInHand[color_of(pieceToHand)][type_of(pieceToHand)]; } } // Update material hash key k ^= Zobrist::psq[bpc][bsq]; st->materialKey ^= Zobrist::psq[bpc][pieceCount[bpc]]; if (type_of(bpc) == PAWN) st->pawnKey ^= Zobrist::psq[bpc][bsq]; // Update castling rights if needed if (st->castlingRights && castlingRightsMask[bsq]) { k ^= Zobrist::castling[st->castlingRights]; st->castlingRights &= ~castlingRightsMask[bsq]; k ^= Zobrist::castling[st->castlingRights]; } } } // Update the key with the final value st->key = k; // Calculate checkers bitboard (if move gives check) st->checkersBB = givesCheck ? attackers_to(square(them), us) & pieces(us) : Bitboard(0); sideToMove = ~sideToMove; if ( counting_rule() && (!st->countingLimit || (captured && count(sideToMove) == 1)) && counting_limit()) { st->countingLimit = 2 * counting_limit(); st->countingPly = counting_rule() == MAKRUK_COUNTING && count(sideToMove) == 1 ? 2 * count() : 0; } // 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 = captures_to_hand() ? st->pliesFromNull : 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(type_of(m) == DROP || empty(from) || type_of(m) == CASTLING || is_gating(m) || (type_of(m) == PROMOTION && sittuyin_promotion()) || (is_pass(m) && pass())); assert(type_of(st->capturedPiece) != KING); // Add the blast pieces if (st->capturedPiece && blast_on_capture()) { Bitboard blast = attacks_bb(to) | to; while (blast) { Square bsq = pop_lsb(blast); Piece unpromotedBpc = st->unpromotedBycatch[bsq]; Piece bpc = st->demotedBycatch & bsq ? make_piece(color_of(unpromotedBpc), promoted_piece_type(type_of(unpromotedBpc))) : unpromotedBpc; bool isPromoted = (st->promotedBycatch | st->demotedBycatch) & bsq; // Update board and piece lists if (bpc) { put_piece(bpc, bsq, isPromoted, st->demotedBycatch & bsq ? unpromotedBpc : NO_PIECE); if (captures_to_hand()) remove_from_hand(!drop_loop() && (st->promotedBycatch & bsq) ? make_piece(~color_of(unpromotedBpc), PAWN) : ~unpromotedBpc); } } // Reset piece since it exploded itself pc = piece_on(to); } // Remove gated piece if (is_gating(m)) { Piece gating_piece = make_piece(us, gating_type(m)); remove_piece(gating_square(m)); board[gating_square(m)] = NO_PIECE; add_to_hand(gating_piece); st->gatesBB[us] |= gating_square(m); } if (type_of(m) == PROMOTION) { assert(relative_rank(us, to, max_rank()) >= promotion_rank() || sittuyin_promotion()); assert(type_of(pc) == promotion_type(m)); assert(type_of(pc) >= KNIGHT && type_of(pc) < KING); remove_piece(to); pc = make_piece(us, PAWN); put_piece(pc, to); } else if (type_of(m) == PIECE_PROMOTION) { Piece unpromotedPiece = unpromoted_piece_on(to); remove_piece(to); pc = unpromotedPiece; put_piece(pc, to); } else if (type_of(m) == PIECE_DEMOTION) { remove_piece(to); Piece unpromotedPc = pc; pc = make_piece(us, promoted_piece_type(type_of(pc))); put_piece(pc, to, true, unpromotedPc); } if (type_of(m) == CASTLING) { Square rfrom, rto; do_castling(us, from, to, rfrom, rto); } else { if (type_of(m) == DROP) undrop_piece(make_piece(us, in_hand_piece_type(m)), to); // Remove the dropped piece else move_piece(to, from); // Put the piece back at the source square if (st->capturedPiece) { Square capsq = to; if (type_of(m) == EN_PASSANT) { capsq -= pawn_push(us); assert(type_of(pc) == PAWN); assert(to == st->previous->epSquare); assert(relative_rank(~us, to, max_rank()) <= Rank(double_step_rank_max() + 1)); assert(piece_on(capsq) == NO_PIECE); assert(st->capturedPiece == make_piece(~us, PAWN)); } put_piece(st->capturedPiece, capsq, st->capturedpromoted, st->unpromotedCapturedPiece); // Restore the captured piece if (captures_to_hand()) remove_from_hand(!drop_loop() && st->capturedpromoted ? (st->unpromotedCapturedPiece ? ~st->unpromotedCapturedPiece : make_piece(~color_of(st->capturedPiece), PAWN)) : ~st->capturedPiece); } } if (flip_enclosed_pieces()) { // Flip pieces Bitboard to_flip = st->flippedPieces; while(to_flip) { Square s = pop_lsb(to_flip); Piece resulting = ~piece_on(s); remove_piece(s); put_piece(resulting, s); } } // 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 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" to = make_square(kingSide ? castling_kingside_file() : castling_queenside_file(), castling_rank(us)); rto = to + (kingSide ? WEST : EAST); Piece castlingKingPiece = piece_on(Do ? from : to); Piece castlingRookPiece = piece_on(Do ? rfrom : rto); if (Do && Eval::useNNUE) { auto& dp = st->dirtyPiece; dp.piece[0] = castlingKingPiece; dp.from[0] = from; dp.to[0] = to; dp.piece[1] = castlingRookPiece; 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 it for us put_piece(castlingKingPiece, Do ? to : from); put_piece(castlingRookPiece, Do ? rto : rfrom); } /// Position::do_null_move() is used to do a "null move": it flips /// the side to move without executing any move on the board. void Position::do_null_move(StateInfo& newSt) { assert(!checkers()); assert(&newSt != st); std::memcpy(&newSt, st, offsetof(StateInfo, accumulator)); newSt.previous = st; st = &newSt; st->dirtyPiece.dirty_num = 0; st->dirtyPiece.piece[0] = NO_PIECE; // Avoid checks in UpdateAccumulator() st->accumulator.computed[WHITE] = false; st->accumulator.computed[BLACK] = false; if (st->epSquare != SQ_NONE) { st->key ^= Zobrist::enpassant[file_of(st->epSquare)]; st->epSquare = SQ_NONE; } st->key ^= Zobrist::side; prefetch(TT.first_entry(key())); ++st->rule50; st->pliesFromNull = 0; sideToMove = ~sideToMove; set_check_info(st); st->repetition = 0; assert(pos_is_ok()); } /// Position::undo_null_move() must be used to undo a "null move" 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 = moved_piece(m); Piece captured = piece_on(to); Key k = st->key ^ Zobrist::side; if (captured) { k ^= Zobrist::psq[captured][to]; if (captures_to_hand()) { Piece removeFromHand = !drop_loop() && is_promoted(to) ? make_piece(~color_of(captured), PAWN) : ~captured; k ^= Zobrist::inHand[removeFromHand][pieceCountInHand[color_of(removeFromHand)][type_of(removeFromHand)] + 1] ^ Zobrist::inHand[removeFromHand][pieceCountInHand[color_of(removeFromHand)][type_of(removeFromHand)]]; } } if (type_of(m) == DROP) { Piece pc_hand = make_piece(sideToMove, in_hand_piece_type(m)); return k ^ Zobrist::psq[pc][to] ^ Zobrist::inHand[pc_hand][pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)]] ^ Zobrist::inHand[pc_hand][pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)] - 1]; } return k ^ Zobrist::psq[pc][to] ^ Zobrist::psq[pc][from]; } Value Position::blast_see(Move m) const { assert(is_ok(m)); Square from = from_sq(m); Square to = to_sq(m); Color us = color_of(moved_piece(m)); Bitboard fromto = type_of(m) == DROP ? square_bb(to) : from | to; Bitboard blast = ((attacks_bb(to) & ~pieces(PAWN)) | fromto) & pieces(); Value result = VALUE_ZERO; // Add the least valuable attacker for quiet moves if (!capture(m)) { Bitboard attackers = attackers_to(to, pieces() ^ fromto, ~us); Value minAttacker = VALUE_INFINITE; while (attackers) { Square s = pop_lsb(attackers); if (extinction_piece_types().find(type_of(piece_on(s))) == extinction_piece_types().end()) minAttacker = std::min(minAttacker, blast & s ? VALUE_ZERO : CapturePieceValue[MG][piece_on(s)]); } if (minAttacker == VALUE_INFINITE) return VALUE_ZERO; result += minAttacker; if (type_of(m) == DROP) result -= CapturePieceValue[MG][dropped_piece_type(m)]; } // Sum up blast piece values while (blast) { Piece bpc = piece_on(pop_lsb(blast)); if (extinction_piece_types().find(type_of(bpc)) != extinction_piece_types().end()) return color_of(bpc) == us ? extinction_value() : capture(m) ? -extinction_value() : VALUE_ZERO; result += color_of(bpc) == us ? -CapturePieceValue[MG][bpc] : CapturePieceValue[MG][bpc]; } return capture(m) || must_capture() ? result - 1 : std::min(result, VALUE_ZERO); } /// 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 && type_of(m) != DROP && type_of(m) != PIECE_PROMOTION) return VALUE_ZERO >= threshold; Square from = from_sq(m), to = to_sq(m); // nCheck if (check_counting() && color_of(moved_piece(m)) == sideToMove && gives_check(m)) return true; // Atomic explosion SEE if (blast_on_capture()) return blast_see(m) >= threshold; // Extinction if ( extinction_value() != VALUE_NONE && piece_on(to) && ( ( extinction_piece_types().find(type_of(piece_on(to))) != extinction_piece_types().end() && pieceCount[piece_on(to)] == extinction_piece_count() + 1) || ( extinction_piece_types().find(ALL_PIECES) != extinction_piece_types().end() && count(~sideToMove) == extinction_piece_count() + 1))) return extinction_value() < VALUE_ZERO; // Do not evaluate SEE if value would be unreliable if (must_capture() || !checking_permitted() || is_gating(m) || count() == count()) return VALUE_ZERO >= threshold; int swap = PieceValue[MG][piece_on(to)] - threshold; if (swap < 0) return false; swap = PieceValue[MG][moved_piece(m)] - swap; if (swap <= 0) return true; Bitboard occupied = (type_of(m) != DROP ? pieces() ^ from : pieces()) ^ to; Color stm = color_of(moved_piece(m)); Bitboard attackers = attackers_to(to, occupied); Bitboard stmAttackers, bb; int res = 1; // Flying general rule if (var->flyingGeneral) { if (attackers & pieces(stm, KING)) attackers |= attacks_bb(stm, ROOK, to, occupied & ~pieces(ROOK)) & pieces(~stm, KING); if (attackers & pieces(~stm, KING)) attackers |= attacks_bb(~stm, ROOK, to, occupied & ~pieces(ROOK)) & pieces(stm, KING); } // Janggi cannons can not capture each other if (type_of(moved_piece(m)) == JANGGI_CANNON && !(attackers & pieces(~stm) & ~pieces(JANGGI_CANNON))) attackers &= ~pieces(~stm, JANGGI_CANNON); while (true) { stm = ~stm; attackers &= occupied; // If stm has no more attackers then give up: stm loses if (!(stmAttackers = attackers & pieces(stm))) break; // Don't allow pinned pieces to attack as long as there are // pinners on their original square. if (pinners(~stm) & occupied) stmAttackers &= ~blockers_for_king(stm); if (!stmAttackers) break; res ^= 1; // Locate and remove the next least valuable attacker, and add to // the bitboard 'attackers' any X-ray attackers behind it. if ((bb = stmAttackers & pieces(PAWN))) { if ((swap = PawnValueMg - swap) < res) break; occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); } else if ((bb = stmAttackers & pieces(KNIGHT))) { if ((swap = KnightValueMg - swap) < res) break; occupied ^= least_significant_square_bb(bb); } else if ((bb = stmAttackers & pieces(BISHOP))) { if ((swap = BishopValueMg - swap) < res) break; occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(BISHOP, QUEEN); } else if ((bb = stmAttackers & pieces(ROOK))) { if ((swap = RookValueMg - swap) < res) break; occupied ^= least_significant_square_bb(bb); attackers |= attacks_bb(to, occupied) & pieces(ROOK, QUEEN); } else if ((bb = stmAttackers & pieces(QUEEN))) { if ((swap = QueenValueMg - swap) < res) break; occupied ^= least_significant_square_bb(bb); attackers |= (attacks_bb(to, occupied) & pieces(BISHOP, QUEEN)) | (attacks_bb(to, occupied) & pieces(ROOK , QUEEN)); } // fairy pieces // pick next piece without considering value else if ((bb = stmAttackers & ~pieces(KING))) { if ((swap = PieceValue[MG][piece_on(lsb(bb))] - swap) < res) break; occupied ^= lsb(bb); } 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_optinal_game_end() tests whether the position may end the game by /// 50-move rule, by repetition, or a variant rule that allows a player to claim a game result. bool Position::is_optional_game_end(Value& result, int ply, int countStarted) const { // n-move rule if (n_move_rule() && st->rule50 > (2 * n_move_rule() - 1) && (!checkers() || MoveList(*this).size())) { result = var->materialCounting ? convert_mate_value(material_counting_result(), ply) : VALUE_DRAW; return true; } // n-fold repetition if (n_fold_rule()) { int end = captures_to_hand() ? st->pliesFromNull : std::min(st->rule50, st->pliesFromNull); if (end >= 4) { StateInfo* stp = st->previous->previous; int cnt = 0; bool perpetualThem = st->checkersBB && stp->checkersBB; bool perpetualUs = st->previous->checkersBB && stp->previous->checkersBB; int moveRepetition = var->moveRepetitionIllegal && type_of(st->move) == NORMAL && !st->previous->checkersBB && !stp->previous->checkersBB && (board_bb(~side_to_move(), type_of(piece_on(to_sq(st->move)))) & board_bb(side_to_move(), KING)) ? (stp->move == reverse_move(st->move) ? 2 : is_pass(stp->move) ? 1 : 0) : 0; for (int i = 4; i <= end; i += 2) { // Janggi repetition rule if (moveRepetition > 0) { if (i + 1 <= end && stp->previous->previous->previous->checkersBB) moveRepetition = 0; else if (moveRepetition < 4) { if (stp->previous->previous->move == reverse_move((moveRepetition == 1 ? st : stp)->move)) moveRepetition++; else moveRepetition = 0; } else { assert(moveRepetition == 4); if (!stp->previous->previous->capturedPiece && from_sq(stp->move) == to_sq(stp->previous->previous->move)) { result = VALUE_MATE; return true; } else moveRepetition = 0; } } stp = stp->previous->previous; perpetualThem &= bool(stp->checkersBB); // Return a draw score if a position repeats once earlier but strictly // after the root, or repeats twice before or at the root. if ( stp->key == st->key && ++cnt + 1 == (ply > i && !var->moveRepetitionIllegal ? 2 : n_fold_rule())) { result = convert_mate_value( var->perpetualCheckIllegal && perpetualThem ? VALUE_MATE : var->perpetualCheckIllegal && perpetualUs ? -VALUE_MATE : var->nFoldValueAbsolute && sideToMove == BLACK ? -var->nFoldValue : var->nFoldValue, ply); if (result == VALUE_DRAW && var->materialCounting) result = convert_mate_value(material_counting_result(), ply); return true; } if (i + 1 <= end) perpetualUs &= bool(stp->previous->checkersBB); } } } // counting rules if ( counting_rule() && st->countingLimit && counting_ply(countStarted) > st->countingLimit && (!checkers() || MoveList(*this).size())) { result = VALUE_DRAW; return true; } // sittuyin stalemate due to optional promotion (3.9 c.7) if ( sittuyin_promotion() && count(sideToMove) == 2 && count(sideToMove) == 1 && !checkers()) { bool promotionsOnly = true; for (const auto& m : MoveList(*this)) if (type_of(m) != PROMOTION) { promotionsOnly = false; break; } if (promotionsOnly) { result = VALUE_DRAW; return true; } } return false; } /// Position::is_immediate_game_end() tests whether the position ends the game /// immediately by a variant rule, i.e., there are no more legal moves. /// It does not not detect stalemates. bool Position::is_immediate_game_end(Value& result, int ply) const { // Extinction // Extinction does not apply for pseudo-royal pieces, because they can not be captured if (extinction_value() != VALUE_NONE && (!var->extinctionPseudoRoyal || blast_on_capture())) { for (Color c : { ~sideToMove, sideToMove }) for (PieceType pt : extinction_piece_types()) if ( count_with_hand( c, pt) <= var->extinctionPieceCount && count_with_hand(~c, pt) >= var->extinctionOpponentPieceCount + (extinction_claim() && c == sideToMove)) { result = c == sideToMove ? extinction_value(ply) : -extinction_value(ply); return true; } } // capture the flag if ( capture_the_flag_piece() && flag_move() && (capture_the_flag(sideToMove) & pieces(sideToMove, capture_the_flag_piece()))) { result = (capture_the_flag(~sideToMove) & pieces(~sideToMove, capture_the_flag_piece())) && sideToMove == WHITE ? VALUE_DRAW : mate_in(ply); return true; } if ( capture_the_flag_piece() && (!flag_move() || capture_the_flag_piece() == KING) && (capture_the_flag(~sideToMove) & pieces(~sideToMove, capture_the_flag_piece()))) { bool gameEnd = true; // Check whether king can move to CTF zone if ( flag_move() && sideToMove == BLACK && !checkers() && count(sideToMove) && (capture_the_flag(sideToMove) & attacks_from(sideToMove, KING, square(sideToMove)))) { assert(capture_the_flag_piece() == KING); gameEnd = true; for (const auto& m : MoveList(*this)) if (type_of(moved_piece(m)) == KING && (capture_the_flag(sideToMove) & to_sq(m)) && legal(m)) { gameEnd = false; break; } } if (gameEnd) { result = mated_in(ply); return true; } } // nCheck if (check_counting() && checks_remaining(~sideToMove) == 0) { result = mated_in(ply); return true; } // Connect-n if (connect_n() > 0) { Bitboard b; for (Direction d : {NORTH, NORTH_EAST, EAST, SOUTH_EAST}) { b = pieces(~sideToMove); for (int i = 1; i < connect_n() && b; i++) b &= shift(d, b); if (b) { result = mated_in(ply); return true; } } } // Check for bikjang rule (Janggi) and double passing if (st->pliesFromNull > 0 && ((st->bikjang && st->previous->bikjang) || (st->pass && st->previous->pass))) { result = var->materialCounting ? convert_mate_value(material_counting_result(), ply) : VALUE_DRAW; return true; } // Tsume mode: Assume that side with king wins when not in check if (tsumeMode && !count(~sideToMove) && count(sideToMove) && !checkers()) { result = mate_in(ply); return true; } // Failing to checkmate with virtual pieces is a loss if (two_boards() && !checkers()) { int virtualCount = 0; for (PieceType pt : piece_types()) virtualCount += std::max(-count_in_hand(~sideToMove, pt), 0); if (virtualCount > 0) { result = mate_in(ply); return true; } } return false; } // 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 = captures_to_hand() ? st->pliesFromNull : 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 = captures_to_hand() ? st->pliesFromNull : std::min(st->rule50, st->pliesFromNull); if (end < 3 || var->nFoldValue != VALUE_DRAW || var->perpetualCheckIllegal || var->materialCounting || var->moveRepetitionIllegal) return false; Key originalKey = st->key; StateInfo* stp = st->previous; for (int i = 3; i <= end; i += 2) { stp = stp->previous->previous; Key moveKey = originalKey ^ stp->key; if ( (j = H1(moveKey), cuckoo[j] == moveKey) || (j = H2(moveKey), cuckoo[j] == moveKey)) { Move move = cuckooMove[j]; Square s1 = from_sq(move); Square s2 = to_sq(move); if (!((between_bb(s1, s2) ^ s2) & pieces())) { if (ply > i) return true; // For nodes before or at the root, check that the move is a // repetition rather than a move to the current position. // In the cuckoo table, both moves Rc1c5 and Rc5c1 are stored in // the same location, so we have to select which square to check. if (color_of(piece_on(empty(s1) ? s2 : s1)) != side_to_move()) continue; // For repetitions before or at the root, require one more if (stp->repetition) return true; } } } return false; } /// Position::counting_limit() returns the counting limit in full moves. int Position::counting_limit() const { assert(counting_rule()); switch (counting_rule()) { case MAKRUK_COUNTING: // No counting for side to move if (count() || count(~sideToMove) == 1) return 0; // Board's honor rule if (count(sideToMove) > 1) return 64; // Pieces' honor rule if (count(~sideToMove) > 1) return 8; if (count(~sideToMove) == 1) return 16; if (count(~sideToMove) > 1) return 22; if (count(~sideToMove) > 1) return 32; if (count(~sideToMove) == 1) return 44; return 64; case ASEAN_COUNTING: if (count() || count(sideToMove) > 1) return 0; if (count(~sideToMove)) return 16; if (count(~sideToMove)) return 44; if (count(~sideToMove)) return 64; return 0; default: assert(false); return 0; } } /// 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 = max_rank(); 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(variant(), 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) || (count(WHITE) && piece_on(square(WHITE)) != make_piece(WHITE, KING)) || (count(BLACK) && piece_on(square(BLACK)) != make_piece(BLACK, KING)) || ( ep_square() != SQ_NONE && relative_rank(~sideToMove, ep_square(), max_rank()) > Rank(double_step_rank_max() + 1))) assert(0 && "pos_is_ok: Default"); if (Fast) return true; if ( pieceCount[make_piece(~sideToMove, KING)] && (attackers_to(square(~sideToMove)) & pieces(sideToMove))) assert(0 && "pos_is_ok: Kings"); if ( pieceCount[make_piece(WHITE, PAWN)] > 64 || pieceCount[make_piece(BLACK, PAWN)] > 64) assert(0 && "pos_is_ok: Pawns"); if ( (pieces(WHITE) & pieces(BLACK)) || (pieces(WHITE) | pieces(BLACK)) != pieces() || popcount(pieces(WHITE)) > 64 || popcount(pieces(BLACK)) > 64) 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; ASSERT_ALIGNED(&si, Eval::NNUE::CacheLineSize); set_state(&si); if (std::memcmp(&si, st, sizeof(StateInfo))) assert(0 && "pos_is_ok: State"); for (Color c : {WHITE, BLACK}) for (PieceType pt = PAWN; pt <= KING; ++pt) { Piece pc = make_piece(c, pt); if ( pieceCount[pc] != popcount(pieces(c, pt)) || pieceCount[pc] != std::count(board, board + SQUARE_NB, pc)) assert(0 && "pos_is_ok: Pieces"); } for (Color c : { WHITE, BLACK }) for (CastlingRights cr : {c & KING_SIDE, c & QUEEN_SIDE}) { if (!can_castle(cr)) continue; if ( piece_on(castlingRookSquare[cr]) != make_piece(c, castling_rook_piece()) || castlingRightsMask[castlingRookSquare[cr]] != cr || (count(c) && (castlingRightsMask[square(c)] & cr) != cr)) assert(0 && "pos_is_ok: Castling"); } return true; } } // namespace Stockfish Fairy-Stockfish-fairy_sf_14_0_1_xq/src/position.h000066400000000000000000001163041414571233100221500ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef POSITION_H_INCLUDED #define POSITION_H_INCLUDED #include #include #include // For std::unique_ptr #include #include #include "bitboard.h" #include "evaluate.h" #include "psqt.h" #include "types.h" #include "variant.h" #include "movegen.h" #include "nnue/nnue_accumulator.h" namespace Stockfish { /// StateInfo struct stores information needed to restore a Position object to /// its previous state when we retract a move. Whenever a move is made on the /// board (by calling Position::do_move), a StateInfo object must be passed. struct StateInfo { // Copied when making a move Key pawnKey; Key materialKey; Value nonPawnMaterial[COLOR_NB]; int castlingRights; int rule50; int pliesFromNull; int countingPly; int countingLimit; CheckCount checksRemaining[COLOR_NB]; Square epSquare; Square castlingKingSquare[COLOR_NB]; Bitboard gatesBB[COLOR_NB]; // Not copied when making a move (will be recomputed anyhow) Key key; Bitboard checkersBB; Piece unpromotedCapturedPiece; Piece unpromotedBycatch[SQUARE_NB]; Bitboard promotedBycatch; Bitboard demotedBycatch; StateInfo* previous; Bitboard blockersForKing[COLOR_NB]; Bitboard pinners[COLOR_NB]; Bitboard checkSquares[PIECE_TYPE_NB]; Piece capturedPiece; Bitboard nonSlidingRiders; Bitboard flippedPieces; Bitboard pseudoRoyals; OptBool legalCapture; bool capturedpromoted; bool shak; bool bikjang; bool pass; Move move; 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> 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 Variant* v, const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th, bool sfen = false); Position& set(const std::string& code, Color c, StateInfo* si); std::string fen(bool sfen = false, bool showPromoted = false, int countStarted = 0, std::string holdings = "-") const; // Variant rule properties const Variant* variant() const; Rank max_rank() const; File max_file() const; int ranks() const; int files() const; bool two_boards() const; Bitboard board_bb() const; Bitboard board_bb(Color c, PieceType pt) const; const std::set& piece_types() const; const std::string& piece_to_char() const; const std::string& piece_to_char_synonyms() const; Rank promotion_rank() const; const std::set >& promotion_piece_types() const; bool sittuyin_promotion() const; int promotion_limit(PieceType pt) const; PieceType promoted_piece_type(PieceType pt) const; bool piece_promotion_on_capture() const; bool mandatory_pawn_promotion() const; bool mandatory_piece_promotion() const; bool piece_demotion() const; bool blast_on_capture() const; bool endgame_eval() const; bool double_step_enabled() const; Rank double_step_rank_max() const; Rank double_step_rank_min() const; bool castling_enabled() const; bool castling_dropped_piece() const; File castling_kingside_file() const; File castling_queenside_file() const; Rank castling_rank(Color c) const; File castling_king_file() const; PieceType castling_king_piece() const; PieceType castling_rook_piece() const; PieceType king_type() const; PieceType nnue_king() const; Square nnue_king_square(Color c) const; bool nnue_use_pockets() const; bool nnue_applicable() const; bool checking_permitted() const; bool drop_checks() const; bool must_capture() const; bool has_capture() const; bool must_drop() const; bool piece_drops() const; bool drop_loop() const; bool captures_to_hand() const; bool first_rank_pawn_drops() const; bool drop_on_top() const; EnclosingRule enclosing_drop() const; Bitboard drop_region(Color c) const; Bitboard drop_region(Color c, PieceType pt) const; bool sittuyin_rook_drop() const; bool drop_opposite_colored_bishop() const; bool drop_promoted() const; PieceType drop_no_doubled() const; bool immobility_illegal() const; bool gating() const; bool arrow_gating() const; bool seirawan_gating() const; bool cambodian_moves() const; Bitboard diagonal_lines() const; bool pass() const; bool pass_on_stalemate() const; Bitboard promoted_soldiers(Color c) const; bool makpong() const; EnclosingRule flip_enclosed_pieces() const; // winning conditions int n_move_rule() const; int n_fold_rule() const; Value stalemate_value(int ply = 0) const; Value checkmate_value(int ply = 0) const; Value extinction_value(int ply = 0) const; bool extinction_claim() const; const std::set& extinction_piece_types() const; bool extinction_single_piece() const; int extinction_piece_count() const; int extinction_opponent_piece_count() const; bool extinction_pseudo_royal() const; PieceType capture_the_flag_piece() const; Bitboard capture_the_flag(Color c) const; bool flag_move() const; bool check_counting() const; int connect_n() const; CheckCount checks_remaining(Color c) const; MaterialCounting material_counting() const; CountingRule counting_rule() const; // Variant-specific properties int count_in_hand(PieceType pt) const; int count_in_hand(Color c, PieceType pt) const; int count_with_hand(Color c, PieceType pt) const; bool bikjang() const; bool allow_virtual_drop(Color c, PieceType pt) const; // Position representation Bitboard pieces(PieceType pt = ALL_PIECES) 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; Bitboard pieces(Color c, PieceType pt1, PieceType pt2, PieceType pt3) const; Bitboard major_pieces(Color c) const; Bitboard non_sliding_riders() const; Piece piece_on(Square s) const; Piece unpromoted_piece_on(Square s) const; Square ep_square() const; Square castling_king_square(Color c) const; Bitboard gates(Color c) const; bool empty(Square s) const; int count(Color c, PieceType pt) const; template int count(Color c) const; template int count() const; template Square square(Color c) const; Square square(Color c, PieceType pt) 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; // Attacks to/from a given square Bitboard attackers_to(Square s) const; Bitboard attackers_to(Square s, Color c) const; Bitboard attackers_to(Square s, Bitboard occupied) const; Bitboard attackers_to(Square s, Bitboard occupied, Color c) const; Bitboard attackers_to(Square s, Bitboard occupied, Color c, Bitboard janggiCannons) const; Bitboard attacks_from(Color c, PieceType pt, Square s) const; Bitboard moves_from(Color c, PieceType pt, Square s) const; Bitboard slider_blockers(Bitboard sliders, Square s, Bitboard& pinners, Color c) const; // Properties of moves bool legal(Move m) const; bool pseudo_legal(const Move m) const; bool virtual_drop(Move m) const; bool capture(Move m) const; bool capture_or_promotion(Move m) const; bool gives_check(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; bool is_promoted(Square s) 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 Value blast_see(Move m) const; 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_immediate_game_end() const; bool is_immediate_game_end(Value& result, int ply = 0) const; bool is_optional_game_end() const; bool is_optional_game_end(Value& result, int ply = 0, int countStarted = 0) const; bool is_game_end(Value& result, int ply = 0) const; Value material_counting_result() const; bool is_draw(int ply) const; bool has_game_cycle(int ply) const; bool has_repeated() const; int counting_limit() const; int counting_ply(int countStarted) 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; void put_piece(Piece pc, Square s, bool isPromoted = false, Piece unpromotedPc = NO_PIECE); void remove_piece(Square s); private: // Initialization helpers (used while setting up a position) void set_castling_right(Color c, Square rfrom); void set_state(StateInfo* si) const; void set_check_info(StateInfo* si) const; // Other helpers void move_piece(Square from, Square to); template void do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto); // Data members Piece board[SQUARE_NB]; Piece unpromotedBoard[SQUARE_NB]; Bitboard byTypeBB[PIECE_TYPE_NB]; Bitboard byColorBB[COLOR_NB]; int pieceCount[PIECE_NB]; int castlingRightsMask[SQUARE_NB]; Square castlingRookSquare[CASTLING_RIGHT_NB]; Bitboard castlingPath[CASTLING_RIGHT_NB]; Thread* thisThread; StateInfo* st; int gamePly; Color sideToMove; Score psq; // variant-specific const Variant* var; bool tsumeMode; bool chess960; int pieceCountInHand[COLOR_NB][PIECE_TYPE_NB]; int virtualPieces; Bitboard promotedPieces; void add_to_hand(Piece pc); void remove_from_hand(Piece pc); void drop_piece(Piece pc_hand, Piece pc_drop, Square s); void undrop_piece(Piece pc_hand, Square s); }; extern std::ostream& operator<<(std::ostream& os, const Position& pos); inline const Variant* Position::variant() const { assert(var != nullptr); return var; } inline Rank Position::max_rank() const { assert(var != nullptr); return var->maxRank; } inline File Position::max_file() const { assert(var != nullptr); return var->maxFile; } inline int Position::ranks() const { assert(var != nullptr); return var->maxRank + 1; } inline int Position::files() const { assert(var != nullptr); return var->maxFile + 1; } inline bool Position::two_boards() const { assert(var != nullptr); return var->twoBoards; } inline Bitboard Position::board_bb() const { assert(var != nullptr); return board_size_bb(var->maxFile, var->maxRank); } inline Bitboard Position::board_bb(Color c, PieceType pt) const { assert(var != nullptr); return var->mobilityRegion[c][pt] ? var->mobilityRegion[c][pt] & board_bb() : board_bb(); } inline const std::set& Position::piece_types() const { assert(var != nullptr); return var->pieceTypes; } inline const std::string& Position::piece_to_char() const { assert(var != nullptr); return var->pieceToChar; } inline const std::string& Position::piece_to_char_synonyms() const { assert(var != nullptr); return var->pieceToCharSynonyms; } inline Rank Position::promotion_rank() const { assert(var != nullptr); return var->promotionRank; } inline const std::set >& Position::promotion_piece_types() const { assert(var != nullptr); return var->promotionPieceTypes; } inline bool Position::sittuyin_promotion() const { assert(var != nullptr); return var->sittuyinPromotion; } inline int Position::promotion_limit(PieceType pt) const { assert(var != nullptr); return var->promotionLimit[pt]; } inline PieceType Position::promoted_piece_type(PieceType pt) const { assert(var != nullptr); return var->promotedPieceType[pt]; } inline bool Position::piece_promotion_on_capture() const { assert(var != nullptr); return var->piecePromotionOnCapture; } inline bool Position::mandatory_pawn_promotion() const { assert(var != nullptr); return var->mandatoryPawnPromotion; } inline bool Position::mandatory_piece_promotion() const { assert(var != nullptr); return var->mandatoryPiecePromotion; } inline bool Position::piece_demotion() const { assert(var != nullptr); return var->pieceDemotion; } inline bool Position::blast_on_capture() const { assert(var != nullptr); return var->blastOnCapture; } inline bool Position::endgame_eval() const { assert(var != nullptr); return var->endgameEval && !count_in_hand(ALL_PIECES) && count() == 2; } inline bool Position::double_step_enabled() const { assert(var != nullptr); return var->doubleStep; } inline Rank Position::double_step_rank_max() const { assert(var != nullptr); return var->doubleStepRank; } inline Rank Position::double_step_rank_min() const { assert(var != nullptr); return var->doubleStepRankMin; } inline bool Position::castling_enabled() const { assert(var != nullptr); return var->castling; } inline bool Position::castling_dropped_piece() const { assert(var != nullptr); return var->castlingDroppedPiece; } inline File Position::castling_kingside_file() const { assert(var != nullptr); return var->castlingKingsideFile; } inline File Position::castling_queenside_file() const { assert(var != nullptr); return var->castlingQueensideFile; } inline Rank Position::castling_rank(Color c) const { assert(var != nullptr); return relative_rank(c, var->castlingRank, max_rank()); } inline File Position::castling_king_file() const { assert(var != nullptr); return var->castlingKingFile; } inline PieceType Position::castling_king_piece() const { assert(var != nullptr); return var->castlingKingPiece; } inline PieceType Position::castling_rook_piece() const { assert(var != nullptr); return var->castlingRookPiece; } inline PieceType Position::king_type() const { assert(var != nullptr); return var->kingType; } inline PieceType Position::nnue_king() const { assert(var != nullptr); return var->nnueKing; } inline Square Position::nnue_king_square(Color c) const { return nnue_king() ? square(c, nnue_king()) : SQ_NONE; } inline bool Position::nnue_use_pockets() const { assert(var != nullptr); return var->nnueUsePockets; } inline bool Position::nnue_applicable() const { // Do not use NNUE during setup phases (placement, sittuyin) return (!count_in_hand(ALL_PIECES) || nnue_use_pockets()) && !virtualPieces; } inline bool Position::checking_permitted() const { assert(var != nullptr); return var->checking; } inline bool Position::drop_checks() const { assert(var != nullptr); return var->dropChecks; } inline bool Position::must_capture() const { assert(var != nullptr); return var->mustCapture; } inline bool Position::has_capture() const { // Check for cached value if (st->legalCapture != NO_VALUE) return st->legalCapture == VALUE_TRUE; if (checkers()) { for (const auto& mevasion : MoveList(*this)) if (capture(mevasion) && legal(mevasion)) { st->legalCapture = VALUE_TRUE; return true; } } else { for (const auto& mcap : MoveList(*this)) if (capture(mcap) && legal(mcap)) { st->legalCapture = VALUE_TRUE; return true; } } st->legalCapture = VALUE_FALSE; return false; } inline bool Position::must_drop() const { assert(var != nullptr); return var->mustDrop; } inline bool Position::piece_drops() const { assert(var != nullptr); return var->pieceDrops; } inline bool Position::drop_loop() const { assert(var != nullptr); return var->dropLoop; } inline bool Position::captures_to_hand() const { assert(var != nullptr); return var->capturesToHand; } inline bool Position::first_rank_pawn_drops() const { assert(var != nullptr); return var->firstRankPawnDrops; } inline bool Position::drop_on_top() const { assert(var != nullptr); return var->dropOnTop; } inline EnclosingRule Position::enclosing_drop() const { assert(var != nullptr); return var->enclosingDrop; } inline Bitboard Position::drop_region(Color c) const { assert(var != nullptr); return c == WHITE ? var->whiteDropRegion : var->blackDropRegion; } inline Bitboard Position::drop_region(Color c, PieceType pt) const { Bitboard b = drop_region(c) & board_bb(c, pt); // Connect4-style drops if (drop_on_top()) b &= shift(pieces()) | Rank1BB; // Pawns on back ranks if (pt == PAWN) { if (!var->promotionZonePawnDrops) b &= ~zone_bb(c, promotion_rank(), max_rank()); if (!first_rank_pawn_drops()) b &= ~rank_bb(relative_rank(c, RANK_1, max_rank())); } // Doubled shogi pawns if (pt == drop_no_doubled()) for (File f = FILE_A; f <= max_file(); ++f) if (popcount(file_bb(f) & pieces(c, pt)) >= var->dropNoDoubledCount) b &= ~file_bb(f); // Sittuyin rook drops if (pt == ROOK && sittuyin_rook_drop()) b &= rank_bb(relative_rank(c, RANK_1, max_rank())); // Filter out squares where the drop does not enclose at least one opponent's piece if (enclosing_drop()) { // Reversi start if (var->enclosingDropStart & ~pieces()) b &= var->enclosingDropStart; else { if (enclosing_drop() == REVERSI) { Bitboard theirs = pieces(~c); b &= shift(theirs) | shift(theirs) | shift(theirs) | shift(theirs) | shift(theirs) | shift(theirs) | shift(theirs) | shift(theirs); Bitboard b2 = b; while (b2) { Square s = pop_lsb(b2); if (!(attacks_bb(c, QUEEN, s, board_bb() & ~pieces(~c)) & ~PseudoAttacks[c][KING][s] & pieces(c))) b ^= s; } } else { assert(enclosing_drop() == ATAXX); Bitboard ours = pieces(c); b &= shift(ours) | shift(ours) | shift(ours) | shift(ours) | shift(ours) | shift(ours) | shift(ours) | shift(ours); } } } return b; } inline bool Position::sittuyin_rook_drop() const { assert(var != nullptr); return var->sittuyinRookDrop; } inline bool Position::drop_opposite_colored_bishop() const { assert(var != nullptr); return var->dropOppositeColoredBishop; } inline bool Position::drop_promoted() const { assert(var != nullptr); return var->dropPromoted; } inline PieceType Position::drop_no_doubled() const { assert(var != nullptr); return var->dropNoDoubled; } inline bool Position::immobility_illegal() const { assert(var != nullptr); return var->immobilityIllegal; } inline bool Position::gating() const { assert(var != nullptr); return var->gating; } inline bool Position::arrow_gating() const { assert(var != nullptr); return var->arrowGating; } inline bool Position::seirawan_gating() const { assert(var != nullptr); return var->seirawanGating; } inline bool Position::cambodian_moves() const { assert(var != nullptr); return var->cambodianMoves; } inline Bitboard Position::diagonal_lines() const { assert(var != nullptr); return var->diagonalLines; } inline bool Position::pass() const { assert(var != nullptr); return var->pass || var->passOnStalemate; } inline bool Position::pass_on_stalemate() const { assert(var != nullptr); return var->passOnStalemate; } inline Bitboard Position::promoted_soldiers(Color c) const { assert(var != nullptr); return pieces(c, SOLDIER) & zone_bb(c, var->soldierPromotionRank, max_rank()); } inline bool Position::makpong() const { assert(var != nullptr); return var->makpongRule; } inline int Position::n_move_rule() const { assert(var != nullptr); return var->nMoveRule; } inline int Position::n_fold_rule() const { assert(var != nullptr); return var->nFoldRule; } inline EnclosingRule Position::flip_enclosed_pieces() const { assert(var != nullptr); return var->flipEnclosedPieces; } inline Value Position::stalemate_value(int ply) const { assert(var != nullptr); if (var->stalematePieceCount) { int c = count(sideToMove) - count(~sideToMove); return c == 0 ? VALUE_DRAW : convert_mate_value(c < 0 ? var->stalemateValue : -var->stalemateValue, ply); } // Check for checkmate of pseudo-royal pieces if (var->extinctionPseudoRoyal) { Bitboard pseudoRoyals = st->pseudoRoyals & pieces(sideToMove); Bitboard pseudoRoyalsTheirs = st->pseudoRoyals & pieces(~sideToMove); while (pseudoRoyals) { Square sr = pop_lsb(pseudoRoyals); if ( !(blast_on_capture() && (pseudoRoyalsTheirs & attacks_bb(sr))) && attackers_to(sr, ~sideToMove)) return convert_mate_value(var->checkmateValue, ply); } } return convert_mate_value(var->stalemateValue, ply); } inline Value Position::checkmate_value(int ply) const { assert(var != nullptr); // Check for illegal mate by shogi pawn drop if ( var->shogiPawnDropMateIllegal && !(checkers() & ~pieces(SHOGI_PAWN)) && !st->capturedPiece && st->pliesFromNull > 0 && (st->materialKey != st->previous->materialKey)) { return mate_in(ply); } // Check for shatar mate rule if (var->shatarMateRule) { // Mate by knight is illegal if (!(checkers() & ~pieces(KNIGHT))) return mate_in(ply); StateInfo* stp = st; while (stp->checkersBB) { // Return mate score if there is at least one shak in series of checks if (stp->shak) return convert_mate_value(var->checkmateValue, ply); if (stp->pliesFromNull < 2) break; stp = stp->previous->previous; } // Niol return VALUE_DRAW; } // Checkmate using virtual pieces if (two_boards() && var->checkmateValue < VALUE_ZERO) { Value virtualMaterial = VALUE_ZERO; for (PieceType pt : piece_types()) virtualMaterial += std::max(-count_in_hand(~sideToMove, pt), 0) * PieceValue[MG][pt]; if (virtualMaterial > 0) return -VALUE_VIRTUAL_MATE + virtualMaterial / 20 + ply; } // Return mate value return convert_mate_value(var->checkmateValue, ply); } inline Value Position::extinction_value(int ply) const { assert(var != nullptr); return convert_mate_value(var->extinctionValue, ply); } inline bool Position::extinction_claim() const { assert(var != nullptr); return var->extinctionClaim; } inline const std::set& Position::extinction_piece_types() const { assert(var != nullptr); return var->extinctionPieceTypes; } inline bool Position::extinction_single_piece() const { assert(var != nullptr); return var->extinctionValue == -VALUE_MATE && std::any_of(var->extinctionPieceTypes.begin(), var->extinctionPieceTypes.end(), [](PieceType pt) { return pt != ALL_PIECES; }); } inline int Position::extinction_piece_count() const { assert(var != nullptr); return var->extinctionPieceCount; } inline int Position::extinction_opponent_piece_count() const { assert(var != nullptr); return var->extinctionOpponentPieceCount; } inline bool Position::extinction_pseudo_royal() const { assert(var != nullptr); return var->extinctionPseudoRoyal; } inline PieceType Position::capture_the_flag_piece() const { assert(var != nullptr); return var->flagPiece; } inline Bitboard Position::capture_the_flag(Color c) const { assert(var != nullptr); return c == WHITE ? var->whiteFlag : var->blackFlag; } inline bool Position::flag_move() const { assert(var != nullptr); return var->flagMove; } inline bool Position::check_counting() const { assert(var != nullptr); return var->checkCounting; } inline int Position::connect_n() const { assert(var != nullptr); return var->connectN; } inline CheckCount Position::checks_remaining(Color c) const { return st->checksRemaining[c]; } inline MaterialCounting Position::material_counting() const { assert(var != nullptr); return var->materialCounting; } inline CountingRule Position::counting_rule() const { assert(var != nullptr); return var->countingRule; } inline bool Position::is_immediate_game_end() const { Value result; return is_immediate_game_end(result); } inline bool Position::is_optional_game_end() const { Value result; return is_optional_game_end(result); } inline bool Position::is_draw(int ply) const { Value result; return is_optional_game_end(result, ply); } inline bool Position::is_game_end(Value& result, int ply) const { return is_immediate_game_end(result, ply) || is_optional_game_end(result, ply); } 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::unpromoted_piece_on(Square s) const { return unpromotedBoard[s]; } inline Piece Position::moved_piece(Move m) const { if (type_of(m) == DROP) return make_piece(sideToMove, dropped_piece_type(m)); return piece_on(from_sq(m)); } inline Bitboard Position::pieces(PieceType pt) 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)); } inline Bitboard Position::pieces(Color c, PieceType pt1, PieceType pt2, PieceType pt3) const { return pieces(c) & (pieces(pt1) | pieces(pt2) | pieces(pt3)); } inline Bitboard Position::major_pieces(Color c) const { return pieces(c) & (pieces(QUEEN) | pieces(AIWOK) | pieces(ARCHBISHOP) | pieces(CHANCELLOR) | pieces(AMAZON)); } inline Bitboard Position::non_sliding_riders() const { return st->nonSlidingRiders; } inline int Position::count(Color c, PieceType pt) const { return pieceCount[make_piece(c, pt)]; } template inline int Position::count(Color c) const { return pieceCount[make_piece(c, Pt)]; } template inline int Position::count() const { return count(WHITE) + count(BLACK); } template inline Square Position::square(Color c) const { assert(count(c) == 1); return lsb(pieces(c, Pt)); } inline Square Position::square(Color c, PieceType pt) const { assert(count(c, pt) == 1); return lsb(pieces(c, pt)); } inline Square Position::ep_square() const { return st->epSquare; } inline Square Position::castling_king_square(Color c) const { return st->castlingKingSquare[c]; } inline Bitboard Position::gates(Color c) const { assert(var != nullptr); return st->gatesBB[c]; } inline bool Position::is_on_semiopen_file(Color c, Square s) const { return !((pieces(c, PAWN) | pieces(c, SHOGI_PAWN, SOLDIER)) & 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::attacks_from(Color c, PieceType pt, Square s) const { if (var->fastAttacks || var->fastAttacks2) return attacks_bb(c, pt, s, byTypeBB[ALL_PIECES]) & board_bb(); PieceType movePt = pt == KING ? king_type() : pt; Bitboard b = attacks_bb(c, movePt, s, byTypeBB[ALL_PIECES]); // Xiangqi soldier if (pt == SOLDIER && !(promoted_soldiers(c) & s)) b &= file_bb(file_of(s)); // Janggi cannon restrictions if (pt == JANGGI_CANNON) { b &= ~pieces(pt); b &= attacks_bb(c, pt, s, pieces() ^ pieces(pt)); } // Janggi palace moves if (diagonal_lines() & s) { PieceType diagType = movePt == WAZIR ? FERS : movePt == SOLDIER ? PAWN : movePt == ROOK ? BISHOP : NO_PIECE_TYPE; if (diagType) b |= attacks_bb(c, diagType, s, pieces()) & diagonal_lines(); else if (movePt == JANGGI_CANNON) b |= rider_attacks_bb(s, pieces()) & rider_attacks_bb(s, pieces() ^ pieces(pt)) & ~pieces(pt) & diagonal_lines(); } return b & board_bb(c, pt); } inline Bitboard Position::moves_from(Color c, PieceType pt, Square s) const { if (var->fastAttacks || var->fastAttacks2) return moves_bb(c, pt, s, byTypeBB[ALL_PIECES]) & board_bb(); PieceType movePt = pt == KING ? king_type() : pt; Bitboard b = moves_bb(c, movePt, s, byTypeBB[ALL_PIECES]); // Xiangqi soldier if (pt == SOLDIER && !(promoted_soldiers(c) & s)) b &= file_bb(file_of(s)); // Janggi cannon restrictions if (pt == JANGGI_CANNON) { b &= ~pieces(pt); b &= attacks_bb(c, pt, s, pieces() ^ pieces(pt)); } // Janggi palace moves if (diagonal_lines() & s) { PieceType diagType = movePt == WAZIR ? FERS : movePt == SOLDIER ? PAWN : movePt == ROOK ? BISHOP : NO_PIECE_TYPE; if (diagType) b |= attacks_bb(c, diagType, s, pieces()) & diagonal_lines(); else if (movePt == JANGGI_CANNON) b |= rider_attacks_bb(s, pieces()) & rider_attacks_bb(s, pieces() ^ pieces(pt)) & ~pieces(pt) & diagonal_lines(); } return b & board_bb(c, pt); } inline Bitboard Position::attackers_to(Square s) const { return attackers_to(s, pieces()); } inline Bitboard Position::attackers_to(Square s, Color c) const { return attackers_to(s, byTypeBB[ALL_PIECES], c); } inline Bitboard Position::attackers_to(Square s, Bitboard occupied, Color c) const { return attackers_to(s, occupied, c, byTypeBB[JANGGI_CANNON]); } 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::pawn_passed(Color c, Square s) const { return !(pieces(~c, PAWN) & passed_pawn_span(c, s)); } 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->rule50 < 14 ? st->key : st->key ^ make_key((st->rule50 - 14) / 8); } 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::counting_ply(int countStarted) const { return countStarted == 0 || (count(WHITE) <= 1 || count(BLACK) <= 1) ? st->countingPly : countStarted < 0 ? 0 : std::min(st->countingPly, std::max(1 + gamePly - countStarted, 0)); } inline int Position::rule50_count() const { return st->rule50; } inline bool Position::opposite_bishops() const { return count(WHITE) == 1 && count(BLACK) == 1 && opposite_colors(square(WHITE), square(BLACK)); } inline bool Position::is_promoted(Square s) const { return promotedPieces & s; } 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) == PROMOTION || type_of(m) == EN_PASSANT || (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 && from_sq(m) != to_sq(m)) || type_of(m) == EN_PASSANT; } inline bool Position::virtual_drop(Move m) const { assert(is_ok(m)); return type_of(m) == DROP && count_in_hand(side_to_move(), in_hand_piece_type(m)) <= 0; } 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, bool isPromoted, Piece unpromotedPc) { board[s] = pc; byTypeBB[ALL_PIECES] |= byTypeBB[type_of(pc)] |= s; byColorBB[color_of(pc)] |= s; pieceCount[pc]++; pieceCount[make_piece(color_of(pc), ALL_PIECES)]++; psq += PSQT::psq[pc][s]; if (isPromoted) promotedPieces |= s; unpromotedBoard[s] = unpromotedPc; } inline void Position::remove_piece(Square s) { Piece pc = board[s]; byTypeBB[ALL_PIECES] ^= s; byTypeBB[type_of(pc)] ^= s; byColorBB[color_of(pc)] ^= s; board[s] = NO_PIECE; pieceCount[pc]--; pieceCount[make_piece(color_of(pc), ALL_PIECES)]--; psq -= PSQT::psq[pc][s]; promotedPieces -= s; unpromotedBoard[s] = NO_PIECE; } inline void Position::move_piece(Square from, Square to) { Piece pc = board[from]; Bitboard fromTo = square_bb(from) ^ to; // from == to needs to cancel out byTypeBB[ALL_PIECES] ^= fromTo; byTypeBB[type_of(pc)] ^= fromTo; byColorBB[color_of(pc)] ^= fromTo; board[from] = NO_PIECE; board[to] = pc; psq += PSQT::psq[pc][to] - PSQT::psq[pc][from]; if (is_promoted(from)) promotedPieces ^= fromTo; unpromotedBoard[to] = unpromotedBoard[from]; unpromotedBoard[from] = NO_PIECE; } inline void Position::do_move(Move m, StateInfo& newSt) { do_move(m, newSt, gives_check(m)); } inline StateInfo* Position::state() const { return st; } // Variant-specific inline int Position::count_in_hand(PieceType pt) const { return pieceCountInHand[WHITE][pt] + pieceCountInHand[BLACK][pt]; } inline int Position::count_in_hand(Color c, PieceType pt) const { return pieceCountInHand[c][pt]; } inline int Position::count_with_hand(Color c, PieceType pt) const { return pieceCount[make_piece(c, pt)] + pieceCountInHand[c][pt]; } inline bool Position::bikjang() const { return st->bikjang; } inline bool Position::allow_virtual_drop(Color c, PieceType pt) const { assert(two_boards()); // Do we allow a virtual drop? return pt != KING && ( count_in_hand(c, PAWN) >= -(pt == PAWN) && count_in_hand(c, KNIGHT) >= -(pt == PAWN) && count_in_hand(c, BISHOP) >= -(pt == PAWN) && count_in_hand(c, ROOK) >= 0 && count_in_hand(c, QUEEN) >= 0); } inline Value Position::material_counting_result() const { auto weigth_count = [this](PieceType pt, int v){ return v * (count(WHITE, pt) - count(BLACK, pt)); }; int materialCount; Value result; switch (var->materialCounting) { case JANGGI_MATERIAL: materialCount = weigth_count(ROOK, 13) + weigth_count(JANGGI_CANNON, 7) + weigth_count(HORSE, 5) + weigth_count(JANGGI_ELEPHANT, 3) + weigth_count(WAZIR, 3) + weigth_count(SOLDIER, 2) - 1; result = materialCount > 0 ? VALUE_MATE : -VALUE_MATE; break; case UNWEIGHTED_MATERIAL: result = count(WHITE, ALL_PIECES) > count(BLACK, ALL_PIECES) ? VALUE_MATE : count(WHITE, ALL_PIECES) < count(BLACK, ALL_PIECES) ? -VALUE_MATE : VALUE_DRAW; break; case WHITE_DRAW_ODDS: result = VALUE_MATE; break; case BLACK_DRAW_ODDS: result = -VALUE_MATE; break; default: assert(false); result = VALUE_DRAW; } return sideToMove == WHITE ? result : -result; } inline void Position::add_to_hand(Piece pc) { pieceCountInHand[color_of(pc)][type_of(pc)]++; pieceCountInHand[color_of(pc)][ALL_PIECES]++; psq += PSQT::psq[pc][SQ_NONE]; } inline void Position::remove_from_hand(Piece pc) { pieceCountInHand[color_of(pc)][type_of(pc)]--; pieceCountInHand[color_of(pc)][ALL_PIECES]--; psq -= PSQT::psq[pc][SQ_NONE]; } inline void Position::drop_piece(Piece pc_hand, Piece pc_drop, Square s) { assert(pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)] > 0 || var->twoBoards); put_piece(pc_drop, s, pc_drop != pc_hand, pc_drop != pc_hand ? pc_hand : NO_PIECE); remove_from_hand(pc_hand); virtualPieces += (pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)] < 0); } inline void Position::undrop_piece(Piece pc_hand, Square s) { virtualPieces -= (pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)] < 0); remove_piece(s); board[s] = NO_PIECE; add_to_hand(pc_hand); assert(pieceCountInHand[color_of(pc_hand)][type_of(pc_hand)] > 0 || var->twoBoards); } } // namespace Stockfish #endif // #ifndef POSITION_H_INCLUDED Fairy-Stockfish-fairy_sf_14_0_1_xq/src/psqt.cpp000066400000000000000000000434201414571233100216240ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "psqt.h" #include #include #include #include "bitboard.h" #include "types.h" #include "piece.h" #include "variant.h" #include "misc.h" namespace Stockfish { Value EvalPieceValue[PHASE_NB][PIECE_NB]; Value CapturePieceValue[PHASE_NB][PIECE_NB]; Value PieceValue[PHASE_NB][PIECE_NB] = { { VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg, FersValueMg, AlfilValueMg, FersAlfilValueMg, SilverValueMg, AiwokValueMg, BersValueMg, ArchbishopValueMg, ChancellorValueMg, AmazonValueMg, KnibisValueMg, BiskniValueMg, KnirooValueMg, RookniValueMg, ShogiPawnValueMg, LanceValueMg, ShogiKnightValueMg, GoldValueMg, DragonHorseValueMg, ClobberPieceValueMg, BreakthroughPieceValueMg, ImmobilePieceValueMg, CannonPieceValueMg, JanggiCannonPieceValueMg, SoldierValueMg, HorseValueMg, ElephantValueMg, JanggiElephantValueMg, BannerValueMg, WazirValueMg, CommonerValueMg, CentaurValueMg, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg, FersValueMg, AlfilValueMg, FersAlfilValueMg, SilverValueMg, AiwokValueMg, BersValueMg, ArchbishopValueMg, ChancellorValueMg, AmazonValueMg, KnibisValueMg, BiskniValueMg, KnirooValueMg, RookniValueMg, ShogiPawnValueMg, LanceValueMg, ShogiKnightValueMg, GoldValueMg, DragonHorseValueMg, ClobberPieceValueMg, BreakthroughPieceValueMg, ImmobilePieceValueMg, CannonPieceValueMg, JanggiCannonPieceValueMg, SoldierValueMg, HorseValueMg, ElephantValueMg, JanggiElephantValueMg, BannerValueMg, WazirValueMg, CommonerValueMg, CentaurValueMg, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, }, { VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg, FersValueEg, AlfilValueEg, FersAlfilValueEg, SilverValueEg, AiwokValueEg, BersValueEg, ArchbishopValueEg, ChancellorValueEg, AmazonValueEg, KnibisValueEg, BiskniValueEg, KnirooValueEg, RookniValueEg, ShogiPawnValueEg, LanceValueEg, ShogiKnightValueEg, GoldValueEg, DragonHorseValueEg, ClobberPieceValueEg, BreakthroughPieceValueEg, ImmobilePieceValueEg, CannonPieceValueEg, JanggiCannonPieceValueEg, SoldierValueEg, HorseValueEg, ElephantValueEg, JanggiElephantValueEg, BannerValueEg, WazirValueEg, CommonerValueEg, CentaurValueEg, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg, FersValueEg, AlfilValueEg, FersAlfilValueEg, SilverValueEg, AiwokValueEg, BersValueEg, ArchbishopValueEg, ChancellorValueEg, AmazonValueEg, KnibisValueEg, BiskniValueEg, KnirooValueEg, RookniValueEg, ShogiPawnValueEg, LanceValueEg, ShogiKnightValueEg, GoldValueEg, DragonHorseValueEg, ClobberPieceValueEg, BreakthroughPieceValueEg, ImmobilePieceValueEg, CannonPieceValueEg, JanggiCannonPieceValueEg, SoldierValueEg, HorseValueEg, ElephantValueEg, JanggiElephantValueEg, BannerValueEg, WazirValueEg, CommonerValueEg, CentaurValueEg, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, VALUE_ZERO, }, }; namespace { auto constexpr S = make_score; // 'Bonus' contains Piece-Square parameters. // Scores are explicit for files A to D, implicitly mirrored for E to H. constexpr Score Bonus[PIECE_TYPE_NB][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(-37,-40), S(-4 ,-21), S( -6,-26), S(-16, -8) }, { S(-11,-26), S( 6, -9), S( 13,-12), S( 3, 1) }, { S(-5 ,-11), S( 15, -1), S( -4, -1), S( 12, 7) }, { S(-4 ,-14), S( 8, -4), S( 18, 0), S( 27, 12) }, { S(-8 ,-12), S( 20, -1), S( 15,-10), S( 22, 11) }, { S(-11,-21), S( 4, 4), S( 1, 3), S( 8, 4) }, { S(-12,-22), S(-10,-14), S( 4, -1), S( 0, 1) }, { S(-34,-32), S( 1,-29), S(-10,-26), S(-16,-17) } }, { // 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,-54), 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,-11), S( 8, 1) }, { S(-5,-50), S( 6,-27), S(10,-24), S( 8, -8) }, { S(-2,-74), S(-2,-52), S( 1,-43), S(-2,-34) } } }; constexpr Score KingBonus[RANK_NB][int(FILE_NB) / 2] = { { 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( 2, -8), S( 4, -6), S( 11, 9), S( 18, 5), S( 16, 16), S( 21, 6), S( 9, -6), S( -3,-18) }, { S( -9, -9), S(-15, -7), S( 11,-10), S( 15, 5), S( 31, 2), S( 23, 3), S( 6, -8), S(-20, -5) }, { S( -3, 7), S(-20, 1), S( 8, -8), S( 19, -2), S( 39,-14), S( 17,-13), S( 2,-11), S( -5, -6) }, { S( 11, 12), S( -4, 6), S(-11, 2), S( 2, -6), S( 11, -5), S( 0, -4), S(-12, 14), S( 5, 9) }, { S( 3, 27), S(-11, 18), S( -6, 19), S( 22, 29), S( -8, 30), S( -5, 9), S(-14, 8), S(-11, 14) }, { S( -7, -1), S( 6,-14), S( -2, 13), S(-11, 22), S( 4, 24), S(-14, 17), S( 10, 7), S( -9, 7) } }; // Estimate piece value Value piece_value(Phase phase, PieceType pt) { const PieceInfo* pi = pieceMap.find(pt)->second; int v0 = (phase == MG ? 55 : 60) * pi->steps[MODALITY_CAPTURE].size() + (phase == MG ? 30 : 40) * pi->steps[MODALITY_QUIET].size() + (phase == MG ? 185 : 180) * pi->slider[MODALITY_CAPTURE].size() + (phase == MG ? 55 : 50) * pi->slider[MODALITY_QUIET].size() // Hoppers are more useful with more pieces on the board + (phase == MG ? 100 : 80) * pi->hopper[MODALITY_CAPTURE].size() + (phase == MG ? 80 : 60) * pi->hopper[MODALITY_QUIET].size() // Rook sliding directions are more valuable, especially in endgame + (phase == MG ? 10 : 30) * std::count_if(pi->slider[MODALITY_CAPTURE].begin(), pi->slider[MODALITY_CAPTURE].end(), [](const std::pair& d) { return std::abs(d.first) == NORTH || std::abs(d.first) == 1; }) + (phase == MG ? 30 : 45) * std::count_if(pi->slider[MODALITY_QUIET].begin(), pi->slider[MODALITY_QUIET].end(), [](const std::pair& d) { return std::abs(d.first) == NORTH || std::abs(d.first) == 1; }); return Value(v0 * exp(double(v0) / 10000)); } } // namespace namespace PSQT { Score psq[PIECE_NB][SQUARE_NB + 1]; // 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(const Variant* v) { PieceType strongestPiece = NO_PIECE_TYPE; for (PieceType pt : v->pieceTypes) { if (is_custom(pt)) { PieceValue[MG][pt] = piece_value(MG, pt); PieceValue[EG][pt] = piece_value(EG, pt); } if (PieceValue[MG][pt] > PieceValue[MG][strongestPiece]) strongestPiece = pt; } Value maxPromotion = VALUE_ZERO; for (PieceType pt : v->promotionPieceTypes) maxPromotion = std::max(maxPromotion, PieceValue[EG][pt]); for (PieceType pt = PAWN; pt <= KING; ++pt) { Piece pc = make_piece(WHITE, pt); Score score = make_score(PieceValue[MG][pc], PieceValue[EG][pc]); // Consider promotion types in pawn score if (pt == PAWN) { score -= make_score(0, (QueenValueEg - maxPromotion) / 100); if (v->blastOnCapture) score += make_score(mg_value(score) * 3 / 2, eg_value(score)); } const PieceInfo* pi = pieceMap.find(pt)->second; bool isSlider = pi->slider[MODALITY_QUIET].size() || pi->slider[MODALITY_CAPTURE].size() || pi->hopper[MODALITY_QUIET].size() || pi->hopper[MODALITY_CAPTURE].size(); bool isPawn = !isSlider && pi->steps[MODALITY_QUIET].size() && !std::any_of(pi->steps[MODALITY_QUIET].begin(), pi->steps[MODALITY_QUIET].end(), [](const std::pair& d) { return d.first < SOUTH / 2; }); bool isSlowLeaper = !isSlider && !std::any_of(pi->steps[MODALITY_QUIET].begin(), pi->steps[MODALITY_QUIET].end(), [](const std::pair& d) { return dist(d.first) > 1; }); // Scale slider piece values with board size if (isSlider) { constexpr int lc = 5; constexpr int rm = 5; constexpr int r0 = rm + RANK_8; int r1 = rm + (v->maxRank + v->maxFile - 2 * v->capturesToHand) / 2; int leaper = pi->steps[MODALITY_QUIET].size() + pi->steps[MODALITY_CAPTURE].size(); int slider = pi->slider[MODALITY_QUIET].size() + pi->slider[MODALITY_CAPTURE].size() + pi->hopper[MODALITY_QUIET].size() + pi->hopper[MODALITY_CAPTURE].size(); score = make_score(mg_value(score) * (lc * leaper + r1 * slider) / (lc * leaper + r0 * slider), eg_value(score) * (lc * leaper + r1 * slider) / (lc * leaper + r0 * slider)); } // Piece values saturate earlier in drop variants if (v->capturesToHand || v->twoBoards) score = make_score(mg_value(score) * 7000 / (7000 + mg_value(score)), eg_value(score) * 7000 / (7000 + eg_value(score))); // In variants where checks are prohibited, strong pieces are less mobile, so limit their value if (!v->checking) score = make_score(std::min(mg_value(score), Value(1800)) / 2, std::min(eg_value(score), Value(1800)) * 3 / 5); // With check counting, strong pieces are even more dangerous else if (v->checkCounting) score = make_score(mg_value(score) * (20000 + mg_value(score)) / 22000, eg_value(score) * (20000 + eg_value(score)) / 21000); // Increase leapers' value in makpong else if (v->makpongRule) { if (std::any_of(pi->steps[MODALITY_CAPTURE].begin(), pi->steps[MODALITY_CAPTURE].end(), [](const std::pair& d) { return dist(d.first) > 1 && !d.second; })) score = make_score(mg_value(score) * 4200 / (3500 + mg_value(score)), eg_value(score) * 4700 / (3500 + mg_value(score))); } // Adjust piece values for atomic captures if (v->blastOnCapture) score = make_score(mg_value(score) * 7000 / (7000 + mg_value(score)), eg_value(score)); // In variants such as horde where all pieces need to be captured, weak pieces such as pawns are more useful if ( v->extinctionValue == -VALUE_MATE && v->extinctionPieceCount == 0 && v->extinctionPieceTypes.find(ALL_PIECES) != v->extinctionPieceTypes.end()) score += make_score(0, std::max(KnightValueEg - PieceValue[EG][pt], VALUE_ZERO) / 20); // The strongest piece of a variant usually has some dominance, such as rooks in Makruk and Xiangqi. // This does not apply to drop variants. if (pt == strongestPiece && !v->capturesToHand) score += make_score(std::max(QueenValueMg - PieceValue[MG][pt], VALUE_ZERO) / 20, std::max(QueenValueEg - PieceValue[EG][pt], VALUE_ZERO) / 20); // For antichess variants, use negative piece values if (v->extinctionValue == VALUE_MATE) score = -make_score(mg_value(score) / 8, eg_value(score) / 8 / (1 + !pi->slider[MODALITY_CAPTURE].size())); // Override variant piece value if (v->pieceValue[MG][pt]) score = make_score(v->pieceValue[MG][pt], eg_value(score)); if (v->pieceValue[EG][pt]) score = make_score(mg_value(score), v->pieceValue[EG][pt]); CapturePieceValue[MG][pc] = CapturePieceValue[MG][~pc] = mg_value(score); CapturePieceValue[EG][pc] = CapturePieceValue[EG][~pc] = eg_value(score); // For drop variants, halve the piece values to compensate for double changes by captures if (v->capturesToHand) score = score / 2; EvalPieceValue[MG][pc] = EvalPieceValue[MG][~pc] = mg_value(score); EvalPieceValue[EG][pc] = EvalPieceValue[EG][~pc] = eg_value(score); // Determine pawn rank std::istringstream ss(v->startFen); unsigned char token; Rank rc = v->maxRank; Rank pawnRank = RANK_2; while ((ss >> token) && !isspace(token)) { if (token == '/') --rc; else if (token == v->pieceToChar[PAWN] || token == v->pieceToChar[SHOGI_PAWN]) pawnRank = rc; } for (Square s = SQ_A1; s <= SQ_MAX; ++s) { File f = std::max(File(edge_distance(file_of(s), v->maxFile)), FILE_A); Rank r = rank_of(s); psq[ pc][s] = score + ( pt == PAWN ? PBonus[std::min(r, RANK_8)][std::min(file_of(s), FILE_H)] : pt == KING ? KingBonus[std::clamp(Rank(r - pawnRank + 1), RANK_1, RANK_8)][std::min(f, FILE_D)] * (1 + v->capturesToHand) : pt <= QUEEN ? Bonus[pc][std::min(r, RANK_8)][std::min(f, FILE_D)] * (1 + v->blastOnCapture) : pt == HORSE ? Bonus[KNIGHT][std::min(r, RANK_8)][std::min(f, FILE_D)] : pt == COMMONER && v->extinctionValue == -VALUE_MATE && v->extinctionPieceTypes.find(COMMONER) != v->extinctionPieceTypes.end() ? KingBonus[std::clamp(Rank(r - pawnRank + 1), RANK_1, RANK_8)][std::min(f, FILE_D)] : isSlider ? make_score(5, 5) * (2 * f + std::max(std::min(r, Rank(v->maxRank - r)), RANK_1) - v->maxFile - 1) : isPawn ? make_score(5, 5) * (2 * f - v->maxFile) : make_score(10, 10) * (1 + isSlowLeaper) * (f + std::max(std::min(r, Rank(v->maxRank - r)), RANK_1) - v->maxFile / 2)); // Add a penalty for unpromoted soldiers if (pt == SOLDIER && r < v->soldierPromotionRank) psq[pc][s] -= score * (v->soldierPromotionRank - r) / (4 + f); // Corners are valuable in reversi if (v->enclosingDrop == REVERSI) { if (f == FILE_A && (r == RANK_1 || r == v->maxRank)) psq[pc][s] += make_score(1000, 1000); } // In atomic variants pieces are "self-defending" and should therefore be pushed forward if (v->blastOnCapture) psq[pc][s] += make_score(40, 0) * (r - v->maxRank / 2); // Safe king squares if (r == RANK_1 && f <= FILE_B && ((pt == KING && v->checkCounting) || (pt == COMMONER && v->blastOnCapture))) psq[pc][s] += make_score(100, 0); psq[~pc][rank_of(s) <= v->maxRank ? flip_rank(s, v->maxRank) : s] = -psq[pc][s]; } // Pieces in hand psq[ pc][SQ_NONE] = score + make_score(35, 10) * (1 + !isSlider); psq[~pc][SQ_NONE] = -psq[pc][SQ_NONE]; } } } // namespace PSQT } // namespace Stockfish Fairy-Stockfish-fairy_sf_14_0_1_xq/src/psqt.h000066400000000000000000000021041414571233100212630ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef PSQT_H_INCLUDED #define PSQT_H_INCLUDED #include "types.h" #include "variant.h" namespace Stockfish::PSQT { extern Score psq[PIECE_NB][SQUARE_NB + 1]; // Fill psqt array from a set of internally linked parameters extern void init(const Variant*); } // namespace Stockfish::PSQT #endif // PSQT_H_INCLUDED Fairy-Stockfish-fairy_sf_14_0_1_xq/src/pyffish.cpp000066400000000000000000000353471414571233100223160ustar00rootroot00000000000000/* Based on Jean-Francois Romang work https://github.com/jromang/Stockfish/blob/pyfish/src/pyfish.cpp */ #include #include #include "misc.h" #include "types.h" #include "bitboard.h" #include "evaluate.h" #include "position.h" #include "search.h" #include "syzygy/tbprobe.h" #include "thread.h" #include "tt.h" #include "uci.h" #include "piece.h" #include "variant.h" #include "apiutil.h" using namespace Stockfish; static PyObject* PyFFishError; void buildPosition(Position& pos, StateListPtr& states, const char *variant, const char *fen, PyObject *moveList, const bool chess960) { states = StateListPtr(new std::deque(1)); // Drop old and create a new one const Variant* v = variants.find(std::string(variant))->second; UCI::init_variant(v); if (strcmp(fen, "startpos") == 0) fen = v->startFen.c_str(); pos.set(v, std::string(fen), chess960, &states->back(), Threads.main()); // parse move list int numMoves = PyList_Size(moveList); for (int i = 0; i < numMoves ; i++) { PyObject *MoveStr = PyUnicode_AsEncodedString( PyList_GetItem(moveList, i), "UTF-8", "strict"); std::string moveStr(PyBytes_AS_STRING(MoveStr)); Py_XDECREF(MoveStr); Move m; if ((m = UCI::to_move(pos, moveStr)) != MOVE_NONE) { // do the move states->emplace_back(); pos.do_move(m, states->back()); } else PyErr_SetString(PyExc_ValueError, (std::string("Invalid move '") + moveStr + "'").c_str()); } return; } extern "C" PyObject* pyffish_version(PyObject* self) { return Py_BuildValue("(iii)", 0, 0, 66); } extern "C" PyObject* pyffish_info(PyObject* self) { return Py_BuildValue("s", engine_info().c_str()); } extern "C" PyObject* pyffish_variants(PyObject* self, PyObject *args) { PyObject* varList = PyList_New(0); for (std::string v : variants.get_keys()) { PyObject* variant = Py_BuildValue("s", v.c_str()); PyList_Append(varList, variant); Py_XDECREF(variant); } PyObject* Result = Py_BuildValue("O", varList); Py_XDECREF(varList); return Result; } // INPUT option name, option value extern "C" PyObject* pyffish_setOption(PyObject* self, PyObject *args) { const char *name; PyObject *valueObj; if (!PyArg_ParseTuple(args, "sO", &name, &valueObj)) return NULL; if (Options.count(name)) { PyObject *Value = PyUnicode_AsEncodedString( PyObject_Str(valueObj), "UTF-8", "strict"); Options[name] = std::string(PyBytes_AS_STRING(Value)); Py_XDECREF(Value); } else { PyErr_SetString(PyExc_ValueError, (std::string("No such option ") + name + "'").c_str()); return NULL; } Py_RETURN_NONE; } // INPUT variant config extern "C" PyObject* pyffish_loadVariantConfig(PyObject* self, PyObject *args) { const char *config; if (!PyArg_ParseTuple(args, "s", &config)) return NULL; std::stringstream ss(config); variants.parse_istream(ss); Options["UCI_Variant"].set_combo(variants.get_keys()); Py_RETURN_NONE; } // INPUT variant extern "C" PyObject* pyffish_startFen(PyObject* self, PyObject *args) { const char *variant; if (!PyArg_ParseTuple(args, "s", &variant)) { return NULL; } return Py_BuildValue("s", variants.find(std::string(variant))->second->startFen.c_str()); } // INPUT variant extern "C" PyObject* pyffish_twoBoards(PyObject* self, PyObject *args) { const char *variant; if (!PyArg_ParseTuple(args, "s", &variant)) { return NULL; } return Py_BuildValue("O", variants.find(std::string(variant))->second->twoBoards ? Py_True : Py_False); } // INPUT variant, fen, move extern "C" PyObject* pyffish_getSAN(PyObject* self, PyObject *args) { PyObject* moveList = PyList_New(0); Position pos; const char *fen, *variant, *move; int chess960 = false; Notation notation = NOTATION_DEFAULT; if (!PyArg_ParseTuple(args, "sss|pi", &variant, &fen, &move, &chess960, ¬ation)) { return NULL; } if (notation == NOTATION_DEFAULT) notation = default_notation(variants.find(std::string(variant))->second); StateListPtr states(new std::deque(1)); buildPosition(pos, states, variant, fen, moveList, chess960); std::string moveStr = move; Py_XDECREF(moveList); return Py_BuildValue("s", SAN::move_to_san(pos, UCI::to_move(pos, moveStr), notation).c_str()); } // INPUT variant, fen, movelist extern "C" PyObject* pyffish_getSANmoves(PyObject* self, PyObject *args) { PyObject* sanMoves = PyList_New(0), *moveList; Position pos; const char *fen, *variant; int chess960 = false; Notation notation = NOTATION_DEFAULT; if (!PyArg_ParseTuple(args, "ssO!|pi", &variant, &fen, &PyList_Type, &moveList, &chess960, ¬ation)) { return NULL; } if (notation == NOTATION_DEFAULT) notation = default_notation(variants.find(std::string(variant))->second); StateListPtr states(new std::deque(1)); buildPosition(pos, states, variant, fen, sanMoves, chess960); int numMoves = PyList_Size(moveList); for (int i=0; iemplace_back(); pos.do_move(m, states->back()); } else { PyErr_SetString(PyExc_ValueError, (std::string("Invalid move '") + moveStr + "'").c_str()); return NULL; } } PyObject *Result = Py_BuildValue("O", sanMoves); Py_XDECREF(sanMoves); return Result; } // INPUT variant, fen, move list extern "C" PyObject* pyffish_legalMoves(PyObject* self, PyObject *args) { PyObject* legalMoves = PyList_New(0), *moveList; Position pos; const char *fen, *variant; int chess960 = false; if (!PyArg_ParseTuple(args, "ssO!|p", &variant, &fen, &PyList_Type, &moveList, &chess960)) { return NULL; } StateListPtr states(new std::deque(1)); buildPosition(pos, states, variant, fen, moveList, chess960); for (const auto& m : MoveList(pos)) { PyObject *moveStr; moveStr = Py_BuildValue("s", UCI::move(pos, m).c_str()); PyList_Append(legalMoves, moveStr); Py_XDECREF(moveStr); } PyObject *Result = Py_BuildValue("O", legalMoves); Py_XDECREF(legalMoves); return Result; } // INPUT variant, fen, move list extern "C" PyObject* pyffish_getFEN(PyObject* self, PyObject *args) { PyObject *moveList; Position pos; const char *fen, *variant; int chess960 = false, sfen = false, showPromoted = false, countStarted = 0; if (!PyArg_ParseTuple(args, "ssO!|pppi", &variant, &fen, &PyList_Type, &moveList, &chess960, &sfen, &showPromoted, &countStarted)) { return NULL; } StateListPtr states(new std::deque(1)); buildPosition(pos, states, variant, fen, moveList, chess960); return Py_BuildValue("s", pos.fen(sfen, showPromoted, countStarted).c_str()); } // INPUT variant, fen, move list extern "C" PyObject* pyffish_givesCheck(PyObject* self, PyObject *args) { PyObject *moveList; Position pos; const char *fen, *variant; int chess960 = false; if (!PyArg_ParseTuple(args, "ssO!|p", &variant, &fen, &PyList_Type, &moveList, &chess960)) { return NULL; } StateListPtr states(new std::deque(1)); buildPosition(pos, states, variant, fen, moveList, chess960); return Py_BuildValue("O", pos.checkers() ? Py_True : Py_False); } // INPUT variant, fen, move list // should only be called when the move list is empty extern "C" PyObject* pyffish_gameResult(PyObject* self, PyObject *args) { PyObject *moveList; Position pos; const char *fen, *variant; bool gameEnd; Value result; int chess960 = false; if (!PyArg_ParseTuple(args, "ssO!|p", &variant, &fen, &PyList_Type, &moveList, &chess960)) { return NULL; } StateListPtr states(new std::deque(1)); buildPosition(pos, states, variant, fen, moveList, chess960); assert(!MoveList(pos).size()); gameEnd = pos.is_immediate_game_end(result); if (!gameEnd) result = pos.checkers() ? pos.checkmate_value() : pos.stalemate_value(); return Py_BuildValue("i", result); } // INPUT variant, fen, move list extern "C" PyObject* pyffish_isImmediateGameEnd(PyObject* self, PyObject *args) { PyObject *moveList; Position pos; const char *fen, *variant; bool gameEnd; Value result; int chess960 = false; if (!PyArg_ParseTuple(args, "ssO!|p", &variant, &fen, &PyList_Type, &moveList, &chess960)) { return NULL; } StateListPtr states(new std::deque(1)); buildPosition(pos, states, variant, fen, moveList, chess960); gameEnd = pos.is_immediate_game_end(result); return Py_BuildValue("(Oi)", gameEnd ? Py_True : Py_False, result); } // INPUT variant, fen, move list extern "C" PyObject* pyffish_isOptionalGameEnd(PyObject* self, PyObject *args) { PyObject *moveList; Position pos; const char *fen, *variant; bool gameEnd; Value result; int chess960 = false, countStarted = 0; if (!PyArg_ParseTuple(args, "ssO!|pi", &variant, &fen, &PyList_Type, &moveList, &chess960, &countStarted)) { return NULL; } StateListPtr states(new std::deque(1)); buildPosition(pos, states, variant, fen, moveList, chess960); gameEnd = pos.is_optional_game_end(result, 0, countStarted); return Py_BuildValue("(Oi)", gameEnd ? Py_True : Py_False, result); } // INPUT variant, fen, move list extern "C" PyObject* pyffish_hasInsufficientMaterial(PyObject* self, PyObject *args) { PyObject *moveList; Position pos; const char *fen, *variant; int chess960 = false; if (!PyArg_ParseTuple(args, "ssO!|p", &variant, &fen, &PyList_Type, &moveList, &chess960)) { return NULL; } StateListPtr states(new std::deque(1)); buildPosition(pos, states, variant, fen, moveList, chess960); bool wInsufficient = has_insufficient_material(WHITE, pos); bool bInsufficient = has_insufficient_material(BLACK, pos); return Py_BuildValue("(OO)", wInsufficient ? Py_True : Py_False, bInsufficient ? Py_True : Py_False); } // INPUT variant, fen extern "C" PyObject* pyffish_validateFen(PyObject* self, PyObject *args) { const char *fen, *variant; int chess960 = false; if (!PyArg_ParseTuple(args, "ss|p", &fen, &variant, &chess960)) { return NULL; } return Py_BuildValue("i", FEN::validate_fen(std::string(fen), variants.find(std::string(variant))->second, chess960)); } static PyMethodDef PyFFishMethods[] = { {"version", (PyCFunction)pyffish_version, METH_NOARGS, "Get package version."}, {"info", (PyCFunction)pyffish_info, METH_NOARGS, "Get Stockfish version info."}, {"variants", (PyCFunction)pyffish_variants, METH_NOARGS, "Get supported variants."}, {"set_option", (PyCFunction)pyffish_setOption, METH_VARARGS, "Set UCI option."}, {"load_variant_config", (PyCFunction)pyffish_loadVariantConfig, METH_VARARGS, "Load variant configuration."}, {"start_fen", (PyCFunction)pyffish_startFen, METH_VARARGS, "Get starting position FEN."}, {"two_boards", (PyCFunction)pyffish_twoBoards, METH_VARARGS, "Checks whether the variant is played on two boards."}, {"get_san", (PyCFunction)pyffish_getSAN, METH_VARARGS, "Get SAN move from given FEN and UCI move."}, {"get_san_moves", (PyCFunction)pyffish_getSANmoves, METH_VARARGS, "Get SAN movelist from given FEN and UCI movelist."}, {"legal_moves", (PyCFunction)pyffish_legalMoves, METH_VARARGS, "Get legal moves from given FEN and movelist."}, {"get_fen", (PyCFunction)pyffish_getFEN, METH_VARARGS, "Get resulting FEN from given FEN and movelist."}, {"gives_check", (PyCFunction)pyffish_givesCheck, METH_VARARGS, "Get check status from given FEN and movelist."}, {"game_result", (PyCFunction)pyffish_gameResult, METH_VARARGS, "Get result from given FEN, considering variant end, checkmate, and stalemate."}, {"is_immediate_game_end", (PyCFunction)pyffish_isImmediateGameEnd, METH_VARARGS, "Get result from given FEN if variant rules ends the game."}, {"is_optional_game_end", (PyCFunction)pyffish_isOptionalGameEnd, METH_VARARGS, "Get result from given FEN it rules enable game end by player."}, {"has_insufficient_material", (PyCFunction)pyffish_hasInsufficientMaterial, METH_VARARGS, "Checks for insufficient material."}, {"validate_fen", (PyCFunction)pyffish_validateFen, METH_VARARGS, "Validate an input FEN."}, {NULL, NULL, 0, NULL}, // sentinel }; static PyModuleDef pyffishmodule = { PyModuleDef_HEAD_INIT, "pyffish", "Fairy-Stockfish extension module.", -1, PyFFishMethods, }; PyMODINIT_FUNC PyInit_pyffish() { PyObject* module; module = PyModule_Create(&pyffishmodule); if (module == NULL) { return NULL; } PyFFishError = PyErr_NewException("pyffish.error", NULL, NULL); Py_INCREF(PyFFishError); PyModule_AddObject(module, "error", PyFFishError); // values PyModule_AddObject(module, "VALUE_MATE", PyLong_FromLong(VALUE_MATE)); PyModule_AddObject(module, "VALUE_DRAW", PyLong_FromLong(VALUE_DRAW)); // notations PyModule_AddObject(module, "NOTATION_DEFAULT", PyLong_FromLong(NOTATION_DEFAULT)); PyModule_AddObject(module, "NOTATION_SAN", PyLong_FromLong(NOTATION_SAN)); PyModule_AddObject(module, "NOTATION_LAN", PyLong_FromLong(NOTATION_LAN)); PyModule_AddObject(module, "NOTATION_SHOGI_HOSKING", PyLong_FromLong(NOTATION_SHOGI_HOSKING)); PyModule_AddObject(module, "NOTATION_SHOGI_HODGES", PyLong_FromLong(NOTATION_SHOGI_HODGES)); PyModule_AddObject(module, "NOTATION_SHOGI_HODGES_NUMBER", PyLong_FromLong(NOTATION_SHOGI_HODGES_NUMBER)); PyModule_AddObject(module, "NOTATION_JANGGI", PyLong_FromLong(NOTATION_JANGGI)); PyModule_AddObject(module, "NOTATION_XIANGQI_WXF", PyLong_FromLong(NOTATION_XIANGQI_WXF)); // validation PyModule_AddObject(module, "FEN_OK", PyLong_FromLong(FEN::FEN_OK)); // initialize stockfish pieceMap.init(); variants.init(); UCI::init(Options); PSQT::init(variants.find(Options["UCI_Variant"])->second); Bitboards::init(); Position::init(); Bitbases::init(); Search::init(); Threads.set(Options["Threads"]); Search::clear(); // After threads are up return module; }; Fairy-Stockfish-fairy_sf_14_0_1_xq/src/search.cpp000066400000000000000000002347031414571233100221100ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include // For std::memset #include #include #include "evaluate.h" #include "misc.h" #include "movegen.h" #include "movepick.h" #include "partner.h" #include "position.h" #include "search.h" #include "thread.h" #include "timeman.h" #include "tt.h" #include "uci.h" #include "xboard.h" #include "syzygy/tbprobe.h" namespace Stockfish { 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, Root }; constexpr uint64_t TtHitAverageWindow = 4096; constexpr uint64_t TtHitAverageResolution = 1024; // Futility margin Value futility_margin(Depth d, bool improving) { return Value(214 * (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 + 534) / 1024 + (!i && r > 904); } int futility_move_count(bool improving, Depth depth, const Position& pos) { return (3 + depth * depth + 2 * pos.blast_on_capture()) / (2 - improving + pos.blast_on_capture()); } // History and stats update bonus, based on depth int stat_bonus(Depth d) { return d > 14 ? 73 : 6 * d * d + 229 * d - 215; } // Add a small random component to draw evaluations to avoid 3-fold 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 + std::max(level, 0); } Move pick_best(size_t multiPV); int level; Move best = MOVE_NONE; }; template Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode); template 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 uint64_t perft(Position& pos, Depth depth) { StateInfo st; ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); uint64_t cnt, nodes = 0; const bool leaf = (depth == 2); for (const auto& m : MoveList(pos)) { assert(pos.pseudo_legal(m)); if (Root && depth <= 1) cnt = 1, nodes++; else { pos.do_move(m, st); cnt = leaf ? MoveList(pos).size() : perft(pos, depth - 1); nodes += cnt; pos.undo_move(m); } if (Root) sync_cout << UCI::move(pos, m) << ": " << 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(21.9 * 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(rootPos, Limits.perft); sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl; return; } Color us = rootPos.side_to_move(); Time.init(rootPos, Limits, us, rootPos.game_ply()); TT.new_search(); Eval::NNUE::verify(); if (rootMoves.empty() || (Options["Protocol"] == "xboard" && rootPos.is_optional_game_end())) { rootMoves.emplace_back(MOVE_NONE); Value variantResult; Value result = rootPos.is_game_end(variantResult) ? variantResult : rootPos.checkers() ? rootPos.checkmate_value() : rootPos.stalemate_value(); if (Options["Protocol"] == "xboard") { // rotate MOVE_NONE to front (for optional game end) std::rotate(rootMoves.rbegin(), rootMoves.rbegin() + 1, rootMoves.rend()); sync_cout << ( result == VALUE_DRAW ? "1/2-1/2 {Draw}" : (rootPos.side_to_move() == BLACK ? -result : result) == VALUE_MATE ? "1-0 {White wins}" : "0-1 {Black wins}") << sync_endl; } else sync_cout << "info depth 0 score " << UCI::value(result) << sync_endl; } else { Threads.start_searching(); // start non-main threads Thread::search(); // main thread start searching } // Sit in bughouse variants if partner requested it or we are dead if (rootPos.two_boards() && !Threads.abort && Options["Protocol"] == "xboard") { while (!Threads.stop && (Partner.sitRequested || (Partner.weDead && !Partner.partnerDead)) && Time.elapsed() < Limits.time[us] - 1000) {} } // 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(); 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; if (Options["Protocol"] == "xboard") { Move bestMove = bestThread->rootMoves[0].pv[0]; // Wait for virtual drop to become real if (rootPos.two_boards() && rootPos.virtual_drop(bestMove)) { Partner.ptell("fast"); while (!Threads.abort && !Partner.partnerDead && !Partner.fast && Limits.time[us] - Time.elapsed() > Partner.opptime) {} Partner.ptell("x"); // Find best real move for (const auto& m : this->rootMoves) if (!rootPos.virtual_drop(m.pv[0])) { bestMove = m.pv[0]; break; } } // Send move only when not in analyze mode and not at game end if (!Limits.infinite && !ponder && rootMoves[0].pv[0] != MOVE_NONE && !Threads.abort.exchange(true)) { sync_cout << "move " << UCI::move(rootPos, bestMove) << sync_endl; if (XBoard::stateMachine->moveAfterSearch) { XBoard::stateMachine->do_move(bestMove); XBoard::stateMachine->moveAfterSearch = false; if (Options["Ponder"] && ( bestThread->rootMoves[0].pv.size() > 1 || bestThread->rootMoves[0].extract_ponder_from_tt(rootPos))) XBoard::stateMachine->ponderMove = bestThread->rootMoves[0].pv[1]; } } return; } sync_cout << "bestmove " << UCI::move(rootPos, bestThread->rootMoves[0].pv[0]); if (bestThread->rootMoves[0].pv.size() > 1 || bestThread->rootMoves[0].extract_ponder_from_tt(rootPos)) std::cout << " ponder " << UCI::move(rootPos, bestThread->rootMoves[0].pv[1]); 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 statScore 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 for (int i = 0; i <= MAX_PLY + 2; ++i) (ss+i)->ply = i; 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 shiftedElo = Options["UCI_Elo"] - 1346.6; double floatLevel = Options["UCI_LimitStrength"] ? std::clamp(shiftedElo > 0 ? std::pow(shiftedElo / 143.4, 1 / 0.806) : shiftedElo / 143.4 + std::pow(shiftedElo / 500, 5), -20.0, 20.0) : double(Options["Skill Level"]); int intLevel = int(floatLevel) + ((floatLevel - int(floatLevel)) * 1024 > rng.rand() % 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; trend = SCORE_ZERO; 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 * (1 + rootPos.captures_to_hand())); alpha = std::max(prev - delta,-VALUE_INFINITE); beta = std::min(prev + delta, VALUE_INFINITE); // Adjust trend based on root move's previousScore (dynamic contempt) int tr = 113 * prev / (abs(prev) + 147); trend = (us == WHITE ? make_score(tr, tr / 2) : -make_score(tr, tr / 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 = Stockfish::search(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 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.073 + std::max(1.0, 2.25 - 9.9 / rootDepth) * totBestMoveChanges / Threads.size(); double totalTime = Time.optimum() * fallingEval * reduction * bestMoveInstability; // Cap used time in case of a single legal move for a better viewer experience in tournaments // yielding correct scores and sufficiently fast moves. if (rootMoves.size() == 1) totalTime = std::min(500.0, totalTime); // Update partner in bughouse variants if (completedDepth >= 8 && rootPos.two_boards() && Options["Protocol"] == "xboard") { // Communicate clock times relevant for sitting decisions if (Limits.time[us]) Partner.ptell("time " + std::to_string((Limits.time[us] - Time.elapsed()) / 10)); if (Limits.time[~us]) Partner.ptell("otim " + std::to_string(Limits.time[~us] / 10)); // We are dead and need to sit if (!Partner.weDead && bestValue <= VALUE_MATED_IN_MAX_PLY) { Partner.ptell("dead"); Partner.weDead = true; } // We were dead but are fine again else if (Partner.weDead && bestValue > VALUE_MATED_IN_MAX_PLY) { Partner.ptell("x"); Partner.weDead = false; } // We win by force, so partner should sit else if (!Partner.weWin && bestValue >= VALUE_MATE_IN_MAX_PLY && Limits.time[~us] < Partner.time) { Partner.ptell("sit"); Partner.weWin = true; } // We are no longer winning else if (Partner.weWin && (bestValue < VALUE_MATE_IN_MAX_PLY || Limits.time[~us] > Partner.time)) { Partner.ptell("x"); Partner.weWin = false; } // We can win if partner delivers required material quickly else if ( !Partner.weVirtualWin && bestValue >= VALUE_VIRTUAL_MATE_IN_MAX_PLY && bestValue <= VALUE_VIRTUAL_MATE && Limits.time[us] - Time.elapsed() > Partner.opptime) { Partner.ptell("fast"); Partner.weVirtualWin = true; } // Virtual mate is gone else if ( Partner.weVirtualWin && (bestValue < VALUE_VIRTUAL_MATE_IN_MAX_PLY || bestValue > VALUE_VIRTUAL_MATE || Limits.time[us] - Time.elapsed() < Partner.opptime)) { Partner.ptell("slow"); Partner.weVirtualWin = false; } // We need to survive a virtual mate and play fast else if ( !Partner.weVirtualLoss && (bestValue <= -VALUE_VIRTUAL_MATE_IN_MAX_PLY && bestValue >= -VALUE_VIRTUAL_MATE) && Limits.time[~us] > Partner.time) { Partner.ptell("sit"); Partner.weVirtualLoss = true; Partner.fast = true; } // Virtual mate threat is over else if ( Partner.weVirtualLoss && (bestValue > -VALUE_VIRTUAL_MATE_IN_MAX_PLY || bestValue < -VALUE_VIRTUAL_MATE || Limits.time[~us] < Partner.time)) { Partner.ptell("x"); Partner.weVirtualLoss = false; Partner.fast = false; } } // Stop the search if we have exceeded the totalTime if (Time.elapsed() > totalTime) { // If we are allowed to ponder do not stop the search now but // keep pondering until the GUI sends "ponderhit" or "stop". if (mainThread->ponder) mainThread->stopOnPonderhit = true; else if (!(rootPos.two_boards() && (Partner.sitRequested || Partner.weDead))) 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 Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) { constexpr bool PvNode = nodeType != NonPV; constexpr bool rootNode = nodeType == Root; const Depth maxNextDepth = rootNode ? depth : depth + 1; // Check if we have an upcoming move which draws by repetition, or // if the opponent had an alternative move earlier to this position. if ( !rootNode && pos.rule50_count() >= 3 && alpha < VALUE_DRAW && pos.has_game_cycle(ss->ply)) { alpha = value_draw(pos.this_thread()); if (alpha >= beta) return alpha; } // Dive into quiescence search when the depth reaches zero if (depth <= 0) return qsearch(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; ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); TTEntry* tte; Key posKey; Move ttMove, move, excludedMove, bestMove; Depth extension, newDepth; Value bestValue, value, ttValue, eval, maxValue, probCutBeta; bool 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(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) { Value variantResult; if (pos.is_game_end(variantResult, ss->ply)) return variantResult; // Step 2. Check for aborted search and immediate draw if ( Threads.stop.load(std::memory_order_relaxed) || 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)->ttPv = false; (ss+1)->excludedMove = bestMove = MOVE_NONE; (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE; ss->doubleExtensions = (ss-1)->doubleExtensions; 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+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, ss->ttHit); ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; ttMove = rootNode ? thisThread->rootMoves[thisThread->pvIdx].pv[0] : ss->ttHit ? tte->move() : MOVE_NONE; if (!excludedMove) ss->ttPv = PvNode || (ss->ttHit && tte->is_pv()); // Update low ply history for previous move if we are near root and position is or has been in PV 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 * ss->ttHit; // At non-PV nodes we check for an early TT cutoff if ( !PvNode && ss->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) { // Bonus for a quiet ttMove that fails high 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); } } // Partial workaround for the graph history interaction problem // For high rule50 counts don't produce transposition table cutoffs. if (pos.rule50_count() < 90) return ttValue; } // Step 5. Tablebases probe if (!rootNode && TB::Cardinality) { int piecesCount = pos.count(); if ( piecesCount <= TB::Cardinality && (piecesCount < TB::Cardinality || depth >= TB::ProbeDepth) && pos.rule50_count() == 0 && Options["UCI_Variant"] == "chess" && !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(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 (ss->ttHit) { // Never assume anything about values stored in TT ss->staticEval = eval = tte->eval(); if (eval == VALUE_NONE) ss->staticEval = eval = evaluate(pos); // Randomize draw evaluation 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 { // In case of null move search use previous static eval with a different sign // and addition of two tempos if ((ss-1)->currentMove != MOVE_NULL) ss->staticEval = eval = evaluate(pos); else ss->staticEval = eval = -(ss-1)->staticEval; // Save static evaluation into transposition table tte->save(posKey, VALUE_NONE, ss->ttPv, BOUND_NONE, DEPTH_NONE, MOVE_NONE, eval); } // Use static evaluation difference to improve quiet move ordering if (is_ok((ss-1)->currentMove) && !(ss-1)->inCheck && !priorCapture) { int bonus = std::clamp(-depth * 4 * int((ss-1)->staticEval + ss->staticEval), -1000, 1000); thisThread->mainHistory[~us][from_to((ss-1)->currentMove)] << bonus; } // Set up improving flag that is used in various pruning heuristics // We define position as improving if static evaluation of position is better // Than the previous static evaluation at our turn // In case of us being in check at our previous move we look at move prior to it improving = (ss-2)->staticEval == VALUE_NONE ? ss->staticEval > (ss-4)->staticEval || (ss-4)->staticEval == VALUE_NONE : ss->staticEval > (ss-2)->staticEval; // Skip early pruning in case of mandatory capture if (pos.must_capture() && pos.has_capture()) goto moves_loop; // Step 7. Futility pruning: child node (~50 Elo) if ( !PvNode && depth < 9 - 3 * pos.blast_on_capture() && eval - futility_margin(depth, improving) * (1 + pos.check_counting() + 2 * pos.must_capture() + pos.extinction_single_piece() + !pos.checking_permitted()) >= beta && eval < VALUE_KNOWN_WIN) // Do not return unproven wins return eval; // Step 8. Null move search with verification search (~40 Elo) if ( !PvNode && (ss-1)->currentMove != MOVE_NULL && (ss-1)->statScore < 23767 && eval >= beta && eval >= ss->staticEval && ss->staticEval >= beta - 20 * depth - 22 * improving + 168 * ss->ttPv + 159 + 200 * (!pos.double_step_enabled() && pos.piece_to_char()[PAWN] != ' ') && !excludedMove && pos.non_pawn_material(us) && pos.count(~us) != pos.count(~us) && !pos.flip_enclosed_pieces() && (ss->ply >= thisThread->nmpMinPly || us != thisThread->nmpColor)) { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and value Depth R = (1090 - 300 * pos.must_capture() - 250 * !pos.checking_permitted() + 81 * depth) / 256 + std::min(int(eval - beta) / 205, pos.must_capture() || pos.blast_on_capture() ? 0 : 3); ss->currentMove = MOVE_NULL; ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; pos.do_null_move(st); Value nullValue = -search(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 < 14)) return nullValue; assert(!thisThread->nmpMinPly); // Recursive verification is not allowed // Do verification search at high depths, with null move pruning disabled // for us, until ply exceeds nmpMinPly. thisThread->nmpMinPly = ss->ply + 3 * (depth-R) / 4; thisThread->nmpColor = us; Value v = search(pos, ss, beta-1, beta, depth-R, false); thisThread->nmpMinPly = 0; if (v >= beta) return nullValue; } } probCutBeta = beta + (209 + 20 * !!pos.capture_the_flag_piece() + 50 * pos.captures_to_hand()) * (1 + pos.check_counting() + pos.extinction_single_piece()) - 44 * improving; // Step 9. ProbCut (~4 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 && !( ss->ttHit && tte->depth() >= depth - 3 && ttValue != VALUE_NONE && ttValue < 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] [history_slot(pos.moved_piece(move))] [to_sq(move)]; pos.do_move(move, st); // Perform a preliminary qsearch to verify that the move holds value = -qsearch(pos, ss+1, -probCutBeta, -probCutBeta+1); // If the qsearch held, perform the regular search if (value >= probCutBeta) value = -search(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 ( !(ss->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 10. 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 ttCapture = ttMove && pos.capture_or_promotion(ttMove); // Step 11. A small Probcut idea, when we are in check probCutBeta = beta + 409; if ( ss->inCheck && !PvNode && depth >= 4 && ttCapture && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3 && ttValue >= probCutBeta && abs(ttValue) <= VALUE_KNOWN_WIN && abs(beta) <= VALUE_KNOWN_WIN ) return probCutBeta; 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; bool doubleExtension = false; // Indicate PvNodes that will probably fail low if the node was searched // at a depth equal or greater than the current depth, and the result of this search was a fail low. bool likelyFailLow = PvNode && ttMove && (tte->bound() & BOUND_UPPER) && tte->depth() >= depth; // 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 && Options["Protocol"] != "xboard") sync_cout << "info depth " << depth << " currmove " << UCI::move(pos, move) << " 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) || pos.count(us) == pos.count(us)) && bestValue > VALUE_TB_LOSS_IN_MAX_PLY) { // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold moveCountPruning = moveCount >= futility_move_count(improving, depth, pos); // Reduced depth of the next LMR search int lmrDepth = std::max(newDepth - reduction(improving, depth, moveCount), 0); if (pos.must_capture() && pos.attackers_to(to_sq(move), ~us)) {} else if ( captureOrPromotion || givesCheck) { // 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; // SEE based pruning if (!pos.see_ge(move, Value(-218 - 120 * pos.captures_to_hand()) * depth)) // (~25 Elo) continue; } else { // Continuation history based pruning (~20 Elo) if ( lmrDepth < 5 && (*contHist[0])[history_slot(movedPiece)][to_sq(move)] < CounterMovePruneThreshold && (*contHist[1])[history_slot(movedPiece)][to_sq(move)] < CounterMovePruneThreshold) continue; // Futility pruning: parent node (~5 Elo) if ( lmrDepth < 7 && !ss->inCheck && !pos.extinction_single_piece() && ss->staticEval + (174 + 157 * lmrDepth) * (1 + pos.check_counting()) <= alpha && (*contHist[0])[history_slot(movedPiece)][to_sq(move)] + (*contHist[1])[history_slot(movedPiece)][to_sq(move)] + (*contHist[3])[history_slot(movedPiece)][to_sq(move)] + (*contHist[5])[history_slot(movedPiece)][to_sq(move)] / 3 < 28255) continue; // Prune moves with negative SEE (~20 Elo) if (!pos.see_ge(move, Value(-(30 - std::min(lmrDepth, 18) + 10 * !!pos.capture_the_flag_piece()) * lmrDepth * lmrDepth))) 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 ( !rootNode && depth >= 7 - 2 * (pos.count() == 1) && move == ttMove && !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 - 2 * depth; Depth singularDepth = (depth - 1) / 2; ss->excludedMove = move; value = search(pos, ss, singularBeta - 1, singularBeta, singularDepth, cutNode); ss->excludedMove = MOVE_NONE; if (value < singularBeta) { extension = 1; singularQuietLMR = !ttCapture; // Avoid search explosion by limiting the number of double extensions to at most 3 if ( !PvNode && value < singularBeta - 93 && ss->doubleExtensions < 3) { extension = 2; doubleExtension = true; } } // Multi-cut pruning // Our ttMove is assumed to fail high, and now we failed high also on a reduced // search without the ttMove. So we assume this expected Cut-node is not singular, // that multiple moves fail high, and we can prune the whole subtree by returning // a soft bound. else if (singularBeta >= beta) return singularBeta; // 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(pos, ss, beta - 1, beta, (depth + 3) / 2, cutNode); ss->excludedMove = MOVE_NONE; if (value >= beta) return beta; } } else if ( givesCheck && depth > 6 && abs(ss->staticEval) > Value(100)) extension = 1; // Losing chess capture extension else if ( pos.must_capture() && pos.capture(move) && (ss->inCheck || MoveList(pos).size() == 1)) extension = 1; // Add extension to new depth newDepth += extension; ss->doubleExtensions = (ss-1)->doubleExtensions + (extension == 2); // Speculative prefetch as early as possible // Update the current move (this must be done after singular extension search) ss->currentMove = move; ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck] [captureOrPromotion] [history_slot(movedPiece)] [to_sq(move)]; // Step 15. Make the move pos.do_move(move, st, givesCheck); // Step 16. Late moves reduction / extension (LMR, ~200 Elo) // We use various heuristics for the sons of a node after the first son has // been searched. In general we would like to reduce them, but there are many // cases where we extend a son if it has good chances to be "interesting". if ( depth >= 3 && moveCount > 1 + 2 * rootNode && !(pos.must_capture() && pos.has_capture()) && ( !captureOrPromotion || (cutNode && (ss-1)->moveCount > 1) || !ss->ttPv) && (!PvNode || ss->ply > 1 || thisThread->id() % 4 != 3)) { Depth r = reduction(improving, depth, moveCount); if (PvNode) r--; // Decrease reduction if the ttHit running average is large (~0 Elo) if (thisThread->ttHitAverage > 537 * TtHitAverageResolution * TtHitAverageWindow / 1024) r--; // Decrease reduction if position is or has been on the PV // and node is not likely to fail low. (~3 Elo) if ( ss->ttPv && !likelyFailLow) r -= 2; // Increase reduction at root and non-PV nodes when the best move does not change frequently if ( (rootNode || !PvNode) && thisThread->bestMoveChanges <= 2) r++; // Decrease reduction if opponent's move count is high (~1 Elo) if ((ss-1)->moveCount > 13) r--; // Decrease reduction if ttMove has been singularly extended (~1 Elo) if (singularQuietLMR) r--; // Increase reduction for cut nodes (~3 Elo) if (cutNode) r += 1 + !captureOrPromotion; if (!captureOrPromotion) { // Increase reduction if ttMove is a capture (~3 Elo) if (ttCapture) r++; ss->statScore = thisThread->mainHistory[us][from_to(move)] + (*contHist[0])[history_slot(movedPiece)][to_sq(move)] + (*contHist[1])[history_slot(movedPiece)][to_sq(move)] + (*contHist[3])[history_slot(movedPiece)][to_sq(move)] - 4923; // Decrease/increase reduction for moves with a good/bad history (~30 Elo) if (!ss->inCheck) r -= ss->statScore / (14721 - 4434 * pos.captures_to_hand()); } // In general we want to cap the LMR depth search at newDepth. But if // reductions are really negative and movecount is low, we allow this move // to be searched deeper than the first move, unless ttMove was extended by 2. Depth d = std::clamp(newDepth - r, 1, newDepth + (r < -1 && moveCount <= 5 && !doubleExtension)); value = -search(pos, ss+1, -(alpha+1), -alpha, d, true); // If the son is reduced and fails high it will be re-searched at full depth 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(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode); // If the move passed LMR update its stats if (didLMR && !captureOrPromotion) { int bonus = value > alpha ? stat_bonus(newDepth) : -stat_bonus(newDepth); 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(pos, ss+1, -beta, -alpha, std::min(maxNextDepth, 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 and LMR 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 break; } } } // If the move is worse than some previously searched move, remember it to update its stats later 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(pos).size()); if (!moveCount) bestValue = excludedMove ? alpha : ss->inCheck ? pos.checkmate_value(ss->ply) : pos.stalemate_value(ss->ply); // If there is a move which produces search value greater than alpha we update stats of searched moves else if (bestMove) update_all_stats(pos, ss, bestMove, bestValue, beta, prevSq, quietsSearched, quietCount, capturesSearched, captureCount, depth); // Bonus for prior countermove that caused the fail low else if ( (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; // Write gathered information in transposition table if (!excludedMove && !(rootNode && thisThread->pvIdx)) tte->save(posKey, value_to_tt(bestValue, ss->ply), ss->ttPv, bestValue >= beta ? BOUND_LOWER : PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER, depth, bestMove, ss->staticEval); assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); return bestValue; } // qsearch() is the quiescence search function, which is called by the main search // function with zero depth, or recursively with further decreasing depth per call. template Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { static_assert(nodeType != Root); constexpr bool PvNode = nodeType == PV; assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE); assert(PvNode || (alpha == beta - 1)); assert(depth <= 0); Move pv[MAX_PLY+1]; StateInfo st; ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); TTEntry* tte; Key posKey; Move ttMove, move, bestMove; Depth ttDepth; Value bestValue, value, ttValue, futilityValue, futilityBase, oldAlpha; bool 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(); bestMove = MOVE_NONE; ss->inCheck = pos.checkers(); moveCount = 0; Value gameResult; if (pos.is_game_end(gameResult, ss->ply)) return gameResult; // Check for maximum ply reached if (ss->ply >= MAX_PLY) return !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, ss->ttHit); ttValue = ss->ttHit ? value_from_tt(tte->value(), ss->ply, pos.rule50_count()) : VALUE_NONE; ttMove = ss->ttHit ? tte->move() : MOVE_NONE; pvHit = ss->ttHit && tte->is_pv(); if ( !PvNode && ss->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 (ss->ttHit) { // Never assume anything about values stored in TT if ((ss->staticEval = bestValue = tte->eval()) == VALUE_NONE) ss->staticEval = bestValue = evaluate(pos); // Can ttValue be used as a better position evaluation? if ( ttValue != VALUE_NONE && (tte->bound() & (ttValue > bestValue ? BOUND_LOWER : BOUND_UPPER))) bestValue = ttValue; } else // In case of null move search use previous static eval with a different sign // and addition of two tempos ss->staticEval = bestValue = (ss-1)->currentMove != MOVE_NULL ? evaluate(pos) : -(ss-1)->staticEval; // Stand pat. Return immediately if static value is at least beta if (bestValue >= beta) { // Save gathered info in transposition table if (!ss->ttHit) tte->save(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, DEPTH_NONE, MOVE_NONE, ss->staticEval); return bestValue; } if (PvNode && bestValue > alpha) alpha = bestValue; futilityBase = bestValue + 155; } 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 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 and moveCount pruning if ( bestValue > VALUE_TB_LOSS_IN_MAX_PLY && !givesCheck && !( pos.extinction_value() == -VALUE_MATE && pos.piece_on(to_sq(move)) && pos.extinction_piece_types().find(type_of(pos.piece_on(to_sq(move)))) != pos.extinction_piece_types().end()) && futilityBase > -VALUE_KNOWN_WIN && type_of(move) != PROMOTION) { 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 ( bestValue > VALUE_TB_LOSS_IN_MAX_PLY && !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] [history_slot(pos.moved_piece(move))] [to_sq(move)]; // Continuation history based pruning if ( !captureOrPromotion && bestValue > VALUE_TB_LOSS_IN_MAX_PLY && (*contHist[0])[history_slot(pos.moved_piece(move))][to_sq(move)] < CounterMovePruneThreshold && (*contHist[1])[history_slot(pos.moved_piece(move))][to_sq(move)] < CounterMovePruneThreshold) continue; // Make and search the move pos.do_move(move, st, givesCheck); value = -qsearch(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) { assert(!MoveList(pos).size()); return pos.checkmate_value(ss->ply); // Plies to mate from the root } // Save gathered info in transposition table tte->save(posKey, value_to_tt(bestValue, ss->ply), pvHit, bestValue >= beta ? BOUND_LOWER : PvNode && bestValue > oldAlpha ? BOUND_EXACT : BOUND_UPPER, ttDepth, bestMove, ss->staticEval); 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 : std::min(bonus1, stat_bonus(depth)); // smaller bonus if (!pos.capture_or_promotion(bestMove)) { // Increase stats for the best move in case it was a quiet move update_quiet_stats(pos, ss, bestMove, bonus2, depth); // Decrease stats for all non-best quiet moves for (int i = 0; i < quietCount; ++i) { thisThread->mainHistory[us][from_to(quietsSearched[i])] << -bonus2; update_continuation_histories(ss, pos.moved_piece(quietsSearched[i]), to_sq(quietsSearched[i]), -bonus2); } } else // Increase stats for the best move in case it was a capture move captureHistory[moved_piece][to_sq(bestMove)][captured] << bonus1; // Extra penalty for a quiet early move that was not a TT move or // main killer move in previous ply when it gets refuted. if ( ((ss-1)->moveCount == 1 + (ss-1)->ttHit || ((ss-1)->currentMove == (ss-1)->killers[0])) && !pos.captured_piece()) update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -bonus1); // Decrease stats for all non-best capture moves for (int i = 0; i < captureCount; ++i) { moved_piece = pos.moved_piece(capturesSearched[i]); captured = type_of(pos.piece_on(to_sq(capturesSearched[i]))); captureHistory[moved_piece][to_sq(capturesSearched[i])][captured] << -bonus1; } } // 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}) { // Only update first 2 continuation histories if we are in check if (ss->inCheck && i > 2) break; if (is_ok((ss-i)->currentMove)) (*(ss-i)->continuationHistory)[history_slot(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) { // Update killers 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); // Penalty for reversed move in case of moved piece not being a pawn if (type_of(pos.moved_piece(move)) != PAWN && type_of(move) != DROP) thisThread->mainHistory[us][from_to(reverse_move(move))] << -bonus; // Update countermove history if (is_ok((ss-1)->currentMove)) { Square prevSq = to_sq((ss-1)->currentMove); thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] = move; } // Update low ply history 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() % 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 ( rootPos.two_boards() && Time.elapsed() < Limits.time[rootPos.side_to_move()] - 1000 && (Partner.sitRequested || (Partner.weDead && !Partner.partnerDead) || Partner.weVirtualWin)) 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"; if (Options["Protocol"] == "xboard") { ss << d << " " << UCI::value(v) << " " << elapsed / 10 << " " << nodesSearched << " " << rootMoves[i].selDepth << " " << nodesSearched * 1000 / elapsed << " " << tbHits << "\t"; // Do not print PVs with virtual drops in bughouse variants if (!pos.two_boards()) for (Move m : rootMoves[i].pv) ss << " " << UCI::move(pos, m); } else { 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(pos, m); } } 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; ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); 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(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; } } } // namespace Stockfish Fairy-Stockfish-fairy_sf_14_0_1_xq/src/search.h000066400000000000000000000061511414571233100215470ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef SEARCH_H_INCLUDED #define SEARCH_H_INCLUDED #include #include "misc.h" #include "movepick.h" #include "types.h" namespace Stockfish { class Position; namespace Search { /// Threshold used for countermoves based pruning constexpr int CounterMovePruneThreshold = 0; /// Stack struct keeps track of the information we need to remember from nodes /// shallower and deeper in the tree during the search. Each search thread has /// its own array of Stack objects, indexed by the current ply. 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; bool ttHit; int doubleExtensions; }; /// 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; Value tbScore; std::vector pv; }; typedef std::vector 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 searchmoves, banmoves; TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime; int movestogo, depth, mate, perft, infinite; int64_t nodes; }; extern LimitsType Limits; void init(); void clear(); } // namespace Search } // namespace Stockfish #endif // #ifndef SEARCH_H_INCLUDED Fairy-Stockfish-fairy_sf_14_0_1_xq/src/syzygy/000077500000000000000000000000001414571233100215045ustar00rootroot00000000000000Fairy-Stockfish-fairy_sf_14_0_1_xq/src/syzygy/tbprobe.cpp000066400000000000000000001704421414571233100236550ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include // For std::memset and std::memcpy #include #include #include #include #include #include #include #include "../bitboard.h" #include "../movegen.h" #include "../position.h" #include "../search.h" #include "../types.h" #include "../uci.h" #include "tbprobe.h" #ifndef _WIN32 #include #include #include #include #else #define WIN32_LEAN_AND_MEAN #ifndef NOMINMAX # define NOMINMAX // Disable macros min() and max() #endif #include #endif using namespace Stockfish::Tablebases; int Stockfish::Tablebases::MaxCardinality; namespace Stockfish { namespace { constexpr int TBPIECES = 7; // Max number of supported pieces 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( " PNBRQ" + std::string(KING - QUEEN - 1, ' ') + "K" + std::string(PIECE_TYPE_NB - KING - 1, ' ') + " pnbrq" + std::string(KING - QUEEN - 1, ' ') + "k" + std::string(PIECE_TYPE_NB - KING - 1, ' ')); int MapPawns[SQUARE_NB]; int MapB1H1H7[SQUARE_NB]; int MapA1D1D4[SQUARE_NB]; int MapKK[10][SQUARE_NB]; // [MapA1D1D4][SQUARE_NB] int Binomial[6][SQUARE_NB]; // [k][n] k elements from a set of n elements int LeadPawnIdx[6][SQUARE_NB]; // [leadPawnsCnt][SQUARE_NB] int LeadPawnsSize[6][4]; // [leadPawnsCnt][FILE_A..FILE_D] // Comparison function to sort leading pawns in ascending MapPawns[] order bool pawns_comp(Square i, Square j) { return MapPawns[i] < MapPawns[j]; } int off_A1H8(Square sq) { return int(rank_of(sq)) - file_of(sq); } constexpr Value WDL_to_value[] = { -VALUE_MATE + MAX_PLY + 1, VALUE_DRAW - 2, VALUE_DRAW, VALUE_DRAW + 2, VALUE_MATE - MAX_PLY - 1 }; template inline void swap_endian(T& x) { static_assert(std::is_unsigned::value, "Argument of swap_endian not unsigned"); uint8_t tmp, *c = (uint8_t*)&x; for (int i = 0; i < Half; ++i) tmp = c[i], c[i] = c[End - i], c[End - i] = tmp; } template<> inline void swap_endian(uint8_t&) {} template T number(void* addr) { T v; if ((uintptr_t)addr & (alignof(T) - 1)) // Unaligned pointer (very rare) std::memcpy(&v, addr, sizeof(T)); else v = *((T*)addr); if (LE != IsLittleEndian) swap_endian(v); return v; } // DTZ tables don't store valid scores for moves that reset the rule50 counter // like captures and pawn moves but we can easily recover the correct dtz of the // previous move if we know the position's WDL score. int dtz_before_zeroing(WDLScore wdl) { return wdl == WDLWin ? 1 : wdl == WDLCursedWin ? 101 : wdl == WDLBlessedLoss ? -101 : wdl == WDLLoss ? -1 : 0; } // Return the sign of a number (-1, 0, 1) template int sign_of(T val) { return (T(0) < val) - (val < T(0)); } // Numbers in little endian used by sparseIndex[] to point into blockLength[] struct SparseEntry { char block[4]; // Number of block char offset[2]; // Offset within the block }; static_assert(sizeof(SparseEntry) == 6, "SparseEntry must be 6 bytes"); 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 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 base64; // base64[l - min_sym_len] is the 64bit-padded lowest symbol of length l std::vector symlen; // Number of values (-1) represented by a given Huffman symbol: 1..256 Piece pieces[TBPIECES]; // Position pieces: the order of pieces defines the groups uint64_t groupIdx[TBPIECES+1]; // Start index used for the encoding of the group's pieces int groupLen[TBPIECES+1]; // Number of pieces in a given group: KRKN -> (3, 1) uint16_t map_idx[4]; // WDLWin, WDLLoss, WDLCursedWin, WDLBlessedLoss (used in DTZ) }; // struct TBTable contains indexing information to access the corresponding TBFile. // There are 2 types of TBTable, corresponding to a WDL or a DTZ file. TBTable // is populated at init time but the nested PairsData records are populated at // first access, when the corresponding file is memory mapped. template struct TBTable { typedef typename std::conditional::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); ~TBTable() { if (baseAddress) TBFile::unmap(baseAddress, mapping); } }; template<> TBTable::TBTable(const std::string& code) : TBTable() { StateInfo st; Position pos; key = pos.set(code, WHITE, &st).material_key(); pieceCount = pos.count(); hasPawns = pos.pieces(PAWN); hasUniquePieces = false; for (Color c : { WHITE, BLACK }) for (PieceType pt = PAWN; pt < KING; ++pt) if (popcount(pos.pieces(c, pt)) == 1) hasUniquePieces = true; // Set the leading color. In case both sides have pawns the leading color // is the side with less pawns because this leads to better compression. bool c = !pos.count(BLACK) || ( pos.count(WHITE) && pos.count(BLACK) >= pos.count(WHITE)); pawnCount[0] = pos.count(c ? WHITE : BLACK); pawnCount[1] = pos.count(c ? BLACK : WHITE); key2 = pos.set(code, BLACK, &st).material_key(); } template<> TBTable::TBTable(const TBTable& wdl) : TBTable() { // Use the corresponding WDL table to avoid recalculating all from scratch key = wdl.key; key2 = wdl.key2; pieceCount = wdl.pieceCount; hasPawns = wdl.hasPawns; hasUniquePieces = wdl.hasUniquePieces; pawnCount[0] = wdl.pawnCount[0]; pawnCount[1] = wdl.pawnCount[1]; } // class TBTables creates and keeps ownership of the TBTable objects, one for // each TB file found. It supports a fast, hash based, table lookup. Populated // at init time, accessed at probe time. class TBTables { struct Entry { Key key; TBTable* wdl; TBTable* dtz; template TBTable* get() const { return (TBTable*)(Type == WDL ? (void*)wdl : (void*)dtz); } }; static constexpr int Size = 1 << 12; // 4K table, indexed by key's 12 lsb static constexpr int Overflow = 1; // Number of elements allowed to map to the last bucket Entry hashTable[Size + Overflow]; std::deque> wdlTable; std::deque> dtzTable; void insert(Key key, TBTable* wdl, TBTable* dtz) { uint32_t homeBucket = (uint32_t)key & (Size - 1); Entry entry{ key, wdl, dtz }; // Ensure last element is empty to avoid overflow when looking up for (uint32_t bucket = homeBucket; bucket < Size + Overflow - 1; ++bucket) { Key otherKey = hashTable[bucket].key; if (otherKey == key || !hashTable[bucket].get()) { hashTable[bucket] = entry; return; } // Robin Hood hashing: If we've probed for longer than this element, // insert here and search for a new spot for the other element instead. uint32_t otherHomeBucket = (uint32_t)otherKey & (Size - 1); if (otherHomeBucket > homeBucket) { std::swap(entry, hashTable[bucket]); key = otherKey; homeBucket = otherHomeBucket; } } std::cerr << "TB hash table size too low!" << std::endl; exit(EXIT_FAILURE); } public: template TBTable* get(Key key) { for (const Entry* entry = &hashTable[(uint32_t)key & (Size - 1)]; ; ++entry) { if (entry->key == key || !entry->get()) return entry->get(); } } void clear() { memset(hashTable, 0, sizeof(hashTable)); wdlTable.clear(); dtzTable.clear(); } size_t size() const { return wdlTable.size(); } void add(const std::vector& pieces); }; TBTables TBTables; // If the corresponding file exists two new objects TBTable and TBTable // are created and added to the lists and hash table. Called at init time. void TBTables::add(const std::vector& pieces) { std::string code; for (PieceType pt : pieces) code += PieceToChar[pt]; TBFile file(code.insert(code.find('K', 1), "v") + ".rtbw"); // KRK -> KRvK if (!file.is_open()) // Only WDL file is checked return; file.close(); MaxCardinality = std::max((int)pieces.size(), MaxCardinality); wdlTable.emplace_back(code); dtzTable.emplace_back(wdlTable.back()); // Insert into the hash keys for both colors: KRvK with KR white and black insert(wdlTable.back().key , &wdlTable.back(), &dtzTable.back()); insert(wdlTable.back().key2, &wdlTable.back(), &dtzTable.back()); } // TB tables are compressed with canonical Huffman code. The compressed data is divided into // blocks of size d->sizeofBlock, and each block stores a variable number of symbols. // Each symbol represents either a WDL or a (remapped) DTZ value, or a pair of other symbols // (recursively). If you keep expanding the symbols in a block, you end up with up to 65536 // WDL or DTZ values. Each symbol represents up to 256 values and will correspond after // Huffman coding to at least 1 bit. So a block of 32 bytes corresponds to at most // 32 x 8 x 256 = 65536 values. This maximum is only reached for tables that consist mostly // of draws or mostly of wins, but such tables are actually quite common. In principle, the // blocks in WDL tables are 64 bytes long (and will be aligned on cache lines). But for // mostly-draw or mostly-win tables this can leave many 64-byte blocks only half-filled, so // in such cases blocks are 32 bytes long. The blocks of DTZ tables are up to 1024 bytes long. // The generator picks the size that leads to the smallest table. The "book" of symbols and // Huffman codes is the same for all blocks in the table. A non-symmetric pawnless TB file // will have one table for wtm and one for btm, a TB file with pawns will have tables per // file a,b,c,d also in this case one set for wtm and one for btm. int decompress_pairs(PairsData* d, uint64_t idx) { // Special case where all table positions store the same value if (d->flags & TBFlag::SingleValue) return d->minSymLen; // First we need to locate the right block that stores the value at index "idx". // Because each block n stores blockLength[n] + 1 values, the index i of the block // that contains the value at position idx is: // // for (i = -1, sum = 0; sum <= idx; i++) // sum += blockLength[i + 1] + 1; // // This can be slow, so we use SparseIndex[] populated with a set of SparseEntry that // point to known indices into blockLength[]. Namely SparseIndex[k] is a SparseEntry // that stores the blockLength[] index and the offset within that block of the value // with index I(k), where: // // I(k) = k * d->span + d->span / 2 (1) // First step is to get the 'k' of the I(k) nearest to our idx, using definition (1) uint32_t k = uint32_t(idx / d->span); // Then we read the corresponding SparseIndex[] entry uint32_t block = number(&d->sparseIndex[k].block); int offset = number(&d->sparseIndex[k].offset); // Now compute the difference idx - I(k). From definition of k we know that // // idx = k * d->span + idx % d->span (2) // // So from (1) and (2) we can compute idx - I(K): int diff = idx % d->span - d->span / 2; // Sum the above to offset to find the offset corresponding to our idx offset += diff; // Move to previous/next block, until we reach the correct block that contains idx, // that is when 0 <= offset <= d->blockLength[block] while (offset < 0) offset += d->blockLength[--block] + 1; while (offset > d->blockLength[block]) offset -= d->blockLength[block++] + 1; // Finally, we find the start address of our block of canonical Huffman symbols uint32_t* ptr = (uint32_t*)(d->data + ((uint64_t)block * d->sizeofBlock)); // Read the first 64 bits in our block, this is a (truncated) sequence of // unknown number of symbols of unknown length but we know the first one // is at the beginning of this 64 bits sequence. uint64_t buf64 = number(ptr); ptr += 2; int buf64Size = 64; Sym sym; while (true) { int len = 0; // This is the symbol length - d->min_sym_len // Now get the symbol length. For any symbol s64 of length l right-padded // to 64 bits we know that d->base64[l-1] >= s64 >= d->base64[l] so we // can find the symbol length iterating through base64[]. while (buf64 < d->base64[len]) ++len; // All the symbols of a given length are consecutive integers (numerical // sequence property), so we can compute the offset of our symbol of // length len, stored at the beginning of buf64. sym = Sym((buf64 - d->base64[len]) >> (64 - len - d->minSymLen)); // Now add the value of the lowest symbol of length len to get our symbol sym += number(&d->lowestSym[len]); // If our offset is within the number of values represented by symbol sym // we are done... if (offset < d->symlen[sym] + 1) break; // ...otherwise update the offset and continue to iterate offset -= d->symlen[sym] + 1; len += d->minSymLen; // Get the real length buf64 <<= len; // Consume the just processed symbol buf64Size -= len; if (buf64Size <= 32) { // Refill the buffer buf64Size += 32; buf64 |= (uint64_t)number(ptr++) << (64 - buf64Size); } } // Ok, now we have our symbol that expands into d->symlen[sym] + 1 symbols. // We binary-search for our value recursively expanding into the left and // right child symbols until we reach a leaf node where symlen[sym] + 1 == 1 // that will store the value we need. while (d->symlen[sym]) { Sym left = d->btree[sym].get(); // If a symbol contains 36 sub-symbols (d->symlen[sym] + 1 = 36) and // expands in a pair (d->symlen[left] = 23, d->symlen[right] = 11), then // we know that, for instance the ten-th value (offset = 10) will be on // the left side because in Recursive Pairing child symbols are adjacent. if (offset < d->symlen[left] + 1) sym = left; else { offset -= d->symlen[left] + 1; sym = d->btree[sym].get(); } } return d->btree[sym].get(); } bool check_dtz_stm(TBTable*, int, File) { return true; } bool check_dtz_stm(TBTable* entry, int stm, File f) { auto flags = entry->get(stm, f)->flags; return (flags & TBFlag::STM) == stm || ((entry->key == entry->key2) && !entry->hasPawns); } // DTZ scores are sorted by frequency of occurrence and then assigned the // values 0, 1, 2, ... in order of decreasing frequency. This is done for each // of the four WDLScore values. The mapping information necessary to reconstruct // the original values is stored in the TB file and read during map[] init. WDLScore map_score(TBTable*, File, int value, WDLScore) { return WDLScore(value - 2); } int map_score(TBTable* entry, File f, int value, WDLScore wdl) { constexpr int WDLMap[] = { 1, 3, 0, 2, 0 }; auto flags = entry->get(0, f)->flags; uint8_t* map = entry->map; uint16_t* idx = entry->get(0, f)->map_idx; if (flags & TBFlag::Mapped) { if (flags & TBFlag::Wide) value = ((uint16_t *)map)[idx[WDLMap[wdl + 2]] + value]; else value = map[idx[WDLMap[wdl + 2]] + value]; } // DTZ tables store distance to zero in number of moves or plies. We // want to return plies, so we have convert to plies when needed. if ( (wdl == WDLWin && !(flags & TBFlag::WinPlies)) || (wdl == WDLLoss && !(flags & TBFlag::LossPlies)) || wdl == WDLCursedWin || wdl == WDLBlessedLoss) value *= 2; return value + 1; } // Compute a unique index out of a position and use it to probe the TB file. To // encode k pieces of same type and color, first sort the pieces by square in // ascending order s1 <= s2 <= ... <= sk then compute the unique index as: // // idx = Binomial[1][s1] + Binomial[2][s2] + ... + Binomial[k][sk] // template Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result) { Square squares[TBPIECES]; Piece pieces[TBPIECES]; uint64_t idx; int next = 0, size = 0, leadPawnsCnt = 0; PairsData* d; Bitboard b, leadPawns = 0; File tbFile = FILE_A; // A given TB entry like KRK has associated two material keys: KRvk and Kvkr. // If both sides have the same pieces keys are equal. In this case TB tables // only store the 'white to move' case, so if the position to lookup has black // to move, we need to switch the color and flip the squares before to lookup. bool symmetricBlackToMove = (entry->key == entry->key2 && pos.side_to_move()); // TB files are calculated for white as stronger side. For instance we have // KRvK, not KvKR. A position where stronger side is white will have its // material key == entry->key, otherwise we have to switch the color and // flip the squares before to lookup. bool blackStronger = (pos.material_key() != entry->key); int flipColor = (symmetricBlackToMove || blackStronger) * PIECE_TYPE_NB; 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 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& visited) { visited[s] = true; // We can set it now because tree is acyclic Sym sr = d->btree[s].get(); if (sr == 0xFFF) return 0; Sym sl = d->btree[s].get(); if (!visited[sl]) d->symlen[sl] = set_symlen(d, sl, visited); if (!visited[sr]) d->symlen[sr] = set_symlen(d, sr, visited); return d->symlen[sl] + d->symlen[sr] + 1; } uint8_t* set_sizes(PairsData* d, uint8_t* data) { d->flags = *data++; if (d->flags & TBFlag::SingleValue) { d->blocksNum = d->blockLengthSize = 0; d->span = d->sparseIndexSize = 0; // Broken MSVC zero-init d->minSymLen = *data++; // Here we store the single value return data; } // groupLen[] is a zero-terminated list of group lengths, the last groupIdx[] // element stores the biggest index that is the tb size. uint64_t tbSize = d->groupIdx[std::find(d->groupLen, d->groupLen + 7, 0) - d->groupLen]; d->sizeofBlock = 1ULL << *data++; d->span = 1ULL << *data++; d->sparseIndexSize = size_t((tbSize + d->span - 1) / d->span); // Round up auto padding = number(data++); d->blocksNum = number(data); data += sizeof(uint32_t); d->blockLengthSize = d->blocksNum + padding; // Padded to ensure SparseIndex[] // does not point out of range. d->maxSymLen = *data++; d->minSymLen = *data++; d->lowestSym = (Sym*)data; d->base64.resize(d->maxSymLen - d->minSymLen + 1); // The canonical code is ordered such that longer symbols (in terms of // the number of bits of their Huffman code) have lower numeric value, // so that d->lowestSym[i] >= d->lowestSym[i+1] (when read as LittleEndian). // Starting from this we compute a base64[] table indexed by symbol length // and containing 64 bit values so that d->base64[i] >= d->base64[i+1]. // See https://en.wikipedia.org/wiki/Huffman_coding for (int i = d->base64.size() - 2; i >= 0; --i) { d->base64[i] = (d->base64[i + 1] + number(&d->lowestSym[i]) - number(&d->lowestSym[i + 1])) / 2; assert(d->base64[i] * 2 >= d->base64[i+1]); } // Now left-shift by an amount so that d->base64[i] gets shifted 1 bit more // than d->base64[i+1] and given the above assert condition, we ensure that // d->base64[i] >= d->base64[i+1]. Moreover for any symbol s64 of length i // and right-padded to 64 bits holds d->base64[i-1] >= s64 >= d->base64[i]. for (size_t i = 0; i < d->base64.size(); ++i) d->base64[i] <<= 64 - i - d->minSymLen; // Right-padding to 64 bits data += d->base64.size() * sizeof(Sym); d->symlen.resize(number(data)); data += sizeof(uint16_t); d->btree = (LR*)data; // The compression scheme used is "Recursive Pairing", that replaces the most // frequent adjacent pair of symbols in the source message by a new symbol, // reevaluating the frequencies of all of the symbol pairs with respect to // the extended alphabet, and then repeating the process. // See http://www.larsson.dogma.net/dcc99.pdf std::vector visited(d->symlen.size()); for (Sym sym = 0; sym < d->symlen.size(); ++sym) if (!visited[sym]) d->symlen[sym] = set_symlen(d, sym, visited); return data + d->symlen.size() * sizeof(LR) + (d->symlen.size() & 1); } uint8_t* set_dtz_map(TBTable&, uint8_t* data, File) { return data; } uint8_t* set_dtz_map(TBTable& e, uint8_t* data, File maxFile) { e.map = data; for (File f = FILE_A; f <= maxFile; ++f) { auto flags = e.get(0, f)->flags; if (flags & TBFlag::Mapped) { if (flags & TBFlag::Wide) { data += (uintptr_t)data & 1; // Word alignment, we may have a mixed table for (int i = 0; i < 4; ++i) { // Sequence like 3,x,x,x,1,x,0,2,x,x e.get(0, f)->map_idx[i] = (uint16_t)((uint16_t *)data - (uint16_t *)e.map + 1); data += 2 * number(data) + 2; } } else { for (int i = 0; i < 4; ++i) { e.get(0, f)->map_idx[i] = (uint16_t)(data - e.map + 1); data += *data + 1; } } } } return data += (uintptr_t)data & 1; // Word alignment } // Populate entry's PairsData records with data from the just memory mapped file. // Called at first access. template void set(T& e, uint8_t* data) { PairsData* d; enum { Split = 1, HasPawns = 2 }; assert(e.hasPawns == bool(*data & HasPawns)); assert((e.key != e.key2) == bool(*data & Split)); data++; // First byte stores flags const int sides = T::Sides == 2 && (e.key != e.key2) ? 2 : 1; const File maxFile = e.hasPawns ? FILE_D : FILE_A; bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides assert(!pp || e.pawnCount[0]); for (File f = FILE_A; f <= maxFile; ++f) { for (int i = 0; i < sides; i++) *e.get(i, f) = PairsData(); int order[][2] = { { *data & 0xF, pp ? *(data + 1) & 0xF : 0xF }, { *data >> 4, pp ? *(data + 1) >> 4 : 0xF } }; data += 1 + pp; for (int k = 0; k < e.pieceCount; ++k, ++data) for (int i = 0; i < sides; i++) { int p = i ? *data >> 4 : *data & 0xF; e.get(i, f)->pieces[k] = make_piece(Color(p >> 3), PieceType(p & 7)); } for (int i = 0; i < sides; ++i) set_groups(e, e.get(i, f), order[i], f); } data += (uintptr_t)data & 1; // Word alignment for (File f = FILE_A; f <= maxFile; ++f) for (int i = 0; i < sides; i++) data = set_sizes(e.get(i, f), data); data = set_dtz_map(e, data, maxFile); for (File f = FILE_A; f <= maxFile; ++f) for (int i = 0; i < sides; i++) { (d = e.get(i, f))->sparseIndex = (SparseEntry*)data; data += d->sparseIndexSize * sizeof(SparseEntry); } for (File f = FILE_A; f <= maxFile; ++f) for (int i = 0; i < sides; i++) { (d = e.get(i, f))->blockLength = (uint16_t*)data; data += d->blockLengthSize * sizeof(uint16_t); } for (File f = FILE_A; f <= maxFile; ++f) for (int i = 0; i < sides; i++) { data = (uint8_t*)(((uintptr_t)data + 0x3F) & ~0x3F); // 64 byte alignment (d = e.get(i, f))->data = data; data += d->blocksNum * d->sizeofBlock; } } // If the TB file corresponding to the given position is already memory mapped // then return its base address, otherwise try to memory map and init it. Called // at every probe, memory map and init only at first access. Function is thread // safe and can be called concurrently. template void* mapped(TBTable& e, const Position& pos) { static std::mutex mutex; // Use 'acquire' to avoid a thread reading 'ready' == true while // another is still working. (compiler reordering may cause this). if (e.ready.load(std::memory_order_acquire)) return e.baseAddress; // Could be nullptr if file does not exist std::scoped_lock lk(mutex); if (e.ready.load(std::memory_order_relaxed)) // Recheck under lock return e.baseAddress; // Pieces strings in decreasing order for each color, like ("KPP","KR") std::string fname, w, b; for (PieceType pt = KING; pt >= PAWN; --pt) { w += std::string(popcount(pos.pieces(WHITE, pt)), PieceToChar[pt]); b += std::string(popcount(pos.pieces(BLACK, pt)), PieceToChar[pt]); } fname = (e.key == pos.material_key() ? w + 'v' + b : b + 'v' + w) + (Type == WDL ? ".rtbw" : ".rtbz"); uint8_t* data = TBFile(fname).map(&e.baseAddress, &e.mapping, Type); if (data) set(e, data); e.ready.store(true, std::memory_order_release); return e.baseAddress; } template::Ret> Ret probe_table(const Position& pos, ProbeState* result, WDLScore wdl = WDLDraw) { if (pos.count() == 2) // KvK return Ret(WDLDraw); TBTable* entry = TBTables.get(pos.material_key()); if (!entry || !mapped(*entry, pos)) return *result = FAIL, Ret(); return do_probe_table(pos, entry, wdl, result); } // For a position where the side to move has a winning capture it is not necessary // to store a winning value so the generator treats such positions as "don't cares" // and tries to assign to it a value that improves the compression ratio. Similarly, // if the side to move has a drawing capture, then the position is at least drawn. // If the position is won, then the TB needs to store a win value. But if the // position is drawn, the TB may store a loss value if that is better for compression. // All of this means that during probing, the engine must look at captures and probe // their results and must probe the position itself. The "best" result of these // probes is the correct result for the position. // DTZ tables do not store values when a following move is a zeroing winning move // (winning capture or winning pawn move). Also DTZ store wrong values for positions // where the best move is an ep-move (even if losing). So in all these cases set // the state to ZEROING_BEST_MOVE. template WDLScore search(Position& pos, ProbeState* result) { WDLScore value, bestValue = WDLLoss; StateInfo st; auto moveList = MoveList(pos); size_t totalCount = moveList.size(), moveCount = 0; for (const Move move : moveList) { if ( !pos.capture(move) && (!CheckZeroingMoves || type_of(pos.moved_piece(move)) != PAWN)) continue; moveCount++; pos.do_move(move, st); value = -search(pos, result); pos.undo_move(move); if (*result == FAIL) return WDLDraw; if (value > bestValue) { bestValue = value; if (value >= WDLWin) { *result = ZEROING_BEST_MOVE; // Winning DTZ-zeroing move return value; } } } // In case we have already searched all the legal moves we don't have to probe // the TB because the stored score could be wrong. For instance TB tables // do not contain information on position with ep rights, so in this case // the result of probe_wdl_table is wrong. Also in case of only capture // moves, for instance here 4K3/4q3/6p1/2k5/6p1/8/8/8 w - - 0 7, we have to // return with ZEROING_BEST_MOVE set. bool noMoreMoves = (moveCount && moveCount == totalCount); if (noMoreMoves) value = bestValue; else { value = probe_table(pos, result); if (*result == FAIL) return WDLDraw; } // DTZ stores a "don't care" value if bestValue is a win if (bestValue >= value) return *result = ( bestValue > WDLDraw || noMoreMoves ? ZEROING_BEST_MOVE : OK), bestValue; return *result = OK, value; } } // namespace /// Tablebases::init() is called at startup and after every change to /// "SyzygyPath" UCI option to (re)create the various tables. It is not thread /// safe, nor it needs to be. void Tablebases::init(const std::string& paths) { TBTables.clear(); MaxCardinality = 0; TBFile::Paths = paths; #ifdef LARGEBOARDS // Tablebases are not working for large-board version return; #endif if (paths.empty() || paths == "") return; // MapB1H1H7[] encodes a square below a1-h8 diagonal to 0..27 int code = 0; for (Square s = SQ_A1; s <= SQ_H8; ++s) if (off_A1H8(s) < 0) MapB1H1H7[s] = code++; // MapA1D1D4[] encodes a square in the a1-d1-d4 triangle to 0..9 std::vector diagonal; code = 0; for (Square s = SQ_A1; s <= SQ_D4; ++s) if (off_A1H8(s) < 0 && file_of(s) <= FILE_D) MapA1D1D4[s] = code++; else if (!off_A1H8(s) && file_of(s) <= FILE_D) diagonal.push_back(s); // Diagonal squares are encoded as last ones for (auto s : diagonal) MapA1D1D4[s] = code++; // MapKK[] encodes all the 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> 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[WHITE][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 <= QUEEN; ++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 <= QUEEN; ++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 <= QUEEN; ++p5) TBTables.add({KING, p1, p2, p3, p4, KING, p5}); } for (PieceType p4 = PAWN; p4 <= QUEEN; ++p4) { TBTables.add({KING, p1, p2, p3, KING, p4}); for (PieceType p5 = PAWN; p5 <= p4; ++p5) TBTables.add({KING, p1, p2, p3, KING, p4, p5}); } } for (PieceType p3 = PAWN; p3 <= p1; ++p3) for (PieceType p4 = PAWN; p4 <= (p1 == p3 ? p2 : p3); ++p4) TBTables.add({KING, p1, p2, KING, p3, p4}); } } sync_cout << "info string Found " << TBTables.size() << " tablebases" << sync_endl; } // Probe the WDL table for a particular position. // If *result != FAIL, the probe was successful. // The return value is from the point of view of the side to move: // -2 : loss // -1 : loss, but draw under 50-move rule // 0 : draw // 1 : win, but draw under 50-move rule // 2 : win WDLScore Tablebases::probe_wdl(Position& pos, ProbeState* result) { *result = OK; return search(pos, result); } // Probe the DTZ table for a particular position. // If *result != FAIL, the probe was successful. // The return value is from the point of view of the side to move: // n < -100 : loss, but draw under 50-move rule // -100 <= n < -1 : loss in n ply (assuming 50-move counter == 0) // -1 : loss, the side to move is mated // 0 : draw // 1 < n <= 100 : win in n ply (assuming 50-move counter == 0) // 100 < n : win, but draw under 50-move rule // // The return value n can be off by 1: a return value -n can mean a loss // in n+1 ply and a return value +n can mean a win in n+1 ply. This // cannot happen for tables with positions exactly on the "edge" of // the 50-move rule. // // This implies that if dtz > 0 is returned, the position is certainly // a win if dtz + 50-move-counter <= 99. Care must be taken that the engine // picks moves that preserve dtz + 50-move-counter <= 99. // // If n = 100 immediately after a capture or pawn move, then the position // is also certainly a win, and during the whole phase until the next // capture or pawn move, the inequality to be preserved is // dtz + 50-move-counter <= 100. // // In short, if a move is available resulting in dtz + 50-move-counter <= 99, // then do not accept moves leading to dtz + 50-move-counter == 100. int Tablebases::probe_dtz(Position& pos, ProbeState* result) { *result = OK; WDLScore wdl = search(pos, result); if (*result == FAIL || wdl == WDLDraw) // DTZ tables don't store draws return 0; // DTZ stores a 'don't care' value in this case, or even a plain wrong // one as in case the best move is a losing ep, so it cannot be probed. if (*result == ZEROING_BEST_MOVE) return dtz_before_zeroing(wdl); int dtz = probe_table(pos, result, wdl); if (*result == FAIL) return 0; if (*result != CHANGE_STM) return (dtz + 100 * (wdl == WDLBlessedLoss || wdl == WDLCursedWin)) * sign_of(wdl); // DTZ stores results for the other side, so we need to do a 1-ply search and // find the winning move that minimizes DTZ. StateInfo st; int minDTZ = 0xFFFF; for (const Move move : MoveList(pos)) { bool zeroing = pos.capture(move) || type_of(pos.moved_piece(move)) == PAWN; pos.do_move(move, st); // For zeroing moves we want the dtz of the move _before_ doing it, // otherwise we will get the dtz of the next move sequence. Search the // position after the move to get the score sign (because even in a // winning position we could make a losing capture or going for a draw). dtz = zeroing ? -dtz_before_zeroing(search(pos, result)) : -probe_dtz(pos, result); // If the move mates, force minDTZ to 1 if (dtz == 1 && pos.checkers() && MoveList(pos).size() == 0) minDTZ = 1; // Convert result from 1-ply search. Zeroing moves are already accounted // by dtz_before_zeroing() that returns the DTZ of the previous move. if (!zeroing) dtz += sign_of(dtz); // Skip the draws and if we are winning only pick positive dtz if (dtz < minDTZ && sign_of(dtz) == sign_of(wdl)) minDTZ = dtz; pos.undo_move(move); if (*result == FAIL) return 0; } // When there are no legal moves, the position is mate: we return -1 return minDTZ == 0xFFFF ? -1 : minDTZ; } // Use the DTZ tables to rank root moves. // // A return value false indicates that not all probes were successful. bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) { ProbeState result; 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 if (pos.is_draw(1)) { // In case a root move leads to a draw by repetition or // 50-move rule, we set dtz to zero. Note: since we are // only 1 ply from the root, this must be a true 3-fold // repetition inside the game history. dtz = 0; } else { // Otherwise, take dtz for the new position and correct by 1 ply dtz = -probe_dtz(pos, &result); dtz = dtz > 0 ? dtz + 1 : dtz < 0 ? dtz - 1 : dtz; } // Make sure that a mating move is assigned a dtz value of 1 if ( pos.checkers() && dtz == 2 && MoveList(pos).size() == 0) dtz = 1; pos.undo_move(m.pv[0]); if (result == FAIL) return false; // Better moves are ranked higher. Certain wins are ranked equally. // Losing moves are ranked equally unless a 50-move draw is in sight. int r = dtz > 0 ? (dtz + cnt50 <= 99 && !rep ? 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; WDLScore wdl; bool rule50 = Options["Syzygy50MoveRule"]; // Probe and rank each move for (auto& m : rootMoves) { pos.do_move(m.pv[0], st); if (pos.is_draw(1)) wdl = WDLDraw; else wdl = -probe_wdl(pos, &result); pos.undo_move(m.pv[0]); if (result == FAIL) return false; m.tbRank = WDL_to_rank[wdl + 2]; if (!rule50) wdl = wdl > WDLDraw ? WDLWin : wdl < WDLDraw ? WDLLoss : WDLDraw; m.tbScore = WDL_to_value[wdl + 2]; } return true; } } // namespace Stockfish Fairy-Stockfish-fairy_sf_14_0_1_xq/src/syzygy/tbprobe.h000066400000000000000000000047321414571233100233200ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef TBPROBE_H #define TBPROBE_H #include #include "../search.h" namespace Stockfish::Tablebases { enum WDLScore { WDLLoss = -2, // Loss WDLBlessedLoss = -1, // Loss, but draw under 50-move rule WDLDraw = 0, // Draw WDLCursedWin = 1, // Win, but draw under 50-move rule WDLWin = 2, // Win 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; } } // namespace Stockfish::Tablebases #endif Fairy-Stockfish-fairy_sf_14_0_1_xq/src/thread.cpp000066400000000000000000000207641414571233100221120ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include // For std::count #include "movegen.h" #include "partner.h" #include "search.h" #include "thread.h" #include "uci.h" #include "syzygy/tbprobe.h" #include "tt.h" #include "xboard.h" namespace Stockfish { ThreadPool Threads; // Global object /// Thread constructor launches the thread and waits until it goes to sleep /// in idle_loop(). Note that 'searching' and 'exit' should be already set. Thread::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this) { 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 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 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 lk(mutex); searching = false; cv.notify_one(); // Wake up anyone waiting for search finished // Start ponder search from separate thread to prevent deadlock if (Threads.size() && this == Threads.main() && XBoard::stateMachine && XBoard::stateMachine->ponderMove) { NativeThread t(&XBoard::StateMachine::ponder, XBoard::stateMachine); t.detach(); } 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 = abort = false; increaseDepth = true; main()->ponder = ponderMode; Search::Limits = limits; Search::RootMoves rootMoves; for (const auto& m : MoveList(pos)) if ( (limits.searchmoves.empty() || std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m)) && (limits.banmoves.empty() || !std::count(limits.banmoves.begin(), limits.banmoves.end(), m))) rootMoves.emplace_back(m); // Add virtual drops if (pos.two_boards() && Partner.opptime && limits.time[pos.side_to_move()] > Partner.opptime + 1000) { if (pos.checkers()) { for (const auto& m : MoveList(pos)) if (pos.virtual_drop(m) && pos.legal(m)) rootMoves.emplace_back(m); } else { for (const auto& m : MoveList(pos)) if (pos.virtual_drop(m) && pos.legal(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.variant(), 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 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(); } } // namespace Stockfish Fairy-Stockfish-fairy_sf_14_0_1_xq/src/thread.h000066400000000000000000000072741414571233100215600ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef THREAD_H_INCLUDED #define THREAD_H_INCLUDED #include #include #include #include #include #include "material.h" #include "movepick.h" #include "pawns.h" #include "position.h" #include "search.h" #include "thread_win32_osx.h" namespace Stockfish { /// Thread class keeps together all the thread-related stuff. We use /// per-thread pawn and material hash tables so that once we get a /// pointer to an entry its life time is unlimited and we don't have /// to care about someone changing the entry under our feet. class Thread { std::mutex mutex; std::condition_variable cv; size_t idx; bool exit = false, searching = true; // Set before starting std::thread NativeThread stdThread; public: explicit Thread(size_t); virtual ~Thread(); virtual void search(); void clear(); void idle_loop(); void start_searching(); void wait_for_search_finished(); size_t id() const { return idx; } Pawns::Table pawnsTable; Material::Table materialTable; size_t pvIdx, pvLast; uint64_t ttHitAverage; int selDepth, nmpMinPly; Color nmpColor; std::atomic 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 trend; }; /// 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; Thread* bestThread; // to fetch best move when in XBoard mode }; /// 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 { void start_thinking(Position&, StateListPtr&, const Search::LimitsType&, bool = false); void clear(); void set(size_t); MainThread* main() const { return static_cast(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; std::atomic_bool abort, sit; StateListPtr setupStates; private: uint64_t accumulate(std::atomic Thread::* member) const { uint64_t sum = 0; for (Thread* th : *this) sum += (th->*member).load(std::memory_order_relaxed); return sum; } }; extern ThreadPool Threads; } // namespace Stockfish #endif // #ifndef THREAD_H_INCLUDED Fairy-Stockfish-fairy_sf_14_0_1_xq/src/thread_win32_osx.h000066400000000000000000000044161414571233100234660ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef THREAD_WIN32_OSX_H_INCLUDED #define THREAD_WIN32_OSX_H_INCLUDED #include /// 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 namespace Stockfish { static const size_t TH_STACK_SIZE = 8 * 1024 * 1024; template > void* start_routine(void* ptr) { P* p = reinterpret_cast(ptr); (p->first->*(p->second))(); // Call member function pointer delete p; return NULL; } class NativeThread { pthread_t thread; public: template> 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, new P(obj, fun)); } void join() { pthread_join(thread, NULL); } void detach() { pthread_detach(thread); } }; } // namespace Stockfish #else // Default case: use STL classes namespace Stockfish { typedef std::thread NativeThread; } // namespace Stockfish #endif #endif // #ifndef THREAD_WIN32_OSX_H_INCLUDED Fairy-Stockfish-fairy_sf_14_0_1_xq/src/timeman.cpp000066400000000000000000000100571414571233100222670ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include "partner.h" #include "search.h" #include "timeman.h" #include "uci.h" namespace Stockfish { 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(const Position& pos, 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)); // Adjust time management for four-player variants if (pos.two_boards()) { if (Partner.partnerDead && Partner.opptime) timeLeft -= Partner.opptime; else { timeLeft = std::min(timeLeft, 5000 + std::min(std::abs(limits.time[us] - Partner.opptime), TimePoint(Partner.opptime))); if (Partner.fast || Partner.partnerDead) timeLeft /= 4; } } // 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.0084 + std::pow(ply + 3.0, 0.5) * 0.0042, 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; } } // namespace Stockfish Fairy-Stockfish-fairy_sf_14_0_1_xq/src/timeman.h000066400000000000000000000032071414571233100217330ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef TIMEMAN_H_INCLUDED #define TIMEMAN_H_INCLUDED #include "misc.h" #include "search.h" #include "thread.h" namespace Stockfish { /// The TimeManagement class computes the optimal time to think depending on /// the maximum available time, the game move number and other parameters. class TimeManagement { public: void init(const Position& pos, 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; } // namespace Stockfish #endif // #ifndef TIMEMAN_H_INCLUDED Fairy-Stockfish-fairy_sf_14_0_1_xq/src/tt.cpp000066400000000000000000000127211414571233100212640ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include // For std::memset #include #include #include "bitboard.h" #include "misc.h" #include "thread.h" #include "tt.h" #include "uci.h" namespace Stockfish { 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) move32 = (uint32_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_large_pages_free(table); clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster); table = static_cast(aligned_large_pages_alloc(clusterCount * sizeof(Cluster))); if (!table) { 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 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 & (GENERATION_DELTA - 1))); // Refresh return found = (bool)tte[i].depth8, &tte[i]; } // Find an entry to be replaced according to the replacement strategy TTEntry* replace = tte; for (int i = 1; i < ClusterSize; ++i) // Due to our packed storage format for generation and its cyclic // nature we add GENERATION_CYCLE (256 is the modulus, plus what // is needed to keep the unrelated lowest n bits from affecting // the result) to calculate the entry age correctly even after // generation8 overflows into the next cycle. if ( replace->depth8 - ((GENERATION_CYCLE + generation8 - replace->genBound8) & GENERATION_MASK) > tte[i].depth8 - ((GENERATION_CYCLE + generation8 - tte[i].genBound8) & GENERATION_MASK)) replace = &tte[i]; return found = false, replace; } /// TranspositionTable::hashfull() returns an approximation of the hashtable /// occupation during a search. The hash is x permill full, as per UCI protocol. 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 & GENERATION_MASK) == generation8; return cnt / ClusterSize; } } // namespace Stockfish Fairy-Stockfish-fairy_sf_14_0_1_xq/src/tt.h000066400000000000000000000066171414571233100207400ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef TT_H_INCLUDED #define TT_H_INCLUDED #include "misc.h" #include "types.h" namespace Stockfish { /// TTEntry struct is the 12 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 32 bit (official SF: 16 bit) /// value 16 bit /// eval value 16 bit struct TTEntry { Move move() const { return (Move )move32; } 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; uint32_t move32; 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 = 5; struct Cluster { TTEntry entry[ClusterSize]; char padding[4]; // Pad to 64 bytes }; static_assert(sizeof(Cluster) == 64, "Unexpected Cluster size"); // Constants used to refresh the hash table periodically static constexpr unsigned GENERATION_BITS = 3; // nb of bits reserved for other things static constexpr int GENERATION_DELTA = (1 << GENERATION_BITS); // increment for generation field static constexpr int GENERATION_CYCLE = 255 + (1 << GENERATION_BITS); // cycle length static constexpr int GENERATION_MASK = (0xFF << GENERATION_BITS) & 0xFF; // mask to pull out generation number public: ~TranspositionTable() { aligned_large_pages_free(table); } void new_search() { generation8 += GENERATION_DELTA; } // Lower bits are used for other things TTEntry* probe(const Key key, bool& found) const; int hashfull() const; void resize(size_t mbSize); void clear(); TTEntry* first_entry(const Key key) const { return &table[mul_hi64(key, clusterCount)].entry[0]; } private: friend struct TTEntry; size_t clusterCount; Cluster* table; uint8_t generation8; // Size must be not bigger than TTEntry::genBound8 }; extern TranspositionTable TT; } // namespace Stockfish #endif // #ifndef TT_H_INCLUDED Fairy-Stockfish-fairy_sf_14_0_1_xq/src/tune.cpp000066400000000000000000000073661414571233100216210ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include "types.h" #include "misc.h" #include "uci.h" using std::string; namespace Stockfish { bool Tune::update_on_last; const UCI::Option* LastOption = nullptr; static std::map 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::init_option() { make_option(name, value, range); } template<> void Tune::Entry::read_option() { if (Options.count(name)) value = int(Options[name]); } template<> void Tune::Entry::init_option() { make_option(name, value, range); } template<> void Tune::Entry::read_option() { if (Options.count(name)) value = Value(int(Options[name])); } template<> void Tune::Entry::init_option() { make_option("m" + name, mg_value(value), range); make_option("e" + name, eg_value(value), range); } template<> void Tune::Entry::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::init_option() {} template<> void Tune::Entry::read_option() { value(); } } // namespace Stockfish // Init options with tuning session results instead of default values. Useful to // get correct bench signature after a tuning session or to test tuned values. // Just copy fishtest tuning results in a result.txt file and extract the // values with: // // cat results.txt | sed 's/^param: \([^,]*\), best: \([^,]*\).*/ TuneResults["\1"] = int(round(\2));/' // // Then paste the output below, as the function body #include namespace Stockfish { void Tune::read_results() { /* ...insert your values here... */ } } // namespace Stockfish Fairy-Stockfish-fairy_sf_14_0_1_xq/src/tune.h000066400000000000000000000136231414571233100212570ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef TUNE_H_INCLUDED #define TUNE_H_INCLUDED #include #include #include #include namespace Stockfish { typedef std::pair 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) /// 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 struct Entry : public EntryBase { static_assert(!std::is_const::value, "Parameter cannot be const!"); static_assert( std::is_same::value || std::is_same::value || std::is_same::value || std::is_same::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 facility to fill the container, each Entry corresponds to a parameter // to tune. We use variadic templates to deal with an unspecified number of // entries, each one of a possible different type. static std::string next(std::string& names, bool pop = true); int add(const SetRange&, std::string&&) { return 0; } template int add(const SetRange& range, std::string&& names, T& value, Args&&... args) { list.push_back(std::unique_ptr(new Entry(next(names), value, range))); return add(range, std::move(names), args...); } // Template specialization for arrays: recursively handle multi-dimensional arrays template 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 int add(const SetRange&, std::string&& names, SetRange& value, Args&&... args) { return add(value, (next(names), std::move(names)), args...); } std::vector> list; public: template 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 } // namespace Stockfish #endif // #ifndef TUNE_H_INCLUDED Fairy-Stockfish-fairy_sf_14_0_1_xq/src/types.h000066400000000000000000000653471414571233100214620ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef 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 #include #include #include #include #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' #pragma comment(linker, "/STACK:8000000") // Use 8 MB stack size for MSVC #pragma comment(lib, "advapi32.lib") // Fix linker error #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(__GNUC__ ) && (__GNUC__ < 9 || (__GNUC__ == 9 && __GNUC_MINOR__ <= 2)) && defined(_WIN32) && !defined(__clang__) #define ALIGNAS_ON_STACK_VARIABLES_BROKEN #endif #define ASSERT_ALIGNED(ptr, alignment) assert(reinterpret_cast(ptr) % alignment == 0) #if defined(_WIN64) && defined(_MSC_VER) // No Makefile used # include // Microsoft header for _BitScanForward64() # define IS_64BIT #endif #if defined(USE_POPCNT) && (defined(__INTEL_COMPILER) || defined(_MSC_VER)) # include // Intel and Microsoft header for _mm_popcnt_u64() #endif #if !defined(NO_PREFETCH) && (defined(__INTEL_COMPILER) || defined(_MSC_VER)) # include // Intel and Microsoft header for _mm_prefetch() #endif #if defined(USE_PEXT) # include // Header for _pext_u64() intrinsic # ifdef LARGEBOARDS # define pext(b, m) (_pext_u64(b, m) ^ (_pext_u64(b >> 64, m >> 64) << popcount((m << 64) >> 64))) # else # define pext(b, m) _pext_u64(b, m) # endif #else # define pext(b, m) 0 #endif namespace Stockfish { #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; #ifdef LARGEBOARDS #if defined(__GNUC__) && defined(IS_64BIT) typedef unsigned __int128 Bitboard; #else struct Bitboard { uint64_t b64[2]; constexpr Bitboard() : b64 {0, 0} {} constexpr Bitboard(uint64_t i) : b64 {0, i} {} constexpr Bitboard(uint64_t hi, uint64_t lo) : b64 {hi, lo} {}; constexpr operator bool() const { return b64[0] || b64[1]; } constexpr operator long long unsigned () const { return b64[1]; } constexpr operator unsigned() const { return b64[1]; } constexpr Bitboard operator << (const unsigned int bits) const { return Bitboard( bits >= 64 ? b64[1] << (bits - 64) : bits == 0 ? b64[0] : ((b64[0] << bits) | (b64[1] >> (64 - bits))), bits >= 64 ? 0 : b64[1] << bits); } constexpr Bitboard operator >> (const unsigned int bits) const { return Bitboard(bits >= 64 ? 0 : b64[0] >> bits, bits >= 64 ? b64[0] >> (bits - 64) : bits == 0 ? b64[1] : ((b64[1] >> bits) | (b64[0] << (64 - bits)))); } constexpr Bitboard operator << (const int bits) const { return *this << unsigned(bits); } constexpr Bitboard operator >> (const int bits) const { return *this >> unsigned(bits); } constexpr bool operator == (const Bitboard y) const { return (b64[0] == y.b64[0]) && (b64[1] == y.b64[1]); } constexpr bool operator != (const Bitboard y) const { return !(*this == y); } inline Bitboard& operator |=(const Bitboard x) { b64[0] |= x.b64[0]; b64[1] |= x.b64[1]; return *this; } inline Bitboard& operator &=(const Bitboard x) { b64[0] &= x.b64[0]; b64[1] &= x.b64[1]; return *this; } inline Bitboard& operator ^=(const Bitboard x) { b64[0] ^= x.b64[0]; b64[1] ^= x.b64[1]; return *this; } constexpr Bitboard operator ~ () const { return Bitboard(~b64[0], ~b64[1]); } constexpr Bitboard operator - () const { return Bitboard(-b64[0] - (b64[1] > 0), -b64[1]); } constexpr Bitboard operator | (const Bitboard x) const { return Bitboard(b64[0] | x.b64[0], b64[1] | x.b64[1]); } constexpr Bitboard operator & (const Bitboard x) const { return Bitboard(b64[0] & x.b64[0], b64[1] & x.b64[1]); } constexpr Bitboard operator ^ (const Bitboard x) const { return Bitboard(b64[0] ^ x.b64[0], b64[1] ^ x.b64[1]); } constexpr Bitboard operator - (const Bitboard x) const { return Bitboard(b64[0] - x.b64[0] - (b64[1] < x.b64[1]), b64[1] - x.b64[1]); } constexpr Bitboard operator - (const int x) const { return *this - Bitboard(x); } inline Bitboard operator * (const Bitboard x) const { uint64_t a_lo = (uint32_t)b64[1]; uint64_t a_hi = b64[1] >> 32; uint64_t b_lo = (uint32_t)x.b64[1]; uint64_t b_hi = x.b64[1] >> 32; uint64_t t1 = (a_hi * b_lo) + ((a_lo * b_lo) >> 32); uint64_t t2 = (a_lo * b_hi) + (t1 & 0xFFFFFFFF); return Bitboard(b64[0] * x.b64[1] + b64[1] * x.b64[0] + (a_hi * b_hi) + (t1 >> 32) + (t2 >> 32), (t2 << 32) + (a_lo * b_lo & 0xFFFFFFFF)); } }; #endif constexpr int SQUARE_BITS = 7; #else typedef uint64_t Bitboard; constexpr int SQUARE_BITS = 6; #endif #ifdef ALLVARS constexpr int MAX_MOVES = 4096; #else constexpr int MAX_MOVES = 1024; #endif 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 = 1 + (1 << SQUARE_BITS) }; enum MoveType : int { NORMAL, EN_PASSANT = 1 << (2 * SQUARE_BITS), CASTLING = 2 << (2 * SQUARE_BITS), PROMOTION = 3 << (2 * SQUARE_BITS), DROP = 4 << (2 * SQUARE_BITS), PIECE_PROMOTION = 5 << (2 * SQUARE_BITS), PIECE_DEMOTION = 6 << (2 * SQUARE_BITS), SPECIAL = 7 << (2 * SQUARE_BITS), }; constexpr int MOVE_TYPE_BITS = 4; 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 CheckCount : int { CHECKS_0 = 0, CHECKS_NB = 11 }; enum MaterialCounting { NO_MATERIAL_COUNTING, JANGGI_MATERIAL, UNWEIGHTED_MATERIAL, WHITE_DRAW_ODDS, BLACK_DRAW_ODDS }; enum CountingRule { NO_COUNTING, MAKRUK_COUNTING, ASEAN_COUNTING }; enum EnclosingRule { NO_ENCLOSING, REVERSI, ATAXX }; enum OptBool { NO_VALUE, VALUE_FALSE, VALUE_TRUE }; 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, XBOARD_VALUE_MATE = 200000, VALUE_VIRTUAL_MATE = 3000, VALUE_VIRTUAL_MATE_IN_MAX_PLY = VALUE_VIRTUAL_MATE - MAX_PLY, 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, FersValueMg = 420, FersValueEg = 450, AlfilValueMg = 350, AlfilValueEg = 330, FersAlfilValueMg = 700, FersAlfilValueEg = 650, SilverValueMg = 660, SilverValueEg = 640, AiwokValueMg = 2300, AiwokValueEg = 2700, BersValueMg = 1800, BersValueEg = 1900, ArchbishopValueMg = 2200, ArchbishopValueEg = 2200, ChancellorValueMg = 2300, ChancellorValueEg = 2600, AmazonValueMg = 2700, AmazonValueEg = 2850, KnibisValueMg = 1100, KnibisValueEg = 1200, BiskniValueMg = 750, BiskniValueEg = 700, KnirooValueMg = 1050, KnirooValueEg = 1250, RookniValueMg = 800, RookniValueEg = 950, ShogiPawnValueMg = 90, ShogiPawnValueEg = 100, LanceValueMg = 400, LanceValueEg = 240, ShogiKnightValueMg = 420, ShogiKnightValueEg = 290, GoldValueMg = 720, GoldValueEg = 700, DragonHorseValueMg = 1550, DragonHorseValueEg = 1550, ClobberPieceValueMg = 300, ClobberPieceValueEg = 300, BreakthroughPieceValueMg = 300, BreakthroughPieceValueEg = 300, ImmobilePieceValueMg = 50, ImmobilePieceValueEg = 50, CannonPieceValueMg = 800, CannonPieceValueEg = 700, JanggiCannonPieceValueMg = 800, JanggiCannonPieceValueEg = 600, SoldierValueMg = 200, SoldierValueEg = 270, HorseValueMg = 520, HorseValueEg = 800, ElephantValueMg = 300, ElephantValueEg = 300, JanggiElephantValueMg = 340, JanggiElephantValueEg = 350, BannerValueMg = 3400, BannerValueEg = 3500, WazirValueMg = 400, WazirValueEg = 350, CommonerValueMg = 700, CommonerValueEg = 900, CentaurValueMg = 1800, CentaurValueEg = 1900, MidgameLimit = 15258, EndgameLimit = 3915 }; constexpr int PIECE_TYPE_BITS = 6; // PIECE_TYPE_NB = pow(2, PIECE_TYPE_BITS) enum PieceType { NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, FERS, MET = FERS, ALFIL, FERS_ALFIL, SILVER, KHON = SILVER, AIWOK, BERS, DRAGON = BERS, ARCHBISHOP, CHANCELLOR, AMAZON, KNIBIS, BISKNI, KNIROO, ROOKNI, SHOGI_PAWN, LANCE, SHOGI_KNIGHT, GOLD, DRAGON_HORSE, CLOBBER_PIECE, BREAKTHROUGH_PIECE, IMMOBILE_PIECE, CANNON, JANGGI_CANNON, SOLDIER, HORSE, ELEPHANT, JANGGI_ELEPHANT, BANNER, WAZIR, COMMONER, CENTAUR, CUSTOM_PIECES, FAIRY_PIECES = QUEEN + 1, FAIRY_PIECES_END = CUSTOM_PIECES - 1, PIECE_TYPE_NB = 1 << PIECE_TYPE_BITS, KING = PIECE_TYPE_NB - 1, CUSTOM_PIECES_END = KING - 1, CUSTOM_PIECES_ROYAL = CUSTOM_PIECES_END, CUSTOM_PIECES_NB = CUSTOM_PIECES_END - CUSTOM_PIECES + 1, ALL_PIECES = 0, }; static_assert(KING < PIECE_TYPE_NB, "KING exceeds PIECE_TYPE_NB."); static_assert(PIECE_TYPE_BITS <= 6, "PIECE_TYPE uses more than 6 bit"); static_assert(!(PIECE_TYPE_NB & (PIECE_TYPE_NB - 1)), "PIECE_TYPE_NB is not a power of 2"); static_assert(2 * SQUARE_BITS + MOVE_TYPE_BITS + 2 * PIECE_TYPE_BITS <= 32, "Move encoding uses more than 32 bits"); enum Piece { NO_PIECE, W_PAWN = PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING = KING, B_PAWN = PAWN + PIECE_TYPE_NB, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING = KING + PIECE_TYPE_NB, PIECE_NB = 2 * PIECE_TYPE_NB }; enum RiderType : int { NO_RIDER = 0, RIDER_BISHOP = 1 << 0, RIDER_ROOK_H = 1 << 1, RIDER_ROOK_V = 1 << 2, RIDER_CANNON_H = 1 << 3, RIDER_CANNON_V = 1 << 4, RIDER_HORSE = 1 << 5, RIDER_ELEPHANT = 1 << 6, RIDER_JANGGI_ELEPHANT = 1 << 7, RIDER_CANNON_DIAG = 1 << 8, RIDER_NIGHTRIDER = 1 << 9, RIDER_GRASSHOPPER_H = 1 << 10, RIDER_GRASSHOPPER_V = 1 << 11, RIDER_GRASSHOPPER_D = 1 << 12, HOPPING_RIDERS = RIDER_CANNON_H | RIDER_CANNON_V | RIDER_CANNON_DIAG | RIDER_GRASSHOPPER_H | RIDER_GRASSHOPPER_V | RIDER_GRASSHOPPER_D, LAME_LEAPERS = RIDER_HORSE | RIDER_ELEPHANT | RIDER_JANGGI_ELEPHANT, ASYMMETRICAL_RIDERS = RIDER_HORSE | RIDER_JANGGI_ELEPHANT | RIDER_GRASSHOPPER_H | RIDER_GRASSHOPPER_V | RIDER_GRASSHOPPER_D, NON_SLIDING_RIDERS = HOPPING_RIDERS | LAME_LEAPERS | RIDER_NIGHTRIDER, }; extern Value PieceValue[PHASE_NB][PIECE_NB]; extern Value EvalPieceValue[PHASE_NB][PIECE_NB]; // variant piece values for evaluation extern Value CapturePieceValue[PHASE_NB][PIECE_NB]; // variant piece values for captures/search 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 { #ifdef LARGEBOARDS SQ_A1, SQ_B1, SQ_C1, SQ_D1, SQ_E1, SQ_F1, SQ_G1, SQ_H1, SQ_I1, SQ_J1, SQ_K1, SQ_L1, SQ_A2, SQ_B2, SQ_C2, SQ_D2, SQ_E2, SQ_F2, SQ_G2, SQ_H2, SQ_I2, SQ_J2, SQ_K2, SQ_L2, SQ_A3, SQ_B3, SQ_C3, SQ_D3, SQ_E3, SQ_F3, SQ_G3, SQ_H3, SQ_I3, SQ_J3, SQ_K3, SQ_L3, SQ_A4, SQ_B4, SQ_C4, SQ_D4, SQ_E4, SQ_F4, SQ_G4, SQ_H4, SQ_I4, SQ_J4, SQ_K4, SQ_L4, SQ_A5, SQ_B5, SQ_C5, SQ_D5, SQ_E5, SQ_F5, SQ_G5, SQ_H5, SQ_I5, SQ_J5, SQ_K5, SQ_L5, SQ_A6, SQ_B6, SQ_C6, SQ_D6, SQ_E6, SQ_F6, SQ_G6, SQ_H6, SQ_I6, SQ_J6, SQ_K6, SQ_L6, SQ_A7, SQ_B7, SQ_C7, SQ_D7, SQ_E7, SQ_F7, SQ_G7, SQ_H7, SQ_I7, SQ_J7, SQ_K7, SQ_L7, SQ_A8, SQ_B8, SQ_C8, SQ_D8, SQ_E8, SQ_F8, SQ_G8, SQ_H8, SQ_I8, SQ_J8, SQ_K8, SQ_L8, SQ_A9, SQ_B9, SQ_C9, SQ_D9, SQ_E9, SQ_F9, SQ_G9, SQ_H9, SQ_I9, SQ_J9, SQ_K9, SQ_L9, SQ_A10, SQ_B10, SQ_C10, SQ_D10, SQ_E10, SQ_F10, SQ_G10, SQ_H10, SQ_I10, SQ_J10, SQ_K10, SQ_L10, #else 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, #endif SQ_NONE, SQUARE_ZERO = 0, #ifdef LARGEBOARDS SQUARE_NB = 120, SQUARE_BIT_MASK = 127, #else SQUARE_NB = 64, SQUARE_BIT_MASK = 63, #endif SQ_MAX = SQUARE_NB - 1, SQUARE_NB_CHESS = 64, SQUARE_NB_SHOGI = 81, }; enum Direction : int { #ifdef LARGEBOARDS NORTH = 12, #else NORTH = 8, #endif 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 { #ifdef LARGEBOARDS FILE_A, FILE_B, FILE_C, FILE_D, FILE_E, FILE_F, FILE_G, FILE_H, FILE_I, FILE_J, FILE_K, FILE_L, #else FILE_A, FILE_B, FILE_C, FILE_D, FILE_E, FILE_F, FILE_G, FILE_H, #endif FILE_NB, FILE_MAX = FILE_NB - 1 }; enum Rank : int { #ifdef LARGEBOARDS RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, RANK_9, RANK_10, #else RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, #endif RANK_NB, RANK_MAX = RANK_NB - 1 }; // 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[12]; Piece handPiece[12]; int handCount[12]; // From and to squares, which may be SQ_NONE Square from[12]; Square to[12]; }; /// 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_BIT_OPERATORS_ON(T) \ constexpr T operator~ (T d) { return (T)~(int)d; } \ constexpr T operator| (T d1, T d2) { return (T)((int)d1 | (int)d2); } \ constexpr T operator& (T d1, T d2) { return (T)((int)d1 & (int)d2); } \ constexpr T operator^ (T d1, T d2) { return (T)((int)d1 ^ (int)d2); } \ inline T& operator|= (T& d1, T d2) { return (T&)((int&)d1 |= (int)d2); } \ inline T& operator&= (T& d1, T d2) { return (T&)((int&)d1 &= (int)d2); } \ inline T& operator^= (T& d1, T d2) { return (T&)((int&)d1 ^= (int)d2); } #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_INCR_OPERATORS_ON(CheckCount) ENABLE_BASE_OPERATORS_ON(Score) ENABLE_BASE_OPERATORS_ON(PieceType) ENABLE_BIT_OPERATORS_ON(RiderType) ENABLE_BASE_OPERATORS_ON(RiderType) #undef ENABLE_FULL_OPERATORS_ON #undef ENABLE_INCR_OPERATORS_ON #undef ENABLE_BASE_OPERATORS_ON #undef ENABLE_BIT_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, Rank maxRank = RANK_8) { // Swap A1 <-> A8 return Square(s + NORTH * (maxRank - 2 * (s / NORTH))); } constexpr Square flip_file(Square s, File maxFile = FILE_H) { // Swap A1 <-> H1 return Square(s + maxFile - 2 * (s % NORTH)); } constexpr Piece operator~(Piece pc) { return Piece(pc ^ PIECE_TYPE_NB); // 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 Value convert_mate_value(Value v, int ply) { return v == VALUE_MATE ? mate_in(ply) : v == -VALUE_MATE ? mated_in(ply) : v; } constexpr Square make_square(File f, Rank r) { return Square(r * FILE_NB + f); } constexpr Piece make_piece(Color c, PieceType pt) { return Piece((c << PIECE_TYPE_BITS) + pt); } constexpr PieceType type_of(Piece pc) { return PieceType(pc & (PIECE_TYPE_NB - 1)); } inline Color color_of(Piece pc) { assert(pc != NO_PIECE); return Color(pc >> PIECE_TYPE_BITS); } constexpr bool is_ok(Square s) { return s >= SQ_A1 && s <= SQ_MAX; } constexpr File file_of(Square s) { return File(s % FILE_NB); } constexpr Rank rank_of(Square s) { return Rank(s / FILE_NB); } constexpr Rank relative_rank(Color c, Rank r, Rank maxRank = RANK_8) { return Rank(c == WHITE ? r : maxRank - r); } constexpr Rank relative_rank(Color c, Square s, Rank maxRank = RANK_8) { return relative_rank(c, rank_of(s), maxRank); } constexpr Square relative_square(Color c, Square s, Rank maxRank = RANK_8) { return make_square(file_of(s), relative_rank(c, s, maxRank)); } constexpr Direction pawn_push(Color c) { return c == WHITE ? NORTH : SOUTH; } constexpr MoveType type_of(Move m) { return MoveType(m & (15 << (2 * SQUARE_BITS))); } constexpr Square to_sq(Move m) { return Square(m & SQUARE_BIT_MASK); } constexpr Square from_sq(Move m) { return type_of(m) == DROP ? SQ_NONE : Square((m >> SQUARE_BITS) & SQUARE_BIT_MASK); } inline int from_to(Move m) { return to_sq(m) + (from_sq(m) << SQUARE_BITS); } inline PieceType promotion_type(Move m) { return type_of(m) == PROMOTION ? PieceType((m >> (2 * SQUARE_BITS + MOVE_TYPE_BITS)) & (PIECE_TYPE_NB - 1)) : NO_PIECE_TYPE; } inline PieceType gating_type(Move m) { return PieceType((m >> (2 * SQUARE_BITS + MOVE_TYPE_BITS)) & (PIECE_TYPE_NB - 1)); } inline Square gating_square(Move m) { return Square((m >> (2 * SQUARE_BITS + MOVE_TYPE_BITS + PIECE_TYPE_BITS)) & SQUARE_BIT_MASK); } inline bool is_gating(Move m) { return gating_type(m) && (type_of(m) == NORMAL || type_of(m) == CASTLING); } inline bool is_pass(Move m) { return type_of(m) == SPECIAL && from_sq(m) == to_sq(m); } constexpr Move make_move(Square from, Square to) { return Move((from << SQUARE_BITS) + to); } template inline Move make(Square from, Square to, PieceType pt = NO_PIECE_TYPE) { return Move((pt << (2 * SQUARE_BITS + MOVE_TYPE_BITS)) + T + (from << SQUARE_BITS) + to); } constexpr Move make_drop(Square to, PieceType pt_in_hand, PieceType pt_dropped) { return Move((pt_in_hand << (2 * SQUARE_BITS + MOVE_TYPE_BITS + PIECE_TYPE_BITS)) + (pt_dropped << (2 * SQUARE_BITS + MOVE_TYPE_BITS)) + DROP + to); } constexpr Move reverse_move(Move m) { return make_move(to_sq(m), from_sq(m)); } template constexpr Move make_gating(Square from, Square to, PieceType pt, Square gate) { return Move((gate << (2 * SQUARE_BITS + MOVE_TYPE_BITS + PIECE_TYPE_BITS)) + (pt << (2 * SQUARE_BITS + MOVE_TYPE_BITS)) + T + (from << SQUARE_BITS) + to); } constexpr PieceType dropped_piece_type(Move m) { return PieceType((m >> (2 * SQUARE_BITS + MOVE_TYPE_BITS)) & (PIECE_TYPE_NB - 1)); } constexpr PieceType in_hand_piece_type(Move m) { return PieceType((m >> (2 * SQUARE_BITS + MOVE_TYPE_BITS + PIECE_TYPE_BITS)) & (PIECE_TYPE_NB - 1)); } inline bool is_custom(PieceType pt) { return pt >= CUSTOM_PIECES && pt <= CUSTOM_PIECES_END; } inline bool is_ok(Move m) { return from_sq(m) != to_sq(m) || type_of(m) == PROMOTION || type_of(m) == SPECIAL; // Catch MOVE_NULL and MOVE_NONE } inline int dist(Direction d) { return std::abs(d % NORTH) < NORTH / 2 ? std::max(std::abs(d / NORTH), int(std::abs(d % NORTH))) : std::max(std::abs(d / NORTH) + 1, int(NORTH - std::abs(d % NORTH))); } /// Based on a congruential pseudo random number generator constexpr Key make_key(uint64_t seed) { return seed * 6364136223846793005ULL + 1442695040888963407ULL; } } // namespace Stockfish #endif // #ifndef TYPES_H_INCLUDED #include "tune.h" // Global visibility to tuning setup Fairy-Stockfish-fairy_sf_14_0_1_xq/src/uci.cpp000066400000000000000000000471771414571233100214320ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #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 "xboard.h" #include "syzygy/tbprobe.h" using namespace std; namespace Stockfish { extern vector setup_bench(const Position&, istream&); namespace { // 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; // Parse as SFEN if specified bool sfen = token == "sfen"; if (token == "startpos") { fen = variants.find(Options["UCI_Variant"])->second->startFen; is >> token; // Consume "moves" token if any } else if (token == "fen" || token == "sfen") while (is >> token && token != "moves") fen += token + " "; else return; states = StateListPtr(new std::deque(1)); // Drop old and create a new one pos.set(variants.find(Options["UCI_Variant"])->second, fen, Options["UCI_Chess960"], &states->back(), Threads.main(), sfen); // 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(1)); Position p; p.set(pos.variant(), pos.fen(), Options["UCI_Chess960"], &states->back(), Threads.main()); Eval::NNUE::verify(); sync_cout << "\n" << Eval::trace(p) << sync_endl; } // setoption() is called when engine receives the "setoption" UCI command. The // function updates the UCI option ("name") to the given value ("value"). void setoption(istringstream& is) { string token, name, value; is >> token; // Consume "name" token if (Options["Protocol"] == "ucci") name = token; else // 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; // UCI dialects do not allow spaces else if ( (Options["Protocol"] == "ucci" || Options["Protocol"] == "usi") && (std::replace(name.begin(), name.end(), '_', ' '), 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, const std::vector& banmoves = {}) { Search::LimitsType limits; string token; bool ponderMode = false; limits.startTime = now(); // As early as possible! limits.banmoves = banmoves; bool isUsi = Options["Protocol"] == "usi"; 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[isUsi ? BLACK : WHITE]; else if (token == "btime") is >> limits.time[isUsi ? WHITE : BLACK]; else if (token == "winc") is >> limits.inc[isUsi ? BLACK : WHITE]; else if (token == "binc") is >> limits.inc[isUsi ? WHITE : 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; // UCCI commands else if (token == "time") is >> limits.time[pos.side_to_move()]; else if (token == "opptime") is >> limits.time[~pos.side_to_move()]; else if (token == "increment") is >> limits.inc[pos.side_to_move()]; else if (token == "oppinc") is >> limits.inc[~pos.side_to_move()]; // USI commands else if (token == "byoyomi") { int byoyomi = 0; is >> byoyomi; limits.inc[WHITE] = limits.inc[BLACK] = byoyomi; limits.time[WHITE] += byoyomi; limits.time[BLACK] += byoyomi; } 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 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 << " (" << pos.fen() << ")" << 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[] = {-3.68389304, 30.07065921, -60.52878723, 149.53378557}; double bs[] = {-2.0181857, 15.85685038, -29.83452023, 47.59078827}; 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, -2000.0, 2000.0); // Return win rate in per mille (rounded to nearest) return int(0.5 + 1000 / (1 + std::exp((a - x) / b))); } // load() is called when engine receives the "load" command. // The function reads variant configuration files. void load(istringstream& is) { string token; std::getline(is >> std::ws, token); std::size_t end = token.find_last_not_of(' '); if (end != std::string::npos) Options["VariantPath"] = token.erase(end + 1); } // check() is called when engine receives the "check" command. // The function reads a variant configuration file and validates it. void check(istringstream& is) { string token; std::getline(is >> std::ws, token); std::size_t end = token.find_last_not_of(' '); if (end != std::string::npos) variants.parse(token.erase(end + 1)); } } // 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(1)); assert(variants.find(Options["UCI_Variant"])->second != nullptr); pos.set(variants.find(Options["UCI_Variant"])->second, variants.find(Options["UCI_Variant"])->second->startFen, false, &states->back(), Threads.main()); for (int i = 1; i < argc; ++i) cmd += std::string(argv[i]) + " "; // XBoard state machine XBoard::stateMachine = new XBoard::StateMachine(pos, states); // UCCI banmoves state std::vector banmoves = {}; if (argc > 1 && (std::strcmp(argv[1], "noautoload") == 0)) { cmd = ""; argc = 1; } else if (argc == 1 || !(std::strcmp(argv[1], "load") == 0)) { // Check environment for variants.ini file char *envVariantPath = std::getenv("FAIRY_STOCKFISH_VARIANT_PATH"); if (envVariantPath != NULL) Options["VariantPath"] = std::string(envVariantPath); } 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" || token == "usi" || token == "ucci" || token == "xboard") { Options["Protocol"].set_default(token); string defaultVariant = string( #ifdef LARGEBOARDS token == "usi" ? "shogi" : token == "ucci" ? "xiangqi" #else token == "usi" ? "minishogi" : token == "ucci" ? "minixiangqi" #endif : "chess"); Options["UCI_Variant"].set_default(defaultVariant); std::istringstream ss("startpos"); position(pos, ss, states); if (token == "uci" || token == "usi" || token == "ucci") sync_cout << "id name " << engine_info(true) << "\n" << Options << "\n" << token << "ok" << sync_endl; } else if (Options["Protocol"] == "xboard") XBoard::stateMachine->process_command(token, is); else if (token == "setoption") setoption(is); // UCCI-specific banmoves command else if (token == "banmoves") while (is >> token) banmoves.push_back(UCI::to_move(pos, token)); else if (token == "go") go(pos, is, states, banmoves); else if (token == "position") position(pos, is, states), banmoves.clear(); else if (token == "ucinewgame" || token == "usinewgame" || token == "uccinewgame") 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 if (token == "export_net") { std::optional filename; std::string f; if (is >> skipws >> f) filename = f; Eval::NNUE::save_eval(filename); } else if (token == "load") { load(is); argc = 1; } // continue reading stdin else if (token == "check") check(is); // UCI-Cyclone omits the "position" keyword else if (token == "fen" || token == "startpos") { #ifdef LARGEBOARDS if (Options["Protocol"] == "uci" && Options["UCI_Variant"] == "chess") { Options["Protocol"].set_default("ucicyclone"); Options["UCI_Variant"].set_default("xiangqi"); } #endif is.seekg(0); position(pos, is, states); } else if (!token.empty() && token[0] != '#') 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 The score from the engine's point of view in centipawns. /// mate 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 (Options["Protocol"] == "xboard") { if (abs(v) < VALUE_MATE_IN_MAX_PLY) ss << v * 100 / PawnValueEg; else ss << (v > 0 ? XBOARD_VALUE_MATE + VALUE_MATE - v + 1 : -XBOARD_VALUE_MATE - VALUE_MATE - v - 1) / 2; } else if (abs(v) < VALUE_MATE_IN_MAX_PLY) ss << "cp " << v * 100 / PawnValueEg; else if (Options["Protocol"] == "usi") // In USI, mate distance is given in ply ss << "mate " << (v > 0 ? VALUE_MATE - v : -VALUE_MATE - v); else ss << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v - 1) / 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(const Position& pos, Square s) { #ifdef LARGEBOARDS if (Options["Protocol"] == "usi") return rank_of(s) < RANK_10 ? std::string{ char('1' + pos.max_file() - file_of(s)), char('a' + pos.max_rank() - rank_of(s)) } : std::string{ char('0' + (pos.max_file() - file_of(s) + 1) / 10), char('0' + (pos.max_file() - file_of(s) + 1) % 10), char('a' + pos.max_rank() - rank_of(s)) }; else if (pos.max_rank() == RANK_10 && Options["Protocol"] != "uci") return std::string{ char('a' + file_of(s)), char('0' + rank_of(s)) }; else return rank_of(s) < RANK_10 ? std::string{ char('a' + file_of(s)), char('1' + (rank_of(s) % 10)) } : std::string{ char('a' + file_of(s)), char('0' + ((rank_of(s) + 1) / 10)), char('0' + ((rank_of(s) + 1) % 10)) }; #else return Options["Protocol"] == "usi" ? std::string{ char('1' + pos.max_file() - file_of(s)), char('a' + pos.max_rank() - rank_of(s)) } : std::string{ char('a' + file_of(s)), char('1' + rank_of(s)) }; #endif } /// UCI::dropped_piece() generates a piece label string from a Move. string UCI::dropped_piece(const Position& pos, Move m) { assert(type_of(m) == DROP); if (dropped_piece_type(m) == pos.promoted_piece_type(in_hand_piece_type(m))) // Dropping as promoted piece return std::string{'+', pos.piece_to_char()[in_hand_piece_type(m)]}; else return std::string{pos.piece_to_char()[dropped_piece_type(m)]}; } /// 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(const Position& pos, Move m) { Square from = from_sq(m); Square to = to_sq(m); if (m == MOVE_NONE) return Options["Protocol"] == "usi" ? "resign" : "(none)"; if (m == MOVE_NULL) return "0000"; if (is_pass(m) && Options["Protocol"] == "xboard") return "@@@@"; if (is_gating(m) && gating_square(m) == to) from = to_sq(m), to = from_sq(m); else if (type_of(m) == CASTLING && !pos.is_chess960()) { to = make_square(to > from ? pos.castling_kingside_file() : pos.castling_queenside_file(), rank_of(from)); // If the castling move is ambiguous with a normal king move, switch to 960 notation if (pos.pseudo_legal(make_move(from, to))) to = to_sq(m); } string move = (type_of(m) == DROP ? UCI::dropped_piece(pos, m) + (Options["Protocol"] == "usi" ? '*' : '@') : UCI::square(pos, from)) + UCI::square(pos, to); if (type_of(m) == PROMOTION) move += pos.piece_to_char()[make_piece(BLACK, promotion_type(m))]; else if (type_of(m) == PIECE_PROMOTION) move += '+'; else if (type_of(m) == PIECE_DEMOTION) move += '-'; else if (is_gating(m)) { move += pos.piece_to_char()[make_piece(BLACK, gating_type(m))]; if (gating_square(m) != from) move += UCI::square(pos, gating_square(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) { if (str[4] == '=') // shogi moves refraining from promotion might use equals sign str.pop_back(); else // Junior could send promotion piece in uppercase str[4] = char(tolower(str[4])); } for (const auto& m : MoveList(pos)) if (str == UCI::move(pos, m) || (is_pass(m) && str == UCI::square(pos, from_sq(m)) + UCI::square(pos, to_sq(m)))) return m; return MOVE_NONE; } } // namespace Stockfish Fairy-Stockfish-fairy_sf_14_0_1_xq/src/uci.h000066400000000000000000000055111414571233100210610ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef UCI_H_INCLUDED #define UCI_H_INCLUDED #include #include #include #include "types.h" #include "variant.h" namespace Stockfish { class Position; namespace UCI { #ifndef _WIN32 constexpr char SepChar = ':'; #else constexpr char SepChar = ';'; #endif void init_variant(const Variant* v); 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 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(const char* v, const std::vector& variants, OnChange = nullptr); Option(double v, int minv, int maxv, OnChange = nullptr); Option& operator=(const std::string&); void operator<<(const Option&); operator double() const; operator std::string() const; bool operator==(const char*) const; bool operator!=(const char*) const; void set_combo(std::vector newComboValues); void set_default(std::string newDefault); const std::string get_type() const; private: friend std::ostream& operator<<(std::ostream&, const OptionsMap&); std::string defaultValue, currentValue, type; int min, max; std::vector comboValues; size_t idx; OnChange on_change; }; void init(OptionsMap&); void loop(int argc, char* argv[]); std::string value(Value v); std::string square(const Position& pos, Square s); std::string dropped_piece(const Position& pos, Move m); std::string move(const Position& pos, Move m); 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; } // namespace Stockfish #endif // #ifndef UCI_H_INCLUDED Fairy-Stockfish-fairy_sf_14_0_1_xq/src/ucioption.cpp000066400000000000000000000330521414571233100226460ustar00rootroot00000000000000/* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include "evaluate.h" #include "misc.h" #include "piece.h" #include "search.h" #include "thread.h" #include "tt.h" #include "uci.h" #include "variant.h" #include "syzygy/tbprobe.h" using std::string; namespace Stockfish { UCI::OptionsMap Options; // Global object namespace PSQT { void init(const Variant* v); } namespace UCI { // standard variants of XBoard/WinBoard std::set standard_variants = { "normal", "nocastle", "fischerandom", "knightmate", "3check", "makruk", "shatranj", "asean", "seirawan", "crazyhouse", "bughouse", "suicide", "giveaway", "losers", "atomic", "capablanca", "gothic", "janus", "caparandom", "grand", "shogi", "xiangqi" }; void init_variant(const Variant* v) { pieceMap.init(v); Bitboards::init_pieces(); } /// '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::NNUE::init(); } void on_eval_file(const Option& ) { Eval::NNUE::init(); } void on_variant_path(const Option& o) { std::stringstream ss((std::string)o); std::string path; while (std::getline(ss, path, SepChar)) variants.parse(path); Options["UCI_Variant"].set_combo(variants.get_keys()); } void on_variant_set(const Option &o) { // Re-initialize NNUE Eval::NNUE::init(); const Variant* v = variants.find(o)->second; init_variant(v); PSQT::init(v); } void on_variant_change(const Option &o) { // Variant initialization on_variant_set(o); const Variant* v = variants.find(o)->second; // Do not send setup command for known variants if (standard_variants.find(o) != standard_variants.end()) return; int pocketsize = v->pieceDrops ? (v->pocketSize ? v->pocketSize : v->pieceTypes.size()) : 0; if (Options["Protocol"] == "xboard") { // Overwrite setup command for Janggi variants auto itJanggi = variants.find("janggi"); if ( itJanggi != variants.end() && v->variantTemplate == itJanggi->second->variantTemplate && v->startFen == itJanggi->second->startFen && v->pieceToCharTable == itJanggi->second->pieceToCharTable) { sync_cout << "setup (PH.R.AE..K.C.ph.r.ae..k.c.) 9x10+0_janggi " << "rhea1aehr/4k4/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/4K4/RHEA1AEHR w - - 0 1" << sync_endl; return; } // Send setup command sync_cout << "setup (" << v->pieceToCharTable << ") " << v->maxFile + 1 << "x" << v->maxRank + 1 << "+" << pocketsize << "_" << v->variantTemplate << " " << v->startFen << sync_endl; // Send piece command with Betza notation // https://www.gnu.org/software/xboard/Betza.html for (PieceType pt : v->pieceTypes) { string suffix = pt == PAWN && v->doubleStep ? "ifmnD" : pt == KING && v->cambodianMoves ? "ismN" : pt == FERS && v->cambodianMoves ? "ifD" : ""; // Janggi palace moves if (v->diagonalLines) { PieceType pt2 = pt == KING ? v->kingType : pt; if (pt2 == WAZIR) suffix += "F"; else if (pt2 == SOLDIER) suffix += "fF"; else if (pt2 == ROOK) suffix += "B"; else if (pt2 == JANGGI_CANNON) suffix += "pB"; } // Castling if (pt == KING && v->castling) suffix += "O" + std::to_string((v->castlingKingsideFile - v->castlingQueensideFile) / 2); // Drop region if (v->pieceDrops) { if (pt == PAWN && !v->firstRankPawnDrops) suffix += "j"; else if (pt == v->dropNoDoubled) suffix += std::string(v->dropNoDoubledCount, 'f'); else if (pt == BISHOP && v->dropOppositeColoredBishop) suffix += "s"; suffix += "@" + std::to_string(pt == PAWN && !v->promotionZonePawnDrops ? v->promotionRank : v->maxRank + 1); } sync_cout << "piece " << v->pieceToChar[pt] << "& " << pieceMap.find(pt == KING ? v->kingType : pt)->second->betza << suffix << sync_endl; PieceType promType = v->promotedPieceType[pt]; if (promType) sync_cout << "piece +" << v->pieceToChar[pt] << "& " << pieceMap.find(promType)->second->betza << sync_endl; } } else sync_cout << "info string variant " << (std::string)o << " files " << v->maxFile + 1 << " ranks " << v->maxRank + 1 << " pocket " << pocketsize << " template " << v->variantTemplate << " startpos " << v->startFen << sync_endl; } /// 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["Protocol"] << Option("uci", {"uci", "usi", "ucci", "ucicyclone", "xboard"}); o["Debug Log File"] << Option("", on_logger); 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, -20, 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_Variant"] << Option("chess", variants.get_keys(), on_variant_change); o["UCI_AnalyseMode"] << Option(false); o["UCI_LimitStrength"] << Option(false); o["UCI_Elo"] << Option(1350, 500, 2850); o["UCI_ShowWDL"] << Option(false); o["SyzygyPath"] << Option("", 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); #ifndef NNUE_EMBEDDING_OFF o["EvalFile"] << Option((std::string(EvalFileDefaultName) + UCI::SepChar + EvalFile2DefaultName).c_str(), on_eval_file); #else o["EvalFile"] << Option("", on_eval_file); #endif o["TsumeMode"] << Option(false); o["VariantPath"] << Option("", on_variant_path); } /// 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) { if (Options["Protocol"] == "xboard") { for (size_t idx = 0; idx < om.size(); ++idx) for (const auto& it : om) if (it.second.idx == idx && it.first != "Protocol" && it.first != "UCI_Variant" && it.first != "Threads" && it.first != "Hash") { const Option& o = it.second; os << "\nfeature option=\"" << it.first << " -" << o.type; if (o.type == "string" || o.type == "combo") os << " " << o.defaultValue; else if (o.type == "check") os << " " << int(o.defaultValue == "true"); if (o.type == "combo") for (string value : o.comboValues) if (value != o.defaultValue) os << " /// " << value; if (o.type == "spin") os << " " << int(stof(o.defaultValue)) << " " << o.min << " " << o.max; os << "\""; break; } } else for (size_t idx = 0; idx < om.size(); ++idx) for (const auto& it : om) if (it.second.idx == idx) { const Option& o = it.second; // UCI dialects do not allow spaces if (Options["Protocol"] == "ucci" || Options["Protocol"] == "usi") { string name = it.first; std::replace(name.begin(), name.end(), ' ', '_'); // UCCI skips "name" os << "\noption " << (Options["Protocol"] == "ucci" ? "" : "name ") << name << " type " << o.type; } else 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 == "combo") for (string value : o.comboValues) os << " var " << value; 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(const char* v, const std::vector& values, OnChange f) : type("combo"), min(0), max(0), comboValues(values), 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::operator double() const { assert(type == "check" || type == "spin"); return (type == "spin" ? stof(currentValue) : currentValue == "true"); } Option::operator std::string() const { assert(type == "string" || type == "combo"); return currentValue; } bool Option::operator==(const char* s) const { assert(type == "combo"); return !CaseInsensitiveLess()(currentValue, s) && !CaseInsensitiveLess()(s, currentValue); } bool Option::operator!=(const char* s) const { assert(type == "combo"); return !(*this == s); } /// 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 == "combo" && (std::find(comboValues.begin(), comboValues.end(), v) == comboValues.end())) || (type == "spin" && (stof(v) < min || stof(v) > max))) return *this; if (type == "combo") { OptionsMap comboMap; // To have case insensitive compare for (string token : comboValues) comboMap[token] << Option(); if (!comboMap.count(v) || v == "var") return *this; } if (type != "button") currentValue = v; if (on_change) on_change(*this); return *this; } void Option::set_combo(std::vector newComboValues) { comboValues = newComboValues; } void Option::set_default(std::string newDefault) { defaultValue = currentValue = newDefault; // When changing the variant default, suppress variant definition output, // but still do the essential re-initialization of the variant if (on_change) (on_change == on_variant_change ? on_variant_set : on_change)(*this); } const std::string Option::get_type() const { return type; } } // namespace UCI } // namespace Stockfish Fairy-Stockfish-fairy_sf_14_0_1_xq/src/variant.cpp000066400000000000000000001707061414571233100223110ustar00rootroot00000000000000/* Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish Copyright (C) 2018-2021 Fabian Fichter Fairy-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. Fairy-Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include "parser.h" #include "piece.h" #include "variant.h" using std::string; namespace Stockfish { VariantMap variants; // Global object namespace { // Base variant Variant* variant_base() { Variant* v = new Variant(); return v; } // Base for all fairy variants Variant* chess_variant_base() { Variant* v = variant_base()->init(); v->pieceToCharTable = "PNBRQ................Kpnbrq................k"; return v; } // Standard chess // https://en.wikipedia.org/wiki/Chess Variant* chess_variant() { Variant* v = chess_variant_base()->init(); v->nnueAlias = "nn-"; return v; } // Chess960 aka Fischer random chess // https://en.wikipedia.org/wiki/Fischer_random_chess Variant* chess960_variant() { Variant* v = chess_variant()->init(); v->chess960 = true; v->nnueAlias = "nn-"; return v; } // Standard chess without castling Variant* nocastle_variant() { Variant* v = chess_variant()->init(); v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - - 0 1"; v->castling = false; return v; } // Armageddon Chess // https://en.wikipedia.org/wiki/Fast_chess#Armageddon Variant* armageddon_variant() { Variant* v = chess_variant()->init(); v->materialCounting = BLACK_DRAW_ODDS; return v; } // Pseudo-variant only used for endgame initialization Variant* fairy_variant() { Variant* v = chess_variant_base()->init(); v->add_piece(SILVER, 's'); v->add_piece(FERS, 'f'); return v; } // Makruk (Thai Chess) // https://en.wikipedia.org/wiki/Makruk Variant* makruk_variant() { Variant* v = chess_variant_base()->init(); v->variantTemplate = "makruk"; v->pieceToCharTable = "PN.R.M....SKpn.r.m....sk"; v->remove_piece(BISHOP); v->remove_piece(QUEEN); v->add_piece(KHON, 's'); v->add_piece(MET, 'm'); v->startFen = "rnsmksnr/8/pppppppp/8/8/PPPPPPPP/8/RNSKMSNR w - - 0 1"; v->promotionRank = RANK_6; v->promotionPieceTypes = {MET}; v->doubleStep = false; v->castling = false; v->nMoveRule = 0; v->countingRule = MAKRUK_COUNTING; return v; } // Makpong (Defensive Chess) // A Makruk variant used for tie-breaks // https://www.mayhematics.com/v/vol8/vc64b.pdf, p. 177 Variant* makpong_variant() { Variant* v = makruk_variant()->init(); v->makpongRule = true; return v; } // Ouk Chatrang, Cambodian chess // https://en.wikipedia.org/wiki/Makruk#Cambodian_chess Variant* cambodian_variant() { Variant* v = makruk_variant()->init(); v->startFen = "rnsmksnr/8/pppppppp/8/8/PPPPPPPP/8/RNSKMSNR w DEde - 0 1"; v->gating = true; v->cambodianMoves = true; v->nnueAlias = "makruk"; return v; } // Kar Ouk // A variant of Cambodian chess where the first check wins // https://en.wikipedia.org/wiki/Makruk#Ka_Ouk Variant* karouk_variant() { Variant* v = cambodian_variant()->init(); v->checkCounting = true; return v; } // ASEAN chess // A simplified version of south-east asian variants // https://aseanchess.org/laws-of-asean-chess/ Variant* asean_variant() { Variant* v = chess_variant_base()->init(); v->remove_piece(BISHOP); v->remove_piece(QUEEN); v->add_piece(KHON, 'b'); v->add_piece(MET, 'q'); v->startFen = "rnbqkbnr/8/pppppppp/8/8/PPPPPPPP/8/RNBQKBNR w - - 0 1"; v->promotionPieceTypes = {ROOK, KNIGHT, KHON, MET}; v->doubleStep = false; v->castling = false; v->countingRule = ASEAN_COUNTING; return v; } // Ai-wok // A makruk variant where the met is replaced by a super-piece moving as rook, knight, or met Variant* aiwok_variant() { Variant* v = makruk_variant()->init(); v->pieceToCharTable = "PN.R...A..SKpn.r...a..sk"; v->remove_piece(MET); v->add_piece(AIWOK, 'a'); v->startFen = "rnsaksnr/8/pppppppp/8/8/PPPPPPPP/8/RNSKASNR w - - 0 1"; v->promotionPieceTypes = {AIWOK}; return v; } // Shatranj // The medieval form of chess, originating from chaturanga // https://en.wikipedia.org/wiki/Shatranj Variant* shatranj_variant() { Variant* v = chess_variant_base()->init(); v->variantTemplate = "shatranj"; v->pieceToCharTable = "PN.R.QB....Kpn.r.qb....k"; v->remove_piece(BISHOP); v->remove_piece(QUEEN); v->add_piece(ALFIL, 'b'); v->add_piece(FERS, 'q'); v->startFen = "rnbkqbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBKQBNR w - - 0 1"; v->promotionPieceTypes = {FERS}; v->doubleStep = false; v->castling = false; v->extinctionValue = -VALUE_MATE; v->extinctionClaim = true; v->extinctionPieceTypes = {ALL_PIECES}; v->extinctionPieceCount = 1; v->extinctionOpponentPieceCount = 2; v->stalemateValue = -VALUE_MATE; v->nMoveRule = 70; return v; } // Chaturanga // The actual rules of the game are not known. This reflects the rules as used on chess.com. // https://en.wikipedia.org/wiki/Chaturanga Variant* chaturanga_variant() { Variant* v = shatranj_variant()->init(); v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - - 0 1"; v->extinctionValue = VALUE_NONE; v->nnueAlias = "shatranj"; return v; } // Amazon chess // The queen has the additional power of moving like a knight. // https://www.chessvariants.com/diffmove.dir/amazone.html Variant* amazon_variant() { Variant* v = chess_variant_base()->init(); v->pieceToCharTable = "PNBR..............AKpnbr..............ak"; v->remove_piece(QUEEN); v->add_piece(AMAZON, 'a'); v->startFen = "rnbakbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBAKBNR w KQkq - 0 1"; v->promotionPieceTypes = {AMAZON, ROOK, BISHOP, KNIGHT}; return v; } // Nightrider chess // Knights are replaced by nightriders. // https://en.wikipedia.org/wiki/Nightrider_(chess) Variant* nightrider_variant() { Variant* v = chess_variant_base()->init(); v->remove_piece(KNIGHT); v->add_piece(CUSTOM_PIECES, 'n', "NN"); v->promotionPieceTypes = {QUEEN, ROOK, BISHOP, CUSTOM_PIECES}; return v; } // Grasshopper chess // https://en.wikipedia.org/wiki/Grasshopper_chess Variant* grasshopper_variant() { Variant* v = chess_variant_base()->init(); v->add_piece(CUSTOM_PIECES, 'g', "gQ"); v->promotionPieceTypes.insert(CUSTOM_PIECES); v->startFen = "rnbqkbnr/gggggggg/pppppppp/8/8/PPPPPPPP/GGGGGGGG/RNBQKBNR w KQkq - 0 1"; v->doubleStep = false; return v; } // Hoppel-Poppel // A variant from Germany where knights capture like bishops and vice versa // https://www.chessvariants.com/diffmove.dir/hoppel-poppel.html Variant* hoppelpoppel_variant() { Variant* v = chess_variant_base()->init(); v->remove_piece(KNIGHT); v->remove_piece(BISHOP); v->add_piece(KNIBIS, 'n'); v->add_piece(BISKNI, 'b'); v->promotionPieceTypes = {QUEEN, ROOK, BISKNI, KNIBIS}; return v; } // New Zealand // Knights capture like rooks and vice versa. Variant* newzealand_variant() { Variant* v = chess_variant_base()->init(); v->remove_piece(ROOK); v->remove_piece(KNIGHT); v->add_piece(ROOKNI, 'r'); v->add_piece(KNIROO, 'n'); v->castlingRookPiece = ROOKNI; v->promotionPieceTypes = {QUEEN, ROOKNI, BISHOP, KNIROO}; return v; } // King of the Hill // https://lichess.org/variant/kingOfTheHill Variant* kingofthehill_variant() { Variant* v = chess_variant_base()->init(); v->flagPiece = KING; v->whiteFlag = (Rank4BB | Rank5BB) & (FileDBB | FileEBB); v->blackFlag = (Rank4BB | Rank5BB) & (FileDBB | FileEBB); v->flagMove = false; return v; } // Racing Kings // https://lichess.org/variant/racingKings Variant* racingkings_variant() { Variant* v = chess_variant_base()->init(); v->startFen = "8/8/8/8/8/8/krbnNBRK/qrbnNBRQ w - - 0 1"; v->flagPiece = KING; v->whiteFlag = Rank8BB; v->blackFlag = Rank8BB; v->flagMove = true; v->castling = false; v->checking = false; return v; } // Knightmate // https://www.chessvariants.com/diffobjective.dir/knightmate.html Variant* knightmate_variant() { Variant* v = chess_variant_base()->init(); v->add_piece(COMMONER, 'm'); v->remove_piece(KNIGHT); v->startFen = "rmbqkbmr/pppppppp/8/8/8/8/PPPPPPPP/RMBQKBMR w KQkq - 0 1"; v->kingType = KNIGHT; v->castlingKingPiece = KING; v->promotionPieceTypes = {COMMONER, QUEEN, ROOK, BISHOP}; return v; } // Losers chess // https://www.chessclub.com/help/Wild17 Variant* losers_variant() { Variant* v = chess_variant_base()->init(); v->checkmateValue = VALUE_MATE; v->stalemateValue = VALUE_MATE; v->extinctionValue = VALUE_MATE; v->extinctionPieceTypes = {ALL_PIECES}; v->extinctionPieceCount = 1; v->mustCapture = true; return v; } // Giveaway chess // Antichess with castling. // https://www.chessvariants.com/diffobjective.dir/giveaway.old.html Variant* giveaway_variant() { Variant* v = chess_variant_base()->init(); v->variantTemplate = "giveaway"; v->remove_piece(KING); v->add_piece(COMMONER, 'k'); v->castlingKingPiece = COMMONER; v->promotionPieceTypes = {COMMONER, QUEEN, ROOK, BISHOP, KNIGHT}; v->stalemateValue = VALUE_MATE; v->extinctionValue = VALUE_MATE; v->extinctionPieceTypes = {ALL_PIECES}; v->mustCapture = true; v->nnueAlias = "antichess"; return v; } // Antichess // https://lichess.org/variant/antichess Variant* antichess_variant() { Variant* v = giveaway_variant()->init(); v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - - 0 1"; v->castling = false; return v; } // Suicide chess // Antichess with modified stalemate adjudication. // https://www.freechess.org/Help/HelpFiles/suicide_chess.html Variant* suicide_variant() { Variant* v = antichess_variant()->init(); v->stalematePieceCount = true; v->nnueAlias = "antichess"; return v; } // Codrus // Lose the king to win. Captures are mandatory. // http://www.binnewirtz.com/Schlagschach1.htm Variant* codrus_variant() { Variant* v = giveaway_variant()->init(); v->promotionPieceTypes = {QUEEN, ROOK, BISHOP, KNIGHT}; v->extinctionPieceTypes = {COMMONER}; return v; } // Extinction chess // https://en.wikipedia.org/wiki/Extinction_chess Variant* extinction_variant() { Variant* v = chess_variant_base()->init(); v->remove_piece(KING); v->add_piece(COMMONER, 'k'); v->castlingKingPiece = COMMONER; v->promotionPieceTypes = {COMMONER, QUEEN, ROOK, BISHOP, KNIGHT}; v->extinctionValue = -VALUE_MATE; v->extinctionPieceTypes = {COMMONER, QUEEN, ROOK, BISHOP, KNIGHT, PAWN}; return v; } // Kinglet // https://en.wikipedia.org/wiki/V._R._Parton#Kinglet_chess Variant* kinglet_variant() { Variant* v = extinction_variant()->init(); v->promotionPieceTypes = {COMMONER}; v->extinctionPieceTypes = {PAWN}; return v; } // Three Kings Chess // https://github.com/cutechess/cutechess/blob/master/projects/lib/src/board/threekingsboard.h Variant* threekings_variant() { Variant* v = chess_variant_base()->init(); v->remove_piece(KING); v->add_piece(COMMONER, 'k'); v->castlingKingPiece = COMMONER; v->startFen = "knbqkbnk/pppppppp/8/8/8/8/PPPPPPPP/KNBQKBNK w - - 0 1"; v->extinctionValue = -VALUE_MATE; v->extinctionPieceTypes = {COMMONER}; v->extinctionPieceCount = 2; return v; } // Horde chess // https://en.wikipedia.org/wiki/Dunsany%27s_chess#Horde_chess Variant* horde_variant() { Variant* v = chess_variant_base()->init(); v->startFen = "rnbqkbnr/pppppppp/8/1PP2PP1/PPPPPPPP/PPPPPPPP/PPPPPPPP/PPPPPPPP w kq - 0 1"; v->doubleStepRankMin = RANK_1; v->enPassantRegion = Rank3BB | Rank6BB; // exclude en passant on second rank v->extinctionValue = -VALUE_MATE; v->extinctionPieceTypes = {ALL_PIECES}; return v; } // Atomic chess without checks (ICC rules) // https://www.chessclub.com/help/atomic Variant* nocheckatomic_variant() { Variant* v = chess_variant_base()->init(); v->variantTemplate = "atomic"; v->remove_piece(KING); v->add_piece(COMMONER, 'k'); v->castlingKingPiece = COMMONER; v->extinctionValue = -VALUE_MATE; v->extinctionPieceTypes = {COMMONER}; v->blastOnCapture = true; v->nnueAlias = "atomic"; return v; } // Atomic chess // https://en.wikipedia.org/wiki/Atomic_chess Variant* atomic_variant() { Variant* v = nocheckatomic_variant()->init(); v->extinctionPseudoRoyal = true; return v; } // Three-check chess // Check the king three times to win // https://lichess.org/variant/threeCheck Variant* threecheck_variant() { Variant* v = chess_variant_base()->init(); v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 3+3 0 1"; v->checkCounting = true; return v; } // Five-check chess // Check the king five times to win Variant* fivecheck_variant() { Variant* v = threecheck_variant()->init(); v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 5+5 0 1"; v->nnueAlias = "3check"; return v; } // Crazyhouse // Chess with piece drops // https://en.wikipedia.org/wiki/Crazyhouse Variant* crazyhouse_variant() { Variant* v = chess_variant_base()->init(); v->variantTemplate = "crazyhouse"; v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 0 1"; v->pieceDrops = true; v->capturesToHand = true; return v; } // Loop chess // Variant of crazyhouse where promoted pawns are not demoted when captured // https://en.wikipedia.org/wiki/Crazyhouse#Variations Variant* loop_variant() { Variant* v = crazyhouse_variant()->init(); v->dropLoop = true; v->nnueAlias = "crazyhouse"; return v; } // Chessgi // Variant of loop chess where pawns can be dropped to the first rank // https://en.wikipedia.org/wiki/Crazyhouse#Variations Variant* chessgi_variant() { Variant* v = loop_variant()->init(); v->firstRankPawnDrops = true; v->nnueAlias = "crazyhouse"; return v; } // Bughouse // A four player variant where captured pieces are introduced on the other board // https://en.wikipedia.org/wiki/Bughouse_chess Variant* bughouse_variant() { Variant* v = crazyhouse_variant()->init(); v->variantTemplate = "bughouse"; v->twoBoards = true; v->capturesToHand = false; v->stalemateValue = -VALUE_MATE; return v; } // Koedem (Bughouse variant) // http://schachclub-oetigheim.de/wp-content/uploads/2016/04/Koedem-rules.pdf Variant* koedem_variant() { Variant* v = bughouse_variant()->init(); v->remove_piece(KING); v->add_piece(COMMONER, 'k'); v->castlingKingPiece = COMMONER; v->mustDrop = true; v->mustDropType = COMMONER; v->extinctionValue = -VALUE_MATE; v->extinctionPieceTypes = {COMMONER}; v->extinctionOpponentPieceCount = 2; // own all kings/commoners return v; } // Pocket Knight chess // Each player has an additional knight in hand which can be dropped at any move // https://www.chessvariants.com/other.dir/pocket.html Variant* pocketknight_variant() { Variant* v = chess_variant_base()->init(); v->variantTemplate = "bughouse"; v->pocketSize = 2; v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[Nn] w KQkq - 0 1"; v->pieceDrops = true; v->capturesToHand = false; return v; } // Placement/Pre-chess // A shuffle variant where the players determine the placing of the back rank pieces // https://www.chessvariants.com/link/placement-chess Variant* placement_variant() { Variant* v = chess_variant_base()->init(); v->variantTemplate = "bughouse"; v->startFen = "8/pppppppp/8/8/8/8/PPPPPPPP/8[KQRRBBNNkqrrbbnn] w - - 0 1"; v->mustDrop = true; v->pieceDrops = true; v->capturesToHand = false; v->whiteDropRegion = Rank1BB; v->blackDropRegion = Rank8BB; v->dropOppositeColoredBishop = true; v->castlingDroppedPiece = true; v->nnueAlias = "nn-"; return v; } // Sittuyin (Burmese chess) // Regional chess variant from Myanmar, similar to Makruk but with a setup phase. // https://en.wikipedia.org/wiki/Sittuyin Variant* sittuyin_variant() { Variant* v = makruk_variant()->init(); v->variantTemplate = "bughouse"; v->pieceToCharTable = "PN.R.F....SKpn.r.f....sk"; v->startFen = "8/8/4pppp/pppp4/4PPPP/PPPP4/8/8[KFRRSSNNkfrrssnn] w - - 0 1"; v->remove_piece(MET); v->add_piece(MET, 'f'); v->mustDrop = true; v->pieceDrops = true; v->capturesToHand = false; v->whiteDropRegion = Rank1BB | Rank2BB | Rank3BB; v->blackDropRegion = Rank8BB | Rank7BB | Rank6BB; v->sittuyinRookDrop = true; v->promotionRank = RANK_1; // no regular promotions v->sittuyinPromotion = true; v->promotionLimit[FERS] = 1; v->immobilityIllegal = false; v->countingRule = ASEAN_COUNTING; v->nMoveRule = 50; return v; } // S-Chess (aka Seirawan-, or SHarper chess) // 8x8 variant introducing the knighted pieces from capablanca chess // via gating when a piece first moves from its initial square. Variant* seirawan_variant() { Variant* v = chess_variant_base()->init(); v->variantTemplate = "seirawan"; v->pieceToCharTable = "PNBRQ.E..........H...Kpnbrq.e..........h...k"; v->add_piece(ARCHBISHOP, 'h'); v->add_piece(CHANCELLOR, 'e'); v->startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[HEhe] w KQBCDFGkqbcdfg - 0 1"; v->gating = true; v->seirawanGating = true; v->promotionPieceTypes = {ARCHBISHOP, CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT}; return v; } // S-House // A hybrid variant of S-Chess and Crazyhouse. // Pieces in the pocket can either be gated or dropped. Variant* shouse_variant() { Variant* v = seirawan_variant()->init(); v->variantTemplate = "crazyhouse"; v->pieceDrops = true; v->capturesToHand = true; return v; } // Base used for most shogi variants Variant* minishogi_variant_base() { Variant* v = variant_base()->init(); v->variantTemplate = "shogi"; v->maxRank = RANK_5; v->maxFile = FILE_E; v->reset_pieces(); v->add_piece(SHOGI_PAWN, 'p'); v->add_piece(SILVER, 's'); v->add_piece(GOLD, 'g'); v->add_piece(BISHOP, 'b'); v->add_piece(DRAGON_HORSE, 'h'); v->add_piece(ROOK, 'r'); v->add_piece(DRAGON, 'd'); v->add_piece(KING, 'k'); v->startFen = "rbsgk/4p/5/P4/KGSBR[-] w 0 1"; v->pieceDrops = true; v->capturesToHand = true; v->promotionRank = RANK_5; v->promotionPieceTypes = {}; v->doubleStep = false; v->castling = false; v->promotedPieceType[SHOGI_PAWN] = GOLD; v->promotedPieceType[SILVER] = GOLD; v->promotedPieceType[BISHOP] = DRAGON_HORSE; v->promotedPieceType[ROOK] = DRAGON; v->dropNoDoubled = SHOGI_PAWN; v->immobilityIllegal = true; v->shogiPawnDropMateIllegal = true; v->stalemateValue = -VALUE_MATE; v->nFoldRule = 4; v->nMoveRule = 0; v->perpetualCheckIllegal = true; return v; } // Minishogi // 5x5 variant of shogi // https://en.wikipedia.org/wiki/Minishogi Variant* minishogi_variant() { Variant* v = minishogi_variant_base()->init(); v->pieceToCharTable = "P.BR.S...G.+.++.+Kp.br.s...g.+.++.+k"; v->pocketSize = 5; v->nFoldValue = -VALUE_MATE; v->nFoldValueAbsolute = true; v->nnueAlias = "minishogi"; return v; } // Kyoto shogi // 5x5 variant of shogi with pieces alternating between promotion and demotion // https://en.wikipedia.org/wiki/Kyoto_shogi Variant* kyotoshogi_variant() { Variant* v = minishogi_variant_base()->init(); v->add_piece(LANCE, 'l'); v->add_piece(SHOGI_KNIGHT, 'n'); v->startFen = "p+nks+l/5/5/5/+LSK+NP[-] w 0 1"; v->promotionRank = RANK_1; v->mandatoryPiecePromotion = true; v->pieceDemotion = true; v->dropPromoted = true; v->promotedPieceType[LANCE] = GOLD; v->promotedPieceType[SILVER] = BISHOP; v->promotedPieceType[SHOGI_KNIGHT] = GOLD; v->promotedPieceType[SHOGI_PAWN] = ROOK; v->promotedPieceType[GOLD] = NO_PIECE_TYPE; v->promotedPieceType[BISHOP] = NO_PIECE_TYPE; v->promotedPieceType[ROOK] = NO_PIECE_TYPE; v->immobilityIllegal = false; v->shogiPawnDropMateIllegal = false; v->dropNoDoubled = NO_PIECE_TYPE; return v; } // Micro shogi // 4x5 shogi variant where pieces promoted and demote when capturing // https://en.wikipedia.org/wiki/Micro_shogi Variant* microshogi_variant() { Variant* v = kyotoshogi_variant()->init(); v->maxFile = FILE_D; v->startFen = "kb+r+l/p3/4/3P/+L+RBK[-] w 0 1"; v->promotionRank = RANK_1; v->piecePromotionOnCapture = true; v->promotedPieceType[LANCE] = SILVER; v->promotedPieceType[BISHOP] = GOLD; v->promotedPieceType[ROOK] = GOLD; v->promotedPieceType[SHOGI_PAWN] = SHOGI_KNIGHT; v->promotedPieceType[SILVER] = NO_PIECE_TYPE; v->promotedPieceType[GOLD] = NO_PIECE_TYPE; v->promotedPieceType[SHOGI_KNIGHT] = NO_PIECE_TYPE; return v; } // Dobutsu // Educational shogi variant on a 3x4 board // https://en.wikipedia.org/wiki/D%C5%8Dbutsu_sh%C5%8Dgi Variant* dobutsu_variant() { Variant* v = minishogi_variant_base()->init(); v->pieceToCharTable = "C....E...G.+.....Lc....e...g.+.....l"; v->pocketSize = 3; v->maxRank = RANK_4; v->maxFile = FILE_C; v->reset_pieces(); v->add_piece(SHOGI_PAWN, 'c'); v->add_piece(GOLD, 'h'); v->add_piece(FERS, 'e'); v->add_piece(WAZIR, 'g'); v->add_piece(KING, 'l'); v->startFen = "gle/1c1/1C1/ELG[-] w 0 1"; v->promotionRank = RANK_4; v->immobilityIllegal = false; v->shogiPawnDropMateIllegal = false; v->flagPiece = KING; v->whiteFlag = Rank4BB; v->blackFlag = Rank1BB; v->dropNoDoubled = NO_PIECE_TYPE; return v; } // Goro goro shogi // https://en.wikipedia.org/wiki/D%C5%8Dbutsu_sh%C5%8Dgi#Variation Variant* gorogoroshogi_variant() { Variant* v = minishogi_variant_base()->init(); v->pieceToCharTable = "P....S...G.+....+Kp....s...g.+....+k"; v->pocketSize = 3; v->maxRank = RANK_6; v->maxFile = FILE_E; v->startFen = "sgkgs/5/1ppp1/1PPP1/5/SGKGS[-] w 0 1"; v->promotionRank = RANK_5; return v; } // Judkins shogi // https://en.wikipedia.org/wiki/Judkins_shogi Variant* judkinsshogi_variant() { Variant* v = minishogi_variant_base()->init(); v->pieceToCharTable = "PNBR.S...G.++++.+Kpnbr.s...g.++++.+k"; v->maxRank = RANK_6; v->maxFile = FILE_F; v->add_piece(SHOGI_KNIGHT, 'n'); v->startFen = "rbnsgk/5p/6/6/P5/KGSNBR[-] w 0 1"; v->promotionRank = RANK_5; v->promotedPieceType[SHOGI_KNIGHT] = GOLD; return v; } // Tori shogi // https://en.wikipedia.org/wiki/Tori_shogi Variant* torishogi_variant() { Variant* v = variant_base()->init(); v->variantTemplate = "shogi"; v->pieceToCharTable = "S.....FLR.C+.....+.PKs.....flr.c+.....+.pk"; v->maxRank = RANK_7; v->maxFile = FILE_G; v->reset_pieces(); v->add_piece(SHOGI_PAWN, 's'); v->add_piece(KING, 'k'); v->add_piece(CUSTOM_PIECES, 'f', "FsfW"); // falcon v->add_piece(CUSTOM_PIECES + 1, 'c', "FvW"); // crane v->add_piece(CUSTOM_PIECES + 2, 'l', "fRrbBlbF"); // left quail v->add_piece(CUSTOM_PIECES + 3, 'r', "fRlbBrbF"); // right quail v->add_piece(CUSTOM_PIECES + 4, 'p', "bFfD"); // pheasant v->add_piece(CUSTOM_PIECES + 5, 'g', "fAbD"); // goose v->add_piece(CUSTOM_PIECES + 6, 'e', "KbRfBbF2"); // eagle v->startFen = "rpckcpl/3f3/sssssss/2s1S2/SSSSSSS/3F3/LPCKCPR[-] w 0 1"; v->pieceDrops = true; v->capturesToHand = true; v->promotionRank = RANK_6; v->promotionPieceTypes = {}; v->doubleStep = false; v->castling = false; v->promotedPieceType[SHOGI_PAWN] = CUSTOM_PIECES + 5; // swallow promotes to goose v->promotedPieceType[CUSTOM_PIECES] = CUSTOM_PIECES + 6; // falcon promotes to eagle v->mandatoryPiecePromotion = true; v->dropNoDoubled = SHOGI_PAWN; v->dropNoDoubledCount = 2; v->immobilityIllegal = true; v->shogiPawnDropMateIllegal = true; v->stalemateValue = -VALUE_MATE; v->nFoldValue = VALUE_MATE; v->nFoldRule = 4; v->nMoveRule = 0; v->perpetualCheckIllegal = true; return v; } // EuroShogi // https://en.wikipedia.org/wiki/EuroShogi Variant* euroshogi_variant() { Variant* v = minishogi_variant_base()->init(); v->pieceToCharTable = "PNBR.....G.++++Kpnbr.....g.++++k"; v->maxRank = RANK_8; v->maxFile = FILE_H; v->add_piece(CUSTOM_PIECES, 'n', "fNsW"); v->startFen = "1nbgkgn1/1r4b1/pppppppp/8/8/PPPPPPPP/1B4R1/1NGKGBN1[-] w 0 1"; v->promotionRank = RANK_6; v->promotedPieceType[CUSTOM_PIECES] = GOLD; v->mandatoryPiecePromotion = true; return v; } // Los Alamos chess // https://en.wikipedia.org/wiki/Los_Alamos_chess Variant* losalamos_variant() { Variant* v = chess_variant_base()->init(); v->pieceToCharTable = "PN.RQ................Kpn.rq................k"; v->maxRank = RANK_6; v->maxFile = FILE_F; v->remove_piece(BISHOP); v->startFen = "rnqknr/pppppp/6/6/PPPPPP/RNQKNR w - - 0 1"; v->promotionRank = RANK_6; v->promotionPieceTypes = {QUEEN, ROOK, KNIGHT}; v->doubleStep = false; v->castling = false; return v; } // Gardner's minichess // https://en.wikipedia.org/wiki/Minichess#5%C3%975_chess Variant* gardner_variant() { Variant* v = chess_variant_base()->init(); v->maxRank = RANK_5; v->maxFile = FILE_E; v->startFen = "rnbqk/ppppp/5/PPPPP/RNBQK w - - 0 1"; v->promotionRank = RANK_5; v->doubleStep = false; v->castling = false; return v; } // Almost chess // Queens are replaced by chancellors // https://en.wikipedia.org/wiki/Almost_chess Variant* almost_variant() { Variant* v = chess_variant_base()->init(); v->pieceToCharTable = "PNBR............CKpnbr............ck"; v->remove_piece(QUEEN); v->add_piece(CHANCELLOR, 'c'); v->startFen = "rnbckbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBCKBNR w KQkq - 0 1"; v->promotionPieceTypes = {CHANCELLOR, ROOK, BISHOP, KNIGHT}; return v; } // Chigorin chess // Asymmetric variant with knight vs. bishop movements // https://www.chessvariants.com/diffsetup.dir/chigorin.html Variant* chigorin_variant() { Variant* v = chess_variant_base()->init(); v->pieceToCharTable = "PNBR............CKpnbrq............k"; v->add_piece(CHANCELLOR, 'c'); v->startFen = "rbbqkbbr/pppppppp/8/8/8/8/PPPPPPPP/RNNCKNNR w KQkq - 0 1"; v->promotionPieceTypes = {QUEEN, CHANCELLOR, ROOK, BISHOP, KNIGHT}; return v; } // Shatar (Mongolian chess) // https://en.wikipedia.org/wiki/Shatar Variant* shatar_variant() { Variant* v = chess_variant_base()->init(); v->pieceToCharTable = "PNBR..........J......Kpnbr..........j......k"; v->remove_piece(QUEEN); v->add_piece(BERS, 'j'); v->startFen = "rnbjkbnr/ppp1pppp/8/3p4/3P4/8/PPP1PPPP/RNBJKBNR w - - 0 1"; v->promotionPieceTypes = {BERS}; v->doubleStep = false; v->castling = false; v->extinctionValue = VALUE_DRAW; // Robado v->extinctionPieceTypes = {ALL_PIECES}; v->extinctionPieceCount = 1; v->shatarMateRule = true; return v; } // Coregal chess // Queens are also subject to check and checkmate // https://www.chessvariants.com/winning.dir/coregal.html Variant* coregal_variant() { Variant* v = chess_variant_base()->init(); v->extinctionValue = -VALUE_MATE; v->extinctionPieceTypes = {QUEEN}; v->extinctionPseudoRoyal = true; v->extinctionPieceCount = 64; // no matter how many queens, all are royal return v; } // Clobber // https://en.wikipedia.org/wiki/Clobber Variant* clobber_variant() { Variant* v = chess_variant_base()->init(); v->pieceToCharTable = "P.................p................."; v->maxRank = RANK_6; v->maxFile = FILE_E; v->reset_pieces(); v->add_piece(CLOBBER_PIECE, 'p'); v->startFen = "PpPpP/pPpPp/PpPpP/pPpPp/PpPpP/pPpPp w 0 1"; v->promotionPieceTypes = {}; v->doubleStep = false; v->castling = false; v->stalemateValue = -VALUE_MATE; v->immobilityIllegal = false; return v; } // Breakthrough // https://en.wikipedia.org/wiki/Breakthrough_(board_game) Variant* breakthrough_variant() { Variant* v = chess_variant_base()->init(); v->pieceToCharTable = "P.................p................."; v->reset_pieces(); v->add_piece(BREAKTHROUGH_PIECE, 'p'); v->startFen = "pppppppp/pppppppp/8/8/8/8/PPPPPPPP/PPPPPPPP w 0 1"; v->promotionPieceTypes = {}; v->doubleStep = false; v->castling = false; v->stalemateValue = -VALUE_MATE; v->flagPiece = BREAKTHROUGH_PIECE; v->whiteFlag = Rank8BB; v->blackFlag = Rank1BB; return v; } // Ataxx // https://en.wikipedia.org/wiki/Ataxx Variant* ataxx_variant() { Variant* v = chess_variant_base()->init(); v->pieceToCharTable = "P.................p................."; v->maxRank = RANK_7; v->maxFile = FILE_G; v->reset_pieces(); v->add_piece(CUSTOM_PIECES, 'p', "mDmNmA"); v->startFen = "P5p/7/7/7/7/7/p5P[PPPPPPPPPPPPPPPPPPPPPPPPPppppppppppppppppppppppppp] w 0 1"; v->promotionPieceTypes = {}; v->pieceDrops = true; v->doubleStep = false; v->castling = false; v->immobilityIllegal = false; v->stalemateValue = -VALUE_MATE; v->stalematePieceCount = true; v->passOnStalemate = true; v->enclosingDrop = ATAXX; v->flipEnclosedPieces = ATAXX; v->materialCounting = UNWEIGHTED_MATERIAL; return v; } // Minixiangqi // http://mlwi.magix.net/bg/minixiangqi.htm Variant* minixiangqi_variant() { Variant* v = chess_variant_base()->init(); v->variantTemplate = "xiangqi"; v->pieceToCharTable = "PN.R.....K.C.pn.r.....k.c."; v->maxRank = RANK_7; v->maxFile = FILE_G; v->reset_pieces(); v->add_piece(ROOK, 'r'); v->add_piece(HORSE, 'n', 'h'); v->add_piece(KING, 'k'); v->add_piece(CANNON, 'c'); v->add_piece(SOLDIER, 'p'); v->startFen = "rcnkncr/p1ppp1p/7/7/7/P1PPP1P/RCNKNCR w - - 0 1"; v->mobilityRegion[WHITE][KING] = (Rank1BB | Rank2BB | Rank3BB) & (FileCBB | FileDBB | FileEBB); v->mobilityRegion[BLACK][KING] = (Rank5BB | Rank6BB | Rank7BB) & (FileCBB | FileDBB | FileEBB); v->kingType = WAZIR; v->promotionPieceTypes = {}; v->doubleStep = false; v->castling = false; v->stalemateValue = -VALUE_MATE; //v->nFoldValue = VALUE_MATE; v->perpetualCheckIllegal = true; v->flyingGeneral = true; return v; } #ifdef LARGEBOARDS // Shogi (Japanese chess) // https://en.wikipedia.org/wiki/Shogi Variant* shogi_variant() { Variant* v = minishogi_variant_base()->init(); v->maxRank = RANK_9; v->maxFile = FILE_I; v->add_piece(LANCE, 'l'); v->add_piece(SHOGI_KNIGHT, 'n'); v->startFen = "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL[-] w 0 1"; v->promotionRank = RANK_7; v->promotedPieceType[LANCE] = GOLD; v->promotedPieceType[SHOGI_KNIGHT] = GOLD; return v; } // Sho-Shogi // 16-th century shogi variant with one additional piece and no drops // https://en.wikipedia.org/wiki/Sho_shogi Variant* shoshogi_variant() { Variant* v = shogi_variant()->init(); v->pieceToCharTable = "PNBRLSE..G.+.++.++Kpnbrlse..g.+.++.++k"; v->remove_piece(KING); v->add_piece(COMMONER, 'k'); v->add_piece(CUSTOM_PIECES, 'e', "FsfW"); // drunk elephant v->startFen = "lnsgkgsnl/1r2e2b1/ppppppppp/9/9/9/PPPPPPPPP/1B2E2R1/LNSGKGSNL w 0 1"; v->capturesToHand = false; v->pieceDrops = false; v->promotedPieceType[CUSTOM_PIECES] = COMMONER; v->castlingKingPiece = COMMONER; v->extinctionValue = -VALUE_MATE; v->extinctionPieceTypes = {COMMONER}; v->extinctionPseudoRoyal = true; v->extinctionPieceCount = 0; return v; } // Yari shogi // https://en.wikipedia.org/wiki/Yari_shogi Variant* yarishogi_variant() { Variant* v = variant_base()->init(); v->variantTemplate = "shogi"; v->pieceToCharTable = "PNBR.......++++Kpnbr.......++++k"; v->maxRank = RANK_9; v->maxFile = FILE_G; v->reset_pieces(); v->add_piece(KING, 'k'); v->add_piece(SHOGI_PAWN, 'p'); v->add_piece(ROOK, 'l'); v->add_piece(CUSTOM_PIECES, 'n', "fRffN"); // Yari knight v->add_piece(CUSTOM_PIECES + 1, 'b', "fFfR"); // Yari bishop v->add_piece(CUSTOM_PIECES + 2, 'r', "frlR"); // Yari rook v->add_piece(CUSTOM_PIECES + 3, 'g', "WfFbR"); // Yari gold v->add_piece(CUSTOM_PIECES + 4, 's', "fKbR"); // Yari silver v->startFen = "rnnkbbr/7/ppppppp/7/7/7/PPPPPPP/7/RBBKNNR[-] w 0 1"; v->promotionRank = RANK_7; v->promotedPieceType[SHOGI_PAWN] = CUSTOM_PIECES + 4; v->promotedPieceType[CUSTOM_PIECES] = CUSTOM_PIECES + 3; v->promotedPieceType[CUSTOM_PIECES + 1] = CUSTOM_PIECES + 3; v->promotedPieceType[CUSTOM_PIECES + 2] = ROOK; v->pieceDrops = true; v->capturesToHand = true; v->promotionPieceTypes = {}; v->doubleStep = false; v->castling = false; v->dropNoDoubled = SHOGI_PAWN; v->immobilityIllegal = true; v->shogiPawnDropMateIllegal = false; v->stalemateValue = -VALUE_MATE; v->nFoldRule = 3; v->nMoveRule = 0; v->perpetualCheckIllegal = true; return v; } // Okisaki shogi // https://en.wikipedia.org/wiki/Okisaki_shogi Variant* okisakishogi_variant() { Variant* v = minishogi_variant_base()->init(); v->maxRank = RANK_10; v->maxFile = FILE_J; v->add_piece(CUSTOM_PIECES, 'l', "vR"); // Vertical slider v->add_piece(KNIGHT, 'n'); v->add_piece(QUEEN, 'q'); v->startFen = "lnsgkqgsnl/1r6b1/pppppppppp/10/10/10/10/PPPPPPPPPP/1B6R1/LNSGQKGSNL[-] w 0 1"; v->promotionRank = RANK_8; v->promotedPieceType[CUSTOM_PIECES] = GOLD; v->promotedPieceType[KNIGHT] = GOLD; return v; } // Capablanca chess // https://en.wikipedia.org/wiki/Capablanca_chess Variant* capablanca_variant() { Variant* v = chess_variant_base()->init(); v->pieceToCharTable = "PNBRQ..AC............Kpnbrq..ac............k"; v->maxRank = RANK_8; v->maxFile = FILE_J; v->castlingKingsideFile = FILE_I; v->castlingQueensideFile = FILE_C; v->add_piece(ARCHBISHOP, 'a'); v->add_piece(CHANCELLOR, 'c'); v->startFen = "rnabqkbcnr/pppppppppp/10/10/10/10/PPPPPPPPPP/RNABQKBCNR w KQkq - 0 1"; v->promotionPieceTypes = {ARCHBISHOP, CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT}; return v; } // Capahouse // Capablanca chess with crazyhouse-style piece drops // https://www.pychess.org/variant/capahouse Variant* capahouse_variant() { Variant* v = capablanca_variant()->init(); v->startFen = "rnabqkbcnr/pppppppppp/10/10/10/10/PPPPPPPPPP/RNABQKBCNR[] w KQkq - 0 1"; v->pieceDrops = true; v->capturesToHand = true; return v; } // Capablanca random chess (CRC) // Shuffle variant of capablanca chess // https://en.wikipedia.org/wiki/Capablanca_random_chess Variant* caparandom_variant() { Variant* v = capablanca_variant()->init(); v->chess960 = true; v->nnueAlias = "capablanca"; return v; } // Gothic chess // Capablanca chess with changed starting position // https://www.chessvariants.com/large.dir/gothicchess.html Variant* gothic_variant() { Variant* v = capablanca_variant()->init(); v->startFen = "rnbqckabnr/pppppppppp/10/10/10/10/PPPPPPPPPP/RNBQCKABNR w KQkq - 0 1"; v->nnueAlias = "capablanca"; return v; } // Janus chess // 10x8 variant with two archbishops per side // https://en.wikipedia.org/wiki/Janus_Chess Variant* janus_variant() { Variant* v = chess_variant_base()->init(); v->pieceToCharTable = "PNBRQ............J...Kpnbrq............j...k"; v->maxRank = RANK_8; v->maxFile = FILE_J; v->castlingKingsideFile = FILE_I; v->castlingQueensideFile = FILE_B; v->add_piece(ARCHBISHOP, 'j'); v->startFen = "rjnbkqbnjr/pppppppppp/10/10/10/10/PPPPPPPPPP/RJNBKQBNJR w KQkq - 0 1"; v->promotionPieceTypes = {ARCHBISHOP, QUEEN, ROOK, BISHOP, KNIGHT}; return v; } // Modern chess // 9x9 variant with archbishops // https://en.wikipedia.org/wiki/Modern_chess Variant* modern_variant() { Variant* v = chess_variant_base()->init(); v->pieceToCharTable = "PNBRQ..M.............Kpnbrq..m.............k"; v->maxRank = RANK_9; v->maxFile = FILE_I; v->promotionRank = RANK_9; v->castlingKingsideFile = FILE_G; v->castlingQueensideFile = FILE_C; v->add_piece(ARCHBISHOP, 'm'); v->startFen = "rnbqkmbnr/ppppppppp/9/9/9/9/9/PPPPPPPPP/RNBMKQBNR w KQkq - 0 1"; v->promotionPieceTypes = {ARCHBISHOP, QUEEN, ROOK, BISHOP, KNIGHT}; return v; } // Chancellor chess // 9x9 variant with chancellors // https://en.wikipedia.org/wiki/Chancellor_chess Variant* chancellor_variant() { Variant* v = chess_variant_base()->init(); v->pieceToCharTable = "PNBRQ...........CKpnbrq...........ck"; v->maxRank = RANK_9; v->maxFile = FILE_I; v->promotionRank = RANK_9; v->castlingKingsideFile = FILE_G; v->castlingQueensideFile = FILE_C; v->add_piece(CHANCELLOR, 'c'); v->startFen = "rnbqkcnbr/ppppppppp/9/9/9/9/9/PPPPPPPPP/RNBQKCNBR w KQkq - 0 1"; v->promotionPieceTypes = {CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT}; return v; } // Embassy chess // Capablanca chess with different starting position // https://en.wikipedia.org/wiki/Embassy_chess Variant* embassy_variant() { Variant* v = capablanca_variant()->init(); v->castlingKingsideFile = FILE_H; v->castlingQueensideFile = FILE_B; v->startFen = "rnbqkcabnr/pppppppppp/10/10/10/10/PPPPPPPPPP/RNBQKCABNR w KQkq - 0 1"; v->nnueAlias = "capablanca"; return v; } // Centaur chess (aka Royal Court) // 10x8 variant with a knight+commoner compound // https://www.chessvariants.com/large.dir/contest/royalcourt.html Variant* centaur_variant() { Variant* v = chess_variant_base()->init(); v->pieceToCharTable = "PNBRQ...............CKpnbrq...............ck"; v->maxRank = RANK_8; v->maxFile = FILE_J; v->castlingKingsideFile = FILE_I; v->castlingQueensideFile = FILE_C; v->add_piece(CENTAUR, 'c'); v->startFen = "rcnbqkbncr/pppppppppp/10/10/10/10/PPPPPPPPPP/RCNBQKBNCR w KQkq - 0 1"; v->promotionPieceTypes = {CENTAUR, QUEEN, ROOK, BISHOP, KNIGHT}; return v; } // Jeson mor // Mongolian chess variant with knights only and a king of the hill like goal // https://en.wikipedia.org/wiki/Jeson_Mor Variant* jesonmor_variant() { Variant* v = chess_variant_base()->init(); v->maxRank = RANK_9; v->maxFile = FILE_I; v->reset_pieces(); v->add_piece(KNIGHT, 'n'); v->startFen = "nnnnnnnnn/9/9/9/9/9/9/9/NNNNNNNNN w - - 0 1"; v->promotionPieceTypes = {}; v->doubleStep = false; v->castling = false; v->stalemateValue = -VALUE_MATE; v->flagPiece = KNIGHT; v->whiteFlag = make_bitboard(SQ_E5); v->blackFlag = make_bitboard(SQ_E5); v->flagMove = true; return v; } // Courier chess // Medieval variant of Shatranj on a 12x8 board // https://en.wikipedia.org/wiki/Courier_chess Variant* courier_variant() { Variant* v = chess_variant_base()->init(); v->maxRank = RANK_8; v->maxFile = FILE_L; v->remove_piece(QUEEN); v->add_piece(ALFIL, 'e'); v->add_piece(FERS, 'f'); v->add_piece(COMMONER, 'm'); v->add_piece(WAZIR, 'w'); v->startFen = "rnebmk1wbenr/1ppppp1pppp1/6f5/p5p4p/P5P4P/6F5/1PPPPP1PPPP1/RNEBMK1WBENR w - - 0 1"; v->promotionPieceTypes = {FERS}; v->doubleStep = false; v->castling = false; v->extinctionValue = -VALUE_MATE; v->extinctionClaim = true; v->extinctionPieceTypes = {ALL_PIECES}; v->extinctionPieceCount = 1; v->extinctionOpponentPieceCount = 2; v->stalemateValue = -VALUE_MATE; return v; } // Grand chess // 10x10 variant with chancellors and archbishops // https://en.wikipedia.org/wiki/Grand_chess Variant* grand_variant() { Variant* v = chess_variant_base()->init(); v->variantTemplate = "grand"; v->pieceToCharTable = "PNBRQ..AC............Kpnbrq..ac............k"; v->maxRank = RANK_10; v->maxFile = FILE_J; v->add_piece(ARCHBISHOP, 'a'); v->add_piece(CHANCELLOR, 'c'); v->startFen = "r8r/1nbqkcabn1/pppppppppp/10/10/10/10/PPPPPPPPPP/1NBQKCABN1/R8R w - - 0 1"; v->promotionPieceTypes = {ARCHBISHOP, CHANCELLOR, QUEEN, ROOK, BISHOP, KNIGHT}; v->promotionRank = RANK_8; v->promotionLimit[ARCHBISHOP] = 1; v->promotionLimit[CHANCELLOR] = 1; v->promotionLimit[QUEEN] = 1; v->promotionLimit[ROOK] = 2; v->promotionLimit[BISHOP] = 2; v->promotionLimit[KNIGHT] = 2; v->mandatoryPawnPromotion = false; v->immobilityIllegal = true; v->doubleStepRank = RANK_3; v->doubleStepRankMin = RANK_3; v->castling = false; return v; } // Opulent chess // Variant of Grand chess with two extra pieces // https://www.chessvariants.com/rules/opulent-chess Variant* opulent_variant() { Variant* v = grand_variant()->init(); v->pieceToCharTable = "PNBRQ..AC....W.......LKpnbrq..ac....w.......lk"; v->remove_piece(KNIGHT); v->add_piece(CUSTOM_PIECES, 'n', "NW"); v->add_piece(CUSTOM_PIECES + 1, 'w', "CF"); v->add_piece(CUSTOM_PIECES + 2, 'l', "FDH"); v->startFen = "rw6wr/clbnqknbla/pppppppppp/10/10/10/10/PPPPPPPPPP/CLBNQKNBLA/RW6WR w - - 0 1"; v->promotionPieceTypes.erase(KNIGHT); v->promotionPieceTypes.insert(CUSTOM_PIECES); v->promotionPieceTypes.insert(CUSTOM_PIECES + 1); v->promotionPieceTypes.insert(CUSTOM_PIECES + 2); v->promotionLimit[CUSTOM_PIECES] = 2; v->promotionLimit[CUSTOM_PIECES + 1] = 2; v->promotionLimit[CUSTOM_PIECES + 2] = 2; return v; } // Tencubed // https://www.chessvariants.com/contests/10/tencubedchess.html Variant* tencubed_variant() { Variant* v = chess_variant_base()->init(); v->pieceToCharTable = "PNBRQ.CAM...........WKpnbrq.cam...........wk"; v->maxRank = RANK_10; v->maxFile = FILE_J; v->startFen = "2cwamwc2/1rnbqkbnr1/pppppppppp/10/10/10/10/PPPPPPPPPP/1RNBQKBNR1/2CWAMWC2 w - - 0 1"; v->add_piece(ARCHBISHOP, 'a'); v->add_piece(CHANCELLOR, 'm'); v->add_piece(CUSTOM_PIECES, 'c', "DAW"); // Champion v->add_piece(CUSTOM_PIECES + 1, 'w', "CF"); // Wizard v->promotionPieceTypes = {ARCHBISHOP, CHANCELLOR, QUEEN}; v->promotionRank = RANK_10; v->doubleStepRank = RANK_3; v->doubleStepRankMin = RANK_3; v->castling = false; return v; } // Shako // 10x10 variant with cannons by Jean-Louis Cazaux // https://www.chessvariants.com/large.dir/shako.html Variant* shako_variant() { Variant* v = chess_variant_base()->init(); v->pieceToCharTable = "PNBRQ.E....C.........Kpnbrq.e....c.........k"; v->maxRank = RANK_10; v->maxFile = FILE_J; v->add_piece(FERS_ALFIL, 'e'); v->add_piece(CANNON, 'c'); v->startFen = "c8c/ernbqkbnre/pppppppppp/10/10/10/10/PPPPPPPPPP/ERNBQKBNRE/C8C w KQkq - 0 1"; v->promotionPieceTypes = { QUEEN, ROOK, BISHOP, KNIGHT, CANNON, FERS_ALFIL }; v->promotionRank = RANK_10; v->castlingKingsideFile = FILE_H; v->castlingQueensideFile = FILE_D; v->castlingRank = RANK_2; v->doubleStepRank = RANK_3; v->doubleStepRankMin = RANK_3; return v; } // Clobber 10x10 // Clobber on a 10x10, mainly played by computers // https://en.wikipedia.org/wiki/Clobber Variant* clobber10_variant() { Variant* v = clobber_variant()->init(); v->maxRank = RANK_10; v->maxFile = FILE_J; v->startFen = "PpPpPpPpPp/pPpPpPpPpP/PpPpPpPpPp/pPpPpPpPpP/PpPpPpPpPp/" "pPpPpPpPpP/PpPpPpPpPp/pPpPpPpPpP/PpPpPpPpPp/pPpPpPpPpP w 0 1"; return v; } #ifdef ALLVARS // Game of the Amazons // https://en.wikipedia.org/wiki/Game_of_the_Amazons Variant* amazons_variant() { Variant* v = chess_variant_base()->init(); v->pieceToCharTable = "P...Q.................p...q................."; v->maxRank = RANK_10; v->maxFile = FILE_J; v->reset_pieces(); v->add_piece(CUSTOM_PIECES, 'q', "mQ"); v->add_piece(IMMOBILE_PIECE, 'p'); v->startFen = "3q2q3/10/10/q8q/10/10/Q8Q/10/10/3Q2Q3[PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPpppppppppppppppppppppppppppppppppppppppppppppp] w - - 0 1"; v->stalemateValue = -VALUE_MATE; v->arrowGating = true; return v; } #endif // Xiangqi (Chinese chess) // https://en.wikipedia.org/wiki/Xiangqi Variant* xiangqi_variant() { Variant* v = minixiangqi_variant()->init(); v->pieceToCharTable = "PN.R.AB..K.C..........pn.r.ab..k.c.........."; v->maxRank = RANK_10; v->maxFile = FILE_I; v->add_piece(ELEPHANT, 'b', 'e'); v->add_piece(FERS, 'a'); v->startFen = "rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1"; v->mobilityRegion[WHITE][KING] = (Rank1BB | Rank2BB | Rank3BB) & (FileDBB | FileEBB | FileFBB); v->mobilityRegion[BLACK][KING] = (Rank8BB | Rank9BB | Rank10BB) & (FileDBB | FileEBB | FileFBB); v->mobilityRegion[WHITE][FERS] = v->mobilityRegion[WHITE][KING]; v->mobilityRegion[BLACK][FERS] = v->mobilityRegion[BLACK][KING]; v->mobilityRegion[WHITE][ELEPHANT] = Rank1BB | Rank2BB | Rank3BB | Rank4BB | Rank5BB; v->mobilityRegion[BLACK][ELEPHANT] = Rank6BB | Rank7BB | Rank8BB | Rank9BB | Rank10BB; v->soldierPromotionRank = RANK_6; return v; } // Manchu/Yitong chess // Asymmetric Xiangqi variant with a super-piece // https://en.wikipedia.org/wiki/Manchu_chess Variant* manchu_variant() { Variant* v = xiangqi_variant()->init(); v->pieceToCharTable = "PN.R.AB..K.C....M.....pn.r.ab..k.c.........."; v->add_piece(BANNER, 'm'); v->startFen = "rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/9/9/M1BAKAB2 w - - 0 1"; return v; } // Supply chess // https://en.wikipedia.org/wiki/Xiangqi#Variations Variant* supply_variant() { Variant* v = xiangqi_variant()->init(); v->variantTemplate = "bughouse"; v->startFen = "rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR[] w - - 0 1"; v->twoBoards = true; v->pieceDrops = true; v->dropChecks = false; v->whiteDropRegion = v->mobilityRegion[WHITE][ELEPHANT]; v->blackDropRegion = v->mobilityRegion[BLACK][ELEPHANT]; return v; } // Janggi (Korean chess) // https://en.wikipedia.org/wiki/Janggi // Official tournament rules with bikjang and material counting. Variant* janggi_variant() { Variant* v = xiangqi_variant()->init(); v->variantTemplate = "janggi"; v->pieceToCharTable = ".N.R.AB.P..C.........K.n.r.ab.p..c.........k"; v->remove_piece(FERS); v->remove_piece(CANNON); v->remove_piece(ELEPHANT); v->add_piece(WAZIR, 'a'); v->add_piece(JANGGI_CANNON, 'c'); v->add_piece(JANGGI_ELEPHANT, 'b', 'e'); v->startFen = "rnba1abnr/4k4/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/4K4/RNBA1ABNR w - - 0 1"; v->mobilityRegion[WHITE][WAZIR] = v->mobilityRegion[WHITE][KING]; v->mobilityRegion[BLACK][WAZIR] = v->mobilityRegion[BLACK][KING]; v->soldierPromotionRank = RANK_1; v->flyingGeneral = false; v->bikjangRule = true; v->materialCounting = JANGGI_MATERIAL; v->diagonalLines = make_bitboard(SQ_D1, SQ_F1, SQ_E2, SQ_D3, SQ_F3, SQ_D8, SQ_F8, SQ_E9, SQ_D10, SQ_F10); v->pass = true; v->nFoldValue = VALUE_DRAW; v->perpetualCheckIllegal = true; return v; } // Traditional rules of Janggi, where bikjang is a draw Variant* janggi_traditional_variant() { Variant* v = janggi_variant()->init(); v->bikjangRule = true; v->materialCounting = NO_MATERIAL_COUNTING; v->nnueAlias = "janggi"; return v; } // Modern rules of Janggi, where bikjang is not considered, but material counting is. // The repetition rules are also adjusted for better compatibility with Kakao Janggi. Variant* janggi_modern_variant() { Variant* v = janggi_variant()->init(); v->bikjangRule = false; v->materialCounting = JANGGI_MATERIAL; v->moveRepetitionIllegal = true; v->nFoldRule = 4; // avoid nFold being triggered before move repetition v->nMoveRule = 100; // avoid adjudication before reaching 200 half-moves v->nnueAlias = "janggi"; return v; } // Casual rules of Janggi, where bikjang and material counting are not considered Variant* janggi_casual_variant() { Variant* v = janggi_variant()->init(); v->bikjangRule = false; v->materialCounting = NO_MATERIAL_COUNTING; v->nnueAlias = "janggi"; return v; } #endif } // namespace /// VariantMap::init() is called at startup to initialize all predefined variants void VariantMap::init() { // Add to UCI_Variant option add("chess", chess_variant()); add("normal", chess_variant()); add("fischerandom", chess960_variant()); add("nocastle", nocastle_variant()); add("armageddon", armageddon_variant()); add("fairy", fairy_variant()); // fairy variant used for endgame code initialization add("makruk", makruk_variant()); add("makpong", makpong_variant()); add("cambodian", cambodian_variant()); add("karouk", karouk_variant()); add("asean", asean_variant()); add("ai-wok", aiwok_variant()); add("shatranj", shatranj_variant()); add("chaturanga", chaturanga_variant()); add("amazon", amazon_variant()); add("nightrider", nightrider_variant()); add("grasshopper", grasshopper_variant()); add("hoppelpoppel", hoppelpoppel_variant()); add("newzealand", newzealand_variant()); add("kingofthehill", kingofthehill_variant()); add("racingkings", racingkings_variant()); add("knightmate", knightmate_variant()); add("losers", losers_variant()); add("giveaway", giveaway_variant()); add("antichess", antichess_variant()); add("suicide", suicide_variant()); add("codrus", codrus_variant()); add("extinction", extinction_variant()); add("kinglet", kinglet_variant()); add("threekings", threekings_variant()); add("horde", horde_variant()); add("nocheckatomic", nocheckatomic_variant()); add("atomic", atomic_variant()); add("3check", threecheck_variant()); add("5check", fivecheck_variant()); add("crazyhouse", crazyhouse_variant()); add("loop", loop_variant()); add("chessgi", chessgi_variant()); add("bughouse", bughouse_variant()); add("koedem", koedem_variant()); add("pocketknight", pocketknight_variant()); add("placement", placement_variant()); add("sittuyin", sittuyin_variant()); add("seirawan", seirawan_variant()); add("shouse", shouse_variant()); add("minishogi", minishogi_variant()); add("mini", minishogi_variant()); add("kyotoshogi", kyotoshogi_variant()); add("micro", microshogi_variant()); add("dobutsu", dobutsu_variant()); add("gorogoro", gorogoroshogi_variant()); add("judkins", judkinsshogi_variant()); add("torishogi", torishogi_variant()); add("euroshogi", euroshogi_variant()); add("losalamos", losalamos_variant()); add("gardner", gardner_variant()); add("almost", almost_variant()); add("chigorin", chigorin_variant()); add("shatar", shatar_variant()); add("coregal", coregal_variant()); add("clobber", clobber_variant()); add("breakthrough", breakthrough_variant()); add("ataxx", ataxx_variant()); add("minixiangqi", minixiangqi_variant()); #ifdef LARGEBOARDS add("shogi", shogi_variant()); add("shoshogi", shoshogi_variant()); add("yarishogi", yarishogi_variant()); add("okisakishogi", okisakishogi_variant()); add("capablanca", capablanca_variant()); add("capahouse", capahouse_variant()); add("caparandom", caparandom_variant()); add("gothic", gothic_variant()); add("janus", janus_variant()); add("modern", modern_variant()); add("chancellor", chancellor_variant()); add("embassy", embassy_variant()); add("centaur", centaur_variant()); add("jesonmor", jesonmor_variant()); add("courier", courier_variant()); add("grand", grand_variant()); add("opulent", opulent_variant()); add("tencubed", tencubed_variant()); add("shako", shako_variant()); add("clobber10", clobber10_variant()); #ifdef ALLVARS add("amazons", amazons_variant()); #endif add("xiangqi", xiangqi_variant()); add("manchu", manchu_variant()); add("supply", supply_variant()); add("janggi", janggi_variant()); add("janggitraditional", janggi_traditional_variant()); add("janggimodern", janggi_modern_variant()); add("janggicasual", janggi_casual_variant()); #endif } /// VariantMap::parse_istream reads variants from an INI-style configuration input stream. template void VariantMap::parse_istream(std::istream& file) { std::string variant, variant_template, key, value, input; while (file.peek() != '[' && std::getline(file, input)) {} std::vector varsToErase = {}; while (file.get() && std::getline(std::getline(file, variant, ']'), input)) { // Extract variant template, if specified if (!std::getline(std::getline(std::stringstream(variant), variant, ':'), variant_template)) variant_template = ""; // Read variant rules Config attribs = {}; while (file.peek() != '[' && std::getline(file, input)) { std::stringstream ss(input); if (ss.peek() != ';' && ss.peek() != '#') { if (DoCheck && !input.empty() && input.find('=') == std::string::npos) std::cerr << "Invalid sytax: '" << input << "'." << std::endl; if (std::getline(std::getline(ss, key, '=') >> std::ws, value) && !key.empty()) attribs[key.erase(key.find_last_not_of(" ") + 1)] = value; } } // Create variant if (variants.find(variant) != variants.end()) std::cerr << "Variant '" << variant << "' already exists." << std::endl; else if (!variant_template.empty() && variants.find(variant_template) == variants.end()) std::cerr << "Variant template '" << variant_template << "' does not exist." << std::endl; else { if (DoCheck) std::cerr << "Parsing variant: " << variant << std::endl; Variant* v = !variant_template.empty() ? VariantParser(attribs).parse((new Variant(*variants.find(variant_template)->second))->init()) : VariantParser(attribs).parse(); if (v->maxFile <= FILE_MAX && v->maxRank <= RANK_MAX) { add(variant, v); // In order to allow inheritance, we need to temporarily add configured variants // even when only checking them, but we remove them later after parsing is finished. if (DoCheck) varsToErase.push_back(variant); } else delete v; } } // Clean up temporary variants for (std::string tempVar : varsToErase) { delete variants[tempVar]; variants.erase(tempVar); } } /// VariantMap::parse reads variants from an INI-style configuration file. template void VariantMap::parse(std::string path) { if (path.empty() || path == "") return; std::ifstream file(path); if (!file.is_open()) { std::cerr << "Unable to open file " << path << std::endl; return; } parse_istream(file); file.close(); } template void VariantMap::parse(std::string path); template void VariantMap::parse(std::string path); void VariantMap::add(std::string s, Variant* v) { insert(std::pair(s, v->conclude())); } void VariantMap::clear_all() { for (auto const& element : *this) delete element.second; clear(); } std::vector VariantMap::get_keys() { std::vector keys; for (auto const& element : *this) keys.push_back(element.first); return keys; } } // namespace Stockfish Fairy-Stockfish-fairy_sf_14_0_1_xq/src/variant.h000066400000000000000000000300131414571233100217400ustar00rootroot00000000000000/* Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish Copyright (C) 2018-2021 Fabian Fichter Fairy-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. Fairy-Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef VARIANT_H_INCLUDED #define VARIANT_H_INCLUDED #include #include #include #include #include #include #include #include "types.h" #include "bitboard.h" namespace Stockfish { /// Variant struct stores information needed to determine the rules of a variant. struct Variant { std::string variantTemplate = "fairy"; std::string pieceToCharTable = "-"; int pocketSize = 0; Rank maxRank = RANK_8; File maxFile = FILE_H; bool chess960 = false; bool twoBoards = false; int pieceValue[PHASE_NB][PIECE_TYPE_NB] = {}; std::string customPiece[CUSTOM_PIECES_NB] = {}; std::set pieceTypes = { PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING }; std::string pieceToChar = " PNBRQ" + std::string(KING - QUEEN - 1, ' ') + "K" + std::string(PIECE_TYPE_NB - KING - 1, ' ') + " pnbrq" + std::string(KING - QUEEN - 1, ' ') + "k" + std::string(PIECE_TYPE_NB - KING - 1, ' '); std::string pieceToCharSynonyms = std::string(PIECE_NB, ' '); std::string startFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; Bitboard mobilityRegion[COLOR_NB][PIECE_TYPE_NB] = {}; Rank promotionRank = RANK_8; std::set > promotionPieceTypes = { QUEEN, ROOK, BISHOP, KNIGHT }; bool sittuyinPromotion = false; int promotionLimit[PIECE_TYPE_NB] = {}; // 0 means unlimited PieceType promotedPieceType[PIECE_TYPE_NB] = {}; bool piecePromotionOnCapture = false; bool mandatoryPawnPromotion = true; bool mandatoryPiecePromotion = false; bool pieceDemotion = false; bool blastOnCapture = false; bool doubleStep = true; Rank doubleStepRank = RANK_2; Rank doubleStepRankMin = RANK_2; Bitboard enPassantRegion = AllSquares; bool castling = true; bool castlingDroppedPiece = false; File castlingKingsideFile = FILE_G; File castlingQueensideFile = FILE_C; Rank castlingRank = RANK_1; File castlingKingFile = FILE_E; PieceType castlingKingPiece = KING; PieceType castlingRookPiece = ROOK; PieceType kingType = KING; bool checking = true; bool dropChecks = true; bool mustCapture = false; bool mustDrop = false; PieceType mustDropType = ALL_PIECES; bool pieceDrops = false; bool dropLoop = false; bool capturesToHand = false; bool firstRankPawnDrops = false; bool promotionZonePawnDrops = false; bool dropOnTop = false; EnclosingRule enclosingDrop = NO_ENCLOSING; Bitboard enclosingDropStart = 0; Bitboard whiteDropRegion = AllSquares; Bitboard blackDropRegion = AllSquares; bool sittuyinRookDrop = false; bool dropOppositeColoredBishop = false; bool dropPromoted = false; PieceType dropNoDoubled = NO_PIECE_TYPE; int dropNoDoubledCount = 1; bool immobilityIllegal = false; bool gating = false; bool arrowGating = false; bool seirawanGating = false; bool cambodianMoves = false; Bitboard diagonalLines = 0; bool pass = false; bool passOnStalemate = false; bool makpongRule = false; bool flyingGeneral = false; Rank soldierPromotionRank = RANK_1; EnclosingRule flipEnclosedPieces = NO_ENCLOSING; // game end int nMoveRule = 50; int nFoldRule = 3; Value nFoldValue = VALUE_DRAW; bool nFoldValueAbsolute = false; bool perpetualCheckIllegal = false; bool moveRepetitionIllegal = false; Value stalemateValue = VALUE_DRAW; bool stalematePieceCount = false; // multiply stalemate value by sign(count(~stm) - count(stm)) Value checkmateValue = -VALUE_MATE; bool shogiPawnDropMateIllegal = false; bool shatarMateRule = false; bool bikjangRule = false; Value extinctionValue = VALUE_NONE; bool extinctionClaim = false; bool extinctionPseudoRoyal = false; std::set extinctionPieceTypes = {}; int extinctionPieceCount = 0; int extinctionOpponentPieceCount = 0; PieceType flagPiece = NO_PIECE_TYPE; Bitboard whiteFlag = 0; Bitboard blackFlag = 0; bool flagMove = false; bool checkCounting = false; int connectN = 0; MaterialCounting materialCounting = NO_MATERIAL_COUNTING; CountingRule countingRule = NO_COUNTING; // Derived properties bool fastAttacks = true; bool fastAttacks2 = true; std::string nnueAlias = ""; PieceType nnueKing = KING; int nnueDimensions; bool nnueUsePockets; int pieceSquareIndex[COLOR_NB][PIECE_NB]; int pieceHandIndex[COLOR_NB][PIECE_NB]; int kingSquareIndex[SQUARE_NB]; int nnueMaxPieces; bool endgameEval = false; void add_piece(PieceType pt, char c, std::string betza = "", char c2 = ' ') { pieceToChar[make_piece(WHITE, pt)] = toupper(c); pieceToChar[make_piece(BLACK, pt)] = tolower(c); pieceToCharSynonyms[make_piece(WHITE, pt)] = toupper(c2); pieceToCharSynonyms[make_piece(BLACK, pt)] = tolower(c2); pieceTypes.insert(pt); // Add betza notation for custom piece if (is_custom(pt)) customPiece[pt - CUSTOM_PIECES] = betza; } void add_piece(PieceType pt, char c, char c2) { add_piece(pt, c, "", c2); } void remove_piece(PieceType pt) { pieceToChar[make_piece(WHITE, pt)] = ' '; pieceToChar[make_piece(BLACK, pt)] = ' '; pieceToCharSynonyms[make_piece(WHITE, pt)] = ' '; pieceToCharSynonyms[make_piece(BLACK, pt)] = ' '; pieceTypes.erase(pt); } void reset_pieces() { pieceToChar = std::string(PIECE_NB, ' '); pieceToCharSynonyms = std::string(PIECE_NB, ' '); pieceTypes.clear(); } // Reset values that always need to be redefined Variant* init() { nnueAlias = ""; return this; } // Pre-calculate derived properties Variant* conclude() { fastAttacks = std::all_of(pieceTypes.begin(), pieceTypes.end(), [this](PieceType pt) { return ( pt < FAIRY_PIECES || pt == COMMONER || pt == IMMOBILE_PIECE || pt == ARCHBISHOP || pt == CHANCELLOR || (pt == KING && kingType == KING)) && !(mobilityRegion[WHITE][pt] || mobilityRegion[BLACK][pt]); }) && !cambodianMoves && !diagonalLines; fastAttacks2 = std::all_of(pieceTypes.begin(), pieceTypes.end(), [this](PieceType pt) { return ( pt < FAIRY_PIECES || pt == COMMONER || pt == FERS || pt == WAZIR || pt == BREAKTHROUGH_PIECE || pt == SHOGI_PAWN || pt == GOLD || pt == SILVER || pt == SHOGI_KNIGHT || pt == DRAGON || pt == DRAGON_HORSE || pt == LANCE || (pt == KING && kingType == KING)) && !(mobilityRegion[WHITE][pt] || mobilityRegion[BLACK][pt]); }) && !cambodianMoves && !diagonalLines; // Initialize calculated NNUE properties nnueKing = pieceTypes.find(KING) != pieceTypes.end() ? KING : extinctionPieceCount == 0 && extinctionPieceTypes.find(COMMONER) != extinctionPieceTypes.end() ? COMMONER : NO_PIECE_TYPE; if (nnueKing != NO_PIECE_TYPE) { std::string fenBoard = startFen.substr(0, startFen.find(' ')); // Switch NNUE from KA to A if there is no unique piece if ( std::count(fenBoard.begin(), fenBoard.end(), pieceToChar[make_piece(WHITE, nnueKing)]) != 1 || std::count(fenBoard.begin(), fenBoard.end(), pieceToChar[make_piece(BLACK, nnueKing)]) != 1) nnueKing = NO_PIECE_TYPE; } int nnueSquares = (maxRank + 1) * (maxFile + 1); nnueUsePockets = (pieceDrops && (!mustDrop || capturesToHand)) || seirawanGating; int nnuePockets = nnueUsePockets ? 2 * int(maxFile + 1) : 0; int nnueNonDropPieceIndices = (2 * pieceTypes.size() - (nnueKing != NO_PIECE_TYPE)) * nnueSquares; int nnuePieceIndices = nnueNonDropPieceIndices + 2 * (pieceTypes.size() - (nnueKing != NO_PIECE_TYPE)) * nnuePockets; int i = 0; for (PieceType pt : pieceTypes) { for (Color c : { WHITE, BLACK}) { pieceSquareIndex[c][make_piece(c, pt)] = 2 * i * nnueSquares; pieceSquareIndex[c][make_piece(~c, pt)] = (2 * i + (pt != nnueKing)) * nnueSquares; pieceHandIndex[c][make_piece(c, pt)] = 2 * i * nnuePockets + nnueNonDropPieceIndices; pieceHandIndex[c][make_piece(~c, pt)] = (2 * i + 1) * nnuePockets + nnueNonDropPieceIndices; } i++; } // Map king squares to enumeration of actually available squares. // E.g., for xiangqi map from 0-89 to 0-8. // Variants might be initialized before bitboards, so do not rely on precomputed bitboards (like SquareBB). int nnueKingSquare = 0; if (nnueKing) for (Square s = SQ_A1; s < nnueSquares; ++s) { Square bitboardSquare = Square(s + s / (maxFile + 1) * (FILE_MAX - maxFile)); if ( !mobilityRegion[WHITE][nnueKing] || !mobilityRegion[BLACK][nnueKing] || (mobilityRegion[WHITE][nnueKing] & make_bitboard(bitboardSquare)) || (mobilityRegion[BLACK][nnueKing] & make_bitboard(relative_square(BLACK, bitboardSquare, maxRank)))) { kingSquareIndex[s] = nnueKingSquare++ * nnuePieceIndices; } } else kingSquareIndex[SQ_A1] = nnueKingSquare++ * nnuePieceIndices; nnueDimensions = nnueKingSquare * nnuePieceIndices; // Determine maximum piece count std::istringstream ss(startFen); ss >> std::noskipws; unsigned char token; nnueMaxPieces = 0; while ((ss >> token) && !isspace(token)) { if (pieceToChar.find(token) != std::string::npos || pieceToCharSynonyms.find(token) != std::string::npos) nnueMaxPieces++; } if (twoBoards) nnueMaxPieces *= 2; // For endgame evaluation to be applicable, no special win rules must apply. // Furthermore, rules significantly changing game mechanics also invalidate it. endgameEval = std::none_of(pieceTypes.begin(), pieceTypes.end(), [this](PieceType pt) { return mobilityRegion[WHITE][pt] || mobilityRegion[BLACK][pt]; }) && extinctionValue == VALUE_NONE && checkmateValue == -VALUE_MATE && stalemateValue == VALUE_DRAW && !materialCounting && !flagPiece && !mustCapture && !checkCounting && !makpongRule && !connectN && !blastOnCapture && !capturesToHand && !twoBoards && kingType == KING; return this; } }; class VariantMap : public std::map { public: void init(); template void parse(std::string path); template void parse_istream(std::istream& file); void clear_all(); std::vector get_keys(); private: void add(std::string s, Variant* v); }; extern VariantMap variants; } // namespace Stockfish #endif // #ifndef VARIANT_H_INCLUDED Fairy-Stockfish-fairy_sf_14_0_1_xq/src/variants.ini000066400000000000000000000631321414571233100224630ustar00rootroot00000000000000# Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish # Copyright (C) 2018-2021 Fabian Fichter # # Fairy-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. # # Fairy-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 . # This is a configuration file to add user-defined variants to Fairy-Stockfish. ################################################ ### Usage: # Add "load" and the file path to the SF call (e.g., "stockfish.exe load variants.ini") # or set the UCI option "VariantPath" to the path of this file in order to load it. # In order to validate the configuration without actually loading the variants # run "stockfish.exe check variants.ini", which reports potential config errors. ################################################ ### Variant configuration: # The variant name needs to be specified as a section in square brackets, # followed by its rule configurations as key-value pairs as described below. # If you encounter problems configuring variants, please report them at: # https://github.com/ianfab/Fairy-Stockfish/issues ### Inheritance # If a variant is similar to a previously defined variant, # inheritance can be used to simplify the definition. To inherit from the # configuration of an existing variant, specify the parent variant after the child # variant name separated by a colon, e.g., [gothic:capablanca]. # When inheritance is used, only the differences to the parent variant need to be defined, # see the examples in this file, e.g., 3check-crazyhouse. # When no inheritance is used, the default template applies, # which is basically standard chess but without any predefined pieces. ### Piece types # Firstly, the piece types for a variant need to be defined. # For that, specify the letter used for each piece type, e.g.: # pawn = p # # See the list below for all available predefined piece types (and their Betza notation): # pawn (fmWfceF) # knight (N) # bishop (B) # rook (R) # queen (Q) # fers (F) # alfil (A) # fersAlfil (FA) # silver (FfW) # aiwok (RNF) # bers (RF) # archbishop (BN) # chancellor (RN) # amazon (QN) # knibis (mNcB) # biskni (mBcN) # kniroo (mNcR) # rookni (mRcN) # shogiPawn (fW) # lance (fR) # shogiKnight (fN) # gold (WfF) # dragonHorse (BW) # clobber (cW) # breakthrough (fmWfF) # immobile () # cannon (mRcpR) # janggiCannon (pR) # soldier (fsW) # horse (nN) # elephant (nA) # janggiElephant (nZ) # banner (RcpRnN) # wazir (W) # commoner (K) # centaur (KN) # king (K) ### Custom pieces # Custom pieces can be defined by using one of the available slots: # customPiece1, customPiece2, ..., customPiece25 # E.g., pawns without double steps could be described as: # customPiece1 = p:mfWcfF # # You can define custom king movements in the same way you can define another custom piece. # E.g., to make the king move like a centaur: # king = k:KN # In constrast to other custom pieces the Betza notation for the king is optional though # and defaults to a standard chess king (betza: K) when skipped, e.g.: # king = k # # The movements of custom pieces can be defined using the Betza notation. # https://www.gnu.org/software/xboard/Betza.html # In Fairy-Stockfish only a subset of Betza notation can be used. The supported features are: # - all base moves/atoms (W, F, etc.) # - all directional modifiers (f, b, etc.) # - limited and unlimited distance sliders/riders for W/R, F/B, and N directions # - hoppers and grasshoppers for W/R and F/B directions, i.e., pR, pB, gR, and gB # - lame leapers (n) for N, A, and Z directions, i.e., nN, nA, and nZ ### Piece values # The predefined and precalculated piece values can be overriden # by specifying the pieceValueMg and pieceValueEg options, e.g., # pieceValueMg = p:150 n:800 # pieceValueEg = p:200 n:900 # # For orientation, the internal predefined piece values can be found in types.h. # A suitable piece for gauging the piece values is the rook, which internally has: # pieceValueMg = r:1276 # pieceValueEg = r:1380 ### Option types # [bool]: boolean flag to enable/disable a feature [true, false] # [Rank]: denotes a rank of the board [1-10] # [File]: denotes a file of the board [1-12, a-i] # [int]: any natural number [0, 1, ...] # [PieceType]: a piece type [letters defined for pieces, e.g., p] # [Bitboard]: list of squares [e.g., d4 e4 d5 e5]. * can be used as wildcard for files (e.g., *1 is the first rank) # [Value]: game result for the side to move [win, loss, draw] # [MaterialCounting]: material couting rules for adjudication [janggi, unweighted, whitedrawodds, blackdrawodds, none] # [CountingRule]: makruk or ASEAN counting rules [makruk, asean, none] # [EnclosingRule]: reversi or ataxx enclosing rules [reversi, ataxx, none] ### Additional options relevant for usage in Winboard/XBoard # A few options only have the purpose of improving compatibility with Winboard/Xboard. # These do not need to be specified when using other GUIs, but can be essential for Winboard/Xboard. # # variantTemplate: base variant to inherit GUI logic from [values: fairy, shogi, bughouse] (default: fairy) # pieceToCharTable: mapping of piece characters to images, # see https://www.gnu.org/software/xboard/whats_new/4.9.0/index.html#tag-B1 (default: -) # pocketSize: number of pockets shown by XBoard/WinBoard for drop variants [int] (default: 0) ### Rule definition options # maxRank: maximum rank [Rank] (default: 8) # maxFile: maximum file [File] (default: 8) # chess960: allow chess960 castling [bool] (default: false) # twoBoards: the game is influenced by a second board (e.g., bughouse) [bool] (default: false) # startFen: FEN of starting position (default: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1) # mobilityRegion: the mobility area can be defined via options specific to color and piece, # .e.g., mobilityRegionWhiteRook, mobilityRegionBlackJanggiElephant, etc. [Bitboard] # promotionRank: relative rank required to reach for promotion [Rank] (default: 8) # promotionPieceTypes: pawn promotion options using their one-letter representations (default: nbrq) # sittuyinPromotion: enable Sittuyin-style pawn promotion [bool] (default: false) # promotionLimit: maximum number of pieces of a type, e.g., q:1 r:2 (default: ) # promotedPieceType: mapping between unpromoted and promoted non-pawn piece types, e.g., p:g s:g (default: ) # piecePromotionOnCapture: piece promotion only allowed on captures (e.g., micro shogi) [bool] (default: false) # mandatoryPawnPromotion: pawn promotion is mandatory [bool] (default: true) # mandatoryPiecePromotion: piece promotion (and demotion if enabled) is mandatory [bool] (default: false) # pieceDemotion: enable demotion of pieces (e.g., Kyoto shogi) [bool] (default: false) # blastOnCapture: captures explode all adjacent non-pawn pieces (e.g., atomic chess) [bool] (default: false) # doubleStep: enable pawn double step [bool] (default: true) # doubleStepRank: relative rank from where pawn double steps are allowed [Rank] (default: 2) # doubleStepRankMin: earlist relative rank from where pawn double steps are allowed [Rank] (default: 2) # enPassantRegion: define region (target squares) where en passant is allowed after double steps [Bitboard] # castling: enable castling [bool] (default: true) # castlingDroppedPiece: enable castling with dropped rooks/kings [bool] (default: false) # castlingKingsideFile: destination file of king after kingside castling [File] (default: g) # castlingQueensideFile: destination file of king after queenside castling [File] (default: c) # castlingRank: relative rank of castling [Rank] (default: 1) # castlingKingFile: starting file of the castlingKingPiece if there can be more than one of that type [File] (default: e) # castlingKingPiece: first piece type that participates in castling [PieceType] (default: k) # castlingRookPiece: second piece type that participates in castling [PieceType] (default: r) # checking: allow checks [bool] (default: true) # dropChecks: allow checks by piece drops [bool] (default: true) # mustCapture: captures are mandatory (check evasion still takes precedence) [bool] (default: false) # mustDrop: drops are mandatory (e.g., for Sittuyin setup phase) [bool] (default: false) # mustDropType: piece type for which piece drops are mandatory [PieceType] (default: *) # pieceDrops: enable piece drops [bool] (default: false) # dropLoop: captures promoted pieces are not demoted [bool] (default: false) # capturesToHand: captured pieces are go to opponent's hand [bool] (default: false) # firstRankPawnDrops: allow pawn drops to first rank [bool] (default: false) # promotionZonePawnDrops: allow pawn drops in promotion zone [bool] (default: false) # dropOnTop: piece drops need to be on top of pieces on board (e.g., for connect4) [bool] (default: false) # enclosingDrop: require piece drop to enclose pieces [EnclosingRule] (default: none) # enclosingDropStart: drop region for starting phase disregarding enclosingDrop (e.g., for reversi) [Bitboard] # whiteDropRegion: restrict region for piece drops of all white pieces [Bitboard] # blackDropRegion: restrict region for piece drops of all black pieces [Bitboard] # sittuyinRookDrop: restrict region of rook drops to first rank [bool] (default: false) # dropOppositeColoredBishop: dropped bishops have to be on opposite-colored squares [bool] (default: false) # dropPromoted: pieces may be dropped in promoted state [bool] (default: false) # dropNoDoubled: specified piece type can not be dropped to the same file (e.g. shogi pawn) [PieceType] (default: -) # dropNoDoubledCount: specifies the count of already existing pieces for dropNoDoubled [PieceType] (default: 1) # immobilityIllegal: pieces may not move to squares where they can never move from [bool] (default: false) # gating: maintain squares on backrank with extra rights in castling field of FEN [bool] (default: false) # arrowGating: allow gating in Game of the Amazons style [bool] (default: false) # seirawanGating: allow gating of pieces in hand like in S-Chess, requires "gating = true" [bool] (default: false) # cambodianMoves: enable special moves of cambodian chess, requires "gating = true" [bool] (default: false) # diagonalLines: enable special moves along diagonal for specific squares (Janggi) [Bitboard] # pass: allow passing [bool] (default: false) # passOnStalemate: allow passing in case of stalemate [bool] (default: false) # makpongRule: the king may not move away from check [bool] (default: false) # flyingGeneral: disallow general face-off like in xiangqi [bool] (default: false) # soldierPromotionRank: restrict soldier to shogi pawn movements until reaching n-th rank [bool] (default: 1) # flipEnclosedPieces: change color of pieces that are enclosed by a drop [EnclosingRule] (default: none) # nMoveRule: move count for 50/n-move rule [int] (default: 50) # nFoldRule: move count for 3/n-fold repetition rule [int] (default: 3) # nFoldValue: result in case of 3/n-fold repetition [Value] (default: draw) # nFoldValueAbsolute: result in case of 3/n-fold repetition is from white's point of view [bool] (default: false) # perpetualCheckIllegal: prohibit perpetual checks [bool] (default: false) # moveRepetitionIllegal: prohibit moving back and forth with the same piece nFoldRule-1 times [bool] (default: false) # stalemateValue: result in case of stalemate [Value] (default: draw) # stalematePieceCount: count material in case of stalemate [bool] (default: false) # checkmateValue: result in case of checkmate [Value] (default: loss) # shogiPawnDropMateIllegal: prohibit checkmate via shogi pawn drops [bool] (default: false) # shatarMateRule: enable shatar mating rules [bool] (default: false) # bikjangRule: consider Janggi bikjang (facing kings) rule [bool] (default: false) # extinctionValue: result when one of extinctionPieceTypes is extinct [Value] (default: none) # extinctionClaim: extinction of opponent pieces can only be claimed as side to move [bool] (default: false) # extinctionPseudoRoyal: treat the last extinction piece like a royal piece [bool] (default: false) # extinctionPieceTypes: list of piece types for extinction rules, e.g., pnbrq (* means all) (default: ) # extinctionPieceCount: piece count at which the game is decided by extinction rule (default: 0) # extinctionOpponentPieceCount: opponent piece count required to adjudicate by extinction rule (default: 0) # flagPiece: piece type for capture the flag win rule [PieceType] (default: -) # whiteFlag: white's target region for capture the flag win rule [Bitboard] (default: ) # blackFlag: black's target region for capture the flag win rule [Bitboard] (default: ) # flagMove: black gets one more move after white captures the flag [bool] (default: false) # checkCounting: enable check count win rule (check count is communicated via FEN, see 3check) [bool] (default: false) # connectN: number of aligned pieces for win [int] (default: 0) # materialCounting: enable material counting rules [MaterialCounting] (default: none) # countingRule: enable counting rules [CountingRule] (default: none) ################################################ ### Example for minishogi configuration that would be equivalent to the built-in variant: # [minishogi] # variantTemplate = shogi # maxRank = 5 # maxFile = 5 # shogiPawn = p # silver = s # gold = g # bishop = b # dragonHorse = h # rook = r # bers = d # king = k # startFen = rbsgk/4p/5/P4/KGSBR[-] w 0 1 # pieceDrops = true # capturesToHand = true # promotionRank = 5 # doubleStep = false # castling = false # promotedPieceType = p:g s:g b:h r:d # dropNoDoubled = p # immobilityIllegal = true # shogiPawnDropMateIllegal = true # stalemateValue = loss # nFoldRule = 4 # nMoveRule = 0 # perpetualCheckIllegal = true # pocketSize = 5 # nFoldValue = loss # nFoldValueAbsolute = true # Hybrid variant of three-check chess and crazyhouse, using crazyhouse as a template [3check-crazyhouse:crazyhouse] startFen = rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 3+3 0 1 checkCounting = true # Hybrid variant of atomic and giveaway [atomic-giveaway:giveaway] blastOnCapture = true # Hybrid variant of atomic, giveaway, and king of the hill [atomic-giveaway-hill:giveaway] blastOnCapture = true flagPiece = k whiteFlag = d4 e4 d5 e5 blackFlag = d4 e4 d5 e5 # Crazyhouse with mandatory captures, using crazyhouse as a template [coffeehouse:crazyhouse] mustCapture = true # Hybrid variant of makruk and crazyhouse [makrukhouse:makruk] startFen = rnsmksnr/8/pppppppp/8/8/PPPPPPPP/8/RNSKMSNR[] w - - 0 1 pieceDrops = true capturesToHand = true # Hybrid variant of xiangqi and crazyhouse [xiangqihouse:xiangqi] startFen = rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR[] w - - 0 1 pieceDrops = true capturesToHand = true dropChecks = false whiteDropRegion = *1 *2 *3 *4 *5 blackDropRegion = *6 *7 *8 *9 *10 # Hybrid variant of janggi and crazyhouse [janggihouse:janggi] startFen = rnba1abnr/4k4/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/4K4/RNBA1ABNR[] w - - 0 1 pieceDrops = true capturesToHand = true # Hybrid variant of antichess and losalamos [anti-losalamos:losalamos] king = - commoner = k promotionPieceTypes = nrqk mustCapture = true stalemateValue = win extinctionValue = win extinctionPieceTypes = * # Indian great chess # https://www.chessvariants.com/historic.dir/indiangr1.html [indiangreat] pieceToCharTable = PNBRQ..VW.........G..Kpnbrq..vw.........g..k pawn = p knight = n bishop = b rook = r queen = q king = k archbishop = v chancellor = w amazon = g maxRank = 10 maxFile = 10 startFen = rnbqkgvbnr/ppppwwpppp/4pp4/10/10/10/10/4PP4/PPPPWWPPPP/RNBVGKQBNR w - - 0 1 promotionRank = 10 promotionPieceTypes = q doubleStep = false castling = false # Centaurking # A variant demonstrating how to define a custom royal piece movement [centaurking:chess] king = k:KN # Mahajarah and the Sepoys # https://en.wikipedia.org/wiki/Maharajah_and_the_Sepoys [maharajah] pawn = p knight = n bishop = b rook = r queen = q king = k amazon = m pieceToCharTable = PNBRQ.............MKpnbrq.............mk startFen = rnbqkbnr/pppppppp/8/8/8/8/8/4M3 w kq - 0 1 extinctionValue = loss extinctionPieceTypes = m extinctionPseudoRoyal = true # Maharajah # https://vchess.club/#/variants/Maharajah (Balanced version of Maharajah and the Sepoys) [maharajah2:maharajah] amazon = - customPiece1 = m:QNAD pieceToCharTable = PNBRQ.............MKpnbrq.............mk startFen = 3mm3/8/8/8/8/8/PPPPPPPP/RNBQKBNR w KQ - 0 1 extinctionPieceTypes = m # Upside-down [upsidedown:chess] startFen = RNBKQBNR/PPPPPPPP/8/8/8/8/pppppppp/rnbkqbnr w - - 0 1 # Peasant revolt # https://www.chessvariants.com/large.dir/peasantrevolt.html [peasant:chess] startFen = 1nn1k1n1/4p3/8/8/8/8/PPPPPPPP/4K3 w - - 0 1 # https://www.chessvariants.com/unequal.dir/weak.html [weak:chess] startFen = nnnnknnn/pppppppp/2p2p2/1pppppp1/8/8/PPPPPPPP/RNBQKBNR w KQ - 0 1 # Semi-torpedo chess [semitorpedo:chess] doubleStepRank = 3 # This variant is similar to Capablanca Chess, but with two archbishops and no chancellor piece. [gemini:janus] startFen = rnbaqkabnr/pppppppppp/10/10/10/10/PPPPPPPPPP/RNBAQKABNR w KQkq - 0 1 archbishop = a pieceToCharTable = PNBRQ............A...Kpnbrq............a...k castlingKingsideFile = i castlingQueensideFile = c # https://www.chessvariants.com/diffsetup.dir/pawnsonly.html [pawnsonly] pawn = p queen = q startFen = 8/pppppppp/8/8/8/8/PPPPPPPP/8 w - - 0 1 promotionPieceTypes = q castling = false stalemateValue = loss flagPiece = q whiteFlag = *8 blackFlag = *1 [tictactoe] maxRank = 3 maxFile = 3 immobile = p startFen = 3/3/3[PPPPPpppp] w - - 0 1 pieceDrops = true doubleStep = false castling = false stalemateValue = draw immobilityIllegal = false connectN = 3 [cfour] maxRank = 6 maxFile = 7 immobile = p startFen = 7/7/7/7/7/7[PPPPPPPPPPPPPPPPPPPPPppppppppppppppppppppp] w - - 0 1 pieceDrops = true dropOnTop = true doubleStep = false castling = false stalemateValue = draw immobilityIllegal = false connectN = 4 nMoveRule = 0 [flipersi] immobile = p startFen = 8/8/8/8/8/8/8/8[PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPpppppppppppppppppppppppppppppppp] w 0 1 pieceDrops = true promotionPieceTypes = - doubleStep = false castling = false stalemateValue = loss stalematePieceCount = true materialCounting = unweighted enclosingDrop = reversi enclosingDropStart = d4 e4 d5 e5 immobilityIllegal = false flipEnclosedPieces = reversi passOnStalemate = false [flipello:flipersi] startFen = 8/8/8/3pP3/3Pp3/8/8/8[PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPpppppppppppppppppppppppppppppppp] w 0 1 passOnStalemate = true [grandhouse:grand] startFen = r8r/1nbqkcabn1/pppppppppp/10/10/10/10/PPPPPPPPPP/1NBQKCABN1/R8R[] w - - 0 1 pieceDrops = true capturesToHand = true [shogun:crazyhouse] variantTemplate = shogi pieceToCharTable = PNBR.F.....++++.+Kpnbr.f.....++++.+k pocketSize = 8 startFen = rnb+fkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNB+FKBNR[] w KQkq - 0 1 commoner = c centaur = g archbishop = a chancellor = m fers = f promotionRank = 6 promotionLimit = g:1 a:1 m:1 q:1 promotionPieceTypes = - promotedPieceType = p:c n:g b:a r:m f:q mandatoryPawnPromotion = false firstRankPawnDrops = true promotionZonePawnDrops = true whiteDropRegion = *1 *2 *3 *4 *5 blackDropRegion = *4 *5 *6 *7 *8 immobilityIllegal = true # Asymmetric variant with one army using pieces that move like knights but attack like other pieces (kniroo and knibis) [orda:chess] pieceToCharTable = PNBRQ..AH...........LKp...q..ah.y.........lk centaur = h knibis = a kniroo = l silver = y promotionPieceTypes = qh startFen = lhaykahl/8/pppppppp/8/8/8/PPPPPPPP/RNBQKBNR w KQ - 0 1 flagPiece = k whiteFlag = *8 blackFlag = *1 # Ordamirror # https://vchess.club/#/variants/Ordamirror [ordamirror:chess] pieceToCharTable = P...Q..AH.F.........LKp...q..ah.f.........lk centaur = h knibis = a kniroo = l customPiece1 = f:mQcN promotionPieceTypes = lhaf startFen = lhafkahl/8/pppppppp/8/8/PPPPPPPP/8/LHAFKAHL w - - 0 1 flagPiece = k whiteFlag = *8 blackFlag = *1 # Hybrid variant of Gothic-chess and crazyhouse, using Capablanca as a template [gothhouse:capablanca] startFen = rnbqckabnr/pppppppppp/10/10/10/10/PPPPPPPPPP/RNBQCKABNR[] w KQkq - 0 1 pieceDrops = true capturesToHand = true # Synochess # https://www.pychess.org/variant/synochess [synochess:pocketknight] pieceToCharTable = PNBRQAE...SCH........Kpnbrqae...sch........k pocketSize = 8 janggiCannon = c soldier = s horse = h fersAlfil = e commoner = a stalemateValue = loss perpetualCheckIllegal = true startFen = rneakenr/8/1c4c1/1ss2ss1/8/8/PPPPPPPP/RNBQKBNR[ss] w KQ - 0 1 flyingGeneral = true capturesToHand = false blackDropRegion = *5 flagPiece = k whiteFlag = *8 blackFlag = *1 # Capture chess # https://vchess.club/#/variants/Capture [capture:chess] mustCapture = true # Double Army chess # https://vchess.club/#/variants/Doublearmy [doublearmy:chess] pieceToCharTable = PNBRQ.....C...........Kpnbrq.....c...........k commoner = c startFen = rnbqkbnr/pppppppp/rnbqcbnr/pppppppp/PPPPPPPP/RNBQCBNR/PPPPPPPP/RNBQKBNR w KQkq - 0 1 # Pawn Massacre chess # https://vchess.club/#/variants/Pawnmassacre [pawnsmassacre:chess] startFen = RNBKQBNR/pppppppp/8/8/8/8/PPPPPPPP/rnbkqbnr w - - 0 1 # Screen chess (Below version assumes 1 drop per turn instead of the whole blind setup as in vchess) # https://vchess.club/#/variants/Screen [screen:placement] dropNoDoubled = p startFen = 8/8/8/8/8/8/8/8[KQRRBBNNPPPPPPPPkqrrbbnnpppppppp] w - - 0 1 whiteDropRegion = *1 *2 *3 *4 blackDropRegion = *8 *7 *6 *5 # Crossing chess # https://vchess.club/#/variants/Crossing [crossing:kingofthehill] whiteFlag = *5 blackFlag = *4 # 4x5 Chess # https://greenchess.net/rules.php?v=4x5-chess --> Solved draw [4x5chess:gardner] maxRank = 5 maxFile = d startFen = rnbk/pppp/4/PPPP/RNBK w - - 0 1 # 4x6 Chess # https://greenchess.net/rules.php?v=4x6-chess --> Solved draw [4x6chess:gardner] maxRank = 6 maxFile = d promotionRank = 6 startFen = rnbk/pppp/4/4/PPPP/RNBK w - - 0 1 # 5x6 chess # https://greenchess.net/rules.php?v=5x6-chess [5x6chess:gardner] maxRank = 6 maxFile = e promotionRank = 6 startFen = rnbqk/ppppp/5/5/PPPPP/RNBQK w - - 0 1 # Active chess # https://greenchess.net/rules.php?v=active [active:chess] maxFile = i startFen = rnbkqbnrq/ppppppppp/9/9/9/9/PPPPPPPPP/RNBKQBNRQ w KQkq - 0 1 # Checkless 6x6 Atomic [6x6atom:nocheckatomic] extinctionPseudoRoyal = true maxRank = 6 maxFile = f promotionRank = 6 doubleStep = false startFen = rbqkbr/pppppp/6/6/PPPPPP/RBQKBR w - - 0 1 # Advanced Pawn chess # https://greenchess.net/rules.php?v=advanced-pawn [advancedpawn:chess] doubleStep = false startFen = rnbqkbnr/8/pppppppp/8/8/PPPPPPPP/8/RNBQKBNR w KQkq - 0 1 # Capture-all Chess # https://greenchess.net/rules.php?v=capture-all [captureall:extinction] extinctionPieceTypes = * # Corner Rook Chess # https://greenchess.net/rules.php?v=corner-rook [cornerrook:chess] doubleStep = false castling = false startFen = r6r/1nbqkbn1/pppppppp/8/8/PPPPPPPP/1NBQKBN1/R6R w - - 0 1 # Diana Chess # https://greenchess.net/rules.php?v=diana [diana:losalamos] pieceToCharTable = PNBRQ................Kpnbrq................k bishop = b promotionPieceTypes = rbn castling = true castlingKingsideFile = e castlingQueensideFile = b startFen = rbnkbr/pppppp/6/6/PPPPPP/RBNKBR w KQkq - 0 1 # Microchess # https://greenchess.net/rules.php?v=microchess [microchess:gardner] maxRank = 5 maxFile = d startFen = rbnk/p3/4/3P/RBNK w - - 0 1 # Empire Chess # https://vchess.club/#/variants/Empire [empire:chess] pieceToCharTable = PNBRQ.....ST.C.D.E...Kpnbrq.....st.c.d.e...k customPiece1 = e:mQcN customPiece2 = c:mQcB customPiece3 = t:mQcR customPiece4 = d:mQcK soldier = s promotionPieceTypes = q startFen = rnbqkbnr/pppppppp/8/8/8/PPPSSPPP/8/TECDKCET w kq - 0 1 stalemateValue = loss nFoldValue = loss flagPiece = k whiteFlag = *8 blackFlag = *1 flyingGeneral = true # Shinobi Chess # https://vchess.club/#/variants/Shinobi [shinobi:chess] variantTemplate = shogi pieceToCharTable = PNBRQ.DJMLH.....CKpnbrq.djmlh.....ck pocketSize = 8 commoner = c bers = d archbishop = j fers = m shogiKnight = h lance = l promotionRank = 7 promotionPieceTypes = - promotedPieceType = p:c m:b h:n l:r mandatoryPiecePromotion = true stalemateValue = loss perpetualCheckIllegal = true startFen = rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/LH1CK1HL[LHMMDJ] w kq - 0 1 pieceDrops = true whiteDropRegion = *1 *2 *3 *4 blackDropRegion = *5 *6 *7 *8 immobilityIllegal = true flagPiece = k whiteFlag = *8 blackFlag = *1 # Wildebeest # https://vchess.club/#/variants/Wildebeest # Limitations: No flexible castling, no pawn triple steps [wildebeest:chess] maxRank = 10 maxFile = k customPiece1 = c:C customPiece2 = w:NC pieceToCharTable = PNBRQ.......C....WKpnbrq.......c....wk startFen = rnccwkqbbnr/ppppppppppp/11/11/11/11/11/11/PPPPPPPPPPP/RNBBQKWCCNR w KQkq - 0 1 promotionPieceTypes = qw promotionRank = 9 mandatoryPawnPromotion = false castling = false # Pandemonium # A variant that combines drops and powerful pieces, and there is no draw # https://www.chessvariants.com/rules/pandemonium [pandemonium] variantTemplate = shogi pieceToCharTable = PNBRFSA.UV.+++++++.++Kpnbrfsa.uv.+++++++.++k maxFile = 9 maxRank = 9 pocketSize = 9 startFen = rnbsksbnr/2+f1+u1+a2/p1p1p1p1p/4v4/9/4V4/P1P1P1P1P/2+F1+U1+A2/RNBSKSBNR[] w - - 0 1 customPiece1 = o:NA customPiece2 = s:WF customPiece3 = u:D customPiece4 = w:DWF castling = false pieceDrops = true capturesToHand = true immobilityIllegal = true soldier = p knight = n bishop = b rook = r king = k queen = q commoner = g dragonHorse = h bers = d alfil = a archbishop = c chancellor = m fers = f wazir = v centaur = t promotionRank = 7 promotedPieceType = p:g n:o b:h r:d a:c v:m f:q s:w u:t doubleStep = false perpetualCheckIllegal = true nMoveRule = 0 nFoldValue = loss stalemateValue = loss Fairy-Stockfish-fairy_sf_14_0_1_xq/src/xboard.cpp000066400000000000000000000335301414571233100221150ustar00rootroot00000000000000/* Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish Copyright (C) 2018-2021 Fabian Fichter Fairy-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. Fairy-Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include "evaluate.h" #include "misc.h" #include "partner.h" #include "search.h" #include "thread.h" #include "types.h" #include "uci.h" #include "xboard.h" namespace Stockfish { namespace { const Search::LimitsType analysisLimits = []{ Search::LimitsType limits; limits.infinite = 1; return limits; }(); } // namespace namespace XBoard { StateMachine* stateMachine = nullptr; // go() starts the search for game play, analysis, or perft. void StateMachine::go(Search::LimitsType searchLimits, bool ponder) { searchLimits.startTime = now(); // As early as possible! Threads.start_thinking(pos, states, searchLimits, ponder); } // ponder() starts a ponder search void StateMachine::ponder() { sync_cout << "Hint: " << UCI::move(pos, ponderMove) << sync_endl; ponderHighlight = highlight(UCI::square(pos, from_sq(ponderMove))); do_move(ponderMove); ponderMove = MOVE_NONE; go(limits, true); } // stop() stops an ongoing search (if any) // and does not print/apply a move if aborted void StateMachine::stop(bool abort) { if (abort) Threads.abort = true; Threads.stop = true; Threads.main()->wait_for_search_finished(); // Ensure that current position does not get out of sync with GUI if (Threads.main()->ponder) { assert(moveList.size()); undo_move(); Threads.main()->ponder = false; } } // setboard() is called when engine receives the "setboard" XBoard command. void StateMachine::setboard(std::string fen) { if (fen.empty()) fen = variants.find(Options["UCI_Variant"])->second->startFen; states = StateListPtr(new std::deque(1)); // Drop old and create a new one moveList.clear(); pos.set(variants.find(Options["UCI_Variant"])->second, fen, Options["UCI_Chess960"], &states->back(), Threads.main()); } // do_move() is called when engine needs to apply a move when using XBoard protocol. void StateMachine::do_move(Move m) { // transfer states back if (Threads.setupStates.get()) states = std::move(Threads.setupStates); if (m == MOVE_NONE) return; moveList.push_back(m); states->emplace_back(); pos.do_move(m, states->back()); } // undo_move() is called when the engine receives the undo command in XBoard protocol. void StateMachine::undo_move() { // transfer states back if (Threads.setupStates.get()) states = std::move(Threads.setupStates); pos.undo_move(moveList.back()); states->pop_back(); moveList.pop_back(); } std::string StateMachine::highlight(std::string square) { Bitboard promotions = 0, captures = 0, quiets = 0; // Collect targets for (const auto& m : MoveList(pos)) { Square from = from_sq(m), to = to_sq(m); if (is_ok(from) && UCI::square(pos, from) == square && !is_pass(m)) { if (type_of(m) == PROMOTION) promotions |= to; else if (pos.capture(m)) captures |= to; else { if (type_of(m) == CASTLING && !pos.is_chess960()) to = make_square(to > from ? pos.castling_kingside_file() : pos.castling_queenside_file(), rank_of(from)); quiets |= to; } } } // Generate color FEN int emptyCnt; std::ostringstream ss; for (Rank r = pos.max_rank(); r >= RANK_1; --r) { for (File f = FILE_A; f <= pos.max_file(); ++f) { for (emptyCnt = 0; f <= pos.max_file() && !((promotions | captures | quiets) & make_square(f, r)); ++f) ++emptyCnt; if (emptyCnt) ss << emptyCnt; if (f <= pos.max_file()) ss << (promotions & make_square(f, r) ? "M" : captures & make_square(f, r) ? "R" : "Y"); } if (r > RANK_1) ss << '/'; } return ss.str(); } /// StateMachine::process_command() processes commands of the XBoard protocol. void StateMachine::process_command(std::string token, std::istringstream& is) { if (token == "protover") { std::string vars = "chess"; for (std::string v : variants.get_keys()) if (v != "chess") vars += "," + v; sync_cout << "feature setboard=1 usermove=1 time=1 memory=1 smp=1 colors=0 draw=0 " << "highlight=1 name=0 sigint=0 ping=1 myname=\"" << engine_info(false, true) << "\" " << "variants=\"" << vars << "\"" << Options << sync_endl; sync_cout << "feature done=1" << sync_endl; } else if (token == "accepted" || token == "rejected") {} else if (token == "hover" || token == "put") {} else if (token == "lift") { if (is >> token) { if (Threads.main()->ponder) { if (token == UCI::square(pos, from_sq(moveList.back()))) sync_cout << "highlight " << ponderHighlight << sync_endl; else { Move currentPonderMove = moveList.back(); stop(); sync_cout << "highlight " << highlight(token) << sync_endl; // Restart ponder search with random guess auto moves = MoveList(pos); std::vector filteredMoves; copy_if(moves.begin(), moves.end(), back_inserter(filteredMoves), [&](const Move m) { return is_ok(from_sq(m)) && UCI::square(pos, from_sq(m)) == token; }); if (filteredMoves.size()) { static PRNG rng(now()); ponderMove = filteredMoves.at(rng.rand() % filteredMoves.size()); } else ponderMove = currentPonderMove; ponder(); } } else sync_cout << "highlight " << highlight(token) << sync_endl; } } else if (token == "ping") { if (!(is >> token)) token = ""; sync_cout << "pong " << token << sync_endl; } else if (token == "new") { stop(); Search::clear(); setboard(); // play second by default playColor = ~pos.side_to_move(); Threads.sit = false; Partner.reset(); } else if (token == "variant") { stop(); if (is >> token) Options["UCI_Variant"] = token; setboard(); } else if (token == "force" || token == "result") { stop(); playColor = COLOR_NB; } else if (token == "?") { if (!Threads.main()->ponder) stop(false); } else if (token == "go") { stop(); playColor = pos.side_to_move(); go(limits); moveAfterSearch = true; } else if (token == "level" || token == "st" || token == "sd" || token == "time" || token == "otim") { int num; if (token == "level") { // moves to go is >> limits.movestogo; // base time is >> token; size_t idx = token.find(":"); if (idx != std::string::npos) num = std::stoi(token.substr(0, idx)) * 60 + std::stoi(token.substr(idx + 1)); else num = std::stoi(token) * 60; limits.time[WHITE] = num * 1000; limits.time[BLACK] = num * 1000; // increment is >> num; limits.inc[WHITE] = num * 1000; limits.inc[BLACK] = num * 1000; } else if (token == "sd") is >> limits.depth; else if (token == "st") { is >> num; limits.movetime = num * 1000; limits.time[WHITE] = limits.time[BLACK] = 0; } // Note: time/otim are in centi-, not milliseconds else if (token == "time") { is >> num; Color us = playColor != COLOR_NB ? playColor : pos.side_to_move(); if (limits.time[us]) limits.time[us] = num * 10; } else if (token == "otim") { is >> num; Color them = playColor != COLOR_NB ? ~playColor : ~pos.side_to_move(); if (limits.time[them]) limits.time[them] = num * 10; } } else if (token == "setboard") { stop(); std::string fen; std::getline(is >> std::ws, fen); // Check if setboard actually indicates a passing move // to avoid unnecessarily clearing the move history if (pos.pass()) { StateInfo st; Position p; p.set(pos.variant(), fen, pos.is_chess960(), &st, pos.this_thread()); Move m; std::string passMove = "@@@@"; if ((m = UCI::to_move(pos, passMove)) != MOVE_NONE) do_move(m); // apply setboard if passing does not lead to a match if (pos.key() != p.key()) setboard(fen); } else setboard(fen); // Winboard sends setboard after passing moves if (Options["UCI_AnalyseMode"]) go(analysisLimits); else if (pos.side_to_move() == playColor) { go(limits); moveAfterSearch = true; } } else if (token == "cores") { stop(); if (is >> token) Options["Threads"] = token; } else if (token == "memory") { stop(); if (is >> token) Options["Hash"] = token; } else if (token == "hard" || token == "easy") Options["Ponder"] = token == "hard"; else if (token == "option") { std::string name, value; is.get(); std::getline(is, name, '='); std::getline(is, value); if (Options.count(name)) { if (Options[name].get_type() == "check") value = value == "1" ? "true" : "false"; Options[name] = value; } } else if (token == "analyze") { stop(); Options["UCI_AnalyseMode"] = std::string("true"); go(analysisLimits); } else if (token == "exit") { stop(); Options["UCI_AnalyseMode"] = std::string("false"); } else if (token == "undo") { stop(); if (moveList.size()) { undo_move(); if (Options["UCI_AnalyseMode"]) go(analysisLimits); } } // Bughouse commands else if (token == "partner") Partner.parse_partner(is); else if (token == "ptell") { Partner.parse_ptell(is, pos); // play move requested by partner // Partner.moveRequested can only be set if search was successfully aborted if (moveAfterSearch && Partner.moveRequested) { assert(Threads.abort); stop(); sync_cout << "move " << UCI::move(pos, Partner.moveRequested) << sync_endl; do_move(Partner.moveRequested); moveAfterSearch = false; Partner.moveRequested = MOVE_NONE; } } else if (token == "holding") { stop(); // holding [] [] std::string white_holdings, black_holdings; if ( std::getline(is, token, '[') && std::getline(is, white_holdings, ']') && std::getline(is, token, '[') && std::getline(is, black_holdings, ']')) { std::string fen; char color, pieceType; // Use the obtained holding if available to avoid race conditions if (is >> color && is >> pieceType) { fen = pos.fen(); fen.insert(fen.find(']'), 1, toupper(color) == 'B' ? tolower(pieceType) : toupper(pieceType)); } else { std::transform(black_holdings.begin(), black_holdings.end(), black_holdings.begin(), ::tolower); fen = pos.fen(false, false, 0, white_holdings + black_holdings); } setboard(fen); } // restart search if (moveAfterSearch) go(limits); } // Additional custom non-XBoard commands else if (token == "perft") { stop(); Search::LimitsType perft_limits; is >> perft_limits.perft; go(perft_limits); } else if (token == "d") sync_cout << pos << sync_endl; else if (token == "eval") sync_cout << Eval::trace(pos) << sync_endl; // Move strings and unknown commands else { bool isMove = false; if (token == "usermove") { is >> token; isMove = true; } // Handle pondering if (Threads.main()->ponder) { assert(moveList.size()); if (token == UCI::move(pos, moveList.back())) { // ponderhit moveAfterSearch = true; Threads.main()->ponder = false; return; } } stop(false); // Apply move Move m; if ((m = UCI::to_move(pos, token)) != MOVE_NONE) do_move(m); else sync_cout << (isMove ? "Illegal move: " : "Error (unknown command): ") << token << sync_endl; // Restart search if applicable if (Options["UCI_AnalyseMode"]) go(analysisLimits); else if (pos.side_to_move() == playColor) { moveAfterSearch = true; go(limits); } } } } // namespace XBoard } // namespace StockfishFairy-Stockfish-fairy_sf_14_0_1_xq/src/xboard.h000066400000000000000000000036251414571233100215640ustar00rootroot00000000000000/* Fairy-Stockfish, a UCI chess variant playing engine derived from Stockfish Copyright (C) 2018-2021 Fabian Fichter Fairy-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. Fairy-Stockfish is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef XBOARD_H_INCLUDED #define XBOARD_H_INCLUDED #include #include #include #include "types.h" namespace Stockfish { class Position; namespace XBoard { /// StateMachine class maintains the states required by XBoard protocol class StateMachine { public: StateMachine(Position& uciPos, StateListPtr& uciPosStates) : pos(uciPos), states(uciPosStates) { moveList = std::deque(); moveAfterSearch = false; playColor = COLOR_NB; ponderMove = MOVE_NONE; ponderHighlight = ""; } void go(Search::LimitsType searchLimits, bool ponder = false); void ponder(); void stop(bool abort = true); void setboard(std::string fen = ""); void do_move(Move m); void undo_move(); std::string highlight(std::string square); void process_command(std::string token, std::istringstream& is); bool moveAfterSearch; Move ponderMove; private: Position& pos; StateListPtr& states; std::deque moveList; Search::LimitsType limits; Color playColor; std::string ponderHighlight; }; extern StateMachine* stateMachine; } // namespace XBoard } // namespace Stockfish #endif // #ifndef XBOARD_H_INCLUDED Fairy-Stockfish-fairy_sf_14_0_1_xq/test.py000066400000000000000000001056011414571233100206730ustar00rootroot00000000000000# -*- coding: utf-8 -*- import faulthandler import unittest import pyffish as sf faulthandler.enable() CHESS = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" CHESS960 = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w HAha - 0 1" CAPA = "rnabqkbcnr/pppppppppp/10/10/10/10/PPPPPPPPPP/RNABQKBCNR w KQkq - 0 1" CAPAHOUSE = "rnabqkbcnr/pppppppppp/10/10/10/10/PPPPPPPPPP/RNABQKBCNR[] w KQkq - 0 1" SITTUYIN = "8/8/4pppp/pppp4/4PPPP/PPPP4/8/8[KFRRSSNNkfrrssnn] w - - 0 1" MAKRUK = "rnsmksnr/8/pppppppp/8/8/PPPPPPPP/8/RNSKMSNR w - - 0 1" SHOGI = "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL[-] w - - 0 1" SHOGI_SFEN = "lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1" SEIRAWAN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[EHeh] w KQBCDFGkqbcdfg - 0 1" GRAND = "r8r/1nbqkcabn1/pppppppppp/10/10/10/10/PPPPPPPPPP/1NBQKCABN1/R8R w - - 0 1" GRANDHOUSE = "r8r/1nbqkcabn1/pppppppppp/10/10/10/10/PPPPPPPPPP/1NBQKCABN1/R8R[] w - - 0 1" XIANGQI = "rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1" SHOGUN = "rnb+fkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNB+FKBNR[] w KQkq - 0 1" JANGGI = "rnba1abnr/4k4/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/4K4/RNBA1ABNR w - - 0 1" ini_text = """ # Hybrid variant of Grand-chess and crazyhouse, using Grand-chess as a template [grandhouse:grand] startFen = r8r/1nbqkcabn1/pppppppppp/10/10/10/10/PPPPPPPPPP/1NBQKCABN1/R8R[] w - - 0 1 pieceDrops = true capturesToHand = true # Shogun chess [shogun:crazyhouse] startFen = rnb+fkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNB+FKBNR[] w KQkq - 0 1 commoner = c centaur = g archbishop = a chancellor = m fers = f promotionRank = 6 promotionLimit = g:1 a:1 m:1 q:1 promotionPieceTypes = - promotedPieceType = p:c n:g b:a r:m f:q mandatoryPawnPromotion = false firstRankPawnDrops = true promotionZonePawnDrops = true whiteDropRegion = *1 *2 *3 *4 *5 blackDropRegion = *4 *5 *6 *7 *8 immobilityIllegal = true # Asymmetric variant with one army using pieces that move like knights but attack like other pieces (kniroo and knibis) [orda:chess] startFen = lhaykahl/8/pppppppp/8/8/8/PPPPPPPP/RNBQKBNR w KQ - 0 1 centaur = h knibis = a kniroo = l silver = y promotionPieceTypes = qh flagPiece = k whiteFlag = *8 blackFlag = *1 [diana:losalamos] pieceToCharTable = PNBRQ................Kpnbrq................k bishop = b promotionPieceTypes = rbn castling = true castlingKingsideFile = e castlingQueensideFile = b startFen = rbnkbr/pppppp/6/6/PPPPPP/RBNKBR w KQkq - 0 1 [passchess:chess] pass = true """ sf.load_variant_config(ini_text) variant_positions = { "chess": { "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1": (False, False), # startpos "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -": (False, False), # startpos "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR": (False, False), # startpos "rnbqkbnr/ppp2ppp/4p3/3pP3/8/8/PPPP1PPP/RNBQKBNR w KQkq d6 0 3": (False, False), "k7/8/8/8/8/8/8/K7 w - - 0 1": (True, True), # K vs K "k7/b7/8/8/8/8/8/K7 w - - 0 1": (True, True), # K vs KB "k7/n7/8/8/8/8/8/K7 w - - 0 1": (True, True), # K vs KN "k7/p7/8/8/8/8/8/K7 w - - 0 1": (True, False), # K vs KP "k7/r7/8/8/8/8/8/K7 w - - 0 1": (True, False), # K vs KR "k7/q7/8/8/8/8/8/K7 w - - 0 1": (True, False), # K vs KQ "k7/nn6/8/8/8/8/8/K7 w - - 0 1": (True, False), # K vsNN K "k7/bb6/8/8/8/8/8/K7 w - - 0 1": (True, False), # K vs KBB opp color "k7/b1b5/8/8/8/8/8/K7 w - - 0 1": (True, True), # K vs KBB same color "kb6/8/8/8/8/8/8/K1B6 w - - 0 1": (True, True), # KB vs KB same color "kb6/8/8/8/8/8/8/KB7 w - - 0 1": (False, False), # KB vs KB opp color "8/8/8/8/8/6KN/8/6nk w - - 0 1": (False, False), # KN vs KN }, "atomic": { "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1": (False, False), # startpos "8/8/8/8/3K4/3k4/8/8 b - - 0 1": (True, True), # K vs K "k7/p7/8/8/8/8/8/K7 w - - 0 1": (True, False), # K vs KP "k7/q7/8/8/8/8/8/K7 w - - 0 1": (True, False), # K vs KQ }, "crazyhouse": { "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR/ w KQkq - 0 1": (False, False), # lichess style startpos }, "3check": { "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 3+3 0 1": (False, False), # startpos "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 +0+2": (False, False), # lichess style check count "k7/n7/8/8/8/8/8/K7 w - - 1+2 0 1": (True, False), # K vs KN "k7/b7/8/8/8/8/8/K7 w - - 3+1 0 1": (True, False), # K vs KB }, "horde": { "rnbqkbnr/pppppppp/8/1PP2PP1/PPPPPPPP/PPPPPPPP/PPPPPPPP/PPPPPPPP w kq - 0 1": (False, False), # startpos }, "racingkings": { "8/8/8/8/8/8/krbnNBRK/qrbnNBRQ w - - 0 1": (False, False), # startpos "8/8/8/8/8/8/K6k/8 w - - 0 1": (False, False), # KvK }, "placement": { "8/pppppppp/8/8/8/8/PPPPPPPP/8[KQRRBBNNkqrrbbnn] w - - 0 1": (False, False), # startpos "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 0 1": (False, False), # chess startpos "k7/8/8/8/8/8/8/K7[] w - - 0 1": (True, True), # K vs K }, "newzealand": { "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1": (False, False), # startpos }, "seirawan": { "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[HEhe] w KQBCDFGkqbcdfg - 0 1": (False, False), # startpos "k7/8/8/8/8/8/8/K7[] w - - 0 1": (True, True), # K vs K "k7/8/8/8/8/8/8/KH6[] w - - 0 1": (False, True), # KH vs K "k7/8/8/8/8/8/8/4K3[E] w E - 0 1": (False, True), # KE vs K }, "cambodian": { "rnsmksnr/8/pppppppp/8/8/PPPPPPPP/8/RNSKMSNR w DEde 0 0 1": (False, False), # startpos "1ns1ksn1/r6r/pppmpppp/3p4/8/PPPPPPPP/RK2N2R/1NS1MS2 w Ee - 6 5": (False, False), }, "sittuyin": { "8/8/4pppp/pppp4/4PPPP/PPPP4/8/8[KFRRSSNNkfrrssnn] w - - 0 1": (False, False), # startpos "k7/8/8/8/8/8/8/K7 w - - 0 1": (True, True), # K vs K, skip pocket "k6P/8/8/8/8/8/8/K7[] w - - 0 1": (True, True), # KP vs K "k6P/8/8/8/8/8/8/K6p[] w - - 0 1": (False, False), # KP vs KP "k7/8/8/8/8/8/8/KFF5[] w - - 0 1": (False, True), # KFF vs K "k7/8/8/8/8/8/8/KS6[] w - - 0 1": (False, True), # KS vs K }, "makpong": { "8/8/8/4k2K/5m~2/4m~3/8/8 w - 128 8 58": (True, False), # KFF vs K "k7/n7/8/8/8/8/8/K7 w - - 0 1": (True, False), # K vs KN "k7/8/8/8/8/8/8/K7 w - - 0 1": (True, True), # K vs K }, "xiangqi": { "rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1": (False, False), # startpos "5k3/4a4/3CN4/9/1PP5p/9/8P/4C4/4A4/2B1K4 w - - 0 46": (False, False), # issue #53 "4k4/9/9/9/9/9/9/9/9/4K4 w - - 0 1": (True, True), # K vs K "4k4/9/9/4p4/9/9/9/9/9/4KR3 w - - 0 1": (False, False), # KR vs KP "4k4/9/9/9/9/9/9/9/9/3KN4 w - - 0 1": (False, True), # KN vs K "4k4/9/4b4/9/9/9/9/4B4/9/4K4 w - - 0 1": (True, True), # KB vs KB "4k4/9/9/9/9/9/9/9/4A4/4KC3 w - - 0 1": (False, True), # KCA vs K }, "janggi": { JANGGI: (False, False), # startpos "rhea1aehr/4k4/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/4K4/RHEA1AEHR w - - 0 1": (False, False), # startpos "5k3/4a4/3CN4/9/1PP5p/9/8P/4C4/4A4/2B1K4 w - - 0 46": (False, False), # issue #53 "4k4/9/9/9/9/4B4/4B4/9/9/4K4 w - - 0 1": (False, True), # KEE vs K "4k4/9/9/9/9/9/9/9/4A4/4KC3 w - - 0 1": (False, True), # KCA vs K }, "shako": { "k9/10/10/10/10/10/10/10/10/KC8 w - - 0 1": (True, True), # KC vs K "k9/10/10/10/10/10/10/10/10/KCC7 w - - 0 1": (False, True), # KCC vs K "k9/10/10/10/10/10/10/10/10/KEC7 w - - 0 1": (False, True), # KEC vs K "k9/10/10/10/10/10/10/10/10/KNE7 w - - 0 1": (False, True), # KNE vs K "kb8/10/10/10/10/10/10/10/10/KE8 w - - 0 1": (False, False), # KE vs KB opp color "kb8/10/10/10/10/10/10/10/10/K1E7 w - - 0 1": (True, True), # KE vs KB same color }, "orda": { "k7/8/8/8/8/8/8/K7 w - - 0 1": (False, False), # K vs K }, "tencubed": { "2cwamwc2/1rnbqkbnr1/pppppppppp/10/10/10/10/PPPPPPPPPP/1RNBQKBNR1/2CWAMWC2 w - - 0 1": (False, False), # startpos "10/5k4/10/10/10/10/10/10/5KC3/10 w - - 0 1": (False, True), # KC vs K "10/5k4/10/10/10/10/10/10/5K4/10 w - - 0 1": (True, True), # K vs K }, } invalid_variant_positions = { "chess": ( "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 a", # invalid full move "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - b 1", # invalid half move "rnbqkbnr/ppp2ppp/4p3/3pP3/8/8/PPPP1PPP/RNBQKBNR w KQkq -6 0 3", # invalid en passant "rnbqkbnr/ppp2ppp/4p3/3pP3/8/8/PPPP1PPP/RNBQKBNR w KQkq .6 0 3", # invalid en passant "rnbqkbnr/ppp2ppp/4p3/3pP3/8/8/PPPP1PPP/RNBQKBNR w KQkq d- 0 3", # invalid en passant "rnbqkbnr/ppp2ppp/4p3/3pP3/8/8/PPPP1PPP/RNBQKBNR w KQkq 0 3", # invalid/missing en passant "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 123 - 0 1", # invalid castling "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR g KQkq - 0 1", # invalid side to move "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNH w KQkq - 0 1", # invalid piece type "rnbqkbnr/pppppppp/7/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", # invalid file count "rnbqkbnr/pppppppp/9/7/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", # invalid file count "rnbqkbnr/pppppppp/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", # invalid rank count "rnbqkbn1/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", # missing castling rook "1nbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", # missing castling rook "rnbqkbnr/pppppppp/8/8/8/4K3/PPPPPPPP/RNBQ1BNR w KQkq - 0 1", # king not on castling rank "rnbqkbnr/pppppppp/8/8/8/RNBQKBNR/PPPPPPPP/8 w KQkq - 0 1", # not on castling rank "8/pppppppp/rnbqkbnr/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", # not on castling rank ), "atomic": ( "rnbqkbnr/pppppppp/8/8/8/RNBQKBNR/PPPPPPPP/8 w KQkq - 0 1", # wrong castling rank ), "3check": ( "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 3+a 0 1", # invalid check count "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 +a+2", # invalid lichess check count "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 +1+4", # invalid lichess check count ), "horde": ( "rnbqkbnr/pppppppp/8/1PP2PP1/PPPPPPPP/PPPPPPPP/PPPPPPPP/PPPPPPPK w kq - 0 1", # wrong king count "rnbq1bnr/pppppppp/8/1PP2PP1/PPPPPPPP/PPPPPPPP/PPPPPPPP/PPPPPPPP w kq - 0 1", # wrong king count ), "sittuyin": ( "8/8/4pppp/pppp4/4PPPP/PPPP4/8/8[FRRSSNNkfrrssnn] w - - 0 1", # wrong king count ), "shako": { "c8c/ernbqkbnre/pppppppppp/10/10/10/10/PPPPPPPPPP/C8C/ERNBQKBNRE w KQkq - 0 1", # not on castling rank }, "seirawan": { "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQK1NR[HEhe] w KQBCDFGkqbcdfg - 0 1", # white gating flag "rnbqkb1r/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[HEhe] w KQBCDFGkqbcdfg - 0 1", # black gating flag } } class TestPyffish(unittest.TestCase): def test_version(self): result = sf.version() self.assertEqual(len(result), 3) def test_info(self): result = sf.info() self.assertTrue(result.startswith("Fairy-Stockfish")) def test_variants_loaded(self): variants = sf.variants() self.assertTrue("shogun" in variants) def test_set_option(self): result = sf.set_option("UCI_Variant", "capablanca") self.assertIsNone(result) def test_two_boards(self): self.assertFalse(sf.two_boards("chess")) self.assertTrue(sf.two_boards("bughouse")) def test_start_fen(self): result = sf.start_fen("capablanca") self.assertEqual(result, CAPA) result = sf.start_fen("capahouse") self.assertEqual(result, CAPAHOUSE) result = sf.start_fen("xiangqi") self.assertEqual(result, XIANGQI) result = sf.start_fen("grandhouse") self.assertEqual(result, GRANDHOUSE) result = sf.start_fen("shogun") self.assertEqual(result, SHOGUN) def test_legal_moves(self): fen = "10/10/10/10/10/k9/10/K9 w - - 0 1" result = sf.legal_moves("capablanca", fen, []) self.assertEqual(result, ["a1b1"]) result = sf.legal_moves("grand", GRAND, ["a3a5"]) self.assertIn("a10b10", result) result = sf.legal_moves("xiangqi", XIANGQI, ["h3h10"]) self.assertIn("i10h10", result) result = sf.legal_moves("xiangqi", XIANGQI, ["h3h10"]) self.assertIn("i10h10", result) result = sf.legal_moves("shogun", SHOGUN, ["c2c4", "b8c6", "b2b4", "b7b5", "c4b5", "c6b8"]) self.assertIn("b5b6+", result) # In Janggi stalemate position pass move (in place king move) is possible fen = "4k4/c7R/9/3R1R3/9/9/9/9/9/3K5 b - - 0 1" result = sf.legal_moves("janggi", fen, []) self.assertEqual(result, ["e10e10"]) def test_short_castling(self): legals = ['f5f4', 'a7a6', 'b7b6', 'c7c6', 'd7d6', 'e7e6', 'i7i6', 'j7j6', 'a7a5', 'b7b5', 'c7c5', 'e7e5', 'i7i5', 'j7j5', 'b8a6', 'b8c6', 'h6g4', 'h6i4', 'h6j5', 'h6f7', 'h6g8', 'h6i8', 'd5a2', 'd5b3', 'd5f3', 'd5c4', 'd5e4', 'd5c6', 'd5e6', 'd5f7', 'd5g8', 'j8g8', 'j8h8', 'j8i8', 'e8f7', 'c8b6', 'c8d6', 'g6g2', 'g6g3', 'g6f4', 'g6g4', 'g6h4', 'g6e5', 'g6g5', 'g6i5', 'g6a6', 'g6b6', 'g6c6', 'g6d6', 'g6e6', 'g6f6', 'g6h8', 'f8f7', 'f8g8', 'f8i8'] moves = ['b2b4', 'f7f5', 'c2c3', 'g8d5', 'a2a4', 'h8g6', 'f2f3', 'i8h6', 'h2h3'] result = sf.legal_moves("capablanca", CAPA, moves) self.assertCountEqual(legals, result) self.assertIn("f8i8", result) moves = ['a2a4', 'f7f5', 'b2b3', 'g8d5', 'b1a3', 'i8h6', 'c1a2', 'h8g6', 'c2c4'] result = sf.legal_moves("capablanca", CAPA, moves) self.assertIn("f8i8", result) moves = ['f2f4', 'g7g6', 'g1d4', 'j7j6', 'h1g3', 'b8a6', 'i1h3', 'h7h6'] result = sf.legal_moves("capablanca", CAPA, moves) self.assertIn("f1i1", result) # Check that chess960 castling notation is used for otherwise ambiguous castling move # d1e1 is a normal king move, so castling has to be d1f1 result = sf.legal_moves("diana", "rbnk1r/pppbpp/3p2/5P/PPPPPB/RBNK1R w KQkq - 2 3", []) self.assertIn("d1f1", result) # Test configurable piece perft legals = ['a3a4', 'b3b4', 'c3c4', 'd3d4', 'e3e4', 'f3f4', 'g3g4', 'e1e2', 'f1f2', 'b1a2', 'b1b2', 'b1c2', 'c1b2', 'c1c2', 'c1d2', 'a1a2', 'g1g2', 'd1c2', 'd1d2', 'd1e2'] result = sf.legal_moves("yarishogi", sf.start_fen("yarishogi"), []) self.assertCountEqual(legals, result) def test_get_fen(self): result = sf.get_fen("chess", CHESS, []) self.assertEqual(result, CHESS) # incomplete FENs result = sf.get_fen("chess", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR", []) self.assertEqual(result, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - - 0 1") result = sf.get_fen("chess", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq -", []) self.assertEqual(result, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") result = sf.get_fen("chess", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 1 2", []) self.assertEqual(result, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w - - 1 2") # alternative piece symbols result = sf.get_fen("janggi", "rhea1aehr/4k4/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/4K4/RHEA1AEHR w - - 0 1", []) self.assertEqual(result, JANGGI) result = sf.get_fen("capablanca", CAPA, []) self.assertEqual(result, CAPA) result = sf.get_fen("xiangqi", XIANGQI, []) self.assertEqual(result, XIANGQI) result = sf.get_fen("seirawan", SEIRAWAN, []) self.assertEqual(result, SEIRAWAN) # test idempotence for S-Chess960 gating flags fen1 = sf.get_fen("seirawan", SEIRAWAN, [], True) fen2 = sf.get_fen("seirawan", fen1, [], True) self.assertEqual(fen1, fen2) fen = "rnab1kbcnr/ppppPppppp/10/4q5/10/10/PPPPP1PPPP/RNABQKBCNR[p] b KQkq - 0 3" result = sf.get_fen("capahouse", CAPA, ["f2f4", "e7e5", "f4e5", "e8e5", "P@e7"]) self.assertEqual(result, fen) fen0 = "reb1k2r/ppppqppp/2nb1n2/4p3/4P3/N1P2N2/PB1PQPPP/RE2KBHR[h] b KQkqac - 2 6" fen1 = "reb2rk1/ppppqppp/2nb1n2/4p3/4P3/N1P2N2/PB1PQPPP/RE2KBHR[h] w KQac - 3 7" result = sf.get_fen("seirawan", fen0, ["e8g8"]) self.assertEqual(result, fen1) result = sf.get_fen("chess", CHESS, [], True, False, False) self.assertEqual(result, CHESS960) # test O-O-O fen = "rbkqnrbn/pppppppp/8/8/8/8/PPPPPPPP/RBKQNRBN w AFaf - 0 1" moves = ["d2d4", "f7f5", "e1f3", "h8g6", "h1g3", "c7c6", "c2c3", "e7e6", "b1d3", "d7d5", "d1c2", "b8d6", "e2e3", "d8d7", "c1a1"] result = sf.get_fen("chess", fen, moves, True, False, False) self.assertEqual(result, "r1k1nrb1/pp1q2pp/2pbp1n1/3p1p2/3P4/2PBPNN1/PPQ2PPP/2KR1RB1 b fa - 2 8") # passing should not affect castling rights fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" result = sf.get_fen("passchess", fen, ["e1e1", "e8e8"]) self.assertEqual(result, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 2 2") # SFEN result = sf.get_fen("shogi", SHOGI, [], False, True) self.assertEqual(result, SHOGI_SFEN) # makruk FEN fen = "rnsmksnr/8/1pM~1pppp/p7/8/PPPP1PPP/8/RNSKMSNR b - - 0 3" result = sf.get_fen("makruk", MAKRUK, ["e3e4", "d6d5", "e4d5", "a6a5", "d5c6m"], False, False, True) self.assertEqual(result, fen) result = sf.get_fen("makruk", fen, [], False, False, True) self.assertEqual(result, fen) # makruk piece honor counting fen = "8/3k4/8/2K1S1P1/8/8/8/8 w - - 0 1" moves = ["g5g6m"] result = sf.get_fen("makruk", fen, moves, False, False, True) self.assertEqual(result, "8/3k4/6M~1/2K1S3/8/8/8/8 b - 88 8 1") fen = "8/2K3k1/5m2/4S1S1/8/8/8/8 w - 128 97 1" moves = ["e5f6"] result = sf.get_fen("makruk", fen, moves, False, False, True) self.assertEqual(result, "8/2K3k1/5S2/6S1/8/8/8/8 b - 44 8 1") # ignore count_started for piece honor counting fen = "8/3k4/8/2K1S1P1/8/8/8/8 w - - 0 1" moves = ["g5g6m"] result = sf.get_fen("makruk", fen, moves, False, False, True, -1) self.assertEqual(result, "8/3k4/6M~1/2K1S3/8/8/8/8 b - 88 8 1") fen = "8/2K3k1/5m2/4S1S1/8/8/8/8 w - 128 1 30" moves = ["e5f6"] result = sf.get_fen("makruk", fen, moves, False, False, True, 58) self.assertEqual(result, "8/2K3k1/5S2/6S1/8/8/8/8 b - 44 8 30") # makruk board honor counting fen = "3k4/2m5/8/4MP2/3KS3/8/8/8 w - - 0 1" moves = ["f5f6m"] result = sf.get_fen("makruk", fen, moves, False, False, True) self.assertEqual(result, "3k4/2m5/5M~2/4M3/3KS3/8/8/8 b - 128 0 1") fen = "3k4/2m5/5M~2/4M3/3KS3/8/8/8 w - 128 0 33" moves = ["d4d5"] result = sf.get_fen("makruk", fen, moves, False, False, True) self.assertEqual(result, "3k4/2m5/5M~2/3KM3/4S3/8/8/8 b - 128 1 33") fen = "3k4/2m5/5M~2/4M3/3KS3/8/8/8 w - 128 36 1" moves = ["d4d5"] result = sf.get_fen("makruk", fen, moves, False, False, True) self.assertEqual(result, "3k4/2m5/5M~2/3KM3/4S3/8/8/8 b - 128 37 1") fen = "3k4/2m5/5M~2/4M3/3KS3/8/8/8 w - 128 0 33" moves = ["d4d5"] result = sf.get_fen("makruk", fen, moves, False, False, True, -1) self.assertEqual(result, "3k4/2m5/5M~2/3KM3/4S3/8/8/8 b - 128 0 33") fen = "3k4/2m5/5M~2/4M3/3KS3/8/8/8 w - 128 7 33" moves = ["d4d5"] result = sf.get_fen("makruk", fen, moves, False, False, True, 58) self.assertEqual(result, "3k4/2m5/5M~2/3KM3/4S3/8/8/8 b - 128 8 33") # asean counting fen = "4k3/3r4/2K5/8/3R4/8/8/8 w - - 0 1" moves = ["d4d7"] result = sf.get_fen("asean", fen, moves, False, False, False) self.assertEqual(result, "4k3/3R4/2K5/8/8/8/8/8 b - 32 0 1") fen = "4k3/3r4/2K5/8/3R4/1P6/8/8 w - - 0 1" moves = ["d4d7"] result = sf.get_fen("asean", fen, moves, False, False, False) self.assertEqual(result, "4k3/3R4/2K5/8/8/1P6/8/8 b - - 0 1") fen = "8/2P1k3/2K5/8/8/8/8/8 w - - 0 1" moves = ["c7c8b"] result = sf.get_fen("asean", fen, moves, False, False, False) self.assertEqual(result, "2B5/4k3/2K5/8/8/8/8/8 b - 88 0 1") fen = "8/8/4K3/3Q4/1k1N4/5b2/8/8 w - - 0 1" moves = ["d4f3"] result = sf.get_fen("asean", fen, moves, False, False, False) self.assertEqual(result, "8/8/4K3/3Q4/1k6/5N2/8/8 b - 128 0 1") fen = "3Q4/4P3/4K3/3Q4/1k6/8/8/8 w - - 0 1" moves = ["e7e8q"] result = sf.get_fen("asean", fen, moves, False, False, False) self.assertEqual(result, "3QQ3/8/4K3/3Q4/1k6/8/8/8 b - - 0 1") def test_get_san(self): fen = "4k3/8/3R4/8/1R3R2/8/3R4/4K3 w - - 0 1" result = sf.get_san("chess", fen, "b4d4") self.assertEqual(result, "Rbd4") result = sf.get_san("chess", fen, "f4d4") self.assertEqual(result, "Rfd4") result = sf.get_san("chess", fen, "d2d4") self.assertEqual(result, "R2d4") result = sf.get_san("chess", fen, "d6d4") self.assertEqual(result, "R6d4") fen = "4k3/8/3R4/3P4/1RP1PR2/8/3R4/4K3 w - - 0 1" result = sf.get_san("chess", fen, "d2d4") self.assertEqual(result, "Rd4") fen = "1r2k3/P1P5/8/8/8/8/8/4K3 w - - 0 1" result = sf.get_san("chess", fen, "c7b8q") self.assertEqual(result, "cxb8=Q+") fen = "1r2k3/P1P5/8/8/8/8/8/4K3 w - - 0 1" result = sf.get_san("chess", fen, "c7b8q", False, sf.NOTATION_LAN) self.assertEqual(result, "c7xb8=Q+") result = sf.get_san("capablanca", CAPA, "e2e4") self.assertEqual(result, "e4") result = sf.get_san("capablanca", CAPA, "e2e4", False, sf.NOTATION_LAN) self.assertEqual(result, "e2-e4") result = sf.get_san("capablanca", CAPA, "h1i3") self.assertEqual(result, "Ci3") result = sf.get_san("sittuyin", SITTUYIN, "R@a1") self.assertEqual(result, "R@a1") fen = "3rr3/1kn3n1/1ss1p1pp/1pPpP3/6PP/p3KN2/2SSFN2/3R3R[] b - - 0 14" result = sf.get_san("sittuyin", fen, "c6c5") self.assertEqual(result, "Scxc5") fen = "7R/1r6/3k1np1/3s2N1/3s3P/4n3/6p1/2R3K1[] w - - 2 55" result = sf.get_san("sittuyin", fen, "h4h4f") self.assertEqual(result, "h4=F") fen = "k7/2K3P1/8/4P3/8/8/8/1R6[] w - - 0 1" result = sf.get_san("sittuyin", fen, "e5f6f") self.assertEqual(result, "e5f6=F") result = sf.get_san("shogi", SHOGI, "i3i4") self.assertEqual(result, "P-16") result = sf.get_san("shogi", SHOGI, "i3i4", False, sf.NOTATION_SHOGI_HOSKING) self.assertEqual(result, "P16") result = sf.get_san("shogi", SHOGI, "f1e2", False, sf.NOTATION_SHOGI_HOSKING) self.assertEqual(result, "G49-58") result = sf.get_san("shogi", SHOGI, "f1e2", False, sf.NOTATION_SHOGI_HODGES) self.assertEqual(result, "G4i-5h") result = sf.get_san("shogi", SHOGI, "f1e2", False, sf.NOTATION_SHOGI_HODGES_NUMBER) self.assertEqual(result, "G49-58") fen = "lnsgkgsnl/1r5b1/pppppp1pp/6p2/9/2P6/PP1PPPPPP/1B5R1/LNSGKGSNL w -" result = sf.get_san("shogi", fen, "b2h8", False, sf.NOTATION_SHOGI_HODGES) self.assertEqual(result, "Bx2b=") result = sf.get_san("shogi", fen, "b2h8+", False, sf.NOTATION_SHOGI_HODGES) self.assertEqual(result, "Bx2b+") fen = "lnsgkg1nl/1r5s1/pppppp1pp/6p2/9/2P6/PP1PPPPPP/7R1/LNSGKGSNL[Bb] w " result = sf.get_san("shogi", fen, "B@g7", False, sf.NOTATION_SHOGI_HODGES) self.assertEqual(result, "B*3c") result = sf.get_san("shogi", fen, "B@g7", False, sf.NOTATION_SHOGI_HODGES_NUMBER) self.assertEqual(result, "B*33") fen = "lnsgkg1nl/1r4s+B1/pppppp1pp/6p2/9/2P6/PP1PPPPPP/7R1/LNSGKGSNL[B] w " result = sf.get_san("shogi", fen, "h8g7", False, sf.NOTATION_SHOGI_HODGES) self.assertEqual(result, "+B-3c") fen = "lnk2gsnl/7b1/p1p+SGp1pp/6p2/1pP6/4P4/PP3PPPP/1S2G2R1/L2GK1bNL[PRppns] w " result = sf.get_san("shogi", fen, "d7d8", False, sf.NOTATION_SHOGI_HODGES) self.assertEqual(result, "+S-6b") result = sf.get_san("xiangqi", XIANGQI, "h1g3") self.assertEqual(result, "Hg3") result = sf.get_san("xiangqi", XIANGQI, "h1g3", False, sf.NOTATION_XIANGQI_WXF) self.assertEqual(result, "H2+3") result = sf.get_san("xiangqi", XIANGQI, "c1e3") self.assertEqual(result, "Ece3") result = sf.get_san("xiangqi", XIANGQI, "c1e3", False, sf.NOTATION_XIANGQI_WXF) self.assertEqual(result, "E7+5") result = sf.get_san("xiangqi", XIANGQI, "h3h10") self.assertEqual(result, "Cxh10") result = sf.get_san("xiangqi", XIANGQI, "h3h10", False, sf.NOTATION_XIANGQI_WXF) self.assertEqual(result, "C2+7") result = sf.get_san("xiangqi", XIANGQI, "h3h5") self.assertEqual(result, "Ch5") # WXF notation does not denote check or checkmate fen = "4k4/4a3R/9/9/9/9/9/9/4K4/9 w - - 0 1" result = sf.get_san("xiangqi", fen, "i9e9", False) self.assertEqual(result, "Rxe9+") result = sf.get_san("xiangqi", fen, "i9e9", False, sf.NOTATION_XIANGQI_WXF) self.assertEqual(result, "R1=5") result = sf.get_san("xiangqi", fen, "i9i10", False) self.assertEqual(result, "Ri10#") result = sf.get_san("xiangqi", fen, "i9i10", False, sf.NOTATION_XIANGQI_WXF) self.assertEqual(result, "R1+1") # skip disambiguation for elephants and advisors, but not for pieces that require it fen = "rnbakabnr/9/1c5c1/p1p1p1p1p/4P4/1NB6/P1P1P3P/1C1A3C1/9/RNBAK4 w - - 0 1" result = sf.get_san("xiangqi", fen, "c5e3", False, sf.NOTATION_XIANGQI_WXF) self.assertEqual(result, "E7-5") result = sf.get_san("xiangqi", fen, "d1e2", False, sf.NOTATION_XIANGQI_WXF) self.assertEqual(result, "A6+5") result = sf.get_san("xiangqi", fen, "b5c7", False, sf.NOTATION_XIANGQI_WXF) self.assertEqual(result, "H++7") result = sf.get_san("xiangqi", fen, "e6e7", False, sf.NOTATION_XIANGQI_WXF) self.assertEqual(result, "P++1") result = sf.get_san("xiangqi", fen, "e4e5", False, sf.NOTATION_XIANGQI_WXF) self.assertEqual(result, "P-+1") # Tandem pawns fen = "rnbakabnr/9/1c5c1/p1p1P1p1p/4P4/9/P3P3P/1C5C1/9/RNBAKABNR w - - 0 1" result = sf.get_san("xiangqi", fen, "e7d7", False, sf.NOTATION_XIANGQI_WXF) self.assertEqual(result, "15=6") result = sf.get_san("xiangqi", fen, "e6d6", False, sf.NOTATION_XIANGQI_WXF) self.assertEqual(result, "25=6") result = sf.get_san("xiangqi", fen, "e4e5", False, sf.NOTATION_XIANGQI_WXF) self.assertEqual(result, "35+1") # use tandem pawn notation for pair of tandem pawns fen = "5k3/9/3P5/3P1P1P1/5P3/9/9/9/9/4K4 w - - 0 1" result = sf.get_san("xiangqi", fen, "d7e7", False, sf.NOTATION_XIANGQI_WXF) self.assertEqual(result, "26=5") result = sf.get_san("xiangqi", fen, "f6e6", False, sf.NOTATION_XIANGQI_WXF) self.assertEqual(result, "24=5") fen = "1rb1ka2r/4a4/2ncb1nc1/p1p1p1p1p/9/2P6/P3PNP1P/2N1C2C1/9/R1BAKAB1R w - - 1 7" result = sf.get_san("xiangqi", fen, "c3e2") self.assertEqual(result, "Hce2") result = sf.get_san("xiangqi", fen, "c3d5") self.assertEqual(result, "Hd5") result = sf.get_san("janggi", JANGGI, "b1c3", False, sf.NOTATION_JANGGI) self.assertEqual(result, "H02-83") fen = "1b1aa2b1/5k3/3ncn3/1pp1pp3/5r2p/9/P1PPB1PPB/2N1CCN1c/9/R2AKAR2 w - - 19 17" result = sf.get_san("janggi", fen, "d1e2", False, sf.NOTATION_SAN) self.assertEqual(result, "Ade2") fen = "1Pbcka3/3nNn1c1/N2CaC3/1pB6/9/9/5P3/9/4K4/9 w - - 0 23" result = sf.get_san("janggi", fen, "f8f10", False, sf.NOTATION_SAN) self.assertEqual(result, "Cfxf10") fen = "rnsm1s1r/4n1k1/1ppppppp/p7/2PPP3/PP3PPP/4N2R/RNSKMS2 b - - 1 5" result = sf.get_san("makruk", fen, "f8f7") self.assertEqual(result, "Sf7") fen = "4k3/8/8/4S3/8/2S5/8/4K3 w - - 0 1" result = sf.get_san("makruk", fen, "e5d4") self.assertEqual(result, "Sed4") result = sf.get_san("makruk", fen, "c3d4") self.assertEqual(result, "Scd4") fen = "4k3/8/8/3S4/8/3S4/8/4K3 w - - 0 1" result = sf.get_san("makruk", fen, "d3d4") self.assertEqual(result, "Sd4") UCI_moves = ["e2e4", "e7e5", "g1f3", "b8c6h", "f1c4", "f8c5e"] SAN_moves = ["e4", "e5", "Nf3", "Nc6/H", "Bc4", "Bc5/E"] fen = SEIRAWAN for i, move in enumerate(UCI_moves): result = sf.get_san("seirawan", fen, move) self.assertEqual(result, SAN_moves[i]) fen = sf.get_fen("seirawan", SEIRAWAN, UCI_moves[:i + 1]) result = sf.get_san("seirawan", fen, "e1g1") self.assertEqual(result, "O-O") result = sf.get_san("seirawan", fen, "e1g1h") self.assertEqual(result, "O-O/He1") result = sf.get_san("seirawan", fen, "e1g1e") self.assertEqual(result, "O-O/Ee1") result = sf.get_san("seirawan", fen, "h1e1h") self.assertEqual(result, "O-O/Hh1") result = sf.get_san("seirawan", fen, "h1e1e") self.assertEqual(result, "O-O/Eh1") def test_get_san_moves(self): UCI_moves = ["e2e4", "e7e5", "g1f3", "b8c6h", "f1c4", "f8c5e"] SAN_moves = ["e4", "e5", "Nf3", "Nc6/H", "Bc4", "Bc5/E"] result = sf.get_san_moves("seirawan", SEIRAWAN, UCI_moves) self.assertEqual(result, SAN_moves) UCI_moves = ["c3c4", "g7g6", "b2h8"] SAN_moves = ["P-76", "P-34", "Bx22="] result = sf.get_san_moves("shogi", SHOGI, UCI_moves) self.assertEqual(result, SAN_moves) UCI_moves = ["h3e3", "h10g8", "h1g3", "c10e8", "a1a3", "i10h10"] SAN_moves = ["C2=5", "H8+7", "H2+3", "E3+5", "R9+2", "R9=8"] result = sf.get_san_moves("xiangqi", XIANGQI, UCI_moves, False, sf.NOTATION_XIANGQI_WXF) self.assertEqual(result, SAN_moves) UCI_moves = ["e2e4", "d7d5", "f1a6+", "d8d6"] SAN_moves = ["e4", "d5", "Ba6=A", "Qd6"] result = sf.get_san_moves("shogun", SHOGUN, UCI_moves) self.assertEqual(result, SAN_moves) def test_gives_check(self): result = sf.gives_check("capablanca", CAPA, []) self.assertFalse(result) result = sf.gives_check("capablanca", CAPA, ["e2e4"]) self.assertFalse(result) moves = ["g2g3", "d7d5", "a2a3", "c8h3"] result = sf.gives_check("capablanca", CAPA, moves) self.assertTrue(result) def test_game_result(self): result = sf.game_result("chess", CHESS, ["f2f3", "e7e5", "g2g4", "d8h4"]) self.assertEqual(result, -sf.VALUE_MATE) # shogi pawn drop mate result = sf.game_result("shogi", "lnsg3nk/1r2b1gs1/ppppppp1p/7N1/7p1/9/PPPPPPPP1/1B5R1/LNSGKGS1L[P] w 0 1", ["P@i8"]) self.assertEqual(result, sf.VALUE_MATE) # losers checkmate result = sf.game_result("losers", CHESS, ["f2f3", "e7e5", "g2g4", "d8h4"]) self.assertEqual(result, sf.VALUE_MATE) # suicide stalemate result = sf.game_result("suicide", "8/8/8/7p/7P/8/8/8 w - - 0 1", []) self.assertEqual(result, sf.VALUE_DRAW) result = sf.game_result("suicide", "8/8/8/7p/7P/7P/8/8 w - - 0 1", []) self.assertEqual(result, -sf.VALUE_MATE) result = sf.game_result("suicide", "8/8/8/7p/7P/8/8/n7 w - - 0 1", []) self.assertEqual(result, sf.VALUE_MATE) # atomic check- and stalemate # checkmate result = sf.game_result("atomic", "BQ6/Rk6/8/8/8/8/8/4K3 b - - 0 1", []) self.assertEqual(result, -sf.VALUE_MATE) # stalemate result = sf.game_result("atomic", "KQ6/Rk6/2B5/8/8/8/8/8 b - - 0 1", []) self.assertEqual(result, sf.VALUE_DRAW) def test_is_immediate_game_end(self): result = sf.is_immediate_game_end("capablanca", CAPA, []) self.assertFalse(result[0]) # bikjang (facing kings) moves = "e2e3 e9f9 h3d3 e7f7 i1i3 h10i8 i3h3 c10e7 h3h8 i10i9 h8b8 i9g9 d3f3 f9e9 f3f10 e7c10 f10c10 b10c8 c10g10 g9f9 b8c8 a10b10 b3f3 f9h9 a1a2 h9f9 a2d2 b10b9 d2d10 e9d10 c8c10 d10d9 f3f9 i8g9 f9b9 a7a6 g10g7 f7f6 e4e5 c7d7 g1e4 i7i6 e4b6 d9d8 c10c8 d8d9 b9g9 d7d6 b6e8 i6h6 e5e6 f6e6 c1e4 a6b6 e4b6 d6d5 c4c5 d9d10 e3d3 h6i6 c5c6 d5c5" result = sf.is_immediate_game_end("janggi", JANGGI, moves.split()) self.assertFalse(result[0]) moves = "e2e3 e9f9 h3d3 e7f7 i1i3 h10i8 i3h3 c10e7 h3h8 i10i9 h8b8 i9g9 d3f3 f9e9 f3f10 e7c10 f10c10 b10c8 c10g10 g9f9 b8c8 a10b10 b3f3 f9h9 a1a2 h9f9 a2d2 b10b9 d2d10 e9d10 c8c10 d10d9 f3f9 i8g9 f9b9 a7a6 g10g7 f7f6 e4e5 c7d7 g1e4 i7i6 e4b6 d9d8 c10c8 d8d9 b9g9 d7d6 b6e8 i6h6 e5e6 f6e6 c1e4 a6b6 e4b6 d6d5 c4c5 d9d10 e3d3 h6i6 c5c6 d5c5 d3d3" result = sf.is_immediate_game_end("janggi", JANGGI, moves.split()) self.assertTrue(result[0]) self.assertEqual(result[1], -sf.VALUE_MATE) def test_is_optional_game_end(self): result = sf.is_optional_game_end("capablanca", CAPA, []) self.assertFalse(result[0]) # sittuyin stalemate due to optional promotion result = sf.is_optional_game_end("sittuyin", "1k4PK/3r4/8/8/8/8/8/8[] w - - 0 1", []) self.assertTrue(result[0]) self.assertEqual(result[1], sf.VALUE_DRAW) def test_has_insufficient_material(self): for variant, positions in variant_positions.items(): for fen, expected_result in positions.items(): result = sf.has_insufficient_material(variant, fen, []) self.assertEqual(result, expected_result, "{}: {}".format(variant, fen)) def test_validate_fen(self): # valid for variant, positions in variant_positions.items(): for fen in positions: self.assertEqual(sf.validate_fen(fen, variant), sf.FEN_OK, "{}: {}".format(variant, fen)) # invalid for variant, positions in invalid_variant_positions.items(): for fen in positions: self.assertNotEqual(sf.validate_fen(fen, variant), sf.FEN_OK, "{}: {}".format(variant, fen)) # chess960 self.assertEqual(sf.validate_fen(CHESS960, "chess", True), sf.FEN_OK, "{}: {}".format(variant, fen)) self.assertEqual(sf.validate_fen("nrbqbkrn/pppppppp/8/8/8/8/PPPPPPPP/NRBQBKRN w BGbg - 0 1", "newzealand", True), sf.FEN_OK, "{}: {}".format(variant, fen)) # all variants starting positions for variant in sf.variants(): fen = sf.start_fen(variant) self.assertEqual(sf.validate_fen(fen, variant), sf.FEN_OK, "{}: {}".format(variant, fen)) if __name__ == '__main__': unittest.main(verbosity=2) Fairy-Stockfish-fairy_sf_14_0_1_xq/tests/000077500000000000000000000000001414571233100205015ustar00rootroot00000000000000Fairy-Stockfish-fairy_sf_14_0_1_xq/tests/instrumented.sh000077500000000000000000000063471414571233100235730ustar00rootroot00000000000000#!/bin/bash # check for errors under valgrind or sanitizers. error() { echo "instrumented testing failed on line $1" exit 1 } trap 'error ${LINENO}' ERR # define suitable post and prefixes for testing options case $1 in --valgrind) echo "valgrind testing started" prefix='' exeprefix='valgrind --error-exitcode=42 --errors-for-leak-kinds=all --leak-check=full' postfix='1>/dev/null' threads="1" ;; --valgrind-thread) echo "valgrind-thread testing started" prefix='' exeprefix='valgrind --fair-sched=try --error-exitcode=42' postfix='1>/dev/null' threads="2" ;; --sanitizer-undefined) echo "sanitizer-undefined testing started" prefix='!' exeprefix='' postfix='2>&1 | grep -A50 "runtime error:"' threads="1" ;; --sanitizer-thread) echo "sanitizer-thread testing started" prefix='!' exeprefix='' postfix='2>&1 | grep -A50 "WARNING: ThreadSanitizer:"' threads="2" cat << EOF > tsan.supp race:Stockfish::TTEntry::move race:Stockfish::TTEntry::depth race:Stockfish::TTEntry::bound race:Stockfish::TTEntry::save race:Stockfish::TTEntry::value race:Stockfish::TTEntry::eval race:Stockfish::TTEntry::is_pv race:Stockfish::TranspositionTable::probe race:Stockfish::TranspositionTable::hashfull EOF export TSAN_OPTIONS="suppressions=./tsan.supp" ;; *) echo "unknown testing started" prefix='' exeprefix='' postfix='' threads="1" ;; esac # simple command line testing for args in "eval" \ "go nodes 1000" \ "go depth 10" \ "go movetime 1000" \ "go wtime 8000 btime 8000 winc 500 binc 500" \ "bench 128 $threads 8 default depth" 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 10\n" expect "bestmove" send "quit\n" expect eof # return error code of the spawned program, useful for valgrind lassign [wait] pid spawnid os_error_flag value exit \$value EOF #download TB as needed if [ ! -d ../tests/syzygy ]; then curl -sL https://api.github.com/repos/niklasf/python-chess/tarball/9b9aa13f9f36d08aadfabff872882f4ab1494e95 | tar -xzf - mv niklasf-python-chess-9b9aa13 ../tests/syzygy fi cat << EOF > syzygy.exp set timeout 600 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" Fairy-Stockfish-fairy_sf_14_0_1_xq/tests/js/000077500000000000000000000000001414571233100211155ustar00rootroot00000000000000Fairy-Stockfish-fairy_sf_14_0_1_xq/tests/js/README.md000066400000000000000000000203201414571233100223710ustar00rootroot00000000000000

ffish.js

Package Downloads Version

Package-ES6 Downloads-ES6 Version-ES6

The package **ffish.js** is a high performance WebAssembly chess variant library based on [_Fairy-Stockfish_](https://github.com/ianfab/Fairy-Stockfish). It is available as a [standard module](https://www.npmjs.com/package/ffish), as an [ES6 module](https://www.npmjs.com/package/ffish-es6) and aims to have a syntax similar to [python-chess](https://python-chess.readthedocs.io/en/latest/index.html). ## Install instructions ### Standard module ```bash npm install ffish ``` ### ES6 module ```bash npm install ffish-es6 ``` ## Examples Load the API in JavaScript: ### Standard module ```javascript const ffish = require('ffish'); ``` ### ES6 module ```javascript import Module from 'ffish-es6'; let ffish = null; new Module().then(loadedModule => { ffish = loadedModule; console.log(`initialized ${ffish} ${loadedModule}`); } }); ``` ### Available variants Show all available variants supported by _Fairy-Stockfish_ and **ffish.js**. ```javascript ffish.variants() ``` ``` 3check 3check-crazyhouse 5check ai-wok almost amazon anti-losalamos antichess\ armageddon asean ataxx atomic breakthrough bughouse cambodian capablanca\ capahouse caparandom centaur cfour chancellor chaturanga chess chessgi chigorin\ clobber clobber10 codrus coffeehouse courier crazyhouse dobutsu embassy euroshogi\ extinction fairy fischerandom flipello flipersi gardner gemini giveaway gorogoro\ gothic grand grandhouse hoppelpoppel horde indiangreat janggi janggicasual\ janggihouse janggimodern janggitraditional janus jesonmor judkins karouk kinglet\ kingofthehill knightmate koedem kyotoshogi loop losalamos losers makpong makruk\ makrukhouse manchu micro mini minishogi minixiangqi modern newzealand nocastle\ nocheckatomic normal orda pawnsonly peasant placement pocketknight racingkings\ seirawan semitorpedo shako shatar shatranj shogi shogun shouse sittuyin suicide\ supply threekings tictactoe upsidedown weak xiangqi xiangqihouse ``` ### Board object Create a new variant board from its default starting position. The event `onRuntimeInitialized` ensures that the wasm file was properly loaded. ```javascript ffish['onRuntimeInitialized'] = () => { let board = new ffish.Board("chess"); } ``` Set a custom fen position including fen validiation: ```javascript fen = "rnb1kbnr/ppp1pppp/8/3q4/8/8/PPPP1PPP/RNBQKBNR w KQkq - 0 3"; if (ffish.validateFen(fen) == 1) { // ffish.validateFen(fen) can return different error codes, it returns 1 for FEN_OK board.setFen(fen); } else { console.error(`Fen couldn't be parsed.`); } ``` Alternatively, you can initialize a board with a custom FEN directly: ```javascript let board2 = new ffish.Board("chess", "rnb1kbnr/ppp1pppp/8/3q4/8/8/PPPP1PPP/RNBQKBNR w KQkq - 0 3"); ``` ### ASCII board You can show an ASCII representation of the board using the `toString()` method ```javascript let board = new ffish.Board("chess", "rnb1kbnr/ppp1pppp/8/3q4/8/8/PPPP1PPP/RNBQKBNR w KQkq - 0 3"); console.log(board.toString()) ``` ``` r n b . k b n r p p p . p p p p . . . . . . . . . . . q . . . . . . . . . . . . . . . . . . . . P P P P . P P P R N B Q K B N R ``` or a more detailed representation using `.toVerboseString()`. ```javascript let board = new ffish.Board("chess", "rnb1kbnr/ppp1pppp/8/3q4/8/8/PPPP1PPP/RNBQKBNR w KQkq - 0 3"); console.log(board.toVerboseString()) ``` ``` +---+---+---+---+---+---+---+---+ | r | n | b | | k | b | n | r |8 +---+---+---+---+---+---+---+---+ | p | p | p | | p | p | p | p |7 +---+---+---+---+---+---+---+---+ | | | | | | | | |6 +---+---+---+---+---+---+---+---+ | | | | q | | | | |5 +---+---+---+---+---+---+---+---+ | | | | | | | | |4 +---+---+---+---+---+---+---+---+ | | | | | | | | |3 +---+---+---+---+---+---+---+---+ | P | P | P | P | | P | P | P |2 +---+---+---+---+---+---+---+---+ | R | N | B | Q | K | B | N | R |1 * +---+---+---+---+---+---+---+---+ a b c d e f g h Fen: rnb1kbnr/ppp1pppp/8/3q4/8/8/PPPP1PPP/RNBQKBNR w KQkq - 0 3 Sfen: rnb1kbnr/ppp1pppp/8/3q4/8/8/PPPP1PPP/RNBQKBNR b - 5 Key: 39B6F80E84D75BFB Checkers: ``` ## Move generation and application Add a new move: ```javascript board.push("g2g4"); ``` Generate all legal moves in UCI and SAN notation: ```javascript let legalMoves = board.legalMoves().split(" "); let legalMovesSan = board.legalMovesSan().split(" "); for (var i = 0; i < legalMovesSan.length; i++) { console.log(`${i}: ${legalMoves[i]}, ${legalMovesSan[i]}`) } ``` ## Memory management Unfortunately, it is impossible for Emscripten to call the destructor on C++ objects. Therefore, you need to call `.delete()` to free the heap memory of an object. ```javascript board.delete(); ``` ## PGN parsing Read a string from a file and parse it as a single PGN game. ```javascript fs = require('fs'); let pgnFilePath = "data/pgn/kasparov-deep-blue-1997.pgn" fs.readFile(pgnFilePath, 'utf8', function (err,data) { if (err) { return console.log(err); } game = ffish.readGamePGN(data); console.log(game.headerKeys()); console.log(game.headers("White")); const mainlineMoves = game.mainlineMoves().split(" "); let board = new ffish.Board(game.headers("Variant").toLowerCase()); for (let idx = 0; idx < mainlineMoves.length; ++idx) { board.push(mainlineMoves[idx]); } // or use board.pushMoves(game.mainlineMoves()); to push all moves at once let finalFen = board.fen(); board.delete(); game.delete(); } ``` ## Custom variants Fairy-Stockfish also allows defining custom variants by loading a configuration file. See e.g. the configuration for **connect4**, **tictactoe** or **janggihouse** in [variants.ini](https://github.com/ianfab/Fairy-Stockfish/blob/master/src/variants.ini). ```javascript fs = require('fs'); let configFilePath = './variants.ini'; fs.readFile(configFilePath, 'utf8', function (err,data) { if (err) { return console.log(err); } ffish.loadVariantConfig(data) let board = new ffish.Board("tictactoe"); board.delete(); }); ``` ## Remaining features For an example of each available function see [test.js](https://github.com/ianfab/Fairy-Stockfish/blob/master/tests/js/test.js). ## Build instuctions It is built using emscripten/Embind from C++ source code. * https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html If you want to disable variants with a board greater than 8x8, you can add the flag `largeboards=no`. The pre-compiled wasm binary is built with `largeboards=yes`. It is recommended to set `debug=yes` before running tests. ### Compile as standard module ```bash cd src make -f Makefile_js build ``` ### Compile as ES6/ES2015 module Some environments such as [vue-js](https://vuejs.org/) may require the library to be exported as a ES6/ES2015 module. ```bash cd src make -f Makefile_js build es6=yes ``` Make sure that the wasm file is in the `public` directory. Reference: [emscripten/#10114](https://github.com/emscripten-core/emscripten/issues/10114) ## Instructions to run the tests ```bash npm install npm test ``` ## Instructions to run the example server ```bash npm install ``` ```bash node index.js ``` ## Example Projects ### ffish-test A simple toy website which demonstrates the core functionality of ffish.js and [chessgroundx](https://github.com/gbtami/chessgroundx). Source code: https://github.com/thearst3rd/ffish-test See it deployed at: https://ffish-test.herokuapp.com Fairy-Stockfish-fairy_sf_14_0_1_xq/tests/js/index.js000066400000000000000000000055241414571233100225700ustar00rootroot00000000000000const express = require('express') const ffish = require('./ffish.js'); const { PerformanceObserver, performance } = require('perf_hooks'); const { Chess } = require('chess.js') const { Crazyhouse } = require('crazyhouse.js') const app = express(); app.get('/', (req, res) => { let board = new ffish.Board("chess"); //, "rnb1kbnr/ppp1pppp/8/3q4/8/8/PPPP1PPP/RNBQKBNR w KQkq - 0 3", false); let legalMoves = board.legalMoves(); let it = 1000; console.log("Standard Chess") console.log("==================") var t0 = performance.now() for (let i = 0; i < it; ++i) { legalMoves = board.legalMoves().split(" "); } var t1 = performance.now() console.log(`Call to board.legalMoves()+legalMoves.split(" ") took ${(t1 - t0).toFixed(2)} milliseconds.`) var t0 = performance.now() for (let i = 0; i < it; ++i) { legalMoves = board.legalMovesSan().split(" ") } var t1 = performance.now() console.log(`board.legalMovesSan().split(" ").length: ${legalMoves.length}`) console.log(`Call to board.legalMovesSan()+legalMoves.split(" ") took ${(t1 - t0).toFixed(2)} milliseconds.`) // pass in a FEN string to load a particular position const chess = new Chess( "rnb1kbnr/ppp1pppp/8/3q4/8/8/PPPP1PPP/RNBQKBNR w KQkq - 0 3" ) var t0 = performance.now() for (let i = 0; i < it; ++i) { legalMoves = chess.moves() } var t1 = performance.now() console.log(`chess.moves().length: ${legalMoves.length}`) console.log(`Call to chess.moves() took ${(t1 - t0).toFixed(2)} milliseconds.`) console.log("Crazyhouse") console.log("===========") let crazyhouseFen = "rnb1kb1r/ppp2ppp/4pn2/8/3P4/2N2Q2/PPP2PPP/R1B1KB1R/QPnp b KQkq - 0 6"; board = new ffish.Board("crazyhouse", crazyhouseFen); var t0 = performance.now() for (let i = 0; i < it; ++i) { legalMoves = board.legalMovesSan().split(" ") } var t1 = performance.now() console.log(`board.legalMoves().split(" ").length: ${legalMoves.length}`) console.log(`Call to board.legalMoves() took ${(t1 - t0).toFixed(2)} milliseconds.`) cz_moves = ["e4", "d5", "exd5", "Qxd5", "Nf3", "Nf6", "Nc3", "e6", "d4", "Qxf3", "Qxf3"] // pass in a FEN string to load a particular position const crazyhouse = new Crazyhouse() for (let idx = 0; idx < cz_moves.length; ++idx) { crazyhouse.move(cz_moves[idx]) } var t0 = performance.now() for (let i = 0; i < it; ++i) { legalMoves = crazyhouse.moves() } var t1 = performance.now() console.log(`crazyhouse.moves().length: ${legalMoves.length}`) console.log(`Call to crazyhouse.moves() took ${(t1 - t0).toFixed(2)} milliseconds.`) let legalMovesSan = board.legalMovesSan().split(" "); for (var i = 0; i < legalMovesSan.length; i++) { console.log(`${i}: ${legalMoves[i]}, ${legalMovesSan[i]}`); } console.log(board.fen()); res.send(String("Test server of ffish.js")); }); app.listen(8000, () => { console.log('Test server of ffish.js listening on port 8000.') console.log('http://127.0.0.1:8000/') }); Fairy-Stockfish-fairy_sf_14_0_1_xq/tests/js/package.json000066400000000000000000000016761414571233100234150ustar00rootroot00000000000000{ "name": "ffish", "version": "0.6.3", "description": "A high performance WebAssembly chess variant library based on Fairy-Stockfish", "main": "ffish.js", "scripts": { "test": "mocha --timeout 80000", "dev": "node index" }, "author": [ { "name": "Fabian Fichter", "url": "https://github.com/ianfab" }, { "name": "Johannes Czech", "url": "https://github.com/QueensGambit" } ], "repository": { "type": "git", "url": "git+https://github.com/ianfab/Fairy-Stockfish.git" }, "keywords": ["wasm", "library", "chess-variants", "fairy-stockfish"], "license": "GPL-3.0", "homepage": "https://github.com/ianfab/Fairy-Stockfish/tree/master/tests/js#readme", "dependencies": {}, "devDependencies": { "chai": "^4.2.0", "chess": "^0.4.3", "chess.js": "^0.11.0", "crazyhouse.js": "0.0.8", "express": "^4.17.1", "mocha": "^8.0.1", "performance": "^1.4.0" } } Fairy-Stockfish-fairy_sf_14_0_1_xq/tests/js/test.js000066400000000000000000001144441414571233100224420ustar00rootroot00000000000000before(() => { chai = require('chai'); return new Promise((resolve) => { pgnDir = __dirname + '/../pgn/'; srcDir = __dirname + '/../../src/'; ffish = require('./ffish.js'); WHITE = true; BLACK = false; ffish['onRuntimeInitialized'] = () => { resolve(); } }); }); describe('ffish.loadVariantConfig(config)', function () { it("it loads a custom variant configuration from a string", () => { fs = require('fs'); let configFilePath = srcDir + 'variants.ini'; fs.readFile(configFilePath, 'utf8', function (err,data) { if (err) { return console.log(err); } ffish.loadVariantConfig(data) let board = new ffish.Board("tictactoe"); chai.expect(board.fen()).to.equal("3/3/3[PPPPPpppp] w - - 0 1"); board.delete(); }); }); }); describe('Board()', function () { it("it creates a chess board from the default position", () => { const board = new ffish.Board(); chai.expect(board.fen()).to.equal("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); chai.expect(board.is960()).to.equal(false); board.delete(); }); }); describe('Board(uciVariant) ', function () { it("it creates a board object from a given UCI-variant", () => { const board = new ffish.Board("chess"); chai.expect(board.fen()).to.equal("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); chai.expect(board.is960()).to.equal(false); board.delete(); }); }); describe('Board(uciVariant) with large board', function () { it("it creates a large-board object from a given UCI-variant", () => { const board = new ffish.Board("xiangqi"); chai.expect(board.fen()).to.equal("rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1"); chai.expect(board.is960()).to.equal(false); board.delete(); }); }); describe('Board(uciVariant, fen) ', function () { it("it creates a board object for a given UCI-variant with a given FEN", () => { const board = new ffish.Board("crazyhouse", "rnbqkb1r/pp3ppp/5p2/2pp4/8/5N2/PPPP1PPP/RNBQKB1R/Np w KQkq - 0 5"); chai.expect(board.fen()).to.equal("rnbqkb1r/pp3ppp/5p2/2pp4/8/5N2/PPPP1PPP/RNBQKB1R[Np] w KQkq - 0 5"); chai.expect(board.is960()).to.equal(false); board.delete(); }); }); describe('Board(uciVariant, fen, is960)', function () { it("it creates a board object for a given UCI-variant with a given FEN and is960 identifier", () => { const board = new ffish.Board("chess", "rnknb1rq/pp2ppbp/3p2p1/2p5/4PP2/2N1N1P1/PPPP3P/R1K1BBRQ b KQkq - 1 5", true); chai.expect(board.fen()).to.equal("rnknb1rq/pp2ppbp/3p2p1/2p5/4PP2/2N1N1P1/PPPP3P/R1K1BBRQ b GAga - 1 5"); chai.expect(board.is960()).to.equal(true); board.delete(); }); }); describe('board.legalMoves()', function () { it("it returns all legal moves in uci notation as a concatenated string", () => { const crazyhouseBoard = new ffish.Board("crazyhouse", "r1b3nr/pppp1kpp/2n5/2b1p3/4P3/2N5/PPPP1PPP/R1B1K1NR/QPbq w KQ - 0 7"); const expectedMovesCrazyhouse = 'a2a3 b2b3 d2d3 f2f3 g2g3 h2h3 a2a4 b2b4 d2d4 f2f4 g2g4 h2h4 c3b1 c3d1 c3e2 c3a4 c3b5 c3d5' + ' g1e2 g1f3 g1h3 a1b1 P@e2 P@a3 P@b3 P@d3 P@e3 P@f3 P@g3 P@h3 P@a4 P@b4 P@c4 P@d4 P@f4 P@g4 P@h4 P@a5 P@b5' + ' P@d5 P@f5 P@g5 P@h5 P@a6 P@b6 P@d6 P@e6 P@f6 P@g6 P@h6 P@e7 Q@b1 Q@d1 Q@f1 Q@e2 Q@a3 Q@b3 Q@d3 Q@e3 Q@f3 ' + 'Q@g3 Q@h3 Q@a4 Q@b4 Q@c4 Q@d4 Q@f4 Q@g4 Q@h4 Q@a5 Q@b5 Q@d5 Q@f5 Q@g5 Q@h5 Q@a6 Q@b6 Q@d6 Q@e6 Q@f6 Q@g6' + ' Q@h6 Q@e7 Q@b8 Q@d8 Q@e8 Q@f8 e1d1 e1f1 e1e2'; chai.expect(crazyhouseBoard.legalMoves().split(' ').sort().join()).to.equal(expectedMovesCrazyhouse.split(' ').sort().join()); crazyhouseBoard.delete(); // test atomic(960) castling rights const atomicBoard = new ffish.Board("atomic", "b1rkr1q1/pp3p2/n1p4p/6p1/1P2P1PP/8/PbR2P1Q/BN1KRN1B b Eec - 0 10", true); const expectedMovesAtomic = 'c6c5 h6h5 b7b6 f7f6 b7b5 f7f5 g5h4 a6b4 a6c5 a6c7 a6b8 b2a1 b2c1 b2a3 b2c3 b2d4 b2e5 b2f6 b2g7' + ' b2h8 c8c7 c8b8 e8e4 e8e5 e8e6 e8e7 e8f8 g8g6 g8g7 g8h7 g8f8 g8h8 d8c8 d8d7 d8e7'; chai.expect(atomicBoard.legalMoves().split(' ').sort().join()).to.equal(expectedMovesAtomic.split(' ').sort().join()); atomicBoard.delete(); }); }); describe('board.legalMovesSan()', function () { it("it returns all legal moves in SAN notation as a concatenated string", () => { const board = new ffish.Board("crazyhouse", "r1b3nr/pppp1kpp/2n5/2b1p3/4P3/2N5/PPPP1PPP/R1B1K1NR/QPbq w KQ - 0 7"); const expectedMoves = 'a3 b3 d3 f3 g3 h3 a4 b4 d4 f4 g4 h4 Nb1 Nd1 Nce2 Na4 Nb5 Nd5 Nge2 Nf3 Nh3 Rb1 P@e2 P@a3' + ' P@b3 P@d3 P@e3 P@f3 P@g3 P@h3 P@a4 P@b4 P@c4 P@d4 P@f4 P@g4 P@h4 P@a5 P@b5 P@d5 P@f5 P@g5 P@h5 P@a6 P@b6' + ' P@d6 P@e6+ P@f6 P@g6+ P@h6 P@e7 Q@b1 Q@d1 Q@f1 Q@e2 Q@a3 Q@b3+ Q@d3 Q@e3 Q@f3+ Q@g3 Q@h3 Q@a4 Q@b4 Q@c4+' + ' Q@d4 Q@f4+ Q@g4 Q@h4 Q@a5 Q@b5 Q@d5+ Q@f5+ Q@g5 Q@h5+ Q@a6 Q@b6 Q@d6 Q@e6+ Q@f6+ Q@g6+ Q@h6 Q@e7+ Q@b8' + ' Q@d8 Q@e8+ Q@f8+ Kd1 Kf1 Ke2'; chai.expect(board.legalMovesSan().split(' ').sort().join()).to.equal(expectedMoves.split(' ').sort().join()); board.delete(); }); }); describe('board.numberLegalMoves()', function () { it("it returns all legal moves in uci notation as a concatenated string", () => { const board = new ffish.Board("crazyhouse", "r1b3nr/pppp1kpp/2n5/2b1p3/4P3/2N5/PPPP1PPP/R1B1K1NR/QPbq w KQ - 0 7"); chai.expect(board.numberLegalMoves()).to.equal(90); board.delete(); const yariboard = new ffish.Board("yarishogi"); chai.expect(yariboard.numberLegalMoves()).to.equal(20); yariboard.delete(); }); }); describe('board.push(uciMove)', function () { it("it pushes a move in uci notation to the board", () => { let board = new ffish.Board(); board.push("e2e4"); board.push("e7e5"); board.push("g1f3"); chai.expect(board.fen()).to.equal("rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2"); chai.expect(board.push("q2q7")).to.equal(false); chai.expect(board.fen()).to.equal("rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2"); board.delete(); }); }); describe('board.pushSan(sanMove)', function () { it("it pushes a move in san notation to the board", () => { let board = new ffish.Board(); board.pushSan("e4"); board.pushSan("e5"); board.pushSan("Nf3"); chai.expect(board.fen()).to.equal("rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2"); chai.expect(board.pushSan("Nf3")).to.equal(false); chai.expect(board.fen()).to.equal("rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2"); board.delete(); }); }); describe('board.pushSan(sanMove, notation)', function () { it("it pushes a move in san notation to the board", () => { let board = new ffish.Board(); board.pushSan("e4", ffish.Notation.SAN); board.pushSan("e5", ffish.Notation.SAN); board.pushSan("Nf3", ffish.Notation.SAN); chai.expect(board.fen()).to.equal("rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2"); board.delete(); }); }); describe('board.pop()', function () { it("it undos the last move", () => { let board = new ffish.Board(); board.push("e2e4"); board.push("e7e5"); board.pop(); board.push("e7e5"); board.push("g1f3"); board.push("b8c6"); board.push("f1b5"); board.pop(); board.pop(); chai.expect(board.fen()).to.equal("rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2"); board.delete(); }); }); describe('board.reset()', function () { it("it resets the board to its starting position", () => { let board = new ffish.Board(); board.pushSan("e4"); board.pushSan("e5"); board.pushSan("Nf3"); board.reset(); chai.expect(board.fen()).to.equal("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); board.delete(); }); }); describe('board.is960()', function () { it("it checks if the board originates from a 960 position", () => { let board = new ffish.Board(); chai.expect(board.is960()).to.equal(false); const board2 = new ffish.Board("chess", "rnknb1rq/pp2ppbp/3p2p1/2p5/4PP2/2N1N1P1/PPPP3P/R1K1BBRQ b KQkq - 1 5", true); chai.expect(board2.is960()).to.equal(true); board.delete(); }); }); describe('board.fen()', function () { it("it returns the current position in fen format", () => { let board = new ffish.Board(); chai.expect(board.fen()).to.equal("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); board.delete(); }); }); describe('board.fen(showPromoted, countStarted)', function () { it("it returns the current position in fen format. showPromoted makes promoted pieces always followed by the symbol ~ regardless of variant. countStarted overwrites the start of makruk's board honor counting.", () => { let board = new ffish.Board("makruk", "8/6ks/3M~2r1/2K1M3/8/3R4/8/8 w - 128 18 50"); chai.expect(board.fen(true, 0)).to.equal("8/6ks/3M~2r1/2K1M3/8/3R4/8/8 w - 128 18 50"); chai.expect(board.fen(true, -1)).to.equal("8/6ks/3M~2r1/2K1M3/8/3R4/8/8 w - 128 0 50"); chai.expect(board.fen(true, 89)).to.equal("8/6ks/3M~2r1/2K1M3/8/3R4/8/8 w - 128 10 50"); }); }); describe('board.setFen(fen)', function () { it("it sets a custom position via fen", () => { let board = new ffish.Board(); board.setFen("r1bqkbnr/ppp2ppp/2np4/1B6/3NP3/8/PPP2PPP/RNBQK2R b KQkq - 0 5"); chai.expect(board.fen()).to.equal("r1bqkbnr/ppp2ppp/2np4/1B6/3NP3/8/PPP2PPP/RNBQK2R b KQkq - 0 5"); board.delete(); }); }); describe('board.sanMove(uciMove)', function () { it("it converts an uci move into san", () => { const board = new ffish.Board(); const san = board.sanMove("g1f3"); chai.expect(san).to.equal("Nf3"); board.delete(); }); }); describe('board.sanMove(ffish.Notation)', function () { it("it converts an uci move into san using a given notation", () => { const board = new ffish.Board(); chai.expect(board.sanMove("g1f3", ffish.Notation.DEFAULT)).to.equal("Nf3"); chai.expect(board.sanMove("g1f3", ffish.Notation.SAN)).to.equal("Nf3"); chai.expect(board.sanMove("g1f3", ffish.Notation.LAN)).to.equal("Ng1-f3"); chai.expect(board.sanMove("g1f3", ffish.Notation.SHOGI_HOSKING)).to.equal("N36"); chai.expect(board.sanMove("g1f3", ffish.Notation.SHOGI_HODGES)).to.equal("N-3f"); chai.expect(board.sanMove("g1f3", ffish.Notation.SHOGI_HODGES_NUMBER)).to.equal("N-36"); chai.expect(board.sanMove("g1f3", ffish.Notation.JANGGI)).to.equal("N87-66"); chai.expect(board.sanMove("g1f3", ffish.Notation.XIANGQI_WXF)).to.equal("N2+3"); board.delete(); }); }); describe('board.variationSan(uciMoves)', function () { it("it converts a list of uci moves into san notation. The board will not changed by this method.", () => { let board = new ffish.Board(); board.push("e2e4") const sanMoves = board.variationSan("e7e5 g1f3 b8c6 f1c4"); chai.expect(sanMoves).to.equal("1...e5 2. Nf3 Nc6 3. Bc4"); const sanMovesInvalid = board.variationSan("e7e5 g1f3 b8c6 f1c7"); chai.expect(sanMovesInvalid).to.equal(""); board.delete(); }); }); describe('board.variationSan(uciMoves, notation)', function () { it("it converts a list of uci moves into san notation given a notation format", () => { let board = new ffish.Board(); board.push("e2e4") const sanMoves = board.variationSan("e7e5 g1f3 b8c6 f1c4", ffish.Notation.LAN); chai.expect(sanMoves).to.equal("1...e7-e5 2. Ng1-f3 Nb8-c6 3. Bf1-c4"); board.delete(); }); }); describe('board.variationSan(uciMoves, notation, moveNumbers)', function () { it("it converts a list of uci moves into san notation given a notation format and optionally disabling move numbers", () => { let board = new ffish.Board(); board.push("e2e4") const sanMoves = board.variationSan("e7e5 g1f3 b8c6 f1c4", ffish.Notation.SAN, false); chai.expect(sanMoves).to.equal("e5 Nf3 Nc6 Bc4"); board.delete(); }); }); describe('board.turn()', function () { it("it returns the side to move", () => { let board = new ffish.Board(); chai.expect(board.turn()).to.equal(true); board.push("e2e4"); chai.expect(board.turn()).to.equal(false); board.delete(); }); }); describe('board.fullmoveNumber()', function () { it("it returns the move number starting with 1 and is increment after each move of the 2nd player", () => { let board = new ffish.Board(); chai.expect(board.fullmoveNumber()).to.equal(1); board.push("e2e4"); chai.expect(board.fullmoveNumber()).to.equal(1); board.push("e7e5"); board.push("g1f3"); board.push("g8f6"); board.push("f3e5"); chai.expect(board.fullmoveNumber()).to.equal(3); board.delete(); }); }); describe('board.halfmoveClock()', function () { it("it returns the halfmoveClock / 50-move-rule-counter", () => { let board = new ffish.Board(); chai.expect(board.halfmoveClock()).to.equal(0); board.push("e2e4"); board.push("e7e5"); chai.expect(board.halfmoveClock()).to.equal(0); board.push("g1f3"); board.push("g8f6"); chai.expect(board.halfmoveClock()).to.equal(2); board.push("f3e5"); chai.expect(board.halfmoveClock()).to.equal(0); board.delete(); }); }); describe('board.gamePly()', function () { it("it returns the current game ply", () => { let board = new ffish.Board(); chai.expect(board.gamePly()).to.equal(0); board.push("e2e4"); chai.expect(board.gamePly()).to.equal(1); board.push("e7e5"); board.push("g1f3"); board.push("g8f6"); board.push("f3e5"); chai.expect(board.gamePly()).to.equal(5); board.delete(); }); }); describe('board.hasInsufficientMaterial(side)', function () { it("it returns if the given side has insufficient mating material", () => { let board = new ffish.Board(); chai.expect(board.hasInsufficientMaterial(true)).to.equal(false); chai.expect(board.hasInsufficientMaterial(false)).to.equal(false); board.setFen("8/5k2/8/8/8/2K5/6R1/8 w - - 0 1"); chai.expect(board.hasInsufficientMaterial(true)).to.equal(false); chai.expect(board.hasInsufficientMaterial(false)).to.equal(true); board.setFen("8/5k2/8/8/8/2K5/6q1/8 w - - 0 1"); chai.expect(board.hasInsufficientMaterial(true)).to.equal(true); chai.expect(board.hasInsufficientMaterial(false)).to.equal(false); board.setFen("8/5k2/8/8/8/2K5/6B1/8 w - - 0 1"); chai.expect(board.hasInsufficientMaterial(true)).to.equal(true); chai.expect(board.hasInsufficientMaterial(false)).to.equal(true); board.delete(); }); }); describe('board.isInsufficientMaterial()', function () { it("it returns if the game is drawn due to insufficient material", () => { let board = new ffish.Board(); chai.expect(board.isInsufficientMaterial()).to.equal(false); board.setFen("8/5k2/8/8/8/2K5/6R1/8 w - - 0 1"); chai.expect(board.isInsufficientMaterial()).to.equal(false); board.setFen("8/5k2/8/8/8/2K5/6q1/8 w - - 0 1"); chai.expect(board.isInsufficientMaterial()).to.equal(false); board.setFen("8/5k2/8/8/8/2K5/6B1/8 w - - 0 1"); chai.expect(board.isInsufficientMaterial()).to.equal(true); board.delete(); }); }); describe('board.isGameOver()', function () { it("it checks if the game is over", () => { // No legal moves let board = new ffish.Board(); chai.expect(board.isGameOver()).to.equal(false); board.setFen("r1bqkb1r/pppp1ppp/2n2n2/4p2Q/2B1P3/8/PPPP1PPP/RNB1K1NR w KQkq - 4 4"); board.pushSan("Qxf7#"); chai.expect(board.isGameOver()).to.equal(true); // Insufficient material board.setFen("3Rk3/8/8/8/8/8/2N5/3K4 b - - 0 1"); chai.expect(board.isGameOver()).to.equal(false); board.pushSan("Kxd8"); chai.expect(board.isGameOver()).to.equal(true); // Optional draw claimed board.reset(); board.pushSanMoves("Nf3 Nc6 Ng1 Nb8 Nf3 Nc6 Ng1"); chai.expect(board.isGameOver(false)).to.equal(false); chai.expect(board.isGameOver(true)).to.equal(false); board.pushSan("Nb8"); chai.expect(board.isGameOver(false)).to.equal(false); chai.expect(board.isGameOver(true)).to.equal(true); board.delete(); }); }); describe('board.result()', function () { it("it returns a string representing the winner of the game", () => { // Scholar's mate (win for white) let board = new ffish.Board(); chai.expect(board.result()).to.equal("*"); board.pushSanMoves("e4 e5 Bc4 Nc6 Qh5 Nf6"); chai.expect(board.result()).to.equal("*"); board.pushSan("Qxf7#"); chai.expect(board.result()).to.equal("1-0"); // Fool's mate (win for black) board.reset(); board.pushSanMoves("f3 e5 g4"); chai.expect(board.result()).to.equal("*"); board.pushSan("Qh4#"); chai.expect(board.result()).to.equal("0-1"); // Stalemate board.setFen("2Q2bnr/4p1pq/5pkr/7p/7P/4P3/PPPP1PP1/RNB1KBNR w KQ - 1 10"); chai.expect(board.result()).to.equal("*"); board.pushSan("Qe6"); chai.expect(board.result()).to.equal("1/2-1/2"); // Draw claimed by n-fold repetition board.reset(); board.pushSanMoves("Nf3 Nc6 Ng1 Nb8 Nf3 Nc6 Ng1"); chai.expect(board.result(false)).to.equal("*"); chai.expect(board.result(true)).to.equal("*"); board.pushSan("Nb8"); chai.expect(board.result(false)).to.equal("*"); chai.expect(board.result(true)).to.equal("1/2-1/2"); // Draw claimed by n-move rule board.setFen("rnbqkbn1/ppppppp1/6r1/7p/2R4P/8/PPPPPPP1/RNBQKBN1 b Qq - 99 51"); chai.expect(board.result(false)).to.equal("*"); chai.expect(board.result(true)).to.equal("*"); board.pushSan("Rh6"); chai.expect(board.result(false)).to.equal("*"); chai.expect(board.result(true)).to.equal("1/2-1/2"); // Insufficient material board.setFen("3Rk3/8/8/8/8/8/2N5/3K4 b - - 0 1"); chai.expect(board.result()).to.equal("*"); board.pushSan("Kxd8"); chai.expect(board.result()).to.equal("1/2-1/2"); // Insufficient material with material counting - black draw odds (armageddon) board.delete(); board = new ffish.Board("armageddon"); board.setFen("3Rk3/8/8/8/8/8/2N5/3K4 b - - 0 1"); chai.expect(board.result()).to.equal("*"); board.pushSan("Kxd8"); chai.expect(board.result()).to.equal("0-1"); // Stalemate with material counting - black draw odds (armageddon) board.setFen("2Q2bnr/4p1pq/5pkr/7p/7P/4P3/PPPP1PP1/RNB1KBNR w KQ - 1 10"); chai.expect(board.result()).to.equal("*"); board.pushSan("Qe6"); chai.expect(board.result()).to.equal("0-1"); // Atomic chess exploded king (variant ending) board.delete(); board = new ffish.Board("atomic"); board.pushMoves("e2e4 e7e5 d1h5 a7a6"); chai.expect(board.result()).to.equal("*"); board.push("h5f7"); chai.expect(board.result()).to.equal("1-0"); // Exploded king AND insufficient material - exploded king takes priority board.setFen("3qk3/8/8/8/8/8/8/3QK3 w - - 0 1"); chai.expect(board.result()).to.equal("*"); board.push("d1d8"); chai.expect(board.result()).to.equal("1-0"); board.delete(); }) }) describe('board.isCheck()', function () { it("it checks if a player is in check", () => { let board = new ffish.Board(); chai.expect(board.isCheck()).to.equal(false); board.setFen("rnbqkb1r/pppp1Bpp/5n2/4p3/4P3/8/PPPP1PPP/RNBQK1NR b KQkq - 0 3"); chai.expect(board.isCheck()).to.equal(true); board.setFen("r1bqkb1r/pppp1ppp/2n2n2/4p2Q/2B1P3/8/PPPP1PPP/RNB1K1NR w KQkq - 4 4"); board.pushSan("Qxf7#"); chai.expect(board.isCheck()).to.equal(true); board.delete(); }); }); describe('board.isBikjang()', function () { it("it checks if a player is in bikjang (only relevant for janggi)", () => { let board = new ffish.Board("janggi"); chai.expect(board.isBikjang()).to.equal(false); board.setFen("rnba1abnr/4k4/1c5c1/p1p3p1p/9/9/P1P3P1P/1C5C1/4K4/RNBA1ABNR w - - 0 1"); chai.expect(board.isBikjang()).to.equal(true); board.delete(); }); }); describe('board.moveStack()', function () { it("it returns the move stack in UCI notation", () => { let board = new ffish.Board(); chai.expect(board.isBikjang()).to.equal(false); board.push("e2e4"); board.push("e7e5"); board.push("g1f3"); chai.expect(board.moveStack()).to.equal("e2e4 e7e5 g1f3"); board.setFen("r1bqkb1r/pppp1ppp/2n2n2/4p2Q/2B1P3/8/PPPP1PPP/RNB1K1NR w KQkq - 4 4"); board.pushSan("Qxf7#"); chai.expect(board.moveStack()).to.equal("h5f7"); board.delete(); }); }); describe('board.pushMoves(uciMoves)', function () { it("it pushes multiple uci moves on the board, passed as a string with ' ' as delimiter", () => { let board = new ffish.Board(); board.pushMoves("e2e4 e7e5 g1f3"); chai.expect(board.fen()).to.equal("rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2"); board.delete(); }); }); describe('board.pushSanMoves(sanMoves)', function () { it("it pushes multiple san moves on the board, passed as a string with ' ' as delimiter", () => { let board = new ffish.Board(); board.pushSanMoves("e4 e5 Nf3"); chai.expect(board.fen()).to.equal("rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2"); board.delete(); }); }); describe('board.pushSanMoves(sanMoves, notation)', function () { it("it pushes multiple san moves on the board, passed as a string with ' ' as delimiter", () => { let board = new ffish.Board(); board.pushSanMoves("e4 e5 Nf3", ffish.Notation.SAN); chai.expect(board.fen()).to.equal("rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2"); board.delete(); }); }); describe('board.pocket(turn)', function () { it("it returns the pocket for the given player as a string with no delimeter. All pieces are returned in lower case.", () => { let board = new ffish.Board("crazyhouse", "rnb1kbnr/ppp1pppp/8/8/8/5q2/PPPP1PPP/RNBQKB1R/Pnp w KQkq - 0 4"); chai.expect(board.pocket(WHITE)).to.equal("p"); chai.expect(board.pocket(BLACK)).to.equal("np"); board.delete(); let board2 = new ffish.Board("crazyhouse", "rnb1kbnr/ppp1pppp/8/8/8/5q2/PPPP1PPP/RNBQKB1R[Pnp] w KQkq - 0 4"); chai.expect(board2.pocket(WHITE)).to.equal("p"); chai.expect(board2.pocket(BLACK)).to.equal("np"); board2.delete(); }); }); describe('board.toString()', function () { it("it returns a compact string representation of the board.", () => { const board = new ffish.Board("chess", "rnb1kbnr/ppp1pppp/8/3q4/8/8/PPPP1PPP/RNBQKBNR w KQkq - 0 3"); chai.expect(board.toString()).to.equal("r n b . k b n r\n" + "p p p . p p p p\n" + ". . . . . . . .\n" + ". . . q . . . .\n" + ". . . . . . . .\n" + ". . . . . . . .\n" + "P P P P . P P P\n" + "R N B Q K B N R"); board.delete(); const board2 = new ffish.Board("xiangqi"); chai.expect(board2.toString()).to.equal("r n b a k a b n r\n" + ". . . . . . . . .\n" + ". c . . . . . c .\n" + "p . p . p . p . p\n" + ". . . . . . . . .\n" + ". . . . . . . . .\n" + "P . P . P . P . P\n" + ". C . . . . . C .\n" + ". . . . . . . . .\n" + "R N B A K A B N R"); board2.delete(); }); }); describe('board.toVerboseString()', function () { it("it returns a verbose string representation of the board.", () => { const board = new ffish.Board("chess", "rnb1kbnr/ppp1pppp/8/3q4/8/8/PPPP1PPP/RNBQKBNR w KQkq - 0 3"); chai.expect(board.toVerboseString()).to.equal("\n +---+---+---+---+---+---+---+---+\n" + " | r | n | b | | k | b | n | r |8 \n" + " +---+---+---+---+---+---+---+---+\n" + " | p | p | p | | p | p | p | p |7\n" + " +---+---+---+---+---+---+---+---+\n" + " | | | | | | | | |6\n" + " +---+---+---+---+---+---+---+---+\n" + " | | | | q | | | | |5\n" + " +---+---+---+---+---+---+---+---+\n" + " | | | | | | | | |4\n" + " +---+---+---+---+---+---+---+---+\n" + " | | | | | | | | |3\n" + " +---+---+---+---+---+---+---+---+\n" + " | P | P | P | P | | P | P | P |2\n" + " +---+---+---+---+---+---+---+---+\n" + " | R | N | B | Q | K | B | N | R |1 *\n" + " +---+---+---+---+---+---+---+---+\n" + " a b c d e f g h\n\n" + "Fen: rnb1kbnr/ppp1pppp/8/3q4/8/8/PPPP1PPP/RNBQKBNR w KQkq - 0 3\n" + "Sfen: rnb1kbnr/ppp1pppp/8/3q4/8/8/PPPP1PPP/RNBQKBNR b - 5\n" + "Key: 39B6F80E84D75BFB\nCheckers: ") board.delete(); const board2 = new ffish.Board("xiangqi"); chai.expect(board2.toVerboseString()).to.equal("\n +---+---+---+---+---+---+---+---+---+\n" + " | r | n | b | a | k | a | b | n | r |10 \n" + " +---+---+---+---+---+---+---+---+---+\n" + " | | | | | | | | | |9\n" + " +---+---+---+---+---+---+---+---+---+\n" + " | | c | | | | | | c | |8\n" + " +---+---+---+---+---+---+---+---+---+\n" + " | p | | p | | p | | p | | p |7\n" + " +---+---+---+---+---+---+---+---+---+\n" + " | | | | | | | | | |6\n" + " +---+---+---+---+---+---+---+---+---+\n" + " | | | | | | | | | |5\n" + " +---+---+---+---+---+---+---+---+---+\n" + " | P | | P | | P | | P | | P |4\n" + " +---+---+---+---+---+---+---+---+---+\n" + " | | C | | | | | | C | |3\n" + " +---+---+---+---+---+---+---+---+---+\n" + " | | | | | | | | | |2\n" + " +---+---+---+---+---+---+---+---+---+\n" + " | R | N | B | A | K | A | B | N | R |1 *\n" + " +---+---+---+---+---+---+---+---+---+\n" + " a b c d e f g h i\n\n" + "Fen: rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1\n" + "Sfen: rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR b - 1\n" + "Key: CF494C075A7D927E\nCheckers: "); board2.delete(); }); }); describe('board.variant()', function () { it("it returns the uci-variant of the board.", () => { const board = new ffish.Board("chess"); chai.expect(board.variant()).to.equal("chess"); board.delete(); const board2 = new ffish.Board(""); chai.expect(board2.variant()).to.equal("chess"); board2.delete(); const board3 = new ffish.Board("standard"); chai.expect(board3.variant()).to.equal("chess"); board3.delete(); const board4 = new ffish.Board("Standard"); chai.expect(board4.variant()).to.equal("chess"); board4.delete(); const board5 = new ffish.Board("atomic"); chai.expect(board5.variant()).to.equal("atomic"); board5.delete(); }); }); describe('ffish.info()', function () { it("it returns the version of the Fairy-Stockfish binary", () => { chai.expect(ffish.info()).to.be.a('string'); }); }); describe('ffish.setOption(name, value)', function () { it("it sets a string uci option value pair", () => { ffish.setOption("VariantPath", "variants.ini"); chai.expect(true).to.equal(true); }); }); describe('ffish.setOptionInt(name, value)', function () { it("it sets a int uci option value pair", () => { ffish.setOptionInt("Threads", 4); chai.expect(true).to.equal(true); }); }); describe('ffish.setOptionBool(name, value)', function () { it("it sets a boolean uci option value pair", () => { ffish.setOptionBool("Ponder", true); chai.expect(true).to.equal(true); }); }); describe('ffish.startingFen(uciVariant)', function () { it("it returns the starting fen for the given uci-variant.", () => { chai.expect(ffish.startingFen("chess")).to.equal("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); }); }); describe('ffish.validateFen(fen)', function () { it("it validates a given chess fen and returns +1 if fen is valid. Otherwise an error code will be returned.", () => { chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")).to.equal(1); chai.expect(ffish.validateFen("6k1/R7/2p4p/2P2p1P/PPb2Bp1/2P1K1P1/5r2/8 b - - 4 39")).to.equal(1); }); }); describe('ffish.validateFen(fen, uciVariant)', function () { it("it validates a given fen and returns +1 if fen is valid. Otherwise an error code will be returned.", () => { // check if starting fens are valid for all variants const variants = ffish.variants().split(" ") for (let idx = 0; idx < variants.length; ++idx) { const variant = variants[idx]; const startFen = ffish.startingFen(variant); chai.expect(ffish.validateFen(startFen, variant)).to.equal(1, "Invalid start FEN for " + variant); // check if the FEN is still valid if board.fen() is returned const board = new ffish.Board(variant); chai.expect(ffish.validateFen(board.fen(), variant)).to.equal(1); board.delete(); } // alternative or skipped pocket formulation chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR/RB w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(1); chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR/ w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(1); chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(1); chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[-] w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(1); // error id checks chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[]wKQkq-3+301", "3check-crazyhouse")).to.equal(-10); chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-6); chai.expect(ffish.validateFen("rnbqkbnr/ppppXppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-10); chai.expect(ffish.validateFen("rnbqkbnr/pppppKpp/8/8/8/8/PPPPPPPP/RNBQ1BNR[] w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-9); chai.expect(ffish.validateFen("rnbqkbnr/ppppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-8); chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-8); chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[77] w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-7); chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] o KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-6); chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w K6kq - 3+3 0 1", "3check-crazyhouse")).to.equal(-5); chai.expect(ffish.validateFen("rnbq1bnr/pppkpppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-5); chai.expect(ffish.validateFen("rnbqkbn1/pppppppr/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-5); chai.expect(ffish.validateFen("rnbkqbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR/RB w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-5); chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq ss 3+3 0 1", "3check-crazyhouse")).to.equal(-4); chai.expect(ffish.validateFen("rnbqkknr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 3+3 0 1", "3check-crazyhouse")).to.equal(-3); chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 3+3 x 1", "3check-crazyhouse")).to.equal(-2); chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[] w KQkq - 3+3 0 -13", "3check-crazyhouse")).to.equal(-1); chai.expect(ffish.validateFen("", "chess")).to.equal(0); }); }); describe('ffish.validateFen(fen, uciVariant, chess960)', function () { it("it validates a given X-FEN and returns +1 if fen is valid. Otherwise an error code will be returned.", () => { chai.expect(ffish.validateFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w AHah - 0 1", "chess", true)).to.equal(1); chai.expect(ffish.validateFen("nrbqbkrn/pppppppp/8/8/8/8/PPPPPPPP/NRBQBKRN w BGbg - 0 1", "chess", true)).to.equal(1); }); }); describe('ffish.readGamePGN(pgn)', function () { it("it reads a pgn string and returns a game object", () => { fs = require('fs'); let pgnFiles = ['deep_blue_kasparov_1997.pgn', 'lichess_pgn_2018.12.21_JannLee_vs_CrazyAra.j9eQS4TF.pgn', 'c60_ruy_lopez.pgn', 'pychess-variants_zJxHRVm1.pgn', 'Syrov - Dgebuadze.pgn'] let expectedFens = ["1r6/5kp1/RqQb1p1p/1p1PpP2/1Pp1B3/2P4P/6P1/5K2 b - - 14 45", "3r2kr/2pb1Q2/4ppp1/3pN2p/1P1P4/3PbP2/P1P3PP/6NK[PPqrrbbnn] b - - 1 37", "r1bqkbnr/pppp1ppp/2n5/1B2p3/4P3/5N2/PPPP1PPP/RNBQK2R b KQkq - 3 3", "r1bQkb1r/ppp1pppp/2P5/2n2q2/8/2N2N2/PPP2PPP/R1BEKB1R[Hh] b KQCFkqcf - 0 8", "5rk1/4p3/2p3rR/2p1P3/2Pp1B2/1P1P2P1/2N1n3/6K1 w - - 1 44"] for (let idx = 0; idx < pgnFiles.length; ++idx) { let pgnFilePath = pgnDir + pgnFiles[idx]; fs.readFile(pgnFilePath, 'utf8', function (err,data) { if (err) { return console.log(err); } let game = ffish.readGamePGN(data); const variant = game.headers("Variant").toLowerCase(); let board = new ffish.Board(variant); const mainlineMoves = game.mainlineMoves().split(" "); for (let idx2 = 0; idx2 < mainlineMoves.length; ++idx2) { board.push(mainlineMoves[idx2]); chai.expect(ffish.validateFen(board.fen(), variant)).to.equal(1); } chai.expect(board.fen()).to.equal(expectedFens[idx]); board.delete(); game.delete(); }); } }); }); describe('game.headerKeys()', function () { it("it returns all available header keys of the loaded game", () => { fs = require('fs'); let pgnFile = 'lichess_pgn_2018.12.21_JannLee_vs_CrazyAra.j9eQS4TF.pgn' let pgnFilePath = pgnDir + pgnFile; fs.readFile(pgnFilePath, 'utf8', function (err,data) { if (err) { return console.log(err); } let game = ffish.readGamePGN(data); chai.expect(game.headerKeys()).to.equal('Annotator Termination Variant ECO WhiteTitle BlackRatingDiff UTCTime Result WhiteElo Black UTCDate TimeControl BlackElo Event WhiteRatingDiff BlackTitle White Date Opening Site'); game.delete(); }); }); }); describe('game.headers(key)', function () { it("it returns the value for a given header key of a loaded game", () => { fs = require('fs'); let pgnFile = 'pychess-variants_zJxHRVm1.pgn'; let pgnFilePath = pgnDir + pgnFile; fs.readFile(pgnFilePath, 'utf8', function (err,data) { if (err) { return console.log(err); } let game = ffish.readGamePGN(data); chai.expect(game.headers("White")).to.equal("catask"); chai.expect(game.headers("Black")).to.equal("Fairy-Stockfish"); chai.expect(game.headers("Variant")).to.equal("Seirawan"); chai.expect(game.headers("FEN")).to.equal("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[HEhe] w KQBCDFGkqbcdfg - 0 1"); game.delete(); }); }); }); describe('game.mainlineMoves()', function () { it("it returns the mainline of the loaded game in UCI notation", () => { fs = require('fs'); let pgnFile = 'lichess_pgn_2018.12.21_JannLee_vs_CrazyAra.j9eQS4TF.pgn'; let pgnFilePath = pgnDir + pgnFile; fs.readFile(pgnFilePath, 'utf8', function (err,data) { if (err) { return console.log(err); } let game = ffish.readGamePGN(data); chai.expect(game.mainlineMoves()).to.equal('e2e4 b8c6 b1c3 g8f6 d2d4 d7d5 e4e5 f6e4 f1b5 a7a6 b5c6 b7c6 g1e2 c8f5 e1g1 e7e6 f2f3 e4c3 b2c3 h7h5 N@e3 N@h4 N@a5 B@d7 e3f5 h4f5 B@b7 N@e3 a5c6 e3d1 c6d8 a8d8 f1d1 Q@b5 b7a6 b5a6 P@d3 N@e3 c1e3 f5e3 P@d6 e3d1 a1d1 B@e3 g1h1 f8d6 e5d6 a6d6 B@b4 d6b4 c3b4 P@f2 Q@f1 R@g1 f1g1 f2g1q d1g1 P@f2 N@g6 f2g1q e2g1 Q@e7 Q@d6 f7g6 d6e7 e8e7 R@f7 e7f7 N@e5 f7g8 N@f6 g7f6 Q@f7'); game.delete(); }); }); }); describe('ffish.variants()', function () { it("it returns all currently available variants", () => { chai.expect(ffish.variants().includes("chess")).to.equal(true); chai.expect(ffish.variants().includes("crazyhouse")).to.equal(true); chai.expect(ffish.variants().includes("janggi")).to.equal(true); }); }); Fairy-Stockfish-fairy_sf_14_0_1_xq/tests/perft.sh000077500000000000000000000246141414571233100221670ustar00rootroot00000000000000#!/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 60 lassign \$argv var pos depth result chess960 if {\$chess960 eq ""} {set chess960 false} spawn ./stockfish send "setoption name UCI_Chess960 value \$chess960\\n" send "setoption name UCI_Variant value \$var\\n" send "position \$pos\\ngo perft \$depth\\n" expect "Nodes searched? \$result" {} timeout {exit 1} send "quit\\n" expect eof EOF # chess if [[ $1 == "" || $1 == "chess" ]]; then expect perft.exp chess startpos 5 4865609 > /dev/null expect perft.exp chess "fen r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq -" 5 193690690 > /dev/null expect perft.exp chess "fen 8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - -" 6 11030083 > /dev/null expect perft.exp chess "fen r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1" 5 15833292 > /dev/null expect perft.exp chess "fen rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8" 5 89941194 > /dev/null expect perft.exp chess "fen r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10" 5 164075551 > /dev/null fi # variants if [[ $1 == "all" || $1 == "variant" ]]; then # small board expect perft.exp losalamos startpos 5 191846 > /dev/null expect perft.exp losalamos "fen 6/2P3/6/1K1k2/6/6 w - - 0 1" 6 187431 > /dev/null # fairy expect perft.exp makruk startpos 4 273026 > /dev/null expect perft.exp cambodian startpos 4 361793 > /dev/null expect perft.exp cambodian "fen r1s1ks1r/3nm3/pppNpppp/3n4/5P2/PPPPPNPP/8/R1SKMS1R b DEe 0 0 5" 2 72 > /dev/null expect perft.exp karouk "fen rn1mksnr/3s4/pppppppp/8/4N3/PPPPPPPP/8/R1SKMSNR b DEde - 3 2" 4 358535 > /dev/null expect perft.exp makpong "fen 3mk3/r3s1R1/1psppnp1/p1pn4/1P2NP2/P1PPP1P1/4NS2/R1SKM3 w - - 0 1" 4 593103 > /dev/null expect perft.exp asean startpos 4 273026 > /dev/null expect perft.exp ai-wok startpos 4 485045 > /dev/null expect perft.exp ai-wok "fen 8/8/8/2sp2k1/7p/3P4/6K1/7r w - - 0 1" 5 30055 > /dev/null expect perft.exp sittuyin startpos 3 580096 > /dev/null expect perft.exp sittuyin "fen 8/8/6R1/s3r3/P5R1/1KP3p1/1F2kr2/8[] b - - 0 72" 4 652686 > /dev/null expect perft.exp sittuyin "fen 2r5/6k1/6p1/3s2P1/3npR2/8/p2N2F1/3K4[] w - - 1 50" 4 373984 > /dev/null expect perft.exp sittuyin "fen 8/6s1/5P2/3n4/pR2K2S/1P6/1k4p1/8[] w - - 1 50" 4 268869 > /dev/null expect perft.exp sittuyin "fen 1k5K/3r2P1/8/8/8/8/8/8[] w - - 0 1" 5 68662 > /dev/null expect perft.exp shatar startpos 4 177344 > /dev/null expect perft.exp shatranj startpos 4 68122 > /dev/null expect perft.exp amazon startpos 4 318185 > /dev/null expect perft.exp nightrider startpos 4 419019 > /dev/null expect perft.exp grasshopper startpos 4 635298 > /dev/null expect perft.exp hoppelpoppel startpos 4 202459 > /dev/null expect perft.exp newzealand startpos 4 200310 > /dev/null # alternative goals expect perft.exp racingkings startpos 4 296242 > /dev/null expect perft.exp racingkings "fen 6r1/2K5/5k2/8/3R4/8/8/8 w - - 0 1" 4 86041 > /dev/null expect perft.exp racingkings "fen 6R1/2k5/5K2/8/3r4/8/8/8 b - - 0 1" 4 86009 > /dev/null expect perft.exp racingkings "fen 4brn1/2K2k2/8/8/8/8/8/8 w - - 0 1" 6 265932 > /dev/null expect perft.exp kingofthehill "fen rnb2b1r/ppp2ppp/3k4/8/1PKp1pn1/3Pq3/PBP1P2P/RN1Q1B1R w - - 4 12" 3 19003 > /dev/null expect perft.exp 3check "fen 7r/1p4p1/pk3p2/RN6/8/P5Pp/3p1P1P/4R1K1 w - - 1+3 1 39" 3 12407 > /dev/null expect perft.exp 3check "fen 7r/1p4p1/pk3p2/RN6/8/P5Pp/3p1P1P/4R1K1 w - - 1 39 +2+0" 3 12407 > /dev/null expect perft.exp atomic startpos 4 197326 > /dev/null expect perft.exp atomic "fen rn2kb1r/1pp1p2p/p2q1pp1/3P4/2P3b1/4PN2/PP3PPP/R2QKB1R b KQkq - 0 1" 4 1434825 > /dev/null expect perft.exp atomic "fen rn1qkb1r/p5pp/2p5/3p4/N3P3/5P2/PPP4P/R1BQK3 w Qkq - 0 1" 4 714499 > /dev/null expect perft.exp atomic "fen r4b1r/2kb1N2/p2Bpnp1/8/2Pp3p/1P1PPP2/P5PP/R3K2R b KQ - 0 1" 2 148 > /dev/null expect perft.exp antichess startpos 4 153299 > /dev/null expect perft.exp giveaway startpos 4 153299 > /dev/null expect perft.exp giveaway "fen 8/1p6/8/8/8/8/P7/8 w - - 0 1" 4 3 > /dev/null expect perft.exp giveaway "fen 8/2p5/8/8/8/8/P7/8 w - - 0 1" 12 2557 > /dev/null expect perft.exp codrus startpos 4 153299 > /dev/null expect perft.exp codrus "fen 5bnr/2pp2pp/P4k2/R3Q1P1/8/2N1K3/1P1PP1PP/2B2BNR w - - 1 15" 4 19 > /dev/null expect perft.exp horde startpos 4 23310 > /dev/null expect perft.exp horde "fen 4k3/pp4q1/3P2p1/8/P3PP2/PPP2r2/PPP5/PPPP4 b - - 0 1" 4 56539 > /dev/null expect perft.exp horde "fen k7/5p2/4p2P/3p2P1/2p2P2/1p2P2P/p2P2P1/2P2P2 w - - 0 1" 4 33781 > /dev/null expect perft.exp horde "fen 4k3/7r/8/P7/2p1n2P/3p2P1/1P3P2/PPP1PPP1 w - - 0 1" 4 128809 > /dev/null expect perft.exp horde "fen rnbqkbnr/6p1/2p1Pp1P/P1PPPP2/Pp4PP/1p2PPPP/1P2PPPP/PP1nPPPP b kq a3 0 18" 4 197287 > /dev/null expect perft.exp coregal startpos 4 195896 > /dev/null expect perft.exp coregal "fen rn2kb1r/ppp1pppp/6q1/8/2PP2b1/5B2/PP3P1P/R1BQK1NR w KQkq - 1 9" 3 20421 > /dev/null expect perft.exp coregal "fen 2Q5/3Pq2k/6p1/4Bp1p/5P1P/8/8/K7 w - - 2 72" 4 55970 > /dev/null expect perft.exp coregal "fen r3kb1r/1pp1pppp/p1q2n2/3P4/6b1/2N2N2/PPP2PPP/R1BQ1RK1 b kq - 0 9" 4 136511 > /dev/null expect perft.exp knightmate startpos 4 139774 > /dev/null expect perft.exp losers startpos 4 152955 > /dev/null expect perft.exp kinglet startpos 4 197742 > /dev/null expect perft.exp threekings startpos 4 199514 > /dev/null # pockets expect perft.exp crazyhouse startpos 4 197281 > /dev/null expect perft.exp crazyhouse "fen 2k5/8/8/8/8/8/8/4K3[QRBNPqrbnp] w - - 0 1" 2 75353 > /dev/null expect perft.exp crazyhouse "fen 2k5/8/8/8/8/8/8/4K3[Qn] w - - 0 1" 3 88634 > /dev/null expect perft.exp crazyhouse "fen 2k5/8/8/8/8/8/8/4K3/Qn w - - 0 1" 3 88634 > /dev/null expect perft.exp crazyhouse "fen r1bqk2r/pppp1ppp/2n1p3/4P3/1b1Pn3/2NB1N2/PPP2PPP/R1BQK2R[] b KQkq - 0 1" 3 58057 > /dev/null expect perft.exp loop startpos 4 197281 > /dev/null expect perft.exp loop "fen 5R2/2p1Nb2/2B4k/6p1/8/P3PP2/1PPqR3/3R1BKn[QBNPPPPrrrnppp] b - - 1 1" 2 31983 > /dev/null expect perft.exp chessgi startpos 4 197281 > /dev/null expect perft.exp chessgi "fen 5Rp1/2p1Nb2/2B4k/6p1/8/P3PP2/1PPqR3/3R1BKn[QBNPPPPrrrnpp] b - - 1 48" 2 32816 > /dev/null expect perft.exp pocketknight startpos 3 88617 > /dev/null expect perft.exp placement startpos 3 50560 > /dev/null expect perft.exp placement "fen rnbq1bnr/pppppppp/8/8/8/8/PPPPPPPP/QR1BKNN1[BRk] w - - 0 1" 6 17804 > /dev/null expect perft.exp placement "fen 1n1r1q2/pppppppp/8/8/8/8/PPPPPPPP/1N1B1R1N[KQRBkrbbn] b - - 0 4" 5 145152 > /dev/null expect perft.exp placement "fen r3k3/pppppppp/8/8/8/8/PPPPPPPP/R6R[Kr] w q - 0 1" 4 18492 > /dev/null expect perft.exp seirawan startpos 4 782599 > /dev/null expect perft.exp seirawan "fen reb1k2r/ppppqppp/2nb1n2/4p3/4P3/N1P2N2/PB1PQPPP/RE2KBHR[h] b KQkqc - 3 7" 4 890467 > /dev/null expect perft.exp shouse startpos 3 546694 > /dev/null expect perft.exp euroshogi startpos 4 380499 > /dev/null expect perft.exp minishogi startpos 5 533203 > /dev/null expect perft.exp kyotoshogi startpos 5 225903 > /dev/null expect perft.exp torishogi startpos 4 103857 > /dev/null # non-chess expect perft.exp ataxx startpos 4 155888 > /dev/null expect perft.exp ataxx "fen 7/7/7/7/ppppppp/ppppppp/PPPPPPP[PPPPPPPPPPPPPPPPPPPPPppppppppppppppppppppp] w 0 1" 5 452980 > /dev/null expect perft.exp breakthrough startpos 4 256036 > /dev/null expect perft.exp breakthrough "fen 1p2pp1p/2p2ppp/2P5/8/8/3P2P1/1p1P2PP/1PP1PP1P w - - 1 26" 4 121264 > /dev/null expect perft.exp clobber startpos 3 80063 > /dev/null # 960 variants expect perft.exp atomic "fen 8/8/8/8/8/8/2k5/rR4KR w KQ - 0 1" 4 61401 true > /dev/null expect perft.exp atomic "fen r3k1rR/5K2/8/8/8/8/8/8 b kq - 0 1" 4 98729 true > /dev/null expect perft.exp atomic "fen Rr2k1rR/3K4/3p4/8/8/8/7P/8 w kq - 0 1" 4 241478 true > /dev/null expect perft.exp atomic "fen 1R4kr/4K3/8/8/8/8/8/8 b k - 0 1" 4 17915 true > /dev/null expect perft.exp extinction "fen rnbqb1kr/pppppppp/8/8/8/8/PPPPPPPP/RNBQB1KR w AHah - 0 1" 4 195286 true > /dev/null expect perft.exp seirawan "fen qbbrnkrn/pppppppp/8/8/8/8/PPPPPPPP/QBBRNKRN[HEhe] w ABCDEFGHabcdefgh - 0 1" 3 21170 true > /dev/null fi # large-board variants if [[ $1 == "all" || $1 == "largeboard" ]]; then expect perft.exp shogi startpos 4 719731 > /dev/null expect perft.exp shoshogi startpos 4 445372 > /dev/null # configurable pieces expect perft.exp yarishogi startpos 4 158404 > /dev/null # configurable pieces expect perft.exp capablanca startpos 4 805128 > /dev/null expect perft.exp embassy startpos 4 809539 > /dev/null expect perft.exp janus startpos 4 772074 > /dev/null expect perft.exp modern startpos 4 433729 > /dev/null expect perft.exp chancellor startpos 4 436656 > /dev/null expect perft.exp courier startpos 4 500337 > /dev/null expect perft.exp grand startpos 3 259514 > /dev/null expect perft.exp grand "fen r8r/1nbqkcabn1/ppp2ppppp/3p6/4pP4/10/10/PPPPP1PPPP/1NBQKCABN1/R8R w - e7 0 3" 2 5768 > /dev/null expect perft.exp opulent startpos 3 133829 > /dev/null expect perft.exp tencubed startpos 3 68230 > /dev/null expect perft.exp centaur startpos 3 24490 > /dev/null expect perft.exp shako "fen 4kc3c/ernbq1b1re/ppp3p1pp/3p2pp2/4p5/5P4/2PN2P3/PP1PP2PPP/ER1BQKBNR1/5C3C w KQ - 0 9" 3 26325 > /dev/null expect perft.exp shako "fen 4ncr1k1/1cr2P4/pp2p2pp1/P7PN/2Ep1p4/B3P1eN2/2P1n1P3/1B1P1K4/9p/5C2CR w - - 0 1" 3 180467 > /dev/null expect perft.exp xiangqi startpos 4 3290240 > /dev/null expect perft.exp xiangqi "fen 1rbaka2R/5r3/6n2/2p1p1p2/4P1bP1/PpC3Bc1/1nPR2P2/2N2AN2/1c2K1p2/2BAC4 w - - 0 1" 4 4485547 > /dev/null expect perft.exp xiangqi "fen 4kcP1N/8n/3rb4/9/9/9/9/3p1A3/4K4/5CB2 w - - 0 1" 4 92741 > /dev/null expect perft.exp manchu startpos 4 798554 > /dev/null expect perft.exp janggi startpos 4 1065277 > /dev/null expect perft.exp janggi "fen 1n1kaabn1/cr2N4/5C1c1/p1pNp3p/9/9/P1PbP1P1P/3r1p3/4A4/R1BA1KB1R b - - 0 1" 4 76763 > /dev/null expect perft.exp janggi "fen 1Pbcka3/3nNn1c1/N2CaC3/1pB6/9/9/5P3/9/4K4/9 w - - 0 23" 4 151202 > /dev/null expect perft.exp jesonmor startpos 3 27960 > /dev/null fi # special variants if [[ $1 == "all" ]]; then expect perft.exp amazons startpos 1 2176 > /dev/null fi rm perft.exp echo "perft testing OK" Fairy-Stockfish-fairy_sf_14_0_1_xq/tests/pgn/000077500000000000000000000000001414571233100212655ustar00rootroot00000000000000Fairy-Stockfish-fairy_sf_14_0_1_xq/tests/pgn/Syrov - Dgebuadze.pgn000066400000000000000000000226161414571233100251140ustar00rootroot00000000000000[Event "RLP-Open R7"] [Site "?"] [Date "????.??.??"] [Round "1.2"] [White "Syrov, Arkadi"] [Black "Dgebuadze, Alexandre"] [Result "0-1"] [ECO "B31"] [WhiteElo "2291"] [BlackElo "2508"] [Annotator "Syrov, Arkadi"] [PlyCount "86"] 1. e4 {[%emt 0:00:01]} c5 {[%emt 0:00:21] keine völlige Überraschung, da ich gesehen habe, dass er schon c5 gespielt hat, aber nur gegen Morra. Ich hatte hauptsächlich mit Französisch gerechnet.} 2. Nf3 {[%emt 0:00:05]} Nc6 { [%emt 0:00:04]} 3. Bb5 {[%emt 0:00:04]} g6 {[%emt 0:00:09]} 4. Bxc6 {[%emt 0: 00:05]} bxc6 {[%emt 0:00:07]} 5. O-O {[%emt 0:00:05]} Bg7 {[%emt 0:00:16]} 6. Re1 {[%emt 0:00:05] jetzt begann er lange zu denken, was ich nicht erwartet habe. Bis hierher habe ich gefühlt 10 Partien in der Datenbank.} d5 {[%emt 0: 21:13] das ist nicht die Hauptvariante. Während ich herumlief und auf seinen Zug wartete, habe ich versucht mich an die Theorie nach Sh6 c3 zu erinnern. Von d5 wusste ich nichts, also reagierte ich Standardmäßig.} 7. d3 {[%emt 0: 02:47]} Bg4 {[%emt 0:01:22]} 8. Nbd2 {[%emt 0:03:46] Im nachhinein ist das eine schlechte Entscheidung. Ich hätte gleich h3 spielen sollen. Ich wollte auf f3 mit dem Springer schlagen und habe mich selbst ausgetrixt, denn er bekommt eine neue Option.} (8. h3 Bxf3 9. Qxf3 $14 {sollte etwas besser sein.}) 8... Nf6 {[%emt 0:03:45]} 9. e5 {[%emt 0:05:32]} (9. h3 {wäre glaube ich immer noch besser gewesen und dann wieder mit der Dame auf f3 schlagen. Aber ich habe die ganze Idee mit Le6 nicht betrachtet.}) 9... Nd7 {[%emt 0:02:01]} 10. h3 {[%emt 0:00:14]} Be6 {[%emt 0:09:32]} 11. c4 $2 {[%emt 0:17:01] die Standardidee um die Doppelbauern festzulegen.} (11. Ng5 $1 {war meine Alternative. Ich habe es wegen:} Nxe5 {nicht gespielt.} (11... Nf8 {war der Plan meines Gegners, was auch sinnvoll aussieht. Aber konkret hat Weiß eine starke Antwort (Natürlich habe ich alles folgende nicht gesehen):} 12. Nb3 $1 h6 13. Nxe6 Nxe6 14. Be3 Bxe5 (14... Qb6 15. f4 $14 {[%csl Gc5][%cal Gd1f3, Gf3f2]} a5 (15... O-O 16. Qf3 a5 17. Qf2 d4 18. Bc1 $16 {ist einfach traurig für Schwarz}) 16. Qf3 a4 17. Nxc5 $1 Nxc5 18. Qf2 Nxd3 19. cxd3 Qb5 20. Bc5 e6 (20... Qxd3 $2 21. Bxe7 $3 Kxe7 22. Qc5+ $18) 21. d4 $14 {[%csl Gc6]}) 15. Nxc5 Nxc5 16. Bxc5 Bf6 17. Qd2 $1 $14 {Schwarz kann nicht rochieren, da h6 hängt und es ist klar, dass Weiß einen kleinen Vorteil haben sollte.}) 12. f4 Nd7 13. Nxe6 (13. Rxe6 fxe6 14. Nxe6 Qb6 15. Nxg7+ Kf7 {schien auch nicht funktionieren. Ich habe keine Figuren, die im Angriff sind.}) 13... fxe6 14. Rxe6 O-O {und ich wusste nicht was das soll. Denn der Bauer auf f4 ist Schwach, ich dachte während der Partie, dass Weiß hier schlechter steht. Was anscheinend eine riesige Fehleinschätzung ist. Denn nach} (14... Rc8 15. Nf3 c4 {soll Schwarz probieren, aber es ist immer noch besser für Weiß} 16. dxc4 Nc5 17. Re1 $14) 15. Nf3 {hängt c6 und e7 und nach} Rc8 (15... Qc7 16. Rxe7 Rae8 17. Qe2 Rxe7 18. Qxe7 $16 {ist einfach ein Bauer}) 16. Qe1 Bf6 17. f5 $1 { hat Schwarz riesige Probleme.}) (11. d4 {habe ich auch kurz überlegt, aber ich habe nicht ganz verstanden, was das mir bringt, außer die Stellung zu öffnen, was meinem Gegner zugute kommen sollte.}) (11. b3 {war auch eine Möglichkeit an der ich überlegt habe und dann ohne c4 spielen. Oder in einem besseren Moment c4 spielen. Vielleicht wäre das eine bessere Möglichkeit.}) ( 11. Qe2 h6 12. b3 {[%cal Ga2a4] und ohne c4 spielen sieht auch sehr gut aus.}) 11... h6 {[%emt 0:07:58] ein logischer Zug um Sg5 aus der Stellung zu nehmen. Und manchmal wird g5 zur Idee.} 12. Qe2 {[%emt 0:00:48] sah logisch aus, um c4 zu decken, den e5 zu decken und dann meinen Problemspringer auf d2 zu überführen.} Qb6 {[%emt 0:02:13] ein etwas mysteriöser Zug. Die Idee ist wahrscheinlich in manchen Varianten Db4 zu haben und b2 im Visier zu behalten. Ich war hier ehrlich gesagt etwas verloren und wusste nicht wirklich was ich als nächstes machen sollte und begann etwas kreativ zu werden, bis ich alle meine Ideen selbst wiederlegt habe.} 13. Nb1 {[%emt 0:13:59][%cal Gb1c3,Gb2b3, Gc3a4] wenn ich noch einen Zug machen darf, steht Schwarz glaube ich ganz schlecht, aber den gibt mir Schwarz natürlich nicht.} (13. a4 a5 14. Ra3 { war eine sau komische Idee, die ich hatte um den c5 unter Druck zu setzen, aber ich war mir über die Bewertung der Schlussstellung nicht sicher.} Nb8 { ich glaube Schwarz sollte das schwache b4 Feld ausnutzen.} 15. Rb3 Qa7 { hier steht die Dame dachte ich am besten} (15... Qc7 16. Rc3 Na6 17. b3 Nb4 18. Ba3 {habe ich vielleicht motive mit nimmt auf d5 und d4, da die Dame auf c7 in der Turmlinie steht.}) 16. Rc3 Na6 17. b3 Nb4 18. Ba3 O-O {ich kann den c5 nicht noch mehr angreifen, da nach Tec1 d4 kommt und ich weiß nicht, was ich weiter machen soll.}) (13. b3 a5 {hat mir nicht gefallen, da ich entweder a4 spielen muss, oder a4 zulassen muss. In beiden fällen hat mir die Perspektive nicht gefallen, da entweder b4 sehr schwach wird und Sb4 und Lf5 kommen, oder all dass und der b3 wird schwach. (Bzw a2, wenn ich nicht auf b3 mit dem Bauern nehme).}) (13. Nf1 {ich wusste nicht, wohin der Springer von hier gehen will.}) (13. Nb3 {ich dachte das läuft einfach in a5 und a4 mit Tempo.}) 13... a5 {[%emt 0:00:21] sonst käme Sc3 und auf a5 dann, b3 oder Sa4.} 14. Bf4 { [%emt 0:07:12] wieder war ich etwas verloren und hatte keine Ahnung wie ich meine Figuren hinstellen soll. Es ist klar, dass er a4 im nächsten Zug machen wird und was ich mache ist mir ein Rätsel.} a4 {[%emt 0:02:34]} 15. Na3 { [%emt 0:02:47] ich habe wirklich keine Ahnung was hier richtig ist und was nicht.} g5 {[%emt 0:03:33]} 16. Bg3 {[%emt 0:03:12]} O-O {[%emt 0:09:10]} ( 16... g4 17. hxg4 Bxg4 18. Rab1 {[%cal Ge2e3] ich dachte nicht, dass es so viel für Schwarz bringt.}) 17. Rab1 {[%emt 0:00:59] um b2 zu decken, damit sich die Dame bewegen kann. Vielleicht wäre h4 sofort richtig gewesen!?} Rfe8 {[%emt 0:02:02]} (17... Bf5 $1 {gefolgt von e6 sieht stark aus} 18. Nc2 e6 19. Ne3 Bg6 $15) 18. h4 {[%emt 0:01:14]} f6 {[%emt 0:04:19]} (18... g4 19. Nh2 h5 20. f3 {hier war ich ziemlich optimistisch gute Chancen zu haben.}) {Hier habe ich überlegt, dass ich eigentlich nichts machen will. Ich will glaube ich alles so lassen wie es ist. Aber ich konnte einfach keinen Zug finden, der irgendwie sinnvoll ist. Im nachhinein sieht Sc2 gut aus.} 19. hxg5 {[%emt 0:05: 39] ich glaube das ist der Verlustzug. Ich wollte einfach hg, hg einschieben, und dann weiterschauen. Aber das hat ein Problem...} (19. Nc2 {und die Stellung ist noch spielbar}) 19... fxg5 {[%emt 0:01:10] nachdem dieser Zug kam, verstand ich, dass ich in riesigen Problemen stecke. Es kommt einfach Tf8-f5 und was mache ich?} 20. Nh2 {[%emt 0:00:59] ich wollte dem Lg4 und Tf8-Motiv aus dem Weg gehen. Und vielleicht Dh5. Ich wollte noch viellecht f4 spielen um etwas Gegenspiel zu bekommen.} (20. Nc2 {hätte ich spielen sollen} Rf8 21. Red1 Bg4 22. e6 Bxf3 23. gxf3 Nf6 24. f4 gxf4 25. Bxf4 Qd8 $15 {[%cal Gd8e8, Ge8h5] aber das soll Schwarz noch finden.}) 20... Rf8 {[%emt 0:00:51]} 21. Qe3 {[%emt 0:06:01]} Rf5 {[%emt 0:02:37]} 22. f4 {[%emt 0:00:14] sonst fliegt e5 ersatzlos und damit auch meine Stellung, dachte ich. Wenn ich es nicht mache, geht es noch halbwegs, laut dem Blechheinie} (22. Nc2 $15) 22... Raf8 {[%emt 0: 01:40]} 23. fxg5 {[%emt 0:01:14] auch glaube ich ohne wirklich sinnvolle Alternative} Rxg5 {[%emt 0:00:26]} 24. Bf4 {[%emt 0:00:42]} Rg6 {[%emt 0:07:46] } (24... Rxf4 25. Qxf4 Bxe5 26. Qh4 {und ich dachte ich hätte noch gegenchancen.}) (24... Bxe5 {war eine Idee, die ich gar nicht gesehen habe. Aber ich habe noch eine Möglichkeit eine völlige Chaos-Stellung zu erreichen: } 25. Bxg5 Bd4 26. Qxd4 cxd4 27. Bxe7 {und was hier abgeht, weiß nur Stockfish.}) 25. Nf3 {[%emt 0:03:59] ich glaube das verliert forciert.} (25. Re2 {ist vielleicht der Zug um weiterzuspielen. Jetzt kann ich vielleicht den Turm nach f1 stellen und ich glaube ich verliere nichts sofort.}) 25... d4 { [%emt 0:01:46]} (25... Rg4 26. Bh2 d4 27. Qe2 {ist glaube ich noch spielbar}) 26. Qd2 {[%emt 0:01:38]} Qb4 {[%emt 0:02:22]} (26... Rg4 27. Re4 (27. Bxh6 $2 Rxf3 28. Bxg7 Kxg7 $19) 27... Bf5 28. Nh2 {wäre dachte ich noch spielbar} Rg6 29. Re2 Qb4 {ist aber auch traurig und laut dem PC gewonnen}) 27. Qc1 {[%emt 0: 03:50] das Endspiel ist hoffnungslos.} Rg4 {[%emt 0:00:40]} 28. Nc2 {[%emt 0: 00:51]} (28. Bd2 Qb8 29. Nh2 Rg6 {war so traurig, dass ich es gleich verworfen habe und lieber eine Qualle gegeben habe. Leider muss er sie nicht sofort nehmen}) 28... Qb6 {[%emt 0:01:06]} 29. Re4 {[%emt 0:00:42]} Bf5 {[%emt 0:00: 08]} 30. Nh2 {[%emt 0:00:06]} Rg6 {[%emt 0:00:57]} 31. Qe1 {[%emt 0:00:46]} Qd8 {[%emt 0:02:31] der Rest ist eigentlich nicht mehr interessant. Ich glaube nicht, dass ich danach noch irgendeine Chance habe.} (31... Bxe4 32. Qxe4 { ist vielleicht noch nicht ganz klar, da der schwarze Turm auf g6 etwas in der Luft rumhängt.}) 32. b3 {[%emt 0:01:17]} Bxe4 {[%emt 0:01:05]} 33. Qxe4 { [%emt 0:00:03]} Re6 {[%emt 0:01:13]} 34. Nf3 {[%emt 0:00:18]} Qe8 {[%emt 0:00: 31]} 35. g3 {[%emt 0:01:23]} Qh5 {[%emt 0:01:00]} 36. Re1 {[%emt 0:02:55]} axb3 {[%emt 0:00:17]} 37. axb3 {[%emt 0:00:04]} Qg4 {[%emt 0:02:41]} 38. Re2 { [%emt 0:04:05]} Nf6 {[%emt 0:02:39] noch eine schöne letzte ressource.} 39. Nh2 {[%emt 0:00:38]} Nxe4 {[%emt 0:00:55]} 40. Nxg4 {[%emt 0:00:04]} Nc3 { [%emt 0:00:11]} 41. Rh2 {[%emt 0:02:24]} Rg6 {[%emt 0:00:10]} 42. Nxh6+ { [%emt 0:00:15]} Bxh6 {[%emt 0:00:10]} 43. Rxh6 {[%emt 0:00:04]} Ne2+ {[%emt 0: 00:06] Zwei Fehler: 1: Sg5 falsch eingeschätzt. Und 2: hg fg übersehen.} 0-1 Fairy-Stockfish-fairy_sf_14_0_1_xq/tests/pgn/c60_ruy_lopez.pgn000066400000000000000000000002031414571233100244660ustar00rootroot00000000000000[Event "?"] [Site "?"] [Date "2020.09.24"] [Round "?"] [White "White"] [Black "Black"] [Result "*"] 1. e4 e5 2. Nf3 Nc6 3. Bb5 * Fairy-Stockfish-fairy_sf_14_0_1_xq/tests/pgn/deep_blue_kasparov_1997.pgn000066400000000000000000000013161414571233100263170ustar00rootroot00000000000000[Event "IBM Man-Machine"] [Site "New York, NY USA"] [Date "1997.05.04"] [EventDate "?"] [Round "2"] [Result "1-0"] [White "Deep Blue (Computer)"] [Black "Garry Kasparov"] [ECO "C93"] [WhiteElo "?"] [BlackElo "?"] [PlyCount "89"] 1.e4 e5 2.Nf3 Nc6 3.Bb5 a6 4.Ba4 Nf6 5.O-O Be7 6.Re1 b5 7.Bb3 d6 8.c3 O-O 9.h3 h6 10.d4 Re8 11.Nbd2 Bf8 12.Nf1 Bd7 13.Ng3 Na5 14.Bc2 c5 15.b3 Nc6 16.d5 Ne7 17.Be3 Ng6 18.Qd2 Nh7 19.a4 Nh4 20.Nxh4 Qxh4 21.Qe2 Qd8 22.b4 Qc7 23.Rec1 c4 24.Ra3 Rec8 25.Rca1 Qd8 26.f4 Nf6 27.fxe5 dxe5 28.Qf1 Ne8 29.Qf2 Nd6 30.Bb6 Qe8 31.R3a2 Be7 32.Bc5 Bf8 33.Nf5 Bxf5 34.exf5 f6 35.Bxd6 Bxd6 36.axb5 axb5 37.Be4 Rxa2 38.Qxa2 Qd7 39.Qa7 Rc7 40.Qb6 Rb7 41.Ra8+ Kf7 42.Qa6 Qc7 43.Qc6 Qb6+ 44.Kf1 Rb8 45.Ra6 1-0 Fairy-Stockfish-fairy_sf_14_0_1_xq/tests/pgn/lichess_pgn_2018.12.21_JannLee_vs_CrazyAra.j9eQS4TF.pgn000066400000000000000000000144241414571233100325620ustar00rootroot00000000000000[Event "Rated Crazyhouse game"] [Site "https://lichess.org/j9eQS4TF"] [Date "2018.12.21"] [White "JannLee"] [Black "CrazyAra"] [Result "1-0"] [UTCDate "2018.12.21"] [UTCTime "18:33:01"] [WhiteElo "2641"] [BlackElo "2610"] [WhiteRatingDiff "+6"] [BlackRatingDiff "-13"] [WhiteTitle "LM"] [BlackTitle "BOT"] [Variant "Crazyhouse"] [TimeControl "300+15"] [ECO "B00"] [Opening "Nimzowitsch Defense: Scandinavian Variation, Bogoljubov Variation, Vehre Variation"] [Termination "Normal"] [Annotator "lichess.org"] 1. e4 { [%eval 1.52] [%clk 0:05:00] } 1... Nc6 { [%eval 0.77] [%clk 0:05:00] } 2. Nc3 { [%eval 0.66] [%clk 0:05:11] } 2... Nf6?! { (0.66 → 1.19) Inaccuracy. e5 was best. } { [%eval 1.19] [%clk 0:05:08] } (2... e5 3. Nf3 Nf6 4. Bc4 Bc5 5. O-O d6 6. d3 O-O 7. Ng5) 3. d4 { [%eval 0.67] [%clk 0:05:11] } 3... d5 { [%eval 0.84] [%clk 0:05:08] } { B00 Nimzowitsch Defense: Scandinavian Variation, Bogoljubov Variation, Vehre Variation } 4. e5 { [%eval 0.61] [%clk 0:05:24] } 4... Ne4 { [%eval 0.75] [%clk 0:05:15] } 5. Bb5 { [%eval 2.46] [%clk 0:05:26] } 5... a6 { [%eval 3.05] [%clk 0:05:07] } 6. Bxc6+ { [%eval 2.5] [%clk 0:05:28] } 6... bxc6 { [%eval 3.2] [%clk 0:05:14] } 7. Nge2 { [%eval 0.48] [%clk 0:05:17] } 7... Bf5?? { (0.48 → 2.75) Blunder. Bg4 was best. } { [%eval 2.75] [%clk 0:05:03] } (7... Bg4 8. O-O Nxc3 9. bxc3 B@h3 10. f3 Bxg2 11. fxg4 Bxf1 12. Qxf1) 8. O-O { [%eval 3.0] [%clk 0:05:20] } 8... e6 { [%eval 2.65] [%clk 0:04:53] } 9. f3 { [%eval 2.62] [%clk 0:05:17] } 9... Nxc3 { [%eval 2.61] [%clk 0:05:01] } 10. bxc3 { [%eval 2.55] [%clk 0:05:32] } 10... h5?! { (2.55 → 3.85) Inaccuracy. B@a4 was best. } { [%eval 3.85] [%clk 0:04:50] } (10... B@a4 11. N@e3) 11. N@e3 { [%eval 3.49] [%clk 0:03:36] } 11... N@h4? { (3.49 → 5.60) Mistake. Bh7 was best. } { [%eval 5.6] [%clk 0:04:38] } (11... Bh7 12. Rb1 B@a4 13. Nf4 h4 14. Rb7 Be7 15. N@h5 Rg8 16. c4 dxc4 17. Nxc4 Baxc2 18. Qd2) 12. N@a5?? { (5.60 → 2.40) Blunder. Nxf5 was best. } { [%eval 2.4] [%clk 0:03:09] } (12. Nxf5 Nxf5 13. B@b7 B@a4 14. N@a5 N@e3 15. Bxe3 Nxe3 16. Qd2 Nxc2 17. Bxa8 Qxa8 18. Rab1 P@b4) 12... B@d7?? { (2.40 → 4.92) Blunder. Nxg2 was best. } { [%eval 4.92] [%clk 0:04:32] } (12... Nxg2 13. Nxg2 P@d7 14. Rb1 h4 15. Ngf4 B@g5 16. Rf2 Bfe7 17. Rg2 Kf8 18. Bd2 Kg8 19. N@h5) 13. Nxf5 { [%eval 3.65] [%clk 0:03:20] } 13... Nxf5 { [%eval 5.1] [%clk 0:04:39] } 14. B@b7 { [%eval 4.07] [%clk 0:03:33] } 14... N@e3 { [%eval 4.93] [%clk 0:04:26] } 15. Nxc6?? { (4.93 → 1.71) Blunder. Bxe3 was best. } { [%eval 1.71] [%clk 0:03:14] } (15. Bxe3 Nxe3) 15... Nxd1 { [%eval 3.35] [%clk 0:04:16] } 16. Nxd8 { [%eval 1.06] [%clk 0:03:26] } 16... Rxd8?? { (1.06 → 4.55) Blunder. Nxd4 was best. } { [%eval 4.55] [%clk 0:04:24] } (16... Nxd4 17. cxd4 P@f2+ 18. Rxf2 Nxf2 19. Kxf2 Rxd8 20. Bd2 Q@h4+ 21. Q@g3 Qxg3+ 22. hxg3 R@h2 23. N@f4) 17. Rxd1 { [%eval 4.69] [%clk 0:03:28] } 17... Q@b5 { [%eval 5.71] [%clk 0:04:13] } 18. Bxa6?? { (5.71 → 1.41) Blunder. P@g6 was best. } { [%eval 1.41] [%clk 0:03:21] } (18. P@g6 fxg6 19. Bxa6 Qxa6 20. P@f7+ Ke7 21. Nf4 Bb5 22. c4 Bxc4 23. Q@g5+ P@f6 24. exf6+ gxf6) 18... Qxa6 { [%eval 1.22] [%clk 0:04:03] } 19. P@d3?? { (1.22 → -2.85) Blunder. P@g6 was best. } { [%eval -2.85] [%clk 0:03:31] } (19. P@g6 B@g8) 19... N@e3?? { (-2.85 → 0.05) Blunder. h4 was best. } { [%eval 0.05] [%clk 0:03:55] } (19... h4) 20. Bxe3 { [%eval -0.32] [%clk 0:03:37] } 20... Nxe3 { [%eval 0.0] [%clk 0:04:01] } 21. P@d6?? { (0.00 → -2.41) Blunder. N@g6 was best. } { [%eval -2.41] [%clk 0:01:29] } (21. N@g6 Nxd1 22. Rxd1 B@e3+ 23. P@f2 fxg6 24. fxe3 B@h4 25. B@g3 Bxg3 26. hxg3 B@g5 27. B@f4 Bfe7) 21... Nxd1?? { (-2.41 → Mate in 3) Checkmate is now unavoidable. Bxd6 was best. } { [%eval #3] [%clk 0:03:55] } (21... Bxd6 22. exd6) 22. Rxd1?? { (Mate in 3 → -1.88) Lost forced checkmate sequence. N@f6+ was best. } { [%eval -1.88] [%clk 0:01:44] } (22. N@f6+ gxf6 23. N@g7+ Bxg7 24. Q@e7#) 22... B@e3+?? { (-1.88 → 0.00) Blunder. Bxd6 was best. } { [%eval 0.0] [%clk 0:04:02] } (22... Bxd6 23. exd6 Qxd6 24. B@e3 B@h4 25. N@e5 O-O 26. g3 B@g5 27. N@f4 Qxe5 28. dxe5 N@h3+ 29. Nxh3) 23. Kh1 { [%eval -0.42] [%clk 0:01:51] } 23... Bxd6 { [%eval 0.07] [%clk 0:03:55] } 24. exd6 { [%eval 0.55] [%clk 0:02:02] } 24... Qxd6 { [%eval 1.38] [%clk 0:04:01] } 25. B@b4?? { (1.38 → -8.92) Blunder. B@e5 was best. } { [%eval -8.92] [%clk 0:01:08] } (25. B@e5 B@e7) 25... Qxb4?? { (-8.92 → 0.53) Blunder. P@h3 was best. } { [%eval 0.53] [%clk 0:03:55] } (25... P@h3) 26. cxb4 { [%eval -0.77] [%clk 0:01:21] } 26... P@f2?? { (-0.77 → 4.95) Blunder. B@f6 was best. } { [%eval 4.95] [%clk 0:03:49] } (26... B@f6 27. Q@g3 B@d6 28. N@e5 P@h3 29. Q@f1 Bxd4 30. Nxf7 Kxf7 31. P@g6+ Kf8 32. Qxh3 N@f2+ 33. Qxf2) 27. Q@f1?? { (4.95 → -0.96) Blunder. N@g6 was best. } { [%eval -0.96] [%clk 0:00:19] } (27. N@g6) 27... R@g1+?? { (-0.96 → 8.81) Blunder. B@f6 was best. } { [%eval 8.81] [%clk 0:03:42] } (27... B@f6 28. N@h3 B@h4 29. N@e5 Bxe5 30. dxe5 N@f5 31. Nxf2 Bexf2 32. P@h7 Kf8 33. N@h3 Be3 34. B@g1) 28. Qxg1?? { (8.81 → 0.00) Blunder. Nxg1 was best. } { [%eval 0.0] [%clk 0:00:23] } (28. Nxg1) 28... fxg1=Q+ { [%eval 0.0] [%clk 0:03:49] } 29. Rxg1 { [%eval 0.07] [%clk 0:00:37] } 29... P@f2 { [%eval 0.58] [%clk 0:03:30] } 30. N@g6?! { (0.58 → 0.00) Inaccuracy. Rf1 was best. } { [%eval 0.0] [%clk 0:00:27] } (30. Rf1 B@d6) 30... fxg1=Q+?? { (0.00 → 6.00) Blunder. fxg6 was best. } { [%eval 6.0] [%clk 0:03:34] } (30... fxg6 31. P@f7+ Kf8 32. R@g8+ Ke7 33. f8=Q+ Rxf8 34. Rxg7+ P@f7 35. P@f6+ Kxf6 36. Q@e5+ Ke7 37. Qxe3) 31. Nxg1 { [%eval 4.02] [%clk 0:00:41] } 31... Q@e7? { (4.02 → 8.29) Mistake. B@e7 was best. } { [%eval 8.29] [%clk 0:03:18] } (31... B@e7 32. Q@f6) 32. Q@d6?? { (8.29 → -5.15) Blunder. Q@f6 was best. } { [%eval -5.15] [%clk 0:00:27] } (32. Q@f6 B@g5) 32... fxg6?? { (-5.15 → Mate in 6) Checkmate is now unavoidable. B@f6 was best. } { [%eval #6] [%clk 0:03:10] } (32... B@f6 33. Qxe7+ Bxe7 34. Q@e1 Bxg1 35. Qxg1 R@a1 36. B@e1 Q@e2 37. R@f1 fxg6 38. N@e5 N@f7 39. Nxf7) 33. Qxe7+ { [%eval #5] [%clk 0:00:40] } 33... Kxe7 { [%eval #5] [%clk 0:03:25] } 34. R@f7+ { [%eval #4] [%clk 0:00:54] } 34... Kxf7 { [%eval #4] [%clk 0:03:35] } 35. N@e5+ { [%eval #3] [%clk 0:01:08] } 35... Kg8 { [%eval #2] [%clk 0:03:30] } 36. N@f6+ { [%eval #1] [%clk 0:01:19] } 36... gxf6 { [%eval #1] [%clk 0:03:42] } 37. Q@f7# { [%clk 0:01:18] } { White wins by checkmate. } 1-0 Fairy-Stockfish-fairy_sf_14_0_1_xq/tests/pgn/pychess-variants_zJxHRVm1.pgn000066400000000000000000000006561414571233100267560ustar00rootroot00000000000000[Event "PyChess casual game"] [Site "https://www.pychess.org/zJxHRVm1"] [Date "2020.05.28"] [Round "-"] [White "catask"] [Black "Fairy-Stockfish"] [Result "1-0"] [TimeControl "300+3"] [WhiteElo "1500?"] [BlackElo "1500?"] [Variant "Seirawan"] [FEN "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR[HEhe] w KQBCDFGkqbcdfg - 0 1"] [SetUp "1"] 1. e4 d5 2. exd5 Qxd5/E 3. Nc3 Qf5 4. d4 Nf6 5. Nf3 Nc6 6. d5 Ne4 7. dxc6 Nc5 8. Qxd8/E# 1-0 Fairy-Stockfish-fairy_sf_14_0_1_xq/tests/protocol.sh000077500000000000000000000025631414571233100227070ustar00rootroot00000000000000#!/bin/bash # verify protocol implementations error() { echo "protocol testing failed on line $1" exit 1 } trap 'error ${LINENO}' ERR echo "protocol testing started" cat << EOF > uci.exp spawn ./stockfish send "uci\\n" expect "default chess" expect "uciok" send "quit\\n" expect eof EOF cat << EOF > ucci.exp spawn ./stockfish send "ucci\\n" expect "option UCI_Variant" expect "default xiangqi" expect "ucciok" send "quit\\n" expect eof EOF cat << EOF > usi.exp spawn ./stockfish send "usi\\n" expect "default shogi" expect "usiok" send "quit\\n" expect eof EOF cat << EOF > ucicyclone.exp spawn ./stockfish send "uci\\n" expect "uciok" send "startpos\\n" send "d\\n" expect "rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1" send "quit\\n" expect eof EOF cat << EOF > xboard.exp spawn ./stockfish load variants.ini send "xboard\\n" send "protover 2\\n" expect "feature done=1" send "ping\\n" expect "pong" send "ping\\n" expect "pong" send "variant 3check-crazyhouse\\n" expect "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR\\\\\[] w KQkq - 3+3 0 1" send "quit\\n" expect eof EOF for exp in uci.exp ucci.exp usi.exp ucicyclone.exp xboard.exp do echo "Testing $exp" timeout 5 expect $exp > /dev/null rm $exp done echo "protocol testing OK" Fairy-Stockfish-fairy_sf_14_0_1_xq/tests/reprosearch.sh000077500000000000000000000023741414571233100233630ustar00rootroot00000000000000#!/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" Fairy-Stockfish-fairy_sf_14_0_1_xq/tests/signature.sh000077500000000000000000000013761414571233100230500ustar00rootroot00000000000000#!/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