From 27c0b43fcf6764dd4ed8e8870f8090da838bbe24 Mon Sep 17 00:00:00 2001 From: Milan Zamazal Date: Wed, 9 Nov 2016 18:45:41 +0000 Subject: [PATCH] Import stockfish_8.orig.tar.gz [dgit import orig stockfish_8.orig.tar.gz] --- .travis.yml | 55 ++ AUTHORS | 98 +++ Copying.txt | 674 ++++++++++++++++ Readme.md | 115 +++ appveyor.yml | 45 ++ src/Makefile | 545 +++++++++++++ src/benchmark.cpp | 183 +++++ src/bitbase.cpp | 180 +++++ src/bitboard.cpp | 333 ++++++++ src/bitboard.h | 338 ++++++++ src/endgame.cpp | 845 ++++++++++++++++++++ src/endgame.h | 125 +++ src/evaluate.cpp | 927 ++++++++++++++++++++++ src/evaluate.h | 40 + src/main.cpp | 54 ++ src/material.cpp | 225 ++++++ src/material.h | 73 ++ src/misc.cpp | 187 +++++ src/misc.h | 100 +++ src/movegen.cpp | 420 ++++++++++ src/movegen.h | 70 ++ src/movepick.cpp | 350 +++++++++ src/movepick.h | 123 +++ src/pawns.cpp | 291 +++++++ src/pawns.h | 88 +++ src/position.cpp | 1168 ++++++++++++++++++++++++++++ src/position.h | 414 ++++++++++ src/psqt.cpp | 126 +++ src/search.cpp | 1656 ++++++++++++++++++++++++++++++++++++++++ src/search.h | 111 +++ src/syzygy/tbcore.cpp | 1378 +++++++++++++++++++++++++++++++++ src/syzygy/tbcore.h | 169 ++++ src/syzygy/tbprobe.cpp | 824 ++++++++++++++++++++ src/syzygy/tbprobe.h | 19 + src/thread.cpp | 223 ++++++ src/thread.h | 111 +++ src/thread_win32.h | 70 ++ src/timeman.cpp | 132 ++++ src/timeman.h | 48 ++ src/tt.cpp | 117 +++ src/tt.h | 121 +++ src/types.h | 435 +++++++++++ src/uci.cpp | 289 +++++++ src/uci.h | 80 ++ src/ucioption.cpp | 163 ++++ 45 files changed, 14138 insertions(+) create mode 100644 .travis.yml create mode 100644 AUTHORS create mode 100644 Copying.txt create mode 100644 Readme.md create mode 100644 appveyor.yml create mode 100644 src/Makefile create mode 100644 src/benchmark.cpp create mode 100644 src/bitbase.cpp create mode 100644 src/bitboard.cpp create mode 100644 src/bitboard.h create mode 100644 src/endgame.cpp create mode 100644 src/endgame.h create mode 100644 src/evaluate.cpp create mode 100644 src/evaluate.h create mode 100644 src/main.cpp create mode 100644 src/material.cpp create mode 100644 src/material.h create mode 100644 src/misc.cpp create mode 100644 src/misc.h create mode 100644 src/movegen.cpp create mode 100644 src/movegen.h create mode 100644 src/movepick.cpp create mode 100644 src/movepick.h create mode 100644 src/pawns.cpp create mode 100644 src/pawns.h create mode 100644 src/position.cpp create mode 100644 src/position.h create mode 100644 src/psqt.cpp create mode 100644 src/search.cpp create mode 100644 src/search.h create mode 100644 src/syzygy/tbcore.cpp create mode 100644 src/syzygy/tbcore.h create mode 100644 src/syzygy/tbprobe.cpp create mode 100644 src/syzygy/tbprobe.h create mode 100644 src/thread.cpp create mode 100644 src/thread.h create mode 100644 src/thread_win32.h create mode 100644 src/timeman.cpp create mode 100644 src/timeman.h create mode 100644 src/tt.cpp create mode 100644 src/tt.h create mode 100644 src/types.h create mode 100644 src/uci.cpp create mode 100644 src/uci.h create mode 100644 src/ucioption.cpp diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..af0bb2b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,55 @@ +language: cpp +sudo: required +dist: trusty + +matrix: + include: + - os: linux + compiler: gcc + addons: + apt: + sources: ['ubuntu-toolchain-r-test'] + packages: ['g++-6', 'g++-6-multilib', 'g++-multilib', 'valgrind'] + env: + - COMPILER=g++-6 + - COMP=gcc + + - os: linux + compiler: clang + addons: + apt: + sources: ['ubuntu-toolchain-r-test'] + packages: ['clang', 'g++-multilib', 'valgrind'] + env: + - COMPILER=clang++ + - COMP=clang + + - os: osx + compiler: gcc + env: + - COMPILER=g++ + - COMP=gcc + + - os: osx + compiler: clang + env: + - COMPILER=clang++ V='Apple LLVM 6.0' # Apple LLVM version 6.0 (clang-600.0.54) (based on LLVM 3.5svn) + - COMP=clang + +branches: + only: + - master + +before_script: + - cd src + +script: + - make clean && make build ARCH=x86-64 && ./stockfish bench 2>&1 >/dev/null | grep 'Nodes searched' | tee bench1 + - make clean && make build ARCH=x86-32 && ./stockfish bench 2>&1 >/dev/null | grep 'Nodes searched' | tee bench2 + - echo "Checking for same bench numbers..." + - diff bench1 bench2 > result + - test ! -s result + # if valgrind is available check the build is without error, reduce depth to speedup testing, but not too shallow to catch more cases. + - if [ -x "$(command -v valgrind )" ] ; then make clean && make ARCH=x86-64 debug=yes build && valgrind --error-exitcode=42 ./stockfish bench 128 1 10 default depth 1>/dev/null ; fi + # use g++-6 as a proxy for having sanitizers ... might need revision as they become available for more recent versions of clang/gcc than trusty provides + - if [[ "$COMPILER" == "g++-6" ]]; then make clean && make ARCH=x86-64 sanitize=yes build && ! ./stockfish bench 2>&1 | grep "runtime error:" ; fi diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..5cd63d2 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,98 @@ +# Generated with git shortlog -sn | cut -c8-', which sorts by commits (manually ordered the first four authors) + +Tord Romstad +Marco Costalba +Joona Kiiski +Gary Linscott +lucasart +mstembera +Lucas Braesch +Stefan Geschwentner +Reuven Peleg +Chris Caino +joergoster +VoyagerOne +Jean-Francois Romang +homoSapiensSapiens +Alain SAVARD +Arjun Temurnikar +Stéphane Nicolet +Uri Blass +jundery +Ralph Stößer +Ajith +Leonid Pechenik +Stefano80 +Tom Vijlbrief +hxim +snicolet +Daylen Yang +Henri Wiechers +Jonathan Calovski +mbootsector +David Zar +Eelco de Groot +Jerry Donald +Joerg Oster +Jörg Oster +Ryan Schmitt +mcostalba +Alexander Kure +Dan Schmidt +H. Felix Wittmann +Joseph R. Prostko +Justin Blanchard +Linus Arver +NicklasPersson +Rodrigo Exterckötter Tjäder +Ron Britvich +Ronald de Man +RyanTaker +Vince Negri +ceebo +jhellis3 +ppigazzini +shane31 +Andy Duplain +Auguste Pop +Balint Pfliegel +Chris Cain +DU-jdto +Dariusz Orzechowski +DiscanX +Ernesto Gatti +Gregor Cramer +Guenther Demetz +Hiraoka Takuya +Hongzhi Cheng +Joseph Hellis +Kelly Wilson +Ken T Takusagawa +Kojirion +Luca Brivio +Matt Sullivan +Matthew Lai +Matthew Sullivan +Michel Van den Bergh +Mysseno +Oskar Werkelin Ahlin +Pablo Vazquez +Pascal Romaret +Ralph Stoesser +Ralph Stößer +Raminder Singh +Richard Lloyd +Ryan Takker +Stephane Nicolet +Thanar2 +absimaldata +braich +gguliash +kinderchocolate +loco-loco +pellanda +renouve +sf-x +thaspel +unknown +uriblass diff --git a/Copying.txt b/Copying.txt new file mode 100644 index 0000000..818433e --- /dev/null +++ b/Copying.txt @@ -0,0 +1,674 @@ + 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 +. diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..29ebf59 --- /dev/null +++ b/Readme.md @@ -0,0 +1,115 @@ +### Overview + +[![Build Status](https://travis-ci.org/official-stockfish/Stockfish.svg?branch=master)](https://travis-ci.org/official-stockfish/Stockfish) +[![Build Status](https://ci.appveyor.com/api/projects/status/github/official-stockfish/Stockfish?svg=true)](https://ci.appveyor.com/project/mcostalba/stockfish) + +Stockfish is a free UCI chess engine derived from Glaurung 2.1. It is +not a complete chess program and requires some UCI-compatible GUI +(e.g. XBoard with PolyGlot, 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. + +This version of Stockfish supports up to 128 cores. The engine defaults +to one search thread, so it is therefore recommended to inspect the value of +the *Threads* UCI parameter, and to make sure it equals the number of CPU +cores on your computer. + +This version of Stockfish has support for Syzygybases. + + +### Files + +This distribution of Stockfish consists of the following files: + + * Readme.md, the file you are currently reading. + + * Copying.txt, a text file containing the GNU General Public License. + + * src, a subdirectory containing the full source code, including a Makefile + that can be used to compile Stockfish on Unix-like systems. + + +### Syzygybases + +**Configuration** + +Syzygybases are configured using the UCI options "SyzygyPath", +"SyzygyProbeDepth", "Syzygy50MoveRule" and "SyzygyProbeLimit". + +The option "SyzygyPath" should be set to the directory or directories that +contain the .rtbw and .rtbz files. Multiple directories should 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. + +Increasing the "SyzygyProbeDepth" option lets the engine probe less +aggressively. Set this option to a higher value if you experience too much +slowdown (in terms of nps) due to TB probing. + +Set the "Syzygy50MoveRule" option to false if you want tablebase positions +that are drawn by the 50-move rule to count as win or loss. This may be useful +for correspondence games (because of tablebase adjudication). + +The "SyzygyProbeLimit" option should normally be left at its default value. + +**What to expect** +If the engine is searching a position that is not in the tablebases (e.g. +a position with 7 pieces), it will access the tablebases during the search. +If the engine reports a very large score (typically 123.xx), this means +that it has found a winning line into a tablebase position. + +If the engine is given a position to search that is in the tablebases, it +will use the tablebases at the beginning of the search to preselect all +good moves, i.e. all moves that preserve the win or preserve the draw while +taking into account the 50-move rule. +It will then perform a search only on those moves. **The engine will not move +immediately**, unless there is only a single good move. **The engine likely +will not report a mate score even if the position is known to be won.** + +It is therefore clear that behaviour is not identical to what one might +be used to with Nalimov tablebases. There are technical reasons for this +difference, the main technical reason being that Nalimov tablebases use the +DTM metric (distance-to-mate), while Syzygybases use a variation of the +DTZ metric (distance-to-zero, zero meaning any move that resets the 50-move +counter). This special metric is one of the reasons that Syzygybases are +more compact than Nalimov tablebases, while still storing all information +needed for optimal play and in addition being able to take into account +the 50-move rule. + + +### Compiling it yourself + +On Unix-like systems, it should be possible to compile Stockfish +directly from the source code with the included Makefile. + +Stockfish has support for 32 or 64-bit CPUs, the hardware POPCNT +instruction, big-endian machines such as Power PC, and other platforms. + +In general it is recommended to run `make help` to see a list of make +targets with corresponding descriptions. When not using the Makefile to +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. + + +### Terms of use + +Stockfish is free, and distributed under the **GNU General Public License** +(GPL). Essentially, this means that you are free to do almost exactly +what you want with the program, including distributing it among your +friends, making it available for download from your web site, selling +it (either by itself or as part of some bigger software package), or +using it as the starting point for a software project of your own. + +The only real limitation is that whenever you distribute Stockfish in +some way, you must always include the full source code, or a pointer +to where the source code can be found. If you make any changes to the +source code, these changes must also be made available under the GPL. + +For full details, read the copy of the GPL found in the file named +*Copying.txt*. diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..a46a0f3 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,45 @@ +version: 1.0.{build} +clone_depth: 5 + +branches: + only: + - master + - appveyor + +# Operating system (build VM template) +os: Visual Studio 2015 + +# Build platform, i.e. x86, x64, AnyCPU. This setting is optional. +platform: + - x86 + - x64 + - Any CPU + +# build Configuration, i.e. Debug, Release, etc. +configuration: Debug + +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: + - cd src + - echo project (Stockfish) >> CMakeLists.txt + - echo add_executable(stockfish benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp >> CMakeLists.txt + - echo main.cpp material.cpp misc.cpp movegen.cpp movepick.cpp pawns.cpp position.cpp psqt.cpp >> CMakeLists.txt + - echo search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp syzygy/tbprobe.cpp) >> CMakeLists.txt + - echo set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/src) >> CMakeLists.txt +# - echo target_compile_options(stockfish PUBLIC "/Ox") >> CMakeLists.txt + +build_script: + - cmake -G "Visual Studio 14 2015 Win64" . + - cmake --build . + +before_test: + - cd Debug + - stockfish.exe bench > null diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..cdedd25 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,545 @@ +# Stockfish, a UCI chess playing engine derived from Glaurung 2.1 +# Copyright (C) 2004-2008 Tord Romstad (Glaurung author) +# Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad +# Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad +# +# Stockfish is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Stockfish is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +### ========================================================================== +### Section 1. General Configuration +### ========================================================================== + +### Establish the operating system name +KERNEL = $(shell uname -s) +ifeq ($(KERNEL),Linux) + OS = $(shell uname -o) +endif + +### Executable name +EXE = stockfish + +### Installation dir definitions +PREFIX = /usr/local +BINDIR = $(PREFIX)/bin + +### Built-in benchmark for pgo-builds +PGOBENCH = ./$(EXE) bench + +### Object files +OBJS = benchmark.o bitbase.o bitboard.o endgame.o evaluate.o main.o \ + material.o misc.o movegen.o movepick.o pawns.o position.o psqt.o \ + search.o thread.o timeman.o tt.o uci.o ucioption.o syzygy/tbprobe.o + +### ========================================================================== +### Section 2. High-level Configuration +### ========================================================================== +# +# flag --- Comp switch --- Description +# ---------------------------------------------------------------------------- +# +# debug = yes/no --- -DNDEBUG --- Enable/Disable debug mode +# sanitize = yes/no --- (-fsanitize ) --- enable undefined behavior checks +# optimize = yes/no --- (-O3/-fast etc.) --- Enable/Disable optimizations +# arch = (name) --- (-arch) --- Target architecture +# bits = 64/32 --- -DIS_64BIT --- 64-/32-bit operating system +# prefetch = yes/no --- -DUSE_PREFETCH --- Use prefetch asm-instruction +# popcnt = yes/no --- -DUSE_POPCNT --- Use popcnt asm-instruction +# sse = yes/no --- -msse --- Use Intel Streaming SIMD Extensions +# pext = yes/no --- -DUSE_PEXT --- Use pext x86_64 asm-instruction +# +# Note that Makefile is space sensitive, so when adding new architectures +# or modifying existing flags, you have to make sure there are no extra spaces +# at the end of the line for flag values. + +### 2.1. General and architecture defaults +optimize = yes +debug = no +sanitize = no +bits = 32 +prefetch = no +popcnt = no +sse = no +pext = no + +### 2.2 Architecture specific + +ifeq ($(ARCH),general-32) + arch = any +endif + +ifeq ($(ARCH),x86-32-old) + arch = i386 +endif + +ifeq ($(ARCH),x86-32) + arch = i386 + prefetch = yes + sse = yes +endif + +ifeq ($(ARCH),general-64) + arch = any + bits = 64 +endif + +ifeq ($(ARCH),x86-64) + arch = x86_64 + bits = 64 + prefetch = yes + sse = yes +endif + +ifeq ($(ARCH),x86-64-modern) + arch = x86_64 + bits = 64 + prefetch = yes + popcnt = yes + sse = yes +endif + +ifeq ($(ARCH),x86-64-bmi2) + arch = x86_64 + bits = 64 + prefetch = yes + popcnt = yes + sse = yes + pext = yes +endif + +ifeq ($(ARCH),armv7) + arch = armv7 + prefetch = yes +endif + +ifeq ($(ARCH),ppc-32) + arch = ppc +endif + +ifeq ($(ARCH),ppc-64) + arch = ppc64 + bits = 64 +endif + + +### ========================================================================== +### Section 3. Low-level configuration +### ========================================================================== + +### 3.1 Selecting compiler (default = gcc) + +CXXFLAGS += -Wall -Wcast-qual -fno-exceptions -fno-rtti -std=c++11 $(EXTRACXXFLAGS) +DEPENDFLAGS += -std=c++11 +LDFLAGS += $(EXTRALDFLAGS) + +ifeq ($(COMP),) + COMP=gcc +endif + +ifeq ($(COMP),gcc) + comp=gcc + CXX=g++ + CXXFLAGS += -pedantic -Wextra -Wshadow + + ifeq ($(ARCH),armv7) + ifeq ($(OS),Android) + CXXFLAGS += -m$(bits) + endif + else + CXXFLAGS += -m$(bits) + 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 + + ifeq ($(ARCH),armv7) + ifeq ($(OS),Android) + CXXFLAGS += -m$(bits) + LDFLAGS += -m$(bits) + endif + else + CXXFLAGS += -m$(bits) + LDFLAGS += -m$(bits) + endif + + ifeq ($(KERNEL),Darwin) + CXXFLAGS += -stdlib=libc++ + DEPENDFLAGS += -stdlib=libc++ + endif +endif + +ifeq ($(comp),icc) + profile_prepare = icc-profile-prepare + profile_make = icc-profile-make + profile_use = icc-profile-use + profile_clean = icc-profile-clean +else + profile_prepare = gcc-profile-prepare + profile_make = gcc-profile-make + profile_use = gcc-profile-use + profile_clean = gcc-profile-clean +endif + +ifeq ($(KERNEL),Darwin) + CXXFLAGS += -arch $(arch) -mmacosx-version-min=10.9 + LDFLAGS += -arch $(arch) -mmacosx-version-min=10.9 +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 + +### On mingw use Windows threads, otherwise POSIX +ifneq ($(comp),mingw) + # 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) + LDFLAGS += -lpthread + endif + endif +endif + +### 3.2.1 Debugging +ifeq ($(debug),no) + CXXFLAGS += -DNDEBUG +else + CXXFLAGS += -g +endif + +### 3.2.2 Debugging with undefined behavior sanitizers +ifeq ($(sanitize),yes) + CXXFLAGS += -g3 -fsanitize=undefined +endif + +### 3.3 Optimization +ifeq ($(optimize),yes) + + CXXFLAGS += -O3 + + ifeq ($(comp),gcc) + + ifeq ($(KERNEL),Darwin) + ifeq ($(arch),i386) + CXXFLAGS += -mdynamic-no-pic + endif + ifeq ($(arch),x86_64) + CXXFLAGS += -mdynamic-no-pic + endif + endif + + ifeq ($(OS), Android) + CXXFLAGS += -fno-gcse -mthumb -march=armv7-a -mfloat-abi=softfp + endif + endif + + ifeq ($(comp),icc) + ifeq ($(KERNEL),Darwin) + CXXFLAGS += -mdynamic-no-pic + endif + endif + + ifeq ($(comp),clang) + ifeq ($(KERNEL),Darwin) + ifeq ($(pext),no) + CXXFLAGS += -flto + LDFLAGS += $(CXXFLAGS) + endif + ifeq ($(arch),i386) + CXXFLAGS += -mdynamic-no-pic + endif + ifeq ($(arch),x86_64) + CXXFLAGS += -mdynamic-no-pic + endif + endif + endif +endif + +### 3.4 Bits +ifeq ($(bits),64) + CXXFLAGS += -DIS_64BIT +endif + +### 3.5 prefetch +ifeq ($(prefetch),yes) + ifeq ($(sse),yes) + CXXFLAGS += -msse + DEPENDFLAGS += -msse + endif +else + CXXFLAGS += -DNO_PREFETCH +endif + +### 3.6 popcnt +ifeq ($(popcnt),yes) + ifeq ($(comp),icc) + CXXFLAGS += -msse3 -DUSE_POPCNT + else + CXXFLAGS += -msse3 -mpopcnt -DUSE_POPCNT + 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, it works since gcc 4.5 but not on mingw under Windows. +### This is a mix of compile and link time options because the lto link phase +### needs access to the optimization flags. +ifeq ($(comp),gcc) + ifeq ($(optimize),yes) + ifeq ($(debug),no) + CXXFLAGS += -flto + LDFLAGS += $(CXXFLAGS) + endif + endif +endif + +ifeq ($(comp),mingw) + ifeq ($(KERNEL),Linux) + ifeq ($(optimize),yes) + ifeq ($(debug),no) + CXXFLAGS += -flto + LDFLAGS += $(CXXFLAGS) + 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 "build > Standard build" + @echo "profile-build > PGO build" + @echo "strip > Strip executable" + @echo "install > Install executable" + @echo "clean > Clean up" + @echo "" + @echo "Supported archs:" + @echo "" + @echo "x86-64 > x86 64-bit" + @echo "x86-64-modern > x86 64-bit with popcnt support" + @echo "x86-64-bmi2 > x86 64-bit with pext support" + @echo "x86-32 > x86 32-bit with SSE support" + @echo "x86-32-old > x86 32-bit fall back for old hardware" + @echo "ppc-64 > PPC 64-bit" + @echo "ppc-32 > PPC 32-bit" + @echo "armv7 > ARMv7 32-bit" + @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 "" + @echo "Simple examples. If you don't know what to do, you likely want to run: " + @echo "" + @echo "make build ARCH=x86-64 (This is for 64-bit systems)" + @echo "make build ARCH=x86-32 (This is for 32-bit systems)" + @echo "" + @echo "Advanced examples, for experienced users: " + @echo "" + @echo "make build ARCH=x86-64 COMP=clang" + @echo "make profile-build ARCH=x86-64-modern COMP=gcc COMPCXX=g++-4.8" + @echo "" + + +.PHONY: build profile-build +build: + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) config-sanity + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) all + +profile-build: + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) config-sanity + @echo "" + @echo "Step 0/4. Preparing for profile build." + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_prepare) + @echo "" + @echo "Step 1/4. Building executable for benchmark ..." + @touch *.cpp *.h syzygy/*.cpp syzygy/*.h + $(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 final executable ..." + @touch *.cpp *.h syzygy/*.cpp syzygy/*.h + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_use) + @echo "" + @echo "Step 4/4. Deleting profile data ..." + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_clean) + +strip: + strip $(EXE) + +install: + -mkdir -p -m 755 $(BINDIR) + -cp $(EXE) $(BINDIR) + -strip $(BINDIR)/$(EXE) + +clean: + $(RM) $(EXE) $(EXE).exe *.o .depend *~ core bench.txt *.gcda ./syzygy/*.o ./syzygy/*.gcda + +default: + help + +### ========================================================================== +### Section 5. Private targets +### ========================================================================== + +all: $(EXE) .depend + +config-sanity: + @echo "" + @echo "Config:" + @echo "debug: '$(debug)'" + @echo "optimize: '$(optimize)'" + @echo "arch: '$(arch)'" + @echo "bits: '$(bits)'" + @echo "kernel: '$(KERNEL)'" + @echo "os: '$(OS)'" + @echo "prefetch: '$(prefetch)'" + @echo "popcnt: '$(popcnt)'" + @echo "sse: '$(sse)'" + @echo "pext: '$(pext)'" + @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 "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \ + test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || test "$(arch)" = "armv7" + @test "$(bits)" = "32" || test "$(bits)" = "64" + @test "$(prefetch)" = "yes" || test "$(prefetch)" = "no" + @test "$(popcnt)" = "yes" || test "$(popcnt)" = "no" + @test "$(sse)" = "yes" || test "$(sse)" = "no" + @test "$(pext)" = "yes" || test "$(pext)" = "no" + @test "$(comp)" = "gcc" || test "$(comp)" = "icc" || test "$(comp)" = "mingw" || test "$(comp)" = "clang" + +$(EXE): $(OBJS) + $(CXX) -o $@ $(OBJS) $(LDFLAGS) + +gcc-profile-prepare: + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) gcc-profile-clean + +gcc-profile-make: + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ + EXTRACXXFLAGS='-fprofile-generate' \ + EXTRALDFLAGS='-lgcov' \ + all + +gcc-profile-use: + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ + EXTRACXXFLAGS='-fprofile-use -fno-peel-loops -fno-tracer' \ + EXTRALDFLAGS='-lgcov' \ + all + +gcc-profile-clean: + @rm -rf *.gcda *.gcno syzygy/*.gcda syzygy/*.gcno bench.txt + +icc-profile-prepare: + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) icc-profile-clean + @mkdir profdir + +icc-profile-make: + $(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 + +icc-profile-clean: + @rm -rf profdir bench.txt + +.depend: + -@$(CXX) $(DEPENDFLAGS) -MM $(OBJS:.o=.cpp) > $@ 2> /dev/null + +-include .depend + diff --git a/src/benchmark.cpp b/src/benchmark.cpp new file mode 100644 index 0000000..f2d1f06 --- /dev/null +++ b/src/benchmark.cpp @@ -0,0 +1,183 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include + +#include "misc.h" +#include "position.h" +#include "search.h" +#include "thread.h" +#include "uci.h" + +using namespace std; + +namespace { + +const vector Defaults = { + "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", + "r1bq1r1k/1pp1n1pp/1p1p4/4p2Q/4Pp2/1BNP4/PPP2PPP/3R1RK1 w - - 2 14", + "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", + "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", + + // 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 + "8/8/8/8/8/6k1/6p1/6K1 w - -", + "5k2/5P2/5K2/8/8/8/8/8 b - -", + "8/8/8/8/8/4k3/4p3/4K3 w - -", + "8/8/8/8/8/5K2/8/3Q1k2 b - -", + "7k/7P/6K1/8/3B4/8/8/8 b - -" +}; + +} // namespace + +/// benchmark() runs a simple benchmark by letting Stockfish analyze a set +/// of positions for a given limit each. There are five parameters: the +/// transposition table size, the number of search threads that should +/// be used, the limit value spent for each position (optional, default is +/// depth 13), an optional file name where to look for positions in FEN +/// format (defaults are the positions defined above) and the type of the +/// limit value: depth (default), time in millisecs or number of nodes. + +void benchmark(const Position& current, istream& is) { + + string token; + vector fens; + Search::LimitsType limits; + + // 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"; + + Options["Hash"] = ttSize; + Options["Threads"] = threads; + Search::clear(); + + if (limitType == "time") + limits.movetime = stoi(limit); // movetime is in millisecs + + else if (limitType == "nodes") + limits.nodes = stoi(limit); + + else if (limitType == "mate") + limits.mate = stoi(limit); + + else + limits.depth = stoi(limit); + + if (fenFile == "default") + fens = Defaults; + + else if (fenFile == "current") + fens.push_back(current.fen()); + + else + { + string fen; + ifstream file(fenFile); + + if (!file.is_open()) + { + cerr << "Unable to open file " << fenFile << endl; + return; + } + + while (getline(file, fen)) + if (!fen.empty()) + fens.push_back(fen); + + file.close(); + } + + uint64_t nodes = 0; + TimePoint elapsed = now(); + Position pos; + + for (size_t i = 0; i < fens.size(); ++i) + { + StateListPtr states(new std::deque(1)); + pos.set(fens[i], Options["UCI_Chess960"], &states->back(), Threads.main()); + + cerr << "\nPosition: " << i + 1 << '/' << fens.size() << endl; + + if (limitType == "perft") + nodes += Search::perft(pos, limits.depth * ONE_PLY); + + else + { + limits.startTime = now(); + Threads.start_thinking(pos, states, limits); + Threads.main()->wait_for_search_finished(); + nodes += Threads.nodes_searched(); + } + } + + 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; +} diff --git a/src/bitbase.cpp b/src/bitbase.cpp new file mode 100644 index 0000000..4170952 --- /dev/null +++ b/src/bitbase.cpp @@ -0,0 +1,180 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include + +#include "bitboard.h" +#include "types.h" + +namespace { + + // There are 24 possible pawn squares: the first 4 files and ranks from 2 to 7 + const unsigned MAX_INDEX = 2*24*64*64; // stm * psq * wksq * bksq = 196608 + + // Each uint32_t stores results of 32 positions, one per bit + uint32_t KPKBitbase[MAX_INDEX / 32]; + + // 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 us, Square bksq, Square wksq, Square psq) { + return wksq | (bksq << 6) | (us << 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) + { return us == WHITE ? classify(db) : classify(db); } + + template Result classify(const std::vector& db); + + Color us; + Square ksq[COLOR_NB], psq; + Result result; + }; + +} // namespace + + +bool Bitbases::probe(Square wksq, Square wpsq, Square bksq, Color us) { + + assert(file_of(wpsq) <= FILE_D); + + unsigned idx = index(us, bksq, wksq, wpsq); + return KPKBitbase[idx / 32] & (1 << (idx & 0x1F)); +} + + +void Bitbases::init() { + + 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); + + // Map 32 results into one KPKBitbase[] entry + for (idx = 0; idx < MAX_INDEX; ++idx) + if (db[idx] == WIN) + KPKBitbase[idx / 32] |= 1 << (idx & 0x1F); +} + + +namespace { + + KPKPosition::KPKPosition(unsigned idx) { + + ksq[WHITE] = Square((idx >> 0) & 0x3F); + ksq[BLACK] = Square((idx >> 6) & 0x3F); + us = Color ((idx >> 12) & 0x01); + psq = make_square(File((idx >> 13) & 0x3), RANK_7 - Rank((idx >> 15) & 0x7)); + + // Check 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 + || (us == WHITE && (StepAttacksBB[PAWN][psq] & ksq[BLACK]))) + result = INVALID; + + // Immediate win if a pawn can be promoted without getting captured + else if ( us == WHITE + && rank_of(psq) == RANK_7 + && ksq[us] != psq + NORTH + && ( distance(ksq[~us], psq + NORTH) > 1 + || (StepAttacksBB[KING][ksq[us]] & (psq + NORTH)))) + result = WIN; + + // Immediate draw if it is a stalemate or a king captures undefended pawn + else if ( us == BLACK + && ( !(StepAttacksBB[KING][ksq[us]] & ~(StepAttacksBB[KING][ksq[~us]] | StepAttacksBB[PAWN][psq])) + || (StepAttacksBB[KING][ksq[us]] & psq & ~StepAttacksBB[KING][ksq[~us]]))) + result = DRAW; + + // Position will be classified later + else + result = UNKNOWN; + } + + template + 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 Color Them = (Us == WHITE ? BLACK : WHITE); + const Result Good = (Us == WHITE ? WIN : DRAW); + const Result Bad = (Us == WHITE ? DRAW : WIN); + + Result r = INVALID; + Bitboard b = StepAttacksBB[KING][ksq[Us]]; + + while (b) + r |= Us == WHITE ? db[index(Them, ksq[Them] , pop_lsb(&b), psq)] + : db[index(Them, pop_lsb(&b), ksq[Them] , psq)]; + + if (Us == WHITE) + { + if (rank_of(psq) < RANK_7) // Single push + r |= db[index(Them, ksq[Them], ksq[Us], psq + NORTH)]; + + if ( rank_of(psq) == RANK_2 // Double push + && psq + NORTH != ksq[Us] + && psq + NORTH != ksq[Them]) + r |= db[index(Them, ksq[Them], ksq[Us], psq + NORTH + NORTH)]; + } + + return result = r & Good ? Good : r & UNKNOWN ? UNKNOWN : Bad; + } + +} // namespace diff --git a/src/bitboard.cpp b/src/bitboard.cpp new file mode 100644 index 0000000..e329ab5 --- /dev/null +++ b/src/bitboard.cpp @@ -0,0 +1,333 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bitboard.h" +#include "misc.h" + +uint8_t PopCnt16[1 << 16]; +int SquareDistance[SQUARE_NB][SQUARE_NB]; + +Bitboard RookMasks [SQUARE_NB]; +Bitboard RookMagics [SQUARE_NB]; +Bitboard* RookAttacks[SQUARE_NB]; +unsigned RookShifts [SQUARE_NB]; + +Bitboard BishopMasks [SQUARE_NB]; +Bitboard BishopMagics [SQUARE_NB]; +Bitboard* BishopAttacks[SQUARE_NB]; +unsigned BishopShifts [SQUARE_NB]; + +Bitboard SquareBB[SQUARE_NB]; +Bitboard FileBB[FILE_NB]; +Bitboard RankBB[RANK_NB]; +Bitboard AdjacentFilesBB[FILE_NB]; +Bitboard InFrontBB[COLOR_NB][RANK_NB]; +Bitboard StepAttacksBB[PIECE_NB][SQUARE_NB]; +Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; +Bitboard LineBB[SQUARE_NB][SQUARE_NB]; +Bitboard DistanceRingBB[SQUARE_NB][8]; +Bitboard ForwardBB[COLOR_NB][SQUARE_NB]; +Bitboard PassedPawnMask[COLOR_NB][SQUARE_NB]; +Bitboard PawnAttackSpan[COLOR_NB][SQUARE_NB]; +Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; + +namespace { + + // De Bruijn sequences. See chessprogramming.wikispaces.com/BitScan + const uint64_t DeBruijn64 = 0x3F79D71B4CB0A89ULL; + const uint32_t DeBruijn32 = 0x783A9B23; + + int MSBTable[256]; // To implement software msb() + Square BSFTable[SQUARE_NB]; // To implement software bitscan + Bitboard RookTable[0x19000]; // To store rook attacks + Bitboard BishopTable[0x1480]; // To store bishop attacks + + typedef unsigned (Fn)(Square, Bitboard); + + void init_magics(Bitboard table[], Bitboard* attacks[], Bitboard magics[], + Bitboard masks[], unsigned shifts[], Square deltas[], Fn index); + + // bsf_index() returns the index into BSFTable[] to look up the bitscan. Uses + // Matt Taylor's folding for 32 bit case, extended to 64 bit by Kim Walisch. + + unsigned bsf_index(Bitboard b) { + b ^= b - 1; + return Is64Bit ? (b * DeBruijn64) >> 58 + : ((unsigned(b) ^ unsigned(b >> 32)) * DeBruijn32) >> 26; + } + + + // popcount16() counts the non-zero bits using SWAR-Popcount algorithm + + unsigned popcount16(unsigned u) { + u -= (u >> 1) & 0x5555U; + u = ((u >> 2) & 0x3333U) + (u & 0x3333U); + u = ((u >> 4) + u) & 0x0F0FU; + return (u * 0x0101U) >> 8; + } +} + +#ifdef NO_BSF + +/// Software fall-back of lsb() and msb() for CPU lacking hardware support + +Square lsb(Bitboard b) { + assert(b); + return BSFTable[bsf_index(b)]; +} + +Square msb(Bitboard b) { + + assert(b); + unsigned b32; + int result = 0; + + if (b > 0xFFFFFFFF) + { + b >>= 32; + result = 32; + } + + b32 = unsigned(b); + + if (b32 > 0xFFFF) + { + b32 >>= 16; + result += 16; + } + + if (b32 > 0xFF) + { + b32 >>= 8; + result += 8; + } + + return Square(result + MSBTable[b32]); +} + +#endif // ifdef NO_BSF + + +/// Bitboards::pretty() returns an ASCII representation of a bitboard suitable +/// to be printed to standard output. Useful for debugging. + +const std::string Bitboards::pretty(Bitboard b) { + + std::string s = "+---+---+---+---+---+---+---+---+\n"; + + for (Rank r = RANK_8; r >= RANK_1; --r) + { + for (File f = FILE_A; f <= FILE_H; ++f) + s += b & make_square(f, r) ? "| X " : "| "; + + s += "|\n+---+---+---+---+---+---+---+---+\n"; + } + + return s; +} + + +/// Bitboards::init() initializes various bitboard tables. It is called at +/// startup and relies on global objects to be already zero-initialized. + +void Bitboards::init() { + + for (unsigned i = 0; i < (1 << 16); ++i) + PopCnt16[i] = (uint8_t) popcount16(i); + + for (Square s = SQ_A1; s <= SQ_H8; ++s) + { + SquareBB[s] = 1ULL << s; + BSFTable[bsf_index(SquareBB[s])] = s; + } + + for (Bitboard b = 2; b < 256; ++b) + MSBTable[b] = MSBTable[b - 1] + !more_than_one(b); + + for (File f = FILE_A; f <= FILE_H; ++f) + FileBB[f] = f > FILE_A ? FileBB[f - 1] << 1 : FileABB; + + for (Rank r = RANK_1; r <= RANK_8; ++r) + RankBB[r] = r > RANK_1 ? RankBB[r - 1] << 8 : Rank1BB; + + for (File f = FILE_A; f <= FILE_H; ++f) + AdjacentFilesBB[f] = (f > FILE_A ? FileBB[f - 1] : 0) | (f < FILE_H ? FileBB[f + 1] : 0); + + for (Rank r = RANK_1; r < RANK_8; ++r) + InFrontBB[WHITE][r] = ~(InFrontBB[BLACK][r + 1] = InFrontBB[BLACK][r] | RankBB[r]); + + for (Color c = WHITE; c <= BLACK; ++c) + for (Square s = SQ_A1; s <= SQ_H8; ++s) + { + ForwardBB[c][s] = InFrontBB[c][rank_of(s)] & FileBB[file_of(s)]; + PawnAttackSpan[c][s] = InFrontBB[c][rank_of(s)] & AdjacentFilesBB[file_of(s)]; + PassedPawnMask[c][s] = ForwardBB[c][s] | PawnAttackSpan[c][s]; + } + + for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) + for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) + if (s1 != s2) + { + SquareDistance[s1][s2] = std::max(distance(s1, s2), distance(s1, s2)); + DistanceRingBB[s1][SquareDistance[s1][s2] - 1] |= s2; + } + + int steps[][9] = { {}, { 7, 9 }, { 17, 15, 10, 6, -6, -10, -15, -17 }, + {}, {}, {}, { 9, 7, -7, -9, 8, 1, -1, -8 } }; + + for (Color c = WHITE; c <= BLACK; ++c) + for (PieceType pt = PAWN; pt <= KING; ++pt) + for (Square s = SQ_A1; s <= SQ_H8; ++s) + for (int i = 0; steps[pt][i]; ++i) + { + Square to = s + Square(c == WHITE ? steps[pt][i] : -steps[pt][i]); + + if (is_ok(to) && distance(s, to) < 3) + StepAttacksBB[make_piece(c, pt)][s] |= to; + } + + Square RookDeltas[] = { NORTH, EAST, SOUTH, WEST }; + Square BishopDeltas[] = { NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST }; + + init_magics(RookTable, RookAttacks, RookMagics, RookMasks, RookShifts, RookDeltas, magic_index); + init_magics(BishopTable, BishopAttacks, BishopMagics, BishopMasks, BishopShifts, BishopDeltas, magic_index); + + for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) + { + PseudoAttacks[QUEEN][s1] = PseudoAttacks[BISHOP][s1] = attacks_bb(s1, 0); + PseudoAttacks[QUEEN][s1] |= PseudoAttacks[ ROOK][s1] = attacks_bb< ROOK>(s1, 0); + + for (Piece pc = W_BISHOP; pc <= W_ROOK; ++pc) + for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) + { + if (!(PseudoAttacks[pc][s1] & s2)) + continue; + + LineBB[s1][s2] = (attacks_bb(pc, s1, 0) & attacks_bb(pc, s2, 0)) | s1 | s2; + BetweenBB[s1][s2] = attacks_bb(pc, s1, SquareBB[s2]) & attacks_bb(pc, s2, SquareBB[s1]); + } + } +} + + +namespace { + + Bitboard sliding_attack(Square deltas[], Square sq, Bitboard occupied) { + + Bitboard attack = 0; + + for (int i = 0; i < 4; ++i) + for (Square s = sq + deltas[i]; + is_ok(s) && distance(s, s - deltas[i]) == 1; + s += deltas[i]) + { + attack |= s; + + if (occupied & s) + break; + } + + return attack; + } + + + // 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 + // chessprogramming.wikispaces.com/Magic+Bitboards. In particular, here we + // use the so called "fancy" approach. + + void init_magics(Bitboard table[], Bitboard* attacks[], Bitboard magics[], + Bitboard masks[], unsigned shifts[], Square deltas[], Fn index) { + + int seeds[][RANK_NB] = { { 8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020 }, + { 728, 10316, 55013, 32803, 12281, 15100, 16645, 255 } }; + + Bitboard occupancy[4096], reference[4096], edges, b; + int age[4096] = {0}, current = 0, i, size; + + // attacks[s] is a pointer to the beginning of the attacks table for square 's' + attacks[SQ_A1] = table; + + for (Square s = SQ_A1; s <= SQ_H8; ++s) + { + // Board edges are not considered in the relevant occupancies + edges = ((Rank1BB | Rank8BB) & ~rank_bb(s)) | ((FileABB | FileHBB) & ~file_bb(s)); + + // Given a square 's', the mask is the bitboard of sliding attacks from + // 's' computed on an empty board. The index must be big enough to contain + // all the attacks for each possible subset of the mask and so is 2 power + // the number of 1s of the mask. Hence we deduce the size of the shift to + // apply to the 64 or 32 bits word to get the index. + masks[s] = sliding_attack(deltas, s, 0) & ~edges; + shifts[s] = (Is64Bit ? 64 : 32) - popcount(masks[s]); + + // Use Carry-Rippler trick to enumerate all subsets of masks[s] and + // store the corresponding sliding attack bitboard in reference[]. + b = size = 0; + do { + occupancy[size] = b; + reference[size] = sliding_attack(deltas, s, b); + + if (HasPext) + attacks[s][pext(b, masks[s])] = reference[size]; + + size++; + b = (b - masks[s]) & masks[s]; + } while (b); + + // Set the offset for the table of the next square. We have individual + // table sizes for each square with "Fancy Magic Bitboards". + if (s < SQ_H8) + attacks[s + 1] = attacks[s] + size; + + if (HasPext) + continue; + + PRNG rng(seeds[Is64Bit][rank_of(s)]); + + // Find a magic for square 's' picking up an (almost) random number + // until we find the one that passes the verification test. + do { + do + magics[s] = rng.sparse_rand(); + while (popcount((magics[s] * masks[s]) >> 56) < 6); + + // 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. + for (++current, i = 0; i < size; ++i) + { + unsigned idx = index(s, occupancy[i]); + + if (age[idx] < current) + { + age[idx] = current; + attacks[s][idx] = reference[i]; + } + else if (attacks[s][idx] != reference[i]) + break; + } + } while (i < size); + } + } +} diff --git a/src/bitboard.h b/src/bitboard.h new file mode 100644 index 0000000..715f6c4 --- /dev/null +++ b/src/bitboard.h @@ -0,0 +1,338 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef BITBOARD_H_INCLUDED +#define BITBOARD_H_INCLUDED + +#include + +#include "types.h" + +namespace Bitbases { + +void init(); +bool probe(Square wksq, Square wpsq, Square bksq, Color us); + +} + +namespace Bitboards { + +void init(); +const std::string pretty(Bitboard b); + +} + +const Bitboard DarkSquares = 0xAA55AA55AA55AA55ULL; + +const Bitboard FileABB = 0x0101010101010101ULL; +const Bitboard FileBBB = FileABB << 1; +const Bitboard FileCBB = FileABB << 2; +const Bitboard FileDBB = FileABB << 3; +const Bitboard FileEBB = FileABB << 4; +const Bitboard FileFBB = FileABB << 5; +const Bitboard FileGBB = FileABB << 6; +const Bitboard FileHBB = FileABB << 7; + +const Bitboard Rank1BB = 0xFF; +const Bitboard Rank2BB = Rank1BB << (8 * 1); +const Bitboard Rank3BB = Rank1BB << (8 * 2); +const Bitboard Rank4BB = Rank1BB << (8 * 3); +const Bitboard Rank5BB = Rank1BB << (8 * 4); +const Bitboard Rank6BB = Rank1BB << (8 * 5); +const Bitboard Rank7BB = Rank1BB << (8 * 6); +const Bitboard Rank8BB = Rank1BB << (8 * 7); + +extern int SquareDistance[SQUARE_NB][SQUARE_NB]; + +extern Bitboard SquareBB[SQUARE_NB]; +extern Bitboard FileBB[FILE_NB]; +extern Bitboard RankBB[RANK_NB]; +extern Bitboard AdjacentFilesBB[FILE_NB]; +extern Bitboard InFrontBB[COLOR_NB][RANK_NB]; +extern Bitboard StepAttacksBB[PIECE_NB][SQUARE_NB]; +extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; +extern Bitboard LineBB[SQUARE_NB][SQUARE_NB]; +extern Bitboard DistanceRingBB[SQUARE_NB][8]; +extern Bitboard ForwardBB[COLOR_NB][SQUARE_NB]; +extern Bitboard PassedPawnMask[COLOR_NB][SQUARE_NB]; +extern Bitboard PawnAttackSpan[COLOR_NB][SQUARE_NB]; +extern Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; + + +/// 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 & SquareBB[s]; +} + +inline Bitboard operator|(Bitboard b, Square s) { + return b | SquareBB[s]; +} + +inline Bitboard operator^(Bitboard b, Square s) { + return b ^ SquareBB[s]; +} + +inline Bitboard& operator|=(Bitboard& b, Square s) { + return b |= SquareBB[s]; +} + +inline Bitboard& operator^=(Bitboard& b, Square s) { + return b ^= SquareBB[s]; +} + +inline bool more_than_one(Bitboard b) { + return b & (b - 1); +} + + +/// rank_bb() and file_bb() return a bitboard representing all the squares on +/// the given file or rank. + +inline Bitboard rank_bb(Rank r) { + return RankBB[r]; +} + +inline Bitboard rank_bb(Square s) { + return RankBB[rank_of(s)]; +} + +inline Bitboard file_bb(File f) { + return FileBB[f]; +} + +inline Bitboard file_bb(Square s) { + return FileBB[file_of(s)]; +} + + +/// shift() moves a bitboard one step along direction D. Mainly for pawns + +template +inline Bitboard shift(Bitboard b) { + return D == NORTH ? b << 8 : D == SOUTH ? b >> 8 + : D == NORTH_EAST ? (b & ~FileHBB) << 9 : D == SOUTH_EAST ? (b & ~FileHBB) >> 7 + : D == NORTH_WEST ? (b & ~FileABB) << 7 : D == SOUTH_WEST ? (b & ~FileABB) >> 9 + : 0; +} + + +/// adjacent_files_bb() returns a bitboard representing all the squares on the +/// adjacent files of the given one. + +inline Bitboard adjacent_files_bb(File f) { + return AdjacentFilesBB[f]; +} + + +/// between_bb() returns a bitboard representing all the squares between the two +/// given ones. For instance, between_bb(SQ_C4, SQ_F7) returns a bitboard with +/// the bits for square d5 and e6 set. If s1 and s2 are not on the same rank, file +/// or diagonal, 0 is returned. + +inline Bitboard between_bb(Square s1, Square s2) { + return BetweenBB[s1][s2]; +} + + +/// in_front_bb() returns a bitboard representing all the squares on all the ranks +/// in front of the given one, from the point of view of the given color. For +/// instance, in_front_bb(BLACK, RANK_3) will return the squares on ranks 1 and 2. + +inline Bitboard in_front_bb(Color c, Rank r) { + return InFrontBB[c][r]; +} + + +/// forward_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: +/// ForwardBB[c][s] = in_front_bb(c, s) & file_bb(s) + +inline Bitboard forward_bb(Color c, Square s) { + return ForwardBB[c][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: +/// PawnAttackSpan[c][s] = in_front_bb(c, s) & adjacent_files_bb(s); + +inline Bitboard pawn_attack_span(Color c, Square s) { + return PawnAttackSpan[c][s]; +} + + +/// passed_pawn_mask() returns a bitboard mask which can be used to test if a +/// pawn of the given color and on the given square is a passed pawn: +/// PassedPawnMask[c][s] = pawn_attack_span(c, s) | forward_bb(c, s) + +inline Bitboard passed_pawn_mask(Color c, Square s) { + return PassedPawnMask[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 LineBB[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. Works with squares, ranks, files. + +template inline int distance(T x, T y) { return x < y ? y - x : x - y; } +template<> inline int distance(Square x, Square y) { return SquareDistance[x][y]; } + +template inline int distance(T2 x, T2 y); +template<> inline int distance(Square x, Square y) { return distance(file_of(x), file_of(y)); } +template<> inline int distance(Square x, Square y) { return distance(rank_of(x), rank_of(y)); } + + +/// attacks_bb() returns a bitboard representing all the squares attacked by a +/// piece of type Pt (bishop or rook) placed on 's'. The helper magic_index() +/// looks up the index using the 'magic bitboards' approach. +template +inline unsigned magic_index(Square s, Bitboard occupied) { + + extern Bitboard RookMasks[SQUARE_NB]; + extern Bitboard RookMagics[SQUARE_NB]; + extern unsigned RookShifts[SQUARE_NB]; + extern Bitboard BishopMasks[SQUARE_NB]; + extern Bitboard BishopMagics[SQUARE_NB]; + extern unsigned BishopShifts[SQUARE_NB]; + + Bitboard* const Masks = Pt == ROOK ? RookMasks : BishopMasks; + Bitboard* const Magics = Pt == ROOK ? RookMagics : BishopMagics; + unsigned* const Shifts = Pt == ROOK ? RookShifts : BishopShifts; + + if (HasPext) + return unsigned(pext(occupied, Masks[s])); + + if (Is64Bit) + return unsigned(((occupied & Masks[s]) * Magics[s]) >> Shifts[s]); + + unsigned lo = unsigned(occupied) & unsigned(Masks[s]); + unsigned hi = unsigned(occupied >> 32) & unsigned(Masks[s] >> 32); + return (lo * unsigned(Magics[s]) ^ hi * unsigned(Magics[s] >> 32)) >> Shifts[s]; +} + +template +inline Bitboard attacks_bb(Square s, Bitboard occupied) { + + extern Bitboard* RookAttacks[SQUARE_NB]; + extern Bitboard* BishopAttacks[SQUARE_NB]; + + return (Pt == ROOK ? RookAttacks : BishopAttacks)[s][magic_index(s, occupied)]; +} + +inline Bitboard attacks_bb(Piece pc, Square s, Bitboard occupied) { + + switch (type_of(pc)) + { + case BISHOP: return attacks_bb(s, occupied); + case ROOK : return attacks_bb(s, occupied); + case QUEEN : return attacks_bb(s, occupied) | attacks_bb(s, occupied); + default : return StepAttacksBB[pc][s]; + } +} + + +/// popcount() counts the number of non-zero bits in a bitboard + +inline int popcount(Bitboard b) { + +#ifndef USE_POPCNT + + extern uint8_t PopCnt16[1 << 16]; + union { Bitboard bb; uint16_t u[4]; } v = { b }; + return PopCnt16[v.u[0]] + PopCnt16[v.u[1]] + PopCnt16[v.u[2]] + PopCnt16[v.u[3]]; + +#elif defined(_MSC_VER) || defined(__INTEL_COMPILER) + + return (int)_mm_popcnt_u64(b); + +#else // Assumed gcc or compatible compiler + + return __builtin_popcountll(b); + +#endif +} + + +/// lsb() and msb() return the least/most significant bit in a non-zero bitboard + +#if defined(__GNUC__) + +inline Square lsb(Bitboard b) { + assert(b); + return Square(__builtin_ctzll(b)); +} + +inline Square msb(Bitboard b) { + assert(b); + return Square(63 - __builtin_clzll(b)); +} + +#elif defined(_WIN64) && defined(_MSC_VER) + +inline Square lsb(Bitboard b) { + assert(b); + unsigned long idx; + _BitScanForward64(&idx, b); + return (Square) idx; +} + +inline Square msb(Bitboard b) { + assert(b); + unsigned long idx; + _BitScanReverse64(&idx, b); + return (Square) idx; +} + +#else + +#define NO_BSF // Fallback on software implementation for other cases + +Square lsb(Bitboard b); +Square msb(Bitboard b); + +#endif + + +/// pop_lsb() finds and clears the least significant bit in a non-zero bitboard + +inline Square pop_lsb(Bitboard* b) { + const Square s = lsb(*b); + *b &= *b - 1; + return s; +} + + +/// frontmost_sq() and backmost_sq() return the square corresponding to the +/// most/least advanced bit relative to the given color. + +inline Square frontmost_sq(Color c, Bitboard b) { return c == WHITE ? msb(b) : lsb(b); } +inline Square backmost_sq(Color c, Bitboard b) { return c == WHITE ? lsb(b) : msb(b); } + +#endif // #ifndef BITBOARD_H_INCLUDED diff --git a/src/endgame.cpp b/src/endgame.cpp new file mode 100644 index 0000000..cbca34b --- /dev/null +++ b/src/endgame.cpp @@ -0,0 +1,845 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#include "bitboard.h" +#include "endgame.h" +#include "movegen.h" + +using std::string; + +namespace { + + // Table used to drive the king towards the edge of the board + // in KX vs K and KQ vs KR endgames. + const int PushToEdges[SQUARE_NB] = { + 100, 90, 80, 70, 70, 80, 90, 100, + 90, 70, 60, 50, 50, 60, 70, 90, + 80, 60, 40, 30, 30, 40, 60, 80, + 70, 50, 30, 20, 20, 30, 50, 70, + 70, 50, 30, 20, 20, 30, 50, 70, + 80, 60, 40, 30, 30, 40, 60, 80, + 90, 70, 60, 50, 50, 60, 70, 90, + 100, 90, 80, 70, 70, 80, 90, 100 + }; + + // Table used to drive the king towards a corner square of the + // right color in KBN vs K endgames. + const int PushToCorners[SQUARE_NB] = { + 200, 190, 180, 170, 160, 150, 140, 130, + 190, 180, 170, 160, 150, 140, 130, 140, + 180, 170, 155, 140, 140, 125, 140, 150, + 170, 160, 140, 120, 110, 140, 150, 160, + 160, 150, 140, 110, 120, 140, 160, 170, + 150, 140, 125, 140, 140, 155, 170, 180, + 140, 130, 140, 150, 160, 170, 180, 190, + 130, 140, 150, 160, 170, 180, 190, 200 + }; + + // Tables used to drive a piece towards or away from another piece + const int PushClose[8] = { 0, 0, 100, 80, 60, 40, 20, 10 }; + const int PushAway [8] = { 0, 5, 20, 40, 60, 80, 90, 100 }; + + // Pawn Rank based scaling factors used in KRPPKRP endgame + const int KRPPKRPScaleFactors[RANK_NB] = { 0, 9, 10, 14, 21, 44, 0, 0 }; + +#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)) >= FILE_E) + sq = Square(sq ^ 7); // Mirror SQ_H1 -> SQ_A1 + + if (strongSide == BLACK) + sq = ~sq; + + return sq; + } + + // Get the material key of Position out of the given endgame key code + // like "KBPKN". The trick here is to first forge an ad-hoc FEN string + // and then let a Position object do the work for us. + Key key(const string& code, Color c) { + + assert(code.length() > 0 && code.length() < 8); + assert(code[0] == 'K'); + + string sides[] = { code.substr(code.find('K', 1)), // Weak + code.substr(0, code.find('K', 1)) }; // Strong + + std::transform(sides[c].begin(), sides[c].end(), sides[c].begin(), tolower); + + string fen = sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/8/8/" + + sides[1] + char(8 - sides[1].length() + '0') + " w - - 0 10"; + + StateInfo st; + return Position().set(fen, false, &st, nullptr).material_key(); + } + +} // namespace + + +/// Endgames members definitions + +Endgames::Endgames() { + + add("KPK"); + add("KNNK"); + add("KBNK"); + add("KRKP"); + add("KRKB"); + add("KRKN"); + add("KQKP"); + add("KQKR"); + + add("KNPK"); + add("KNPKB"); + add("KRPKR"); + add("KRPKB"); + add("KBPKB"); + add("KBPKN"); + add("KBPPKB"); + add("KRPPKRP"); +} + + +template +void Endgames::add(const string& code) { + map()[key(code, WHITE)] = std::unique_ptr>(new Endgame(WHITE)); + map()[key(code, BLACK)] = std::unique_ptr>(new Endgame(BLACK)); +} + + +/// 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 winnerKSq = pos.square(strongSide); + Square loserKSq = pos.square(weakSide); + + Value result = pos.non_pawn_material(strongSide) + + pos.count(strongSide) * PawnValueEg + + PushToEdges[loserKSq] + + PushClose[distance(winnerKSq, loserKSq)]; + + if ( pos.count(strongSide) + || pos.count(strongSide) + ||(pos.count(strongSide) && pos.count(strongSide)) + ||(pos.count(strongSide) > 1 && opposite_colors(pos.squares(strongSide)[0], + pos.squares(strongSide)[1]))) + result = std::min(result + VALUE_KNOWN_WIN, VALUE_MATE_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 of the right color. +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 winnerKSq = pos.square(strongSide); + Square loserKSq = pos.square(weakSide); + Square bishopSq = pos.square(strongSide); + + // kbnk_mate_table() tries to drive toward corners A1 or H8. If we have a + // bishop that cannot reach the above squares, we flip the kings in order + // to drive the enemy toward corners A8 or H1. + if (opposite_colors(bishopSq, SQ_A1)) + { + winnerKSq = ~winnerKSq; + loserKSq = ~loserKSq; + } + + Value result = VALUE_KNOWN_WIN + + PushClose[distance(winnerKSq, loserKSq)] + + PushToCorners[loserKSq]; + + 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 wksq = normalize(pos, strongSide, pos.square(strongSide)); + Square bksq = normalize(pos, strongSide, pos.square(weakSide)); + Square psq = normalize(pos, strongSide, pos.square(strongSide)); + + Color us = strongSide == pos.side_to_move() ? WHITE : BLACK; + + if (!Bitbases::probe(wksq, psq, bksq, us)) + return VALUE_DRAW; + + Value result = VALUE_KNOWN_WIN + PawnValueEg + Value(rank_of(psq)); + + 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 wksq = relative_square(strongSide, pos.square(strongSide)); + Square bksq = relative_square(strongSide, pos.square(weakSide)); + Square rsq = relative_square(strongSide, pos.square(strongSide)); + Square psq = relative_square(strongSide, pos.square(weakSide)); + + Square queeningSq = make_square(file_of(psq), RANK_1); + Value result; + + // If the stronger side's king is in front of the pawn, it's a win + if (wksq < psq && file_of(wksq) == file_of(psq)) + result = RookValueEg - distance(wksq, psq); + + // If the weaker side's king is too far from the pawn and the rook, + // it's a win. + else if ( distance(bksq, psq) >= 3 + (pos.side_to_move() == weakSide) + && distance(bksq, rsq) >= 3) + result = RookValueEg - distance(wksq, psq); + + // If the pawn is far advanced and supported by the defending king, + // the position is drawish + else if ( rank_of(bksq) <= RANK_3 + && distance(bksq, psq) == 1 + && rank_of(wksq) >= RANK_4 + && distance(wksq, psq) > 2 + (pos.side_to_move() == strongSide)) + result = Value(80) - 8 * distance(wksq, psq); + + else + result = Value(200) - 8 * ( distance(wksq, psq + SOUTH) + - distance(bksq, psq + SOUTH) + - distance(psq, queeningSq)); + + 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(PushToEdges[pos.square(weakSide)]); + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// KR vs KN. The attacking side has slightly better winning chances than +/// in KR vs KB, particularly if the king and the knight are far apart. +template<> +Value Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, RookValueMg, 0)); + assert(verify_material(pos, weakSide, KnightValueMg, 0)); + + Square bksq = pos.square(weakSide); + Square bnsq = pos.square(weakSide); + Value result = Value(PushToEdges[bksq] + PushAway[distance(bksq, bnsq)]); + 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 winnerKSq = pos.square(strongSide); + Square loserKSq = pos.square(weakSide); + Square pawnSq = pos.square(weakSide); + + Value result = Value(PushClose[distance(winnerKSq, loserKSq)]); + + if ( relative_rank(weakSide, pawnSq) != RANK_7 + || distance(loserKSq, pawnSq) != 1 + || !((FileABB | FileCBB | FileFBB | FileHBB) & pawnSq)) + 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 winnerKSq = pos.square(strongSide); + Square loserKSq = pos.square(weakSide); + + Value result = QueenValueEg + - RookValueEg + + PushToEdges[loserKSq] + + PushClose[distance(winnerKSq, loserKSq)]; + + return strongSide == pos.side_to_move() ? result : -result; +} + + +/// Some cases of trivial draws +template<> Value Endgame::operator()(const Position&) const { return VALUE_DRAW; } + + +/// KB and one or more pawns vs K. It checks for draws with rook pawns and +/// a bishop of the wrong color. If such a draw is detected, SCALE_FACTOR_DRAW +/// is returned. If not, the return value is SCALE_FACTOR_NONE, i.e. no scaling +/// will be used. +template<> +ScaleFactor Endgame::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 pawns = pos.pieces(strongSide, PAWN); + File pawnsFile = file_of(lsb(pawns)); + + // All pawns are on a single rook file? + if ( (pawnsFile == FILE_A || pawnsFile == FILE_H) + && !(pawns & ~file_bb(pawnsFile))) + { + Square bishopSq = pos.square(strongSide); + Square queeningSq = relative_square(strongSide, make_square(pawnsFile, RANK_8)); + Square kingSq = pos.square(weakSide); + + if ( opposite_colors(queeningSq, bishopSq) + && distance(queeningSq, kingSq) <= 1) + return SCALE_FACTOR_DRAW; + } + + // If all the pawns are on the same B or G file, then it's potentially a draw + if ( (pawnsFile == FILE_B || pawnsFile == FILE_G) + && !(pos.pieces(PAWN) & ~file_bb(pawnsFile)) + && pos.non_pawn_material(weakSide) == 0 + && pos.count(weakSide) >= 1) + { + // Get weakSide pawn that is closest to the home rank + Square weakPawnSq = backmost_sq(weakSide, pos.pieces(weakSide, PAWN)); + + Square strongKingSq = pos.square(strongSide); + Square weakKingSq = pos.square(weakSide); + Square bishopSq = pos.square(strongSide); + + // 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, weakPawnSq) == RANK_7 + && (pos.pieces(strongSide, PAWN) & (weakPawnSq + pawn_push(weakSide))) + && (opposite_colors(bishopSq, weakPawnSq) || pos.count(strongSide) == 1)) + { + int strongKingDist = distance(weakPawnSq, strongKingSq); + int weakKingDist = distance(weakPawnSq, weakKingSq); + + // 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, weakKingSq) >= 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 kingSq = pos.square(weakSide); + Square rsq = pos.square(weakSide); + + if ( relative_rank(weakSide, kingSq) <= RANK_2 + && relative_rank(weakSide, pos.square(strongSide)) >= RANK_4 + && relative_rank(weakSide, rsq) == RANK_3 + && ( pos.pieces(weakSide, PAWN) + & pos.attacks_from(kingSq) + & pos.attacks_from(rsq, strongSide))) + 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 wksq = normalize(pos, strongSide, pos.square(strongSide)); + Square bksq = normalize(pos, strongSide, pos.square(weakSide)); + Square wrsq = normalize(pos, strongSide, pos.square(strongSide)); + Square wpsq = normalize(pos, strongSide, pos.square(strongSide)); + Square brsq = normalize(pos, strongSide, pos.square(weakSide)); + + File f = file_of(wpsq); + Rank r = rank_of(wpsq); + Square queeningSq = make_square(f, 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 ( r <= RANK_5 + && distance(bksq, queeningSq) <= 1 + && wksq <= SQ_H5 + && (rank_of(brsq) == RANK_6 || (r <= RANK_3 && rank_of(wrsq) != 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 ( r == RANK_6 + && distance(bksq, queeningSq) <= 1 + && rank_of(wksq) + tempo <= RANK_6 + && (rank_of(brsq) == RANK_1 || (!tempo && distance(brsq, wpsq) >= 3))) + return SCALE_FACTOR_DRAW; + + if ( r >= RANK_6 + && bksq == queeningSq + && rank_of(brsq) == RANK_1 + && (!tempo || distance(wksq, wpsq) >= 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 ( wpsq == SQ_A7 + && wrsq == SQ_A8 + && (bksq == SQ_H7 || bksq == SQ_G7) + && file_of(brsq) == FILE_A + && (rank_of(brsq) <= RANK_3 || file_of(wksq) >= FILE_D || rank_of(wksq) <= 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 ( r <= RANK_5 + && bksq == wpsq + NORTH + && distance(wksq, wpsq) - tempo >= 2 + && distance(wksq, brsq) - 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 ( r == RANK_7 + && f != FILE_A + && file_of(wrsq) == f + && wrsq != queeningSq + && (distance(wksq, queeningSq) < distance(bksq, queeningSq) - 2 + tempo) + && (distance(wksq, queeningSq) < distance(bksq, wrsq) + tempo)) + return ScaleFactor(SCALE_FACTOR_MAX - 2 * distance(wksq, queeningSq)); + + // Similar to the above, but with the pawn further back + if ( f != FILE_A + && file_of(wrsq) == f + && wrsq < wpsq + && (distance(wksq, queeningSq) < distance(bksq, queeningSq) - 2 + tempo) + && (distance(wksq, wpsq + NORTH) < distance(bksq, wpsq + NORTH) - 2 + tempo) + && ( distance(bksq, wrsq) + tempo >= 3 + || ( distance(wksq, queeningSq) < distance(bksq, wrsq) + tempo + && (distance(wksq, wpsq + NORTH) < distance(bksq, wrsq) + tempo)))) + return ScaleFactor( SCALE_FACTOR_MAX + - 8 * distance(wpsq, queeningSq) + - 2 * distance(wksq, queeningSq)); + + // If the pawn is not far advanced and the defending king is somewhere in + // the pawn's path, it's probably a draw. + if (r <= RANK_4 && bksq > wpsq) + { + if (file_of(bksq) == file_of(wpsq)) + return ScaleFactor(10); + if ( distance(bksq, wpsq) == 1 + && distance(wksq, bksq) > 2) + return ScaleFactor(24 - 2 * distance(wksq, bksq)); + } + 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 ksq = pos.square(weakSide); + Square bsq = pos.square(weakSide); + Square psq = pos.square(strongSide); + Rank rk = relative_rank(strongSide, psq); + Square 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 (rk == RANK_5 && !opposite_colors(bsq, psq)) + { + int d = distance(psq + 3 * push, ksq); + + if (d <= 2 && !(d == 0 && ksq == pos.square(strongSide) + 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 ( rk == RANK_6 + && distance(psq + 2 * push, ksq) <= 1 + && (PseudoAttacks[BISHOP][bsq] & (psq + push)) + && distance(bsq, psq) >= 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 wpsq1 = pos.squares(strongSide)[0]; + Square wpsq2 = pos.squares(strongSide)[1]; + Square bksq = pos.square(weakSide); + + // Does the stronger side have a passed pawn? + if (pos.pawn_passed(strongSide, wpsq1) || pos.pawn_passed(strongSide, wpsq2)) + return SCALE_FACTOR_NONE; + + Rank r = std::max(relative_rank(strongSide, wpsq1), relative_rank(strongSide, wpsq2)); + + if ( distance(bksq, wpsq1) <= 1 + && distance(bksq, wpsq2) <= 1 + && relative_rank(strongSide, bksq) > r) + { + assert(r > RANK_1 && r < RANK_7); + return ScaleFactor(KRPPKRPScaleFactors[r]); + } + 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 ksq = pos.square(weakSide); + Bitboard pawns = pos.pieces(strongSide, PAWN); + + // If all pawns are ahead of the king, on a single rook file and + // the king is within one file of the pawns, it's a draw. + if ( !(pawns & ~in_front_bb(weakSide, rank_of(ksq))) + && !((pawns & ~FileABB) && (pawns & ~FileHBB)) + && distance(ksq, lsb(pawns)) <= 1) + 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 pawnSq = pos.square(strongSide); + Square strongBishopSq = pos.square(strongSide); + Square weakBishopSq = pos.square(weakSide); + Square weakKingSq = pos.square(weakSide); + + // Case 1: Defending king blocks the pawn, and cannot be driven away + if ( file_of(weakKingSq) == file_of(pawnSq) + && relative_rank(strongSide, pawnSq) < relative_rank(strongSide, weakKingSq) + && ( opposite_colors(weakKingSq, strongBishopSq) + || relative_rank(strongSide, weakKingSq) <= RANK_6)) + return SCALE_FACTOR_DRAW; + + // Case 2: Opposite colored bishops + if (opposite_colors(strongBishopSq, weakBishopSq)) + { + // We assume that the position is drawn in the following three situations: + // + // a. The pawn is on rank 5 or further back. + // b. The defending king is somewhere in the pawn's path. + // c. The defending bishop attacks some square along the pawn's path, + // and is at least three squares away from the pawn. + // + // These rules are probably not perfect, but in practice they work + // reasonably well. + + if (relative_rank(strongSide, pawnSq) <= RANK_5) + return SCALE_FACTOR_DRAW; + else + { + Bitboard path = forward_bb(strongSide, pawnSq); + + if (path & pos.pieces(weakSide, KING)) + return SCALE_FACTOR_DRAW; + + if ( (pos.attacks_from(weakBishopSq) & path) + && distance(weakBishopSq, pawnSq) >= 3) + 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 wbsq = pos.square(strongSide); + Square bbsq = pos.square(weakSide); + + if (!opposite_colors(wbsq, bbsq)) + return SCALE_FACTOR_NONE; + + Square ksq = pos.square(weakSide); + Square psq1 = pos.squares(strongSide)[0]; + Square psq2 = pos.squares(strongSide)[1]; + Rank r1 = rank_of(psq1); + Rank r2 = rank_of(psq2); + Square blockSq1, blockSq2; + + if (relative_rank(strongSide, psq1) > relative_rank(strongSide, psq2)) + { + blockSq1 = psq1 + pawn_push(strongSide); + blockSq2 = make_square(file_of(psq2), rank_of(psq1)); + } + else + { + blockSq1 = psq2 + pawn_push(strongSide); + blockSq2 = make_square(file_of(psq1), rank_of(psq2)); + } + + switch (distance(psq1, psq2)) + { + 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(ksq) == file_of(blockSq1) + && relative_rank(strongSide, ksq) >= relative_rank(strongSide, blockSq1) + && opposite_colors(ksq, wbsq)) + 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 ( ksq == blockSq1 + && opposite_colors(ksq, wbsq) + && ( bbsq == blockSq2 + || (pos.attacks_from(blockSq2) & pos.pieces(weakSide, BISHOP)) + || distance(r1, r2) >= 2)) + return SCALE_FACTOR_DRAW; + + else if ( ksq == blockSq2 + && opposite_colors(ksq, wbsq) + && ( bbsq == blockSq1 + || (pos.attacks_from(blockSq1) & 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 pawnSq = pos.square(strongSide); + Square strongBishopSq = pos.square(strongSide); + Square weakKingSq = pos.square(weakSide); + + if ( file_of(weakKingSq) == file_of(pawnSq) + && relative_rank(strongSide, pawnSq) < relative_rank(strongSide, weakKingSq) + && ( opposite_colors(weakKingSq, strongBishopSq) + || relative_rank(strongSide, weakKingSq) <= RANK_6)) + return SCALE_FACTOR_DRAW; + + return SCALE_FACTOR_NONE; +} + + +/// KNP vs K. There is a single rule: if the pawn is a rook pawn on the 7th rank +/// and the defending king prevents the pawn from advancing, the position is drawn. +template<> +ScaleFactor Endgame::operator()(const Position& pos) const { + + assert(verify_material(pos, strongSide, KnightValueMg, 1)); + assert(verify_material(pos, weakSide, VALUE_ZERO, 0)); + + // Assume strongSide is white and the pawn is on files A-D + Square pawnSq = normalize(pos, strongSide, pos.square(strongSide)); + Square weakKingSq = normalize(pos, strongSide, pos.square(weakSide)); + + if (pawnSq == SQ_A7 && distance(SQ_A8, weakKingSq) <= 1) + return SCALE_FACTOR_DRAW; + + return SCALE_FACTOR_NONE; +} + + +/// KNP vs KB. If knight can block bishop from taking pawn, it's a win. +/// Otherwise the position is drawn. +template<> +ScaleFactor Endgame::operator()(const Position& pos) const { + + Square pawnSq = pos.square(strongSide); + Square bishopSq = pos.square(weakSide); + Square weakKingSq = pos.square(weakSide); + + // King needs to get close to promoting pawn to prevent knight from blocking. + // Rules for this are very tricky, so just approximate. + if (forward_bb(strongSide, pawnSq) & pos.attacks_from(bishopSq)) + return ScaleFactor(distance(weakKingSq, pawnSq)); + + return SCALE_FACTOR_NONE; +} + + +/// KP vs KP. This is done by removing the weakest side's pawn and probing the +/// KP vs 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 wksq = normalize(pos, strongSide, pos.square(strongSide)); + Square bksq = normalize(pos, strongSide, pos.square(weakSide)); + Square psq = 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(psq) >= RANK_5 && file_of(psq) != FILE_A) + return SCALE_FACTOR_NONE; + + // Probe the KPK bitbase with the weakest side's pawn removed. If it's a draw, + // it's probably at least a draw even with the pawn. + return Bitbases::probe(wksq, psq, bksq, us) ? SCALE_FACTOR_NONE : SCALE_FACTOR_DRAW; +} diff --git a/src/endgame.h b/src/endgame.h new file mode 100644 index 0000000..5f6b4bb --- /dev/null +++ b/src/endgame.h @@ -0,0 +1,125 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef ENDGAME_H_INCLUDED +#define ENDGAME_H_INCLUDED + +#include +#include +#include +#include +#include + +#include "position.h" +#include "types.h" + + +/// EndgameType lists all supported endgames + +enum EndgameType { + + // Evaluation functions + + KNNK, // KNN vs K + KXK, // Generic "mate lone king" eval + KBNK, // KBN vs K + KPK, // KP vs K + KRKP, // KR vs KP + KRKB, // KR vs KB + KRKN, // KR vs KN + KQKP, // KQ vs KP + KQKR, // KQ vs KR + + + // Scaling functions + 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 + KNPK, // KNP vs K + KNPKB, // KNP vs KB + 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 templates for endgame evaluation and scaling functions + +template +struct EndgameBase { + + virtual ~EndgameBase() = default; + virtual Color strong_side() const = 0; + virtual T operator()(const Position&) const = 0; +}; + + +template> +struct Endgame : public EndgameBase { + + explicit Endgame(Color c) : strongSide(c), weakSide(~c) {} + Color strong_side() const { return strongSide; } + T operator()(const Position&) const; + +private: + Color strongSide, weakSide; +}; + + +/// The Endgames class stores 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(). + +class Endgames { + + template using Map = std::map>>; + + template> + void add(const std::string& code); + + template + Map& map() { + return std::get::value>(maps); + } + + std::pair, Map> maps; + +public: + Endgames(); + + template + EndgameBase* probe(Key key) { + return map().count(key) ? map()[key].get() : nullptr; + } +}; + +#endif // #ifndef ENDGAME_H_INCLUDED diff --git a/src/evaluate.cpp b/src/evaluate.cpp new file mode 100644 index 0000000..434ebd6 --- /dev/null +++ b/src/evaluate.cpp @@ -0,0 +1,927 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include // For std::memset +#include +#include + +#include "bitboard.h" +#include "evaluate.h" +#include "material.h" +#include "pawns.h" + +namespace { + + namespace Trace { + + enum Term { // The first 8 entries are for PieceType + MATERIAL = 8, IMBALANCE, MOBILITY, THREAT, PASSED, SPACE, TOTAL, TERM_NB + }; + + double scores[TERM_NB][COLOR_NB][PHASE_NB]; + + double to_cp(Value v) { return double(v) / PawnValueEg; } + + void add(int idx, Color c, Score s) { + scores[idx][c][MG] = to_cp(mg_value(s)); + scores[idx][c][EG] = to_cp(eg_value(s)); + } + + void add(int idx, Score w, Score b = SCORE_ZERO) { + add(idx, WHITE, w); add(idx, BLACK, b); + } + + std::ostream& operator<<(std::ostream& os, Term t) { + + if (t == MATERIAL || t == IMBALANCE || t == Term(PAWN) || t == TOTAL) + os << " --- --- | --- --- | "; + else + os << std::setw(5) << scores[t][WHITE][MG] << " " + << std::setw(5) << scores[t][WHITE][EG] << " | " + << std::setw(5) << scores[t][BLACK][MG] << " " + << std::setw(5) << scores[t][BLACK][EG] << " | "; + + os << std::setw(5) << scores[t][WHITE][MG] - scores[t][BLACK][MG] << " " + << std::setw(5) << scores[t][WHITE][EG] - scores[t][BLACK][EG] << " \n"; + + return os; + } + } + + using namespace Trace; + + // Struct EvalInfo contains various information computed and collected + // by the evaluation functions. + struct EvalInfo { + + // attackedBy[color][piece type] is a bitboard representing all squares + // attacked by a given color and piece type (can be also ALL_PIECES). + Bitboard attackedBy[COLOR_NB][PIECE_TYPE_NB]; + + // attackedBy2[color] are the squares attacked by 2 pieces of a given color, + // possibly via x-ray or by one pawn and one piece. Diagonal x-ray through + // pawn or squares attacked by 2 pawns are not explicitly added. + Bitboard attackedBy2[COLOR_NB]; + + // kingRing[color] is the zone around the king which is considered + // by the king safety evaluation. This consists of the squares directly + // adjacent to the king, and the three (or two, for a king on an edge file) + // squares two ranks in front of the king. For instance, if black's king + // is on g8, kingRing[BLACK] is a bitboard containing the squares f8, h8, + // f7, g7, h7, f6, g6 and h6. + Bitboard kingRing[COLOR_NB]; + + // kingAttackersCount[color] is the number of pieces of the given color + // which attack a square in the kingRing of the enemy king. + int kingAttackersCount[COLOR_NB]; + + // kingAttackersWeight[color] is the sum of the "weights" of the pieces of the + // given color which attack a square in the kingRing of the enemy king. The + // weights of the individual piece types are given by the elements in the + // KingAttackWeights array. + int kingAttackersWeight[COLOR_NB]; + + // kingAdjacentZoneAttacksCount[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 kingAdjacentZoneAttacksCount[WHITE]. + int kingAdjacentZoneAttacksCount[COLOR_NB]; + + Bitboard pinnedPieces[COLOR_NB]; + Material::Entry* me; + Pawns::Entry* pi; + }; + + #define V(v) Value(v) + #define S(mg, eg) make_score(mg, eg) + + // MobilityBonus[PieceType][attacked] contains bonuses for middle and end + // game, indexed by piece type and number of attacked squares in the MobilityArea. + const Score MobilityBonus[][32] = { + {}, {}, + { S(-75,-76), S(-56,-54), S( -9,-26), S( -2,-10), S( 6, 5), S( 15, 11), // Knights + S( 22, 26), S( 30, 28), S( 36, 29) }, + { S(-48,-58), S(-21,-19), S( 16, -2), S( 26, 12), S( 37, 22), S( 51, 42), // Bishops + S( 54, 54), S( 63, 58), S( 65, 63), S( 71, 70), S( 79, 74), S( 81, 86), + S( 92, 90), S( 97, 94) }, + { S(-56,-78), S(-25,-18), S(-11, 26), S( -5, 55), S( -4, 70), S( -1, 81), // Rooks + S( 8,109), S( 14,120), S( 21,128), S( 23,143), S( 31,154), S( 32,160), + S( 43,165), S( 49,168), S( 59,169) }, + { S(-40,-35), S(-25,-12), S( 2, 7), S( 4, 19), S( 14, 37), S( 24, 55), // Queens + S( 25, 62), S( 40, 76), S( 43, 79), S( 47, 87), S( 54, 94), S( 56,102), + S( 60,111), S( 70,116), S( 72,118), S( 73,122), S( 75,128), S( 77,130), + S( 85,133), S( 94,136), S( 99,140), S(108,157), S(112,158), S(113,161), + S(118,174), S(119,177), S(123,191), S(128,199) } + }; + + // Outpost[knight/bishop][supported by pawn] contains bonuses for knights and + // bishops outposts, bigger if outpost piece is supported by a pawn. + const Score Outpost[][2] = { + { S(43,11), S(65,20) }, // Knights + { S(20, 3), S(29, 8) } // Bishops + }; + + // ReachableOutpost[knight/bishop][supported by pawn] contains bonuses for + // knights and bishops which can reach an outpost square in one move, bigger + // if outpost square is supported by a pawn. + const Score ReachableOutpost[][2] = { + { S(21, 5), S(35, 8) }, // Knights + { S( 8, 0), S(14, 4) } // Bishops + }; + + // RookOnFile[semiopen/open] contains bonuses for each rook when there is no + // friendly pawn on the rook file. + const Score RookOnFile[2] = { S(20, 7), S(45, 20) }; + + // ThreatBySafePawn[PieceType] contains bonuses according to which piece + // type is attacked by a pawn which is protected or is not attacked. + const Score ThreatBySafePawn[PIECE_TYPE_NB] = { + S(0, 0), S(0, 0), S(176, 139), S(131, 127), S(217, 218), S(203, 215) + }; + + // Threat[by minor/by rook][attacked PieceType] contains + // bonuses according to which piece type attacks which one. + // Attacks on lesser pieces which are pawn-defended are not considered. + const Score Threat[][PIECE_TYPE_NB] = { + { S(0, 0), S(0, 33), S(45, 43), S(46, 47), S(72,107), S(48,118) }, // by Minor + { S(0, 0), S(0, 25), S(40, 62), S(40, 59), S( 0, 34), S(35, 48) } // by Rook + }; + + // ThreatByKing[on one/on many] contains bonuses for King attacks on + // pawns or pieces which are not pawn-defended. + const Score ThreatByKing[2] = { S(3, 62), S(9, 138) }; + + // Passed[mg/eg][Rank] contains midgame and endgame bonuses for passed pawns. + // We don't use a Score because we process the two components independently. + const Value Passed[][RANK_NB] = { + { V(5), V( 5), V(31), V(73), V(166), V(252) }, + { V(7), V(14), V(38), V(73), V(166), V(252) } + }; + + // PassedFile[File] contains a bonus according to the file of a passed pawn + const Score PassedFile[FILE_NB] = { + S( 9, 10), S( 2, 10), S( 1, -8), S(-20,-12), + S(-20,-12), S( 1, -8), S( 2, 10), S( 9, 10) + }; + + // Assorted bonuses and penalties used by evaluation + const Score MinorBehindPawn = S(16, 0); + const Score BishopPawns = S( 8, 12); + const Score RookOnPawn = S( 8, 24); + const Score TrappedRook = S(92, 0); + const Score CloseEnemies = S( 7, 0); + const Score SafeCheck = S(20, 20); + const Score OtherCheck = S(10, 10); + const Score ThreatByHangingPawn = S(71, 61); + const Score LooseEnemies = S( 0, 25); + const Score WeakQueen = S(35, 0); + const Score Hanging = S(48, 27); + const Score ThreatByPawnPush = S(38, 22); + const Score Unstoppable = S( 0, 20); + const Score PawnlessFlank = S(20, 80); + const Score HinderPassedPawn = S( 7, 0); + + // Penalty for a bishop on a1/h1 (a8/h8 for black) which is trapped by + // a friendly pawn on b2/g2 (b7/g7 for black). This can obviously only + // happen in Chess960 games. + const Score TrappedBishopA1H1 = S(50, 50); + + #undef S + #undef V + + // KingAttackWeights[PieceType] contains king attack weights by piece type + const int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 78, 56, 45, 11 }; + + // Penalties for enemy's safe checks + const int QueenContactCheck = 997; + const int QueenCheck = 695; + const int RookCheck = 638; + const int BishopCheck = 538; + const int KnightCheck = 874; + + + // eval_init() initializes king and attack bitboards for a given color + // adding pawn attacks. To be done at the beginning of the evaluation. + + template + void eval_init(const Position& pos, EvalInfo& ei) { + + const Color Them = (Us == WHITE ? BLACK : WHITE); + const Square Down = (Us == WHITE ? SOUTH : NORTH); + + ei.pinnedPieces[Us] = pos.pinned_pieces(Us); + Bitboard b = ei.attackedBy[Them][KING]; + ei.attackedBy[Them][ALL_PIECES] |= b; + ei.attackedBy[Us][ALL_PIECES] |= ei.attackedBy[Us][PAWN] = ei.pi->pawn_attacks(Us); + ei.attackedBy2[Us] = ei.attackedBy[Us][PAWN] & ei.attackedBy[Us][KING]; + + // Init king safety tables only if we are going to use them + if (pos.non_pawn_material(Us) >= QueenValueMg) + { + ei.kingRing[Them] = b | shift(b); + b &= ei.attackedBy[Us][PAWN]; + ei.kingAttackersCount[Us] = popcount(b); + ei.kingAdjacentZoneAttacksCount[Us] = ei.kingAttackersWeight[Us] = 0; + } + else + ei.kingRing[Them] = ei.kingAttackersCount[Us] = 0; + } + + + // evaluate_pieces() assigns bonuses and penalties to the pieces of a given + // color and type. + + template + Score evaluate_pieces(const Position& pos, EvalInfo& ei, Score* mobility, + const Bitboard* mobilityArea) { + Bitboard b, bb; + Square s; + Score score = SCORE_ZERO; + + const PieceType NextPt = (Us == WHITE ? Pt : PieceType(Pt + 1)); + const Color Them = (Us == WHITE ? BLACK : WHITE); + const Bitboard OutpostRanks = (Us == WHITE ? Rank4BB | Rank5BB | Rank6BB + : Rank5BB | Rank4BB | Rank3BB); + const Square* pl = pos.squares(Us); + + ei.attackedBy[Us][Pt] = 0; + + while ((s = *pl++) != SQ_NONE) + { + // Find attacked squares, including x-ray attacks for bishops and rooks + b = Pt == BISHOP ? attacks_bb(s, pos.pieces() ^ pos.pieces(Us, QUEEN)) + : Pt == ROOK ? attacks_bb< ROOK>(s, pos.pieces() ^ pos.pieces(Us, ROOK, QUEEN)) + : pos.attacks_from(s); + + if (ei.pinnedPieces[Us] & s) + b &= LineBB[pos.square(Us)][s]; + + ei.attackedBy2[Us] |= ei.attackedBy[Us][ALL_PIECES] & b; + ei.attackedBy[Us][ALL_PIECES] |= ei.attackedBy[Us][Pt] |= b; + + if (b & ei.kingRing[Them]) + { + ei.kingAttackersCount[Us]++; + ei.kingAttackersWeight[Us] += KingAttackWeights[Pt]; + ei.kingAdjacentZoneAttacksCount[Us] += popcount(b & ei.attackedBy[Them][KING]); + } + + if (Pt == QUEEN) + b &= ~( ei.attackedBy[Them][KNIGHT] + | ei.attackedBy[Them][BISHOP] + | ei.attackedBy[Them][ROOK]); + + int mob = popcount(b & mobilityArea[Us]); + + mobility[Us] += MobilityBonus[Pt][mob]; + + if (Pt == BISHOP || Pt == KNIGHT) + { + // Bonus for outpost squares + bb = OutpostRanks & ~ei.pi->pawn_attacks_span(Them); + if (bb & s) + score += Outpost[Pt == BISHOP][!!(ei.attackedBy[Us][PAWN] & s)]; + else + { + bb &= b & ~pos.pieces(Us); + if (bb) + score += ReachableOutpost[Pt == BISHOP][!!(ei.attackedBy[Us][PAWN] & bb)]; + } + + // Bonus when behind a pawn + if ( relative_rank(Us, s) < RANK_5 + && (pos.pieces(PAWN) & (s + pawn_push(Us)))) + score += MinorBehindPawn; + + // Penalty for pawns on the same color square as the bishop + if (Pt == BISHOP) + score -= BishopPawns * ei.pi->pawns_on_same_color_squares(Us, s); + + // 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 ( Pt == BISHOP + && pos.is_chess960() + && (s == relative_square(Us, SQ_A1) || s == relative_square(Us, SQ_H1))) + { + Square 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)) ? TrappedBishopA1H1 * 4 + : pos.piece_on(s + d + d) == make_piece(Us, PAWN) ? TrappedBishopA1H1 * 2 + : TrappedBishopA1H1; + } + } + + if (Pt == ROOK) + { + // Bonus for aligning with enemy pawns on the same rank/file + if (relative_rank(Us, s) >= RANK_5) + score += RookOnPawn * popcount(pos.pieces(Them, PAWN) & PseudoAttacks[ROOK][s]); + + // Bonus when on an open or semi-open file + if (ei.pi->semiopen_file(Us, file_of(s))) + score += RookOnFile[!!ei.pi->semiopen_file(Them, file_of(s))]; + + // Penalize when trapped by the king, even more if the king cannot castle + else if (mob <= 3) + { + Square ksq = pos.square(Us); + + if ( ((file_of(ksq) < FILE_E) == (file_of(s) < file_of(ksq))) + && (rank_of(ksq) == rank_of(s) || relative_rank(Us, ksq) == RANK_1) + && !ei.pi->semiopen_side(Us, file_of(ksq), file_of(s) < file_of(ksq))) + score -= (TrappedRook - make_score(mob * 22, 0)) * (1 + !pos.can_castle(Us)); + } + } + + if (Pt == QUEEN) + { + // Penalty if any relative pin or discovered attack against the queen + Bitboard pinners; + if (pos.slider_blockers(pos.pieces(Them, ROOK, BISHOP), s, pinners)) + score -= WeakQueen; + } + } + + if (DoTrace) + Trace::add(Pt, Us, score); + + // Recursively call evaluate_pieces() of next piece type until KING is excluded + return score - evaluate_pieces(pos, ei, mobility, mobilityArea); + } + + template<> + Score evaluate_pieces(const Position&, EvalInfo&, Score*, const Bitboard*) { return SCORE_ZERO; } + template<> + Score evaluate_pieces< true, WHITE, KING>(const Position&, EvalInfo&, Score*, const Bitboard*) { return SCORE_ZERO; } + + + // evaluate_king() assigns bonuses and penalties to a king of a given color + + const Bitboard WhiteCamp = Rank1BB | Rank2BB | Rank3BB | Rank4BB | Rank5BB; + const Bitboard BlackCamp = Rank8BB | Rank7BB | Rank6BB | Rank5BB | Rank4BB; + const Bitboard QueenSide = FileABB | FileBBB | FileCBB | FileDBB; + const Bitboard CenterFiles = FileCBB | FileDBB | FileEBB | FileFBB; + const Bitboard KingSide = FileEBB | FileFBB | FileGBB | FileHBB; + + const Bitboard KingFlank[COLOR_NB][FILE_NB] = { + { QueenSide & WhiteCamp, QueenSide & WhiteCamp, QueenSide & WhiteCamp, CenterFiles & WhiteCamp, + CenterFiles & WhiteCamp, KingSide & WhiteCamp, KingSide & WhiteCamp, KingSide & WhiteCamp }, + { QueenSide & BlackCamp, QueenSide & BlackCamp, QueenSide & BlackCamp, CenterFiles & BlackCamp, + CenterFiles & BlackCamp, KingSide & BlackCamp, KingSide & BlackCamp, KingSide & BlackCamp }, + }; + + template + Score evaluate_king(const Position& pos, const EvalInfo& ei) { + + const Color Them = (Us == WHITE ? BLACK : WHITE); + const Square Up = (Us == WHITE ? NORTH : SOUTH); + + Bitboard undefended, b, b1, b2, safe, other; + int kingDanger; + const Square ksq = pos.square(Us); + + // King shelter and enemy pawns storm + Score score = ei.pi->king_safety(pos, ksq); + + // Main king safety evaluation + if (ei.kingAttackersCount[Them]) + { + // Find the attacked squares which are defended only by the king... + undefended = ei.attackedBy[Them][ALL_PIECES] + & ei.attackedBy[Us][KING] + & ~ei.attackedBy2[Us]; + + // ... and those which are not defended at all in the larger king ring + b = ei.attackedBy[Them][ALL_PIECES] & ~ei.attackedBy[Us][ALL_PIECES] + & ei.kingRing[Us] & ~pos.pieces(Them); + + // Initialize the 'kingDanger' variable, which will be transformed + // later into a king danger score. The initial value is based on the + // number and types of the enemy's attacking pieces, the number of + // attacked and undefended squares around our king and the quality of + // the pawn shelter (current 'score' value). + kingDanger = std::min(807, ei.kingAttackersCount[Them] * ei.kingAttackersWeight[Them]) + + 101 * ei.kingAdjacentZoneAttacksCount[Them] + + 235 * popcount(undefended) + + 134 * (popcount(b) + !!ei.pinnedPieces[Us]) + - 717 * !pos.count(Them) + - 7 * mg_value(score) / 5 - 5; + + // Analyse the enemy's safe queen contact checks. Firstly, find the + // undefended squares around the king reachable by the enemy queen... + b = undefended & ei.attackedBy[Them][QUEEN] & ~pos.pieces(Them); + + // ...and keep squares supported by another enemy piece + kingDanger += QueenContactCheck * popcount(b & ei.attackedBy2[Them]); + + // Analyse the safe enemy's checks which are possible on next move... + safe = ~(ei.attackedBy[Us][ALL_PIECES] | pos.pieces(Them)); + + // ... and some other potential checks, only requiring the square to be + // safe from pawn-attacks, and not being occupied by a blocked pawn. + other = ~( ei.attackedBy[Us][PAWN] + | (pos.pieces(Them, PAWN) & shift(pos.pieces(PAWN)))); + + b1 = pos.attacks_from(ksq); + b2 = pos.attacks_from(ksq); + + // Enemy queen safe checks + if ((b1 | b2) & ei.attackedBy[Them][QUEEN] & safe) + kingDanger += QueenCheck, score -= SafeCheck; + + // For other pieces, also consider the square safe if attacked twice, + // and only defended by a queen. + safe |= ei.attackedBy2[Them] + & ~(ei.attackedBy2[Us] | pos.pieces(Them)) + & ei.attackedBy[Us][QUEEN]; + + // Enemy rooks safe and other checks + if (b1 & ei.attackedBy[Them][ROOK] & safe) + kingDanger += RookCheck, score -= SafeCheck; + + else if (b1 & ei.attackedBy[Them][ROOK] & other) + score -= OtherCheck; + + // Enemy bishops safe and other checks + if (b2 & ei.attackedBy[Them][BISHOP] & safe) + kingDanger += BishopCheck, score -= SafeCheck; + + else if (b2 & ei.attackedBy[Them][BISHOP] & other) + score -= OtherCheck; + + // Enemy knights safe and other checks + b = pos.attacks_from(ksq) & ei.attackedBy[Them][KNIGHT]; + if (b & safe) + kingDanger += KnightCheck, score -= SafeCheck; + + else if (b & other) + score -= OtherCheck; + + // Compute the king danger score and subtract it from the evaluation + if (kingDanger > 0) + score -= make_score(std::min(kingDanger * kingDanger / 4096, 2 * int(BishopValueMg)), 0); + } + + // King tropism: firstly, find squares that opponent attacks in our king flank + File kf = file_of(ksq); + b = ei.attackedBy[Them][ALL_PIECES] & KingFlank[Us][kf]; + + assert(((Us == WHITE ? b << 4 : b >> 4) & b) == 0); + assert(popcount(Us == WHITE ? b << 4 : b >> 4) == popcount(b)); + + // Secondly, add the squares which are attacked twice in that flank and + // which are not defended by our pawns. + b = (Us == WHITE ? b << 4 : b >> 4) + | (b & ei.attackedBy2[Them] & ~ei.attackedBy[Us][PAWN]); + + score -= CloseEnemies * popcount(b); + + // Penalty when our king is on a pawnless flank + if (!(pos.pieces(PAWN) & (KingFlank[WHITE][kf] | KingFlank[BLACK][kf]))) + score -= PawnlessFlank; + + if (DoTrace) + Trace::add(KING, Us, score); + + return score; + } + + + // evaluate_threats() assigns bonuses according to the types of the attacking + // and the attacked pieces. + + template + Score evaluate_threats(const Position& pos, const EvalInfo& ei) { + + const Color Them = (Us == WHITE ? BLACK : WHITE); + const Square Up = (Us == WHITE ? NORTH : SOUTH); + const Square Left = (Us == WHITE ? NORTH_WEST : SOUTH_EAST); + const Square Right = (Us == WHITE ? NORTH_EAST : SOUTH_WEST); + const Bitboard TRank2BB = (Us == WHITE ? Rank2BB : Rank7BB); + const Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB); + + enum { Minor, Rook }; + + Bitboard b, weak, defended, safeThreats; + Score score = SCORE_ZERO; + + // Small bonus if the opponent has loose pawns or pieces + if ( (pos.pieces(Them) ^ pos.pieces(Them, QUEEN, KING)) + & ~(ei.attackedBy[Us][ALL_PIECES] | ei.attackedBy[Them][ALL_PIECES])) + score += LooseEnemies; + + // Non-pawn enemies attacked by a pawn + weak = (pos.pieces(Them) ^ pos.pieces(Them, PAWN)) & ei.attackedBy[Us][PAWN]; + + if (weak) + { + b = pos.pieces(Us, PAWN) & ( ~ei.attackedBy[Them][ALL_PIECES] + | ei.attackedBy[Us][ALL_PIECES]); + + safeThreats = (shift(b) | shift(b)) & weak; + + if (weak ^ safeThreats) + score += ThreatByHangingPawn; + + while (safeThreats) + score += ThreatBySafePawn[type_of(pos.piece_on(pop_lsb(&safeThreats)))]; + } + + // Non-pawn enemies defended by a pawn + defended = (pos.pieces(Them) ^ pos.pieces(Them, PAWN)) & ei.attackedBy[Them][PAWN]; + + // Enemies not defended by a pawn and under our attack + weak = pos.pieces(Them) + & ~ei.attackedBy[Them][PAWN] + & ei.attackedBy[Us][ALL_PIECES]; + + // Add a bonus according to the kind of attacking pieces + if (defended | weak) + { + b = (defended | weak) & (ei.attackedBy[Us][KNIGHT] | ei.attackedBy[Us][BISHOP]); + while (b) + score += Threat[Minor][type_of(pos.piece_on(pop_lsb(&b)))]; + + b = (pos.pieces(Them, QUEEN) | weak) & ei.attackedBy[Us][ROOK]; + while (b) + score += Threat[Rook ][type_of(pos.piece_on(pop_lsb(&b)))]; + + score += Hanging * popcount(weak & ~ei.attackedBy[Them][ALL_PIECES]); + + b = weak & ei.attackedBy[Us][KING]; + if (b) + score += ThreatByKing[more_than_one(b)]; + } + + // Bonus if some pawns can safely push and attack an enemy piece + b = pos.pieces(Us, PAWN) & ~TRank7BB; + b = shift(b | (shift(b & TRank2BB) & ~pos.pieces())); + + b &= ~pos.pieces() + & ~ei.attackedBy[Them][PAWN] + & (ei.attackedBy[Us][ALL_PIECES] | ~ei.attackedBy[Them][ALL_PIECES]); + + b = (shift(b) | shift(b)) + & pos.pieces(Them) + & ~ei.attackedBy[Us][PAWN]; + + score += ThreatByPawnPush * popcount(b); + + if (DoTrace) + Trace::add(THREAT, Us, score); + + return score; + } + + + // evaluate_passed_pawns() evaluates the passed pawns of the given color + + template + Score evaluate_passed_pawns(const Position& pos, const EvalInfo& ei) { + + const Color Them = (Us == WHITE ? BLACK : WHITE); + + Bitboard b, bb, squaresToQueen, defendedSquares, unsafeSquares; + Score score = SCORE_ZERO; + + b = ei.pi->passed_pawns(Us); + + while (b) + { + Square s = pop_lsb(&b); + + assert(pos.pawn_passed(Us, s)); + assert(!(pos.pieces(PAWN) & forward_bb(Us, s))); + + bb = forward_bb(Us, s) & (ei.attackedBy[Them][ALL_PIECES] | pos.pieces(Them)); + score -= HinderPassedPawn * popcount(bb); + + int r = relative_rank(Us, s) - RANK_2; + int rr = r * (r - 1); + + Value mbonus = Passed[MG][r], ebonus = Passed[EG][r]; + + if (rr) + { + Square blockSq = s + pawn_push(Us); + + // Adjust bonus based on the king's proximity + ebonus += distance(pos.square(Them), blockSq) * 5 * rr + - distance(pos.square(Us ), blockSq) * 2 * rr; + + // If blockSq is not the queening square then consider also a second push + if (relative_rank(Us, blockSq) != RANK_8) + ebonus -= distance(pos.square(Us), blockSq + pawn_push(Us)) * rr; + + // If the pawn is free to advance, then increase the bonus + if (pos.empty(blockSq)) + { + // If there is a rook or queen attacking/defending the pawn from behind, + // consider all the squaresToQueen. Otherwise consider only the squares + // in the pawn's path attacked or occupied by the enemy. + defendedSquares = unsafeSquares = squaresToQueen = forward_bb(Us, s); + + bb = forward_bb(Them, s) & pos.pieces(ROOK, QUEEN) & pos.attacks_from(s); + + if (!(pos.pieces(Us) & bb)) + defendedSquares &= ei.attackedBy[Us][ALL_PIECES]; + + if (!(pos.pieces(Them) & bb)) + unsafeSquares &= ei.attackedBy[Them][ALL_PIECES] | pos.pieces(Them); + + // If there aren't any enemy attacks, assign a big bonus. Otherwise + // assign a smaller bonus if the block square isn't attacked. + int k = !unsafeSquares ? 18 : !(unsafeSquares & blockSq) ? 8 : 0; + + // If the path to the queen is fully defended, assign a big bonus. + // Otherwise assign a smaller bonus if the block square is defended. + if (defendedSquares == squaresToQueen) + k += 6; + + else if (defendedSquares & blockSq) + k += 4; + + mbonus += k * rr, ebonus += k * rr; + } + else if (pos.pieces(Us) & blockSq) + mbonus += rr + r * 2, ebonus += rr + r * 2; + } // rr != 0 + + score += make_score(mbonus, ebonus) + PassedFile[file_of(s)]; + } + + if (DoTrace) + Trace::add(PASSED, Us, score); + + // Add the scores to the middlegame and endgame eval + return score; + } + + + // evaluate_space() computes the space evaluation for a given side. The + // space evaluation is a simple bonus based on the number of safe squares + // available for minor pieces on the central four files on ranks 2--4. Safe + // squares one, two or three squares behind a friendly pawn are counted + // twice. Finally, the space bonus is multiplied by a weight. The aim is to + // improve play on game opening. + template + Score evaluate_space(const Position& pos, const EvalInfo& ei) { + + const Color Them = (Us == WHITE ? BLACK : WHITE); + const Bitboard SpaceMask = + Us == WHITE ? (FileCBB | FileDBB | FileEBB | FileFBB) & (Rank2BB | Rank3BB | Rank4BB) + : (FileCBB | FileDBB | FileEBB | FileFBB) & (Rank7BB | Rank6BB | Rank5BB); + + // Find the safe squares for our pieces inside the area defined by + // SpaceMask. A square is unsafe if it is attacked by an enemy + // pawn, or if it is undefended and attacked by an enemy piece. + Bitboard safe = SpaceMask + & ~pos.pieces(Us, PAWN) + & ~ei.attackedBy[Them][PAWN] + & (ei.attackedBy[Us][ALL_PIECES] | ~ei.attackedBy[Them][ALL_PIECES]); + + // Find all squares which are at most three squares behind some friendly pawn + Bitboard behind = pos.pieces(Us, PAWN); + behind |= (Us == WHITE ? behind >> 8 : behind << 8); + behind |= (Us == WHITE ? behind >> 16 : behind << 16); + + // Since SpaceMask[Us] is fully on our half of the board... + assert(unsigned(safe >> (Us == WHITE ? 32 : 0)) == 0); + + // ...count safe + (behind & safe) with a single popcount + int bonus = popcount((Us == WHITE ? safe << 32 : safe >> 32) | (behind & safe)); + bonus = std::min(16, bonus); + int weight = pos.count(Us) - 2 * ei.pi->open_files(); + + return make_score(bonus * weight * weight / 18, 0); + } + + + // evaluate_initiative() computes the initiative correction value for the + // position, i.e., second order bonus/malus based on the known attacking/defending + // status of the players. + Score evaluate_initiative(const Position& pos, int asymmetry, Value eg) { + + int kingDistance = distance(pos.square(WHITE), pos.square(BLACK)) + - distance(pos.square(WHITE), pos.square(BLACK)); + int pawns = pos.count(WHITE) + pos.count(BLACK); + + // Compute the initiative bonus for the attacking side + int initiative = 8 * (asymmetry + kingDistance - 15) + 12 * pawns; + + // Now apply the bonus: note that we find the attacking side by extracting + // the sign of the endgame value, and that we carefully cap the bonus so + // that the endgame score will never be divided by more than two. + int value = ((eg > 0) - (eg < 0)) * std::max(initiative, -abs(eg / 2)); + + return make_score(0, value); + } + + + // evaluate_scale_factor() computes the scale factor for the winning side + ScaleFactor evaluate_scale_factor(const Position& pos, const EvalInfo& ei, Value eg) { + + Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK; + ScaleFactor sf = ei.me->scale_factor(pos, strongSide); + + // If we don't already have an unusual scale factor, check for certain + // types of endgames, and use a lower scale for those. + if ( ei.me->game_phase() < PHASE_MIDGAME + && (sf == SCALE_FACTOR_NORMAL || sf == SCALE_FACTOR_ONEPAWN)) + { + if (pos.opposite_bishops()) + { + // Endgame with opposite-colored bishops and no other pieces (ignoring pawns) + // is almost a draw, in case of KBP vs KB, it is even more a draw. + if ( pos.non_pawn_material(WHITE) == BishopValueMg + && pos.non_pawn_material(BLACK) == BishopValueMg) + sf = more_than_one(pos.pieces(PAWN)) ? ScaleFactor(31) : ScaleFactor(9); + + // Endgame with opposite-colored bishops, but also other pieces. Still + // a bit drawish, but not as drawish as with only the two bishops. + else + sf = ScaleFactor(46); + } + // Endings where weaker side can place his king in front of the opponent's + // pawns are drawish. + else if ( abs(eg) <= BishopValueEg + && pos.count(strongSide) <= 2 + && !pos.pawn_passed(~strongSide, pos.square(~strongSide))) + sf = ScaleFactor(37 + 7 * pos.count(strongSide)); + } + + return sf; + } + +} // namespace + + +/// evaluate() is the main evaluation function. It returns a static evaluation +/// of the position from the point of view of the side to move. + +template +Value Eval::evaluate(const Position& pos) { + + assert(!pos.checkers()); + + Score mobility[COLOR_NB] = { SCORE_ZERO, SCORE_ZERO }; + EvalInfo ei; + + // Probe the material hash table + ei.me = Material::probe(pos); + + // If we have a specialized evaluation function for the current material + // configuration, call it and return. + if (ei.me->specialized_eval_exists()) + return ei.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() + ei.me->imbalance(); + + // Probe the pawn hash table + ei.pi = Pawns::probe(pos); + score += ei.pi->pawns_score(); + + // Initialize attack and king safety bitboards + ei.attackedBy[WHITE][ALL_PIECES] = ei.attackedBy[BLACK][ALL_PIECES] = 0; + ei.attackedBy[WHITE][KING] = pos.attacks_from(pos.square(WHITE)); + ei.attackedBy[BLACK][KING] = pos.attacks_from(pos.square(BLACK)); + eval_init(pos, ei); + eval_init(pos, ei); + + // Pawns blocked or on ranks 2 and 3 will be excluded from the mobility area + Bitboard blockedPawns[] = { + pos.pieces(WHITE, PAWN) & (shift(pos.pieces()) | Rank2BB | Rank3BB), + pos.pieces(BLACK, PAWN) & (shift(pos.pieces()) | Rank7BB | Rank6BB) + }; + + // Do not include in mobility area squares protected by enemy pawns, or occupied + // by our blocked pawns or king. + Bitboard mobilityArea[] = { + ~(ei.attackedBy[BLACK][PAWN] | blockedPawns[WHITE] | pos.square(WHITE)), + ~(ei.attackedBy[WHITE][PAWN] | blockedPawns[BLACK] | pos.square(BLACK)) + }; + + // Evaluate all pieces but king and pawns + score += evaluate_pieces(pos, ei, mobility, mobilityArea); + score += mobility[WHITE] - mobility[BLACK]; + + // Evaluate kings after all other pieces because we need full attack + // information when computing the king safety evaluation. + score += evaluate_king(pos, ei) + - evaluate_king(pos, ei); + + // Evaluate tactical threats, we need full attack information including king + score += evaluate_threats(pos, ei) + - evaluate_threats(pos, ei); + + // Evaluate passed pawns, we need full attack information including king + score += evaluate_passed_pawns(pos, ei) + - evaluate_passed_pawns(pos, ei); + + // If both sides have only pawns, score for potential unstoppable pawns + if (!pos.non_pawn_material(WHITE) && !pos.non_pawn_material(BLACK)) + { + Bitboard b; + if ((b = ei.pi->passed_pawns(WHITE)) != 0) + score += Unstoppable * int(relative_rank(WHITE, frontmost_sq(WHITE, b))); + + if ((b = ei.pi->passed_pawns(BLACK)) != 0) + score -= Unstoppable * int(relative_rank(BLACK, frontmost_sq(BLACK, b))); + } + + // Evaluate space for both sides, only during opening + if (pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK) >= 12222) + score += evaluate_space(pos, ei) + - evaluate_space(pos, ei); + + // Evaluate position potential for the winning side + score += evaluate_initiative(pos, ei.pi->pawn_asymmetry(), eg_value(score)); + + // Evaluate scale factor for the winning side + ScaleFactor sf = evaluate_scale_factor(pos, ei, eg_value(score)); + + // Interpolate between a middlegame and a (scaled by 'sf') endgame score + Value v = mg_value(score) * int(ei.me->game_phase()) + + eg_value(score) * int(PHASE_MIDGAME - ei.me->game_phase()) * sf / SCALE_FACTOR_NORMAL; + + v /= int(PHASE_MIDGAME); + + // In case of tracing add all remaining individual evaluation terms + if (DoTrace) + { + Trace::add(MATERIAL, pos.psq_score()); + Trace::add(IMBALANCE, ei.me->imbalance()); + Trace::add(PAWN, ei.pi->pawns_score()); + Trace::add(MOBILITY, mobility[WHITE], mobility[BLACK]); + Trace::add(SPACE, evaluate_space(pos, ei) + , evaluate_space(pos, ei)); + Trace::add(TOTAL, score); + } + + return (pos.side_to_move() == WHITE ? v : -v) + Eval::Tempo; // Side to move point of view +} + +// Explicit template instantiations +template Value Eval::evaluate(const Position&); +template Value Eval::evaluate(const Position&); + + +/// 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. + +std::string Eval::trace(const Position& pos) { + + std::memset(scores, 0, sizeof(scores)); + + Value v = evaluate(pos); + v = pos.side_to_move() == WHITE ? v : -v; // White's point of view + + std::stringstream ss; + ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2) + << " Eval 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) + << " Bishop | " << Term(BISHOP) + << " Rooks | " << Term(ROOK) + << " Queens | " << Term(QUEEN) + << " Mobility | " << Term(MOBILITY) + << " King safety | " << Term(KING) + << " Threats | " << Term(THREAT) + << " Passed pawns | " << Term(PASSED) + << " Space | " << Term(SPACE) + << "----------------+-------------+-------------+-------------\n" + << " Total | " << Term(TOTAL); + + ss << "\nTotal Evaluation: " << to_cp(v) << " (white side)\n"; + + return ss.str(); +} diff --git a/src/evaluate.h b/src/evaluate.h new file mode 100644 index 0000000..7f655f6 --- /dev/null +++ b/src/evaluate.h @@ -0,0 +1,40 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef EVALUATE_H_INCLUDED +#define EVALUATE_H_INCLUDED + +#include + +#include "types.h" + +class Position; + +namespace Eval { + +const Value Tempo = Value(20); // Must be visible to search + +std::string trace(const Position& pos); + +template +Value evaluate(const Position& pos); +} + +#endif // #ifndef EVALUATE_H_INCLUDED diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..7187d30 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,54 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "bitboard.h" +#include "position.h" +#include "search.h" +#include "thread.h" +#include "tt.h" +#include "uci.h" +#include "syzygy/tbprobe.h" + +namespace PSQT { + void init(); +} + +int main(int argc, char* argv[]) { + + std::cout << engine_info() << std::endl; + + UCI::init(Options); + PSQT::init(); + Bitboards::init(); + Position::init(); + Bitbases::init(); + Search::init(); + Pawns::init(); + Threads.init(); + Tablebases::init(Options["SyzygyPath"]); + TT.resize(Options["Hash"]); + + UCI::loop(argc, argv); + + Threads.exit(); + return 0; +} diff --git a/src/material.cpp b/src/material.cpp new file mode 100644 index 0000000..f6c4e2d --- /dev/null +++ b/src/material.cpp @@ -0,0 +1,225 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include // For std::min +#include +#include // For std::memset + +#include "material.h" +#include "thread.h" + +using namespace std; + +namespace { + + // Polynomial material imbalance parameters + + const int QuadraticOurs[][PIECE_TYPE_NB] = { + // OUR PIECES + // pair pawn knight bishop rook queen + {1667 }, // Bishop pair + { 40, 2 }, // Pawn + { 32, 255, -3 }, // Knight OUR PIECES + { 0, 104, 4, 0 }, // Bishop + { -26, -2, 47, 105, -149 }, // Rook + {-185, 24, 122, 137, -134, 0 } // Queen + }; + + const int QuadraticTheirs[][PIECE_TYPE_NB] = { + // THEIR PIECES + // pair pawn knight bishop rook queen + { 0 }, // Bishop pair + { 36, 0 }, // Pawn + { 9, 63, 0 }, // Knight OUR PIECES + { 59, 65, 42, 0 }, // Bishop + { 46, 39, 24, -24, 0 }, // Rook + { 101, 100, -37, 141, 268, 0 } // Queen + }; + + // Endgame evaluation and scaling functions are accessed directly and not through + // the function maps because they correspond to more than one material hash key. + Endgame EvaluateKXK[] = { Endgame(WHITE), Endgame(BLACK) }; + + Endgame ScaleKBPsK[] = { Endgame(WHITE), Endgame(BLACK) }; + Endgame ScaleKQKRPs[] = { Endgame(WHITE), Endgame(BLACK) }; + Endgame ScaleKPsK[] = { Endgame(WHITE), Endgame(BLACK) }; + Endgame ScaleKPKP[] = { Endgame(WHITE), Endgame(BLACK) }; + + // Helper used to detect a given material distribution + bool is_KXK(const Position& pos, Color us) { + return !more_than_one(pos.pieces(~us)) + && pos.non_pawn_material(us) >= RookValueMg; + } + + bool is_KBPsKs(const Position& pos, Color us) { + return pos.non_pawn_material(us) == BishopValueMg + && pos.count(us) == 1 + && 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 + && pos.count(~us) >= 1; + } + + /// imbalance() calculates the imbalance by comparing the piece count of each + /// piece type for both colors. + template + int imbalance(const int pieceCount[][PIECE_TYPE_NB]) { + + const Color Them = (Us == WHITE ? BLACK : WHITE); + + int bonus = 0; + + // Second-degree polynomial material imbalance by Tord Romstad + for (int pt1 = NO_PIECE_TYPE; pt1 <= QUEEN; ++pt1) + { + if (!pieceCount[Us][pt1]) + continue; + + int v = 0; + + for (int pt2 = NO_PIECE_TYPE; pt2 <= pt1; ++pt2) + v += QuadraticOurs[pt1][pt2] * pieceCount[Us][pt2] + + QuadraticTheirs[pt1][pt2] * pieceCount[Them][pt2]; + + bonus += pieceCount[Us][pt1] * v; + } + + return bonus; + } + +} // namespace + +namespace Material { + +/// Material::probe() looks up the current position's material configuration in +/// the material hash table. It returns a pointer to the Entry if the position +/// is found. Otherwise a new Entry is computed and stored there, so we don't +/// have to recompute all when the same material configuration occurs again. + +Entry* probe(const Position& pos) { + + Key key = pos.material_key(); + Entry* e = pos.this_thread()->materialTable[key]; + + if (e->key == key) + return e; + + std::memset(e, 0, sizeof(Entry)); + e->key = key; + e->factor[WHITE] = e->factor[BLACK] = (uint8_t)SCALE_FACTOR_NORMAL; + e->gamePhase = pos.game_phase(); + + // 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 = pos.this_thread()->endgames.probe(key)) != nullptr) + return e; + + for (Color c = WHITE; c <= BLACK; ++c) + 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? + EndgameBase* sf; + + if ((sf = pos.this_thread()->endgames.probe(key)) != nullptr) + { + e->scalingFunction[sf->strong_side()] = 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; c <= BLACK; ++c) + { + if (is_KBPsKs(pos, c)) + e->scalingFunction[c] = &ScaleKBPsK[c]; + + else if (is_KQKRPs(pos, c)) + e->scalingFunction[c] = &ScaleKQKRPs[c]; + } + + Value npm_w = pos.non_pawn_material(WHITE); + Value npm_b = pos.non_pawn_material(BLACK); + + if (npm_w + npm_b == VALUE_ZERO && pos.pieces(PAWN)) // Only pawns on the board + { + if (!pos.count(BLACK)) + { + assert(pos.count(WHITE) >= 2); + + e->scalingFunction[WHITE] = &ScaleKPsK[WHITE]; + } + else if (!pos.count(WHITE)) + { + assert(pos.count(BLACK) >= 2); + + e->scalingFunction[BLACK] = &ScaleKPsK[BLACK]; + } + else if (pos.count(WHITE) == 1 && pos.count(BLACK) == 1) + { + // This is a special case because we set scaling functions + // for both colors instead of only one. + e->scalingFunction[WHITE] = &ScaleKPKP[WHITE]; + e->scalingFunction[BLACK] = &ScaleKPKP[BLACK]; + } + } + + // Zero or just one pawn makes it difficult to win, even with a small material + // advantage. This catches some trivial draws like KK, KBK and KNK and gives a + // drawish scale factor for cases such as KRKBP and KmmKm (except for KBBKN). + if (!pos.count(WHITE) && npm_w - npm_b <= BishopValueMg) + e->factor[WHITE] = uint8_t(npm_w < RookValueMg ? SCALE_FACTOR_DRAW : + npm_b <= BishopValueMg ? 4 : 14); + + if (!pos.count(BLACK) && npm_b - npm_w <= BishopValueMg) + e->factor[BLACK] = uint8_t(npm_b < RookValueMg ? SCALE_FACTOR_DRAW : + npm_w <= BishopValueMg ? 4 : 14); + + if (pos.count(WHITE) == 1 && npm_w - npm_b <= BishopValueMg) + e->factor[WHITE] = (uint8_t) SCALE_FACTOR_ONEPAWN; + + if (pos.count(BLACK) == 1 && npm_b - npm_w <= BishopValueMg) + e->factor[BLACK] = (uint8_t) SCALE_FACTOR_ONEPAWN; + + // 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->value = int16_t((imbalance(PieceCount) - imbalance(PieceCount)) / 16); + return e; +} + +} // namespace Material diff --git a/src/material.h b/src/material.h new file mode 100644 index 0000000..bec2d66 --- /dev/null +++ b/src/material.h @@ -0,0 +1,73 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef MATERIAL_H_INCLUDED +#define MATERIAL_H_INCLUDED + +#include "endgame.h" +#include "misc.h" +#include "position.h" +#include "types.h" + +namespace Material { + +/// Material::Entry contains various information about a material configuration. +/// It contains a material imbalance evaluation, a function pointer to a special +/// endgame evaluation function (which in most cases is NULL, meaning that the +/// standard evaluation function will be used), and scale factors. +/// +/// The scale factors are used to scale the evaluation score up or down. For +/// instance, in KRB vs KR endgames, the score is scaled down by a factor of 4, +/// which will result in scores of absolute value less than one pawn. + +struct Entry { + + Score imbalance() const { return make_score(value, value); } + Phase game_phase() const { return gamePhase; } + bool specialized_eval_exists() const { return evaluationFunction != nullptr; } + Value evaluate(const Position& pos) const { return (*evaluationFunction)(pos); } + + // scale_factor takes a position and a color as input and returns a scale factor + // for the given color. We have to provide the position in addition to the color + // because the scale factor may also be a function which should be applied to + // the position. For instance, in KBP vs K endgames, the scaling function looks + // for rook pawns and wrong-colored bishops. + ScaleFactor scale_factor(const Position& pos, Color c) const { + ScaleFactor sf = scalingFunction[c] ? (*scalingFunction[c])(pos) + : SCALE_FACTOR_NONE; + return sf != SCALE_FACTOR_NONE ? sf : ScaleFactor(factor[c]); + } + + Key key; + int16_t value; + uint8_t factor[COLOR_NB]; + EndgameBase* evaluationFunction; + EndgameBase* scalingFunction[COLOR_NB]; // Could be one for each + // side (e.g. KPKP, KBPsKs) + Phase gamePhase; +}; + +typedef HashTable Table; + +Entry* probe(const Position& pos); + +} // namespace Material + +#endif // #ifndef MATERIAL_H_INCLUDED diff --git a/src/misc.cpp b/src/misc.cpp new file mode 100644 index 0000000..4c6c254 --- /dev/null +++ b/src/misc.cpp @@ -0,0 +1,187 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include + +#include "misc.h" +#include "thread.h" + +using namespace std; + +namespace { + +/// Version number. If Version is left empty, then compile date in the format +/// DD-MM-YY and show in engine_info. +const string Version = "8"; + +/// 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() { return logBuf->pubsync(), buf->pubsync(); } + int overflow(int c) { return log(buf->sputc((char)c), "<< "); } + int underflow() { return buf->sgetc(); } + int uflow() { 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); + 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. + +const string engine_info(bool to_uci) { + + const string months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec"); + string month, day, year; + stringstream ss, date(__DATE__); // From compiler, format is "Sep 21 2008" + + ss << "Stockfish " << Version << setfill('0'); + + if (Version.empty()) + { + date >> month >> day >> year; + ss << setw(2) << day << setw(2) << (1 + months.find(month) / 4) << year.substr(2); + } + + ss << (Is64Bit ? " 64" : "") + << (HasPext ? " BMI2" : (HasPopCnt ? " POPCNT" : "")) + << (to_uci ? "\nid author ": " by ") + << "T. Romstad, M. Costalba, J. Kiiski, G. Linscott"; + + return ss.str(); +} + + +/// Debug functions used mainly to collect run-time statistics +static int64_t hits[2], means[2]; + +void dbg_hit_on(bool b) { ++hits[0]; if (b) ++hits[1]; } +void dbg_hit_on(bool c, bool b) { if (c) dbg_hit_on(b); } +void dbg_mean_of(int v) { ++means[0]; means[1] += v; } + +void dbg_print() { + + if (hits[0]) + cerr << "Total " << hits[0] << " Hits " << hits[1] + << " hit rate (%) " << 100 * hits[1] / hits[0] << endl; + + if (means[0]) + cerr << "Total " << means[0] << " Mean " + << (double)means[1] / means[0] << endl; +} + + +/// Used to serialize access to std::cout to avoid multiple threads writing at +/// the same time. + +std::ostream& operator<<(std::ostream& os, SyncCout sc) { + + static 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 diff --git a/src/misc.h b/src/misc.h new file mode 100644 index 0000000..a2307fe --- /dev/null +++ b/src/misc.h @@ -0,0 +1,100 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef MISC_H_INCLUDED +#define MISC_H_INCLUDED + +#include +#include +#include +#include +#include + +#include "types.h" + +const std::string engine_info(bool to_uci = false); +void prefetch(void* addr); +void start_logger(const std::string& fname); + +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 + +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); +}; + + +enum SyncCout { IO_LOCK, IO_UNLOCK }; +std::ostream& operator<<(std::ostream&, SyncCout); + +#define sync_cout std::cout << IO_LOCK +#define sync_endl std::endl << IO_UNLOCK + + +/// xorshift64star Pseudo-Random Number Generator +/// This class is based on original code written and dedicated +/// to the public domain by Sebastiano Vigna (2014). +/// It has the following characteristics: +/// +/// - Outputs 64-bit numbers +/// - Passes Dieharder and SmallCrush test batteries +/// - Does not require warm-up, no zeroland to escape +/// - Internal state is a single 64-bit integer +/// - Period is 2^64 - 1 +/// - Speed: 1.60 ns/call (Core i7 @3.40GHz) +/// +/// For further analysis see +/// + +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()); } +}; + +#endif // #ifndef MISC_H_INCLUDED diff --git a/src/movegen.cpp b/src/movegen.cpp new file mode 100644 index 0000000..6a3cdc2 --- /dev/null +++ b/src/movegen.cpp @@ -0,0 +1,420 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "movegen.h" +#include "position.h" + +namespace { + + template + ExtMove* generate_castling(const Position& pos, ExtMove* moveList, Color us) { + + static const bool KingSide = (Cr == WHITE_OO || Cr == BLACK_OO); + + if (pos.castling_impeded(Cr) || !pos.can_castle(Cr)) + return moveList; + + // After castling, the rook and king final positions are the same in Chess960 + // as they would be in standard chess. + Square kfrom = pos.square(us); + Square rfrom = pos.castling_rook_square(Cr); + Square kto = relative_square(us, KingSide ? SQ_G1 : SQ_C1); + Bitboard enemies = pos.pieces(~us); + + assert(!pos.checkers()); + + const Square K = Chess960 ? kto > kfrom ? WEST : EAST + : KingSide ? WEST : EAST; + + for (Square s = kto; s != kfrom; s += K) + if (pos.attackers_to(s) & enemies) + return moveList; + + // Because we generate only legal castling moves we need to verify that + // when moving the castling rook we do not discover some hidden checker. + // For instance an enemy queen in SQ_A1 when castling rook is in SQ_B1. + if (Chess960 && (attacks_bb(kto, pos.pieces() ^ rfrom) & pos.pieces(~us, ROOK, QUEEN))) + return moveList; + + Move m = make(kfrom, rfrom); + + if (Checks && !pos.gives_check(m)) + return moveList; + + *moveList++ = m; + return moveList; + } + + + template + ExtMove* make_promotions(ExtMove* moveList, Square to, Square ksq) { + + if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) + *moveList++ = make(to - D, to, QUEEN); + + if (Type == QUIETS || Type == EVASIONS || Type == NON_EVASIONS) + { + *moveList++ = make(to - D, to, ROOK); + *moveList++ = make(to - D, to, BISHOP); + *moveList++ = make(to - D, to, KNIGHT); + } + + // Knight promotion is the only promotion that can give a direct check + // that's not already included in the queen promotion. + if (Type == QUIET_CHECKS && (StepAttacksBB[W_KNIGHT][to] & ksq)) + *moveList++ = make(to - D, to, KNIGHT); + else + (void)ksq; // Silence a warning under MSVC + + return moveList; + } + + + template + ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard target) { + + // Compute our parametrized parameters at compile time, named according to + // the point of view of white side. + const Color Them = (Us == WHITE ? BLACK : WHITE); + const Bitboard TRank8BB = (Us == WHITE ? Rank8BB : Rank1BB); + const Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB); + const Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB); + const Square Up = (Us == WHITE ? NORTH : SOUTH); + const Square Right = (Us == WHITE ? NORTH_EAST : SOUTH_WEST); + const Square Left = (Us == WHITE ? NORTH_WEST : SOUTH_EAST); + + Bitboard emptySquares; + + Bitboard pawnsOn7 = pos.pieces(Us, PAWN) & TRank7BB; + Bitboard pawnsNotOn7 = pos.pieces(Us, PAWN) & ~TRank7BB; + + Bitboard enemies = (Type == EVASIONS ? pos.pieces(Them) & target: + Type == CAPTURES ? target : pos.pieces(Them)); + + // Single and double pawn pushes, no promotions + if (Type != CAPTURES) + { + emptySquares = (Type == QUIETS || Type == QUIET_CHECKS ? target : ~pos.pieces()); + + Bitboard b1 = shift(pawnsNotOn7) & emptySquares; + Bitboard b2 = shift(b1 & TRank3BB) & emptySquares; + + if (Type == EVASIONS) // Consider only blocking squares + { + b1 &= target; + b2 &= target; + } + + if (Type == QUIET_CHECKS) + { + Square ksq = pos.square(Them); + + b1 &= pos.attacks_from(ksq, Them); + b2 &= pos.attacks_from(ksq, Them); + + // Add pawn pushes which give discovered check. This is possible only + // if the pawn is not on the same file as the enemy king, because we + // don't generate captures. Note that a possible discovery check + // promotion has been already generated amongst the captures. + Bitboard dcCandidates = pos.discovered_check_candidates(); + if (pawnsNotOn7 & dcCandidates) + { + Bitboard dc1 = shift(pawnsNotOn7 & dcCandidates) & emptySquares & ~file_bb(ksq); + Bitboard dc2 = shift(dc1 & TRank3BB) & emptySquares; + + b1 |= dc1; + b2 |= dc2; + } + } + + while (b1) + { + Square to = pop_lsb(&b1); + *moveList++ = make_move(to - Up, to); + } + + while (b2) + { + Square to = pop_lsb(&b2); + *moveList++ = make_move(to - Up - Up, to); + } + } + + // Promotions and underpromotions + if (pawnsOn7 && (Type != EVASIONS || (target & TRank8BB))) + { + if (Type == CAPTURES) + emptySquares = ~pos.pieces(); + + if (Type == EVASIONS) + emptySquares &= target; + + Bitboard b1 = shift(pawnsOn7) & enemies; + Bitboard b2 = shift(pawnsOn7) & enemies; + Bitboard b3 = shift(pawnsOn7) & emptySquares; + + Square ksq = pos.square(Them); + + while (b1) + moveList = make_promotions(moveList, pop_lsb(&b1), ksq); + + while (b2) + moveList = make_promotions(moveList, pop_lsb(&b2), ksq); + + while (b3) + moveList = make_promotions(moveList, pop_lsb(&b3), ksq); + } + + // 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 - Right, to); + } + + while (b2) + { + Square to = pop_lsb(&b2); + *moveList++ = make_move(to - Left, to); + } + + if (pos.ep_square() != SQ_NONE) + { + assert(rank_of(pos.ep_square()) == relative_rank(Us, RANK_6)); + + // An en passant capture can be an evasion only if the checking piece + // is the double pushed pawn and so is in the target. Otherwise this + // is a discovery check and we are forced to do otherwise. + if (Type == EVASIONS && !(target & (pos.ep_square() - Up))) + return moveList; + + b1 = pawnsNotOn7 & pos.attacks_from(pos.ep_square(), Them); + + assert(b1); + + while (b1) + *moveList++ = make(pop_lsb(&b1), pos.ep_square()); + } + } + + return moveList; + } + + + template + ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Color us, + Bitboard target) { + + assert(Pt != KING && Pt != PAWN); + + const Square* pl = pos.squares(us); + + for (Square from = *pl; from != SQ_NONE; from = *++pl) + { + if (Checks) + { + if ( (Pt == BISHOP || Pt == ROOK || Pt == QUEEN) + && !(PseudoAttacks[Pt][from] & target & pos.check_squares(Pt))) + continue; + + if (pos.discovered_check_candidates() & from) + continue; + } + + Bitboard b = pos.attacks_from(from) & target; + + if (Checks) + b &= pos.check_squares(Pt); + + while (b) + *moveList++ = make_move(from, pop_lsb(&b)); + } + + return moveList; + } + + + template + ExtMove* generate_all(const Position& pos, ExtMove* moveList, Bitboard target) { + + const bool Checks = Type == QUIET_CHECKS; + + moveList = generate_pawn_moves(pos, moveList, target); + moveList = generate_moves(pos, moveList, Us, target); + moveList = generate_moves(pos, moveList, Us, target); + moveList = generate_moves< ROOK, Checks>(pos, moveList, Us, target); + moveList = generate_moves< QUEEN, Checks>(pos, moveList, Us, target); + + if (Type != QUIET_CHECKS && Type != EVASIONS) + { + Square ksq = pos.square(Us); + Bitboard b = pos.attacks_from(ksq) & target; + while (b) + *moveList++ = make_move(ksq, pop_lsb(&b)); + } + + if (Type != CAPTURES && Type != EVASIONS && pos.can_castle(Us)) + { + if (pos.is_chess960()) + { + moveList = generate_castling::right, Checks, true>(pos, moveList, Us); + moveList = generate_castling::right, Checks, true>(pos, moveList, Us); + } + else + { + moveList = generate_castling::right, Checks, false>(pos, moveList, Us); + moveList = generate_castling::right, Checks, false>(pos, moveList, Us); + } + } + + return moveList; + } + +} // namespace + + +/// generate generates all pseudo-legal captures and queen +/// promotions. Returns a pointer to the end of the move list. +/// +/// generate generates all pseudo-legal non-captures and +/// underpromotions. Returns a pointer to the end of the move list. +/// +/// generate 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) { + + assert(Type == CAPTURES || Type == QUIETS || Type == NON_EVASIONS); + assert(!pos.checkers()); + + Color us = pos.side_to_move(); + + Bitboard target = Type == CAPTURES ? pos.pieces(~us) + : Type == QUIETS ? ~pos.pieces() + : Type == NON_EVASIONS ? ~pos.pieces(us) : 0; + + return us == WHITE ? generate_all(pos, moveList, target) + : generate_all(pos, moveList, target); +} + +// Explicit template instantiations +template ExtMove* generate(const Position&, ExtMove*); +template ExtMove* generate(const Position&, ExtMove*); +template ExtMove* generate(const Position&, ExtMove*); + + +/// generate generates all pseudo-legal non-captures and knight +/// underpromotions that give check. Returns a pointer to the end of the move list. +template<> +ExtMove* generate(const Position& pos, ExtMove* moveList) { + + assert(!pos.checkers()); + + Color us = pos.side_to_move(); + Bitboard dc = pos.discovered_check_candidates(); + + while (dc) + { + Square from = pop_lsb(&dc); + PieceType pt = type_of(pos.piece_on(from)); + + if (pt == PAWN) + continue; // Will be generated together with direct checks + + Bitboard b = pos.attacks_from(Piece(pt), from) & ~pos.pieces(); + + if (pt == KING) + b &= ~PseudoAttacks[QUEEN][pos.square(~us)]; + + while (b) + *moveList++ = make_move(from, pop_lsb(&b)); + } + + return us == WHITE ? generate_all(pos, moveList, ~pos.pieces()) + : generate_all(pos, moveList, ~pos.pieces()); +} + + +/// generate generates all pseudo-legal check evasions when the side +/// to move is in check. Returns a pointer to the end of the move list. +template<> +ExtMove* generate(const Position& pos, ExtMove* moveList) { + + assert(pos.checkers()); + + Color us = pos.side_to_move(); + Square ksq = pos.square(us); + Bitboard sliderAttacks = 0; + Bitboard sliders = pos.checkers() & ~pos.pieces(KNIGHT, PAWN); + + // Find all the squares attacked by slider checkers. We will remove them from + // the king evasions in order to skip known illegal moves, which avoids any + // useless legality checks later on. + while (sliders) + { + Square checksq = pop_lsb(&sliders); + sliderAttacks |= LineBB[checksq][ksq] ^ checksq; + } + + // Generate evasions for king, capture and non capture moves + Bitboard b = pos.attacks_from(ksq) & ~pos.pieces(us) & ~sliderAttacks; + while (b) + *moveList++ = make_move(ksq, pop_lsb(&b)); + + if (more_than_one(pos.checkers())) + return moveList; // Double check, only a king move can save the day + + // Generate blocking evasions or captures of the checking piece + Square checksq = lsb(pos.checkers()); + Bitboard target = between_bb(checksq, ksq) | checksq; + + return us == WHITE ? generate_all(pos, moveList, target) + : generate_all(pos, moveList, target); +} + + +/// generate generates all the legal moves in the given position + +template<> +ExtMove* generate(const Position& pos, ExtMove* moveList) { + + Bitboard pinned = pos.pinned_pieces(pos.side_to_move()); + Square ksq = pos.square(pos.side_to_move()); + ExtMove* cur = moveList; + + moveList = pos.checkers() ? generate(pos, moveList) + : generate(pos, moveList); + while (cur != moveList) + if ( (pinned || from_sq(*cur) == ksq || type_of(*cur) == ENPASSANT) + && !pos.legal(*cur)) + *cur = (--moveList)->move; + else + ++cur; + + return moveList; +} diff --git a/src/movegen.h b/src/movegen.h new file mode 100644 index 0000000..2721f15 --- /dev/null +++ b/src/movegen.h @@ -0,0 +1,70 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef MOVEGEN_H_INCLUDED +#define MOVEGEN_H_INCLUDED + +#include "types.h" + +class Position; + +enum GenType { + CAPTURES, + QUIETS, + QUIET_CHECKS, + EVASIONS, + NON_EVASIONS, + LEGAL +}; + +struct ExtMove { + Move move; + Value value; + + operator Move() const { return move; } + void operator=(Move m) { move = m; } +}; + +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 { + for (const auto& m : *this) if (m == move) return true; + return false; + } + +private: + ExtMove moveList[MAX_MOVES], *last; +}; + +#endif // #ifndef MOVEGEN_H_INCLUDED diff --git a/src/movepick.cpp b/src/movepick.cpp new file mode 100644 index 0000000..c187c3a --- /dev/null +++ b/src/movepick.cpp @@ -0,0 +1,350 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "movepick.h" +#include "thread.h" + +namespace { + + enum Stages { + MAIN_SEARCH, CAPTURES_INIT, GOOD_CAPTURES, KILLERS, COUNTERMOVE, QUIET_INIT, QUIET, BAD_CAPTURES, + EVASION, EVASIONS_INIT, ALL_EVASIONS, + PROBCUT, PROBCUT_INIT, PROBCUT_CAPTURES, + QSEARCH_WITH_CHECKS, QCAPTURES_1_INIT, QCAPTURES_1, QCHECKS, + QSEARCH_NO_CHECKS, QCAPTURES_2_INIT, QCAPTURES_2, + QSEARCH_RECAPTURES, QRECAPTURES + }; + + // Our insertion sort, which is guaranteed to be stable, as it should be + void insertion_sort(ExtMove* begin, ExtMove* end) + { + ExtMove tmp, *p, *q; + + for (p = begin + 1; p < end; ++p) + { + tmp = *p; + for (q = p; q != begin && *(q-1) < tmp; --q) + *q = *(q-1); + *q = tmp; + } + } + + // pick_best() finds the best move in the range (begin, end) and moves it to + // the front. It's faster than sorting all the moves in advance when there + // are few moves, e.g., the possible captures. + Move pick_best(ExtMove* begin, ExtMove* end) + { + std::swap(*begin, *std::max_element(begin, end)); + return *begin; + } + +} // 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::MovePicker(const Position& p, Move ttm, Depth d, Search::Stack* s) + : pos(p), ss(s), depth(d) { + + assert(d > DEPTH_ZERO); + + Square prevSq = to_sq((ss-1)->currentMove); + countermove = pos.this_thread()->counterMoves[pos.piece_on(prevSq)][prevSq]; + + stage = pos.checkers() ? EVASION : MAIN_SEARCH; + ttMove = ttm && pos.pseudo_legal(ttm) ? ttm : MOVE_NONE; + stage += (ttMove == MOVE_NONE); +} + +MovePicker::MovePicker(const Position& p, Move ttm, Depth d, Square s) + : pos(p) { + + assert(d <= DEPTH_ZERO); + + if (pos.checkers()) + stage = EVASION; + + else if (d > DEPTH_QS_NO_CHECKS) + stage = QSEARCH_WITH_CHECKS; + + else if (d > DEPTH_QS_RECAPTURES) + stage = QSEARCH_NO_CHECKS; + + else + { + stage = QSEARCH_RECAPTURES; + recaptureSquare = s; + return; + } + + ttMove = ttm && pos.pseudo_legal(ttm) ? ttm : MOVE_NONE; + stage += (ttMove == MOVE_NONE); +} + +MovePicker::MovePicker(const Position& p, Move ttm, Value th) + : pos(p), threshold(th) { + + assert(!pos.checkers()); + + stage = PROBCUT; + + // In ProbCut we generate captures with SEE higher than the given threshold + ttMove = ttm + && pos.pseudo_legal(ttm) + && pos.capture(ttm) + && pos.see_ge(ttm, threshold + 1)? ttm : MOVE_NONE; + + stage += (ttMove == MOVE_NONE); +} + + +/// score() assigns a numerical value to each move in a move list. The moves with +/// highest values will be picked first. +template<> +void MovePicker::score() { + // Winning and equal captures in the main search are ordered by MVV, preferring + // captures near our home rank. Surprisingly, this appears to perform slightly + // better than SEE-based move ordering: exchanging big pieces before capturing + // a hanging piece probably helps to reduce the subtree size. + // In the main search we want to push captures with negative SEE values to the + // badCaptures[] array, but instead of doing it now we delay until the move + // has been picked up, saving some SEE calls in case we get a cutoff. + for (auto& m : *this) + m.value = PieceValue[MG][pos.piece_on(to_sq(m))] + - Value(200 * relative_rank(pos.side_to_move(), to_sq(m))); +} + +template<> +void MovePicker::score() { + + const HistoryStats& history = pos.this_thread()->history; + const FromToStats& fromTo = pos.this_thread()->fromTo; + + const CounterMoveStats* cm = (ss-1)->counterMoves; + const CounterMoveStats* fm = (ss-2)->counterMoves; + const CounterMoveStats* f2 = (ss-4)->counterMoves; + + Color c = pos.side_to_move(); + + for (auto& m : *this) + m.value = history[pos.moved_piece(m)][to_sq(m)] + + (cm ? (*cm)[pos.moved_piece(m)][to_sq(m)] : VALUE_ZERO) + + (fm ? (*fm)[pos.moved_piece(m)][to_sq(m)] : VALUE_ZERO) + + (f2 ? (*f2)[pos.moved_piece(m)][to_sq(m)] : VALUE_ZERO) + + fromTo.get(c, m); +} + +template<> +void MovePicker::score() { + // Try captures ordered by MVV/LVA, then non-captures ordered by history value + const HistoryStats& history = pos.this_thread()->history; + const FromToStats& fromTo = pos.this_thread()->fromTo; + Color c = pos.side_to_move(); + + for (auto& m : *this) + if (pos.capture(m)) + m.value = PieceValue[MG][pos.piece_on(to_sq(m))] + - Value(type_of(pos.moved_piece(m))) + HistoryStats::Max; + else + m.value = history[pos.moved_piece(m)][to_sq(m)] + fromTo.get(c, m); +} + + +/// 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. It picks the move with the biggest value from a list of generated moves +/// taking care not to return the ttMove if it has already been searched. + +Move MovePicker::next_move() { + + Move move; + + switch (stage) { + + case MAIN_SEARCH: case EVASION: case QSEARCH_WITH_CHECKS: + case QSEARCH_NO_CHECKS: case PROBCUT: + ++stage; + return ttMove; + + case CAPTURES_INIT: + endBadCaptures = cur = moves; + endMoves = generate(pos, cur); + score(); + ++stage; + + case GOOD_CAPTURES: + while (cur < endMoves) + { + move = pick_best(cur++, endMoves); + if (move != ttMove) + { + if (pos.see_ge(move, VALUE_ZERO)) + return move; + + // Losing capture, move it to the beginning of the array + *endBadCaptures++ = move; + } + } + + ++stage; + move = ss->killers[0]; // First killer move + if ( move != MOVE_NONE + && move != ttMove + && pos.pseudo_legal(move) + && !pos.capture(move)) + return move; + + case KILLERS: + ++stage; + move = ss->killers[1]; // Second killer move + if ( move != MOVE_NONE + && move != ttMove + && pos.pseudo_legal(move) + && !pos.capture(move)) + return move; + + case COUNTERMOVE: + ++stage; + move = countermove; + if ( move != MOVE_NONE + && move != ttMove + && move != ss->killers[0] + && move != ss->killers[1] + && pos.pseudo_legal(move) + && !pos.capture(move)) + return move; + + case QUIET_INIT: + cur = endBadCaptures; + endMoves = generate(pos, cur); + score(); + if (depth < 3 * ONE_PLY) + { + ExtMove* goodQuiet = std::partition(cur, endMoves, [](const ExtMove& m) + { return m.value > VALUE_ZERO; }); + insertion_sort(cur, goodQuiet); + } else + insertion_sort(cur, endMoves); + ++stage; + + case QUIET: + while (cur < endMoves) + { + move = *cur++; + if ( move != ttMove + && move != ss->killers[0] + && move != ss->killers[1] + && move != countermove) + return move; + } + ++stage; + cur = moves; // Point to beginning of bad captures + + case BAD_CAPTURES: + if (cur < endBadCaptures) + return *cur++; + break; + + case EVASIONS_INIT: + cur = moves; + endMoves = generate(pos, cur); + score(); + ++stage; + + case ALL_EVASIONS: + while (cur < endMoves) + { + move = pick_best(cur++, endMoves); + if (move != ttMove) + return move; + } + break; + + case PROBCUT_INIT: + cur = moves; + endMoves = generate(pos, cur); + score(); + ++stage; + + case PROBCUT_CAPTURES: + while (cur < endMoves) + { + move = pick_best(cur++, endMoves); + if ( move != ttMove + && pos.see_ge(move, threshold + 1)) + return move; + } + break; + + case QCAPTURES_1_INIT: case QCAPTURES_2_INIT: + cur = moves; + endMoves = generate(pos, cur); + score(); + ++stage; + + case QCAPTURES_1: case QCAPTURES_2: + while (cur < endMoves) + { + move = pick_best(cur++, endMoves); + if (move != ttMove) + return move; + } + if (stage == QCAPTURES_2) + break; + cur = moves; + endMoves = generate(pos, cur); + ++stage; + + case QCHECKS: + while (cur < endMoves) + { + move = cur++->move; + if (move != ttMove) + return move; + } + break; + + case QSEARCH_RECAPTURES: + cur = moves; + endMoves = generate(pos, cur); + score(); + ++stage; + + case QRECAPTURES: + while (cur < endMoves) + { + move = pick_best(cur++, endMoves); + if (to_sq(move) == recaptureSquare) + return move; + } + break; + + default: + assert(false); + } + + return MOVE_NONE; +} diff --git a/src/movepick.h b/src/movepick.h new file mode 100644 index 0000000..6fbd8be --- /dev/null +++ b/src/movepick.h @@ -0,0 +1,123 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef MOVEPICK_H_INCLUDED +#define MOVEPICK_H_INCLUDED + +#include // For std::max +#include // For std::memset + +#include "movegen.h" +#include "position.h" +#include "types.h" + + +/// The Stats struct stores moves statistics. According to the template parameter +/// the class can store History and Countermoves. History records how often +/// different moves have been successful or unsuccessful during the current search +/// and is used for reduction and move ordering decisions. +/// Countermoves store the move that refute a previous one. Entries are stored +/// using only the moving piece and destination square, hence two moves with +/// different origin but same destination and piece will be considered identical. +template +struct Stats { + + static const Value Max = Value(1 << 28); + + const T* operator[](Piece pc) const { return table[pc]; } + T* operator[](Piece pc) { return table[pc]; } + void clear() { std::memset(table, 0, sizeof(table)); } + void update(Piece pc, Square to, Move m) { table[pc][to] = m; } + void update(Piece pc, Square to, Value v) { + + if (abs(int(v)) >= 324) + return; + + table[pc][to] -= table[pc][to] * abs(int(v)) / (CM ? 936 : 324); + table[pc][to] += int(v) * 32; + } + +private: + T table[PIECE_NB][SQUARE_NB]; +}; + +typedef Stats MoveStats; +typedef Stats HistoryStats; +typedef Stats CounterMoveStats; +typedef Stats CounterMoveHistoryStats; + +struct FromToStats { + + Value get(Color c, Move m) const { return table[c][from_sq(m)][to_sq(m)]; } + void clear() { std::memset(table, 0, sizeof(table)); } + void update(Color c, Move m, Value v) { + + if (abs(int(v)) >= 324) + return; + + Square from = from_sq(m); + Square to = to_sq(m); + + table[c][from][to] -= table[c][from][to] * abs(int(v)) / 324; + table[c][from][to] += int(v) * 32; + } + +private: + Value table[COLOR_NB][SQUARE_NB][SQUARE_NB]; +}; + + +/// 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. +namespace Search { struct Stack; } + +class MovePicker { +public: + MovePicker(const MovePicker&) = delete; + MovePicker& operator=(const MovePicker&) = delete; + + MovePicker(const Position&, Move, Value); + MovePicker(const Position&, Move, Depth, Square); + MovePicker(const Position&, Move, Depth, Search::Stack*); + + Move next_move(); + +private: + template void score(); + ExtMove* begin() { return cur; } + ExtMove* end() { return endMoves; } + + const Position& pos; + const Search::Stack* ss; + Move countermove; + Depth depth; + Move ttMove; + Square recaptureSquare; + Value threshold; + int stage; + ExtMove *cur, *endMoves, *endBadCaptures; + ExtMove moves[MAX_MOVES]; +}; + +#endif // #ifndef MOVEPICK_H_INCLUDED diff --git a/src/pawns.cpp b/src/pawns.cpp new file mode 100644 index 0000000..80f5e49 --- /dev/null +++ b/src/pawns.cpp @@ -0,0 +1,291 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include + +#include "bitboard.h" +#include "pawns.h" +#include "position.h" +#include "thread.h" + +namespace { + + #define V Value + #define S(mg, eg) make_score(mg, eg) + + // Isolated pawn penalty by opposed flag + const Score Isolated[2] = { S(45, 40), S(30, 27) }; + + // Backward pawn penalty by opposed flag + const Score Backward[2] = { S(56, 33), S(41, 19) }; + + // Unsupported pawn penalty for pawns which are neither isolated or backward + const Score Unsupported = S(17, 8); + + // Connected pawn bonus by opposed, phalanx, twice supported and rank + Score Connected[2][2][2][RANK_NB]; + + // Doubled pawn penalty + const Score Doubled = S(18,38); + + // Lever bonus by rank + const Score Lever[RANK_NB] = { + S( 0, 0), S( 0, 0), S(0, 0), S(0, 0), + S(17, 16), S(33, 32), S(0, 0), S(0, 0) + }; + + // Weakness of our pawn shelter in front of the king by [distance from edge][rank] + const Value ShelterWeakness[][RANK_NB] = { + { V( 97), V(21), V(26), V(51), V(87), V( 89), V( 99) }, + { V(120), V( 0), V(28), V(76), V(88), V(103), V(104) }, + { V(101), V( 7), V(54), V(78), V(77), V( 92), V(101) }, + { V( 80), V(11), V(44), V(68), V(87), V( 90), V(119) } + }; + + // Danger of enemy pawns moving toward our king by [type][distance from edge][rank] + const Value StormDanger[][4][RANK_NB] = { + { { V( 0), V( 67), V( 134), V(38), V(32) }, + { V( 0), V( 57), V( 139), V(37), V(22) }, + { V( 0), V( 43), V( 115), V(43), V(27) }, + { V( 0), V( 68), V( 124), V(57), V(32) } }, + { { V(20), V( 43), V( 100), V(56), V(20) }, + { V(23), V( 20), V( 98), V(40), V(15) }, + { V(23), V( 39), V( 103), V(36), V(18) }, + { V(28), V( 19), V( 108), V(42), V(26) } }, + { { V( 0), V( 0), V( 75), V(14), V( 2) }, + { V( 0), V( 0), V( 150), V(30), V( 4) }, + { V( 0), V( 0), V( 160), V(22), V( 5) }, + { V( 0), V( 0), V( 166), V(24), V(13) } }, + { { V( 0), V(-283), V(-281), V(57), V(31) }, + { V( 0), V( 58), V( 141), V(39), V(18) }, + { V( 0), V( 65), V( 142), V(48), V(32) }, + { V( 0), V( 60), V( 126), V(51), V(19) } } + }; + + // Max bonus for king safety. Corresponds to start position with all the pawns + // in front of the king and no enemy pawn on the horizon. + const Value MaxSafetyBonus = V(258); + + #undef S + #undef V + + template + Score evaluate(const Position& pos, Pawns::Entry* e) { + + const Color Them = (Us == WHITE ? BLACK : WHITE); + const Square Up = (Us == WHITE ? NORTH : SOUTH); + const Square Right = (Us == WHITE ? NORTH_EAST : SOUTH_WEST); + const Square Left = (Us == WHITE ? NORTH_WEST : SOUTH_EAST); + + Bitboard b, neighbours, stoppers, doubled, supported, phalanx; + Square s; + bool opposed, lever, connected, backward; + Score score = SCORE_ZERO; + const Square* pl = pos.squares(Us); + const Bitboard* pawnAttacksBB = StepAttacksBB[make_piece(Us, PAWN)]; + + Bitboard ourPawns = pos.pieces(Us , PAWN); + Bitboard theirPawns = pos.pieces(Them, PAWN); + + e->passedPawns[Us] = e->pawnAttacksSpan[Us] = 0; + e->semiopenFiles[Us] = 0xFF; + e->kingSquares[Us] = SQ_NONE; + e->pawnAttacks[Us] = shift(ourPawns) | shift(ourPawns); + e->pawnsOnSquares[Us][BLACK] = popcount(ourPawns & DarkSquares); + e->pawnsOnSquares[Us][WHITE] = pos.count(Us) - e->pawnsOnSquares[Us][BLACK]; + + // Loop through all pawns of the current color and score each pawn + while ((s = *pl++) != SQ_NONE) + { + assert(pos.piece_on(s) == make_piece(Us, PAWN)); + + File f = file_of(s); + + e->semiopenFiles[Us] &= ~(1 << f); + e->pawnAttacksSpan[Us] |= pawn_attack_span(Us, s); + + // Flag the pawn + opposed = theirPawns & forward_bb(Us, s); + stoppers = theirPawns & passed_pawn_mask(Us, s); + lever = theirPawns & pawnAttacksBB[s]; + doubled = ourPawns & (s + Up); + neighbours = ourPawns & adjacent_files_bb(f); + phalanx = neighbours & rank_bb(s); + supported = neighbours & rank_bb(s - Up); + connected = supported | phalanx; + + // A pawn is backward when it is behind all pawns of the same color on the + // adjacent files and cannot be safely advanced. + if (!neighbours || lever || relative_rank(Us, s) >= RANK_5) + backward = false; + else + { + // Find the backmost rank with neighbours or stoppers + b = rank_bb(backmost_sq(Us, neighbours | stoppers)); + + // The pawn is backward when it cannot safely progress to that rank: + // either there is a stopper in the way on this rank, or there is a + // stopper on adjacent file which controls the way to that rank. + backward = (b | shift(b & adjacent_files_bb(f))) & stoppers; + + assert(!backward || !(pawn_attack_span(Them, s + Up) & neighbours)); + } + + // Passed pawns will be properly scored in evaluation because we need + // full attack info to evaluate them. + if (!stoppers && !(ourPawns & forward_bb(Us, s))) + e->passedPawns[Us] |= s; + + // Score this pawn + if (!neighbours) + score -= Isolated[opposed]; + + else if (backward) + score -= Backward[opposed]; + + else if (!supported) + score -= Unsupported; + + if (connected) + score += Connected[opposed][!!phalanx][more_than_one(supported)][relative_rank(Us, s)]; + + if (doubled) + score -= Doubled; + + if (lever) + score += Lever[relative_rank(Us, s)]; + } + + return score; + } + +} // namespace + +namespace Pawns { + +/// Pawns::init() initializes some tables needed by evaluation. Instead of using +/// hard-coded tables, when makes sense, we prefer to calculate them with a formula +/// to reduce independent parameters and to allow easier tuning and better insight. + +void init() { + + static const int Seed[RANK_NB] = { 0, 8, 19, 13, 71, 94, 169, 324 }; + + for (int opposed = 0; opposed <= 1; ++opposed) + for (int phalanx = 0; phalanx <= 1; ++phalanx) + for (int apex = 0; apex <= 1; ++apex) + for (Rank r = RANK_2; r < RANK_8; ++r) + { + int v = (Seed[r] + (phalanx ? (Seed[r + 1] - Seed[r]) / 2 : 0)) >> opposed; + v += (apex ? v / 2 : 0); + Connected[opposed][phalanx][apex][r] = make_score(v, v * 5 / 8); + } +} + + +/// Pawns::probe() looks up the current position's pawns configuration in +/// the pawns hash table. It returns a pointer to the Entry if the position +/// is found. Otherwise a new Entry is computed and stored there, so we don't +/// have to recompute all when the same pawns configuration occurs again. + +Entry* probe(const Position& pos) { + + Key key = pos.pawn_key(); + Entry* e = pos.this_thread()->pawnsTable[key]; + + if (e->key == key) + return e; + + e->key = key; + e->score = evaluate(pos, e) - evaluate(pos, e); + e->asymmetry = popcount(e->semiopenFiles[WHITE] ^ e->semiopenFiles[BLACK]); + e->openFiles = popcount(e->semiopenFiles[WHITE] & e->semiopenFiles[BLACK]); + return e; +} + + +/// Entry::shelter_storm() calculates shelter and storm penalties for the file +/// the king is on, as well as the two adjacent files. + +template +Value Entry::shelter_storm(const Position& pos, Square ksq) { + + const Color Them = (Us == WHITE ? BLACK : WHITE); + + enum { NoFriendlyPawn, Unblocked, BlockedByPawn, BlockedByKing }; + + Bitboard b = pos.pieces(PAWN) & (in_front_bb(Us, rank_of(ksq)) | rank_bb(ksq)); + Bitboard ourPawns = b & pos.pieces(Us); + Bitboard theirPawns = b & pos.pieces(Them); + Value safety = MaxSafetyBonus; + File center = std::max(FILE_B, std::min(FILE_G, file_of(ksq))); + + for (File f = center - File(1); f <= center + File(1); ++f) + { + b = ourPawns & file_bb(f); + Rank rkUs = b ? relative_rank(Us, backmost_sq(Us, b)) : RANK_1; + + b = theirPawns & file_bb(f); + Rank rkThem = b ? relative_rank(Us, frontmost_sq(Them, b)) : RANK_1; + + safety -= ShelterWeakness[std::min(f, FILE_H - f)][rkUs] + + StormDanger + [f == file_of(ksq) && rkThem == relative_rank(Us, ksq) + 1 ? BlockedByKing : + rkUs == RANK_1 ? NoFriendlyPawn : + rkThem == rkUs + 1 ? BlockedByPawn : Unblocked] + [std::min(f, FILE_H - f)][rkThem]; + } + + return safety; +} + + +/// 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) { + + kingSquares[Us] = ksq; + castlingRights[Us] = pos.can_castle(Us); + int minKingPawnDistance = 0; + + Bitboard pawns = pos.pieces(Us, PAWN); + if (pawns) + while (!(DistanceRingBB[ksq][minKingPawnDistance++] & pawns)) {} + + Value bonus = shelter_storm(pos, ksq); + + // If we can castle use the bonus after the castling if it is bigger + if (pos.can_castle(MakeCastling::right)) + bonus = std::max(bonus, shelter_storm(pos, relative_square(Us, SQ_G1))); + + if (pos.can_castle(MakeCastling::right)) + bonus = std::max(bonus, shelter_storm(pos, relative_square(Us, SQ_C1))); + + return make_score(bonus, -16 * minKingPawnDistance); +} + +// Explicit template instantiation +template Score Entry::do_king_safety(const Position& pos, Square ksq); +template Score Entry::do_king_safety(const Position& pos, Square ksq); + +} // namespace Pawns diff --git a/src/pawns.h b/src/pawns.h new file mode 100644 index 0000000..e26ae67 --- /dev/null +++ b/src/pawns.h @@ -0,0 +1,88 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef PAWNS_H_INCLUDED +#define PAWNS_H_INCLUDED + +#include "misc.h" +#include "position.h" +#include "types.h" + +namespace Pawns { + +/// Pawns::Entry contains various information about a pawn structure. A lookup +/// to the pawn hash table (performed by calling the probe function) returns a +/// pointer to an Entry object. + +struct Entry { + + Score pawns_score() const { return score; } + 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 pawn_asymmetry() const { return asymmetry; } + int open_files() const { return openFiles; } + + int semiopen_file(Color c, File f) const { + return semiopenFiles[c] & (1 << f); + } + + int semiopen_side(Color c, File f, bool leftSide) const { + return semiopenFiles[c] & (leftSide ? (1 << f) - 1 : ~((1 << (f + 1)) - 1)); + } + + int pawns_on_same_color_squares(Color c, Square s) const { + return pawnsOnSquares[c][!!(DarkSquares & s)]; + } + + template + Score king_safety(const Position& pos, Square ksq) { + return kingSquares[Us] == ksq && castlingRights[Us] == pos.can_castle(Us) + ? kingSafety[Us] : (kingSafety[Us] = do_king_safety(pos, ksq)); + } + + template + Score do_king_safety(const Position& pos, Square ksq); + + template + Value shelter_storm(const Position& pos, Square ksq); + + Key key; + Score score; + 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 semiopenFiles[COLOR_NB]; + int pawnsOnSquares[COLOR_NB][COLOR_NB]; // [color][light/dark squares] + int asymmetry; + int openFiles; +}; + +typedef HashTable Table; + +void init(); +Entry* probe(const Position& pos); + +} // namespace Pawns + +#endif // #ifndef PAWNS_H_INCLUDED diff --git a/src/position.cpp b/src/position.cpp new file mode 100644 index 0000000..c09a953 --- /dev/null +++ b/src/position.cpp @@ -0,0 +1,1168 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#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" + +using std::string; + +namespace PSQT { + extern Score psq[PIECE_NB][SQUARE_NB]; +} + +namespace Zobrist { + + Key psq[PIECE_NB][SQUARE_NB]; + Key enpassant[FILE_NB]; + Key castling[CASTLING_RIGHT_NB]; + Key side; +} + +namespace { + +const string PieceToChar(" PNBRQK pnbrqk"); + +// min_attacker() is a helper function used by see() to locate the least +// valuable attacker for the side to move, remove the attacker we just found +// from the bitboards and scan for new X-ray attacks behind it. + +template +PieceType min_attacker(const Bitboard* bb, Square to, Bitboard stmAttackers, + Bitboard& occupied, Bitboard& attackers) { + + Bitboard b = stmAttackers & bb[Pt]; + if (!b) + return min_attacker(bb, to, stmAttackers, occupied, attackers); + + occupied ^= b & ~(b - 1); + + if (Pt == PAWN || Pt == BISHOP || Pt == QUEEN) + attackers |= attacks_bb(to, occupied) & (bb[BISHOP] | bb[QUEEN]); + + if (Pt == ROOK || Pt == QUEEN) + attackers |= attacks_bb(to, occupied) & (bb[ROOK] | bb[QUEEN]); + + attackers &= occupied; // After X-ray that may add already processed pieces + return (PieceType)Pt; +} + +template<> +PieceType min_attacker(const Bitboard*, Square, Bitboard, Bitboard&, Bitboard&) { + return KING; // No need to update bitboards: it is the last cycle +} + +} // namespace + + +/// operator<<(Position) returns an ASCII representation of the position + +std::ostream& operator<<(std::ostream& os, const Position& pos) { + + os << "\n +---+---+---+---+---+---+---+---+\n"; + + for (Rank r = RANK_8; r >= RANK_1; --r) + { + for (File f = FILE_A; f <= FILE_H; ++f) + os << " | " << PieceToChar[pos.piece_on(make_square(f, r))]; + + os << " |\n +---+---+---+---+---+---+---+---+\n"; + } + + os << "\nFen: " << pos.fen() << "\nKey: " << std::hex << std::uppercase + << std::setfill('0') << std::setw(16) << pos.key() << std::dec << "\nCheckers: "; + + for (Bitboard b = pos.checkers(); b; ) + os << UCI::square(pop_lsb(&b)) << " "; + + return os; +} + + +/// Position::init() initializes at startup the various arrays used to compute +/// hash keys. + +void Position::init() { + + PRNG rng(1070372); + + for (Piece pc : Pieces) + for (Square s = SQ_A1; s <= SQ_H8; ++s) + Zobrist::psq[pc][s] = rng.rand(); + + for (File f = FILE_A; f <= FILE_H; ++f) + Zobrist::enpassant[f] = rng.rand(); + + for (int cr = NO_CASTLING; cr <= ANY_CASTLING; ++cr) + { + Zobrist::castling[cr] = 0; + Bitboard b = cr; + while (b) + { + Key k = Zobrist::castling[1ULL << pop_lsb(&b)]; + Zobrist::castling[cr] ^= k ? k : rng.rand(); + } + } + + Zobrist::side = rng.rand(); +} + + +/// Position::set() initializes the position object with the given FEN string. +/// This function is not very robust - make sure that input FENs are correct, +/// this is assumed to be the responsibility of the GUI. + +Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Thread* th) { +/* + A FEN string defines a particular position using only the ASCII character set. + + A FEN string contains six fields separated by a space. The fields are: + + 1) Piece placement (from white's perspective). Each rank is described, starting + with rank 8 and ending with rank 1. Within each rank, the contents of each + square are described from file A through file H. Following the Standard + Algebraic Notation (SAN), each piece is identified by a single letter taken + from the standard English names. White pieces are designated using upper-case + letters ("PNBRQK") whilst Black uses lowercase ("pnbrqk"). Blank squares are + noted using digits 1 through 8 (the number of blank squares), and "/" + separates ranks. + + 2) Active color. "w" means white moves next, "b" means black. + + 3) Castling availability. If neither side can castle, this is "-". Otherwise, + this has one or more letters: "K" (White can castle kingside), "Q" (White + can castle queenside), "k" (Black can castle kingside), and/or "q" (Black + can castle queenside). + + 4) En passant target square (in algebraic notation). If there's no en passant + target square, this is "-". If a pawn has just made a 2-square move, this + is the position "behind" the pawn. This is recorded regardless of whether + there is a pawn in position to make an en passant capture. + + 5) Halfmove clock. This is the number of halfmoves since the last pawn advance + or capture. This is used to determine if a draw can be claimed under the + fifty-move rule. + + 6) Fullmove number. The number of the full move. It starts at 1, and is + incremented after Black's move. +*/ + + unsigned char col, row, token; + size_t idx; + Square sq = SQ_A8; + std::istringstream ss(fenStr); + + std::memset(this, 0, sizeof(Position)); + std::memset(si, 0, sizeof(StateInfo)); + std::fill_n(&pieceList[0][0], sizeof(pieceList) / sizeof(Square), SQ_NONE); + st = si; + + ss >> std::noskipws; + + // 1. Piece placement + while ((ss >> token) && !isspace(token)) + { + if (isdigit(token)) + sq += Square(token - '0'); // Advance the given number of files + + else if (token == '/') + sq -= Square(16); + + else if ((idx = PieceToChar.find(token)) != string::npos) + { + put_piece(Piece(idx), sq); + ++sq; + } + } + + // 2. Active color + ss >> token; + sideToMove = (token == 'w' ? WHITE : BLACK); + ss >> token; + + // 3. Castling availability. Compatible with 3 standards: Normal FEN standard, + // Shredder-FEN that uses the letters of the columns on which the rooks began + // the game instead of KQkq and also X-FEN standard that, in case of Chess960, + // if an inner rook is associated with the castling right, the castling tag is + // replaced by the file letter of the involved rook, as for the Shredder-FEN. + while ((ss >> token) && !isspace(token)) + { + Square rsq; + Color c = islower(token) ? BLACK : WHITE; + Piece rook = make_piece(c, ROOK); + + token = char(toupper(token)); + + if (token == 'K') + for (rsq = relative_square(c, SQ_H1); piece_on(rsq) != rook; --rsq) {} + + else if (token == 'Q') + for (rsq = relative_square(c, SQ_A1); piece_on(rsq) != rook; ++rsq) {} + + else if (token >= 'A' && token <= 'H') + rsq = make_square(File(token - 'A'), relative_rank(c, RANK_1)); + + else + continue; + + set_castling_right(c, rsq); + } + + // 4. En passant square. Ignore if no pawn capture is possible + if ( ((ss >> col) && (col >= 'a' && col <= 'h')) + && ((ss >> row) && (row == '3' || row == '6'))) + { + st->epSquare = make_square(File(col - 'a'), Rank(row - '1')); + + if (!(attackers_to(st->epSquare) & pieces(sideToMove, PAWN))) + st->epSquare = SQ_NONE; + } + else + st->epSquare = SQ_NONE; + + // 5-6. Halfmove clock and fullmove number + ss >> std::skipws >> st->rule50 >> gamePly; + + // Convert from fullmove starting from 1 to ply starting from 0, + // handle also common incorrect FEN with fullmove = 0. + gamePly = std::max(2 * (gamePly - 1), 0) + (sideToMove == BLACK); + + chess960 = isChess960; + thisThread = th; + set_state(st); + + assert(pos_is_ok()); + + return *this; +} + + +/// Position::set_castling_right() is a helper function used to set castling +/// rights given the corresponding color and the rook starting square. + +void Position::set_castling_right(Color c, Square rfrom) { + + Square kfrom = square(c); + CastlingSide cs = kfrom < rfrom ? KING_SIDE : QUEEN_SIDE; + CastlingRight cr = (c | cs); + + st->castlingRights |= cr; + castlingRightsMask[kfrom] |= cr; + castlingRightsMask[rfrom] |= cr; + castlingRookSquare[cr] = rfrom; + + Square kto = relative_square(c, cs == KING_SIDE ? SQ_G1 : SQ_C1); + Square rto = relative_square(c, cs == KING_SIDE ? SQ_F1 : SQ_D1); + + for (Square s = std::min(rfrom, rto); s <= std::max(rfrom, rto); ++s) + if (s != kfrom && s != rfrom) + castlingPath[cr] |= s; + + for (Square s = std::min(kfrom, kto); s <= std::max(kfrom, kto); ++s) + if (s != kfrom && s != rfrom) + castlingPath[cr] |= s; +} + + +/// Position::set_check_info() sets king attacks to detect if a move gives check + +void Position::set_check_info(StateInfo* si) const { + + si->blockersForKing[WHITE] = slider_blockers(pieces(BLACK), square(WHITE), si->pinnersForKing[WHITE]); + si->blockersForKing[BLACK] = slider_blockers(pieces(WHITE), square(BLACK), si->pinnersForKing[BLACK]); + + Square ksq = square(~sideToMove); + + si->checkSquares[PAWN] = attacks_from(ksq, ~sideToMove); + si->checkSquares[KNIGHT] = attacks_from(ksq); + si->checkSquares[BISHOP] = attacks_from(ksq); + si->checkSquares[ROOK] = attacks_from(ksq); + si->checkSquares[QUEEN] = si->checkSquares[BISHOP] | si->checkSquares[ROOK]; + si->checkSquares[KING] = 0; +} + + +/// Position::set_state() computes the hash keys of the position, and other +/// data that once computed is updated incrementally as moves are made. +/// The function is only used when a new position is set up, and to verify +/// the correctness of the StateInfo data when running in debug mode. + +void Position::set_state(StateInfo* si) const { + + si->key = si->pawnKey = si->materialKey = 0; + si->nonPawnMaterial[WHITE] = si->nonPawnMaterial[BLACK] = VALUE_ZERO; + si->psq = SCORE_ZERO; + si->checkersBB = attackers_to(square(sideToMove)) & pieces(~sideToMove); + + set_check_info(si); + + for (Bitboard b = pieces(); b; ) + { + Square s = pop_lsb(&b); + Piece pc = piece_on(s); + si->key ^= Zobrist::psq[pc][s]; + si->psq += PSQT::psq[pc][s]; + } + + 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 (Bitboard b = pieces(PAWN); b; ) + { + Square s = pop_lsb(&b); + si->pawnKey ^= Zobrist::psq[piece_on(s)][s]; + } + + for (Piece pc : Pieces) + { + if (type_of(pc) != PAWN && type_of(pc) != KING) + si->nonPawnMaterial[color_of(pc)] += pieceCount[pc] * PieceValue[MG][pc]; + + for (int cnt = 0; cnt < pieceCount[pc]; ++cnt) + si->materialKey ^= Zobrist::psq[pc][cnt]; + } +} + + +/// Position::fen() returns a FEN representation of the position. In case of +/// Chess960 the Shredder-FEN notation is used. This is mainly a debugging function. + +const string Position::fen() const { + + int emptyCnt; + std::ostringstream ss; + + for (Rank r = RANK_8; r >= RANK_1; --r) + { + for (File f = FILE_A; f <= FILE_H; ++f) + { + for (emptyCnt = 0; f <= FILE_H && empty(make_square(f, r)); ++f) + ++emptyCnt; + + if (emptyCnt) + ss << emptyCnt; + + if (f <= FILE_H) + ss << PieceToChar[piece_on(make_square(f, r))]; + } + + if (r > RANK_1) + ss << '/'; + } + + ss << (sideToMove == WHITE ? " w " : " b "); + + if (can_castle(WHITE_OO)) + ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE | KING_SIDE))) : 'K'); + + if (can_castle(WHITE_OOO)) + ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE | QUEEN_SIDE))) : 'Q'); + + if (can_castle(BLACK_OO)) + ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK | KING_SIDE))) : 'k'); + + if (can_castle(BLACK_OOO)) + ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK | QUEEN_SIDE))) : 'q'); + + if (!can_castle(WHITE) && !can_castle(BLACK)) + ss << '-'; + + ss << (ep_square() == SQ_NONE ? " - " : " " + UCI::square(ep_square()) + " ") + << st->rule50 << " " << 1 + (gamePly - (sideToMove == BLACK)) / 2; + + return ss.str(); +} + + +/// Position::game_phase() calculates the game phase interpolating total non-pawn +/// material between endgame and midgame limits. + +Phase Position::game_phase() const { + + Value npm = st->nonPawnMaterial[WHITE] + st->nonPawnMaterial[BLACK]; + + npm = std::max(EndgameLimit, std::min(npm, MidgameLimit)); + + return Phase(((npm - EndgameLimit) * PHASE_MIDGAME) / (MidgameLimit - EndgameLimit)); +} + + +/// Position::slider_blockers() returns a bitboard of all the pieces (both colors) +/// that are blocking attacks on the square 's' from 'sliders'. A piece blocks a +/// slider if removing that piece from the board would result in a position where +/// square 's' is attacked. For example, a king-attack blocking piece can be either +/// a pinned or a discovered check piece, according if its color is the opposite +/// or the same of the color of the slider. + +Bitboard Position::slider_blockers(Bitboard sliders, Square s, Bitboard& pinners) const { + + Bitboard result = 0; + pinners = 0; + + // Snipers are sliders that attack 's' when a piece is removed + Bitboard snipers = ( (PseudoAttacks[ROOK ][s] & pieces(QUEEN, ROOK)) + | (PseudoAttacks[BISHOP][s] & pieces(QUEEN, BISHOP))) & sliders; + + while (snipers) + { + Square sniperSq = pop_lsb(&snipers); + Bitboard b = between_bb(s, sniperSq) & pieces(); + + if (!more_than_one(b)) + { + result |= b; + if (b & pieces(color_of(piece_on(s)))) + pinners |= sniperSq; + } + } + return result; +} + + +/// Position::attackers_to() computes a bitboard of all pieces which attack a +/// given square. Slider attacks use the occupied bitboard to indicate occupancy. + +Bitboard Position::attackers_to(Square s, Bitboard occupied) const { + + return (attacks_from(s, BLACK) & pieces(WHITE, PAWN)) + | (attacks_from(s, WHITE) & pieces(BLACK, PAWN)) + | (attacks_from(s) & pieces(KNIGHT)) + | (attacks_bb(s, occupied) & pieces(ROOK, QUEEN)) + | (attacks_bb(s, occupied) & pieces(BISHOP, QUEEN)) + | (attacks_from(s) & pieces(KING)); +} + + +/// Position::legal() tests whether a pseudo-legal move is legal + +bool Position::legal(Move m) const { + + assert(is_ok(m)); + + Color us = sideToMove; + Square from = from_sq(m); + + assert(color_of(moved_piece(m)) == us); + assert(piece_on(square(us)) == make_piece(us, KING)); + + // En passant captures are a tricky special case. Because they are rather + // uncommon, we do it simply by testing whether the king is attacked after + // the move is made. + if (type_of(m) == ENPASSANT) + { + Square ksq = square(us); + Square to = to_sq(m); + Square capsq = to - pawn_push(us); + Bitboard occupied = (pieces() ^ from ^ capsq) | to; + + assert(to == ep_square()); + assert(moved_piece(m) == make_piece(us, PAWN)); + assert(piece_on(capsq) == make_piece(~us, PAWN)); + assert(piece_on(to) == NO_PIECE); + + return !(attacks_bb< ROOK>(ksq, occupied) & pieces(~us, QUEEN, ROOK)) + && !(attacks_bb(ksq, occupied) & pieces(~us, QUEEN, BISHOP)); + } + + // If the moving piece is a king, check whether the destination + // square is attacked by the opponent. Castling moves are checked + // for legality during move generation. + if (type_of(piece_on(from)) == KING) + return type_of(m) == CASTLING || !(attackers_to(to_sq(m)) & pieces(~us)); + + // A non-king move is legal if and only if it is not pinned or it + // is moving along the ray towards or away from the king. + return !(pinned_pieces(us) & from) + || aligned(from, to_sq(m), square(us)); +} + + +/// Position::pseudo_legal() takes a random move and tests whether the move is +/// pseudo legal. It is used to validate moves from TT that can be corrupted +/// due to SMP concurrent access or hash position key aliasing. + +bool Position::pseudo_legal(const Move m) const { + + Color us = sideToMove; + Square from = from_sq(m); + Square to = to_sq(m); + Piece pc = moved_piece(m); + + // Use a slower but simpler function for uncommon cases + if (type_of(m) != NORMAL) + return MoveList(*this).contains(m); + + // Is not a promotion, so promotion piece must be empty + if (promotion_type(m) - KNIGHT != NO_PIECE_TYPE) + return false; + + // If the 'from' square is not occupied by a piece belonging to the side to + // move, the move is obviously not legal. + if (pc == NO_PIECE || color_of(pc) != us) + return false; + + // The destination square cannot be occupied by a friendly piece + if (pieces(us) & to) + return false; + + // Handle the special case of a pawn move + if (type_of(pc) == PAWN) + { + // We have already handled promotion moves, so destination + // cannot be on the 8th/1st rank. + if (rank_of(to) == relative_rank(us, RANK_8)) + return false; + + if ( !(attacks_from(from, us) & 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 + && (rank_of(from) == relative_rank(us, RANK_2)) + && empty(to) + && empty(to - pawn_push(us)))) + return false; + } + else if (!(attacks_from(pc, from) & to)) + return false; + + // Evasions generator already takes care to avoid some kind of illegal moves + // and legal() relies on this. We therefore have to take care that the same + // kind of moves are filtered out here. + if (checkers()) + { + if (type_of(pc) != KING) + { + // Double check? In this case a king move is required + if (more_than_one(checkers())) + return false; + + // Our move must be a blocking evasion or a capture of the checking piece + if (!((between_bb(lsb(checkers()), square(us)) | checkers()) & to)) + return false; + } + // In case of king moves under check we have to remove king so as to catch + // invalid moves like b1a1 when opposite queen is on c1. + else if (attackers_to(to, pieces() ^ from) & pieces(~us)) + return false; + } + + return true; +} + + +/// Position::gives_check() tests whether a pseudo-legal move gives a check + +bool Position::gives_check(Move m) const { + + assert(is_ok(m)); + assert(color_of(moved_piece(m)) == sideToMove); + + Square from = from_sq(m); + Square to = to_sq(m); + + // Is there a direct check? + if (st->checkSquares[type_of(piece_on(from))] & to) + return true; + + // Is there a discovered check? + if ( (discovered_check_candidates() & from) + && !aligned(from, to, square(~sideToMove))) + return true; + + switch (type_of(m)) + { + case NORMAL: + return false; + + case PROMOTION: + return attacks_bb(Piece(promotion_type(m)), 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 ENPASSANT: + { + Square capsq = make_square(file_of(to), rank_of(from)); + Bitboard b = (pieces() ^ from ^ capsq) | to; + + return (attacks_bb< ROOK>(square(~sideToMove), b) & pieces(sideToMove, QUEEN, ROOK)) + | (attacks_bb(square(~sideToMove), b) & pieces(sideToMove, QUEEN, BISHOP)); + } + case CASTLING: + { + Square kfrom = from; + Square rfrom = to; // Castling is encoded as 'King captures the rook' + Square kto = relative_square(sideToMove, rfrom > kfrom ? SQ_G1 : SQ_C1); + Square rto = relative_square(sideToMove, rfrom > kfrom ? SQ_F1 : SQ_D1); + + return (PseudoAttacks[ROOK][rto] & square(~sideToMove)) + && (attacks_bb(rto, (pieces() ^ kfrom ^ rfrom) | rto | kto) & square(~sideToMove)); + } + default: + assert(false); + return false; + } +} + + +/// Position::do_move() makes a move, and saves all information necessary +/// to a StateInfo object. The move is assumed to be legal. Pseudo-legal +/// moves should be filtered out before this function is called. + +void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { + + assert(is_ok(m)); + assert(&newSt != st); + + ++nodes; + Key k = st->key ^ Zobrist::side; + + // Copy some fields of the old state to our new StateInfo object except the + // ones which are going to be recalculated from scratch anyway and then switch + // our state pointer to point to the new (ready to be updated) state. + std::memcpy(&newSt, st, offsetof(StateInfo, key)); + newSt.previous = st; + st = &newSt; + + // Increment ply counters. In particular, rule50 will be reset to zero later on + // in case of a capture or a pawn move. + ++gamePly; + ++st->rule50; + ++st->pliesFromNull; + + Color us = sideToMove; + Color them = ~us; + Square from = from_sq(m); + Square to = to_sq(m); + Piece pc = piece_on(from); + Piece captured = type_of(m) == ENPASSANT ? make_piece(them, PAWN) : piece_on(to); + + assert(color_of(pc) == us); + assert(captured == NO_PIECE || color_of(captured) == (type_of(m) != CASTLING ? them : us)); + assert(type_of(captured) != KING); + + if (type_of(m) == CASTLING) + { + assert(pc == make_piece(us, KING)); + assert(captured == make_piece(us, ROOK)); + + Square rfrom, rto; + do_castling(us, from, to, rfrom, rto); + + st->psq += PSQT::psq[captured][rto] - PSQT::psq[captured][rfrom]; + k ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto]; + captured = NO_PIECE; + } + + if (captured) + { + Square capsq = to; + + // If the captured piece is a pawn, update pawn hash key, otherwise + // update non-pawn material. + if (type_of(captured) == PAWN) + { + if (type_of(m) == ENPASSANT) + { + capsq -= pawn_push(us); + + assert(pc == make_piece(us, PAWN)); + assert(to == st->epSquare); + assert(relative_rank(us, to) == RANK_6); + assert(piece_on(to) == NO_PIECE); + assert(piece_on(capsq) == make_piece(them, PAWN)); + + board[capsq] = NO_PIECE; // Not done by remove_piece() + } + + st->pawnKey ^= Zobrist::psq[captured][capsq]; + } + else + st->nonPawnMaterial[them] -= PieceValue[MG][captured]; + + // Update board and piece lists + remove_piece(captured, capsq); + + // Update material hash key and prefetch access to materialTable + k ^= Zobrist::psq[captured][capsq]; + st->materialKey ^= Zobrist::psq[captured][pieceCount[captured]]; + prefetch(thisThread->materialTable[st->materialKey]); + + // Update incremental scores + st->psq -= PSQT::psq[captured][capsq]; + + // Reset rule 50 counter + st->rule50 = 0; + } + + // Update hash key + k ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; + + // Reset en passant square + if (st->epSquare != SQ_NONE) + { + k ^= Zobrist::enpassant[file_of(st->epSquare)]; + st->epSquare = SQ_NONE; + } + + // Update castling rights if needed + if (st->castlingRights && (castlingRightsMask[from] | castlingRightsMask[to])) + { + int cr = castlingRightsMask[from] | castlingRightsMask[to]; + k ^= Zobrist::castling[st->castlingRights & cr]; + st->castlingRights &= ~cr; + } + + // Move the piece. The tricky Chess960 castling is handled earlier + if (type_of(m) != CASTLING) + move_piece(pc, from, to); + + // If the moving piece is a pawn do some special extra work + if (type_of(pc) == PAWN) + { + // Set en-passant square if the moved pawn can be captured + if ( (int(to) ^ int(from)) == 16 + && (attacks_from(to - pawn_push(us), us) & pieces(them, PAWN))) + { + st->epSquare = (from + to) / 2; + k ^= Zobrist::enpassant[file_of(st->epSquare)]; + } + + else if (type_of(m) == PROMOTION) + { + Piece promotion = make_piece(us, promotion_type(m)); + + assert(relative_rank(us, to) == RANK_8); + assert(type_of(promotion) >= KNIGHT && type_of(promotion) <= QUEEN); + + remove_piece(pc, to); + put_piece(promotion, to); + + // 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 incremental score + st->psq += PSQT::psq[promotion][to] - PSQT::psq[pc][to]; + + // Update material + st->nonPawnMaterial[us] += PieceValue[MG][promotion]; + } + + // Update pawn hash key and prefetch access to pawnsTable + st->pawnKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; + prefetch(thisThread->pawnsTable[st->pawnKey]); + + // Reset rule 50 draw counter + st->rule50 = 0; + } + + // Update incremental scores + st->psq += PSQT::psq[pc][to] - PSQT::psq[pc][from]; + + // Set capture piece + st->capturedPiece = captured; + + // Update the key with the final value + st->key = k; + + // Calculate checkers bitboard (if move gives check) + st->checkersBB = givesCheck ? attackers_to(square(them)) & pieces(us) : 0; + + sideToMove = ~sideToMove; + + // Update king attacks used for fast check detection + set_check_info(st); + + assert(pos_is_ok()); +} + + +/// Position::undo_move() unmakes a move. When it returns, the position should +/// be restored to exactly the same state as before the move was made. + +void Position::undo_move(Move m) { + + assert(is_ok(m)); + + sideToMove = ~sideToMove; + + Color us = sideToMove; + Square from = from_sq(m); + Square to = to_sq(m); + Piece pc = piece_on(to); + + assert(empty(from) || type_of(m) == CASTLING); + assert(type_of(st->capturedPiece) != KING); + + if (type_of(m) == PROMOTION) + { + assert(relative_rank(us, to) == RANK_8); + assert(type_of(pc) == promotion_type(m)); + assert(type_of(pc) >= KNIGHT && type_of(pc) <= QUEEN); + + remove_piece(pc, to); + pc = make_piece(us, PAWN); + put_piece(pc, to); + } + + if (type_of(m) == CASTLING) + { + Square rfrom, rto; + do_castling(us, from, to, rfrom, rto); + } + else + { + move_piece(pc, to, from); // Put the piece back at the source square + + if (st->capturedPiece) + { + Square capsq = to; + + if (type_of(m) == ENPASSANT) + { + capsq -= pawn_push(us); + + assert(type_of(pc) == PAWN); + assert(to == st->previous->epSquare); + assert(relative_rank(us, to) == RANK_6); + assert(piece_on(capsq) == NO_PIECE); + assert(st->capturedPiece == make_piece(~us, PAWN)); + } + + put_piece(st->capturedPiece, capsq); // Restore the captured piece + } + } + + // Finally point our state pointer back to the previous state + st = st->previous; + --gamePly; + + assert(pos_is_ok()); +} + + +/// Position::do_castling() is a helper used to do/undo a castling move. This +/// is a bit tricky in Chess960 where from/to squares can overlap. +template +void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto) { + + bool kingSide = to > from; + rfrom = to; // Castling is encoded as "king captures friendly rook" + rto = relative_square(us, kingSide ? SQ_F1 : SQ_D1); + to = relative_square(us, kingSide ? SQ_G1 : SQ_C1); + + // Remove both pieces first since squares could overlap in Chess960 + remove_piece(make_piece(us, KING), Do ? from : to); + remove_piece(make_piece(us, ROOK), Do ? rfrom : rto); + board[Do ? from : to] = board[Do ? rfrom : rto] = NO_PIECE; // Since remove_piece doesn't do it for us + put_piece(make_piece(us, KING), Do ? to : from); + put_piece(make_piece(us, ROOK), Do ? rto : rfrom); +} + + +/// Position::do(undo)_null_move() is used to do(undo) a "null move": It flips +/// the side to move without executing any move on the board. + +void Position::do_null_move(StateInfo& newSt) { + + assert(!checkers()); + assert(&newSt != st); + + std::memcpy(&newSt, st, sizeof(StateInfo)); + newSt.previous = st; + st = &newSt; + + if (st->epSquare != SQ_NONE) + { + st->key ^= Zobrist::enpassant[file_of(st->epSquare)]; + st->epSquare = SQ_NONE; + } + + st->key ^= Zobrist::side; + prefetch(TT.first_entry(st->key)); + + ++st->rule50; + st->pliesFromNull = 0; + + sideToMove = ~sideToMove; + + set_check_info(st); + + assert(pos_is_ok()); +} + +void Position::undo_null_move() { + + assert(!checkers()); + + st = st->previous; + sideToMove = ~sideToMove; +} + + +/// Position::key_after() computes the new hash key after the given move. Needed +/// for speculative prefetch. It doesn't recognize special moves like castling, +/// en-passant and promotions. + +Key Position::key_after(Move m) const { + + Square from = from_sq(m); + Square to = to_sq(m); + Piece pc = piece_on(from); + Piece captured = piece_on(to); + Key k = st->key ^ Zobrist::side; + + if (captured) + k ^= Zobrist::psq[captured][to]; + + return k ^ Zobrist::psq[pc][to] ^ Zobrist::psq[pc][from]; +} + + +/// Position::see_ge (Static Exchange Evaluation Greater or Equal) tests if the +/// SEE value of move is greater or equal to the given value. We'll use an +/// algorithm similar to alpha-beta pruning with a null window. + +bool Position::see_ge(Move m, Value v) const { + + assert(is_ok(m)); + + // Castling moves are implemented as king capturing the rook so cannot be + // handled correctly. Simply assume the SEE value is VALUE_ZERO that is always + // correct unless in the rare case the rook ends up under attack. + if (type_of(m) == CASTLING) + return VALUE_ZERO >= v; + + Square from = from_sq(m), to = to_sq(m); + PieceType nextVictim = type_of(piece_on(from)); + Color stm = ~color_of(piece_on(from)); // First consider opponent's move + Value balance; // Values of the pieces taken by us minus opponent's ones + Bitboard occupied, stmAttackers; + + if (type_of(m) == ENPASSANT) + { + occupied = SquareBB[to - pawn_push(~stm)]; // Remove the captured pawn + balance = PieceValue[MG][PAWN]; + } + else + { + balance = PieceValue[MG][piece_on(to)]; + occupied = 0; + } + + if (balance < v) + return false; + + if (nextVictim == KING) + return true; + + balance -= PieceValue[MG][nextVictim]; + + if (balance >= v) + return true; + + bool relativeStm = true; // True if the opponent is to move + occupied ^= pieces() ^ from ^ to; + + // Find all attackers to the destination square, with the moving piece removed, + // but possibly an X-ray attacker added behind it. + Bitboard attackers = attackers_to(to, occupied) & occupied; + + while (true) + { + stmAttackers = attackers & pieces(stm); + + // Don't allow pinned pieces to attack pieces except the king as long all + // pinners are on their original square. + if (!(st->pinnersForKing[stm] & ~occupied)) + stmAttackers &= ~st->blockersForKing[stm]; + + if (!stmAttackers) + return relativeStm; + + // Locate and remove the next least valuable attacker + nextVictim = min_attacker(byTypeBB, to, stmAttackers, occupied, attackers); + + if (nextVictim == KING) + return relativeStm == bool(attackers & pieces(~stm)); + + balance += relativeStm ? PieceValue[MG][nextVictim] + : -PieceValue[MG][nextVictim]; + + relativeStm = !relativeStm; + + if (relativeStm == (balance >= v)) + return relativeStm; + + stm = ~stm; + } +} + + +/// Position::is_draw() tests whether the position is drawn by 50-move rule +/// or by repetition. It does not detect stalemates. + +bool Position::is_draw() const { + + if (st->rule50 > 99 && (!checkers() || MoveList(*this).size())) + return true; + + StateInfo* stp = st; + for (int i = 2, e = std::min(st->rule50, st->pliesFromNull); i <= e; i += 2) + { + stp = stp->previous->previous; + + if (stp->key == st->key) + return true; // Draw at first repetition + } + + return false; +} + + +/// Position::flip() flips position with the white and black sides reversed. This +/// is only useful for debugging e.g. for finding evaluation symmetry bugs. + +void Position::flip() { + + string f, token; + std::stringstream ss(fen()); + + for (Rank r = RANK_8; r >= RANK_1; --r) // Piece placement + { + std::getline(ss, token, r > RANK_1 ? '/' : ' '); + f.insert(0, token + (f.empty() ? " " : "/")); + } + + ss >> token; // Active color + f += (token == "w" ? "B " : "W "); // Will be lowercased later + + ss >> token; // Castling availability + f += token + " "; + + std::transform(f.begin(), f.end(), f.begin(), + [](char c) { return char(islower(c) ? toupper(c) : tolower(c)); }); + + ss >> token; // En passant square + f += (token == "-" ? token : token.replace(1, 1, token[1] == '3' ? "6" : "3")); + + std::getline(ss, token); // Half and full moves + f += token; + + set(f, is_chess960(), st, this_thread()); + + assert(pos_is_ok()); +} + + +/// Position::pos_is_ok() performs some consistency checks for the position object. +/// This is meant to be helpful when debugging. + +bool Position::pos_is_ok(int* failedStep) const { + + const bool Fast = true; // Quick (default) or full check? + + enum { Default, King, Bitboards, State, Lists, Castling }; + + for (int step = Default; step <= (Fast ? Default : Castling); step++) + { + if (failedStep) + *failedStep = step; + + if (step == Default) + if ( (sideToMove != WHITE && sideToMove != BLACK) + || piece_on(square(WHITE)) != W_KING + || piece_on(square(BLACK)) != B_KING + || ( ep_square() != SQ_NONE + && relative_rank(sideToMove, ep_square()) != RANK_6)) + return false; + + if (step == King) + if ( std::count(board, board + SQUARE_NB, W_KING) != 1 + || std::count(board, board + SQUARE_NB, B_KING) != 1 + || attackers_to(square(~sideToMove)) & pieces(sideToMove)) + return false; + + if (step == Bitboards) + { + if ( (pieces(WHITE) & pieces(BLACK)) + ||(pieces(WHITE) | pieces(BLACK)) != pieces()) + return false; + + for (PieceType p1 = PAWN; p1 <= KING; ++p1) + for (PieceType p2 = PAWN; p2 <= KING; ++p2) + if (p1 != p2 && (pieces(p1) & pieces(p2))) + return false; + } + + if (step == State) + { + StateInfo si = *st; + set_state(&si); + if (std::memcmp(&si, st, sizeof(StateInfo))) + return false; + } + + if (step == Lists) + for (Piece pc : Pieces) + { + if (pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc)))) + return false; + + for (int i = 0; i < pieceCount[pc]; ++i) + if (board[pieceList[pc][i]] != pc || index[pieceList[pc][i]] != i) + return false; + } + + if (step == Castling) + for (Color c = WHITE; c <= BLACK; ++c) + for (CastlingSide s = KING_SIDE; s <= QUEEN_SIDE; s = CastlingSide(s + 1)) + { + if (!can_castle(c | s)) + continue; + + if ( piece_on(castlingRookSquare[c | s]) != make_piece(c, ROOK) + || castlingRightsMask[castlingRookSquare[c | s]] != (c | s) + ||(castlingRightsMask[square(c)] & (c | s)) != (c | s)) + return false; + } + } + + return true; +} diff --git a/src/position.h b/src/position.h new file mode 100644 index 0000000..9aa4c44 --- /dev/null +++ b/src/position.h @@ -0,0 +1,414 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef POSITION_H_INCLUDED +#define POSITION_H_INCLUDED + +#include +#include +#include // For std::unique_ptr +#include + +#include "bitboard.h" +#include "types.h" + + +/// StateInfo struct stores information needed to restore a Position object to +/// its previous state when we retract a move. Whenever a move is made on the +/// board (by calling Position::do_move), a StateInfo object must be passed. + +struct StateInfo { + + // Copied when making a move + Key pawnKey; + Key materialKey; + Value nonPawnMaterial[COLOR_NB]; + int castlingRights; + int rule50; + int pliesFromNull; + Score psq; + Square epSquare; + + // Not copied when making a move (will be recomputed anyhow) + Key key; + Bitboard checkersBB; + Piece capturedPiece; + StateInfo* previous; + Bitboard blockersForKing[COLOR_NB]; + Bitboard pinnersForKing[COLOR_NB]; + Bitboard checkSquares[PIECE_TYPE_NB]; +}; + +// In a std::deque references to elements are unaffected upon 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 std::string& fenStr, bool isChess960, StateInfo* si, Thread* th); + const std::string fen() const; + + // Position representation + Bitboard pieces() const; + Bitboard pieces(PieceType pt) const; + Bitboard pieces(PieceType pt1, PieceType pt2) const; + Bitboard pieces(Color c) const; + Bitboard pieces(Color c, PieceType pt) const; + Bitboard pieces(Color c, PieceType pt1, PieceType pt2) const; + Piece piece_on(Square s) const; + Square ep_square() const; + bool empty(Square s) const; + template int count(Color c) const; + template const Square* squares(Color c) const; + template Square square(Color c) const; + + // Castling + int can_castle(Color c) const; + int can_castle(CastlingRight cr) const; + bool castling_impeded(CastlingRight cr) const; + Square castling_rook_square(CastlingRight cr) const; + + // Checking + Bitboard checkers() const; + Bitboard discovered_check_candidates() const; + Bitboard pinned_pieces(Color c) const; + Bitboard check_squares(PieceType pt) const; + + // Attacks to/from a given square + Bitboard attackers_to(Square s) const; + Bitboard attackers_to(Square s, Bitboard occupied) const; + Bitboard attacks_from(Piece pc, Square s) const; + template Bitboard attacks_from(Square s) const; + template Bitboard attacks_from(Square s, Color c) const; + Bitboard slider_blockers(Bitboard sliders, Square s, Bitboard& pinners) const; + + // Properties of moves + bool legal(Move m) const; + bool pseudo_legal(const Move m) const; + bool capture(Move m) const; + bool capture_or_promotion(Move m) const; + bool gives_check(Move m) const; + bool advanced_pawn_push(Move m) const; + Piece moved_piece(Move m) const; + Piece captured_piece() const; + + // Piece specific + bool pawn_passed(Color c, Square s) const; + bool opposite_bishops() const; + + // Doing and undoing moves + void do_move(Move m, StateInfo& st, bool givesCheck); + void undo_move(Move m); + void do_null_move(StateInfo& st); + void undo_null_move(); + + // Static Exchange Evaluation + bool see_ge(Move m, Value value) 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; + Phase game_phase() const; + int game_ply() const; + bool is_chess960() const; + Thread* this_thread() const; + uint64_t nodes_searched() const; + bool is_draw() const; + int rule50_count() const; + Score psq_score() const; + Value non_pawn_material(Color c) const; + + // Position consistency check, for debugging + bool pos_is_ok(int* failedStep = nullptr) const; + void flip(); + +private: + // Initialization helpers (used while setting up a position) + void set_castling_right(Color c, Square rfrom); + void set_state(StateInfo* si) const; + void set_check_info(StateInfo* si) const; + + // Other helpers + void put_piece(Piece pc, Square s); + void remove_piece(Piece pc, Square s); + void move_piece(Piece pc, Square from, Square to); + template + void do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto); + + // Data members + Piece board[SQUARE_NB]; + Bitboard byTypeBB[PIECE_TYPE_NB]; + Bitboard byColorBB[COLOR_NB]; + int pieceCount[PIECE_NB]; + Square pieceList[PIECE_NB][16]; + int index[SQUARE_NB]; + int castlingRightsMask[SQUARE_NB]; + Square castlingRookSquare[CASTLING_RIGHT_NB]; + Bitboard castlingPath[CASTLING_RIGHT_NB]; + uint64_t nodes; + int gamePly; + Color sideToMove; + Thread* thisThread; + StateInfo* st; + bool chess960; +}; + +extern std::ostream& operator<<(std::ostream& os, const Position& pos); + +inline Color Position::side_to_move() const { + return sideToMove; +} + +inline bool Position::empty(Square s) const { + return board[s] == NO_PIECE; +} + +inline Piece Position::piece_on(Square s) const { + return board[s]; +} + +inline Piece Position::moved_piece(Move m) const { + return board[from_sq(m)]; +} + +inline Bitboard Position::pieces() const { + return byTypeBB[ALL_PIECES]; +} + +inline Bitboard Position::pieces(PieceType pt) const { + return byTypeBB[pt]; +} + +inline Bitboard Position::pieces(PieceType pt1, PieceType pt2) const { + return byTypeBB[pt1] | byTypeBB[pt2]; +} + +inline Bitboard Position::pieces(Color c) const { + return byColorBB[c]; +} + +inline Bitboard Position::pieces(Color c, PieceType pt) const { + return byColorBB[c] & byTypeBB[pt]; +} + +inline Bitboard Position::pieces(Color c, PieceType pt1, PieceType pt2) const { + return byColorBB[c] & (byTypeBB[pt1] | byTypeBB[pt2]); +} + +template inline int Position::count(Color c) const { + return pieceCount[make_piece(c, Pt)]; +} + +template inline const Square* Position::squares(Color c) const { + return pieceList[make_piece(c, Pt)]; +} + +template inline Square Position::square(Color c) const { + assert(pieceCount[make_piece(c, Pt)] == 1); + return pieceList[make_piece(c, Pt)][0]; +} + +inline Square Position::ep_square() const { + return st->epSquare; +} + +inline int Position::can_castle(CastlingRight cr) const { + return st->castlingRights & cr; +} + +inline int Position::can_castle(Color c) const { + return st->castlingRights & ((WHITE_OO | WHITE_OOO) << (2 * c)); +} + +inline bool Position::castling_impeded(CastlingRight cr) const { + return byTypeBB[ALL_PIECES] & castlingPath[cr]; +} + +inline Square Position::castling_rook_square(CastlingRight cr) const { + return castlingRookSquare[cr]; +} + +template +inline Bitboard Position::attacks_from(Square s) const { + return Pt == BISHOP || Pt == ROOK ? attacks_bb(s, byTypeBB[ALL_PIECES]) + : Pt == QUEEN ? attacks_from(s) | attacks_from(s) + : StepAttacksBB[Pt][s]; +} + +template<> +inline Bitboard Position::attacks_from(Square s, Color c) const { + return StepAttacksBB[make_piece(c, PAWN)][s]; +} + +inline Bitboard Position::attacks_from(Piece pc, Square s) const { + return attacks_bb(pc, s, byTypeBB[ALL_PIECES]); +} + +inline Bitboard Position::attackers_to(Square s) const { + return attackers_to(s, byTypeBB[ALL_PIECES]); +} + +inline Bitboard Position::checkers() const { + return st->checkersBB; +} + +inline Bitboard Position::discovered_check_candidates() const { + return st->blockersForKing[~sideToMove] & pieces(sideToMove); +} + +inline Bitboard Position::pinned_pieces(Color c) const { + return st->blockersForKing[c] & pieces(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_mask(c, s)); +} + +inline bool Position::advanced_pawn_push(Move m) const { + return type_of(moved_piece(m)) == PAWN + && relative_rank(sideToMove, from_sq(m)) > RANK_4; +} + +inline Key Position::key() const { + return st->key; +} + +inline Key Position::pawn_key() const { + return st->pawnKey; +} + +inline Key Position::material_key() const { + return st->materialKey; +} + +inline Score Position::psq_score() const { + return st->psq; +} + +inline Value Position::non_pawn_material(Color c) const { + return st->nonPawnMaterial[c]; +} + +inline int Position::game_ply() const { + return gamePly; +} + +inline int Position::rule50_count() const { + return st->rule50; +} + +inline uint64_t Position::nodes_searched() const { + return nodes; +} + +inline bool Position::opposite_bishops() const { + return pieceCount[W_BISHOP] == 1 + && pieceCount[B_BISHOP] == 1 + && opposite_colors(square(WHITE), square(BLACK)); +} + +inline bool Position::is_chess960() const { + return chess960; +} + +inline bool Position::capture_or_promotion(Move m) const { + assert(is_ok(m)); + return type_of(m) != NORMAL ? type_of(m) != CASTLING : !empty(to_sq(m)); +} + +inline bool Position::capture(Move m) const { + assert(is_ok(m)); + // Castling is encoded as "king captures rook" + return (!empty(to_sq(m)) && type_of(m) != CASTLING) || type_of(m) == ENPASSANT; +} + +inline Piece Position::captured_piece() const { + return st->capturedPiece; +} + +inline Thread* Position::this_thread() const { + return thisThread; +} + +inline void Position::put_piece(Piece pc, Square s) { + + board[s] = pc; + byTypeBB[ALL_PIECES] |= s; + byTypeBB[type_of(pc)] |= s; + byColorBB[color_of(pc)] |= s; + index[s] = pieceCount[pc]++; + pieceList[pc][index[s]] = s; + pieceCount[make_piece(color_of(pc), ALL_PIECES)]++; +} + +inline void Position::remove_piece(Piece pc, Square s) { + + // WARNING: This is not a reversible operation. If we remove a piece in + // do_move() and then replace it in undo_move() we will put it at the end of + // the list and not in its original place, it means index[] and pieceList[] + // are not invariant to a do_move() + undo_move() sequence. + byTypeBB[ALL_PIECES] ^= s; + byTypeBB[type_of(pc)] ^= s; + byColorBB[color_of(pc)] ^= s; + /* board[s] = NO_PIECE; Not needed, overwritten by the capturing one */ + Square lastSquare = pieceList[pc][--pieceCount[pc]]; + index[lastSquare] = index[s]; + pieceList[pc][index[lastSquare]] = lastSquare; + pieceList[pc][pieceCount[pc]] = SQ_NONE; + pieceCount[make_piece(color_of(pc), ALL_PIECES)]--; +} + +inline void Position::move_piece(Piece pc, Square from, Square to) { + + // index[from] is not updated and becomes stale. This works as long as index[] + // is accessed just by known occupied squares. + Bitboard from_to_bb = SquareBB[from] ^ SquareBB[to]; + byTypeBB[ALL_PIECES] ^= from_to_bb; + byTypeBB[type_of(pc)] ^= from_to_bb; + byColorBB[color_of(pc)] ^= from_to_bb; + board[from] = NO_PIECE; + board[to] = pc; + index[to] = index[from]; + pieceList[pc][index[to]] = to; +} + +#endif // #ifndef POSITION_H_INCLUDED diff --git a/src/psqt.cpp b/src/psqt.cpp new file mode 100644 index 0000000..41a9b49 --- /dev/null +++ b/src/psqt.cpp @@ -0,0 +1,126 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include + +#include "types.h" + +Value PieceValue[PHASE_NB][PIECE_NB] = { + { VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg }, + { VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg } +}; + +namespace PSQT { + +#define S(mg, eg) make_score(mg, eg) + +// Bonus[PieceType][Square / 2] contains Piece-Square scores. For each piece +// type on a given square a (middlegame, endgame) score pair is assigned. Table +// is defined for files A..D and white side: it is symmetric for black side and +// second half of the files. +const Score Bonus[][RANK_NB][int(FILE_NB) / 2] = { + { }, + { // Pawn + { S( 0, 0), S( 0, 0), S( 0, 0), S( 0, 0) }, + { S(-16, 7), S( 1,-4), S( 7, 8), S( 3,-2) }, + { S(-23,-4), S( -7,-5), S( 19, 5), S(24, 4) }, + { S(-22, 3), S(-14, 3), S( 20,-8), S(35,-3) }, + { S(-11, 8), S( 0, 9), S( 3, 7), S(21,-6) }, + { S(-11, 8), S(-13,-5), S( -6, 2), S(-2, 4) }, + { S( -9, 3), S( 15,-9), S( -8, 1), S(-4,18) } + }, + { // Knight + { S(-143, -97), S(-96,-82), S(-80,-46), S(-73,-14) }, + { S( -83, -69), S(-43,-55), S(-21,-17), S(-10, 9) }, + { S( -71, -50), S(-22,-39), S( 0, -8), S( 9, 28) }, + { S( -25, -41), S( 18,-25), S( 43, 7), S( 47, 38) }, + { S( -26, -46), S( 16,-25), S( 38, 2), S( 50, 41) }, + { S( -11, -55), S( 37,-38), S( 56, -8), S( 71, 27) }, + { S( -62, -64), S(-17,-50), S( 5,-24), S( 14, 13) }, + { S(-195,-110), S(-66,-90), S(-42,-50), S(-29,-13) } + }, + { // Bishop + { S(-54,-68), S(-23,-40), S(-35,-46), S(-44,-28) }, + { S(-30,-43), S( 10,-17), S( 2,-23), S( -9, -5) }, + { S(-19,-32), S( 17, -9), S( 11,-13), S( 1, 8) }, + { S(-21,-36), S( 18,-13), S( 11,-15), S( 0, 7) }, + { S(-21,-36), S( 14,-14), S( 6,-17), S( -1, 3) }, + { S(-27,-35), S( 6,-13), S( 2,-10), S( -8, 1) }, + { S(-33,-44), S( 7,-21), S( -4,-22), S(-12, -4) }, + { S(-45,-65), S(-21,-42), S(-29,-46), S(-39,-27) } + }, + { // Rook + { S(-25, 0), S(-16, 0), S(-16, 0), S(-9, 0) }, + { S(-21, 0), S( -8, 0), S( -3, 0), S( 0, 0) }, + { S(-21, 0), S( -9, 0), S( -4, 0), S( 2, 0) }, + { S(-22, 0), S( -6, 0), S( -1, 0), S( 2, 0) }, + { S(-22, 0), S( -7, 0), S( 0, 0), S( 1, 0) }, + { S(-21, 0), S( -7, 0), S( 0, 0), S( 2, 0) }, + { S(-12, 0), S( 4, 0), S( 8, 0), S(12, 0) }, + { S(-23, 0), S(-15, 0), S(-11, 0), S(-5, 0) } + }, + { // Queen + { S( 0,-70), S(-3,-57), S(-4,-41), S(-1,-29) }, + { S(-4,-58), S( 6,-30), S( 9,-21), S( 8, -4) }, + { S(-2,-39), S( 6,-17), S( 9, -7), S( 9, 5) }, + { S(-1,-29), S( 8, -5), S(10, 9), S( 7, 17) }, + { S(-3,-27), S( 9, -5), S( 8, 10), S( 7, 23) }, + { S(-2,-40), S( 6,-16), S( 8,-11), S(10, 3) }, + { S(-2,-54), S( 7,-30), S( 7,-21), S( 6, -7) }, + { S(-1,-75), S(-4,-54), S(-1,-44), S( 0,-30) } + }, + { // King + { S(291, 28), S(344, 76), S(294,103), S(219,112) }, + { S(289, 70), S(329,119), S(263,170), S(205,159) }, + { S(226,109), S(271,164), S(202,195), S(136,191) }, + { S(204,131), S(212,194), S(175,194), S(137,204) }, + { S(177,132), S(205,187), S(143,224), S( 94,227) }, + { S(147,118), S(188,178), S(113,199), S( 70,197) }, + { S(116, 72), S(158,121), S( 93,142), S( 48,161) }, + { S( 94, 30), S(120, 76), S( 78,101), S( 31,111) } + } +}; + +#undef S + +Score psq[PIECE_NB][SQUARE_NB]; + +// init() initializes piece-square tables: the white halves of the tables are +// copied from Bonus[] adding the piece value, then the black halves of the +// tables are initialized by flipping and changing the sign of the white scores. +void init() { + + for (Piece pc = W_PAWN; pc <= W_KING; ++pc) + { + PieceValue[MG][~pc] = PieceValue[MG][pc]; + PieceValue[EG][~pc] = PieceValue[EG][pc]; + + Score v = make_score(PieceValue[MG][pc], PieceValue[EG][pc]); + + for (Square s = SQ_A1; s <= SQ_H8; ++s) + { + File f = std::min(file_of(s), FILE_H - file_of(s)); + psq[ pc][ s] = v + Bonus[pc][rank_of(s)][f]; + psq[~pc][~s] = -psq[pc][s]; + } + } +} + +} // namespace PSQT diff --git a/src/search.cpp b/src/search.cpp new file mode 100644 index 0000000..19749b6 --- /dev/null +++ b/src/search.cpp @@ -0,0 +1,1656 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include // For std::memset +#include +#include + +#include "evaluate.h" +#include "misc.h" +#include "movegen.h" +#include "movepick.h" +#include "position.h" +#include "search.h" +#include "timeman.h" +#include "thread.h" +#include "tt.h" +#include "uci.h" +#include "syzygy/tbprobe.h" + +namespace Search { + + SignalsType Signals; + LimitsType Limits; +} + +namespace Tablebases { + + int Cardinality; + bool RootInTB; + bool UseRule50; + Depth ProbeDepth; + Value Score; +} + +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 }; + + // Razoring and futility margin based on depth + const int razor_margin[4] = { 483, 570, 603, 554 }; + Value futility_margin(Depth d) { return Value(150 * d / ONE_PLY); } + + // Futility and reductions lookup tables, initialized at startup + int FutilityMoveCounts[2][16]; // [improving][depth] + int Reductions[2][2][64][64]; // [pv][improving][depth][moveNumber] + + template Depth reduction(bool i, Depth d, int mn) { + return Reductions[PvNode][i][std::min(d / ONE_PLY, 63)][std::min(mn, 63)] * ONE_PLY; + } + + // Skill structure is used to implement strength limit + struct Skill { + Skill(int l) : level(l) {} + bool enabled() const { return level < 20; } + bool time_to_pick(Depth depth) const { return depth / ONE_PLY == 1 + level; } + Move best_move(size_t multiPV) { return best ? best : pick_best(multiPV); } + Move pick_best(size_t multiPV); + + int level; + Move best = MOVE_NONE; + }; + + // EasyMoveManager structure is used to detect an 'easy move'. When the PV is + // stable across multiple search iterations, we can quickly return the best move. + struct EasyMoveManager { + + void clear() { + stableCnt = 0; + expectedPosKey = 0; + pv[0] = pv[1] = pv[2] = MOVE_NONE; + } + + Move get(Key key) const { + return expectedPosKey == key ? pv[2] : MOVE_NONE; + } + + void update(Position& pos, const std::vector& newPv) { + + assert(newPv.size() >= 3); + + // Keep track of how many times in a row the 3rd ply remains stable + stableCnt = (newPv[2] == pv[2]) ? stableCnt + 1 : 0; + + if (!std::equal(newPv.begin(), newPv.begin() + 3, pv)) + { + std::copy(newPv.begin(), newPv.begin() + 3, pv); + + StateInfo st[2]; + pos.do_move(newPv[0], st[0], pos.gives_check(newPv[0])); + pos.do_move(newPv[1], st[1], pos.gives_check(newPv[1])); + expectedPosKey = pos.key(); + pos.undo_move(newPv[1]); + pos.undo_move(newPv[0]); + } + } + + int stableCnt; + Key expectedPosKey; + Move pv[3]; + }; + + // Set of rows with half bits set to 1 and half to 0. It is used to allocate + // the search depths across the threads. + typedef std::vector Row; + + const Row HalfDensity[] = { + {0, 1}, + {1, 0}, + {0, 0, 1, 1}, + {0, 1, 1, 0}, + {1, 1, 0, 0}, + {1, 0, 0, 1}, + {0, 0, 0, 1, 1, 1}, + {0, 0, 1, 1, 1, 0}, + {0, 1, 1, 1, 0, 0}, + {1, 1, 1, 0, 0, 0}, + {1, 1, 0, 0, 0, 1}, + {1, 0, 0, 0, 1, 1}, + {0, 0, 0, 0, 1, 1, 1, 1}, + {0, 0, 0, 1, 1, 1, 1, 0}, + {0, 0, 1, 1, 1, 1, 0 ,0}, + {0, 1, 1, 1, 1, 0, 0 ,0}, + {1, 1, 1, 1, 0, 0, 0 ,0}, + {1, 1, 1, 0, 0, 0, 0 ,1}, + {1, 1, 0, 0, 0, 0, 1 ,1}, + {1, 0, 0, 0, 0, 1, 1 ,1}, + }; + + const size_t HalfDensitySize = std::extent::value; + + EasyMoveManager EasyMove; + Value DrawValue[COLOR_NB]; + + 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); + + Value value_to_tt(Value v, int ply); + Value value_from_tt(Value v, int ply); + void update_pv(Move* pv, Move move, Move* childPv); + void update_cm_stats(Stack* ss, Piece pc, Square s, Value bonus); + void update_stats(const Position& pos, Stack* ss, Move move, Move* quiets, int quietsCnt, Value bonus); + void check_time(); + +} // namespace + + +/// Search::init() is called during startup to initialize various lookup tables + +void Search::init() { + + for (int imp = 0; imp <= 1; ++imp) + for (int d = 1; d < 64; ++d) + for (int mc = 1; mc < 64; ++mc) + { + double r = log(d) * log(mc) / 2; + if (r < 0.80) + continue; + + Reductions[NonPV][imp][d][mc] = int(std::round(r)); + Reductions[PV][imp][d][mc] = std::max(Reductions[NonPV][imp][d][mc] - 1, 0); + + // Increase reduction for non-PV nodes when eval is not improving + if (!imp && Reductions[NonPV][imp][d][mc] >= 2) + Reductions[NonPV][imp][d][mc]++; + } + + for (int d = 0; d < 16; ++d) + { + FutilityMoveCounts[0][d] = int(2.4 + 0.773 * pow(d + 0.00, 1.8)); + FutilityMoveCounts[1][d] = int(2.9 + 1.045 * pow(d + 0.49, 1.8)); + } +} + + +/// Search::clear() resets search state to zero, to obtain reproducible results + +void Search::clear() { + + TT.clear(); + + for (Thread* th : Threads) + { + th->history.clear(); + th->counterMoves.clear(); + th->fromTo.clear(); + th->counterMoveHistory.clear(); + } + + Threads.main()->previousScore = VALUE_INFINITE; +} + + +/// Search::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 Search::perft(Position& pos, Depth depth) { + + StateInfo st; + uint64_t cnt, nodes = 0; + const bool leaf = (depth == 2 * ONE_PLY); + + for (const auto& m : MoveList(pos)) + { + if (Root && depth <= ONE_PLY) + cnt = 1, nodes++; + else + { + pos.do_move(m, st, pos.gives_check(m)); + cnt = leaf ? MoveList(pos).size() : perft(pos, depth - ONE_PLY); + nodes += cnt; + pos.undo_move(m); + } + if (Root) + sync_cout << UCI::move(m, pos.is_chess960()) << ": " << cnt << sync_endl; + } + return nodes; +} + +template uint64_t Search::perft(Position&, Depth); + + +/// MainThread::search() is called by the main thread when the program receives +/// the UCI 'go' command. It searches from the root position and outputs the "bestmove". + +void MainThread::search() { + + Color us = rootPos.side_to_move(); + Time.init(Limits, us, rootPos.game_ply()); + + int contempt = Options["Contempt"] * PawnValueEg / 100; // From centipawns + DrawValue[ us] = VALUE_DRAW - Value(contempt); + DrawValue[~us] = VALUE_DRAW + Value(contempt); + + if (rootMoves.empty()) + { + rootMoves.push_back(RootMove(MOVE_NONE)); + sync_cout << "info depth 0 score " + << UCI::value(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW) + << sync_endl; + } + else + { + for (Thread* th : Threads) + if (th != this) + th->start_searching(); + + Thread::search(); // Let's start searching! + } + + // 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(); + + // When we reach the maximum depth, we can arrive here without a raise of + // Signals.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 (which also raises Signals.stop). + if (!Signals.stop && (Limits.ponder || Limits.infinite)) + { + Signals.stopOnPonderhit = true; + wait(Signals.stop); + } + + // Stop the threads if not already stopped + Signals.stop = true; + + // Wait until all threads have finished + for (Thread* th : Threads) + if (th != this) + th->wait_for_search_finished(); + + // Check if there are threads with a better score than main thread + Thread* bestThread = this; + if ( !this->easyMovePlayed + && Options["MultiPV"] == 1 + && !Limits.depth + && !Skill(Options["Skill Level"]).enabled() + && rootMoves[0].pv[0] != MOVE_NONE) + { + for (Thread* th : Threads) + if ( th->completedDepth > bestThread->completedDepth + && th->rootMoves[0].score > bestThread->rootMoves[0].score) + bestThread = th; + } + + previousScore = bestThread->rootMoves[0].score; + + // Send new PV when needed + if (bestThread != this) + sync_cout << UCI::pv(bestThread->rootPos, bestThread->completedDepth, -VALUE_INFINITE, VALUE_INFINITE) << sync_endl; + + sync_cout << "bestmove " << UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); + + if (bestThread->rootMoves[0].pv.size() > 1 || bestThread->rootMoves[0].extract_ponder_from_tt(rootPos)) + std::cout << " ponder " << UCI::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960()); + + std::cout << sync_endl; +} + + +// Thread::search() is the main iterative deepening loop. It calls search() +// repeatedly with increasing depth until the allocated thinking time has been +// consumed, the user stops the search, or the maximum search depth is reached. + +void Thread::search() { + + Stack stack[MAX_PLY+7], *ss = stack+5; // To allow referencing (ss-5) and (ss+2) + Value bestValue, alpha, beta, delta; + Move easyMove = MOVE_NONE; + MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr); + + std::memset(ss-5, 0, 8 * sizeof(Stack)); + + bestValue = delta = alpha = -VALUE_INFINITE; + beta = VALUE_INFINITE; + completedDepth = DEPTH_ZERO; + + if (mainThread) + { + easyMove = EasyMove.get(rootPos.key()); + EasyMove.clear(); + mainThread->easyMovePlayed = mainThread->failedLow = false; + mainThread->bestMoveChanges = 0; + TT.new_search(); + } + + size_t multiPV = Options["MultiPV"]; + Skill skill(Options["Skill Level"]); + + // 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()); + + // Iterative deepening loop until requested to stop or the target depth is reached + while ( (rootDepth += ONE_PLY) < DEPTH_MAX + && !Signals.stop + && (!Limits.depth || Threads.main()->rootDepth / ONE_PLY <= Limits.depth)) + { + // Set up the new depths for the helper threads skipping on average every + // 2nd ply (using a half-density matrix). + if (!mainThread) + { + const Row& row = HalfDensity[(idx - 1) % HalfDensitySize]; + if (row[(rootDepth / ONE_PLY + rootPos.game_ply()) % row.size()]) + continue; + } + + // Age out PV variability metric + if (mainThread) + mainThread->bestMoveChanges *= 0.505, mainThread->failedLow = false; + + // 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; + + // MultiPV loop. We perform a full root search for each PV line + for (PVIdx = 0; PVIdx < multiPV && !Signals.stop; ++PVIdx) + { + // Reset aspiration window starting size + if (rootDepth >= 5 * ONE_PLY) + { + delta = Value(18); + alpha = std::max(rootMoves[PVIdx].previousScore - delta,-VALUE_INFINITE); + beta = std::min(rootMoves[PVIdx].previousScore + delta, VALUE_INFINITE); + } + + // Start with a small aspiration window and, in the case of a fail + // high/low, re-search with a bigger window until we're not failing + // high/low anymore. + while (true) + { + bestValue = ::search(rootPos, ss, alpha, beta, rootDepth, 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.end()); + + // If search has been stopped, break immediately. Sorting and + // writing PV back to TT is safe because RootMoves is still + // valid, although it refers to the previous iteration. + if (Signals.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); + + if (mainThread) + { + mainThread->failedLow = true; + Signals.stopOnPonderhit = false; + } + } + else if (bestValue >= beta) + { + alpha = (alpha + beta) / 2; + beta = std::min(bestValue + delta, VALUE_INFINITE); + } + 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(), rootMoves.begin() + PVIdx + 1); + + if (!mainThread) + continue; + + if (Signals.stop || PVIdx + 1 == multiPV || Time.elapsed() > 3000) + sync_cout << UCI::pv(rootPos, rootDepth, alpha, beta) << sync_endl; + } + + if (!Signals.stop) + completedDepth = rootDepth; + + 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); + + // Have we found a "mate in x"? + if ( Limits.mate + && bestValue >= VALUE_MATE_IN_MAX_PLY + && VALUE_MATE - bestValue <= 2 * Limits.mate) + Signals.stop = true; + + // Do we have time for the next iteration? Can we stop searching now? + if (Limits.use_time_management()) + { + if (!Signals.stop && !Signals.stopOnPonderhit) + { + // Stop the search if only one legal move is available, or if all + // of the available time has been used, or if we matched an easyMove + // from the previous search and just did a fast verification. + const int F[] = { mainThread->failedLow, + bestValue - mainThread->previousScore }; + + int improvingFactor = std::max(229, std::min(715, 357 + 119 * F[0] - 6 * F[1])); + double unstablePvFactor = 1 + mainThread->bestMoveChanges; + + bool doEasyMove = rootMoves[0].pv[0] == easyMove + && mainThread->bestMoveChanges < 0.03 + && Time.elapsed() > Time.optimum() * 5 / 42; + + if ( rootMoves.size() == 1 + || Time.elapsed() > Time.optimum() * unstablePvFactor * improvingFactor / 628 + || (mainThread->easyMovePlayed = doEasyMove, doEasyMove)) + { + // If we are allowed to ponder do not stop the search now but + // keep pondering until the GUI sends "ponderhit" or "stop". + if (Limits.ponder) + Signals.stopOnPonderhit = true; + else + Signals.stop = true; + } + } + + if (rootMoves[0].pv.size() >= 3) + EasyMove.update(rootPos, rootMoves[0].pv); + else + EasyMove.clear(); + } + } + + if (!mainThread) + return; + + // Clear any candidate easy move that wasn't stable for the last search + // iterations; the second condition prevents consecutive fast moves. + if (EasyMove.stableCnt < 6 || mainThread->easyMovePlayed) + EasyMove.clear(); + + // 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_move(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) { + + const bool PvNode = NT == PV; + const bool rootNode = PvNode && (ss-1)->ply == 0; + + assert(-VALUE_INFINITE <= alpha && alpha < beta && beta <= VALUE_INFINITE); + assert(PvNode || (alpha == beta - 1)); + assert(DEPTH_ZERO < depth && depth < DEPTH_MAX); + assert(!(PvNode && cutNode)); + assert(depth / ONE_PLY * ONE_PLY == depth); + + Move pv[MAX_PLY+1], quietsSearched[64]; + StateInfo st; + TTEntry* tte; + Key posKey; + Move ttMove, move, excludedMove, bestMove; + Depth extension, newDepth; + Value bestValue, value, ttValue, eval, nullValue; + bool ttHit, inCheck, givesCheck, singularExtensionNode, improving; + bool captureOrPromotion, doFullDepthSearch, moveCountPruning; + Piece moved_piece; + int moveCount, quietCount; + + // Step 1. Initialize node + Thread* thisThread = pos.this_thread(); + inCheck = pos.checkers(); + moveCount = quietCount = ss->moveCount = 0; + ss->history = VALUE_ZERO; + bestValue = -VALUE_INFINITE; + ss->ply = (ss-1)->ply + 1; + + // Check for the available remaining time + if (thisThread->resetCalls.load(std::memory_order_relaxed)) + { + thisThread->resetCalls = false; + thisThread->callsCnt = 0; + } + if (++thisThread->callsCnt > 4096) + { + for (Thread* th : Threads) + th->resetCalls = true; + + check_time(); + } + + // Used to send selDepth info to GUI + if (PvNode && thisThread->maxPly < ss->ply) + thisThread->maxPly = ss->ply; + + if (!rootNode) + { + // Step 2. Check for aborted search and immediate draw + if (Signals.stop.load(std::memory_order_relaxed) || pos.is_draw() || ss->ply >= MAX_PLY) + return ss->ply >= MAX_PLY && !inCheck ? evaluate(pos) + : DrawValue[pos.side_to_move()]; + + // 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->currentMove = (ss+1)->excludedMove = bestMove = MOVE_NONE; + ss->counterMoves = nullptr; + (ss+1)->skipEarlyPruning = false; + (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE; + + // 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 = pos.key() ^ Key(excludedMove); + tte = TT.probe(posKey, ttHit); + ttValue = ttHit ? value_from_tt(tte->value(), ss->ply) : VALUE_NONE; + ttMove = rootNode ? thisThread->rootMoves[thisThread->PVIdx].pv[0] + : ttHit ? tte->move() : MOVE_NONE; + + // At non-PV nodes we check for an early TT cutoff + if ( !PvNode + && ttHit + && tte->depth() >= depth + && ttValue != VALUE_NONE // Possible in case of TT access race + && (ttValue >= beta ? (tte->bound() & BOUND_LOWER) + : (tte->bound() & BOUND_UPPER))) + { + // If ttMove is quiet, update killers, history, counter move on TT hit + if (ttValue >= beta && ttMove) + { + int d = depth / ONE_PLY; + + if (!pos.capture_or_promotion(ttMove)) + { + Value bonus = Value(d * d + 2 * d - 2); + update_stats(pos, ss, ttMove, nullptr, 0, bonus); + } + + // Extra penalty for a quiet TT move in previous ply when it gets refuted + if ((ss-1)->moveCount == 1 && !pos.captured_piece()) + { + Value penalty = Value(d * d + 4 * d + 1); + Square prevSq = to_sq((ss-1)->currentMove); + update_cm_stats(ss-1, pos.piece_on(prevSq), prevSq, -penalty); + } + } + return ttValue; + } + + // Step 4a. Tablebase probe + if (!rootNode && TB::Cardinality) + { + int piecesCnt = pos.count(WHITE) + pos.count(BLACK); + + if ( piecesCnt <= TB::Cardinality + && (piecesCnt < TB::Cardinality || depth >= TB::ProbeDepth) + && pos.rule50_count() == 0 + && !pos.can_castle(ANY_CASTLING)) + { + int found, v = Tablebases::probe_wdl(pos, &found); + + if (found) + { + thisThread->tbHits++; + + int drawScore = TB::UseRule50 ? 1 : 0; + + value = v < -drawScore ? -VALUE_MATE + MAX_PLY + ss->ply + : v > drawScore ? VALUE_MATE - MAX_PLY - ss->ply + : VALUE_DRAW + 2 * v * drawScore; + + tte->save(posKey, value_to_tt(value, ss->ply), BOUND_EXACT, + std::min(DEPTH_MAX - ONE_PLY, depth + 6 * ONE_PLY), + MOVE_NONE, VALUE_NONE, TT.generation()); + + return value; + } + } + } + + // Step 5. Evaluate the position statically + if (inCheck) + { + ss->staticEval = eval = VALUE_NONE; + goto moves_loop; + } + + else if (ttHit) + { + // Never assume anything on values stored in TT + if ((ss->staticEval = eval = tte->eval()) == VALUE_NONE) + eval = ss->staticEval = evaluate(pos); + + // Can ttValue be used as a better position evaluation? + if (ttValue != VALUE_NONE) + if (tte->bound() & (ttValue > eval ? BOUND_LOWER : BOUND_UPPER)) + eval = ttValue; + } + else + { + eval = ss->staticEval = + (ss-1)->currentMove != MOVE_NULL ? evaluate(pos) + : -(ss-1)->staticEval + 2 * Eval::Tempo; + + tte->save(posKey, VALUE_NONE, BOUND_NONE, DEPTH_NONE, MOVE_NONE, + ss->staticEval, TT.generation()); + } + + if (ss->skipEarlyPruning) + goto moves_loop; + + // Step 6. Razoring (skipped when in check) + if ( !PvNode + && depth < 4 * ONE_PLY + && ttMove == MOVE_NONE + && eval + razor_margin[depth / ONE_PLY] <= alpha) + { + if (depth <= ONE_PLY) + return qsearch(pos, ss, alpha, beta, DEPTH_ZERO); + + Value ralpha = alpha - razor_margin[depth / ONE_PLY]; + Value v = qsearch(pos, ss, ralpha, ralpha+1, DEPTH_ZERO); + if (v <= ralpha) + return v; + } + + // Step 7. Futility pruning: child node (skipped when in check) + if ( !rootNode + && depth < 7 * ONE_PLY + && eval - futility_margin(depth) >= beta + && eval < VALUE_KNOWN_WIN // Do not return unproven wins + && pos.non_pawn_material(pos.side_to_move())) + return eval; + + // Step 8. Null move search with verification search (is omitted in PV nodes) + if ( !PvNode + && eval >= beta + && (ss->staticEval >= beta - 35 * (depth / ONE_PLY - 6) || depth >= 13 * ONE_PLY) + && pos.non_pawn_material(pos.side_to_move())) + { + ss->currentMove = MOVE_NULL; + ss->counterMoves = nullptr; + + assert(eval - beta >= 0); + + // Null move dynamic reduction based on depth and value + Depth R = ((823 + 67 * depth / ONE_PLY) / 256 + std::min((eval - beta) / PawnValueMg, 3)) * ONE_PLY; + + pos.do_null_move(st); + (ss+1)->skipEarlyPruning = true; + nullValue = depth-R < ONE_PLY ? -qsearch(pos, ss+1, -beta, -beta+1, DEPTH_ZERO) + : - search(pos, ss+1, -beta, -beta+1, depth-R, !cutNode); + (ss+1)->skipEarlyPruning = false; + pos.undo_null_move(); + + if (nullValue >= beta) + { + // Do not return unproven mate scores + if (nullValue >= VALUE_MATE_IN_MAX_PLY) + nullValue = beta; + + if (depth < 12 * ONE_PLY && abs(beta) < VALUE_KNOWN_WIN) + return nullValue; + + // Do verification search at high depths + ss->skipEarlyPruning = true; + Value v = depth-R < ONE_PLY ? qsearch(pos, ss, beta-1, beta, DEPTH_ZERO) + : search(pos, ss, beta-1, beta, depth-R, false); + ss->skipEarlyPruning = false; + + if (v >= beta) + return nullValue; + } + } + + // Step 9. ProbCut (skipped when in check) + // 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 >= 5 * ONE_PLY + && abs(beta) < VALUE_MATE_IN_MAX_PLY) + { + Value rbeta = std::min(beta + 200, VALUE_INFINITE); + Depth rdepth = depth - 4 * ONE_PLY; + + assert(rdepth >= ONE_PLY); + assert((ss-1)->currentMove != MOVE_NONE); + assert((ss-1)->currentMove != MOVE_NULL); + + MovePicker mp(pos, ttMove, rbeta - ss->staticEval); + + while ((move = mp.next_move()) != MOVE_NONE) + if (pos.legal(move)) + { + ss->currentMove = move; + ss->counterMoves = &thisThread->counterMoveHistory[pos.moved_piece(move)][to_sq(move)]; + pos.do_move(move, st, pos.gives_check(move)); + value = -search(pos, ss+1, -rbeta, -rbeta+1, rdepth, !cutNode); + pos.undo_move(move); + if (value >= rbeta) + return value; + } + } + + // Step 10. Internal iterative deepening (skipped when in check) + if ( depth >= 6 * ONE_PLY + && !ttMove + && (PvNode || ss->staticEval + 256 >= beta)) + { + Depth d = (3 * depth / (4 * ONE_PLY) - 2) * ONE_PLY; + ss->skipEarlyPruning = true; + search(pos, ss, alpha, beta, d, cutNode); + ss->skipEarlyPruning = false; + + tte = TT.probe(posKey, ttHit); + ttMove = ttHit ? tte->move() : MOVE_NONE; + } + +moves_loop: // When in check search starts from here + + const CounterMoveStats* cmh = (ss-1)->counterMoves; + const CounterMoveStats* fmh = (ss-2)->counterMoves; + const CounterMoveStats* fmh2 = (ss-4)->counterMoves; + + MovePicker mp(pos, ttMove, depth, ss); + value = bestValue; // Workaround a bogus 'uninitialized' warning under gcc + improving = ss->staticEval >= (ss-2)->staticEval + /* || ss->staticEval == VALUE_NONE Already implicit in the previous condition */ + ||(ss-2)->staticEval == VALUE_NONE; + + singularExtensionNode = !rootNode + && depth >= 8 * ONE_PLY + && ttMove != MOVE_NONE + && ttValue != VALUE_NONE + && !excludedMove // Recursive singular search is not allowed + && (tte->bound() & BOUND_LOWER) + && tte->depth() >= depth - 3 * ONE_PLY; + + // Step 11. Loop through moves + // Loop through all pseudo-legal moves until no moves remain or a beta cutoff occurs + while ((move = mp.next_move()) != 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. + if (rootNode && !std::count(thisThread->rootMoves.begin() + thisThread->PVIdx, + thisThread->rootMoves.end(), move)) + continue; + + ss->moveCount = ++moveCount; + + if (rootNode && thisThread == Threads.main() && Time.elapsed() > 3000) + sync_cout << "info depth " << depth / ONE_PLY + << " currmove " << UCI::move(move, pos.is_chess960()) + << " currmovenumber " << moveCount + thisThread->PVIdx << sync_endl; + + if (PvNode) + (ss+1)->pv = nullptr; + + extension = DEPTH_ZERO; + captureOrPromotion = pos.capture_or_promotion(move); + moved_piece = pos.moved_piece(move); + + givesCheck = type_of(move) == NORMAL && !pos.discovered_check_candidates() + ? pos.check_squares(type_of(pos.piece_on(from_sq(move)))) & to_sq(move) + : pos.gives_check(move); + + moveCountPruning = depth < 16 * ONE_PLY + && moveCount >= FutilityMoveCounts[improving][depth / ONE_PLY]; + + // Step 12. Extend checks + if ( givesCheck + && !moveCountPruning + && pos.see_ge(move, VALUE_ZERO)) + extension = ONE_PLY; + + // Singular extension search. 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 extend the ttMove. + if ( singularExtensionNode + && move == ttMove + && !extension + && pos.legal(move)) + { + Value rBeta = std::max(ttValue - 2 * depth / ONE_PLY, -VALUE_MATE); + Depth d = (depth / (2 * ONE_PLY)) * ONE_PLY; + ss->excludedMove = move; + ss->skipEarlyPruning = true; + value = search(pos, ss, rBeta - 1, rBeta, d, cutNode); + ss->skipEarlyPruning = false; + ss->excludedMove = MOVE_NONE; + + if (value < rBeta) + extension = ONE_PLY; + } + + // Update the current move (this must be done after singular extension search) + newDepth = depth - ONE_PLY + extension; + + // Step 13. Pruning at shallow depth + if ( !rootNode + && bestValue > VALUE_MATED_IN_MAX_PLY) + { + if ( !captureOrPromotion + && !givesCheck + && !pos.advanced_pawn_push(move)) + { + // Move count based pruning + if (moveCountPruning) + continue; + + // Reduced depth of the next LMR search + int lmrDepth = std::max(newDepth - reduction(improving, depth, moveCount), DEPTH_ZERO) / ONE_PLY; + + // Countermoves based pruning + if ( lmrDepth < 3 + && (!cmh || (*cmh )[moved_piece][to_sq(move)] < VALUE_ZERO) + && (!fmh || (*fmh )[moved_piece][to_sq(move)] < VALUE_ZERO) + && (!fmh2 || (*fmh2)[moved_piece][to_sq(move)] < VALUE_ZERO || (cmh && fmh))) + continue; + + // Futility pruning: parent node + if ( lmrDepth < 7 + && !inCheck + && ss->staticEval + 256 + 200 * lmrDepth <= alpha) + continue; + + // Prune moves with negative SEE + if ( lmrDepth < 8 + && !pos.see_ge(move, Value(-35 * lmrDepth * lmrDepth))) + continue; + } + else if ( depth < 7 * ONE_PLY + && !extension + && !pos.see_ge(move, Value(-35 * depth / ONE_PLY * depth / ONE_PLY))) + continue; + } + + // Speculative prefetch as early as possible + prefetch(TT.first_entry(pos.key_after(move))); + + // Check for legality just before making the move + if (!rootNode && !pos.legal(move)) + { + ss->moveCount = --moveCount; + continue; + } + + ss->currentMove = move; + ss->counterMoves = &thisThread->counterMoveHistory[moved_piece][to_sq(move)]; + + // Step 14. Make the move + pos.do_move(move, st, givesCheck); + + // Step 15. Reduced depth search (LMR). If the move fails high it will be + // re-searched at full depth. + if ( depth >= 3 * ONE_PLY + && moveCount > 1 + && (!captureOrPromotion || moveCountPruning)) + { + Depth r = reduction(improving, depth, moveCount); + + if (captureOrPromotion) + r -= r ? ONE_PLY : DEPTH_ZERO; + else + { + // Increase reduction for cut nodes + if (cutNode) + r += 2 * ONE_PLY; + + // Decrease reduction for moves that escape a capture. Filter out + // castling moves, because they are coded as "king captures rook" and + // hence break make_move(). Also use see() instead of see_sign(), + // because the destination square is empty. + else if ( type_of(move) == NORMAL + && type_of(pos.piece_on(to_sq(move))) != PAWN + && !pos.see_ge(make_move(to_sq(move), from_sq(move)), VALUE_ZERO)) + r -= 2 * ONE_PLY; + + ss->history = thisThread->history[moved_piece][to_sq(move)] + + (cmh ? (*cmh )[moved_piece][to_sq(move)] : VALUE_ZERO) + + (fmh ? (*fmh )[moved_piece][to_sq(move)] : VALUE_ZERO) + + (fmh2 ? (*fmh2)[moved_piece][to_sq(move)] : VALUE_ZERO) + + thisThread->fromTo.get(~pos.side_to_move(), move) + - 8000; // Correction factor + + // Decrease/increase reduction by comparing opponent's stat score + if (ss->history > VALUE_ZERO && (ss-1)->history < VALUE_ZERO) + r -= ONE_PLY; + + else if (ss->history < VALUE_ZERO && (ss-1)->history > VALUE_ZERO) + r += ONE_PLY; + + // Decrease/increase reduction for moves with a good/bad history + r = std::max(DEPTH_ZERO, (r / ONE_PLY - ss->history / 20000) * ONE_PLY); + } + + Depth d = std::max(newDepth - r, ONE_PLY); + + value = -search(pos, ss+1, -(alpha+1), -alpha, d, true); + + doFullDepthSearch = (value > alpha && d != newDepth); + } + else + doFullDepthSearch = !PvNode || moveCount > 1; + + // Step 16. Full depth search when LMR is skipped or fails high + if (doFullDepthSearch) + value = newDepth < ONE_PLY ? + givesCheck ? -qsearch(pos, ss+1, -(alpha+1), -alpha, DEPTH_ZERO) + : -qsearch(pos, ss+1, -(alpha+1), -alpha, DEPTH_ZERO) + : - search(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode); + + // 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 = newDepth < ONE_PLY ? + givesCheck ? -qsearch(pos, ss+1, -beta, -alpha, DEPTH_ZERO) + : -qsearch(pos, ss+1, -beta, -alpha, DEPTH_ZERO) + : - search(pos, ss+1, -beta, -alpha, newDepth, false); + } + + // Step 17. Undo move + pos.undo_move(move); + + assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); + + // Step 18. 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 (Signals.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.pv.resize(1); + + assert((ss+1)->pv); + + for (Move* m = (ss+1)->pv; *m != MOVE_NONE; ++m) + rm.pv.push_back(*m); + + // We record how often the best move has been changed in each + // iteration. This information is used for time management: When + // the best move changes frequently, we allocate some more time. + if (moveCount > 1 && thisThread == Threads.main()) + ++static_cast(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) + { + // If there is an easy move for this position, clear it if unstable + if ( PvNode + && thisThread == Threads.main() + && EasyMove.get(pos.key()) + && (move != EasyMove.get(pos.key()) || moveCount > 1)) + EasyMove.clear(); + + 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 (!captureOrPromotion && move != bestMove && 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 (Signals.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 || !inCheck || excludedMove || !MoveList(pos).size()); + + if (!moveCount) + bestValue = excludedMove ? alpha + : inCheck ? mated_in(ss->ply) : DrawValue[pos.side_to_move()]; + else if (bestMove) + { + int d = depth / ONE_PLY; + + // Quiet best move: update killers, history and countermoves + if (!pos.capture_or_promotion(bestMove)) + { + Value bonus = Value(d * d + 2 * d - 2); + update_stats(pos, ss, bestMove, quietsSearched, quietCount, bonus); + } + + // Extra penalty for a quiet TT move in previous ply when it gets refuted + if ((ss-1)->moveCount == 1 && !pos.captured_piece()) + { + Value penalty = Value(d * d + 4 * d + 1); + Square prevSq = to_sq((ss-1)->currentMove); + update_cm_stats(ss-1, pos.piece_on(prevSq), prevSq, -penalty); + } + } + // Bonus for prior countermove that caused the fail low + else if ( depth >= 3 * ONE_PLY + && !pos.captured_piece() + && is_ok((ss-1)->currentMove)) + { + int d = depth / ONE_PLY; + Value bonus = Value(d * d + 2 * d - 2); + Square prevSq = to_sq((ss-1)->currentMove); + update_cm_stats(ss-1, pos.piece_on(prevSq), prevSq, bonus); + } + + tte->save(posKey, value_to_tt(bestValue, ss->ply), + bestValue >= beta ? BOUND_LOWER : + PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER, + depth, bestMove, ss->staticEval, TT.generation()); + + assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); + + return bestValue; + } + + + // qsearch() is the quiescence search function, which is called by the main + // search function when the remaining depth is zero (or, to be more precise, + // less than ONE_PLY). + + template + Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { + + const bool PvNode = NT == PV; + + assert(InCheck == !!pos.checkers()); + assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE); + assert(PvNode || (alpha == beta - 1)); + assert(depth <= DEPTH_ZERO); + assert(depth / ONE_PLY * ONE_PLY == depth); + + Move pv[MAX_PLY+1]; + StateInfo st; + TTEntry* tte; + Key posKey; + Move ttMove, move, bestMove; + Value bestValue, value, ttValue, futilityValue, futilityBase, oldAlpha; + bool ttHit, givesCheck, evasionPrunable; + Depth ttDepth; + + 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; + } + + ss->currentMove = bestMove = MOVE_NONE; + ss->ply = (ss-1)->ply + 1; + + // Check for an instant draw or if the maximum ply has been reached + if (pos.is_draw() || ss->ply >= MAX_PLY) + return ss->ply >= MAX_PLY && !InCheck ? evaluate(pos) + : DrawValue[pos.side_to_move()]; + + 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 = InCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS + : DEPTH_QS_NO_CHECKS; + + // Transposition table lookup + posKey = pos.key(); + tte = TT.probe(posKey, ttHit); + ttMove = ttHit ? tte->move() : MOVE_NONE; + ttValue = ttHit ? value_from_tt(tte->value(), ss->ply) : VALUE_NONE; + + if ( !PvNode + && ttHit + && tte->depth() >= ttDepth + && ttValue != VALUE_NONE // Only in case of TT access race + && (ttValue >= beta ? (tte->bound() & BOUND_LOWER) + : (tte->bound() & BOUND_UPPER))) + return ttValue; + + // Evaluate the position statically + if (InCheck) + { + ss->staticEval = VALUE_NONE; + bestValue = futilityBase = -VALUE_INFINITE; + } + else + { + if (ttHit) + { + // Never assume anything on 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) + if (tte->bound() & (ttValue > bestValue ? BOUND_LOWER : BOUND_UPPER)) + bestValue = ttValue; + } + else + ss->staticEval = bestValue = + (ss-1)->currentMove != MOVE_NULL ? evaluate(pos) + : -(ss-1)->staticEval + 2 * Eval::Tempo; + + // Stand pat. Return immediately if static value is at least beta + if (bestValue >= beta) + { + if (!ttHit) + tte->save(pos.key(), value_to_tt(bestValue, ss->ply), BOUND_LOWER, + DEPTH_NONE, MOVE_NONE, ss->staticEval, TT.generation()); + + return bestValue; + } + + if (PvNode && bestValue > alpha) + alpha = bestValue; + + futilityBase = bestValue + 128; + } + + // 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 checks (only if depth >= DEPTH_QS_CHECKS) will + // be generated. + MovePicker mp(pos, ttMove, depth, 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 = type_of(move) == NORMAL && !pos.discovered_check_candidates() + ? pos.check_squares(type_of(pos.piece_on(from_sq(move)))) & to_sq(move) + : pos.gives_check(move); + + // Futility pruning + if ( !InCheck + && !givesCheck + && futilityBase > -VALUE_KNOWN_WIN + && !pos.advanced_pawn_push(move)) + { + assert(type_of(move) != ENPASSANT); // Due to !pos.advanced_pawn_push + + 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; + } + } + + // Detect non-capture evasions that are candidates to be pruned + evasionPrunable = InCheck + && bestValue > VALUE_MATED_IN_MAX_PLY + && !pos.capture(move); + + // Don't search moves with negative SEE values + if ( (!InCheck || evasionPrunable) + && type_of(move) != PROMOTION + && !pos.see_ge(move, VALUE_ZERO)) + 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)) + continue; + + ss->currentMove = move; + + // Make and search the move + pos.do_move(move, st, givesCheck); + value = givesCheck ? -qsearch(pos, ss+1, -beta, -alpha, depth - ONE_PLY) + : -qsearch(pos, ss+1, -beta, -alpha, depth - ONE_PLY); + 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) + { + 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; + bestMove = move; + } + else // Fail high + { + tte->save(posKey, value_to_tt(value, ss->ply), BOUND_LOWER, + ttDepth, move, ss->staticEval, TT.generation()); + + return value; + } + } + } + } + + // All legal moves have been searched. A special case: If we're in check + // and no legal moves were found, it is checkmate. + if (InCheck && bestValue == -VALUE_INFINITE) + return mated_in(ss->ply); // Plies to mate from the root + + tte->save(posKey, value_to_tt(bestValue, ss->ply), + PvNode && bestValue > oldAlpha ? BOUND_EXACT : BOUND_UPPER, + ttDepth, bestMove, ss->staticEval, TT.generation()); + + assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); + + return bestValue; + } + + + // value_to_tt() adjusts a mate score from "plies to mate from the root" to + // "plies to mate from the current position". Non-mate 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_MATE_IN_MAX_PLY ? v + ply + : v <= VALUE_MATED_IN_MAX_PLY ? v - ply : v; + } + + + // value_from_tt() is the inverse of value_to_tt(): It adjusts a mate score + // from the transposition table (which refers to the plies to mate/be mated + // from current position) to "plies to mate/be mated from the root". + + Value value_from_tt(Value v, int ply) { + + return v == VALUE_NONE ? VALUE_NONE + : v >= VALUE_MATE_IN_MAX_PLY ? v - ply + : v <= VALUE_MATED_IN_MAX_PLY ? v + ply : 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_cm_stats() updates countermove and follow-up move history + + void update_cm_stats(Stack* ss, Piece pc, Square s, Value bonus) { + + CounterMoveStats* cmh = (ss-1)->counterMoves; + CounterMoveStats* fmh1 = (ss-2)->counterMoves; + CounterMoveStats* fmh2 = (ss-4)->counterMoves; + + if (cmh) + cmh->update(pc, s, bonus); + + if (fmh1) + fmh1->update(pc, s, bonus); + + if (fmh2) + fmh2->update(pc, s, bonus); + } + + + // update_stats() updates killers, history, countermove and countermove plus + // follow-up move history when a new quiet best move is found. + + void update_stats(const Position& pos, Stack* ss, Move move, + Move* quiets, int quietsCnt, Value bonus) { + + if (ss->killers[0] != move) + { + ss->killers[1] = ss->killers[0]; + ss->killers[0] = move; + } + + Color c = pos.side_to_move(); + Thread* thisThread = pos.this_thread(); + thisThread->fromTo.update(c, move, bonus); + thisThread->history.update(pos.moved_piece(move), to_sq(move), bonus); + update_cm_stats(ss, pos.moved_piece(move), to_sq(move), bonus); + + if ((ss-1)->counterMoves) + { + Square prevSq = to_sq((ss-1)->currentMove); + thisThread->counterMoves.update(pos.piece_on(prevSq), prevSq, move); + } + + // Decrease all the other played quiet moves + for (int i = 0; i < quietsCnt; ++i) + { + thisThread->fromTo.update(c, quiets[i], -bonus); + thisThread->history.update(pos.moved_piece(quiets[i]), to_sq(quiets[i]), -bonus); + update_cm_stats(ss, pos.moved_piece(quiets[i]), to_sq(quiets[i]), -bonus); + } + } + + + // 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; + } + + + // 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 check_time() { + + static TimePoint lastInfoTime = now(); + + int elapsed = Time.elapsed(); + TimePoint tick = Limits.startTime + elapsed; + + if (tick - lastInfoTime >= 1000) + { + lastInfoTime = tick; + dbg_print(); + } + + // An engine may not stop pondering until told so by the GUI + if (Limits.ponder) + return; + + if ( (Limits.use_time_management() && elapsed > Time.maximum() - 10) + || (Limits.movetime && elapsed >= Limits.movetime) + || (Limits.nodes && Threads.nodes_searched() >= (uint64_t)Limits.nodes)) + Signals.stop = true; + } + +} // namespace + + +/// 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; + int 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 = (i <= PVIdx); + + if (depth == ONE_PLY && !updated) + continue; + + Depth d = updated ? depth : depth - ONE_PLY; + Value v = updated ? rootMoves[i].score : rootMoves[i].previousScore; + + bool tb = TB::RootInTB && abs(v) < VALUE_MATE - MAX_PLY; + v = tb ? TB::Score : v; + + if (ss.rdbuf()->in_avail()) // Not at first line + ss << "\n"; + + ss << "info" + << " depth " << d / ONE_PLY + << " seldepth " << pos.this_thread()->maxPly + << " multipv " << i + 1 + << " score " << UCI::value(v); + + if (!tb && i == PVIdx) + ss << (v >= beta ? " lowerbound" : v <= alpha ? " upperbound" : ""); + + ss << " nodes " << nodesSearched + << " nps " << nodesSearched * 1000 / elapsed; + + if (elapsed > 1000) // Earlier makes little sense + ss << " hashfull " << TT.hashfull(); + + ss << " tbhits " << tbHits + << " time " << elapsed + << " pv"; + + for (Move m : rootMoves[i].pv) + ss << " " << UCI::move(m, pos.is_chess960()); + } + + return ss.str(); +} + + +/// RootMove::extract_ponder_from_tt() is called in case we have no ponder move +/// before exiting the search, for instance, in case we stop the search during a +/// fail high at root. We try hard to have a ponder move to return to the GUI, +/// otherwise in case of 'ponder on' we have nothing to think on. + +bool RootMove::extract_ponder_from_tt(Position& pos) { + + StateInfo st; + bool ttHit; + + assert(pv.size() == 1); + + if (!pv[0]) + return false; + + pos.do_move(pv[0], st, pos.gives_check(pv[0])); + 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::filter_root_moves(Position& pos, Search::RootMoves& rootMoves) { + + RootInTB = false; + UseRule50 = Options["Syzygy50MoveRule"]; + ProbeDepth = Options["SyzygyProbeDepth"] * ONE_PLY; + Cardinality = Options["SyzygyProbeLimit"]; + + // Skip TB probing when no TB found: !TBLargest -> !TB::Cardinality + if (Cardinality > MaxCardinality) + { + Cardinality = MaxCardinality; + ProbeDepth = DEPTH_ZERO; + } + + if (Cardinality < popcount(pos.pieces()) || pos.can_castle(ANY_CASTLING)) + return; + + // If the current root position is in the tablebases, then RootMoves + // contains only moves that preserve the draw or the win. + RootInTB = root_probe(pos, rootMoves, TB::Score); + + if (RootInTB) + Cardinality = 0; // Do not probe tablebases during the search + + else // If DTZ tables are missing, use WDL tables as a fallback + { + // Filter out moves that do not preserve the draw or the win. + RootInTB = root_probe_wdl(pos, rootMoves, TB::Score); + + // Only probe during search if winning + if (RootInTB && TB::Score <= VALUE_DRAW) + Cardinality = 0; + } + + if (RootInTB && !UseRule50) + TB::Score = TB::Score > VALUE_DRAW ? VALUE_MATE - MAX_PLY - 1 + : TB::Score < VALUE_DRAW ? -VALUE_MATE + MAX_PLY + 1 + : VALUE_DRAW; +} diff --git a/src/search.h b/src/search.h new file mode 100644 index 0000000..d8051ec --- /dev/null +++ b/src/search.h @@ -0,0 +1,111 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef SEARCH_H_INCLUDED +#define SEARCH_H_INCLUDED + +#include +#include + +#include "misc.h" +#include "movepick.h" +#include "types.h" + +class Position; + +namespace Search { + +/// 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; + int ply; + Move currentMove; + Move excludedMove; + Move killers[2]; + Value staticEval; + Value history; + bool skipEarlyPruning; + int moveCount; + CounterMoveStats* counterMoves; +}; + + +/// 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 operator<(const RootMove& m) const { return m.score < score; } // Descending sort + bool operator==(const Move& m) const { return pv[0] == m; } + bool extract_ponder_from_tt(Position& pos); + + Value score = -VALUE_INFINITE; + Value previousScore = -VALUE_INFINITE; + 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, if we are in analysis mode or +/// if we have to ponder while it's our opponent's turn to move. + +struct LimitsType { + + LimitsType() { // Init explicitly due to broken value-initialization of non POD in MSVC + nodes = time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = + npmsec = movestogo = depth = movetime = mate = infinite = ponder = 0; + } + + bool use_time_management() const { + return !(mate | movetime | depth | nodes | infinite); + } + + std::vector searchmoves; + int time[COLOR_NB], inc[COLOR_NB], npmsec, movestogo, depth, movetime, mate, infinite, ponder; + int64_t nodes; + TimePoint startTime; +}; + + +/// SignalsType struct stores atomic flags updated during the search, typically +/// in an async fashion e.g. to stop the search by the GUI. + +struct SignalsType { + std::atomic_bool stop, stopOnPonderhit; +}; + +extern SignalsType Signals; +extern LimitsType Limits; + +void init(); +void clear(); +template uint64_t perft(Position& pos, Depth depth); + +} // namespace Search + +#endif // #ifndef SEARCH_H_INCLUDED diff --git a/src/syzygy/tbcore.cpp b/src/syzygy/tbcore.cpp new file mode 100644 index 0000000..f45da95 --- /dev/null +++ b/src/syzygy/tbcore.cpp @@ -0,0 +1,1378 @@ +/* + Copyright (c) 2011-2013 Ronald de Man + This file may be redistributed and/or modified without restrictions. + + tbcore.c contains engine-independent routines of the tablebase probing code. + This file should not need too much adaptation to add tablebase probing to + a particular engine, provided the engine is written in C or C++. +*/ + +#include +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#include +#endif +#include "tbcore.h" + +#define TBMAX_PIECE 254 +#define TBMAX_PAWN 256 +#define HSHMAX 5 + +#define Swap(a,b) {int tmp=a;a=b;b=tmp;} + +#define TB_PAWN 1 +#define TB_KNIGHT 2 +#define TB_BISHOP 3 +#define TB_ROOK 4 +#define TB_QUEEN 5 +#define TB_KING 6 + +#define TB_WPAWN TB_PAWN +#define TB_BPAWN (TB_PAWN | 8) + +static LOCK_T TB_mutex; + +static bool initialized = false; +static int num_paths = 0; +static char *path_string = NULL; +static char **paths = NULL; + +static int TBnum_piece, TBnum_pawn; +static struct TBEntry_piece TB_piece[TBMAX_PIECE]; +static struct TBEntry_pawn TB_pawn[TBMAX_PAWN]; + +static struct TBHashEntry TB_hash[1 << TBHASHBITS][HSHMAX]; + +#define DTZ_ENTRIES 64 + +static struct DTZTableEntry DTZ_table[DTZ_ENTRIES]; + +static void init_indices(void); +static uint64 calc_key_from_pcs(int *pcs, int mirror); +static void free_wdl_entry(struct TBEntry *entry); +static void free_dtz_entry(struct TBEntry *entry); + +static FD open_tb(const char *str, const char *suffix) +{ + int i; + FD fd; + char file[256]; + + for (i = 0; i < num_paths; i++) { + strcpy(file, paths[i]); + strcat(file, "/"); + strcat(file, str); + strcat(file, suffix); +#ifndef _WIN32 + fd = open(file, O_RDONLY); +#else + fd = CreateFile(file, GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); +#endif + if (fd != FD_ERR) return fd; + } + return FD_ERR; +} + +static void close_tb(FD fd) +{ +#ifndef _WIN32 + close(fd); +#else + CloseHandle(fd); +#endif +} + +static char *map_file(const char *name, const char *suffix, uint64 *mapping) +{ + FD fd = open_tb(name, suffix); + if (fd == FD_ERR) + return NULL; +#ifndef _WIN32 + struct stat statbuf; + fstat(fd, &statbuf); + *mapping = statbuf.st_size; + char *data = (char *)mmap(NULL, statbuf.st_size, PROT_READ, + MAP_SHARED, fd, 0); + if (data == (char *)(-1)) { + printf("Could not mmap() %s.\n", name); + exit(1); + } +#else + DWORD size_low, size_high; + size_low = GetFileSize(fd, &size_high); +// *size = ((uint64)size_high) << 32 | ((uint64)size_low); + HANDLE map = CreateFileMapping(fd, NULL, PAGE_READONLY, size_high, size_low, + NULL); + if (map == NULL) { + printf("CreateFileMapping() failed.\n"); + exit(1); + } + *mapping = (uint64)map; + char *data = (char *)MapViewOfFile(map, FILE_MAP_READ, 0, 0, 0); + if (data == NULL) { + printf("MapViewOfFile() failed, name = %s%s, error = %lu.\n", name, suffix, GetLastError()); + exit(1); + } +#endif + close_tb(fd); + return data; +} + +#ifndef _WIN32 +static void unmap_file(char *data, uint64 size) +{ + if (!data) return; + munmap(data, size); +} +#else +static void unmap_file(char *data, uint64 mapping) +{ + if (!data) return; + UnmapViewOfFile(data); + CloseHandle((HANDLE)mapping); +} +#endif + +static void add_to_hash(struct TBEntry *ptr, uint64 key) +{ + int i, hshidx; + + hshidx = key >> (64 - TBHASHBITS); + i = 0; + while (i < HSHMAX && TB_hash[hshidx][i].ptr) + i++; + if (i == HSHMAX) { + printf("HSHMAX too low!\n"); + exit(1); + } else { + TB_hash[hshidx][i].key = key; + TB_hash[hshidx][i].ptr = ptr; + } +} + +static char pchr[] = {'K', 'Q', 'R', 'B', 'N', 'P'}; + +static void init_tb(char *str) +{ + FD fd; + struct TBEntry *entry; + int i, j, pcs[16]; + uint64 key, key2; + int color; + char *s; + + fd = open_tb(str, WDLSUFFIX); + if (fd == FD_ERR) return; + close_tb(fd); + + for (i = 0; i < 16; i++) + pcs[i] = 0; + color = 0; + for (s = str; *s; s++) + switch (*s) { + case 'P': + pcs[TB_PAWN | color]++; + break; + case 'N': + pcs[TB_KNIGHT | color]++; + break; + case 'B': + pcs[TB_BISHOP | color]++; + break; + case 'R': + pcs[TB_ROOK | color]++; + break; + case 'Q': + pcs[TB_QUEEN | color]++; + break; + case 'K': + pcs[TB_KING | color]++; + break; + case 'v': + color = 0x08; + break; + } + for (i = 0; i < 8; i++) + if (pcs[i] != pcs[i+8]) + break; + key = calc_key_from_pcs(pcs, 0); + key2 = calc_key_from_pcs(pcs, 1); + if (pcs[TB_WPAWN] + pcs[TB_BPAWN] == 0) { + if (TBnum_piece == TBMAX_PIECE) { + printf("TBMAX_PIECE limit too low!\n"); + exit(1); + } + entry = (struct TBEntry *)&TB_piece[TBnum_piece++]; + } else { + if (TBnum_pawn == TBMAX_PAWN) { + printf("TBMAX_PAWN limit too low!\n"); + exit(1); + } + entry = (struct TBEntry *)&TB_pawn[TBnum_pawn++]; + } + entry->key = key; + entry->ready = 0; + entry->num = 0; + for (i = 0; i < 16; i++) + entry->num += (ubyte)pcs[i]; + entry->symmetric = (key == key2); + entry->has_pawns = (pcs[TB_WPAWN] + pcs[TB_BPAWN] > 0); + if (entry->num > Tablebases::MaxCardinality) + Tablebases::MaxCardinality = entry->num; + + if (entry->has_pawns) { + struct TBEntry_pawn *ptr = (struct TBEntry_pawn *)entry; + ptr->pawns[0] = (ubyte)pcs[TB_WPAWN]; + ptr->pawns[1] = (ubyte)pcs[TB_BPAWN]; + if (pcs[TB_BPAWN] > 0 + && (pcs[TB_WPAWN] == 0 || pcs[TB_BPAWN] < pcs[TB_WPAWN])) { + ptr->pawns[0] = (ubyte)pcs[TB_BPAWN]; + ptr->pawns[1] = (ubyte)pcs[TB_WPAWN]; + } + } else { + struct TBEntry_piece *ptr = (struct TBEntry_piece *)entry; + for (i = 0, j = 0; i < 16; i++) + if (pcs[i] == 1) j++; + if (j >= 3) ptr->enc_type = 0; + else if (j == 2) ptr->enc_type = 2; + else { /* only for suicide */ + j = 16; + for (i = 0; i < 16; i++) { + if (pcs[i] < j && pcs[i] > 1) j = pcs[i]; + ptr->enc_type = ubyte(1 + j); + } + } + } + add_to_hash(entry, key); + if (key2 != key) add_to_hash(entry, key2); +} + +void Tablebases::init(const std::string& path) +{ + char str[16]; + int i, j, k, l; + + if (initialized) { + free(path_string); + free(paths); + struct TBEntry *entry; + for (i = 0; i < TBnum_piece; i++) { + entry = (struct TBEntry *)&TB_piece[i]; + free_wdl_entry(entry); + } + for (i = 0; i < TBnum_pawn; i++) { + entry = (struct TBEntry *)&TB_pawn[i]; + free_wdl_entry(entry); + } + for (i = 0; i < DTZ_ENTRIES; i++) + if (DTZ_table[i].entry) + free_dtz_entry(DTZ_table[i].entry); + } else { + init_indices(); + initialized = true; + } + + const char *p = path.c_str(); + if (strlen(p) == 0 || !strcmp(p, "")) return; + path_string = (char *)malloc(strlen(p) + 1); + strcpy(path_string, p); + num_paths = 0; + for (i = 0;; i++) { + if (path_string[i] != SEP_CHAR) + num_paths++; + while (path_string[i] && path_string[i] != SEP_CHAR) + i++; + if (!path_string[i]) break; + path_string[i] = 0; + } + paths = (char **)malloc(num_paths * sizeof(char *)); + for (i = j = 0; i < num_paths; i++) { + while (!path_string[j]) j++; + paths[i] = &path_string[j]; + while (path_string[j]) j++; + } + + LOCK_INIT(TB_mutex); + + TBnum_piece = TBnum_pawn = 0; + MaxCardinality = 0; + + for (i = 0; i < (1 << TBHASHBITS); i++) + for (j = 0; j < HSHMAX; j++) { + TB_hash[i][j].key = 0ULL; + TB_hash[i][j].ptr = NULL; + } + + for (i = 0; i < DTZ_ENTRIES; i++) + DTZ_table[i].entry = NULL; + + for (i = 1; i < 6; i++) { + sprintf(str, "K%cvK", pchr[i]); + init_tb(str); + } + + for (i = 1; i < 6; i++) + for (j = i; j < 6; j++) { + sprintf(str, "K%cvK%c", pchr[i], pchr[j]); + init_tb(str); + } + + for (i = 1; i < 6; i++) + for (j = i; j < 6; j++) { + sprintf(str, "K%c%cvK", pchr[i], pchr[j]); + init_tb(str); + } + + for (i = 1; i < 6; i++) + for (j = i; j < 6; j++) + for (k = 1; k < 6; k++) { + sprintf(str, "K%c%cvK%c", pchr[i], pchr[j], pchr[k]); + init_tb(str); + } + + for (i = 1; i < 6; i++) + for (j = i; j < 6; j++) + for (k = j; k < 6; k++) { + sprintf(str, "K%c%c%cvK", pchr[i], pchr[j], pchr[k]); + init_tb(str); + } + + for (i = 1; i < 6; i++) + for (j = i; j < 6; j++) + for (k = i; k < 6; k++) + for (l = (i == k) ? j : k; l < 6; l++) { + sprintf(str, "K%c%cvK%c%c", pchr[i], pchr[j], pchr[k], pchr[l]); + init_tb(str); + } + + for (i = 1; i < 6; i++) + for (j = i; j < 6; j++) + for (k = j; k < 6; k++) + for (l = 1; l < 6; l++) { + sprintf(str, "K%c%c%cvK%c", pchr[i], pchr[j], pchr[k], pchr[l]); + init_tb(str); + } + + for (i = 1; i < 6; i++) + for (j = i; j < 6; j++) + for (k = j; k < 6; k++) + for (l = k; l < 6; l++) { + sprintf(str, "K%c%c%c%cvK", pchr[i], pchr[j], pchr[k], pchr[l]); + init_tb(str); + } + + printf("info string Found %d tablebases.\n", TBnum_piece + TBnum_pawn); +} + +static const signed char offdiag[] = { + 0,-1,-1,-1,-1,-1,-1,-1, + 1, 0,-1,-1,-1,-1,-1,-1, + 1, 1, 0,-1,-1,-1,-1,-1, + 1, 1, 1, 0,-1,-1,-1,-1, + 1, 1, 1, 1, 0,-1,-1,-1, + 1, 1, 1, 1, 1, 0,-1,-1, + 1, 1, 1, 1, 1, 1, 0,-1, + 1, 1, 1, 1, 1, 1, 1, 0 +}; + +static const ubyte triangle[] = { + 6, 0, 1, 2, 2, 1, 0, 6, + 0, 7, 3, 4, 4, 3, 7, 0, + 1, 3, 8, 5, 5, 8, 3, 1, + 2, 4, 5, 9, 9, 5, 4, 2, + 2, 4, 5, 9, 9, 5, 4, 2, + 1, 3, 8, 5, 5, 8, 3, 1, + 0, 7, 3, 4, 4, 3, 7, 0, + 6, 0, 1, 2, 2, 1, 0, 6 +}; + +static const ubyte invtriangle[] = { + 1, 2, 3, 10, 11, 19, 0, 9, 18, 27 +}; + +static const ubyte invdiag[] = { + 0, 9, 18, 27, 36, 45, 54, 63, + 7, 14, 21, 28, 35, 42, 49, 56 +}; + +static const ubyte flipdiag[] = { + 0, 8, 16, 24, 32, 40, 48, 56, + 1, 9, 17, 25, 33, 41, 49, 57, + 2, 10, 18, 26, 34, 42, 50, 58, + 3, 11, 19, 27, 35, 43, 51, 59, + 4, 12, 20, 28, 36, 44, 52, 60, + 5, 13, 21, 29, 37, 45, 53, 61, + 6, 14, 22, 30, 38, 46, 54, 62, + 7, 15, 23, 31, 39, 47, 55, 63 +}; + +static const ubyte lower[] = { + 28, 0, 1, 2, 3, 4, 5, 6, + 0, 29, 7, 8, 9, 10, 11, 12, + 1, 7, 30, 13, 14, 15, 16, 17, + 2, 8, 13, 31, 18, 19, 20, 21, + 3, 9, 14, 18, 32, 22, 23, 24, + 4, 10, 15, 19, 22, 33, 25, 26, + 5, 11, 16, 20, 23, 25, 34, 27, + 6, 12, 17, 21, 24, 26, 27, 35 +}; + +static const ubyte diag[] = { + 0, 0, 0, 0, 0, 0, 0, 8, + 0, 1, 0, 0, 0, 0, 9, 0, + 0, 0, 2, 0, 0, 10, 0, 0, + 0, 0, 0, 3, 11, 0, 0, 0, + 0, 0, 0, 12, 4, 0, 0, 0, + 0, 0, 13, 0, 0, 5, 0, 0, + 0, 14, 0, 0, 0, 0, 6, 0, + 15, 0, 0, 0, 0, 0, 0, 7 +}; + +static const ubyte flap[] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 6, 12, 18, 18, 12, 6, 0, + 1, 7, 13, 19, 19, 13, 7, 1, + 2, 8, 14, 20, 20, 14, 8, 2, + 3, 9, 15, 21, 21, 15, 9, 3, + 4, 10, 16, 22, 22, 16, 10, 4, + 5, 11, 17, 23, 23, 17, 11, 5, + 0, 0, 0, 0, 0, 0, 0, 0 +}; + +static const ubyte ptwist[] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 47, 35, 23, 11, 10, 22, 34, 46, + 45, 33, 21, 9, 8, 20, 32, 44, + 43, 31, 19, 7, 6, 18, 30, 42, + 41, 29, 17, 5, 4, 16, 28, 40, + 39, 27, 15, 3, 2, 14, 26, 38, + 37, 25, 13, 1, 0, 12, 24, 36, + 0, 0, 0, 0, 0, 0, 0, 0 +}; + +static const ubyte invflap[] = { + 8, 16, 24, 32, 40, 48, + 9, 17, 25, 33, 41, 49, + 10, 18, 26, 34, 42, 50, + 11, 19, 27, 35, 43, 51 +}; + +static const ubyte invptwist[] = { + 52, 51, 44, 43, 36, 35, 28, 27, 20, 19, 12, 11, + 53, 50, 45, 42, 37, 34, 29, 26, 21, 18, 13, 10, + 54, 49, 46, 41, 38, 33, 30, 25, 22, 17, 14, 9, + 55, 48, 47, 40, 39, 32, 31, 24, 23, 16, 15, 8 +}; + +static const ubyte file_to_file[] = { + 0, 1, 2, 3, 3, 2, 1, 0 +}; + +static const short KK_idx[10][64] = { + { -1, -1, -1, 0, 1, 2, 3, 4, + -1, -1, -1, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 33, + 34, 35, 36, 37, 38, 39, 40, 41, + 42, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 52, 53, 54, 55, 56, 57 }, + { 58, -1, -1, -1, 59, 60, 61, 62, + 63, -1, -1, -1, 64, 65, 66, 67, + 68, 69, 70, 71, 72, 73, 74, 75, + 76, 77, 78, 79, 80, 81, 82, 83, + 84, 85, 86, 87, 88, 89, 90, 91, + 92, 93, 94, 95, 96, 97, 98, 99, + 100,101,102,103,104,105,106,107, + 108,109,110,111,112,113,114,115}, + {116,117, -1, -1, -1,118,119,120, + 121,122, -1, -1, -1,123,124,125, + 126,127,128,129,130,131,132,133, + 134,135,136,137,138,139,140,141, + 142,143,144,145,146,147,148,149, + 150,151,152,153,154,155,156,157, + 158,159,160,161,162,163,164,165, + 166,167,168,169,170,171,172,173 }, + {174, -1, -1, -1,175,176,177,178, + 179, -1, -1, -1,180,181,182,183, + 184, -1, -1, -1,185,186,187,188, + 189,190,191,192,193,194,195,196, + 197,198,199,200,201,202,203,204, + 205,206,207,208,209,210,211,212, + 213,214,215,216,217,218,219,220, + 221,222,223,224,225,226,227,228 }, + {229,230, -1, -1, -1,231,232,233, + 234,235, -1, -1, -1,236,237,238, + 239,240, -1, -1, -1,241,242,243, + 244,245,246,247,248,249,250,251, + 252,253,254,255,256,257,258,259, + 260,261,262,263,264,265,266,267, + 268,269,270,271,272,273,274,275, + 276,277,278,279,280,281,282,283 }, + {284,285,286,287,288,289,290,291, + 292,293, -1, -1, -1,294,295,296, + 297,298, -1, -1, -1,299,300,301, + 302,303, -1, -1, -1,304,305,306, + 307,308,309,310,311,312,313,314, + 315,316,317,318,319,320,321,322, + 323,324,325,326,327,328,329,330, + 331,332,333,334,335,336,337,338 }, + { -1, -1,339,340,341,342,343,344, + -1, -1,345,346,347,348,349,350, + -1, -1,441,351,352,353,354,355, + -1, -1, -1,442,356,357,358,359, + -1, -1, -1, -1,443,360,361,362, + -1, -1, -1, -1, -1,444,363,364, + -1, -1, -1, -1, -1, -1,445,365, + -1, -1, -1, -1, -1, -1, -1,446 }, + { -1, -1, -1,366,367,368,369,370, + -1, -1, -1,371,372,373,374,375, + -1, -1, -1,376,377,378,379,380, + -1, -1, -1,447,381,382,383,384, + -1, -1, -1, -1,448,385,386,387, + -1, -1, -1, -1, -1,449,388,389, + -1, -1, -1, -1, -1, -1,450,390, + -1, -1, -1, -1, -1, -1, -1,451 }, + {452,391,392,393,394,395,396,397, + -1, -1, -1, -1,398,399,400,401, + -1, -1, -1, -1,402,403,404,405, + -1, -1, -1, -1,406,407,408,409, + -1, -1, -1, -1,453,410,411,412, + -1, -1, -1, -1, -1,454,413,414, + -1, -1, -1, -1, -1, -1,455,415, + -1, -1, -1, -1, -1, -1, -1,456 }, + {457,416,417,418,419,420,421,422, + -1,458,423,424,425,426,427,428, + -1, -1, -1, -1, -1,429,430,431, + -1, -1, -1, -1, -1,432,433,434, + -1, -1, -1, -1, -1,435,436,437, + -1, -1, -1, -1, -1,459,438,439, + -1, -1, -1, -1, -1, -1,460,440, + -1, -1, -1, -1, -1, -1, -1,461 } +}; + +static int binomial[5][64]; +static int pawnidx[5][24]; +static int pfactor[5][4]; + +static void init_indices(void) +{ + int i, j, k; + +// binomial[k-1][n] = Bin(n, k) + for (i = 0; i < 5; i++) + for (j = 0; j < 64; j++) { + int f = j; + int l = 1; + for (k = 1; k <= i; k++) { + f *= (j - k); + l *= (k + 1); + } + binomial[i][j] = f / l; + } + + for (i = 0; i < 5; i++) { + int s = 0; + for (j = 0; j < 6; j++) { + pawnidx[i][j] = s; + s += (i == 0) ? 1 : binomial[i - 1][ptwist[invflap[j]]]; + } + pfactor[i][0] = s; + s = 0; + for (; j < 12; j++) { + pawnidx[i][j] = s; + s += (i == 0) ? 1 : binomial[i - 1][ptwist[invflap[j]]]; + } + pfactor[i][1] = s; + s = 0; + for (; j < 18; j++) { + pawnidx[i][j] = s; + s += (i == 0) ? 1 : binomial[i - 1][ptwist[invflap[j]]]; + } + pfactor[i][2] = s; + s = 0; + for (; j < 24; j++) { + pawnidx[i][j] = s; + s += (i == 0) ? 1 : binomial[i - 1][ptwist[invflap[j]]]; + } + pfactor[i][3] = s; + } +} + +static uint64 encode_piece(struct TBEntry_piece *ptr, ubyte *norm, int *pos, int *factor) +{ + uint64 idx; + int i, j, k, m, l, p; + int n = ptr->num; + + if (pos[0] & 0x04) { + for (i = 0; i < n; i++) + pos[i] ^= 0x07; + } + if (pos[0] & 0x20) { + for (i = 0; i < n; i++) + pos[i] ^= 0x38; + } + + for (i = 0; i < n; i++) + if (offdiag[pos[i]]) break; + if (i < (ptr->enc_type == 0 ? 3 : 2) && offdiag[pos[i]] > 0) + for (i = 0; i < n; i++) + pos[i] = flipdiag[pos[i]]; + + switch (ptr->enc_type) { + + case 0: /* 111 */ + i = (pos[1] > pos[0]); + j = (pos[2] > pos[0]) + (pos[2] > pos[1]); + + if (offdiag[pos[0]]) + idx = triangle[pos[0]] * 63*62 + (pos[1] - i) * 62 + (pos[2] - j); + else if (offdiag[pos[1]]) + idx = 6*63*62 + diag[pos[0]] * 28*62 + lower[pos[1]] * 62 + pos[2] - j; + else if (offdiag[pos[2]]) + idx = 6*63*62 + 4*28*62 + (diag[pos[0]]) * 7*28 + (diag[pos[1]] - i) * 28 + lower[pos[2]]; + else + idx = 6*63*62 + 4*28*62 + 4*7*28 + (diag[pos[0]] * 7*6) + (diag[pos[1]] - i) * 6 + (diag[pos[2]] - j); + i = 3; + break; + + case 1: /* K3 */ + j = (pos[2] > pos[0]) + (pos[2] > pos[1]); + + idx = KK_idx[triangle[pos[0]]][pos[1]]; + if (idx < 441) + idx = idx + 441 * (pos[2] - j); + else { + idx = 441*62 + (idx - 441) + 21 * lower[pos[2]]; + if (!offdiag[pos[2]]) + idx -= j * 21; + } + i = 3; + break; + + default: /* K2 */ + idx = KK_idx[triangle[pos[0]]][pos[1]]; + i = 2; + break; + } + idx *= factor[0]; + + for (; i < n;) { + int t = norm[i]; + for (j = i; j < i + t; j++) + for (k = j + 1; k < i + t; k++) + if (pos[j] > pos[k]) Swap(pos[j], pos[k]); + int s = 0; + for (m = i; m < i + t; m++) { + p = pos[m]; + for (l = 0, j = 0; l < i; l++) + j += (p > pos[l]); + s += binomial[m - i][p - j]; + } + idx += ((uint64)s) * ((uint64)factor[i]); + i += t; + } + + return idx; +} + +// determine file of leftmost pawn and sort pawns +static int pawn_file(struct TBEntry_pawn *ptr, int *pos) +{ + int i; + + for (i = 1; i < ptr->pawns[0]; i++) + if (flap[pos[0]] > flap[pos[i]]) + Swap(pos[0], pos[i]); + + return file_to_file[pos[0] & 0x07]; +} + +static uint64 encode_pawn(struct TBEntry_pawn *ptr, ubyte *norm, int *pos, int *factor) +{ + uint64 idx; + int i, j, k, m, s, t; + int n = ptr->num; + + if (pos[0] & 0x04) + for (i = 0; i < n; i++) + pos[i] ^= 0x07; + + for (i = 1; i < ptr->pawns[0]; i++) + for (j = i + 1; j < ptr->pawns[0]; j++) + if (ptwist[pos[i]] < ptwist[pos[j]]) + Swap(pos[i], pos[j]); + + t = ptr->pawns[0] - 1; + idx = pawnidx[t][flap[pos[0]]]; + for (i = t; i > 0; i--) + idx += binomial[t - i][ptwist[pos[i]]]; + idx *= factor[0]; + +// remaining pawns + i = ptr->pawns[0]; + t = i + ptr->pawns[1]; + if (t > i) { + for (j = i; j < t; j++) + for (k = j + 1; k < t; k++) + if (pos[j] > pos[k]) Swap(pos[j], pos[k]); + s = 0; + for (m = i; m < t; m++) { + int p = pos[m]; + for (k = 0, j = 0; k < i; k++) + j += (p > pos[k]); + s += binomial[m - i][p - j - 8]; + } + idx += ((uint64)s) * ((uint64)factor[i]); + i = t; + } + + for (; i < n;) { + t = norm[i]; + for (j = i; j < i + t; j++) + for (k = j + 1; k < i + t; k++) + if (pos[j] > pos[k]) Swap(pos[j], pos[k]); + s = 0; + for (m = i; m < i + t; m++) { + int p = pos[m]; + for (k = 0, j = 0; k < i; k++) + j += (p > pos[k]); + s += binomial[m - i][p - j]; + } + idx += ((uint64)s) * ((uint64)factor[i]); + i += t; + } + + return idx; +} + +// place k like pieces on n squares +static int subfactor(int k, int n) +{ + int i, f, l; + + f = n; + l = 1; + for (i = 1; i < k; i++) { + f *= n - i; + l *= i + 1; + } + + return f / l; +} + +static uint64 calc_factors_piece(int *factor, int num, int order, ubyte *norm, ubyte enc_type) +{ + int i, k, n; + uint64 f; + static int pivfac[] = { 31332, 28056, 462 }; + + n = 64 - norm[0]; + + f = 1; + for (i = norm[0], k = 0; i < num || k == order; k++) { + if (k == order) { + factor[0] = static_cast(f); + f *= pivfac[enc_type]; + } else { + factor[i] = static_cast(f); + f *= subfactor(norm[i], n); + n -= norm[i]; + i += norm[i]; + } + } + + return f; +} + +static uint64 calc_factors_pawn(int *factor, int num, int order, int order2, ubyte *norm, int file) +{ + int i, k, n; + uint64 f; + + i = norm[0]; + if (order2 < 0x0f) i += norm[i]; + n = 64 - i; + + f = 1; + for (k = 0; i < num || k == order || k == order2; k++) { + if (k == order) { + factor[0] = static_cast(f); + f *= pfactor[norm[0] - 1][file]; + } else if (k == order2) { + factor[norm[0]] = static_cast(f); + f *= subfactor(norm[norm[0]], 48 - norm[0]); + } else { + factor[i] = static_cast(f); + f *= subfactor(norm[i], n); + n -= norm[i]; + i += norm[i]; + } + } + + return f; +} + +static void set_norm_piece(struct TBEntry_piece *ptr, ubyte *norm, ubyte *pieces) +{ + int i, j; + + for (i = 0; i < ptr->num; i++) + norm[i] = 0; + + switch (ptr->enc_type) { + case 0: + norm[0] = 3; + break; + case 2: + norm[0] = 2; + break; + default: + norm[0] = ubyte(ptr->enc_type - 1); + break; + } + + for (i = norm[0]; i < ptr->num; i += norm[i]) + for (j = i; j < ptr->num && pieces[j] == pieces[i]; j++) + norm[i]++; +} + +static void set_norm_pawn(struct TBEntry_pawn *ptr, ubyte *norm, ubyte *pieces) +{ + int i, j; + + for (i = 0; i < ptr->num; i++) + norm[i] = 0; + + norm[0] = ptr->pawns[0]; + if (ptr->pawns[1]) norm[ptr->pawns[0]] = ptr->pawns[1]; + + for (i = ptr->pawns[0] + ptr->pawns[1]; i < ptr->num; i += norm[i]) + for (j = i; j < ptr->num && pieces[j] == pieces[i]; j++) + norm[i]++; +} + +static void setup_pieces_piece(struct TBEntry_piece *ptr, unsigned char *data, uint64 *tb_size) +{ + int i; + int order; + + for (i = 0; i < ptr->num; i++) + ptr->pieces[0][i] = ubyte(data[i + 1] & 0x0f); + order = data[0] & 0x0f; + set_norm_piece(ptr, ptr->norm[0], ptr->pieces[0]); + tb_size[0] = calc_factors_piece(ptr->factor[0], ptr->num, order, ptr->norm[0], ptr->enc_type); + + for (i = 0; i < ptr->num; i++) + ptr->pieces[1][i] = ubyte(data[i + 1] >> 4); + order = data[0] >> 4; + set_norm_piece(ptr, ptr->norm[1], ptr->pieces[1]); + tb_size[1] = calc_factors_piece(ptr->factor[1], ptr->num, order, ptr->norm[1], ptr->enc_type); +} + +static void setup_pieces_piece_dtz(struct DTZEntry_piece *ptr, unsigned char *data, uint64 *tb_size) +{ + int i; + int order; + + for (i = 0; i < ptr->num; i++) + ptr->pieces[i] = ubyte(data[i + 1] & 0x0f); + order = data[0] & 0x0f; + set_norm_piece((struct TBEntry_piece *)ptr, ptr->norm, ptr->pieces); + tb_size[0] = calc_factors_piece(ptr->factor, ptr->num, order, ptr->norm, ptr->enc_type); +} + +static void setup_pieces_pawn(struct TBEntry_pawn *ptr, unsigned char *data, uint64 *tb_size, int f) +{ + int i, j; + int order, order2; + + j = 1 + (ptr->pawns[1] > 0); + order = data[0] & 0x0f; + order2 = ptr->pawns[1] ? (data[1] & 0x0f) : 0x0f; + for (i = 0; i < ptr->num; i++) + ptr->file[f].pieces[0][i] = ubyte(data[i + j] & 0x0f); + set_norm_pawn(ptr, ptr->file[f].norm[0], ptr->file[f].pieces[0]); + tb_size[0] = calc_factors_pawn(ptr->file[f].factor[0], ptr->num, order, order2, ptr->file[f].norm[0], f); + + order = data[0] >> 4; + order2 = ptr->pawns[1] ? (data[1] >> 4) : 0x0f; + for (i = 0; i < ptr->num; i++) + ptr->file[f].pieces[1][i] = ubyte(data[i + j] >> 4); + set_norm_pawn(ptr, ptr->file[f].norm[1], ptr->file[f].pieces[1]); + tb_size[1] = calc_factors_pawn(ptr->file[f].factor[1], ptr->num, order, order2, ptr->file[f].norm[1], f); +} + +static void setup_pieces_pawn_dtz(struct DTZEntry_pawn *ptr, unsigned char *data, uint64 *tb_size, int f) +{ + int i, j; + int order, order2; + + j = 1 + (ptr->pawns[1] > 0); + order = data[0] & 0x0f; + order2 = ptr->pawns[1] ? (data[1] & 0x0f) : 0x0f; + for (i = 0; i < ptr->num; i++) + ptr->file[f].pieces[i] = ubyte(data[i + j] & 0x0f); + set_norm_pawn((struct TBEntry_pawn *)ptr, ptr->file[f].norm, ptr->file[f].pieces); + tb_size[0] = calc_factors_pawn(ptr->file[f].factor, ptr->num, order, order2, ptr->file[f].norm, f); +} + +static void calc_symlen(struct PairsData *d, int s, char *tmp) +{ + int s1, s2; + + ubyte* w = d->sympat + 3 * s; + s2 = (w[2] << 4) | (w[1] >> 4); + if (s2 == 0x0fff) + d->symlen[s] = 0; + else { + s1 = ((w[1] & 0xf) << 8) | w[0]; + if (!tmp[s1]) calc_symlen(d, s1, tmp); + if (!tmp[s2]) calc_symlen(d, s2, tmp); + d->symlen[s] = ubyte(d->symlen[s1] + d->symlen[s2] + 1); + } + tmp[s] = 1; +} + +ushort ReadUshort(ubyte* d) { + return ushort(d[0] | (d[1] << 8)); +} + +uint32 ReadUint32(ubyte* d) { + return d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24); +} + +static struct PairsData *setup_pairs(unsigned char *data, uint64 tb_size, uint64 *size, unsigned char **next, ubyte *flags, int wdl) +{ + struct PairsData *d; + int i; + + *flags = data[0]; + if (data[0] & 0x80) { + d = (struct PairsData *)malloc(sizeof(struct PairsData)); + d->idxbits = 0; + if (wdl) + d->min_len = data[1]; + else + d->min_len = 0; + *next = data + 2; + size[0] = size[1] = size[2] = 0; + return d; + } + + int blocksize = data[1]; + int idxbits = data[2]; + int real_num_blocks = ReadUint32(&data[4]); + int num_blocks = real_num_blocks + *(ubyte *)(&data[3]); + int max_len = data[8]; + int min_len = data[9]; + int h = max_len - min_len + 1; + int num_syms = ReadUshort(&data[10 + 2 * h]); + d = (struct PairsData *)malloc(sizeof(struct PairsData) + (h - 1) * sizeof(base_t) + num_syms); + d->blocksize = blocksize; + d->idxbits = idxbits; + d->offset = (ushort*)(&data[10]); + d->symlen = ((ubyte *)d) + sizeof(struct PairsData) + (h - 1) * sizeof(base_t); + d->sympat = &data[12 + 2 * h]; + d->min_len = min_len; + *next = &data[12 + 2 * h + 3 * num_syms + (num_syms & 1)]; + + uint64 num_indices = (tb_size + (1ULL << idxbits) - 1) >> idxbits; + size[0] = 6ULL * num_indices; + size[1] = 2ULL * num_blocks; + size[2] = (1ULL << blocksize) * real_num_blocks; + + // char tmp[num_syms]; + char tmp[4096]; + for (i = 0; i < num_syms; i++) + tmp[i] = 0; + for (i = 0; i < num_syms; i++) + if (!tmp[i]) + calc_symlen(d, i, tmp); + + d->base[h - 1] = 0; + for (i = h - 2; i >= 0; i--) + d->base[i] = (d->base[i + 1] + ReadUshort((ubyte*)(d->offset + i)) - ReadUshort((ubyte*)(d->offset + i + 1))) / 2; + for (i = 0; i < h; i++) + d->base[i] <<= 64 - (min_len + i); + + d->offset -= d->min_len; + + return d; +} + +static int init_table_wdl(struct TBEntry *entry, char *str) +{ + ubyte *next; + int f, s; + uint64 tb_size[8]; + uint64 size[8 * 3]; + ubyte flags; + + // first mmap the table into memory + + entry->data = map_file(str, WDLSUFFIX, &entry->mapping); + if (!entry->data) { + printf("Could not find %s" WDLSUFFIX, str); + return 0; + } + + ubyte *data = (ubyte *)entry->data; + if (data[0] != WDL_MAGIC[0] || + data[1] != WDL_MAGIC[1] || + data[2] != WDL_MAGIC[2] || + data[3] != WDL_MAGIC[3]) { + printf("Corrupted table.\n"); + unmap_file(entry->data, entry->mapping); + entry->data = 0; + return 0; + } + + int split = data[4] & 0x01; + int files = data[4] & 0x02 ? 4 : 1; + + data += 5; + + if (!entry->has_pawns) { + struct TBEntry_piece *ptr = (struct TBEntry_piece *)entry; + setup_pieces_piece(ptr, data, &tb_size[0]); + data += ptr->num + 1; + data += ((uintptr_t)data) & 0x01; + + ptr->precomp[0] = setup_pairs(data, tb_size[0], &size[0], &next, &flags, 1); + data = next; + if (split) { + ptr->precomp[1] = setup_pairs(data, tb_size[1], &size[3], &next, &flags, 1); + data = next; + } else + ptr->precomp[1] = NULL; + + ptr->precomp[0]->indextable = (char *)data; + data += size[0]; + if (split) { + ptr->precomp[1]->indextable = (char *)data; + data += size[3]; + } + + ptr->precomp[0]->sizetable = (ushort *)data; + data += size[1]; + if (split) { + ptr->precomp[1]->sizetable = (ushort *)data; + data += size[4]; + } + + data = (ubyte *)((((uintptr_t)data) + 0x3f) & ~0x3f); + ptr->precomp[0]->data = data; + data += size[2]; + if (split) { + data = (ubyte *)((((uintptr_t)data) + 0x3f) & ~0x3f); + ptr->precomp[1]->data = data; + } + } else { + struct TBEntry_pawn *ptr = (struct TBEntry_pawn *)entry; + s = 1 + (ptr->pawns[1] > 0); + for (f = 0; f < 4; f++) { + setup_pieces_pawn((struct TBEntry_pawn *)ptr, data, &tb_size[2 * f], f); + data += ptr->num + s; + } + data += ((uintptr_t)data) & 0x01; + + for (f = 0; f < files; f++) { + ptr->file[f].precomp[0] = setup_pairs(data, tb_size[2 * f], &size[6 * f], &next, &flags, 1); + data = next; + if (split) { + ptr->file[f].precomp[1] = setup_pairs(data, tb_size[2 * f + 1], &size[6 * f + 3], &next, &flags, 1); + data = next; + } else + ptr->file[f].precomp[1] = NULL; + } + + for (f = 0; f < files; f++) { + ptr->file[f].precomp[0]->indextable = (char *)data; + data += size[6 * f]; + if (split) { + ptr->file[f].precomp[1]->indextable = (char *)data; + data += size[6 * f + 3]; + } + } + + for (f = 0; f < files; f++) { + ptr->file[f].precomp[0]->sizetable = (ushort *)data; + data += size[6 * f + 1]; + if (split) { + ptr->file[f].precomp[1]->sizetable = (ushort *)data; + data += size[6 * f + 4]; + } + } + + for (f = 0; f < files; f++) { + data = (ubyte *)((((uintptr_t)data) + 0x3f) & ~0x3f); + ptr->file[f].precomp[0]->data = data; + data += size[6 * f + 2]; + if (split) { + data = (ubyte *)((((uintptr_t)data) + 0x3f) & ~0x3f); + ptr->file[f].precomp[1]->data = data; + data += size[6 * f + 5]; + } + } + } + + return 1; +} + +static int init_table_dtz(struct TBEntry *entry) +{ + ubyte *data = (ubyte *)entry->data; + ubyte *next; + int f, s; + uint64 tb_size[4]; + uint64 size[4 * 3]; + + if (!data) + return 0; + + if (data[0] != DTZ_MAGIC[0] || + data[1] != DTZ_MAGIC[1] || + data[2] != DTZ_MAGIC[2] || + data[3] != DTZ_MAGIC[3]) { + printf("Corrupted table.\n"); + return 0; + } + + int files = data[4] & 0x02 ? 4 : 1; + + data += 5; + + if (!entry->has_pawns) { + struct DTZEntry_piece *ptr = (struct DTZEntry_piece *)entry; + setup_pieces_piece_dtz(ptr, data, &tb_size[0]); + data += ptr->num + 1; + data += ((uintptr_t)data) & 0x01; + + ptr->precomp = setup_pairs(data, tb_size[0], &size[0], &next, &(ptr->flags), 0); + data = next; + + ptr->map = data; + if (ptr->flags & 2) { + int i; + for (i = 0; i < 4; i++) { + ptr->map_idx[i] = static_cast(data + 1 - ptr->map); + data += 1 + data[0]; + } + data += ((uintptr_t)data) & 0x01; + } + + ptr->precomp->indextable = (char *)data; + data += size[0]; + + ptr->precomp->sizetable = (ushort *)data; + data += size[1]; + + data = (ubyte *)((((uintptr_t)data) + 0x3f) & ~0x3f); + ptr->precomp->data = data; + data += size[2]; + } else { + struct DTZEntry_pawn *ptr = (struct DTZEntry_pawn *)entry; + s = 1 + (ptr->pawns[1] > 0); + for (f = 0; f < 4; f++) { + setup_pieces_pawn_dtz(ptr, data, &tb_size[f], f); + data += ptr->num + s; + } + data += ((uintptr_t)data) & 0x01; + + for (f = 0; f < files; f++) { + ptr->file[f].precomp = setup_pairs(data, tb_size[f], &size[3 * f], &next, &(ptr->flags[f]), 0); + data = next; + } + + ptr->map = data; + for (f = 0; f < files; f++) { + if (ptr->flags[f] & 2) { + int i; + for (i = 0; i < 4; i++) { + ptr->map_idx[f][i] = static_cast(data + 1 - ptr->map); + data += 1 + data[0]; + } + } + } + data += ((uintptr_t)data) & 0x01; + + for (f = 0; f < files; f++) { + ptr->file[f].precomp->indextable = (char *)data; + data += size[3 * f]; + } + + for (f = 0; f < files; f++) { + ptr->file[f].precomp->sizetable = (ushort *)data; + data += size[3 * f + 1]; + } + + for (f = 0; f < files; f++) { + data = (ubyte *)((((uintptr_t)data) + 0x3f) & ~0x3f); + ptr->file[f].precomp->data = data; + data += size[3 * f + 2]; + } + } + + return 1; +} + +template +static ubyte decompress_pairs(struct PairsData *d, uint64 idx) +{ + if (!d->idxbits) + return ubyte(d->min_len); + + uint32 mainidx = static_cast(idx >> d->idxbits); + int litidx = (idx & ((1ULL << d->idxbits) - 1)) - (1ULL << (d->idxbits - 1)); + uint32 block = *(uint32 *)(d->indextable + 6 * mainidx); + if (!LittleEndian) + block = BSWAP32(block); + + ushort idxOffset = *(ushort *)(d->indextable + 6 * mainidx + 4); + if (!LittleEndian) + idxOffset = ushort((idxOffset << 8) | (idxOffset >> 8)); + litidx += idxOffset; + + if (litidx < 0) { + do { + litidx += d->sizetable[--block] + 1; + } while (litidx < 0); + } else { + while (litidx > d->sizetable[block]) + litidx -= d->sizetable[block++] + 1; + } + + uint32 *ptr = (uint32 *)(d->data + (block << d->blocksize)); + + int m = d->min_len; + ushort *offset = d->offset; + base_t *base = d->base - m; + ubyte *symlen = d->symlen; + int sym, bitcnt; + + uint64 code = *((uint64 *)ptr); + if (LittleEndian) + code = BSWAP64(code); + + ptr += 2; + bitcnt = 0; // number of "empty bits" in code + for (;;) { + int l = m; + while (code < base[l]) l++; + sym = offset[l]; + if (!LittleEndian) + sym = ((sym & 0xff) << 8) | (sym >> 8); + sym += static_cast((code - base[l]) >> (64 - l)); + if (litidx < (int)symlen[sym] + 1) break; + litidx -= (int)symlen[sym] + 1; + code <<= l; + bitcnt += l; + if (bitcnt >= 32) { + bitcnt -= 32; + uint32 tmp = *ptr++; + if (LittleEndian) + tmp = BSWAP32(tmp); + code |= ((uint64)tmp) << bitcnt; + } + } + + ubyte *sympat = d->sympat; + while (symlen[sym] != 0) { + ubyte* w = sympat + (3 * sym); + int s1 = ((w[1] & 0xf) << 8) | w[0]; + if (litidx < (int)symlen[s1] + 1) + sym = s1; + else { + litidx -= (int)symlen[s1] + 1; + sym = (w[2] << 4) | (w[1] >> 4); + } + } + + return sympat[3 * sym]; +} + +void load_dtz_table(char *str, uint64 key1, uint64 key2) +{ + int i; + struct TBEntry *ptr, *ptr3; + struct TBHashEntry *ptr2; + + DTZ_table[0].key1 = key1; + DTZ_table[0].key2 = key2; + DTZ_table[0].entry = NULL; + + // find corresponding WDL entry + ptr2 = TB_hash[key1 >> (64 - TBHASHBITS)]; + for (i = 0; i < HSHMAX; i++) + if (ptr2[i].key == key1) break; + if (i == HSHMAX) return; + ptr = ptr2[i].ptr; + + ptr3 = (struct TBEntry *)malloc(ptr->has_pawns + ? sizeof(struct DTZEntry_pawn) + : sizeof(struct DTZEntry_piece)); + + ptr3->data = map_file(str, DTZSUFFIX, &ptr3->mapping); + ptr3->key = ptr->key; + ptr3->num = ptr->num; + ptr3->symmetric = ptr->symmetric; + ptr3->has_pawns = ptr->has_pawns; + if (ptr3->has_pawns) { + struct DTZEntry_pawn *entry = (struct DTZEntry_pawn *)ptr3; + entry->pawns[0] = ((struct TBEntry_pawn *)ptr)->pawns[0]; + entry->pawns[1] = ((struct TBEntry_pawn *)ptr)->pawns[1]; + } else { + struct DTZEntry_piece *entry = (struct DTZEntry_piece *)ptr3; + entry->enc_type = ((struct TBEntry_piece *)ptr)->enc_type; + } + if (!init_table_dtz(ptr3)) + free(ptr3); + else + DTZ_table[0].entry = ptr3; +} + +static void free_wdl_entry(struct TBEntry *entry) +{ + unmap_file(entry->data, entry->mapping); + if (!entry->has_pawns) { + struct TBEntry_piece *ptr = (struct TBEntry_piece *)entry; + free(ptr->precomp[0]); + if (ptr->precomp[1]) + free(ptr->precomp[1]); + } else { + struct TBEntry_pawn *ptr = (struct TBEntry_pawn *)entry; + int f; + for (f = 0; f < 4; f++) { + free(ptr->file[f].precomp[0]); + if (ptr->file[f].precomp[1]) + free(ptr->file[f].precomp[1]); + } + } +} + +static void free_dtz_entry(struct TBEntry *entry) +{ + unmap_file(entry->data, entry->mapping); + if (!entry->has_pawns) { + struct DTZEntry_piece *ptr = (struct DTZEntry_piece *)entry; + free(ptr->precomp); + } else { + struct DTZEntry_pawn *ptr = (struct DTZEntry_pawn *)entry; + int f; + for (f = 0; f < 4; f++) + free(ptr->file[f].precomp); + } + free(entry); +} + +static int wdl_to_map[5] = { 1, 3, 0, 2, 0 }; +static ubyte pa_flags[5] = { 8, 0, 0, 0, 4 }; + diff --git a/src/syzygy/tbcore.h b/src/syzygy/tbcore.h new file mode 100644 index 0000000..cdaf2ac --- /dev/null +++ b/src/syzygy/tbcore.h @@ -0,0 +1,169 @@ +/* + Copyright (c) 2011-2013 Ronald de Man +*/ + +#ifndef TBCORE_H +#define TBCORE_H + +#ifndef _WIN32 +#include +#define SEP_CHAR ':' +#define FD int +#define FD_ERR -1 +#else +#include +#define SEP_CHAR ';' +#define FD HANDLE +#define FD_ERR INVALID_HANDLE_VALUE +#endif + +#ifndef _WIN32 +#define LOCK_T pthread_mutex_t +#define LOCK_INIT(x) pthread_mutex_init(&(x), NULL) +#define LOCK(x) pthread_mutex_lock(&(x)) +#define UNLOCK(x) pthread_mutex_unlock(&(x)) +#else +#define LOCK_T HANDLE +#define LOCK_INIT(x) do { x = CreateMutex(NULL, FALSE, NULL); } while (0) +#define LOCK(x) WaitForSingleObject(x, INFINITE) +#define UNLOCK(x) ReleaseMutex(x) +#endif + +#ifndef _MSC_VER +#define BSWAP32(v) __builtin_bswap32(v) +#define BSWAP64(v) __builtin_bswap64(v) +#else +#define BSWAP32(v) _byteswap_ulong(v) +#define BSWAP64(v) _byteswap_uint64(v) +#endif + +#define WDLSUFFIX ".rtbw" +#define DTZSUFFIX ".rtbz" +#define WDLDIR "RTBWDIR" +#define DTZDIR "RTBZDIR" +#define TBPIECES 6 + +typedef unsigned long long uint64; +typedef unsigned int uint32; +typedef unsigned char ubyte; +typedef unsigned short ushort; + +const ubyte WDL_MAGIC[4] = { 0x71, 0xe8, 0x23, 0x5d }; +const ubyte DTZ_MAGIC[4] = { 0xd7, 0x66, 0x0c, 0xa5 }; + +#define TBHASHBITS 10 + +struct TBHashEntry; + +typedef uint64 base_t; + +struct PairsData { + char *indextable; + ushort *sizetable; + ubyte *data; + ushort *offset; + ubyte *symlen; + ubyte *sympat; + int blocksize; + int idxbits; + int min_len; + base_t base[1]; // C++ complains about base[]... +}; + +struct TBEntry { + char *data; + uint64 key; + uint64 mapping; + ubyte ready; + ubyte num; + ubyte symmetric; + ubyte has_pawns; +} +#ifndef _WIN32 +__attribute__((__may_alias__)) +#endif +; + +struct TBEntry_piece { + char *data; + uint64 key; + uint64 mapping; + ubyte ready; + ubyte num; + ubyte symmetric; + ubyte has_pawns; + ubyte enc_type; + struct PairsData *precomp[2]; + int factor[2][TBPIECES]; + ubyte pieces[2][TBPIECES]; + ubyte norm[2][TBPIECES]; +}; + +struct TBEntry_pawn { + char *data; + uint64 key; + uint64 mapping; + ubyte ready; + ubyte num; + ubyte symmetric; + ubyte has_pawns; + ubyte pawns[2]; + struct { + struct PairsData *precomp[2]; + int factor[2][TBPIECES]; + ubyte pieces[2][TBPIECES]; + ubyte norm[2][TBPIECES]; + } file[4]; +}; + +struct DTZEntry_piece { + char *data; + uint64 key; + uint64 mapping; + ubyte ready; + ubyte num; + ubyte symmetric; + ubyte has_pawns; + ubyte enc_type; + struct PairsData *precomp; + int factor[TBPIECES]; + ubyte pieces[TBPIECES]; + ubyte norm[TBPIECES]; + ubyte flags; // accurate, mapped, side + ushort map_idx[4]; + ubyte *map; +}; + +struct DTZEntry_pawn { + char *data; + uint64 key; + uint64 mapping; + ubyte ready; + ubyte num; + ubyte symmetric; + ubyte has_pawns; + ubyte pawns[2]; + struct { + struct PairsData *precomp; + int factor[TBPIECES]; + ubyte pieces[TBPIECES]; + ubyte norm[TBPIECES]; + } file[4]; + ubyte flags[4]; + ushort map_idx[4][4]; + ubyte *map; +}; + +struct TBHashEntry { + uint64 key; + struct TBEntry *ptr; +}; + +struct DTZTableEntry { + uint64 key1; + uint64 key2; + struct TBEntry *entry; +}; + +#endif + diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp new file mode 100644 index 0000000..0281ccc --- /dev/null +++ b/src/syzygy/tbprobe.cpp @@ -0,0 +1,824 @@ +/* + Copyright (c) 2013 Ronald de Man + This file may be redistributed and/or modified without restrictions. + + tbprobe.cpp contains the Stockfish-specific routines of the + tablebase probing code. It should be relatively easy to adapt + this code to other chess engines. +*/ + +#define NOMINMAX + +#include + +#include "../position.h" +#include "../movegen.h" +#include "../bitboard.h" +#include "../search.h" + +#include "tbprobe.h" +#include "tbcore.h" + +#include "tbcore.cpp" + +namespace Zobrist { + extern Key psq[PIECE_NB][SQUARE_NB]; +} + +int Tablebases::MaxCardinality = 0; + +// Given a position with 6 or fewer pieces, produce a text string +// of the form KQPvKRP, where "KQP" represents the white pieces if +// mirror == 0 and the black pieces if mirror == 1. +static void prt_str(Position& pos, char *str, int mirror) +{ + Color color; + PieceType pt; + int i; + + color = !mirror ? WHITE : BLACK; + for (pt = KING; pt >= PAWN; --pt) + for (i = popcount(pos.pieces(color, pt)); i > 0; i--) + *str++ = pchr[6 - pt]; + *str++ = 'v'; + color = ~color; + for (pt = KING; pt >= PAWN; --pt) + for (i = popcount(pos.pieces(color, pt)); i > 0; i--) + *str++ = pchr[6 - pt]; + *str++ = 0; +} + +// Given a position, produce a 64-bit material signature key. +// If the engine supports such a key, it should equal the engine's key. +static uint64 calc_key(Position& pos, int mirror) +{ + Color color; + PieceType pt; + int i; + uint64 key = 0; + + color = !mirror ? WHITE : BLACK; + for (pt = PAWN; pt <= KING; ++pt) + for (i = popcount(pos.pieces(color, pt)); i > 0; i--) + key ^= Zobrist::psq[make_piece(WHITE, pt)][i - 1]; + color = ~color; + for (pt = PAWN; pt <= KING; ++pt) + for (i = popcount(pos.pieces(color, pt)); i > 0; i--) + key ^= Zobrist::psq[make_piece(BLACK, pt)][i - 1]; + + return key; +} + +// Produce a 64-bit material key corresponding to the material combination +// defined by pcs[16], where pcs[1], ..., pcs[6] is the number of white +// pawns, ..., kings and pcs[9], ..., pcs[14] is the number of black +// pawns, ..., kings. +static uint64 calc_key_from_pcs(int *pcs, int mirror) +{ + int color; + PieceType pt; + int i; + uint64 key = 0; + + color = !mirror ? 0 : 8; + for (pt = PAWN; pt <= KING; ++pt) + for (i = 0; i < pcs[color + pt]; i++) + key ^= Zobrist::psq[make_piece(WHITE, pt)][i]; + color ^= 8; + for (pt = PAWN; pt <= KING; ++pt) + for (i = 0; i < pcs[color + pt]; i++) + key ^= Zobrist::psq[make_piece(BLACK, pt)][i]; + + return key; +} + +bool is_little_endian() { + union { + int i; + char c[sizeof(int)]; + } x; + x.i = 1; + return x.c[0] == 1; +} + +static ubyte decompress_pairs(struct PairsData *d, uint64 idx) +{ + static const bool isLittleEndian = is_little_endian(); + return isLittleEndian ? decompress_pairs(d, idx) + : decompress_pairs(d, idx); +} + +// probe_wdl_table and probe_dtz_table require similar adaptations. +static int probe_wdl_table(Position& pos, int *success) +{ + struct TBEntry *ptr; + struct TBHashEntry *ptr2; + uint64 idx; + uint64 key; + int i; + ubyte res; + int p[TBPIECES]; + + // Obtain the position's material signature key. + key = pos.material_key(); + + // Test for KvK. + if (key == (Zobrist::psq[W_KING][0] ^ Zobrist::psq[B_KING][0])) + return 0; + + ptr2 = TB_hash[key >> (64 - TBHASHBITS)]; + for (i = 0; i < HSHMAX; i++) + if (ptr2[i].key == key) break; + if (i == HSHMAX) { + *success = 0; + return 0; + } + + ptr = ptr2[i].ptr; + if (!ptr->ready) { + LOCK(TB_mutex); + if (!ptr->ready) { + char str[16]; + prt_str(pos, str, ptr->key != key); + if (!init_table_wdl(ptr, str)) { + ptr2[i].key = 0ULL; + *success = 0; + UNLOCK(TB_mutex); + return 0; + } + // Memory barrier to ensure ptr->ready = 1 is not reordered. +#ifdef _MSC_VER + _ReadWriteBarrier(); +#else + __asm__ __volatile__ ("" ::: "memory"); +#endif + ptr->ready = 1; + } + UNLOCK(TB_mutex); + } + + int bside, mirror, cmirror; + if (!ptr->symmetric) { + if (key != ptr->key) { + cmirror = 8; + mirror = 0x38; + bside = (pos.side_to_move() == WHITE); + } else { + cmirror = mirror = 0; + bside = !(pos.side_to_move() == WHITE); + } + } else { + cmirror = pos.side_to_move() == WHITE ? 0 : 8; + mirror = pos.side_to_move() == WHITE ? 0 : 0x38; + bside = 0; + } + + // p[i] is to contain the square 0-63 (A1-H8) for a piece of type + // pc[i] ^ cmirror, where 1 = white pawn, ..., 14 = black king. + // Pieces of the same type are guaranteed to be consecutive. + if (!ptr->has_pawns) { + struct TBEntry_piece *entry = (struct TBEntry_piece *)ptr; + ubyte *pc = entry->pieces[bside]; + for (i = 0; i < entry->num;) { + Bitboard bb = pos.pieces((Color)((pc[i] ^ cmirror) >> 3), + (PieceType)(pc[i] & 0x07)); + do { + p[i++] = pop_lsb(&bb); + } while (bb); + } + idx = encode_piece(entry, entry->norm[bside], p, entry->factor[bside]); + res = decompress_pairs(entry->precomp[bside], idx); + } else { + struct TBEntry_pawn *entry = (struct TBEntry_pawn *)ptr; + int k = entry->file[0].pieces[0][0] ^ cmirror; + Bitboard bb = pos.pieces((Color)(k >> 3), (PieceType)(k & 0x07)); + i = 0; + do { + p[i++] = pop_lsb(&bb) ^ mirror; + } while (bb); + int f = pawn_file(entry, p); + ubyte *pc = entry->file[f].pieces[bside]; + for (; i < entry->num;) { + bb = pos.pieces((Color)((pc[i] ^ cmirror) >> 3), + (PieceType)(pc[i] & 0x07)); + do { + p[i++] = pop_lsb(&bb) ^ mirror; + } while (bb); + } + idx = encode_pawn(entry, entry->file[f].norm[bside], p, entry->file[f].factor[bside]); + res = decompress_pairs(entry->file[f].precomp[bside], idx); + } + + return ((int)res) - 2; +} + +static int probe_dtz_table(Position& pos, int wdl, int *success) +{ + struct TBEntry *ptr; + uint64 idx; + int i, res; + int p[TBPIECES]; + + // Obtain the position's material signature key. + uint64 key = pos.material_key(); + + if (DTZ_table[0].key1 != key && DTZ_table[0].key2 != key) { + for (i = 1; i < DTZ_ENTRIES; i++) + if (DTZ_table[i].key1 == key) break; + if (i < DTZ_ENTRIES) { + struct DTZTableEntry table_entry = DTZ_table[i]; + for (; i > 0; i--) + DTZ_table[i] = DTZ_table[i - 1]; + DTZ_table[0] = table_entry; + } else { + struct TBHashEntry *ptr2 = TB_hash[key >> (64 - TBHASHBITS)]; + for (i = 0; i < HSHMAX; i++) + if (ptr2[i].key == key) break; + if (i == HSHMAX) { + *success = 0; + return 0; + } + ptr = ptr2[i].ptr; + char str[16]; + int mirror = (ptr->key != key); + prt_str(pos, str, mirror); + if (DTZ_table[DTZ_ENTRIES - 1].entry) + free_dtz_entry(DTZ_table[DTZ_ENTRIES-1].entry); + for (i = DTZ_ENTRIES - 1; i > 0; i--) + DTZ_table[i] = DTZ_table[i - 1]; + load_dtz_table(str, calc_key(pos, mirror), calc_key(pos, !mirror)); + } + } + + ptr = DTZ_table[0].entry; + if (!ptr) { + *success = 0; + return 0; + } + + int bside, mirror, cmirror; + if (!ptr->symmetric) { + if (key != ptr->key) { + cmirror = 8; + mirror = 0x38; + bside = (pos.side_to_move() == WHITE); + } else { + cmirror = mirror = 0; + bside = !(pos.side_to_move() == WHITE); + } + } else { + cmirror = pos.side_to_move() == WHITE ? 0 : 8; + mirror = pos.side_to_move() == WHITE ? 0 : 0x38; + bside = 0; + } + + if (!ptr->has_pawns) { + struct DTZEntry_piece *entry = (struct DTZEntry_piece *)ptr; + if ((entry->flags & 1) != bside && !entry->symmetric) { + *success = -1; + return 0; + } + ubyte *pc = entry->pieces; + for (i = 0; i < entry->num;) { + Bitboard bb = pos.pieces((Color)((pc[i] ^ cmirror) >> 3), + (PieceType)(pc[i] & 0x07)); + do { + p[i++] = pop_lsb(&bb); + } while (bb); + } + idx = encode_piece((struct TBEntry_piece *)entry, entry->norm, p, entry->factor); + res = decompress_pairs(entry->precomp, idx); + + if (entry->flags & 2) + res = entry->map[entry->map_idx[wdl_to_map[wdl + 2]] + res]; + + if (!(entry->flags & pa_flags[wdl + 2]) || (wdl & 1)) + res *= 2; + } else { + struct DTZEntry_pawn *entry = (struct DTZEntry_pawn *)ptr; + int k = entry->file[0].pieces[0] ^ cmirror; + Bitboard bb = pos.pieces((Color)(k >> 3), (PieceType)(k & 0x07)); + i = 0; + do { + p[i++] = pop_lsb(&bb) ^ mirror; + } while (bb); + int f = pawn_file((struct TBEntry_pawn *)entry, p); + if ((entry->flags[f] & 1) != bside) { + *success = -1; + return 0; + } + ubyte *pc = entry->file[f].pieces; + for (; i < entry->num;) { + bb = pos.pieces((Color)((pc[i] ^ cmirror) >> 3), + (PieceType)(pc[i] & 0x07)); + do { + p[i++] = pop_lsb(&bb) ^ mirror; + } while (bb); + } + idx = encode_pawn((struct TBEntry_pawn *)entry, entry->file[f].norm, p, entry->file[f].factor); + res = decompress_pairs(entry->file[f].precomp, idx); + + if (entry->flags[f] & 2) + res = entry->map[entry->map_idx[f][wdl_to_map[wdl + 2]] + res]; + + if (!(entry->flags[f] & pa_flags[wdl + 2]) || (wdl & 1)) + res *= 2; + } + + return res; +} + +// Add underpromotion captures to list of captures. +static ExtMove *add_underprom_caps(Position& pos, ExtMove *stack, ExtMove *end) +{ + ExtMove *moves, *extra = end; + + for (moves = stack; moves < end; moves++) { + Move move = moves->move; + if (type_of(move) == PROMOTION && !pos.empty(to_sq(move))) { + (*extra++).move = (Move)(move - (1 << 12)); + (*extra++).move = (Move)(move - (2 << 12)); + (*extra++).move = (Move)(move - (3 << 12)); + } + } + + return extra; +} + +static int probe_ab(Position& pos, int alpha, int beta, int *success) +{ + int v; + ExtMove stack[64]; + ExtMove *moves, *end; + StateInfo st; + + // Generate (at least) all legal non-ep captures including (under)promotions. + // It is OK to generate more, as long as they are filtered out below. + if (!pos.checkers()) { + end = generate(pos, stack); + // Since underpromotion captures are not included, we need to add them. + end = add_underprom_caps(pos, stack, end); + } else + end = generate(pos, stack); + + for (moves = stack; moves < end; moves++) { + Move capture = moves->move; + if (!pos.capture(capture) || type_of(capture) == ENPASSANT + || !pos.legal(capture)) + continue; + pos.do_move(capture, st, pos.gives_check(capture)); + v = -probe_ab(pos, -beta, -alpha, success); + pos.undo_move(capture); + if (*success == 0) return 0; + if (v > alpha) { + if (v >= beta) { + *success = 2; + return v; + } + alpha = v; + } + } + + v = probe_wdl_table(pos, success); + if (*success == 0) return 0; + if (alpha >= v) { + *success = 1 + (alpha > 0); + return alpha; + } else { + *success = 1; + return v; + } +} + +// Probe the WDL table for a particular position. +// If *success != 0, 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 +int Tablebases::probe_wdl(Position& pos, int *success) +{ + int v; + + *success = 1; + v = probe_ab(pos, -2, 2, success); + + // If en passant is not possible, we are done. + if (pos.ep_square() == SQ_NONE) + return v; + if (!(*success)) return 0; + + // Now handle en passant. + int v1 = -3; + // Generate (at least) all legal en passant captures. + ExtMove stack[192]; + ExtMove *moves, *end; + StateInfo st; + + if (!pos.checkers()) + end = generate(pos, stack); + else + end = generate(pos, stack); + + for (moves = stack; moves < end; moves++) { + Move capture = moves->move; + if (type_of(capture) != ENPASSANT + || !pos.legal(capture)) + continue; + pos.do_move(capture, st, pos.gives_check(capture)); + int v0 = -probe_ab(pos, -2, 2, success); + pos.undo_move(capture); + if (*success == 0) return 0; + if (v0 > v1) v1 = v0; + } + if (v1 > -3) { + if (v1 >= v) v = v1; + else if (v == 0) { + // Check whether there is at least one legal non-ep move. + for (moves = stack; moves < end; moves++) { + Move capture = moves->move; + if (type_of(capture) == ENPASSANT) continue; + if (pos.legal(capture)) break; + } + if (moves == end && !pos.checkers()) { + end = generate(pos, end); + for (; moves < end; moves++) { + Move move = moves->move; + if (pos.legal(move)) + break; + } + } + // If not, then we are forced to play the losing ep capture. + if (moves == end) + v = v1; + } + } + + return v; +} + +// This routine treats a position with en passant captures as one without. +static int probe_dtz_no_ep(Position& pos, int *success) +{ + int wdl, dtz; + + wdl = probe_ab(pos, -2, 2, success); + if (*success == 0) return 0; + + if (wdl == 0) return 0; + + if (*success == 2) + return wdl == 2 ? 1 : 101; + + ExtMove stack[192]; + ExtMove *moves, *end = NULL; + StateInfo st; + + if (wdl > 0) { + // Generate at least all legal non-capturing pawn moves + // including non-capturing promotions. + if (!pos.checkers()) + end = generate(pos, stack); + else + end = generate(pos, stack); + + for (moves = stack; moves < end; moves++) { + Move move = moves->move; + if (type_of(pos.moved_piece(move)) != PAWN || pos.capture(move) + || !pos.legal(move)) + continue; + pos.do_move(move, st, pos.gives_check(move)); + int v = -Tablebases::probe_wdl(pos, success); + pos.undo_move(move); + if (*success == 0) return 0; + if (v == wdl) + return v == 2 ? 1 : 101; + } + } + + dtz = 1 + probe_dtz_table(pos, wdl, success); + if (*success >= 0) { + if (wdl & 1) dtz += 100; + return wdl >= 0 ? dtz : -dtz; + } + + if (wdl > 0) { + int best = 0xffff; + for (moves = stack; moves < end; moves++) { + Move move = moves->move; + if (pos.capture(move) || type_of(pos.moved_piece(move)) == PAWN + || !pos.legal(move)) + continue; + pos.do_move(move, st, pos.gives_check(move)); + int v = -Tablebases::probe_dtz(pos, success); + pos.undo_move(move); + if (*success == 0) return 0; + if (v > 0 && v + 1 < best) + best = v + 1; + } + return best; + } else { + int best = -1; + if (!pos.checkers()) + end = generate(pos, stack); + else + end = generate(pos, stack); + for (moves = stack; moves < end; moves++) { + int v; + Move move = moves->move; + if (!pos.legal(move)) + continue; + pos.do_move(move, st, pos.gives_check(move)); + if (st.rule50 == 0) { + if (wdl == -2) v = -1; + else { + v = probe_ab(pos, 1, 2, success); + v = (v == 2) ? 0 : -101; + } + } else { + v = -Tablebases::probe_dtz(pos, success) - 1; + } + pos.undo_move(move); + if (*success == 0) return 0; + if (v < best) + best = v; + } + return best; + } +} + +static int wdl_to_dtz[] = { + -1, -101, 0, 101, 1 +}; + +// Probe the DTZ table for a particular position. +// If *success != 0, 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) +// 0 : draw +// 1 < n <= 100 : win in n ply (assuming 50-move counter == 0) +// 100 < n : win, but draw under 50-move rule +// +// The return value n can be off by 1: a return value -n can mean a loss +// in n+1 ply and a return value +n can mean a win in n+1 ply. This +// cannot happen for tables with positions exactly on the "edge" of +// the 50-move rule. +// +// This implies that if dtz > 0 is returned, the position is certainly +// a win if dtz + 50-move-counter <= 99. Care must be taken that the engine +// picks moves that preserve dtz + 50-move-counter <= 99. +// +// If n = 100 immediately after a capture or pawn move, then the position +// is also certainly a win, and during the whole phase until the next +// capture or pawn move, the inequality to be preserved is +// dtz + 50-movecounter <= 100. +// +// In short, if a move is available resulting in dtz + 50-move-counter <= 99, +// then do not accept moves leading to dtz + 50-move-counter == 100. +// +int Tablebases::probe_dtz(Position& pos, int *success) +{ + *success = 1; + int v = probe_dtz_no_ep(pos, success); + + if (pos.ep_square() == SQ_NONE) + return v; + if (*success == 0) return 0; + + // Now handle en passant. + int v1 = -3; + + ExtMove stack[192]; + ExtMove *moves, *end; + StateInfo st; + + if (!pos.checkers()) + end = generate(pos, stack); + else + end = generate(pos, stack); + + for (moves = stack; moves < end; moves++) { + Move capture = moves->move; + if (type_of(capture) != ENPASSANT + || !pos.legal(capture)) + continue; + pos.do_move(capture, st, pos.gives_check(capture)); + int v0 = -probe_ab(pos, -2, 2, success); + pos.undo_move(capture); + if (*success == 0) return 0; + if (v0 > v1) v1 = v0; + } + if (v1 > -3) { + v1 = wdl_to_dtz[v1 + 2]; + if (v < -100) { + if (v1 >= 0) + v = v1; + } else if (v < 0) { + if (v1 >= 0 || v1 < -100) + v = v1; + } else if (v > 100) { + if (v1 > 0) + v = v1; + } else if (v > 0) { + if (v1 == 1) + v = v1; + } else if (v1 >= 0) { + v = v1; + } else { + for (moves = stack; moves < end; moves++) { + Move move = moves->move; + if (type_of(move) == ENPASSANT) continue; + if (pos.legal(move)) break; + } + if (moves == end && !pos.checkers()) { + end = generate(pos, end); + for (; moves < end; moves++) { + Move move = moves->move; + if (pos.legal(move)) + break; + } + } + if (moves == end) + v = v1; + } + } + + return v; +} + +// Check whether there has been at least one repetition of positions +// since the last capture or pawn move. +static int has_repeated(StateInfo *st) +{ + while (1) { + int i = 4, e = std::min(st->rule50, st->pliesFromNull); + if (e < i) + return 0; + StateInfo *stp = st->previous->previous; + do { + stp = stp->previous->previous; + if (stp->key == st->key) + return 1; + i += 2; + } while (i <= e); + st = st->previous; + } +} + +static Value wdl_to_Value[5] = { + -VALUE_MATE + MAX_PLY + 1, + VALUE_DRAW - 2, + VALUE_DRAW, + VALUE_DRAW + 2, + VALUE_MATE - MAX_PLY - 1 +}; + +// Use the DTZ tables to filter out moves that don't preserve the win or draw. +// If the position is lost, but DTZ is fairly high, only keep moves that +// maximise DTZ. +// +// A return value false indicates that not all probes were successful and that +// no moves were filtered out. +bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves, Value& score) +{ + int success; + + int dtz = probe_dtz(pos, &success); + if (!success) return false; + + StateInfo st; + + // Probe each move. + for (size_t i = 0; i < rootMoves.size(); i++) { + Move move = rootMoves[i].pv[0]; + pos.do_move(move, st, pos.gives_check(move)); + int v = 0; + if (pos.checkers() && dtz > 0) { + ExtMove s[192]; + if (generate(pos, s) == s) + v = 1; + } + if (!v) { + if (st.rule50 != 0) { + v = -Tablebases::probe_dtz(pos, &success); + if (v > 0) v++; + else if (v < 0) v--; + } else { + v = -Tablebases::probe_wdl(pos, &success); + v = wdl_to_dtz[v + 2]; + } + } + pos.undo_move(move); + if (!success) return false; + rootMoves[i].score = (Value)v; + } + + // Obtain 50-move counter for the root position. + // In Stockfish there seems to be no clean way, so we do it like this: + int cnt50 = st.previous->rule50; + + // Use 50-move counter to determine whether the root position is + // won, lost or drawn. + int wdl = 0; + if (dtz > 0) + wdl = (dtz + cnt50 <= 100) ? 2 : 1; + else if (dtz < 0) + wdl = (-dtz + cnt50 <= 100) ? -2 : -1; + + // Determine the score to report to the user. + score = wdl_to_Value[wdl + 2]; + // If the position is winning or losing, but too few moves left, adjust the + // score to show how close it is to winning or losing. + // NOTE: int(PawnValueEg) is used as scaling factor in score_to_uci(). + if (wdl == 1 && dtz <= 100) + score = (Value)(((200 - dtz - cnt50) * int(PawnValueEg)) / 200); + else if (wdl == -1 && dtz >= -100) + score = -(Value)(((200 + dtz - cnt50) * int(PawnValueEg)) / 200); + + // Now be a bit smart about filtering out moves. + size_t j = 0; + if (dtz > 0) { // winning (or 50-move rule draw) + int best = 0xffff; + for (size_t i = 0; i < rootMoves.size(); i++) { + int v = rootMoves[i].score; + if (v > 0 && v < best) + best = v; + } + int max = best; + // If the current phase has not seen repetitions, then try all moves + // that stay safely within the 50-move budget, if there are any. + if (!has_repeated(st.previous) && best + cnt50 <= 99) + max = 99 - cnt50; + for (size_t i = 0; i < rootMoves.size(); i++) { + int v = rootMoves[i].score; + if (v > 0 && v <= max) + rootMoves[j++] = rootMoves[i]; + } + } else if (dtz < 0) { // losing (or 50-move rule draw) + int best = 0; + for (size_t i = 0; i < rootMoves.size(); i++) { + int v = rootMoves[i].score; + if (v < best) + best = v; + } + // Try all moves, unless we approach or have a 50-move rule draw. + if (-best * 2 + cnt50 < 100) + return true; + for (size_t i = 0; i < rootMoves.size(); i++) { + if (rootMoves[i].score == best) + rootMoves[j++] = rootMoves[i]; + } + } else { // drawing + // Try all moves that preserve the draw. + for (size_t i = 0; i < rootMoves.size(); i++) { + if (rootMoves[i].score == 0) + rootMoves[j++] = rootMoves[i]; + } + } + rootMoves.resize(j, Search::RootMove(MOVE_NONE)); + + return true; +} + +// Use the WDL tables to filter out moves that don't preserve the win or draw. +// 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 and that +// no moves were filtered out. +bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, Value& score) +{ + int success; + + int wdl = Tablebases::probe_wdl(pos, &success); + if (!success) return false; + score = wdl_to_Value[wdl + 2]; + + StateInfo st; + + int best = -2; + + // Probe each move. + for (size_t i = 0; i < rootMoves.size(); i++) { + Move move = rootMoves[i].pv[0]; + pos.do_move(move, st, pos.gives_check(move)); + int v = -Tablebases::probe_wdl(pos, &success); + pos.undo_move(move); + if (!success) return false; + rootMoves[i].score = (Value)v; + if (v > best) + best = v; + } + + size_t j = 0; + for (size_t i = 0; i < rootMoves.size(); i++) { + if (rootMoves[i].score == best) + rootMoves[j++] = rootMoves[i]; + } + rootMoves.resize(j, Search::RootMove(MOVE_NONE)); + + return true; +} + diff --git a/src/syzygy/tbprobe.h b/src/syzygy/tbprobe.h new file mode 100644 index 0000000..b23fdf6 --- /dev/null +++ b/src/syzygy/tbprobe.h @@ -0,0 +1,19 @@ +#ifndef TBPROBE_H +#define TBPROBE_H + +#include "../search.h" + +namespace Tablebases { + +extern int MaxCardinality; + +void init(const std::string& path); +int probe_wdl(Position& pos, int *success); +int probe_dtz(Position& pos, int *success); +bool root_probe(Position& pos, Search::RootMoves& rootMoves, Value& score); +bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, Value& score); +void filter_root_moves(Position& pos, Search::RootMoves& rootMoves); + +} + +#endif diff --git a/src/thread.cpp b/src/thread.cpp new file mode 100644 index 0000000..1f1490a --- /dev/null +++ b/src/thread.cpp @@ -0,0 +1,223 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include // For std::count +#include + +#include "movegen.h" +#include "search.h" +#include "thread.h" +#include "uci.h" +#include "syzygy/tbprobe.h" + +ThreadPool Threads; // Global object + +/// Thread constructor launches the thread and then waits until it goes to sleep +/// in idle_loop(). + +Thread::Thread() { + + resetCalls = exit = false; + maxPly = callsCnt = 0; + tbHits = 0; + history.clear(); + counterMoves.clear(); + idx = Threads.size(); // Start from 0 + + std::unique_lock lk(mutex); + searching = true; + nativeThread = std::thread(&Thread::idle_loop, this); + sleepCondition.wait(lk, [&]{ return !searching; }); +} + + +/// Thread destructor waits for thread termination before returning + +Thread::~Thread() { + + mutex.lock(); + exit = true; + sleepCondition.notify_one(); + mutex.unlock(); + nativeThread.join(); +} + + +/// Thread::wait_for_search_finished() waits on sleep condition +/// until not searching + +void Thread::wait_for_search_finished() { + + std::unique_lock lk(mutex); + sleepCondition.wait(lk, [&]{ return !searching; }); +} + + +/// Thread::wait() waits on sleep condition until condition is true + +void Thread::wait(std::atomic_bool& condition) { + + std::unique_lock lk(mutex); + sleepCondition.wait(lk, [&]{ return bool(condition); }); +} + + +/// Thread::start_searching() wakes up the thread that will start the search + +void Thread::start_searching(bool resume) { + + std::unique_lock lk(mutex); + + if (!resume) + searching = true; + + sleepCondition.notify_one(); +} + + +/// Thread::idle_loop() is where the thread is parked when it has no work to do + +void Thread::idle_loop() { + + while (!exit) + { + std::unique_lock lk(mutex); + + searching = false; + + while (!searching && !exit) + { + sleepCondition.notify_one(); // Wake up any waiting thread + sleepCondition.wait(lk); + } + + lk.unlock(); + + if (!exit) + search(); + } +} + + +/// ThreadPool::init() creates and launches requested threads that will go +/// immediately to sleep. We cannot use a constructor because Threads is a +/// static object and we need a fully initialized engine at this point due to +/// allocation of Endgames in the Thread constructor. + +void ThreadPool::init() { + + push_back(new MainThread); + read_uci_options(); +} + + +/// ThreadPool::exit() terminates threads before the program exits. Cannot be +/// done in destructor because threads must be terminated before deleting any +/// static objects while still in main(). + +void ThreadPool::exit() { + + while (size()) + delete back(), pop_back(); +} + + +/// ThreadPool::read_uci_options() updates internal threads parameters from the +/// corresponding UCI options and creates/destroys threads to match requested +/// number. Thread objects are dynamically allocated. + +void ThreadPool::read_uci_options() { + + size_t requested = Options["Threads"]; + + assert(requested > 0); + + while (size() < requested) + push_back(new Thread); + + while (size() > requested) + delete back(), pop_back(); +} + + +/// ThreadPool::nodes_searched() returns the number of nodes searched + +uint64_t ThreadPool::nodes_searched() const { + + uint64_t nodes = 0; + for (Thread* th : *this) + nodes += th->rootPos.nodes_searched(); + return nodes; +} + + +/// ThreadPool::tb_hits() returns the number of TB hits + +uint64_t ThreadPool::tb_hits() const { + + uint64_t hits = 0; + for (Thread* th : *this) + hits += th->tbHits; + return hits; +} + + +/// ThreadPool::start_thinking() wakes up the main thread sleeping in idle_loop() +/// and starts a new search, then returns immediately. + +void ThreadPool::start_thinking(Position& pos, StateListPtr& states, + const Search::LimitsType& limits) { + + main()->wait_for_search_finished(); + + Search::Signals.stopOnPonderhit = Search::Signals.stop = false; + 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)) + rootMoves.push_back(Search::RootMove(m)); + + if (!rootMoves.empty()) + Tablebases::filter_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 + + StateInfo tmp = setupStates->back(); + + for (Thread* th : Threads) + { + th->maxPly = 0; + th->tbHits = 0; + th->rootDepth = DEPTH_ZERO; + th->rootMoves = rootMoves; + th->rootPos.set(pos.fen(), pos.is_chess960(), &setupStates->back(), th); + } + + setupStates->back() = tmp; // Restore st->previous, cleared by Position::set() + + main()->start_searching(); +} diff --git a/src/thread.h b/src/thread.h new file mode 100644 index 0000000..d1165bb --- /dev/null +++ b/src/thread.h @@ -0,0 +1,111 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef THREAD_H_INCLUDED +#define THREAD_H_INCLUDED + +#include +#include +#include +#include +#include +#include + +#include "material.h" +#include "movepick.h" +#include "pawns.h" +#include "position.h" +#include "search.h" +#include "thread_win32.h" + + +/// Thread struct keeps together all the thread-related stuff. We also 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::thread nativeThread; + Mutex mutex; + ConditionVariable sleepCondition; + bool exit, searching; + +public: + Thread(); + virtual ~Thread(); + virtual void search(); + void idle_loop(); + void start_searching(bool resume = false); + void wait_for_search_finished(); + void wait(std::atomic_bool& b); + + Pawns::Table pawnsTable; + Material::Table materialTable; + Endgames endgames; + size_t idx, PVIdx; + int maxPly, callsCnt; + uint64_t tbHits; + + Position rootPos; + Search::RootMoves rootMoves; + Depth rootDepth; + Depth completedDepth; + std::atomic_bool resetCalls; + HistoryStats history; + MoveStats counterMoves; + FromToStats fromTo; + CounterMoveHistoryStats counterMoveHistory; +}; + + +/// MainThread is a derived class with a specific overload for the main thread + +struct MainThread : public Thread { + virtual void search(); + + bool easyMovePlayed, failedLow; + double bestMoveChanges; + Value previousScore; +}; + + +/// ThreadPool struct handles all the threads-related stuff like init, starting, +/// parking and, most importantly, launching a thread. All the access to threads +/// data is done through this class. + +struct ThreadPool : public std::vector { + + void init(); // No constructor and destructor, threads rely on globals that should + void exit(); // be initialized and valid during the whole thread lifetime. + + MainThread* main() { return static_cast(at(0)); } + void start_thinking(Position&, StateListPtr&, const Search::LimitsType&); + void read_uci_options(); + uint64_t nodes_searched() const; + uint64_t tb_hits() const; + +private: + StateListPtr setupStates; +}; + +extern ThreadPool Threads; + +#endif // #ifndef THREAD_H_INCLUDED diff --git a/src/thread_win32.h b/src/thread_win32.h new file mode 100644 index 0000000..47516c6 --- /dev/null +++ b/src/thread_win32.h @@ -0,0 +1,70 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef THREAD_WIN32_H_INCLUDED +#define THREAD_WIN32_H_INCLUDED + +/// STL thread library used by mingw and gcc when cross compiling for Windows +/// relies on libwinpthread. Currently libwinpthread implements mutexes directly +/// on top of Windows semaphores. Semaphores, being kernel objects, require kernel +/// mode transition in order to lock or unlock, which is very slow compared to +/// interlocked operations (about 30% slower on bench test). To work around this +/// issue, we define our wrappers to the low level Win32 calls. We use critical +/// sections to support Windows XP and older versions. Unfortunately, cond_wait() +/// is racy between unlock() and WaitForSingleObject() but they have the same +/// speed performance as the SRW locks. + +#include +#include + +#if defined(_WIN32) && !defined(_MSC_VER) + +#ifndef NOMINMAX +# define NOMINMAX // Disable macros min() and max() +#endif + +#define WIN32_LEAN_AND_MEAN +#include +#undef WIN32_LEAN_AND_MEAN +#undef NOMINMAX + +/// Mutex and ConditionVariable struct are wrappers of the low level locking +/// machinery and are modeled after the corresponding C++11 classes. + +struct Mutex { + Mutex() { InitializeCriticalSection(&cs); } + ~Mutex() { DeleteCriticalSection(&cs); } + void lock() { EnterCriticalSection(&cs); } + void unlock() { LeaveCriticalSection(&cs); } + +private: + CRITICAL_SECTION cs; +}; + +typedef std::condition_variable_any ConditionVariable; + +#else // Default case: use STL classes + +typedef std::mutex Mutex; +typedef std::condition_variable ConditionVariable; + +#endif + +#endif // #ifndef THREAD_WIN32_H_INCLUDED diff --git a/src/timeman.cpp b/src/timeman.cpp new file mode 100644 index 0000000..6d3b731 --- /dev/null +++ b/src/timeman.cpp @@ -0,0 +1,132 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include + +#include "search.h" +#include "timeman.h" +#include "uci.h" + +TimeManagement Time; // Our global time management object + +namespace { + + enum TimeType { OptimumTime, MaxTime }; + + const int MoveHorizon = 50; // Plan time management at most this many moves ahead + const double MaxRatio = 7.09; // When in trouble, we can step over reserved time with this ratio + const double StealRatio = 0.35; // However we must not steal time from remaining moves over this ratio + + + // move_importance() is a skew-logistic function based on naive statistical + // analysis of "how many games are still undecided after n half-moves". Game + // is considered "undecided" as long as neither side has >275cp advantage. + // Data was extracted from the CCRL game database with some simple filtering criteria. + + double move_importance(int ply) { + + const double XScale = 7.64; + const double XShift = 58.4; + const double Skew = 0.183; + + return pow((1 + exp((ply - XShift) / XScale)), -Skew) + DBL_MIN; // Ensure non-zero + } + + template + int remaining(int myTime, int movesToGo, int ply, int slowMover) { + + const double TMaxRatio = (T == OptimumTime ? 1 : MaxRatio); + const double TStealRatio = (T == OptimumTime ? 0 : StealRatio); + + double moveImportance = (move_importance(ply) * slowMover) / 100; + double otherMovesImportance = 0; + + for (int i = 1; i < movesToGo; ++i) + otherMovesImportance += move_importance(ply + 2 * i); + + double ratio1 = (TMaxRatio * moveImportance) / (TMaxRatio * moveImportance + otherMovesImportance); + double ratio2 = (moveImportance + TStealRatio * otherMovesImportance) / (moveImportance + otherMovesImportance); + + return int(myTime * std::min(ratio1, ratio2)); // Intel C++ asks for an explicit cast + } + +} // namespace + + +/// init() is called at the beginning of the search and calculates the allowed +/// thinking time out of the time control and current game ply. We support four +/// different kinds of time controls, passed in 'limits': +/// +/// inc == 0 && movestogo == 0 means: x basetime [sudden death!] +/// inc == 0 && movestogo != 0 means: x moves in y minutes +/// inc > 0 && movestogo == 0 means: x basetime + z increment +/// inc > 0 && movestogo != 0 means: x moves in y minutes + z increment + +void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { + + int minThinkingTime = Options["Minimum Thinking Time"]; + int moveOverhead = Options["Move Overhead"]; + int slowMover = Options["Slow Mover"]; + int npmsec = Options["nodestime"]; + + // 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: Given npms (nodes per millisecond) must be much lower then + // the real engine speed to avoid time losses. + if (npmsec) + { + if (!availableNodes) // Only once at game start + availableNodes = npmsec * limits.time[us]; // Time is in msec + + // Convert from millisecs to nodes + limits.time[us] = (int)availableNodes; + limits.inc[us] *= npmsec; + limits.npmsec = npmsec; + } + + startTime = limits.startTime; + optimumTime = maximumTime = std::max(limits.time[us], minThinkingTime); + + const int MaxMTG = limits.movestogo ? std::min(limits.movestogo, MoveHorizon) : MoveHorizon; + + // We calculate optimum time usage for different hypothetical "moves to go"-values + // and choose the minimum of calculated search time values. Usually the greatest + // hypMTG gives the minimum values. + for (int hypMTG = 1; hypMTG <= MaxMTG; ++hypMTG) + { + // Calculate thinking time for hypothetical "moves to go"-value + int hypMyTime = limits.time[us] + + limits.inc[us] * (hypMTG - 1) + - moveOverhead * (2 + std::min(hypMTG, 40)); + + hypMyTime = std::max(hypMyTime, 0); + + int t1 = minThinkingTime + remaining(hypMyTime, hypMTG, ply, slowMover); + int t2 = minThinkingTime + remaining(hypMyTime, hypMTG, ply, slowMover); + + optimumTime = std::min(t1, optimumTime); + maximumTime = std::min(t2, maximumTime); + } + + if (Options["Ponder"]) + optimumTime += optimumTime / 4; +} diff --git a/src/timeman.h b/src/timeman.h new file mode 100644 index 0000000..9930a4b --- /dev/null +++ b/src/timeman.h @@ -0,0 +1,48 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef TIMEMAN_H_INCLUDED +#define TIMEMAN_H_INCLUDED + +#include "misc.h" +#include "search.h" +#include "thread.h" + +/// The TimeManagement class computes the optimal time to think depending on +/// the maximum available time, the game move number and other parameters. + +class TimeManagement { +public: + void init(Search::LimitsType& limits, Color us, int ply); + int optimum() const { return optimumTime; } + int maximum() const { return maximumTime; } + int elapsed() const { return int(Search::Limits.npmsec ? Threads.nodes_searched() : now() - startTime); } + + int64_t availableNodes; // When in 'nodes as time' mode + +private: + TimePoint startTime; + int optimumTime; + int maximumTime; +}; + +extern TimeManagement Time; + +#endif // #ifndef TIMEMAN_H_INCLUDED diff --git a/src/tt.cpp b/src/tt.cpp new file mode 100644 index 0000000..f5b72ba --- /dev/null +++ b/src/tt.cpp @@ -0,0 +1,117 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include // For std::memset +#include + +#include "bitboard.h" +#include "tt.h" + +TranspositionTable TT; // Our global transposition table + + +/// 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) { + + size_t newClusterCount = size_t(1) << msb((mbSize * 1024 * 1024) / sizeof(Cluster)); + + if (newClusterCount == clusterCount) + return; + + clusterCount = newClusterCount; + + free(mem); + mem = calloc(clusterCount * sizeof(Cluster) + CacheLineSize - 1, 1); + + if (!mem) + { + std::cerr << "Failed to allocate " << mbSize + << "MB for transposition table." << std::endl; + exit(EXIT_FAILURE); + } + + table = (Cluster*)((uintptr_t(mem) + CacheLineSize - 1) & ~(CacheLineSize - 1)); +} + + +/// TranspositionTable::clear() overwrites the entire transposition table +/// with zeros. It is called whenever the table is resized, or when the +/// user asks the program to clear the table (from the UCI interface). + +void TranspositionTable::clear() { + + std::memset(table, 0, clusterCount * sizeof(Cluster)); +} + + +/// 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 = key >> 48; // Use the high 16 bits as key inside the cluster + + for (int i = 0; i < ClusterSize; ++i) + if (!tte[i].key16 || tte[i].key16 == key16) + { + if ((tte[i].genBound8 & 0xFC) != generation8 && tte[i].key16) + tte[i].genBound8 = uint8_t(generation8 | tte[i].bound()); // Refresh + + return found = (bool)tte[i].key16, &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 259 (256 is the modulus plus 3 to keep the lowest + // two bound bits from affecting the result) to calculate the entry + // age correctly even after generation8 overflows into the next cycle. + if ( replace->depth8 - ((259 + generation8 - replace->genBound8) & 0xFC) * 2 + > tte[i].depth8 - ((259 + generation8 - tte[i].genBound8) & 0xFC) * 2) + 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 / ClusterSize; i++) + { + const TTEntry* tte = &table[i].entry[0]; + for (int j = 0; j < ClusterSize; j++) + if ((tte[j].genBound8 & 0xFC) == generation8) + cnt++; + } + return cnt; +} diff --git a/src/tt.h b/src/tt.h new file mode 100644 index 0000000..677f38e --- /dev/null +++ b/src/tt.h @@ -0,0 +1,121 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef TT_H_INCLUDED +#define TT_H_INCLUDED + +#include "misc.h" +#include "types.h" + +/// TTEntry struct is the 10 bytes transposition table entry, defined as below: +/// +/// key 16 bit +/// move 16 bit +/// value 16 bit +/// eval value 16 bit +/// generation 6 bit +/// bound type 2 bit +/// depth 8 bit + +struct TTEntry { + + Move move() const { return (Move )move16; } + Value value() const { return (Value)value16; } + Value eval() const { return (Value)eval16; } + Depth depth() const { return (Depth)(depth8 * int(ONE_PLY)); } + Bound bound() const { return (Bound)(genBound8 & 0x3); } + + void save(Key k, Value v, Bound b, Depth d, Move m, Value ev, uint8_t g) { + + assert(d / ONE_PLY * ONE_PLY == d); + + // Preserve any existing move for the same position + if (m || (k >> 48) != key16) + move16 = (uint16_t)m; + + // Don't overwrite more valuable entries + if ( (k >> 48) != key16 + || d / ONE_PLY > depth8 - 4 + /* || g != (genBound8 & 0xFC) // Matching non-zero keys are already refreshed by probe() */ + || b == BOUND_EXACT) + { + key16 = (uint16_t)(k >> 48); + value16 = (int16_t)v; + eval16 = (int16_t)ev; + genBound8 = (uint8_t)(g | b); + depth8 = (int8_t)(d / ONE_PLY); + } + } + +private: + friend class TranspositionTable; + + uint16_t key16; + uint16_t move16; + int16_t value16; + int16_t eval16; + uint8_t genBound8; + int8_t depth8; +}; + + +/// A TranspositionTable consists of a power of 2 number of clusters and each +/// cluster consists of ClusterSize number of TTEntry. Each non-empty entry +/// contains information of exactly one position. The size of a cluster should +/// divide the size of a cache line size, to ensure that clusters never cross +/// cache lines. This ensures best cache performance, as the cacheline is +/// prefetched, as soon as possible. + +class TranspositionTable { + + static const int CacheLineSize = 64; + static const int ClusterSize = 3; + + struct Cluster { + TTEntry entry[ClusterSize]; + char padding[2]; // Align to a divisor of the cache line size + }; + + static_assert(CacheLineSize % sizeof(Cluster) == 0, "Cluster size incorrect"); + +public: + ~TranspositionTable() { free(mem); } + void new_search() { generation8 += 4; } // Lower 2 bits are used by Bound + uint8_t generation() const { return generation8; } + TTEntry* probe(const Key key, bool& found) const; + int hashfull() const; + void resize(size_t mbSize); + void clear(); + + // The lowest order bits of the key are used to get the index of the cluster + TTEntry* first_entry(const Key key) const { + return &table[(size_t)key & (clusterCount - 1)].entry[0]; + } + +private: + size_t clusterCount; + Cluster* table; + void* mem; + uint8_t generation8; // Size must be not bigger than TTEntry::genBound8 +}; + +extern TranspositionTable TT; + +#endif // #ifndef TT_H_INCLUDED diff --git a/src/types.h b/src/types.h new file mode 100644 index 0000000..519f6af --- /dev/null +++ b/src/types.h @@ -0,0 +1,435 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#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' +#endif + +/// Predefined macros hell: +/// +/// __GNUC__ Compiler is gcc, Clang or Intel on Linux +/// __INTEL_COMPILER Compiler is Intel +/// _MSC_VER Compiler is MSVC or Intel on Windows +/// _WIN32 Building on Windows (any) +/// _WIN64 Building on Windows 64 bit + +#if defined(_WIN64) && defined(_MSC_VER) // No Makefile used +# include // 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 +# define pext(b, m) _pext_u64(b, m) +#else +# define pext(b, m) (0) +#endif + +#ifdef USE_POPCNT +const bool HasPopCnt = true; +#else +const bool HasPopCnt = false; +#endif + +#ifdef USE_PEXT +const bool HasPext = true; +#else +const bool HasPext = false; +#endif + +#ifdef IS_64BIT +const bool Is64Bit = true; +#else +const bool Is64Bit = false; +#endif + +typedef uint64_t Key; +typedef uint64_t Bitboard; + +const int MAX_MOVES = 256; +const int MAX_PLY = 128; + +/// A move needs 16 bits to be stored +/// +/// bit 0- 5: destination square (from 0 to 63) +/// bit 6-11: origin square (from 0 to 63) +/// bit 12-13: promotion piece type - 2 (from KNIGHT-2 to QUEEN-2) +/// bit 14-15: special move flag: promotion (1), en passant (2), castling (3) +/// NOTE: EN-PASSANT bit is set only when a pawn can be captured +/// +/// Special cases are MOVE_NONE and MOVE_NULL. We can sneak these in because in +/// any normal move destination square is always different from origin square +/// while MOVE_NONE and MOVE_NULL have the same origin and destination square. + +enum Move : int { + MOVE_NONE, + MOVE_NULL = 65 +}; + +enum MoveType { + NORMAL, + PROMOTION = 1 << 14, + ENPASSANT = 2 << 14, + CASTLING = 3 << 14 +}; + +enum Color { + WHITE, BLACK, NO_COLOR, COLOR_NB = 2 +}; + +enum CastlingSide { + KING_SIDE, QUEEN_SIDE, CASTLING_SIDE_NB = 2 +}; + +enum CastlingRight { + NO_CASTLING, + WHITE_OO, + WHITE_OOO = WHITE_OO << 1, + BLACK_OO = WHITE_OO << 2, + BLACK_OOO = WHITE_OO << 3, + ANY_CASTLING = WHITE_OO | WHITE_OOO | BLACK_OO | BLACK_OOO, + CASTLING_RIGHT_NB = 16 +}; + +template struct MakeCastling { + static const CastlingRight + right = C == WHITE ? S == QUEEN_SIDE ? WHITE_OOO : WHITE_OO + : S == QUEEN_SIDE ? BLACK_OOO : BLACK_OO; +}; + +enum Phase { + PHASE_ENDGAME, + PHASE_MIDGAME = 128, + MG = 0, EG = 1, PHASE_NB = 2 +}; + +enum ScaleFactor { + SCALE_FACTOR_DRAW = 0, + SCALE_FACTOR_ONEPAWN = 48, + SCALE_FACTOR_NORMAL = 64, + SCALE_FACTOR_MAX = 128, + SCALE_FACTOR_NONE = 255 +}; + +enum Bound { + BOUND_NONE, + BOUND_UPPER, + BOUND_LOWER, + BOUND_EXACT = BOUND_UPPER | BOUND_LOWER +}; + +enum Value : int { + VALUE_ZERO = 0, + VALUE_DRAW = 0, + VALUE_KNOWN_WIN = 10000, + VALUE_MATE = 32000, + VALUE_INFINITE = 32001, + VALUE_NONE = 32002, + + VALUE_MATE_IN_MAX_PLY = VALUE_MATE - 2 * MAX_PLY, + VALUE_MATED_IN_MAX_PLY = -VALUE_MATE + 2 * MAX_PLY, + + PawnValueMg = 188, PawnValueEg = 248, + KnightValueMg = 753, KnightValueEg = 832, + BishopValueMg = 826, BishopValueEg = 897, + RookValueMg = 1285, RookValueEg = 1371, + QueenValueMg = 2513, QueenValueEg = 2650, + + MidgameLimit = 15258, EndgameLimit = 3915 +}; + +enum PieceType { + NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, + ALL_PIECES = 0, + PIECE_TYPE_NB = 8 +}; + +enum Piece { + NO_PIECE, + W_PAWN = 1, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, + B_PAWN = 9, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING, + PIECE_NB = 16 +}; + +const Piece Pieces[] = { W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, + B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING }; +extern Value PieceValue[PHASE_NB][PIECE_NB]; + +enum Depth { + + ONE_PLY = 1, + + DEPTH_ZERO = 0 * ONE_PLY, + DEPTH_QS_CHECKS = 0 * ONE_PLY, + DEPTH_QS_NO_CHECKS = -1 * ONE_PLY, + DEPTH_QS_RECAPTURES = -5 * ONE_PLY, + + DEPTH_NONE = -6 * ONE_PLY, + DEPTH_MAX = MAX_PLY * ONE_PLY +}; + +static_assert(!(ONE_PLY & (ONE_PLY - 1)), "ONE_PLY is not a power of 2"); + +enum Square { + SQ_A1, SQ_B1, SQ_C1, SQ_D1, SQ_E1, SQ_F1, SQ_G1, SQ_H1, + SQ_A2, SQ_B2, SQ_C2, SQ_D2, SQ_E2, SQ_F2, SQ_G2, SQ_H2, + SQ_A3, SQ_B3, SQ_C3, SQ_D3, SQ_E3, SQ_F3, SQ_G3, SQ_H3, + SQ_A4, SQ_B4, SQ_C4, SQ_D4, SQ_E4, SQ_F4, SQ_G4, SQ_H4, + SQ_A5, SQ_B5, SQ_C5, SQ_D5, SQ_E5, SQ_F5, SQ_G5, SQ_H5, + SQ_A6, SQ_B6, SQ_C6, SQ_D6, SQ_E6, SQ_F6, SQ_G6, SQ_H6, + SQ_A7, SQ_B7, SQ_C7, SQ_D7, SQ_E7, SQ_F7, SQ_G7, SQ_H7, + SQ_A8, SQ_B8, SQ_C8, SQ_D8, SQ_E8, SQ_F8, SQ_G8, SQ_H8, + SQ_NONE, + + SQUARE_NB = 64, + + NORTH = 8, + EAST = 1, + SOUTH = -8, + WEST = -1, + + NORTH_EAST = NORTH + EAST, + SOUTH_EAST = SOUTH + EAST, + SOUTH_WEST = SOUTH + WEST, + NORTH_WEST = NORTH + WEST +}; + +enum File : int { + FILE_A, FILE_B, FILE_C, FILE_D, FILE_E, FILE_F, FILE_G, FILE_H, FILE_NB +}; + +enum Rank : int { + RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, RANK_NB +}; + + +/// Score enum stores a middlegame and an endgame value in a single integer +/// (enum). The least significant 16 bits are used to store the endgame value +/// and the upper 16 bits are used to store the middlegame value. Take some +/// care to avoid left-shifting a signed int to avoid undefined behavior. +enum Score : int { SCORE_ZERO }; + +inline Score make_score(int mg, int eg) { + return Score((int)((unsigned int)eg << 16) + mg); +} + +/// Extracting the signed lower and upper 16 bits is not so trivial because +/// according to the standard a simple cast to short is implementation defined +/// and so is a right shift of a signed integer. +inline Value eg_value(Score s) { + + union { uint16_t u; int16_t s; } eg = { uint16_t(unsigned(s + 0x8000) >> 16) }; + return Value(eg.s); +} + +inline Value mg_value(Score s) { + + union { uint16_t u; int16_t s; } mg = { uint16_t(unsigned(s)) }; + return Value(mg.s); +} + +#define ENABLE_BASE_OPERATORS_ON(T) \ +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*(int i, T d) { return T(i * int(d)); } \ +inline T operator*(T d, int i) { return T(int(d) * i); } \ +inline T operator-(T d) { return T(-int(d)); } \ +inline T& operator+=(T& d1, T d2) { return d1 = d1 + d2; } \ +inline T& operator-=(T& d1, T d2) { return d1 = d1 - d2; } \ +inline T& operator*=(T& d, int i) { return d = T(int(d) * i); } + +#define ENABLE_FULL_OPERATORS_ON(T) \ +ENABLE_BASE_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); } \ +inline T operator/(T d, int i) { return T(int(d) / i); } \ +inline int operator/(T d1, T d2) { return int(d1) / int(d2); } \ +inline T& operator/=(T& d, int i) { return d = T(int(d) / i); } + +ENABLE_FULL_OPERATORS_ON(Value) +ENABLE_FULL_OPERATORS_ON(PieceType) +ENABLE_FULL_OPERATORS_ON(Piece) +ENABLE_FULL_OPERATORS_ON(Color) +ENABLE_FULL_OPERATORS_ON(Depth) +ENABLE_FULL_OPERATORS_ON(Square) +ENABLE_FULL_OPERATORS_ON(File) +ENABLE_FULL_OPERATORS_ON(Rank) + +ENABLE_BASE_OPERATORS_ON(Score) + +#undef ENABLE_FULL_OPERATORS_ON +#undef ENABLE_BASE_OPERATORS_ON + +/// Additional operators to add integers to a Value +inline Value operator+(Value v, int i) { return Value(int(v) + i); } +inline Value operator-(Value v, int i) { return Value(int(v) - i); } +inline Value& operator+=(Value& v, int i) { return v = v + i; } +inline Value& operator-=(Value& v, int i) { return v = v - i; } + +/// 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. +inline Score operator*(Score s1, Score s2); + +/// 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); +} + +inline Color operator~(Color c) { + return Color(c ^ BLACK); // Toggle color +} + +inline Square operator~(Square s) { + return Square(s ^ SQ_A8); // Vertical flip SQ_A1 -> SQ_A8 +} + +inline Piece operator~(Piece pc) { + return Piece(pc ^ 8); // Swap color of piece B_KNIGHT -> W_KNIGHT +} + +inline CastlingRight operator|(Color c, CastlingSide s) { + return CastlingRight(WHITE_OO << ((s == QUEEN_SIDE) + 2 * c)); +} + +inline Value mate_in(int ply) { + return VALUE_MATE - ply; +} + +inline Value mated_in(int ply) { + return -VALUE_MATE + ply; +} + +inline Square make_square(File f, Rank r) { + return Square((r << 3) + f); +} + +inline Piece make_piece(Color c, PieceType pt) { + return Piece((c << 3) + pt); +} + +inline PieceType type_of(Piece pc) { + return PieceType(pc & 7); +} + +inline Color color_of(Piece pc) { + assert(pc != NO_PIECE); + return Color(pc >> 3); +} + +inline bool is_ok(Square s) { + return s >= SQ_A1 && s <= SQ_H8; +} + +inline File file_of(Square s) { + return File(s & 7); +} + +inline Rank rank_of(Square s) { + return Rank(s >> 3); +} + +inline Square relative_square(Color c, Square s) { + return Square(s ^ (c * 56)); +} + +inline Rank relative_rank(Color c, Rank r) { + return Rank(r ^ (c * 7)); +} + +inline Rank relative_rank(Color c, Square s) { + return relative_rank(c, rank_of(s)); +} + +inline bool opposite_colors(Square s1, Square s2) { + int s = int(s1) ^ int(s2); + return ((s >> 3) ^ s) & 1; +} + +inline Square pawn_push(Color c) { + return c == WHITE ? NORTH : SOUTH; +} + +inline Square from_sq(Move m) { + return Square((m >> 6) & 0x3F); +} + +inline Square to_sq(Move m) { + return Square(m & 0x3F); +} + +inline MoveType type_of(Move m) { + return MoveType(m & (3 << 14)); +} + +inline PieceType promotion_type(Move m) { + return PieceType(((m >> 12) & 3) + KNIGHT); +} + +inline Move make_move(Square from, Square to) { + return Move((from << 6) + to); +} + +template +inline Move make(Square from, Square to, PieceType pt = KNIGHT) { + return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to); +} + +inline bool is_ok(Move m) { + return from_sq(m) != to_sq(m); // Catch MOVE_NULL and MOVE_NONE +} + +#endif // #ifndef TYPES_H_INCLUDED diff --git a/src/uci.cpp b/src/uci.cpp new file mode 100644 index 0000000..b195b87 --- /dev/null +++ b/src/uci.cpp @@ -0,0 +1,289 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include + +#include "evaluate.h" +#include "movegen.h" +#include "position.h" +#include "search.h" +#include "thread.h" +#include "timeman.h" +#include "uci.h" + +using namespace std; + +extern void benchmark(const Position& pos, istream& is); + +namespace { + + // FEN string of the initial position, normal chess + const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; + + // 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. + StateListPtr States(new std::deque(1)); + + + // position() is called when engine receives the "position" UCI command. + // The function sets up the position described in the given FEN string ("fen") + // or the starting position ("startpos") and then makes the moves given in the + // following move list ("moves"). + + void position(Position& pos, istringstream& is) { + + Move m; + string token, fen; + + is >> token; + + if (token == "startpos") + { + fen = StartFEN; + is >> token; // Consume "moves" token if any + } + else if (token == "fen") + while (is >> token && token != "moves") + fen += token + " "; + else + return; + + States = StateListPtr(new std::deque(1)); + pos.set(fen, Options["UCI_Chess960"], &States->back(), Threads.main()); + + // Parse move list (if any) + while (is >> token && (m = UCI::to_move(pos, token)) != MOVE_NONE) + { + States->push_back(StateInfo()); + pos.do_move(m, States->back(), pos.gives_check(m)); + } + } + + + // setoption() is called when engine receives the "setoption" UCI command. The + // function updates the UCI option ("name") to the given value ("value"). + + void setoption(istringstream& is) { + + string token, name, value; + + is >> token; // Consume "name" token + + // Read option name (can contain spaces) + while (is >> token && token != "value") + name += string(" ", name.empty() ? 0 : 1) + token; + + // Read option value (can contain spaces) + while (is >> token) + value += string(" ", value.empty() ? 0 : 1) + token; + + if (Options.count(name)) + Options[name] = value; + else + sync_cout << "No such option: " << name << sync_endl; + } + + + // go() is called when engine receives the "go" UCI command. The function sets + // the thinking time and other parameters from the input string, then starts + // the search. + + void go(Position& pos, istringstream& is) { + + Search::LimitsType limits; + string token; + + limits.startTime = now(); // As early as possible! + + while (is >> token) + if (token == "searchmoves") + while (is >> token) + limits.searchmoves.push_back(UCI::to_move(pos, token)); + + else if (token == "wtime") is >> limits.time[WHITE]; + else if (token == "btime") is >> limits.time[BLACK]; + else if (token == "winc") is >> limits.inc[WHITE]; + else if (token == "binc") is >> limits.inc[BLACK]; + else if (token == "movestogo") is >> limits.movestogo; + else if (token == "depth") is >> limits.depth; + else if (token == "nodes") is >> limits.nodes; + else if (token == "movetime") is >> limits.movetime; + else if (token == "mate") is >> limits.mate; + else if (token == "infinite") limits.infinite = 1; + else if (token == "ponder") limits.ponder = 1; + + Threads.start_thinking(pos, States, limits); + } + +} // 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; + + pos.set(StartFEN, false, &States->back(), Threads.main()); + + for (int i = 1; i < argc; ++i) + cmd += std::string(argv[i]) + " "; + + do { + if (argc == 1 && !getline(cin, cmd)) // Block here waiting for input or EOF + cmd = "quit"; + + istringstream is(cmd); + + token.clear(); // getline() could return empty or blank line + is >> skipws >> token; + + // The GUI sends 'ponderhit' to tell us to ponder on the same move the + // opponent has played. In case Signals.stopOnPonderhit is set we are + // waiting for 'ponderhit' to stop the search (for instance because we + // already ran out of time), otherwise we should continue searching but + // switching from pondering to normal search. + if ( token == "quit" + || token == "stop" + || (token == "ponderhit" && Search::Signals.stopOnPonderhit)) + { + Search::Signals.stop = true; + Threads.main()->start_searching(true); // Could be sleeping + } + else if (token == "ponderhit") + Search::Limits.ponder = 0; // Switch to normal search + + else if (token == "uci") + sync_cout << "id name " << engine_info(true) + << "\n" << Options + << "\nuciok" << sync_endl; + + else if (token == "ucinewgame") + { + Search::clear(); + Time.availableNodes = 0; + } + else if (token == "isready") sync_cout << "readyok" << sync_endl; + else if (token == "go") go(pos, is); + else if (token == "position") position(pos, is); + else if (token == "setoption") setoption(is); + + // Additional custom non-UCI commands, useful for debugging + else if (token == "flip") pos.flip(); + else if (token == "bench") benchmark(pos, is); + else if (token == "d") sync_cout << pos << sync_endl; + else if (token == "eval") sync_cout << Eval::trace(pos) << sync_endl; + else if (token == "perft") + { + int depth; + stringstream ss; + + is >> depth; + ss << Options["Hash"] << " " + << Options["Threads"] << " " << depth << " current perft"; + + benchmark(pos, ss); + } + else + sync_cout << "Unknown command: " << cmd << sync_endl; + + } while (token != "quit" && argc == 1); // Passed args have one-shot behaviour + + Threads.main()->wait_for_search_finished(); +} + + +/// 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) { + + stringstream ss; + + if (abs(v) < VALUE_MATE - MAX_PLY) + ss << "cp " << v * 100 / PawnValueEg; + else + ss << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2; + + return ss.str(); +} + + +/// UCI::square() converts a Square to a string in algebraic notation (g1, a7, etc.) + +std::string UCI::square(Square s) { + return std::string{ char('a' + file_of(s)), char('1' + rank_of(s)) }; +} + + +/// UCI::move() converts a Move to a string in coordinate notation (g1f3, a7a8q). +/// The only special case is castling, where we print in the e1g1 notation in +/// normal chess mode, and in e1h1 notation in chess960 mode. Internally all +/// castling moves are always encoded as 'king captures rook'. + +string UCI::move(Move m, bool chess960) { + + Square from = from_sq(m); + Square to = to_sq(m); + + if (m == MOVE_NONE) + return "(none)"; + + if (m == MOVE_NULL) + return "0000"; + + if (type_of(m) == CASTLING && !chess960) + to = make_square(to > from ? FILE_G : FILE_C, rank_of(from)); + + string move = UCI::square(from) + UCI::square(to); + + if (type_of(m) == PROMOTION) + move += " pnbrqk"[promotion_type(m)]; + + return move; +} + + +/// UCI::to_move() converts a string representing a move in coordinate notation +/// (g1f3, a7a8q) to the corresponding legal Move, if any. + +Move UCI::to_move(const Position& pos, string& str) { + + if (str.length() == 5) // Junior could send promotion piece in uppercase + str[4] = char(tolower(str[4])); + + for (const auto& m : MoveList(pos)) + if (str == UCI::move(m, pos.is_chess960())) + return m; + + return MOVE_NONE; +} diff --git a/src/uci.h b/src/uci.h new file mode 100644 index 0000000..4697877 --- /dev/null +++ b/src/uci.h @@ -0,0 +1,80 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef UCI_H_INCLUDED +#define UCI_H_INCLUDED + +#include +#include + +#include "types.h" + +class Position; + +namespace UCI { + +class Option; + +/// Custom comparator because UCI options should be case insensitive +struct CaseInsensitiveLess { + bool operator() (const std::string&, const std::string&) const; +}; + +/// Our options container is actually a std::map +typedef std::map 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(int v, int min, int max, OnChange = nullptr); + + Option& operator=(const std::string&); + void operator<<(const Option&); + operator int() const; + operator std::string() const; + +private: + friend std::ostream& operator<<(std::ostream&, const OptionsMap&); + + std::string defaultValue, currentValue, type; + int min, max; + size_t idx; + OnChange on_change; +}; + +void init(OptionsMap&); +void loop(int argc, char* argv[]); +std::string value(Value v); +std::string square(Square s); +std::string move(Move m, bool chess960); +std::string pv(const Position& pos, Depth depth, Value alpha, Value beta); +Move to_move(const Position& pos, std::string& str); + +} // namespace UCI + +extern UCI::OptionsMap Options; + +#endif // #ifndef UCI_H_INCLUDED diff --git a/src/ucioption.cpp b/src/ucioption.cpp new file mode 100644 index 0000000..ab931bb --- /dev/null +++ b/src/ucioption.cpp @@ -0,0 +1,163 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include + +#include "misc.h" +#include "search.h" +#include "thread.h" +#include "tt.h" +#include "uci.h" +#include "syzygy/tbprobe.h" + +using std::string; + +UCI::OptionsMap Options; // Global object + +namespace UCI { + +/// 'On change' actions, triggered by an option's value change +void on_clear_hash(const Option&) { Search::clear(); } +void on_hash_size(const Option& o) { TT.resize(o); } +void on_logger(const Option& o) { start_logger(o); } +void on_threads(const Option&) { Threads.read_uci_options(); } +void on_tb_path(const Option& o) { Tablebases::init(o); } + + +/// 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); }); +} + + +/// init() initializes the UCI options to their hard-coded default values + +void init(OptionsMap& o) { + + const int MaxHashMB = Is64Bit ? 1024 * 1024 : 2048; + + o["Debug Log File"] << Option("", on_logger); + o["Contempt"] << Option(0, -100, 100); + o["Threads"] << Option(1, 1, 128, on_threads); + o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size); + o["Clear Hash"] << Option(on_clear_hash); + o["Ponder"] << Option(false); + o["MultiPV"] << Option(1, 1, 500); + o["Skill Level"] << Option(20, 0, 20); + o["Move Overhead"] << Option(30, 0, 5000); + o["Minimum Thinking Time"] << Option(20, 0, 5000); + o["Slow Mover"] << Option(89, 10, 1000); + o["nodestime"] << Option(0, 0, 10000); + o["UCI_Chess960"] << Option(false); + o["SyzygyPath"] << Option("", on_tb_path); + o["SyzygyProbeDepth"] << Option(1, 1, 100); + o["Syzygy50MoveRule"] << Option(true); + o["SyzygyProbeLimit"] << Option(6, 0, 6); +} + + +/// operator<<() is used to print all the options default values in chronological +/// insertion order (the idx field) and in the format defined by the UCI protocol. + +std::ostream& operator<<(std::ostream& os, const OptionsMap& om) { + + for (size_t idx = 0; idx < om.size(); ++idx) + for (const auto& it : om) + if (it.second.idx == idx) + { + const Option& o = it.second; + os << "\noption name " << it.first << " type " << o.type; + + if (o.type != "button") + os << " default " << o.defaultValue; + + if (o.type == "spin") + os << " min " << o.min << " max " << o.max; + + break; + } + + return os; +} + + +/// Option class constructors and conversion operators + +Option::Option(const char* v, OnChange f) : type("string"), min(0), max(0), on_change(f) +{ defaultValue = currentValue = v; } + +Option::Option(bool v, OnChange f) : type("check"), min(0), max(0), on_change(f) +{ defaultValue = currentValue = (v ? "true" : "false"); } + +Option::Option(OnChange f) : type("button"), min(0), max(0), on_change(f) +{} + +Option::Option(int v, int minv, int maxv, OnChange f) : type("spin"), min(minv), max(maxv), on_change(f) +{ defaultValue = currentValue = std::to_string(v); } + +Option::operator int() const { + assert(type == "check" || type == "spin"); + return (type == "spin" ? stoi(currentValue) : currentValue == "true"); +} + +Option::operator std::string() const { + assert(type == "string"); + return currentValue; +} + + +/// operator<<() inits options and assigns idx in the correct printing order + +void Option::operator<<(const Option& o) { + + static size_t insert_order = 0; + + *this = o; + idx = insert_order++; +} + + +/// operator=() updates currentValue and triggers on_change() action. It's up to +/// the GUI to check for option's limits, but we could receive the new value from +/// the user by console window, so let's check the bounds anyway. + +Option& Option::operator=(const string& v) { + + assert(!type.empty()); + + if ( (type != "button" && v.empty()) + || (type == "check" && v != "true" && v != "false") + || (type == "spin" && (stoi(v) < min || stoi(v) > max))) + return *this; + + if (type != "button") + currentValue = v; + + if (on_change) + on_change(*this); + + return *this; +} + +} // namespace UCI -- 2.30.2