From: Oliver Korff Date: Sat, 23 Aug 2014 14:43:44 +0000 (+0100) Subject: Import stockfish_5.0.dd+git20140823.orig.tar.gz X-Git-Tag: archive/raspbian/11-1+rpi1~1^2^2^2~1 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=f54e2c147841a5c43cf9eff2b990932617b6070c;p=stockfish.git Import stockfish_5.0.dd+git20140823.orig.tar.gz [dgit import orig stockfish_5.0.dd+git20140823.orig.tar.gz] --- f54e2c147841a5c43cf9eff2b990932617b6070c 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..d4ea7a0 --- /dev/null +++ b/Readme.md @@ -0,0 +1,58 @@ +### Overview + +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. + + +### 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. + + +### 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/src/Makefile b/src/Makefile new file mode 100644 index 0000000..187e7b3 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,496 @@ +# Stockfish, a UCI chess playing engine derived from Glaurung 2.1 +# Copyright (C) 2004-2008 Tord Romstad (Glaurung author) +# Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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 +UNAME = $(shell uname) + +### Executable name +EXE = stockfish + +### Installation dir definitions +PREFIX = /usr/local +# Haiku has a non-standard filesystem layout +ifeq ($(UNAME),Haiku) + PREFIX=/boot/system/non-packaged +endif +BINDIR = $(PREFIX)/bin + +### Built-in benchmark for pgo-builds +PGOBENCH = ./$(EXE) bench 32 1 1 default time + +### Object files +OBJS = benchmark.o bitbase.o bitboard.o endgame.o evaluate.o main.o \ + material.o misc.o movegen.o movepick.o notation.o pawns.o \ + position.o search.o thread.o timeman.o tt.o uci.o ucioption.o + +### ========================================================================== +### Section 2. High-level Configuration +### ========================================================================== +# +# flag --- Comp switch --- Description +# ---------------------------------------------------------------------------- +# +# debug = yes/no --- -DNDEBUG --- Enable/Disable debug mode +# optimize = yes/no --- (-O3/-fast etc.) --- Enable/Disable optimizations +# arch = (name) --- (-arch) --- Target architecture +# os = (name) --- --- Target operating system +# bits = 64/32 --- -DIS_64BIT --- 64-/32-bit operating system +# prefetch = yes/no --- -DUSE_PREFETCH --- Use prefetch x86 asm-instruction +# bsfq = yes/no --- -DUSE_BSFQ --- Use bsfq x86_64 asm-instruction (only +# with GCC and ICC 64-bit) +# popcnt = yes/no --- -DUSE_POPCNT --- Use popcnt x86_64 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 +os = any +bits = 32 +prefetch = no +bsfq = 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 + bsfq = yes + sse = yes +endif + +ifeq ($(ARCH),x86-64-modern) + arch = x86_64 + bits = 64 + prefetch = yes + bsfq = yes + popcnt = yes + sse = yes +endif + +ifeq ($(ARCH),x86-64-bmi2) + arch = x86_64 + bits = 64 + prefetch = yes + bsfq = yes + popcnt = yes + sse = yes + pext = yes +endif + +ifeq ($(ARCH),armv7) + arch = armv7 + prefetch = yes + bsfq = 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 $(EXTRACXXFLAGS) +LDFLAGS += $(EXTRALDFLAGS) + +ifeq ($(COMP),) + COMP=gcc +endif + +ifeq ($(COMP),gcc) + comp=gcc + CXX=g++ + CXXFLAGS += -ansi -pedantic -Wno-long-long -Wextra -Wshadow +endif + +ifeq ($(COMP),mingw) + comp=mingw + CXX=g++ + CXXFLAGS += -Wextra -Wshadow + LDFLAGS += -static-libstdc++ -static-libgcc +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 -Wno-long-long -Wextra -Wshadow +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 ($(UNAME),Darwin) + CXXFLAGS += -arch $(arch) -mmacosx-version-min=10.6 + LDFLAGS += -arch $(arch) -mmacosx-version-min=10.6 +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 ($(arch),armv7) + # Haiku has pthreads in its libroot, so only link it in on other platforms + ifneq ($(UNAME),Haiku) + LDFLAGS += -lpthread + endif + endif +endif + +### 3.4 Debugging +ifeq ($(debug),no) + CXXFLAGS += -DNDEBUG +else + CXXFLAGS += -g +endif + +### 3.5 Optimization +ifeq ($(optimize),yes) + + ifeq ($(comp),gcc) + CXXFLAGS += -O3 + + ifeq ($(UNAME),Darwin) + ifeq ($(arch),i386) + CXXFLAGS += -mdynamic-no-pic + endif + ifeq ($(arch),x86_64) + CXXFLAGS += -mdynamic-no-pic + endif + endif + + ifeq ($(arch),armv7) + CXXFLAGS += -fno-gcse -mthumb -march=armv7-a -mfloat-abi=softfp + endif + endif + + ifeq ($(comp),mingw) + CXXFLAGS += -O3 + endif + + ifeq ($(comp),icc) + ifeq ($(UNAME),Darwin) + CXXFLAGS += -fast -mdynamic-no-pic + else + CXXFLAGS += -fast + endif + endif + + ifeq ($(comp),clang) + CXXFLAGS += -O3 + + ifeq ($(UNAME),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.6. Bits +ifeq ($(bits),64) + CXXFLAGS += -DIS_64BIT +endif + +### 3.7 prefetch +ifeq ($(prefetch),yes) + ifeq ($(sse),yes) + CXXFLAGS += -msse + DEPENDFLAGS += -msse + endif +else + CXXFLAGS += -DNO_PREFETCH +endif + +### 3.8 bsfq +ifeq ($(bsfq),yes) + CXXFLAGS += -DUSE_BSFQ +endif + +### 3.9 popcnt +ifeq ($(popcnt),yes) + CXXFLAGS += -msse3 -DUSE_POPCNT +endif + +### 3.10 pext +ifeq ($(pext),yes) + CXXFLAGS += -DUSE_PEXT + ifeq ($(comp),$(filter $(comp),gcc clang mingw)) + CXXFLAGS += -mbmi -mbmi2 + endif +endif + +### 3.11 Link Time Optimization, it works since gcc 4.5 but not on mingw. +### 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) + GCC_MAJOR := `$(CXX) -dumpversion | cut -f1 -d.` + GCC_MINOR := `$(CXX) -dumpversion | cut -f2 -d.` + ifeq (1,$(shell expr \( $(GCC_MAJOR) \> 4 \) \| \( $(GCC_MAJOR) \= 4 \& $(GCC_MINOR) \>= 5 \))) + CXXFLAGS += -flto + LDFLAGS += $(CXXFLAGS) + endif + endif + endif +endif + +### ========================================================================== +### Section 4. Public targets +### ========================================================================== + +help: + @echo "" + @echo "To compile stockfish, type: " + @echo "" + @echo "make target ARCH=arch [COMP=comp]" + @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 "Non-standard targets:" + @echo "" + @echo "make hpux > Compile for HP-UX. Compiler = aCC" + @echo "" + @echo "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 "" + +.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 + $(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 + $(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 + +default: + help + +### ========================================================================== +### Section 5. Private targets +### ========================================================================== + +all: $(EXE) .depend + +config-sanity: + @echo "" + @echo "Config:" + @echo "debug: '$(debug)'" + @echo "optimize: '$(optimize)'" + @echo "arch: '$(arch)'" + @echo "os: '$(os)'" + @echo "bits: '$(bits)'" + @echo "prefetch: '$(prefetch)'" + @echo "bsfq: '$(bsfq)'" + @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 "$(os)" = "any" + @test "$(bits)" = "32" || test "$(bits)" = "64" + @test "$(prefetch)" = "yes" || test "$(prefetch)" = "no" + @test "$(bsfq)" = "yes" || test "$(bsfq)" = "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' \ + EXTRALDFLAGS='-lgcov' \ + all + +gcc-profile-clean: + @rm -rf *.gcda *.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 + + +### ========================================================================== +### Section 6. Non-standard targets +### ========================================================================== + +hpux: + $(MAKE) \ + CXX='/opt/aCC/bin/aCC -AA +hpxstd98 -mt +O3 -DNDEBUG -DNO_PREFETCH' \ + CXXFLAGS="" \ + LDFLAGS="" \ + all + diff --git a/src/benchmark.cpp b/src/benchmark.cpp new file mode 100644 index 0000000..f82ec62 --- /dev/null +++ b/src/benchmark.cpp @@ -0,0 +1,159 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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 "notation.h" +#include "position.h" +#include "search.h" +#include "thread.h" +#include "tt.h" +#include "ucioption.h" + +using namespace std; + +static const char* 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" +}; + + +/// 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 secs or number of nodes. + +void benchmark(const Position& current, istream& is) { + + string token; + Search::LimitsType limits; + vector fens; + + // 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; + TT.clear(); + + if (limitType == "time") + limits.movetime = 1000 * atoi(limit.c_str()); // movetime is in ms + + else if (limitType == "nodes") + limits.nodes = atoi(limit.c_str()); + + else if (limitType == "mate") + limits.mate = atoi(limit.c_str()); + + else + limits.depth = atoi(limit.c_str()); + + if (fenFile == "default") + fens.assign(Defaults, Defaults + 30); + + else if (fenFile == "current") + fens.push_back(current.fen()); + + else + { + string fen; + ifstream file(fenFile.c_str()); + + 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; + Search::StateStackPtr st; + Time::point elapsed = Time::now(); + + for (size_t i = 0; i < fens.size(); ++i) + { + Position pos(fens[i], Options["UCI_Chess960"], Threads.main()); + + cerr << "\nPosition: " << i + 1 << '/' << fens.size() << endl; + + if (limitType == "perft") + nodes += Search::perft(pos, limits.depth * ONE_PLY); + + else + { + Threads.start_thinking(pos, limits, st); + Threads.wait_for_think_finished(); + nodes += Search::RootPos.nodes_searched(); + } + } + + elapsed = Time::now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero' + + dbg_print(); // Just before to exit + + 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..0f274c8 --- /dev/null +++ b/src/bitbase.cpp @@ -0,0 +1,175 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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 "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 + }; + + inline Result& operator|=(Result& r, Result v) { return r = Result(r | v); } + + struct KPKPosition { + + KPKPosition(unsigned idx); + operator Result() const { return result; } + Result classify(const std::vector& db) + { return us == WHITE ? classify(db) : classify(db); } + + private: + template Result classify(const std::vector& db); + + Color us; + Square bksq, wksq, psq; + Result result; + }; + +} // namespace + + +bool Bitbases::probe_kpk(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_kpk() { + + unsigned idx, repeat = 1; + std::vector db; + db.reserve(MAX_INDEX); + + // Initialize db with known win / draw positions + for (idx = 0; idx < MAX_INDEX; ++idx) + db.push_back(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) { + + wksq = Square((idx >> 0) & 0x3F); + bksq = Square((idx >> 6) & 0x3F); + us = Color ((idx >> 12) & 0x01); + psq = make_square(File((idx >> 13) & 0x3), RANK_7 - Rank((idx >> 15) & 0x7)); + result = UNKNOWN; + + // Check if two pieces are on the same square or if a king can be captured + if ( square_distance(wksq, bksq) <= 1 + || wksq == psq + || bksq == psq + || (us == WHITE && (StepAttacksBB[PAWN][psq] & bksq))) + result = INVALID; + + else if (us == WHITE) + { + // Immediate win if a pawn can be promoted without getting captured + if ( rank_of(psq) == RANK_7 + && wksq != psq + DELTA_N + && ( square_distance(bksq, psq + DELTA_N) > 1 + ||(StepAttacksBB[KING][wksq] & (psq + DELTA_N)))) + result = WIN; + } + // Immediate draw if it is a stalemate or a king captures undefended pawn + else if ( !(StepAttacksBB[KING][bksq] & ~(StepAttacksBB[KING][wksq] | StepAttacksBB[PAWN][psq])) + || (StepAttacksBB[KING][bksq] & psq & ~StepAttacksBB[KING][wksq])) + result = DRAW; + } + + 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); + + Result r = INVALID; + Bitboard b = StepAttacksBB[KING][Us == WHITE ? wksq : bksq]; + + while (b) + r |= Us == WHITE ? db[index(Them, bksq, pop_lsb(&b), psq)] + : db[index(Them, pop_lsb(&b), wksq, psq)]; + + if (Us == WHITE && rank_of(psq) < RANK_7) + { + Square s = psq + DELTA_N; + r |= db[index(BLACK, bksq, wksq, s)]; // Single push + + if (rank_of(psq) == RANK_2 && s != wksq && s != bksq) + r |= db[index(BLACK, bksq, wksq, s + DELTA_N)]; // Double push + } + + if (Us == WHITE) + return result = r & WIN ? WIN : r & UNKNOWN ? UNKNOWN : DRAW; + else + return result = r & DRAW ? DRAW : r & UNKNOWN ? UNKNOWN : WIN; + } + +} // namespace diff --git a/src/bitboard.cpp b/src/bitboard.cpp new file mode 100644 index 0000000..b3dbd00 --- /dev/null +++ b/src/bitboard.cpp @@ -0,0 +1,330 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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 // For memset + +#include "bitboard.h" +#include "bitcount.h" +#include "rkiss.h" + +CACHE_LINE_ALIGNMENT + +Bitboard RMasks[SQUARE_NB]; +Bitboard RMagics[SQUARE_NB]; +Bitboard* RAttacks[SQUARE_NB]; +unsigned RShifts[SQUARE_NB]; + +Bitboard BMasks[SQUARE_NB]; +Bitboard BMagics[SQUARE_NB]; +Bitboard* BAttacks[SQUARE_NB]; +unsigned BShifts[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 DistanceRingsBB[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]; + +int SquareDistance[SQUARE_NB][SQUARE_NB]; + +namespace { + + // De Bruijn sequences. See chessprogramming.wikispaces.com/BitScan + const uint64_t DeBruijn_64 = 0x3F79D71B4CB0A89ULL; + const uint32_t DeBruijn_32 = 0x783A9B23; + + CACHE_LINE_ALIGNMENT + + int MS1BTable[256]; + Square BSFTable[SQUARE_NB]; + Bitboard RTable[0x19000]; // Storage space for rook attacks + Bitboard BTable[0x1480]; // Storage space for bishop attacks + + typedef unsigned (Fn)(Square, Bitboard); + + void init_magics(Bitboard table[], Bitboard* attacks[], Bitboard magics[], + Bitboard masks[], unsigned shifts[], Square deltas[], Fn index); + + FORCE_INLINE unsigned bsf_index(Bitboard b) { + + // Matt Taylor's folding for 32 bit systems, extended to 64 bits by Kim Walisch + b ^= (b - 1); + return Is64Bit ? (b * DeBruijn_64) >> 58 + : ((unsigned(b) ^ unsigned(b >> 32)) * DeBruijn_32) >> 26; + } +} + +/// lsb()/msb() finds the least/most significant bit in a non-zero bitboard. +/// pop_lsb() finds and clears the least significant bit in a non-zero bitboard. + +#ifndef USE_BSFQ + +Square lsb(Bitboard b) { return BSFTable[bsf_index(b)]; } + +Square pop_lsb(Bitboard* b) { + + Bitboard bb = *b; + *b = bb & (bb - 1); + return BSFTable[bsf_index(bb)]; +} + +Square msb(Bitboard 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 + MS1BTable[b32]); +} + +#endif // ifndef USE_BSFQ + + +/// Bitboards::pretty() returns an ASCII representation of a bitboard to be +/// printed to standard output. This is sometimes 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.append(b & make_square(f, r) ? "| X " : "| "); + + s.append("|\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 (Square s = SQ_A1; s <= SQ_H8; ++s) + { + SquareBB[s] = 1ULL << s; + BSFTable[bsf_index(SquareBB[s])] = s; + } + + for (Bitboard b = 1; b < 256; ++b) + MS1BTable[b] = more_than_one(b) ? MS1BTable[b - 1] : lsb(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(file_distance(s1, s2), rank_distance(s1, s2)); + DistanceRingsBB[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) && square_distance(s, to) < 3) + StepAttacksBB[make_piece(c, pt)][s] |= to; + } + + Square RDeltas[] = { DELTA_N, DELTA_E, DELTA_S, DELTA_W }; + Square BDeltas[] = { DELTA_NE, DELTA_SE, DELTA_SW, DELTA_NW }; + + init_magics(RTable, RAttacks, RMagics, RMasks, RShifts, RDeltas, magic_index); + init_magics(BTable, BAttacks, BMagics, BMasks, BShifts, BDeltas, 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 (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) + { + Piece pc = (PseudoAttacks[BISHOP][s1] & s2) ? W_BISHOP : + (PseudoAttacks[ROOK][s1] & s2) ? W_ROOK : NO_PIECE; + + if (pc == NO_PIECE) + 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) && square_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 MagicBoosters[][RANK_NB] = { { 969, 1976, 2850, 542, 2069, 2852, 1708, 164 }, + { 3101, 552, 3555, 926, 834, 26, 2131, 1117 } }; + RKISS rk; + Bitboard occupancy[4096], reference[4096], edges, b; + int i, size, booster; + + // 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_u64(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; + + booster = MagicBoosters[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] = rk.magic_rand(booster); + while (popcount((magics[s] * masks[s]) >> 56) < 6); + + std::memset(attacks[s], 0, size * sizeof(Bitboard)); + + // A good magic must map every possible occupancy to an index that + // looks up the correct sliding attack in the attacks[s] database. + // Note that we build up the database for square 's' as a side + // effect of verifying the magic. + for (i = 0; i < size; ++i) + { + Bitboard& attack = attacks[s][index(s, occupancy[i])]; + + if (attack && attack != reference[i]) + break; + + assert(reference[i]); + + attack = reference[i]; + } + } while (i < size); + } + } +} diff --git a/src/bitboard.h b/src/bitboard.h new file mode 100644 index 0000000..6a1755b --- /dev/null +++ b/src/bitboard.h @@ -0,0 +1,343 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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 Bitboards { + +void init(); +const std::string pretty(Bitboard b); + +} + +namespace Bitbases { + +void init_kpk(); +bool probe_kpk(Square wksq, Square wpsq, Square bksq, Color us); + +} + +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); + +CACHE_LINE_ALIGNMENT + +extern Bitboard RMasks[SQUARE_NB]; +extern Bitboard RMagics[SQUARE_NB]; +extern Bitboard* RAttacks[SQUARE_NB]; +extern unsigned RShifts[SQUARE_NB]; + +extern Bitboard BMasks[SQUARE_NB]; +extern Bitboard BMagics[SQUARE_NB]; +extern Bitboard* BAttacks[SQUARE_NB]; +extern unsigned BShifts[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 DistanceRingsBB[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]; + +extern int SquareDistance[SQUARE_NB][SQUARE_NB]; + +const Bitboard DarkSquares = 0xAA55AA55AA55AA55ULL; + +/// 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); +} + +inline int square_distance(Square s1, Square s2) { + return SquareDistance[s1][s2]; +} + +inline int file_distance(Square s1, Square s2) { + return abs(file_of(s1) - file_of(s2)); +} + +inline int rank_distance(Square s1, Square s2) { + return abs(rank_of(s1) - rank_of(s2)); +} + + +/// shift_bb() moves bitboard one step along direction Delta. Mainly for pawns. + +template +inline Bitboard shift_bb(Bitboard b) { + + return Delta == DELTA_N ? b << 8 : Delta == DELTA_S ? b >> 8 + : Delta == DELTA_NE ? (b & ~FileHBB) << 9 : Delta == DELTA_SE ? (b & ~FileHBB) >> 7 + : Delta == DELTA_NW ? (b & ~FileABB) << 7 : Delta == DELTA_SW ? (b & ~FileABB) >> 9 + : 0; +} + + +/// rank_bb() and file_bb() take a file or a square as input and return +/// a bitboard representing all 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)]; +} + + +/// adjacent_files_bb() takes a file as input and returns a bitboard representing +/// all squares on the adjacent files. + +inline Bitboard adjacent_files_bb(File f) { + return AdjacentFilesBB[f]; +} + + +/// in_front_bb() takes a color and a rank as input, and returns a bitboard +/// representing all the squares on all ranks in front of the rank, from the +/// given color's point of view. For instance, in_front_bb(BLACK, RANK_3) will +/// give all squares on ranks 1 and 2. + +inline Bitboard in_front_bb(Color c, Rank r) { + return InFrontBB[c][r]; +} + + +/// between_bb() returns a bitboard representing all squares between two squares. +/// 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]; +} + + +/// forward_bb() takes a color and a square as input, and returns a bitboard +/// representing all squares along the line in front of the square, from the +/// point of view of the given color. Definition of the table is: +/// 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() takes a color and a square as input, and returns a bitboard +/// representing all squares that can be attacked by a pawn of the given color +/// when it moves along its file starting from the given square. Definition is: +/// 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() takes a color and a square as input, and returns a +/// bitboard mask which can be used to test if a pawn of the given color on +/// the given square is a passed pawn. Definition of the table is: +/// 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]; +} + + +/// squares_of_color() returns a bitboard representing all squares with the same +/// color of the given square. + +inline Bitboard squares_of_color(Square s) { + return DarkSquares & s ? DarkSquares : ~DarkSquares; +} + + +/// 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; +} + + +/// Functions for computing sliding attack bitboards. Function attacks_bb() takes +/// a square and a bitboard of occupied squares as input, and returns a bitboard +/// representing all squares attacked by Pt (bishop or rook) on the given square. +template +FORCE_INLINE unsigned magic_index(Square s, Bitboard occ) { + + Bitboard* const Masks = Pt == ROOK ? RMasks : BMasks; + Bitboard* const Magics = Pt == ROOK ? RMagics : BMagics; + unsigned* const Shifts = Pt == ROOK ? RShifts : BShifts; + + if (HasPext) + return unsigned(_pext_u64(occ, Masks[s])); + + if (Is64Bit) + return unsigned(((occ & Masks[s]) * Magics[s]) >> Shifts[s]); + + unsigned lo = unsigned(occ) & unsigned(Masks[s]); + unsigned hi = unsigned(occ >> 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 occ) { + return (Pt == ROOK ? RAttacks : BAttacks)[s][magic_index(s, occ)]; +} + +inline Bitboard attacks_bb(Piece pc, Square s, Bitboard occ) { + + switch (type_of(pc)) + { + case BISHOP: return attacks_bb(s, occ); + case ROOK : return attacks_bb(s, occ); + case QUEEN : return attacks_bb(s, occ) | attacks_bb(s, occ); + default : return StepAttacksBB[pc][s]; + } +} + +/// lsb()/msb() finds the least/most significant bit in a non-zero bitboard. +/// pop_lsb() finds and clears the least significant bit in a non-zero bitboard. + +#ifdef USE_BSFQ + +# if defined(_MSC_VER) && !defined(__INTEL_COMPILER) + +FORCE_INLINE Square lsb(Bitboard b) { + unsigned long idx; + _BitScanForward64(&idx, b); + return (Square) idx; +} + +FORCE_INLINE Square msb(Bitboard b) { + unsigned long idx; + _BitScanReverse64(&idx, b); + return (Square) idx; +} + +# elif defined(__arm__) + +FORCE_INLINE int lsb32(uint32_t v) { + __asm__("rbit %0, %1" : "=r"(v) : "r"(v)); + return __builtin_clz(v); +} + +FORCE_INLINE Square msb(Bitboard b) { + return (Square) (63 - __builtin_clzll(b)); +} + +FORCE_INLINE Square lsb(Bitboard b) { + return (Square) (uint32_t(b) ? lsb32(uint32_t(b)) : 32 + lsb32(uint32_t(b >> 32))); +} + +# else + +FORCE_INLINE Square lsb(Bitboard b) { // Assembly code by Heinz van Saanen + Bitboard idx; + __asm__("bsfq %1, %0": "=r"(idx): "rm"(b) ); + return (Square) idx; +} + +FORCE_INLINE Square msb(Bitboard b) { + Bitboard idx; + __asm__("bsrq %1, %0": "=r"(idx): "rm"(b) ); + return (Square) idx; +} + +# endif + +FORCE_INLINE Square pop_lsb(Bitboard* b) { + const Square s = lsb(*b); + *b &= *b - 1; + return s; +} + +#else // if defined(USE_BSFQ) + +extern Square msb(Bitboard b); +extern Square lsb(Bitboard b); +extern Square pop_lsb(Bitboard* b); + +#endif + +/// frontmost_sq() and backmost_sq() find 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/bitcount.h b/src/bitcount.h new file mode 100644 index 0000000..f84c51c --- /dev/null +++ b/src/bitcount.h @@ -0,0 +1,105 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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 BITCOUNT_H_INCLUDED +#define BITCOUNT_H_INCLUDED + +#include +#include "types.h" + +enum BitCountType { + CNT_64, + CNT_64_MAX15, + CNT_32, + CNT_32_MAX15, + CNT_HW_POPCNT +}; + +/// Determine at compile time the best popcount<> specialization according to +/// whether the platform is 32 or 64 bit, the maximum number of non-zero +/// bits to count and if the hardware popcnt instruction is available. +const BitCountType Full = HasPopCnt ? CNT_HW_POPCNT : Is64Bit ? CNT_64 : CNT_32; +const BitCountType Max15 = HasPopCnt ? CNT_HW_POPCNT : Is64Bit ? CNT_64_MAX15 : CNT_32_MAX15; + + +/// popcount() counts the number of non-zero bits in a bitboard +template inline int popcount(Bitboard); + +template<> +inline int popcount(Bitboard b) { + b -= (b >> 1) & 0x5555555555555555ULL; + b = ((b >> 2) & 0x3333333333333333ULL) + (b & 0x3333333333333333ULL); + b = ((b >> 4) + b) & 0x0F0F0F0F0F0F0F0FULL; + return (b * 0x0101010101010101ULL) >> 56; +} + +template<> +inline int popcount(Bitboard b) { + b -= (b >> 1) & 0x5555555555555555ULL; + b = ((b >> 2) & 0x3333333333333333ULL) + (b & 0x3333333333333333ULL); + return (b * 0x1111111111111111ULL) >> 60; +} + +template<> +inline int popcount(Bitboard b) { + unsigned w = unsigned(b >> 32), v = unsigned(b); + v -= (v >> 1) & 0x55555555; // 0-2 in 2 bits + w -= (w >> 1) & 0x55555555; + v = ((v >> 2) & 0x33333333) + (v & 0x33333333); // 0-4 in 4 bits + w = ((w >> 2) & 0x33333333) + (w & 0x33333333); + v = ((v >> 4) + v + (w >> 4) + w) & 0x0F0F0F0F; + return (v * 0x01010101) >> 24; +} + +template<> +inline int popcount(Bitboard b) { + unsigned w = unsigned(b >> 32), v = unsigned(b); + v -= (v >> 1) & 0x55555555; // 0-2 in 2 bits + w -= (w >> 1) & 0x55555555; + v = ((v >> 2) & 0x33333333) + (v & 0x33333333); // 0-4 in 4 bits + w = ((w >> 2) & 0x33333333) + (w & 0x33333333); + return ((v + w) * 0x11111111) >> 28; +} + +template<> +inline int popcount(Bitboard b) { + +#ifndef USE_POPCNT + + assert(false); + return b != 0; // Avoid 'b not used' warning + +#elif defined(_MSC_VER) && defined(__INTEL_COMPILER) + + return _mm_popcnt_u64(b); + +#elif defined(_MSC_VER) + + return (int)__popcnt64(b); + +#else + + __asm__("popcnt %1, %0" : "=r" (b) : "r" (b)); + return b; + +#endif +} + +#endif // #ifndef BITCOUNT_H_INCLUDED diff --git a/src/endgame.cpp b/src/endgame.cpp new file mode 100644 index 0000000..311443e --- /dev/null +++ b/src/endgame.cpp @@ -0,0 +1,856 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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 "bitcount.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 }; + +#ifndef NDEBUG + bool verify_material(const Position& pos, Color c, Value npm, int num_pawns) { + return pos.non_pawn_material(c) == npm && pos.count(c) == num_pawns; + } +#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.list(strongSide)[0]) >= 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"; + + return Position(fen, false, NULL).material_key(); + } + + template + void delete_endgame(const typename M::value_type& p) { delete p.second; } + +} // 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"); +} + +Endgames::~Endgames() { + + for_each(m1.begin(), m1.end(), delete_endgame); + for_each(m2.begin(), m2.end(), delete_endgame); +} + +template +void Endgames::add(const string& code) { + + map((Endgame*)0)[key(code, WHITE)] = new Endgame(WHITE); + map((Endgame*)0)[key(code, BLACK)] = 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.king_square(strongSide); + Square loserKSq = pos.king_square(weakSide); + + Value result = pos.non_pawn_material(strongSide) + + pos.count(strongSide) * PawnValueEg + + PushToEdges[loserKSq] + + PushClose[square_distance(winnerKSq, loserKSq)]; + + if ( pos.count(strongSide) + || pos.count(strongSide) + ||(pos.count(strongSide) && pos.count(strongSide)) + || pos.bishop_pair(strongSide)) + result += VALUE_KNOWN_WIN; + + 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.king_square(strongSide); + Square loserKSq = pos.king_square(weakSide); + Square bishopSq = pos.list(strongSide)[0]; + + // 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[square_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.king_square(strongSide)); + Square bksq = normalize(pos, strongSide, pos.king_square(weakSide)); + Square psq = normalize(pos, strongSide, pos.list(strongSide)[0]); + + Color us = strongSide == pos.side_to_move() ? WHITE : BLACK; + + if (!Bitbases::probe_kpk(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.king_square(strongSide)); + Square bksq = relative_square(strongSide, pos.king_square(weakSide)); + Square rsq = relative_square(strongSide, pos.list(strongSide)[0]); + Square psq = relative_square(strongSide, pos.list(weakSide)[0]); + + 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 - square_distance(wksq, psq); + + // If the weaker side's king is too far from the pawn and the rook, + // it's a win. + else if ( square_distance(bksq, psq) >= 3 + (pos.side_to_move() == weakSide) + && square_distance(bksq, rsq) >= 3) + result = RookValueEg - square_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 + && square_distance(bksq, psq) == 1 + && rank_of(wksq) >= RANK_4 + && square_distance(wksq, psq) > 2 + (pos.side_to_move() == strongSide)) + result = Value(80) - 8 * square_distance(wksq, psq); + + else + result = Value(200) - 8 * ( square_distance(wksq, psq + DELTA_S) + - square_distance(bksq, psq + DELTA_S) + - square_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.king_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.king_square(weakSide); + Square bnsq = pos.list(weakSide)[0]; + Value result = Value(PushToEdges[bksq] + PushAway[square_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.king_square(strongSide); + Square loserKSq = pos.king_square(weakSide); + Square pawnSq = pos.list(weakSide)[0]; + + Value result = Value(PushClose[square_distance(winnerKSq, loserKSq)]); + + if ( relative_rank(weakSide, pawnSq) != RANK_7 + || square_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.king_square(strongSide); + Square loserKSq = pos.king_square(weakSide); + + Value result = QueenValueEg + - RookValueEg + + PushToEdges[loserKSq] + + PushClose[square_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 pawnFile = file_of(pos.list(strongSide)[0]); + + // All pawns are on a single rook file ? + if ( (pawnFile == FILE_A || pawnFile == FILE_H) + && !(pawns & ~file_bb(pawnFile))) + { + Square bishopSq = pos.list(strongSide)[0]; + Square queeningSq = relative_square(strongSide, make_square(pawnFile, RANK_8)); + Square kingSq = pos.king_square(weakSide); + + if ( opposite_colors(queeningSq, bishopSq) + && square_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 ( (pawnFile == FILE_B || pawnFile == FILE_G) + && !(pos.pieces(PAWN) & ~file_bb(pawnFile)) + && 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.king_square(strongSide); + Square weakKingSq = pos.king_square(weakSide); + Square bishopSq = pos.list(strongSide)[0]; + + // 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 = square_distance(weakPawnSq, strongKingSq); + int weakKingDist = square_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.king_square(weakSide); + Square rsq = pos.list(weakSide)[0]; + + if ( relative_rank(weakSide, kingSq) <= RANK_2 + && relative_rank(weakSide, pos.king_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.king_square(strongSide)); + Square bksq = normalize(pos, strongSide, pos.king_square(weakSide)); + Square wrsq = normalize(pos, strongSide, pos.list(strongSide)[0]); + Square wpsq = normalize(pos, strongSide, pos.list(strongSide)[0]); + Square brsq = normalize(pos, strongSide, pos.list(weakSide)[0]); + + 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 + && square_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 + && square_distance(bksq, queeningSq) <= 1 + && rank_of(wksq) + tempo <= RANK_6 + && (rank_of(brsq) == RANK_1 || (!tempo && abs(file_of(brsq) - f) >= 3))) + return SCALE_FACTOR_DRAW; + + if ( r >= RANK_6 + && bksq == queeningSq + && rank_of(brsq) == RANK_1 + && (!tempo || square_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 + DELTA_N + && square_distance(wksq, wpsq) - tempo >= 2 + && square_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 + && (square_distance(wksq, queeningSq) < square_distance(bksq, queeningSq) - 2 + tempo) + && (square_distance(wksq, queeningSq) < square_distance(bksq, wrsq) + tempo)) + return ScaleFactor(SCALE_FACTOR_MAX - 2 * square_distance(wksq, queeningSq)); + + // Similar to the above, but with the pawn further back + if ( f != FILE_A + && file_of(wrsq) == f + && wrsq < wpsq + && (square_distance(wksq, queeningSq) < square_distance(bksq, queeningSq) - 2 + tempo) + && (square_distance(wksq, wpsq + DELTA_N) < square_distance(bksq, wpsq + DELTA_N) - 2 + tempo) + && ( square_distance(bksq, wrsq) + tempo >= 3 + || ( square_distance(wksq, queeningSq) < square_distance(bksq, wrsq) + tempo + && (square_distance(wksq, wpsq + DELTA_N) < square_distance(bksq, wrsq) + tempo)))) + return ScaleFactor( SCALE_FACTOR_MAX + - 8 * square_distance(wpsq, queeningSq) + - 2 * square_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 ( abs(file_of(bksq) - file_of(wpsq)) == 1 + && square_distance(wksq, bksq) > 2) + return ScaleFactor(24 - 2 * square_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.king_square(weakSide); + Square bsq = pos.list(weakSide)[0]; + Square psq = pos.list(strongSide)[0]; + 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 = square_distance(psq + 3 * push, ksq); + + if (d <= 2 && !(d == 0 && ksq == pos.king_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 + && square_distance(psq + 2 * push, ksq) <= 1 + && (PseudoAttacks[BISHOP][bsq] & (psq + push)) + && file_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.list(strongSide)[0]; + Square wpsq2 = pos.list(strongSide)[1]; + Square bksq = pos.king_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 ( file_distance(bksq, wpsq1) <= 1 + && file_distance(bksq, wpsq2) <= 1 + && relative_rank(strongSide, bksq) > r) + { + switch (r) { + case RANK_2: return ScaleFactor(10); + case RANK_3: return ScaleFactor(10); + case RANK_4: return ScaleFactor(15); + case RANK_5: return ScaleFactor(20); + case RANK_6: return ScaleFactor(40); + default: assert(false); + } + } + 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.king_square(weakSide); + Bitboard pawns = pos.pieces(strongSide, PAWN); + Square psq = pos.list(strongSide)[0]; + + // 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)) + && file_distance(ksq, psq) <= 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.list(strongSide)[0]; + Square strongBishopSq = pos.list(strongSide)[0]; + Square weakBishopSq = pos.list(weakSide)[0]; + Square weakKingSq = pos.king_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) + && square_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.list(strongSide)[0]; + Square bbsq = pos.list(weakSide)[0]; + + if (!opposite_colors(wbsq, bbsq)) + return SCALE_FACTOR_NONE; + + Square ksq = pos.king_square(weakSide); + Square psq1 = pos.list(strongSide)[0]; + Square psq2 = pos.list(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 (file_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)) + || abs(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.list(strongSide)[0]; + Square strongBishopSq = pos.list(strongSide)[0]; + Square weakKingSq = pos.king_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.list(strongSide)[0]); + Square weakKingSq = normalize(pos, strongSide, pos.king_square(weakSide)); + + if (pawnSq == SQ_A7 && square_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.list(strongSide)[0]; + Square bishopSq = pos.list(weakSide)[0]; + Square weakKingSq = pos.king_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(square_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.king_square(strongSide)); + Square bksq = normalize(pos, strongSide, pos.king_square(weakSide)); + Square psq = normalize(pos, strongSide, pos.list(strongSide)[0]); + + 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_kpk(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..c3d5dd0 --- /dev/null +++ b/src/endgame.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-2014 Marco Costalba, Joona Kiiski, 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 "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 + SCALE_FUNS, + + 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. Type eg_fun::type returns either ScaleFactor +/// or Value depending on whether the template parameter is 0 or 1. + +template struct eg_fun { typedef Value type; }; +template<> struct eg_fun<1> { typedef ScaleFactor type; }; + + +/// Base and derived templates for endgame evaluation and scaling functions + +template +struct EndgameBase { + + virtual ~EndgameBase() {} + virtual Color color() const = 0; + virtual T operator()(const Position&) const = 0; +}; + + +template SCALE_FUNS)>::type> +struct Endgame : public EndgameBase { + + explicit Endgame(Color c) : strongSide(c), weakSide(~c) {} + Color color() const { return strongSide; } + T operator()(const Position&) const; + +private: + const Color strongSide, weakSide; +}; + + +/// The Endgames class stores the pointers to endgame evaluation and scaling +/// base objects in two std::map typedefs. We then use polymorphism to invoke +/// the actual endgame function by calling its virtual operator(). + +class Endgames { + + typedef std::map::type>*> M1; + typedef std::map::type>*> M2; + + M1 m1; + M2 m2; + + M1& map(M1::mapped_type) { return m1; } + M2& map(M2::mapped_type) { return m2; } + + template void add(const std::string& code); + +public: + Endgames(); + ~Endgames(); + + template T probe(Key key, T& eg) + { return eg = map(eg).count(key) ? map(eg)[key] : NULL; } +}; + +#endif // #ifndef ENDGAME_H_INCLUDED diff --git a/src/evaluate.cpp b/src/evaluate.cpp new file mode 100644 index 0000000..9f62706 --- /dev/null +++ b/src/evaluate.cpp @@ -0,0 +1,880 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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 "bitcount.h" +#include "evaluate.h" +#include "material.h" +#include "pawns.h" +#include "thread.h" +#include "ucioption.h" + +namespace { + + // Struct EvalInfo contains various information computed and collected + // by the evaluation functions. + struct EvalInfo { + + // Pointers to material and pawn hash table entries + Material::Entry* mi; + Pawns::Entry* pi; + + // attackedBy[color][piece type] is a bitboard representing all squares + // attacked by a given color and piece type, attackedBy[color][ALL_PIECES] + // contains all squares attacked by the given color. + Bitboard attackedBy[COLOR_NB][PIECE_TYPE_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 "weight" 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 variables + // QueenAttackWeight, RookAttackWeight, BishopAttackWeight and + // KnightAttackWeight in evaluate.cpp + int kingAttackersWeight[COLOR_NB]; + + // kingAdjacentZoneAttacksCount[color] is the number of attacks to squares + // directly adjacent to the king of the given color. Pieces which attack + // more than one square are counted multiple times. For instance, if black's + // king is on g8 and there's a white knight on g5, this knight adds + // 2 to kingAdjacentZoneAttacksCount[BLACK]. + int kingAdjacentZoneAttacksCount[COLOR_NB]; + + Bitboard pinnedPieces[COLOR_NB]; + }; + + namespace Tracing { + + enum Terms { // First 8 entries are for PieceType + MATERIAL = 8, IMBALANCE, MOBILITY, THREAT, PASSED, SPACE, TOTAL, TERMS_NB + }; + + Score terms[COLOR_NB][TERMS_NB]; + EvalInfo ei; + ScaleFactor sf; + + double to_cp(Value v); + void add_term(int idx, Score term_w, Score term_b = SCORE_ZERO); + void format_row(std::stringstream& ss, const char* name, int idx); + std::string do_trace(const Position& pos); + } + + // Evaluation weights, indexed by evaluation term + enum { Mobility, PawnStructure, PassedPawns, Space, KingSafety }; + const struct Weight { int mg, eg; } Weights[] = { + {289, 344}, {233, 201}, {221, 273}, {46, 0}, {318, 0} + }; + + typedef 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 not occupied by + // friendly pieces. + const Score MobilityBonus[][32] = { + {}, {}, + { S(-65,-50), S(-42,-30), S(-9,-10), S( 3, 0), S(15, 10), S(27, 20), // Knights + S( 37, 28), S( 42, 31), S(44, 33) }, + { S(-52,-47), S(-28,-23), S( 6, 1), S(20, 15), S(34, 29), S(48, 43), // Bishops + S( 60, 55), S( 68, 63), S(74, 68), S(77, 72), S(80, 75), S(82, 77), + S( 84, 79), S( 86, 81) }, + { S(-47,-53), S(-31,-26), S(-5, 0), S( 1, 16), S( 7, 32), S(13, 48), // Rooks + S( 18, 64), S( 22, 80), S(26, 96), S(29,109), S(31,115), S(33,119), + S( 35,122), S( 36,123), S(37,124) }, + { S(-42,-40), S(-28,-23), S(-5, -7), S( 0, 0), S( 6, 10), S(11, 19), // Queens + S( 13, 29), S( 18, 38), S(20, 40), S(21, 41), S(22, 41), S(22, 41), + S( 22, 41), S( 23, 41), S(24, 41), S(25, 41), S(25, 41), S(25, 41), + S( 25, 41), S( 25, 41), S(25, 41), S(25, 41), S(25, 41), S(25, 41), + S( 25, 41), S( 25, 41), S(25, 41), S(25, 41) } + }; + + // Outpost[PieceType][Square] contains bonuses for knights and bishops outposts, + // indexed by piece type and square (from white's point of view). + const Value Outpost[][SQUARE_NB] = { + {// A B C D E F G H + V(0), V(0), V(0), V(0), V(0), V(0), V(0), V(0), // Knights + V(0), V(0), V(0), V(0), V(0), V(0), V(0), V(0), + V(0), V(0), V(4), V(8), V(8), V(4), V(0), V(0), + V(0), V(4),V(17),V(26),V(26),V(17), V(4), V(0), + V(0), V(8),V(26),V(35),V(35),V(26), V(8), V(0), + V(0), V(4),V(17),V(17),V(17),V(17), V(4), V(0) }, + { + V(0), V(0), V(0), V(0), V(0), V(0), V(0), V(0), // Bishops + V(0), V(0), V(0), V(0), V(0), V(0), V(0), V(0), + V(0), V(0), V(5), V(5), V(5), V(5), V(0), V(0), + V(0), V(5),V(10),V(10),V(10),V(10), V(5), V(0), + V(0),V(10),V(21),V(21),V(21),V(21),V(10), V(0), + V(0), V(5), V(8), V(8), V(8), V(8), V(5), V(0) } + }; + + // Threat[attacking][attacked] contains bonuses according to which piece + // type attacks which one. + const Score Threat[][PIECE_TYPE_NB] = { + { S(0, 0), S( 7, 39), S(24, 49), S(24, 49), S(41,100), S(41,100) }, // Minor + { S(0, 0), S(15, 39), S(15, 45), S(15, 45), S(15, 45), S(24, 49) } // Major + }; + + // ThreatenedByPawn[PieceType] contains a penalty according to which piece + // type is attacked by an enemy pawn. + const Score ThreatenedByPawn[] = { + S(0, 0), S(0, 0), S(80, 119), S(80, 119), S(117, 199), S(127, 218) + }; + + // Hanging contains a bonus for each enemy hanging piece + const Score Hanging = S(23, 20); + + #undef S + + const Score RookOnPawn = make_score(10, 28); + const Score RookOpenFile = make_score(43, 21); + const Score RookSemiopenFile = make_score(19, 10); + const Score BishopPawns = make_score( 8, 12); + const Score MinorBehindPawn = make_score(16, 0); + const Score TrappedRook = make_score(92, 0); + const Score Unstoppable = make_score( 0, 20); + + // 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 = make_score(50, 50); + + // SpaceMask[Color] contains the area of the board which is considered + // by the space evaluation. In the middlegame, each side is given a bonus + // based on how many squares inside this area are safe and available for + // friendly minor pieces. + const Bitboard SpaceMask[] = { + (FileCBB | FileDBB | FileEBB | FileFBB) & (Rank2BB | Rank3BB | Rank4BB), + (FileCBB | FileDBB | FileEBB | FileFBB) & (Rank7BB | Rank6BB | Rank5BB) + }; + + // King danger constants and variables. The king danger scores are taken + // from KingDanger[]. Various little "meta-bonuses" measuring the strength + // of the enemy attack are added up into an integer, which is used as an + // index to KingDanger[]. + // + // KingAttackWeights[PieceType] contains king attack weights by piece type + const int KingAttackWeights[] = { 0, 0, 2, 2, 3, 5 }; + + // Bonuses for enemy's safe checks + const int QueenContactCheck = 24; + const int RookContactCheck = 16; + const int QueenCheck = 12; + const int RookCheck = 8; + const int BishopCheck = 2; + const int KnightCheck = 3; + + // KingDanger[attackUnits] contains the actual king danger weighted + // scores, indexed by a calculated integer number. + Score KingDanger[128]; + + + // apply_weight() weighs score 'v' by weight 'w' trying to prevent overflow + Score apply_weight(Score v, const Weight& w) { + return make_score(mg_value(v) * w.mg / 256, eg_value(v) * w.eg / 256); + } + + + // init_eval_info() initializes king bitboards for given color adding + // pawn attacks. To be done at the beginning of the evaluation. + + template + void init_eval_info(const Position& pos, EvalInfo& ei) { + + const Color Them = (Us == WHITE ? BLACK : WHITE); + const Square Down = (Us == WHITE ? DELTA_S : DELTA_N); + + ei.pinnedPieces[Us] = pos.pinned_pieces(Us); + + Bitboard b = ei.attackedBy[Them][KING] = pos.attacks_from(pos.king_square(Them)); + ei.attackedBy[Us][ALL_PIECES] = ei.attackedBy[Us][PAWN] = ei.pi->pawn_attacks(Us); + + // Init king safety tables only if we are going to use them + if (pos.count(Us) && pos.non_pawn_material(Us) > QueenValueMg + PawnValueMg) + { + ei.kingRing[Them] = b | shift_bb(b); + b &= ei.attackedBy[Us][PAWN]; + ei.kingAttackersCount[Us] = b ? popcount(b) : 0; + ei.kingAdjacentZoneAttacksCount[Us] = ei.kingAttackersWeight[Us] = 0; + } + else + ei.kingRing[Them] = ei.kingAttackersCount[Us] = 0; + } + + + // evaluate_outpost() evaluates bishop and knight outpost squares + + template + Score evaluate_outpost(const Position& pos, const EvalInfo& ei, Square s) { + + const Color Them = (Us == WHITE ? BLACK : WHITE); + + assert (Pt == BISHOP || Pt == KNIGHT); + + // Initial bonus based on square + Value bonus = Outpost[Pt == BISHOP][relative_square(Us, s)]; + + // Increase bonus if supported by pawn, especially if the opponent has + // no minor piece which can trade with the outpost piece. + if (bonus && (ei.attackedBy[Us][PAWN] & s)) + { + if ( !pos.pieces(Them, KNIGHT) + && !(squares_of_color(s) & pos.pieces(Them, BISHOP))) + bonus += bonus + bonus / 2; + else + bonus += bonus / 2; + } + + return make_score(bonus * 2, bonus / 2); + } + + + // evaluate_pieces() assigns bonuses and penalties to the pieces of a given color + + template + Score evaluate_pieces(const Position& pos, EvalInfo& ei, Score* mobility, Bitboard* mobilityArea) { + + Bitboard b; + Square s; + Score score = SCORE_ZERO; + + const PieceType NextPt = (Us == WHITE ? Pt : PieceType(Pt + 1)); + const Color Them = (Us == WHITE ? BLACK : WHITE); + const Square* pl = pos.list(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.king_square(Us)][s]; + + ei.attackedBy[Us][ALL_PIECES] |= ei.attackedBy[Us][Pt] |= b; + + if (b & ei.kingRing[Them]) + { + ei.kingAttackersCount[Us]++; + ei.kingAttackersWeight[Us] += KingAttackWeights[Pt]; + Bitboard bb = b & ei.attackedBy[Them][KING]; + if (bb) + ei.kingAdjacentZoneAttacksCount[Us] += popcount(bb); + } + + if (Pt == QUEEN) + b &= ~( ei.attackedBy[Them][KNIGHT] + | ei.attackedBy[Them][BISHOP] + | ei.attackedBy[Them][ROOK]); + + int mob = Pt != QUEEN ? popcount(b & mobilityArea[Us]) + : popcount(b & mobilityArea[Us]); + + mobility[Us] += MobilityBonus[Pt][mob]; + + // Decrease score if we are attacked by an enemy pawn. The remaining part + // of threat evaluation must be done later when we have full attack info. + if (ei.attackedBy[Them][PAWN] & s) + score -= ThreatenedByPawn[Pt]; + + if (Pt == BISHOP || Pt == KNIGHT) + { + // Penalty for bishop with same colored pawns + if (Pt == BISHOP) + score -= BishopPawns * ei.pi->pawns_on_same_color_squares(Us, s); + + // Bishop and knight outpost square + if (!(pos.pieces(Them, PAWN) & pawn_attack_span(Us, s))) + score += evaluate_outpost(pos, ei, s); + + // Bishop or knight behind a pawn + if ( relative_rank(Us, s) < RANK_5 + && (pos.pieces(PAWN) & (s + pawn_push(Us)))) + score += MinorBehindPawn; + } + + if (Pt == ROOK) + { + // Rook piece attacking enemy pawns on the same rank/file + if (relative_rank(Us, s) >= RANK_5) + { + Bitboard pawns = pos.pieces(Them, PAWN) & PseudoAttacks[ROOK][s]; + if (pawns) + score += popcount(pawns) * RookOnPawn; + } + + // Give a bonus for a rook on a open or semi-open file + if (ei.pi->semiopen_file(Us, file_of(s))) + score += ei.pi->semiopen_file(Them, file_of(s)) ? RookOpenFile : RookSemiopenFile; + + if (mob > 3 || ei.pi->semiopen_file(Us, file_of(s))) + continue; + + Square ksq = pos.king_square(Us); + + // Penalize rooks which are trapped by a king. Penalize more if the + // king has lost its castling capability. + 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)); + } + + // 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 ? DELTA_E : DELTA_W); + 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 (Trace) + Tracing::terms[Us][Pt] = score; + + return score - evaluate_pieces(pos, ei, mobility, mobilityArea); + } + + template<> + Score evaluate_pieces(const Position&, EvalInfo&, Score*, Bitboard*) { return SCORE_ZERO; } + template<> + Score evaluate_pieces(const Position&, EvalInfo&, Score*, Bitboard*) { return SCORE_ZERO; } + + + // evaluate_king() assigns bonuses and penalties to a king of a given color + + template + Score evaluate_king(const Position& pos, const EvalInfo& ei) { + + const Color Them = (Us == WHITE ? BLACK : WHITE); + + Bitboard undefended, b, b1, b2, safe; + int attackUnits; + const Square ksq = pos.king_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 around the king which have no defenders + // apart from the king itself + undefended = ei.attackedBy[Them][ALL_PIECES] + & ei.attackedBy[Us][KING] + & ~( ei.attackedBy[Us][PAWN] | ei.attackedBy[Us][KNIGHT] + | ei.attackedBy[Us][BISHOP] | ei.attackedBy[Us][ROOK] + | ei.attackedBy[Us][QUEEN]); + + // Initialize the 'attackUnits' variable, which is used later on as an + // index to the KingDanger[] array. 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). + attackUnits = std::min(20, (ei.kingAttackersCount[Them] * ei.kingAttackersWeight[Them]) / 2) + + 3 * (ei.kingAdjacentZoneAttacksCount[Them] + popcount(undefended)) + + 2 * (ei.pinnedPieces[Us] != 0) + - mg_value(score) / 32; + + // Analyse the enemy's safe queen contact checks. Firstly, find the + // undefended squares around the king that are attacked by the enemy's + // queen... + b = undefended & ei.attackedBy[Them][QUEEN] & ~pos.pieces(Them); + if (b) + { + // ...and then remove squares not supported by another enemy piece + b &= ( ei.attackedBy[Them][PAWN] | ei.attackedBy[Them][KNIGHT] + | ei.attackedBy[Them][BISHOP] | ei.attackedBy[Them][ROOK]); + + if (b) + attackUnits += QueenContactCheck * popcount(b); + } + + // Analyse the enemy's safe rook contact checks. Firstly, find the + // undefended squares around the king that are attacked by the enemy's + // rooks... + b = undefended & ei.attackedBy[Them][ROOK] & ~pos.pieces(Them); + + // Consider only squares where the enemy's rook gives check + b &= PseudoAttacks[ROOK][ksq]; + + if (b) + { + // ...and then remove squares not supported by another enemy piece + b &= ( ei.attackedBy[Them][PAWN] | ei.attackedBy[Them][KNIGHT] + | ei.attackedBy[Them][BISHOP] | ei.attackedBy[Them][QUEEN]); + + if (b) + attackUnits += RookContactCheck * popcount(b); + } + + // Analyse the enemy's safe distance checks for sliders and knights + safe = ~(pos.pieces(Them) | ei.attackedBy[Us][ALL_PIECES]); + + b1 = pos.attacks_from(ksq) & safe; + b2 = pos.attacks_from(ksq) & safe; + + // Enemy queen safe checks + b = (b1 | b2) & ei.attackedBy[Them][QUEEN]; + if (b) + attackUnits += QueenCheck * popcount(b); + + // Enemy rooks safe checks + b = b1 & ei.attackedBy[Them][ROOK]; + if (b) + attackUnits += RookCheck * popcount(b); + + // Enemy bishops safe checks + b = b2 & ei.attackedBy[Them][BISHOP]; + if (b) + attackUnits += BishopCheck * popcount(b); + + // Enemy knights safe checks + b = pos.attacks_from(ksq) & ei.attackedBy[Them][KNIGHT] & safe; + if (b) + attackUnits += KnightCheck * popcount(b); + + // To index KingDanger[] attackUnits must be in [0, 99] range + attackUnits = std::min(99, std::max(0, attackUnits)); + + // Finally, extract the king danger score from the KingDanger[] + // array and subtract the score from evaluation. + score -= KingDanger[attackUnits]; + } + + if (Trace) + Tracing::terms[Us][KING] = score; + + return score; + } + + + // evaluate_threats() assigns bonuses according to the type of attacking piece + + template + Score evaluate_threats(const Position& pos, const EvalInfo& ei) { + + const Color Them = (Us == WHITE ? BLACK : WHITE); + + enum { Minor, Major }; + + // Enemies not defended by a pawn and under our attack + Bitboard b, weakEnemies = pos.pieces(Them) + & ~ei.attackedBy[Them][PAWN] + & ei.attackedBy[Us][ALL_PIECES]; + if (!weakEnemies) + return SCORE_ZERO; + + Score score = SCORE_ZERO; + + b = weakEnemies & (ei.attackedBy[Us][KNIGHT] | ei.attackedBy[Us][BISHOP]); + if (b) + score += Threat[Minor][type_of(pos.piece_on(lsb(b)))]; + + b = weakEnemies & (ei.attackedBy[Us][ROOK] | ei.attackedBy[Us][QUEEN]); + if (b) + score += Threat[Major][type_of(pos.piece_on(lsb(b)))]; + + b = weakEnemies & ~ei.attackedBy[Them][ALL_PIECES]; + if (b) + score += more_than_one(b) ? Hanging * popcount(b) : Hanging; + + if (Trace) + Tracing::terms[Us][Tracing::THREAT] = 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, 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)); + + int r = relative_rank(Us, s) - RANK_2; + int rr = r * (r - 1); + + // Base bonus based on rank + Value mbonus = Value(17 * rr), ebonus = Value(7 * (rr + r + 1)); + + if (rr) + { + Square blockSq = s + pawn_push(Us); + + // Adjust bonus based on the king's proximity + ebonus += square_distance(pos.king_square(Them), blockSq) * 5 * rr + - square_distance(pos.king_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 -= square_distance(pos.king_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); + + Bitboard 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 ? 15 : !(unsafeSquares & blockSq) ? 9 : 0; + + // If the path to 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 * 3 + r * 2 + 3, ebonus += rr + r * 2; + + } // rr != 0 + + if (pos.count(Us) < pos.count(Them)) + ebonus += ebonus / 4; + + score += make_score(mbonus, ebonus); + } + + if (Trace) + Tracing::terms[Us][Tracing::PASSED] = apply_weight(score, Weights[PassedPawns]); + + // Add the scores to the middlegame and endgame eval + return apply_weight(score, Weights[PassedPawns]); + } + + + // evaluate_unstoppable_pawns() scores the most advanced among the passed and + // candidate pawns. In case both players have no pieces but pawns, this is + // somewhat related to the possibility that pawns are unstoppable. + + Score evaluate_unstoppable_pawns(Color us, const EvalInfo& ei) { + + Bitboard b = ei.pi->passed_pawns(us) | ei.pi->candidate_pawns(us); + + return b ? Unstoppable * int(relative_rank(us, frontmost_sq(us, b))) : SCORE_ZERO; + } + + + // 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 scaled by a weight taken from the + // material hash table. The aim is to improve play on game opening. + template + int evaluate_space(const Position& pos, const EvalInfo& ei) { + + const Color Them = (Us == WHITE ? BLACK : WHITE); + + // 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[Us] + & ~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 + return popcount((Us == WHITE ? safe << 32 : safe >> 32) | (behind & safe)); + } + + + // do_evaluate() is the evaluation entry point, called directly from evaluate() + + template + Value do_evaluate(const Position& pos) { + + assert(!pos.checkers()); + + EvalInfo ei; + Score score, mobility[2] = { SCORE_ZERO, SCORE_ZERO }; + Thread* thisThread = pos.this_thread(); + + // Initialize score by reading the incrementally updated scores included + // in the position object (material + piece square tables). + // Score is computed from the point of view of white. + score = pos.psq_score(); + + // Probe the material hash table + ei.mi = Material::probe(pos, thisThread->materialTable, thisThread->endgames); + score += ei.mi->material_value(); + + // If we have a specialized evaluation function for the current material + // configuration, call it and return. + if (ei.mi->specialized_eval_exists()) + return ei.mi->evaluate(pos); + + // Probe the pawn hash table + ei.pi = Pawns::probe(pos, thisThread->pawnsTable); + score += apply_weight(ei.pi->pawns_value(), Weights[PawnStructure]); + + // Initialize attack and king safety bitboards + init_eval_info(pos, ei); + init_eval_info(pos, ei); + + ei.attackedBy[WHITE][ALL_PIECES] |= ei.attackedBy[WHITE][KING]; + ei.attackedBy[BLACK][ALL_PIECES] |= ei.attackedBy[BLACK][KING]; + + // Do not include in mobility squares protected by enemy pawns or occupied by our pawns or king + Bitboard mobilityArea[] = { ~(ei.attackedBy[BLACK][PAWN] | pos.pieces(WHITE, PAWN, KING)), + ~(ei.attackedBy[WHITE][PAWN] | pos.pieces(BLACK, PAWN, KING)) }; + + // Evaluate pieces and mobility + score += evaluate_pieces(pos, ei, mobility, mobilityArea); + score += apply_weight(mobility[WHITE] - mobility[BLACK], Weights[Mobility]); + + // Evaluate kings after all other pieces because we need complete 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)) + score += evaluate_unstoppable_pawns(WHITE, ei) + - evaluate_unstoppable_pawns(BLACK, ei); + + // Evaluate space for both sides, only in middlegame + if (ei.mi->space_weight()) + { + int s = evaluate_space(pos, ei) - evaluate_space(pos, ei); + score += apply_weight(s * ei.mi->space_weight(), Weights[Space]); + } + + // Scale winning side if position is more drawish than it appears + ScaleFactor sf = eg_value(score) > VALUE_DRAW ? ei.mi->scale_factor(pos, WHITE) + : ei.mi->scale_factor(pos, BLACK); + + // If we don't already have an unusual scale factor, check for opposite + // colored bishop endgames, and use a lower scale for those. + if ( ei.mi->game_phase() < PHASE_MIDGAME + && pos.opposite_bishops() + && (sf == SCALE_FACTOR_NORMAL || sf == SCALE_FACTOR_ONEPAWN)) + { + // Ignoring any pawns, do both sides only have a single bishop and no + // other pieces? + if ( pos.non_pawn_material(WHITE) == BishopValueMg + && pos.non_pawn_material(BLACK) == BishopValueMg) + { + // Check for KBP vs KB with only a single pawn that is almost + // certainly a draw or at least two pawns. + bool one_pawn = (pos.count(WHITE) + pos.count(BLACK) == 1); + sf = one_pawn ? ScaleFactor(8) : ScaleFactor(32); + } + else + // Endgame with opposite-colored bishops, but also other pieces. Still + // a bit drawish, but not as drawish as with only the two bishops. + sf = ScaleFactor(50 * sf / SCALE_FACTOR_NORMAL); + } + + // Interpolate between a middlegame and a (scaled by 'sf') endgame score + Value v = mg_value(score) * int(ei.mi->game_phase()) + + eg_value(score) * int(PHASE_MIDGAME - ei.mi->game_phase()) * sf / SCALE_FACTOR_NORMAL; + + v /= int(PHASE_MIDGAME); + + // In case of tracing add all single evaluation contributions for both white and black + if (Trace) + { + Tracing::add_term(Tracing::MATERIAL, pos.psq_score()); + Tracing::add_term(Tracing::IMBALANCE, ei.mi->material_value()); + Tracing::add_term(PAWN, ei.pi->pawns_value()); + Tracing::add_term(Tracing::MOBILITY, apply_weight(mobility[WHITE], Weights[Mobility]) + , apply_weight(mobility[BLACK], Weights[Mobility])); + Score w = ei.mi->space_weight() * evaluate_space(pos, ei); + Score b = ei.mi->space_weight() * evaluate_space(pos, ei); + Tracing::add_term(Tracing::SPACE, apply_weight(w, Weights[Space]), apply_weight(b, Weights[Space])); + Tracing::add_term(Tracing::TOTAL, score); + Tracing::ei = ei; + Tracing::sf = sf; + } + + return pos.side_to_move() == WHITE ? v : -v; + } + + + // Tracing function definitions + + double Tracing::to_cp(Value v) { return double(v) / PawnValueEg; } + + void Tracing::add_term(int idx, Score wScore, Score bScore) { + + terms[WHITE][idx] = wScore; + terms[BLACK][idx] = bScore; + } + + void Tracing::format_row(std::stringstream& ss, const char* name, int idx) { + + Score wScore = terms[WHITE][idx]; + Score bScore = terms[BLACK][idx]; + + switch (idx) { + case MATERIAL: case IMBALANCE: case PAWN: case TOTAL: + ss << std::setw(15) << name << " | --- --- | --- --- | " + << std::setw(5) << to_cp(mg_value(wScore - bScore)) << " " + << std::setw(5) << to_cp(eg_value(wScore - bScore)) << " \n"; + break; + default: + ss << std::setw(15) << name << " | " << std::noshowpos + << std::setw(5) << to_cp(mg_value(wScore)) << " " + << std::setw(5) << to_cp(eg_value(wScore)) << " | " + << std::setw(5) << to_cp(mg_value(bScore)) << " " + << std::setw(5) << to_cp(eg_value(bScore)) << " | " + << std::setw(5) << to_cp(mg_value(wScore - bScore)) << " " + << std::setw(5) << to_cp(eg_value(wScore - bScore)) << " \n"; + } + } + + std::string Tracing::do_trace(const Position& pos) { + + std::memset(terms, 0, sizeof(terms)); + + Value v = do_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"; + + format_row(ss, "Material", MATERIAL); + format_row(ss, "Imbalance", IMBALANCE); + format_row(ss, "Pawns", PAWN); + format_row(ss, "Knights", KNIGHT); + format_row(ss, "Bishops", BISHOP); + format_row(ss, "Rooks", ROOK); + format_row(ss, "Queens", QUEEN); + format_row(ss, "Mobility", MOBILITY); + format_row(ss, "King safety", KING); + format_row(ss, "Threats", THREAT); + format_row(ss, "Passed pawns", PASSED); + format_row(ss, "Space", SPACE); + + ss << "----------------+-------------+-------------+-------------\n"; + format_row(ss, "Total", TOTAL); + + ss << "\nTotal Evaluation: " << to_cp(v) << " (white side)\n"; + + return ss.str(); + } + +} // namespace + + +namespace Eval { + + /// evaluate() is the main evaluation function. It returns a static evaluation + /// of the position always from the point of view of the side to move. + + Value evaluate(const Position& pos) { + return do_evaluate(pos) + Tempo; + } + + + /// 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. It's mainly used for + /// debugging. + std::string trace(const Position& pos) { + return Tracing::do_trace(pos); + } + + + /// init() computes evaluation weights from the corresponding UCI parameters + /// and setup king tables. + + void init() { + + const double MaxSlope = 30; + const double Peak = 1280; + + for (int t = 0, i = 1; i < 100; ++i) + { + t = int(std::min(Peak, std::min(0.4 * i * i, t + MaxSlope))); + KingDanger[i] = apply_weight(make_score(t, 0), Weights[KingSafety]); + } + } + +} // namespace Eval diff --git a/src/evaluate.h b/src/evaluate.h new file mode 100644 index 0000000..fff2476 --- /dev/null +++ b/src/evaluate.h @@ -0,0 +1,37 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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 "types.h" + +class Position; + +namespace Eval { + +const Value Tempo = Value(17); // Must be visible to search + +extern void init(); +extern Value evaluate(const Position& pos); +extern std::string trace(const Position& pos); + +} + +#endif // #ifndef EVALUATE_H_INCLUDED diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..cbb260f --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,47 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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 "evaluate.h" +#include "position.h" +#include "search.h" +#include "thread.h" +#include "tt.h" +#include "ucioption.h" + +int main(int argc, char* argv[]) { + + std::cout << engine_info() << std::endl; + + UCI::init(Options); + Bitboards::init(); + Position::init(); + Bitbases::init_kpk(); + Search::init(); + Pawns::init(); + Eval::init(); + Threads.init(); + TT.resize(Options["Hash"]); + + UCI::loop(argc, argv); + + Threads.exit(); +} diff --git a/src/material.cpp b/src/material.cpp new file mode 100644 index 0000000..b7db134 --- /dev/null +++ b/src/material.cpp @@ -0,0 +1,248 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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 + +#include "material.h" + +using namespace std; + +namespace { + + // Polynomial material balance parameters + + // pair pawn knight bishop rook queen + const int Linear[6] = { 1852, -162, -1122, -183, 249, -154 }; + + const int QuadraticSameSide[][PIECE_TYPE_NB] = { + // OUR PIECES + // pair pawn knight bishop rook queen + { 0 }, // Bishop pair + { 39, 2 }, // Pawn + { 35, 271, -4 }, // knight OUR PIECES + { 0, 105, 4, 0 }, // Bishop + { -27, -2, 46, 100, -141 }, // Rook + {-177, 25, 129, 142, -137, 0 } // Queen + }; + + const int QuadraticOppositeSide[][PIECE_TYPE_NB] = { + // THEIR PIECES + // pair pawn knight bishop rook queen + { 0 }, // Bishop pair + { 37, 0 }, // Pawn + { 10, 62, 0 }, // Knight OUR PIECES + { 57, 64, 39, 0 }, // Bishop + { 50, 40, 23, -22, 0 }, // Rook + { 98, 105, -39, 141, 274, 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 templates used to detect a given material distribution + template bool is_KXK(const Position& pos) { + const Color Them = (Us == WHITE ? BLACK : WHITE); + return !more_than_one(pos.pieces(Them)) + && pos.non_pawn_material(Us) >= RookValueMg; + } + + template bool is_KBPsKs(const Position& pos) { + return pos.non_pawn_material(Us) == BishopValueMg + && pos.count(Us) == 1 + && pos.count(Us) >= 1; + } + + template bool is_KQKRPs(const Position& pos) { + const Color Them = (Us == WHITE ? BLACK : WHITE); + return !pos.count(Us) + && pos.non_pawn_material(Us) == QueenValueMg + && pos.count(Us) == 1 + && pos.count(Them) == 1 + && pos.count(Them) >= 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 = Linear[pt1]; + + for (int pt2 = NO_PIECE_TYPE; pt2 <= pt1; ++pt2) + v += QuadraticSameSide[pt1][pt2] * pieceCount[Us][pt2] + + QuadraticOppositeSide[pt1][pt2] * pieceCount[Them][pt2]; + + bonus += pieceCount[Us][pt1] * v; + } + + return bonus; + } + +} // namespace + +namespace Material { + +/// Material::probe() takes a position object as input, looks up a MaterialEntry +/// object, and returns a pointer to it. If the material configuration is not +/// already present in the table, it is computed and stored there, so we don't +/// have to recompute everything when the same material configuration occurs again. + +Entry* probe(const Position& pos, Table& entries, Endgames& endgames) { + + Key key = pos.material_key(); + Entry* e = entries[key]; + + // If e->key matches the position's material hash key, it means that we + // have analysed this material configuration before, and we can simply + // return the information we found the last time instead of recomputing it. + 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 (endgames.probe(key, e->evaluationFunction)) + return e; + + if (is_KXK(pos)) + { + e->evaluationFunction = &EvaluateKXK[WHITE]; + return e; + } + + if (is_KXK(pos)) + { + e->evaluationFunction = &EvaluateKXK[BLACK]; + return e; + } + + // OK, we didn't find any special evaluation function for the current + // material configuration. Is there a suitable scaling function? + // + // We face problems when there are several conflicting applicable + // scaling functions and we need to decide which one to use. + EndgameBase* sf; + + if (endgames.probe(key, sf)) + { + e->scalingFunction[sf->color()] = sf; + return e; + } + + // Generic scaling functions that refer to more than one material + // distribution. They should be probed after the specialized ones. + // Note that these ones don't return after setting the function. + if (is_KBPsKs(pos)) + e->scalingFunction[WHITE] = &ScaleKBPsK[WHITE]; + + if (is_KBPsKs(pos)) + e->scalingFunction[BLACK] = &ScaleKBPsK[BLACK]; + + if (is_KQKRPs(pos)) + e->scalingFunction[WHITE] = &ScaleKQKRPs[WHITE]; + + else if (is_KQKRPs(pos)) + e->scalingFunction[BLACK] = &ScaleKQKRPs[BLACK]; + + 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)) + { + 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]; + } + } + + // No pawns makes it difficult to win, even with a material advantage. This + // catches some trivial draws like KK, KBK and KNK and gives a very 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 : 12); + + if (!pos.count(BLACK) && npm_b - npm_w <= BishopValueMg) + e->factor[BLACK] = uint8_t(npm_b < RookValueMg ? SCALE_FACTOR_DRAW : npm_w <= BishopValueMg ? 4 : 12); + + 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; + + // Compute the space weight + if (npm_w + npm_b >= 2 * QueenValueMg + 4 * RookValueMg + 2 * KnightValueMg) + { + int minorPieceCount = pos.count(WHITE) + pos.count(WHITE) + + pos.count(BLACK) + pos.count(BLACK); + + e->spaceWeight = make_score(minorPieceCount * minorPieceCount, 0); + } + + // 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..f05541c --- /dev/null +++ b/src/material.h @@ -0,0 +1,74 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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 balance 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 material_value() const { return make_score(value, value); } + Score space_weight() const { return spaceWeight; } + Phase game_phase() const { return gamePhase; } + bool specialized_eval_exists() const { return evaluationFunction != NULL; } + 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 need not be a constant: It can also be a function + // which should be applied to the position. For instance, in KBP vs K endgames, + // a scaling function for draws with rook pawns and wrong-colored bishops. + + ScaleFactor scale_factor(const Position& pos, Color c) const { + + return !scalingFunction[c] || (*scalingFunction[c])(pos) == SCALE_FACTOR_NONE + ? ScaleFactor(factor[c]) : (*scalingFunction[c])(pos); + } + + Key key; + int16_t value; + uint8_t factor[COLOR_NB]; + EndgameBase* evaluationFunction; + EndgameBase* scalingFunction[COLOR_NB]; + Score spaceWeight; + Phase gamePhase; +}; + +typedef HashTable Table; + +Entry* probe(const Position& pos, Table& entries, Endgames& endgames); + +} // namespace Material + +#endif // #ifndef MATERIAL_H_INCLUDED diff --git a/src/misc.cpp b/src/misc.cpp new file mode 100644 index 0000000..b5a14db --- /dev/null +++ b/src/misc.cpp @@ -0,0 +1,204 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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 "thread.h" + +using namespace std; + +/// Version number. If Version is left empty, then compile date in the format +/// DD-MM-YY and show in engine_info. +static const string Version = ""; + + +/// 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 ? " SSE4.2" : "")) + << (to_uci ? "\nid author ": " by ") + << "Tord Romstad, Marco Costalba and Joona Kiiski"; + + 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_c(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; +} + + +/// 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 splitted streambuf for cin and cout + + Tie(streambuf* b, ofstream* f) : buf(b), file(f) {} + + int sync() { return file->rdbuf()->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; + ofstream* file; + + int log(int c, const char* prefix) { + + static int last = '\n'; + + if (last == '\n') + file->rdbuf()->sputn(prefix, 3); + + return last = file->rdbuf()->sputc((char)c); + } +}; + +class Logger { + + Logger() : in(cin.rdbuf(), &file), out(cout.rdbuf(), &file) {} + ~Logger() { start(false); } + + ofstream file; + Tie in, out; + +public: + static void start(bool b) { + + static Logger l; + + if (b && !l.file.is_open()) + { + l.file.open("io_log.txt", ifstream::out | ifstream::app); + cin.rdbuf(&l.in); + cout.rdbuf(&l.out); + } + else if (!b && l.file.is_open()) + { + cout.rdbuf(l.out.buf); + cin.rdbuf(l.in.buf); + l.file.close(); + } + } +}; + + +/// 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(bool b) { Logger::start(b); } + + +/// timed_wait() waits for msec milliseconds. It is mainly a helper to wrap +/// the conversion from milliseconds to struct timespec, as used by pthreads. + +void timed_wait(WaitCondition& sleepCond, Lock& sleepLock, int msec) { + +#ifdef _WIN32 + int tm = msec; +#else + timespec ts, *tm = &ts; + uint64_t ms = Time::now() + msec; + + ts.tv_sec = ms / 1000; + ts.tv_nsec = (ms % 1000) * 1000000LL; +#endif + + cond_timedwait(sleepCond, sleepLock, tm); +} + + +/// 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(char*) {} + +#else + +void prefetch(char* 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(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..92bde8f --- /dev/null +++ b/src/misc.h @@ -0,0 +1,68 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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 "types.h" + +extern const std::string engine_info(bool to_uci = false); +extern void timed_wait(WaitCondition&, Lock&, int); +extern void prefetch(char* addr); +extern void start_logger(bool b); + +extern void dbg_hit_on(bool b); +extern void dbg_hit_on_c(bool c, bool b); +extern void dbg_mean_of(int v); +extern void dbg_print(); + + +struct Log : public std::ofstream { + Log(const std::string& f = "log.txt") : std::ofstream(f.c_str(), std::ios::out | std::ios::app) {} + ~Log() { if (is_open()) close(); } +}; + + +namespace Time { + typedef int64_t point; + inline point now() { return system_time_to_msec(); } +} + + +template +struct HashTable { + HashTable() : table(Size, Entry()) {} + Entry* operator[](Key k) { return &table[(uint32_t)k & (Size - 1)]; } + +private: + std::vector table; +}; + + +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 + +#endif // #ifndef MISC_H_INCLUDED diff --git a/src/movegen.cpp b/src/movegen.cpp new file mode 100644 index 0000000..dab830b --- /dev/null +++ b/src/movegen.cpp @@ -0,0 +1,418 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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* mlist, Color us, const CheckInfo* ci) { + + static const bool KingSide = (Cr == WHITE_OO || Cr == BLACK_OO); + + if (pos.castling_impeded(Cr) || !pos.can_castle(Cr)) + return mlist; + + // After castling, the rook and king final positions are the same in Chess960 + // as they would be in standard chess. + Square kfrom = pos.king_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 ? DELTA_W : DELTA_E + : KingSide ? DELTA_W : DELTA_E; + + for (Square s = kto; s != kfrom; s += K) + if (pos.attackers_to(s) & enemies) + return mlist; + + // 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 mlist; + + Move m = make(kfrom, rfrom); + + if (Checks && !pos.gives_check(m, *ci)) + return mlist; + + (mlist++)->move = m; + + return mlist; + } + + + template + inline ExtMove* generate_promotions(ExtMove* mlist, Bitboard pawnsOn7, + Bitboard target, const CheckInfo* ci) { + + Bitboard b = shift_bb(pawnsOn7) & target; + + while (b) + { + Square to = pop_lsb(&b); + + if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) + (mlist++)->move = make(to - Delta, to, QUEEN); + + if (Type == QUIETS || Type == EVASIONS || Type == NON_EVASIONS) + { + (mlist++)->move = make(to - Delta, to, ROOK); + (mlist++)->move = make(to - Delta, to, BISHOP); + (mlist++)->move = make(to - Delta, 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] & ci->ksq)) + (mlist++)->move = make(to - Delta, to, KNIGHT); + else + (void)ci; // Silence a warning under MSVC + } + + return mlist; + } + + + template + ExtMove* generate_pawn_moves(const Position& pos, ExtMove* mlist, + Bitboard target, const CheckInfo* ci) { + + // 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 ? DELTA_N : DELTA_S); + const Square Right = (Us == WHITE ? DELTA_NE : DELTA_SW); + const Square Left = (Us == WHITE ? DELTA_NW : DELTA_SE); + + Bitboard b1, b2, dc1, dc2, 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()); + + b1 = shift_bb(pawnsNotOn7) & emptySquares; + b2 = shift_bb(b1 & TRank3BB) & emptySquares; + + if (Type == EVASIONS) // Consider only blocking squares + { + b1 &= target; + b2 &= target; + } + + if (Type == QUIET_CHECKS) + { + b1 &= pos.attacks_from(ci->ksq, Them); + b2 &= pos.attacks_from(ci->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. + if (pawnsNotOn7 & ci->dcCandidates) + { + dc1 = shift_bb(pawnsNotOn7 & ci->dcCandidates) & emptySquares & ~file_bb(ci->ksq); + dc2 = shift_bb(dc1 & TRank3BB) & emptySquares; + + b1 |= dc1; + b2 |= dc2; + } + } + + while (b1) + { + Square to = pop_lsb(&b1); + (mlist++)->move = make_move(to - Up, to); + } + + while (b2) + { + Square to = pop_lsb(&b2); + (mlist++)->move = 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; + + mlist = generate_promotions(mlist, pawnsOn7, enemies, ci); + mlist = generate_promotions(mlist, pawnsOn7, enemies, ci); + mlist = generate_promotions(mlist, pawnsOn7, emptySquares, ci); + } + + // Standard and en-passant captures + if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) + { + b1 = shift_bb(pawnsNotOn7) & enemies; + b2 = shift_bb(pawnsNotOn7) & enemies; + + while (b1) + { + Square to = pop_lsb(&b1); + (mlist++)->move = make_move(to - Right, to); + } + + while (b2) + { + Square to = pop_lsb(&b2); + (mlist++)->move = 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 mlist; + + b1 = pawnsNotOn7 & pos.attacks_from(pos.ep_square(), Them); + + assert(b1); + + while (b1) + (mlist++)->move = make(pop_lsb(&b1), pos.ep_square()); + } + } + + return mlist; + } + + + template FORCE_INLINE + ExtMove* generate_moves(const Position& pos, ExtMove* mlist, Color us, + Bitboard target, const CheckInfo* ci) { + + assert(Pt != KING && Pt != PAWN); + + const Square* pl = pos.list(us); + + for (Square from = *pl; from != SQ_NONE; from = *++pl) + { + if (Checks) + { + if ( (Pt == BISHOP || Pt == ROOK || Pt == QUEEN) + && !(PseudoAttacks[Pt][from] & target & ci->checkSq[Pt])) + continue; + + if (unlikely(ci->dcCandidates) && (ci->dcCandidates & from)) + continue; + } + + Bitboard b = pos.attacks_from(from) & target; + + if (Checks) + b &= ci->checkSq[Pt]; + + while (b) + (mlist++)->move = make_move(from, pop_lsb(&b)); + } + + return mlist; + } + + + template FORCE_INLINE + ExtMove* generate_all(const Position& pos, ExtMove* mlist, Bitboard target, + const CheckInfo* ci = NULL) { + + const bool Checks = Type == QUIET_CHECKS; + + mlist = generate_pawn_moves(pos, mlist, target, ci); + mlist = generate_moves(pos, mlist, Us, target, ci); + mlist = generate_moves(pos, mlist, Us, target, ci); + mlist = generate_moves< ROOK, Checks>(pos, mlist, Us, target, ci); + mlist = generate_moves< QUEEN, Checks>(pos, mlist, Us, target, ci); + + if (Type != QUIET_CHECKS && Type != EVASIONS) + { + Square ksq = pos.king_square(Us); + Bitboard b = pos.attacks_from(ksq) & target; + while (b) + (mlist++)->move = make_move(ksq, pop_lsb(&b)); + } + + if (Type != CAPTURES && Type != EVASIONS && pos.can_castle(Us)) + { + if (pos.is_chess960()) + { + mlist = generate_castling::right, Checks, true>(pos, mlist, Us, ci); + mlist = generate_castling::right, Checks, true>(pos, mlist, Us, ci); + } + else + { + mlist = generate_castling::right, Checks, false>(pos, mlist, Us, ci); + mlist = generate_castling::right, Checks, false>(pos, mlist, Us, ci); + } + } + + return mlist; + } + + +} // 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* mlist) { + + 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, mlist, target) + : generate_all(pos, mlist, 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* mlist) { + + assert(!pos.checkers()); + + Color us = pos.side_to_move(); + CheckInfo ci(pos); + Bitboard dc = ci.dcCandidates; + + 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][ci.ksq]; + + while (b) + (mlist++)->move = make_move(from, pop_lsb(&b)); + } + + return us == WHITE ? generate_all(pos, mlist, ~pos.pieces(), &ci) + : generate_all(pos, mlist, ~pos.pieces(), &ci); +} + + +/// 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* mlist) { + + assert(pos.checkers()); + + Color us = pos.side_to_move(); + Square ksq = pos.king_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) + (mlist++)->move = make_move(ksq, pop_lsb(&b)); + + if (more_than_one(pos.checkers())) + return mlist; // 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, mlist, target) + : generate_all(pos, mlist, target); +} + + +/// generate generates all the legal moves in the given position + +template<> +ExtMove* generate(const Position& pos, ExtMove* mlist) { + + ExtMove *end, *cur = mlist; + Bitboard pinned = pos.pinned_pieces(pos.side_to_move()); + Square ksq = pos.king_square(pos.side_to_move()); + + end = pos.checkers() ? generate(pos, mlist) + : generate(pos, mlist); + while (cur != end) + if ( (pinned || from_sq(cur->move) == ksq || type_of(cur->move) == ENPASSANT) + && !pos.legal(cur->move, pinned)) + cur->move = (--end)->move; + else + ++cur; + + return end; +} diff --git a/src/movegen.h b/src/movegen.h new file mode 100644 index 0000000..c18fa07 --- /dev/null +++ b/src/movegen.h @@ -0,0 +1,58 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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" + +enum GenType { + CAPTURES, + QUIETS, + QUIET_CHECKS, + EVASIONS, + NON_EVASIONS, + LEGAL +}; + +class Position; + +template +ExtMove* generate(const Position& pos, ExtMove* mlist); + +/// 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) : cur(mlist), last(generate(pos, mlist)) { last->move = MOVE_NONE; } + void operator++() { ++cur; } + Move operator*() const { return cur->move; } + size_t size() const { return last - mlist; } + bool contains(Move m) const { + for (const ExtMove* it(mlist); it != last; ++it) if (it->move == m) return true; + return false; + } + +private: + ExtMove mlist[MAX_MOVES]; + ExtMove *cur, *last; +}; + +#endif // #ifndef MOVEGEN_H_INCLUDED diff --git a/src/movepick.cpp b/src/movepick.cpp new file mode 100644 index 0000000..95172b9 --- /dev/null +++ b/src/movepick.cpp @@ -0,0 +1,392 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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_S1, KILLERS_S1, QUIETS_1_S1, QUIETS_2_S1, BAD_CAPTURES_S1, + EVASION, EVASIONS_S2, + QSEARCH_0, CAPTURES_S3, QUIET_CHECKS_S3, + QSEARCH_1, CAPTURES_S4, + PROBCUT, CAPTURES_S5, + RECAPTURE, CAPTURES_S6, + STOP + }; + + // Our insertion sort, which is guaranteed (and also needed) to be stable + 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; + } + } + + // Unary predicate used by std::partition to split positive values from remaining + // ones so as to sort the two sets separately, with the second sort delayed. + inline bool has_positive_value(const ExtMove& ms) { return ms.value > 0; } + + // Picks 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. possible captures. + inline ExtMove* pick_best(ExtMove* begin, ExtMove* end) + { + std::swap(*begin, *std::max_element(begin, end)); + return begin; + } +} + + +/// 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, const HistoryStats& h, + Move* cm, Move* fm, Search::Stack* s) : pos(p), history(h), depth(d) { + + assert(d > DEPTH_ZERO); + + cur = end = moves; + endBadCaptures = moves + MAX_MOVES - 1; + countermoves = cm; + followupmoves = fm; + ss = s; + + if (pos.checkers()) + stage = EVASION; + + else + stage = MAIN_SEARCH; + + ttMove = (ttm && pos.pseudo_legal(ttm) ? ttm : MOVE_NONE); + end += (ttMove != MOVE_NONE); +} + +MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const HistoryStats& h, + Square s) : pos(p), history(h), cur(moves), end(moves) { + + assert(d <= DEPTH_ZERO); + + if (pos.checkers()) + stage = EVASION; + + else if (d > DEPTH_QS_NO_CHECKS) + stage = QSEARCH_0; + + else if (d > DEPTH_QS_RECAPTURES) + { + stage = QSEARCH_1; + + // Skip TT move if is not a capture or a promotion. This avoids qsearch + // tree explosion due to a possible perpetual check or similar rare cases + // when TT table is full. + if (ttm && !pos.capture_or_promotion(ttm)) + ttm = MOVE_NONE; + } + else + { + stage = RECAPTURE; + recaptureSquare = s; + ttm = MOVE_NONE; + } + + ttMove = (ttm && pos.pseudo_legal(ttm) ? ttm : MOVE_NONE); + end += (ttMove != MOVE_NONE); +} + +MovePicker::MovePicker(const Position& p, Move ttm, const HistoryStats& h, PieceType pt) + : pos(p), history(h), cur(moves), end(moves) { + + assert(!pos.checkers()); + + stage = PROBCUT; + + // In ProbCut we generate only captures that are better than the parent's + // captured piece. + captureThreshold = PieceValue[MG][pt]; + ttMove = (ttm && pos.pseudo_legal(ttm) ? ttm : MOVE_NONE); + + if (ttMove && (!pos.capture(ttMove) || pos.see(ttMove) <= captureThreshold)) + ttMove = MOVE_NONE; + + end += (ttMove != MOVE_NONE); +} + + +/// score() assign 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/LVA. + // Suprisingly, this appears to perform slightly better than SEE based + // move ordering. The reason is probably that in a position with a winning + // capture, capturing a more valuable (but sufficiently defended) piece + // first usually doesn't hurt. The opponent will have to recapture, and + // the hanging piece will still be hanging (except in the unusual cases + // where it is possible to recapture with the hanging piece). Exchanging + // big pieces before capturing a hanging piece probably helps to reduce + // the subtree size. + // In 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 in pick_move_from_list(). This way we save some SEE + // calls in case we get a cutoff. + Move m; + + for (ExtMove* it = moves; it != end; ++it) + { + m = it->move; + it->value = PieceValue[MG][pos.piece_on(to_sq(m))] + - Value(type_of(pos.moved_piece(m))); + + if (type_of(m) == ENPASSANT) + it->value += PieceValue[MG][PAWN]; + + else if (type_of(m) == PROMOTION) + it->value += PieceValue[MG][promotion_type(m)] - PieceValue[MG][PAWN]; + } +} + +template<> +void MovePicker::score() { + + Move m; + + for (ExtMove* it = moves; it != end; ++it) + { + m = it->move; + it->value = history[pos.moved_piece(m)][to_sq(m)]; + } +} + +template<> +void MovePicker::score() { + // Try good captures ordered by MVV/LVA, then non-captures if destination square + // is not under attack, ordered by history value, then bad-captures and quiet + // moves with a negative SEE. This last group is ordered by the SEE value. + Move m; + Value see; + + for (ExtMove* it = moves; it != end; ++it) + { + m = it->move; + if ((see = pos.see_sign(m)) < VALUE_ZERO) + it->value = see - HistoryStats::Max; // At the bottom + + else if (pos.capture(m)) + it->value = PieceValue[MG][pos.piece_on(to_sq(m))] + - Value(type_of(pos.moved_piece(m))) + HistoryStats::Max; + else + it->value = history[pos.moved_piece(m)][to_sq(m)]; + } +} + + +/// generate_next_stage() generates, scores and sorts the next bunch of moves, +/// when there are no more moves to try for the current stage. + +void MovePicker::generate_next_stage() { + + cur = moves; + + switch (++stage) { + + case CAPTURES_S1: case CAPTURES_S3: case CAPTURES_S4: case CAPTURES_S5: case CAPTURES_S6: + end = generate(pos, moves); + score(); + return; + + case KILLERS_S1: + cur = killers; + end = cur + 2; + + killers[0].move = ss->killers[0]; + killers[1].move = ss->killers[1]; + killers[2].move = killers[3].move = MOVE_NONE; + killers[4].move = killers[5].move = MOVE_NONE; + + // Please note that following code is racy and could yield to rare (less + // than 1 out of a million) duplicated entries in SMP case. This is harmless. + + // Be sure countermoves are different from killers + for (int i = 0; i < 2; ++i) + if ( countermoves[i] != (cur+0)->move + && countermoves[i] != (cur+1)->move) + (end++)->move = countermoves[i]; + + // Be sure followupmoves are different from killers and countermoves + for (int i = 0; i < 2; ++i) + if ( followupmoves[i] != (cur+0)->move + && followupmoves[i] != (cur+1)->move + && followupmoves[i] != (cur+2)->move + && followupmoves[i] != (cur+3)->move) + (end++)->move = followupmoves[i]; + return; + + case QUIETS_1_S1: + endQuiets = end = generate(pos, moves); + score(); + end = std::partition(cur, end, has_positive_value); + insertion_sort(cur, end); + return; + + case QUIETS_2_S1: + cur = end; + end = endQuiets; + if (depth >= 3 * ONE_PLY) + insertion_sort(cur, end); + return; + + case BAD_CAPTURES_S1: + // Just pick them in reverse order to get MVV/LVA ordering + cur = moves + MAX_MOVES - 1; + end = endBadCaptures; + return; + + case EVASIONS_S2: + end = generate(pos, moves); + if (end > moves + 1) + score(); + return; + + case QUIET_CHECKS_S3: + end = generate(pos, moves); + return; + + case EVASION: case QSEARCH_0: case QSEARCH_1: case PROBCUT: case RECAPTURE: + stage = STOP; + /* Fall through */ + + case STOP: + end = cur + 1; // Avoid another next_phase() call + return; + + default: + assert(false); + } +} + + +/// 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. +template<> +Move MovePicker::next_move() { + + Move move; + + while (true) + { + while (cur == end) + generate_next_stage(); + + switch (stage) { + + case MAIN_SEARCH: case EVASION: case QSEARCH_0: case QSEARCH_1: case PROBCUT: + ++cur; + return ttMove; + + case CAPTURES_S1: + move = pick_best(cur++, end)->move; + if (move != ttMove) + { + if (pos.see_sign(move) >= VALUE_ZERO) + return move; + + // Losing capture, move it to the tail of the array + (endBadCaptures--)->move = move; + } + break; + + case KILLERS_S1: + move = (cur++)->move; + if ( move != MOVE_NONE + && move != ttMove + && pos.pseudo_legal(move) + && !pos.capture(move)) + return move; + break; + + case QUIETS_1_S1: case QUIETS_2_S1: + move = (cur++)->move; + if ( move != ttMove + && move != killers[0].move + && move != killers[1].move + && move != killers[2].move + && move != killers[3].move + && move != killers[4].move + && move != killers[5].move) + return move; + break; + + case BAD_CAPTURES_S1: + return (cur--)->move; + + case EVASIONS_S2: case CAPTURES_S3: case CAPTURES_S4: + move = pick_best(cur++, end)->move; + if (move != ttMove) + return move; + break; + + case CAPTURES_S5: + move = pick_best(cur++, end)->move; + if (move != ttMove && pos.see(move) > captureThreshold) + return move; + break; + + case CAPTURES_S6: + move = pick_best(cur++, end)->move; + if (to_sq(move) == recaptureSquare) + return move; + break; + + case QUIET_CHECKS_S3: + move = (cur++)->move; + if (move != ttMove) + return move; + break; + + case STOP: + return MOVE_NONE; + + default: + assert(false); + } + } +} + + +/// Version of next_move() to use at split point nodes where the move is grabbed +/// from the split point's shared MovePicker object. This function is not thread +/// safe so must be lock protected by the caller. +template<> +Move MovePicker::next_move() { return ss->splitPoint->movePicker->next_move(); } diff --git a/src/movepick.h b/src/movepick.h new file mode 100644 index 0000000..c512d13 --- /dev/null +++ b/src/movepick.h @@ -0,0 +1,112 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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 "search.h" +#include "types.h" + + +/// The Stats struct stores moves statistics. According to the template parameter +/// the class can store History, Gains 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. Gains records the move's +/// best evaluation gain from one ply to the next and is used for pruning 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(2000); + + const T* operator[](Piece pc) const { return table[pc]; } + void clear() { std::memset(table, 0, sizeof(table)); } + + void update(Piece pc, Square to, Move m) { + + if (m == table[pc][to].first) + return; + + table[pc][to].second = table[pc][to].first; + table[pc][to].first = m; + } + + void update(Piece pc, Square to, Value v) { + + if (Gain) + table[pc][to] = std::max(v, table[pc][to] - 1); + + else if (abs(table[pc][to] + v) < Max) + table[pc][to] += v; + } + +private: + T table[PIECE_NB][SQUARE_NB]; +}; + +typedef Stats< true, Value> GainsStats; +typedef Stats HistoryStats; +typedef Stats > MovesStats; + + +/// MovePicker class is used to pick one pseudo legal move at a time from the +/// current position. The most important method is next_move(), which returns a +/// new pseudo legal move each time it is called, until there are no moves left, +/// when MOVE_NONE is returned. In order to improve the efficiency of the alpha +/// beta algorithm, MovePicker attempts to return the moves which are most likely +/// to get a cut-off first. + +class MovePicker { + + MovePicker& operator=(const MovePicker&); // Silence a warning under MSVC + +public: + MovePicker(const Position&, Move, Depth, const HistoryStats&, Square); + MovePicker(const Position&, Move, const HistoryStats&, PieceType); + MovePicker(const Position&, Move, Depth, const HistoryStats&, Move*, Move*, Search::Stack*); + + template Move next_move(); + +private: + template void score(); + void generate_next_stage(); + + const Position& pos; + const HistoryStats& history; + Search::Stack* ss; + Move* countermoves; + Move* followupmoves; + Depth depth; + Move ttMove; + ExtMove killers[6]; + Square recaptureSquare; + Value captureThreshold; + int stage; + ExtMove *cur, *end, *endQuiets, *endBadCaptures; + ExtMove moves[MAX_MOVES]; +}; + +#endif // #ifndef MOVEPICK_H_INCLUDED diff --git a/src/notation.cpp b/src/notation.cpp new file mode 100644 index 0000000..cace78b --- /dev/null +++ b/src/notation.cpp @@ -0,0 +1,256 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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 "movegen.h" +#include "notation.h" +#include "position.h" + +using namespace std; + +static const char* PieceToChar[COLOR_NB] = { " PNBRQK", " pnbrqk" }; + + +/// score_to_uci() converts a value to a string suitable for use with the UCI +/// protocol specifications: +/// +/// 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 score_to_uci(Value v, Value alpha, Value beta) { + + stringstream ss; + + if (abs(v) < VALUE_MATE_IN_MAX_PLY) + ss << "cp " << v * 100 / PawnValueEg; + else + ss << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2; + + ss << (v >= beta ? " lowerbound" : v <= alpha ? " upperbound" : ""); + + return ss.str(); +} + + +/// move_to_uci() converts a move to a string in coordinate notation +/// (g1f3, a7a8q, etc.). The only special case is castling moves, where we print +/// in the e1g1 notation in normal chess mode, and in e1h1 notation in chess960 +/// mode. Internally castling moves are always encoded as "king captures rook". + +const string move_to_uci(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 = to_string(from) + to_string(to); + + if (type_of(m) == PROMOTION) + move += PieceToChar[BLACK][promotion_type(m)]; // Lower case + + return move; +} + + +/// move_from_uci() takes a position and a string representing a move in +/// simple coordinate notation and returns an equivalent legal Move if any. + +Move move_from_uci(const Position& pos, string& str) { + + if (str.length() == 5) // Junior could send promotion piece in uppercase + str[4] = char(tolower(str[4])); + + for (MoveList it(pos); *it; ++it) + if (str == move_to_uci(*it, pos.is_chess960())) + return *it; + + return MOVE_NONE; +} + + +/// move_to_san() takes a position and a legal Move as input and returns its +/// short algebraic notation representation. + +const string move_to_san(Position& pos, Move m) { + + if (m == MOVE_NONE) + return "(none)"; + + if (m == MOVE_NULL) + return "(null)"; + + assert(MoveList(pos).contains(m)); + + Bitboard others, b; + string san; + Color us = pos.side_to_move(); + Square from = from_sq(m); + Square to = to_sq(m); + Piece pc = pos.piece_on(from); + PieceType pt = type_of(pc); + + if (type_of(m) == CASTLING) + san = to > from ? "O-O" : "O-O-O"; + else + { + if (pt != PAWN) + { + san = PieceToChar[WHITE][pt]; // Upper case + + // A disambiguation occurs if we have more then one piece of type 'pt' + // that can reach 'to' with a legal move. + others = b = (pos.attacks_from(pc, to) & pos.pieces(us, pt)) ^ from; + + while (b) + { + Square s = pop_lsb(&b); + if (!pos.legal(make_move(s, to), pos.pinned_pieces(us))) + others ^= s; + } + + if (!others) + { /* Disambiguation is not needed */ } + + else if (!(others & file_bb(from))) + san += to_char(file_of(from)); + + else if (!(others & rank_bb(from))) + san += to_char(rank_of(from)); + + else + san += to_string(from); + } + else if (pos.capture(m)) + san = to_char(file_of(from)); + + if (pos.capture(m)) + san += 'x'; + + san += to_string(to); + + if (type_of(m) == PROMOTION) + san += string("=") + PieceToChar[WHITE][promotion_type(m)]; + } + + if (pos.gives_check(m, CheckInfo(pos))) + { + StateInfo st; + pos.do_move(m, st); + san += MoveList(pos).size() ? "+" : "#"; + pos.undo_move(m); + } + + return san; +} + + +/// pretty_pv() formats human-readable search information, typically to be +/// appended to the search log file. It uses the two helpers below to pretty +/// format the time and score respectively. + +static string format(int64_t msecs) { + + const int MSecMinute = 1000 * 60; + const int MSecHour = 1000 * 60 * 60; + + int64_t hours = msecs / MSecHour; + int64_t minutes = (msecs % MSecHour) / MSecMinute; + int64_t seconds = ((msecs % MSecHour) % MSecMinute) / 1000; + + stringstream ss; + + if (hours) + ss << hours << ':'; + + ss << setfill('0') << setw(2) << minutes << ':' << setw(2) << seconds; + + return ss.str(); +} + +static string format(Value v) { + + stringstream ss; + + if (v >= VALUE_MATE_IN_MAX_PLY) + ss << "#" << (VALUE_MATE - v + 1) / 2; + + else if (v <= VALUE_MATED_IN_MAX_PLY) + ss << "-#" << (VALUE_MATE + v) / 2; + + else + ss << setprecision(2) << fixed << showpos << double(v) / PawnValueEg; + + return ss.str(); +} + +string pretty_pv(Position& pos, int depth, Value value, int64_t msecs, Move pv[]) { + + const uint64_t K = 1000; + const uint64_t M = 1000000; + + std::stack st; + Move* m = pv; + string san, str, padding; + stringstream ss; + + ss << setw(2) << depth << setw(8) << format(value) << setw(8) << format(msecs); + + if (pos.nodes_searched() < M) + ss << setw(8) << pos.nodes_searched() / 1 << " "; + + else if (pos.nodes_searched() < K * M) + ss << setw(7) << pos.nodes_searched() / K << "K "; + + else + ss << setw(7) << pos.nodes_searched() / M << "M "; + + str = ss.str(); + padding = string(str.length(), ' '); + + while (*m != MOVE_NONE) + { + san = move_to_san(pos, *m) + ' '; + + if ((str.length() + san.length()) % 80 <= san.length()) // Exceed 80 cols + str += "\n" + padding; + + str += san; + + st.push(StateInfo()); + pos.do_move(*m++, st.top()); + } + + while (m != pv) + pos.undo_move(*--m); + + return str; +} diff --git a/src/notation.h b/src/notation.h new file mode 100644 index 0000000..a8168c5 --- /dev/null +++ b/src/notation.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-2014 Marco Costalba, Joona Kiiski, 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 NOTATION_H_INCLUDED +#define NOTATION_H_INCLUDED + +#include + +#include "types.h" + +class Position; + +std::string score_to_uci(Value v, Value alpha = -VALUE_INFINITE, Value beta = VALUE_INFINITE); +Move move_from_uci(const Position& pos, std::string& str); +const std::string move_to_uci(Move m, bool chess960); +const std::string move_to_san(Position& pos, Move m); +std::string pretty_pv(Position& pos, int depth, Value score, int64_t msecs, Move pv[]); + +inline char to_char(File f, bool tolower = true) { + return char(f - FILE_A + (tolower ? 'a' : 'A')); +} + +inline char to_char(Rank r) { + return char(r - RANK_1 + '1'); +} + +inline const std::string to_string(Square s) { + char ch[] = { to_char(file_of(s)), to_char(rank_of(s)), 0 }; + return ch; +} + +#endif // #ifndef NOTATION_H_INCLUDED diff --git a/src/pawns.cpp b/src/pawns.cpp new file mode 100644 index 0000000..d137c5f --- /dev/null +++ b/src/pawns.cpp @@ -0,0 +1,325 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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 "bitcount.h" +#include "pawns.h" +#include "position.h" + +namespace { + + #define V Value + #define S(mg, eg) make_score(mg, eg) + + // Doubled pawn penalty by file + const Score Doubled[FILE_NB] = { + S(13, 43), S(20, 48), S(23, 48), S(23, 48), + S(23, 48), S(23, 48), S(20, 48), S(13, 43) }; + + // Isolated pawn penalty by opposed flag and file + const Score Isolated[2][FILE_NB] = { + { S(37, 45), S(54, 52), S(60, 52), S(60, 52), + S(60, 52), S(60, 52), S(54, 52), S(37, 45) }, + { S(25, 30), S(36, 35), S(40, 35), S(40, 35), + S(40, 35), S(40, 35), S(36, 35), S(25, 30) } }; + + // Backward pawn penalty by opposed flag and file + const Score Backward[2][FILE_NB] = { + { S(30, 42), S(43, 46), S(49, 46), S(49, 46), + S(49, 46), S(49, 46), S(43, 46), S(30, 42) }, + { S(20, 28), S(29, 31), S(33, 31), S(33, 31), + S(33, 31), S(33, 31), S(29, 31), S(20, 28) } }; + + // Connected pawn bonus by file and rank (initialized by formula) + Score Connected[FILE_NB][RANK_NB]; + + // Candidate passed pawn bonus by rank + const Score CandidatePassed[RANK_NB] = { + S( 0, 0), S( 6, 13), S(6,13), S(14,29), + S(34,68), S(83,166), S(0, 0), S( 0, 0) }; + + // Levers bonus by rank + const Score Lever[RANK_NB] = { + S( 0, 0), S( 0, 0), S(0, 0), S(0, 0), + S(20,20), S(40,40), S(0, 0), S(0, 0) }; + + // Bonus for file distance of the two outermost pawns + const Score PawnsFileSpan = S(0, 15); + + // Unsupported pawn penalty + const Score UnsupportedPawnPenalty = S(20, 10); + + // Weakness of our pawn shelter in front of the king indexed by [rank] + const Value ShelterWeakness[RANK_NB] = + { V(100), V(0), V(27), V(73), V(92), V(101), V(101) }; + + // Danger of enemy pawns moving toward our king indexed by + // [no friendly pawn | pawn unblocked | pawn blocked][rank of enemy pawn] + const Value StormDanger[][RANK_NB] = { + { V( 0), V(64), V(128), V(51), V(26) }, + { V(26), V(32), V( 96), V(38), V(20) }, + { V( 0), V( 0), V(160), V(25), V(13) } }; + + // 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(263); + + #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 ? DELTA_N : DELTA_S); + const Square Right = (Us == WHITE ? DELTA_NE : DELTA_SW); + const Square Left = (Us == WHITE ? DELTA_NW : DELTA_SE); + + Bitboard b, p, doubled; + Square s; + File f; + bool passed, isolated, opposed, connected, backward, candidate, unsupported, lever; + Score value = SCORE_ZERO; + const Square* pl = pos.list(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->candidatePawns[Us] = 0; + e->kingSquares[Us] = SQ_NONE; + e->semiopenFiles[Us] = 0xFF; + e->pawnAttacks[Us] = shift_bb(ourPawns) | shift_bb(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)); + + f = file_of(s); + + // This file cannot be semi-open + e->semiopenFiles[Us] &= ~(1 << f); + + // Previous rank + p = rank_bb(s - pawn_push(Us)); + + // Our rank plus previous one + b = rank_bb(s) | p; + + // Flag the pawn as passed, isolated, doubled, + // unsupported or connected (but not the backward one). + connected = ourPawns & adjacent_files_bb(f) & b; + unsupported = !(ourPawns & adjacent_files_bb(f) & p); + isolated = !(ourPawns & adjacent_files_bb(f)); + doubled = ourPawns & forward_bb(Us, s); + opposed = theirPawns & forward_bb(Us, s); + passed = !(theirPawns & passed_pawn_mask(Us, s)); + lever = theirPawns & pawnAttacksBB[s]; + + // Test for backward pawn. + // If the pawn is passed, isolated, or connected it cannot be + // backward. If there are friendly pawns behind on adjacent files + // or if it can capture an enemy pawn it cannot be backward either. + if ( (passed | isolated | connected) + || (ourPawns & pawn_attack_span(Them, s)) + || (pos.attacks_from(s, Us) & theirPawns)) + backward = false; + else + { + // We now know that there are no friendly pawns beside or behind this + // pawn on adjacent files. We now check whether the pawn is + // backward by looking in the forward direction on the adjacent + // files, and picking the closest pawn there. + b = pawn_attack_span(Us, s) & (ourPawns | theirPawns); + b = pawn_attack_span(Us, s) & rank_bb(backmost_sq(Us, b)); + + // If we have an enemy pawn in the same or next rank, the pawn is + // backward because it cannot advance without being captured. + backward = (b | shift_bb(b)) & theirPawns; + } + + assert(opposed | passed | (pawn_attack_span(Us, s) & theirPawns)); + + // A not-passed pawn is a candidate to become passed, if it is free to + // advance and if the number of friendly pawns beside or behind this + // pawn on adjacent files is higher than or equal to the number of + // enemy pawns in the forward direction on the adjacent files. + candidate = !(opposed | passed | backward | isolated) + && (b = pawn_attack_span(Them, s + pawn_push(Us)) & ourPawns) != 0 + && popcount(b) >= popcount(pawn_attack_span(Us, s) & theirPawns); + + // Passed pawns will be properly scored in evaluation because we need + // full attack info to evaluate passed pawns. Only the frontmost passed + // pawn on each file is considered a true passed pawn. + if (passed && !doubled) + e->passedPawns[Us] |= s; + + // Score this pawn + if (isolated) + value -= Isolated[opposed][f]; + + if (unsupported && !isolated) + value -= UnsupportedPawnPenalty; + + if (doubled) + value -= Doubled[f] / rank_distance(s, lsb(doubled)); + + if (backward) + value -= Backward[opposed][f]; + + if (connected) + value += Connected[f][relative_rank(Us, s)]; + + if (lever) + value += Lever[relative_rank(Us, s)]; + + if (candidate) + { + value += CandidatePassed[relative_rank(Us, s)]; + + if (!doubled) + e->candidatePawns[Us] |= s; + } + } + + // In endgame it's better to have pawns on both wings. So give a bonus according + // to file distance between left and right outermost pawns. + if (pos.count(Us) > 1) + { + b = e->semiopenFiles[Us] ^ 0xFF; + value += PawnsFileSpan * int(msb(b) - lsb(b)); + } + + return value; + } + +} // namespace + +namespace Pawns { + +/// init() initializes some tables by formula instead of hard-coding their values + +void init() { + + const int bonusByFile[] = { 1, 3, 3, 4, 4, 3, 3, 1 }; + + for (Rank r = RANK_1; r < RANK_8; ++r) + for (File f = FILE_A; f <= FILE_H; ++f) + { + int bonus = r * (r - 1) * (r - 2) + bonusByFile[f] * (r / 2 + 1); + Connected[f][r] = make_score(bonus, bonus); + } +} + + +/// probe() takes a position as input, computes a Entry object, and returns a +/// pointer to it. The result is also stored in a hash table, so we don't have +/// to recompute everything when the same pawn structure occurs again. + +Entry* probe(const Position& pos, Table& entries) { + + Key key = pos.pawn_key(); + Entry* e = entries[key]; + + if (e->key == key) + return e; + + e->key = key; + e->value = evaluate(pos, e) - evaluate(pos, e); + 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); + const Bitboard Edges = (FileABB | FileHBB) & (Rank2BB | Rank3BB); + + 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 kf = std::max(FILE_B, std::min(FILE_G, file_of(ksq))); + + for (File f = kf - File(1); f <= kf + 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; + + if ( (Edges & make_square(f, rkThem)) + && file_of(ksq) == f + && relative_rank(Us, ksq) == rkThem - 1) + safety += 200; + else + safety -= ShelterWeakness[rkUs] + + StormDanger[rkUs == RANK_1 ? 0 : + rkThem != rkUs + 1 ? 1 : 2][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); + minKPdistance[Us] = 0; + + Bitboard pawns = pos.pieces(Us, PAWN); + if (pawns) + while (!(DistanceRingsBB[ksq][minKPdistance[Us]++] & pawns)) {} + + if (relative_rank(Us, ksq) > RANK_4) + return make_score(0, -16 * minKPdistance[Us]); + + 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 * minKPdistance[Us]); +} + +// 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..28fe1c0 --- /dev/null +++ b/src/pawns.h @@ -0,0 +1,84 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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_value() const { return value; } + Bitboard pawn_attacks(Color c) const { return pawnAttacks[c]; } + Bitboard passed_pawns(Color c) const { return passedPawns[c]; } + Bitboard candidate_pawns(Color c) const { return candidatePawns[c]; } + + 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 value; + Bitboard passedPawns[COLOR_NB]; + Bitboard candidatePawns[COLOR_NB]; + Bitboard pawnAttacks[COLOR_NB]; + Square kingSquares[COLOR_NB]; + Score kingSafety[COLOR_NB]; + int minKPdistance[COLOR_NB]; + int castlingRights[COLOR_NB]; + int semiopenFiles[COLOR_NB]; + int pawnsOnSquares[COLOR_NB][COLOR_NB]; // [color][light/dark squares] +}; + +typedef HashTable Table; + +void init(); +Entry* probe(const Position& pos, Table& entries); + +} // namespace Pawns + +#endif // #ifndef PAWNS_H_INCLUDED diff --git a/src/platform.h b/src/platform.h new file mode 100644 index 0000000..151c882 --- /dev/null +++ b/src/platform.h @@ -0,0 +1,116 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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 PLATFORM_H_INCLUDED +#define PLATFORM_H_INCLUDED + +#ifdef _MSC_VER + +// Disable some silly and noisy warnings from MSVC compiler +#pragma warning(disable: 4127) // Conditional expression is constant +#pragma warning(disable: 4146) // Unary minus operator applied to unsigned type +#pragma warning(disable: 4800) // Forcing value to bool 'true' or 'false' +#pragma warning(disable: 4996) // Function _ftime() may be unsafe + +// MSVC does not support +typedef signed __int8 int8_t; +typedef unsigned __int8 uint8_t; +typedef signed __int16 int16_t; +typedef unsigned __int16 uint16_t; +typedef signed __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; + +#else +# include +#endif + +#ifndef _WIN32 // Linux - Unix + +# include + +inline int64_t system_time_to_msec() { + timeval t; + gettimeofday(&t, NULL); + return t.tv_sec * 1000LL + t.tv_usec / 1000; +} + +# include +typedef pthread_mutex_t Lock; +typedef pthread_cond_t WaitCondition; +typedef pthread_t NativeHandle; +typedef void*(*pt_start_fn)(void*); + +# define lock_init(x) pthread_mutex_init(&(x), NULL) +# define lock_grab(x) pthread_mutex_lock(&(x)) +# define lock_release(x) pthread_mutex_unlock(&(x)) +# define lock_destroy(x) pthread_mutex_destroy(&(x)) +# define cond_destroy(x) pthread_cond_destroy(&(x)) +# define cond_init(x) pthread_cond_init(&(x), NULL) +# define cond_signal(x) pthread_cond_signal(&(x)) +# define cond_wait(x,y) pthread_cond_wait(&(x),&(y)) +# define cond_timedwait(x,y,z) pthread_cond_timedwait(&(x),&(y),z) +# define thread_create(x,f,t) pthread_create(&(x),NULL,(pt_start_fn)f,t) +# define thread_join(x) pthread_join(x, NULL) + +#else // Windows and MinGW + +# include + +inline int64_t system_time_to_msec() { + _timeb t; + _ftime(&t); + return t.time * 1000LL + t.millitm; +} + +#ifndef NOMINMAX +# define NOMINMAX // disable macros min() and max() +#endif + +#define WIN32_LEAN_AND_MEAN +#include +#undef WIN32_LEAN_AND_MEAN +#undef NOMINMAX + +// We use critical sections on Windows to support Windows XP and older versions. +// Unfortunately, cond_wait() is racy between lock_release() and WaitForSingleObject() +// but apart from this they have the same speed performance of SRW locks. +typedef CRITICAL_SECTION Lock; +typedef HANDLE WaitCondition; +typedef HANDLE NativeHandle; + +// On Windows 95 and 98 parameter lpThreadId may not be null +inline DWORD* dwWin9xKludge() { static DWORD dw; return &dw; } + +# define lock_init(x) InitializeCriticalSection(&(x)) +# define lock_grab(x) EnterCriticalSection(&(x)) +# define lock_release(x) LeaveCriticalSection(&(x)) +# define lock_destroy(x) DeleteCriticalSection(&(x)) +# define cond_init(x) { x = CreateEvent(0, FALSE, FALSE, 0); } +# define cond_destroy(x) CloseHandle(x) +# define cond_signal(x) SetEvent(x) +# define cond_wait(x,y) { lock_release(y); WaitForSingleObject(x, INFINITE); lock_grab(y); } +# define cond_timedwait(x,y,z) { lock_release(y); WaitForSingleObject(x,z); lock_grab(y); } +# define thread_create(x,f,t) (x = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)f,t,0,dwWin9xKludge())) +# define thread_join(x) { WaitForSingleObject(x, INFINITE); CloseHandle(x); } + +#endif + +#endif // #ifndef PLATFORM_H_INCLUDED diff --git a/src/position.cpp b/src/position.cpp new file mode 100644 index 0000000..0724e2a --- /dev/null +++ b/src/position.cpp @@ -0,0 +1,1279 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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 + +#include "bitcount.h" +#include "movegen.h" +#include "notation.h" +#include "position.h" +#include "psqtab.h" +#include "rkiss.h" +#include "thread.h" +#include "tt.h" +#include "notation.h" + +using std::string; + +static const string PieceToChar(" PNBRQK pnbrqk"); + +CACHE_LINE_ALIGNMENT + +Value PieceValue[PHASE_NB][PIECE_NB] = { +{ VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg }, +{ VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg } }; + +static Score psq[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; + +namespace Zobrist { + + Key psq[COLOR_NB][PIECE_TYPE_NB][SQUARE_NB]; + Key enpassant[FILE_NB]; + Key castling[CASTLING_RIGHT_NB]; + Key side; + Key exclusion; +} + +Key Position::exclusion_key() const { return st->key ^ Zobrist::exclusion;} + +namespace { + +// 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 FORCE_INLINE +PieceType min_attacker(const Bitboard* bb, const Square& to, const 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<> FORCE_INLINE +PieceType min_attacker(const Bitboard*, const Square&, const Bitboard&, Bitboard&, Bitboard&) { + return KING; // No need to update bitboards: it is the last cycle +} + +} // namespace + + +/// CheckInfo c'tor + +CheckInfo::CheckInfo(const Position& pos) { + + Color them = ~pos.side_to_move(); + ksq = pos.king_square(them); + + pinned = pos.pinned_pieces(pos.side_to_move()); + dcCandidates = pos.discovered_check_candidates(); + + checkSq[PAWN] = pos.attacks_from(ksq, them); + checkSq[KNIGHT] = pos.attacks_from(ksq); + checkSq[BISHOP] = pos.attacks_from(ksq); + checkSq[ROOK] = pos.attacks_from(ksq); + checkSq[QUEEN] = checkSq[BISHOP] | checkSq[ROOK]; + checkSq[KING] = 0; +} + + +/// Position::init() initializes at startup the various arrays used to compute +/// hash keys and the piece square tables. The latter is a two-step operation: +/// Firstly, the white halves of the tables are copied from PSQT[] tables. +/// Secondly, the black halves of the tables are initialized by flipping and +/// changing the sign of the white scores. + +void Position::init() { + + RKISS rk; + + for (Color c = WHITE; c <= BLACK; ++c) + for (PieceType pt = PAWN; pt <= KING; ++pt) + for (Square s = SQ_A1; s <= SQ_H8; ++s) + Zobrist::psq[c][pt][s] = rk.rand(); + + for (File f = FILE_A; f <= FILE_H; ++f) + Zobrist::enpassant[f] = rk.rand(); + + for (int cf = NO_CASTLING; cf <= ANY_CASTLING; ++cf) + { + Bitboard b = cf; + while (b) + { + Key k = Zobrist::castling[1ULL << pop_lsb(&b)]; + Zobrist::castling[cf] ^= k ? k : rk.rand(); + } + } + + Zobrist::side = rk.rand(); + Zobrist::exclusion = rk.rand(); + + for (PieceType pt = PAWN; pt <= KING; ++pt) + { + PieceValue[MG][make_piece(BLACK, pt)] = PieceValue[MG][pt]; + PieceValue[EG][make_piece(BLACK, pt)] = PieceValue[EG][pt]; + + Score v = make_score(PieceValue[MG][pt], PieceValue[EG][pt]); + + for (Square s = SQ_A1; s <= SQ_H8; ++s) + { + psq[WHITE][pt][ s] = (v + PSQT[pt][s]); + psq[BLACK][pt][~s] = -(v + PSQT[pt][s]); + } + } +} + + +/// Position::operator=() creates a copy of 'pos'. We want the new born Position +/// object to not depend on any external data so we detach state pointer from +/// the source one. + +Position& Position::operator=(const Position& pos) { + + std::memcpy(this, &pos, sizeof(Position)); + startState = *st; + st = &startState; + nodes = 0; + + assert(pos_is_ok()); + + return *this; +} + + +/// Position::clear() erases the position object to a pristine state, with an +/// empty board, white to move, and no castling rights. + +void Position::clear() { + + std::memset(this, 0, sizeof(Position)); + startState.epSquare = SQ_NONE; + st = &startState; + + for (int i = 0; i < PIECE_TYPE_NB; ++i) + for (int j = 0; j < 16; ++j) + pieceList[WHITE][i][j] = pieceList[BLACK][i][j] = SQ_NONE; +} + + +/// 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. + +void Position::set(const string& fenStr, bool isChess960, 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); + + clear(); + 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(sq, color_of(Piece(idx)), type_of(Piece(idx))); + ++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; + + token = char(toupper(token)); + + if (token == 'K') + for (rsq = relative_square(c, SQ_H1); type_of(piece_on(rsq)) != ROOK; --rsq) {} + + else if (token == 'Q') + for (rsq = relative_square(c, SQ_A1); type_of(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; + } + + // 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()); +} + + +/// 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 = king_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_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->npMaterial[WHITE] = si->npMaterial[BLACK] = VALUE_ZERO; + si->psq = SCORE_ZERO; + + si->checkersBB = attackers_to(king_square(sideToMove)) & pieces(~sideToMove); + + for (Bitboard b = pieces(); b; ) + { + Square s = pop_lsb(&b); + Piece pc = piece_on(s); + si->key ^= Zobrist::psq[color_of(pc)][type_of(pc)][s]; + si->psq += psq[color_of(pc)][type_of(pc)][s]; + } + + if (ep_square() != SQ_NONE) + si->key ^= Zobrist::enpassant[file_of(ep_square())]; + + if (sideToMove == BLACK) + si->key ^= Zobrist::side; + + si->key ^= Zobrist::castling[st->castlingRights]; + + for (Bitboard b = pieces(PAWN); b; ) + { + Square s = pop_lsb(&b); + si->pawnKey ^= Zobrist::psq[color_of(piece_on(s))][PAWN][s]; + } + + for (Color c = WHITE; c <= BLACK; ++c) + for (PieceType pt = PAWN; pt <= KING; ++pt) + for (int cnt = 0; cnt < pieceCount[c][pt]; ++cnt) + si->materialKey ^= Zobrist::psq[c][pt][cnt]; + + for (Color c = WHITE; c <= BLACK; ++c) + for (PieceType pt = KNIGHT; pt <= QUEEN; ++pt) + si->npMaterial[c] += pieceCount[c][pt] * PieceValue[MG][pt]; +} + + +/// 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 ? to_char(file_of(castling_rook_square(WHITE | KING_SIDE)), false) : 'K'); + + if (can_castle(WHITE_OOO)) + ss << (chess960 ? to_char(file_of(castling_rook_square(WHITE | QUEEN_SIDE)), false) : 'Q'); + + if (can_castle(BLACK_OO)) + ss << (chess960 ? to_char(file_of(castling_rook_square(BLACK | KING_SIDE)), true) : 'k'); + + if (can_castle(BLACK_OOO)) + ss << (chess960 ? to_char(file_of(castling_rook_square(BLACK | QUEEN_SIDE)), true) : 'q'); + + if (!can_castle(WHITE) && !can_castle(BLACK)) + ss << '-'; + + ss << (ep_square() == SQ_NONE ? " - " : " " + to_string(ep_square()) + " ") + << st->rule50 << " " << 1 + (gamePly - (sideToMove == BLACK)) / 2; + + return ss.str(); +} + + +/// Position::pretty() returns an ASCII representation of the position to be +/// printed to the standard output together with the move's san notation. + +const string Position::pretty(Move m) const { + + std::ostringstream ss; + + if (m) + ss << "\nMove: " << (sideToMove == BLACK ? ".." : "") + << move_to_san(*const_cast(this), m); + + ss << "\n +---+---+---+---+---+---+---+---+\n"; + + for (Rank r = RANK_8; r >= RANK_1; --r) + { + for (File f = FILE_A; f <= FILE_H; ++f) + ss << " | " << PieceToChar[piece_on(make_square(f, r))]; + + ss << " |\n +---+---+---+---+---+---+---+---+\n"; + } + + ss << "\nFen: " << fen() << "\nKey: " << std::hex << std::uppercase + << std::setfill('0') << std::setw(16) << st->key << "\nCheckers: "; + + for (Bitboard b = checkers(); b; ) + ss << to_string(pop_lsb(&b)) << " "; + + ss << "\nLegal moves: "; + for (MoveList it(*this); *it; ++it) + ss << move_to_san(*const_cast(this), *it) << " "; + + 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->npMaterial[WHITE] + st->npMaterial[BLACK]; + + npm = std::max(EndgameLimit, std::min(npm, MidgameLimit)); + + return Phase(((npm - EndgameLimit) * 128) / (MidgameLimit - EndgameLimit)); +} + + +/// Position::check_blockers() returns a bitboard of all the pieces with color +/// 'c' that are blocking check on the king with color 'kingColor'. A piece +/// blocks a check if removing that piece from the board would result in a +/// position where the king is in check. A check blocking piece can be either a +/// pinned or a discovered check piece, according if its color 'c' is the same +/// or the opposite of 'kingColor'. + +Bitboard Position::check_blockers(Color c, Color kingColor) const { + + Bitboard b, pinners, result = 0; + Square ksq = king_square(kingColor); + + // Pinners are sliders that give check when a pinned piece is removed + pinners = ( (pieces( ROOK, QUEEN) & PseudoAttacks[ROOK ][ksq]) + | (pieces(BISHOP, QUEEN) & PseudoAttacks[BISHOP][ksq])) & pieces(~kingColor); + + while (pinners) + { + b = between_bb(ksq, pop_lsb(&pinners)) & pieces(); + + if (!more_than_one(b)) + result |= b & pieces(c); + } + return result; +} + + +/// Position::attackers_to() computes a bitboard of all pieces which attack a +/// given square. Slider attacks use the occ bitboard to indicate occupancy. + +Bitboard Position::attackers_to(Square s, Bitboard occ) const { + + return (attacks_from(s, BLACK) & pieces(WHITE, PAWN)) + | (attacks_from(s, WHITE) & pieces(BLACK, PAWN)) + | (attacks_from(s) & pieces(KNIGHT)) + | (attacks_bb(s, occ) & pieces(ROOK, QUEEN)) + | (attacks_bb(s, occ) & pieces(BISHOP, QUEEN)) + | (attacks_from(s) & pieces(KING)); +} + + +/// Position::legal() tests whether a pseudo-legal move is legal + +bool Position::legal(Move m, Bitboard pinned) const { + + assert(is_ok(m)); + assert(pinned == pinned_pieces(sideToMove)); + + Color us = sideToMove; + Square from = from_sq(m); + + assert(color_of(moved_piece(m)) == us); + assert(piece_on(king_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 = king_square(us); + Square to = to_sq(m); + Square capsq = to - pawn_push(us); + Bitboard occ = (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, occ) & pieces(~us, QUEEN, ROOK)) + && !(attacks_bb(ksq, occ) & 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 + || !(pinned & from) + || aligned(from, to_sq(m), king_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) - 2 != 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()), king_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 CheckInfo& ci) const { + + assert(is_ok(m)); + assert(ci.dcCandidates == discovered_check_candidates()); + assert(color_of(moved_piece(m)) == sideToMove); + + Square from = from_sq(m); + Square to = to_sq(m); + PieceType pt = type_of(piece_on(from)); + + // Is there a direct check? + if (ci.checkSq[pt] & to) + return true; + + // Is there a discovered check? + if ( unlikely(ci.dcCandidates) + && (ci.dcCandidates & from) + && !aligned(from, to, ci.ksq)) + return true; + + switch (type_of(m)) + { + case NORMAL: + return false; + + case PROMOTION: + return attacks_bb(Piece(promotion_type(m)), to, pieces() ^ from) & ci.ksq; + + // 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>(ci.ksq, b) & pieces(sideToMove, QUEEN, ROOK)) + | (attacks_bb(ci.ksq, 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] & ci.ksq) + && (attacks_bb(rto, (pieces() ^ kfrom ^ rfrom) | rto | kto) & ci.ksq); + } + 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) { + + CheckInfo ci(*this); + do_move(m, newSt, ci, gives_check(m, ci)); +} + +void Position::do_move(Move m, StateInfo& newSt, const CheckInfo& ci, bool moveIsCheck) { + + assert(is_ok(m)); + assert(&newSt != st); + + ++nodes; + Key k = st->key; + + // 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, StateCopySize64 * sizeof(uint64_t)); + + newSt.previous = st; + st = &newSt; + + // Update side to move + k ^= Zobrist::side; + + // 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); + PieceType pt = type_of(pc); + PieceType captured = type_of(m) == ENPASSANT ? PAWN : type_of(piece_on(to)); + + assert(color_of(pc) == us); + assert(piece_on(to) == NO_PIECE || color_of(piece_on(to)) == them || type_of(m) == CASTLING); + assert(captured != KING); + + if (type_of(m) == CASTLING) + { + assert(pc == make_piece(us, KING)); + + Square rfrom, rto; + do_castling(from, to, rfrom, rto); + + captured = NO_PIECE_TYPE; + st->psq += psq[us][ROOK][rto] - psq[us][ROOK][rfrom]; + k ^= Zobrist::psq[us][ROOK][rfrom] ^ Zobrist::psq[us][ROOK][rto]; + } + + if (captured) + { + Square capsq = to; + + // If the captured piece is a pawn, update pawn hash key, otherwise + // update non-pawn material. + if (captured == PAWN) + { + if (type_of(m) == ENPASSANT) + { + capsq += pawn_push(them); + + assert(pt == 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; + } + + st->pawnKey ^= Zobrist::psq[them][PAWN][capsq]; + } + else + st->npMaterial[them] -= PieceValue[MG][captured]; + + // Update board and piece lists + remove_piece(capsq, them, captured); + + // Update material hash key and prefetch access to materialTable + k ^= Zobrist::psq[them][captured][capsq]; + st->materialKey ^= Zobrist::psq[them][captured][pieceCount[them][captured]]; + prefetch((char*)thisThread->materialTable[st->materialKey]); + + // Update incremental scores + st->psq -= psq[them][captured][capsq]; + + // Reset rule 50 counter + st->rule50 = 0; + } + + // Update hash key + k ^= Zobrist::psq[us][pt][from] ^ Zobrist::psq[us][pt][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; + } + + // Prefetch TT access as soon as we know the new hash key + prefetch((char*)TT.first_entry(k)); + + // Move the piece. The tricky Chess960 castling is handled earlier + if (type_of(m) != CASTLING) + move_piece(from, to, us, pt); + + // If the moving piece is a pawn do some special extra work + if (pt == PAWN) + { + // Set en-passant square if the moved pawn can be captured + if ( (int(to) ^ int(from)) == 16 + && (attacks_from(from + pawn_push(us), us) & pieces(them, PAWN))) + { + st->epSquare = Square((from + to) / 2); + k ^= Zobrist::enpassant[file_of(st->epSquare)]; + } + + else if (type_of(m) == PROMOTION) + { + PieceType promotion = promotion_type(m); + + assert(relative_rank(us, to) == RANK_8); + assert(promotion >= KNIGHT && promotion <= QUEEN); + + remove_piece(to, us, PAWN); + put_piece(to, us, promotion); + + // Update hash keys + k ^= Zobrist::psq[us][PAWN][to] ^ Zobrist::psq[us][promotion][to]; + st->pawnKey ^= Zobrist::psq[us][PAWN][to]; + st->materialKey ^= Zobrist::psq[us][promotion][pieceCount[us][promotion]-1] + ^ Zobrist::psq[us][PAWN][pieceCount[us][PAWN]]; + + // Update incremental score + st->psq += psq[us][promotion][to] - psq[us][PAWN][to]; + + // Update material + st->npMaterial[us] += PieceValue[MG][promotion]; + } + + // Update pawn hash key and prefetch access to pawnsTable + st->pawnKey ^= Zobrist::psq[us][PAWN][from] ^ Zobrist::psq[us][PAWN][to]; + prefetch((char*)thisThread->pawnsTable[st->pawnKey]); + + // Reset rule 50 draw counter + st->rule50 = 0; + } + + // Update incremental scores + st->psq += psq[us][pt][to] - psq[us][pt][from]; + + // Set capture piece + st->capturedType = captured; + + // Update the key with the final value + st->key = k; + + // Update checkers bitboard: piece must be already moved due to attacks_from() + st->checkersBB = 0; + + if (moveIsCheck) + { + if (type_of(m) != NORMAL) + st->checkersBB = attackers_to(king_square(them)) & pieces(us); + else + { + // Direct checks + if (ci.checkSq[pt] & to) + st->checkersBB |= to; + + // Discovered checks + if (unlikely(ci.dcCandidates) && (ci.dcCandidates & from)) + { + if (pt != ROOK) + st->checkersBB |= attacks_from(king_square(them)) & pieces(us, QUEEN, ROOK); + + if (pt != BISHOP) + st->checkersBB |= attacks_from(king_square(them)) & pieces(us, QUEEN, BISHOP); + } + } + } + + sideToMove = ~sideToMove; + + 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); + PieceType pt = type_of(piece_on(to)); + + assert(empty(from) || type_of(m) == CASTLING); + assert(st->capturedType != KING); + + if (type_of(m) == PROMOTION) + { + assert(pt == promotion_type(m)); + assert(relative_rank(us, to) == RANK_8); + assert(promotion_type(m) >= KNIGHT && promotion_type(m) <= QUEEN); + + remove_piece(to, us, promotion_type(m)); + put_piece(to, us, PAWN); + pt = PAWN; + } + + if (type_of(m) == CASTLING) + { + Square rfrom, rto; + do_castling(from, to, rfrom, rto); + } + else + { + move_piece(to, from, us, pt); // Put the piece back at the source square + + if (st->capturedType) + { + Square capsq = to; + + if (type_of(m) == ENPASSANT) + { + capsq -= pawn_push(us); + + assert(pt == PAWN); + assert(to == st->previous->epSquare); + assert(relative_rank(us, to) == RANK_6); + assert(piece_on(capsq) == NO_PIECE); + } + + put_piece(capsq, ~us, st->capturedType); // 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, especially in Chess960. +template +void Position::do_castling(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(sideToMove, kingSide ? SQ_F1 : SQ_D1); + to = relative_square(sideToMove, kingSide ? SQ_G1 : SQ_C1); + + // Remove both pieces first since squares could overlap in Chess960 + remove_piece(Do ? from : to, sideToMove, KING); + remove_piece(Do ? rfrom : rto, sideToMove, ROOK); + board[Do ? from : to] = board[Do ? rfrom : rto] = NO_PIECE; // Since remove_piece doesn't do it for us + put_piece(Do ? to : from, sideToMove, KING); + put_piece(Do ? rto : rfrom, sideToMove, ROOK); +} + + +/// 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()); + + std::memcpy(&newSt, st, sizeof(StateInfo)); // Fully copy here + + 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((char*)TT.first_entry(st->key)); + + ++st->rule50; + st->pliesFromNull = 0; + + sideToMove = ~sideToMove; + + assert(pos_is_ok()); +} + +void Position::undo_null_move() { + + assert(!checkers()); + + st = st->previous; + sideToMove = ~sideToMove; +} + + +/// Position::see() is a static exchange evaluator: It tries to estimate the +/// material gain or loss resulting from a move. + +Value Position::see_sign(Move m) const { + + assert(is_ok(m)); + + // Early return if SEE cannot be negative because captured piece value + // is not less then capturing one. Note that king moves always return + // here because king midgame value is set to 0. + if (PieceValue[MG][moved_piece(m)] <= PieceValue[MG][piece_on(to_sq(m))]) + return VALUE_KNOWN_WIN; + + return see(m); +} + +Value Position::see(Move m) const { + + Square from, to; + Bitboard occupied, attackers, stmAttackers; + Value swapList[32]; + int slIndex = 1; + PieceType captured; + Color stm; + + assert(is_ok(m)); + + from = from_sq(m); + to = to_sq(m); + swapList[0] = PieceValue[MG][piece_on(to)]; + stm = color_of(piece_on(from)); + occupied = pieces() ^ from; + + // Castling moves are implemented as king capturing the rook so cannot be + // handled correctly. Simply return 0 that is always the correct value + // unless in the rare case the rook ends up under attack. + if (type_of(m) == CASTLING) + return VALUE_ZERO; + + if (type_of(m) == ENPASSANT) + { + occupied ^= to - pawn_push(stm); // Remove the captured pawn + swapList[0] = PieceValue[MG][PAWN]; + } + + // Find all attackers to the destination square, with the moving piece + // removed, but possibly an X-ray attacker added behind it. + attackers = attackers_to(to, occupied) & occupied; + + // If the opponent has no attackers we are finished + stm = ~stm; + stmAttackers = attackers & pieces(stm); + if (!stmAttackers) + return swapList[0]; + + // The destination square is defended, which makes things rather more + // difficult to compute. We proceed by building up a "swap list" containing + // the material gain or loss at each stop in a sequence of captures to the + // destination square, where the sides alternately capture, and always + // capture with the least valuable piece. After each capture, we look for + // new X-ray attacks from behind the capturing piece. + captured = type_of(piece_on(from)); + + do { + assert(slIndex < 32); + + // Add the new entry to the swap list + swapList[slIndex] = -swapList[slIndex - 1] + PieceValue[MG][captured]; + + // Locate and remove the next least valuable attacker + captured = min_attacker(byTypeBB, to, stmAttackers, occupied, attackers); + + // Stop before processing a king capture + if (captured == KING) + { + if (stmAttackers == attackers) + ++slIndex; + + break; + } + + stm = ~stm; + stmAttackers = attackers & pieces(stm); + ++slIndex; + + } while (stmAttackers); + + // Having built the swap list, we negamax through it to find the best + // achievable score from the point of view of the side to move. + while (--slIndex) + swapList[slIndex - 1] = std::min(-swapList[slIndex], swapList[slIndex - 1]); + + return swapList[0]; +} + + +/// Position::is_draw() tests whether the position is drawn by material, 50 moves +/// rule or 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. + +static char toggle_case(char c) { + return char(islower(c) ? toupper(c) : tolower(c)); +} + +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(), toggle_case); + + 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(), 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* step) const { + + // Which parts of the position should be verified? + const bool all = false; + + const bool testBitboards = all || false; + const bool testState = all || false; + const bool testKingCount = all || false; + const bool testKingCapture = all || false; + const bool testPieceCounts = all || false; + const bool testPieceList = all || false; + const bool testCastlingSquares = all || false; + + if (step) + *step = 1; + + if ( (sideToMove != WHITE && sideToMove != BLACK) + || piece_on(king_square(WHITE)) != W_KING + || piece_on(king_square(BLACK)) != B_KING + || ( ep_square() != SQ_NONE + && relative_rank(sideToMove, ep_square()) != RANK_6)) + return false; + + if (step && ++*step, testBitboards) + { + // The intersection of the white and black pieces must be empty + if (pieces(WHITE) & pieces(BLACK)) + return false; + + // The union of the white and black pieces must be equal to all + // occupied squares + if ((pieces(WHITE) | pieces(BLACK)) != pieces()) + return false; + + // Separate piece type bitboards must have empty intersections + 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 && ++*step, testState) + { + StateInfo si; + set_state(&si); + if ( st->key != si.key + || st->pawnKey != si.pawnKey + || st->materialKey != si.materialKey + || st->npMaterial[WHITE] != si.npMaterial[WHITE] + || st->npMaterial[BLACK] != si.npMaterial[BLACK] + || st->psq != si.psq + || st->checkersBB != si.checkersBB) + return false; + } + + if (step && ++*step, testKingCount) + if ( std::count(board, board + SQUARE_NB, W_KING) != 1 + || std::count(board, board + SQUARE_NB, B_KING) != 1) + return false; + + if (step && ++*step, testKingCapture) + if (attackers_to(king_square(~sideToMove)) & pieces(sideToMove)) + return false; + + if (step && ++*step, testPieceCounts) + for (Color c = WHITE; c <= BLACK; ++c) + for (PieceType pt = PAWN; pt <= KING; ++pt) + if (pieceCount[c][pt] != popcount(pieces(c, pt))) + return false; + + if (step && ++*step, testPieceList) + for (Color c = WHITE; c <= BLACK; ++c) + for (PieceType pt = PAWN; pt <= KING; ++pt) + for (int i = 0; i < pieceCount[c][pt]; ++i) + if ( board[pieceList[c][pt][i]] != make_piece(c, pt) + || index[pieceList[c][pt][i]] != i) + return false; + + if (step && ++*step, testCastlingSquares) + 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 ( (castlingRightsMask[king_square(c)] & (c | s)) != (c | s) + || piece_on(castlingRookSquare[c | s]) != make_piece(c, ROOK) + || castlingRightsMask[castlingRookSquare[c | s]] != (c | s)) + return false; + } + + return true; +} diff --git a/src/position.h b/src/position.h new file mode 100644 index 0000000..d950411 --- /dev/null +++ b/src/position.h @@ -0,0 +1,434 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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 "bitboard.h" +#include "types.h" + + +/// The checkInfo struct is initialized at c'tor time and keeps info used +/// to detect if a move gives check. +class Position; +struct Thread; + +struct CheckInfo { + + explicit CheckInfo(const Position&); + + Bitboard dcCandidates; + Bitboard pinned; + Bitboard checkSq[PIECE_TYPE_NB]; + Square ksq; +}; + + +/// The 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 as a parameter. + +struct StateInfo { + Key pawnKey, materialKey; + Value npMaterial[COLOR_NB]; + int castlingRights, rule50, pliesFromNull; + Score psq; + Square epSquare; + + Key key; + Bitboard checkersBB; + PieceType capturedType; + StateInfo* previous; +}; + + +/// When making a move the current StateInfo up to 'key' excluded is copied to +/// the new one. Here we calculate the quad words (64bits) needed to be copied. +const size_t StateCopySize64 = offsetof(StateInfo, key) / sizeof(uint64_t) + 1; + + +/// The Position class stores the information regarding the board representation +/// like pieces, side to move, hash keys, castling info, etc. The most important +/// methods are do_move() and undo_move(), used by the search to update node info +/// when traversing the search tree. + +class Position { +public: + Position() {} + Position(const Position& pos, Thread* t) { *this = pos; thisThread = t; } + Position(const std::string& f, bool c960, Thread* t) { set(f, c960, t); } + Position& operator=(const Position&); + static void init(); + + // Text input/output + void set(const std::string& fenStr, bool isChess960, Thread* th); + const std::string fen() const; + const std::string pretty(Move m = MOVE_NONE) 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 king_square(Color c) const; + Square ep_square() const; + bool empty(Square s) const; + template int count(Color c) const; + template const Square* list(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; + + // Attacks to/from a given square + Bitboard attackers_to(Square s) const; + Bitboard attackers_to(Square s, Bitboard occ) 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; + + // Properties of moves + bool legal(Move m, Bitboard pinned) 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 CheckInfo& ci) const; + bool advanced_pawn_push(Move m) const; + Piece moved_piece(Move m) const; + PieceType captured_piece_type() const; + + // Piece specific + bool pawn_passed(Color c, Square s) const; + bool pawn_on_7th(Color c) const; + bool bishop_pair(Color c) const; + bool opposite_bishops() const; + + // Doing and undoing moves + void do_move(Move m, StateInfo& st); + void do_move(Move m, StateInfo& st, const CheckInfo& ci, bool moveIsCheck); + void undo_move(Move m); + void do_null_move(StateInfo& st); + void undo_null_move(); + + // Static exchange evaluation + Value see(Move m) const; + Value see_sign(Move m) const; + + // Accessing hash keys + Key key() const; + Key exclusion_key() const; + Key pawn_key() const; + Key material_key() const; + + // Incremental piece-square evaluation + Score psq_score() const; + Value non_pawn_material(Color c) 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; + void set_nodes_searched(uint64_t n); + bool is_draw() const; + + // Position consistency check, for debugging + bool pos_is_ok(int* step = NULL) const; + void flip(); + +private: + // Initialization helpers (used while setting up a position) + void clear(); + void set_castling_right(Color c, Square rfrom); + void set_state(StateInfo* si) const; + + // Helper functions + Bitboard check_blockers(Color c, Color kingColor) const; + void put_piece(Square s, Color c, PieceType pt); + void remove_piece(Square s, Color c, PieceType pt); + void move_piece(Square from, Square to, Color c, PieceType pt); + template + void do_castling(Square from, Square& to, Square& rfrom, Square& rto); + + // Board and pieces + Piece board[SQUARE_NB]; + Bitboard byTypeBB[PIECE_TYPE_NB]; + Bitboard byColorBB[COLOR_NB]; + int pieceCount[COLOR_NB][PIECE_TYPE_NB]; + Square pieceList[COLOR_NB][PIECE_TYPE_NB][16]; + int index[SQUARE_NB]; + + // Other info + int castlingRightsMask[SQUARE_NB]; + Square castlingRookSquare[CASTLING_RIGHT_NB]; + Bitboard castlingPath[CASTLING_RIGHT_NB]; + StateInfo startState; + uint64_t nodes; + int gamePly; + Color sideToMove; + Thread* thisThread; + StateInfo* st; + bool chess960; +}; + +inline uint64_t Position::nodes_searched() const { + return nodes; +} + +inline void Position::set_nodes_searched(uint64_t n) { + nodes = n; +} + +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 bool Position::empty(Square s) const { + return board[s] == NO_PIECE; +} + +inline Color Position::side_to_move() const { + return sideToMove; +} + +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[c][Pt]; +} + +template inline const Square* Position::list(Color c) const { + return pieceList[c][Pt]; +} + +inline Square Position::ep_square() const { + return st->epSquare; +} + +inline Square Position::king_square(Color c) const { + return pieceList[c][KING][0]; +} + +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 check_blockers(sideToMove, ~sideToMove); +} + +inline Bitboard Position::pinned_pieces(Color c) const { + return check_blockers(c, c); +} + +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->npMaterial[c]; +} + +inline int Position::game_ply() const { + return gamePly; +} + +inline bool Position::opposite_bishops() const { + + return pieceCount[WHITE][BISHOP] == 1 + && pieceCount[BLACK][BISHOP] == 1 + && opposite_colors(pieceList[WHITE][BISHOP][0], pieceList[BLACK][BISHOP][0]); +} + +inline bool Position::bishop_pair(Color c) const { + + return pieceCount[c][BISHOP] >= 2 + && opposite_colors(pieceList[c][BISHOP][0], pieceList[c][BISHOP][1]); +} + +inline bool Position::pawn_on_7th(Color c) const { + return pieces(c, PAWN) & rank_bb(relative_rank(c, RANK_7)); +} + +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 { + + // Note that castling is encoded as "king captures the rook" + assert(is_ok(m)); + return (!empty(to_sq(m)) && type_of(m) != CASTLING) || type_of(m) == ENPASSANT; +} + +inline PieceType Position::captured_piece_type() const { + return st->capturedType; +} + +inline Thread* Position::this_thread() const { + return thisThread; +} + +inline void Position::put_piece(Square s, Color c, PieceType pt) { + + board[s] = make_piece(c, pt); + byTypeBB[ALL_PIECES] |= s; + byTypeBB[pt] |= s; + byColorBB[c] |= s; + index[s] = pieceCount[c][pt]++; + pieceList[c][pt][index[s]] = s; +} + +inline void Position::move_piece(Square from, Square to, Color c, PieceType pt) { + + // 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[pt] ^= from_to_bb; + byColorBB[c] ^= from_to_bb; + board[from] = NO_PIECE; + board[to] = make_piece(c, pt); + index[to] = index[from]; + pieceList[c][pt][index[to]] = to; +} + +inline void Position::remove_piece(Square s, Color c, PieceType pt) { + + // 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 guaranteed to be invariant to a do_move() + undo_move() sequence. + byTypeBB[ALL_PIECES] ^= s; + byTypeBB[pt] ^= s; + byColorBB[c] ^= s; + /* board[s] = NO_PIECE; */ // Not needed, will be overwritten by capturing + Square lastSquare = pieceList[c][pt][--pieceCount[c][pt]]; + index[lastSquare] = index[s]; + pieceList[c][pt][index[lastSquare]] = lastSquare; + pieceList[c][pt][pieceCount[c][pt]] = SQ_NONE; +} + +#endif // #ifndef POSITION_H_INCLUDED diff --git a/src/psqtab.h b/src/psqtab.h new file mode 100644 index 0000000..a88d31d --- /dev/null +++ b/src/psqtab.h @@ -0,0 +1,98 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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 PSQTAB_H_INCLUDED +#define PSQTAB_H_INCLUDED + +#include "types.h" + +#define S(mg, eg) make_score(mg, eg) + + +/// PSQT[PieceType][Square] contains Piece-Square scores. For each piece type on +/// a given square a (middlegame, endgame) score pair is assigned. PSQT is defined +/// for the white side and the tables are symmetric for the black side. + +static const Score PSQT[][SQUARE_NB] = { + { }, + { // Pawn + S( 0, 0), S( 0, 0), S( 0, 0), S( 0, 0), S(0, 0), S( 0, 0), S( 0, 0), S( 0, 0), + S(-20, 0), S( 0, 0), S( 0, 0), S( 0, 0), S(0, 0), S( 0, 0), S( 0, 0), S(-20, 0), + S(-20, 0), S( 0, 0), S(10, 0), S(20, 0), S(20, 0), S(10, 0), S( 0, 0), S(-20, 0), + S(-20, 0), S( 0, 0), S(20, 0), S(40, 0), S(40, 0), S(20, 0), S( 0, 0), S(-20, 0), + S(-20, 0), S( 0, 0), S(10, 0), S(20, 0), S(20, 0), S(10, 0), S( 0, 0), S(-20, 0), + S(-20, 0), S( 0, 0), S( 0, 0), S( 0, 0), S(0, 0), S( 0, 0), S( 0, 0), S(-20, 0), + S(-20, 0), S( 0, 0), S( 0, 0), S( 0, 0), S(0, 0), S( 0, 0), S( 0, 0), S(-20, 0), + S( 0, 0), S( 0, 0), S( 0, 0), S( 0, 0), S(0, 0), S( 0, 0), S( 0, 0), S( 0, 0) + }, + { // Knight + S(-144,-98), S(-109,-83), S(-85,-51), S(-73,-16), S(-73,-16), S(-85,-51), S(-109,-83), S(-144,-98), + S( -88,-68), S( -43,-53), S(-19,-21), S( -7, 14), S( -7, 14), S(-19,-21), S( -43,-53), S( -88,-68), + S( -69,-53), S( -24,-38), S( 0, -6), S( 12, 29), S( 12, 29), S( 0, -6), S( -24,-38), S( -69,-53), + S( -28,-42), S( 17,-27), S( 41, 5), S( 53, 40), S( 53, 40), S( 41, 5), S( 17,-27), S( -28,-42), + S( -30,-42), S( 15,-27), S( 39, 5), S( 51, 40), S( 51, 40), S( 39, 5), S( 15,-27), S( -30,-42), + S( -10,-53), S( 35,-38), S( 59, -6), S( 71, 29), S( 71, 29), S( 59, -6), S( 35,-38), S( -10,-53), + S( -64,-68), S( -19,-53), S( 5,-21), S( 17, 14), S( 17, 14), S( 5,-21), S( -19,-53), S( -64,-68), + S(-200,-98), S( -65,-83), S(-41,-51), S(-29,-16), S(-29,-16), S(-41,-51), S( -65,-83), S(-200,-98) + }, + { // Bishop + S(-54,-65), S(-27,-42), S(-34,-44), S(-43,-26), S(-43,-26), S(-34,-44), S(-27,-42), S(-54,-65), + S(-29,-43), S( 8,-20), S( 1,-22), S( -8, -4), S( -8, -4), S( 1,-22), S( 8,-20), S(-29,-43), + S(-20,-33), S( 17,-10), S( 10,-12), S( 1, 6), S( 1, 6), S( 10,-12), S( 17,-10), S(-20,-33), + S(-19,-35), S( 18,-12), S( 11,-14), S( 2, 4), S( 2, 4), S( 11,-14), S( 18,-12), S(-19,-35), + S(-22,-35), S( 15,-12), S( 8,-14), S( -1, 4), S( -1, 4), S( 8,-14), S( 15,-12), S(-22,-35), + S(-28,-33), S( 9,-10), S( 2,-12), S( -7, 6), S( -7, 6), S( 2,-12), S( 9,-10), S(-28,-33), + S(-32,-43), S( 5,-20), S( -2,-22), S(-11, -4), S(-11, -4), S( -2,-22), S( 5,-20), S(-32,-43), + S(-49,-65), S(-22,-42), S(-29,-44), S(-38,-26), S(-38,-26), S(-29,-44), S(-22,-42), S(-49,-65) + }, + { // Rook + S(-22, 3), S(-17, 3), S(-12, 3), S(-8, 3), S(-8, 3), S(-12, 3), S(-17, 3), S(-22, 3), + S(-22, 3), S( -7, 3), S( -2, 3), S( 2, 3), S( 2, 3), S( -2, 3), S( -7, 3), S(-22, 3), + S(-22, 3), S( -7, 3), S( -2, 3), S( 2, 3), S( 2, 3), S( -2, 3), S( -7, 3), S(-22, 3), + S(-22, 3), S( -7, 3), S( -2, 3), S( 2, 3), S( 2, 3), S( -2, 3), S( -7, 3), S(-22, 3), + S(-22, 3), S( -7, 3), S( -2, 3), S( 2, 3), S( 2, 3), S( -2, 3), S( -7, 3), S(-22, 3), + S(-22, 3), S( -7, 3), S( -2, 3), S( 2, 3), S( 2, 3), S( -2, 3), S( -7, 3), S(-22, 3), + S(-11, 3), S( 4, 3), S( 9, 3), S(13, 3), S(13, 3), S( 9, 3), S( 4, 3), S(-11, 3), + S(-22, 3), S(-17, 3), S(-12, 3), S(-8, 3), S(-8, 3), S(-12, 3), S(-17, 3), S(-22, 3) + }, + { // Queen + S(-2,-80), S(-2,-54), S(-2,-42), S(-2,-30), S(-2,-30), S(-2,-42), S(-2,-54), S(-2,-80), + S(-2,-54), S( 8,-30), S( 8,-18), S( 8, -6), S( 8, -6), S( 8,-18), S( 8,-30), S(-2,-54), + S(-2,-42), S( 8,-18), S( 8, -6), S( 8, 6), S( 8, 6), S( 8, -6), S( 8,-18), S(-2,-42), + S(-2,-30), S( 8, -6), S( 8, 6), S( 8, 18), S( 8, 18), S( 8, 6), S( 8, -6), S(-2,-30), + S(-2,-30), S( 8, -6), S( 8, 6), S( 8, 18), S( 8, 18), S( 8, 6), S( 8, -6), S(-2,-30), + S(-2,-42), S( 8,-18), S( 8, -6), S( 8, 6), S( 8, 6), S( 8, -6), S( 8,-18), S(-2,-42), + S(-2,-54), S( 8,-30), S( 8,-18), S( 8, -6), S( 8, -6), S( 8,-18), S( 8,-30), S(-2,-54), + S(-2,-80), S(-2,-54), S(-2,-42), S(-2,-30), S(-2,-30), S(-2,-42), S(-2,-54), S(-2,-80) + }, + { // King + S(298, 27), S(332, 81), S(273,108), S(225,116), S(225,116), S(273,108), S(332, 81), S(298, 27), + S(287, 74), S(321,128), S(262,155), S(214,163), S(214,163), S(262,155), S(321,128), S(287, 74), + S(224,111), S(258,165), S(199,192), S(151,200), S(151,200), S(199,192), S(258,165), S(224,111), + S(196,135), S(230,189), S(171,216), S(123,224), S(123,224), S(171,216), S(230,189), S(196,135), + S(173,135), S(207,189), S(148,216), S(100,224), S(100,224), S(148,216), S(207,189), S(173,135), + S(146,111), S(180,165), S(121,192), S( 73,200), S( 73,200), S(121,192), S(180,165), S(146,111), + S(119, 74), S(153,128), S( 94,155), S( 46,163), S( 46,163), S( 94,155), S(153,128), S(119, 74), + S( 98, 27), S(132, 81), S( 73,108), S( 25,116), S( 25,116), S( 73,108), S(132, 81), S( 98, 27) + } +}; + +#undef S + +#endif // #ifndef PSQTAB_H_INCLUDED diff --git a/src/rkiss.h b/src/rkiss.h new file mode 100644 index 0000000..f3468db --- /dev/null +++ b/src/rkiss.h @@ -0,0 +1,81 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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 . + + This file is based on original code by Heinz van Saanen and is + available under 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. +*/ + +#ifndef RKISS_H_INCLUDED +#define RKISS_H_INCLUDED + +#include "types.h" + +/// RKISS is our pseudo random number generator (PRNG) used to compute hash keys. +/// George Marsaglia invented the RNG-Kiss-family in the early 90's. This is a +/// specific version that Heinz van Saanen derived from some public domain code +/// by Bob Jenkins. Following the feature list, as tested by Heinz. +/// +/// - Quite platform independent +/// - Passes ALL dieharder tests! Here *nix sys-rand() e.g. fails miserably:-) +/// - ~12 times faster than my *nix sys-rand() +/// - ~4 times faster than SSE2-version of Mersenne twister +/// - Average cycle length: ~2^126 +/// - 64 bit seed +/// - Return doubles with a full 53 bit mantissa +/// - Thread safe + +class RKISS { + + uint64_t a, b, c, d; + + uint64_t rotate_L(uint64_t x, unsigned k) const { + return (x << k) | (x >> (64 - k)); + } + + uint64_t rand64() { + + const uint64_t e = a - rotate_L(b, 7); + a = b ^ rotate_L(c, 13); + b = c + rotate_L(d, 37); + c = d + e; + return d = e + a; + } + +public: + RKISS(int seed = 73) { + + a = 0xF1EA5EED, b = c = d = 0xD4E12C77; + + for (int i = 0; i < seed; ++i) // Scramble a few rounds + rand64(); + } + + template T rand() { return T(rand64()); } + + /// Special generator used to fast init magic numbers. Here the + /// trick is to rotate the randoms of a given quantity 's' known + /// to be optimal to quickly find a good magic candidate. + template T magic_rand(int s) { + return rotate_L(rotate_L(rand(), (s >> 0) & 0x3F) & rand() + , (s >> 6) & 0x3F) & rand(); + } +}; + +#endif // #ifndef RKISS_H_INCLUDED diff --git a/src/search.cpp b/src/search.cpp new file mode 100644 index 0000000..991926c --- /dev/null +++ b/src/search.cpp @@ -0,0 +1,1658 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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 +#include +#include + +#include "evaluate.h" +#include "movegen.h" +#include "movepick.h" +#include "notation.h" +#include "rkiss.h" +#include "search.h" +#include "timeman.h" +#include "thread.h" +#include "tt.h" +#include "ucioption.h" + +namespace Search { + + volatile SignalsType Signals; + LimitsType Limits; + std::vector RootMoves; + Position RootPos; + Time::point SearchTime; + StateStackPtr SetupStates; +} + +using std::string; +using Eval::evaluate; +using namespace Search; + +namespace { + + // Different node types, used as template parameter + enum NodeType { Root, PV, NonPV }; + + // Dynamic razoring margin based on depth + inline Value razor_margin(Depth d) { return Value(512 + 16 * d); } + + // Futility lookup tables (initialized at startup) and their access functions + int FutilityMoveCounts[2][32]; // [improving][depth] + + inline Value futility_margin(Depth d) { + return Value(100 * d); + } + + // Reduction lookup tables (initialized at startup) and their access function + int8_t Reductions[2][2][64][64]; // [pv][improving][depth][moveNumber] + + template inline Depth reduction(bool i, Depth d, int mn) { + + return (Depth) Reductions[PvNode][i][std::min(int(d) / ONE_PLY, 63)][std::min(mn, 63)]; + } + + size_t PVIdx; + TimeManager TimeMgr; + double BestMoveChanges; + Value DrawValue[COLOR_NB]; + HistoryStats History; + GainsStats Gains; + MovesStats Countermoves, Followupmoves; + + 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); + + void id_loop(Position& pos); + Value value_to_tt(Value v, int ply); + Value value_from_tt(Value v, int ply); + void update_stats(const Position& pos, Stack* ss, Move move, Depth depth, Move* quiets, int quietsCnt); + string uci_pv(const Position& pos, int depth, Value alpha, Value beta); + + struct Skill { + Skill(int l, size_t rootSize) : level(l), + candidates(l < 20 ? std::min(4, (int)rootSize) : 0), + best(MOVE_NONE) {} + ~Skill() { + if (candidates) // Swap best PV line with the sub-optimal one + std::swap(RootMoves[0], *std::find(RootMoves.begin(), + RootMoves.end(), best ? best : pick_move())); + } + + size_t candidates_size() const { return candidates; } + bool time_to_pick(int depth) const { return depth == 1 + level; } + Move pick_move(); + + int level; + size_t candidates; + Move best; + }; + +} // namespace + + +/// Search::init() is called during startup to initialize various lookup tables + +void Search::init() { + + int d; // depth (ONE_PLY == 2) + int hd; // half depth (ONE_PLY == 1) + int mc; // moveCount + + // Init reductions array + for (hd = 1; hd < 64; ++hd) for (mc = 1; mc < 64; ++mc) + { + double pvRed = 0.00 + log(double(hd)) * log(double(mc)) / 3.00; + double nonPVRed = 0.33 + log(double(hd)) * log(double(mc)) / 2.25; + Reductions[1][1][hd][mc] = int8_t( pvRed >= 1.0 ? pvRed * int(ONE_PLY) : 0); + Reductions[0][1][hd][mc] = int8_t(nonPVRed >= 1.0 ? nonPVRed * int(ONE_PLY) : 0); + + Reductions[1][0][hd][mc] = Reductions[1][1][hd][mc]; + Reductions[0][0][hd][mc] = Reductions[0][1][hd][mc]; + + if (Reductions[0][0][hd][mc] > 2 * ONE_PLY) + Reductions[0][0][hd][mc] += ONE_PLY; + + else if (Reductions[0][0][hd][mc] > 1 * ONE_PLY) + Reductions[0][0][hd][mc] += ONE_PLY / 2; + } + + // Init futility move count array + for (d = 0; d < 32; ++d) + { + FutilityMoveCounts[0][d] = int(2.4 + 0.222 * pow(d + 0.00, 1.8)); + FutilityMoveCounts[1][d] = int(3.0 + 0.300 * pow(d + 0.98, 1.8)); + } +} + + +/// 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 returned. +template +uint64_t Search::perft(Position& pos, Depth depth) { + + StateInfo st; + uint64_t cnt, nodes = 0; + CheckInfo ci(pos); + const bool leaf = depth == 2 * ONE_PLY; + + for (MoveList it(pos); *it; ++it) + { + if (Root && depth <= ONE_PLY) + cnt = 1, nodes++; + else + { + pos.do_move(*it, st, ci, pos.gives_check(*it, ci)); + cnt = leaf ? MoveList(pos).size() : perft(pos, depth - ONE_PLY); + nodes += cnt; + pos.undo_move(*it); + } + if (Root) + sync_cout << move_to_uci(*it, pos.is_chess960()) << ": " << cnt << sync_endl; + } + return nodes; +} + +template uint64_t Search::perft(Position& pos, Depth depth); + + +/// Search::think() is the external interface to Stockfish's search, and is +/// called by the main thread when the program receives the UCI 'go' command. It +/// searches from RootPos and at the end prints the "bestmove" to output. + +void Search::think() { + + TimeMgr.init(Limits, RootPos.game_ply(), RootPos.side_to_move()); + + int cf = Options["Contempt Factor"] * PawnValueEg / 100; // From centipawns + DrawValue[ RootPos.side_to_move()] = VALUE_DRAW - Value(cf); + DrawValue[~RootPos.side_to_move()] = VALUE_DRAW + Value(cf); + + if (RootMoves.empty()) + { + RootMoves.push_back(MOVE_NONE); + sync_cout << "info depth 0 score " + << score_to_uci(RootPos.checkers() ? -VALUE_MATE : VALUE_DRAW) + << sync_endl; + + goto finalize; + } + + if (Options["Write Search Log"]) + { + Log log(Options["Search Log Filename"]); + log << "\nSearching: " << RootPos.fen() + << "\ninfinite: " << Limits.infinite + << " ponder: " << Limits.ponder + << " time: " << Limits.time[RootPos.side_to_move()] + << " increment: " << Limits.inc[RootPos.side_to_move()] + << " moves to go: " << Limits.movestogo + << "\n" << std::endl; + } + + // Reset the threads, still sleeping: will wake up at split time + for (size_t i = 0; i < Threads.size(); ++i) + Threads[i]->maxPly = 0; + + Threads.timer->run = true; + Threads.timer->notify_one(); // Wake up the recurring timer + + id_loop(RootPos); // Let's start searching ! + + Threads.timer->run = false; // Stop the timer + + if (Options["Write Search Log"]) + { + Time::point elapsed = Time::now() - SearchTime + 1; + + Log log(Options["Search Log Filename"]); + log << "Nodes: " << RootPos.nodes_searched() + << "\nNodes/second: " << RootPos.nodes_searched() * 1000 / elapsed + << "\nBest move: " << move_to_san(RootPos, RootMoves[0].pv[0]); + + StateInfo st; + RootPos.do_move(RootMoves[0].pv[0], st); + log << "\nPonder move: " << move_to_san(RootPos, RootMoves[0].pv[1]) << std::endl; + RootPos.undo_move(RootMoves[0].pv[0]); + } + +finalize: + + // When search is stopped this info is not printed + sync_cout << "info nodes " << RootPos.nodes_searched() + << " time " << Time::now() - SearchTime + 1 << sync_endl; + + // 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; + RootPos.this_thread()->wait_for(Signals.stop); + } + + // Best move could be MOVE_NONE when searching on a stalemate position + sync_cout << "bestmove " << move_to_uci(RootMoves[0].pv[0], RootPos.is_chess960()) + << " ponder " << move_to_uci(RootMoves[0].pv[1], RootPos.is_chess960()) + << sync_endl; +} + + +namespace { + + // id_loop() is the main iterative deepening loop. It calls search() repeatedly + // with increasing depth until the allocated thinking time has been consumed, + // user stops the search, or the maximum search depth is reached. + + void id_loop(Position& pos) { + + Stack stack[MAX_PLY_PLUS_6], *ss = stack+2; // To allow referencing (ss-2) + int depth; + Value bestValue, alpha, beta, delta; + + std::memset(ss-2, 0, 5 * sizeof(Stack)); + + depth = 0; + BestMoveChanges = 0; + bestValue = delta = alpha = -VALUE_INFINITE; + beta = VALUE_INFINITE; + + TT.new_search(); + History.clear(); + Gains.clear(); + Countermoves.clear(); + Followupmoves.clear(); + + size_t multiPV = Options["MultiPV"]; + Skill skill(Options["Skill Level"], RootMoves.size()); + + // Do we have to play with skill handicap? In this case enable MultiPV search + // that we will use behind the scenes to retrieve a set of possible moves. + multiPV = std::max(multiPV, skill.candidates_size()); + + // Iterative deepening loop until requested to stop or target depth reached + while (++depth <= MAX_PLY && !Signals.stop && (!Limits.depth || depth <= Limits.depth)) + { + // Age out PV variability metric + BestMoveChanges *= 0.5; + + // 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 (size_t i = 0; i < RootMoves.size(); ++i) + RootMoves[i].prevScore = RootMoves[i].score; + + // MultiPV loop. We perform a full root search for each PV line + for (PVIdx = 0; PVIdx < std::min(multiPV, RootMoves.size()) && !Signals.stop; ++PVIdx) + { + // Reset aspiration window starting size + if (depth >= 5) + { + delta = Value(16); + alpha = std::max(RootMoves[PVIdx].prevScore - delta,-VALUE_INFINITE); + beta = std::min(RootMoves[PVIdx].prevScore + 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(pos, ss, alpha, beta, depth * ONE_PLY, 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()); + + // Write PV back to transposition table in case the relevant + // entries have been overwritten during the search. + for (size_t i = 0; i <= PVIdx; ++i) + RootMoves[i].insert_pv_in_tt(pos); + + // 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 previous iteration. + if (Signals.stop) + break; + + // When failing high/low give some update (without cluttering + // the UI) before a re-search. + if ( (bestValue <= alpha || bestValue >= beta) + && Time::now() - SearchTime > 3000) + sync_cout << uci_pv(pos, depth, alpha, beta) << sync_endl; + + // In case of failing low/high increase aspiration window and + // re-search, otherwise exit the loop. + if (bestValue <= alpha) + { + alpha = std::max(bestValue - delta, -VALUE_INFINITE); + + Signals.failedLowAtRoot = true; + Signals.stopOnPonderhit = false; + } + else if (bestValue >= beta) + beta = std::min(bestValue + delta, VALUE_INFINITE); + + else + break; + + delta += 3 * delta / 8; + + 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 (PVIdx + 1 == std::min(multiPV, RootMoves.size()) || Time::now() - SearchTime > 3000) + sync_cout << uci_pv(pos, depth, alpha, beta) << sync_endl; + } + + // If skill levels are enabled and time is up, pick a sub-optimal best move + if (skill.candidates_size() && skill.time_to_pick(depth)) + skill.pick_move(); + + if (Options["Write Search Log"]) + { + RootMove& rm = RootMoves[0]; + if (skill.best != MOVE_NONE) + rm = *std::find(RootMoves.begin(), RootMoves.end(), skill.best); + + Log log(Options["Search Log Filename"]); + log << pretty_pv(pos, depth, rm.score, Time::now() - SearchTime, &rm.pv[0]) + << std::endl; + } + + // 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() && !Signals.stop && !Signals.stopOnPonderhit) + { + // Take some extra time if the best move has changed + if (depth > 4 && multiPV == 1) + TimeMgr.pv_instability(BestMoveChanges); + + // Stop the search if only one legal move is available or all + // of the available time has been used. + if ( RootMoves.size() == 1 + || Time::now() - SearchTime > TimeMgr.available_time()) + { + // 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; + } + } + } + } + + + // search<>() is the main search function for both PV and non-PV nodes and for + // normal and SplitPoint nodes. When called just after a split point the search + // is simpler because we have already probed the hash table, done a null move + // search, and searched the first move before splitting, so we don't have to + // repeat all this work again. We also don't need to store anything to the hash + // table here: This is taken care of after we return from the split point. + + template + Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) { + + const bool RootNode = NT == Root; + const bool PvNode = NT == PV || NT == Root; + + assert(-VALUE_INFINITE <= alpha && alpha < beta && beta <= VALUE_INFINITE); + assert(PvNode || (alpha == beta - 1)); + assert(depth > DEPTH_ZERO); + + Move quietsSearched[64]; + StateInfo st; + const TTEntry *tte; + SplitPoint* splitPoint; + Key posKey; + Move ttMove, move, excludedMove, bestMove; + Depth ext, newDepth, predictedDepth; + Value bestValue, value, ttValue, eval, nullValue, futilityValue; + bool inCheck, givesCheck, pvMove, singularExtensionNode, improving; + bool captureOrPromotion, dangerous, doFullDepthSearch; + int moveCount, quietCount; + + // Step 1. Initialize node + Thread* thisThread = pos.this_thread(); + inCheck = pos.checkers(); + + if (SpNode) + { + splitPoint = ss->splitPoint; + bestMove = splitPoint->bestMove; + bestValue = splitPoint->bestValue; + tte = NULL; + ttMove = excludedMove = MOVE_NONE; + ttValue = VALUE_NONE; + + assert(splitPoint->bestValue > -VALUE_INFINITE && splitPoint->moveCount > 0); + + goto moves_loop; + } + + moveCount = quietCount = 0; + bestValue = -VALUE_INFINITE; + ss->currentMove = ss->ttMove = (ss+1)->excludedMove = bestMove = MOVE_NONE; + ss->ply = (ss-1)->ply + 1; + (ss+1)->skipNullMove = false; (ss+1)->reduction = DEPTH_ZERO; + (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE; + + // 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 || 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; + } + + // Step 4. Transposition table lookup + // We don't want the score of a partial search to overwrite a previous full search + // TT value, so we use a different position key in case of an excluded move. + excludedMove = ss->excludedMove; + posKey = excludedMove ? pos.exclusion_key() : pos.key(); + tte = TT.probe(posKey); + ss->ttMove = ttMove = RootNode ? RootMoves[PVIdx].pv[0] : tte ? tte->move() : MOVE_NONE; + ttValue = tte ? value_from_tt(tte->value(), ss->ply) : VALUE_NONE; + + // At PV nodes we check for exact scores, whilst at non-PV nodes we check for + // a fail high/low. The biggest advantage to probing at PV nodes is to have a + // smooth experience in analysis mode. We don't probe at Root nodes otherwise + // we should also update RootMoveList to avoid bogus output. + if ( !RootNode + && tte + && tte->depth() >= depth + && ttValue != VALUE_NONE // Only in case of TT access race + && ( PvNode ? tte->bound() == BOUND_EXACT + : ttValue >= beta ? (tte->bound() & BOUND_LOWER) + : (tte->bound() & BOUND_UPPER))) + { + ss->currentMove = ttMove; // Can be MOVE_NONE + + // If ttMove is quiet, update killers, history, counter move and followup move on TT hit + if (ttValue >= beta && ttMove && !pos.capture_or_promotion(ttMove) && !inCheck) + update_stats(pos, ss, ttMove, depth, NULL, 0); + + return ttValue; + } + + // Step 5. Evaluate the position statically and update parent's gain statistics + if (inCheck) + { + ss->staticEval = eval = VALUE_NONE; + goto moves_loop; + } + + else if (tte) + { + // Never assume anything on values stored in TT + if ((ss->staticEval = eval = tte->eval_value()) == 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; + + TT.store(posKey, VALUE_NONE, BOUND_NONE, DEPTH_NONE, MOVE_NONE, ss->staticEval); + } + + if ( !pos.captured_piece_type() + && ss->staticEval != VALUE_NONE + && (ss-1)->staticEval != VALUE_NONE + && (move = (ss-1)->currentMove) != MOVE_NULL + && move != MOVE_NONE + && type_of(move) == NORMAL) + { + Square to = to_sq(move); + Gains.update(pos.piece_on(to), to, -(ss-1)->staticEval - ss->staticEval); + } + + // Step 6. Razoring (skipped when in check) + if ( !PvNode + && depth < 4 * ONE_PLY + && eval + razor_margin(depth) <= alpha + && ttMove == MOVE_NONE + && !pos.pawn_on_7th(pos.side_to_move())) + { + if ( depth <= ONE_PLY + && eval + razor_margin(3 * ONE_PLY) <= alpha) + return qsearch(pos, ss, alpha, beta, DEPTH_ZERO); + + Value ralpha = alpha - razor_margin(depth); + 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 ( !PvNode + && !ss->skipNullMove + && depth < 7 * ONE_PLY + && eval - futility_margin(depth) >= beta + && abs(beta) < VALUE_MATE_IN_MAX_PLY + && abs(eval) < VALUE_KNOWN_WIN + && pos.non_pawn_material(pos.side_to_move())) + return eval - futility_margin(depth); + + // Step 8. Null move search with verification search (is omitted in PV nodes) + if ( !PvNode + && !ss->skipNullMove + && depth >= 2 * ONE_PLY + && eval >= beta + && pos.non_pawn_material(pos.side_to_move())) + { + ss->currentMove = MOVE_NULL; + + assert(eval - beta >= 0); + + // Null move dynamic reduction based on depth and value + Depth R = 3 * ONE_PLY + + depth / 4 + + (abs(beta) < VALUE_KNOWN_WIN ? int(eval - beta) / PawnValueMg * ONE_PLY + : DEPTH_ZERO); + + pos.do_null_move(st); + (ss+1)->skipNullMove = 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)->skipNullMove = 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->skipNullMove = 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->skipNullMove = false; + + if (v >= beta) + return nullValue; + } + } + + // Step 9. ProbCut (skipped when in check) + // If we have a very good capture (i.e. SEE > seeValues[captured_piece_type]) + // and a reduced search returns a value much above beta, we can (almost) safely + // prune the previous move. + if ( !PvNode + && depth >= 5 * ONE_PLY + && !ss->skipNullMove + && 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, History, pos.captured_piece_type()); + CheckInfo ci(pos); + + while ((move = mp.next_move()) != MOVE_NONE) + if (pos.legal(move, ci.pinned)) + { + ss->currentMove = move; + pos.do_move(move, st, ci, pos.gives_check(move, ci)); + 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 >= (PvNode ? 5 * ONE_PLY : 8 * ONE_PLY) + && !ttMove + && (PvNode || ss->staticEval + 256 >= beta)) + { + Depth d = depth - 2 * ONE_PLY - (PvNode ? DEPTH_ZERO : depth / 4); + + ss->skipNullMove = true; + search(pos, ss, alpha, beta, d, true); + ss->skipNullMove = false; + + tte = TT.probe(posKey); + ttMove = tte ? tte->move() : MOVE_NONE; + } + +moves_loop: // When in check and at SpNode search starts from here + + Square prevMoveSq = to_sq((ss-1)->currentMove); + Move countermoves[] = { Countermoves[pos.piece_on(prevMoveSq)][prevMoveSq].first, + Countermoves[pos.piece_on(prevMoveSq)][prevMoveSq].second }; + + Square prevOwnMoveSq = to_sq((ss-2)->currentMove); + Move followupmoves[] = { Followupmoves[pos.piece_on(prevOwnMoveSq)][prevOwnMoveSq].first, + Followupmoves[pos.piece_on(prevOwnMoveSq)][prevOwnMoveSq].second }; + + MovePicker mp(pos, ttMove, depth, History, countermoves, followupmoves, ss); + CheckInfo ci(pos); + value = bestValue; // Workaround a bogus 'uninitialized' warning under gcc + improving = ss->staticEval >= (ss-2)->staticEval + || ss->staticEval == VALUE_NONE + ||(ss-2)->staticEval == VALUE_NONE; + + singularExtensionNode = !RootNode + && !SpNode + && depth >= 8 * ONE_PLY + && abs(beta) < VALUE_KNOWN_WIN + && ttMove != MOVE_NONE + /* && ttValue != VALUE_NONE Already implicit in the next condition */ + && abs(ttValue) < VALUE_KNOWN_WIN + && !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(RootMoves.begin() + PVIdx, RootMoves.end(), move)) + continue; + + if (SpNode) + { + // Shared counter cannot be decremented later if the move turns out to be illegal + if (!pos.legal(move, ci.pinned)) + continue; + + moveCount = ++splitPoint->moveCount; + splitPoint->mutex.unlock(); + } + else + ++moveCount; + + if (RootNode) + { + Signals.firstRootMove = (moveCount == 1); + + if (thisThread == Threads.main() && Time::now() - SearchTime > 3000) + sync_cout << "info depth " << depth / ONE_PLY + << " currmove " << move_to_uci(move, pos.is_chess960()) + << " currmovenumber " << moveCount + PVIdx << sync_endl; + } + + ext = DEPTH_ZERO; + captureOrPromotion = pos.capture_or_promotion(move); + + givesCheck = type_of(move) == NORMAL && !ci.dcCandidates + ? ci.checkSq[type_of(pos.piece_on(from_sq(move)))] & to_sq(move) + : pos.gives_check(move, ci); + + dangerous = givesCheck + || type_of(move) != NORMAL + || pos.advanced_pawn_push(move); + + // Step 12. Extend checks + if (givesCheck && pos.see_sign(move) >= VALUE_ZERO) + ext = 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 + && !ext + && pos.legal(move, ci.pinned)) + { + Value rBeta = ttValue - int(depth); + ss->excludedMove = move; + ss->skipNullMove = true; + value = search(pos, ss, rBeta - 1, rBeta, depth / 2, cutNode); + ss->skipNullMove = false; + ss->excludedMove = MOVE_NONE; + + if (value < rBeta) + ext = ONE_PLY; + } + + // Update the current move (this must be done after singular extension search) + newDepth = depth - ONE_PLY + ext; + + // Step 13. Pruning at shallow depth (exclude PV nodes) + if ( !PvNode + && !captureOrPromotion + && !inCheck + && !dangerous + /* && move != ttMove Already implicit in the next condition */ + && bestValue > VALUE_MATED_IN_MAX_PLY) + { + // Move count based pruning + if ( depth < 16 * ONE_PLY + && moveCount >= FutilityMoveCounts[improving][depth] ) + { + if (SpNode) + splitPoint->mutex.lock(); + + continue; + } + + predictedDepth = newDepth - reduction(improving, depth, moveCount); + + // Futility pruning: parent node + if (predictedDepth < 7 * ONE_PLY) + { + futilityValue = ss->staticEval + futility_margin(predictedDepth) + + 128 + Gains[pos.moved_piece(move)][to_sq(move)]; + + if (futilityValue <= alpha) + { + bestValue = std::max(bestValue, futilityValue); + + if (SpNode) + { + splitPoint->mutex.lock(); + if (bestValue > splitPoint->bestValue) + splitPoint->bestValue = bestValue; + } + continue; + } + } + + // Prune moves with negative SEE at low depths + if (predictedDepth < 4 * ONE_PLY && pos.see_sign(move) < VALUE_ZERO) + { + if (SpNode) + splitPoint->mutex.lock(); + + continue; + } + } + + // Check for legality just before making the move + if (!RootNode && !SpNode && !pos.legal(move, ci.pinned)) + { + moveCount--; + continue; + } + + pvMove = PvNode && moveCount == 1; + ss->currentMove = move; + if (!SpNode && !captureOrPromotion && quietCount < 64) + quietsSearched[quietCount++] = move; + + // Step 14. Make the move + pos.do_move(move, st, ci, 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 + && !pvMove + && !captureOrPromotion + && move != ttMove + && move != ss->killers[0] + && move != ss->killers[1]) + { + ss->reduction = reduction(improving, depth, moveCount); + + if (!PvNode && cutNode) + ss->reduction += ONE_PLY; + + else if (History[pos.piece_on(to_sq(move))][to_sq(move)] < 0) + ss->reduction += ONE_PLY / 2; + + if (move == countermoves[0] || move == countermoves[1]) + ss->reduction = std::max(DEPTH_ZERO, ss->reduction - ONE_PLY); + + // Decrease reduction for moves that escape a capture + if ( ss->reduction + && type_of(move) == NORMAL + && type_of(pos.piece_on(to_sq(move))) != PAWN + && pos.see(make_move(to_sq(move), from_sq(move))) < 0) + ss->reduction = std::max(DEPTH_ZERO, ss->reduction - ONE_PLY); + + Depth d = std::max(newDepth - ss->reduction, ONE_PLY); + if (SpNode) + alpha = splitPoint->alpha; + + value = -search(pos, ss+1, -(alpha+1), -alpha, d, true); + + // Re-search at intermediate depth if reduction is very high + if (value > alpha && ss->reduction >= 4 * ONE_PLY) + { + Depth d2 = std::max(newDepth - 2 * ONE_PLY, ONE_PLY); + value = -search(pos, ss+1, -(alpha+1), -alpha, d2, true); + } + + doFullDepthSearch = (value > alpha && ss->reduction != DEPTH_ZERO); + ss->reduction = DEPTH_ZERO; + } + else + doFullDepthSearch = !pvMove; + + // Step 16. Full depth search, when LMR is skipped or fails high + if (doFullDepthSearch) + { + if (SpNode) + alpha = splitPoint->alpha; + + 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 to try another move. + if (PvNode && (pvMove || (value > alpha && (RootNode || value < beta)))) + 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 new best move + if (SpNode) + { + splitPoint->mutex.lock(); + bestValue = splitPoint->bestValue; + alpha = splitPoint->alpha; + } + + // Finished searching the move. If a stop or a cutoff occurred, the return + // value of the search cannot be trusted, and we return immediately without + // updating best move, PV and TT. + if (Signals.stop || thisThread->cutoff_occurred()) + return VALUE_ZERO; + + if (RootNode) + { + RootMove& rm = *std::find(RootMoves.begin(), RootMoves.end(), move); + + // PV move or new best move ? + if (pvMove || value > alpha) + { + rm.score = value; + rm.extract_pv_from_tt(pos); + + // 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 (!pvMove) + ++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 = SpNode ? splitPoint->bestValue = value : value; + + if (value > alpha) + { + bestMove = SpNode ? splitPoint->bestMove = move : move; + + if (PvNode && value < beta) // Update alpha! Always alpha < beta + alpha = SpNode ? splitPoint->alpha = value : value; + else + { + assert(value >= beta); // Fail high + + if (SpNode) + splitPoint->cutoff = true; + + break; + } + } + } + + // Step 19. Check for splitting the search + if ( !SpNode + && Threads.size() >= 2 + && depth >= Threads.minimumSplitDepth + && ( !thisThread->activeSplitPoint + || !thisThread->activeSplitPoint->allSlavesSearching) + && thisThread->splitPointsSize < MAX_SPLITPOINTS_PER_THREAD) + { + assert(bestValue > -VALUE_INFINITE && bestValue < beta); + + thisThread->split(pos, ss, alpha, beta, &bestValue, &bestMove, + depth, moveCount, &mp, NT, cutNode); + + if (Signals.stop || thisThread->cutoff_occurred()) + return VALUE_ZERO; + + if (bestValue >= beta) + break; + } + } + + if (SpNode) + return bestValue; + + // Following condition would detect a stop or a cutoff set 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 || thisThread->cutoff_occurred()) + 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 mate or stalemate. If we are in a singular extension search then + // return a fail low score. + if (!moveCount) + bestValue = excludedMove ? alpha + : inCheck ? mated_in(ss->ply) : DrawValue[pos.side_to_move()]; + + // Quiet best move: update killers, history, countermoves and followupmoves + else if (bestValue >= beta && !pos.capture_or_promotion(bestMove) && !inCheck) + update_stats(pos, ss, bestMove, depth, quietsSearched, quietCount - 1); + + TT.store(posKey, value_to_tt(bestValue, ss->ply), + bestValue >= beta ? BOUND_LOWER : + PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER, + depth, bestMove, ss->staticEval); + + assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); + + return bestValue; + } + + + // qsearch() is the quiescence search function, which is called by the main + // search function 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(NT == PV || NT == NonPV); + assert(InCheck == !!pos.checkers()); + assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE); + assert(PvNode || (alpha == beta - 1)); + assert(depth <= DEPTH_ZERO); + + StateInfo st; + const TTEntry* tte; + Key posKey; + Move ttMove, move, bestMove; + Value bestValue, value, ttValue, futilityValue, futilityBase, oldAlpha; + bool givesCheck, evasionPrunable; + Depth ttDepth; + + // To flag BOUND_EXACT a node with eval above alpha and no available moves + if (PvNode) + oldAlpha = alpha; + + 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()]; + + // 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); + ttMove = tte ? tte->move() : MOVE_NONE; + ttValue = tte ? value_from_tt(tte->value(),ss->ply) : VALUE_NONE; + + if ( tte + && tte->depth() >= ttDepth + && ttValue != VALUE_NONE // Only in case of TT access race + && ( PvNode ? tte->bound() == BOUND_EXACT + : ttValue >= beta ? (tte->bound() & BOUND_LOWER) + : (tte->bound() & BOUND_UPPER))) + { + ss->currentMove = ttMove; // Can be MOVE_NONE + return ttValue; + } + + // Evaluate the position statically + if (InCheck) + { + ss->staticEval = VALUE_NONE; + bestValue = futilityBase = -VALUE_INFINITE; + } + else + { + if (tte) + { + // Never assume anything on values stored in TT + if ((ss->staticEval = bestValue = tte->eval_value()) == 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 (!tte) + TT.store(pos.key(), value_to_tt(bestValue, ss->ply), BOUND_LOWER, + DEPTH_NONE, MOVE_NONE, ss->staticEval); + + 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, History, to_sq((ss-1)->currentMove)); + CheckInfo ci(pos); + + // 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 && !ci.dcCandidates + ? ci.checkSq[type_of(pos.piece_on(from_sq(move)))] & to_sq(move) + : pos.gives_check(move, ci); + + // Futility pruning + if ( !PvNode + && !InCheck + && !givesCheck + && move != ttMove + && 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 < beta) + { + bestValue = std::max(bestValue, futilityValue); + continue; + } + + if (futilityBase < beta && pos.see(move) <= VALUE_ZERO) + { + 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) + && !pos.can_castle(pos.side_to_move()); + + // Don't search moves with negative SEE values + if ( !PvNode + && (!InCheck || evasionPrunable) + && move != ttMove + && type_of(move) != PROMOTION + && pos.see_sign(move) < VALUE_ZERO) + continue; + + // Check for legality just before making the move + if (!pos.legal(move, ci.pinned)) + continue; + + ss->currentMove = move; + + // Make and search the move + pos.do_move(move, st, ci, 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 new best move + if (value > bestValue) + { + bestValue = value; + + if (value > alpha) + { + if (PvNode && value < beta) // Update alpha here! Always alpha < beta + { + alpha = value; + bestMove = move; + } + else // Fail high + { + TT.store(posKey, value_to_tt(value, ss->ply), BOUND_LOWER, + ttDepth, move, ss->staticEval); + + 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 + + TT.store(posKey, value_to_tt(bestValue, ss->ply), + PvNode && bestValue > oldAlpha ? BOUND_EXACT : BOUND_UPPER, + ttDepth, bestMove, ss->staticEval); + + assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); + + return bestValue; + } + + + // value_to_tt() adjusts a mate 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_stats() updates killers, history, countermoves and followupmoves stats after a fail-high + // of a quiet move. + + void update_stats(const Position& pos, Stack* ss, Move move, Depth depth, Move* quiets, int quietsCnt) { + + if (ss->killers[0] != move) + { + ss->killers[1] = ss->killers[0]; + ss->killers[0] = move; + } + + // Increase history value of the cut-off move and decrease all the other + // played quiet moves. + Value bonus = Value(int(depth) * int(depth)); + History.update(pos.moved_piece(move), to_sq(move), bonus); + for (int i = 0; i < quietsCnt; ++i) + { + Move m = quiets[i]; + History.update(pos.moved_piece(m), to_sq(m), -bonus); + } + + if (is_ok((ss-1)->currentMove)) + { + Square prevMoveSq = to_sq((ss-1)->currentMove); + Countermoves.update(pos.piece_on(prevMoveSq), prevMoveSq, move); + } + + if (is_ok((ss-2)->currentMove) && (ss-1)->currentMove == (ss-1)->ttMove) + { + Square prevOwnMoveSq = to_sq((ss-2)->currentMove); + Followupmoves.update(pos.piece_on(prevOwnMoveSq), prevOwnMoveSq, move); + } + } + + + // When playing with a strength handicap, choose best move among the first 'candidates' + // RootMoves using a statistical rule dependent on 'level'. Idea by Heinz van Saanen. + + Move Skill::pick_move() { + + static RKISS rk; + + // PRNG sequence should be not deterministic + for (int i = Time::now() % 50; i > 0; --i) + rk.rand(); + + // RootMoves are already sorted by score in descending order + int variance = std::min(RootMoves[0].score - RootMoves[candidates - 1].score, PawnValueMg); + int weakness = 120 - 2 * level; + int max_s = -VALUE_INFINITE; + best = MOVE_NONE; + + // Choose best move. For each move score we add two terms both dependent on + // weakness. One deterministic and bigger for weaker moves, and one random, + // then we choose the move with the resulting highest score. + for (size_t i = 0; i < candidates; ++i) + { + int s = RootMoves[i].score; + + // Don't allow crazy blunders even at very low skills + if (i > 0 && RootMoves[i - 1].score > s + 2 * PawnValueMg) + break; + + // This is our magic formula + s += ( weakness * int(RootMoves[0].score - s) + + variance * (rk.rand() % weakness)) / 128; + + if (s > max_s) + { + max_s = s; + best = RootMoves[i].pv[0]; + } + } + return best; + } + + + // 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, int depth, Value alpha, Value beta) { + + std::stringstream ss; + Time::point elapsed = Time::now() - SearchTime + 1; + size_t uciPVSize = std::min((size_t)Options["MultiPV"], RootMoves.size()); + int selDepth = 0; + + for (size_t i = 0; i < Threads.size(); ++i) + if (Threads[i]->maxPly > selDepth) + selDepth = Threads[i]->maxPly; + + for (size_t i = 0; i < uciPVSize; ++i) + { + bool updated = (i <= PVIdx); + + if (depth == 1 && !updated) + continue; + + int d = updated ? depth : depth - 1; + Value v = updated ? RootMoves[i].score : RootMoves[i].prevScore; + + if (ss.rdbuf()->in_avail()) // Not at first line + ss << "\n"; + + ss << "info depth " << d + << " seldepth " << selDepth + << " score " << (i == PVIdx ? score_to_uci(v, alpha, beta) : score_to_uci(v)) + << " nodes " << pos.nodes_searched() + << " nps " << pos.nodes_searched() * 1000 / elapsed + << " time " << elapsed + << " multipv " << i + 1 + << " pv"; + + for (size_t j = 0; RootMoves[i].pv[j] != MOVE_NONE; ++j) + ss << " " << move_to_uci(RootMoves[i].pv[j], pos.is_chess960()); + } + + return ss.str(); + } + +} // namespace + + +/// RootMove::extract_pv_from_tt() builds a PV by adding moves from the TT table. +/// We also consider both failing high nodes and BOUND_EXACT nodes here to +/// ensure that we have a ponder move even when we fail high at root. This +/// results in a long PV to print that is important for position analysis. + +void RootMove::extract_pv_from_tt(Position& pos) { + + StateInfo state[MAX_PLY_PLUS_6], *st = state; + const TTEntry* tte; + int ply = 1; // At root ply is 1... + Move m = pv[0]; // ...instead pv[] array starts from 0 + Value expectedScore = score; + + pv.clear(); + + do { + pv.push_back(m); + + assert(MoveList(pos).contains(pv[ply - 1])); + + pos.do_move(pv[ply++ - 1], *st++); + tte = TT.probe(pos.key()); + expectedScore = -expectedScore; + + } while ( tte + && expectedScore == value_from_tt(tte->value(), ply) + && pos.pseudo_legal(m = tte->move()) // Local copy, TT could change + && pos.legal(m, pos.pinned_pieces(pos.side_to_move())) + && ply < MAX_PLY + && (!pos.is_draw() || ply <= 2)); + + pv.push_back(MOVE_NONE); // Must be zero-terminating + + while (--ply) pos.undo_move(pv[ply - 1]); +} + + +/// RootMove::insert_pv_in_tt() is called at the end of a search iteration, and +/// inserts the PV back into the TT. This makes sure the old PV moves are searched +/// first, even if the old TT entries have been overwritten. + +void RootMove::insert_pv_in_tt(Position& pos) { + + StateInfo state[MAX_PLY_PLUS_6], *st = state; + const TTEntry* tte; + int idx = 0; // Ply starts from 1, we need to start from 0 + + do { + tte = TT.probe(pos.key()); + + if (!tte || tte->move() != pv[idx]) // Don't overwrite correct entries + TT.store(pos.key(), VALUE_NONE, BOUND_NONE, DEPTH_NONE, pv[idx], VALUE_NONE); + + assert(MoveList(pos).contains(pv[idx])); + + pos.do_move(pv[idx++], *st++); + + } while (pv[idx] != MOVE_NONE); + + while (idx) pos.undo_move(pv[--idx]); +} + + +/// Thread::idle_loop() is where the thread is parked when it has no work to do + +void Thread::idle_loop() { + + // Pointer 'this_sp' is not null only if we are called from split(), and not + // at the thread creation. This means we are the split point's master. + SplitPoint* this_sp = splitPointsSize ? activeSplitPoint : NULL; + + assert(!this_sp || (this_sp->masterThread == this && searching)); + + while (true) + { + // If we are not searching, wait for a condition to be signaled instead of + // wasting CPU time polling for work. + while (!searching || exit) + { + if (exit) + { + assert(!this_sp); + return; + } + + // Grab the lock to avoid races with Thread::notify_one() + mutex.lock(); + + // If we are master and all slaves have finished then exit idle_loop + if (this_sp && this_sp->slavesMask.none()) + { + mutex.unlock(); + break; + } + + // Do sleep after retesting sleep conditions under lock protection. In + // particular we need to avoid a deadlock in case a master thread has, + // in the meanwhile, allocated us and sent the notify_one() call before + // we had the chance to grab the lock. + if (!searching && !exit) + sleepCondition.wait(mutex); + + mutex.unlock(); + } + + // If this thread has been assigned work, launch a search + if (searching) + { + assert(!exit); + + Threads.mutex.lock(); + + assert(searching); + assert(activeSplitPoint); + SplitPoint* sp = activeSplitPoint; + + Threads.mutex.unlock(); + + Stack stack[MAX_PLY_PLUS_6], *ss = stack+2; // To allow referencing (ss-2) + Position pos(*sp->pos, this); + + std::memcpy(ss-2, sp->ss-2, 5 * sizeof(Stack)); + ss->splitPoint = sp; + + sp->mutex.lock(); + + assert(activePosition == NULL); + + activePosition = &pos; + + if (sp->nodeType == NonPV) + search(pos, ss, sp->alpha, sp->beta, sp->depth, sp->cutNode); + + else if (sp->nodeType == PV) + search(pos, ss, sp->alpha, sp->beta, sp->depth, sp->cutNode); + + else if (sp->nodeType == Root) + search(pos, ss, sp->alpha, sp->beta, sp->depth, sp->cutNode); + + else + assert(false); + + assert(searching); + + searching = false; + activePosition = NULL; + sp->slavesMask.reset(idx); + sp->allSlavesSearching = false; + sp->nodes += pos.nodes_searched(); + + // Wake up the master thread so to allow it to return from the idle + // loop in case we are the last slave of the split point. + if ( this != sp->masterThread + && sp->slavesMask.none()) + { + assert(!sp->masterThread->searching); + sp->masterThread->notify_one(); + } + + // After releasing the lock we can't access any SplitPoint related data + // in a safe way because it could have been released under our feet by + // the sp master. + sp->mutex.unlock(); + + // Try to late join to another split point if none of its slaves has + // already finished. + if (Threads.size() > 2) + for (size_t i = 0; i < Threads.size(); ++i) + { + const int size = Threads[i]->splitPointsSize; // Local copy + sp = size ? &Threads[i]->splitPoints[size - 1] : NULL; + + if ( sp + && sp->allSlavesSearching + && available_to(Threads[i])) + { + // Recheck the conditions under lock protection + Threads.mutex.lock(); + sp->mutex.lock(); + + if ( sp->allSlavesSearching + && available_to(Threads[i])) + { + sp->slavesMask.set(idx); + activeSplitPoint = sp; + searching = true; + } + + sp->mutex.unlock(); + Threads.mutex.unlock(); + + break; // Just a single attempt + } + } + } + + // If this thread is the master of a split point and all slaves have finished + // their work at this split point, return from the idle loop. + if (this_sp && this_sp->slavesMask.none()) + { + this_sp->mutex.lock(); + bool finished = this_sp->slavesMask.none(); // Retest under lock protection + this_sp->mutex.unlock(); + if (finished) + return; + } + } +} + + +/// check_time() is called by the timer thread when the timer triggers. It 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 Time::point lastInfoTime = Time::now(); + int64_t nodes = 0; // Workaround silly 'uninitialized' gcc warning + + if (Time::now() - lastInfoTime >= 1000) + { + lastInfoTime = Time::now(); + dbg_print(); + } + + if (Limits.ponder) + return; + + if (Limits.nodes) + { + Threads.mutex.lock(); + + nodes = RootPos.nodes_searched(); + + // Loop across all split points and sum accumulated SplitPoint nodes plus + // all the currently active positions nodes. + for (size_t i = 0; i < Threads.size(); ++i) + for (int j = 0; j < Threads[i]->splitPointsSize; ++j) + { + SplitPoint& sp = Threads[i]->splitPoints[j]; + + sp.mutex.lock(); + + nodes += sp.nodes; + + for (size_t idx = 0; idx < Threads.size(); ++idx) + if (sp.slavesMask.test(idx) && Threads[idx]->activePosition) + nodes += Threads[idx]->activePosition->nodes_searched(); + + sp.mutex.unlock(); + } + + Threads.mutex.unlock(); + } + + Time::point elapsed = Time::now() - SearchTime; + bool stillAtFirstMove = Signals.firstRootMove + && !Signals.failedLowAtRoot + && elapsed > TimeMgr.available_time() * 75 / 100; + + bool noMoreTime = elapsed > TimeMgr.maximum_time() - 2 * TimerThread::Resolution + || stillAtFirstMove; + + if ( (Limits.use_time_management() && noMoreTime) + || (Limits.movetime && elapsed >= Limits.movetime) + || (Limits.nodes && nodes >= Limits.nodes)) + Signals.stop = true; +} diff --git a/src/search.h b/src/search.h new file mode 100644 index 0000000..4fe5a5b --- /dev/null +++ b/src/search.h @@ -0,0 +1,113 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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 + +#include "misc.h" +#include "position.h" +#include "types.h" + +struct SplitPoint; + +namespace Search { + +/// The 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 { + SplitPoint* splitPoint; + int ply; + Move currentMove; + Move ttMove; + Move excludedMove; + Move killers[2]; + Depth reduction; + Value staticEval; + bool skipNullMove; +}; + + +/// RootMove struct is used for moves at the root of the tree. For each root +/// move we store a score, a node count, 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 { + + RootMove(Move m) : score(-VALUE_INFINITE), prevScore(-VALUE_INFINITE) { + pv.push_back(m); pv.push_back(MOVE_NONE); + } + + bool operator<(const RootMove& m) const { return score > m.score; } // Ascending sort + bool operator==(const Move& m) const { return pv[0] == m; } + + void extract_pv_from_tt(Position& pos); + void insert_pv_in_tt(Position& pos); + + Value score; + Value prevScore; + std::vector pv; +}; + + +/// The 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() { // Using memset on a std::vector is undefined behavior + time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = movestogo = + depth = nodes = 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], movestogo, depth, nodes, movetime, mate, infinite, ponder; +}; + + +/// The SignalsType struct stores volatile flags updated during the search +/// typically in an async fashion e.g. to stop the search by the GUI. + +struct SignalsType { + bool stop, stopOnPonderhit, firstRootMove, failedLowAtRoot; +}; + +typedef std::auto_ptr > StateStackPtr; + +extern volatile SignalsType Signals; +extern LimitsType Limits; +extern std::vector RootMoves; +extern Position RootPos; +extern Time::point SearchTime; +extern StateStackPtr SetupStates; + +extern void init(); +extern void think(); +template uint64_t perft(Position& pos, Depth depth); + +} // namespace Search + +#endif // #ifndef SEARCH_H_INCLUDED diff --git a/src/thread.cpp b/src/thread.cpp new file mode 100644 index 0000000..3b98ac6 --- /dev/null +++ b/src/thread.cpp @@ -0,0 +1,379 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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 "ucioption.h" + +using namespace Search; + +ThreadPool Threads; // Global object + +extern void check_time(); + +namespace { + + // start_routine() is the C function which is called when a new thread + // is launched. It is a wrapper to the virtual function idle_loop(). + + extern "C" { long start_routine(ThreadBase* th) { th->idle_loop(); return 0; } } + + + // Helpers to launch a thread after creation and joining before delete. Must be + // outside Thread c'tor and d'tor because the object will be fully initialized + // when start_routine (and hence virtual idle_loop) is called and when joining. + + template T* new_thread() { + T* th = new T(); + thread_create(th->handle, start_routine, th); // Will go to sleep + return th; + } + + void delete_thread(ThreadBase* th) { + th->exit = true; // Search must be already finished + th->notify_one(); + thread_join(th->handle); // Wait for thread termination + delete th; + } + +} + + +// notify_one() wakes up the thread when there is some work to do + +void ThreadBase::notify_one() { + + mutex.lock(); + sleepCondition.notify_one(); + mutex.unlock(); +} + + +// wait_for() set the thread to sleep until condition 'b' turns true + +void ThreadBase::wait_for(volatile const bool& b) { + + mutex.lock(); + while (!b) sleepCondition.wait(mutex); + mutex.unlock(); +} + + +// Thread c'tor just inits data and does not launch any execution thread. +// Such a thread will only be started when c'tor returns. + +Thread::Thread() /* : splitPoints() */ { // Value-initialization bug in MSVC + + searching = false; + maxPly = splitPointsSize = 0; + activeSplitPoint = NULL; + activePosition = NULL; + idx = Threads.size(); // Starts from 0 +} + + +// cutoff_occurred() checks whether a beta cutoff has occurred in the +// current active split point, or in some ancestor of the split point. + +bool Thread::cutoff_occurred() const { + + for (SplitPoint* sp = activeSplitPoint; sp; sp = sp->parentSplitPoint) + if (sp->cutoff) + return true; + + return false; +} + + +// Thread::available_to() checks whether the thread is available to help the +// thread 'master' at a split point. An obvious requirement is that thread must +// be idle. With more than two threads, this is not sufficient: If the thread is +// the master of some split point, it is only available as a slave to the slaves +// which are busy searching the split point at the top of slave's split point +// stack (the "helpful master concept" in YBWC terminology). + +bool Thread::available_to(const Thread* master) const { + + if (searching) + return false; + + // Make a local copy to be sure it doesn't become zero under our feet while + // testing next condition and so leading to an out of bounds access. + const int size = splitPointsSize; + + // No split points means that the thread is available as a slave for any + // other thread otherwise apply the "helpful master" concept if possible. + return !size || splitPoints[size - 1].slavesMask.test(master->idx); +} + + +// TimerThread::idle_loop() is where the timer thread waits msec milliseconds +// and then calls check_time(). If msec is 0 thread sleeps until it's woken up. + +void TimerThread::idle_loop() { + + while (!exit) + { + mutex.lock(); + + if (!exit) + sleepCondition.wait_for(mutex, run ? Resolution : INT_MAX); + + mutex.unlock(); + + if (run) + check_time(); + } +} + + +// MainThread::idle_loop() is where the main thread is parked waiting to be started +// when there is a new search. The main thread will launch all the slave threads. + +void MainThread::idle_loop() { + + while (true) + { + mutex.lock(); + + thinking = false; + + while (!thinking && !exit) + { + Threads.sleepCondition.notify_one(); // Wake up the UI thread if needed + sleepCondition.wait(mutex); + } + + mutex.unlock(); + + if (exit) + return; + + searching = true; + + Search::think(); + + assert(searching); + + searching = false; + } +} + + +// init() is called at startup to create and launch requested threads, that will +// go immediately to sleep. We cannot use a c'tor because Threads is a static +// object and we need a fully initialized engine at this point due to allocation +// of Endgames in Thread c'tor. + +void ThreadPool::init() { + + timer = new_thread(); + push_back(new_thread()); + read_uci_options(); +} + + +// exit() cleanly terminates the threads before the program exits. Cannot be done in +// d'tor because we have to terminate the threads before to free ThreadPool object. + +void ThreadPool::exit() { + + delete_thread(timer); // As first because check_time() accesses threads data + + for (iterator it = begin(); it != end(); ++it) + delete_thread(*it); +} + + +// read_uci_options() updates internal threads parameters from the corresponding +// UCI options and creates/destroys threads to match the requested number. Thread +// objects are dynamically allocated to avoid creating all possible threads +// in advance (which include pawns and material tables), even if only a few +// are to be used. + +void ThreadPool::read_uci_options() { + + minimumSplitDepth = Options["Min Split Depth"] * ONE_PLY; + size_t requested = Options["Threads"]; + + assert(requested > 0); + + // If zero (default) then set best minimum split depth automatically + if (!minimumSplitDepth) + minimumSplitDepth = requested < 8 ? 4 * ONE_PLY : 7 * ONE_PLY; + + while (size() < requested) + push_back(new_thread()); + + while (size() > requested) + { + delete_thread(back()); + pop_back(); + } +} + + +// available_slave() tries to find an idle thread which is available as a slave +// for the thread 'master'. + +Thread* ThreadPool::available_slave(const Thread* master) const { + + for (const_iterator it = begin(); it != end(); ++it) + if ((*it)->available_to(master)) + return *it; + + return NULL; +} + + +// split() does the actual work of distributing the work at a node between +// several available threads. If it does not succeed in splitting the node +// (because no idle threads are available), the function immediately returns. +// If splitting is possible, a SplitPoint object is initialized with all the +// data that must be copied to the helper threads and then helper threads are +// told that they have been assigned work. This will cause them to instantly +// leave their idle loops and call search(). When all threads have returned from +// search() then split() returns. + +void Thread::split(Position& pos, const Stack* ss, Value alpha, Value beta, Value* bestValue, + Move* bestMove, Depth depth, int moveCount, + MovePicker* movePicker, int nodeType, bool cutNode) { + + assert(pos.pos_is_ok()); + assert(-VALUE_INFINITE < *bestValue && *bestValue <= alpha && alpha < beta && beta <= VALUE_INFINITE); + assert(depth >= Threads.minimumSplitDepth); + assert(searching); + assert(splitPointsSize < MAX_SPLITPOINTS_PER_THREAD); + + // Pick the next available split point from the split point stack + SplitPoint& sp = splitPoints[splitPointsSize]; + + sp.masterThread = this; + sp.parentSplitPoint = activeSplitPoint; + sp.slavesMask = 0, sp.slavesMask.set(idx); + sp.depth = depth; + sp.bestValue = *bestValue; + sp.bestMove = *bestMove; + sp.alpha = alpha; + sp.beta = beta; + sp.nodeType = nodeType; + sp.cutNode = cutNode; + sp.movePicker = movePicker; + sp.moveCount = moveCount; + sp.pos = &pos; + sp.nodes = 0; + sp.cutoff = false; + sp.ss = ss; + + // Try to allocate available threads and ask them to start searching setting + // 'searching' flag. This must be done under lock protection to avoid concurrent + // allocation of the same slave by another master. + Threads.mutex.lock(); + sp.mutex.lock(); + + sp.allSlavesSearching = true; // Must be set under lock protection + ++splitPointsSize; + activeSplitPoint = &sp; + activePosition = NULL; + + for (Thread* slave; (slave = Threads.available_slave(this)) != NULL; ) + { + sp.slavesMask.set(slave->idx); + slave->activeSplitPoint = &sp; + slave->searching = true; // Slave leaves idle_loop() + slave->notify_one(); // Could be sleeping + } + + // Everything is set up. The master thread enters the idle loop, from which + // it will instantly launch a search, because its 'searching' flag is set. + // The thread will return from the idle loop when all slaves have finished + // their work at this split point. + sp.mutex.unlock(); + Threads.mutex.unlock(); + + Thread::idle_loop(); // Force a call to base class idle_loop() + + // In the helpful master concept, a master can help only a sub-tree of its + // split point and because everything is finished here, it's not possible + // for the master to be booked. + assert(!searching); + assert(!activePosition); + + // We have returned from the idle loop, which means that all threads are + // finished. Note that setting 'searching' and decreasing splitPointsSize is + // done under lock protection to avoid a race with Thread::available_to(). + Threads.mutex.lock(); + sp.mutex.lock(); + + searching = true; + --splitPointsSize; + activeSplitPoint = sp.parentSplitPoint; + activePosition = &pos; + pos.set_nodes_searched(pos.nodes_searched() + sp.nodes); + *bestMove = sp.bestMove; + *bestValue = sp.bestValue; + + sp.mutex.unlock(); + Threads.mutex.unlock(); +} + +// wait_for_think_finished() waits for main thread to go to sleep then returns + +void ThreadPool::wait_for_think_finished() { + + MainThread* t = main(); + t->mutex.lock(); + while (t->thinking) sleepCondition.wait(t->mutex); + t->mutex.unlock(); +} + + +// start_thinking() wakes up the main thread sleeping in MainThread::idle_loop() +// so to start a new search, then returns immediately. + +void ThreadPool::start_thinking(const Position& pos, const LimitsType& limits, StateStackPtr& states) { + + wait_for_think_finished(); + + SearchTime = Time::now(); // As early as possible + + Signals.stopOnPonderhit = Signals.firstRootMove = false; + Signals.stop = Signals.failedLowAtRoot = false; + + RootMoves.clear(); + RootPos = pos; + Limits = limits; + if (states.get()) // If we don't set a new position, preserve current state + { + SetupStates = states; // Ownership transfer here + assert(!states.get()); + } + + for (MoveList it(pos); *it; ++it) + if ( limits.searchmoves.empty() + || std::count(limits.searchmoves.begin(), limits.searchmoves.end(), *it)) + RootMoves.push_back(RootMove(*it)); + + main()->thinking = true; + main()->notify_one(); // Starts main thread +} diff --git a/src/thread.h b/src/thread.h new file mode 100644 index 0000000..26aed39 --- /dev/null +++ b/src/thread.h @@ -0,0 +1,176 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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 "material.h" +#include "movepick.h" +#include "pawns.h" +#include "position.h" +#include "search.h" + +const int MAX_THREADS = 128; +const int MAX_SPLITPOINTS_PER_THREAD = 8; + +struct Mutex { + Mutex() { lock_init(l); } + ~Mutex() { lock_destroy(l); } + + void lock() { lock_grab(l); } + void unlock() { lock_release(l); } + +private: + friend struct ConditionVariable; + + Lock l; +}; + +struct ConditionVariable { + ConditionVariable() { cond_init(c); } + ~ConditionVariable() { cond_destroy(c); } + + void wait(Mutex& m) { cond_wait(c, m.l); } + void wait_for(Mutex& m, int ms) { timed_wait(c, m.l, ms); } + void notify_one() { cond_signal(c); } + +private: + WaitCondition c; +}; + +struct Thread; + +struct SplitPoint { + + // Const data after split point has been setup + const Position* pos; + const Search::Stack* ss; + Thread* masterThread; + Depth depth; + Value beta; + int nodeType; + bool cutNode; + + // Const pointers to shared data + MovePicker* movePicker; + SplitPoint* parentSplitPoint; + + // Shared data + Mutex mutex; + std::bitset slavesMask; + volatile bool allSlavesSearching; + volatile uint64_t nodes; + volatile Value alpha; + volatile Value bestValue; + volatile Move bestMove; + volatile int moveCount; + volatile bool cutoff; +}; + + +/// ThreadBase struct is the base of the hierarchy from where we derive all the +/// specialized thread classes. + +struct ThreadBase { + + ThreadBase() : handle(NativeHandle()), exit(false) {} + virtual ~ThreadBase() {} + virtual void idle_loop() = 0; + void notify_one(); + void wait_for(volatile const bool& b); + + Mutex mutex; + ConditionVariable sleepCondition; + NativeHandle handle; + volatile bool exit; +}; + + +/// Thread struct keeps together all the thread related stuff like locks, state +/// and especially split points. 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. + +struct Thread : public ThreadBase { + + Thread(); + virtual void idle_loop(); + bool cutoff_occurred() const; + bool available_to(const Thread* master) const; + + void split(Position& pos, const Search::Stack* ss, Value alpha, Value beta, Value* bestValue, Move* bestMove, + Depth depth, int moveCount, MovePicker* movePicker, int nodeType, bool cutNode); + + SplitPoint splitPoints[MAX_SPLITPOINTS_PER_THREAD]; + Material::Table materialTable; + Endgames endgames; + Pawns::Table pawnsTable; + Position* activePosition; + size_t idx; + int maxPly; + SplitPoint* volatile activeSplitPoint; + volatile int splitPointsSize; + volatile bool searching; +}; + + +/// MainThread and TimerThread are derived classes used to characterize the two +/// special threads: the main one and the recurring timer. + +struct MainThread : public Thread { + MainThread() : thinking(true) {} // Avoid a race with start_thinking() + virtual void idle_loop(); + volatile bool thinking; +}; + +struct TimerThread : public ThreadBase { + TimerThread() : run(false) {} + virtual void idle_loop(); + bool run; + static const int Resolution = 5; // msec between two check_time() calls +}; + + +/// ThreadPool struct handles all the threads related stuff like init, starting, +/// parking and, most importantly, launching a slave thread at a split point. +/// All the access to shared thread data is done through this class. + +struct ThreadPool : public std::vector { + + void init(); // No c'tor and d'tor, threads rely on globals that should + void exit(); // be initialized and are valid during the whole thread lifetime. + + MainThread* main() { return static_cast((*this)[0]); } + void read_uci_options(); + Thread* available_slave(const Thread* master) const; + void wait_for_think_finished(); + void start_thinking(const Position&, const Search::LimitsType&, Search::StateStackPtr&); + + Depth minimumSplitDepth; + Mutex mutex; + ConditionVariable sleepCondition; + TimerThread* timer; +}; + +extern ThreadPool Threads; + +#endif // #ifndef THREAD_H_INCLUDED diff --git a/src/timeman.cpp b/src/timeman.cpp new file mode 100644 index 0000000..c305e61 --- /dev/null +++ b/src/timeman.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-2014 Marco Costalba, Joona Kiiski, 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 "ucioption.h" + +namespace { + + enum TimeType { OptimumTime, MaxTime }; + + const int MoveHorizon = 50; // Plan time management at most this many moves ahead + const double MaxRatio = 7.0; // When in trouble, we can step over reserved time with this ratio + const double StealRatio = 0.33; // However we must not steal time from remaining moves over this ratio + + const double xscale = 9.3; + const double xshift = 59.8; + const double skewfactor = 0.172; + + + // 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 CCRL game database with some simple filtering criteria. + + double move_importance(int ply) { + + return pow((1 + exp((ply - xshift) / xscale)), -skewfactor) + DBL_MIN; // Ensure non-zero + } + + template + int remaining(int myTime, int movesToGo, int currentPly, int slowMover) + { + const double TMaxRatio = (T == OptimumTime ? 1 : MaxRatio); + const double TStealRatio = (T == OptimumTime ? 0 : StealRatio); + + double thisMoveImportance = (move_importance(currentPly) * slowMover) / 100; + double otherMovesImportance = 0; + + for (int i = 1; i < movesToGo; ++i) + otherMovesImportance += move_importance(currentPly + 2 * i); + + double ratio1 = (TMaxRatio * thisMoveImportance) / (TMaxRatio * thisMoveImportance + otherMovesImportance); + double ratio2 = (thisMoveImportance + TStealRatio * otherMovesImportance) / (thisMoveImportance + otherMovesImportance); + + return int(myTime * std::min(ratio1, ratio2)); + } + +} // namespace + + +void TimeManager::init(const Search::LimitsType& limits, int currentPly, Color us) +{ + /* We support four different kinds of time controls: + + increment == 0 && movesToGo == 0 means: x basetime [sudden death!] + increment == 0 && movesToGo != 0 means: x moves in y minutes + increment > 0 && movesToGo == 0 means: x basetime + z increment + increment > 0 && movesToGo != 0 means: x moves in y minutes + z increment + + Time management is adjusted by following UCI parameters: + + emergencyMoveHorizon: Be prepared to always play at least this many moves + emergencyBaseTime : Always attempt to keep at least this much time (in ms) at clock + emergencyMoveTime : Plus attempt to keep at least this much time for each remaining emergency move + minThinkingTime : No matter what, use at least this much thinking before doing the move + */ + + int hypMTG, hypMyTime, t1, t2; + + // Read uci parameters + int emergencyMoveHorizon = Options["Emergency Move Horizon"]; + int emergencyBaseTime = Options["Emergency Base Time"]; + int emergencyMoveTime = Options["Emergency Move Time"]; + int minThinkingTime = Options["Minimum Thinking Time"]; + int slowMover = Options["Slow Mover"]; + + // Initialize unstablePvFactor to 1 and search times to maximum values + unstablePvFactor = 1; + optimumSearchTime = maximumSearchTime = std::max(limits.time[us], minThinkingTime); + + // 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 (hypMTG = 1; hypMTG <= (limits.movestogo ? std::min(limits.movestogo, MoveHorizon) : MoveHorizon); ++hypMTG) + { + // Calculate thinking time for hypothetical "moves to go"-value + hypMyTime = limits.time[us] + + limits.inc[us] * (hypMTG - 1) + - emergencyBaseTime + - emergencyMoveTime * std::min(hypMTG, emergencyMoveHorizon); + + hypMyTime = std::max(hypMyTime, 0); + + t1 = minThinkingTime + remaining(hypMyTime, hypMTG, currentPly, slowMover); + t2 = minThinkingTime + remaining(hypMyTime, hypMTG, currentPly, slowMover); + + optimumSearchTime = std::min(optimumSearchTime, t1); + maximumSearchTime = std::min(maximumSearchTime, t2); + } + + if (Options["Ponder"]) + optimumSearchTime += optimumSearchTime / 4; + + // Make sure that maxSearchTime is not over absoluteMaxSearchTime + optimumSearchTime = std::min(optimumSearchTime, maximumSearchTime); +} diff --git a/src/timeman.h b/src/timeman.h new file mode 100644 index 0000000..a15551a --- /dev/null +++ b/src/timeman.h @@ -0,0 +1,39 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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 + +/// The TimeManager class computes the optimal time to think depending on the +/// maximum available time, the game move number and other parameters. + +class TimeManager { +public: + void init(const Search::LimitsType& limits, int currentPly, Color us); + void pv_instability(double bestMoveChanges) { unstablePvFactor = 1 + bestMoveChanges; } + int available_time() const { return int(optimumSearchTime * unstablePvFactor * 0.71); } + int maximum_time() const { return maximumSearchTime; } + +private: + int optimumSearchTime; + int maximumSearchTime; + double unstablePvFactor; +}; + +#endif // #ifndef TIMEMAN_H_INCLUDED diff --git a/src/tt.cpp b/src/tt.cpp new file mode 100644 index 0000000..248cbad --- /dev/null +++ b/src/tt.cpp @@ -0,0 +1,122 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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 "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 TTClusterSize number of TTEntry. + +void TranspositionTable::resize(uint64_t mbSize) { + + assert(msb((mbSize * 1024 * 1024) / sizeof(TTCluster)) < 32); + + uint32_t newClusterCount = 1 << msb((mbSize * 1024 * 1024) / sizeof(TTCluster)); + + if (newClusterCount == clusterCount) + return; + + clusterCount = newClusterCount; + + free(mem); + mem = calloc(clusterCount * sizeof(TTCluster) + CACHE_LINE_SIZE - 1, 1); + + if (!mem) + { + std::cerr << "Failed to allocate " << mbSize + << "MB for transposition table." << std::endl; + exit(EXIT_FAILURE); + } + + table = (TTCluster*)((uintptr_t(mem) + CACHE_LINE_SIZE - 1) & ~(CACHE_LINE_SIZE - 1)); +} + + +/// TranspositionTable::clear() overwrites the entire transposition table +/// with zeroes. 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(TTCluster)); +} + + +/// TranspositionTable::probe() looks up the current position in the +/// transposition table. Returns a pointer to the TTEntry or NULL if +/// position is not found. + +const TTEntry* TranspositionTable::probe(const Key key) const { + + TTEntry* tte = first_entry(key); + uint16_t key16 = key >> 48; + + for (unsigned i = 0; i < TTClusterSize; ++i, ++tte) + if (tte->key16 == key16) + { + tte->genBound8 = generation | tte->bound(); // Refresh + return tte; + } + + return NULL; +} + + +/// TranspositionTable::store() writes a new entry containing position key and +/// valuable information of current position. The lowest order bits of position +/// key are used to decide in which cluster the position will be placed. +/// When a new entry is written and there are no empty entries available in the +/// cluster, it replaces the least valuable of the entries. A TTEntry t1 is considered +/// to be more valuable than a TTEntry t2 if t1 is from the current search and t2 +/// is from a previous search, or if the depth of t1 is bigger than the depth of t2. + +void TranspositionTable::store(const Key key, Value v, Bound b, Depth d, Move m, Value statV) { + + TTEntry *tte, *replace; + uint16_t key16 = key >> 48; // Use the high 16 bits as key inside the cluster + + tte = replace = first_entry(key); + + for (unsigned i = 0; i < TTClusterSize; ++i, ++tte) + { + if (!tte->key16 || tte->key16 == key16) // Empty or overwrite old + { + if (!m) + m = tte->move(); // Preserve any existing ttMove + + replace = tte; + break; + } + + // Implement replace strategy + if ( (( tte->genBound8 & 0xFC) == generation || tte->bound() == BOUND_EXACT) + - ((replace->genBound8 & 0xFC) == generation) + - (tte->depth8 < replace->depth8) < 0) + replace = tte; + } + + replace->save(key16, v, b, d, m, generation, statV); +} diff --git a/src/tt.h b/src/tt.h new file mode 100644 index 0000000..16523b3 --- /dev/null +++ b/src/tt.h @@ -0,0 +1,114 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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" + +/// The TTEntry 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_value() const { return (Value)evalValue; } + Depth depth() const { return (Depth)(depth8) + DEPTH_NONE; } + Bound bound() const { return (Bound)(genBound8 & 0x3); } + +private: + friend class TranspositionTable; + + void save(uint16_t k, Value v, Bound b, Depth d, Move m, uint8_t g, Value ev) { + + key16 = (uint16_t)k; + move16 = (uint16_t)m; + value16 = (int16_t)v; + evalValue = (int16_t)ev; + genBound8 = (uint8_t)(g | b); + depth8 = (uint8_t)(d - DEPTH_NONE); + } + + uint16_t key16; + uint16_t move16; + int16_t value16; + int16_t evalValue; + uint8_t genBound8; + uint8_t depth8; +}; + +/// TTCluster is a 32 bytes cluster of TT entries consisting of: +/// +/// 3 x TTEntry (3 x 10 bytes) +/// padding (2 bytes) + +const unsigned TTClusterSize = 3; + +struct TTCluster { + TTEntry entry[TTClusterSize]; + char padding[2]; +}; + +/// A TranspositionTable consists of a power of 2 number of clusters and each +/// cluster consists of TTClusterSize number of TTEntry. Each non-empty entry +/// contains information of exactly one position. The size of a cluster should +/// not be bigger than a cache line size. In case it is less, it should be padded +/// to guarantee always aligned accesses. + +class TranspositionTable { + +public: + ~TranspositionTable() { free(mem); } + void new_search() { generation += 4; } // Lower 2 bits are used by Bound + + const TTEntry* probe(const Key key) const; + TTEntry* first_entry(const Key key) const; + void resize(uint64_t mbSize); + void clear(); + void store(const Key key, Value v, Bound type, Depth d, Move m, Value statV); + +private: + uint32_t clusterCount; + TTCluster* table; + void* mem; + uint8_t generation; // Size must be not bigger than TTEntry::genBound8 +}; + +extern TranspositionTable TT; + + +/// TranspositionTable::first_entry() returns a pointer to the first entry of +/// a cluster given a position. The lowest order bits of the key are used to +/// get the index of the cluster inside the table. + +inline TTEntry* TranspositionTable::first_entry(const Key key) const { + + return &table[(uint32_t)key & (clusterCount - 1)].entry[0]; +} + +#endif // #ifndef TT_H_INCLUDED diff --git a/src/types.h b/src/types.h new file mode 100644 index 0000000..f921f9e --- /dev/null +++ b/src/types.h @@ -0,0 +1,441 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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 + +/// For Linux and OSX configuration is done automatically using Makefile. To get +/// started type 'make help'. +/// +/// For Windows, part of the configuration is detected automatically, but some +/// switches need to be set manually: +/// +/// -DNDEBUG | Disable debugging mode. Always use this. +/// +/// -DNO_PREFETCH | Disable use of prefetch asm-instruction. A must if you want +/// | the executable to run on some very old machines. +/// +/// -DUSE_POPCNT | Add runtime support for use of popcnt asm-instruction. Works +/// | only in 64-bit mode. For compiling requires hardware with +/// | popcnt support. + +#include +#include +#include +#include + +#include "platform.h" + +#define unlikely(x) (x) // For code annotation purposes + +#if defined(_WIN64) && !defined(IS_64BIT) +# include // MSVC popcnt and bsfq instrinsics +# define IS_64BIT +# define USE_BSFQ +#endif + +#if defined(USE_POPCNT) && defined(_MSC_VER) && defined(__INTEL_COMPILER) +# include // Intel header for _mm_popcnt_u64() intrinsic +#endif + +#if defined(USE_PEXT) +# include // Header for _pext_u64() intrinsic +#else +# define _pext_u64(b, m) (0) +#endif + +# if !defined(NO_PREFETCH) && (defined(__INTEL_COMPILER) || defined(_MSC_VER)) +# include // Intel and Microsoft header for _mm_prefetch() +# endif + +#define CACHE_LINE_SIZE 64 +#if defined(_MSC_VER) || defined(__INTEL_COMPILER) +# define CACHE_LINE_ALIGNMENT __declspec(align(CACHE_LINE_SIZE)) +#else +# define CACHE_LINE_ALIGNMENT __attribute__ ((aligned(CACHE_LINE_SIZE))) +#endif + +#ifdef _MSC_VER +# define FORCE_INLINE __forceinline +#elif defined(__GNUC__) +# define FORCE_INLINE inline __attribute__((always_inline)) +#else +# define FORCE_INLINE inline +#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 = 120; +const int MAX_PLY_PLUS_6 = MAX_PLY + 6; + +/// 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 { + 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 { + 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 - MAX_PLY, + VALUE_MATED_IN_MAX_PLY = -VALUE_MATE + MAX_PLY, + + VALUE_ENSURE_INTEGER_SIZE_P = INT_MAX, + VALUE_ENSURE_INTEGER_SIZE_N = INT_MIN, + + PawnValueMg = 198, PawnValueEg = 258, + KnightValueMg = 817, KnightValueEg = 846, + BishopValueMg = 836, BishopValueEg = 857, + RookValueMg = 1270, RookValueEg = 1278, + QueenValueMg = 2521, QueenValueEg = 2558, + + MidgameLimit = 15581, EndgameLimit = 3998 +}; + +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 +}; + +enum Depth { + + ONE_PLY = 2, + + 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 +}; + +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, + + DELTA_N = 8, + DELTA_E = 1, + DELTA_S = -8, + DELTA_W = -1, + + DELTA_NN = DELTA_N + DELTA_N, + DELTA_NE = DELTA_N + DELTA_E, + DELTA_SE = DELTA_S + DELTA_E, + DELTA_SS = DELTA_S + DELTA_S, + DELTA_SW = DELTA_S + DELTA_W, + DELTA_NW = DELTA_N + DELTA_W +}; + +enum File { + FILE_A, FILE_B, FILE_C, FILE_D, FILE_E, FILE_F, FILE_G, FILE_H, FILE_NB +}; + +enum Rank { + RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, RANK_NB +}; + + +/// The 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. The compiler +/// is free to choose the enum type as long as it can store the data, so we +/// ensure that Score is an integer type by assigning some big int values. +enum Score { + SCORE_ZERO, + SCORE_ENSURE_INTEGER_SIZE_P = INT_MAX, + SCORE_ENSURE_INTEGER_SIZE_N = INT_MIN +}; + +inline Score make_score(int mg, int eg) { return Score((mg << 16) + eg); } + +/// 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 mg_value(Score s) { + return Value(((s + 0x8000) & ~0xffff) / 0x10000); +} + +inline Value eg_value(Score s) { + return Value((int)(unsigned(s) & 0x7FFFU) - (int)(unsigned(s) & 0x8000U)); +} + +#define ENABLE_BASE_OPERATORS_ON(T) \ +inline T operator+(const T d1, const T d2) { return T(int(d1) + int(d2)); } \ +inline T operator-(const T d1, const T d2) { return T(int(d1) - int(d2)); } \ +inline T operator*(int i, const T d) { return T(i * int(d)); } \ +inline T operator*(const T d, int i) { return T(int(d) * i); } \ +inline T operator-(const T d) { return T(-int(d)); } \ +inline T& operator+=(T& d1, const T d2) { return d1 = d1 + d2; } \ +inline T& operator-=(T& d1, const T d2) { return d1 = d1 - d2; } \ +inline T& operator*=(T& d, int i) { return d = T(int(d) * i); } + +ENABLE_BASE_OPERATORS_ON(Score) + +#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/(const T d, int i) { return T(int(d) / i); } \ +inline T& operator/=(T& d, int i) { return d = T(int(d) / i); } + +ENABLE_FULL_OPERATORS_ON(Value) +ENABLE_FULL_OPERATORS_ON(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) + +#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); +} + +CACHE_LINE_ALIGNMENT + +extern Value PieceValue[PHASE_NB][PIECE_NB]; + +struct ExtMove { + Move move; + Value value; +}; + +inline bool operator<(const ExtMove& f, const ExtMove& s) { + return f.value < s.value; +} + +inline Color operator~(Color c) { + return Color(c ^ BLACK); +} + +inline Square operator~(Square s) { + return Square(s ^ SQ_A8); // Vertical flip SQ_A1 -> SQ_A8 +} + +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 ? DELTA_N : DELTA_S; +} + +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) + 2); +} + +inline Move make_move(Square from, Square to) { + return Move(to | (from << 6)); +} + +template +inline Move make(Square from, Square to, PieceType pt = KNIGHT) { + return Move(to | (from << 6) | T | ((pt - KNIGHT) << 12)); +} + +inline bool is_ok(Move m) { + return from_sq(m) != to_sq(m); // Catches also 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..6027295 --- /dev/null +++ b/src/uci.cpp @@ -0,0 +1,215 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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 "evaluate.h" +#include "notation.h" +#include "position.h" +#include "search.h" +#include "thread.h" +#include "tt.h" +#include "ucioption.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"; + + // Keep a track of the position keys along the setup moves (from the start position + // to the position just before the search starts). This is needed by the repetition + // draw detection code. + Search::StateStackPtr SetupStates; + + + // 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; + + pos.set(fen, Options["UCI_Chess960"], Threads.main()); + SetupStates = Search::StateStackPtr(new std::stack()); + + // Parse move list (if any) + while (is >> token && (m = move_from_uci(pos, token)) != MOVE_NONE) + { + SetupStates->push(StateInfo()); + pos.do_move(m, SetupStates->top()); + } + } + + + // 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()) + token; + + // Read option value (can contain spaces) + while (is >> token) + value += string(" ", !value.empty()) + token; + + if (Options.count(name)) + Options[name] = value; + else + sync_cout << "No such option: " << name << sync_endl; + } + + + // go() is called when engine receives the "go" UCI command. The function sets + // the thinking time and other parameters from the input string, and starts + // the search. + + void go(const Position& pos, istringstream& is) { + + Search::LimitsType limits; + string token; + + while (is >> token) + { + if (token == "searchmoves") + while (is >> token) + limits.searchmoves.push_back(move_from_uci(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 = true; + else if (token == "ponder") limits.ponder = true; + } + + Threads.start_thinking(pos, limits, SetupStates); + } + +} // namespace + + +/// Wait for a command from the user, parse this text string as an UCI command, +/// and call the appropriate functions. Also intercepts EOF from stdin to ensure +/// that we exit gracefully if the GUI dies unexpectedly. In addition to the UCI +/// commands, the function also supports a few debug commands. + +void UCI::loop(int argc, char* argv[]) { + + Position pos(StartFEN, false, Threads.main()); // The root position + string token, cmd; + + for (int i = 1; i < argc; ++i) + cmd += std::string(argv[i]) + " "; + + do { + if (argc == 1 && !getline(cin, cmd)) // Block here waiting for input + cmd = "quit"; + + istringstream is(cmd); + + is >> skipws >> token; + + if (token == "quit" || token == "stop" || token == "ponderhit") + { + // 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 + // switch from pondering to normal search. + if (token != "ponderhit" || Search::Signals.stopOnPonderhit) + { + Search::Signals.stop = true; + Threads.main()->notify_one(); // Could be sleeping + } + else + Search::Limits.ponder = false; + } + else if (token == "perft") + { + int depth; + stringstream ss; + + is >> depth; + ss << Options["Hash"] << " " + << Options["Threads"] << " " << depth << " current " << token; + + benchmark(pos, ss); + } + else if (token == "key") + sync_cout << hex << uppercase << setfill('0') + << "position key: " << setw(16) << pos.key() + << "\nmaterial key: " << setw(16) << pos.material_key() + << "\npawn key: " << setw(16) << pos.pawn_key() + << dec << nouppercase << setfill(' ') << sync_endl; + + else if (token == "uci") + sync_cout << "id name " << engine_info(true) + << "\n" << Options + << "\nuciok" << sync_endl; + + else if (token == "ucinewgame") TT.clear(); + else if (token == "go") go(pos, is); + else if (token == "position") position(pos, is); + else if (token == "setoption") setoption(is); + else if (token == "flip") pos.flip(); + else if (token == "bench") benchmark(pos, is); + else if (token == "d") sync_cout << pos.pretty() << sync_endl; + else if (token == "isready") sync_cout << "readyok" << sync_endl; + else if (token == "eval") sync_cout << Eval::trace(pos) << sync_endl; + else + sync_cout << "Unknown command: " << cmd << sync_endl; + + } while (token != "quit" && argc == 1); // Passed args have one-shot behaviour + + Threads.wait_for_think_finished(); // Cannot quit whilst the search is running +} diff --git a/src/ucioption.cpp b/src/ucioption.cpp new file mode 100644 index 0000000..434a5b2 --- /dev/null +++ b/src/ucioption.cpp @@ -0,0 +1,160 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2014 Marco Costalba, Joona Kiiski, 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 "evaluate.h" +#include "misc.h" +#include "thread.h" +#include "tt.h" +#include "ucioption.h" + +using std::string; + +UCI::OptionsMap Options; // Global object + +namespace UCI { + +/// 'On change' actions, triggered by an option's value change +void on_logger(const Option& o) { start_logger(o); } +void on_eval(const Option&) { Eval::init(); } +void on_threads(const Option&) { Threads.read_uci_options(); } +void on_hash_size(const Option& o) { TT.resize(o); } +void on_clear_hash(const Option&) { TT.clear(); } + + +/// Our case insensitive less() function as required by UCI protocol +bool ci_less(char c1, char c2) { return tolower(c1) < tolower(c2); } + +bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const { + return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), ci_less); +} + + +/// init() initializes the UCI options to their hard-coded default values + +void init(OptionsMap& o) { + + o["Write Debug Log"] << Option(false, on_logger); + o["Write Search Log"] << Option(false); + o["Search Log Filename"] << Option("SearchLog.txt"); + o["Contempt Factor"] << Option(0, -50, 50); + o["Min Split Depth"] << Option(0, 0, 12, on_threads); + o["Threads"] << Option(1, 1, MAX_THREADS, on_threads); + o["Hash"] << Option(16, 1, 65536, on_hash_size); + o["Clear Hash"] << Option(on_clear_hash); + o["Ponder"] << Option(true); + o["MultiPV"] << Option(1, 1, 500); + o["Skill Level"] << Option(20, 0, 20); + o["Emergency Move Horizon"] << Option(40, 0, 50); + o["Emergency Base Time"] << Option(60, 0, 30000); + o["Emergency Move Time"] << Option(30, 0, 5000); + o["Minimum Thinking Time"] << Option(20, 0, 5000); + o["Slow Mover"] << Option(80, 10, 1000); + o["UCI_Chess960"] << Option(false); +} + + +/// 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 (OptionsMap::const_iterator it = om.begin(); it != om.end(); ++it) + 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) +{ std::ostringstream ss; ss << v; defaultValue = currentValue = ss.str(); } + + +Option::operator int() const { + assert(type == "check" || type == "spin"); + return (type == "spin" ? atoi(currentValue.c_str()) : 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" && (atoi(v.c_str()) < min || atoi(v.c_str()) > max))) + return *this; + + if (type != "button") + currentValue = v; + + if (on_change) + on_change(*this); + + return *this; +} + +} // namespace UCI diff --git a/src/ucioption.h b/src/ucioption.h new file mode 100644 index 0000000..87411a4 --- /dev/null +++ b/src/ucioption.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-2014 Marco Costalba, Joona Kiiski, 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 UCIOPTION_H_INCLUDED +#define UCIOPTION_H_INCLUDED + +#include +#include + +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 = NULL); + Option(bool v, OnChange = NULL); + Option(const char* v, OnChange = NULL); + Option(int v, int min, int max, OnChange = NULL); + + Option& operator=(const std::string& v); + void operator<<(const Option& o); + 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[]); + +} // namespace UCI + +extern UCI::OptionsMap Options; + +#endif // #ifndef UCIOPTION_H_INCLUDED