From: Marco Costalba Date: Mon, 1 Sep 2008 05:59:13 +0000 (+0200) Subject: Initial import of Glaurung 2.1 X-Git-Url: https://git.sesse.net/?p=stockfish;a=commitdiff_plain;h=bb751d6c890f5c50c642366d601740366cfae8d0 Initial import of Glaurung 2.1 --- bb751d6c890f5c50c642366d601740366cfae8d0 diff --git a/Copying.txt b/Copying.txt new file mode 100644 index 00000000..818433ec --- /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.txt b/Readme.txt new file mode 100644 index 00000000..defd2fc8 --- /dev/null +++ b/Readme.txt @@ -0,0 +1,328 @@ +1. Introduction +--------------- + +Glaurung is a free UCI chess engine. It is not a complete chess +program, but requires some UCI compatible GUI (like XBoard with +PolyGlot, eboard, José, 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 Glaurung with your +GUI. + +Glaurung 2 is a completely rewritten version of Glaurung. Apart from +the parallel search code, almost no code is shared with Glaurung +1.2.1, the previous stable version. The new program is clearly +stronger than the old, but has a less attractive style of play, +because there are still a few major holes in its evaluation function +(most notably space and development). + +This version of Glaurung supports up to 8 CPUs, but has not been +tested thoroughly with more than 2. The program tries to detect the +number of CPUs on your computer and set the number of search threads +accordingly, but please be aware that the detection is not always +correct. 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. + + +2. Files +-------- + +This distribution of Glaurung consists of the following files: + + * Readme.txt, 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 Glaurung on Unix-like + systems. For further information about how to compile Glaurung + yourself, read section 4 below. + + * MacOSX/, a subdirectory containing excutables for Apple Macintosh + computers running Mac OS X 10.4 (Tiger) and newer. There are two + executables, one for OS X 10.4, and one for OS X 10.5. The + executable for OS X 10.4 will work in 10.5 as well, but the one + for 10.5 is faster. + + * LinuxX86/, a subdirectory containing 32-bit and 64-bit x86 GNU/Linux + executables. + + * Windows/, a subdirectory containing 32-bit and 64-bit Windows + executables. + + * polyglot.ini, for using Glaurung with Fabien Letouzey's PolyGlot + adapter. + + +3. Opening books +---------------- + +This version of Glaurung has experimental support for PolyGlot opening +books. For information about how to create such books, consult the +PolyGlot documentation. The book file can be selected by setting the +UCI parameter "Book File". + +A book file contributed by Salvo Spitaleri can be found on the +Glaurung web page. + + +4. Compiling it yourself +------------------------ + +On Unix-like systems, it should usually be possible to compile +Glaurung directly from the source code with the included Makefile. +The exception is computer with big-endian CPUs, like PowerPC +Macintoshes. Some of the bitboard routines in the current version of +Glaurung are endianness-sensitive, and won't work on a big-endian CPU. +Ensuring that the line with #define USE_32BIT_ATTACKS" near the top +of bitboard.h is commented out should solve this problem. +Commenting out the line with "#define USE_32BIT_ATTACKS" near the + +There is also a problem with compiling Glaurung on certain 64-bit +systems, regardless of the endianness. If Glaurung segfaults +immediately after startup, try to comment out the line with +"#define USE_FOLDED_BITSCAN" near the beginning of bitboard.h and +recompile. + +Finally, even if Glaurung does work without any changes on your +computer, it might be possible to improve the performance by changing +some of the #define directives in bitboard.h. The default settings +are optimized for 64-bit CPUs. On 32-bit CPUs, it is probably better +to switch on USE_32BIT_ATTACKS, and to use BITCOUNT_SWAR_32 instead of +BITCOUNT_SWAR_64. For computers with very little memory (like +handheld devices), it is possible to conserve memory by defining +USE_COMPACT_ROOK_ATTACKS. + + + +5. History +---------- + +2007-05-06: Glaurung 2 - epsilon +-------------------------------- + +The first public release, and the first version of my new program +which is able to match the old Glaurung 1.2.1 on a single CPU. Lots +of features and chess knowledge is still missing. + +2007-05-10: Glaurung 2 - epsilon/2 +---------------------------------- + +This version is very close to 2 - epsilon. The major changes are: + + * A number of compatibility problems which appeared when trying to + compile Glaurung 2 - epsilon on various operating systems and CPUs + have been solved. + + * Fixed a major bug in the detection of rooks trapped inside a + friendly king. + + * Added knowledge about several types of drawn endgames. + + * Fixed a few FRC related bugs. FRC now works, but because of + serious holes in the evaluation function the program plays very + badly. + + * A slightly more sophisticated king safety evaluation. + +2007-06-07: Glaurung 2 - epsilon/3 +---------------------------------- + +The first public version with support for multiple CPUs. Unless you +have a dual-core (or better) computer, use Glaurung with a PolyGlot +book, or runs games with ponder on, you may want to skip this version, +which is almost certainly no stronger than 2 - epsilon/2 when running +on a single CPU. The main changes compared to the previous version +are: + + * Parallel search, with support for 1-4 CPUs. The program currently + always allocates a separate pawn hash table and material hash + table for four threads, which is a pure waste of RAM if your + computer has just a single CPU. This will be fixed in a future + version. + + * Fixed a bug in book randomization. When using Polyglot books, the + previous version would always select exactly the same move in the + same position after a restart of the program. Thanks to Pavel + Háse for pointing this out. + + * Fixed a UCI pondering bug: Glaurung no longer instantly prints its + best move when the maximum depth is reached during a ponder + search, as the previous version did. According to the UCI + protocol, it is not allowed to print the best move before the + engine has received the "stop" or "quit" command. + + * Additional search information: The new version displays hash + saturation and the current line(s) of search. + + * Several minor bug fixes and optimizations in the search and + evaluation. + +2007-06-08: Glaurung 2 - epsilon/4 +---------------------------------- + +A bugfix release, with only a single important change: + + * Fixed a very serious pondering bug. As pointed out by Marc + Lacrosse, the previous version would lose on time in almost every + single game with pondering enabled. The new version handles + pondering correctly (or so I hope). When playing with ponder + off, the new version is identical to version 2 - epsilon/3. + +2007-06-25: Glaurung 2 - epsilon/5 +---------------------------------- + +Another minor update, including the following improvements and bug +fixes: + + * As Werner Schüle discovered, the previous version would sometimes + stop thinking and lose on time right before delivering checkmate + (which is of course a very unfortunate moment to lose on time). + I haven't been able to reproduce Werner's problem on my computer + (probably because I run a different OS), but I have fixed the bug + which I suspect caused the time losses. I hope the time losses + will no longer occur with 2 - epsilon/5. + + * The program is now slightly less resource-hungry on computers + with less than 4 CPU cores: The previous version would always + allocated separate pawn and material hash tables for four + threads, even when running on a single-core CPU. The new version + only allocates pawn and material hash tables for the threads + which are actually used. + + * A minor reorganization of the memory layout has made the parallel + search about 10% more efficient (at least on my computer, but the + results are likely to vary considerably on different systems). + + * The Intel Mac OS X binary is much faster than before, thanks to + the Intel C++ compiler (previous versions were compiled with + GCC). + + * A few other very minor bug fixes and enhancements. + +2007-11-21: Glaurung 2.0 +------------------------ + +The first stable (or so I hope) and feature-complete version of +Glaurung 2. The following are the main changes compared to the +previous version: + + * The license has been changed from GPL version 2 to GPL version 3. + + * MultiPV mode. + + * Support for the "searchmoves" option in the UCI "go" command. + This means that it is possible to ask Glaurung to exclude some + moves from its analysis, or to restrict its analysis to just a + handful of moves selected by the user. This feature must also be + supported by the GUI under which Glaurung is run. Glaurung's own + GUI does currently not support this feature. + + * Chess960 support now works. The program still plays this game + very badly, because of lack of opening knowledge. + + * Much more aggressive pruning in the last few plies of the main + search. + + * Somewhat better scaling on multi-CPU systems, and support for up + to 8 CPUs. + + * Lots of new UCI parameters. + + * Improved time managment, especially in games with pondering on + (i.e. when the engine is allowed to think when it's the + opponent's turn to move). + + * Some evaluation improvements, and some new basic endgame + patterns. + + * The program should no longer crash if the game lasts longer than + 1000 plies. + + * Many minor bug fixes and other tiny improvements throughout the + code. + + * More generously commented code, and numerous cosmetic changes in + coding style. + +2007-11-22: Glaurung 2.0.1 +-------------------------- + + * Fixed (or so I hope) a bug which would occasionally cause one of + the search threads to get stuck forever in its idle loop. + +2008-05-14: Glaurung 2.1 +------------------------ + +This version contains far too many changes to list them all, but most +of them are minor and cosmetic. The most important and noticable +changes are a lot of new UCI parameters, and many improvements in the +evaluation function. The highlights are: + + * Extensive changes in the evaluation function. The addition of + king safety is the most important improvement, but there are also + numerous little improvements elsewhere in the evaluation. There + is still much work left to do in the evaluation function, though. + Space and development are still missing, and the tuning is likely + to be very poor. Currently, the program is optimized for an + entertaining style rather than maximum strength. + + * More accurate forward pruning. The previous version used the + null move refutation move to improve the pruning accuracy by + means of a very simple trick: It did not allow pruning of any + moves with the piece captured by the null move refutation move. + In Glaurung 2.1, this has been enhanced: It does not allow + pruning of moves which defend the destination square of the null + move refutation move, nor of moves which block the ray of the + piece in the case that the moving piece in the null move + refutation move is a slider. + + * More conservative use of LMR at PV nodes. The previous version + searched the first 6 moves with full depth, 2.1 by default + searches the first 14 moves with full depth (but there is a new + UCI parameter for configuring this). I am not at all sure + whether this is an improvement. More thorough testing is + required. + + * Feedback from the evaluation to the search. The search passes an + object of type 'EvalInfo' to the eval, and the eval fills this + struct with various potentially useful information (like the sets + of squares attacked by each piece type, the middle game and + endgame components of the eval, etc.). At the moment, almost + none of this information is actually used by the search. The + only exception is that the evaluation function is now used to + adjust the futility pruning margin in the quiescence search. + + * Less extensions. This hurts the programs performance a lot in most + test suites, but I hope it improves the branching factor in deep + searches. + + * A very long list of new UCI parameters, especially for tuning the + evaluation. + + +6. Terms of use +--------------- + +Glaurung 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 Glaurung 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. + + +7. Feedback +----------- + +The author's e-mail address is tord@glaurungchess.com + diff --git a/polyglot.ini b/polyglot.ini new file mode 100644 index 00000000..7d5c8d9b --- /dev/null +++ b/polyglot.ini @@ -0,0 +1,70 @@ + +[PolyGlot] + +EngineDir = . +EngineCommand = ./glaurung + +Book = false +BookFile = book.bin + +Log = true +LogFile = glaurung.log + +Resign = true +ResignScore = 600 + +[Engine] + +Hash = 128 +Threads = 1 +OwnBook = false +Book File = book.bin +Use Search Log = false +Mobility (Middle Game) = 100 +Mobility (Endgame) = 100 +Pawn Structure (Middle Game) = 100 +Pawn Structure (Endgame) = 100 +Passed Pawns (Middle Game) = 100 +Passed Pawns (Endgame) = 100 +Aggressiveness = 100 +Cowardice = 100 +King Safety Curve = Quadratic +Quadratic = Linear +King Safety Coefficient = 40 +King Safety X Intercept = 0 +King Safety Max Slope = 30 +King Safety Max Value = 500 +Queen Contact Check Bonus = 4 +Rook Contact Check Bonus = 2 +Queen Check Bonus = 2 +Rook Check Bonus = 1 +Bishop Check Bonus = 1 +Knight Check Bonus = 1 +Discovered Check Bonus = 3 +Mate Threat Bonus = 3 +Check Extension (PV nodes) = 2 +Check Extension (non-PV nodes) = 1 +Single Reply Extension (PV nodes) = 2 +Single Reply Extension (non-PV nodes) = 2 +Mate Threat Extension (PV nodes) = 0 +Mate Threat Extension (non-PV nodes) = 0 +Pawn Push to 7th Extension (PV nodes) = 1 +Pawn Push to 7th Extension (non-PV nodes) = 1 +Passed Pawn Extension (PV nodes) = 1 +Passed Pawn Extension (non-PV nodes) = 0 +Pawn Endgame Extension (PV nodes) = 2 +Pawn Endgame Extension (non-PV nodes) = 2 +Full Depth Moves (PV nodes) = 14 +Full Depth Moves (non-PV nodes) = 3 +Threat Depth = 5 +Selective Plies = 7 +Futility Pruning (Main Search) = true +Futility Pruning (Quiescence Search) = true +Futility Margin 0 = 50 +Futility Margin 1 = 100 +Futility Margin 2 = 300 +Maximum Razoring Depth = 3 +Razoring Margin = 300 +Randomness = 0 +Minimum Split Depth = 4 +Maximum Number of Threads per Split Point = 5 diff --git a/src/COPYING b/src/COPYING new file mode 100644 index 00000000..818433ec --- /dev/null +++ b/src/COPYING @@ -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/src/Makefile b/src/Makefile new file mode 100644 index 00000000..2e37cccc --- /dev/null +++ b/src/Makefile @@ -0,0 +1,145 @@ +# Glaurung, a UCI chess playing engine. +# Copyright (C) 2004-2007 Tord Romstad + +# This file is part of Glaurung. +# +# Glaurung 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. +# +# Glaurung 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 . + + +### +### Files +### + +EXE = glaurung + +OBJS = bitboard.o color.o pawns.o material.o endgame.o evaluate.o main.o \ + misc.o move.o movegen.o history.o movepick.o search.o piece.o \ + position.o square.o direction.o tt.o value.o uci.o ucioption.o \ + mersenne.o book.o bitbase.o san.o benchmark.o + + +### +### Rules +### + +all: $(EXE) .depend + +clean: + $(RM) *.o .depend glaurung + + +### +### Compiler: +### + +CXX = g++ +# CXX = g++-4.2 +# CXX = icpc + + +### +### Dependencies +### + +$(EXE): $(OBJS) + $(CXX) $(LDFLAGS) -o $@ $(OBJS) + +.depend: + $(CXX) -MM $(OBJS:.o=.cpp) > $@ + +include .depend + + +### +### Compiler and linker switches +### + +# Enable/disable debugging: + +CXXFLAGS += -DNDEBUG + + +# Compile with full warnings, and symbol names + +CXXFLAGS += -Wall -g + + +# General optimization flags. Note that -O2 might be faster than -O3 on some +# systems; this requires testing. + +CXXFLAGS += -O3 -fno-exceptions -fomit-frame-pointer -fno-rtti -fstrict-aliasing + + +# Compiler optimization flags for the Intel C++ compiler in Mac OS X: + +# CXXFLAGS += -mdynamic-no-pic -no-prec-div -ipo -static -xP + + +# Profiler guided optimization with the Intel C++ compiler. To use it, first +# create the directory ./profdata if it does not already exist, and delete its +# contents if it does exist. Then compile with -prof_gen, and run the +# resulting binary for a while (for instance, do ./glaurung bench 128 1, and +# wait 15 minutes for the benchmark to complete). Then do a 'make clean', and +# recompile with -prof_use. + +# CXXFLAGS += -prof_gen -prof_dir ./profdata +# CXXFLAGS += -prof_use -prof_dir ./profdata + + +# Profiler guided optimization with GCC. I've never been able to make this +# work. + +# CXXFLAGS += -fprofile-generate +# LDFLAGS += -fprofile-generate +# CXXFLAGS += -fprofile-use +# CXXFLAGS += -fprofile-use + + +# General linker flags + +LDFLAGS += -lm -lpthread + + +# Compiler switches for generating binaries for various CPUs in Mac OS X. +# Note that 'arch ppc' and 'arch ppc64' only works with g++, and not with +# the intel compiler. + +# CXXFLAGS += -arch ppc +# CXXFLAGS += -arch ppc64 +# CXXFLAGS += -arch i386 +# CXXFLAGS += -arch x86_64 +# LDFLAGS += -arch ppc +# LDFLAGS += -arch ppc64 +# LDFLAGS += -arch i386 +# LDFLAGS += -arch x86_64 + + +# Backwards compatibility with Mac OS X 10.4 when compiling under 10.5 with +# GCC 4.0. I haven't found a way to make it work with GCC 4.2. + +# CXXFLAGS += -isysroot /Developer/SDKs/MacOSX10.4u.sdk +# CXXFLAGS += -mmacosx-version-min=10.4 +# LDFLAGS += -isysroot /Developer/SDKs/MacOSX10.4u.sdk +# LDFLAGS += -Wl,-syslibroot /Developer/SDKs/MacOSX10.4u.sdk +# LDFLAGS += -mmacosx-version-min=10.4 + + +# Backwards compatibility with Mac OS X 10.4 when compiling with ICC. Doesn't +# work yet. :-( + +# CXXFLAGS += -DMAC_OS_X_VERSION_MIN_REQUIRED=1040 +# CXXFLAGS += -DMAC_OS_X_VERSION_MAX_ALLOWED=1040 +# CXXFLAGS += -D__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__=1040 +# CXXFLAGS += -F/Developer/SDKs/MacOSX10.4u.sdk/ +# LDFLAGS += -Wl,-syslibroot -Wl,/Developer/SDKs/MacOSX10.4u.sdk diff --git a/src/benchmark.cpp b/src/benchmark.cpp new file mode 100644 index 00000000..4bd1ac84 --- /dev/null +++ b/src/benchmark.cpp @@ -0,0 +1,91 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +//// +//// Includes +//// + +#include "benchmark.h" +#include "search.h" +#include "thread.h" +#include "ucioption.h" + + +//// +//// Variables +//// + +const std::string BenchmarkPositions[15] = { + "r4rk1/1b2qppp/p1n1p3/1p6/1b1PN3/3BRN2/PP3PPP/R2Q2K1 b - - 7 16", + "4r1k1/ppq3pp/3b4/2pP4/2Q1p3/4B1P1/PP5P/R5K1 b - - 0 20", + "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" +}; + + +//// +//// Functions +//// + +/// benchmark() runs a simple benchmark by letting Glaurung analyze 15 +/// positions for 60 seconds each. There are two parameters; the +/// transposition table size and the number of search threads that should +/// be used. The analysis is written to a file named bench.txt. + +void benchmark(const std::string &ttSize, const std::string &threads) { + Position pos; + Move moves[1] = {MOVE_NONE}; + int i; + + i = atoi(ttSize.c_str()); + if(i < 4 || i > 1024) { + std::cerr << "The hash table size must be between 4 and 1024" << std::endl; + exit(EXIT_FAILURE); + } + + i = atoi(threads.c_str()); + if(i < 1 || i > THREAD_MAX) { + std::cerr << "The number of threads must be between 1 and " << THREAD_MAX + << std::endl; + exit(EXIT_FAILURE); + } + + set_option_value("Hash", ttSize); + set_option_value("Threads", threads); + set_option_value("OwnBook", "false"); + set_option_value("Use Search Log", "true"); + set_option_value("Search Log Filename", "bench.txt"); + + for(i = 0; i < 15; i++) { + pos.from_fen(BenchmarkPositions[i]); + think(pos, true, false, 0, 0, 0, 0, 0, 60000, moves); + } + +} diff --git a/src/benchmark.h b/src/benchmark.h new file mode 100644 index 00000000..9ca68f83 --- /dev/null +++ b/src/benchmark.h @@ -0,0 +1,37 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +#if !defined(BENCHMARK_H_INCLUDED) +#define BENCHMARK_H_INCLUDED + +//// +//// Includes +//// + +#include + + +//// +//// Prototypes +//// + +extern void benchmark(const std::string &ttSize, const std::string &threads); + + +#endif // !defined(BENCHMARK_H_INCLUDED) diff --git a/src/bitbase.cpp b/src/bitbase.cpp new file mode 100644 index 00000000..80db0a41 --- /dev/null +++ b/src/bitbase.cpp @@ -0,0 +1,348 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +//// +//// Includes +//// + +#include + +#include "bitbase.h" +#include "bitboard.h" +#include "move.h" +#include "square.h" + + +//// +//// Local definitions +//// + +namespace { + + enum Result { + RESULT_UNKNOWN, + RESULT_INVALID, + RESULT_WIN, + RESULT_LOSS, + RESULT_DRAW + }; + + struct KPKPosition { + void from_index(int index); + int to_index() const; + bool is_legal() const; + bool is_immediate_draw() const; + bool is_immediate_win() const; + Bitboard wk_attacks() const; + Bitboard bk_attacks() const; + Bitboard pawn_attacks() const; + + Square whiteKingSquare, blackKingSquare, pawnSquare; + Color sideToMove; + }; + + + Result *Bitbase; + const int IndexMax = 2*24*64*64; + int UnknownCount = 0; + + void initialize(); + bool next_iteration(); + Result classify_wtm(const KPKPosition &p); + Result classify_btm(const KPKPosition &p); + int compute_index(Square wksq, Square bksq, Square psq, Color stm); + int compress_result(Result r); + +} + + +//// +//// Functions +//// + +void generate_kpk_bitbase(uint8_t bitbase[]) { + // Allocate array and initialize: + Bitbase = new Result[IndexMax]; + initialize(); + + // Iterate until all positions are classified: + while(next_iteration()); + + // Compress bitbase into the supplied parameter: + int i, j, b; + for(i = 0; i < 24576; i++) { + for(b = 0, j = 0; j < 8; b |= (compress_result(Bitbase[8*i+j]) << j), j++); + bitbase[i] = b; + } + + // Release allocated memory: + delete [] Bitbase; +} + + +namespace { + + void KPKPosition::from_index(int index) { + int s; + sideToMove = Color(index % 2); + blackKingSquare = Square((index / 2) % 64); + whiteKingSquare = Square((index / 128) % 64); + s = (index / 8192) % 24; + pawnSquare = make_square(File(s % 4), Rank(s / 4 + 1)); + } + + + int KPKPosition::to_index() const { + return compute_index(whiteKingSquare, blackKingSquare, pawnSquare, + sideToMove); + } + + + bool KPKPosition::is_legal() const { + if(whiteKingSquare == pawnSquare || whiteKingSquare == blackKingSquare || + pawnSquare == blackKingSquare) + return false; + if(sideToMove == WHITE) { + if(bit_is_set(this->wk_attacks(), blackKingSquare)) + return false; + if(bit_is_set(this->pawn_attacks(), blackKingSquare)) + return false; + } + else { + if(bit_is_set(this->bk_attacks(), whiteKingSquare)) + return false; + } + return true; + } + + + bool KPKPosition::is_immediate_draw() const { + if(sideToMove == BLACK) { + Bitboard wka = this->wk_attacks(); + Bitboard bka = this->bk_attacks(); + + // Case 1: Stalemate + if((bka & ~(wka | this->pawn_attacks())) == EmptyBoardBB) + return true; + + // Case 2: King can capture pawn + if(bit_is_set(bka, pawnSquare) && !bit_is_set(wka, pawnSquare)) + return true; + } + else { + // Case 1: Stalemate + if(whiteKingSquare == SQ_A8 && pawnSquare == SQ_A7 && + (blackKingSquare == SQ_C7 || blackKingSquare == SQ_C8)) + return true; + } + + return false; + } + + + bool KPKPosition::is_immediate_win() const { + // The position is an immediate win if it is white to move and the white + // pawn can be promoted without getting captured: + return + sideToMove == WHITE && + square_rank(pawnSquare) == RANK_7 && + (square_distance(blackKingSquare, pawnSquare+DELTA_N) > 1 || + bit_is_set(this->wk_attacks(), pawnSquare+DELTA_N)); + } + + + Bitboard KPKPosition::wk_attacks() const { + return StepAttackBB[WK][whiteKingSquare]; + } + + + Bitboard KPKPosition::bk_attacks() const { + return StepAttackBB[BK][blackKingSquare]; + } + + + Bitboard KPKPosition::pawn_attacks() const { + return StepAttackBB[WP][pawnSquare]; + } + + + void initialize() { + KPKPosition p; + for(int i = 0; i < IndexMax; i++) { + p.from_index(i); + if(!p.is_legal()) + Bitbase[i] = RESULT_INVALID; + else if(p.is_immediate_draw()) + Bitbase[i] = RESULT_DRAW; + else if(p.is_immediate_win()) + Bitbase[i] = RESULT_WIN; + else { + Bitbase[i] = RESULT_UNKNOWN; + UnknownCount++; + } + } + } + + + bool next_iteration() { + KPKPosition p; + int previousUnknownCount = UnknownCount; + + for(int i = 0; i < IndexMax; i++) + if(Bitbase[i] == RESULT_UNKNOWN) { + p.from_index(i); + + Bitbase[i] = (p.sideToMove == WHITE)? classify_wtm(p) : classify_btm(p); + + if(Bitbase[i] == RESULT_WIN || Bitbase[i] == RESULT_LOSS || + Bitbase[i] == RESULT_DRAW) + UnknownCount--; + } + + return UnknownCount != previousUnknownCount; + } + + + Result classify_wtm(const KPKPosition &p) { + + // If one move leads to a position classified as RESULT_LOSS, the result + // of the current position is RESULT_WIN. If all moves lead to positions + // classified as RESULT_DRAW, the current position is classified as + // RESULT_DRAW. Otherwise, the current position is classified as + // RESULT_UNKNOWN. + + bool unknownFound = false; + Bitboard b; + Square s; + + // King moves + b = p.wk_attacks(); + while(b) { + s = pop_1st_bit(&b); + switch(Bitbase[compute_index(s, p.blackKingSquare, p.pawnSquare, + BLACK)]) { + case RESULT_LOSS: + return RESULT_WIN; + + case RESULT_UNKNOWN: + unknownFound = true; + break; + + case RESULT_DRAW: case RESULT_INVALID: + break; + + default: + assert(false); + } + } + + // Pawn moves + if(square_rank(p.pawnSquare) < RANK_7) { + s = p.pawnSquare + DELTA_N; + switch(Bitbase[compute_index(p.whiteKingSquare, p.blackKingSquare, s, + BLACK)]) { + case RESULT_LOSS: + return RESULT_WIN; + + case RESULT_UNKNOWN: + unknownFound = true; + break; + + case RESULT_DRAW: case RESULT_INVALID: + break; + + default: + assert(false); + } + + if(square_rank(s) == RANK_3 && + s != p.whiteKingSquare && s != p.blackKingSquare) { + s += DELTA_N; + switch(Bitbase[compute_index(p.whiteKingSquare, p.blackKingSquare, s, + BLACK)]) { + case RESULT_LOSS: + return RESULT_WIN; + + case RESULT_UNKNOWN: + unknownFound = true; + break; + + case RESULT_DRAW: case RESULT_INVALID: + break; + + default: + assert(false); + } + } + } + + return unknownFound? RESULT_UNKNOWN : RESULT_DRAW; + } + + + Result classify_btm(const KPKPosition &p) { + + // If one move leads to a position classified as RESULT_DRAW, the result + // of the current position is RESULT_DRAW. If all moves lead to positions + // classified as RESULT_WIN, the current position is classified as + // RESULT_LOSS. Otherwise, the current position is classified as + // RESULT_UNKNOWN. + + bool unknownFound = false; + Bitboard b; + Square s; + + // King moves + b = p.bk_attacks(); + while(b) { + s = pop_1st_bit(&b); + switch(Bitbase[compute_index(p.whiteKingSquare, s, p.pawnSquare, + WHITE)]) { + case RESULT_DRAW: + return RESULT_DRAW; + + case RESULT_UNKNOWN: + unknownFound = true; + break; + + case RESULT_WIN: case RESULT_INVALID: + break; + + default: + assert(false); + } + } + + return unknownFound? RESULT_UNKNOWN : RESULT_LOSS; + } + + + int compute_index(Square wksq, Square bksq, Square psq, Color stm) { + int p = int(square_file(psq)) + (int(square_rank(psq)) - 1) * 4; + int result = int(stm) + 2*int(bksq) + 128*int(wksq) + 8192*p; + assert(result >= 0 && result < IndexMax); + return result; + } + + + int compress_result(Result r) { + return (r == RESULT_WIN || r == RESULT_LOSS)? 1 : 0; + } + +} diff --git a/src/bitbase.h b/src/bitbase.h new file mode 100644 index 00000000..2de54136 --- /dev/null +++ b/src/bitbase.h @@ -0,0 +1,37 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +#if !defined(BITBASE_H_INCLUDED) +#define BITBASE_H_INCLUDED + +//// +//// Includes +//// + +#include "types.h" + + +//// +//// Prototypes +//// + +extern void generate_kpk_bitbase(uint8_t bitbase[]); + + +#endif // !defined(BITBASE_H_INCLUDED) diff --git a/src/bitboard.cpp b/src/bitboard.cpp new file mode 100644 index 00000000..0bbd155b --- /dev/null +++ b/src/bitboard.cpp @@ -0,0 +1,541 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +//// +//// Includes +//// + +#include + +#include "bitboard.h" +#include "direction.h" + + +//// +//// Constants and variables +//// + +const Bitboard SquaresByColorBB[2] = {BlackSquaresBB, WhiteSquaresBB}; + +const Bitboard FileBB[8] = { + FileABB, FileBBB, FileCBB, FileDBB, FileEBB, FileFBB, FileGBB, FileHBB +}; + +const Bitboard NeighboringFilesBB[8] = { + FileBBB, FileABB|FileCBB, FileBBB|FileDBB, FileCBB|FileEBB, + FileDBB|FileFBB, FileEBB|FileGBB, FileFBB|FileHBB, FileGBB +}; + +const Bitboard ThisAndNeighboringFilesBB[8] = { + FileABB|FileBBB, FileABB|FileBBB|FileCBB, + FileBBB|FileCBB|FileDBB, FileCBB|FileDBB|FileEBB, + FileDBB|FileEBB|FileFBB, FileEBB|FileFBB|FileGBB, + FileFBB|FileGBB|FileHBB, FileGBB|FileHBB +}; + +const Bitboard RankBB[8] = { + Rank1BB, Rank2BB, Rank3BB, Rank4BB, Rank5BB, Rank6BB, Rank7BB, Rank8BB +}; + +const Bitboard RelativeRankBB[2][8] = { + { + Rank1BB, Rank2BB, Rank3BB, Rank4BB, Rank5BB, Rank6BB, Rank7BB, Rank8BB + }, + { + Rank8BB, Rank7BB, Rank6BB, Rank5BB, Rank4BB, Rank3BB, Rank2BB, Rank1BB + } +}; + +const Bitboard InFrontBB[2][8] = { + { + Rank2BB | Rank3BB | Rank4BB | Rank5BB | Rank6BB | Rank7BB | Rank8BB, + Rank3BB | Rank4BB | Rank5BB | Rank6BB | Rank7BB | Rank8BB, + Rank4BB | Rank5BB | Rank6BB | Rank7BB | Rank8BB, + Rank5BB | Rank6BB | Rank7BB | Rank8BB, + Rank6BB | Rank7BB | Rank8BB, + Rank7BB | Rank8BB, + Rank8BB, + EmptyBoardBB + }, + { + EmptyBoardBB, + Rank1BB, + Rank2BB | Rank1BB, + Rank3BB | Rank2BB | Rank1BB, + Rank4BB | Rank3BB | Rank2BB | Rank1BB, + Rank5BB | Rank4BB | Rank3BB | Rank2BB | Rank1BB, + Rank6BB | Rank5BB | Rank4BB | Rank3BB | Rank2BB | Rank1BB, + Rank7BB | Rank6BB | Rank5BB | Rank4BB | Rank3BB | Rank2BB | Rank1BB + } +}; + +#if defined(USE_COMPACT_ROOK_ATTACKS) + +Bitboard RankAttacks[8][64], FileAttacks[8][64]; + +#elif defined(USE_32BIT_ATTACKS) + +const uint64_t RMult[64] = { + 0xd7445cdec88002c0ULL, 0xd0a505c1f2001722ULL, 0xe065d1c896002182ULL, + 0x9a8c41e75a000892ULL, 0x8900b10c89002aa8ULL, 0x9b28d1c1d60005a2ULL, + 0x15d6c88de002d9aULL, 0xb1dbfc802e8016a9ULL, 0x149a1042d9d60029ULL, + 0xb9c08050599e002fULL, 0x132208c3af300403ULL, 0xc1000ce2e9c50070ULL, + 0x9d9aa13c99020012ULL, 0xb6b078daf71e0046ULL, 0x9d880182fb6e002eULL, + 0x52889f467e850037ULL, 0xda6dc008d19a8480ULL, 0x468286034f902420ULL, + 0x7140ac09dc54c020ULL, 0xd76ffffa39548808ULL, 0xea901c4141500808ULL, + 0xc91004093f953a02ULL, 0x2882afa8f6bb402ULL, 0xaebe335692442c01ULL, + 0xe904a22079fb91eULL, 0x13a514851055f606ULL, 0x76c782018c8fe632ULL, + 0x1dc012a9d116da06ULL, 0x3c9e0037264fffa6ULL, 0x2036002853c6e4a2ULL, + 0xe3fe08500afb47d4ULL, 0xf38af25c86b025c2ULL, 0xc0800e2182cf9a40ULL, + 0x72002480d1f60673ULL, 0x2500200bae6e9b53ULL, 0xc60018c1eefca252ULL, + 0x600590473e3608aULL, 0x46002c4ab3fe51b2ULL, 0xa200011486bcc8d2ULL, + 0xb680078095784c63ULL, 0x2742002639bf11aeULL, 0xc7d60021a5bdb142ULL, + 0xc8c04016bb83d820ULL, 0xbd520028123b4842ULL, 0x9d1600344ac2a832ULL, + 0x6a808005631c8a05ULL, 0x604600a148d5389aULL, 0xe2e40103d40dea65ULL, + 0x945b5a0087c62a81ULL, 0x12dc200cd82d28eULL, 0x2431c600b5f9ef76ULL, + 0xfb142a006a9b314aULL, 0x6870e00a1c97d62ULL, 0x2a9db2004a2689a2ULL, + 0xd3594600caf5d1a2ULL, 0xee0e4900439344a7ULL, 0x89c4d266ca25007aULL, + 0x3e0013a2743f97e3ULL, 0x180e31a0431378aULL, 0x3a9e465a4d42a512ULL, + 0x98d0a11a0c0d9cc2ULL, 0x8e711c1aba19b01eULL, 0x8dcdc836dd201142ULL, + 0x5ac08a4735370479ULL, +}; + +const int RShift[64] = { + 20, 21, 21, 21, 21, 21, 21, 20, 21, 22, 22, 22, 22, 22, 22, 21, + 21, 22, 22, 22, 22, 22, 22, 21, 21, 22, 22, 22, 22, 22, 22, 21, + 21, 22, 22, 22, 22, 22, 22, 21, 21, 22, 22, 22, 22, 22, 22, 21, + 21, 22, 22, 22, 22, 22, 22, 21, 20, 21, 21, 21, 21, 21, 21, 20 +}; + +#else // if defined(USE_32BIT_ATTACKS) + +const uint64_t RMult[64] = { + 0xa8002c000108020ULL, 0x4440200140003000ULL, 0x8080200010011880ULL, + 0x380180080141000ULL, 0x1a00060008211044ULL, 0x410001000a0c0008ULL, + 0x9500060004008100ULL, 0x100024284a20700ULL, 0x802140008000ULL, + 0x80c01002a00840ULL, 0x402004282011020ULL, 0x9862000820420050ULL, + 0x1001448011100ULL, 0x6432800200800400ULL, 0x40100010002000cULL, + 0x2800d0010c080ULL, 0x90c0008000803042ULL, 0x4010004000200041ULL, + 0x3010010200040ULL, 0xa40828028001000ULL, 0x123010008000430ULL, + 0x24008004020080ULL, 0x60040001104802ULL, 0x582200028400d1ULL, + 0x4000802080044000ULL, 0x408208200420308ULL, 0x610038080102000ULL, + 0x3601000900100020ULL, 0x80080040180ULL, 0xc2020080040080ULL, + 0x80084400100102ULL, 0x4022408200014401ULL, 0x40052040800082ULL, + 0xb08200280804000ULL, 0x8a80a008801000ULL, 0x4000480080801000ULL, + 0x911808800801401ULL, 0x822a003002001894ULL, 0x401068091400108aULL, + 0x4a10a00004cULL, 0x2000800640008024ULL, 0x1486408102020020ULL, + 0x100a000d50041ULL, 0x810050020b0020ULL, 0x204000800808004ULL, + 0x20048100a000cULL, 0x112000831020004ULL, 0x9000040810002ULL, + 0x440490200208200ULL, 0x8910401000200040ULL, 0x6404200050008480ULL, + 0x4b824a2010010100ULL, 0x4080801810c0080ULL, 0x400802a0080ULL, + 0x8224080110026400ULL, 0x40002c4104088200ULL, 0x1002100104a0282ULL, + 0x1208400811048021ULL, 0x3201014a40d02001ULL, 0x5100019200501ULL, + 0x101000208001005ULL, 0x2008450080702ULL, 0x1002080301d00cULL, + 0x410201ce5c030092ULL +}; + +const int RShift[64] = { + 52, 53, 53, 53, 53, 53, 53, 52, 53, 54, 54, 54, 54, 54, 54, 53, + 53, 54, 54, 54, 54, 54, 54, 53, 53, 54, 54, 54, 54, 54, 54, 53, + 53, 54, 54, 54, 54, 54, 54, 53, 53, 54, 54, 54, 54, 54, 54, 53, + 53, 54, 54, 54, 54, 54, 54, 53, 52, 53, 53, 53, 53, 53, 53, 52 +}; + +#endif // defined(USE_32BIT_ATTACKS) + +#if !defined(USE_COMPACT_ROOK_ATTACKS) +Bitboard RMask[64]; +int RAttackIndex[64]; +Bitboard RAttacks[0x19000]; +#endif + +#if defined(USE_32BIT_ATTACKS) + +const uint64_t BMult[64] = { + 0x54142844c6a22981ULL, 0x710358a6ea25c19eULL, 0x704f746d63a4a8dcULL, + 0xbfed1a0b80f838c5ULL, 0x90561d5631e62110ULL, 0x2804260376e60944ULL, + 0x84a656409aa76871ULL, 0xf0267f64c28b6197ULL, 0x70764ebb762f0585ULL, + 0x92aa09e0cfe161deULL, 0x41ee1f6bb266f60eULL, 0xddcbf04f6039c444ULL, + 0x5a3fab7bac0d988aULL, 0xd3727877fa4eaa03ULL, 0xd988402d868ddaaeULL, + 0x812b291afa075c7cULL, 0x94faf987b685a932ULL, 0x3ed867d8470d08dbULL, + 0x92517660b8901de8ULL, 0x2d97e43e058814b4ULL, 0x880a10c220b25582ULL, + 0xc7c6520d1f1a0477ULL, 0xdbfc7fbcd7656aa6ULL, 0x78b1b9bfb1a2b84fULL, + 0x2f20037f112a0bc1ULL, 0x657171ea2269a916ULL, 0xc08302b07142210eULL, + 0x880a4403064080bULL, 0x3602420842208c00ULL, 0x852800dc7e0b6602ULL, + 0x595a3fbbaa0f03b2ULL, 0x9f01411558159d5eULL, 0x2b4a4a5f88b394f2ULL, + 0x4afcbffc292dd03aULL, 0x4a4094a3b3f10522ULL, 0xb06f00b491f30048ULL, + 0xd5b3820280d77004ULL, 0x8b2e01e7c8e57a75ULL, 0x2d342794e886c2e6ULL, + 0xc302c410cde21461ULL, 0x111f426f1379c274ULL, 0xe0569220abb31588ULL, + 0x5026d3064d453324ULL, 0xe2076040c343cd8aULL, 0x93efd1e1738021eeULL, + 0xb680804bed143132ULL, 0x44e361b21986944cULL, 0x44c60170ef5c598cULL, + 0xf4da475c195c9c94ULL, 0xa3afbb5f72060b1dULL, 0xbc75f410e41c4ffcULL, + 0xb51c099390520922ULL, 0x902c011f8f8ec368ULL, 0x950b56b3d6f5490aULL, + 0x3909e0635bf202d0ULL, 0x5744f90206ec10ccULL, 0xdc59fd76317abbc1ULL, + 0x881c7c67fcbfc4f6ULL, 0x47ca41e7e440d423ULL, 0xeb0c88112048d004ULL, + 0x51c60e04359aef1aULL, 0x1aa1fe0e957a5554ULL, 0xdd9448db4f5e3104ULL, + 0xdc01f6dca4bebbdcULL, +}; + +const int BShift[64] = { + 26, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 25, 25, 25, 25, 27, 27, 27, 27, 25, 23, 23, 25, 27, 27, + 27, 27, 25, 23, 23, 25, 27, 27, 27, 27, 25, 25, 25, 25, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, 26, 27, 27, 27, 27, 27, 27, 26 +}; + +#else // if defined(USE_32BIT_ATTACKS) + +const uint64_t BMult[64] = { + 0x440049104032280ULL, 0x1021023c82008040ULL, 0x404040082000048ULL, + 0x48c4440084048090ULL, 0x2801104026490000ULL, 0x4100880442040800ULL, + 0x181011002e06040ULL, 0x9101004104200e00ULL, 0x1240848848310401ULL, + 0x2000142828050024ULL, 0x1004024d5000ULL, 0x102044400800200ULL, + 0x8108108820112000ULL, 0xa880818210c00046ULL, 0x4008008801082000ULL, + 0x60882404049400ULL, 0x104402004240810ULL, 0xa002084250200ULL, + 0x100b0880801100ULL, 0x4080201220101ULL, 0x44008080a00000ULL, + 0x202200842000ULL, 0x5006004882d00808ULL, 0x200045080802ULL, + 0x86100020200601ULL, 0xa802080a20112c02ULL, 0x80411218080900ULL, + 0x200a0880080a0ULL, 0x9a01010000104000ULL, 0x28008003100080ULL, + 0x211021004480417ULL, 0x401004188220806ULL, 0x825051400c2006ULL, + 0x140c0210943000ULL, 0x242800300080ULL, 0xc2208120080200ULL, + 0x2430008200002200ULL, 0x1010100112008040ULL, 0x8141050100020842ULL, + 0x822081014405ULL, 0x800c049e40400804ULL, 0x4a0404028a000820ULL, + 0x22060201041200ULL, 0x360904200840801ULL, 0x881a08208800400ULL, + 0x60202c00400420ULL, 0x1204440086061400ULL, 0x8184042804040ULL, + 0x64040315300400ULL, 0xc01008801090a00ULL, 0x808010401140c00ULL, + 0x4004830c2020040ULL, 0x80005002020054ULL, 0x40000c14481a0490ULL, + 0x10500101042048ULL, 0x1010100200424000ULL, 0x640901901040ULL, + 0xa0201014840ULL, 0x840082aa011002ULL, 0x10010840084240aULL, + 0x420400810420608ULL, 0x8d40230408102100ULL, 0x4a00200612222409ULL, + 0xa08520292120600ULL +}; + +const int BShift[64] = { + 58, 59, 59, 59, 59, 59, 59, 58, 59, 59, 59, 59, 59, 59, 59, 59, + 59, 59, 57, 57, 57, 57, 59, 59, 59, 59, 57, 55, 55, 57, 59, 59, + 59, 59, 57, 55, 55, 57, 59, 59, 59, 59, 57, 57, 57, 57, 59, 59, + 59, 59, 59, 59, 59, 59, 59, 59, 58, 59, 59, 59, 59, 59, 59, 58 +}; + +#endif // defined(USE_32BIT_ATTACKS) + +Bitboard BMask[64]; +int BAttackIndex[64]; +Bitboard BAttacks[0x1480]; + +Bitboard SetMaskBB[64]; +Bitboard ClearMaskBB[64]; + +Bitboard StepAttackBB[16][64]; +Bitboard RayBB[64][8]; +Bitboard BetweenBB[64][64]; + +Bitboard PassedPawnMask[2][64]; +Bitboard OutpostMask[2][64]; + +Bitboard BishopPseudoAttacks[64]; +Bitboard RookPseudoAttacks[64]; +Bitboard QueenPseudoAttacks[64]; + + +//// +//// Local definitions +//// + +namespace { + void init_masks(); + void init_ray_bitboards(); + void init_attacks(); + void init_between_bitboards(); + Bitboard sliding_attacks(int sq, Bitboard block, int dirs, int deltas[][2], + int fmin, int fmax, int rmin, int rmax); + Bitboard index_to_bitboard(int index, Bitboard mask); + void init_sliding_attacks(Bitboard attacks[], + int attackIndex[], Bitboard mask[], + const int shift[2], const Bitboard mult[], + int deltas[][2]); + void init_pseudo_attacks(); +#if defined(USE_COMPACT_ROOK_ATTACKS) + void init_file_and_rank_attacks(); +#endif +}; + + +//// +//// Functions +//// + +/// print_bitboard() prints a bitboard in an easily readable format to the +/// standard output. This is sometimes useful for debugging. + +void print_bitboard(Bitboard b) { + for(Rank r = RANK_8; r >= RANK_1; r--) { + std::cout << "+---+---+---+---+---+---+---+---+" << std::endl; + for(File f = FILE_A; f <= FILE_H; f++) + std::cout << "| " << (bit_is_set(b, make_square(f, r))? 'X' : ' ') << ' '; + std::cout << "|" << std::endl; + } + std::cout << "+---+---+---+---+---+---+---+---+" << std::endl; +} + + +/// init_bitboards() initializes various bitboard arrays. It is called during +/// program initialization. + +void init_bitboards() { + int rookDeltas[4][2] = {{0,1},{0,-1},{1,0},{-1,0}}; + int bishopDeltas[4][2] = {{1,1},{-1,1},{1,-1},{-1,-1}}; + init_masks(); + init_ray_bitboards(); + init_attacks(); + init_between_bitboards(); +#if defined(USE_COMPACT_ROOK_ATTACKS) + init_file_and_rank_attacks(); +#else + init_sliding_attacks(RAttacks, RAttackIndex, RMask, RShift, + RMult, rookDeltas); +#endif + init_sliding_attacks(BAttacks, BAttackIndex, BMask, BShift, + BMult, bishopDeltas); + init_pseudo_attacks(); +} + + +#if defined(USE_FOLDED_BITSCAN) + +static const int BitTable[64] = { + 63, 30, 3, 32, 25, 41, 22, 33, 15, 50, 42, 13, 11, 53, 19, 34, 61, 29, 2, + 51, 21, 43, 45, 10, 18, 47, 1, 54, 9, 57, 0, 35, 62, 31, 40, 4, 49, 5, 52, + 26, 60, 6, 23, 44, 46, 27, 56, 16, 7, 39, 48, 24, 59, 14, 12, 55, 38, 28, + 58, 20, 37, 17, 36, 8 +}; + + +/// first_1() finds the least significant nonzero bit in a nonzero bitboard. + +Square first_1(Bitboard b) { + b ^= (b - 1); + uint32_t fold = int(b) ^ int(b >> 32); + return Square(BitTable[(fold * 0x783a9b23) >> 26]); +} + + +/// pop_1st_bit() finds and clears the least significant nonzero bit in a +/// nonzero bitboard. + +Square pop_1st_bit(Bitboard *b) { + Bitboard bb = *b ^ (*b - 1); + uint32_t fold = int(bb) ^ int(bb >> 32); + *b &= (*b - 1); + return Square(BitTable[(fold * 0x783a9b23) >> 26]); +} + +#else + +static const int BitTable[64] = { + 0, 1, 2, 7, 3, 13, 8, 19, 4, 25, 14, 28, 9, 34, 20, 40, 5, 17, 26, 38, 15, + 46, 29, 48, 10, 31, 35, 54, 21, 50, 41, 57, 63, 6, 12, 18, 24, 27, 33, 39, + 16, 37, 45, 47, 30, 53, 49, 56, 62, 11, 23, 32, 36, 44, 52, 55, 61, 22, 43, + 51, 60, 42, 59, 58 +}; + + +/// first_1() finds the least significant nonzero bit in a nonzero bitboard. + +Square first_1(Bitboard b) { + return Square(BitTable[((b & -b) * 0x218a392cd3d5dbfULL) >> 58]); +} + + +/// pop_1st_bit() finds and clears the least significant nonzero bit in a +/// nonzero bitboard. + +Square pop_1st_bit(Bitboard *b) { + Bitboard bb = *b; + *b &= (*b - 1); + return Square(BitTable[((bb & -bb) * 0x218a392cd3d5dbfULL) >> 58]); +} + +#endif // defined(USE_FOLDED_BITSCAN) + + +namespace { + + // All functions below are used to precompute various bitboards during + // program initialization. Some of the functions may be difficult to + // understand, but they all seem to work correctly, and it should never + // be necessary to touch any of them. + + void init_masks() { + for(Square s = SQ_A1; s <= SQ_H8; s++) { + SetMaskBB[s] = (1ULL << s); + ClearMaskBB[s] = ~SetMaskBB[s]; + } + for(Color c = WHITE; c <= BLACK; c++) + for(Square s = SQ_A1; s <= SQ_H8; s++) { + PassedPawnMask[c][s] = + in_front_bb(c, s) & this_and_neighboring_files_bb(s); + OutpostMask[c][s] = in_front_bb(c, s) & neighboring_files_bb(s); + } + } + + + void init_ray_bitboards() { + int d[8] = {1, -1, 16, -16, 17, -17, 15, -15}; + for(int i = 0; i < 128; i = i + 9 & ~8) { + for(int j = 0; j < 8; j++) { + RayBB[(i&7)|((i>>4)<<3)][j] = EmptyBoardBB; + for(int k = i + d[j]; (k & 0x88) == 0; k += d[j]) + set_bit(&(RayBB[(i&7)|((i>>4)<<3)][j]), Square((k&7)|((k>>4)<<3))); + } + } + } + + + void init_attacks() { + int i, j, k, l; + int step[16][8] = { + {0}, + {7,9,0}, {17,15,10,6,-6,-10,-15,-17}, {9,7,-7,-9,0}, {8,1,-1,-8,0}, + {9,7,-7,-9,8,1,-1,-8}, {9,7,-7,-9,8,1,-1,-8}, {0}, {0}, + {-7,-9,0}, {17,15,10,6,-6,-10,-15,-17}, {9,7,-7,-9,0}, {8,1,-1,-8,0}, + {9,7,-7,-9,8,1,-1,-8}, {9,7,-7,-9,8,1,-1,-8} + }; + + for(i = 0; i < 64; i++) { + for(j = 0; j <= int(BK); j++) { + StepAttackBB[j][i] = EmptyBoardBB; + for(k = 0; k < 8 && step[j][k] != 0; k++) { + l = i + step[j][k]; + if(l >= 0 && l < 64 && abs((i&7) - (l&7)) < 3) + StepAttackBB[j][i] |= (1ULL << l); + } + } + } + } + + + Bitboard sliding_attacks(int sq, Bitboard block, int dirs, int deltas[][2], + int fmin=0, int fmax=7, int rmin=0, int rmax=7) { + Bitboard result = 0ULL; + int rk = sq / 8, fl = sq % 8, r, f, i; + for(i = 0; i < dirs; i++) { + int dx = deltas[i][0], dy = deltas[i][1]; + for(f = fl+dx, r = rk+dy; + (dx==0 || (f>=fmin && f<=fmax)) && (dy==0 || (r>=rmin && r<=rmax)); + f += dx, r += dy) { + result |= (1ULL << (f + r*8)); + if(block & (1ULL << (f + r*8))) break; + } + } + return result; + } + + + void init_between_bitboards() { + SquareDelta step[8] = { + DELTA_E, DELTA_W, DELTA_N, DELTA_S, DELTA_NE, DELTA_SW, DELTA_NW, DELTA_SE + }; + SignedDirection d; + for(Square s1 = SQ_A1; s1 <= SQ_H8; s1++) + for(Square s2 = SQ_A1; s2 <= SQ_H8; s2++) { + BetweenBB[s1][s2] = EmptyBoardBB; + d = signed_direction_between_squares(s1, s2); + if(d != SIGNED_DIR_NONE) + for(Square s3 = s1 + step[d]; s3 != s2; s3 += step[d]) + set_bit(&(BetweenBB[s1][s2]), s3); + } + } + + + Bitboard index_to_bitboard(int index, Bitboard mask) { + int i, j, bits = count_1s(mask); + Bitboard result = 0ULL; + for(i = 0; i < bits; i++) { + j = pop_1st_bit(&mask); + if(index & (1 << i)) result |= (1ULL << j); + } + return result; + } + + + void init_sliding_attacks(Bitboard attacks[], + int attackIndex[], Bitboard mask[], + const int shift[2], const Bitboard mult[], + int deltas[][2]) { + int i, j, k, index = 0; + Bitboard b; + for(i = 0; i < 64; i++) { + attackIndex[i] = index; + mask[i] = sliding_attacks(i, 0ULL, 4, deltas, 1, 6, 1, 6); + j = (1 << (64 - shift[i])); + for(k = 0; k < j; k++) { +#if defined(USE_32BIT_ATTACKS) + b = index_to_bitboard(k, mask[i]); + attacks[index + + (unsigned(int(b) * int(mult[i]) ^ + int(b >> 32) * int(mult[i] >> 32)) + >> shift[i])] = + sliding_attacks(i, b, 4, deltas); +#else + b = index_to_bitboard(k, mask[i]); + attacks[index + ((b * mult[i]) >> shift[i])] = + sliding_attacks(i, b, 4, deltas); +#endif + } + index += j; + } + } + + + void init_pseudo_attacks() { + Square s; + for(s = SQ_A1; s <= SQ_H8; s++) { + BishopPseudoAttacks[s] = bishop_attacks_bb(s, EmptyBoardBB); + RookPseudoAttacks[s] = rook_attacks_bb(s, EmptyBoardBB); + QueenPseudoAttacks[s] = queen_attacks_bb(s, EmptyBoardBB); + } + } + +#if defined(USE_COMPACT_ROOK_ATTACKS) + void init_file_and_rank_attacks() { + int i, j, k, l, m, s; + Bitboard b1, b2; + for(i = 0; i < 64; i++) { + + for(m = 0; m <= 1; m++) { + b1 = 0ULL; + for(j = 0; j < 6; j++) if(i & (1<= 0 && l <= 7; l += s) { + b2 |= (m? RankBB[l] : FileBB[l]); + if(b1 & (1ULL << (l*(1+m*7)))) break; + } + } + if(m) FileAttacks[j][(b1*0xd6e8802041d0c441ULL) >> 58] = b2; + else RankAttacks[j][i] = b2; + } + } + } + } +#endif // defined(USE_COMPACT_ROOK_ATTACKS) + +} diff --git a/src/bitboard.h b/src/bitboard.h new file mode 100644 index 00000000..d9a9bf5d --- /dev/null +++ b/src/bitboard.h @@ -0,0 +1,421 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +#if !defined(BITBOARD_H_INCLUDED) +#define BITBOARD_H_INCLUDED + + +//// +//// Defines +//// + +//#define USE_COMPACT_ROOK_ATTACKS +//#define USE_32BIT_ATTACKS +#define USE_FOLDED_BITSCAN + +#define BITCOUNT_SWAR_64 +//#define BITCOUNT_SWAR_32 +//#define BITCOUNT_LOOP + + + +//// +//// Includes +//// + +#include "direction.h" +#include "piece.h" +#include "square.h" +#include "types.h" + + +//// +//// Types +//// + +typedef uint64_t Bitboard; + + +//// +//// Constants and variables +//// + +const Bitboard EmptyBoardBB = 0ULL; + +const Bitboard WhiteSquaresBB = 0x55AA55AA55AA55AAULL; +const Bitboard BlackSquaresBB = 0xAA55AA55AA55AA55ULL; + +extern const Bitboard SquaresByColorBB[2]; + +const Bitboard FileABB = 0x0101010101010101ULL; +const Bitboard FileBBB = 0x0202020202020202ULL; +const Bitboard FileCBB = 0x0404040404040404ULL; +const Bitboard FileDBB = 0x0808080808080808ULL; +const Bitboard FileEBB = 0x1010101010101010ULL; +const Bitboard FileFBB = 0x2020202020202020ULL; +const Bitboard FileGBB = 0x4040404040404040ULL; +const Bitboard FileHBB = 0x8080808080808080ULL; + +extern const Bitboard FileBB[8]; +extern const Bitboard NeighboringFilesBB[8]; +extern const Bitboard ThisAndNeighboringFilesBB[8]; + +const Bitboard Rank1BB = 0xFFULL; +const Bitboard Rank2BB = 0xFF00ULL; +const Bitboard Rank3BB = 0xFF0000ULL; +const Bitboard Rank4BB = 0xFF000000ULL; +const Bitboard Rank5BB = 0xFF00000000ULL; +const Bitboard Rank6BB = 0xFF0000000000ULL; +const Bitboard Rank7BB = 0xFF000000000000ULL; +const Bitboard Rank8BB = 0xFF00000000000000ULL; + +extern const Bitboard RankBB[8]; +extern const Bitboard RelativeRankBB[2][8]; +extern const Bitboard InFrontBB[2][8]; + +extern Bitboard SetMaskBB[64]; +extern Bitboard ClearMaskBB[64]; + +extern Bitboard StepAttackBB[16][64]; +extern Bitboard RayBB[64][8]; +extern Bitboard BetweenBB[64][64]; + +extern Bitboard PassedPawnMask[2][64]; +extern Bitboard OutpostMask[2][64]; + +#if defined(USE_COMPACT_ROOK_ATTACKS) +extern Bitboard RankAttacks[8][64], FileAttacks[8][64]; +#else +extern const uint64_t RMult[64]; +extern const int RShift[64]; +extern Bitboard RMask[64]; +extern int RAttackIndex[64]; +extern Bitboard RAttacks[0x19000]; +#endif // defined(USE_COMPACT_ROOK_ATTACKS) + +extern const uint64_t BMult[64]; +extern const int BShift[64]; +extern Bitboard BMask[64]; +extern int BAttackIndex[64]; +extern Bitboard BAttacks[0x1480]; + +extern Bitboard BishopPseudoAttacks[64]; +extern Bitboard RookPseudoAttacks[64]; +extern Bitboard QueenPseudoAttacks[64]; + + +//// +//// Inline functions +//// + +/// Functions for testing whether a given bit is set in a bitboard, and for +/// setting and clearing bits. + +inline Bitboard set_mask_bb(Square s) { + // return 1ULL << s; + return SetMaskBB[s]; +} + +inline Bitboard clear_mask_bb(Square s) { + // return ~set_mask_bb(s); + return ClearMaskBB[s]; +} + +inline Bitboard bit_is_set(Bitboard b, Square s) { + return b & set_mask_bb(s); +} + +inline void set_bit(Bitboard *b, Square s) { + *b |= set_mask_bb(s); +} + +inline void clear_bit(Bitboard *b, Square s) { + *b &= clear_mask_bb(s); +} + + +/// rank_bb() and file_bb() gives a bitboard containing all squares on a given +/// file or rank. It is also possible to pass a square as input to these +/// functions. + +inline Bitboard rank_bb(Rank r) { + return RankBB[r]; +} + +inline Bitboard rank_bb(Square s) { + return rank_bb(square_rank(s)); +} + +inline Bitboard file_bb(File f) { + return FileBB[f]; +} + +inline Bitboard file_bb(Square s) { + return file_bb(square_file(s)); +} + + +/// neighboring_files_bb takes a file or a square as input, and returns a +/// bitboard representing all squares on the neighboring files. + +inline Bitboard neighboring_files_bb(File f) { + return NeighboringFilesBB[f]; +} + +inline Bitboard neighboring_files_bb(Square s) { + return neighboring_files_bb(square_file(s)); +} + + +/// this_and_neighboring_files_bb takes a file or a square as input, and +/// returns a bitboard representing all squares on the given and neighboring +/// files. + +inline Bitboard this_and_neighboring_files_bb(File f) { + return ThisAndNeighboringFilesBB[f]; +} + +inline Bitboard this_and_neighboring_files_bb(Square s) { + return this_and_neighboring_files_bb(square_file(s)); +} + + +/// relative_rank_bb() takes a color and a rank as input, and returns a bitboard +/// representing all squares on the given rank from the given color's point of +/// view. For instance, relative_rank_bb(WHITE, 7) gives all squares on the +/// 7th rank, while relative_rank_bb(BLACK, 7) gives all squares on the 2nd +/// rank. + +inline Bitboard relative_rank_bb(Color c, Rank r) { + return RelativeRankBB[c][r]; +} + + +/// in_front_bb() takes a color and a rank or square as input, and returns a +/// bitboard representing all the squares on all ranks in front of the rank +/// (or square), from the given color's point of view. For instance, +/// in_front_bb(WHITE, RANK_5) will give all squares on ranks 6, 7 and 8, while +/// in_front_bb(BLACK, SQ_D3) will give all squares on ranks 1 and 2. + +inline Bitboard in_front_bb(Color c, Rank r) { + return InFrontBB[c][r]; +} + +inline Bitboard in_front_bb(Color c, Square s) { + return in_front_bb(c, square_rank(s)); +} + + +/// ray_bb() gives a bitboard representing all squares along the ray in a +/// given direction from a given square. + +inline Bitboard ray_bb(Square s, SignedDirection d) { + return RayBB[s][d]; +} + + +/// Functions for computing sliding attack bitboards. rook_attacks_bb(), +/// bishop_attacks_bb() and queen_attacks_bb() all take a square and a +/// bitboard of occupied squares as input, and return a bitboard representing +/// all squares attacked by a rook, bishop or queen on the given square. + +#if defined(USE_COMPACT_ROOK_ATTACKS) + +inline Bitboard file_attacks_bb(Square s, Bitboard blockers) { + Bitboard b = (blockers >> square_file(s)) & 0x01010101010100ULL; + return + FileAttacks[square_rank(s)][(b*0xd6e8802041d0c441ULL)>>58] & file_bb(s); +} + +inline Bitboard rank_attacks_bb(Square s, Bitboard blockers) { + Bitboard b = (blockers >> ((s & 56) + 1)) & 63; + return RankAttacks[square_file(s)][b] & rank_bb(s); +} + +inline Bitboard rook_attacks_bb(Square s, Bitboard blockers) { + return file_attacks_bb(s, blockers) | rank_attacks_bb(s, blockers); +} + +#elif defined(USE_32BIT_ATTACKS) + +inline Bitboard rook_attacks_bb(Square s, Bitboard blockers) { + Bitboard b = blockers & RMask[s]; + return RAttacks[RAttackIndex[s] + + (unsigned(int(b) * int(RMult[s]) ^ + int(b >> 32) * int(RMult[s] >> 32)) + >> RShift[s])]; +} + +#else + +inline Bitboard rook_attacks_bb(Square s, Bitboard blockers) { + Bitboard b = blockers & RMask[s]; + return RAttacks[RAttackIndex[s] + ((b * RMult[s]) >> RShift[s])]; +} + +#endif + +#if defined(USE_32BIT_ATTACKS) + +inline Bitboard bishop_attacks_bb(Square s, Bitboard blockers) { + Bitboard b = blockers & BMask[s]; + return BAttacks[BAttackIndex[s] + + (unsigned(int(b) * int(BMult[s]) ^ + int(b >> 32) * int(BMult[s] >> 32)) + >> BShift[s])]; +} + +#else // defined(USE_32BIT_ATTACKS) + +inline Bitboard bishop_attacks_bb(Square s, Bitboard blockers) { + Bitboard b = blockers & BMask[s]; + return BAttacks[BAttackIndex[s] + ((b * BMult[s]) >> BShift[s])]; +} + +#endif // defined(USE_32BIT_ATTACKS) + +inline Bitboard queen_attacks_bb(Square s, Bitboard blockers) { + return rook_attacks_bb(s, blockers) | bishop_attacks_bb(s, blockers); +} + + +/// squares_between returns a bitboard representing all squares between +/// two squares. For instance, squares_between(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 line, file or diagonal, EmptyBoardBB is returned. + +inline Bitboard squares_between(Square s1, Square s2) { + return BetweenBB[s1][s2]; +} + + +/// squares_in_front_of 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. For instance, +/// squares_in_front_of(BLACK, SQ_E4) returns a bitboard with the squares +/// e3, e2 and e1 set. + +inline Bitboard squares_in_front_of(Color c, Square s) { + return in_front_bb(c, s) & file_bb(s); +} + + +/// squares_behind is similar to squares_in_front, but returns the squares +/// behind the square instead of in front of the square. + +inline Bitboard squares_behind(Color c, Square s) { + return in_front_bb(opposite_color(c), s) & file_bb(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. + +inline Bitboard passed_pawn_mask(Color c, Square s) { + return PassedPawnMask[c][s]; +} + + +/// outpost_mask takes a color and a square as input, and returns a bitboard +/// mask which can be used to test whether a piece on the square can possibly +/// be driven away by an enemy pawn. + +inline Bitboard outpost_mask(Color c, Square s) { + return OutpostMask[c][s]; +} + + +/// isolated_pawn_mask takes a square as input, and returns a bitboard mask +/// which can be used to test whether a pawn on the given square is isolated. + +inline Bitboard isolated_pawn_mask(Square s) { + return neighboring_files_bb(s); +} + + +/// count_1s() counts the number of nonzero bits in a bitboard. + +#if defined(BITCOUNT_LOOP) + +inline int count_1s(Bitboard b) { + int r; + for(r = 0; b; r++, b &= b - 1); + return r; +} + +inline int count_1s_max_15(Bitboard b) { + return count_1s(b); +} + +#elif defined(BITCOUNT_SWAR_32) + +inline int count_1s(Bitboard b) { + unsigned w = unsigned(b >> 32), v = unsigned(b); + v = v - ((v >> 1) & 0x55555555); + w = w - ((w >> 1) & 0x55555555); + v = (v & 0x33333333) + ((v >> 2) & 0x33333333); + w = (w & 0x33333333) + ((w >> 2) & 0x33333333); + v = (v + (v >> 4)) & 0x0F0F0F0F; + w = (w + (w >> 4)) & 0x0F0F0F0F; + v = ((v+w) * 0x01010101) >> 24; // mul is fast on amd procs + return int(v); +} + +inline int count_1s_max_15(Bitboard b) { + unsigned w = unsigned(b >> 32), v = unsigned(b); + v = v - ((v >> 1) & 0x55555555); + w = w - ((w >> 1) & 0x55555555); + v = (v & 0x33333333) + ((v >> 2) & 0x33333333); + w = (w & 0x33333333) + ((w >> 2) & 0x33333333); + v = ((v+w) * 0x11111111) >> 28; + return int(v); +} + +#elif defined(BITCOUNT_SWAR_64) + +inline int count_1s(Bitboard b) { + b -= ((b>>1) & 0x5555555555555555ULL); + b = ((b>>2) & 0x3333333333333333ULL) + (b & 0x3333333333333333ULL); + b = ((b>>4) + b) & 0x0F0F0F0F0F0F0F0FULL; + b *= 0x0101010101010101ULL; + return int(b >> 56); +} + +inline int count_1s_max_15(Bitboard b) { + b -= (b>>1) & 0x5555555555555555ULL; + b = ((b>>2) & 0x3333333333333333ULL) + (b & 0x3333333333333333ULL); + b *= 0x1111111111111111ULL; + return int(b >> 60); +} + +#endif // BITCOUNT + + +//// +//// Prototypes +//// + +extern void print_bitboard(Bitboard b); +extern void init_bitboards(); +extern Square first_1(Bitboard b); +extern Square pop_1st_bit(Bitboard *b); + + +#endif // !defined(BITBOARD_H_INCLUDED) diff --git a/src/book.cpp b/src/book.cpp new file mode 100644 index 00000000..bd2f4818 --- /dev/null +++ b/src/book.cpp @@ -0,0 +1,578 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +/* + The code in this file is based on the opening book code in PolyGlot + by Fabien Letouzey. PolyGlot is available under the GNU General + Public License, and can be downloaded from http://wbec-ridderkerk.nl +*/ + + +//// +//// Includes +//// + +#include +#include + +#include "book.h" +#include "mersenne.h" +#include "movegen.h" + + +//// +//// Global variables +//// + +Book OpeningBook; + + +//// +//// Local definitions +//// + +namespace { + + /// Random numbers from PolyGlot, used to compute book hash keys. + + const uint64_t Random64[781] = { + 0x9D39247E33776D41ULL, 0x2AF7398005AAA5C7ULL, 0x44DB015024623547ULL, + 0x9C15F73E62A76AE2ULL, 0x75834465489C0C89ULL, 0x3290AC3A203001BFULL, + 0x0FBBAD1F61042279ULL, 0xE83A908FF2FB60CAULL, 0x0D7E765D58755C10ULL, + 0x1A083822CEAFE02DULL, 0x9605D5F0E25EC3B0ULL, 0xD021FF5CD13A2ED5ULL, + 0x40BDF15D4A672E32ULL, 0x011355146FD56395ULL, 0x5DB4832046F3D9E5ULL, + 0x239F8B2D7FF719CCULL, 0x05D1A1AE85B49AA1ULL, 0x679F848F6E8FC971ULL, + 0x7449BBFF801FED0BULL, 0x7D11CDB1C3B7ADF0ULL, 0x82C7709E781EB7CCULL, + 0xF3218F1C9510786CULL, 0x331478F3AF51BBE6ULL, 0x4BB38DE5E7219443ULL, + 0xAA649C6EBCFD50FCULL, 0x8DBD98A352AFD40BULL, 0x87D2074B81D79217ULL, + 0x19F3C751D3E92AE1ULL, 0xB4AB30F062B19ABFULL, 0x7B0500AC42047AC4ULL, + 0xC9452CA81A09D85DULL, 0x24AA6C514DA27500ULL, 0x4C9F34427501B447ULL, + 0x14A68FD73C910841ULL, 0xA71B9B83461CBD93ULL, 0x03488B95B0F1850FULL, + 0x637B2B34FF93C040ULL, 0x09D1BC9A3DD90A94ULL, 0x3575668334A1DD3BULL, + 0x735E2B97A4C45A23ULL, 0x18727070F1BD400BULL, 0x1FCBACD259BF02E7ULL, + 0xD310A7C2CE9B6555ULL, 0xBF983FE0FE5D8244ULL, 0x9F74D14F7454A824ULL, + 0x51EBDC4AB9BA3035ULL, 0x5C82C505DB9AB0FAULL, 0xFCF7FE8A3430B241ULL, + 0x3253A729B9BA3DDEULL, 0x8C74C368081B3075ULL, 0xB9BC6C87167C33E7ULL, + 0x7EF48F2B83024E20ULL, 0x11D505D4C351BD7FULL, 0x6568FCA92C76A243ULL, + 0x4DE0B0F40F32A7B8ULL, 0x96D693460CC37E5DULL, 0x42E240CB63689F2FULL, + 0x6D2BDCDAE2919661ULL, 0x42880B0236E4D951ULL, 0x5F0F4A5898171BB6ULL, + 0x39F890F579F92F88ULL, 0x93C5B5F47356388BULL, 0x63DC359D8D231B78ULL, + 0xEC16CA8AEA98AD76ULL, 0x5355F900C2A82DC7ULL, 0x07FB9F855A997142ULL, + 0x5093417AA8A7ED5EULL, 0x7BCBC38DA25A7F3CULL, 0x19FC8A768CF4B6D4ULL, + 0x637A7780DECFC0D9ULL, 0x8249A47AEE0E41F7ULL, 0x79AD695501E7D1E8ULL, + 0x14ACBAF4777D5776ULL, 0xF145B6BECCDEA195ULL, 0xDABF2AC8201752FCULL, + 0x24C3C94DF9C8D3F6ULL, 0xBB6E2924F03912EAULL, 0x0CE26C0B95C980D9ULL, + 0xA49CD132BFBF7CC4ULL, 0xE99D662AF4243939ULL, 0x27E6AD7891165C3FULL, + 0x8535F040B9744FF1ULL, 0x54B3F4FA5F40D873ULL, 0x72B12C32127FED2BULL, + 0xEE954D3C7B411F47ULL, 0x9A85AC909A24EAA1ULL, 0x70AC4CD9F04F21F5ULL, + 0xF9B89D3E99A075C2ULL, 0x87B3E2B2B5C907B1ULL, 0xA366E5B8C54F48B8ULL, + 0xAE4A9346CC3F7CF2ULL, 0x1920C04D47267BBDULL, 0x87BF02C6B49E2AE9ULL, + 0x092237AC237F3859ULL, 0xFF07F64EF8ED14D0ULL, 0x8DE8DCA9F03CC54EULL, + 0x9C1633264DB49C89ULL, 0xB3F22C3D0B0B38EDULL, 0x390E5FB44D01144BULL, + 0x5BFEA5B4712768E9ULL, 0x1E1032911FA78984ULL, 0x9A74ACB964E78CB3ULL, + 0x4F80F7A035DAFB04ULL, 0x6304D09A0B3738C4ULL, 0x2171E64683023A08ULL, + 0x5B9B63EB9CEFF80CULL, 0x506AACF489889342ULL, 0x1881AFC9A3A701D6ULL, + 0x6503080440750644ULL, 0xDFD395339CDBF4A7ULL, 0xEF927DBCF00C20F2ULL, + 0x7B32F7D1E03680ECULL, 0xB9FD7620E7316243ULL, 0x05A7E8A57DB91B77ULL, + 0xB5889C6E15630A75ULL, 0x4A750A09CE9573F7ULL, 0xCF464CEC899A2F8AULL, + 0xF538639CE705B824ULL, 0x3C79A0FF5580EF7FULL, 0xEDE6C87F8477609DULL, + 0x799E81F05BC93F31ULL, 0x86536B8CF3428A8CULL, 0x97D7374C60087B73ULL, + 0xA246637CFF328532ULL, 0x043FCAE60CC0EBA0ULL, 0x920E449535DD359EULL, + 0x70EB093B15B290CCULL, 0x73A1921916591CBDULL, 0x56436C9FE1A1AA8DULL, + 0xEFAC4B70633B8F81ULL, 0xBB215798D45DF7AFULL, 0x45F20042F24F1768ULL, + 0x930F80F4E8EB7462ULL, 0xFF6712FFCFD75EA1ULL, 0xAE623FD67468AA70ULL, + 0xDD2C5BC84BC8D8FCULL, 0x7EED120D54CF2DD9ULL, 0x22FE545401165F1CULL, + 0xC91800E98FB99929ULL, 0x808BD68E6AC10365ULL, 0xDEC468145B7605F6ULL, + 0x1BEDE3A3AEF53302ULL, 0x43539603D6C55602ULL, 0xAA969B5C691CCB7AULL, + 0xA87832D392EFEE56ULL, 0x65942C7B3C7E11AEULL, 0xDED2D633CAD004F6ULL, + 0x21F08570F420E565ULL, 0xB415938D7DA94E3CULL, 0x91B859E59ECB6350ULL, + 0x10CFF333E0ED804AULL, 0x28AED140BE0BB7DDULL, 0xC5CC1D89724FA456ULL, + 0x5648F680F11A2741ULL, 0x2D255069F0B7DAB3ULL, 0x9BC5A38EF729ABD4ULL, + 0xEF2F054308F6A2BCULL, 0xAF2042F5CC5C2858ULL, 0x480412BAB7F5BE2AULL, + 0xAEF3AF4A563DFE43ULL, 0x19AFE59AE451497FULL, 0x52593803DFF1E840ULL, + 0xF4F076E65F2CE6F0ULL, 0x11379625747D5AF3ULL, 0xBCE5D2248682C115ULL, + 0x9DA4243DE836994FULL, 0x066F70B33FE09017ULL, 0x4DC4DE189B671A1CULL, + 0x51039AB7712457C3ULL, 0xC07A3F80C31FB4B4ULL, 0xB46EE9C5E64A6E7CULL, + 0xB3819A42ABE61C87ULL, 0x21A007933A522A20ULL, 0x2DF16F761598AA4FULL, + 0x763C4A1371B368FDULL, 0xF793C46702E086A0ULL, 0xD7288E012AEB8D31ULL, + 0xDE336A2A4BC1C44BULL, 0x0BF692B38D079F23ULL, 0x2C604A7A177326B3ULL, + 0x4850E73E03EB6064ULL, 0xCFC447F1E53C8E1BULL, 0xB05CA3F564268D99ULL, + 0x9AE182C8BC9474E8ULL, 0xA4FC4BD4FC5558CAULL, 0xE755178D58FC4E76ULL, + 0x69B97DB1A4C03DFEULL, 0xF9B5B7C4ACC67C96ULL, 0xFC6A82D64B8655FBULL, + 0x9C684CB6C4D24417ULL, 0x8EC97D2917456ED0ULL, 0x6703DF9D2924E97EULL, + 0xC547F57E42A7444EULL, 0x78E37644E7CAD29EULL, 0xFE9A44E9362F05FAULL, + 0x08BD35CC38336615ULL, 0x9315E5EB3A129ACEULL, 0x94061B871E04DF75ULL, + 0xDF1D9F9D784BA010ULL, 0x3BBA57B68871B59DULL, 0xD2B7ADEEDED1F73FULL, + 0xF7A255D83BC373F8ULL, 0xD7F4F2448C0CEB81ULL, 0xD95BE88CD210FFA7ULL, + 0x336F52F8FF4728E7ULL, 0xA74049DAC312AC71ULL, 0xA2F61BB6E437FDB5ULL, + 0x4F2A5CB07F6A35B3ULL, 0x87D380BDA5BF7859ULL, 0x16B9F7E06C453A21ULL, + 0x7BA2484C8A0FD54EULL, 0xF3A678CAD9A2E38CULL, 0x39B0BF7DDE437BA2ULL, + 0xFCAF55C1BF8A4424ULL, 0x18FCF680573FA594ULL, 0x4C0563B89F495AC3ULL, + 0x40E087931A00930DULL, 0x8CFFA9412EB642C1ULL, 0x68CA39053261169FULL, + 0x7A1EE967D27579E2ULL, 0x9D1D60E5076F5B6FULL, 0x3810E399B6F65BA2ULL, + 0x32095B6D4AB5F9B1ULL, 0x35CAB62109DD038AULL, 0xA90B24499FCFAFB1ULL, + 0x77A225A07CC2C6BDULL, 0x513E5E634C70E331ULL, 0x4361C0CA3F692F12ULL, + 0xD941ACA44B20A45BULL, 0x528F7C8602C5807BULL, 0x52AB92BEB9613989ULL, + 0x9D1DFA2EFC557F73ULL, 0x722FF175F572C348ULL, 0x1D1260A51107FE97ULL, + 0x7A249A57EC0C9BA2ULL, 0x04208FE9E8F7F2D6ULL, 0x5A110C6058B920A0ULL, + 0x0CD9A497658A5698ULL, 0x56FD23C8F9715A4CULL, 0x284C847B9D887AAEULL, + 0x04FEABFBBDB619CBULL, 0x742E1E651C60BA83ULL, 0x9A9632E65904AD3CULL, + 0x881B82A13B51B9E2ULL, 0x506E6744CD974924ULL, 0xB0183DB56FFC6A79ULL, + 0x0ED9B915C66ED37EULL, 0x5E11E86D5873D484ULL, 0xF678647E3519AC6EULL, + 0x1B85D488D0F20CC5ULL, 0xDAB9FE6525D89021ULL, 0x0D151D86ADB73615ULL, + 0xA865A54EDCC0F019ULL, 0x93C42566AEF98FFBULL, 0x99E7AFEABE000731ULL, + 0x48CBFF086DDF285AULL, 0x7F9B6AF1EBF78BAFULL, 0x58627E1A149BBA21ULL, + 0x2CD16E2ABD791E33ULL, 0xD363EFF5F0977996ULL, 0x0CE2A38C344A6EEDULL, + 0x1A804AADB9CFA741ULL, 0x907F30421D78C5DEULL, 0x501F65EDB3034D07ULL, + 0x37624AE5A48FA6E9ULL, 0x957BAF61700CFF4EULL, 0x3A6C27934E31188AULL, + 0xD49503536ABCA345ULL, 0x088E049589C432E0ULL, 0xF943AEE7FEBF21B8ULL, + 0x6C3B8E3E336139D3ULL, 0x364F6FFA464EE52EULL, 0xD60F6DCEDC314222ULL, + 0x56963B0DCA418FC0ULL, 0x16F50EDF91E513AFULL, 0xEF1955914B609F93ULL, + 0x565601C0364E3228ULL, 0xECB53939887E8175ULL, 0xBAC7A9A18531294BULL, + 0xB344C470397BBA52ULL, 0x65D34954DAF3CEBDULL, 0xB4B81B3FA97511E2ULL, + 0xB422061193D6F6A7ULL, 0x071582401C38434DULL, 0x7A13F18BBEDC4FF5ULL, + 0xBC4097B116C524D2ULL, 0x59B97885E2F2EA28ULL, 0x99170A5DC3115544ULL, + 0x6F423357E7C6A9F9ULL, 0x325928EE6E6F8794ULL, 0xD0E4366228B03343ULL, + 0x565C31F7DE89EA27ULL, 0x30F5611484119414ULL, 0xD873DB391292ED4FULL, + 0x7BD94E1D8E17DEBCULL, 0xC7D9F16864A76E94ULL, 0x947AE053EE56E63CULL, + 0xC8C93882F9475F5FULL, 0x3A9BF55BA91F81CAULL, 0xD9A11FBB3D9808E4ULL, + 0x0FD22063EDC29FCAULL, 0xB3F256D8ACA0B0B9ULL, 0xB03031A8B4516E84ULL, + 0x35DD37D5871448AFULL, 0xE9F6082B05542E4EULL, 0xEBFAFA33D7254B59ULL, + 0x9255ABB50D532280ULL, 0xB9AB4CE57F2D34F3ULL, 0x693501D628297551ULL, + 0xC62C58F97DD949BFULL, 0xCD454F8F19C5126AULL, 0xBBE83F4ECC2BDECBULL, + 0xDC842B7E2819E230ULL, 0xBA89142E007503B8ULL, 0xA3BC941D0A5061CBULL, + 0xE9F6760E32CD8021ULL, 0x09C7E552BC76492FULL, 0x852F54934DA55CC9ULL, + 0x8107FCCF064FCF56ULL, 0x098954D51FFF6580ULL, 0x23B70EDB1955C4BFULL, + 0xC330DE426430F69DULL, 0x4715ED43E8A45C0AULL, 0xA8D7E4DAB780A08DULL, + 0x0572B974F03CE0BBULL, 0xB57D2E985E1419C7ULL, 0xE8D9ECBE2CF3D73FULL, + 0x2FE4B17170E59750ULL, 0x11317BA87905E790ULL, 0x7FBF21EC8A1F45ECULL, + 0x1725CABFCB045B00ULL, 0x964E915CD5E2B207ULL, 0x3E2B8BCBF016D66DULL, + 0xBE7444E39328A0ACULL, 0xF85B2B4FBCDE44B7ULL, 0x49353FEA39BA63B1ULL, + 0x1DD01AAFCD53486AULL, 0x1FCA8A92FD719F85ULL, 0xFC7C95D827357AFAULL, + 0x18A6A990C8B35EBDULL, 0xCCCB7005C6B9C28DULL, 0x3BDBB92C43B17F26ULL, + 0xAA70B5B4F89695A2ULL, 0xE94C39A54A98307FULL, 0xB7A0B174CFF6F36EULL, + 0xD4DBA84729AF48ADULL, 0x2E18BC1AD9704A68ULL, 0x2DE0966DAF2F8B1CULL, + 0xB9C11D5B1E43A07EULL, 0x64972D68DEE33360ULL, 0x94628D38D0C20584ULL, + 0xDBC0D2B6AB90A559ULL, 0xD2733C4335C6A72FULL, 0x7E75D99D94A70F4DULL, + 0x6CED1983376FA72BULL, 0x97FCAACBF030BC24ULL, 0x7B77497B32503B12ULL, + 0x8547EDDFB81CCB94ULL, 0x79999CDFF70902CBULL, 0xCFFE1939438E9B24ULL, + 0x829626E3892D95D7ULL, 0x92FAE24291F2B3F1ULL, 0x63E22C147B9C3403ULL, + 0xC678B6D860284A1CULL, 0x5873888850659AE7ULL, 0x0981DCD296A8736DULL, + 0x9F65789A6509A440ULL, 0x9FF38FED72E9052FULL, 0xE479EE5B9930578CULL, + 0xE7F28ECD2D49EECDULL, 0x56C074A581EA17FEULL, 0x5544F7D774B14AEFULL, + 0x7B3F0195FC6F290FULL, 0x12153635B2C0CF57ULL, 0x7F5126DBBA5E0CA7ULL, + 0x7A76956C3EAFB413ULL, 0x3D5774A11D31AB39ULL, 0x8A1B083821F40CB4ULL, + 0x7B4A38E32537DF62ULL, 0x950113646D1D6E03ULL, 0x4DA8979A0041E8A9ULL, + 0x3BC36E078F7515D7ULL, 0x5D0A12F27AD310D1ULL, 0x7F9D1A2E1EBE1327ULL, + 0xDA3A361B1C5157B1ULL, 0xDCDD7D20903D0C25ULL, 0x36833336D068F707ULL, + 0xCE68341F79893389ULL, 0xAB9090168DD05F34ULL, 0x43954B3252DC25E5ULL, + 0xB438C2B67F98E5E9ULL, 0x10DCD78E3851A492ULL, 0xDBC27AB5447822BFULL, + 0x9B3CDB65F82CA382ULL, 0xB67B7896167B4C84ULL, 0xBFCED1B0048EAC50ULL, + 0xA9119B60369FFEBDULL, 0x1FFF7AC80904BF45ULL, 0xAC12FB171817EEE7ULL, + 0xAF08DA9177DDA93DULL, 0x1B0CAB936E65C744ULL, 0xB559EB1D04E5E932ULL, + 0xC37B45B3F8D6F2BAULL, 0xC3A9DC228CAAC9E9ULL, 0xF3B8B6675A6507FFULL, + 0x9FC477DE4ED681DAULL, 0x67378D8ECCEF96CBULL, 0x6DD856D94D259236ULL, + 0xA319CE15B0B4DB31ULL, 0x073973751F12DD5EULL, 0x8A8E849EB32781A5ULL, + 0xE1925C71285279F5ULL, 0x74C04BF1790C0EFEULL, 0x4DDA48153C94938AULL, + 0x9D266D6A1CC0542CULL, 0x7440FB816508C4FEULL, 0x13328503DF48229FULL, + 0xD6BF7BAEE43CAC40ULL, 0x4838D65F6EF6748FULL, 0x1E152328F3318DEAULL, + 0x8F8419A348F296BFULL, 0x72C8834A5957B511ULL, 0xD7A023A73260B45CULL, + 0x94EBC8ABCFB56DAEULL, 0x9FC10D0F989993E0ULL, 0xDE68A2355B93CAE6ULL, + 0xA44CFE79AE538BBEULL, 0x9D1D84FCCE371425ULL, 0x51D2B1AB2DDFB636ULL, + 0x2FD7E4B9E72CD38CULL, 0x65CA5B96B7552210ULL, 0xDD69A0D8AB3B546DULL, + 0x604D51B25FBF70E2ULL, 0x73AA8A564FB7AC9EULL, 0x1A8C1E992B941148ULL, + 0xAAC40A2703D9BEA0ULL, 0x764DBEAE7FA4F3A6ULL, 0x1E99B96E70A9BE8BULL, + 0x2C5E9DEB57EF4743ULL, 0x3A938FEE32D29981ULL, 0x26E6DB8FFDF5ADFEULL, + 0x469356C504EC9F9DULL, 0xC8763C5B08D1908CULL, 0x3F6C6AF859D80055ULL, + 0x7F7CC39420A3A545ULL, 0x9BFB227EBDF4C5CEULL, 0x89039D79D6FC5C5CULL, + 0x8FE88B57305E2AB6ULL, 0xA09E8C8C35AB96DEULL, 0xFA7E393983325753ULL, + 0xD6B6D0ECC617C699ULL, 0xDFEA21EA9E7557E3ULL, 0xB67C1FA481680AF8ULL, + 0xCA1E3785A9E724E5ULL, 0x1CFC8BED0D681639ULL, 0xD18D8549D140CAEAULL, + 0x4ED0FE7E9DC91335ULL, 0xE4DBF0634473F5D2ULL, 0x1761F93A44D5AEFEULL, + 0x53898E4C3910DA55ULL, 0x734DE8181F6EC39AULL, 0x2680B122BAA28D97ULL, + 0x298AF231C85BAFABULL, 0x7983EED3740847D5ULL, 0x66C1A2A1A60CD889ULL, + 0x9E17E49642A3E4C1ULL, 0xEDB454E7BADC0805ULL, 0x50B704CAB602C329ULL, + 0x4CC317FB9CDDD023ULL, 0x66B4835D9EAFEA22ULL, 0x219B97E26FFC81BDULL, + 0x261E4E4C0A333A9DULL, 0x1FE2CCA76517DB90ULL, 0xD7504DFA8816EDBBULL, + 0xB9571FA04DC089C8ULL, 0x1DDC0325259B27DEULL, 0xCF3F4688801EB9AAULL, + 0xF4F5D05C10CAB243ULL, 0x38B6525C21A42B0EULL, 0x36F60E2BA4FA6800ULL, + 0xEB3593803173E0CEULL, 0x9C4CD6257C5A3603ULL, 0xAF0C317D32ADAA8AULL, + 0x258E5A80C7204C4BULL, 0x8B889D624D44885DULL, 0xF4D14597E660F855ULL, + 0xD4347F66EC8941C3ULL, 0xE699ED85B0DFB40DULL, 0x2472F6207C2D0484ULL, + 0xC2A1E7B5B459AEB5ULL, 0xAB4F6451CC1D45ECULL, 0x63767572AE3D6174ULL, + 0xA59E0BD101731A28ULL, 0x116D0016CB948F09ULL, 0x2CF9C8CA052F6E9FULL, + 0x0B090A7560A968E3ULL, 0xABEEDDB2DDE06FF1ULL, 0x58EFC10B06A2068DULL, + 0xC6E57A78FBD986E0ULL, 0x2EAB8CA63CE802D7ULL, 0x14A195640116F336ULL, + 0x7C0828DD624EC390ULL, 0xD74BBE77E6116AC7ULL, 0x804456AF10F5FB53ULL, + 0xEBE9EA2ADF4321C7ULL, 0x03219A39EE587A30ULL, 0x49787FEF17AF9924ULL, + 0xA1E9300CD8520548ULL, 0x5B45E522E4B1B4EFULL, 0xB49C3B3995091A36ULL, + 0xD4490AD526F14431ULL, 0x12A8F216AF9418C2ULL, 0x001F837CC7350524ULL, + 0x1877B51E57A764D5ULL, 0xA2853B80F17F58EEULL, 0x993E1DE72D36D310ULL, + 0xB3598080CE64A656ULL, 0x252F59CF0D9F04BBULL, 0xD23C8E176D113600ULL, + 0x1BDA0492E7E4586EULL, 0x21E0BD5026C619BFULL, 0x3B097ADAF088F94EULL, + 0x8D14DEDB30BE846EULL, 0xF95CFFA23AF5F6F4ULL, 0x3871700761B3F743ULL, + 0xCA672B91E9E4FA16ULL, 0x64C8E531BFF53B55ULL, 0x241260ED4AD1E87DULL, + 0x106C09B972D2E822ULL, 0x7FBA195410E5CA30ULL, 0x7884D9BC6CB569D8ULL, + 0x0647DFEDCD894A29ULL, 0x63573FF03E224774ULL, 0x4FC8E9560F91B123ULL, + 0x1DB956E450275779ULL, 0xB8D91274B9E9D4FBULL, 0xA2EBEE47E2FBFCE1ULL, + 0xD9F1F30CCD97FB09ULL, 0xEFED53D75FD64E6BULL, 0x2E6D02C36017F67FULL, + 0xA9AA4D20DB084E9BULL, 0xB64BE8D8B25396C1ULL, 0x70CB6AF7C2D5BCF0ULL, + 0x98F076A4F7A2322EULL, 0xBF84470805E69B5FULL, 0x94C3251F06F90CF3ULL, + 0x3E003E616A6591E9ULL, 0xB925A6CD0421AFF3ULL, 0x61BDD1307C66E300ULL, + 0xBF8D5108E27E0D48ULL, 0x240AB57A8B888B20ULL, 0xFC87614BAF287E07ULL, + 0xEF02CDD06FFDB432ULL, 0xA1082C0466DF6C0AULL, 0x8215E577001332C8ULL, + 0xD39BB9C3A48DB6CFULL, 0x2738259634305C14ULL, 0x61CF4F94C97DF93DULL, + 0x1B6BACA2AE4E125BULL, 0x758F450C88572E0BULL, 0x959F587D507A8359ULL, + 0xB063E962E045F54DULL, 0x60E8ED72C0DFF5D1ULL, 0x7B64978555326F9FULL, + 0xFD080D236DA814BAULL, 0x8C90FD9B083F4558ULL, 0x106F72FE81E2C590ULL, + 0x7976033A39F7D952ULL, 0xA4EC0132764CA04BULL, 0x733EA705FAE4FA77ULL, + 0xB4D8F77BC3E56167ULL, 0x9E21F4F903B33FD9ULL, 0x9D765E419FB69F6DULL, + 0xD30C088BA61EA5EFULL, 0x5D94337FBFAF7F5BULL, 0x1A4E4822EB4D7A59ULL, + 0x6FFE73E81B637FB3ULL, 0xDDF957BC36D8B9CAULL, 0x64D0E29EEA8838B3ULL, + 0x08DD9BDFD96B9F63ULL, 0x087E79E5A57D1D13ULL, 0xE328E230E3E2B3FBULL, + 0x1C2559E30F0946BEULL, 0x720BF5F26F4D2EAAULL, 0xB0774D261CC609DBULL, + 0x443F64EC5A371195ULL, 0x4112CF68649A260EULL, 0xD813F2FAB7F5C5CAULL, + 0x660D3257380841EEULL, 0x59AC2C7873F910A3ULL, 0xE846963877671A17ULL, + 0x93B633ABFA3469F8ULL, 0xC0C0F5A60EF4CDCFULL, 0xCAF21ECD4377B28CULL, + 0x57277707199B8175ULL, 0x506C11B9D90E8B1DULL, 0xD83CC2687A19255FULL, + 0x4A29C6465A314CD1ULL, 0xED2DF21216235097ULL, 0xB5635C95FF7296E2ULL, + 0x22AF003AB672E811ULL, 0x52E762596BF68235ULL, 0x9AEBA33AC6ECC6B0ULL, + 0x944F6DE09134DFB6ULL, 0x6C47BEC883A7DE39ULL, 0x6AD047C430A12104ULL, + 0xA5B1CFDBA0AB4067ULL, 0x7C45D833AFF07862ULL, 0x5092EF950A16DA0BULL, + 0x9338E69C052B8E7BULL, 0x455A4B4CFE30E3F5ULL, 0x6B02E63195AD0CF8ULL, + 0x6B17B224BAD6BF27ULL, 0xD1E0CCD25BB9C169ULL, 0xDE0C89A556B9AE70ULL, + 0x50065E535A213CF6ULL, 0x9C1169FA2777B874ULL, 0x78EDEFD694AF1EEDULL, + 0x6DC93D9526A50E68ULL, 0xEE97F453F06791EDULL, 0x32AB0EDB696703D3ULL, + 0x3A6853C7E70757A7ULL, 0x31865CED6120F37DULL, 0x67FEF95D92607890ULL, + 0x1F2B1D1F15F6DC9CULL, 0xB69E38A8965C6B65ULL, 0xAA9119FF184CCCF4ULL, + 0xF43C732873F24C13ULL, 0xFB4A3D794A9A80D2ULL, 0x3550C2321FD6109CULL, + 0x371F77E76BB8417EULL, 0x6BFA9AAE5EC05779ULL, 0xCD04F3FF001A4778ULL, + 0xE3273522064480CAULL, 0x9F91508BFFCFC14AULL, 0x049A7F41061A9E60ULL, + 0xFCB6BE43A9F2FE9BULL, 0x08DE8A1C7797DA9BULL, 0x8F9887E6078735A1ULL, + 0xB5B4071DBFC73A66ULL, 0x230E343DFBA08D33ULL, 0x43ED7F5A0FAE657DULL, + 0x3A88A0FBBCB05C63ULL, 0x21874B8B4D2DBC4FULL, 0x1BDEA12E35F6A8C9ULL, + 0x53C065C6C8E63528ULL, 0xE34A1D250E7A8D6BULL, 0xD6B04D3B7651DD7EULL, + 0x5E90277E7CB39E2DULL, 0x2C046F22062DC67DULL, 0xB10BB459132D0A26ULL, + 0x3FA9DDFB67E2F199ULL, 0x0E09B88E1914F7AFULL, 0x10E8B35AF3EEAB37ULL, + 0x9EEDECA8E272B933ULL, 0xD4C718BC4AE8AE5FULL, 0x81536D601170FC20ULL, + 0x91B534F885818A06ULL, 0xEC8177F83F900978ULL, 0x190E714FADA5156EULL, + 0xB592BF39B0364963ULL, 0x89C350C893AE7DC1ULL, 0xAC042E70F8B383F2ULL, + 0xB49B52E587A1EE60ULL, 0xFB152FE3FF26DA89ULL, 0x3E666E6F69AE2C15ULL, + 0x3B544EBE544C19F9ULL, 0xE805A1E290CF2456ULL, 0x24B33C9D7ED25117ULL, + 0xE74733427B72F0C1ULL, 0x0A804D18B7097475ULL, 0x57E3306D881EDB4FULL, + 0x4AE7D6A36EB5DBCBULL, 0x2D8D5432157064C8ULL, 0xD1E649DE1E7F268BULL, + 0x8A328A1CEDFE552CULL, 0x07A3AEC79624C7DAULL, 0x84547DDC3E203C94ULL, + 0x990A98FD5071D263ULL, 0x1A4FF12616EEFC89ULL, 0xF6F7FD1431714200ULL, + 0x30C05B1BA332F41CULL, 0x8D2636B81555A786ULL, 0x46C9FEB55D120902ULL, + 0xCCEC0A73B49C9921ULL, 0x4E9D2827355FC492ULL, 0x19EBB029435DCB0FULL, + 0x4659D2B743848A2CULL, 0x963EF2C96B33BE31ULL, 0x74F85198B05A2E7DULL, + 0x5A0F544DD2B1FB18ULL, 0x03727073C2E134B1ULL, 0xC7F6AA2DE59AEA61ULL, + 0x352787BAA0D7C22FULL, 0x9853EAB63B5E0B35ULL, 0xABBDCDD7ED5C0860ULL, + 0xCF05DAF5AC8D77B0ULL, 0x49CAD48CEBF4A71EULL, 0x7A4C10EC2158C4A6ULL, + 0xD9E92AA246BF719EULL, 0x13AE978D09FE5557ULL, 0x730499AF921549FFULL, + 0x4E4B705B92903BA4ULL, 0xFF577222C14F0A3AULL, 0x55B6344CF97AAFAEULL, + 0xB862225B055B6960ULL, 0xCAC09AFBDDD2CDB4ULL, 0xDAF8E9829FE96B5FULL, + 0xB5FDFC5D3132C498ULL, 0x310CB380DB6F7503ULL, 0xE87FBB46217A360EULL, + 0x2102AE466EBB1148ULL, 0xF8549E1A3AA5E00DULL, 0x07A69AFDCC42261AULL, + 0xC4C118BFE78FEAAEULL, 0xF9F4892ED96BD438ULL, 0x1AF3DBE25D8F45DAULL, + 0xF5B4B0B0D2DEEEB4ULL, 0x962ACEEFA82E1C84ULL, 0x046E3ECAAF453CE9ULL, + 0xF05D129681949A4CULL, 0x964781CE734B3C84ULL, 0x9C2ED44081CE5FBDULL, + 0x522E23F3925E319EULL, 0x177E00F9FC32F791ULL, 0x2BC60A63A6F3B3F2ULL, + 0x222BBFAE61725606ULL, 0x486289DDCC3D6780ULL, 0x7DC7785B8EFDFC80ULL, + 0x8AF38731C02BA980ULL, 0x1FAB64EA29A2DDF7ULL, 0xE4D9429322CD065AULL, + 0x9DA058C67844F20CULL, 0x24C0E332B70019B0ULL, 0x233003B5A6CFE6ADULL, + 0xD586BD01C5C217F6ULL, 0x5E5637885F29BC2BULL, 0x7EBA726D8C94094BULL, + 0x0A56A5F0BFE39272ULL, 0xD79476A84EE20D06ULL, 0x9E4C1269BAA4BF37ULL, + 0x17EFEE45B0DEE640ULL, 0x1D95B0A5FCF90BC6ULL, 0x93CBE0B699C2585DULL, + 0x65FA4F227A2B6D79ULL, 0xD5F9E858292504D5ULL, 0xC2B5A03F71471A6FULL, + 0x59300222B4561E00ULL, 0xCE2F8642CA0712DCULL, 0x7CA9723FBB2E8988ULL, + 0x2785338347F2BA08ULL, 0xC61BB3A141E50E8CULL, 0x150F361DAB9DEC26ULL, + 0x9F6A419D382595F4ULL, 0x64A53DC924FE7AC9ULL, 0x142DE49FFF7A7C3DULL, + 0x0C335248857FA9E7ULL, 0x0A9C32D5EAE45305ULL, 0xE6C42178C4BBB92EULL, + 0x71F1CE2490D20B07ULL, 0xF1BCC3D275AFE51AULL, 0xE728E8C83C334074ULL, + 0x96FBF83A12884624ULL, 0x81A1549FD6573DA5ULL, 0x5FA7867CAF35E149ULL, + 0x56986E2EF3ED091BULL, 0x917F1DD5F8886C61ULL, 0xD20D8C88C8FFE65FULL, + 0x31D71DCE64B2C310ULL, 0xF165B587DF898190ULL, 0xA57E6339DD2CF3A0ULL, + 0x1EF6E6DBB1961EC9ULL, 0x70CC73D90BC26E24ULL, 0xE21A6B35DF0C3AD7ULL, + 0x003A93D8B2806962ULL, 0x1C99DED33CB890A1ULL, 0xCF3145DE0ADD4289ULL, + 0xD0E4427A5514FB72ULL, 0x77C621CC9FB3A483ULL, 0x67A34DAC4356550BULL, + 0xF8D626AAAF278509ULL + }; + + + /// Indices to the Random64[] array + + const int RandomPiece = 0; + const int RandomCastle = 768; + const int RandomEnPassant = 772; + const int RandomTurn = 780; + + + /// Convert pieces to the range 0..1 + + const int PieceTo12[] = { + 0, 0, 2, 4, 6, 8, 10, 0, 0, 1, 3, 5, 7, 9, 11 + }; + + + /// Prototypes + + uint64_t book_key(const Position &pos); + uint64_t book_piece_key(Piece p, Square s); + uint64_t book_castle_key(const Position &pos); + uint64_t book_ep_key(const Position &pos); + uint64_t book_color_key(const Position &pos); + + uint64_t read_integer(FILE *file, int size); + +} + + +//// +//// Functions +//// + + +/// Constructor + +Book::Book() { + bookFile = NULL; + bookSize = 0; +} + + +/// Book::open() opens a book file with a given file name. + +void Book::open(const std::string &fName) { + fileName = fName; + bookFile = fopen(fileName.c_str(), "rb"); + if(bookFile != NULL) { + if(fseek(bookFile, 0, SEEK_END) == -1) { + std::cerr << "Failed to open book file " << fileName << std::endl; + exit(EXIT_FAILURE); + } + bookSize = ftell(bookFile) / 16; + if(bookSize == -1) { + std::cerr << "Failed to open book file " << fileName << std::endl; + exit(EXIT_FAILURE); + } + } +} + + +/// Book::close() closes the currently open book file. + +void Book::close() { + if(bookFile != NULL && fclose(bookFile) == EOF) { + std::cerr << "Failed to close book file" << std::endl; + exit(EXIT_FAILURE); + } +} + + +/// Book::is_open() tests whether a book file has been opened. + +bool Book::is_open() const { + return bookFile != NULL && bookSize != 0; +} + + +/// Book::file_name() returns the file name of the currently active book, +/// or the empty string if no book is open. + +const std::string Book::file_name() const { + return this->is_open()? fileName : ""; +} + + +/// Book::get_move() gets a book move for a given position. Returns +/// MOVE_NONE if no book move is found. + +Move Book::get_move(const Position &pos) const { + if(this->is_open()) { + int bestMove = 0, bestScore = 0, move, score; + uint64_t key = book_key(pos); + BookEntry entry; + + for(int i = this->find_key(key); i < bookSize; i++) { + this->read_entry(entry, i); + if(entry.key != key) + break; + move = entry.move; + score = entry.count; + assert(score > 0); + + bestScore += score; + if(int(genrand_int32() % bestScore) < score) + bestMove = move; + } + + if(bestMove != 0) { + MoveStack moves[256]; + int n, j; + n = generate_legal_moves(pos, moves); + for(j = 0; j < n; j++) + if((int(moves[j].move) & 07777) == bestMove) + return moves[j].move; + } + } + return MOVE_NONE; +} + + +/// Book::find_key() takes a book key as input, and does a binary search +/// through the book file for the given key. The index to the first book +/// entry with the same key as the input is returned. When the key is not +/// found in the book file, bookSize is returned. + +int Book::find_key(uint64_t key) const { + int left, right, mid; + BookEntry entry; + + // Binary search (finds the leftmost entry) + left = 0; + right = bookSize - 1; + + assert(left <= right); + + while(left < right) { + mid = (left + right) / 2; + assert(mid >= left && mid < right); + + this->read_entry(entry, mid); + + if(key <= entry.key) + right = mid; + else + left = mid + 1; + } + + assert(left == right); + + this->read_entry(entry, left); + + return (entry.key == key)? left : bookSize; +} + + +/// Book::read_entry() takes a BookEntry reference and an integer index as +/// input, and looks up the opening book entry at the given index in the book +/// file. The book entry is copied to the first input parameter. + +void Book::read_entry(BookEntry& entry, int n) const { + assert(n >= 0 && n < bookSize); + assert(bookFile != NULL); + + if(fseek(bookFile, n*16, SEEK_SET) == -1) { + std::cerr << "Failed to read book entry at index " << n << std::endl; + exit(EXIT_FAILURE); + } + + entry.key = read_integer(bookFile, 8); + entry.move = read_integer(bookFile, 2); + entry.count = read_integer(bookFile, 2); + entry.n = read_integer(bookFile, 2); + entry.sum = read_integer(bookFile, 2); +} + + +//// +//// Local definitions +//// + +namespace { + + uint64_t book_key(const Position &pos) { + uint64_t result = 0ULL; + + for(Color c = WHITE; c <= BLACK; c++) { + Bitboard b = pos.pieces_of_color(c); + Square s; + Piece p; + while(b != EmptyBoardBB) { + s = pop_1st_bit(&b); + p = pos.piece_on(s); + assert(piece_is_ok(p)); + assert(color_of_piece(p) == c); + + result ^= book_piece_key(p, s); + } + } + + result ^= book_castle_key(pos); + result ^= book_ep_key(pos); + result ^= book_color_key(pos); + + return result; + } + + + uint64_t book_piece_key(Piece p, Square s) { + return Random64[RandomPiece + (PieceTo12[int(p)]^1)*64 + int(s)]; + } + + + uint64_t book_castle_key(const Position &pos) { + uint64_t result = 0ULL; + + if(pos.can_castle_kingside(WHITE)) + result ^= Random64[RandomCastle+0]; + if(pos.can_castle_queenside(WHITE)) + result ^= Random64[RandomCastle+1]; + if(pos.can_castle_kingside(BLACK)) + result ^= Random64[RandomCastle+2]; + if(pos.can_castle_queenside(BLACK)) + result ^= Random64[RandomCastle+3]; + return result; + } + + + uint64_t book_ep_key(const Position &pos) { + return (pos.ep_square() == SQ_NONE)? + 0ULL : Random64[RandomEnPassant + square_file(pos.ep_square())]; + } + + + uint64_t book_color_key(const Position &pos) { + return (pos.side_to_move() == WHITE)? Random64[RandomTurn] : 0ULL; + } + + + uint64_t read_integer(FILE *file, int size) { + uint64_t n = 0ULL;; + int i; + int b; + + assert(file != NULL); + assert(size > 0 && size <= 8); + + for(i = 0; i < size; i++) { + b = fgetc(file); + if(b == EOF) { + std::cerr << "Failed to read " << size << " bytes from book file" + << std::endl; + exit(EXIT_FAILURE); + } + assert(b >= 0 && b < 256); + n = (n << 8) | b; + } + return n; + } + +} diff --git a/src/book.h b/src/book.h new file mode 100644 index 00000000..ee3f5c5a --- /dev/null +++ b/src/book.h @@ -0,0 +1,89 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +/* + The code in this file is based on the opening book code in PolyGlot + by Fabien Letouzey. PolyGlot is available under the GNU General + Public License, and can be downloaded from http://wbec-ridderkerk.nl +*/ + + +#if !defined(BOOK_H_INCLUDED) +#define BOOK_H_INCLUDED + + +//// +//// Includes +//// + +#include + +#include "move.h" +#include "position.h" + + +//// +//// Types +//// + +struct BookEntry { + uint64_t key; + uint16_t move; + uint16_t count; + uint16_t n; + uint16_t sum; +}; + +class Book { + +public: + // Constructors + Book(); + + // Open and close book files + void open(const std::string &fName); + void close(); + + // Testing if a book is opened + bool is_open() const; + + // The file name of the currently active book + const std::string file_name() const; + + // Get a book move for a given position + Move get_move(const Position &pos) const; + +private: + int find_key(uint64_t key) const; + void read_entry(BookEntry &entry, int n) const; + + std::string fileName; + FILE *bookFile; + int bookSize; +}; + + +//// +//// Global variables +//// + +extern Book OpeningBook; + + +#endif // !defined(BOOK_H_INCLUDED) diff --git a/src/color.cpp b/src/color.cpp new file mode 100644 index 00000000..50965a0b --- /dev/null +++ b/src/color.cpp @@ -0,0 +1,35 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +//// +//// Includes +//// + +#include "color.h" + + +//// +//// Functions +//// + +/// color_is_ok(), for debugging: + +bool color_is_ok(Color c) { + return c == WHITE || c == BLACK; +} diff --git a/src/color.h b/src/color.h new file mode 100644 index 00000000..37ba7959 --- /dev/null +++ b/src/color.h @@ -0,0 +1,60 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +#if !defined(COLOR_H_INCLUDED) +#define COLOR_H_INCLUDED + +//// +//// Includes +//// + +#include "misc.h" + + +//// +//// Types +//// + +enum Color { + WHITE, + BLACK, + COLOR_NONE +}; + + +//// +//// Inline functions +//// + +inline Color operator+ (Color c, int i) { return Color(int(c) + i); } +inline void operator++ (Color &c, int i) { c = Color(int(c) + 1); } + +inline Color opposite_color(Color c) { + return Color(int(c) ^ 1); +} + + +//// +//// Prototypes +//// + +extern bool color_is_ok(Color c); + + +#endif // !defined(COLOR_H_INCLUDED) diff --git a/src/depth.h b/src/depth.h new file mode 100644 index 00000000..493ecc40 --- /dev/null +++ b/src/depth.h @@ -0,0 +1,61 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +#if !defined(DEPTH_H_INCLUDED) +#define DEPTH_H_INCLUDED + +//// +//// Types +//// + +enum Depth { + DEPTH_ZERO = 0, + DEPTH_MAX = 200 // 100 * OnePly; +}; + + +//// +//// Constants +//// + +/// Note: If OnePly is changed, the constant HistoryMax in history.h should +/// probably also be changed. + +const Depth OnePly = Depth(2); + + +//// +//// Inline functions +//// + +inline Depth operator+ (Depth d, int i) { return Depth(int(d) + i); } +inline Depth operator+ (Depth d1, Depth d2) { return Depth(int(d1) + int(d2)); } +inline void operator+= (Depth &d, int i) { d = Depth(int(d) + i); } +inline void operator+= (Depth &d1, Depth d2) { d1 += int(d2); } +inline Depth operator- (Depth d, int i) { return Depth(int(d) - i); } +inline Depth operator- (Depth d1, Depth d2) { return Depth(int(d1) - int(d2)); } +inline void operator-= (Depth & d, int i) { d = Depth(int(d) - i); } +inline Depth operator* (Depth d, int i) { return Depth(int(d) * i); } +inline Depth operator* (int i, Depth d) { return Depth(int(d) * i); } +inline void operator*= (Depth &d, int i) { d = Depth(int(d) * i); } +inline Depth operator/ (Depth d, int i) { return Depth(int(d) / i); } +inline void operator/= (Depth &d, int i) { d = Depth(int(d) / i); } + + +#endif // !defined(DEPTH_H_INCLUDED) diff --git a/src/direction.cpp b/src/direction.cpp new file mode 100644 index 00000000..45fda4fd --- /dev/null +++ b/src/direction.cpp @@ -0,0 +1,62 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +//// +//// Includes +//// + +#include "direction.h" +#include "square.h" + + +//// +//// Variables +//// + +uint8_t DirectionTable[64][64]; +uint8_t SignedDirectionTable[64][64]; + + +//// +//// Functions +//// + +void init_direction_table() { + SquareDelta deltas[8] = { + DELTA_E, DELTA_W, DELTA_N, DELTA_S, DELTA_NE, DELTA_SW, DELTA_NW, DELTA_SE + }; + for(Square s1 = SQ_A1; s1 <= SQ_H8; s1++) + for(Square s2 = SQ_A1; s2 <= SQ_H8; s2++) { + DirectionTable[s1][s2] = uint8_t(DIR_NONE); + SignedDirectionTable[s1][s2] = uint8_t(SIGNED_DIR_NONE); + if(s1 == s2) continue; + for(SignedDirection d = SIGNED_DIR_E; d <= SIGNED_DIR_SE; d++) { + SquareDelta delta = deltas[d]; + Square s3, s4; + for(s4 = s1 + delta, s3 = s1; + square_distance(s4, s3) == 1 && s4 != s2 && square_is_ok(s4); + s3 = s4, s4 += delta); + if(s4 == s2 && square_distance(s4, s3) == 1) { + SignedDirectionTable[s1][s2] = uint8_t(d); + DirectionTable[s1][s2] = uint8_t(d/2); + break; + } + } + } +} diff --git a/src/direction.h b/src/direction.h new file mode 100644 index 00000000..63c7c73e --- /dev/null +++ b/src/direction.h @@ -0,0 +1,82 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +#if !defined(DIRECTION_H_INCLUDED) +#define DIRECTION_H_INCLUDED + +//// +//// Includes +//// + +#include "square.h" +#include "types.h" + + +//// +//// Types +//// + +enum Direction { + DIR_E = 0, DIR_N = 1, DIR_NE = 2, DIR_NW = 3, DIR_NONE = 4 +}; + +enum SignedDirection { + SIGNED_DIR_E = 0, SIGNED_DIR_W = 1, + SIGNED_DIR_N = 2, SIGNED_DIR_S = 3, + SIGNED_DIR_NE = 4, SIGNED_DIR_SW = 5, + SIGNED_DIR_NW = 6, SIGNED_DIR_SE = 7, + SIGNED_DIR_NONE = 8 +}; + + +//// +//// Variables +//// + +extern uint8_t DirectionTable[64][64]; +extern uint8_t SignedDirectionTable[64][64]; + + +//// +//// Inline functions +//// + +inline void operator++ (Direction &d, int) { d = Direction(int(d) + 1); } + +inline void operator++ (SignedDirection &d, int) { + d = SignedDirection(int(d) + 1); +} + +inline Direction direction_between_squares(Square s1, Square s2) { + return Direction(DirectionTable[s1][s2]); +} + +inline SignedDirection signed_direction_between_squares(Square s1, Square s2) { + return SignedDirection(SignedDirectionTable[s1][s2]); +} + + +//// +//// Prototypes +//// + +extern void init_direction_table(); + + +#endif // !defined(DIRECTION_H_INCLUDED) diff --git a/src/endgame.cpp b/src/endgame.cpp new file mode 100644 index 00000000..c320ba30 --- /dev/null +++ b/src/endgame.cpp @@ -0,0 +1,867 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +//// +//// Includes +//// + +#include + +#include "bitbase.h" +#include "endgame.h" + + +//// +//// Constants and variables +//// + +/// Evaluation functions + +// Generic "mate lone king" eval: +KXKEvaluationFunction EvaluateKXK = KXKEvaluationFunction(WHITE); +KXKEvaluationFunction EvaluateKKX = KXKEvaluationFunction(BLACK); + +// KBN vs K: +KBNKEvaluationFunction EvaluateKBNK = KBNKEvaluationFunction(WHITE); +KBNKEvaluationFunction EvaluateKKBN = KBNKEvaluationFunction(BLACK); + +// KP vs K: +KPKEvaluationFunction EvaluateKPK = KPKEvaluationFunction(WHITE); +KPKEvaluationFunction EvaluateKKP = KPKEvaluationFunction(BLACK); + +// KR vs KP: +KRKPEvaluationFunction EvaluateKRKP = KRKPEvaluationFunction(WHITE); +KRKPEvaluationFunction EvaluateKPKR = KRKPEvaluationFunction(BLACK); + +// KR vs KB: +KRKBEvaluationFunction EvaluateKRKB = KRKBEvaluationFunction(WHITE); +KRKBEvaluationFunction EvaluateKBKR = KRKBEvaluationFunction(BLACK); + +// KR vs KN: +KRKNEvaluationFunction EvaluateKRKN = KRKNEvaluationFunction(WHITE); +KRKNEvaluationFunction EvaluateKNKR = KRKNEvaluationFunction(BLACK); + +// KQ vs KR: +KQKREvaluationFunction EvaluateKQKR = KQKREvaluationFunction(WHITE); +KQKREvaluationFunction EvaluateKRKQ = KQKREvaluationFunction(BLACK); + + +/// Scaling functions + +// KBP vs K: +KBPKScalingFunction ScaleKBPK = KBPKScalingFunction(WHITE); +KBPKScalingFunction ScaleKKBP = KBPKScalingFunction(BLACK); + +// KQ vs KRP: +KQKRPScalingFunction ScaleKQKRP = KQKRPScalingFunction(WHITE); +KQKRPScalingFunction ScaleKRPKQ = KQKRPScalingFunction(BLACK); + +// KRP vs KR: +KRPKRScalingFunction ScaleKRPKR = KRPKRScalingFunction(WHITE); +KRPKRScalingFunction ScaleKRKRP = KRPKRScalingFunction(BLACK); + +// KRPP vs KRP: +KRPPKRPScalingFunction ScaleKRPPKRP = KRPPKRPScalingFunction(WHITE); +KRPPKRPScalingFunction ScaleKRPKRPP = KRPPKRPScalingFunction(BLACK); + +// King and pawns vs king: +KPsKScalingFunction ScaleKPsK = KPsKScalingFunction(WHITE); +KPsKScalingFunction ScaleKKPs = KPsKScalingFunction(BLACK); + +// KBP vs KB: +KBPKBScalingFunction ScaleKBPKB = KBPKBScalingFunction(WHITE); +KBPKBScalingFunction ScaleKBKBP = KBPKBScalingFunction(BLACK); + +// KBP vs KN: +KBPKNScalingFunction ScaleKBPKN = KBPKNScalingFunction(WHITE); +KBPKNScalingFunction ScaleKNKBP = KBPKNScalingFunction(BLACK); + +// KNP vs K: +KNPKScalingFunction ScaleKNPK = KNPKScalingFunction(WHITE); +KNPKScalingFunction ScaleKKNP = KNPKScalingFunction(BLACK); + +// KPKP +KPKPScalingFunction ScaleKPKPw = KPKPScalingFunction(WHITE); +KPKPScalingFunction ScaleKPKPb = KPKPScalingFunction(BLACK); + + +//// +//// Local definitions +//// + +namespace { + + // Table used to drive the defending king towards the edge of the board + // in KX vs K and KQ vs KR endgames: + const uint8_t MateTable[64] = { + 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 defending king towards a corner square of the + // right color in KBN vs K endgames: + const uint8_t KBNKMateTable[64] = { + 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 + }; + + // The attacking side is given a descending bonus based on distance between + // the two kings in basic endgames: + const int DistanceBonus[8] = {0, 0, 100, 80, 60, 40, 20, 10}; + + // Bitbase for KP vs K: + uint8_t KPKBitbase[24576]; + + // Penalty for big distance between king and knight for the defending king + // and knight in KR vs KN endgames: + const int KRKNKingKnightDistancePenalty[8] = { 0, 0, 4, 10, 20, 32, 48, 70 }; + + // Various inline functions for accessing the above arrays: + + inline Value mate_table(Square s) { + return Value(MateTable[s]); + } + + inline Value kbnk_mate_table(Square s) { + return Value(KBNKMateTable[s]); + } + + inline Value distance_bonus(int d) { + return Value(DistanceBonus[d]); + } + + inline Value krkn_king_knight_distance_penalty(int d) { + return Value(KRKNKingKnightDistancePenalty[d]); + } + + // Function for probing the KP vs K bitbase: + int probe_kpk(Square wksq, Square wpsq, Square bksq, Color stm); + +} + + +//// +//// Functions +//// + +/// Constructors + +EndgameEvaluationFunction::EndgameEvaluationFunction(Color c) { + strongerSide = c; + weakerSide = opposite_color(strongerSide); +} + +KXKEvaluationFunction::KXKEvaluationFunction(Color c) : EndgameEvaluationFunction(c) { } +KBNKEvaluationFunction::KBNKEvaluationFunction(Color c) : EndgameEvaluationFunction(c) { } +KPKEvaluationFunction::KPKEvaluationFunction(Color c) : EndgameEvaluationFunction(c) { } +KRKPEvaluationFunction::KRKPEvaluationFunction(Color c) : EndgameEvaluationFunction(c) { } +KRKBEvaluationFunction::KRKBEvaluationFunction(Color c) : EndgameEvaluationFunction(c) { } +KRKNEvaluationFunction::KRKNEvaluationFunction(Color c) : EndgameEvaluationFunction(c) { } +KQKREvaluationFunction::KQKREvaluationFunction(Color c) : EndgameEvaluationFunction(c) { } + + +ScalingFunction::ScalingFunction(Color c) { + strongerSide = c; + weakerSide = opposite_color(c); +} + +KBPKScalingFunction::KBPKScalingFunction(Color c) : ScalingFunction(c) { } +KQKRPScalingFunction::KQKRPScalingFunction(Color c) : ScalingFunction(c) { } +KRPKRScalingFunction::KRPKRScalingFunction(Color c) : ScalingFunction(c) { } +KRPPKRPScalingFunction::KRPPKRPScalingFunction(Color c) : ScalingFunction(c) { } +KPsKScalingFunction::KPsKScalingFunction(Color c) : ScalingFunction(c) { } +KBPKBScalingFunction::KBPKBScalingFunction(Color c) : ScalingFunction(c) { } +KBPKNScalingFunction::KBPKNScalingFunction(Color c) : ScalingFunction(c) { } +KNPKScalingFunction::KNPKScalingFunction(Color c) : ScalingFunction(c) { } +KPKPScalingFunction::KPKPScalingFunction(Color c) : ScalingFunction(c) { } + + +/// 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. + +Value KXKEvaluationFunction::apply(const Position &pos) { + + assert(pos.non_pawn_material(weakerSide) == Value(0)); + assert(pos.pawn_count(weakerSide) == Value(0)); + + Square winnerKSq = pos.king_square(strongerSide); + Square loserKSq = pos.king_square(weakerSide); + + Value result = + pos.non_pawn_material(strongerSide) + + pos.pawn_count(strongerSide) * PawnValueEndgame + + mate_table(loserKSq) + + distance_bonus(square_distance(winnerKSq, loserKSq)); + + if(pos.queen_count(strongerSide) > 0 || pos.rook_count(strongerSide) > 0 || + pos.bishop_count(strongerSide) > 1) + // TODO: check for two equal-colored bishops! + result += VALUE_KNOWN_WIN; + + return (strongerSide == 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. + +Value KBNKEvaluationFunction::apply(const Position &pos) { + + assert(pos.non_pawn_material(weakerSide) == Value(0)); + assert(pos.pawn_count(weakerSide) == Value(0)); + assert(pos.non_pawn_material(strongerSide) == + KnightValueMidgame + BishopValueMidgame); + assert(pos.bishop_count(strongerSide) == 1); + assert(pos.knight_count(strongerSide) == 1); + assert(pos.pawn_count(strongerSide) == 0); + + Square winnerKSq = pos.king_square(strongerSide); + Square loserKSq = pos.king_square(weakerSide); + Square bishopSquare = pos.bishop_list(strongerSide, 0); + + if(square_color(bishopSquare) == BLACK) { + winnerKSq = flop_square(winnerKSq); + loserKSq = flop_square(loserKSq); + } + + Value result = + VALUE_KNOWN_WIN + distance_bonus(square_distance(winnerKSq, loserKSq)) + + kbnk_mate_table(loserKSq); + + return (strongerSide == pos.side_to_move())? result : -result; +} + + +/// KP vs K. This endgame is evaluated with the help of a bitbase. + +Value KPKEvaluationFunction::apply(const Position &pos) { + + assert(pos.non_pawn_material(strongerSide) == Value(0)); + assert(pos.non_pawn_material(weakerSide) == Value(0)); + assert(pos.pawn_count(strongerSide) == 1); + assert(pos.pawn_count(weakerSide) == 0); + + Square wksq, bksq, wpsq; + Color stm; + + if(strongerSide == WHITE) { + wksq = pos.king_square(WHITE); + bksq = pos.king_square(BLACK); + wpsq = pos.pawn_list(WHITE, 0); + stm = pos.side_to_move(); + } + else { + wksq = flip_square(pos.king_square(BLACK)); + bksq = flip_square(pos.king_square(WHITE)); + wpsq = flip_square(pos.pawn_list(BLACK, 0)); + stm = opposite_color(pos.side_to_move()); + } + + if(square_file(wpsq) >= FILE_E) { + wksq = flop_square(wksq); + bksq = flop_square(bksq); + wpsq = flop_square(wpsq); + } + + if(probe_kpk(wksq, wpsq, bksq, stm)) { + Value result = + VALUE_KNOWN_WIN + PawnValueEndgame + Value(square_rank(wpsq)); + return (strongerSide == pos.side_to_move())? result : -result; + } + + return VALUE_DRAW; +} + + +/// 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. + +Value KRKPEvaluationFunction::apply(const Position &pos) { + + assert(pos.non_pawn_material(strongerSide) == RookValueMidgame); + assert(pos.pawn_count(strongerSide) == 0); + assert(pos.non_pawn_material(weakerSide) == 0); + assert(pos.pawn_count(weakerSide) == 1); + + Square wksq, wrsq, bksq, bpsq; + int tempo = (pos.side_to_move() == strongerSide); + + wksq = pos.king_square(strongerSide); + wrsq = pos.rook_list(strongerSide, 0); + bksq = pos.king_square(weakerSide); + bpsq = pos.pawn_list(weakerSide, 0); + + if(strongerSide == BLACK) { + wksq = flip_square(wksq); + wrsq = flip_square(wrsq); + bksq = flip_square(bksq); + bpsq = flip_square(bpsq); + } + + Square queeningSq = make_square(square_file(bpsq), RANK_1); + Value result; + + // If the stronger side's king is in front of the pawn, it's a win: + if(wksq < bpsq && square_file(wksq) == square_file(bpsq)) + result = RookValueEndgame - Value(square_distance(wksq, bpsq)); + + // If the weaker side's king is too far from the pawn and the rook, + // it's a win: + else if(square_distance(bksq, bpsq) - (tempo^1) >= 3 && + square_distance(bksq, wrsq) >= 3) + result = RookValueEndgame - Value(square_distance(wksq, bpsq)); + + // If the pawn is far advanced and supported by the defending king, + // the position is drawish: + else if(square_rank(bksq) <= RANK_3 && square_distance(bksq, bpsq) == 1 && + square_rank(wksq) >= RANK_4 && + square_distance(wksq, bpsq) - tempo > 2) + result = Value(80 - square_distance(wksq, bpsq) * 8); + + else + result = Value(200) + - Value(square_distance(wksq, bpsq + DELTA_S) * 8) + + Value(square_distance(bksq, bpsq + DELTA_S) * 8) + + Value(square_distance(bpsq, queeningSq) * 8); + + return (strongerSide == 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. + +Value KRKBEvaluationFunction::apply(const Position &pos) { + + assert(pos.non_pawn_material(strongerSide) == RookValueMidgame); + assert(pos.pawn_count(strongerSide) == 0); + assert(pos.non_pawn_material(weakerSide) == BishopValueMidgame); + assert(pos.pawn_count(weakerSide) == 0); + assert(pos.bishop_count(weakerSide) == 1); + + Value result = mate_table(pos.king_square(weakerSide)); + return (pos.side_to_move() == strongerSide)? 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. + +Value KRKNEvaluationFunction::apply(const Position &pos) { + + assert(pos.non_pawn_material(strongerSide) == RookValueMidgame); + assert(pos.pawn_count(strongerSide) == 0); + assert(pos.non_pawn_material(weakerSide) == KnightValueMidgame); + assert(pos.pawn_count(weakerSide) == 0); + assert(pos.knight_count(weakerSide) == 1); + + Square defendingKSq = pos.king_square(weakerSide); + Square nSq = pos.knight_list(weakerSide, 0); + + Value result = Value(10) + mate_table(defendingKSq) + + krkn_king_knight_distance_penalty(square_distance(defendingKSq, nSq)); + + return (strongerSide == 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 be +/// able to win KQ vs KR. + +Value KQKREvaluationFunction::apply(const Position &pos) { + assert(pos.non_pawn_material(strongerSide) == QueenValueMidgame); + assert(pos.pawn_count(strongerSide) == 0); + assert(pos.non_pawn_material(weakerSide) == RookValueMidgame); + assert(pos.pawn_count(weakerSide) == 0); + + Square winnerKSq = pos.king_square(strongerSide); + Square loserKSq = pos.king_square(weakerSide); + + Value result = QueenValueEndgame - RookValueEndgame + + mate_table(loserKSq) + distance_bonus(square_distance(winnerKSq, loserKSq)); + + return (strongerSide == pos.side_to_move())? result : -result; +} + + +/// KBPKScalingFunction scales endgames where the stronger side has king, +/// bishop and one or more pawns. It checks for draws with rook pawns and a +/// bishop of the wrong color. If such a draw is detected, ScaleFactor(0) is +/// returned. If not, the return value is SCALE_FACTOR_NONE, i.e. no scaling +/// will be used. + +ScaleFactor KBPKScalingFunction::apply(const Position &pos) { + assert(pos.non_pawn_material(strongerSide) == BishopValueMidgame); + assert(pos.bishop_count(strongerSide) == 1); + assert(pos.pawn_count(strongerSide) >= 1); + + // No assertions about the material of weakerSide, because we want draws to + // be detected even when the weaker side has some pawns. + + Bitboard pawns = pos.pawns(strongerSide); + File pawnFile = square_file(pos.pawn_list(strongerSide, 0)); + + if((pawnFile == FILE_A || pawnFile == FILE_H) && + (pawns & ~file_bb(pawnFile)) == EmptyBoardBB) { + // All pawns are on a single rook file. + + Square bishopSq = pos.bishop_list(strongerSide, 0); + Square queeningSq = + relative_square(strongerSide, make_square(pawnFile, RANK_8)); + Square kingSq = pos.king_square(weakerSide); + + if(square_color(queeningSq) != square_color(bishopSq) && + file_distance(square_file(kingSq), pawnFile) <= 1) { + // The bishop has the wrong color, and the defending king is on the + // file of the pawn(s) or the neighboring file. Find the rank of the + // frontmost pawn: + + Rank rank; + if(strongerSide == WHITE) { + for(rank = RANK_7; (rank_bb(rank) & pawns) == EmptyBoardBB; rank--); + assert(rank >= RANK_2 && rank <= RANK_7); + } + else { + for(rank = RANK_2; (rank_bb(rank) & pawns) == EmptyBoardBB; rank++); + rank = Rank(rank^7); // HACK + assert(rank >= RANK_2 && rank <= RANK_7); + } + // If the defending king has distance 1 to the promotion square or + // is placed somewhere in front of the pawn, it's a draw. + if(square_distance(kingSq, queeningSq) <= 1 || + pawn_rank(strongerSide, kingSq) >= rank) + return ScaleFactor(0); + } + } + return SCALE_FACTOR_NONE; +} + + +/// KQKRPScalingFunction scales endgames where the stronger side has only +/// king and queen, while the weaker side has at least a rook and a pawn. +/// It tests for fortress draws with a rook on the third rank defended by +/// a pawn. + +ScaleFactor KQKRPScalingFunction::apply(const Position &pos) { + assert(pos.non_pawn_material(strongerSide) == QueenValueMidgame); + assert(pos.queen_count(strongerSide) == 1); + assert(pos.pawn_count(strongerSide) == 0); + assert(pos.rook_count(weakerSide) == 1); + assert(pos.pawn_count(weakerSide) >= 1); + + Square kingSq = pos.king_square(weakerSide); + if(pawn_rank(weakerSide, kingSq) <= RANK_2 && + pawn_rank(weakerSide, pos.king_square(strongerSide)) >= RANK_4 && + (pos.rooks(weakerSide) & relative_rank_bb(weakerSide, RANK_3)) && + (pos.pawns(weakerSide) & relative_rank_bb(weakerSide, RANK_2)) && + (pos.king_attacks(kingSq) & pos.pawns(weakerSide))) { + Square rsq = pos.rook_list(weakerSide, 0); + if(pos.pawn_attacks(strongerSide, rsq) & pos.pawns(weakerSide)) + return ScaleFactor(0); + } + return SCALE_FACTOR_NONE; +} + + +/// KRPKRScalingFunction scales KRP vs KR endgames. 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 not very pretty. + +ScaleFactor KRPKRScalingFunction::apply(const Position &pos) { + assert(pos.non_pawn_material(strongerSide) == RookValueMidgame); + assert(pos.pawn_count(strongerSide) == 1); + assert(pos.non_pawn_material(weakerSide) == RookValueMidgame); + assert(pos.pawn_count(weakerSide) == 0); + + Square wksq = pos.king_square(strongerSide); + Square wrsq = pos.rook_list(strongerSide, 0); + Square wpsq = pos.pawn_list(strongerSide, 0); + Square bksq = pos.king_square(weakerSide); + Square brsq = pos.rook_list(weakerSide, 0); + + // Orient the board in such a way that the stronger side is white, and the + // pawn is on the left half of the board: + if(strongerSide == BLACK) { + wksq = flip_square(wksq); + wrsq = flip_square(wrsq); + wpsq = flip_square(wpsq); + bksq = flip_square(bksq); + brsq = flip_square(brsq); + } + if(square_file(wpsq) > FILE_D) { + wksq = flop_square(wksq); + wrsq = flop_square(wrsq); + wpsq = flop_square(wpsq); + bksq = flop_square(bksq); + brsq = flop_square(brsq); + } + + File f = square_file(wpsq); + Rank r = square_rank(wpsq); + Square queeningSq = make_square(f, RANK_8); + int tempo = (pos.side_to_move() == strongerSide); + + // 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 && + (square_rank(brsq) == RANK_6 || (r <= RANK_3 && + square_rank(wrsq) != RANK_6))) + return ScaleFactor(0); + + // 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 && + square_rank(wksq) + tempo <= RANK_6 && + (square_rank(brsq) == RANK_1 || + (!tempo && abs(square_file(brsq) - f) >= 3))) + return ScaleFactor(0); + + if(r >= RANK_6 && bksq == queeningSq && square_rank(brsq) == RANK_1 && + (!tempo || square_distance(wksq, wpsq) >= 2)) + return ScaleFactor(0); + + // 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) && + square_file(brsq) == FILE_A && + (square_rank(brsq) <= RANK_3 || square_file(wksq) >= FILE_D || + square_rank(wksq) <= RANK_5)) + return ScaleFactor(0); + + // 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 ScaleFactor(0); + + // 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 && square_file(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 && square_file(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))); + + return SCALE_FACTOR_NONE; +} + + +/// KRPPKRPScalingFunction scales KRPP vs KRP endgames. There is only a +/// single pattern: If the stronger side has no pawns and the defending king +/// is actively placed, the position is drawish. + +ScaleFactor KRPPKRPScalingFunction::apply(const Position &pos) { + assert(pos.non_pawn_material(strongerSide) == RookValueMidgame); + assert(pos.pawn_count(strongerSide) == 2); + assert(pos.non_pawn_material(weakerSide) == RookValueMidgame); + assert(pos.pawn_count(weakerSide) == 1); + + Square wpsq1 = pos.pawn_list(strongerSide, 0); + Square wpsq2 = pos.pawn_list(strongerSide, 1); + Square bksq = pos.king_square(weakerSide); + + // Does the stronger side have a passed pawn? + if(pos.pawn_is_passed(strongerSide, wpsq1) || + pos.pawn_is_passed(strongerSide, wpsq2)) + return SCALE_FACTOR_NONE; + + Rank r = Max(pawn_rank(strongerSide, wpsq1), pawn_rank(strongerSide, wpsq2)); + + if(file_distance(bksq, wpsq1) <= 1 && file_distance(bksq, wpsq2) <= 1 + && pawn_rank(strongerSide, 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; +} + + +/// KPsKScalingFunction scales endgames with king and two or more pawns +/// against king. 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. + +ScaleFactor KPsKScalingFunction::apply(const Position &pos) { + assert(pos.non_pawn_material(strongerSide) == Value(0)); + assert(pos.pawn_count(strongerSide) >= 2); + assert(pos.non_pawn_material(weakerSide) == Value(0)); + assert(pos.pawn_count(weakerSide) == 0); + + Bitboard pawns = pos.pawns(strongerSide); + + // Are all pawns on the 'a' file? + if((pawns & ~FileABB) == EmptyBoardBB) { + // Does the defending king block the pawns? + Square ksq = pos.king_square(weakerSide); + if(square_distance(ksq, relative_square(strongerSide, SQ_A8)) <= 1) + return ScaleFactor(0); + else if(square_file(ksq) == FILE_A && + (in_front_bb(strongerSide, ksq) & pawns) == EmptyBoardBB) + return ScaleFactor(0); + else + return SCALE_FACTOR_NONE; + } + // Are all pawns on the 'h' file? + else if((pawns & ~FileHBB) == EmptyBoardBB) { + // Does the defending king block the pawns? + Square ksq = pos.king_square(weakerSide); + if(square_distance(ksq, relative_square(strongerSide, SQ_H8)) <= 1) + return ScaleFactor(0); + else if(square_file(ksq) == FILE_H && + (in_front_bb(strongerSide, ksq) & pawns) == EmptyBoardBB) + return ScaleFactor(0); + else + return SCALE_FACTOR_NONE; + } + else + return SCALE_FACTOR_NONE; +} + + +/// KBPKBScalingFunction scales KBP vs KB endgames. 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. + +ScaleFactor KBPKBScalingFunction::apply(const Position &pos) { + assert(pos.non_pawn_material(strongerSide) == BishopValueMidgame); + assert(pos.bishop_count(strongerSide) == 1); + assert(pos.pawn_count(strongerSide) == 1); + assert(pos.non_pawn_material(weakerSide) == BishopValueMidgame); + assert(pos.bishop_count(weakerSide) == 1); + assert(pos.pawn_count(weakerSide) == 0); + + Square pawnSq = pos.pawn_list(strongerSide, 0); + Square strongerBishopSq = pos.bishop_list(strongerSide, 0); + Square weakerBishopSq = pos.bishop_list(weakerSide, 0); + Square weakerKingSq = pos.king_square(weakerSide); + + // Case 1: Defending king blocks the pawn, and cannot be driven away. + if(square_file(weakerKingSq) == square_file(pawnSq) + && pawn_rank(strongerSide, pawnSq) < pawn_rank(strongerSide, weakerKingSq) + && (square_color(weakerKingSq) != square_color(strongerBishopSq) + || pawn_rank(strongerSide, weakerKingSq) <= RANK_6)) + return ScaleFactor(0); + + // Case 2: Opposite colored bishops. + if(square_color(strongerBishopSq) != square_color(weakerBishopSq)) { + + // 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(pawn_rank(strongerSide, pawnSq) <= RANK_5) + return ScaleFactor(0); + else { + Bitboard ray = + ray_bb(pawnSq, (strongerSide == WHITE)? SIGNED_DIR_N : SIGNED_DIR_S); + if(ray & pos.kings(weakerSide)) + return ScaleFactor(0); + if((pos.bishop_attacks(weakerBishopSq) & ray) + && square_distance(weakerBishopSq, pawnSq) >= 3) + return ScaleFactor(0); + } + } + return SCALE_FACTOR_NONE; +} + + +/// KBPKNScalingFunction scales KBP vs KN endgames. 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. + +ScaleFactor KBPKNScalingFunction::apply(const Position &pos) { + assert(pos.non_pawn_material(strongerSide) == BishopValueMidgame); + assert(pos.bishop_count(strongerSide) == 1); + assert(pos.pawn_count(strongerSide) == 1); + assert(pos.non_pawn_material(weakerSide) == KnightValueMidgame); + assert(pos.knight_count(weakerSide) == 1); + assert(pos.pawn_count(weakerSide) == 0); + + Square pawnSq = pos.pawn_list(strongerSide, 0); + Square strongerBishopSq = pos.bishop_list(strongerSide, 0); + Square weakerKingSq = pos.king_square(weakerSide); + + if(square_file(weakerKingSq) == square_file(pawnSq) + && pawn_rank(strongerSide, pawnSq) < pawn_rank(strongerSide, weakerKingSq) + && (square_color(weakerKingSq) != square_color(strongerBishopSq) + || pawn_rank(strongerSide, weakerKingSq) <= RANK_6)) + return ScaleFactor(0); + + return SCALE_FACTOR_NONE; +} + + +/// KNPKScalingFunction scales KNP vs K endgames. 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. + +ScaleFactor KNPKScalingFunction::apply(const Position &pos) { + assert(pos.non_pawn_material(strongerSide) == KnightValueMidgame); + assert(pos.knight_count(strongerSide) == 1); + assert(pos.pawn_count(strongerSide) == 1); + assert(pos.non_pawn_material(weakerSide) == Value(0)); + assert(pos.pawn_count(weakerSide) == 0); + + Square pawnSq = pos.pawn_list(strongerSide, 0); + Square weakerKingSq = pos.king_square(weakerSide); + + if(pawnSq == relative_square(strongerSide, SQ_A7) && + square_distance(weakerKingSq, relative_square(strongerSide, SQ_A8)) <= 1) + return ScaleFactor(0); + + if(pawnSq == relative_square(strongerSide, SQ_H7) && + square_distance(weakerKingSq, relative_square(strongerSide, SQ_H8)) <= 1) + return ScaleFactor(0); + + return SCALE_FACTOR_NONE; +} + + +/// KPKPScalingFunction scales KP vs KP endgames. 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, she 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). + +ScaleFactor KPKPScalingFunction::apply(const Position &pos) { + assert(pos.non_pawn_material(strongerSide) == Value(0)); + assert(pos.non_pawn_material(weakerSide) == Value(0)); + assert(pos.pawn_count(WHITE) == 1); + assert(pos.pawn_count(BLACK) == 1); + + Square wksq, bksq, wpsq; + Color stm; + + if(strongerSide == WHITE) { + wksq = pos.king_square(WHITE); + bksq = pos.king_square(BLACK); + wpsq = pos.pawn_list(WHITE, 0); + stm = pos.side_to_move(); + } + else { + wksq = flip_square(pos.king_square(BLACK)); + bksq = flip_square(pos.king_square(WHITE)); + wpsq = flip_square(pos.pawn_list(BLACK, 0)); + stm = opposite_color(pos.side_to_move()); + } + + if(square_file(wpsq) >= FILE_E) { + wksq = flop_square(wksq); + bksq = flop_square(bksq); + wpsq = flop_square(wpsq); + } + + // 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(square_rank(wpsq) >= RANK_5 && square_file(wpsq) != FILE_A) + return SCALE_FACTOR_NONE; + + // Probe the KPK bitbase with the weakest side's pawn removed. If it's a + // draw, it's probably at least a draw even with the pawn. + if(probe_kpk(wksq, wpsq, bksq, stm)) + return SCALE_FACTOR_NONE; + else + return ScaleFactor(0); +} + + +/// init_bitbases() is called during program initialization, and simply loads +/// bitbases from disk into memory. At the moment, there is only the bitbase +/// for KP vs K, but we may decide to add other bitbases later. + +void init_bitbases() { + generate_kpk_bitbase(KPKBitbase); +} + + +namespace { + + // Probe the KP vs K bitbase: + + int probe_kpk(Square wksq, Square wpsq, Square bksq, Color stm) { + int wp = int(square_file(wpsq)) + (int(square_rank(wpsq)) - 1) * 4; + int index = int(stm) + 2*int(bksq) + 128*int(wksq) + 8192*wp; + + assert(index >= 0 && index < 24576*8); + return KPKBitbase[index/8] & (1 << (index&7)); + } + +} diff --git a/src/endgame.h b/src/endgame.h new file mode 100644 index 00000000..ae3526fe --- /dev/null +++ b/src/endgame.h @@ -0,0 +1,242 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +#if !defined(ENDGAME_H_INCLUDED) +#define ENDGAME_H_INCLUDED + +//// +//// Includes +//// + +#include "position.h" +#include "scale.h" +#include "value.h" + + +//// +//// Types +//// + +/// Abstract base class for all special endgame evaluation functions: + +class EndgameEvaluationFunction { +public: + EndgameEvaluationFunction(Color c); + virtual ~EndgameEvaluationFunction() { } + + virtual Value apply(const Position &pos) =0; + +protected: + Color strongerSide, weakerSide; +}; + + +/// Subclasses for various concrete endgames: + +// Generic "mate lone king" eval: +class KXKEvaluationFunction : public EndgameEvaluationFunction { +public: + KXKEvaluationFunction(Color c); + Value apply(const Position &pos); +}; + +// KBN vs K: +class KBNKEvaluationFunction : public EndgameEvaluationFunction { +public: + KBNKEvaluationFunction(Color c); + Value apply(const Position &pos); +}; + +// KP vs K: +class KPKEvaluationFunction : public EndgameEvaluationFunction { +public: + KPKEvaluationFunction(Color c); + Value apply(const Position &pos); +}; + +// KR vs KP: +class KRKPEvaluationFunction : public EndgameEvaluationFunction { +public: + KRKPEvaluationFunction(Color c); + Value apply(const Position &pos); +}; + +// KR vs KB: +class KRKBEvaluationFunction : public EndgameEvaluationFunction { +public: + KRKBEvaluationFunction(Color c); + Value apply(const Position &pos); +}; + +// KR vs KN: +class KRKNEvaluationFunction : public EndgameEvaluationFunction { +public: + KRKNEvaluationFunction(Color c); + Value apply(const Position &pos); +}; + +// KQ vs KR: +class KQKREvaluationFunction : public EndgameEvaluationFunction { +public: + KQKREvaluationFunction(Color c); + Value apply(const Position &pos); +}; + + +/// Abstract base class for all evaluation scaling functions: + +class ScalingFunction { +public: + ScalingFunction(Color c); + virtual ~ScalingFunction() { } + + virtual ScaleFactor apply(const Position &pos) =0; + +protected: + Color strongerSide, weakerSide; +}; + + +/// Subclasses for various concrete endgames: + +// KBP vs K: +class KBPKScalingFunction : public ScalingFunction { +public: + KBPKScalingFunction(Color c); + ScaleFactor apply(const Position &pos); +}; + +// KQ vs KRP: +class KQKRPScalingFunction: public ScalingFunction { +public: + KQKRPScalingFunction(Color c); + ScaleFactor apply(const Position &pos); +}; + +// KRP vs KR: +class KRPKRScalingFunction : public ScalingFunction { +public: + KRPKRScalingFunction(Color c); + ScaleFactor apply(const Position &pos); +}; + +// KRPP vs KRP: +class KRPPKRPScalingFunction : public ScalingFunction { +public: + KRPPKRPScalingFunction(Color c); + ScaleFactor apply(const Position &pos); +}; + +// King and pawns vs king: +class KPsKScalingFunction : public ScalingFunction { +public: + KPsKScalingFunction(Color c); + ScaleFactor apply(const Position &pos); +}; + +// KBP vs KB: +class KBPKBScalingFunction : public ScalingFunction { +public: + KBPKBScalingFunction(Color c); + ScaleFactor apply(const Position &pos); +}; + +// KBP vs KN: +class KBPKNScalingFunction : public ScalingFunction { +public: + KBPKNScalingFunction(Color c); + ScaleFactor apply(const Position &pos); +}; + +// KNP vs K: +class KNPKScalingFunction : public ScalingFunction { +public: + KNPKScalingFunction(Color c); + ScaleFactor apply(const Position &pos); +}; + +// KP vs KP: +class KPKPScalingFunction : public ScalingFunction { +public: + KPKPScalingFunction(Color c); + ScaleFactor apply(const Position &pos); +}; + + +//// +//// Constants and variables +//// + +// Generic "mate lone king" eval: +extern KXKEvaluationFunction EvaluateKXK, EvaluateKKX; + +// KBN vs K: +extern KBNKEvaluationFunction EvaluateKBNK, EvaluateKKBN; + +// KP vs K: +extern KPKEvaluationFunction EvaluateKPK, EvaluateKKP; + +// KR vs KP: +extern KRKPEvaluationFunction EvaluateKRKP, EvaluateKPKR; + +// KR vs KB: +extern KRKBEvaluationFunction EvaluateKRKB, EvaluateKBKR; + +// KR vs KN: +extern KRKNEvaluationFunction EvaluateKRKN, EvaluateKNKR; + +// KQ vs KR: +extern KQKREvaluationFunction EvaluateKQKR, EvaluateKRKQ; + +// KBP vs K: +extern KBPKScalingFunction ScaleKBPK, ScaleKKBP; + +// KQ vs KRP: +extern KQKRPScalingFunction ScaleKQKRP, ScaleKRPKQ; + +// KRP vs KR: +extern KRPKRScalingFunction ScaleKRPKR, ScaleKRKRP; + +// KRPP vs KRP: +extern KRPPKRPScalingFunction ScaleKRPPKRP, ScaleKRPKRPP; + +// King and pawns vs king: +extern KPsKScalingFunction ScaleKPsK, ScaleKKPs; + +// KBP vs KB: +extern KBPKBScalingFunction ScaleKBPKB, ScaleKBKBP; + +// KBP vs KN: +extern KBPKNScalingFunction ScaleKBPKN, ScaleKNKBP; + +// KNP vs K: +extern KNPKScalingFunction ScaleKNPK, ScaleKKNP; + +// KP vs KP: +extern KPKPScalingFunction ScaleKPKPw, ScaleKPKPb; + + +//// +//// Prototypes +//// + +extern void init_bitbases(); + + +#endif // !defined(ENDGAME_H_INCLUDED) diff --git a/src/evaluate.cpp b/src/evaluate.cpp new file mode 100644 index 00000000..48cbfc13 --- /dev/null +++ b/src/evaluate.cpp @@ -0,0 +1,1221 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +//// +//// Includes +//// + +#include +#include + +#include "evaluate.h" +#include "material.h" +#include "pawns.h" +#include "scale.h" +#include "thread.h" +#include "ucioption.h" + + +//// +//// Local definitions +//// + +namespace { + + const int Sign[2] = {1, -1}; + + // Evaluation grain size, must be a power of 2. + const int GrainSize = 4; + + // Evaluation weights + int WeightMobilityMidgame = 0x100; + int WeightMobilityEndgame = 0x100; + int WeightPawnStructureMidgame = 0x100; + int WeightPawnStructureEndgame = 0x100; + int WeightPassedPawnsMidgame = 0x100; + int WeightPassedPawnsEndgame = 0x100; + int WeightKingSafety[2] = { 0x100, 0x100 }; + + // Internal evaluation weights. These are applied on top of the evaluation + // weights read from UCI parameters. The purpose is to be able to change + // the evaluation weights while keeping the default values of the UCI + // parameters at 100, which looks prettier. + const int WeightMobilityMidgameInternal = 0x100; + const int WeightMobilityEndgameInternal = 0x100; + const int WeightPawnStructureMidgameInternal = 0x100; + const int WeightPawnStructureEndgameInternal = 0x100; + const int WeightPassedPawnsMidgameInternal = 0x100; + const int WeightPassedPawnsEndgameInternal = 0x100; + const int WeightKingSafetyInternal = 0x100; + + // Knight mobility bonus in middle game and endgame, indexed by the number + // of attacked squares not occupied by friendly piecess. + const Value MidgameKnightMobilityBonus[] = { + Value(-30), Value(-20), Value(-10), Value(0), Value(10), + Value(20), Value(25), Value(30), Value(30) + }; + + const Value EndgameKnightMobilityBonus[] = { + Value(-30), Value(-20), Value(-10), Value(0), Value(10), + Value(20), Value(25), Value(30), Value(30) + }; + + // Bishop mobility bonus in middle game and endgame, indexed by the number + // of attacked squares not occupied by friendly pieces. X-ray attacks through + // queens are also included. + const Value MidgameBishopMobilityBonus[] = { + Value(-30), Value(-15), Value(0), Value(15), Value(30), Value(45), + Value(58), Value(66), Value(72), Value(76), Value(78), Value(80), + Value(81), Value(82), Value(83), Value(83) + }; + + const Value EndgameBishopMobilityBonus[] = { + Value(-30), Value(-15), Value(0), Value(15), Value(30), Value(45), + Value(58), Value(66), Value(72), Value(76), Value(78), Value(80), + Value(81), Value(82), Value(83), Value(83) + }; + + // Rook mobility bonus in middle game and endgame, indexed by the number + // of attacked squares not occupied by friendly pieces. X-ray attacks through + // queens and rooks are also included. + const Value MidgameRookMobilityBonus[] = { + Value(-18), Value(-12), Value(-6), Value(0), Value(6), Value(12), + Value(16), Value(21), Value(24), Value(27), Value(28), Value(29), + Value(30), Value(31), Value(32), Value(33) + }; + + const Value EndgameRookMobilityBonus[] = { + Value(-30), Value(-18), Value(-6), Value(6), Value(18), Value(30), + Value(42), Value(54), Value(66), Value(74), Value(78), Value(80), + Value(81), Value(82), Value(83), Value(83) + }; + + // Queen mobility bonus in middle game and endgame, indexed by the number + // of attacked squares not occupied by friendly pieces. + const Value MidgameQueenMobilityBonus[] = { + Value(-10), Value(-8), Value(-6), Value(-4), Value(-2), Value(0), Value(2), + Value(4), Value(6), Value(8), Value(10), Value(12), Value(13), Value(14), + Value(15), Value(16), Value(16), Value(16), Value(16), Value(16), + Value(16), Value(16), Value(16), Value(16), Value(16), Value(16), + Value(16), Value(16), Value(16), Value(16), Value(16), Value(16) + }; + + const Value EndgameQueenMobilityBonus[] = { + Value(-20), Value(-15), Value(-10), Value(-5), Value(0), Value(5), + Value(10), Value(15), Value(19), Value(23), Value(27), Value(29), + Value(30), Value(30), Value(30), Value(30), Value(30), Value(30), + Value(30), Value(30), Value(30), Value(30), Value(30), Value(30), + Value(30), Value(30), Value(30), Value(30), Value(30), Value(30), + Value(30), Value(30) + }; + + + // Outpost bonuses for knights and bishops, indexed by square (from white's + // point of view). + const Value KnightOutpostBonus[64] = { + Value(0),Value(0),Value(0),Value(0),Value(0),Value(0),Value(0),Value(0), + Value(0),Value(0),Value(0),Value(0),Value(0),Value(0),Value(0),Value(0), + Value(0),Value(0),Value(5),Value(10),Value(10),Value(5),Value(0),Value(0), + Value(0),Value(5),Value(20),Value(30),Value(30),Value(20),Value(5),Value(0), + Value(0),Value(10),Value(30),Value(40),Value(40),Value(30),Value(10),Value(0), + Value(0),Value(5),Value(20),Value(20),Value(20),Value(20),Value(5),Value(0), + Value(0),Value(0),Value(0),Value(0),Value(0),Value(0),Value(0),Value(0), + Value(0),Value(0),Value(0),Value(0),Value(0),Value(0),Value(0),Value(0) + }; + + const Value BishopOutpostBonus[64] = { + Value(0),Value(0),Value(0),Value(0),Value(0),Value(0),Value(0),Value(0), + Value(0),Value(0),Value(0),Value(0),Value(0),Value(0),Value(0),Value(0), + Value(0),Value(0),Value(5),Value(5),Value(5),Value(5),Value(0),Value(0), + Value(0),Value(5),Value(10),Value(10),Value(10),Value(10),Value(5),Value(0), + Value(0),Value(10),Value(20),Value(20),Value(20),Value(20),Value(10),Value(0), + Value(0),Value(5),Value(8),Value(8),Value(8),Value(8),Value(5),Value(0), + Value(0),Value(0),Value(0),Value(0),Value(0),Value(0),Value(0),Value(0), + Value(0),Value(0),Value(0),Value(0),Value(0),Value(0),Value(0),Value(0) + }; + + // Bonus for unstoppable passed pawns: + const Value UnstoppablePawnValue = Value(0x500); + + // Rooks and queens on the 7th rank: + const Value MidgameRookOn7thBonus = Value(50); + const Value EndgameRookOn7thBonus = Value(100); + const Value MidgameQueenOn7thBonus = Value(25); + const Value EndgameQueenOn7thBonus = Value(50); + + // Rooks on open files: + const Value RookOpenFileBonus = Value(40); + const Value RookHalfOpenFileBonus = Value(20); + + // Penalty for rooks trapped inside a friendly king which has lost the + // right to castle: + const Value TrappedRookPenalty = Value(180); + + // Penalty for a bishop on a7/h7 (a2/h2 for black) which is trapped by + // enemy pawns: + const Value TrappedBishopA7H7Penalty = Value(300); + + // Bitboard masks for detecting trapped bishops on a7/h7 (a2/h2 for black): + const Bitboard MaskA7H7[2] = { + ((1ULL << SQ_A7) | (1ULL << SQ_H7)), + ((1ULL << SQ_A2) | (1ULL << SQ_H2)) + }; + + // 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 Value TrappedBishopA1H1Penalty = Value(100); + + // Bitboard masks for detecting trapped bishops on a1/h1 (a8/h8 for black): + const Bitboard MaskA1H1[2] = { + ((1ULL << SQ_A1) | (1ULL << SQ_H1)), + ((1ULL << SQ_A8) | (1ULL << SQ_H8)) + }; + + + /// King safety constants and variables. The king safety scores are taken + /// from the array SafetyTable[]. Various little "meta-bonuses" measuring + /// the strength of the attack are added up into an integer, which is used + /// as an index to SafetyTable[]. + + // Attack weights for each piece type. + const int QueenAttackWeight = 5; + const int RookAttackWeight = 3; + const int BishopAttackWeight = 2; + const int KnightAttackWeight = 2; + + // Bonuses for safe checks for each piece type. + int QueenContactCheckBonus = 4; + int RookContactCheckBonus = 2; + int QueenCheckBonus = 2; + int RookCheckBonus = 1; + int BishopCheckBonus = 1; + int KnightCheckBonus = 1; + int DiscoveredCheckBonus = 3; + + // Scan for queen contact mates? + const bool QueenContactMates = true; + + // Bonus for having a mate threat. + int MateThreatBonus = 3; + + // InitKingDanger[] contains bonuses based on the position of the defending + // king. + const int InitKingDanger[64] = { + 2, 0, 2, 5, 5, 2, 0, 2, + 2, 2, 4, 8, 8, 4, 2, 2, + 7, 10, 12, 12, 12, 12, 10, 7, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + }; + + // SafetyTable[] contains the actual king safety scores. It is initialized + // in init_safety(). + Value SafetyTable[100]; + + + // Pawn and material hash tables, indexed by the current thread id: + PawnInfoTable *PawnTable[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + MaterialInfoTable *MaterialTable[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + + // Sizes of pawn and material hash tables: + const int PawnTableSize = 16384; + const int MaterialTableSize = 1024; + + // Array which gives the number of nonzero bits in an 8-bit integer: + uint8_t BitCount8Bit[256]; + + // Function prototypes: + void evaluate_knight(const Position &p, Square s, Color us, EvalInfo &ei); + void evaluate_bishop(const Position &p, Square s, Color us, EvalInfo &ei); + void evaluate_rook(const Position &p, Square s, Color us, EvalInfo &ei); + void evaluate_queen(const Position &p, Square s, Color us, EvalInfo &ei); + void evaluate_king(const Position &p, Square s, Color us, EvalInfo &ei); + + void evaluate_passed_pawns(const Position &pos, EvalInfo &ei); + void evaluate_trapped_bishop_a7h7(const Position &pos, Square s, Color us, + EvalInfo &ei); + void evaluate_trapped_bishop_a1h1(const Position &pos, Square s, Color us, + EvalInfo &ei); + + Value apply_weight(Value v, int w); + Value scale_by_game_phase(Value mv, Value ev, Phase ph, ScaleFactor sf[]); + + int count_1s_8bit(int b); + + int compute_weight(int uciWeight, int internalWeight); + void init_safety(); + +} + + +//// +//// Functions +//// + +/// evaluate() is the main evaluation function. It always computes two +/// values, an endgame score and a middle game score, and interpolates +/// between them based on the remaining material. + +Value evaluate(const Position &pos, EvalInfo &ei, int threadID) { + Color stm; + Square s; + ScaleFactor factor[2] = {SCALE_FACTOR_NORMAL, SCALE_FACTOR_NORMAL}; + Phase phase; + + memset(&ei, 0, sizeof(EvalInfo)); + + assert(pos.is_ok()); + assert(threadID >= 0 && threadID < THREAD_MAX); + + stm = pos.side_to_move(); + + // Initialize by reading the incrementally updated scores included in the + // position object (material + piece square tables): + ei.mgValue = pos.mg_value(); + ei.egValue = pos.eg_value(); + + // Probe the material hash table: + ei.mi = MaterialTable[threadID]->get_material_info(pos); + ei.mgValue += ei.mi->mg_value(); + ei.egValue += ei.mi->eg_value(); + + factor[WHITE] = ei.mi->scale_factor(pos, WHITE); + factor[BLACK] = ei.mi->scale_factor(pos, BLACK); + + // 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); + + phase = pos.game_phase(); + + // Probe the pawn hash table: + ei.pi = PawnTable[threadID]->get_pawn_info(pos); + ei.mgValue += apply_weight(ei.pi->mg_value(), WeightPawnStructureMidgame); + ei.egValue += apply_weight(ei.pi->eg_value(), WeightPawnStructureEndgame); + + // Initialize king attack bitboards and king attack zones for both sides: + ei.attackedBy[WHITE][KING] = pos.king_attacks(pos.king_square(WHITE)); + ei.attackedBy[BLACK][KING] = pos.king_attacks(pos.king_square(BLACK)); + ei.attackZone[WHITE] = + ei.attackedBy[BLACK][KING] | (ei.attackedBy[BLACK][KING] >> 8); + ei.attackZone[BLACK] = + ei.attackedBy[WHITE][KING] | (ei.attackedBy[WHITE][KING] << 8); + + // Initialize pawn attack bitboards for both sides: + ei.attackedBy[WHITE][PAWN] = + ((pos.pawns(WHITE) << 9) & ~FileABB) | ((pos.pawns(WHITE) << 7) & ~FileHBB); + ei.attackCount[WHITE] += + count_1s_max_15(ei.attackedBy[WHITE][PAWN] & ei.attackedBy[BLACK][KING])/2; + ei.attackedBy[BLACK][PAWN] = + ((pos.pawns(BLACK) >> 7) & ~FileABB) | ((pos.pawns(BLACK) >> 9) & ~FileHBB); + ei.attackCount[BLACK] += + count_1s_max_15(ei.attackedBy[BLACK][PAWN] & ei.attackedBy[WHITE][KING])/2; + + // Evaluate pieces: + for(Color c = WHITE; c <= BLACK; c++) { + Bitboard b; + + // Knights + for(int i = 0; i < pos.knight_count(c); i++) { + s = pos.knight_list(c, i); + evaluate_knight(pos, s, c, ei); + } + + // Bishops + for(int i = 0; i < pos.bishop_count(c); i++) { + s = pos.bishop_list(c, i); + evaluate_bishop(pos, s, c, ei); + } + + // Rooks + for(int i = 0; i < pos.rook_count(c); i++) { + s = pos.rook_list(c, i); + evaluate_rook(pos, s, c, ei); + } + + // Queens + for(int i = 0; i < pos.queen_count(c); i++) { + s = pos.queen_list(c, i); + evaluate_queen(pos, s, c, ei); + } + + // Some special patterns: + + // Trapped bishops on a7/h7/a2/h2 + b = pos.bishops(c) & MaskA7H7[c]; + while(b) { + s = pop_1st_bit(&b); + evaluate_trapped_bishop_a7h7(pos, s, c, ei); + } + + // Trapped bishops on a1/h1/a8/h8 in Chess960: + if(Chess960) { + b = pos.bishops(c) & MaskA1H1[c]; + while(b) { + s = pop_1st_bit(&b); + evaluate_trapped_bishop_a1h1(pos, s, c, ei); + } + } + + ei.attackedBy[c][0] = + ei.attackedBy[c][PAWN] | ei.attackedBy[c][KNIGHT] + | ei.attackedBy[c][BISHOP] | ei.attackedBy[c][ROOK] + | ei.attackedBy[c][QUEEN] | ei.attackedBy[c][KING]; + } + + // Kings. Kings are evaluated after all other pieces for both sides, + // because we need complete attack information for all pieces when computing + // the king safety evaluation. + for(Color c = WHITE; c <= BLACK; c++) { + s = pos.king_square(c); + evaluate_king(pos, s, c, ei); + } + + // Evaluate passed pawns. We evaluate passed pawns for both sides at once, + // because we need to know which side promotes first in positions where + // both sides have an unstoppable passed pawn. + if(ei.pi->passed_pawns()) + evaluate_passed_pawns(pos, ei); + + // Middle-game specific evaluation terms + if(phase > PHASE_ENDGAME) { + + // Pawn storms in positions with opposite castling. + if(square_file(pos.king_square(WHITE)) >= FILE_E && + square_file(pos.king_square(BLACK)) <= FILE_D) + ei.mgValue += + ei.pi->queenside_storm_value(WHITE) - + ei.pi->kingside_storm_value(BLACK); + else if(square_file(pos.king_square(WHITE)) <= FILE_D && + square_file(pos.king_square(BLACK)) >= FILE_E) + ei.mgValue += + ei.pi->kingside_storm_value(WHITE) - + ei.pi->queenside_storm_value(BLACK); + } + + // Mobility + ei.mgValue += apply_weight(ei.mgMobility, WeightMobilityMidgame); + ei.egValue += apply_weight(ei.egMobility, WeightMobilityEndgame); + + // If we don't already have an unusual scale factor, check for opposite + // colored bishop endgames, and use a lower scale for those: + if(phase < PHASE_MIDGAME && pos.opposite_colored_bishops() + && ((factor[WHITE] == SCALE_FACTOR_NORMAL && ei.egValue > Value(0)) || + (factor[BLACK] == SCALE_FACTOR_NORMAL && ei.egValue < Value(0)))) { + if(pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK) == + 2*BishopValueMidgame) { + // Only the two bishops + if(pos.pawn_count(WHITE) + pos.pawn_count(BLACK) == 1) { + // KBP vs KB with only a single pawn; almost certainly a draw. + if(factor[WHITE] == SCALE_FACTOR_NORMAL) + factor[WHITE] = ScaleFactor(8); + if(factor[BLACK] == SCALE_FACTOR_NORMAL) + factor[BLACK] = ScaleFactor(8); + } + else { + // At least two pawns + if(factor[WHITE] == SCALE_FACTOR_NORMAL) + factor[WHITE] = ScaleFactor(32); + if(factor[BLACK] == SCALE_FACTOR_NORMAL) + factor[BLACK] = 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. + if(factor[WHITE] == SCALE_FACTOR_NORMAL) + factor[WHITE] = ScaleFactor(50); + if(factor[BLACK] == SCALE_FACTOR_NORMAL) + factor[BLACK] = ScaleFactor(50); + } + } + + // Interpolate between the middle game and the endgame score, and + // return: + Value value = scale_by_game_phase(ei.mgValue, ei.egValue, phase, factor); + + if(ei.mateThreat[stm] != MOVE_NONE) + return 8 * QueenValueMidgame - Sign[stm] * value; + else + return Sign[stm] * value; +} + + +/// quick_evaluate() does a very approximate evaluation of the current position. +/// It currently considers only material and piece square table scores. Perhaps +/// we should add scores from the pawn and material hash tables? + +Value quick_evaluate(const Position &pos) { + Color stm; + Value mgValue, egValue; + ScaleFactor factor[2] = {SCALE_FACTOR_NORMAL, SCALE_FACTOR_NORMAL}; + Phase phase; + + assert(pos.is_ok()); + + stm = pos.side_to_move(); + + mgValue = pos.mg_value(); + egValue = pos.eg_value(); + phase = pos.game_phase(); + + Value value = scale_by_game_phase(mgValue, egValue, phase, factor); + + return Sign[stm] * value; +} + + +/// init_eval() initializes various tables used by the evaluation function. + +void init_eval(int threads) { + assert(threads <= THREAD_MAX); + + for(int i = 0; i < threads; i++) { + if(PawnTable[i] == NULL) + PawnTable[i] = new PawnInfoTable(PawnTableSize); + if(MaterialTable[i] == NULL) + MaterialTable[i] = new MaterialInfoTable(MaterialTableSize); + } + for(int i = threads; i < THREAD_MAX; i++) { + if(PawnTable[i] != NULL) { + delete PawnTable[i]; + PawnTable[i] = NULL; + } + if(MaterialTable[i] != NULL) { + delete MaterialTable[i]; + MaterialTable[i] = NULL; + } + } + + for(Bitboard b = 0ULL; b < 256ULL; b++) + BitCount8Bit[b] = count_1s(b); +} + + +/// quit_eval() releases heap-allocated memory at program termination. + +void quit_eval() { + for(int i = 0; i < THREAD_MAX; i++) { + delete PawnTable[i]; + delete MaterialTable[i]; + } +} + + +/// read_weights() reads evaluation weights from the corresponding UCI +/// parameters. + +void read_weights(Color sideToMove) { + WeightMobilityMidgame = + compute_weight(get_option_value_int("Mobility (Middle Game)"), + WeightMobilityMidgameInternal); + WeightMobilityEndgame = + compute_weight(get_option_value_int("Mobility (Endgame)"), + WeightMobilityEndgameInternal); + WeightPawnStructureMidgame = + compute_weight(get_option_value_int("Pawn Structure (Middle Game)"), + WeightPawnStructureMidgameInternal); + WeightPawnStructureEndgame = + compute_weight(get_option_value_int("Pawn Structure (Endgame)"), + WeightPawnStructureEndgameInternal); + WeightPassedPawnsMidgame = + compute_weight(get_option_value_int("Passed Pawns (Middle Game)"), + WeightPassedPawnsMidgameInternal); + WeightPassedPawnsEndgame = + compute_weight(get_option_value_int("Passed Pawns (Endgame)"), + WeightPassedPawnsEndgameInternal); + WeightKingSafety[sideToMove] = + compute_weight(get_option_value_int("Cowardice"), WeightKingSafetyInternal); + WeightKingSafety[opposite_color(sideToMove)] = + compute_weight(get_option_value_int("Aggressiveness"), + WeightKingSafetyInternal); + WeightKingSafety[opposite_color(sideToMove)] = + (get_option_value_int("Aggressiveness") * 0x100) / 100; + + init_safety(); +} + + +namespace { + + // evaluate_knight() assigns bonuses and penalties to a knight of a given + // color on a given square. + + void evaluate_knight(const Position &p, Square s, Color us, EvalInfo &ei) { + + Color them = opposite_color(us); + Bitboard b = p.knight_attacks(s); + ei.attackedBy[us][KNIGHT] |= b; + + // King attack + if(b & ei.attackZone[us]) { + ei.attackCount[us]++; + ei.attackWeight[us] += KnightAttackWeight; + Bitboard bb = (b & ei.attackedBy[them][KING]); + if(bb) ei.attacked[us] += count_1s_max_15(bb); + } + + // Mobility + int mob = count_1s_max_15(b & ~p.pieces_of_color(us)); + ei.mgMobility += Sign[us] * MidgameKnightMobilityBonus[mob]; + ei.egMobility += Sign[us] * EndgameKnightMobilityBonus[mob]; + + // Knight outposts: + if(p.square_is_weak(s, them)) { + Value v, bonus; + + // Initial bonus based on square: + v = bonus = KnightOutpostBonus[relative_square(us, s)]; + + // Increase bonus if supported by pawn, especially if the opponent has + // no minor piece which can exchange the outpost piece: + if(v && p.pawn_attacks(them, s) & p.pawns(us)) { + bonus += v/2; + if(p.knight_count(them) == 0 && + (SquaresByColorBB[square_color(s)] & + p.bishops(them)) == EmptyBoardBB) { + bonus += v; + } + } + + ei.mgValue += Sign[us] * bonus; + ei.egValue += Sign[us] * bonus; + } + } + + + // evaluate_bishop() assigns bonuses and penalties to a bishop of a given + // color on a given square. + + void evaluate_bishop(const Position &p, Square s, Color us, EvalInfo &ei) { + + Color them = opposite_color(us); + Bitboard b = + bishop_attacks_bb(s, p.occupied_squares() & ~p.queens(us)); + + ei.attackedBy[us][BISHOP] |= b; + + // King attack + if(b & ei.attackZone[us]) { + ei.attackCount[us]++; + ei.attackWeight[us] += BishopAttackWeight; + Bitboard bb = (b & ei.attackedBy[them][KING]); + if(bb) ei.attacked[us] += count_1s_max_15(bb); + } + + // Mobility: + int mob = count_1s_max_15(b & ~p.pieces_of_color(us)); + ei.mgMobility += Sign[us] * MidgameBishopMobilityBonus[mob]; + ei.egMobility += Sign[us] * EndgameBishopMobilityBonus[mob]; + + // Bishop outposts: + if(p.square_is_weak(s, them)) { + Value v, bonus; + + // Initial bonus based on square: + v = bonus = BishopOutpostBonus[relative_square(us, s)]; + + // Increase bonus if supported by pawn, especially if the opponent has + // no minor piece which can exchange the outpost piece: + if(v && p.pawn_attacks(them, s) & p.pawns(us)) { + bonus += v/2; + if(p.knight_count(them) == 0 && + (SquaresByColorBB[square_color(s)] & + p.bishops(them)) == EmptyBoardBB) { + bonus += v; + } + } + + ei.mgValue += Sign[us] * bonus; + ei.egValue += Sign[us] * bonus; + } + } + + + // evaluate_rook() assigns bonuses and penalties to a rook of a given + // color on a given square. + + void evaluate_rook(const Position &p, Square s, Color us, EvalInfo &ei) { + + Color them = opposite_color(us); + + // Open and half-open files: + File f = square_file(s); + if(ei.pi->file_is_half_open(us, f)) { + if(ei.pi->file_is_half_open(them, f)) { + ei.mgValue += Sign[us] * RookOpenFileBonus; + ei.egValue += Sign[us] * RookOpenFileBonus; + } + else { + ei.mgValue += Sign[us] * RookHalfOpenFileBonus; + ei.egValue += Sign[us] * RookHalfOpenFileBonus; + } + } + + // Rook on 7th rank: + if(pawn_rank(us, s) == RANK_7 && + pawn_rank(us, p.king_square(them)) == RANK_8) { + ei.mgValue += Sign[us] * MidgameRookOn7thBonus; + ei.egValue += Sign[us] * EndgameRookOn7thBonus; + } + + //Bitboard b = p.rook_attacks(s); + Bitboard b = + rook_attacks_bb(s, p.occupied_squares() & ~p.rooks_and_queens(us)); + ei.attackedBy[us][ROOK] |= b; + + // King attack + if(b & ei.attackZone[us]) { + ei.attackCount[us]++; + ei.attackWeight[us] += RookAttackWeight; + Bitboard bb = (b & ei.attackedBy[them][KING]); + if(bb) ei.attacked[us] += count_1s_max_15(bb); + } + + // Mobility + int mob = count_1s_max_15(b & ~p.pieces_of_color(us)); + ei.mgMobility += Sign[us] * MidgameRookMobilityBonus[mob]; + ei.egMobility += Sign[us] * EndgameRookMobilityBonus[mob]; + + // Penalize rooks which are trapped inside a king which has lost the + // right to castle: + if(mob <= 6 && !ei.pi->file_is_half_open(us, f)) { + Square ksq = p.king_square(us); + if(square_file(ksq) >= FILE_E && square_file(s) > square_file(ksq) && + (pawn_rank(us, ksq) == RANK_1 || square_rank(ksq) == square_rank(s))) { + // Is there a half-open file between the king and the edge of the + // board? + if(!(ei.pi->has_open_file_to_right(us, square_file(ksq)))) { + ei.mgValue -= p.can_castle(us)? + Sign[us] * ((TrappedRookPenalty - mob * 16) / 2) : + Sign[us] * (TrappedRookPenalty - mob * 16); + } + } + else if(square_file(ksq) <= FILE_D && square_file(s) < square_file(ksq) + && (pawn_rank(us, ksq) == RANK_1 || + square_rank(ksq) == square_rank(s))) { + // Is there a half-open file between the king and the edge of the + // board? + if(!(ei.pi->has_open_file_to_left(us, square_file(ksq)))) { + ei.mgValue -= p.can_castle(us)? + Sign[us] * ((TrappedRookPenalty - mob * 16) / 2) : + Sign[us] * (TrappedRookPenalty - mob * 16); + } + } + } + } + + + // evaluate_queen() assigns bonuses and penalties to a queen of a given + // color on a given square. + + void evaluate_queen(const Position &p, Square s, Color us, EvalInfo &ei) { + + Color them = opposite_color(us); + + // Queen on 7th rank: + if(pawn_rank(us, s) == RANK_7 && + pawn_rank(us, p.king_square(them)) == RANK_8) { + ei.mgValue += Sign[us] * MidgameQueenOn7thBonus; + ei.egValue += Sign[us] * EndgameQueenOn7thBonus; + } + + Bitboard b = p.queen_attacks(s); + ei.attackedBy[us][QUEEN] |= b; + + // King attack + if(b & ei.attackZone[us]) { + ei.attackCount[us]++; + ei.attackWeight[us] += QueenAttackWeight; + Bitboard bb = (b & ei.attackedBy[them][KING]); + if(bb) ei.attacked[us] += count_1s_max_15(bb); + } + + // Mobility + int mob = count_1s(b & ~p.pieces_of_color(us)); + ei.mgMobility += Sign[us] * MidgameQueenMobilityBonus[mob]; + ei.egMobility += Sign[us] * EndgameQueenMobilityBonus[mob]; + } + + + // evaluate_king() assigns bonuses and penalties to a king of a given + // color on a given square. + + void evaluate_king(const Position &p, Square s, Color us, EvalInfo &ei) { + + int shelter = 0, sign = Sign[us]; + + // King shelter. + if(pawn_rank(us, s) <= RANK_4) { + Bitboard pawns = p.pawns(us) & this_and_neighboring_files_bb(s); + Rank r = square_rank(s); + for(int i = 0; i < 3; i++) + shelter += count_1s_8bit(pawns >> ((r+(i+1)*sign) * 8)) * (64>>i); + ei.mgValue += sign * Value(shelter); + } + + // King safety. This is quite complicated, and is almost certainly far + // from optimally tuned. + Color them = opposite_color(us); + if(p.queen_count(them) >= 1 && ei.attackCount[them] >= 2 + && p.non_pawn_material(them) >= QueenValueMidgame + RookValueMidgame + && ei.attacked[them]) { + + // Is it the attackers turn to move? + bool sente = (them == p.side_to_move()); + + // Find the attacked squares around the king which has no defenders + // apart from the king itself: + Bitboard undefended = + ei.attacked_by(them) & ~ei.attacked_by(us, PAWN) + & ~ei.attacked_by(us, KNIGHT) & ~ei.attacked_by(us, BISHOP) + & ~ei.attacked_by(us, ROOK) & ~ei.attacked_by(us, QUEEN) + & ei.attacked_by(us, KING); + Bitboard occ = p.occupied_squares(), b, b2; + + // Initialize the 'attackUnits' variable, which is used later on as an + // index to the SafetyTable[] array. The initial is based on the number + // and types of the attacking pieces, the number of attacked and + // undefended squares around the king, the square of the king, and the + // quality of the pawn shelter. + int attackUnits = + Min((ei.attackCount[them] * ei.attackWeight[them]) / 2, 25) + + (ei.attacked[them] + count_1s_max_15(undefended)) * 3 + + InitKingDanger[relative_square(us, s)] - shelter / 32; + + // Analyse safe queen contact checks: + b = undefended & ei.attacked_by(them, QUEEN) & ~p.pieces_of_color(them); + if(b) { + Bitboard attackedByOthers = + ei.attacked_by(them, PAWN) | ei.attacked_by(them, KNIGHT) + | ei.attacked_by(them, BISHOP) | ei.attacked_by(them, ROOK); + b &= attackedByOthers; + if(b) { + // The bitboard b now contains the squares available for safe queen + // contact checks. + int count = count_1s_max_15(b); + attackUnits += QueenContactCheckBonus * count * (sente? 2 : 1); + + // Is there a mate threat? + if(QueenContactMates && !p.is_check()) { + Bitboard escapeSquares = + p.king_attacks(s) & ~p.pieces_of_color(us) & ~attackedByOthers; + while(b) { + Square from, to = pop_1st_bit(&b); + if(!(escapeSquares + & ~queen_attacks_bb(to, occ & clear_mask_bb(s)))) { + // We have a mate, unless the queen is pinned or there + // is an X-ray attack through the queen. + for(int i = 0; i < p.queen_count(them); i++) { + from = p.queen_list(them, i); + if(bit_is_set(p.queen_attacks(from), to) + && !bit_is_set(p.pinned_pieces(them), from) + && !(rook_attacks_bb(to, occ & clear_mask_bb(from)) + & p.rooks_and_queens(us)) + && !(rook_attacks_bb(to, occ & clear_mask_bb(from)) + & p.rooks_and_queens(us))) + ei.mateThreat[them] = make_move(from, to); + } + } + } + } + } + } + + // Analyse safe rook contact checks: + if(RookContactCheckBonus) { + b = undefended & ei.attacked_by(them, ROOK) & ~p.pieces_of_color(them); + if(b) { + Bitboard attackedByOthers = + ei.attacked_by(them, PAWN) | ei.attacked_by(them, KNIGHT) + | ei.attacked_by(them, BISHOP) | ei.attacked_by(them, QUEEN); + b &= attackedByOthers; + if(b) { + int count = count_1s_max_15(b); + attackUnits += (RookContactCheckBonus * count * (sente? 2 : 1)); + } + } + } + + // Analyse safe distance checks: + if(QueenCheckBonus > 0 || RookCheckBonus > 0) { + b = p.rook_attacks(s) & ~p.pieces_of_color(them) & ~ei.attacked_by(us); + + // Queen checks + b2 = b & ei.attacked_by(them, QUEEN); + if(b2) attackUnits += QueenCheckBonus * count_1s_max_15(b2); + + // Rook checks + b2 = b & ei.attacked_by(them, ROOK); + if(b2) attackUnits += RookCheckBonus * count_1s_max_15(b2); + } + if(QueenCheckBonus > 0 || BishopCheckBonus > 0) { + b = p.bishop_attacks(s) & ~p.pieces_of_color(them) & ~ei.attacked_by(us); + // Queen checks + b2 = b & ei.attacked_by(them, QUEEN); + if(b2) attackUnits += QueenCheckBonus * count_1s_max_15(b2); + + // Bishop checks + b2 = b & ei.attacked_by(them, BISHOP); + if(b2) attackUnits += BishopCheckBonus * count_1s_max_15(b2); + } + if(KnightCheckBonus > 0) { + b = p.knight_attacks(s) & ~p.pieces_of_color(them) & ~ei.attacked_by(us); + // Knight checks + b2 = b & ei.attacked_by(them, KNIGHT); + if(b2) attackUnits += KnightCheckBonus * count_1s_max_15(b2); + } + + // Analyse discovered checks (only for non-pawns right now, consider + // adding pawns later). + if(DiscoveredCheckBonus) { + b = p.discovered_check_candidates(them) & ~p.pawns(); + if(b) + attackUnits += + DiscoveredCheckBonus * count_1s_max_15(b) * (sente? 2 : 1); + } + + // Has a mate threat been found? We don't do anything here if the + // side with the mating move is the side to move, because in that + // case the mating side will get a huge bonus at the end of the main + // evaluation function instead. + if(ei.mateThreat[them] != MOVE_NONE) + attackUnits += MateThreatBonus; + + // Ensure that attackUnits is between 0 and 99, in order to avoid array + // out of bounds errors: + if(attackUnits < 0) attackUnits = 0; + if(attackUnits >= 100) attackUnits = 99; + + // Finally, extract the king safety score from the SafetyTable[] array. + // Add the score to the evaluation, and also to ei.futilityMargin. The + // reason for adding the king safety score to the futility margin is + // that the king safety scores can sometimes be very big, and that + // capturing a single attacking piece can therefore result in a score + // change far bigger than the value of the captured piece. + Value v = apply_weight(SafetyTable[attackUnits], WeightKingSafety[us]); + ei.mgValue -= sign * v; + if(us == p.side_to_move()) + ei.futilityMargin += v; + } + } + + + // evaluate_passed_pawns() evaluates the passed pawns for both sides. + + void evaluate_passed_pawns(const Position &pos, EvalInfo &ei) { + bool hasUnstoppable[2] = {false, false}; + int movesToGo[2] = {100, 100}; + + for(Color us = WHITE; us <= BLACK; us++) { + Color them = opposite_color(us); + Square ourKingSq = pos.king_square(us); + Square theirKingSq = pos.king_square(them); + Bitboard b = ei.pi->passed_pawns() & pos.pawns(us), b2, b3, b4; + + while(b) { + Square s = pop_1st_bit(&b); + assert(pos.piece_on(s) == pawn_of_color(us)); + assert(pos.pawn_is_passed(us, s)); + + int r = int(pawn_rank(us, s) - RANK_2); + int tr = Max(0, r * (r-1)); + Square blockSq = s + pawn_push(us); + + // Base bonus based on rank: + Value mbonus = Value(20 * tr); + Value ebonus = Value(10 + r * r * 10); + + // Adjust bonus based on king proximity: + ebonus -= Value(square_distance(ourKingSq, blockSq) * 3 * tr); + ebonus -= + Value(square_distance(ourKingSq, blockSq + pawn_push(us)) * 1 * tr); + ebonus += Value(square_distance(theirKingSq, blockSq) * 6 * tr); + + // If the pawn is free to advance, increase bonus: + if(pos.square_is_empty(blockSq)) { + + b2 = squares_in_front_of(us, s); + b3 = b2 & ei.attacked_by(them); + b4 = b2 & ei.attacked_by(us); + if((b2 & pos.pieces_of_color(them)) == EmptyBoardBB) { + // There are no enemy pieces in the pawn's path! Are any of the + // squares in the pawn's path attacked by the enemy? + if(b3 == EmptyBoardBB) + // No enemy attacks, huge bonus! + ebonus += Value(tr * ((b2 == b4)? 17 : 15)); + else + // OK, there are enemy attacks. Are those squares which are + // attacked by the enemy also attacked by us? If yes, big bonus + // (but smaller than when there are no enemy attacks), if no, + // somewhat smaller bonus. + ebonus += Value(tr * (((b3 & b4) == b3)? 13 : 8)); + } + else { + // There are some enemy pieces in the pawn's path. While this is + // sad, we still assign a moderate bonus if all squares in the path + // which are either occupied by or attacked by enemy pieces are + // also attacked by us. + if(((b3 | (b2 & pos.pieces_of_color(them))) & ~b4) == EmptyBoardBB) + ebonus += Value(tr * 6); + } + // At last, add a small bonus when there are no *friendly* pieces + // in the pawn's path: + if((b2 & pos.pieces_of_color(us)) == EmptyBoardBB) + ebonus += Value(tr); + } + + // If the pawn is supported by a friendly pawn, increase bonus. + b2 = pos.pawns(us) & neighboring_files_bb(s); + if(b2 & rank_bb(s)) + ebonus += Value(r * 20); + else if(pos.pawn_attacks(them, s) & b2) + ebonus += Value(r * 12); + + // If the other side has only a king, check whether the pawn is + // unstoppable: + if(pos.non_pawn_material(them) == Value(0)) { + Square qsq; + int d; + + qsq = relative_square(us, make_square(square_file(s), RANK_8)); + d = square_distance(s, qsq) - square_distance(theirKingSq, qsq) + + ((us == pos.side_to_move())? 0 : 1); + + if(d < 0) { + int mtg = RANK_8 - pawn_rank(us, s); + int blockerCount = + count_1s_max_15(squares_in_front_of(us,s)&pos.occupied_squares()); + mtg += blockerCount; + d += blockerCount; + if(d < 0) { + hasUnstoppable[us] = true; + movesToGo[us] = Min(movesToGo[us], mtg); + } + } + } + // Rook pawns are a special case: They are sometimes worse, and + // sometimes better than other passed pawns. It is difficult to find + // good rules for determining whether they are good or bad. For now, + // we try the following: Increase the value for rook pawns if the + // other side has no pieces apart from a knight, and decrease the + // value if the other side has a rook or queen. + if(square_file(s) == FILE_A || square_file(s) == FILE_H) { + if(pos.non_pawn_material(them) == KnightValueMidgame + && pos.knight_count(them) == 1) + ebonus += ebonus / 4; + else if(pos.rooks_and_queens(them)) + ebonus -= ebonus / 4; + } + + // Add the scores for this pawn to the middle game and endgame eval. + ei.mgValue += apply_weight(Sign[us] * mbonus, WeightPassedPawnsMidgame); + ei.egValue += apply_weight(Sign[us] * ebonus, WeightPassedPawnsEndgame); + } + } + + // Does either side have an unstoppable passed pawn? + if(hasUnstoppable[WHITE] && !hasUnstoppable[BLACK]) + ei.egValue += UnstoppablePawnValue - Value(0x40 * movesToGo[WHITE]); + else if(hasUnstoppable[BLACK] && !hasUnstoppable[WHITE]) + ei.egValue -= UnstoppablePawnValue - Value(0x40 * movesToGo[BLACK]); + else if(hasUnstoppable[BLACK] && hasUnstoppable[WHITE]) { + // Both sides have unstoppable pawns! Try to find out who queens + // first. We begin by transforming 'movesToGo' to the number of + // plies until the pawn queens for both sides: + movesToGo[WHITE] *= 2; + movesToGo[BLACK] *= 2; + movesToGo[pos.side_to_move()]--; + + // If one side queens at least three plies before the other, that + // side wins: + if(movesToGo[WHITE] <= movesToGo[BLACK] - 3) + ei.egValue += UnstoppablePawnValue - Value(0x40 * (movesToGo[WHITE]/2)); + else if(movesToGo[BLACK] <= movesToGo[WHITE] - 3) + ei.egValue -= UnstoppablePawnValue - Value(0x40 * (movesToGo[BLACK]/2)); + + // We could also add some rules about the situation when one side + // queens exactly one ply before the other: Does the first queen + // check the opponent's king, or attack the opponent's queening square? + // This is slightly tricky to get right, because it is possible that + // the opponent's king has moved somewhere before the first pawn queens. + } + } + + + // evaluate_trapped_bishop_a7h7() determines whether a bishop on a7/h7 + // (a2/h2 for black) is trapped by enemy pawns, and assigns a penalty + // if it is. + + void evaluate_trapped_bishop_a7h7(const Position &pos, Square s, Color us, + EvalInfo &ei) { + Piece pawn = pawn_of_color(opposite_color(us)); + Square b6, b8; + + assert(square_is_ok(s)); + assert(pos.piece_on(s) == bishop_of_color(us)); + + if(square_file(s) == FILE_A) { + b6 = relative_square(us, SQ_B6); + b8 = relative_square(us, SQ_B8); + } + else { + b6 = relative_square(us, SQ_G6); + b8 = relative_square(us, SQ_G8); + } + + if(pos.piece_on(b6) == pawn && pos.see(s, b6) < 0 && pos.see(s, b8) < 0) { + ei.mgValue -= Sign[us] * TrappedBishopA7H7Penalty; + ei.egValue -= Sign[us] * TrappedBishopA7H7Penalty; + } + + } + + + // evaluate_trapped_bishop_a1h1() determines whether a bishop on a1/h1 + // (a8/h8 for black) is trapped by a friendly pawn on b2/g2 (b7/g7 for + // black), and assigns a penalty if it is. This pattern can obviously + // only occur in Chess960 games. + + void evaluate_trapped_bishop_a1h1(const Position &pos, Square s, Color us, + EvalInfo &ei) { + Piece pawn = pawn_of_color(us); + Square b2, b3, c3; + + assert(Chess960); + assert(square_is_ok(s)); + assert(pos.piece_on(s) == bishop_of_color(us)); + + if(square_file(s) == FILE_A) { + b2 = relative_square(us, SQ_B2); + b3 = relative_square(us, SQ_B3); + c3 = relative_square(us, SQ_C3); + } + else { + b2 = relative_square(us, SQ_G2); + b3 = relative_square(us, SQ_G3); + c3 = relative_square(us, SQ_F3); + } + + if(pos.piece_on(b2) == pawn) { + Value penalty; + + if(!pos.square_is_empty(b3)) + penalty = 2*TrappedBishopA1H1Penalty; + else if(pos.piece_on(c3) == pawn) + penalty = TrappedBishopA1H1Penalty; + else + penalty = TrappedBishopA1H1Penalty / 2; + + ei.mgValue -= Sign[us] * penalty; + ei.egValue -= Sign[us] * penalty; + } + + } + + + // apply_weight applies an evaluation weight to a value. + + inline Value apply_weight(Value v, int w) { + return (v*w) / 0x100; + } + + + // scale_by_game_phase interpolates between a middle game and an endgame + // score, based on game phase. It also scales the return value by a + // ScaleFactor array. + + Value scale_by_game_phase(Value mv, Value ev, Phase ph, ScaleFactor sf[]) { + assert(mv > -VALUE_INFINITE && mv < VALUE_INFINITE); + assert(ev > -VALUE_INFINITE && ev < VALUE_INFINITE); + assert(ph >= PHASE_ENDGAME && ph <= PHASE_MIDGAME); + + if(ev > Value(0)) + ev = apply_scale_factor(ev, sf[WHITE]); + else + ev = apply_scale_factor(ev, sf[BLACK]); + + Value result = Value(int((mv * ph + ev * (128 - ph)) / 128)); + return Value(int(result) & ~(GrainSize - 1)); + } + + + // count_1s_8bit() counts the number of nonzero bits in the 8 least + // significant bits of an integer. This function is used by the king + // shield evaluation. + + int count_1s_8bit(int b) { + return int(BitCount8Bit[b & 0xFF]); + } + + + // compute_weight() computes the value of an evaluation weight, by combining + // an UCI-configurable weight with an internal weight. + + int compute_weight(int uciWeight, int internalWeight) { + uciWeight = (uciWeight * 0x100) / 100; + return (uciWeight * internalWeight) / 0x100; + } + + + // init_safety() initizes the king safety evaluation, based on UCI + // parameters. It is called from read_weights(). + + void init_safety() { + double a, b; + int maxSlope, peak, i, j; + + QueenContactCheckBonus = get_option_value_int("Queen Contact Check Bonus"); + RookContactCheckBonus = get_option_value_int("Rook Contact Check Bonus"); + QueenCheckBonus = get_option_value_int("Queen Check Bonus"); + RookCheckBonus = get_option_value_int("Rook Check Bonus"); + BishopCheckBonus = get_option_value_int("Bishop Check Bonus"); + KnightCheckBonus = get_option_value_int("Knight Check Bonus"); + DiscoveredCheckBonus = get_option_value_int("Discovered Check Bonus"); + MateThreatBonus = get_option_value_int("Mate Threat Bonus"); + + a = get_option_value_int("King Safety Coefficient") / 100.0; + b = get_option_value_int("King Safety X Intercept") * 1.0; + maxSlope = get_option_value_int("King Safety Max Slope"); + peak = (get_option_value_int("King Safety Max Value") * 256) / 100; + + for(i = 0; i < 100; i++) { + if(i < b) SafetyTable[i] = Value(0); + else if(get_option_value_string("King Safety Curve") == "Quadratic") + SafetyTable[i] = Value((int)(a * (i - b) * (i - b))); + else if(get_option_value_string("King Safety Curve") == "Linear") + SafetyTable[i] = Value((int)(100 * a * (i - b))); + } + + for(i = 0; i < 100; i++) + if(SafetyTable[i+1] - SafetyTable[i] > maxSlope) { + for(j = i + 1; j < 100; j++) + SafetyTable[j] = SafetyTable[j-1] + Value(maxSlope); + } + for(i = 0; i < 100; i++) + if(SafetyTable[i] > Value(peak)) + SafetyTable[i] = Value(peak); + } + +} diff --git a/src/evaluate.h b/src/evaluate.h new file mode 100644 index 00000000..8662f4b3 --- /dev/null +++ b/src/evaluate.h @@ -0,0 +1,108 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +#if !defined(EVALUATE_H_INCLUDED) +#define EVALUATE_H_INCLUDED + +//// +//// Includes +//// + +#include "material.h" +#include "pawns.h" +#include "position.h" + + +//// +//// Types +//// + +/// The EvalInfo struct contains various information computed and collected +/// by the evaluation function. An EvalInfo object is passed as one of the +/// arguments to the evaluation function, and the search can make use of its +/// contents to make intelligent search decisions. +/// +/// At the moment, this is not utilized very much: The only part of the +/// EvalInfo object which is used by the search is futilityMargin. + +struct EvalInfo { + + // Middle game and endgame evaluations: + Value mgValue, egValue; + + // Pointers to material and pawn hash table entries: + MaterialInfo *mi; + PawnInfo *pi; + + // attackedBy[color][piece type] is a bitboard representing all squares + // attacked by a given color and piece type. attackedBy[color][0] contains + // all squares attacked by the given color. + Bitboard attackedBy[2][8]; + Bitboard attacked_by(Color c) const { return attackedBy[c][0]; } + Bitboard attacked_by(Color c, PieceType pt) const { return attackedBy[c][pt]; } + // attackZone[color] is the zone around the enemy 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, attackZone[WHITE] is a bitboard containing the squares f8, h8, + // f7, g7, h7, f6, g6 and h6. + Bitboard attackZone[2]; + + // attackCount[color] is the number of pieces of the given color which + // attack a square adjacent to the enemy king. + int attackCount[2]; + + // attackWeight[color] is the sum of the "weight" of the pieces of the given + // color which attack a square adjacent to the enemy king. The weights of + // the individual piece types are given by the variables QueenAttackWeight, + // RookAttackWeight, BishopAttackWeight and KnightAttackWeight in + // evaluate.cpp. + int attackWeight[2]; + + // attacked[color] is the number of enemy piece 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 attacked[BLACK]. + int attacked[2]; + + // mateThreat[color] is a move for the given side which gives a direct mate. + Move mateThreat[2]; + + // Middle game and endgame mobility scores. + Value mgMobility, egMobility; + + // Extra futility margin. This is added to the standard futility margin + // in the quiescence search. + Value futilityMargin; +}; + + +//// +//// Prototypes +//// + +extern Value evaluate(const Position &pos, EvalInfo &ei, int threadID); +extern Value quick_evaluate(const Position &pos); +extern void init_eval(int threads); +extern void quit_eval(); +extern void read_weights(Color sideToMove); + + +#endif // !defined(EVALUATE_H_INCLUDED) diff --git a/src/history.cpp b/src/history.cpp new file mode 100644 index 00000000..29631489 --- /dev/null +++ b/src/history.cpp @@ -0,0 +1,100 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +//// +//// Includes +//// + +#include + +#include "history.h" + + +//// +//// Functions +//// + +/// Constructor + +History::History() { + this->clear(); +} + + +/// History::clear() clears the history tables. + +void History::clear() { + memset(history, 0, 2 * 8 * 64 * sizeof(int)); + memset(successCount, 0, 2 * 8 * 64 * sizeof(int)); + memset(failureCount, 0, 2 * 8 * 64 * sizeof(int)); +} + + +/// History::success() registers a move as being successful. This is done +/// whenever a non-capturing move causes a beta cutoff in the main search. +/// The three parameters are the moving piece, the move itself, and the +/// search depth. + +void History::success(Piece p, Move m, Depth d) { + assert(piece_is_ok(p)); + assert(move_is_ok(m)); + + history[p][move_to(m)] += int(d) * int(d); + successCount[p][move_to(m)]++; + + // Prevent history overflow: + if(history[p][move_to(m)] >= HistoryMax) + for(int i = 0; i < 16; i++) + for(int j = 0; j < 64; j++) + history[i][j] /= 2; +} + + +/// History::failure() registers a move as being unsuccessful. The function is +/// called for each non-capturing move which failed to produce a beta cutoff +/// at a node where a beta cutoff was finally found. + +void History::failure(Piece p, Move m) { + assert(piece_is_ok(p)); + assert(move_is_ok(m)); + + failureCount[p][move_to(m)]++; +} + + +/// History::move_ordering_score() returns an integer value used to order the +/// non-capturing moves in the MovePicker class. + +int History::move_ordering_score(Piece p, Move m) const { + assert(piece_is_ok(p)); + assert(move_is_ok(m)); + + return history[p][move_to(m)]; +} + + +/// History::ok_to_prune() decides whether a move has been sufficiently +/// unsuccessful that it makes sense to prune it entirely. + +bool History::ok_to_prune(Piece p, Move m, Depth d) const { + assert(piece_is_ok(p)); + assert(move_is_ok(m)); + + return (int(d) * successCount[p][move_to(m)] < failureCount[p][move_to(m)]); +} diff --git a/src/history.h b/src/history.h new file mode 100644 index 00000000..ced416a8 --- /dev/null +++ b/src/history.h @@ -0,0 +1,76 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +#if !defined(HISTORY_H_INCLUDED) +#define HISTORY_H_INCLUDED + +//// +//// Includes +//// + +#include "depth.h" +#include "move.h" +#include "piece.h" + + +//// +//// Types +//// + +/// The History class stores statistics about how often different moves have +/// been successful or unsuccessful during the current search. These +/// statistics are used for reduction and move ordering decisions. + +class History { + +public: + History(); + void clear(); + void success(Piece p, Move m, Depth d); + void failure(Piece p, Move m); + int move_ordering_score(Piece p, Move m) const; + bool ok_to_prune(Piece p, Move m, Depth d) const; + +private: + int history[16][64]; // [piece][square] + int successCount[16][64]; + int failureCount[16][64]; +}; + + +//// +//// Constants and variables +//// + +/// HistoryMax controls how often the history counters will be scaled down: +/// When the history score for a move gets bigger than HistoryMax, all +/// entries in the table are divided by 2. It is difficult to guess what +/// the ideal value of this constant is. Scaling down the scores often has +/// the effect that parts of the search tree which have been searched +/// recently have a bigger importance for move ordering than the moves which +/// have been searched a long time ago. +/// +/// Note that HistoryMax should probably be changed whenever the constant +/// OnePly in depth.h is changed. This is somewhat annoying. Perhaps it +/// would be better to scale down the history table at regular intervals? + +const int HistoryMax = 50000; + + +#endif // !defined(HISTORY_H_INCLUDED) diff --git a/src/lock.h b/src/lock.h new file mode 100644 index 00000000..2efb2076 --- /dev/null +++ b/src/lock.h @@ -0,0 +1,101 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +#if !defined(LOCK_H_INCLUDED) +#define LOCK_H_INCLUDED + + +// x86 assembly language locks or OS spin locks may perform faster than +// mutex locks on some platforms. On my machine, mutexes seem to be the +// best. + +//#define ASM_LOCK +//#define OS_SPIN_LOCK + + +#if defined(ASM_LOCK) + + +typedef volatile int Lock; + +static inline void LockX86(Lock *lock) { + int dummy; + asm __volatile__("1: movl $1, %0" "\n\t" + " xchgl (%1), %0" "\n\t" " testl %0, %0" "\n\t" + " jz 3f" "\n\t" "2: pause" "\n\t" + " movl (%1), %0" "\n\t" " testl %0, %0" "\n\t" + " jnz 2b" "\n\t" " jmp 1b" "\n\t" "3:" + "\n\t":"=&q"(dummy) + :"q"(lock) + :"cc"); +} + +static inline void UnlockX86(Lock *lock) { + int dummy; + asm __volatile__("movl $0, (%1)":"=&q"(dummy) + :"q"(lock)); +} + +# define lock_init(x, y) (*(x) = 0) +# define lock_grab(x) LockX86(x) +# define lock_release(x) UnlockX86(x) +# define lock_destroy(x) + + +#elif defined(OS_SPIN_LOCK) + + +# include + +typedef OSSpinLock Lock; + +# define lock_init(x, y) (*(x) = 0) +# define lock_grab(x) OSSpinLockLock(x) +# define lock_release(x) OSSpinLockUnlock(x) +# define lock_destroy(x) + + +#elif !defined(_MSC_VER) + +# include + +typedef pthread_mutex_t Lock; + +# define lock_init(x, y) pthread_mutex_init(x, y) +# define lock_grab(x) pthread_mutex_lock(x) +# define lock_release(x) pthread_mutex_unlock(x) +# define lock_destroy(x) pthread_mutex_destroy(x) + + +#else + + +# include + +typedef CRITICAL_SECTION Lock; +# define lock_init(x, y) InitializeCriticalSection(x) +# define lock_grab(x) EnterCriticalSection(x) +# define lock_release(x) LeaveCriticalSection(x) +# define lock_destroy(x) DeleteCriticalSection(x) + + +#endif + + +#endif // !defined(LOCK_H_INCLUDED) diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 00000000..49c66c0d --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,95 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +//// +//// Includes +//// + +#include +#include + +#include "benchmark.h" +#include "bitboard.h" +#include "direction.h" +#include "endgame.h" +#include "evaluate.h" +#include "material.h" +#include "mersenne.h" +#include "misc.h" +#include "movepick.h" +#include "position.h" +#include "search.h" +#include "thread.h" +#include "uci.h" +#include "ucioption.h" + + +//// +//// Functions +//// + +int main(int argc, char *argv[]) { + + // Disable IO buffering + setbuf(stdin, NULL); + setbuf(stdout, NULL); + std::cout.rdbuf()->pubsetbuf(NULL, 0); + std::cin.rdbuf()->pubsetbuf(NULL, 0); + + // Initialization + + init_mersenne(); + init_direction_table(); + init_bitboards(); + init_uci_options(); + Position::init_zobrist(); + Position::init_piece_square_tables(); + MaterialInfo::init(); + MovePicker::init_phase_table(); + init_eval(1); + init_bitbases(); + init_threads(); + + // Make random number generation less deterministic, for book moves + int i = abs(get_system_time() % 10000); + for(int j = 0; j < i; j++) + genrand_int32(); + + // Process command line arguments + if(argc >= 2) { + if(std::string(argv[1]) == "bench") { + if(argc != 4) { + std::cout << "Usage: glaurung bench " << std::endl; + exit(0); + } + benchmark(std::string(argv[2]), std::string(argv[3])); + return 0; + } + } + + // Print copyright notice + std::cout << engine_name() << ". " + << "Copyright (C) 2004-2008 Tord Romstad." + << std::endl; + + // Enter UCI mode + uci_main_loop(); + + return 0; +} diff --git a/src/material.cpp b/src/material.cpp new file mode 100644 index 00000000..c7bb810d --- /dev/null +++ b/src/material.cpp @@ -0,0 +1,406 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +//// +//// Includes +//// + +#include + +#include "material.h" + + +//// +//// Local definitions +//// + +namespace { + + const Value BishopPairMidgameBonus = Value(100); + const Value BishopPairEndgameBonus = Value(100); + + Key KPKMaterialKey, KKPMaterialKey; + Key KBNKMaterialKey, KKBNMaterialKey; + Key KRKPMaterialKey, KPKRMaterialKey; + Key KRKBMaterialKey, KBKRMaterialKey; + Key KRKNMaterialKey, KNKRMaterialKey; + Key KQKRMaterialKey, KRKQMaterialKey; + Key KRPKRMaterialKey, KRKRPMaterialKey; + Key KRPPKRPMaterialKey, KRPKRPPMaterialKey; + Key KNNKMaterialKey, KKNNMaterialKey; + Key KBPKBMaterialKey, KBKBPMaterialKey; + Key KBPKNMaterialKey, KNKBPMaterialKey; + Key KNPKMaterialKey, KKNPMaterialKey; + Key KPKPMaterialKey; + +}; + + +//// +//// Functions +//// + +/// MaterialInfo::init() is called during program initialization. It +/// precomputes material hash keys for a few basic endgames, in order +/// to make it easy to recognize such endgames when they occur. + +void MaterialInfo::init() { + KPKMaterialKey = Position::zobMaterial[WHITE][PAWN][1]; + KKPMaterialKey = Position::zobMaterial[BLACK][PAWN][1]; + KBNKMaterialKey = + Position::zobMaterial[WHITE][BISHOP][1] ^ + Position::zobMaterial[WHITE][KNIGHT][1]; + KKBNMaterialKey = + Position::zobMaterial[BLACK][BISHOP][1] ^ + Position::zobMaterial[BLACK][KNIGHT][1]; + KRKPMaterialKey = + Position::zobMaterial[WHITE][ROOK][1] ^ + Position::zobMaterial[BLACK][PAWN][1]; + KPKRMaterialKey = + Position::zobMaterial[WHITE][PAWN][1] ^ + Position::zobMaterial[BLACK][ROOK][1]; + KRKBMaterialKey = + Position::zobMaterial[WHITE][ROOK][1] ^ + Position::zobMaterial[BLACK][BISHOP][1]; + KBKRMaterialKey = + Position::zobMaterial[WHITE][BISHOP][1] ^ + Position::zobMaterial[BLACK][ROOK][1]; + KRKNMaterialKey = + Position::zobMaterial[WHITE][ROOK][1] ^ + Position::zobMaterial[BLACK][KNIGHT][1]; + KNKRMaterialKey = + Position::zobMaterial[WHITE][KNIGHT][1] ^ + Position::zobMaterial[BLACK][ROOK][1]; + KQKRMaterialKey = + Position::zobMaterial[WHITE][QUEEN][1] ^ + Position::zobMaterial[BLACK][ROOK][1]; + KRKQMaterialKey = + Position::zobMaterial[WHITE][ROOK][1] ^ + Position::zobMaterial[BLACK][QUEEN][1]; + KRPKRMaterialKey = + Position::zobMaterial[WHITE][ROOK][1] ^ + Position::zobMaterial[WHITE][PAWN][1] ^ + Position::zobMaterial[BLACK][ROOK][1]; + KRKRPMaterialKey = + Position::zobMaterial[WHITE][ROOK][1] ^ + Position::zobMaterial[BLACK][ROOK][1] ^ + Position::zobMaterial[BLACK][PAWN][1]; + KRPPKRPMaterialKey = + Position::zobMaterial[WHITE][ROOK][1] ^ + Position::zobMaterial[WHITE][PAWN][1] ^ + Position::zobMaterial[WHITE][PAWN][2] ^ + Position::zobMaterial[BLACK][ROOK][1] ^ + Position::zobMaterial[BLACK][PAWN][1]; + KRPKRPPMaterialKey = + Position::zobMaterial[WHITE][ROOK][1] ^ + Position::zobMaterial[WHITE][PAWN][1] ^ + Position::zobMaterial[BLACK][ROOK][1] ^ + Position::zobMaterial[BLACK][PAWN][1] ^ + Position::zobMaterial[BLACK][PAWN][2]; + KNNKMaterialKey = + Position::zobMaterial[WHITE][KNIGHT][1] ^ + Position::zobMaterial[WHITE][KNIGHT][2]; + KKNNMaterialKey = + Position::zobMaterial[BLACK][KNIGHT][1] ^ + Position::zobMaterial[BLACK][KNIGHT][2]; + KBPKBMaterialKey = + Position::zobMaterial[WHITE][BISHOP][1] ^ + Position::zobMaterial[WHITE][PAWN][1] ^ + Position::zobMaterial[BLACK][BISHOP][1]; + KBKBPMaterialKey = + Position::zobMaterial[WHITE][BISHOP][1] ^ + Position::zobMaterial[BLACK][BISHOP][1] ^ + Position::zobMaterial[BLACK][PAWN][1]; + KBPKNMaterialKey = + Position::zobMaterial[WHITE][BISHOP][1] ^ + Position::zobMaterial[WHITE][PAWN][1] ^ + Position::zobMaterial[BLACK][KNIGHT][1]; + KNKBPMaterialKey = + Position::zobMaterial[WHITE][KNIGHT][1] ^ + Position::zobMaterial[BLACK][BISHOP][1] ^ + Position::zobMaterial[BLACK][PAWN][1]; + KNPKMaterialKey = + Position::zobMaterial[WHITE][KNIGHT][1] ^ + Position::zobMaterial[WHITE][PAWN][1]; + KKNPMaterialKey = + Position::zobMaterial[BLACK][KNIGHT][1] ^ + Position::zobMaterial[BLACK][PAWN][1]; + KPKPMaterialKey = + Position::zobMaterial[WHITE][PAWN][1] ^ + Position::zobMaterial[BLACK][PAWN][1]; +} + + +/// Constructor for the MaterialInfoTable class. + +MaterialInfoTable::MaterialInfoTable(unsigned numOfEntries) { + size = numOfEntries; + entries = new MaterialInfo[size]; + if(entries == NULL) { + std::cerr << "Failed to allocate " << (numOfEntries * sizeof(MaterialInfo)) + << " bytes for material hash table." << std::endl; + exit(EXIT_FAILURE); + } + this->clear(); +} + + +/// Destructor for the MaterialInfoTable class. + +MaterialInfoTable::~MaterialInfoTable() { + delete [] entries; +} + + +/// MaterialInfoTable::clear() clears a material hash table by setting +/// all entries to 0. + +void MaterialInfoTable::clear() { + memset(entries, 0, size * sizeof(MaterialInfo)); +} + + +/// MaterialInfoTable::get_material_info() takes a position object as input, +/// computes or looks up a MaterialInfo object, and returns a pointer to it. +/// If the material configuration is not already present in the table, it +/// is stored there, so we don't have to recompute everything when the +/// same material configuration occurs again. + +MaterialInfo *MaterialInfoTable::get_material_info(const Position &pos) { + Key key = pos.get_material_key(); + int index = key & (size - 1); + MaterialInfo *mi = entries + index; + + // If mi->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(mi->key == key) + return mi; + + // Clear the MaterialInfo object, and set its key: + mi->clear(); + mi->key = key; + + // A special case before looking for a specialized evaluation function: + // KNN vs K is a draw: + if(key == KNNKMaterialKey || key == KKNNMaterialKey) { + mi->factor[WHITE] = mi->factor[BLACK] = 0; + return mi; + } + + // Let's look if we have a specialized evaluation function for this + // particular material configuration: + if(key == KPKMaterialKey) { + mi->evaluationFunction = &EvaluateKPK; + return mi; + } + else if(key == KKPMaterialKey) { + mi->evaluationFunction = &EvaluateKKP; + return mi; + } + else if(key == KBNKMaterialKey) { + mi->evaluationFunction = &EvaluateKBNK; + return mi; + } + else if(key == KKBNMaterialKey) { + mi->evaluationFunction = &EvaluateKKBN; + return mi; + } + else if(key == KRKPMaterialKey) { + mi->evaluationFunction = &EvaluateKRKP; + return mi; + } + else if(key == KPKRMaterialKey) { + mi->evaluationFunction = &EvaluateKPKR; + return mi; + } + else if(key == KRKBMaterialKey) { + mi->evaluationFunction = &EvaluateKRKB; + return mi; + } + else if(key == KBKRMaterialKey) { + mi->evaluationFunction = &EvaluateKBKR; + return mi; + } + else if(key == KRKNMaterialKey) { + mi->evaluationFunction = &EvaluateKRKN; + return mi; + } + else if(key == KNKRMaterialKey) { + mi->evaluationFunction = &EvaluateKNKR; + return mi; + } + else if(key == KQKRMaterialKey) { + mi->evaluationFunction = &EvaluateKQKR; + return mi; + } + else if(key == KRKQMaterialKey) { + mi->evaluationFunction = &EvaluateKRKQ; + return mi; + } + else if(pos.non_pawn_material(BLACK) == Value(0) && + pos.pawn_count(BLACK) == 0 && + pos.non_pawn_material(WHITE) >= RookValueEndgame) { + mi->evaluationFunction = &EvaluateKXK; + return mi; + } + else if(pos.non_pawn_material(WHITE) == Value(0) && + pos.pawn_count(WHITE) == 0 && + pos.non_pawn_material(BLACK) >= RookValueEndgame) { + mi->evaluationFunction = &EvaluateKKX; + return mi; + } + + // OK, we didn't find any special evaluation function for the current + // material configuration. Is there a suitable scaling function? + // + // The code below is rather messy, and it could easily get worse later, + // if we decide to add more special cases. We face problems when there + // are several conflicting applicable scaling functions and we need to + // decide which one to use. + + if(key == KRPKRMaterialKey) { + mi->scalingFunction[WHITE] = &ScaleKRPKR; + return mi; + } + if(key == KRKRPMaterialKey) { + mi->scalingFunction[BLACK] = &ScaleKRKRP; + return mi; + } + if(key == KRPPKRPMaterialKey) { + mi->scalingFunction[WHITE] = &ScaleKRPPKRP; + return mi; + } + else if(key == KRPKRPPMaterialKey) { + mi->scalingFunction[BLACK] = &ScaleKRPKRPP; + return mi; + } + if(key == KBPKBMaterialKey) { + mi->scalingFunction[WHITE] = &ScaleKBPKB; + return mi; + } + if(key == KBKBPMaterialKey) { + mi->scalingFunction[BLACK] = &ScaleKBKBP; + return mi; + } + if(key == KBPKNMaterialKey) { + mi->scalingFunction[WHITE] = &ScaleKBPKN; + return mi; + } + if(key == KNKBPMaterialKey) { + mi->scalingFunction[BLACK] = &ScaleKNKBP; + return mi; + } + if(key == KNPKMaterialKey) { + mi->scalingFunction[WHITE] = &ScaleKNPK; + return mi; + } + if(key == KKNPMaterialKey) { + mi->scalingFunction[BLACK] = &ScaleKKNP; + return mi; + } + + if(pos.non_pawn_material(WHITE) == BishopValueMidgame && + pos.bishop_count(WHITE) == 1 && pos.pawn_count(WHITE) >= 1) + mi->scalingFunction[WHITE] = &ScaleKBPK; + if(pos.non_pawn_material(BLACK) == BishopValueMidgame && + pos.bishop_count(BLACK) == 1 && pos.pawn_count(BLACK) >= 1) + mi->scalingFunction[BLACK] = &ScaleKKBP; + + if(pos.pawn_count(WHITE) == 0 && + pos.non_pawn_material(WHITE) == QueenValueMidgame && + pos.queen_count(WHITE) == 1 && + pos.rook_count(BLACK) == 1 && pos.pawn_count(BLACK) >= 1) + mi->scalingFunction[WHITE] = &ScaleKQKRP; + else if(pos.pawn_count(BLACK) == 0 && + pos.non_pawn_material(BLACK) == QueenValueMidgame && + pos.queen_count(BLACK) == 1 && + pos.rook_count(WHITE) == 1 && pos.pawn_count(WHITE) >= 1) + mi->scalingFunction[BLACK] = &ScaleKRPKQ; + + if(pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK) == Value(0)) { + if(pos.pawn_count(BLACK) == 0) { + assert(pos.pawn_count(WHITE) >= 2); + mi->scalingFunction[WHITE] = &ScaleKPsK; + } + else if(pos.pawn_count(WHITE) == 0) { + assert(pos.pawn_count(BLACK) >= 2); + mi->scalingFunction[BLACK] = &ScaleKKPs; + } + else if(pos.pawn_count(WHITE) == 1 && pos.pawn_count(BLACK) == 1) { + mi->scalingFunction[WHITE] = &ScaleKPKPw; + mi->scalingFunction[BLACK] = &ScaleKPKPb; + } + } + + // Evaluate the material balance. + + Color c; + int sign; + Value egValue = Value(0), mgValue = Value(0); + + for(c = WHITE, sign = 1; c <= BLACK; c++, sign = -sign) { + + // No pawns makes it difficult to win, even with a material advantage: + if(pos.pawn_count(c) == 0 && + pos.non_pawn_material(c) - pos.non_pawn_material(opposite_color(c)) + <= BishopValueMidgame) { + if(pos.non_pawn_material(c) == pos.non_pawn_material(opposite_color(c))) + mi->factor[c] = 0; + else if(pos.non_pawn_material(c) < RookValueMidgame) + mi->factor[c] = 0; + else { + switch(pos.bishop_count(c)) { + case 2: + mi->factor[c] = 32; break; + case 1: + mi->factor[c] = 12; break; + case 0: + mi->factor[c] = 6; break; + } + } + } + + // Bishop pair: + if(pos.bishop_count(c) >= 2) { + mgValue += sign * BishopPairMidgameBonus; + egValue += sign * BishopPairEndgameBonus; + } + + // Knights are stronger when there are many pawns on the board. The + // formula is taken from Larry Kaufman's paper "The Evaluation of Material + // Imbalances in Chess": + // http://mywebpages.comcast.net/danheisman/Articles/evaluation_of_material_imbalance.htm + mgValue += sign * Value(pos.knight_count(c)*(pos.pawn_count(c)-5)*16); + egValue += sign * Value(pos.knight_count(c)*(pos.pawn_count(c)-5)*16); + + // Redundancy of major pieces, again based on Kaufman's paper: + if(pos.rook_count(c) >= 1) { + Value v = Value((pos.rook_count(c) - 1) * 32 + pos.queen_count(c) * 16); + mgValue -= sign * v; + egValue -= sign * v; + } + + } + + mi->mgValue = int16_t(mgValue); + mi->egValue = int16_t(egValue); + + return mi; +} diff --git a/src/material.h b/src/material.h new file mode 100644 index 00000000..7d5c3ce1 --- /dev/null +++ b/src/material.h @@ -0,0 +1,152 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +#if !defined(MATERIAL_H_INCLUDED) +#define MATERIAL_H_INCLUDED + +//// +//// Includes +//// + +#include "endgame.h" +#include "position.h" +#include "scale.h" + + +//// +//// Types +//// + +/// MaterialInfo is a class which 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" for black and white. +/// +/// 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. + +class MaterialInfo { + + friend class MaterialInfoTable; + +public: + Value mg_value() const; + Value eg_value() const; + ScaleFactor scale_factor(const Position &pos, Color c) const; + bool specialized_eval_exists() const; + Value evaluate(const Position &pos) const; + + static void init(); + +private: + void clear(); + + Key key; + int16_t mgValue; + int16_t egValue; + uint8_t factor[2]; + EndgameEvaluationFunction *evaluationFunction; + ScalingFunction *scalingFunction[2]; +}; + + +/// The MaterialInfoTable class represents a pawn hash table. It is basically +/// just an array of MaterialInfo objects and a few methods for accessing these +/// objects. The most important method is get_material_info, which looks up a +/// position in the table and returns a pointer to a MaterialInfo object. + +class MaterialInfoTable { + +public: + MaterialInfoTable(unsigned numOfEntries); + ~MaterialInfoTable(); + void clear(); + MaterialInfo *get_material_info(const Position &pos); + +private: + unsigned size; + MaterialInfo *entries; +}; + + +//// +//// Inline functions +//// + +/// MaterialInfo::mg_value and MaterialInfo::eg_value simply returns the +/// material balance evaluation for the middle game and the endgame. + +inline Value MaterialInfo::mg_value() const { + return Value(mgValue); +} + +inline Value MaterialInfo::eg_value() const { + return Value(egValue); +} + + +/// MaterialInfo::clear() resets a MaterialInfo object to an empty state, +/// with all slots at their default values. + +inline void MaterialInfo::clear() { + mgValue = egValue = 0; + factor[WHITE] = factor[BLACK] = uint8_t(SCALE_FACTOR_NORMAL); + evaluationFunction = NULL; + scalingFunction[WHITE] = scalingFunction[BLACK] = NULL; +} + + +/// MaterialInfo::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 +/// which checks for draws with rook pawns and wrong-colored bishops. + +inline ScaleFactor MaterialInfo::scale_factor(const Position &pos, Color c) + const { + if(scalingFunction[c] != NULL) { + ScaleFactor sf = scalingFunction[c]->apply(pos); + if(sf != SCALE_FACTOR_NONE) + return sf; + } + return ScaleFactor(factor[c]); +} + + +/// MaterialInfo::specialized_eval_exists decides whether there is a +/// specialized evaluation function for the current material configuration, +/// or if the normal evaluation function should be used. + +inline bool MaterialInfo::specialized_eval_exists() const { + return evaluationFunction != NULL; +} + + +/// MaterialInfo::evaluate applies a specialized evaluation function to a +/// given position object. It should only be called when +/// this->specialized_eval_exists() returns 'true'. + +inline Value MaterialInfo::evaluate(const Position &pos) const { + return evaluationFunction->apply(pos); +} + +#endif // !defined(MATERIAL_H_INCLUDED) diff --git a/src/mersenne.cpp b/src/mersenne.cpp new file mode 100644 index 00000000..2213e4ac --- /dev/null +++ b/src/mersenne.cpp @@ -0,0 +1,148 @@ +/* + A C-program for MT19937, with initialization improved 2002/1/26. + Coded by Takuji Nishimura and Makoto Matsumoto. + + Before using, initialize the state by using init_genrand(seed) + or init_by_array(init_key, key_length). + + Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura, + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The names of its contributors may not be used to endorse or promote + products derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + + Any feedback is very welcome. + http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html + email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space) +*/ + +#include "types.h" + +/* Period parameters */ +#define N 624 +#define M 397 +#define MATRIX_A 0x9908b0dfUL /* constant vector a */ +#define UPPER_MASK 0x80000000UL /* most significant w-r bits */ +#define LOWER_MASK 0x7fffffffUL /* least significant r bits */ + +static unsigned long mt[N]; /* the array for the state vector */ +static int mti=N+1; /* mti==N+1 means mt[N] is not initialized */ + +/* initializes mt[N] with a seed */ +void init_genrand(unsigned long s) +{ + mt[0]= s & 0xffffffffUL; + for (mti=1; mti> 30)) + mti); + /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */ + /* In the previous versions, MSBs of the seed affect */ + /* only MSBs of the array mt[]. */ + /* 2002/01/09 modified by Makoto Matsumoto */ + mt[mti] &= 0xffffffffUL; + /* for >32 bit machines */ + } +} + +/* initialize by an array with array-length */ +/* init_key is the array for initializing keys */ +/* key_length is its length */ +/* slight change for C++, 2004/2/26 */ +void init_by_array(unsigned long init_key[], int key_length) +{ + int i, j, k; + init_genrand(19650218UL); + i=1; j=0; + k = (N>key_length ? N : key_length); + for (; k; k--) { + mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1664525UL)) + + init_key[j] + j; /* non linear */ + mt[i] &= 0xffffffffUL; /* for WORDSIZE > 32 machines */ + i++; j++; + if (i>=N) { mt[0] = mt[N-1]; i=1; } + if (j>=key_length) j=0; + } + for (k=N-1; k; k--) { + mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1566083941UL)) + - i; /* non linear */ + mt[i] &= 0xffffffffUL; /* for WORDSIZE > 32 machines */ + i++; + if (i>=N) { mt[0] = mt[N-1]; i=1; } + } + + mt[0] = 0x80000000UL; /* MSB is 1; assuring non-zero initial array */ +} + +/* generates a random number on [0,0xffffffff]-interval */ +uint32_t genrand_int32(void) { + unsigned long y; + static unsigned long mag01[2]={0x0UL, MATRIX_A}; + /* mag01[x] = x * MATRIX_A for x=0,1 */ + + if (mti >= N) { /* generate N words at one time */ + int kk; + + if (mti == N+1) /* if init_genrand() has not been called, */ + init_genrand(5489UL); /* a default initial seed is used */ + + for (kk=0;kk> 1) ^ mag01[y & 0x1UL]; + } + for (;kk> 1) ^ mag01[y & 0x1UL]; + } + y = (mt[N-1]&UPPER_MASK)|(mt[0]&LOWER_MASK); + mt[N-1] = mt[M-1] ^ (y >> 1) ^ mag01[y & 0x1UL]; + + mti = 0; + } + + y = mt[mti++]; + + /* Tempering */ + y ^= (y >> 11); + y ^= (y << 7) & 0x9d2c5680UL; + y ^= (y << 15) & 0xefc60000UL; + y ^= (y >> 18); + + return y; +} + +uint64_t genrand_int64(void) { + uint64_t x, y; + + x = genrand_int32(); y = genrand_int32(); + return (x<<32)|y; +} + +void init_mersenne(void) { + unsigned long init[4]={0x123, 0x234, 0x345, 0x456}, length=4; + init_by_array(init, length); +} diff --git a/src/mersenne.h b/src/mersenne.h new file mode 100644 index 00000000..3c1c2268 --- /dev/null +++ b/src/mersenne.h @@ -0,0 +1,39 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +#if !defined(MERSENNE_H_INCLUDED) +#define MERSENNE_H_INCLUDED + +//// +//// Includes +//// + +#include "types.h" + + +//// +//// Prototypes +//// + +extern uint32_t genrand_int32(void); +extern uint64_t genrand_int64(void); +extern void init_mersenne(void); + + +#endif // !defined(MERSENNE_H_INCLUDED) diff --git a/src/misc.cpp b/src/misc.cpp new file mode 100644 index 00000000..a2dc53a7 --- /dev/null +++ b/src/misc.cpp @@ -0,0 +1,169 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +//// +//// Includes +//// + +#if !defined(_MSC_VER) + +# include +# include +# include + +#else + +# include +# include +# include "dos.h" +int gettimeofday(struct timeval * tp, struct timezone * tzp); + +#endif + +#include +#include +#include + +#include "misc.h" + + +//// +//// Functions +//// + +/// engine_name() returns the full name of the current Glaurung version. +/// This will be either "Glaurung YYMMDD" (where YYMMDD is the date when the +/// program was compiled) or "Glaurung ", depending on whether +/// the constant EngineVersion (defined in misc.h) is empty. + +const std::string engine_name() { + if(EngineVersion == "") { + static const char monthNames[12][4] = { + "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec" + }; + const char *dateString = __DATE__; + std::stringstream s; + int month = 0, day = 0; + + for(int i = 0; i < 12; i++) + if(strncmp(dateString, monthNames[i], 3) == 0) + month = i + 1; + day = atoi(dateString+4); + + s << "Glaurung " << (dateString+9) << std::setfill('0') << std::setw(2) + << month << std::setfill('0') << std::setw(2) << day; + + return s.str(); + } + else + return "Glaurung " + EngineVersion; +} + + +/// get_system_time() returns the current system time, measured in +/// milliseconds. + +int get_system_time() { + struct timeval t; + gettimeofday(&t, NULL); + return t.tv_sec*1000 + t.tv_usec/1000; +} + + +/// cpu_count() tries to detect the number of CPU cores. + +#if !defined(_MSC_VER) + +# if defined(_SC_NPROCESSORS_ONLN) +int cpu_count() { + return Min(sysconf(_SC_NPROCESSORS_ONLN), 8); +} +# else +int cpu_count() { + return 1; +} +# endif + +#else + +int cpu_count() { + SYSTEM_INFO s; + GetSystemInfo(&s); + return Min(s.dwNumberOfProcessors, 8); +} + +#endif + + +/* + From Beowulf, from Olithink +*/ +#ifndef _WIN32 +/* Non-windows version */ +int Bioskey() +{ + fd_set readfds; + struct timeval timeout; + + FD_ZERO(&readfds); + FD_SET(fileno(stdin), &readfds); + /* Set to timeout immediately */ + timeout.tv_sec = 0; + timeout.tv_usec = 0; + select(16, &readfds, 0, 0, &timeout); + + return (FD_ISSET(fileno(stdin), &readfds)); +} + +#else +/* Windows-version */ +#include +#include +int Bioskey() +{ + static int init = 0, + pipe; + static HANDLE inh; + DWORD dw; + /* If we're running under XBoard then we can't use _kbhit() as the input + * commands are sent to us directly over the internal pipe */ + +#if defined(FILE_CNT) + if (stdin->_cnt > 0) + return stdin->_cnt; +#endif + if (!init) { + init = 1; + inh = GetStdHandle(STD_INPUT_HANDLE); + pipe = !GetConsoleMode(inh, &dw); + if (!pipe) { + SetConsoleMode(inh, dw & ~(ENABLE_MOUSE_INPUT | ENABLE_WINDOW_INPUT)); + FlushConsoleInputBuffer(inh); + } + } + if (pipe) { + if (!PeekNamedPipe(inh, NULL, 0, NULL, &dw, NULL)) + return 1; + return dw; + } else { + GetNumberOfConsoleInputEvents(inh, &dw); + return dw <= 1 ? 0 : dw; + } +} +#endif diff --git a/src/misc.h b/src/misc.h new file mode 100644 index 00000000..d6290450 --- /dev/null +++ b/src/misc.h @@ -0,0 +1,60 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +#if !defined(MISC_H_INCLUDED) +#define MISC_H_INCLUDED + + +//// +//// Includes +//// + +#include + + +//// +//// Constants +//// + + +/// Version number. If this is left empty, the current date (in the format +/// YYMMDD) is used as a version number. + +const std::string EngineVersion = "2.1"; + + +//// +//// Macros +//// + +#define Min(x, y) (((x) < (y))? (x) : (y)) +#define Max(x, y) (((x) < (y))? (y) : (x)) + + +//// +//// Prototypes +//// + +extern const std::string engine_name(); +extern int get_system_time(); +extern int cpu_count(); +extern int Bioskey(); + + +#endif // !defined(MISC_H_INCLUDED) diff --git a/src/move.cpp b/src/move.cpp new file mode 100644 index 00000000..2de102df --- /dev/null +++ b/src/move.cpp @@ -0,0 +1,147 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +//// +//// Includes +//// + +#include + +#include "move.h" +#include "piece.h" +#include "position.h" +#include "ucioption.h" + + +//// +//// Functions +//// + +/// move_from_string() takes a position and a string as input, and attempts to +/// convert the string to a move, using simple coordinate notation (g1f3, +/// a7a8q, etc.). In order to correctly parse en passant captures and castling +/// moves, we need the position. This function is not robust, and expects that +/// the input move is legal and correctly formatted. + +Move move_from_string(const Position &pos, const std::string &str) { + Square from, to; + Piece piece; + Color us = pos.side_to_move(); + + if(str.length() < 4) return MOVE_NONE; + + // Read the from and to squares: + from = square_from_string(str.substr(0, 2)); + to = square_from_string(str.substr(2, 4)); + + // Find the moving piece: + piece = pos.piece_on(from); + + // If the string has more than 4 characters, try to interpret the 5th + // character as a promotion: + if(type_of_piece(piece) == PAWN && str.length() >= 5) { + switch(str[4]) { + case 'n': case 'N': + return make_promotion_move(from, to, KNIGHT); + case 'b': case 'B': + return make_promotion_move(from, to, BISHOP); + case 'r': case 'R': + return make_promotion_move(from, to, ROOK); + case 'q': case 'Q': + return make_promotion_move(from, to, QUEEN); + } + } + + if(piece == king_of_color(us)) { + // Is this a castling move? A king move is assumed to be a castling + // move if the destination square is occupied by a friendly rook, or + // if the distance between the source and destination squares is more + // than 1. + if(pos.piece_on(to) == rook_of_color(us)) + return make_castle_move(from, to); + else if(square_distance(from, to) > 1) { + // This is a castling move, but we have to translate it to the + // internal "king captures rook" representation. + SquareDelta delta = (to > from)? DELTA_E : DELTA_W; + Square s; + for(s = from + delta; + pawn_rank(us, s) == RANK_1 && pos.piece_on(s) != rook_of_color(us); + s += delta); + if(pawn_rank(us, s) == RANK_1 && pos.piece_on(s) == rook_of_color(us)) + return make_castle_move(from, s); + } + } + else if(piece == pawn_of_color(us)) { + // En passant move? We assume that a pawn move is an en passant move + // without further testing if the destination square is epSquare. + if(to == pos.ep_square()) + return make_ep_move(from, to); + } + + return make_move(from, to); +} + + +/// move_to_string() 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. + +const std::string move_to_string(Move move) { + std::string str; + + if(move == MOVE_NONE) + str = "(none)"; + else if(move == MOVE_NULL) + str = "0000"; + else { + if(!Chess960) { + if(move_from(move) == SQ_E1 && move_is_short_castle(move)) { + str = "e1g1"; return str; + } + else if(move_from(move) == SQ_E1 && move_is_long_castle(move)) { + str = "e1c1"; return str; + } + if(move_from(move) == SQ_E8 && move_is_short_castle(move)) { + str = "e8g8"; return str; + } + else if(move_from(move) == SQ_E8 && move_is_long_castle(move)) { + str = "e8c8"; return str; + } + } + str = square_to_string(move_from(move)) + square_to_string(move_to(move)); + if(move_promotion(move)) + str += piece_type_to_char(move_promotion(move), false); + } + return str; +} + + +/// Overload the << operator, to make it easier to print moves. + +std::ostream &operator << (std::ostream &os, Move m) { + return os << move_to_string(m); +} + + +/// move_is_ok(), for debugging. + +bool move_is_ok(Move m) { + return square_is_ok(move_from(m)) && square_is_ok(move_to(m)); +} diff --git a/src/move.h b/src/move.h new file mode 100644 index 00000000..65fcc250 --- /dev/null +++ b/src/move.h @@ -0,0 +1,112 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +#if !defined(MOVE_H_INCLUDED) +#define MOVE_H_INCLUDED + +//// +//// Includes +//// + +#include + +#include "misc.h" +#include "piece.h" +#include "square.h" + + +//// +//// Types +//// + +class Position; + +enum Move { + MOVE_NONE = 0, + MOVE_NULL = 65, + MOVE_MAX = 0xFFFFFF +}; + + +struct MoveStack { + Move move; + int score; +}; + + +//// +//// Inline functions +//// + +inline Square move_from(Move m) { + return Square((int(m) >> 6) & 077); +} + +inline Square move_to(Move m) { + return Square(m & 077); +} + +inline PieceType move_promotion(Move m) { + return PieceType((int(m) >> 12) & 7); +} + +inline bool move_is_ep(Move m) { + return bool((int(m) >> 15) & 1); +} + +inline bool move_is_castle(Move m) { + return bool((int(m) >> 16) & 1); +} + +inline bool move_is_short_castle(Move m) { + return move_is_castle(m) && (move_to(m) > move_from(m)); +} + +inline bool move_is_long_castle(Move m) { + return move_is_castle(m) && (move_to(m) < move_from(m)); +} + +inline Move make_promotion_move(Square from, Square to, PieceType promotion) { + return Move(int(to) | (int(from) << 6) | (int(promotion) << 12)); +} + +inline Move make_move(Square from, Square to) { + return Move(int(to) | (int(from) << 6)); +} + +inline Move make_castle_move(Square from, Square to) { + return Move(int(to) | (int(from) << 6) | (1 << 16)); +} + +inline Move make_ep_move(Square from, Square to) { + return Move(int(to) | (int(from) << 6) | (1 << 15)); +} + + +//// +//// Prototypes +//// + +extern std::ostream &operator << (std::ostream &os, Move m); +extern Move move_from_string(const Position &pos, const std::string &str); +extern const std::string move_to_string(Move m); +extern bool move_is_ok(Move m); + + +#endif // !defined(MOVE_H_INCLUDED) diff --git a/src/movegen.cpp b/src/movegen.cpp new file mode 100644 index 00000000..ecdcd8fb --- /dev/null +++ b/src/movegen.cpp @@ -0,0 +1,1238 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +//// +//// Includes +//// + +#include + +#include "movegen.h" + + +//// +//// Local definitions +//// + +namespace { + + int generate_white_pawn_captures(const Position &pos, MoveStack *mlist); + int generate_black_pawn_captures(const Position &pos, MoveStack *mlist); + int generate_white_pawn_noncaptures(const Position &pos, MoveStack *mlist); + int generate_black_pawn_noncaptures(const Position &pos, MoveStack *mlist); + int generate_knight_moves(const Position &pos, MoveStack *mlist, + Color side, Bitboard target); + int generate_bishop_moves(const Position &pos, MoveStack *mlist, + Color side, Bitboard target); + int generate_rook_moves(const Position &pos, MoveStack *mlist, + Color side, Bitboard target); + int generate_queen_moves(const Position &pos, MoveStack *mlist, + Color side, Bitboard target); + int generate_king_moves(const Position &pos, MoveStack *mlist, + Square from, Bitboard target); + int generate_castle_moves(const Position &pos, MoveStack *mlist, Color us); + +} + + +//// +//// Functions +//// + + +/// generate_captures generates() all pseudo-legal captures and queen +/// promotions. The return value is the number of moves generated. + +int generate_captures(const Position &pos, MoveStack *mlist) { + Color us = pos.side_to_move(); + Bitboard target = pos.pieces_of_color(opposite_color(us)); + int n = 0; + + assert(pos.is_ok()); + assert(!pos.is_check()); + + if(us == WHITE) + n += generate_white_pawn_captures(pos, mlist); + else + n += generate_black_pawn_captures(pos, mlist); + n += generate_knight_moves(pos, mlist+n, us, target); + n += generate_bishop_moves(pos, mlist+n, us, target); + n += generate_rook_moves(pos, mlist+n, us, target); + n += generate_queen_moves(pos, mlist+n, us, target); + n += generate_king_moves(pos, mlist+n, pos.king_square(us), target); + + return n; +} + + +/// generate_noncaptures() generates all pseudo-legal non-captures and +/// underpromotions. The return value is the number of moves generated. + +int generate_noncaptures(const Position &pos, MoveStack *mlist) { + Color us = pos.side_to_move(); + Bitboard target = pos.empty_squares(); + int n = 0; + + assert(pos.is_ok()); + assert(!pos.is_check()); + + if(us == WHITE) + n += generate_white_pawn_noncaptures(pos, mlist); + else + n += generate_black_pawn_noncaptures(pos, mlist); + n += generate_knight_moves(pos, mlist+n, us, target); + n += generate_bishop_moves(pos, mlist+n, us, target); + n += generate_rook_moves(pos, mlist+n, us, target); + n += generate_queen_moves(pos, mlist+n, us, target); + n += generate_king_moves(pos, mlist+n, pos.king_square(us), target); + n += generate_castle_moves(pos, mlist+n, us); + + return n; +} + + +/// generate_checks() generates all pseudo-legal non-capturing, non-promoting +/// checks, except castling moves (will add this later). It returns the +/// number of generated moves. + +int generate_checks(const Position &pos, MoveStack *mlist, Bitboard dc) { + Color us, them; + Square ksq, from, to; + Bitboard empty, checkSqs, b1, b2, b3; + int n = 0; + + assert(pos.is_ok()); + assert(!pos.is_check()); + + us = pos.side_to_move(); + them = opposite_color(us); + + ksq = pos.king_square(them); + assert(pos.piece_on(ksq) == king_of_color(them)); + + dc = pos.discovered_check_candidates(us); + empty = pos.empty_squares(); + + // Pawn moves. This is somewhat messy, and we use separate code for white + // and black, because we can't shift by negative numbers in C/C++. :-( + + if(us == WHITE) { + // Pawn moves 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. + + // Find all friendly pawns not on the enemy king's file: + b1 = pos.pawns(us) & ~file_bb(ksq); + + // Discovered checks, single pawn pushes: + b2 = b3 = ((b1 & dc) << 8) & ~Rank8BB & empty; + while(b3) { + to = pop_1st_bit(&b3); + mlist[n++].move = make_move(to - DELTA_N, to); + } + + // Discovered checks, double pawn pushes: + b3 = ((b2 & Rank3BB) << 8) & empty; + while(b3) { + to = pop_1st_bit(&b3); + mlist[n++].move = make_move(to - DELTA_N - DELTA_N, to); + } + + // Direct checks. These are possible only for pawns on neighboring files + // of the enemy king: + + b1 &= (~dc & neighboring_files_bb(ksq)); + + // Direct checks, single pawn pushes: + b2 = (b1 << 8) & empty; + b3 = b2 & pos.black_pawn_attacks(ksq); + while(b3) { + to = pop_1st_bit(&b3); + mlist[n++].move = make_move(to - DELTA_N, to); + } + + // Direct checks, double pawn pushes: + b3 = ((b2 & Rank3BB) << 8) & empty & pos.black_pawn_attacks(ksq); + while(b3) { + to = pop_1st_bit(&b3); + mlist[n++].move = make_move(to - DELTA_N - DELTA_N, to); + } + } + else { // (us == BLACK) + + // Pawn moves 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. + + // Find all friendly pawns not on the enemy king's file: + b1 = pos.pawns(us) & ~file_bb(ksq); + + // Discovered checks, single pawn pushes: + b2 = b3 = ((b1 & dc) >> 8) & ~Rank1BB & empty; + while(b3) { + to = pop_1st_bit(&b3); + mlist[n++].move = make_move(to - DELTA_S, to); + } + + // Discovered checks, double pawn pushes: + b3 = ((b2 & Rank6BB) >> 8) & empty; + while(b3) { + to = pop_1st_bit(&b3); + mlist[n++].move = make_move(to - DELTA_S - DELTA_S, to); + } + + // Direct checks. These are possible only for pawns on neighboring files + // of the enemy king: + + b1 &= (~dc & neighboring_files_bb(ksq)); + + // Direct checks, single pawn pushes: + b2 = (b1 >> 8) & empty; + b3 = b2 & pos.white_pawn_attacks(ksq); + while(b3) { + to = pop_1st_bit(&b3); + mlist[n++].move = make_move(to - DELTA_S, to); + } + + // Direct checks, double pawn pushes: + b3 = ((b2 & Rank6BB) >> 8) & empty & pos.black_pawn_attacks(ksq); + while(b3) { + to = pop_1st_bit(&b3); + mlist[n++].move = make_move(to - DELTA_S - DELTA_S, to); + } + } + + // Knight moves + b1 = pos.knights(us); + if(b1) { + // Discovered knight checks: + b2 = b1 & dc; + while(b2) { + from = pop_1st_bit(&b2); + b3 = pos.knight_attacks(from) & empty; + while(b3) { + to = pop_1st_bit(&b3); + mlist[n++].move = make_move(from, to); + } + } + + // Direct knight checks: + b2 = b1 & ~dc; + checkSqs = pos.knight_attacks(ksq) & empty; + while(b2) { + from = pop_1st_bit(&b2); + b3 = pos.knight_attacks(from) & checkSqs; + while(b3) { + to = pop_1st_bit(&b3); + mlist[n++].move = make_move(from, to); + } + } + } + + // Bishop moves + b1 = pos.bishops(us); + if(b1) { + // Discovered bishop checks: + b2 = b1 & dc; + while(b2) { + from = pop_1st_bit(&b2); + b3 = pos.bishop_attacks(from) & empty; + while(b3) { + to = pop_1st_bit(&b3); + mlist[n++].move = make_move(from, to); + } + } + + // Direct bishop checks: + b2 = b1 & ~dc; + checkSqs = pos.bishop_attacks(ksq) & empty; + while(b2) { + from = pop_1st_bit(&b2); + b3 = pos.bishop_attacks(from) & checkSqs; + while(b3) { + to = pop_1st_bit(&b3); + mlist[n++].move = make_move(from, to); + } + } + } + + // Rook moves + b1 = pos.rooks(us); + if(b1) { + // Discovered rook checks: + b2 = b1 & dc; + while(b2) { + from = pop_1st_bit(&b2); + b3 = pos.rook_attacks(from) & empty; + while(b3) { + to = pop_1st_bit(&b3); + mlist[n++].move = make_move(from, to); + } + } + + // Direct rook checks: + b2 = b1 & ~dc; + checkSqs = pos.rook_attacks(ksq) & empty; + while(b2) { + from = pop_1st_bit(&b2); + b3 = pos.rook_attacks(from) & checkSqs; + while(b3) { + to = pop_1st_bit(&b3); + mlist[n++].move = make_move(from, to); + } + } + } + + // Queen moves + b1 = pos.queens(us); + if(b1) { + // Discovered queen checks are impossible! + + // Direct queen checks: + checkSqs = pos.queen_attacks(ksq) & empty; + while(b1) { + from = pop_1st_bit(&b1); + b2 = pos.queen_attacks(from) & checkSqs; + while(b2) { + to = pop_1st_bit(&b2); + mlist[n++].move = make_move(from, to); + } + } + } + + // King moves + from = pos.king_square(us); + if(bit_is_set(dc, from)) { + b1 = pos.king_attacks(from) & empty & ~QueenPseudoAttacks[ksq]; + while(b1) { + to = pop_1st_bit(&b1); + mlist[n++].move = make_move(from, to); + } + } + + // TODO: Castling moves! + + return n; +} + + +/// generate_evasions() generates all check evasions when the side to move is +/// in check. Unlike the other move generation functions, this one generates +/// only legal moves. It returns the number of generated moves. This +/// function is very ugly, and needs cleaning up some time later. FIXME + +int generate_evasions(const Position &pos, MoveStack *mlist) { + Color us, them; + Bitboard checkers = pos.checkers(); + Bitboard pinned, b1, b2; + Square ksq, from, to; + int n = 0; + + assert(pos.is_ok()); + assert(pos.is_check()); + + us = pos.side_to_move(); + them = opposite_color(us); + + ksq = pos.king_square(us); + assert(pos.piece_on(ksq) == king_of_color(us)); + + // Generate evasions for king: + b1 = pos.king_attacks(ksq) & ~pos.pieces_of_color(us); + b2 = pos.occupied_squares(); + clear_bit(&b2, ksq); + while(b1) { + to = pop_1st_bit(&b1); + + // Make sure to is not attacked by the other side. This is a bit ugly, + // because we can't use Position::square_is_attacked. Instead we use + // the low-level bishop_attacks_bb and rook_attacks_bb with the bitboard + // b2 (the occupied squares with the king removed) in order to test whether + // the king will remain in check on the destination square. + if(((pos.pawn_attacks(us, to) & pos.pawns(them)) == EmptyBoardBB) && + ((pos.knight_attacks(to) & pos.knights(them)) == EmptyBoardBB) && + ((pos.king_attacks(to) & pos.kings(them)) == EmptyBoardBB) && + ((bishop_attacks_bb(to, b2) & pos.bishops_and_queens(them)) + == EmptyBoardBB) && + ((rook_attacks_bb(to, b2) & pos.rooks_and_queens(them)) == EmptyBoardBB)) + mlist[n++].move = make_move(ksq, to); + } + + + // Generate evasions for other pieces only if not double check. We use a + // simple bit twiddling hack here rather than calling count_1s in order to + // save some time (we know that pos.checkers() has at most two nonzero bits). + if(!(checkers & (checkers - 1))) { + Square checksq = first_1(checkers); + assert(pos.color_of_piece_on(checksq) == them); + + // Find pinned pieces: + pinned = pos.pinned_pieces(us); + + // Generate captures of the checking piece: + + // Pawn captures: + b1 = pos.pawn_attacks(them, checksq) & pos.pawns(us) & ~pinned; + while(b1) { + from = pop_1st_bit(&b1); + if(pawn_rank(us, checksq) == RANK_8) { + mlist[n++].move = make_promotion_move(from, checksq, QUEEN); + mlist[n++].move = make_promotion_move(from, checksq, ROOK); + mlist[n++].move = make_promotion_move(from, checksq, BISHOP); + mlist[n++].move = make_promotion_move(from, checksq, KNIGHT); + } + else + mlist[n++].move = make_move(from, checksq); + } + + // Knight captures: + b1 = pos.knight_attacks(checksq) & pos.knights(us) & ~pinned; + while(b1) { + from = pop_1st_bit(&b1); + mlist[n++].move = make_move(from, checksq); + } + + // Bishop and queen captures: + b1 = pos.bishop_attacks(checksq) & pos.bishops_and_queens(us) + & ~pinned; + while(b1) { + from = pop_1st_bit(&b1); + mlist[n++].move = make_move(from, checksq); + } + + // Rook and queen captures: + b1 = pos.rook_attacks(checksq) & pos.rooks_and_queens(us) + & ~pinned; + while(b1) { + from = pop_1st_bit(&b1); + mlist[n++].move = make_move(from, checksq); + } + + // Blocking check evasions are possible only if the checking piece is + // a slider: + if(checkers & pos.sliders()) { + Bitboard blockSquares = squares_between(checksq, ksq); + assert((pos.occupied_squares() & blockSquares) == EmptyBoardBB); + + // Pawn moves. Because a blocking evasion can never be a capture, we + // only generate pawn pushes. As so often, the code for pawns is a bit + // ugly, and uses separate clauses for white and black pawns. :-( + if(us == WHITE) { + // Find non-pinned pawns: + b1 = pos.pawns(WHITE) & ~pinned; + + // Single pawn pushes. We don't have to AND with empty squares here, + // because the blocking squares will always be empty. + b2 = (b1 << 8) & blockSquares; + while(b2) { + to = pop_1st_bit(&b2); + assert(pos.piece_on(to) == EMPTY); + if(square_rank(to) == RANK_8) { + mlist[n++].move = make_promotion_move(to - DELTA_N, to, QUEEN); + mlist[n++].move = make_promotion_move(to - DELTA_N, to, ROOK); + mlist[n++].move = make_promotion_move(to - DELTA_N, to, BISHOP); + mlist[n++].move = make_promotion_move(to - DELTA_N, to, KNIGHT); + } + else + mlist[n++].move = make_move(to - DELTA_N, to); + } + // Double pawn pushes. + b2 = (((b1 << 8) & pos.empty_squares() & Rank3BB) << 8) & blockSquares; + while(b2) { + to = pop_1st_bit(&b2); + assert(pos.piece_on(to) == EMPTY); + assert(square_rank(to) == RANK_4); + mlist[n++].move = make_move(to - DELTA_N - DELTA_N, to); + } + } + else { // (us == BLACK) + // Find non-pinned pawns: + b1 = pos.pawns(BLACK) & ~pinned; + + // Single pawn pushes. We don't have to AND with empty squares here, + // because the blocking squares will always be empty. + b2 = (b1 >> 8) & blockSquares; + while(b2) { + to = pop_1st_bit(&b2); + assert(pos.piece_on(to) == EMPTY); + if(square_rank(to) == RANK_1) { + mlist[n++].move = make_promotion_move(to - DELTA_S, to, QUEEN); + mlist[n++].move = make_promotion_move(to - DELTA_S, to, ROOK); + mlist[n++].move = make_promotion_move(to - DELTA_S, to, BISHOP); + mlist[n++].move = make_promotion_move(to - DELTA_S, to, KNIGHT); + } + else + mlist[n++].move = make_move(to - DELTA_S, to); + } + // Double pawn pushes. + b2 = (((b1 >> 8) & pos.empty_squares() & Rank6BB) >> 8) & blockSquares; + while(b2) { + to = pop_1st_bit(&b2); + assert(pos.piece_on(to) == EMPTY); + assert(square_rank(to) == RANK_5); + mlist[n++].move = make_move(to - DELTA_S - DELTA_S, to); + } + } + + // Knight moves + b1 = pos.knights(us) & ~pinned; + while(b1) { + from = pop_1st_bit(&b1); + b2 = pos.knight_attacks(from) & blockSquares; + while(b2) { + to = pop_1st_bit(&b2); + mlist[n++].move = make_move(from, to); + } + } + + // Bishop moves + b1 = pos.bishops(us) & ~pinned; + while(b1) { + from = pop_1st_bit(&b1); + b2 = pos.bishop_attacks(from) & blockSquares; + while(b2) { + to = pop_1st_bit(&b2); + mlist[n++].move = make_move(from, to); + } + } + + // Rook moves + b1 = pos.rooks(us) & ~pinned; + while(b1) { + from = pop_1st_bit(&b1); + b2 = pos.rook_attacks(from) & blockSquares; + while(b2) { + to = pop_1st_bit(&b2); + mlist[n++].move = make_move(from, to); + } + } + + // Queen moves + b1 = pos.queens(us) & ~pinned; + while(b1) { + from = pop_1st_bit(&b1); + b2 = pos.queen_attacks(from) & blockSquares; + while(b2) { + to = pop_1st_bit(&b2); + mlist[n++].move = make_move(from, to); + } + } + } + + // Finally, the ugly special case of en passant captures. An en passant + // capture can only be a check evasion if the check is not a discovered + // check. If pos.ep_square() is set, the last move made must have been + // a double pawn push. If, furthermore, the checking piece is a pawn, + // an en passant check evasion may be possible. + if(pos.ep_square() != SQ_NONE && (checkers & pos.pawns(them))) { + to = pos.ep_square(); + b1 = pos.pawn_attacks(them, to) & pos.pawns(us); + assert(b1 != EmptyBoardBB); + b1 &= ~pinned; + while(b1) { + from = pop_1st_bit(&b1); + + // Before generating the move, we have to make sure it is legal. + // This is somewhat tricky, because the two disappearing pawns may + // cause new "discovered checks". We test this by removing the + // two relevant bits from the occupied squares bitboard, and using + // the low-level bitboard functions for bishop and rook attacks. + b2 = pos.occupied_squares(); + clear_bit(&b2, from); + clear_bit(&b2, checksq); + if(((bishop_attacks_bb(ksq, b2) & pos.bishops_and_queens(them)) + == EmptyBoardBB) && + ((rook_attacks_bb(ksq, b2) & pos.rooks_and_queens(them)) + == EmptyBoardBB)) + mlist[n++].move = make_ep_move(from, to); + } + } + } + + return n; +} + + +/// generate_legal_moves() computes a complete list of legal moves in the +/// current position. This function is not very fast, and should be used +/// only in situations where performance is unimportant. It wouldn't be +/// very hard to write an efficient legal move generator, but for the moment +/// we don't need it. + +int generate_legal_moves(const Position &pos, MoveStack *mlist) { + assert(pos.is_ok()); + + if(pos.is_check()) + return generate_evasions(pos, mlist); + else { + int i, n; + Bitboard pinned = pos.pinned_pieces(pos.side_to_move()); + + // Generate pseudo-legal moves: + n = generate_captures(pos, mlist); + n += generate_noncaptures(pos, mlist + n); + + // Remove illegal moves from the list: + for(i = 0; i < n; i++) { + if(!pos.move_is_legal(mlist[i].move, pinned)) + mlist[i--].move = mlist[--n].move; + } + + return n; + } +} + + +/// generate_move_if_legal() takes a position and a (not necessarily +/// pseudo-legal) move and a pinned pieces bitboard as input, and tests +/// whether the move is legal. If the move is legal, the move itself is +/// returned. If not, the function returns MOVE_NONE. This function must +/// only be used when the side to move is not in check. + +Move generate_move_if_legal(const Position &pos, Move m, Bitboard pinned) { + Color us, them; + Square from, to; + Piece pc; + + assert(pos.is_ok()); + assert(!pos.is_check()); + assert(move_is_ok(m)); + + us = pos.side_to_move(); + them = opposite_color(us); + from = move_from(m); + pc = pos.piece_on(from); + + // If the from square is not occupied by a piece belonging to the side to + // move, the move is obviously not legal. + if(color_of_piece(pc) != us ) + return MOVE_NONE; + + to = move_to(m); + + // En passant moves: + if(move_is_ep(m)) { + + // The piece must be a pawn: + if(type_of_piece(pc) != PAWN) + return MOVE_NONE; + + // The destination square must be the en passant square: + if(to != pos.ep_square()) + return MOVE_NONE; + + assert(pos.square_is_empty(to)); + assert(pos.piece_on(to - pawn_push(us)) == pawn_of_color(them)); + + // The move is pseudo-legal. If it is legal, return it. + if(pos.move_is_legal(m)) + return m; + else + return MOVE_NONE; + } + + // Castling moves: + else if(move_is_short_castle(m)) { + + // The piece must be a king: + if(type_of_piece(pc) != KING) + return MOVE_NONE; + + // The side to move must still have the right to castle kingside: + if(!pos.can_castle_kingside(us)) + return MOVE_NONE; + + assert(from == pos.king_square(us)); + assert(to == pos.initial_kr_square(us)); + assert(pos.piece_on(to) == rook_of_color(us)); + + Square g1 = relative_square(us, SQ_G1); + Square f1 = relative_square(us, SQ_F1); + Square s; + bool illegal = false; + + for(s = Min(from, g1); s <= Max(from, g1); s++) + if((s != from && s != to && !pos.square_is_empty(s)) || + pos.square_is_attacked(s, them)) + illegal = true; + for(s = Min(to, f1); s <= Max(to, f1); s++) + if(s != from && s != to && !pos.square_is_empty(s)) + illegal = true; + + if(!illegal) + return m; + else + return MOVE_NONE; + } + else if(move_is_long_castle(m)) { + + // The piece must be a king: + if(type_of_piece(pc) != KING) + return MOVE_NONE; + + // The side to move must still have the right to castle kingside: + if(!pos.can_castle_queenside(us)) + return MOVE_NONE; + + assert(from == pos.king_square(us)); + assert(to == pos.initial_qr_square(us)); + assert(pos.piece_on(to) == rook_of_color(us)); + + Square c1 = relative_square(us, SQ_C1); + Square d1 = relative_square(us, SQ_D1); + Square s; + bool illegal = false; + + for(s = Min(from, c1); s <= Max(from, c1); s++) + if((s != from && s != to && !pos.square_is_empty(s)) || + pos.square_is_attacked(s, them)) + illegal = true; + for(s = Min(to, d1); s <= Max(to, d1); s++) + if(s != from && s != to && !pos.square_is_empty(s)) + illegal = true; + if(square_file(to) == FILE_B && + (pos.piece_on(to + DELTA_W) == rook_of_color(them) || + pos.piece_on(to + DELTA_W) == queen_of_color(them))) + illegal = true; + + if(!illegal) + return m; + else + return MOVE_NONE; + } + + // Normal moves + else { + + // The destination square cannot be occupied by a friendly piece: + if(pos.color_of_piece_on(to) == us) + return MOVE_NONE; + + // Proceed according to the type of the moving piece. + switch(type_of_piece(pc)) { + + case PAWN: + // Pawn moves, as usual, are somewhat messy. + if(us == WHITE) { + // If the destination square is on the 8th rank, the move must be a + // promotion. + if(square_rank(to) == RANK_8 && !move_promotion(m)) + return MOVE_NONE; + + // Proceed according to the square delta between the source and + // destionation squares. + switch(to - from) { + + case DELTA_NW: case DELTA_NE: + // Capture. The destination square must be occupied by an enemy piece + // (en passant captures was handled earlier). + if(pos.color_of_piece_on(to) != them) + return MOVE_NONE; + break; + + case DELTA_N: + // Pawn push. The destination square must be empty. + if(!pos.square_is_empty(to)) + return MOVE_NONE; + break; + + case DELTA_NN: + // Double pawn push. The destination square must be on the fourth + // rank, and both the destination square and the square between the + // source and destination squares must be empty. + if(square_rank(to) != RANK_4 || !pos.square_is_empty(to) || + !pos.square_is_empty(from + DELTA_N)) + return MOVE_NONE; + break; + + default: + return MOVE_NONE; + } + } + else { // (us == BLACK) + // If the destination square is on the 1st rank, the move must be a + // promotion. + if(square_rank(to) == RANK_1 && !move_promotion(m)) + return MOVE_NONE; + + // Proceed according to the square delta between the source and + // destionation squares. + switch(to - from) { + + case DELTA_SW: case DELTA_SE: + // Capture. The destination square must be occupied by an enemy piece + // (en passant captures was handled earlier). + if(pos.color_of_piece_on(to) != them) + return MOVE_NONE; + break; + + case DELTA_S: + // Pawn push. The destination square must be empty. + if(!pos.square_is_empty(to)) + return MOVE_NONE; + break; + + case DELTA_SS: + // Double pawn push. The destination square must be on the fifth + // rank, and both the destination square and the square between the + // source and destination squares must be empty. + if(square_rank(to) != RANK_5 || !pos.square_is_empty(to) || + !pos.square_is_empty(from + DELTA_S)) + return MOVE_NONE; + break; + + default: + return MOVE_NONE; + } + } + // The move is pseudo-legal. Return it if it is legal. + if(pos.move_is_legal(m)) + return m; + else + return MOVE_NONE; + break; + + case KNIGHT: + if(pos.knight_attacks_square(from, to) && pos.move_is_legal(m) && + !move_promotion(m)) + return m; + else + return MOVE_NONE; + break; + + case BISHOP: + if(pos.bishop_attacks_square(from, to) && pos.move_is_legal(m) && + !move_promotion(m)) + return m; + else + return MOVE_NONE; + break; + + case ROOK: + if(pos.rook_attacks_square(from, to) && pos.move_is_legal(m) && + !move_promotion(m)) + return m; + else + return MOVE_NONE; + break; + + case QUEEN: + if(pos.queen_attacks_square(from, to) && pos.move_is_legal(m) && + !move_promotion(m)) + return m; + else + return MOVE_NONE; + break; + + case KING: + if(pos.king_attacks_square(from, to) && pos.move_is_legal(m) && + !move_promotion(m)) + return m; + else + return MOVE_NONE; + break; + + default: + assert(false); + } + } + + assert(false); + return MOVE_NONE; +} + + +namespace { + + int generate_white_pawn_captures(const Position &pos, MoveStack *mlist) { + Bitboard pawns = pos.pawns(WHITE); + Bitboard enemyPieces = pos.pieces_of_color(BLACK); + Bitboard b1, b2; + Square sq; + int n = 0; + + // Captures in the a1-h8 direction: + b1 = (pawns << 9) & ~FileABB & enemyPieces; + + // Promotions: + b2 = b1 & Rank8BB; + while(b2) { + sq = pop_1st_bit(&b2); + mlist[n++].move = make_promotion_move(sq - DELTA_NE, sq, QUEEN); + } + + // Non-promotions: + b2 = b1 & ~Rank8BB; + while(b2) { + sq = pop_1st_bit(&b2); + mlist[n++].move = make_move(sq - DELTA_NE, sq); + } + + // Captures in the h1-a8 direction: + b1 = (pawns << 7) & ~FileHBB & enemyPieces; + + // Promotions: + b2 = b1 & Rank8BB; + while(b2) { + sq = pop_1st_bit(&b2); + mlist[n++].move = make_promotion_move(sq - DELTA_NW, sq, QUEEN); + } + + // Non-promotions: + b2 = b1 & ~Rank8BB; + while(b2) { + sq = pop_1st_bit(&b2); + mlist[n++].move = make_move(sq - DELTA_NW, sq); + } + + // Non-capturing promotions: + b1 = (pawns << 8) & pos.empty_squares() & Rank8BB; + while(b1) { + sq = pop_1st_bit(&b1); + mlist[n++].move = make_promotion_move(sq - DELTA_N, sq, QUEEN); + } + + // En passant captures: + if(pos.ep_square() != SQ_NONE) { + assert(square_rank(pos.ep_square()) == RANK_6); + b1 = pawns & pos.black_pawn_attacks(pos.ep_square()); + assert(b1 != EmptyBoardBB); + while(b1) { + sq = pop_1st_bit(&b1); + mlist[n++].move = make_ep_move(sq, pos.ep_square()); + } + } + + return n; + } + + + int generate_black_pawn_captures(const Position &pos, MoveStack *mlist) { + Bitboard pawns = pos.pawns(BLACK); + Bitboard enemyPieces = pos.pieces_of_color(WHITE); + Bitboard b1, b2; + Square sq; + int n = 0; + + // Captures in the a8-h1 direction: + b1 = (pawns >> 7) & ~FileABB & enemyPieces; + + // Promotions: + b2 = b1 & Rank1BB; + while(b2) { + sq = pop_1st_bit(&b2); + mlist[n++].move = make_promotion_move(sq - DELTA_SE, sq, QUEEN); + } + + // Non-promotions: + b2 = b1 & ~Rank1BB; + while(b2) { + sq = pop_1st_bit(&b2); + mlist[n++].move = make_move(sq - DELTA_SE, sq); + } + + // Captures in the h8-a1 direction: + b1 = (pawns >> 9) & ~FileHBB & enemyPieces; + + // Promotions: + b2 = b1 & Rank1BB; + while(b2) { + sq = pop_1st_bit(&b2); + mlist[n++].move = make_promotion_move(sq - DELTA_SW, sq, QUEEN); + } + + // Non-promotions: + b2 = b1 & ~Rank1BB; + while(b2) { + sq = pop_1st_bit(&b2); + mlist[n++].move = make_move(sq - DELTA_SW, sq); + } + + // Non-capturing promotions: + b1 = (pawns >> 8) & pos.empty_squares() & Rank1BB; + while(b1) { + sq = pop_1st_bit(&b1); + mlist[n++].move = make_promotion_move(sq - DELTA_S, sq, QUEEN); + } + + // En passant captures: + if(pos.ep_square() != SQ_NONE) { + assert(square_rank(pos.ep_square()) == RANK_3); + b1 = pawns & pos.white_pawn_attacks(pos.ep_square()); + assert(b1 != EmptyBoardBB); + while(b1) { + sq = pop_1st_bit(&b1); + mlist[n++].move = make_ep_move(sq, pos.ep_square()); + } + } + + return n; + } + + + int generate_white_pawn_noncaptures(const Position &pos, MoveStack *mlist) { + Bitboard pawns = pos.pawns(WHITE); + Bitboard enemyPieces = pos.pieces_of_color(BLACK); + Bitboard emptySquares = pos.empty_squares(); + Bitboard b1, b2; + Square sq; + int n = 0; + + // Underpromotion captures in the a1-h8 direction: + b1 = (pawns << 9) & ~FileABB & enemyPieces & Rank8BB; + while(b1) { + sq = pop_1st_bit(&b1); + mlist[n++].move = make_promotion_move(sq - DELTA_NE, sq, ROOK); + mlist[n++].move = make_promotion_move(sq - DELTA_NE, sq, BISHOP); + mlist[n++].move = make_promotion_move(sq - DELTA_NE, sq, KNIGHT); + } + + // Underpromotion captures in the h1-a8 direction: + b1 = (pawns << 7) & ~FileHBB & enemyPieces & Rank8BB; + while(b1) { + sq = pop_1st_bit(&b1); + mlist[n++].move = make_promotion_move(sq - DELTA_NW, sq, ROOK); + mlist[n++].move = make_promotion_move(sq - DELTA_NW, sq, BISHOP); + mlist[n++].move = make_promotion_move(sq - DELTA_NW, sq, KNIGHT); + } + + // Single pawn pushes: + b1 = (pawns << 8) & emptySquares; + b2 = b1 & Rank8BB; + while(b2) { + sq = pop_1st_bit(&b2); + mlist[n++].move = make_promotion_move(sq - DELTA_N, sq, ROOK); + mlist[n++].move = make_promotion_move(sq - DELTA_N, sq, BISHOP); + mlist[n++].move = make_promotion_move(sq - DELTA_N, sq, KNIGHT); + } + b2 = b1 & ~Rank8BB; + while(b2) { + sq = pop_1st_bit(&b2); + mlist[n++].move = make_move(sq - DELTA_N, sq); + } + + // Double pawn pushes: + b2 = ((b1 & Rank3BB) << 8) & emptySquares; + while(b2) { + sq = pop_1st_bit(&b2); + mlist[n++].move = make_move(sq - DELTA_N - DELTA_N, sq); + } + + return n; + } + + + int generate_black_pawn_noncaptures(const Position &pos, MoveStack *mlist) { + Bitboard pawns = pos.pawns(BLACK); + Bitboard enemyPieces = pos.pieces_of_color(WHITE); + Bitboard emptySquares = pos.empty_squares(); + Bitboard b1, b2; + Square sq; + int n = 0; + + // Underpromotion captures in the a8-h1 direction: + b1 = (pawns >> 7) & ~FileABB & enemyPieces & Rank1BB; + while(b1) { + sq = pop_1st_bit(&b1); + mlist[n++].move = make_promotion_move(sq - DELTA_SE, sq, ROOK); + mlist[n++].move = make_promotion_move(sq - DELTA_SE, sq, BISHOP); + mlist[n++].move = make_promotion_move(sq - DELTA_SE, sq, KNIGHT); + } + + // Underpromotion captures in the h8-a1 direction: + b1 = (pawns >> 9) & ~FileHBB & enemyPieces & Rank1BB; + while(b1) { + sq = pop_1st_bit(&b1); + mlist[n++].move = make_promotion_move(sq - DELTA_SW, sq, ROOK); + mlist[n++].move = make_promotion_move(sq - DELTA_SW, sq, BISHOP); + mlist[n++].move = make_promotion_move(sq - DELTA_SW, sq, KNIGHT); + } + + // Single pawn pushes: + b1 = (pawns >> 8) & emptySquares; + b2 = b1 & Rank1BB; + while(b2) { + sq = pop_1st_bit(&b2); + mlist[n++].move = make_promotion_move(sq - DELTA_S, sq, ROOK); + mlist[n++].move = make_promotion_move(sq - DELTA_S, sq, BISHOP); + mlist[n++].move = make_promotion_move(sq - DELTA_S, sq, KNIGHT); + } + b2 = b1 & ~Rank1BB; + while(b2) { + sq = pop_1st_bit(&b2); + mlist[n++].move = make_move(sq - DELTA_S, sq); + } + + // Double pawn pushes: + b2 = ((b1 & Rank6BB) >> 8) & emptySquares; + while(b2) { + sq = pop_1st_bit(&b2); + mlist[n++].move = make_move(sq - DELTA_S - DELTA_S, sq); + } + + return n; + } + + + int generate_knight_moves(const Position &pos, MoveStack *mlist, + Color side, Bitboard target) { + Square from, to; + Bitboard b; + int i, n = 0; + + for(i = 0; i < pos.knight_count(side); i++) { + from = pos.knight_list(side, i); + b = pos.knight_attacks(from) & target; + while(b) { + to = pop_1st_bit(&b); + mlist[n++].move = make_move(from, to); + } + } + return n; + } + + + int generate_bishop_moves(const Position &pos, MoveStack *mlist, + Color side, Bitboard target) { + Square from, to; + Bitboard b; + int i, n = 0; + + for(i = 0; i < pos.bishop_count(side); i++) { + from = pos.bishop_list(side, i); + b = pos.bishop_attacks(from) & target; + while(b) { + to = pop_1st_bit(&b); + mlist[n++].move = make_move(from, to); + } + } + return n; + } + + + int generate_rook_moves(const Position &pos, MoveStack *mlist, + Color side, Bitboard target) { + Square from, to; + Bitboard b; + int i, n = 0; + + for(i = 0; i < pos.rook_count(side); i++) { + from = pos.rook_list(side, i); + b = pos.rook_attacks(from) & target; + while(b) { + to = pop_1st_bit(&b); + mlist[n++].move = make_move(from, to); + } + } + return n; + } + + + int generate_queen_moves(const Position &pos, MoveStack *mlist, + Color side, Bitboard target) { + Square from, to; + Bitboard b; + int i, n = 0; + + for(i = 0; i < pos.queen_count(side); i++) { + from = pos.queen_list(side, i); + b = pos.queen_attacks(from) & target; + while(b) { + to = pop_1st_bit(&b); + mlist[n++].move = make_move(from, to); + } + } + return n; + } + + + int generate_king_moves(const Position &pos, MoveStack *mlist, + Square from, Bitboard target) { + Square to; + Bitboard b; + int n = 0; + + b = pos.king_attacks(from) & target; + while(b) { + to = pop_1st_bit(&b); + mlist[n++].move = make_move(from, to); + } + return n; + } + + + int generate_castle_moves(const Position &pos, MoveStack *mlist, Color us) { + int n = 0; + + if(pos.can_castle(us)) { + Color them = opposite_color(us); + Square ksq = pos.king_square(us); + assert(pos.piece_on(ksq) == king_of_color(us)); + + if(pos.can_castle_kingside(us)) { + Square rsq = pos.initial_kr_square(us); + Square g1 = relative_square(us, SQ_G1); + Square f1 = relative_square(us, SQ_F1); + Square s; + bool illegal = false; + + assert(pos.piece_on(rsq) == rook_of_color(us)); + + for(s = Min(ksq, g1); s <= Max(ksq, g1); s++) + if((s != ksq && s != rsq && pos.square_is_occupied(s)) + || pos.square_is_attacked(s, them)) + illegal = true; + for(s = Min(rsq, f1); s <= Max(rsq, f1); s++) + if(s != ksq && s != rsq && pos.square_is_occupied(s)) + illegal = true; + + if(!illegal) + mlist[n++].move = make_castle_move(ksq, rsq); + } + + if(pos.can_castle_queenside(us)) { + Square rsq = pos.initial_qr_square(us); + Square c1 = relative_square(us, SQ_C1); + Square d1 = relative_square(us, SQ_D1); + Square s; + bool illegal = false; + + assert(pos.piece_on(rsq) == rook_of_color(us)); + + for(s = Min(ksq, c1); s <= Max(ksq, c1); s++) + if((s != ksq && s != rsq && pos.square_is_occupied(s)) + || pos.square_is_attacked(s, them)) + illegal = true; + for(s = Min(rsq, d1); s <= Max(rsq, d1); s++) + if(s != ksq && s != rsq && pos.square_is_occupied(s)) + illegal = true; + if(square_file(rsq) == FILE_B && + (pos.piece_on(relative_square(us, SQ_A1)) == rook_of_color(them) || + pos.piece_on(relative_square(us, SQ_A1)) == queen_of_color(them))) + illegal = true; + + if(!illegal) + mlist[n++].move = make_castle_move(ksq, rsq); + } + } + + return n; + } + +} diff --git a/src/movegen.h b/src/movegen.h new file mode 100644 index 00000000..93c1c4ab --- /dev/null +++ b/src/movegen.h @@ -0,0 +1,42 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +#if !defined(MOVEGEN_H_INCLUDED) +#define MOVEGEN_H_INCLUDED + +//// +//// Includes +//// + +#include "position.h" + + +//// +//// Prototypes +//// + +extern int generate_captures(const Position &pos, MoveStack *mlist); +extern int generate_noncaptures(const Position &pos, MoveStack *mlist); +extern int generate_checks(const Position &pos, MoveStack *mlist, Bitboard dc); +extern int generate_evasions(const Position &pos, MoveStack *mlist); +extern int generate_legal_moves(const Position &pos, MoveStack *mlist); +extern Move generate_move_if_legal(const Position &pos, Move m, + Bitboard pinned); + +#endif // !defined(MOVEGEN_H_INCLUDED) diff --git a/src/movepick.cpp b/src/movepick.cpp new file mode 100644 index 00000000..6df070bb --- /dev/null +++ b/src/movepick.cpp @@ -0,0 +1,503 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +//// +//// Includes +//// + +#include + +#include "history.h" +#include "movegen.h" +#include "movepick.h" +#include "search.h" +#include "value.h" + + +//// +//// Local definitions +//// + +namespace { + + /// Types + + enum MovegenPhase { + PH_TT_MOVE, // Transposition table move + PH_MATE_KILLER, // Mate killer from the current ply + PH_GOOD_CAPTURES, // Queen promotions and captures with SEE values >= 0 + PH_BAD_CAPTURES, // Queen promotions and captures with SEE valuse <= 0 + PH_KILLER_1, // Killer move 1 from the current ply (not used yet). + PH_KILLER_2, // Killer move 2 from the current ply (not used yet). + PH_NONCAPTURES, // Non-captures and underpromotions + PH_EVASIONS, // Check evasions + PH_QCAPTURES, // Captures in quiescence search + PH_QCHECKS, // Checks in quiescence search + PH_STOP + }; + + + /// Variables + + MovegenPhase PhaseTable[32]; + int MainSearchPhaseIndex; + int EvasionsPhaseIndex; + int QsearchWithChecksPhaseIndex; + int QsearchWithoutChecksPhaseIndex; + +} + + +//// +//// Functions +//// + + +/// Constructor for the MovePicker class. Apart from the position for which +/// it is asked to pick legal moves, MovePicker also wants some 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 about how important good +/// move ordering is at the current node. + +MovePicker::MovePicker(Position &p, bool pvnode, Move ttm, Move mk, + Move k1, Move k2, Depth dpth) { + pos = &p; + pvNode = pvnode; + ttMove = ttm; + mateKiller = (mk == ttm)? MOVE_NONE : mk; + killer1 = k1; + killer2 = k2; + depth = dpth; + movesPicked = 0; + numOfMoves = 0; + numOfBadCaptures = 0; + dc = p.discovered_check_candidates(p.side_to_move()); + + if(p.is_check()) + phaseIndex = EvasionsPhaseIndex; + else if(depth > Depth(0)) + phaseIndex = MainSearchPhaseIndex; + else if(depth == Depth(0)) + phaseIndex = QsearchWithChecksPhaseIndex; + else + phaseIndex = QsearchWithoutChecksPhaseIndex; + + pinned = p.pinned_pieces(p.side_to_move()); + + finished = false; +} + + +/// MovePicker::get_next_move() is the most important method of the MovePicker +/// class. It returns a new legal move every time it is called, until there +/// are no more moves left of the types we are interested in. + +Move MovePicker::get_next_move() { + Move move; + + while(true) { + // If we already have a list of generated moves, pick the best move from + // the list, and return it: + move = this->pick_move_from_list(); + if(move != MOVE_NONE) { + assert(move_is_ok(move)); + return move; + } + + // Next phase: + phaseIndex++; + switch(PhaseTable[phaseIndex]) { + + case PH_TT_MOVE: + if(ttMove != MOVE_NONE) { + assert(move_is_ok(ttMove)); + Move m = generate_move_if_legal(*pos, ttMove, pinned); + if(m != MOVE_NONE) { + assert(m == ttMove); + return m; + } + } + break; + + case PH_MATE_KILLER: + if(mateKiller != MOVE_NONE) { + assert(move_is_ok(mateKiller)); + Move m = generate_move_if_legal(*pos, mateKiller, pinned); + if(m != MOVE_NONE) { + assert(m == mateKiller); + return m; + } + } + break; + + case PH_GOOD_CAPTURES: + // pinned = pos->pinned_pieces(pos->side_to_move()); + numOfMoves = generate_captures(*pos, moves); + this->score_captures(); + movesPicked = 0; + break; + + case PH_BAD_CAPTURES: + badCapturesPicked = 0; + break; + + case PH_NONCAPTURES: + numOfMoves = generate_noncaptures(*pos, moves); + this->score_noncaptures(); + movesPicked = 0; + break; + + case PH_EVASIONS: + assert(pos->is_check()); + // pinned = pos->pinned_pieces(pos->side_to_move()); + numOfMoves = generate_evasions(*pos, moves); + this->score_evasions(); + movesPicked = 0; + break; + + case PH_QCAPTURES: + // pinned = pos->pinned_pieces(pos->side_to_move()); + numOfMoves = generate_captures(*pos, moves); + this->score_qcaptures(); + movesPicked = 0; + break; + + case PH_QCHECKS: + numOfMoves = generate_checks(*pos, moves, dc); + movesPicked = 0; + break; + + case PH_STOP: + return MOVE_NONE; + + default: + assert(false); + return MOVE_NONE; + } + } + + assert(false); + + return MOVE_NONE; +} + + +/// A variant of get_next_move() which takes a lock as a parameter, used to +/// prevent multiple threads from picking the same move at a split point. + +Move MovePicker::get_next_move(Lock &lock) { + Move m; + + lock_grab(&lock); + if(finished) { + lock_release(&lock); + return MOVE_NONE; + } + m = this->get_next_move(); + if(m == MOVE_NONE) + finished = true; + lock_release(&lock); + + return m; +} + + +/// MovePicker::number_of_moves() simply returns the numOfMoves member +/// variable. It is intended to be used in positions where the side to move +/// is in check, for detecting checkmates or situations where there is only +/// a single reply to check. + +int MovePicker::number_of_moves() const { + return numOfMoves; +} + + +/// MovePicker::score_captures(), MovePicker::score_noncaptures(), +/// MovePicker::score_evasions() and MovePicker::score_qcaptures() assign a +/// numerical move ordering score to each move in a move list. The moves +/// with highest scores will be picked first by +/// MovePicker::pick_move_from_list(). + +void MovePicker::score_captures() { + // 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. + for(int i = 0; i < numOfMoves; i++) { + int seeValue = pos->see(moves[i].move); + if(seeValue >= 0) { + if(move_promotion(moves[i].move)) + moves[i].score = QueenValueMidgame; + else + moves[i].score = + int(pos->midgame_value_of_piece_on(move_to(moves[i].move))) - + int(pos->type_of_piece_on(move_from(moves[i].move))); + } + else + moves[i].score = seeValue; + + } +} + +void MovePicker::score_noncaptures() { + for(int i = 0; i < numOfMoves; i++) { + Move m = moves[i].move; + if(m == killer1) + moves[i].score = HistoryMax + 2; + else if(m == killer2) + moves[i].score = HistoryMax + 1; + else + moves[i].score = H.move_ordering_score(pos->piece_on(move_from(m)), m); + } +} + +void MovePicker::score_evasions() { + for(int i = 0; i < numOfMoves; i++) { + Move m = moves[i].move; + if(m == ttMove) + moves[i].score = 2*HistoryMax; + else if(!pos->square_is_empty(move_to(m))) { + int seeScore = pos->see(m); + moves[i].score = (seeScore >= 0)? seeScore + HistoryMax : seeScore; + } + else + moves[i].score = H.move_ordering_score(pos->piece_on(move_from(m)), m); + } +} + +void MovePicker::score_qcaptures() { + // Use MVV/LVA ordering. + for(int i = 0; i < numOfMoves; i++) { + Move m = moves[i].move; + if(move_promotion(m)) + moves[i].score = QueenValueMidgame; + else + moves[i].score = + int(pos->midgame_value_of_piece_on(move_to(m))) - + int(pos->midgame_value_of_piece_on(move_to(m))) / 64; + } +} + + +/// MovePicker::pick_move_from_list() picks the move with the biggest score +/// from a list of generated moves (moves[] or badCaptures[], depending on +/// the current move generation phase). It takes care not to return the +/// transposition table move if that has already been serched previously. +/// While picking captures in the PH_GOOD_CAPTURES phase (i.e. while picking +/// non-losing captures in the main search), it moves all captures with +/// negative SEE values to the badCaptures[] array. + +Move MovePicker::pick_move_from_list() { + int bestScore = -10000000; + int bestIndex; + Move move; + + switch(PhaseTable[phaseIndex]) { + + case PH_GOOD_CAPTURES: + assert(!pos->is_check()); + assert(movesPicked >= 0); + while(movesPicked < numOfMoves) { + bestScore = -10000000; + bestIndex = -1; + for(int i = movesPicked; i < numOfMoves; i++) { + if(moves[i].score < 0) { + // Losing capture, move it to the badCaptures[] array + assert(numOfBadCaptures < 63); + badCaptures[numOfBadCaptures++] = moves[i]; + moves[i--] = moves[--numOfMoves]; + } + else if(moves[i].score > bestScore) { + bestIndex = i; + bestScore = moves[i].score; + } + } + if(bestIndex != -1) { // Found a good capture + MoveStack tmp = moves[movesPicked]; + moves[movesPicked] = moves[bestIndex]; + moves[bestIndex] = tmp; + move = moves[movesPicked++].move; + if(move != ttMove && move != mateKiller && + pos->move_is_legal(move, pinned)) + return move; + } + } + break; + + case PH_NONCAPTURES: + assert(!pos->is_check()); + assert(movesPicked >= 0); + while(movesPicked < numOfMoves) { + bestScore = -10000000; + + // If this is a PV node or we have only picked a few moves, scan + // the entire move list for the best move. If many moves have already + // been searched and it is not a PV node, we are probably failing low + // anyway, so we just pick the first move from the list. + if(pvNode || movesPicked < 12) { + bestIndex = -1; + for(int i = movesPicked; i < numOfMoves; i++) + if(moves[i].score > bestScore) { + bestIndex = i; + bestScore = moves[i].score; + } + } + else + bestIndex = movesPicked; + + if(bestIndex != -1) { + MoveStack tmp = moves[movesPicked]; + moves[movesPicked] = moves[bestIndex]; + moves[bestIndex] = tmp; + move = moves[movesPicked++].move; + if(move != ttMove && move != mateKiller && + pos->move_is_legal(move, pinned)) + return move; + } + } + break; + + case PH_EVASIONS: + assert(pos->is_check()); + assert(movesPicked >= 0); + while(movesPicked < numOfMoves) { + bestScore = -10000000; + bestIndex = -1; + for(int i = movesPicked; i < numOfMoves; i++) + if(moves[i].score > bestScore) { + bestIndex = i; + bestScore = moves[i].score; + } + + if(bestIndex != -1) { + MoveStack tmp = moves[movesPicked]; + moves[movesPicked] = moves[bestIndex]; + moves[bestIndex] = tmp; + move = moves[movesPicked++].move; + return move; + } + } + break; + + case PH_BAD_CAPTURES: + assert(!pos->is_check()); + assert(badCapturesPicked >= 0); + // It's probably a good idea to use SEE move ordering here, instead + // of just picking the first move. FIXME + while(badCapturesPicked < numOfBadCaptures) { + move = badCaptures[badCapturesPicked++].move; + if(move != ttMove && move != mateKiller && + pos->move_is_legal(move, pinned)) + return move; + } + break; + + case PH_QCAPTURES: + assert(!pos->is_check()); + assert(movesPicked >= 0); + while(movesPicked < numOfMoves) { + bestScore = -10000000; + if(movesPicked < 4) { + bestIndex = -1; + for(int i = movesPicked; i < numOfMoves; i++) + if(moves[i].score > bestScore) { + bestIndex = i; + bestScore = moves[i].score; + } + } + else + bestIndex = movesPicked; + + if(bestIndex != -1) { + MoveStack tmp = moves[movesPicked]; + moves[movesPicked] = moves[bestIndex]; + moves[bestIndex] = tmp; + + move = moves[movesPicked++].move; + // Remember to change the line below if we decide to hash the qsearch! + // Maybe also postpone the legality check until after futility pruning? + if(/* move != ttMove && */ pos->move_is_legal(move, pinned)) + return move; + } + } + break; + + case PH_QCHECKS: + assert(!pos->is_check()); + assert(movesPicked >= 0); + // Perhaps we should do something better than just picking the first + // move here? FIXME + while(movesPicked < numOfMoves) { + move = moves[movesPicked++].move; + // Remember to change the line below if we decide to hash the qsearch! + if(/* move != ttMove && */ pos->move_is_legal(move, pinned)) + return move; + } + break; + + default: + break; + } + + return MOVE_NONE; +} + + +/// MovePicker::init_phase_table() initializes the PhaseTable[], +/// MainSearchPhaseIndex, EvasionPhaseIndex, QsearchWithChecksPhaseIndex +/// and QsearchWithoutChecksPhaseIndex variables. It is only called once +/// during program startup, and never again while the program is running. + +void MovePicker::init_phase_table() { + int i = 0; + + // Main search + MainSearchPhaseIndex = i - 1; + PhaseTable[i++] = PH_TT_MOVE; + PhaseTable[i++] = PH_MATE_KILLER; + PhaseTable[i++] = PH_GOOD_CAPTURES; + // PH_KILLER_1 and PH_KILLER_2 are not yet used. + // PhaseTable[i++] = PH_KILLER_1; + // PhaseTable[i++] = PH_KILLER_2; + PhaseTable[i++] = PH_NONCAPTURES; + PhaseTable[i++] = PH_BAD_CAPTURES; + PhaseTable[i++] = PH_STOP; + + // Check evasions + EvasionsPhaseIndex = i - 1; + PhaseTable[i++] = PH_EVASIONS; + PhaseTable[i++] = PH_STOP; + + // Quiescence search with checks + QsearchWithChecksPhaseIndex = i - 1; + PhaseTable[i++] = PH_QCAPTURES; + PhaseTable[i++] = PH_QCHECKS; + PhaseTable[i++] = PH_STOP; + + // Quiescence search without checks + QsearchWithoutChecksPhaseIndex = i - 1; + PhaseTable[i++] = PH_QCAPTURES; + PhaseTable[i++] = PH_STOP; +} diff --git a/src/movepick.h b/src/movepick.h new file mode 100644 index 00000000..ca052c7b --- /dev/null +++ b/src/movepick.h @@ -0,0 +1,89 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +#if !defined MOVEPICK_H_INCLUDED +#define MOVEPICK_H_INCLUDED + +//// +//// Includes +//// + +#include "depth.h" +#include "lock.h" +#include "position.h" + + +//// +//// Types +//// + +/// MovePicker is a class which is used to pick one legal move at a time from +/// the current position. It is initialized with a Position object and a few +/// moves we have reason to believe are good. The most important method is +/// MovePicker::pick_next_move(), which returns a new legal move each time it +/// is called, until there are no legal 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 be strongest first. + +class MovePicker { + +public: + MovePicker(Position &p, bool pvnode, Move ttm, Move mk, Move k1, Move k2, + Depth dpth); + Move get_next_move(); + Move get_next_move(Lock &lock); + int number_of_moves() const; + int current_move_score() const; + Bitboard discovered_check_candidates(); + + static void init_phase_table(); + +private: + void score_captures(); + void score_noncaptures(); + void score_evasions(); + void score_qcaptures(); + Move pick_move_from_list(); + + Position *pos; + Move ttMove, mateKiller, killer1, killer2; + Bitboard pinned, dc; + MoveStack moves[256], badCaptures[64]; + bool pvNode; + Depth depth; + int phaseIndex; + int numOfMoves, numOfBadCaptures; + int movesPicked, badCapturesPicked; + bool finished; +}; + + +//// +//// Inline functions +//// + +/// MovePicker::discovered_check_candidates() returns a bitboard containing +/// all pieces which can possibly give discovered check. This bitboard is +/// computed by the constructor function. + +inline Bitboard MovePicker::discovered_check_candidates() { + return dc; +} + +#endif // !defined(MOVEPICK_H_INCLUDED) diff --git a/src/pawns.cpp b/src/pawns.cpp new file mode 100644 index 00000000..19a37ee3 --- /dev/null +++ b/src/pawns.cpp @@ -0,0 +1,385 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +//// +//// Includes +//// + +#include + +#include "pawns.h" + + +//// +//// Local definitions +//// + +namespace { + + /// Constants and variables + + // Doubled pawn penalty by file, middle game. + const Value DoubledPawnMidgamePenalty[8] = { + Value(20), Value(30), Value(34), Value(34), + Value(34), Value(34), Value(30), Value(20) + }; + + // Doubled pawn penalty by file, endgame. + const Value DoubledPawnEndgamePenalty[8] = { + Value(35), Value(40), Value(40), Value(40), + Value(40), Value(40), Value(40), Value(35) + }; + + // Isolated pawn penalty by file, middle game. + const Value IsolatedPawnMidgamePenalty[8] = { + Value(20), Value(30), Value(34), Value(34), + Value(34), Value(34), Value(30), Value(20) + }; + + // Isolated pawn penalty by file, endgame. + const Value IsolatedPawnEndgamePenalty[8] = { + Value(35), Value(40), Value(40), Value(40), + Value(40), Value(40), Value(40), Value(35) + }; + + // Backward pawn penalty by file, middle game. + const Value BackwardPawnMidgamePenalty[8] = { + Value(16), Value(24), Value(27), Value(27), + Value(27), Value(27), Value(24), Value(16) + }; + + // Backward pawn penalty by file, endgame. + const Value BackwardPawnEndgamePenalty[8] = { + Value(28), Value(32), Value(32), Value(32), + Value(32), Value(32), Value(32), Value(28) + }; + + // Pawn chain membership bonus by file, middle game. + const Value ChainMidgameBonus[8] = { + Value(14), Value(16), Value(17), Value(18), + Value(18), Value(17), Value(16), Value(14) + }; + + // Pawn chain membership bonus by file, endgame. + const Value ChainEndgameBonus[8] = { + Value(16), Value(16), Value(16), Value(16), + Value(16), Value(16), Value(16), Value(16) + }; + + // Candidate passed pawn bonus by rank, middle game. + const Value CandidateMidgameBonus[8] = { + Value(0), Value(12), Value(12), Value(20), + Value(40), Value(90), Value(0), Value(0) + }; + + // Candidate passed pawn bonus by rank, endgame. + const Value CandidateEndgameBonus[8] = { + Value(0), Value(24), Value(24), Value(40), + Value(80), Value(180), Value(0), Value(0) + }; + + // Evaluate pawn storms? + const bool EvaluatePawnStorms = true; + + // Pawn storm tables for positions with opposite castling: + const int QStormTable[64] = { + 0, 0, 0, 0, 0, 0, 0, 0, + -22, -22, -22, -13, -4, 0, 0, 0, + -4, -9, -9, -9, -4, 0, 0, 0, + 9, 18, 22, 18, 9, 0, 0, 0, + 22, 31, 31, 22, 0, 0, 0, 0, + 31, 40, 40, 31, 0, 0, 0, 0, + 31, 40, 40, 31, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 + }; + + const int KStormTable[64] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, -4, -13, -22, -27, -27, + 0, 0, 0, -4, -9, -13, -18, -18, + 0, 0, 0, 0, 9, 9, 9, 9, + 0, 0, 0, 0, 9, 18, 27, 27, + 0, 0, 0, 0, 9, 27, 40, 36, + 0, 0, 0, 0, 0, 31, 40, 31, + 0, 0, 0, 0, 0, 0, 0, 0 + }; + + // Pawn storm open file bonuses by file: + const int KStormOpenFileBonus[8] = { + 45, 45, 30, 0, 0, 0, 0, 0 + }; + + const int QStormOpenFileBonus[8] = { + 0, 0, 0, 0, 0, 30, 45, 30 + }; + +} + + +//// +//// Functions +//// + +/// Constructor + +PawnInfoTable::PawnInfoTable(unsigned numOfEntries) { + size = numOfEntries; + entries = new PawnInfo[size]; + if(entries == NULL) { + std::cerr << "Failed to allocate " << (numOfEntries * sizeof(PawnInfo)) + << " bytes for pawn hash table." << std::endl; + exit(EXIT_FAILURE); + } + this->clear(); +} + + +/// Destructor + +PawnInfoTable::~PawnInfoTable() { + delete [] entries; +} + + +/// PawnInfoTable::clear() clears the pawn hash table by setting all +/// entries to 0. + +void PawnInfoTable::clear() { + memset(entries, 0, size * sizeof(PawnInfo)); +} + + +/// PawnInfoTable::get_pawn_info() takes a position object as input, computes +/// a PawnInfo 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. + +PawnInfo *PawnInfoTable::get_pawn_info(const Position &pos) { + assert(pos.is_ok()); + + Key key = pos.get_pawn_key(); + int index = int(key & (size - 1)); + PawnInfo *pi = entries + index; + + // If pi->key matches the position's pawn hash key, it means that we + // have analysed this pawn structure before, and we can simply return the + // information we found the last time instead of recomputing it: + if(pi->key == key) + return pi; + + // Clear the PawnInfo object, and set the key: + pi->clear(); + pi->key = key; + + Value mgValue[2] = {Value(0), Value(0)}; + Value egValue[2] = {Value(0), Value(0)}; + + // Loop through the pawns for both colors: + for(Color us = WHITE; us <= BLACK; us++) { + Color them = opposite_color(us); + Bitboard ourPawns = pos.pawns(us); + Bitboard theirPawns = pos.pawns(them); + Bitboard pawns = ourPawns; + + // Initialize pawn storm scores by giving bonuses for open files: + if(EvaluatePawnStorms) + for(File f = FILE_A; f <= FILE_H; f++) + if(pos.file_is_half_open(us, f)) { + pi->ksStormValue[us] += KStormOpenFileBonus[f]; + pi->qsStormValue[us] += QStormOpenFileBonus[f]; + } + + // Loop through all pawns of the current color and score each pawn: + while(pawns) { + Square s = pop_1st_bit(&pawns); + File f = square_file(s); + Rank r = square_rank(s); + bool passed, doubled, isolated, backward, chain, candidate; + int bonus; + + assert(pos.piece_on(s) == pawn_of_color(us)); + + // The file containing the pawn is not half open: + pi->halfOpenFiles[us] &= ~(1 << f); + + // Passed, isolated or doubled pawn? + passed = pos.pawn_is_passed(us, s); + isolated = pos.pawn_is_isolated(us, s); + doubled = pos.pawn_is_doubled(us, s); + + if(EvaluatePawnStorms) { + // We calculate kingside and queenside pawn storm + // scores for both colors. These are used when evaluating + // middle game positions with opposite side castling. + // + // Each pawn is given a base score given by a piece square table + // (KStormTable[] or QStormTable[]). This score is increased if + // there are enemy pawns on adjacent files in front of the pawn. + // This is because we want to be able to open files against the + // enemy king, and to avoid blocking the pawn structure (e.g. white + // pawns on h6, g5, black pawns on h7, g6, f7). + + // Kingside pawn storms: + bonus = KStormTable[relative_square(us, s)]; + if(bonus > 0 && outpost_mask(us, s) & theirPawns) { + switch(f) { + + case FILE_F: + bonus += bonus / 4; + break; + + case FILE_G: + bonus += bonus / 2 + bonus / 4; + break; + + case FILE_H: + bonus += bonus / 2; + break; + + default: + break; + } + } + pi->ksStormValue[us] += bonus; + + // Queenside pawn storms: + bonus = QStormTable[relative_square(us, s)]; + if(bonus > 0 && passed_pawn_mask(us, s) & theirPawns) { + switch(f) { + + case FILE_A: + bonus += bonus / 2; + break; + + case FILE_B: + bonus += bonus / 2 + bonus / 4; + break; + + case FILE_C: + bonus += bonus / 2; + break; + + default: + break; + } + } + pi->qsStormValue[us] += bonus; + } + + // Member of a pawn chain? We could speed up the test a little by + // introducing an array of masks indexed by color and square for doing + // the test, but because everything is hashed, it probably won't make + // any noticable difference. + chain = (us == WHITE)? + (ourPawns & neighboring_files_bb(f) & (rank_bb(r) | rank_bb(r-1))) : + (ourPawns & neighboring_files_bb(f) & (rank_bb(r) | rank_bb(r+1))); + + + // Test for backward pawn. + + // If the pawn is isolated, passed, or member of a pawn chain, it cannot + // be backward: + if(passed || isolated || chain) + backward = false; + // If the pawn can capture an enemy pawn, it's not backward: + else if(pos.pawn_attacks(us, s) & theirPawns) + backward = false; + // Check for friendly pawns behind on neighboring files: + else if(ourPawns & in_front_bb(them, r) & neighboring_files_bb(f)) + backward = false; + else { + // We now know that there is no friendly pawns beside or behind this + // pawn on neighboring files. We now check whether the pawn is + // backward by looking in the forward direction on the neighboring + // files, and seeing whether we meet a friendly or an enemy pawn first. + Bitboard b; + if(us == WHITE) { + for(b=pos.pawn_attacks(us, s); !(b&(ourPawns|theirPawns)); b<<=8); + backward = (b | (b << 8)) & theirPawns; + } + else { + for(b=pos.pawn_attacks(us, s); !(b&(ourPawns|theirPawns)); b>>=8); + backward = (b | (b >> 8)) & theirPawns; + } + } + + // Test for candidate passed pawn. + candidate = + (!passed && pos.file_is_half_open(them, f) && + count_1s_max_15(neighboring_files_bb(f) + & (in_front_bb(them, r) | rank_bb(r)) + & ourPawns) + - count_1s_max_15(neighboring_files_bb(f) & in_front_bb(us, r) + & theirPawns) + >= 0); + + // In order to prevent doubled passed pawns from receiving a too big + // bonus, only the frontmost passed pawn on each file is considered as + // a true passed pawn. + if(passed && (ourPawns & squares_in_front_of(us, s))) { + // candidate = true; + passed = false; + } + + // Score this pawn: + Value mv = Value(0), ev = Value(0); + if(isolated) { + mv -= IsolatedPawnMidgamePenalty[f]; + ev -= IsolatedPawnEndgamePenalty[f]; + if(pos.file_is_half_open(them, f)) { + mv -= IsolatedPawnMidgamePenalty[f] / 2; + ev -= IsolatedPawnEndgamePenalty[f] / 2; + } + } + if(doubled) { + mv -= DoubledPawnMidgamePenalty[f]; + ev -= DoubledPawnEndgamePenalty[f]; + } + if(backward) { + mv -= BackwardPawnMidgamePenalty[f]; + ev -= BackwardPawnEndgamePenalty[f]; + if(pos.file_is_half_open(them, f)) { + mv -= BackwardPawnMidgamePenalty[f] / 2; + ev -= BackwardPawnEndgamePenalty[f] / 2; + } + } + if(chain) { + mv += ChainMidgameBonus[f]; + ev += ChainEndgameBonus[f]; + } + if(candidate) { + mv += CandidateMidgameBonus[pawn_rank(us, s)]; + ev += CandidateEndgameBonus[pawn_rank(us, s)]; + } + + mgValue[us] += mv; + egValue[us] += ev; + + // If the pawn is passed, set the square of the pawn in the passedPawns + // bitboard: + if(passed) + set_bit(&(pi->passedPawns), s); + } + } + + pi->mgValue = int16_t(mgValue[WHITE] - mgValue[BLACK]); + pi->egValue = int16_t(egValue[WHITE] - egValue[BLACK]); + + return pi; +} diff --git a/src/pawns.h b/src/pawns.h new file mode 100644 index 00000000..c4a616c2 --- /dev/null +++ b/src/pawns.h @@ -0,0 +1,130 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +#if !defined(PAWNS_H_INCLUDED) +#define PAWNS_H_INCLUDED + +//// +//// Includes +//// + +#include "position.h" + + +//// +//// Types +//// + +/// PawnInfo is a class which contains various information about a pawn +/// structure. Currently, it only includes a middle game and an end game +/// pawn structure evaluation, and a bitboard of passed pawns. We may want +/// to add further information in the future. A lookup to the pawn hash table +/// (performed by calling the get_pawn_info method in a PawnInfoTable object) +/// returns a pointer to a PawnInfo object. + +class PawnInfo { + + friend class PawnInfoTable; + +public: + Value mg_value() const; + Value eg_value() const; + Value kingside_storm_value(Color c) const; + Value queenside_storm_value(Color c) const; + Bitboard passed_pawns() const; + bool file_is_half_open(Color c, File f) const; + bool has_open_file_to_left(Color c, File f) const; + bool has_open_file_to_right(Color c, File f) const; + +private: + void clear(); + + Key key; + Bitboard passedPawns; + int16_t mgValue, egValue; + int8_t ksStormValue[2], qsStormValue[2]; + uint8_t halfOpenFiles[2]; +}; + + +/// The PawnInfoTable class represents a pawn hash table. It is basically +/// just an array of PawnInfo objects and a few methods for accessing these +/// objects. The most important method is get_pawn_info, which looks up a +/// position in the table and returns a pointer to a PawnInfo object. + +class PawnInfoTable { + +public: + PawnInfoTable(unsigned numOfEntries); + ~PawnInfoTable(); + void clear(); + PawnInfo *get_pawn_info(const Position &pos); + +private: + unsigned size; + PawnInfo *entries; +}; + + +//// +//// Inline functions +//// + +inline Value PawnInfo::mg_value() const { + return Value(mgValue); +} + +inline Value PawnInfo::eg_value() const { + return Value(egValue); +} + +inline Bitboard PawnInfo::passed_pawns() const { + return passedPawns; +} + +inline Value PawnInfo::kingside_storm_value(Color c) const { + return Value(ksStormValue[c]); +} + +inline Value PawnInfo::queenside_storm_value(Color c) const { + return Value(qsStormValue[c]); +} + +inline bool PawnInfo::file_is_half_open(Color c, File f) const { + return (halfOpenFiles[c] & (1 << int(f))); +} + +inline bool PawnInfo::has_open_file_to_left(Color c, File f) const { + return halfOpenFiles[c] & ((1 << int(f)) - 1); +} + +inline bool PawnInfo::has_open_file_to_right(Color c, File f) const { + return halfOpenFiles[c] & ~((1 << int(f+1)) - 1); +} + +inline void PawnInfo::clear() { + mgValue = egValue = 0; + passedPawns = EmptyBoardBB; + ksStormValue[WHITE] = ksStormValue[BLACK] = 0; + qsStormValue[WHITE] = qsStormValue[BLACK] = 0; + halfOpenFiles[WHITE] = halfOpenFiles[BLACK] = 0xFF; +} + + +#endif // !defined(PAWNS_H_INCLUDED) diff --git a/src/phase.h b/src/phase.h new file mode 100644 index 00000000..a9c0958f --- /dev/null +++ b/src/phase.h @@ -0,0 +1,33 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +#if !defined(PHASE_H_INCLUDED) +#define PHASE_H_INCLUDED + +//// +//// Types +//// + +enum Phase { + PHASE_ENDGAME = 0, + PHASE_MIDGAME = 128 +}; + + +#endif // !defined(PHASE_H_INCLUDED) diff --git a/src/piece.cpp b/src/piece.cpp new file mode 100644 index 00000000..737a0918 --- /dev/null +++ b/src/piece.cpp @@ -0,0 +1,94 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +//// +//// Includes +//// + +#include + +#include "piece.h" + + +//// +//// Constants and variables +//// + +const int SlidingArray[18] = { + 0, 0, 0, 1, 2, 3, 0, 0, 0, 0, 0, 1, 2, 3, 0, 0, 0, 0 +}; + +const SquareDelta Directions[16][16] = { + {DELTA_ZERO}, + {DELTA_NW, DELTA_NE, DELTA_ZERO}, + {DELTA_SSW, DELTA_SSE, DELTA_SWW, DELTA_SEE, + DELTA_NWW, DELTA_NEE, DELTA_NNW, DELTA_NNE, DELTA_ZERO}, + {DELTA_SE, DELTA_SW, DELTA_NE, DELTA_NW, DELTA_ZERO}, + {DELTA_S, DELTA_E, DELTA_W, DELTA_N, DELTA_ZERO}, + {DELTA_S, DELTA_E, DELTA_W, DELTA_N, + DELTA_SE, DELTA_SW, DELTA_NE, DELTA_NW, DELTA_ZERO}, + {DELTA_S, DELTA_E, DELTA_W, DELTA_N, + DELTA_SE, DELTA_SW, DELTA_NE, DELTA_NW, DELTA_ZERO}, + {DELTA_ZERO}, + {DELTA_ZERO}, + {DELTA_SW, DELTA_SE, DELTA_ZERO}, + {DELTA_SSW, DELTA_SSE, DELTA_SWW, DELTA_SEE, + DELTA_NWW, DELTA_NEE, DELTA_NNW, DELTA_NNE, DELTA_ZERO}, + {DELTA_SE, DELTA_SW, DELTA_NE, DELTA_NW, DELTA_ZERO}, + {DELTA_S, DELTA_E, DELTA_W, DELTA_N, DELTA_ZERO}, + {DELTA_S, DELTA_E, DELTA_W, DELTA_N, + DELTA_SE, DELTA_SW, DELTA_NE, DELTA_NW, DELTA_ZERO}, + {DELTA_S, DELTA_E, DELTA_W, DELTA_N, + DELTA_SE, DELTA_SW, DELTA_NE, DELTA_NW, DELTA_ZERO}, +}; + +const SquareDelta PawnPush[2] = { + DELTA_N, DELTA_S +}; + + +//// +//// Functions +//// + +/// Translating piece types to/from English piece letters: + +static const char PieceChars[] = " pnbrqk"; + +char piece_type_to_char(PieceType pt, bool upcase = false) { + return upcase? toupper(PieceChars[pt]) : PieceChars[pt]; +} + +PieceType piece_type_from_char(char c) { + const char *ch = strchr(PieceChars, tolower(c)); + return ch? PieceType(ch - PieceChars) : NO_PIECE_TYPE; +} + + +/// piece_is_ok() and piece_type_is_ok(), for debugging: + +bool piece_is_ok(Piece pc) { + return + piece_type_is_ok(type_of_piece(pc)) && + color_is_ok(color_of_piece(pc)); +} + +bool piece_type_is_ok(PieceType pc) { + return pc >= PAWN && pc <= KING; +} diff --git a/src/piece.h b/src/piece.h new file mode 100644 index 00000000..2104baa1 --- /dev/null +++ b/src/piece.h @@ -0,0 +1,132 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +#if !defined(PIECE_H_INCLUDED) +#define PIECE_H_INCLUDED + +//// +//// Includes +//// + +#include "color.h" +#include "misc.h" +#include "square.h" + + +//// +//// Types +//// + +enum PieceType { + NO_PIECE_TYPE = 0, + PAWN = 1, KNIGHT = 2, BISHOP = 3, ROOK = 4, QUEEN = 5, KING = 6 +}; + +enum Piece { + NO_PIECE = 0, WP = 1, WN = 2, WB = 3, WR = 4, WQ = 5, WK = 6, + BP = 9, BN = 10, BB = 11, BR = 12, BQ = 13, BK = 14, + EMPTY = 16, OUTSIDE = 17 +}; + + +//// +//// Constants and variables +//// + +const PieceType PieceTypeMin = PAWN; +const PieceType PieceTypeMax = KING; + +extern const int SlidingArray[18]; +extern const SquareDelta Directions[16][16]; +extern const SquareDelta PawnPush[2]; + + +//// +//// Inline functions +//// + +inline Piece operator+ (Piece p, int i) { return Piece(int(p) + i); } +inline void operator++ (Piece &p, int) { p = Piece(int(p) + 1); } +inline Piece operator- (Piece p, int i) { return Piece(int(p) - i); } +inline void operator-- (Piece &p, int) { p = Piece(int(p) - 1); } +inline PieceType operator+ (PieceType p, int i) {return PieceType(int(p) + i);} +inline void operator++ (PieceType &p, int) { p = PieceType(int(p) + 1); } +inline PieceType operator- (PieceType p, int i) {return PieceType(int(p) - i);} +inline void operator-- (PieceType &p, int) { p = PieceType(int(p) - 1); } + +inline PieceType type_of_piece(Piece p) { + return PieceType(int(p) & 7); +} + +inline Color color_of_piece(Piece p) { + return Color(int(p) >> 3); +} + +inline Piece piece_of_color_and_type(Color c, PieceType pt) { + return Piece((int(c) << 3) | int(pt)); +} + +inline Piece pawn_of_color(Color c) { + return piece_of_color_and_type(c, PAWN); +} + +inline Piece knight_of_color(Color c) { + return piece_of_color_and_type(c, KNIGHT); +} + +inline Piece bishop_of_color(Color c) { + return piece_of_color_and_type(c, BISHOP); +} + +inline Piece rook_of_color(Color c) { + return piece_of_color_and_type(c, ROOK); +} + +inline Piece queen_of_color(Color c) { + return piece_of_color_and_type(c, QUEEN); +} + +inline Piece king_of_color(Color c) { + return piece_of_color_and_type(c, KING); +} + +inline int piece_is_slider(Piece p) { + return SlidingArray[int(p)]; +} + +inline int piece_type_is_slider(PieceType pt) { + return SlidingArray[int(pt)]; +} + +inline SquareDelta pawn_push(Color c) { + return PawnPush[c]; +} + + +//// +//// Prototypes +//// + +extern char piece_type_to_char(PieceType pt, bool upcase); +extern PieceType piece_type_from_char(char c); +extern bool piece_is_ok(Piece pc); +extern bool piece_type_is_ok(PieceType pt); + + +#endif // !defined(PIECE_H_INCLUDED) diff --git a/src/position.cpp b/src/position.cpp new file mode 100644 index 00000000..72ab5f75 --- /dev/null +++ b/src/position.cpp @@ -0,0 +1,2238 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +//// +//// Includes +//// + +#include +#include +#include +#include + +#include "mersenne.h" +#include "movegen.h" +#include "movepick.h" +#include "position.h" +#include "psqtab.h" +#include "ucioption.h" + + +//// +//// Variables +//// + +int Position::castleRightsMask[64]; + +Key Position::zobrist[2][8][64]; +Key Position::zobEp[64]; +Key Position::zobCastle[16]; +Key Position::zobMaterial[2][8][16]; +Key Position::zobSideToMove; + +Value Position::MgPieceSquareTable[16][64]; +Value Position::EgPieceSquareTable[16][64]; + + +//// +//// Functions +//// + +/// Constructors + +Position::Position() { } // Do we really need this one? + +Position::Position(const Position &pos) { + this->copy(pos); +} + +Position::Position(const std::string &fen) { + this->from_fen(fen); +} + + +/// Position::from_fen() 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::from_fen(const std::string &fen) { + File file; + Rank rank; + int i; + + this->clear(); + + // Board + rank = RANK_8; + file = FILE_A; + for(i = 0; fen[i] != ' '; i++) { + if(isdigit(fen[i])) + // Skip the given number of files + file += (fen[i] - '1' + 1); + else { + Square square = make_square(file, rank); + switch(fen[i]) { + case 'K': this->put_piece(WK, square); file++; break; + case 'Q': this->put_piece(WQ, square); file++; break; + case 'R': this->put_piece(WR, square); file++; break; + case 'B': this->put_piece(WB, square); file++; break; + case 'N': this->put_piece(WN, square); file++; break; + case 'P': this->put_piece(WP, square); file++; break; + case 'k': this->put_piece(BK, square); file++; break; + case 'q': this->put_piece(BQ, square); file++; break; + case 'r': this->put_piece(BR, square); file++; break; + case 'b': this->put_piece(BB, square); file++; break; + case 'n': this->put_piece(BN, square); file++; break; + case 'p': this->put_piece(BP, square); file++; break; + case '/': file = FILE_A; rank--; break; + case ' ': break; + default: + std::cout << "Error in FEN at character " << i << std::endl; + return; + } + } + } + + // Side to move + i++; + if(fen[i] == 'w') + sideToMove = WHITE; + else if(fen[i] == 'b') + sideToMove = BLACK; + else { + std::cout << "Error in FEN at character " << i << std::endl; + return; + } + + // Castling rights: + i++; + if(fen[i] != ' ') { + std::cout << "Error in FEN at character " << i << std::endl; + return; + } + + i++; + while(strchr("KQkqabcdefghABCDEFGH-", fen[i])) { + if(fen[i] == '-') { + i++; break; + } + else if(fen[i] == 'K') this->allow_oo(WHITE); + else if(fen[i] == 'Q') this->allow_ooo(WHITE); + else if(fen[i] == 'k') this->allow_oo(BLACK); + else if(fen[i] == 'q') this->allow_ooo(BLACK); + else if(fen[i] >= 'A' && fen[i] <= 'H') { + File rookFile, kingFile = FILE_NONE; + for(Square square = SQ_B1; square <= SQ_G1; square++) + if(this->piece_on(square) == WK) + kingFile = square_file(square); + if(kingFile == FILE_NONE) { + std::cout << "Error in FEN at character " << i << std::endl; + return; + } + initialKFile = kingFile; + rookFile = File(fen[i] - 'A') + FILE_A; + if(rookFile < initialKFile) { + this->allow_ooo(WHITE); + initialQRFile = rookFile; + } + else { + this->allow_oo(WHITE); + initialKRFile = rookFile; + } + } + else if(fen[i] >= 'a' && fen[i] <= 'h') { + File rookFile, kingFile = FILE_NONE; + for(Square square = SQ_B8; square <= SQ_G8; square++) + if(this->piece_on(square) == BK) + kingFile = square_file(square); + if(kingFile == FILE_NONE) { + std::cout << "Error in FEN at character " << i << std::endl; + return; + } + initialKFile = kingFile; + rookFile = File(fen[i] - 'a') + FILE_A; + if(rookFile < initialKFile) { + this->allow_ooo(BLACK); + initialQRFile = rookFile; + } + else { + this->allow_oo(BLACK); + initialKRFile = rookFile; + } + } + else { + std::cout << "Error in FEN at character " << i << std::endl; + return; + } + i++; + } + + while(fen[i] == ' ') + i++; + + // En passant square + if(i < int(fen.length()) - 2) + if(fen[i] >= 'a' && fen[i] <= 'h' && (fen[i+1] == '3' || fen[i+1] == '6')) + epSquare = square_from_string(fen.substr(i, 2)); + + // Various initialisation + + for(Square sq = SQ_A1; sq <= SQ_H8; sq++) + castleRightsMask[sq] = ALL_CASTLES; + castleRightsMask[make_square(initialKFile, RANK_1)] ^= + (WHITE_OO|WHITE_OOO); + castleRightsMask[make_square(initialKFile, RANK_8)] ^= + (BLACK_OO|BLACK_OOO); + castleRightsMask[make_square(initialKRFile, RANK_1)] ^= WHITE_OO; + castleRightsMask[make_square(initialKRFile, RANK_8)] ^= BLACK_OO; + castleRightsMask[make_square(initialQRFile, RANK_1)] ^= WHITE_OOO; + castleRightsMask[make_square(initialQRFile, RANK_8)] ^= BLACK_OOO; + + this->find_checkers(); + + key = this->compute_key(); + pawnKey = this->compute_pawn_key(); + materialKey = this->compute_material_key(); + mgValue = this->compute_mg_value(); + egValue = this->compute_eg_value(); + npMaterial[WHITE] = this->compute_non_pawn_material(WHITE); + npMaterial[BLACK] = this->compute_non_pawn_material(BLACK); +} + + +/// Position::to_fen() converts the position object to a FEN string. This is +/// probably only useful for debugging. + +const std::string Position::to_fen() const { + char pieceLetters[] = " PNBRQK pnbrqk"; + std::string result; + int skip; + + for(Rank rank = RANK_8; rank >= RANK_1; rank--) { + skip = 0; + for(File file = FILE_A; file <= FILE_H; file++) { + Square square = make_square(file, rank); + if(this->square_is_occupied(square)) { + if(skip > 0) result += (char)skip + '0'; + result += pieceLetters[this->piece_on(square)]; + skip = 0; + } + else skip++; + } + if(skip > 0) result += (char)skip + '0'; + result += (rank > RANK_1)? '/' : ' '; + } + + result += (sideToMove == WHITE)? 'w' : 'b'; + result += ' '; + if(castleRights == NO_CASTLES) result += '-'; + else { + if(this->can_castle_kingside(WHITE)) result += 'K'; + if(this->can_castle_queenside(WHITE)) result += 'Q'; + if(this->can_castle_kingside(BLACK)) result += 'k'; + if(this->can_castle_queenside(BLACK)) result += 'q'; + } + + result += ' '; + if(this->ep_square() == SQ_NONE) result += '-'; + else result += square_to_string(this->ep_square()); + + return result; +} + + +/// Position::print() prints an ASCII representation of the position to +/// the standard output. + +void Position::print() const { + char pieceStrings[][8] = + {"| ? ", "| P ", "| N ", "| B ", "| R ", "| Q ", "| K ", "| ? ", + "| ? ", "|=P=", "|=N=", "|=B=", "|=R=", "|=Q=", "|=K=" + }; + + for(Rank rank = RANK_8; rank >= RANK_1; rank--) { + std::cout << "+---+---+---+---+---+---+---+---+\n"; + for(File file = FILE_A; file <= FILE_H; file++) { + Square sq = make_square(file, rank); + Piece piece = this->piece_on(sq); + if(piece == EMPTY) + std::cout << ((square_color(sq) == WHITE)? "| " : "| . "); + else + std::cout << pieceStrings[piece]; + } + std::cout << "|\n"; + } + std::cout << "+---+---+---+---+---+---+---+---+\n"; + std::cout << this->to_fen() << std::endl; + std::cout << key << std::endl; +} + + +/// Position::copy() creates a copy of the input position. + +void Position::copy(const Position &pos) { + memcpy(this, &pos, sizeof(Position)); +} + + +/// Position:pinned_pieces() returns a bitboard of all pinned (against the +/// king) pieces for the given color. + +Bitboard Position::pinned_pieces(Color c) const { + Bitboard b1, b2, pinned, pinners, sliders; + Square ksq = this->king_square(c), s; + Color them = opposite_color(c); + + pinned = EmptyBoardBB; + b1 = this->occupied_squares(); + + sliders = this->rooks_and_queens(them) & ~this->checkers(); + if(sliders & RookPseudoAttacks[ksq]) { + b2 = this->rook_attacks(ksq) & this->pieces_of_color(c); + pinners = rook_attacks_bb(ksq, b1 ^ b2) & sliders; + while(pinners) { + s = pop_1st_bit(&pinners); + pinned |= (squares_between(s, ksq) & b2); + } + } + + sliders = this->bishops_and_queens(them) & ~this->checkers(); + if(sliders & BishopPseudoAttacks[ksq]) { + b2 = this->bishop_attacks(ksq) & this->pieces_of_color(c); + pinners = bishop_attacks_bb(ksq, b1 ^ b2) & sliders; + while(pinners) { + s = pop_1st_bit(&pinners); + pinned |= (squares_between(s, ksq) & b2); + } + } + + return pinned; +} + +/// Position:discovered_check_candidates() returns a bitboard containing all +/// pieces for the given side which are candidates for giving a discovered +/// check. The code is almost the same as the function for finding pinned +/// pieces. + +Bitboard Position::discovered_check_candidates(Color c) const { + Bitboard b1, b2, dc, checkers, sliders; + Square ksq = this->king_square(opposite_color(c)), s; + + dc = EmptyBoardBB; + b1 = this->occupied_squares(); + + sliders = this->rooks_and_queens(c); + if(sliders & RookPseudoAttacks[ksq]) { + b2 = this->rook_attacks(ksq) & this->pieces_of_color(c); + checkers = rook_attacks_bb(ksq, b1 ^ b2) & sliders; + while(checkers) { + s = pop_1st_bit(&checkers); + dc |= (squares_between(s, ksq) & b2); + } + } + + sliders = this->bishops_and_queens(c); + if(sliders & BishopPseudoAttacks[ksq]) { + b2 = this->bishop_attacks(ksq) & this->pieces_of_color(c); + checkers = bishop_attacks_bb(ksq, b1 ^ b2) & sliders; + while(checkers) { + s = pop_1st_bit(&checkers); + dc |= (squares_between(s, ksq) & b2); + } + } + + return dc; +} + + +/// Position::square_is_attacked() checks whether the given side attacks the +/// given square. + +bool Position::square_is_attacked(Square s, Color c) const { + return + (this->pawn_attacks(opposite_color(c), s) & this->pawns(c)) || + (this->knight_attacks(s) & this->knights(c)) || + (this->king_attacks(s) & this->kings(c)) || + (this->rook_attacks(s) & this->rooks_and_queens(c)) || + (this->bishop_attacks(s) & this->bishops_and_queens(c)); +} + + +/// Position::attacks_to() computes a bitboard containing all pieces which +/// attacks a given square. There are two versions of this function: One +/// which finds attackers of both colors, and one which only finds the +/// attackers for one side. + +Bitboard Position::attacks_to(Square s) const { + return + (this->black_pawn_attacks(s) & this->pawns(WHITE)) | + (this->white_pawn_attacks(s) & this->pawns(BLACK)) | + (this->knight_attacks(s) & this->pieces_of_type(KNIGHT)) | + (this->rook_attacks(s) & this->rooks_and_queens()) | + (this->bishop_attacks(s) & this->bishops_and_queens()) | + (this->king_attacks(s) & this->pieces_of_type(KING)); +} + +Bitboard Position::attacks_to(Square s, Color c) const { + return this->attacks_to(s) & this->pieces_of_color(c); +} + + +/// Position::piece_attacks_square() tests whether the piece on square f +/// attacks square t. + +bool Position::piece_attacks_square(Square f, Square t) const { + assert(square_is_ok(f)); + assert(square_is_ok(t)); + + switch(this->piece_on(f)) { + case WP: return this->white_pawn_attacks_square(f, t); + case BP: return this->black_pawn_attacks_square(f, t); + case WN: case BN: return this->knight_attacks_square(f, t); + case WB: case BB: return this->bishop_attacks_square(f, t); + case WR: case BR: return this->rook_attacks_square(f, t); + case WQ: case BQ: return this->queen_attacks_square(f, t); + case WK: case BK: return this->king_attacks_square(f, t); + default: return false; + } + + return false; +} + + +/// Position::find_checkers() computes the checkersBB bitboard, which +/// contains a nonzero bit for each checking piece (0, 1 or 2). It +/// currently works by calling Position::attacks_to, which is probably +/// inefficient. Consider rewriting this function to use the last move +/// played, like in non-bitboard versions of Glaurung. + +void Position::find_checkers() { + checkersBB = attacks_to(this->king_square(this->side_to_move()), + opposite_color(this->side_to_move())); +} + + +/// Position::move_is_legal() tests whether a pseudo-legal move is legal. +/// There are two versions of this function: One which takes only a +/// move as input, and one which takes a move and a bitboard of pinned +/// pieces. The latter function is faster, and should always be preferred +/// when a pinned piece bitboard has already been computed. + +bool Position::move_is_legal(Move m) const { + return this->move_is_legal(m, this->pinned_pieces(this->side_to_move())); +} + + +bool Position::move_is_legal(Move m, Bitboard pinned) const { + Color us, them; + Square ksq, from; + + assert(this->is_ok()); + assert(move_is_ok(m)); + assert(pinned == this->pinned_pieces(this->side_to_move())); + + // If we're in check, all pseudo-legal moves are legal, because our + // check evasion generator only generates true legal moves. + if(this->is_check()) return true; + + // Castling moves are checked for legality during move generation. + if(move_is_castle(m)) return true; + + us = this->side_to_move(); + them = opposite_color(us); + + from = move_from(m); + ksq = this->king_square(us); + + assert(this->color_of_piece_on(from) == us); + assert(this->piece_on(ksq) == king_of_color(us)); + + // 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(move_is_ep(m)) { + Square to = move_to(m); + Square capsq = make_square(square_file(to), square_rank(from)); + Bitboard b = this->occupied_squares(); + + assert(to == this->ep_square()); + assert(this->piece_on(from) == pawn_of_color(us)); + assert(this->piece_on(capsq) == pawn_of_color(them)); + assert(this->piece_on(to) == EMPTY); + + clear_bit(&b, from); clear_bit(&b, capsq); set_bit(&b, to); + return + (!(rook_attacks_bb(ksq, b) & this->rooks_and_queens(them)) && + !(bishop_attacks_bb(ksq, b) & this->bishops_and_queens(them))); + } + + // If the moving piece is a king, check whether the destination + // square is attacked by the opponent. + if(from == ksq) return !(this->square_is_attacked(move_to(m), them)); + + // 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. + if(!bit_is_set(pinned, from)) return true; + if(direction_between_squares(from, ksq) == + direction_between_squares(move_to(m), ksq)) + return true; + + return false; +} + + +/// Position::move_is_check() tests whether a pseudo-legal move is a check. +/// There are two versions of this function: One which takes only a move as +/// input, and one which takes a move and a bitboard of discovered check +/// candidates. The latter function is faster, and should always be preferred +/// when a discovered check candidates bitboard has already been computed. + +bool Position::move_is_check(Move m) const { + Bitboard dc = this->discovered_check_candidates(this->side_to_move()); + return this->move_is_check(m, dc); +} + + +bool Position::move_is_check(Move m, Bitboard dcCandidates) const { + Color us, them; + Square ksq, from, to; + + assert(this->is_ok()); + assert(move_is_ok(m)); + assert(dcCandidates == + this->discovered_check_candidates(this->side_to_move())); + + us = this->side_to_move(); + them = opposite_color(us); + + from = move_from(m); + to = move_to(m); + ksq = this->king_square(them); + assert(this->color_of_piece_on(from) == us); + assert(this->piece_on(ksq) == king_of_color(them)); + + // Proceed according to the type of the moving piece: + switch(this->type_of_piece_on(from)) { + case PAWN: + // Normal check? + if(bit_is_set(this->pawn_attacks(them, ksq), to)) + return true; + // Discovered check? + else if(bit_is_set(dcCandidates, from) && + direction_between_squares(from, ksq) != + direction_between_squares(to, ksq)) + return true; + // Promotion with check? + else if(move_promotion(m)) { + Bitboard b = this->occupied_squares(); + clear_bit(&b, from); + + switch(move_promotion(m)) { + case KNIGHT: + return this->knight_attacks_square(to, ksq); + case BISHOP: + return bit_is_set(bishop_attacks_bb(to, b), ksq); + case ROOK: + return bit_is_set(rook_attacks_bb(to, b), ksq); + case QUEEN: + return bit_is_set(queen_attacks_bb(to, b), ksq); + default: + assert(false); + } + } + // En passant capture with check? We have already handled the case + // of direct checks and ordinary discovered check, the only case we + // need to handle is the unusual case of a discovered check through the + // captured pawn. + else if(move_is_ep(m)) { + Square capsq = make_square(square_file(to), square_rank(from)); + Bitboard b = this->occupied_squares(); + + clear_bit(&b, from); clear_bit(&b, capsq); set_bit(&b, to); + return + ((rook_attacks_bb(ksq, b) & this->rooks_and_queens(us)) || + (bishop_attacks_bb(ksq, b) & this->bishops_and_queens(us))); + } + return false; + + case KNIGHT: + // Discovered check? + if(bit_is_set(dcCandidates, from)) + return true; + // Normal check? + else + return bit_is_set(this->knight_attacks(ksq), to); + + case BISHOP: + // Discovered check? + if(bit_is_set(dcCandidates, from)) + return true; + // Normal check? + else + return bit_is_set(this->bishop_attacks(ksq), to); + + case ROOK: + // Discovered check? + if(bit_is_set(dcCandidates, from)) + return true; + // Normal check? + else + return bit_is_set(this->rook_attacks(ksq), to); + + case QUEEN: + // Discovered checks are impossible! + assert(!bit_is_set(dcCandidates, from)); + // Normal check? + return bit_is_set(this->queen_attacks(ksq), to); + + case KING: + // Discovered check? + if(bit_is_set(dcCandidates, from) && + direction_between_squares(from, ksq) != + direction_between_squares(to, ksq)) + return true; + // Castling with check? + if(move_is_castle(m)) { + Square kfrom, kto, rfrom, rto; + Bitboard b = this->occupied_squares(); + + kfrom = from; + rfrom = to; + if(rfrom > kfrom) { + kto = relative_square(us, SQ_G1); + rto = relative_square(us, SQ_F1); + } + else { + kto = relative_square(us, SQ_C1); + rto = relative_square(us, SQ_D1); + } + + clear_bit(&b, kfrom); clear_bit(&b, rfrom); + set_bit(&b, rto); set_bit(&b, kto); + + return bit_is_set(rook_attacks_bb(rto, b), ksq); + } + + return false; + + default: + assert(false); + return false; + } + + assert(false); + return false; +} + + +/// Position::move_is_capture() tests whether a move from the current +/// position is a capture. + +bool Position::move_is_capture(Move m) const { + return + this->color_of_piece_on(move_to(m)) == opposite_color(this->side_to_move()) + || move_is_ep(m); +} + + +/// Position::move_attacks_square() tests whether a move from the current +/// position attacks a given square. Only attacks by the moving piece are +/// considered; the function does not handle X-ray attacks. + +bool Position::move_attacks_square(Move m, Square s) const { + assert(move_is_ok(m)); + assert(square_is_ok(s)); + + Square f = move_from(m), t = move_to(m); + + assert(this->square_is_occupied(f)); + + switch(this->piece_on(f)) { + case WP: return this->white_pawn_attacks_square(t, s); + case BP: return this->black_pawn_attacks_square(t, s); + case WN: case BN: return this->knight_attacks_square(t, s); + case WB: case BB: return this->bishop_attacks_square(t, s); + case WR: case BR: return this->rook_attacks_square(t, s); + case WQ: case BQ: return this->queen_attacks_square(t, s); + case WK: case BK: return this->king_attacks_square(t, s); + default: assert(false); + } + + return false; +} + + + +/// Position::backup() is called when making a move. All information +/// necessary to restore the position when the move is later unmade +/// is saved to an UndoInfo object. The function Position::restore +/// does the reverse operation: When one does a backup followed by +/// a restore with the same UndoInfo object, the position is restored +/// to the state before backup was called. + +void Position::backup(UndoInfo &u) const { + u.castleRights = castleRights; + u.epSquare = epSquare; + u.checkersBB = checkersBB; + u.key = key; + u.pawnKey = pawnKey; + u.materialKey = materialKey; + u.rule50 = rule50; + u.lastMove = lastMove; + u.capture = NO_PIECE_TYPE; + u.mgValue = mgValue; + u.egValue = egValue; +} + + +/// Position::restore() is called when unmaking a move. It copies back +/// the information backed up during a previous call to Position::backup. + +void Position::restore(const UndoInfo &u) { + castleRights = u.castleRights; + epSquare = u.epSquare; + checkersBB = u.checkersBB; + key = u.key; + pawnKey = u.pawnKey; + materialKey = u.materialKey; + rule50 = u.rule50; + lastMove = u.lastMove; + mgValue = u.mgValue; + egValue = u.egValue; +} + + +/// Position::do_move() makes a move, and backs up all information necessary +/// to undo the move to an UndoInfo object. The move is assumed to be legal. +/// Pseudo-legal moves should be filtered out before this function is called. +/// There are two versions of this function, one which takes only the move and +/// the UndoInfo as input, and one which takes a third parameter, a bitboard of +/// discovered check candidates. The second version is faster, because knowing +/// the discovered check candidates makes it easier to update the checkersBB +/// member variable in the position object. + +void Position::do_move(Move m, UndoInfo &u) { + this->do_move(m, u, this->discovered_check_candidates(this->side_to_move())); +} + +void Position::do_move(Move m, UndoInfo &u, Bitboard dcCandidates) { + assert(this->is_ok()); + assert(move_is_ok(m)); + + // Back up the necessary information to our UndoInfo object (except the + // captured piece, which is taken care of later: + this->backup(u); + + // Save the current key to the history[] array, in order to be able to + // detect repetition draws: + history[gamePly] = key; + + // Increment the 50 moves rule draw counter. Resetting it to zero in the + // case of non-reversible moves is taken care of later. + rule50++; + + if(move_is_castle(m)) + this->do_castle_move(m); + else if(move_promotion(m)) + this->do_promotion_move(m, u); + else if(move_is_ep(m)) + this->do_ep_move(m); + else { + Color us, them; + Square from, to; + PieceType piece, capture; + + us = this->side_to_move(); + them = opposite_color(us); + + from = move_from(m); + to = move_to(m); + + assert(this->color_of_piece_on(from) == us); + assert(this->color_of_piece_on(to) == them || this->piece_on(to) == EMPTY); + + piece = this->type_of_piece_on(from); + capture = this->type_of_piece_on(to); + + if(capture) { + assert(capture != KING); + + // Remove captured piece: + clear_bit(&(byColorBB[them]), to); + clear_bit(&(byTypeBB[capture]), to); + + // Update hash key: + key ^= zobrist[them][capture][to]; + + // If the captured piece was a pawn, update pawn hash key: + if(capture == PAWN) + pawnKey ^= zobrist[them][PAWN][to]; + + // Update incremental scores: + mgValue -= this->mg_pst(them, capture, to); + egValue -= this->eg_pst(them, capture, to); + + // Update material: + if(capture != PAWN) + npMaterial[them] -= piece_value_midgame(capture); + + // Update material hash key: + materialKey ^= zobMaterial[them][capture][pieceCount[them][capture]]; + + // Update piece count: + pieceCount[them][capture]--; + + // Update piece list: + pieceList[them][capture][index[to]] = + pieceList[them][capture][pieceCount[them][capture]]; + index[pieceList[them][capture][index[to]]] = index[to]; + + // Remember the captured piece, in order to be able to undo the move + // correctly: + u.capture = capture; + + // Reset rule 50 counter: + rule50 = 0; + } + + // Move the piece: + clear_bit(&(byColorBB[us]), from); + clear_bit(&(byTypeBB[piece]), from); + clear_bit(&(byTypeBB[0]), from); // HACK: byTypeBB[0] == occupied squares + set_bit(&(byColorBB[us]), to); + set_bit(&(byTypeBB[piece]), to); + set_bit(&(byTypeBB[0]), to); // HACK: byTypeBB[0] == occupied squares + board[to] = board[from]; + board[from] = EMPTY; + + // Update hash key: + key ^= zobrist[us][piece][from] ^ zobrist[us][piece][to]; + + // Update incremental scores: + mgValue -= this->mg_pst(us, piece, from); + mgValue += this->mg_pst(us, piece, to); + egValue -= this->eg_pst(us, piece, from); + egValue += this->eg_pst(us, piece, to); + + // If the moving piece was a king, update the king square: + if(piece == KING) + kingSquare[us] = to; + + // If the move was a double pawn push, set the en passant square. + // This code is a bit ugly right now, and should be cleaned up later. + // FIXME + if(epSquare != SQ_NONE) { + key ^= zobEp[epSquare]; + epSquare = SQ_NONE; + } + if(piece == PAWN) { + if(abs(int(to) - int(from)) == 16) { + if((us == WHITE && (this->white_pawn_attacks(from + DELTA_N) & + this->pawns(BLACK))) || + (us == BLACK && (this->black_pawn_attacks(from + DELTA_S) & + this->pawns(WHITE)))) { + epSquare = Square((int(from) + int(to)) / 2); + key ^= zobEp[epSquare]; + } + } + // Reset rule 50 draw counter. + rule50 = 0; + // Update pawn hash key: + pawnKey ^= zobrist[us][PAWN][from] ^ zobrist[us][PAWN][to]; + } + + // Update piece lists: + pieceList[us][piece][index[from]] = to; + index[to] = index[from]; + + // Update castle rights: + key ^= zobCastle[castleRights]; + castleRights &= castleRightsMask[from]; + castleRights &= castleRightsMask[to]; + key ^= zobCastle[castleRights]; + + // Update checkers bitboard: + checkersBB = EmptyBoardBB; + Square ksq = this->king_square(them); + + switch(piece) { + + case PAWN: + if(bit_is_set(this->pawn_attacks(them, ksq), to)) + set_bit(&checkersBB, to); + if(bit_is_set(dcCandidates, from)) + checkersBB |= + ((this->rook_attacks(ksq) & this->rooks_and_queens(us)) | + (this->bishop_attacks(ksq) & this->bishops_and_queens(us))); + break; + + case KNIGHT: + if(bit_is_set(this->knight_attacks(ksq), to)) + set_bit(&checkersBB, to); + if(bit_is_set(dcCandidates, from)) + checkersBB |= + ((this->rook_attacks(ksq) & this->rooks_and_queens(us)) | + (this->bishop_attacks(ksq) & this->bishops_and_queens(us))); + break; + + case BISHOP: + if(bit_is_set(this->bishop_attacks(ksq), to)) + set_bit(&checkersBB, to); + if(bit_is_set(dcCandidates, from)) + checkersBB |= + (this->rook_attacks(ksq) & this->rooks_and_queens(us)); + break; + + case ROOK: + if(bit_is_set(this->rook_attacks(ksq), to)) + set_bit(&checkersBB, to); + if(bit_is_set(dcCandidates, from)) + checkersBB |= + (this->bishop_attacks(ksq) & this->bishops_and_queens(us)); + break; + + case QUEEN: + if(bit_is_set(this->queen_attacks(ksq), to)) + set_bit(&checkersBB, to); + break; + + case KING: + if(bit_is_set(dcCandidates, from)) + checkersBB |= + ((this->rook_attacks(ksq) & this->rooks_and_queens(us)) | + (this->bishop_attacks(ksq) & this->bishops_and_queens(us))); + break; + + default: + assert(false); + break; + } + } + + // Finish + key ^= zobSideToMove; + sideToMove = opposite_color(sideToMove); + gamePly++; + + mgValue += (sideToMove == WHITE)? TempoValueMidgame : -TempoValueMidgame; + egValue += (sideToMove == WHITE)? TempoValueEndgame : -TempoValueEndgame; + + assert(this->is_ok()); +} + + +/// Position::do_castle_move() is a private method used to make a castling +/// move. It is called from the main Position::do_move function. Note that +/// castling moves are encoded as "king captures friendly rook" moves, for +/// instance white short castling in a non-Chess960 game is encoded as e1h1. + +void Position::do_castle_move(Move m) { + Color us, them; + Square kfrom, kto, rfrom, rto; + + assert(this->is_ok()); + assert(move_is_ok(m)); + assert(move_is_castle(m)); + + us = this->side_to_move(); + them = opposite_color(us); + + // Find source squares for king and rook: + kfrom = move_from(m); + rfrom = move_to(m); // HACK: See comment at beginning of function. + + assert(this->piece_on(kfrom) == king_of_color(us)); + assert(this->piece_on(rfrom) == rook_of_color(us)); + + // Find destination squares for king and rook: + if(rfrom > kfrom) { // O-O + kto = relative_square(us, SQ_G1); + rto = relative_square(us, SQ_F1); + } + else { // O-O-O + kto = relative_square(us, SQ_C1); + rto = relative_square(us, SQ_D1); + } + + // Remove pieces from source squares: + clear_bit(&(byColorBB[us]), kfrom); + clear_bit(&(byTypeBB[KING]), kfrom); + clear_bit(&(byTypeBB[0]), kfrom); // HACK: byTypeBB[0] == occupied squares + clear_bit(&(byColorBB[us]), rfrom); + clear_bit(&(byTypeBB[ROOK]), rfrom); + clear_bit(&(byTypeBB[0]), rfrom); // HACK: byTypeBB[0] == occupied squares + + // Put pieces on destination squares: + set_bit(&(byColorBB[us]), kto); + set_bit(&(byTypeBB[KING]), kto); + set_bit(&(byTypeBB[0]), kto); // HACK: byTypeBB[0] == occupied squares + set_bit(&(byColorBB[us]), rto); + set_bit(&(byTypeBB[ROOK]), rto); + set_bit(&(byTypeBB[0]), rto); // HACK: byTypeBB[0] == occupied squares + + // Update board array: + board[kfrom] = board[rfrom] = EMPTY; + board[kto] = king_of_color(us); + board[rto] = rook_of_color(us); + + // Update king square: + kingSquare[us] = kto; + + // Update piece lists: + pieceList[us][KING][index[kfrom]] = kto; + pieceList[us][ROOK][index[rfrom]] = rto; + int tmp = index[rfrom]; + index[kto] = index[kfrom]; + index[rto] = tmp; + + // Update incremental scores: + mgValue -= this->mg_pst(us, KING, kfrom); + mgValue += this->mg_pst(us, KING, kto); + egValue -= this->eg_pst(us, KING, kfrom); + egValue += this->eg_pst(us, KING, kto); + mgValue -= this->mg_pst(us, ROOK, rfrom); + mgValue += this->mg_pst(us, ROOK, rto); + egValue -= this->eg_pst(us, ROOK, rfrom); + egValue += this->eg_pst(us, ROOK, rto); + + // Update hash key: + key ^= zobrist[us][KING][kfrom] ^ zobrist[us][KING][kto]; + key ^= zobrist[us][ROOK][rfrom] ^ zobrist[us][ROOK][rto]; + + // Clear en passant square: + if(epSquare != SQ_NONE) { + key ^= zobEp[epSquare]; + epSquare = SQ_NONE; + } + + // Update castling rights: + key ^= zobCastle[castleRights]; + castleRights &= castleRightsMask[kfrom]; + key ^= zobCastle[castleRights]; + + // Reset rule 50 counter: + rule50 = 0; + + // Update checkers BB: + checkersBB = attacks_to(this->king_square(them), us); +} + + +/// Position::do_promotion_move() is a private method used to make a promotion +/// move. It is called from the main Position::do_move function. The +/// UndoInfo object, which has been initialized in Position::do_move, is +/// used to store the captured piece (if any). + +void Position::do_promotion_move(Move m, UndoInfo &u) { + Color us, them; + Square from, to; + PieceType capture, promotion; + + assert(this->is_ok()); + assert(move_is_ok(m)); + assert(move_promotion(m)); + + us = this->side_to_move(); + them = opposite_color(us); + + from = move_from(m); + to = move_to(m); + + assert(pawn_rank(us, to) == RANK_8); + assert(this->piece_on(from) == pawn_of_color(us)); + assert(this->color_of_piece_on(to) == them || this->square_is_empty(to)); + + capture = this->type_of_piece_on(to); + + if(capture) { + assert(capture != KING); + + // Remove captured piece: + clear_bit(&(byColorBB[them]), to); + clear_bit(&(byTypeBB[capture]), to); + + // Update hash key: + key ^= zobrist[them][capture][to]; + + // Update incremental scores: + mgValue -= this->mg_pst(them, capture, to); + egValue -= this->eg_pst(them, capture, to); + + // Update material. Because our move is a promotion, we know that the + // captured piece is not a pawn. + assert(capture != PAWN); + npMaterial[them] -= piece_value_midgame(capture); + + // Update material hash key: + materialKey ^= zobMaterial[them][capture][pieceCount[them][capture]]; + + // Update piece count: + pieceCount[them][capture]--; + + // Update piece list: + pieceList[them][capture][index[to]] = + pieceList[them][capture][pieceCount[them][capture]]; + index[pieceList[them][capture][index[to]]] = index[to]; + + // Remember the captured piece, in order to be able to undo the move + // correctly: + u.capture = capture; + } + + // Remove pawn: + clear_bit(&(byColorBB[us]), from); + clear_bit(&(byTypeBB[PAWN]), from); + clear_bit(&(byTypeBB[0]), from); // HACK: byTypeBB[0] == occupied squares + board[from] = EMPTY; + + // Insert promoted piece: + promotion = move_promotion(m); + assert(promotion >= KNIGHT && promotion <= QUEEN); + set_bit(&(byColorBB[us]), to); + set_bit(&(byTypeBB[promotion]), to); + set_bit(&(byTypeBB[0]), to); // HACK: byTypeBB[0] == occupied squares + board[to] = piece_of_color_and_type(us, promotion); + + // Update hash key: + key ^= zobrist[us][PAWN][from] ^ zobrist[us][promotion][to]; + + // Update pawn hash key: + pawnKey ^= zobrist[us][PAWN][from]; + + // Update material key: + materialKey ^= zobMaterial[us][PAWN][pieceCount[us][PAWN]]; + materialKey ^= zobMaterial[us][promotion][pieceCount[us][promotion]+1]; + + // Update piece counts: + pieceCount[us][PAWN]--; + pieceCount[us][promotion]++; + + // Update piece lists: + pieceList[us][PAWN][index[from]] = + pieceList[us][PAWN][pieceCount[us][PAWN]]; + index[pieceList[us][PAWN][index[from]]] = index[from]; + pieceList[us][promotion][pieceCount[us][promotion] - 1] = to; + index[to] = pieceCount[us][promotion] - 1; + + // Update incremental scores: + mgValue -= this->mg_pst(us, PAWN, from); + mgValue += this->mg_pst(us, promotion, to); + egValue -= this->eg_pst(us, PAWN, from); + egValue += this->eg_pst(us, promotion, to); + + // Update material: + npMaterial[us] += piece_value_midgame(promotion); + + // Clear the en passant square: + if(epSquare != SQ_NONE) { + key ^= zobEp[epSquare]; + epSquare = SQ_NONE; + } + + // Update castle rights: + key ^= zobCastle[castleRights]; + castleRights &= castleRightsMask[to]; + key ^= zobCastle[castleRights]; + + // Reset rule 50 counter: + rule50 = 0; + + // Update checkers BB: + checkersBB = attacks_to(this->king_square(them), us); +} + + +/// Position::do_ep_move() is a private method used to make an en passant +/// capture. It is called from the main Position::do_move function. Because +/// the captured piece is always a pawn, we don't need to pass an UndoInfo +/// object in which to store the captured piece. + +void Position::do_ep_move(Move m) { + Color us, them; + Square from, to, capsq; + + assert(this->is_ok()); + assert(move_is_ok(m)); + assert(move_is_ep(m)); + + us = this->side_to_move(); + them = opposite_color(us); + + // Find from, to and capture squares: + from = move_from(m); + to = move_to(m); + capsq = (us == WHITE)? (to - DELTA_N) : (to - DELTA_S); + + assert(to == epSquare); + assert(pawn_rank(us, to) == RANK_6); + assert(this->piece_on(to) == EMPTY); + assert(this->piece_on(from) == pawn_of_color(us)); + assert(this->piece_on(capsq) == pawn_of_color(them)); + + // Remove captured piece: + clear_bit(&(byColorBB[them]), capsq); + clear_bit(&(byTypeBB[PAWN]), capsq); + clear_bit(&(byTypeBB[0]), capsq); // HACK: byTypeBB[0] == occupied squares + board[capsq] = EMPTY; + + // Remove moving piece from source square: + clear_bit(&(byColorBB[us]), from); + clear_bit(&(byTypeBB[PAWN]), from); + clear_bit(&(byTypeBB[0]), from); // HACK: byTypeBB[0] == occupied squares + + // Put moving piece on destination square: + set_bit(&(byColorBB[us]), to); + set_bit(&(byTypeBB[PAWN]), to); + set_bit(&(byTypeBB[0]), to); // HACK: byTypeBB[0] == occupied squares + board[to] = board[from]; + board[from] = EMPTY; + + // Update material hash key: + materialKey ^= zobMaterial[them][PAWN][pieceCount[them][PAWN]]; + + // Update piece count: + pieceCount[them][PAWN]--; + + // Update piece list: + pieceList[us][PAWN][index[from]] = to; + index[to] = index[from]; + pieceList[them][PAWN][index[capsq]] = + pieceList[them][PAWN][pieceCount[them][PAWN]]; + index[pieceList[them][PAWN][index[capsq]]] = index[capsq]; + + // Update hash key: + key ^= zobrist[us][PAWN][from] ^ zobrist[us][PAWN][to]; + key ^= zobrist[them][PAWN][capsq]; + key ^= zobEp[epSquare]; + + // Update pawn hash key: + pawnKey ^= zobrist[us][PAWN][from] ^ zobrist[us][PAWN][to]; + pawnKey ^= zobrist[them][PAWN][capsq]; + + // Update incremental scores: + mgValue -= this->mg_pst(them, PAWN, capsq); + mgValue -= this->mg_pst(us, PAWN, from); + mgValue += this->mg_pst(us, PAWN, to); + egValue -= this->eg_pst(them, PAWN, capsq); + egValue -= this->eg_pst(us, PAWN, from); + egValue += this->eg_pst(us, PAWN, to); + + // Reset en passant square: + epSquare = SQ_NONE; + + // Reset rule 50 counter: + rule50 = 0; + + // Update checkers BB: + checkersBB = attacks_to(this->king_square(them), us); +} + + +/// 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. It is +/// important that Position::undo_move is called with the same move and UndoInfo +/// object as the earlier call to Position::do_move. + +void Position::undo_move(Move m, const UndoInfo &u) { + assert(this->is_ok()); + assert(move_is_ok(m)); + + gamePly--; + sideToMove = opposite_color(sideToMove); + + // Restore information from our UndoInfo object (except the captured piece, + // which is taken care of later): + this->restore(u); + + if(move_is_castle(m)) + this->undo_castle_move(m); + else if(move_promotion(m)) + this->undo_promotion_move(m, u); + else if(move_is_ep(m)) + this->undo_ep_move(m); + else { + Color us, them; + Square from, to; + PieceType piece, capture; + + us = this->side_to_move(); + them = opposite_color(us); + + from = move_from(m); + to = move_to(m); + + assert(this->piece_on(from) == EMPTY); + assert(color_of_piece_on(to) == us); + + // Put the piece back at the source square: + piece = this->type_of_piece_on(to); + set_bit(&(byColorBB[us]), from); + set_bit(&(byTypeBB[piece]), from); + set_bit(&(byTypeBB[0]), from); // HACK: byTypeBB[0] == occupied squares + board[from] = piece_of_color_and_type(us, piece); + + // Clear the destination square + clear_bit(&(byColorBB[us]), to); + clear_bit(&(byTypeBB[piece]), to); + clear_bit(&(byTypeBB[0]), to); // HACK: byTypeBB[0] == occupied squares + + // If the moving piece was a king, update the king square: + if(piece == KING) + kingSquare[us] = from; + + // Update piece list: + pieceList[us][piece][index[to]] = from; + index[from] = index[to]; + + capture = u.capture; + + if(capture) { + assert(capture != KING); + // Replace the captured piece: + set_bit(&(byColorBB[them]), to); + set_bit(&(byTypeBB[capture]), to); + set_bit(&(byTypeBB[0]), to); + board[to] = piece_of_color_and_type(them, capture); + + // Update material: + if(capture != PAWN) + npMaterial[them] += piece_value_midgame(capture); + + // Update piece list: + pieceList[them][capture][pieceCount[them][capture]] = to; + index[to] = pieceCount[them][capture]; + + // Update piece count: + pieceCount[them][capture]++; + } + else + board[to] = EMPTY; + } + + assert(this->is_ok()); +} + + +/// Position::undo_castle_move() is a private method used to unmake a castling +/// move. It is called from the main Position::undo_move function. Note that +/// castling moves are encoded as "king captures friendly rook" moves, for +/// instance white short castling in a non-Chess960 game is encoded as e1h1. + +void Position::undo_castle_move(Move m) { + Color us, them; + Square kfrom, kto, rfrom, rto; + + assert(move_is_ok(m)); + assert(move_is_castle(m)); + + // When we have arrived here, some work has already been done by + // Position::undo_move. In particular, the side to move has been switched, + // so the code below is correct. + us = this->side_to_move(); + them = opposite_color(us); + + // Find source squares for king and rook: + kfrom = move_from(m); + rfrom = move_to(m); // HACK: See comment at beginning of function. + + // Find destination squares for king and rook: + if(rfrom > kfrom) { // O-O + kto = relative_square(us, SQ_G1); + rto = relative_square(us, SQ_F1); + } + else { // O-O-O + kto = relative_square(us, SQ_C1); + rto = relative_square(us, SQ_D1); + } + + assert(this->piece_on(kto) == king_of_color(us)); + assert(this->piece_on(rto) == rook_of_color(us)); + + // Remove pieces from destination squares: + clear_bit(&(byColorBB[us]), kto); + clear_bit(&(byTypeBB[KING]), kto); + clear_bit(&(byTypeBB[0]), kto); // HACK: byTypeBB[0] == occupied squares + clear_bit(&(byColorBB[us]), rto); + clear_bit(&(byTypeBB[ROOK]), rto); + clear_bit(&(byTypeBB[0]), rto); // HACK: byTypeBB[0] == occupied squares + + // Put pieces on source squares: + set_bit(&(byColorBB[us]), kfrom); + set_bit(&(byTypeBB[KING]), kfrom); + set_bit(&(byTypeBB[0]), kfrom); // HACK: byTypeBB[0] == occupied squares + set_bit(&(byColorBB[us]), rfrom); + set_bit(&(byTypeBB[ROOK]), rfrom); + set_bit(&(byTypeBB[0]), rfrom); // HACK: byTypeBB[0] == occupied squares + + // Update board: + board[rto] = board[kto] = EMPTY; + board[rfrom] = rook_of_color(us); + board[kfrom] = king_of_color(us); + + // Update king square: + kingSquare[us] = kfrom; + + // Update piece lists: + pieceList[us][KING][index[kto]] = kfrom; + pieceList[us][ROOK][index[rto]] = rfrom; + int tmp = index[rto]; // Necessary because we may have rto == kfrom in FRC. + index[kfrom] = index[kto]; + index[rfrom] = tmp; +} + + +/// Position::undo_promotion_move() is a private method used to unmake a +/// promotion move. It is called from the main Position::do_move +/// function. The UndoInfo object, which has been initialized in +/// Position::do_move, is used to put back the captured piece (if any). + +void Position::undo_promotion_move(Move m, const UndoInfo &u) { + Color us, them; + Square from, to; + PieceType capture, promotion; + + assert(move_is_ok(m)); + assert(move_promotion(m)); + + // When we have arrived here, some work has already been done by + // Position::undo_move. In particular, the side to move has been switched, + // so the code below is correct. + us = this->side_to_move(); + them = opposite_color(us); + + from = move_from(m); + to = move_to(m); + + assert(pawn_rank(us, to) == RANK_8); + assert(this->piece_on(from) == EMPTY); + + // Remove promoted piece: + promotion = move_promotion(m); + assert(this->piece_on(to)==piece_of_color_and_type(us, promotion)); + assert(promotion >= KNIGHT && promotion <= QUEEN); + clear_bit(&(byColorBB[us]), to); + clear_bit(&(byTypeBB[promotion]), to); + clear_bit(&(byTypeBB[0]), to); // HACK: byTypeBB[0] == occupied squares + + // Insert pawn at source square: + set_bit(&(byColorBB[us]), from); + set_bit(&(byTypeBB[PAWN]), from); + set_bit(&(byTypeBB[0]), from); // HACK: byTypeBB[0] == occupied squares + board[from] = pawn_of_color(us); + + // Update material: + npMaterial[us] -= piece_value_midgame(promotion); + + // Update piece list: + pieceList[us][PAWN][pieceCount[us][PAWN]] = from; + index[from] = pieceCount[us][PAWN]; + pieceList[us][promotion][index[to]] = + pieceList[us][promotion][pieceCount[us][promotion] - 1]; + index[pieceList[us][promotion][index[to]]] = index[to]; + + // Update piece counts: + pieceCount[us][promotion]--; + pieceCount[us][PAWN]++; + + capture = u.capture; + if(capture) { + assert(capture != KING); + + // Insert captured piece: + set_bit(&(byColorBB[them]), to); + set_bit(&(byTypeBB[capture]), to); + set_bit(&(byTypeBB[0]), to); // HACK: byTypeBB[0] == occupied squares + board[to] = piece_of_color_and_type(them, capture); + + // Update material. Because the move is a promotion move, we know + // that the captured piece cannot be a pawn. + assert(capture != PAWN); + npMaterial[them] += piece_value_midgame(capture); + + // Update piece list: + pieceList[them][capture][pieceCount[them][capture]] = to; + index[to] = pieceCount[them][capture]; + + // Update piece count: + pieceCount[them][capture]++; + } + else + board[to] = EMPTY; +} + + +/// Position::undo_ep_move() is a private method used to unmake an en passant +/// capture. It is called from the main Position::undo_move function. Because +/// the captured piece is always a pawn, we don't need to pass an UndoInfo +/// object from which to retrieve the captured piece. + +void Position::undo_ep_move(Move m) { + Color us, them; + Square from, to, capsq; + + assert(move_is_ok(m)); + assert(move_is_ep(m)); + + // When we have arrived here, some work has already been done by + // Position::undo_move. In particular, the side to move has been switched, + // so the code below is correct. + us = this->side_to_move(); + them = opposite_color(us); + + // Find from, to and captures squares: + from = move_from(m); + to = move_to(m); + capsq = (us == WHITE)? (to - DELTA_N) : (to - DELTA_S); + + assert(to == this->ep_square()); + assert(pawn_rank(us, to) == RANK_6); + assert(this->piece_on(to) == pawn_of_color(us)); + assert(this->piece_on(from) == EMPTY); + assert(this->piece_on(capsq) == EMPTY); + + // Replace captured piece: + set_bit(&(byColorBB[them]), capsq); + set_bit(&(byTypeBB[PAWN]), capsq); + set_bit(&(byTypeBB[0]), capsq); + board[capsq] = pawn_of_color(them); + + // Remove moving piece from destination square: + clear_bit(&(byColorBB[us]), to); + clear_bit(&(byTypeBB[PAWN]), to); + clear_bit(&(byTypeBB[0]), to); + board[to] = EMPTY; + + // Replace moving piece at source square: + set_bit(&(byColorBB[us]), from); + set_bit(&(byTypeBB[PAWN]), from); + set_bit(&(byTypeBB[0]), from); + board[from] = pawn_of_color(us); + + // Update piece list: + pieceList[us][PAWN][index[to]] = from; + index[from] = index[to]; + pieceList[them][PAWN][pieceCount[them][PAWN]] = capsq; + index[capsq] = pieceCount[them][PAWN]; + + // Update piece count: + pieceCount[them][PAWN]++; +} + + +/// Position::do_null_move makes() a "null move": It switches the side to move +/// and updates the hash key without executing any move on the board. + +void Position::do_null_move(UndoInfo &u) { + assert(this->is_ok()); + assert(!this->is_check()); + + // Back up the information necessary to undo the null move to the supplied + // UndoInfo object. In the case of a null move, the only thing we need to + // remember is the last move made and the en passant square. + u.lastMove = lastMove; + u.epSquare = epSquare; + + // Save the current key to the history[] array, in order to be able to + // detect repetition draws: + history[gamePly] = key; + + // Update the necessary information. + sideToMove = opposite_color(sideToMove); + if(epSquare != SQ_NONE) + key ^= zobEp[epSquare]; + epSquare = SQ_NONE; + rule50++; + gamePly++; + key ^= zobSideToMove; + + mgValue += (sideToMove == WHITE)? TempoValueMidgame : -TempoValueMidgame; + egValue += (sideToMove == WHITE)? TempoValueEndgame : -TempoValueEndgame; + + assert(this->is_ok()); +} + + +/// Position::undo_null_move() unmakes a "null move". + +void Position::undo_null_move(const UndoInfo &u) { + assert(this->is_ok()); + assert(!this->is_check()); + + // Restore information from the supplied UndoInfo object: + lastMove = u.lastMove; + epSquare = u.epSquare; + if(epSquare != SQ_NONE) + key ^= zobEp[epSquare]; + + // Update the necessary information. + sideToMove = opposite_color(sideToMove); + rule50--; + gamePly--; + key ^= zobSideToMove; + + mgValue += (sideToMove == WHITE)? TempoValueMidgame : -TempoValueMidgame; + egValue += (sideToMove == WHITE)? TempoValueEndgame : -TempoValueEndgame; + + assert(this->is_ok()); +} + + +/// Position::see() is a static exchange evaluator: It tries to estimate the +/// material gain or loss resulting from a move. There are two versions of +/// this function: One which takes a move as input, and one which takes a +/// 'from' and a 'to' square. The function does not yet understand promotions +/// or en passant captures. + +int Position::see(Square from, Square to) const { + // Approximate material values, with pawn = 1: + static const int seeValues[18] = { + 0, 1, 3, 3, 5, 10, 100, 0, 0, 1, 3, 3, 5, 10, 100, 0, 0, 0 + }; + Color us, them; + Piece piece, capture; + Bitboard attackers, occ, b; + + assert(square_is_ok(from)); + assert(square_is_ok(to)); + + // Initialize colors: + us = this->color_of_piece_on(from); + them = opposite_color(us); + + // Initialize pieces: + piece = this->piece_on(from); + capture = this->piece_on(to); + + // Find all attackers to the destination square, with the moving piece + // removed, but possibly an X-ray attacker added behind it: + occ = this->occupied_squares(); + clear_bit(&occ, from); + attackers = + (rook_attacks_bb(to, occ) & this->rooks_and_queens()) | + (bishop_attacks_bb(to, occ) & this->bishops_and_queens()) | + (this->knight_attacks(to) & this->knights()) | + (this->king_attacks(to) & this->kings()) | + (this->white_pawn_attacks(to) & this->pawns(BLACK)) | + (this->black_pawn_attacks(to) & this->pawns(WHITE)); + attackers &= occ; + + // If the opponent has no attackers, we are finished: + if((attackers & this->pieces_of_color(them)) == EmptyBoardBB) + return seeValues[capture]; + + // 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 + // destianation 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. + int lastCapturingPieceValue = seeValues[piece]; + int swapList[32], n = 1; + Color c = them; + PieceType pt; + + swapList[0] = seeValues[capture]; + + do { + // Locate the least valuable attacker for the side to move. The loop + // below looks like it is potentially infinite, but it isn't. We know + // that the side to move still has at least one attacker left. + for(pt = PAWN; !(attackers&this->pieces_of_color_and_type(c, pt)); pt++) + assert(pt < KING); + + // Remove the attacker we just found from the 'attackers' bitboard, + // and scan for new X-ray attacks behind the attacker: + b = attackers & this->pieces_of_color_and_type(c, pt); + occ ^= (b & -b); + attackers |= + (rook_attacks_bb(to, occ) & this->rooks_and_queens()) | + (bishop_attacks_bb(to, occ) & this->bishops_and_queens()); + attackers &= occ; + + // Add the new entry to the swap list: + assert(n < 32); + swapList[n] = -swapList[n - 1] + lastCapturingPieceValue; + n++; + + // Remember the value of the capturing piece, and change the side to move + // before beginning the next iteration: + lastCapturingPieceValue = seeValues[pt]; + c = opposite_color(c); + + // Stop after a king capture: + if(pt == KING && (attackers & this->pieces_of_color(c))) { + assert(n < 32); + swapList[n++] = 100; + break; + } + } while(attackers & this->pieces_of_color(c)); + + // 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(--n) swapList[n-1] = Min(-swapList[n], swapList[n-1]); + + return swapList[0]; +} + + +int Position::see(Move m) const { + assert(move_is_ok(m)); + return this->see(move_from(m), move_to(m)); +} + + +/// Position::clear() erases the position object to a pristine state, with an +/// empty board, white to move, and no castling rights. + +void Position::clear() { + int i, j; + + for(i = 0; i < 64; i++) { + board[i] = EMPTY; + index[i] = 0; + } + + for(i = 0; i < 2; i++) + byColorBB[i] = EmptyBoardBB; + + for(i = 0; i < 7; i++) { + byTypeBB[i] = EmptyBoardBB; + pieceCount[0][i] = pieceCount[1][i] = 0; + for(j = 0; j < 8; j++) + pieceList[0][i][j] = pieceList[1][i][j] = SQ_NONE; + } + + checkersBB = EmptyBoardBB; + + lastMove = MOVE_NONE; + + sideToMove = WHITE; + castleRights = NO_CASTLES; + initialKFile = FILE_E; + initialKRFile = FILE_H; + initialQRFile = FILE_A; + epSquare = SQ_NONE; + rule50 = 0; + gamePly = 0; +} + + +/// Position::reset_game_ply() simply sets gamePly to 0. It is used from the +/// UCI interface code, whenever a non-reversible move is made in a +/// 'position fen moves m1 m2 ...' command. This makes it possible +/// for the program to handle games of arbitrary length, as long as the GUI +/// handles draws by the 50 move rule correctly. + +void Position::reset_game_ply() { + gamePly = 0; +} + + +/// Position::put_piece() puts a piece on the given square of the board, +/// updating the board array, bitboards, and piece counts. + +void Position::put_piece(Piece p, Square s) { + Color c = color_of_piece(p); + PieceType pt = type_of_piece(p); + + board[s] = p; + index[s] = pieceCount[c][pt]; + pieceList[c][pt][index[s]] = s; + + set_bit(&(byTypeBB[pt]), s); + set_bit(&(byColorBB[c]), s); + set_bit(&byTypeBB[0], s); // HACK: byTypeBB[0] contains all occupied squares. + + pieceCount[c][pt]++; + + if(pt == KING) + kingSquare[c] = s; +} + + +/// Position::allow_oo() gives the given side the right to castle kingside. +/// Used when setting castling rights during parsing of FEN strings. + +void Position::allow_oo(Color c) { + castleRights |= (1 + int(c)); +} + + +/// Position::allow_ooo() gives the given side the right to castle queenside. +/// Used when setting castling rights during parsing of FEN strings. + +void Position::allow_ooo(Color c) { + castleRights |= (4 + 4*int(c)); +} + + +/// Position::compute_key() computes the hash key of the position. The hash +/// key is usually updated incrementally as moves are made and unmade, the +/// compute_key() function is only used when a new position is set up, and +/// to verify the correctness of the hash key when running in debug mode. + +Key Position::compute_key() const { + Key result = Key(0ULL); + + for(Square s = SQ_A1; s <= SQ_H8; s++) + if(this->square_is_occupied(s)) + result ^= + zobrist[this->color_of_piece_on(s)][this->type_of_piece_on(s)][s]; + + if(this->ep_square() != SQ_NONE) + result ^= zobEp[this->ep_square()]; + result ^= zobCastle[castleRights]; + if(this->side_to_move() == BLACK) result ^= zobSideToMove; + + return result; +} + + +/// Position::compute_pawn_key() computes the hash key of the position. The +/// hash key is usually updated incrementally as moves are made and unmade, +/// the compute_pawn_key() function is only used when a new position is set +/// up, and to verify the correctness of the pawn hash key when running in +/// debug mode. + +Key Position::compute_pawn_key() const { + Key result = Key(0ULL); + Bitboard b; + Square s; + + for(Color c = WHITE; c <= BLACK; c++) { + b = this->pawns(c); + while(b) { + s = pop_1st_bit(&b); + result ^= zobrist[c][PAWN][s]; + } + } + return result; +} + + +/// Position::compute_material_key() computes the hash key of the position. +/// The hash key is usually updated incrementally as moves are made and unmade, +/// the compute_material_key() function is only used when a new position is set +/// up, and to verify the correctness of the material hash key when running in +/// debug mode. + +Key Position::compute_material_key() const { + Key result = Key(0ULL); + for(Color c = WHITE; c <= BLACK; c++) + for(PieceType pt = PAWN; pt <= QUEEN; pt++) { + int count = this->piece_count(c, pt); + for(int i = 0; i <= count; i++) + result ^= zobMaterial[c][pt][i]; + } + return result; +} + + +/// Position::compute_mg_value() and Position::compute_eg_value() compute the +/// incremental scores for the middle game and the endgame. These functions +/// are used to initialize the incremental scores when a new position is set +/// up, and to verify that the scores are correctly updated by do_move +/// and undo_move when the program is running in debug mode. + +Value Position::compute_mg_value() const { + Value result = Value(0); + Bitboard b; + Square s; + + for(Color c = WHITE; c <= BLACK; c++) + for(PieceType pt = PAWN; pt <= KING; pt++) { + b = this->pieces_of_color_and_type(c, pt); + while(b) { + s = pop_1st_bit(&b); + assert(this->piece_on(s) == piece_of_color_and_type(c, pt)); + result += this->mg_pst(c, pt, s); + } + } + result += (this->side_to_move() == WHITE)? + (TempoValueMidgame / 2) : -(TempoValueMidgame / 2); + return result; +} + +Value Position::compute_eg_value() const { + Value result = Value(0); + Bitboard b; + Square s; + + for(Color c = WHITE; c <= BLACK; c++) + for(PieceType pt = PAWN; pt <= KING; pt++) { + b = this->pieces_of_color_and_type(c, pt); + while(b) { + s = pop_1st_bit(&b); + assert(this->piece_on(s) == piece_of_color_and_type(c, pt)); + result += this->eg_pst(c, pt, s); + } + } + result += (this->side_to_move() == WHITE)? + (TempoValueEndgame / 2) : -(TempoValueEndgame / 2); + return result; +} + + +/// Position::compute_non_pawn_material() computes the total non-pawn middle +/// game material score for the given side. Material scores are updated +/// incrementally during the search, this function is only used while +/// initializing a new Position object. + +Value Position::compute_non_pawn_material(Color c) const { + Value result = Value(0); + Square s; + + for(PieceType pt = KNIGHT; pt <= QUEEN; pt++) { + Bitboard b = this->pieces_of_color_and_type(c, pt); + while(b) { + s = pop_1st_bit(&b); + assert(this->piece_on(s) == piece_of_color_and_type(c, pt)); + result += piece_value_midgame(pt); + } + } + return result; +} + + +/// Position::is_mate() returns true or false depending on whether the +/// side to move is checkmated. Note that this function is currently very +/// slow, and shouldn't be used frequently inside the search. + +bool Position::is_mate() { + if(this->is_check()) { + MovePicker mp = MovePicker(*this, false, MOVE_NONE, MOVE_NONE, MOVE_NONE, + MOVE_NONE, Depth(0)); + return mp.get_next_move() == MOVE_NONE; + } + else + return false; +} + + +/// Position::is_draw() tests whether the position is drawn by material, +/// repetition, or the 50 moves rule. It does not detect stalemates, this +/// must be done by the search. + +bool Position::is_draw() const { + // Draw by material? + if(!this->pawns() && + this->non_pawn_material(WHITE) + this->non_pawn_material(BLACK) + <= BishopValueMidgame) + return true; + + // Draw by the 50 moves rule? + if(rule50 > 100 || (rule50 == 100 && !this->is_check())) + return true; + + // Draw by repetition? + for(int i = 2; i < Min(gamePly, rule50); i += 2) + if(history[gamePly - i] == key) + return true; + + return false; +} + + +/// Position::has_mate_threat() tests whether a given color has a mate in one +/// from the current position. This function is quite slow, but it doesn't +/// matter, because it is currently only called from PV nodes, which are rare. + +bool Position::has_mate_threat(Color c) { + UndoInfo u1, u2; + Color stm = this->side_to_move(); + + // The following lines are useless and silly, but prevents gcc from + // emitting a stupid warning stating that u1.lastMove and u1.epSquare might + // be used uninitialized. + u1.lastMove = lastMove; + u1.epSquare = epSquare; + + if(this->is_check()) + return false; + + // If the input color is not equal to the side to move, do a null move + if(c != stm) this->do_null_move(u1); + + MoveStack mlist[120]; + int count; + bool result = false; + + // Generate legal moves + count = generate_legal_moves(*this, mlist); + + // Loop through the moves, and see if one of them is mate. + for(int i = 0; i < count; i++) { + this->do_move(mlist[i].move, u2); + if(this->is_mate()) result = true; + this->undo_move(mlist[i].move, u2); + } + + // Undo null move, if necessary + if(c != stm) this->undo_null_move(u1); + + return result; +} + + +/// Position::init_zobrist() is a static member function which initializes the +/// various arrays used to compute hash keys. + +void Position::init_zobrist() { + + for(int i = 0; i < 2; i++) + for(int j = 0; j < 8; j++) + for(int k = 0; k < 64; k++) + zobrist[i][j][k] = Key(genrand_int64()); + + for(int i = 0; i < 64; i++) + zobEp[i] = Key(genrand_int64()); + + for(int i = 0; i < 16; i++) + zobCastle[i] = genrand_int64(); + + zobSideToMove = genrand_int64(); + + for(int i = 0; i < 2; i++) + for(int j = 0; j < 8; j++) + for(int k = 0; k < 16; k++) + zobMaterial[i][j][k] = (k > 0)? Key(genrand_int64()) : Key(0LL); + + for(int i = 0; i < 16; i++) + zobMaterial[0][KING][i] = zobMaterial[1][KING][i] = Key(0ULL); +} + + +/// Position::init_piece_square_tables() initializes the piece square tables. +/// This is a two-step operation: First, the white halves of the tables are +/// copied from the MgPST[][] and EgPST[][] arrays, with a small random number +/// added to each entry if the "Randomness" UCI parameter is non-zero. +/// Second, the black halves of the tables are initialized by mirroring +/// and changing the sign of the corresponding white scores. + +void Position::init_piece_square_tables() { + int r = get_option_value_int("Randomness"), i; + for(Square s = SQ_A1; s <= SQ_H8; s++) { + for(Piece p = WP; p <= WK; p++) { + i = (r == 0)? 0 : (genrand_int32() % (r*2) - r); + MgPieceSquareTable[p][s] = Value(MgPST[p][s] + i); + EgPieceSquareTable[p][s] = Value(EgPST[p][s] + i); + } + } + for(Square s = SQ_A1; s <= SQ_H8; s++) + for(Piece p = BP; p <= BK; p++) { + MgPieceSquareTable[p][s] = -MgPieceSquareTable[p-8][flip_square(s)]; + EgPieceSquareTable[p][s] = -EgPieceSquareTable[p-8][flip_square(s)]; + } +} + + +/// Position::flipped_copy() makes a copy of the input position, but with +/// the white and black sides reversed. This is only useful for debugging, +/// especially for finding evaluation symmetry bugs. + +void Position::flipped_copy(const Position &pos) { + assert(pos.is_ok()); + + this->clear(); + + // Board + for(Square s = SQ_A1; s <= SQ_H8; s++) + if(!pos.square_is_empty(s)) + this->put_piece(Piece(int(pos.piece_on(s)) ^ 8), flip_square(s)); + + // Side to move + sideToMove = opposite_color(pos.side_to_move()); + + // Castling rights + if(pos.can_castle_kingside(WHITE)) this->allow_oo(BLACK); + if(pos.can_castle_queenside(WHITE)) this->allow_ooo(BLACK); + if(pos.can_castle_kingside(BLACK)) this->allow_oo(WHITE); + if(pos.can_castle_queenside(BLACK)) this->allow_ooo(WHITE); + + initialKFile = pos.initialKFile; + initialKRFile = pos.initialKRFile; + initialQRFile = pos.initialQRFile; + + for(Square sq = SQ_A1; sq <= SQ_H8; sq++) + castleRightsMask[sq] = ALL_CASTLES; + castleRightsMask[make_square(initialKFile, RANK_1)] ^= (WHITE_OO|WHITE_OOO); + castleRightsMask[make_square(initialKFile, RANK_8)] ^= (BLACK_OO|BLACK_OOO); + castleRightsMask[make_square(initialKRFile, RANK_1)] ^= WHITE_OO; + castleRightsMask[make_square(initialKRFile, RANK_8)] ^= BLACK_OO; + castleRightsMask[make_square(initialQRFile, RANK_1)] ^= WHITE_OOO; + castleRightsMask[make_square(initialQRFile, RANK_8)] ^= BLACK_OOO; + + // En passant square + if(pos.epSquare != SQ_NONE) + epSquare = flip_square(pos.epSquare); + + // Checkers + this->find_checkers(); + + // Hash keys + key = this->compute_key(); + pawnKey = this->compute_pawn_key(); + materialKey = this->compute_material_key(); + + // Incremental scores + mgValue = this->compute_mg_value(); + egValue = this->compute_eg_value(); + + // Material + npMaterial[WHITE] = this->compute_non_pawn_material(WHITE); + npMaterial[BLACK] = this->compute_non_pawn_material(BLACK); + + assert(this->is_ok()); +} + + +/// Position::is_ok() performs some consitency checks for the position object. +/// This is meant to be helpful when debugging. + +bool Position::is_ok() const { + + // What features of the position should be verified? + static const bool debugBitboards = false; + static const bool debugKingCount = false; + static const bool debugKingCapture = false; + static const bool debugCheckerCount = false; + static const bool debugKey = false; + static const bool debugMaterialKey = false; + static const bool debugPawnKey = false; + static const bool debugIncrementalEval = false; + static const bool debugNonPawnMaterial = false; + static const bool debugPieceCounts = false; + static const bool debugPieceList = false; + + // Side to move OK? + if(!color_is_ok(this->side_to_move())) + return false; + + // Are the king squares in the position correct? + if(this->piece_on(this->king_square(WHITE)) != WK) + return false; + if(this->piece_on(this->king_square(BLACK)) != BK) + return false; + + // Castle files OK? + if(!file_is_ok(initialKRFile)) + return false; + if(!file_is_ok(initialQRFile)) + return false; + + // Do both sides have exactly one king? + if(debugKingCount) { + int kingCount[2] = {0, 0}; + for(Square s = SQ_A1; s <= SQ_H8; s++) + if(this->type_of_piece_on(s) == KING) + kingCount[this->color_of_piece_on(s)]++; + if(kingCount[0] != 1 || kingCount[1] != 1) + return false; + } + + // Can the side to move capture the opponent's king? + if(debugKingCapture) { + Color us = this->side_to_move(); + Color them = opposite_color(us); + Square ksq = this->king_square(them); + if(this->square_is_attacked(ksq, us)) + return false; + } + + // Is there more than 2 checkers? + if(debugCheckerCount && count_1s(checkersBB) > 2) + return false; + + // Bitboards OK? + if(debugBitboards) { + // The intersection of the white and black pieces must be empty: + if((this->pieces_of_color(WHITE) & this->pieces_of_color(BLACK)) + != EmptyBoardBB) + return false; + + // The union of the white and black pieces must be equal to all + // occupied squares: + if((this->pieces_of_color(WHITE) | this->pieces_of_color(BLACK)) + != this->occupied_squares()) + 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 && (this->pieces_of_type(p1) & this->pieces_of_type(p2))) + return false; + } + + // En passant square OK? + if(this->ep_square() != SQ_NONE) { + // The en passant square must be on rank 6, from the point of view of the + // side to move. + if(pawn_rank(this->side_to_move(), this->ep_square()) != RANK_6) + return false; + } + + // Hash key OK? + if(debugKey && key != this->compute_key()) + return false; + + // Pawn hash key OK? + if(debugPawnKey && pawnKey != this->compute_pawn_key()) + return false; + + // Material hash key OK? + if(debugMaterialKey && materialKey != this->compute_material_key()) + return false; + + // Incremental eval OK? + if(debugIncrementalEval) { + if(mgValue != this->compute_mg_value()) + return false; + if(egValue != this->compute_eg_value()) + return false; + } + + // Non-pawn material OK? + if(debugNonPawnMaterial) { + if(npMaterial[WHITE] != compute_non_pawn_material(WHITE)) + return false; + if(npMaterial[BLACK] != compute_non_pawn_material(BLACK)) + return false; + } + + // Piece counts OK? + if(debugPieceCounts) + for(Color c = WHITE; c <= BLACK; c++) + for(PieceType pt = PAWN; pt <= KING; pt++) + if(pieceCount[c][pt] != count_1s(this->pieces_of_color_and_type(c, pt))) + return false; + + if(debugPieceList) { + for(Color c = WHITE; c <= BLACK; c++) + for(PieceType pt = PAWN; pt <= KING; pt++) + for(int i = 0; i < pieceCount[c][pt]; i++) { + if(this->piece_on(this->piece_list(c, pt, i)) != + piece_of_color_and_type(c, pt)) + return false; + if(index[this->piece_list(c, pt, i)] != i) + return false; + } + } + + return true; +} diff --git a/src/position.h b/src/position.h new file mode 100644 index 00000000..ff907d83 --- /dev/null +++ b/src/position.h @@ -0,0 +1,746 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +#if !defined(POSITION_H_INCLUDED) +#define POSITION_H_INCLUDED + +//// +//// Includes +//// + +#include "bitboard.h" +#include "color.h" +#include "direction.h" +#include "move.h" +#include "piece.h" +#include "phase.h" +#include "square.h" +#include "value.h" + + +//// +//// Constants +//// + +/// FEN string for the initial position: +const std::string StartPosition = + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; + +/// Maximum number of plies per game (220 should be enough, because the +/// maximum search depth is 100, and during position setup we reset the +/// move counter for every non-reversible move): +const int MaxGameLength = 220; + + +//// +//// Types +//// + +/// Castle rights, encoded as bit fields: + +enum CastleRights { + NO_CASTLES = 0, + WHITE_OO = 1, BLACK_OO = 2, + WHITE_OOO = 4, BLACK_OOO = 8, + ALL_CASTLES = 15 +}; + + +/// The UndoInfo struct stores information we need 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), an UndoInfo object +/// must be passed as a parameter. When the move is unmade (by calling +/// Position::undo_move), the same UndoInfo object must be passed again. + +struct UndoInfo { + int castleRights; + Square epSquare; + Bitboard checkersBB; + Key key, pawnKey, materialKey; + int rule50; + Move lastMove; + PieceType capture; + Value mgValue, egValue; +}; + + +/// The position data structure. A position consists of the following data: +/// +/// * For each piece type, a bitboard representing the squares occupied +/// by pieces of that type. +/// * For each color, a bitboard representing the squares occupiecd by +/// pieces of that color. +/// * A bitboard of all occupied squares. +/// * A bitboard of all checking pieces. +/// * A 64-entry array of pieces, indexed by the squares of the board. +/// * The current side to move. +/// * Information about the castling rights for both sides. +/// * The initial files of the kings and both pairs of rooks. This is +/// used to implement the Chess960 castling rules. +/// * The en passant square (which is SQ_NONE if no en passant capture is +/// possible). +/// * The squares of the kings for both sides. +/// * The last move played. +/// * Hash keys for the position itself, the current pawn structure, and +/// the current material situation. +/// * Hash keys for all previous positions in the game (for detecting +/// repetition draws. +/// * A counter for detecting 50 move rule draws. + +class Position { + + friend class MaterialInfo; + +public: + // Constructors + Position(); + Position(const Position &pos); + Position(const std::string &fen); + + // Text input/output + void from_fen(const std::string &fen); + const std::string to_fen() const; + void print() const; + + // Copying + void copy(const Position &pos); + void flipped_copy(const Position &pos); + + // The piece on a given square + Piece piece_on(Square s) const; + PieceType type_of_piece_on(Square s) const; + Color color_of_piece_on(Square s) const; + bool square_is_empty(Square s) const; + bool square_is_occupied(Square s) const; + Value midgame_value_of_piece_on(Square s) const; + Value endgame_value_of_piece_on(Square s) const; + + // Side to move + Color side_to_move() const; + + // Bitboard representation of the position + Bitboard empty_squares() const; + Bitboard occupied_squares() const; + Bitboard pieces_of_color(Color c) const; + Bitboard pieces_of_type(PieceType pt) const; + Bitboard pieces_of_color_and_type(Color c, PieceType pt) const; + Bitboard pawns() const; + Bitboard knights() const; + Bitboard bishops() const; + Bitboard rooks() const; + Bitboard queens() const; + Bitboard kings() const; + Bitboard rooks_and_queens() const; + Bitboard bishops_and_queens() const; + Bitboard sliders() const; + Bitboard pawns(Color c) const; + Bitboard knights(Color c) const; + Bitboard bishops(Color c) const; + Bitboard rooks(Color c) const; + Bitboard queens(Color c) const; + Bitboard kings(Color c) const; + Bitboard rooks_and_queens(Color c) const; + Bitboard bishops_and_queens(Color c) const; + Bitboard sliders_of_color(Color c) const; + + // Number of pieces of each color and type + int piece_count(Color c, PieceType pt) const; + int pawn_count(Color c) const; + int knight_count(Color c) const; + int bishop_count(Color c) const; + int rook_count(Color c) const; + int queen_count(Color c) const; + + // The en passant square: + Square ep_square() const; + + // Current king position for each color + Square king_square(Color c) const; + + // Castling rights. + bool can_castle_kingside(Color c) const; + bool can_castle_queenside(Color c) const; + bool can_castle(Color c) const; + Square initial_kr_square(Color c) const; + Square initial_qr_square(Color c) const; + + // Attack bitboards + Bitboard sliding_attacks(Square s, Direction d) const; + Bitboard ray_attacks(Square s, SignedDirection d) const; + Bitboard pawn_attacks(Color c, Square s) const; + Bitboard white_pawn_attacks(Square s) const; + Bitboard black_pawn_attacks(Square s) const; + Bitboard knight_attacks(Square s) const; + Bitboard bishop_attacks(Square s) const; + Bitboard rook_attacks(Square s) const; + Bitboard queen_attacks(Square s) const; + Bitboard king_attacks(Square s) const; + + // Bitboards for pinned pieces and discovered check candidates + Bitboard discovered_check_candidates(Color c) const; + Bitboard pinned_pieces(Color c) const; + + // Checking pieces + Bitboard checkers() const; + + // Piece lists: + Square piece_list(Color c, PieceType pt, int index) const; + Square pawn_list(Color c, int index) const; + Square knight_list(Color c, int index) const; + Square bishop_list(Color c, int index) const; + Square rook_list(Color c, int index) const; + Square queen_list(Color c, int index) const; + + // Attack information for a given square + bool square_is_attacked(Square s, Color c) const; + Bitboard attacks_to(Square s) const; + Bitboard attacks_to(Square s, Color c) const; + bool is_check() const; + bool piece_attacks_square(Square f, Square t) const; + bool white_pawn_attacks_square(Square f, Square t) const; + bool black_pawn_attacks_square(Square f, Square t) const; + bool knight_attacks_square(Square f, Square t) const; + bool bishop_attacks_square(Square f, Square t) const; + bool rook_attacks_square(Square f, Square t) const; + bool queen_attacks_square(Square f, Square t) const; + bool king_attacks_square(Square f, Square t) const; + + // Properties of moves + bool move_is_legal(Move m) const; + bool move_is_legal(Move m, Bitboard pinned) const; + bool move_is_check(Move m) const; + bool move_is_check(Move m, Bitboard dcCandidates) const; + bool move_is_capture(Move m) const; + bool move_is_pawn_push_to_7th(Move m) const; + bool move_is_passed_pawn_push(Move m) const; + bool move_was_passed_pawn_push(Move m) const; + bool move_attacks_square(Move m, Square s) const; + + // Information about pawns + bool pawn_is_passed(Color c, Square s) const; + bool pawn_is_isolated(Color c, Square s) const; + bool pawn_is_doubled(Color c, Square s) const; + + // Open and half-open files + bool file_is_open(File f) const; + bool file_is_half_open(Color c, File f) const; + + // Weak squares + bool square_is_weak(Square s, Color c) const; + + // Doing and undoing moves + void backup(UndoInfo &u) const; + void restore(const UndoInfo &u); + void do_move(Move m, UndoInfo &u); + void do_move(Move m, UndoInfo &u, Bitboard dcCandidates); + void undo_move(Move m, const UndoInfo &u); + void do_null_move(UndoInfo &u); + void undo_null_move(const UndoInfo &u); + + // Static exchange evaluation + int see(Square from, Square to) const; + int see(Move m) const; + + // Accessing hash keys + Key get_key() const; + Key get_pawn_key() const; + Key get_material_key() const; + + // Incremental evaluation + Value mg_value() const; + Value eg_value() const; + Value non_pawn_material(Color c) const; + Phase game_phase() const; + + // Game termination checks + bool is_mate(); + bool is_draw() const; + + // Check if one side threatens a mate in one + bool has_mate_threat(Color c); + + // Number of plies since the last non-reversible move + int rule_50_counter() const; + + // Other properties of the position + bool opposite_colored_bishops() const; + bool has_pawn_on_7th(Color c) const; + + // Reset the gamePly variable to 0 + void reset_game_ply(); + + // Position consistency check, for debugging + bool is_ok() const; + + // Static member functions: + static void init_zobrist(); + static void init_piece_square_tables(); + +private: + // Initialization helper functions (used while setting up a position) + void clear(); + void put_piece(Piece p, Square s); + void allow_oo(Color c); + void allow_ooo(Color c); + + // Helper functions for doing and undoing moves + void do_castle_move(Move m); + void do_promotion_move(Move m, UndoInfo &u); + void do_ep_move(Move m); + void undo_castle_move(Move m); + void undo_promotion_move(Move m, const UndoInfo &u); + void undo_ep_move(Move m); + void find_checkers(); + + // Computing hash keys from scratch (for initialization and debugging) + Key compute_key() const; + Key compute_pawn_key() const; + Key compute_material_key() const; + + // Computing incremental evaluation scores and material counts + Value mg_pst(Color c, PieceType pt, Square s) const; + Value eg_pst(Color c, PieceType pt, Square s) const; + Value compute_mg_value() const; + Value compute_eg_value() const; + Value compute_non_pawn_material(Color c) const; + + // Bitboards + Bitboard byColorBB[2], byTypeBB[8]; + Bitboard checkersBB; + + // Board + Piece board[64]; + + // Piece counts + int pieceCount[2][8]; // [color][pieceType] + + // Piece lists + Square pieceList[2][8][16]; // [color][pieceType][index] + int index[64]; + + // Other info + Color sideToMove; + int castleRights; + File initialKFile, initialKRFile, initialQRFile; + Square epSquare; + Square kingSquare[2]; + Move lastMove; + Key key, pawnKey, materialKey, history[MaxGameLength]; + int rule50, gamePly; + Value mgValue, egValue; + Value npMaterial[2]; + + // Static variables + static int castleRightsMask[64]; + static Key zobrist[2][8][64]; + static Key zobEp[64]; + static Key zobCastle[16]; + static Key zobMaterial[2][8][16]; + static Key zobSideToMove; + static Value MgPieceSquareTable[16][64]; + static Value EgPieceSquareTable[16][64]; +}; + + +//// +//// Inline functions +//// + +inline Piece Position::piece_on(Square s) const { + return board[s]; +} + +inline Color Position::color_of_piece_on(Square s) const { + return color_of_piece(this->piece_on(s)); +} + +inline PieceType Position::type_of_piece_on(Square s) const { + return type_of_piece(this->piece_on(s)); +} + +inline bool Position::square_is_empty(Square s) const { + return this->piece_on(s) == EMPTY; +} + +inline bool Position::square_is_occupied(Square s) const { + return !this->square_is_empty(s); +} + +inline Value Position::midgame_value_of_piece_on(Square s) const { + return piece_value_midgame(this->piece_on(s)); +} + +inline Value Position::endgame_value_of_piece_on(Square s) const { + return piece_value_endgame(this->piece_on(s)); +} + +inline Color Position::side_to_move() const { + return sideToMove; +} + +inline Bitboard Position::occupied_squares() const { + return byTypeBB[0]; +} + +inline Bitboard Position::empty_squares() const { + return ~(this->occupied_squares()); +} + +inline Bitboard Position::pieces_of_color(Color c) const { + return byColorBB[c]; +} + +inline Bitboard Position::pieces_of_type(PieceType pt) const { + return byTypeBB[pt]; +} + +inline Bitboard Position::pieces_of_color_and_type(Color c, PieceType pt) + const { + return this->pieces_of_color(c) & this->pieces_of_type(pt); +} + +inline Bitboard Position::pawns() const { + return this->pieces_of_type(PAWN); +} + +inline Bitboard Position::knights() const { + return this->pieces_of_type(KNIGHT); +} + +inline Bitboard Position::bishops() const { + return this->pieces_of_type(BISHOP); +} + +inline Bitboard Position::rooks() const { + return this->pieces_of_type(ROOK); +} + +inline Bitboard Position::queens() const { + return this->pieces_of_type(QUEEN); +} + +inline Bitboard Position::kings() const { + return this->pieces_of_type(KING); +} + +inline Bitboard Position::rooks_and_queens() const { + return this->rooks() | this->queens(); +} + +inline Bitboard Position::bishops_and_queens() const { + return this->bishops() | this->queens(); +} + +inline Bitboard Position::sliders() const { + return this->bishops() | this->queens() | this->rooks(); +} + +inline Bitboard Position::pawns(Color c) const { + return this->pieces_of_color_and_type(c, PAWN); +} + +inline Bitboard Position::knights(Color c) const { + return this->pieces_of_color_and_type(c, KNIGHT); +} + +inline Bitboard Position::bishops(Color c) const { + return this->pieces_of_color_and_type(c, BISHOP); +} + +inline Bitboard Position::rooks(Color c) const { + return this->pieces_of_color_and_type(c, ROOK); +} + +inline Bitboard Position::queens(Color c) const { + return this->pieces_of_color_and_type(c, QUEEN); +} + +inline Bitboard Position::kings(Color c) const { + return this->pieces_of_color_and_type(c, KING); +} + +inline Bitboard Position::rooks_and_queens(Color c) const { + return this->rooks_and_queens() & this->pieces_of_color(c); +} + +inline Bitboard Position::bishops_and_queens(Color c) const { + return this->bishops_and_queens() & this->pieces_of_color(c); +} + +inline Bitboard Position::sliders_of_color(Color c) const { + return this->sliders() & this->pieces_of_color(c); +} + +inline int Position::piece_count(Color c, PieceType pt) const { + return pieceCount[c][pt]; +} + +inline int Position::pawn_count(Color c) const { + return this->piece_count(c, PAWN); +} + +inline int Position::knight_count(Color c) const { + return this->piece_count(c, KNIGHT); +} + +inline int Position::bishop_count(Color c) const { + return this->piece_count(c, BISHOP); +} + +inline int Position::rook_count(Color c) const { + return this->piece_count(c, ROOK); +} + +inline int Position::queen_count(Color c) const { + return this->piece_count(c, QUEEN); +} + +inline Square Position::piece_list(Color c, PieceType pt, int index) const { + return pieceList[c][pt][index]; +} + +inline Square Position::pawn_list(Color c, int index) const { + return this->piece_list(c, PAWN, index); +} + +inline Square Position::knight_list(Color c, int index) const { + return this->piece_list(c, KNIGHT, index); +} + +inline Square Position::bishop_list(Color c, int index) const { + return this->piece_list(c, BISHOP, index); +} + +inline Square Position::rook_list(Color c, int index) const { + return this->piece_list(c, ROOK, index); +} + +inline Square Position::queen_list(Color c, int index) const { + return this->piece_list(c, QUEEN, index); +} + +inline Square Position::ep_square() const { + return epSquare; +} + +inline Square Position::king_square(Color c) const { + return kingSquare[c]; +} + +inline bool Position::can_castle_kingside(Color side) const { + return castleRights & (1+int(side)); +} + +inline bool Position::can_castle_queenside(Color side) const { + return castleRights & (4+4*int(side)); +} + +inline bool Position::can_castle(Color side) const { + return can_castle_kingside(side) || can_castle_queenside(side); +} + +inline Square Position::initial_kr_square(Color c) const { + return relative_square(c, make_square(initialKRFile, RANK_1)); +} + +inline Square Position::initial_qr_square(Color c) const { + return relative_square(c, make_square(initialQRFile, RANK_1)); +} + +inline Bitboard Position::pawn_attacks(Color c, Square s) const { + return StepAttackBB[pawn_of_color(c)][s]; +} + +inline Bitboard Position::white_pawn_attacks(Square s) const { + return this->pawn_attacks(WHITE, s); +} + +inline Bitboard Position::black_pawn_attacks(Square s) const { + return this->pawn_attacks(BLACK, s); +} + +inline Bitboard Position::knight_attacks(Square s) const { + return StepAttackBB[KNIGHT][s]; +} + +inline Bitboard Position::rook_attacks(Square s) const { + return rook_attacks_bb(s, this->occupied_squares()); +} + +inline Bitboard Position::bishop_attacks(Square s) const { + return bishop_attacks_bb(s, this->occupied_squares()); +} + +inline Bitboard Position::queen_attacks(Square s) const { + return this->rook_attacks(s) | this->bishop_attacks(s); +} + +inline Bitboard Position::king_attacks(Square s) const { + return StepAttackBB[KING][s]; +} + +inline Bitboard Position::checkers() const { + return checkersBB; +} + +inline bool Position::is_check() const { + return this->checkers() != EmptyBoardBB; +} + +inline bool Position::white_pawn_attacks_square(Square f, Square t) const { + return bit_is_set(this->white_pawn_attacks(f), t); +} + +inline bool Position::black_pawn_attacks_square(Square f, Square t) const { + return bit_is_set(this->black_pawn_attacks(f), t); +} + +inline bool Position::knight_attacks_square(Square f, Square t) const { + return bit_is_set(this->knight_attacks(f), t); +} + +inline bool Position::bishop_attacks_square(Square f, Square t) const { + return bit_is_set(this->bishop_attacks(f), t); +} + +inline bool Position::rook_attacks_square(Square f, Square t) const { + return bit_is_set(this->rook_attacks(f), t); +} + +inline bool Position::queen_attacks_square(Square f, Square t) const { + return bit_is_set(this->queen_attacks(f), t); +} + +inline bool Position::king_attacks_square(Square f, Square t) const { + return bit_is_set(this->king_attacks(f), t); +} + +inline bool Position::pawn_is_passed(Color c, Square s) const { + return !(this->pawns(opposite_color(c)) & passed_pawn_mask(c, s)); +} + +inline bool Position::pawn_is_isolated(Color c, Square s) const { + return !(this->pawns(c) & neighboring_files_bb(s)); +} + +inline bool Position::pawn_is_doubled(Color c, Square s) const { + return this->pawns(c) & squares_behind(c, s); +} + +inline bool Position::file_is_open(File f) const { + return !(this->pawns() & file_bb(f)); +} + +inline bool Position::file_is_half_open(Color c, File f) const { + return !(this->pawns(c) & file_bb(f)); +} + +inline bool Position::square_is_weak(Square s, Color c) const { + return !(this->pawns(c) & outpost_mask(opposite_color(c), s)); +} + +inline Key Position::get_key() const { + return key; +} + +inline Key Position::get_pawn_key() const { + return pawnKey; +} + +inline Key Position::get_material_key() const { + return materialKey; +} + +inline Value Position::mg_pst(Color c, PieceType pt, Square s) const { + return MgPieceSquareTable[piece_of_color_and_type(c, pt)][s]; +} + +inline Value Position::eg_pst(Color c, PieceType pt, Square s) const { + return EgPieceSquareTable[piece_of_color_and_type(c, pt)][s]; +} + +inline Value Position::mg_value() const { + return mgValue; +} + +inline Value Position::eg_value() const { + return egValue; +} + +inline Value Position::non_pawn_material(Color c) const { + return npMaterial[c]; +} + +inline Phase Position::game_phase() const { + + // The purpose of the Value(325) terms below is to make sure the difference + // between MidgameLimit and EndgameLimit is a power of 2, which should make + // the division at the end of the function a bit faster. + + static const Value MidgameLimit = + 2*QueenValueMidgame+2*RookValueMidgame+6*BishopValueMidgame+Value(325); + static const Value EndgameLimit = 4*RookValueMidgame-Value(325); + Value npm = this->non_pawn_material(WHITE) + this->non_pawn_material(BLACK); + + if(npm >= MidgameLimit) + return PHASE_MIDGAME; + else if(npm <= EndgameLimit) + return PHASE_ENDGAME; + else + return Phase(((npm - EndgameLimit) * 128) / (MidgameLimit - EndgameLimit)); +} + +inline bool Position::move_is_pawn_push_to_7th(Move m) const { + Color c = this->side_to_move(); + return + this->piece_on(move_from(m)) == pawn_of_color(c) && + pawn_rank(c, move_to(m)) == RANK_7; +} + +inline bool Position::move_is_passed_pawn_push(Move m) const { + Color c = this->side_to_move(); + return + this->piece_on(move_from(m)) == pawn_of_color(c) && + this->pawn_is_passed(c, move_to(m)); +} + +inline bool Position::move_was_passed_pawn_push(Move m) const { + Color c = opposite_color(this->side_to_move()); + return + this->piece_on(move_to(m)) == pawn_of_color(c) && + this->pawn_is_passed(c, move_to(m)); +} + +inline int Position::rule_50_counter() const { + return rule50; +} + +inline bool Position::opposite_colored_bishops() const { + return + this->bishop_count(WHITE) == 1 && this->bishop_count(BLACK) == 1 && + square_color(this->bishop_list(WHITE, 0)) != + square_color(this->bishop_list(BLACK, 0)); +} + +inline bool Position::has_pawn_on_7th(Color c) const { + return this->pawns(c) & relative_rank_bb(c, RANK_7); +} + + +#endif // !defined(POSITION_H_INCLUDED) diff --git a/src/psqtab.h b/src/psqtab.h new file mode 100644 index 00000000..0607c2d3 --- /dev/null +++ b/src/psqtab.h @@ -0,0 +1,164 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +#if !defined(PSQTAB_H_INCLUDED) +#define PSQTAB_H_INCLUDED + +//// +//// Includes +//// + +#include "position.h" + + +//// +//// Variables +//// + +static const int MgPST[][64] = { + { }, + { // Pawn + 0, 0, 0, 0, 0, 0, 0, 0, + 166, 192, 204, 216, 216, 204, 192, 166, + 166, 192, 210, 242, 242, 210, 192, 166, + 166, 192, 220, 268, 268, 220, 192, 166, + 166, 192, 220, 242, 242, 220, 192, 166, + 166, 192, 210, 216, 216, 210, 192, 166, + 166, 192, 204, 216, 216, 204, 192, 166, + 0, 0, 0, 0, 0, 0, 0, 0 + }, + { // Knight + 704, 730, 756, 768, 768, 756, 730, 704, + 743, 768, 794, 807, 807, 794, 768, 743, + 781, 807, 832, 844, 844, 832, 807, 781, + 807, 832, 857, 870, 870, 857, 832, 807, + 820, 844, 870, 883, 883, 870, 844, 820, + 820, 844, 870, 883, 883, 870, 844, 820, + 781, 807, 832, 844, 844, 832, 807, 781, + 650, 768, 794, 807, 807, 794, 768, 650 + }, + { // Bishop + 786, 786, 792, 797, 797, 792, 786, 786, + 812, 832, 827, 832, 832, 827, 832, 812, + 817, 827, 842, 837, 837, 842, 827, 817, + 822, 832, 837, 852, 852, 837, 832, 822, + 822, 832, 837, 852, 852, 837, 832, 822, + 817, 827, 842, 837, 837, 842, 827, 817, + 812, 832, 827, 832, 832, 827, 832, 812, + 812, 812, 817, 822, 822, 817, 812, 812 + }, + { // Rook + 1267, 1275, 1282, 1289, 1289, 1282, 1275, 1267, + 1267, 1275, 1282, 1289, 1289, 1282, 1275, 1267, + 1267, 1275, 1282, 1289, 1289, 1282, 1275, 1267, + 1267, 1275, 1282, 1289, 1289, 1282, 1275, 1267, + 1267, 1275, 1282, 1289, 1289, 1282, 1275, 1267, + 1267, 1275, 1282, 1289, 1289, 1282, 1275, 1267, + 1267, 1275, 1282, 1289, 1289, 1282, 1275, 1267, + 1267, 1275, 1282, 1289, 1289, 1282, 1275, 1267 + }, + { // Queen + 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, + 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, + 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, + 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, + 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, + 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, + 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560, + 2560, 2560, 2560, 2560, 2560, 2560, 2560, 2560 + }, + { // King + 302, 328, 276, 225, 225, 276, 328, 302, + 276, 302, 251, 200, 200, 251, 302, 276, + 225, 251, 200, 149, 149, 200, 251, 225, + 200, 225, 175, 124, 124, 175, 225, 200, + 175, 200, 149, 98, 98, 149, 200, 175, + 149, 175, 124, 72, 72, 124, 175, 149, + 124, 149, 98, 47, 47, 98, 149, 124, + 98, 124, 72, 21, 21, 72, 124, 98, + } +}; + + +static const int EgPST[][64] = { + { }, + { // Pawn + 0, 0, 0, 0, 0, 0, 0, 0, + 256, 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, + 0, 0, 0, 0, 0, 0, 0, 0 + }, + { // Knight + 730, 756, 781, 794, 794, 781, 756, 730, + 756, 781, 807, 820, 820, 807, 781, 756, + 781, 807, 832, 844, 844, 832, 807, 781, + 794, 820, 844, 857, 857, 844, 820, 794, + 794, 820, 844, 857, 857, 844, 820, 794, + 781, 807, 832, 844, 844, 832, 807, 781, + 756, 781, 807, 820, 820, 807, 781, 756, + 730, 756, 781, 794, 794, 781, 756, 730 + }, + { // Bishop + 786, 802, 809, 817, 817, 809, 802, 786, + 802, 817, 825, 832, 832, 825, 817, 802, + 809, 825, 832, 839, 839, 832, 825, 809, + 817, 832, 839, 847, 847, 839, 832, 817, + 817, 832, 839, 847, 847, 839, 832, 817, + 809, 825, 832, 839, 839, 832, 825, 809, + 802, 817, 825, 832, 832, 825, 817, 802, + 786, 802, 809, 817, 817, 809, 802, 786 + }, + { // Rook + 1282, 1282, 1282, 1282, 1282, 1282, 1282, 1282, + 1282, 1282, 1282, 1282, 1282, 1282, 1282, 1282, + 1282, 1282, 1282, 1282, 1282, 1282, 1282, 1282, + 1282, 1282, 1282, 1282, 1282, 1282, 1282, 1282, + 1282, 1282, 1282, 1282, 1282, 1282, 1282, 1282, + 1282, 1282, 1282, 1282, 1282, 1282, 1282, 1282, + 1282, 1282, 1282, 1282, 1282, 1282, 1282, 1282, + 1282, 1282, 1282, 1282, 1282, 1282, 1282, 1282 + }, + { // Queen + 2499, 2520, 2530, 2540, 2540, 2530, 2520, 2499, + 2520, 2540, 2550, 2560, 2560, 2550, 2540, 2520, + 2530, 2550, 2560, 2570, 2570, 2560, 2550, 2530, + 2540, 2560, 2570, 2580, 2580, 2570, 2560, 2540, + 2540, 2560, 2570, 2580, 2580, 2570, 2560, 2540, + 2530, 2550, 2560, 2570, 2570, 2560, 2550, 2530, + 2520, 2540, 2550, 2560, 2560, 2550, 2540, 2520, + 2499, 2520, 2530, 2540, 2540, 2530, 2520, 2499 + }, + { // King + 16, 78, 108, 139, 139, 108, 78, 16, + 78, 139, 170, 200, 200, 170, 139, 78, + 108, 170, 200, 230, 230, 200, 170, 108, + 139, 200, 230, 261, 261, 230, 200, 139, + 139, 200, 230, 261, 261, 230, 200, 139, + 108, 170, 200, 230, 230, 200, 170, 108, + 78, 139, 170, 200, 200, 170, 139, 78, + 16, 78, 108, 139, 139, 108, 78, 16 + } +}; + + +#endif // !defined(PSQTAB_H_INCLUDED) diff --git a/src/san.cpp b/src/san.cpp new file mode 100644 index 00000000..74fde68e --- /dev/null +++ b/src/san.cpp @@ -0,0 +1,417 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +//// +//// Includes +//// + +#include +#include +#include +#include +#include + +#include "movepick.h" +#include "san.h" + + +//// +//// Local definitions +//// + +namespace { + + /// Types + + enum Ambiguity { + AMBIGUITY_NONE, + AMBIGUITY_FILE, + AMBIGUITY_RANK, + AMBIGUITY_BOTH + }; + + + /// Functions + + Ambiguity move_ambiguity(Position &pos, Move m); + const std::string time_string(int milliseconds); + const std::string score_string(Value v); +} + + +//// +//// Functions +//// + +/// move_to_san() takes a position and a move as input, where it is assumed +/// that the move is a legal move from the position. The return value is +/// a string containing the move in short algebraic notation. + +const std::string move_to_san(Position &pos, Move m) { + std::string str; + + assert(pos.is_ok()); + assert(move_is_ok(m)); + + if(m == MOVE_NONE) { + str = "(none)"; + return str; + } + else if(m == MOVE_NULL) { + str = "(null)"; + return str; + } + else if(move_is_long_castle(m)) + str = "O-O-O"; + else if(move_is_short_castle(m)) + str = "O-O"; + else { + Square from, to; + Piece pc; + + from = move_from(m); + to = move_to(m); + pc = pos.piece_on(move_from(m)); + + str = ""; + + if(type_of_piece(pc) == PAWN) { + if(pos.move_is_capture(m)) + str += file_to_char(square_file(move_from(m))); + } + else { + str += piece_type_to_char(type_of_piece(pc), true); + + Ambiguity amb = move_ambiguity(pos, m); + switch(amb) { + + case AMBIGUITY_NONE: + break; + + case AMBIGUITY_FILE: + str += file_to_char(square_file(from)); + break; + + case AMBIGUITY_RANK: + str += rank_to_char(square_rank(from)); + break; + + case AMBIGUITY_BOTH: + str += square_to_string(from); + break; + + default: + assert(false); + } + } + + if(pos.move_is_capture(m)) + str += "x"; + + str += square_to_string(move_to(m)); + + if(move_promotion(m)) { + str += "="; + str += piece_type_to_char(move_promotion(m), true); + } + } + + // Is the move check? We don't use pos.move_is_check(m) here, because + // Position::move_is_check doesn't detect all checks (not castling moves, + // promotions and en passant captures). + UndoInfo u; + pos.do_move(m, u); + if(pos.is_check()) + str += pos.is_mate()? "#" : "+"; + pos.undo_move(m, u); + + return str; +} + + +/// move_from_san() takes a position and a string as input, and tries to +/// interpret the string as a move in short algebraic notation. On success, +/// the move is returned. On failure (i.e. if the string is unparsable, or +/// if the move is illegal or ambiguous), MOVE_NONE is returned. + +Move move_from_san(Position &pos, const std::string &movestr) { + assert(pos.is_ok()); + + MovePicker mp = MovePicker(pos, false, MOVE_NONE, MOVE_NONE, MOVE_NONE, + MOVE_NONE, OnePly); + + // Castling moves + if(movestr == "O-O-O") { + Move m; + while((m = mp.get_next_move()) != MOVE_NONE) + if(move_is_long_castle(m) && pos.move_is_legal(m)) + return m; + return MOVE_NONE; + } + else if(movestr == "O-O") { + Move m; + while((m = mp.get_next_move()) != MOVE_NONE) + if(move_is_short_castle(m) && pos.move_is_legal(m)) + return m; + return MOVE_NONE; + } + + // Normal moves + const char *cstr = movestr.c_str(); + const char *c; + char *cc; + char str[10]; + int i; + + // Initialize str[] by making a copy of movestr with the characters + // 'x', '=', '+' and '#' removed. + cc = str; + for(i=0, c=cstr; i<10 && *c!='\0' && *c!='\n' && *c!=' '; i++, c++) + if(!strchr("x=+#", *c)) { + *cc = strchr("nrq", *c)? toupper(*c) : *c; + cc++; + } + *cc = '\0'; + + int left = 0, right = strlen(str) - 1; + PieceType pt = NO_PIECE_TYPE, promotion; + Square to; + File fromFile = FILE_NONE; + Rank fromRank = RANK_NONE; + + // Promotion? + if(strchr("BNRQ", str[right])) { + promotion = piece_type_from_char(str[right]); + right--; + } + else + promotion = NO_PIECE_TYPE; + + // Find the moving piece: + if(left < right) { + if(strchr("BNRQK", str[left])) { + pt = piece_type_from_char(str[left]); + left++; + } + else + pt = PAWN; + } + + // Find the to square: + if(left < right) { + if(str[right] < '1' || str[right] > '8' || + str[right-1] < 'a' || str[right-1] > 'h') + return MOVE_NONE; + to = make_square(file_from_char(str[right-1]), rank_from_char(str[right])); + right -= 2; + } + else + return MOVE_NONE; + + // Find the file and/or rank of the from square: + if(left <= right) { + if(strchr("abcdefgh", str[left])) { + fromFile = file_from_char(str[left]); + left++; + } + if(strchr("12345678", str[left])) + fromRank = rank_from_char(str[left]); + } + + // Look for a matching move: + Move m, move = MOVE_NONE; + int matches = 0; + + while((m = mp.get_next_move()) != MOVE_NONE) { + bool match = true; + if(pos.type_of_piece_on(move_from(m)) != pt) + match = false; + else if(move_to(m) != to) + match = false; + else if(move_promotion(m) != promotion) + match = false; + else if(fromFile != FILE_NONE && fromFile != square_file(move_from(m))) + match = false; + else if(fromRank != RANK_NONE && fromRank != square_rank(move_from(m))) + match = false; + if(match) { + move = m; + matches++; + } + } + + if(matches == 1) + return move; + else + return MOVE_NONE; +} + + +/// line_to_san() takes a position and a line (an array of moves representing +/// a sequence of legal moves from the position) as input, and returns a +/// string containing the line in short algebraic notation. If the boolean +/// parameter 'breakLines' is true, line breaks are inserted, with a line +/// length of 80 characters. After a line break, 'startColumn' spaces are +/// inserted at the beginning of the new line. + +const std::string line_to_san(const Position &pos, Move line[], int startColumn, + bool breakLines) { + Position p = Position(pos); + UndoInfo u; + std::stringstream s; + std::string moveStr; + int length, maxLength; + + length = 0; + maxLength = 80 - startColumn; + + for(int i = 0; line[i] != MOVE_NONE; i++) { + moveStr = move_to_san(p, line[i]); + length += moveStr.length() + 1; + if(breakLines && length > maxLength) { + s << "\n"; + for(int j = 0; j < startColumn; j++) + s << " "; + length = moveStr.length() + 1; + } + s << moveStr << " "; + + if(line[i] == MOVE_NULL) + p.do_null_move(u); + else + p.do_move(line[i], u); + } + + return s.str(); +} + + +/// pretty_pv() creates a human-readable string from a position and a PV. +/// It is used to write search information to the log file (which is created +/// when the UCI parameter "Use Search Log" is "true"). + +const std::string pretty_pv(const Position &pos, int time, int depth, + uint64_t nodes, Value score, Move pv[]) { + std::stringstream s; + + // Depth + s << std::setw(2) << std::setfill(' ') << depth << " "; + + // Score + s << std::setw(8) << score_string(score); + + // Time + s << std::setw(8) << std::setfill(' ') << time_string(time) << " "; + + // Nodes + if(nodes < 1000000ULL) + s << std::setw(8) << std::setfill(' ') << nodes << " "; + else if(nodes < 1000000000ULL) + s << std::setw(7) << std::setfill(' ') << nodes/1000ULL << 'k' << " "; + else + s << std::setw(7) << std::setfill(' ') << nodes/1000000ULL << 'M' << " "; + + // PV + s << line_to_san(pos, pv, 30, true); + + return s.str(); +} + + +namespace { + + Ambiguity move_ambiguity(Position &pos, Move m) { + Square from, to; + Piece pc; + + from = move_from(m); + to = move_to(m); + pc = pos.piece_on(from); + + // King moves are never ambiguous, because there is never two kings of + // the same color. + if(type_of_piece(pc) == KING) + return AMBIGUITY_NONE; + + MovePicker mp = MovePicker(pos, false, MOVE_NONE, MOVE_NONE, MOVE_NONE, + MOVE_NONE, OnePly); + Move mv, moveList[8]; + int i, j, n; + + n = 0; + while((mv = mp.get_next_move()) != MOVE_NONE) + if(move_to(mv) == to && pos.piece_on(move_from(mv)) == pc + && pos.move_is_legal(mv)) + moveList[n++] = mv; + if(n == 1) + return AMBIGUITY_NONE; + + j = 0; + for(i = 0; i < n; i++) + if(square_file(move_from(moveList[i])) == square_file(from)) + j++; + if(j == 1) + return AMBIGUITY_FILE; + + j = 0; + for(i = 0; i < n; i++) + if(square_rank(move_from(moveList[i])) == square_rank(from)) + j++; + if(j == 1) + return AMBIGUITY_RANK; + + return AMBIGUITY_BOTH; + } + + + const std::string time_string(int milliseconds) { + std::stringstream s; + + int hours = milliseconds / (1000 * 60 * 60); + int minutes = (milliseconds - hours*1000*60*60) / (60*1000); + int seconds = (milliseconds - hours*1000*60*60 - minutes*60*1000) / 1000; + + if(hours) + s << hours << ':'; + s << std::setw(2) << std::setfill('0') << minutes << ':'; + s << std::setw(2) << std::setfill('0') << seconds; + + return s.str(); + } + + + const std::string score_string(Value v) { + std::stringstream s; + + if(abs(v) >= VALUE_MATE - 200) { + if(v < 0) + s << "-#" << (VALUE_MATE + v) / 2; + else + s << "#" << (VALUE_MATE - v + 1) / 2; + } + else { + float floatScore = float(v) / float(PawnValueMidgame); + if(v >= 0) + s << '+'; + s << std::setprecision(2) << std::fixed << floatScore; + } + return s.str(); + } + +} diff --git a/src/san.h b/src/san.h new file mode 100644 index 00000000..087e52bd --- /dev/null +++ b/src/san.h @@ -0,0 +1,46 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +#if !defined(SAN_H_INCLUDED) +#define SAN_H_INCLUDED + +//// +//// Includes +//// + +#include + +#include "move.h" +#include "position.h" +#include "value.h" + + +//// +//// Prototypes +//// + +extern const std::string move_to_san(Position &pos, Move m); +extern Move move_from_san(Position &pos, const std::string &str); +extern const std::string line_to_san(const Position &pos, Move line[], + int startColumn, bool breakLines); +extern const std::string pretty_pv(const Position &pos, int time, int depth, + uint64_t nodes, Value score, Move pv[]); + + +#endif // !defined(SAN_H_INCLUDED) diff --git a/src/scale.h b/src/scale.h new file mode 100644 index 00000000..5e9af029 --- /dev/null +++ b/src/scale.h @@ -0,0 +1,51 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +#if !defined(SCALE_H_INCLUDED) +#define SCALE_H_INCLUDED + +//// +//// Includes +//// + +#include "value.h" + + +//// +//// Types +//// + +enum ScaleFactor { + SCALE_FACTOR_ZERO = 0, + SCALE_FACTOR_NORMAL = 64, + SCALE_FACTOR_MAX = 128, + SCALE_FACTOR_NONE = 255 +}; + + +//// +//// Inline functions +//// + +inline Value apply_scale_factor(Value v, ScaleFactor f) { + return Value((v * f) / int(SCALE_FACTOR_NORMAL)); +} + + +#endif // !defined(SCALE_H_INCLUDED) diff --git a/src/search.cpp b/src/search.cpp new file mode 100644 index 00000000..2069315d --- /dev/null +++ b/src/search.cpp @@ -0,0 +1,2436 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +//// +//// Includes +//// + +#include +#include +#include +#include +#include + +#include "book.h" +#include "evaluate.h" +#include "history.h" +#include "misc.h" +#include "movepick.h" +#include "san.h" +#include "search.h" +#include "thread.h" +#include "tt.h" +#include "ucioption.h" + + +//// +//// Local definitions +//// + +namespace { + + /// Types + + // The RootMove class is used for moves at the root at 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). + + class RootMove { + + public: + RootMove(); + Move move; + Value score; + int64_t nodes, cumulativeNodes; + Move pv[PLY_MAX_PLUS_2]; + }; + + + // The RootMoveList class is essentially an array of RootMove objects, with + // a handful of methods for accessing the data in the individual moves. + + class RootMoveList { + + public: + RootMoveList(Position &pos, Move searchMoves[]); + Move get_move(int moveNum) const; + Value get_move_score(int moveNum) const; + void set_move_score(int moveNum, Value score); + void set_move_nodes(int moveNum, int64_t nodes); + void set_move_pv(int moveNum, const Move pv[]); + Move get_move_pv(int moveNum, int i) const; + int64_t get_move_cumulative_nodes(int moveNum); + int move_count() const; + Move scan_for_easy_move() const; + void sort(); + void sort_multipv(int n); + + private: + static int compare_root_moves(const RootMove &rm1, const RootMove &rm2); + static const int MaxRootMoves = 500; + RootMove moves[MaxRootMoves]; + int count; + }; + + + /// Constants and variables + + // Minimum number of full depth (i.e. non-reduced) moves at PV and non-PV + // nodes: + int LMRPVMoves = 15; + int LMRNonPVMoves = 4; + + // Depth limit for use of dynamic threat detection: + Depth ThreatDepth = 5*OnePly; + + // Depth limit for selective search: + Depth SelectiveDepth = 7*OnePly; + + // Use internal iterative deepening? + const bool UseIIDAtPVNodes = true; + const bool UseIIDAtNonPVNodes = false; + + // Internal iterative deepening margin. At Non-PV moves, when + // UseIIDAtNonPVNodes is true, we do an internal iterative deepening search + // when the static evaluation is at most IIDMargin below beta. + const Value IIDMargin = Value(0x100); + + // Use easy moves? + const bool UseEasyMove = true; + + // Easy move margin. An easy move candidate must be at least this much + // better than the second best move. + const Value EasyMoveMargin = Value(0x200); + + // Problem margin. If the score of the first move at iteration N+1 has + // dropped by more than this since iteration N, the boolean variable + // "Problem" is set to true, which will make the program spend some extra + // time looking for a better move. + const Value ProblemMargin = Value(0x28); + + // No problem margin. If the boolean "Problem" is true, and a new move + // is found at the root which is less than NoProblemMargin worse than the + // best move from the previous iteration, Problem is set back to false. + const Value NoProblemMargin = Value(0x14); + + // Null move margin. A null move search will not be done if the approximate + // evaluation of the position is more than NullMoveMargin below beta. + const Value NullMoveMargin = Value(0x300); + + // Pruning criterions. See the code and comments in ok_to_prune() to + // understand their precise meaning. + const bool PruneEscapeMoves = false; + const bool PruneDefendingMoves = false; + const bool PruneBlockingMoves = false; + + // Use futility pruning? + bool UseQSearchFutilityPruning = true; + bool UseFutilityPruning = true; + + // Margins for futility pruning in the quiescence search, at frontier + // nodes, and at pre-frontier nodes: + Value FutilityMargin0 = Value(0x80); + Value FutilityMargin1 = Value(0x100); + Value FutilityMargin2 = Value(0x300); + + // Razoring + Depth RazorDepth = 4*OnePly; + Value RazorMargin = Value(0x300); + + // Extensions. Array index 0 is used at non-PV nodes, index 1 at PV nodes. + Depth CheckExtension[2] = {OnePly, OnePly}; + Depth SingleReplyExtension[2] = {OnePly / 2, OnePly / 2}; + Depth PawnPushTo7thExtension[2] = {OnePly / 2, OnePly / 2}; + Depth PassedPawnExtension[2] = {Depth(0), Depth(0)}; + Depth PawnEndgameExtension[2] = {OnePly, OnePly}; + Depth MateThreatExtension[2] = {Depth(0), Depth(0)}; + + // Search depth at iteration 1: + const Depth InitialDepth = OnePly /*+ OnePly/2*/; + + // Node counters + int NodesSincePoll; + int NodesBetweenPolls = 30000; + + // Iteration counter: + int Iteration; + + // Scores and number of times the best move changed for each iteration: + Value ValueByIteration[PLY_MAX_PLUS_2]; + int BestMoveChangesByIteration[PLY_MAX_PLUS_2]; + + // MultiPV mode: + int MultiPV = 1; + + // Time managment variables + int SearchStartTime; + int MaxNodes, MaxDepth; + int MaxSearchTime, AbsoluteMaxSearchTime, ExtraSearchTime; + Move BestRootMove, PonderMove, EasyMove; + int RootMoveNumber; + bool InfiniteSearch; + bool PonderSearch; + bool StopOnPonderhit; + bool AbortSearch; + bool Quit; + bool FailHigh; + bool Problem; + bool PonderingEnabled; + int ExactMaxTime; + + // Show current line? + bool ShowCurrentLine = false; + + // Log file + bool UseLogFile = false; + std::ofstream LogFile; + + // MP related variables + Depth MinimumSplitDepth = 4*OnePly; + int MaxThreadsPerSplitPoint = 4; + Thread Threads[THREAD_MAX]; + Lock MPLock; + bool AllThreadsShouldExit = false; + const int MaxActiveSplitPoints = 8; + SplitPoint SplitPointStack[THREAD_MAX][MaxActiveSplitPoints]; + bool Idle = true; + +#if !defined(_MSC_VER) + pthread_cond_t WaitCond; + pthread_mutex_t WaitLock; +#else + HANDLE SitIdleEvent[THREAD_MAX]; +#endif + + + /// Functions + + void id_loop(const Position &pos, Move searchMoves[]); + Value root_search(Position &pos, SearchStack ss[], RootMoveList &rml); + Value search_pv(Position &pos, SearchStack ss[], Value alpha, Value beta, + Depth depth, int ply, int threadID); + Value search(Position &pos, SearchStack ss[], Value beta, + Depth depth, int ply, bool allowNullmove, int threadID); + Value qsearch(Position &pos, SearchStack ss[], Value alpha, Value beta, + Depth depth, int ply, int threadID); + void sp_search(SplitPoint *sp, int threadID); + void sp_search_pv(SplitPoint *sp, int threadID); + void init_search_stack(SearchStack ss[]); + void init_node(const Position &pos, SearchStack ss[], int ply, int threadID); + void update_pv(SearchStack ss[], int ply); + void sp_update_pv(SearchStack *pss, SearchStack ss[], int ply); + bool connected_moves(const Position &pos, Move m1, Move m2); + Depth extension(const Position &pos, Move m, bool pvNode, bool check, + bool singleReply, bool mateThreat); + bool ok_to_do_nullmove(const Position &pos); + bool ok_to_prune(const Position &pos, Move m, Move threat, Depth d); + + bool fail_high_ply_1(); + int current_search_time(); + int nps(); + void poll(); + void ponderhit(); + void print_current_line(SearchStack ss[], int ply, int threadID); + void wait_for_stop_or_ponderhit(); + + void idle_loop(int threadID, SplitPoint *waitSp); + void init_split_point_stack(); + void destroy_split_point_stack(); + bool thread_should_stop(int threadID); + bool thread_is_available(int slave, int master); + bool idle_thread_exists(int master); + bool split(const Position &pos, SearchStack *ss, int ply, + Value *alpha, Value *beta, Value *bestValue, Depth depth, + int *moves, MovePicker *mp, Bitboard dcCandidates, int master, + bool pvNode); + void wake_sleeping_threads(); + +#if !defined(_MSC_VER) + void *init_thread(void *threadID); +#else + DWORD WINAPI init_thread(LPVOID threadID); +#endif + +} + + +//// +//// Global variables +//// + +// The main transposition table +TranspositionTable TT = TranspositionTable(TTDefaultSize); + + +// Number of active threads: +int ActiveThreads = 1; + +// Locks. In principle, there is no need for IOLock to be a global variable, +// but it could turn out to be useful for debugging. +Lock IOLock; + +History H; // Should be made local? + + +//// +//// Functions +//// + +/// think() is the external interface to Glaurung's search, and is called when +/// the program receives the UCI 'go' command. It initializes various +/// search-related global variables, and calls root_search() + +void think(const Position &pos, bool infinite, bool ponder, int time, + int increment, int movesToGo, int maxDepth, int maxNodes, + int maxTime, Move searchMoves[]) { + + // Look for a book move: + if(!infinite && !ponder && get_option_value_bool("OwnBook")) { + Move bookMove; + if(get_option_value_string("Book File") != OpeningBook.file_name()) { + OpeningBook.close(); + OpeningBook.open("book.bin"); + } + bookMove = OpeningBook.get_move(pos); + if(bookMove != MOVE_NONE) { + std::cout << "bestmove " << bookMove << std::endl; + return; + } + } + + // Initialize global search variables: + Idle = false; + SearchStartTime = get_system_time(); + BestRootMove = MOVE_NONE; + PonderMove = MOVE_NONE; + EasyMove = MOVE_NONE; + for(int i = 0; i < THREAD_MAX; i++) { + Threads[i].nodes = 0ULL; + Threads[i].failHighPly1 = false; + } + NodesSincePoll = 0; + InfiniteSearch = infinite; + PonderSearch = ponder; + StopOnPonderhit = false; + AbortSearch = false; + Quit = false; + FailHigh = false; + Problem = false; + ExactMaxTime = maxTime; + + // Read UCI option values: + TT.set_size(get_option_value_int("Hash")); + if(button_was_pressed("Clear Hash")) + TT.clear(); + PonderingEnabled = get_option_value_int("Ponder"); + MultiPV = get_option_value_int("MultiPV"); + + CheckExtension[1] = Depth(get_option_value_int("Check Extension (PV nodes)")); + CheckExtension[0] = + Depth(get_option_value_int("Check Extension (non-PV nodes)")); + SingleReplyExtension[1] = Depth(get_option_value_int("Single Reply Extension (PV nodes)")); + SingleReplyExtension[0] = + Depth(get_option_value_int("Single Reply Extension (non-PV nodes)")); + PawnPushTo7thExtension[1] = + Depth(get_option_value_int("Pawn Push to 7th Extension (PV nodes)")); + PawnPushTo7thExtension[0] = + Depth(get_option_value_int("Pawn Push to 7th Extension (non-PV nodes)")); + PassedPawnExtension[1] = + Depth(get_option_value_int("Passed Pawn Extension (PV nodes)")); + PassedPawnExtension[0] = + Depth(get_option_value_int("Passed Pawn Extension (non-PV nodes)")); + PawnEndgameExtension[1] = + Depth(get_option_value_int("Pawn Endgame Extension (PV nodes)")); + PawnEndgameExtension[0] = + Depth(get_option_value_int("Pawn Endgame Extension (non-PV nodes)")); + MateThreatExtension[1] = + Depth(get_option_value_int("Mate Threat Extension (PV nodes)")); + MateThreatExtension[0] = + Depth(get_option_value_int("Mate Threat Extension (non-PV nodes)")); + + LMRPVMoves = get_option_value_int("Full Depth Moves (PV nodes)") + 1; + LMRNonPVMoves = get_option_value_int("Full Depth Moves (non-PV nodes)") + 1; + ThreatDepth = get_option_value_int("Threat Depth") * OnePly; + SelectiveDepth = get_option_value_int("Selective Plies") * OnePly; + + Chess960 = get_option_value_bool("UCI_Chess960"); + ShowCurrentLine = get_option_value_bool("UCI_ShowCurrLine"); + UseLogFile = get_option_value_bool("Use Search Log"); + if(UseLogFile) + LogFile.open(get_option_value_string("Search Log Filename").c_str(), + std::ios::out | std::ios::app); + + UseQSearchFutilityPruning = + get_option_value_bool("Futility Pruning (Quiescence Search)"); + UseFutilityPruning = + get_option_value_bool("Futility Pruning (Main Search)"); + + FutilityMargin0 = + value_from_centipawns(get_option_value_int("Futility Margin 0")); + FutilityMargin1 = + value_from_centipawns(get_option_value_int("Futility Margin 1")); + FutilityMargin2 = + value_from_centipawns(get_option_value_int("Futility Margin 2")); + + RazorDepth = (get_option_value_int("Maximum Razoring Depth") + 1) * OnePly; + RazorMargin = value_from_centipawns(get_option_value_int("Razoring Margin")); + + MinimumSplitDepth = get_option_value_int("Minimum Split Depth") * OnePly; + MaxThreadsPerSplitPoint = + get_option_value_int("Maximum Number of Threads per Split Point"); + + read_weights(pos.side_to_move()); + + int newActiveThreads = get_option_value_int("Threads"); + if(newActiveThreads != ActiveThreads) { + ActiveThreads = newActiveThreads; + init_eval(ActiveThreads); + } + + // Write information to search log file: + if(UseLogFile) { + LogFile << "Searching: " << pos.to_fen() << '\n'; + LogFile << "infinite: " << infinite << " ponder: " << ponder + << " time: " << time << " increment: " << increment + << " moves to go: " << movesToGo << '\n'; + } + + // Wake up sleeping threads: + wake_sleeping_threads(); + + for(int i = 1; i < ActiveThreads; i++) + assert(thread_is_available(i, 0)); + + // Set thinking time: + if(!movesToGo) { // Sudden death time control + if(increment) { + MaxSearchTime = time / 30 + increment; + AbsoluteMaxSearchTime = Max(time / 4, increment - 100); + } + else { // Blitz game without increment + MaxSearchTime = time / 40; + AbsoluteMaxSearchTime = time / 8; + } + } + else { // (x moves) / (y minutes) + if(movesToGo == 1) { + MaxSearchTime = time / 2; + AbsoluteMaxSearchTime = Min(time / 2, time - 500); + } + else { + MaxSearchTime = time / Min(movesToGo, 20); + AbsoluteMaxSearchTime = Min((4 * time) / movesToGo, time / 3); + } + } + if(PonderingEnabled) { + MaxSearchTime += MaxSearchTime / 4; + MaxSearchTime = Min(MaxSearchTime, AbsoluteMaxSearchTime); + } + + // Fixed depth or fixed number of nodes? + MaxDepth = maxDepth; + if(MaxDepth) + InfiniteSearch = true; // HACK + + MaxNodes = maxNodes; + if(MaxNodes) { + NodesBetweenPolls = Min(MaxNodes, 30000); + InfiniteSearch = true; // HACK + } + else + NodesBetweenPolls = 30000; + + // We're ready to start thinking. Call the iterative deepening loop + // function: + id_loop(pos, searchMoves); + + if(UseLogFile) + LogFile.close(); + + if(Quit) { + OpeningBook.close(); + stop_threads(); + quit_eval(); + exit(0); + } + + Idle = true; +} + + +/// init_threads() is called during startup. It launches all helper threads, +/// and initializes the split point stack and the global locks and condition +/// objects. + +void init_threads() { + volatile int i; +#if !defined(_MSC_VER) + pthread_t pthread[1]; +#endif + + for(i = 0; i < THREAD_MAX; i++) + Threads[i].activeSplitPoints = 0; + + // Initialize global locks: + lock_init(&MPLock, NULL); + lock_init(&IOLock, NULL); + + init_split_point_stack(); + +#if !defined(_MSC_VER) + pthread_mutex_init(&WaitLock, NULL); + pthread_cond_init(&WaitCond, NULL); +#else + for(i = 0; i < THREAD_MAX; i++) + SitIdleEvent[i] = CreateEvent(0, FALSE, FALSE, 0); +#endif + + // All threads except the main thread should be initialized to idle state: + for(i = 1; i < THREAD_MAX; i++) { + Threads[i].stop = false; + Threads[i].workIsWaiting = false; + Threads[i].idle = true; + Threads[i].running = false; + } + + // Launch the helper threads: + for(i = 1; i < THREAD_MAX; i++) { +#if !defined(_MSC_VER) + pthread_create(pthread, NULL, init_thread, (void*)(&i)); +#else + { + DWORD iID[1]; + CreateThread(NULL, 0, init_thread, (LPVOID)(&i), 0, iID); + } +#endif + + // Wait until the thread has finished launching: + while(!Threads[i].running); + } +} + + +/// stop_threads() is called when the program exits. It makes all the +/// helper threads exit cleanly. + +void stop_threads() { + ActiveThreads = THREAD_MAX; // HACK + Idle = false; // HACK + wake_sleeping_threads(); + AllThreadsShouldExit = true; + for(int i = 1; i < THREAD_MAX; i++) { + Threads[i].stop = true; + while(Threads[i].running); + } + destroy_split_point_stack(); +} + + +/// nodes_searched() returns the total number of nodes searched so far in +/// the current search. + +int64_t nodes_searched() { + int64_t result = 0ULL; + for(int i = 0; i < ActiveThreads; i++) + result += Threads[i].nodes; + return result; +} + + +namespace { + + // id_loop() is the main iterative deepening loop. It calls root_search + // repeatedly with increasing depth until the allocated thinking time has + // been consumed, the user stops the search, or the maximum search depth is + // reached. + + void id_loop(const Position &pos, Move searchMoves[]) { + Position p(pos); + RootMoveList rml(p, searchMoves); + SearchStack ss[PLY_MAX_PLUS_2]; + + // Initialize + TT.new_search(); + H.clear(); + init_search_stack(ss); + + ValueByIteration[0] = Value(0); + ValueByIteration[1] = rml.get_move_score(0); + Iteration = 1; + + EasyMove = rml.scan_for_easy_move(); + + // Iterative deepening loop + while(!AbortSearch && Iteration < PLY_MAX) { + + // Initialize iteration + rml.sort(); + Iteration++; + BestMoveChangesByIteration[Iteration] = 0; + if(Iteration <= 5) + ExtraSearchTime = 0; + + std::cout << "info depth " << Iteration << std::endl; + + // Search to the current depth + ValueByIteration[Iteration] = root_search(p, ss, rml); + + // Erase the easy move if it differs from the new best move + if(ss[0].pv[0] != EasyMove) + EasyMove = MOVE_NONE; + + Problem = false; + + if(!InfiniteSearch) { + // Time to stop? + bool stopSearch = false; + + // Stop search early if there is only a single legal move: + if(Iteration >= 6 && rml.move_count() == 1) + stopSearch = true; + + // Stop search early when the last two iterations returned a mate + // score: + if(Iteration >= 6 + && abs(ValueByIteration[Iteration]) >= abs(VALUE_MATE) - 100 + && abs(ValueByIteration[Iteration-1]) >= abs(VALUE_MATE) - 100) + stopSearch = true; + + // Stop search early if one move seems to be much better than the + // rest: + int64_t nodes = nodes_searched(); + if(Iteration >= 8 && EasyMove == ss[0].pv[0] && + ((rml.get_move_cumulative_nodes(0) > (nodes * 85) / 100 && + current_search_time() > MaxSearchTime / 16) || + (rml.get_move_cumulative_nodes(0) > (nodes * 98) / 100 && + current_search_time() > MaxSearchTime / 32))) + stopSearch = true; + + // Add some extra time if the best move has changed during the last + // two iterations: + if(Iteration > 5 && Iteration <= 50) + ExtraSearchTime = + BestMoveChangesByIteration[Iteration] * (MaxSearchTime / 2) + + BestMoveChangesByIteration[Iteration-1] * (MaxSearchTime / 3); + + // Stop search if most of MaxSearchTime is consumed at the end of the + // iteration. We probably don't have enough time to search the first + // move at the next iteration anyway. + if(current_search_time() > ((MaxSearchTime + ExtraSearchTime)*80) / 128) + stopSearch = true; + + if(stopSearch) { + if(!PonderSearch) + break; + else + StopOnPonderhit = true; + } + } + + // Write PV to transposition table, in case the relevant entries have + // been overwritten during the search: + TT.insert_pv(p, ss[0].pv); + + if(MaxDepth && Iteration >= MaxDepth) + break; + } + + rml.sort(); + + // If we are pondering, we shouldn't print the best move before we + // are told to do so + if(PonderSearch) + wait_for_stop_or_ponderhit(); + else + // Print final search statistics + std::cout << "info nodes " << nodes_searched() << " nps " << nps() + << " time " << current_search_time() + << " hashfull " << TT.full() << std::endl; + + // Print the best move and the ponder move to the standard output: + std::cout << "bestmove " << ss[0].pv[0]; + if(ss[0].pv[1] != MOVE_NONE) + std::cout << " ponder " << ss[0].pv[1]; + std::cout << std::endl; + + if(UseLogFile) { + UndoInfo u; + LogFile << "Nodes: " << nodes_searched() << '\n'; + LogFile << "Nodes/second: " << nps() << '\n'; + LogFile << "Best move: " << move_to_san(p, ss[0].pv[0]) << '\n'; + p.do_move(ss[0].pv[0], u); + LogFile << "Ponder move: " << move_to_san(p, ss[0].pv[1]) << '\n'; + LogFile << std::endl; + } + } + + + // root_search() is the function which searches the root node. It is + // similar to search_pv except that it uses a different move ordering + // scheme (perhaps we should try to use this at internal PV nodes, too?) + // and prints some information to the standard output. + + Value root_search(Position &pos, SearchStack ss[], RootMoveList &rml) { + Value alpha = -VALUE_INFINITE, beta = VALUE_INFINITE, value; + Bitboard dcCandidates = pos.discovered_check_candidates(pos.side_to_move()); + + // Loop through all the moves in the root move list: + for(int i = 0; i < rml.move_count() && !AbortSearch; i++) { + int64_t nodes; + Move move; + UndoInfo u; + Depth ext, newDepth; + + RootMoveNumber = i + 1; + FailHigh = false; + + // Remember the node count before the move is searched. The node counts + // are used to sort the root moves at the next iteration. + nodes = nodes_searched(); + + // Pick the next root move, and print the move and the move number to + // the standard output: + move = ss[0].currentMove = rml.get_move(i); + if(current_search_time() >= 1000) + std::cout << "info currmove " << move + << " currmovenumber " << i + 1 << std::endl; + + // Decide search depth for this move: + ext = extension(pos, move, true, pos.move_is_check(move), false, false); + newDepth = (Iteration-2)*OnePly + ext + InitialDepth; + + // Make the move, and search it. + pos.do_move(move, u, dcCandidates); + + if(i < MultiPV) { + value = -search_pv(pos, ss, -beta, VALUE_INFINITE, newDepth, 1, 0); + // If the value has dropped a lot compared to the last iteration, + // set the boolean variable Problem to true. This variable is used + // for time managment: When Problem is true, we try to complete the + // current iteration before playing a move. + Problem = (Iteration >= 2 && + value <= ValueByIteration[Iteration-1] - ProblemMargin); + if(Problem && StopOnPonderhit) + StopOnPonderhit = false; + } + else { + value = -search(pos, ss, -alpha, newDepth, 1, true, 0); + if(value > alpha) { + // Fail high! Set the boolean variable FailHigh to true, and + // re-search the move with a big window. The variable FailHigh is + // used for time managment: We try to avoid aborting the search + // prematurely during a fail high research. + FailHigh = true; + value = -search_pv(pos, ss, -beta, -alpha, newDepth, 1, 0); + } + } + + pos.undo_move(move, u); + + // Finished searching the move. If AbortSearch is true, the search + // was aborted because the user interrupted the search or because we + // ran out of time. In this case, the return value of the search cannot + // be trusted, and we break out of the loop without updating the best + // move and/or PV: + if(AbortSearch) + break; + + // Remember the node count for this move. The node counts are used to + // sort the root moves at the next iteration. + rml.set_move_nodes(i, nodes_searched() - nodes); + + assert(value >= -VALUE_INFINITE && value <= VALUE_INFINITE); + + if(value <= alpha && i >= MultiPV) + rml.set_move_score(i, -VALUE_INFINITE); + else { + // New best move! + + // Update PV: + rml.set_move_score(i, value); + update_pv(ss, 0); + rml.set_move_pv(i, ss[0].pv); + + if(MultiPV == 1) { + // We record how often the best move has been changed in each + // iteration. This information is used for time managment: When + // the best move changes frequently, we allocate some more time. + if(i > 0) + BestMoveChangesByIteration[Iteration]++; + + // Print search information to the standard output: + std::cout << "info depth " << Iteration + << " score " << value_to_string(value) + << " time " << current_search_time() + << " nodes " << nodes_searched() + << " nps " << nps() + << " pv "; + for(int j = 0; ss[0].pv[j] != MOVE_NONE && j < PLY_MAX; j++) + std::cout << ss[0].pv[j] << " "; + std::cout << std::endl; + + if(UseLogFile) + LogFile << pretty_pv(pos, current_search_time(), Iteration, + nodes_searched(), value, ss[0].pv) + << std::endl; + + alpha = value; + + // Reset the global variable Problem to false if the value isn't too + // far below the final value from the last iteration. + if(value > ValueByIteration[Iteration - 1] - NoProblemMargin) + Problem = false; + } + else { // MultiPV > 1 + rml.sort_multipv(i); + for(int j = 0; j < Min(MultiPV, rml.move_count()); j++) { + int k; + std::cout << "info multipv " << j + 1 + << " score " << value_to_string(rml.get_move_score(j)) + << " depth " << ((j <= i)? Iteration : Iteration - 1) + << " time " << current_search_time() + << " nodes " << nodes_searched() + << " nps " << nps() + << " pv "; + for(k = 0; rml.get_move_pv(j, k) != MOVE_NONE && k < PLY_MAX; k++) + std::cout << rml.get_move_pv(j, k) << " "; + std::cout << std::endl; + } + alpha = rml.get_move_score(Min(i, MultiPV-1)); + } + } + } + return alpha; + } + + + // search_pv() is the main search function for PV nodes. + + Value search_pv(Position &pos, SearchStack ss[], Value alpha, Value beta, + Depth depth, int ply, int threadID) { + assert(alpha >= -VALUE_INFINITE && alpha <= VALUE_INFINITE); + assert(beta > alpha && beta <= VALUE_INFINITE); + assert(ply >= 0 && ply < PLY_MAX); + assert(threadID >= 0 && threadID < ActiveThreads); + + EvalInfo ei; + + // Initialize, and make an early exit in case of an aborted search, + // an instant draw, maximum ply reached, etc. + Value oldAlpha = alpha; + + if(AbortSearch || thread_should_stop(threadID)) + return Value(0); + + if(depth < OnePly) + return qsearch(pos, ss, alpha, beta, Depth(0), ply, threadID); + + init_node(pos, ss, ply, threadID); + + if(pos.is_draw()) + return VALUE_DRAW; + + if(ply >= PLY_MAX - 1) + return evaluate(pos, ei, threadID); + + // Mate distance pruning + alpha = Max(value_mated_in(ply), alpha); + beta = Min(value_mate_in(ply+1), beta); + if(alpha >= beta) + return alpha; + + // Transposition table lookup. At PV nodes, we don't use the TT for + // pruning, but only for move ordering. + Value ttValue; + Depth ttDepth; + Move ttMove = MOVE_NONE; + ValueType ttValueType; + + TT.retrieve(pos, &ttValue, &ttDepth, &ttMove, &ttValueType); + + // Internal iterative deepening. + if(UseIIDAtPVNodes && ttMove == MOVE_NONE && depth >= 5*OnePly) { + search_pv(pos, ss, alpha, beta, depth-2*OnePly, ply, threadID); + ttMove = ss[ply].pv[ply]; + } + + // Initialize a MovePicker object for the current position, and prepare + // to search all moves: + MovePicker mp = MovePicker(pos, true, ttMove, ss[ply].mateKiller, + ss[ply].killer1, ss[ply].killer2, depth); + Move move, movesSearched[256]; + int moveCount = 0; + Value value, bestValue = -VALUE_INFINITE; + Bitboard dcCandidates = mp.discovered_check_candidates(); + bool mateThreat = + MateThreatExtension[1] > Depth(0) + && pos.has_mate_threat(opposite_color(pos.side_to_move())); + + // Loop through all legal moves until no moves remain or a beta cutoff + // occurs. + while(alpha < beta && !thread_should_stop(threadID) + && (move = mp.get_next_move()) != MOVE_NONE) { + UndoInfo u; + Depth ext, newDepth; + bool singleReply = (pos.is_check() && mp.number_of_moves() == 1); + bool moveIsCheck = pos.move_is_check(move, dcCandidates); + bool moveIsCapture = pos.move_is_capture(move); + bool moveIsPassedPawnPush = pos.move_is_passed_pawn_push(move); + + assert(move_is_ok(move)); + movesSearched[moveCount++] = ss[ply].currentMove = move; + + ss[ply].currentMoveCaptureValue = move_is_ep(move)? + PawnValueMidgame : pos.midgame_value_of_piece_on(move_to(move)); + + // Decide the new search depth. + ext = extension(pos, move, true, moveIsCheck, singleReply, mateThreat); + newDepth = depth - OnePly + ext; + + // Make and search the move. + pos.do_move(move, u, dcCandidates); + + if(moveCount == 1) + value = -search_pv(pos, ss, -beta, -alpha, newDepth, ply+1, threadID); + else { + if(depth >= 2*OnePly && ext == Depth(0) && moveCount >= LMRPVMoves + && !moveIsCapture && !move_promotion(move) + && !moveIsPassedPawnPush && !move_is_castle(move) + && move != ss[ply].killer1 && move != ss[ply].killer2) { + ss[ply].reduction = OnePly; + value = -search(pos, ss, -alpha, newDepth-OnePly, ply+1, true, + threadID); + } + else value = alpha + 1; + if(value > alpha) { + ss[ply].reduction = Depth(0); + value = -search(pos, ss, -alpha, newDepth, ply+1, true, threadID); + if(value > alpha && value < beta) { + if(ply == 1 && RootMoveNumber == 1) + // When the search fails high at ply 1 while searching the first + // move at the root, set the flag failHighPly1. This is used for + // time managment: We don't want to stop the search early in + // such cases, because resolving the fail high at ply 1 could + // result in a big drop in score at the root. + Threads[threadID].failHighPly1 = true; + value = -search_pv(pos, ss, -beta, -alpha, newDepth, ply+1, + threadID); + Threads[threadID].failHighPly1 = false; + } + } + } + pos.undo_move(move, u); + + assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); + + // New best move? + if(value > bestValue) { + bestValue = value; + if(value > alpha) { + alpha = value; + update_pv(ss, ply); + if(value == value_mate_in(ply + 1)) + ss[ply].mateKiller = move; + } + // If we are at ply 1, and we are searching the first root move at + // ply 0, set the 'Problem' variable if the score has dropped a lot + // (from the computer's point of view) since the previous iteration: + if(Iteration >= 2 && + -value <= ValueByIteration[Iteration-1] - ProblemMargin) + Problem = true; + } + + // Split? + if(ActiveThreads > 1 && bestValue < beta && depth >= MinimumSplitDepth + && Iteration <= 99 && idle_thread_exists(threadID) + && !AbortSearch && !thread_should_stop(threadID) + && split(pos, ss, ply, &alpha, &beta, &bestValue, depth, + &moveCount, &mp, dcCandidates, threadID, true)) + break; + } + + // All legal moves have been searched. A special case: If there were + // no legal moves, it must be mate or stalemate: + if(moveCount == 0) { + if(pos.is_check()) + return value_mated_in(ply); + else + return VALUE_DRAW; + } + + // If the search is not aborted, update the transposition table, + // history counters, and killer moves. This code is somewhat messy, + // and definitely needs to be cleaned up. FIXME + if(!AbortSearch && !thread_should_stop(threadID)) { + if(bestValue <= oldAlpha) + TT.store(pos, value_to_tt(bestValue, ply), depth, MOVE_NONE, + VALUE_TYPE_UPPER); + else if(bestValue >= beta) { + Move m = ss[ply].pv[ply]; + if(pos.square_is_empty(move_to(m)) && !move_promotion(m) && + !move_is_ep(m)) { + for(int i = 0; i < moveCount - 1; i++) + if(pos.square_is_empty(move_to(movesSearched[i])) + && !move_promotion(movesSearched[i]) + && !move_is_ep(movesSearched[i])) + H.failure(pos.piece_on(move_from(movesSearched[i])), + movesSearched[i]); + + H.success(pos.piece_on(move_from(m)), m, depth); + + if(m != ss[ply].killer1) { + ss[ply].killer2 = ss[ply].killer1; + ss[ply].killer1 = m; + } + } + TT.store(pos, value_to_tt(bestValue, ply), depth, m, VALUE_TYPE_LOWER); + } + else + TT.store(pos, value_to_tt(bestValue, ply), depth, ss[ply].pv[ply], + VALUE_TYPE_EXACT); + } + + return bestValue; + } + + + // search() is the search function for zero-width nodes. + + Value search(Position &pos, SearchStack ss[], Value beta, Depth depth, + int ply, bool allowNullmove, int threadID) { + assert(beta >= -VALUE_INFINITE && beta <= VALUE_INFINITE); + assert(ply >= 0 && ply < PLY_MAX); + assert(threadID >= 0 && threadID < ActiveThreads); + + EvalInfo ei; + + // Initialize, and make an early exit in case of an aborted search, + // an instant draw, maximum ply reached, etc. + if(AbortSearch || thread_should_stop(threadID)) + return Value(0); + + if(depth < OnePly) + return qsearch(pos, ss, beta-1, beta, Depth(0), ply, threadID); + + init_node(pos, ss, ply, threadID); + + if(pos.is_draw()) + return VALUE_DRAW; + + if(ply >= PLY_MAX - 1) + return evaluate(pos, ei, threadID); + + // Mate distance pruning + if(value_mated_in(ply) >= beta) + return beta; + if(value_mate_in(ply+1) < beta) + return beta-1; + + // Transposition table lookup + bool ttFound; + Value ttValue; + Depth ttDepth; + Move ttMove = MOVE_NONE; + ValueType ttValueType; + + ttFound = TT.retrieve(pos, &ttValue, &ttDepth, &ttMove, &ttValueType); + if(ttFound) { + ttValue = value_from_tt(ttValue, ply); + if(ttDepth >= depth + || ttValue >= Max(value_mate_in(100), beta) + || ttValue < Min(value_mated_in(100), beta)) { + if((is_lower_bound(ttValueType) && ttValue >= beta) || + (is_upper_bound(ttValueType) && ttValue < beta)) { + ss[ply].currentMove = ttMove; + return ttValue; + } + } + } + + Value approximateEval = quick_evaluate(pos); + bool mateThreat = false; + + // Null move search + if(!pos.is_check() && allowNullmove && ok_to_do_nullmove(pos) + && approximateEval >= beta - NullMoveMargin) { + UndoInfo u; + Value nullValue; + + ss[ply].currentMove = MOVE_NULL; + pos.do_null_move(u); + nullValue = -search(pos, ss, -(beta-1), depth-4*OnePly, ply+1, false, + threadID); + pos.undo_null_move(u); + + if(nullValue >= beta) { + if(depth >= 6 * OnePly) { // Do zugzwang verification search + Value v = search(pos, ss, beta, depth-5*OnePly, ply, false, threadID); + if(v >= beta) + return beta; + } + else + return beta; + } + else { + // The null move failed low, which means that we may be faced with + // some kind of threat. If the previous move was reduced, check if + // the move that refuted the null move was somehow connected to the + // move which was reduced. If a connection is found, return a fail + // low score (which will cause the reduced move to fail high in the + // parent node, which will trigger a re-search with full depth). + if(nullValue == value_mated_in(ply+2)) + mateThreat = true; + ss[ply].threatMove = ss[ply+1].currentMove; + if(depth < ThreatDepth && ss[ply-1].reduction && + connected_moves(pos, ss[ply-1].currentMove, ss[ply].threatMove)) + return beta - 1; + } + } + // Razoring: + else if(depth < RazorDepth && approximateEval < beta - RazorMargin && + evaluate(pos, ei, threadID) < beta - RazorMargin) { + Value v = qsearch(pos, ss, beta-1, beta, Depth(0), ply, threadID); + if(v < beta) + return v; + } + + // Internal iterative deepening + if(UseIIDAtNonPVNodes && ttMove == MOVE_NONE && depth >= 8*OnePly && + evaluate(pos, ei, threadID) >= beta - IIDMargin) { + search(pos, ss, beta, Min(depth/2, depth-2*OnePly), ply, false, threadID); + ttMove = ss[ply].pv[ply]; + } + + // Initialize a MovePicker object for the current position, and prepare + // to search all moves: + MovePicker mp = MovePicker(pos, false, ttMove, ss[ply].mateKiller, + ss[ply].killer1, ss[ply].killer2, depth); + Move move, movesSearched[256]; + int moveCount = 0; + Value value, bestValue = -VALUE_INFINITE, futilityValue = VALUE_NONE; + Bitboard dcCandidates = mp.discovered_check_candidates(); + bool isCheck = pos.is_check(); + bool useFutilityPruning = + UseFutilityPruning && depth < SelectiveDepth && !isCheck; + + // Loop through all legal moves until no moves remain or a beta cutoff + // occurs. + while(bestValue < beta && !thread_should_stop(threadID) + && (move = mp.get_next_move()) != MOVE_NONE) { + UndoInfo u; + Depth ext, newDepth; + bool singleReply = (isCheck && mp.number_of_moves() == 1); + bool moveIsCheck = pos.move_is_check(move, dcCandidates); + bool moveIsCapture = pos.move_is_capture(move); + bool moveIsPassedPawnPush = pos.move_is_passed_pawn_push(move); + + assert(move_is_ok(move)); + movesSearched[moveCount++] = ss[ply].currentMove = move; + + // Decide the new search depth. + ext = extension(pos, move, false, moveIsCheck, singleReply, mateThreat); + newDepth = depth - OnePly + ext; + + // Futility pruning + if(useFutilityPruning && ext == Depth(0) && !moveIsCapture && + !moveIsPassedPawnPush && !move_promotion(move)) { + + if(moveCount >= 2 + int(depth) + && ok_to_prune(pos, move, ss[ply].threatMove, depth)) + continue; + + if(depth < 3 * OnePly && approximateEval < beta) { + if(futilityValue == VALUE_NONE) + futilityValue = evaluate(pos, ei, threadID) + + ((depth < 2 * OnePly)? FutilityMargin1 : FutilityMargin2); + if(futilityValue < beta) { + if(futilityValue > bestValue) + bestValue = futilityValue; + continue; + } + } + } + + // Make and search the move. + pos.do_move(move, u, dcCandidates); + + if(depth >= 2*OnePly && ext == Depth(0) && moveCount >= LMRNonPVMoves + && !moveIsCapture && !move_promotion(move) && !moveIsPassedPawnPush + && !move_is_castle(move) + && move != ss[ply].killer1 && move != ss[ply].killer2) { + ss[ply].reduction = OnePly; + value = -search(pos, ss, -(beta-1), newDepth-OnePly, ply+1, true, + threadID); + } + else + value = beta; + if(value >= beta) { + ss[ply].reduction = Depth(0); + value = -search(pos, ss, -(beta-1), newDepth, ply+1, true, threadID); + } + pos.undo_move(move, u); + + assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); + + // New best move? + if(value > bestValue) { + bestValue = value; + if(value >= beta) + update_pv(ss, ply); + if(value == value_mate_in(ply + 1)) + ss[ply].mateKiller = move; + } + + // Split? + if(ActiveThreads > 1 && bestValue < beta && depth >= MinimumSplitDepth + && Iteration <= 99 && idle_thread_exists(threadID) + && !AbortSearch && !thread_should_stop(threadID) + && split(pos, ss, ply, &beta, &beta, &bestValue, depth, &moveCount, + &mp, dcCandidates, threadID, false)) + break; + } + + // All legal moves have been searched. A special case: If there were + // no legal moves, it must be mate or stalemate: + if(moveCount == 0) { + if(pos.is_check()) + return value_mated_in(ply); + else + return VALUE_DRAW; + } + + // If the search is not aborted, update the transposition table, + // history counters, and killer moves. This code is somewhat messy, + // and definitely needs to be cleaned up. FIXME + if(!AbortSearch && !thread_should_stop(threadID)) { + if(bestValue < beta) + TT.store(pos, value_to_tt(bestValue, ply), depth, MOVE_NONE, + VALUE_TYPE_UPPER); + else { + Move m = ss[ply].pv[ply]; + + if(pos.square_is_empty(move_to(m)) && !move_promotion(m) && + !move_is_ep(m)) { + for(int i = 0; i < moveCount - 1; i++) + if(pos.square_is_empty(move_to(movesSearched[i])) + && !move_promotion(movesSearched[i]) + && !move_is_ep(movesSearched[i])) + H.failure(pos.piece_on(move_from(movesSearched[i])), + movesSearched[i]); + H.success(pos.piece_on(move_from(m)), m, depth); + + if(m != ss[ply].killer1) { + ss[ply].killer2 = ss[ply].killer1; + ss[ply].killer1 = m; + } + } + TT.store(pos, value_to_tt(bestValue, ply), depth, m, VALUE_TYPE_LOWER); + } + } + + 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 OnePly). + + Value qsearch(Position &pos, SearchStack ss[], Value alpha, Value beta, + Depth depth, int ply, int threadID) { + Value staticValue, bestValue, value; + EvalInfo ei; + + assert(alpha >= -VALUE_INFINITE && alpha <= VALUE_INFINITE); + assert(beta >= -VALUE_INFINITE && beta <= VALUE_INFINITE); + assert(depth <= 0); + assert(ply >= 0 && ply < PLY_MAX); + assert(threadID >= 0 && threadID < ActiveThreads); + + // Initialize, and make an early exit in case of an aborted search, + // an instant draw, maximum ply reached, etc. + if(AbortSearch || thread_should_stop(threadID)) + return Value(0); + + init_node(pos, ss, ply, threadID); + + if(pos.is_draw()) + return VALUE_DRAW; + + // Evaluate the position statically: + staticValue = evaluate(pos, ei, threadID); + + if(ply == PLY_MAX - 1) return staticValue; + + // Initialize "stand pat score", and return it immediately if it is + // at least beta. + if(pos.is_check()) + bestValue = -VALUE_INFINITE; + else { + bestValue = staticValue; + if(bestValue >= beta) + return bestValue; + if(bestValue > alpha) + alpha = bestValue; + } + + // 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 == 0) will be generated. + MovePicker mp = MovePicker(pos, false, MOVE_NONE, MOVE_NONE, MOVE_NONE, + MOVE_NONE, depth); + Move move; + int moveCount = 0; + Bitboard dcCandidates = mp.discovered_check_candidates(); + bool isCheck = pos.is_check(); + + // Loop through the moves until no moves remain or a beta cutoff + // occurs. + while(alpha < beta && ((move = mp.get_next_move()) != MOVE_NONE)) { + UndoInfo u; + bool moveIsCheck = pos.move_is_check(move, dcCandidates); + bool moveIsPassedPawnPush = pos.move_is_passed_pawn_push(move); + + assert(move_is_ok(move)); + + moveCount++; + ss[ply].currentMove = move; + + // Futility pruning + if(UseQSearchFutilityPruning && !isCheck && !moveIsCheck && + !move_promotion(move) && !moveIsPassedPawnPush && + beta - alpha == 1 && + pos.non_pawn_material(pos.side_to_move()) > RookValueMidgame) { + Value futilityValue = + staticValue + + Max(pos.midgame_value_of_piece_on(move_to(move)), + pos.endgame_value_of_piece_on(move_to(move))) + + FutilityMargin0 + + ei.futilityMargin; + if(futilityValue < alpha) { + if(futilityValue > bestValue) + bestValue = futilityValue; + continue; + } + } + + // Don't search captures and checks with negative SEE values. + if(!isCheck && !move_promotion(move) && + pos.midgame_value_of_piece_on(move_from(move)) > + pos.midgame_value_of_piece_on(move_to(move)) && + pos.see(move) < 0) + continue; + + // Make and search the move. + pos.do_move(move, u, dcCandidates); + value = -qsearch(pos, ss, -beta, -alpha, depth-OnePly, ply+1, threadID); + pos.undo_move(move, u); + + assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); + + // New best move? + if(value > bestValue) { + bestValue = value; + if(value > alpha) { + alpha = value; + update_pv(ss, ply); + } + } + } + + // All legal moves have been searched. A special case: If we're in check + // and no legal moves were found, it is checkmate: + if(pos.is_check() && moveCount == 0) // Mate! + return value_mated_in(ply); + + assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); + + return bestValue; + } + + + // sp_search() is used to search from a split point. This function is called + // by each thread working at the split point. It is similar to the normal + // search() function, but simpler. Because we have already probed the hash + // table, done a null move search, and searched the first move before + // splitting, we don't have to repeat all this work in sp_search(). 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. + + void sp_search(SplitPoint *sp, int threadID) { + assert(threadID >= 0 && threadID < ActiveThreads); + assert(ActiveThreads > 1); + + Position pos = Position(sp->pos); + SearchStack *ss = sp->sstack[threadID]; + Value value; + Move move; + int moveCount = sp->moves; + bool isCheck = pos.is_check(); + bool useFutilityPruning = + UseFutilityPruning && sp->depth < SelectiveDepth && !isCheck; + + while(sp->bestValue < sp->beta && !thread_should_stop(threadID) + && (move = sp->mp->get_next_move(sp->lock)) != MOVE_NONE) { + UndoInfo u; + Depth ext, newDepth; + bool moveIsCheck = pos.move_is_check(move, sp->dcCandidates); + bool moveIsCapture = pos.move_is_capture(move); + bool moveIsPassedPawnPush = pos.move_is_passed_pawn_push(move); + + assert(move_is_ok(move)); + + lock_grab(&(sp->lock)); + sp->moves++; + moveCount = sp->moves; + lock_release(&(sp->lock)); + + ss[sp->ply].currentMove = move; + + // Decide the new search depth. + ext = extension(pos, move, false, moveIsCheck, false, false); + newDepth = sp->depth - OnePly + ext; + + // Prune? + if(useFutilityPruning && ext == Depth(0) && !moveIsCapture + && !moveIsPassedPawnPush && !move_promotion(move) + && moveCount >= 2 + int(sp->depth) + && ok_to_prune(pos, move, ss[sp->ply].threatMove, sp->depth)) + continue; + + // Make and search the move. + pos.do_move(move, u, sp->dcCandidates); + if(ext == Depth(0) && moveCount >= LMRNonPVMoves + && !moveIsCapture && !move_promotion(move) && !moveIsPassedPawnPush + && !move_is_castle(move) + && move != ss[sp->ply].killer1 && move != ss[sp->ply].killer2) { + ss[sp->ply].reduction = OnePly; + value = -search(pos, ss, -(sp->beta-1), newDepth - OnePly, sp->ply+1, + true, threadID); + } + else + value = sp->beta; + if(value >= sp->beta) { + ss[sp->ply].reduction = Depth(0); + value = -search(pos, ss, -(sp->beta - 1), newDepth, sp->ply+1, true, + threadID); + } + pos.undo_move(move, u); + + assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); + + if(thread_should_stop(threadID)) + break; + + // New best move? + lock_grab(&(sp->lock)); + if(value > sp->bestValue && !thread_should_stop(threadID)) { + sp->bestValue = value; + if(sp->bestValue >= sp->beta) { + sp_update_pv(sp->parentSstack, ss, sp->ply); + for(int i = 0; i < ActiveThreads; i++) + if(i != threadID && (i == sp->master || sp->slaves[i])) + Threads[i].stop = true; + sp->finished = true; + } + } + lock_release(&(sp->lock)); + } + + lock_grab(&(sp->lock)); + + // If this is the master thread and we have been asked to stop because of + // a beta cutoff higher up in the tree, stop all slave threads: + if(sp->master == threadID && thread_should_stop(threadID)) + for(int i = 0; i < ActiveThreads; i++) + if(sp->slaves[i]) + Threads[i].stop = true; + + sp->cpus--; + sp->slaves[threadID] = 0; + + lock_release(&(sp->lock)); + } + + + // sp_search_pv() is used to search from a PV split point. This function + // is called by each thread working at the split point. It is similar to + // the normal search_pv() function, but simpler. Because we have already + // probed the hash table and searched the first move before splitting, we + // don't have to repeat all this work in sp_search_pv(). 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. + + void sp_search_pv(SplitPoint *sp, int threadID) { + assert(threadID >= 0 && threadID < ActiveThreads); + assert(ActiveThreads > 1); + + Position pos = Position(sp->pos); + SearchStack *ss = sp->sstack[threadID]; + Value value; + Move move; + int moveCount = sp->moves; + + while(sp->alpha < sp->beta && !thread_should_stop(threadID) + && (move = sp->mp->get_next_move(sp->lock)) != MOVE_NONE) { + UndoInfo u; + Depth ext, newDepth; + bool moveIsCheck = pos.move_is_check(move, sp->dcCandidates); + bool moveIsCapture = pos.move_is_capture(move); + bool moveIsPassedPawnPush = pos.move_is_passed_pawn_push(move); + + assert(move_is_ok(move)); + + ss[sp->ply].currentMoveCaptureValue = move_is_ep(move)? + PawnValueMidgame : pos.midgame_value_of_piece_on(move_to(move)); + + lock_grab(&(sp->lock)); + sp->moves++; + moveCount = sp->moves; + lock_release(&(sp->lock)); + + ss[sp->ply].currentMove = move; + + // Decide the new search depth. + ext = extension(pos, move, true, moveIsCheck, false, false); + newDepth = sp->depth - OnePly + ext; + + // Make and search the move. + pos.do_move(move, u, sp->dcCandidates); + if(ext == Depth(0) && moveCount >= LMRPVMoves && !moveIsCapture + && !move_promotion(move) && !moveIsPassedPawnPush + && !move_is_castle(move) + && move != ss[sp->ply].killer1 && move != ss[sp->ply].killer2) { + ss[sp->ply].reduction = OnePly; + value = -search(pos, ss, -sp->alpha, newDepth - OnePly, sp->ply+1, + true, threadID); + } + else + value = sp->alpha + 1; + if(value > sp->alpha) { + ss[sp->ply].reduction = Depth(0); + value = -search(pos, ss, -sp->alpha, newDepth, sp->ply+1, true, + threadID); + if(value > sp->alpha && value < sp->beta) { + if(sp->ply == 1 && RootMoveNumber == 1) + // When the search fails high at ply 1 while searching the first + // move at the root, set the flag failHighPly1. This is used for + // time managment: We don't want to stop the search early in + // such cases, because resolving the fail high at ply 1 could + // result in a big drop in score at the root. + Threads[threadID].failHighPly1 = true; + value = -search_pv(pos, ss, -sp->beta, -sp->alpha, newDepth, + sp->ply+1, threadID); + Threads[threadID].failHighPly1 = false; + } + } + pos.undo_move(move, u); + + assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); + + if(thread_should_stop(threadID)) + break; + + // New best move? + lock_grab(&(sp->lock)); + if(value > sp->bestValue && !thread_should_stop(threadID)) { + sp->bestValue = value; + if(value > sp->alpha) { + sp->alpha = value; + sp_update_pv(sp->parentSstack, ss, sp->ply); + if(value == value_mate_in(sp->ply + 1)) + ss[sp->ply].mateKiller = move; + if(value >= sp->beta) { + for(int i = 0; i < ActiveThreads; i++) + if(i != threadID && (i == sp->master || sp->slaves[i])) + Threads[i].stop = true; + sp->finished = true; + } + } + // If we are at ply 1, and we are searching the first root move at + // ply 0, set the 'Problem' variable if the score has dropped a lot + // (from the computer's point of view) since the previous iteration: + if(Iteration >= 2 && + -value <= ValueByIteration[Iteration-1] - ProblemMargin) + Problem = true; + } + lock_release(&(sp->lock)); + } + + lock_grab(&(sp->lock)); + + // If this is the master thread and we have been asked to stop because of + // a beta cutoff higher up in the tree, stop all slave threads: + if(sp->master == threadID && thread_should_stop(threadID)) + for(int i = 0; i < ActiveThreads; i++) + if(sp->slaves[i]) + Threads[i].stop = true; + + sp->cpus--; + sp->slaves[threadID] = 0; + + lock_release(&(sp->lock)); + } + + + /// The RootMove class + + // Constructor + + RootMove::RootMove() { + nodes = cumulativeNodes = 0ULL; + } + + + /// The RootMoveList class + + // Constructor + + RootMoveList::RootMoveList(Position &pos, Move searchMoves[]) { + MoveStack mlist[MaxRootMoves]; + bool includeAllMoves = (searchMoves[0] == MOVE_NONE); + int i, j = 0, k; + + // Generate all legal moves + count = generate_legal_moves(pos, mlist); + + // Add each move to the moves[] array + for(i = 0; i < count; i++) { + UndoInfo u; + SearchStack ss[PLY_MAX_PLUS_2]; + bool includeMove; + + if(includeAllMoves) + includeMove = true; + else { + includeMove = false; + for(k = 0; searchMoves[k] != MOVE_NONE; k++) + if(searchMoves[k] == mlist[i].move) { + includeMove = true; + break; + } + } + + if(includeMove) { + moves[j].move = mlist[i].move; + moves[j].nodes = 0ULL; + pos.do_move(moves[j].move, u); + moves[j].score = -qsearch(pos, ss, -VALUE_INFINITE, VALUE_INFINITE, + Depth(0), 1, 0); + pos.undo_move(moves[j].move, u); + moves[j].pv[0] = moves[i].move; + moves[j].pv[1] = MOVE_NONE; // FIXME + j++; + } + } + count = j; + this->sort(); + } + + + // Simple accessor methods for the RootMoveList class + + Move RootMoveList::get_move(int moveNum) const { + return moves[moveNum].move; + } + + Value RootMoveList::get_move_score(int moveNum) const { + return moves[moveNum].score; + } + + void RootMoveList::set_move_score(int moveNum, Value score) { + moves[moveNum].score = score; + } + + void RootMoveList::set_move_nodes(int moveNum, int64_t nodes) { + moves[moveNum].nodes = nodes; + moves[moveNum].cumulativeNodes += nodes; + } + + void RootMoveList::set_move_pv(int moveNum, const Move pv[]) { + int j; + for(j = 0; pv[j] != MOVE_NONE; j++) + moves[moveNum].pv[j] = pv[j]; + moves[moveNum].pv[j] = MOVE_NONE; + } + + Move RootMoveList::get_move_pv(int moveNum, int i) const { + return moves[moveNum].pv[i]; + } + + int64_t RootMoveList::get_move_cumulative_nodes(int moveNum) { + return moves[moveNum].cumulativeNodes; + } + + int RootMoveList::move_count() const { + return count; + } + + + // RootMoveList::scan_for_easy_move() is called at the end of the first + // iteration, and is used to detect an "easy move", i.e. a move which appears + // to be much bester than all the rest. If an easy move is found, the move + // is returned, otherwise the function returns MOVE_NONE. It is very + // important that this function is called at the right moment: The code + // assumes that the first iteration has been completed and the moves have + // been sorted. + + Move RootMoveList::scan_for_easy_move() const { + Value bestMoveValue = this->get_move_score(0); + for(int i = 1; i < this->move_count(); i++) + if(this->get_move_score(i) >= bestMoveValue - EasyMoveMargin) + return MOVE_NONE; + return this->get_move(0); + } + + + // RootMoveList::sort() sorts the root move list at the beginning of a new + // iteration. + + void RootMoveList::sort() { + for(int i = 1; i < count; i++) { + RootMove rm = moves[i]; + int j; + for(j = i; j > 0 && compare_root_moves(moves[j-1], rm); j--) + moves[j] = moves[j-1]; + moves[j] = rm; + } + } + + + // RootMoveList::sort_multipv() sorts the first few moves in the root move + // list by their scores and depths. It is used to order the different PVs + // correctly in MultiPV mode. + + void RootMoveList::sort_multipv(int n) { + for(int i = 1; i <= n; i++) { + RootMove rm = moves[i]; + int j; + for(j = i; j > 0 && moves[j-1].score < rm.score; j--) + moves[j] = moves[j-1]; + moves[j] = rm; + } + } + + + // RootMoveList::compare_root_moves() is the comparison function used by + // RootMoveList::sort when sorting the moves. A move m1 is considered to + // be better than a move m2 if it has a higher score, or if the moves have + // equal score but m1 has the higher node count. + + int RootMoveList::compare_root_moves(const RootMove &rm1, + const RootMove &rm2) { + if(rm1.score < rm2.score) return 1; + else if(rm1.score > rm2.score) return 0; + else if(rm1.nodes < rm2.nodes) return 1; + else if(rm1.nodes > rm2.nodes) return 0; + else return 1; + } + + + // init_search_stack() initializes a search stack at the beginning of a + // new search from the root. + + void init_search_stack(SearchStack ss[]) { + for(int i = 0; i < 3; i++) { + ss[i].pv[i] = MOVE_NONE; + ss[i].pv[i+1] = MOVE_NONE; + ss[i].currentMove = MOVE_NONE; + ss[i].mateKiller = MOVE_NONE; + ss[i].killer1 = MOVE_NONE; + ss[i].killer2 = MOVE_NONE; + ss[i].threatMove = MOVE_NONE; + ss[i].reduction = Depth(0); + } + } + + + // init_node() is called at the beginning of all the search functions + // (search(), search_pv(), qsearch(), and so on) and initializes the search + // stack object corresponding to the current node. Once every + // NodesBetweenPolls nodes, init_node() also calls poll(), which polls + // for user input and checks whether it is time to stop the search. + + void init_node(const Position &pos, SearchStack ss[], int ply, int threadID) { + assert(ply >= 0 && ply < PLY_MAX); + assert(threadID >= 0 && threadID < ActiveThreads); + + Threads[threadID].nodes++; + + if(threadID == 0) { + NodesSincePoll++; + if(NodesSincePoll >= NodesBetweenPolls) { + poll(); + NodesSincePoll = 0; + } + } + + ss[ply].pv[ply] = ss[ply].pv[ply+1] = ss[ply].currentMove = MOVE_NONE; + ss[ply+2].mateKiller = MOVE_NONE; + ss[ply+2].killer1 = ss[ply+2].killer2 = MOVE_NONE; + ss[ply].threatMove = MOVE_NONE; + ss[ply].reduction = Depth(0); + ss[ply].currentMoveCaptureValue = Value(0); + + if(Threads[threadID].printCurrentLine) + print_current_line(ss, ply, threadID); + } + + + // update_pv() is called whenever a search returns a value > alpha. It + // updates the PV in the SearchStack object corresponding to the current + // node. + + void update_pv(SearchStack ss[], int ply) { + assert(ply >= 0 && ply < PLY_MAX); + + ss[ply].pv[ply] = ss[ply].currentMove; + int p; + for(p = ply + 1; ss[ply+1].pv[p] != MOVE_NONE; p++) + ss[ply].pv[p] = ss[ply+1].pv[p]; + ss[ply].pv[p] = MOVE_NONE; + } + + + // sp_update_pv() is a variant of update_pv for use at split points. The + // difference between the two functions is that sp_update_pv also updates + // the PV at the parent node. + + void sp_update_pv(SearchStack *pss, SearchStack ss[], int ply) { + assert(ply >= 0 && ply < PLY_MAX); + + ss[ply].pv[ply] = pss[ply].pv[ply] = ss[ply].currentMove; + int p; + for(p = ply + 1; ss[ply+1].pv[p] != MOVE_NONE; p++) + ss[ply].pv[p] = pss[ply].pv[p] = ss[ply+1].pv[p]; + ss[ply].pv[p] = pss[ply].pv[p] = MOVE_NONE; + } + + + // connected_moves() tests whether two moves are 'connected' in the sense + // that the first move somehow made the second move possible (for instance + // if the moving piece is the same in both moves). The first move is + // assumed to be the move that was made to reach the current position, while + // the second move is assumed to be a move from the current position. + + bool connected_moves(const Position &pos, Move m1, Move m2) { + Square f1, t1, f2, t2; + + assert(move_is_ok(m1)); + assert(move_is_ok(m2)); + + if(m2 == MOVE_NONE) + return false; + + // Case 1: The moving piece is the same in both moves. + f2 = move_from(m2); + t1 = move_to(m1); + if(f2 == t1) + return true; + + // Case 2: The destination square for m2 was vacated by m1. + t2 = move_to(m2); + f1 = move_from(m1); + if(t2 == f1) + return true; + + // Case 3: Moving through the vacated square: + if(piece_is_slider(pos.piece_on(f2)) && + bit_is_set(squares_between(f2, t2), f1)) + return true; + + // Case 4: The destination square for m2 is attacked by the moving piece + // in m1: + if(pos.piece_attacks_square(t1, t2)) + return true; + + // Case 5: Discovered check, checking piece is the piece moved in m1: + if(piece_is_slider(pos.piece_on(t1)) && + bit_is_set(squares_between(t1, pos.king_square(pos.side_to_move())), + f2) && + !bit_is_set(squares_between(t2, pos.king_square(pos.side_to_move())), + t2)) { + Bitboard occ = pos.occupied_squares(); + Color us = pos.side_to_move(); + Square ksq = pos.king_square(us); + clear_bit(&occ, f2); + if(pos.type_of_piece_on(t1) == BISHOP) { + if(bit_is_set(bishop_attacks_bb(ksq, occ), t1)) + return true; + } + else if(pos.type_of_piece_on(t1) == ROOK) { + if(bit_is_set(rook_attacks_bb(ksq, occ), t1)) + return true; + } + else { + assert(pos.type_of_piece_on(t1) == QUEEN); + if(bit_is_set(queen_attacks_bb(ksq, occ), t1)) + return true; + } + } + + return false; + } + + + // extension() decides whether a move should be searched with normal depth, + // or with extended depth. Certain classes of moves (checking moves, in + // particular) are searched with bigger depth than ordinary moves. + + Depth extension(const Position &pos, Move m, bool pvNode, + bool check, bool singleReply, bool mateThreat) { + Depth result = Depth(0); + + if(check) + result += CheckExtension[pvNode]; + if(singleReply) + result += SingleReplyExtension[pvNode]; + if(pos.move_is_pawn_push_to_7th(m)) + result += PawnPushTo7thExtension[pvNode]; + if(pos.move_is_passed_pawn_push(m)) + result += PassedPawnExtension[pvNode]; + if(mateThreat) + result += MateThreatExtension[pvNode]; + if(pos.midgame_value_of_piece_on(move_to(m)) >= RookValueMidgame + && (pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK) + - pos.midgame_value_of_piece_on(move_to(m)) == Value(0)) + && !move_promotion(m)) + result += PawnEndgameExtension[pvNode]; + if(pvNode && pos.move_is_capture(m) + && pos.type_of_piece_on(move_to(m)) != PAWN && pos.see(m) >= 0) + result += OnePly/2; + + return Min(result, OnePly); + } + + + // ok_to_do_nullmove() looks at the current position and decides whether + // doing a 'null move' should be allowed. In order to avoid zugzwang + // problems, null moves are not allowed when the side to move has very + // little material left. Currently, the test is a bit too simple: Null + // moves are avoided only when the side to move has only pawns left. It's + // probably a good idea to avoid null moves in at least some more + // complicated endgames, e.g. KQ vs KR. FIXME + + bool ok_to_do_nullmove(const Position &pos) { + if(pos.non_pawn_material(pos.side_to_move()) == Value(0)) + return false; + return true; + } + + + // ok_to_prune() tests whether it is safe to forward prune a move. Only + // non-tactical moves late in the move list close to the leaves are + // candidates for pruning. + + bool ok_to_prune(const Position &pos, Move m, Move threat, Depth d) { + Square mfrom, mto, tfrom, tto; + + assert(move_is_ok(m)); + assert(threat == MOVE_NONE || move_is_ok(threat)); + assert(!move_promotion(m)); + assert(!pos.move_is_check(m)); + assert(!pos.move_is_capture(m)); + assert(!pos.move_is_passed_pawn_push(m)); + assert(d >= OnePly); + + mfrom = move_from(m); + mto = move_to(m); + tfrom = move_from(threat); + tto = move_to(threat); + + // Case 1: Castling moves are never pruned. + if(move_is_castle(m)) + return false; + + // Case 2: Don't prune moves which move the threatened piece + if(!PruneEscapeMoves && threat != MOVE_NONE && mfrom == tto) + return false; + + // Case 3: If the threatened piece has value less than or equal to the + // value of the threatening piece, don't prune move which defend it. + if(!PruneDefendingMoves && threat != MOVE_NONE + && (piece_value_midgame(pos.piece_on(tfrom)) + >= piece_value_midgame(pos.piece_on(tto))) + && pos.move_attacks_square(m, tto)) + return false; + + // Case 4: Don't prune moves with good history. + if(!H.ok_to_prune(pos.piece_on(move_from(m)), m, d)) + return false; + + // Case 5: If the moving piece in the threatened move is a slider, don't + // prune safe moves which block its ray. + if(!PruneBlockingMoves && threat != MOVE_NONE + && piece_is_slider(pos.piece_on(tfrom)) + && bit_is_set(squares_between(tfrom, tto), mto) && pos.see(m) >= 0) + return false; + + return true; + } + + + // fail_high_ply_1() checks if some thread is currently resolving a fail + // high at ply 1 at the node below the first root node. This information + // is used for time managment. + + bool fail_high_ply_1() { + for(int i = 0; i < ActiveThreads; i++) + if(Threads[i].failHighPly1) + return true; + return false; + } + + + // current_search_time() returns the number of milliseconds which have passed + // since the beginning of the current search. + + int current_search_time() { + return get_system_time() - SearchStartTime; + } + + + // nps() computes the current nodes/second count. + + int nps() { + int t = current_search_time(); + return (t > 0)? int((nodes_searched() * 1000) / t) : 0; + } + + + // poll() performs two different functions: It polls for user input, and it + // looks at the time consumed so far and decides if it's time to abort the + // search. + + void poll() { + int t, data; + static int lastInfoTime; + + t = current_search_time(); + + // Poll for input + data = Bioskey(); + if(data) { + char input[256]; + if(fgets(input, 255, stdin) == NULL) + strcpy(input, "quit\n"); + if(strncmp(input, "quit", 4) == 0) { + AbortSearch = true; + PonderSearch = false; + Quit = true; + } + else if(strncmp(input, "stop", 4) == 0) { + AbortSearch = true; + PonderSearch = false; + } + else if(strncmp(input, "ponderhit", 9) == 0) + ponderhit(); + } + + // Print search information + if(t < 1000) + lastInfoTime = 0; + else if(lastInfoTime > t) + // HACK: Must be a new search where we searched less than + // NodesBetweenPolls nodes during the first second of search. + lastInfoTime = 0; + else if(t - lastInfoTime >= 1000) { + lastInfoTime = t; + lock_grab(&IOLock); + std::cout << "info nodes " << nodes_searched() << " nps " << nps() + << " time " << t << " hashfull " << TT.full() << std::endl; + lock_release(&IOLock); + if(ShowCurrentLine) + Threads[0].printCurrentLine = true; + } + + // Should we stop the search? + if(!PonderSearch && Iteration >= 2 && + (!InfiniteSearch && (t > AbsoluteMaxSearchTime || + (RootMoveNumber == 1 && + t > MaxSearchTime + ExtraSearchTime) || + (!FailHigh && !fail_high_ply_1() && !Problem && + t > 6*(MaxSearchTime + ExtraSearchTime))))) + AbortSearch = true; + + if(!PonderSearch && ExactMaxTime && t >= ExactMaxTime) + AbortSearch = true; + + if(!PonderSearch && Iteration >= 3 && MaxNodes + && nodes_searched() >= MaxNodes) + AbortSearch = true; + } + + + // ponderhit() is called when the program is pondering (i.e. thinking while + // it's the opponent's turn to move) in order to let the engine know that + // it correctly predicted the opponent's move. + + void ponderhit() { + int t = current_search_time(); + PonderSearch = false; + if(Iteration >= 2 && + (!InfiniteSearch && (StopOnPonderhit || + t > AbsoluteMaxSearchTime || + (RootMoveNumber == 1 && + t > MaxSearchTime + ExtraSearchTime) || + (!FailHigh && !fail_high_ply_1() && !Problem && + t > 6*(MaxSearchTime + ExtraSearchTime))))) + AbortSearch = true; + } + + + // print_current_line() prints the current line of search for a given + // thread. Called when the UCI option UCI_ShowCurrLine is 'true'. + + void print_current_line(SearchStack ss[], int ply, int threadID) { + assert(ply >= 0 && ply < PLY_MAX); + assert(threadID >= 0 && threadID < ActiveThreads); + + if(!Threads[threadID].idle) { + lock_grab(&IOLock); + std::cout << "info currline " << (threadID + 1); + for(int p = 0; p < ply; p++) + std::cout << " " << ss[p].currentMove; + std::cout << std::endl; + lock_release(&IOLock); + } + Threads[threadID].printCurrentLine = false; + if(threadID + 1 < ActiveThreads) + Threads[threadID + 1].printCurrentLine = true; + } + + + // wait_for_stop_or_ponderhit() is called when the maximum depth is reached + // while the program is pondering. The point is to work around a wrinkle in + // the UCI protocol: When pondering, the engine is not allowed to give a + // "bestmove" before the GUI sends it a "stop" or "ponderhit" command. + // We simply wait here until one of these commands is sent, and return, + // after which the bestmove and pondermove will be printed (in id_loop()). + + void wait_for_stop_or_ponderhit() { + std::string command; + + while(true) { + if(!std::getline(std::cin, command)) + command = "quit"; + + if(command == "quit") { + OpeningBook.close(); + stop_threads(); + quit_eval(); + exit(0); + } + else if(command == "ponderhit" || command == "stop") + break; + } + } + + + // idle_loop() is where the threads are parked when they have no work to do. + // The parameter "waitSp", if non-NULL, is a pointer to an active SplitPoint + // object for which the current thread is the master. + + void idle_loop(int threadID, SplitPoint *waitSp) { + assert(threadID >= 0 && threadID < THREAD_MAX); + + Threads[threadID].running = true; + + while(true) { + if(AllThreadsShouldExit && threadID != 0) + break; + + // If we are not thinking, wait for a condition to be signaled instead + // of wasting CPU time polling for work: + while(threadID != 0 && (Idle || threadID >= ActiveThreads)) { +#if !defined(_MSC_VER) + pthread_mutex_lock(&WaitLock); + if(Idle || threadID >= ActiveThreads) + pthread_cond_wait(&WaitCond, &WaitLock); + pthread_mutex_unlock(&WaitLock); +#else + WaitForSingleObject(SitIdleEvent[threadID], INFINITE); +#endif + } + + // If this thread has been assigned work, launch a search: + if(Threads[threadID].workIsWaiting) { + Threads[threadID].workIsWaiting = false; + if(Threads[threadID].splitPoint->pvNode) + sp_search_pv(Threads[threadID].splitPoint, threadID); + else + sp_search(Threads[threadID].splitPoint, threadID); + Threads[threadID].idle = true; + } + + // If this thread is the master of a split point and all threads have + // finished their work at this split point, return from the idle loop: + if(waitSp != NULL && waitSp->cpus == 0) + return; + } + + Threads[threadID].running = false; + } + + + // init_split_point_stack() is called during program initialization, and + // initializes all split point objects. + + void init_split_point_stack() { + for(int i = 0; i < THREAD_MAX; i++) + for(int j = 0; j < MaxActiveSplitPoints; j++) { + SplitPointStack[i][j].parent = NULL; + lock_init(&(SplitPointStack[i][j].lock), NULL); + } + } + + + // destroy_split_point_stack() is called when the program exits, and + // destroys all locks in the precomputed split point objects. + + void destroy_split_point_stack() { + for(int i = 0; i < THREAD_MAX; i++) + for(int j = 0; j < MaxActiveSplitPoints; j++) + lock_destroy(&(SplitPointStack[i][j].lock)); + } + + + // thread_should_stop() checks whether the thread with a given threadID has + // been asked to stop, directly or indirectly. This can happen if a beta + // cutoff has occured in thre thread's currently active split point, or in + // some ancestor of the current split point. + + bool thread_should_stop(int threadID) { + assert(threadID >= 0 && threadID < ActiveThreads); + + SplitPoint *sp; + + if(Threads[threadID].stop) + return true; + if(ActiveThreads <= 2) + return false; + for(sp = Threads[threadID].splitPoint; sp != NULL; sp = sp->parent) + if(sp->finished) { + Threads[threadID].stop = true; + return true; + } + return false; + } + + + // thread_is_available() checks whether the thread with threadID "slave" is + // available to help the thread with threadID "master" at a split point. An + // obvious requirement is that "slave" must be idle. With more than two + // threads, this is not by itself sufficient: If "slave" is the master of + // some active split point, it is only available as a slave to the other + // threads 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_is_available(int slave, int master) { + assert(slave >= 0 && slave < ActiveThreads); + assert(master >= 0 && master < ActiveThreads); + assert(ActiveThreads > 1); + + if(!Threads[slave].idle || slave == master) + return false; + + if(Threads[slave].activeSplitPoints == 0) + // No active split points means that the thread is available as a slave + // for any other thread. + return true; + + if(ActiveThreads == 2) + return true; + + // Apply the "helpful master" concept if possible. + if(SplitPointStack[slave][Threads[slave].activeSplitPoints-1].slaves[master]) + return true; + + return false; + } + + + // idle_thread_exists() tries to find an idle thread which is available as + // a slave for the thread with threadID "master". + + bool idle_thread_exists(int master) { + assert(master >= 0 && master < ActiveThreads); + assert(ActiveThreads > 1); + + for(int i = 0; i < ActiveThreads; i++) + if(thread_is_available(i, master)) + return true; + return false; + } + + + // split() does the actual work of distributing the work at a node between + // several threads at PV nodes. If it does not succeed in splitting the + // node (because no idle threads are available, or because we have no unused + // split point objects), the function immediately returns false. If + // splitting is possible, a SplitPoint object is initialized with all the + // data that must be copied to the helper threads (the current position and + // search stack, alpha, beta, the search depth, etc.), and we tell our + // helper threads that they have been assigned work. This will cause them + // to instantly leave their idle loops and call sp_search_pv(). When all + // threads have returned from sp_search_pv (or, equivalently, when + // splitPoint->cpus becomes 0), split() returns true. + + bool split(const Position &p, SearchStack *sstck, int ply, + Value *alpha, Value *beta, Value *bestValue, + Depth depth, int *moves, + MovePicker *mp, Bitboard dcCandidates, int master, bool pvNode) { + assert(p.is_ok()); + assert(sstck != NULL); + assert(ply >= 0 && ply < PLY_MAX); + assert(*bestValue >= -VALUE_INFINITE && *bestValue <= *alpha); + assert(!pvNode || *alpha < *beta); + assert(*beta <= VALUE_INFINITE); + assert(depth > Depth(0)); + assert(master >= 0 && master < ActiveThreads); + assert(ActiveThreads > 1); + + SplitPoint *splitPoint; + int i; + + lock_grab(&MPLock); + + // If no other thread is available to help us, or if we have too many + // active split points, don't split: + if(!idle_thread_exists(master) || + Threads[master].activeSplitPoints >= MaxActiveSplitPoints) { + lock_release(&MPLock); + return false; + } + + // Pick the next available split point object from the split point stack: + splitPoint = SplitPointStack[master] + Threads[master].activeSplitPoints; + Threads[master].activeSplitPoints++; + + // Initialize the split point object: + splitPoint->parent = Threads[master].splitPoint; + splitPoint->finished = false; + splitPoint->ply = ply; + splitPoint->depth = depth; + splitPoint->alpha = pvNode? *alpha : (*beta - 1); + splitPoint->beta = *beta; + splitPoint->pvNode = pvNode; + splitPoint->dcCandidates = dcCandidates; + splitPoint->bestValue = *bestValue; + splitPoint->master = master; + splitPoint->mp = mp; + splitPoint->moves = *moves; + splitPoint->cpus = 1; + splitPoint->pos.copy(p); + splitPoint->parentSstack = sstck; + for(i = 0; i < ActiveThreads; i++) + splitPoint->slaves[i] = 0; + + // Copy the current position and the search stack to the master thread: + memcpy(splitPoint->sstack[master], sstck, (ply+1)*sizeof(SearchStack)); + Threads[master].splitPoint = splitPoint; + + // Make copies of the current position and search stack for each thread: + for(i = 0; i < ActiveThreads && splitPoint->cpus < MaxThreadsPerSplitPoint; + i++) + if(thread_is_available(i, master)) { + memcpy(splitPoint->sstack[i], sstck, (ply+1)*sizeof(SearchStack)); + Threads[i].splitPoint = splitPoint; + splitPoint->slaves[i] = 1; + splitPoint->cpus++; + } + + // Tell the threads that they have work to do. This will make them leave + // their idle loop. + for(i = 0; i < ActiveThreads; i++) + if(i == master || splitPoint->slaves[i]) { + Threads[i].workIsWaiting = true; + Threads[i].idle = false; + Threads[i].stop = false; + } + + lock_release(&MPLock); + + // Everything is set up. The master thread enters the idle loop, from + // which it will instantly launch a search, because its workIsWaiting + // slot is 'true'. We send the split point as a second parameter to the + // idle loop, which means that the main thread will return from the idle + // loop when all threads have finished their work at this split point + // (i.e. when // splitPoint->cpus == 0). + idle_loop(master, splitPoint); + + // We have returned from the idle loop, which means that all threads are + // finished. Update alpha, beta and bestvalue, and return: + lock_grab(&MPLock); + if(pvNode) *alpha = splitPoint->alpha; + *beta = splitPoint->beta; + *bestValue = splitPoint->bestValue; + Threads[master].stop = false; + Threads[master].idle = false; + Threads[master].activeSplitPoints--; + Threads[master].splitPoint = splitPoint->parent; + lock_release(&MPLock); + + return true; + } + + + // wake_sleeping_threads() wakes up all sleeping threads when it is time + // to start a new search from the root. + + void wake_sleeping_threads() { + if(ActiveThreads > 1) { + for(int i = 1; i < ActiveThreads; i++) { + Threads[i].idle = true; + Threads[i].workIsWaiting = false; + } +#if !defined(_MSC_VER) + pthread_mutex_lock(&WaitLock); + pthread_cond_broadcast(&WaitCond); + pthread_mutex_unlock(&WaitLock); +#else + for(int i = 1; i < THREAD_MAX; i++) + SetEvent(SitIdleEvent[i]); +#endif + } + } + + + // init_thread() is the function which is called when a new thread is + // launched. It simply calls the idle_loop() function with the supplied + // threadID. There are two versions of this function; one for POSIX threads + // and one for Windows threads. + +#if !defined(_MSC_VER) + + void *init_thread(void *threadID) { + idle_loop(*(int *)threadID, NULL); + return NULL; + } + +#else + + DWORD WINAPI init_thread(LPVOID threadID) { + idle_loop(*(int *)threadID, NULL); + return NULL; + } + +#endif + +} diff --git a/src/search.h b/src/search.h new file mode 100644 index 00000000..831248ba --- /dev/null +++ b/src/search.h @@ -0,0 +1,90 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +#if !defined(SEARCH_H_INCLUDED) +#define SEARCH_H_INCLUDED + +//// +//// Includes +//// + +#include "types.h" +#include "depth.h" +#include "history.h" +#include "lock.h" +#include "movegen.h" +#include "position.h" +#include "tt.h" +#include "value.h" + + +//// +//// Constants +//// + +const int PLY_MAX = 100; +const int PLY_MAX_PLUS_2 = 102; + + +//// +//// Types +//// + +/// The SearchStack 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 SearchStack objects, indexed by the +/// current ply. + +struct SearchStack { + Move pv[PLY_MAX]; + Move currentMove; + Value currentMoveCaptureValue; + Move mateKiller, killer1, killer2; + Move threatMove; + Depth reduction; +}; + + +//// +//// Global variables +//// + +extern TranspositionTable TT; + +extern int ActiveThreads; + +extern Lock SMPLock; + +// Perhaps better to make H local, and pass as parameter to MovePicker? +extern History H; + + +//// +//// Prototypes +//// + +extern void init_threads(); +extern void stop_threads(); +extern void think(const Position &pos, bool infinite, bool ponder, int time, + int increment, int movesToGo, int maxDepth, int maxNodes, + int maxTime, Move searchMoves[]); +extern int64_t nodes_searched(); + + +#endif // !defined(SEARCH_H_INCLUDED) diff --git a/src/square.cpp b/src/square.cpp new file mode 100644 index 00000000..6ac9b2fb --- /dev/null +++ b/src/square.cpp @@ -0,0 +1,85 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +//// +//// Includes +//// + +#include +#include +#include + +#include "square.h" + + +//// +//// Functions +//// + + +/// Translating files, ranks and squares to/from characters and strings: + +File file_from_char(char c) { + return File(c - 'a') + FILE_A; +} + + +char file_to_char(File f) { + return char(f - FILE_A) + 'a'; +} + + +Rank rank_from_char(char c) { + return Rank(c - '1') + RANK_1; +} + + +char rank_to_char(Rank r) { + return char(r - RANK_1) + '1'; +} + + +Square square_from_string(const std::string &str) { + return make_square(file_from_char(str[0]), rank_from_char(str[1])); +} + + +const std::string square_to_string(Square s) { + std::string str; + str += file_to_char(square_file(s)); + str += rank_to_char(square_rank(s)); + return str; +} + + +/// file_is_ok(), rank_is_ok() and square_is_ok(), for debugging: + +bool file_is_ok(File f) { + return f >= FILE_A && f <= FILE_H; +} + + +bool rank_is_ok(Rank r) { + return r >= RANK_1 && r <= RANK_8; +} + + +bool square_is_ok(Square s) { + return file_is_ok(square_file(s)) && rank_is_ok(square_rank(s)); +} diff --git a/src/square.h b/src/square.h new file mode 100644 index 00000000..3afa127a --- /dev/null +++ b/src/square.h @@ -0,0 +1,177 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +#if !defined(SQUARE_H_INCLUDED) +#define SQUARE_H_INCLUDED + +//// +//// Includes +//// + +#include + +#include "color.h" +#include "misc.h" + + +//// +//// Types +//// + +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 +}; + +enum File { + FILE_A, FILE_B, FILE_C, FILE_D, FILE_E, FILE_F, FILE_G, FILE_H, FILE_NONE +}; + +enum Rank { + RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, RANK_NONE +}; + +enum SquareDelta { + DELTA_SSW = -021, DELTA_SS = -020, DELTA_SSE = -017, DELTA_SWW = -012, + DELTA_SW = -011, DELTA_S = -010, DELTA_SE = -07, DELTA_SEE = -06, + DELTA_W = -01, DELTA_ZERO = 0, DELTA_E = 01, DELTA_NWW = 06, DELTA_NW = 07, + DELTA_N = 010, DELTA_NE = 011, DELTA_NEE = 012, DELTA_NNW = 017, + DELTA_NN = 020, DELTA_NNE = 021 +}; + + +//// +//// Constants +//// + +const int FlipMask = 070; +const int FlopMask = 07; + + +//// +//// Inline functions +//// + +inline File operator+ (File x, int i) { return File(int(x) + i); } +inline File operator+ (File x, File y) { return x + int(y); } +inline void operator++ (File &x, int) { x = File(int(x) + 1); } +inline void operator+= (File &x, int i) { x = File(int(x) + i); } +inline File operator- (File x, int i) { return File(int(x) - i); } +inline void operator-- (File &x, int) { x = File(int(x) - 1); } +inline void operator-= (File &x, int i) { x = File(int(x) - i); } + +inline Rank operator+ (Rank x, int i) { return Rank(int(x) + i); } +inline Rank operator+ (Rank x, Rank y) { return x + int(y); } +inline void operator++ (Rank &x, int) { x = Rank(int(x) + 1); } +inline void operator+= (Rank &x, int i) { x = Rank(int(x) + i); } +inline Rank operator- (Rank x, int i) { return Rank(int(x) - i); } +inline void operator-- (Rank &x, int) { x = Rank(int(x) - 1); } +inline void operator-= (Rank &x, int i) { x = Rank(int(x) - i); } + +inline Square operator+ (Square x, int i) { return Square(int(x) + i); } +inline void operator++ (Square &x, int) { x = Square(int(x) + 1); } +inline void operator+= (Square &x, int i) { x = Square(int(x) + i); } +inline Square operator- (Square x, int i) { return Square(int(x) - i); } +inline void operator-- (Square &x, int) { x = Square(int(x) - 1); } +inline void operator-= (Square &x, int i) { x = Square(int(x) - i); } +inline Square operator+ (Square x, SquareDelta i) { return Square(int(x) + i); } +inline void operator+= (Square &x, SquareDelta i) { x = Square(int(x) + i); } +inline Square operator- (Square x, SquareDelta i) { return Square(int(x) - i); } +inline void operator-= (Square &x, SquareDelta i) { x = Square(int(x) - i); } +inline SquareDelta operator- (Square x, Square y) { + return SquareDelta(int(x) - int(y)); +} + +inline Square make_square(File f, Rank r) { + return Square(int(f) | (int(r) << 3)); +} + +inline File square_file(Square s) { + return File(int(s) & 7); +} + +inline Rank square_rank(Square s) { + return Rank(int(s) >> 3); +} + +inline Square flip_square(Square s) { + return Square(int(s) ^ FlipMask); +} + +inline Square flop_square(Square s) { + return Square(int(s) ^ FlopMask); +} + +inline Square relative_square(Color c, Square s) { + return Square(int(s) ^ (int(c) * FlipMask)); +} + +inline Rank pawn_rank(Color c, Square s) { + return square_rank(relative_square(c, s)); +} + +inline Color square_color(Square s) { + return Color((int(square_file(s)) + int(square_rank(s))) & 1); +} + +inline int file_distance(File f1, File f2) { + return abs(int(f1) - int(f2)); +} + +inline int file_distance(Square s1, Square s2) { + return file_distance(square_file(s1), square_file(s2)); +} + +inline int rank_distance(Rank r1, Rank r2) { + return abs(int(r1) - int(r2)); +} + +inline int rank_distance(Square s1, Square s2) { + return rank_distance(square_rank(s1), square_rank(s2)); +} + +inline int square_distance(Square s1, Square s2) { + return Max(file_distance(s1, s2), rank_distance(s1, s2)); +} + + +//// +//// Prototypes +//// + +extern File file_from_char(char c); +extern char file_to_char(File f); +extern Rank rank_from_char(char c); +extern char rank_to_char(Rank r); +extern Square square_from_string(const std::string &str); +extern const std::string square_to_string(Square s); + +extern bool file_is_ok(File f); +extern bool rank_is_ok(Rank r); +extern bool square_is_ok(Square s); + + +#endif // !defined(SQUARE_H_INCLUDED) diff --git a/src/thread.h b/src/thread.h new file mode 100644 index 00000000..19844485 --- /dev/null +++ b/src/thread.h @@ -0,0 +1,78 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +#if !defined(THREAD_H_INCLUDED) +#define THREAD_H_INCLUDED + + +//// +//// Includes +//// + +#include "lock.h" +#include "movepick.h" +#include "position.h" +#include "search.h" + + +//// +//// Constants and variables +//// + +const int THREAD_MAX = 8; + + +//// +//// Types +//// + +struct SplitPoint { + SplitPoint *parent; + Position pos; + SearchStack sstack[THREAD_MAX][PLY_MAX]; + SearchStack *parentSstack; + int ply; + Depth depth; + volatile Value alpha, beta, bestValue; + bool pvNode; + Bitboard dcCandidates; + int master, slaves[THREAD_MAX]; + Lock lock; + MovePicker *mp; + volatile int moves; + volatile int cpus; + bool finished; +}; + + +struct Thread { + SplitPoint *splitPoint; + int activeSplitPoints; + uint64_t nodes; + bool failHighPly1; + volatile bool stop; + volatile bool running; + volatile bool idle; + volatile bool workIsWaiting; + volatile bool printCurrentLine; + unsigned char pad[64]; +}; + + +#endif // !defined(THREAD_H_INCLUDED) diff --git a/src/timeoday.cpp b/src/timeoday.cpp new file mode 100644 index 00000000..1078af30 --- /dev/null +++ b/src/timeoday.cpp @@ -0,0 +1,28 @@ +/* + (c) Copyright 1992 Eric Backus + + This software may be used freely so long as this copyright notice is + left intact. There is no warrantee on this software. + */ +#include +#include +#include "dos.h" + + +int gettimeofday(struct timeval * tp, struct timezone * tzp) +{ + SYSTEMTIME systime; + + if (tp) { + struct tm tmrec; + time_t theTime = time(NULL); + + + tmrec = *localtime(&theTime); + tp->tv_sec = mktime(&tmrec); + GetLocalTime(&systime); /* system time */ + + tp->tv_usec = systime.wMilliseconds * 1000; + } + return 0; +} diff --git a/src/tt.cpp b/src/tt.cpp new file mode 100644 index 00000000..01d43655 --- /dev/null +++ b/src/tt.cpp @@ -0,0 +1,230 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +//// +//// Includes +//// + +#include +#include + +#include "tt.h" + + +//// +//// Functions +//// + +/// Constructor + +TranspositionTable::TranspositionTable(unsigned mbSize) { + size = 0; + generation = 0; + writes = 0; + entries = 0; + this->set_size(mbSize); +} + + +/// Destructor + +TranspositionTable::~TranspositionTable() { + delete [] entries; +} + + +/// TranspositionTable::set_size sets the size of the transposition table, +/// measured in megabytes. + +void TranspositionTable::set_size(unsigned mbSize) { + unsigned newSize; + + assert(mbSize >= 4 && mbSize <= 1024); + + for(newSize = 1024; newSize * 4 * (sizeof(TTEntry)) <= (mbSize << 20); + newSize *= 2); + newSize /= 2; + + if(newSize != size) { + size = newSize; + delete [] entries; + entries = new TTEntry[size * 4]; + if(entries == NULL) { + std::cerr << "Failed to allocate " << mbSize + << " MB for transposition table." + << std::endl; + exit(EXIT_FAILURE); + } + this->clear(); + } +} + + +/// 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). +/// Perhaps we should also clear it when the "ucinewgame" command is recieved? + +void TranspositionTable::clear() { + memset(entries, 0, size * 4 * sizeof(TTEntry)); +} + + +/// TranspositionTable::store writes a new entry containing a position, +/// a value, a value type, a search depth, and a best move to the +/// transposition table. The transposition table is organized in clusters +/// of four TTEntry objects, and when a new entry is written, it replaces +/// the least valuable of the four entries in a cluster. 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 Position &pos, Value v, Depth d, + Move m, ValueType type) { + TTEntry *tte, *replace; + + tte = replace = entries + int(pos.get_key() & (size - 1)) * 4; + for(int i = 0; i < 4; i++) { + if((tte+i)->key() == pos.get_key()) { + if(m == MOVE_NONE) + m = (tte+i)->move(); + *(tte+i) = TTEntry(pos.get_key(), v, type, d, m, generation); + return; + } + if(replace->generation() == generation) { + if((tte+i)->generation() != generation || + (tte+i)->depth() < replace->depth()) + replace = tte+i; + } + else if((tte+i)->generation() != generation && + (tte+i)->depth() < replace->depth()) + replace = tte+i; + } + *replace = TTEntry(pos.get_key(), v, type, d, m, generation); + writes++; +} + + +/// TranspositionTable::retrieve looks up the current position in the +/// transposition table, and extracts the value, value type, depth and +/// best move if the position is found. The return value is true if +/// the position is found, and false if it isn't. + +bool TranspositionTable::retrieve(const Position &pos, Value *value, + Depth *d, Move *move, + ValueType *type) const { + TTEntry *tte; + bool found = false; + + tte = entries + int(pos.get_key() & (size - 1)) * 4; + for(int i = 0; i < 4 && !found ; i++) + if((tte+i)->key() == pos.get_key()) { + tte = tte + i; + found = true; + } + if(!found) { + *move = MOVE_NONE; + return false; + } + + *value = tte->value(); + *type = tte->type(); + *d = tte->depth(); + *move = tte->move(); + + return true; +} + + +/// TranspositionTable::new_search() is called at the beginning of every new +/// search. It increments the "generation" variable, which is used to +/// distinguish transposition table entries from previous searches from +/// entries from the current search. + +void TranspositionTable::new_search() { + generation++; + writes = 0; +} + + +/// TranspositionTable::insert_pv() is called at the end of a search +/// iteration, and inserts the PV back into the PV. This makes sure the +/// old PV moves are searched first, even if the old TT entries have been +/// overwritten. + +void TranspositionTable::insert_pv(const Position &pos, Move pv[]) { + UndoInfo u; + Position p(pos); + + for(int i = 0; pv[i] != MOVE_NONE; i++) { + this->store(p, VALUE_NONE, Depth(0), pv[i], VALUE_TYPE_NONE); + p.do_move(pv[i], u); + } +} + + +/// TranspositionTable::full() returns the permill of all transposition table +/// entries which have received at least one write during the current search. +/// It is used to display the "info hashfull ..." information in UCI. + +int TranspositionTable::full() { + double N = double(size) * 4.0; + return int(1000 * (1 - exp(writes * log(1.0 - 1.0/N)))); +} + + +/// Constructors + +TTEntry::TTEntry() { +} + +TTEntry::TTEntry(Key k, Value v, ValueType t, Depth d, Move m, + int generation) { + key_ = k; + data = (m & 0x7FFFF) | (t << 20) | (generation << 23); + value_ = v; + depth_ = int16_t(d); +} + + +/// Functions for extracting data from TTEntry objects. + +Key TTEntry::key() const { + return key_; +} + +Depth TTEntry::depth() const { + return Depth(depth_); +} + +Move TTEntry::move() const { + return Move(data & 0x7FFFF); +} + +Value TTEntry::value() const { + return Value(value_); +} + +ValueType TTEntry::type() const { + return ValueType((data >> 20) & 3); +} + +int TTEntry::generation() const { + return (data >> 23); +} diff --git a/src/tt.h b/src/tt.h new file mode 100644 index 00000000..f39d37dd --- /dev/null +++ b/src/tt.h @@ -0,0 +1,92 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +#if !defined(TT_H_INCLUDED) +#define TT_H_INCLUDED + +//// +//// Includes +//// + +#include "depth.h" +#include "position.h" +#include "value.h" + + +//// +//// Types +//// + +/// The TTEntry class is the class of transposition table entries. + +class TTEntry { + +public: + TTEntry(); + TTEntry(Key k, Value v, ValueType t, Depth d, Move m, int generation); + Key key() const; + Depth depth() const; + Move move() const; + Value value() const; + ValueType type() const; + int generation() const; + +private: + Key key_; + uint32_t data; + int16_t value_; + int16_t depth_; +}; + + +/// The transposition table class. This is basically just a huge array +/// containing TTEntry objects, and a few methods for writing new entries +/// and reading new ones. + +class TranspositionTable { + +public: + TranspositionTable(unsigned mbSize); + ~TranspositionTable(); + void set_size(unsigned mbSize); + void clear(); + void store(const Position &pos, Value v, Depth d, Move m, ValueType type); + bool retrieve(const Position &pos, Value *value, Depth *d, Move *move, + ValueType *type) const; + void new_search(); + void insert_pv(const Position &pos, Move pv[]); + int full(); + +private: + unsigned size; + int writes; + TTEntry* entries; + uint8_t generation; +}; + + +//// +//// Constants and variables +//// + +// Default transposition table size, in megabytes: +const int TTDefaultSize = 32; + + +#endif // !defined(TT_H_INCLUDED) diff --git a/src/types.h b/src/types.h new file mode 100644 index 00000000..e540afc4 --- /dev/null +++ b/src/types.h @@ -0,0 +1,46 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +#if !defined(TYPES_H_INCLUDED) +#define TYPES_H_INCLUDED + +#if !defined(_MSC_VER) + +#include + +#else + +typedef __int8 int8_t; +typedef unsigned __int8 uint8_t; +typedef __int16 int16; +typedef unsigned __int16 uint16_t; +typedef __int32 int32; +typedef unsigned __int32 uint32_t; +typedef __int64 int64; +typedef unsigned __int64 uint64_t; + +typedef __int16 int16_t; +typedef __int64 int64_t; + +#endif // !defined(_MSC_VER) + +// Hash keys: +typedef uint64_t Key; + +#endif // !defined(TYPES_H_INCLUDED) diff --git a/src/uci.cpp b/src/uci.cpp new file mode 100644 index 00000000..66a2ade3 --- /dev/null +++ b/src/uci.cpp @@ -0,0 +1,391 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +//// +//// Includes +//// + +#include +#include + +#include "book.h" +#include "evaluate.h" +#include "misc.h" +#include "move.h" +#include "movegen.h" +#include "position.h" +#include "san.h" +#include "search.h" +#include "uci.h" +#include "ucioption.h" + + +//// +//// Local definitions: +//// + +namespace { + + // UCIInputParser is a class for parsing UCI input. The class is + // very simple, and basically just consist of a constant input + // string and a current location in the string. There are methods + // for checking if we are at the end of the line, for getting the + // next token (defined as any whitespace-delimited sequence of + // characters), and for getting the rest of the line as a single + // string. + + class UCIInputParser { + + public: + UCIInputParser(const std::string &line); + std::string get_next_token(); + std::string get_rest_of_line(); + bool at_end_of_line(); + + private: + const std::string &inputLine; + int length, currentIndex; + + void skip_whitespace(); + + }; + + + // The root position. This is set up when the user (or in practice, the GUI) + // sends the "position" UCI command. The root position is sent to the think() + // function when the program receives the "go" command. + Position RootPosition; + + // Local functions + void wait_for_command(); + void handle_command(const std::string &command); + void set_option(UCIInputParser &uip); + void set_position(UCIInputParser &uip); + void go(UCIInputParser &uip); +} + + +//// +//// Functions +//// + +/// uci_main_loop() is the only global function in this file. It is +/// called immediately after the program has finished initializing. +/// The program remains in this loop until it receives the "quit" UCI +/// command. + +void uci_main_loop() { + RootPosition.from_fen(StartPosition); + while(1) wait_for_command(); +} + + +//// +//// Local functions +//// + +namespace { + + /// + /// Implementation of the UCIInputParser class. + /// + + // Constructor for the UCIInputParser class. The constructor takes a + // text string containing a single UCI command as input. + + UCIInputParser::UCIInputParser(const std::string &line) : inputLine(line) { + this->currentIndex = 0; + this->length = line.length(); + } + + + // UCIInputParser::skip_whitspace() skips any number of whitespace + // characters from the current location in an input string. + + void UCIInputParser::skip_whitespace() { + while(isspace((int)(unsigned char)this->inputLine[this->currentIndex])) + this->currentIndex++; + } + + + // UCIInputParser::get_next_token() gets the next token in an UCI + // command. A 'token' in an UCI command is simply any + // whitespace-delimited sequence of characters. + + std::string UCIInputParser::get_next_token() { + int i, j; + + this->skip_whitespace(); + for(i = j = this->currentIndex; + j < this->length && !isspace(this->inputLine[j]); + j++); + this->currentIndex = j; + this->skip_whitespace(); + + std::string str = this->inputLine.substr(i, j - i); + + return str; + } + + + // UCIInputParser::get_rest_of_line() returns the rest of the input + // line (from the current location) as a single string. + + std::string UCIInputParser::get_rest_of_line() { + this->skip_whitespace(); + return this->inputLine.substr(this->currentIndex, this->length); + } + + + // UCIInputParser::at_end_of_line() tests whether we have reached the + // end of the input string, i.e. if any more input remains to be + // parsed. + + bool UCIInputParser::at_end_of_line() { + return this->currentIndex == this->length; + } + + + /// + /// Other functions + /// + + + // wait_for_command() waits for a command from the user, and passes + // this command to handle_command. wait_for_command also intercepts + // EOF from stdin, by translating EOF to the "quit" command. This + // ensures that Glaurung exits gracefully if the GUI dies + // unexpectedly. + + void wait_for_command() { + std::string command; + if(!std::getline(std::cin, command)) command = "quit"; + handle_command(command); + } + + + // handle_command() takes a text string as input, uses a + // UCIInputParser object to parse this text string as a UCI command, + // and calls the appropriate functions. In addition to the UCI + // commands, the function also supports a few debug commands. + + void handle_command(const std::string &command) { + UCIInputParser uip(command); + std::string s = uip.get_next_token(); + + if(s == "quit") { + OpeningBook.close(); + stop_threads(); + quit_eval(); + exit(0); + } + else if(s == "uci") { + std::cout << "id name " << engine_name() << std::endl; + std::cout << "id author Tord Romstad" << std::endl; + print_uci_options(); + std::cout << "uciok" << std::endl; + } + else if(s == "ucinewgame") { + TT.clear(); + Position::init_piece_square_tables(); + RootPosition.from_fen(StartPosition); + } + else if(s == "isready") + std::cout << "readyok" << std::endl; + else if(s == "position") + set_position(uip); + else if(s == "setoption") + set_option(uip); + else if(s == "go") + go(uip); + + // The remaining commands are for debugging purposes only. + // Perhaps they should be removed later in order to reduce the + // size of the program binary. + else if(s == "d") + RootPosition.print(); + else if(s == "flip") { + Position p(RootPosition); + RootPosition.flipped_copy(p); + } + else if(s == "eval") { + EvalInfo ei; + std::cout << "Incremental mg: " << RootPosition.mg_value() + << std::endl; + std::cout << "Incremental eg: " << RootPosition.eg_value() + << std::endl; + std::cout << "Full eval: " + << evaluate(RootPosition, ei, 0) + << std::endl; + } + else if(s == "key") { + std::cout << "key: " << RootPosition.get_key() + << " material key: " << RootPosition.get_material_key() + << " pawn key: " << RootPosition.get_pawn_key() + << std::endl; + } + else { + std::cout << "Unknown command: " << command << std::endl; + while(!uip.at_end_of_line()) { + std::cout << uip.get_next_token() << std::endl; + } + } + } + + + // set_position() is called when Glaurung receives the "position" UCI + // command. The input parameter is a UCIInputParser. It is assumed + // that this parser has consumed the first token of the UCI command + // ("position"), and is ready to read the second token ("startpos" + // or "fen", if the input is well-formed). + + void set_position(UCIInputParser &uip) { + std::string token; + + token = uip.get_next_token(); + if(token == "startpos") + RootPosition.from_fen(StartPosition); + else if(token == "fen") { + std::string fen; + while(token != "moves" && !uip.at_end_of_line()) { + token = uip.get_next_token(); + fen += token; + fen += ' '; + } + RootPosition.from_fen(fen); + } + + if(!uip.at_end_of_line()) { + if(token != "moves") + token = uip.get_next_token(); + if(token == "moves") { + Move move; + UndoInfo u; + while(!uip.at_end_of_line()) { + token = uip.get_next_token(); + move = move_from_string(RootPosition, token); + RootPosition.do_move(move, u); + if(RootPosition.rule_50_counter() == 0) + RootPosition.reset_game_ply(); + } + } + } + } + + + // set_option() is called when Glaurung receives the "setoption" UCI + // command. The input parameter is a UCIInputParser. It is assumed + // that this parser has consumed the first token of the UCI command + // ("setoption"), and is ready to read the second token ("name", if + // the input is well-formed). + + void set_option(UCIInputParser &uip) { + std::string token; + if(!uip.at_end_of_line()) { + token = uip.get_next_token(); + if(token == "name" && !uip.at_end_of_line()) { + std::string name = uip.get_next_token(); + std::string nextToken; + while(!uip.at_end_of_line() + && (nextToken = uip.get_next_token()) != "value") + name += (" " + nextToken); + if(nextToken == "value") + set_option_value(name, uip.get_rest_of_line()); + else + push_button(name); + } + } + } + + + // go() is called when Glaurung receives the "go" UCI command. The + // input parameter is a UCIInputParser. It is assumed that this + // parser has consumed the first token of the UCI command ("go"), + // and is ready to read the second token. The function sets the + // thinking time and other parameters from the input string, and + // calls think() (defined in search.cpp) with the appropriate + // parameters. + + void go(UCIInputParser &uip) { + std::string token; + int time[2] = {0, 0}, inc[2] = {0, 0}, movesToGo = 0, depth = 0, nodes = 0; + int moveTime = 0; + bool infinite = false, ponder = false; + Move searchMoves[500]; + + searchMoves[0] = MOVE_NONE; + + while(!uip.at_end_of_line()) { + token = uip.get_next_token(); + + if(token == "infinite") + infinite = true; + else if(token == "ponder") + ponder = true; + else if(token == "wtime") { + if(!uip.at_end_of_line()) + time[0] = atoi(uip.get_next_token().c_str()); + } + else if(token == "btime") { + if(!uip.at_end_of_line()) + time[1] = atoi(uip.get_next_token().c_str()); + } + else if(token == "winc") { + if(!uip.at_end_of_line()) + inc[0] = atoi(uip.get_next_token().c_str()); + } + else if(token == "binc") { + if(!uip.at_end_of_line()) + inc[1] = atoi(uip.get_next_token().c_str()); + } + else if(token == "movestogo") { + if(!uip.at_end_of_line()) + movesToGo = atoi(uip.get_next_token().c_str()); + } + else if(token == "depth") { + if(!uip.at_end_of_line()) + depth = atoi(uip.get_next_token().c_str()); + } + else if(token == "nodes") { + if(!uip.at_end_of_line()) + nodes = atoi(uip.get_next_token().c_str()); + } + else if(token == "movetime") { + if(!uip.at_end_of_line()) + moveTime = atoi(uip.get_next_token().c_str()); + } + else if(token == "searchmoves" && !uip.at_end_of_line()) { + int numOfMoves = 0; + while(!uip.at_end_of_line()) { + token = uip.get_next_token(); + searchMoves[numOfMoves++] = move_from_string(RootPosition, token); + } + searchMoves[numOfMoves] = MOVE_NONE; + } + } + + if(moveTime) + infinite = true; // HACK + + think(RootPosition, infinite, ponder, time[RootPosition.side_to_move()], + inc[RootPosition.side_to_move()], movesToGo, depth, nodes, moveTime, + searchMoves); + } + +} diff --git a/src/uci.h b/src/uci.h new file mode 100644 index 00000000..23ece63f --- /dev/null +++ b/src/uci.h @@ -0,0 +1,30 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +#if !defined(UCI_H_INCLUDED) +#define UCI_H_INCLUDED + +//// +//// Prototypes +//// + +extern void uci_main_loop(); + + +#endif // !defined(UCI_H_INCLUDED) diff --git a/src/ucioption.cpp b/src/ucioption.cpp new file mode 100644 index 00000000..764324d6 --- /dev/null +++ b/src/ucioption.cpp @@ -0,0 +1,270 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +//// +//// Includes +//// + +#include +#include +#include + +#include "misc.h" +#include "thread.h" +#include "ucioption.h" + + +//// +//// Variables +//// + +bool Chess960 = false; + + +//// +//// Local definitions +//// + +namespace { + + /// + /// Types + /// + + enum OptionType { SPIN, COMBO, CHECK, STRING, BUTTON, OPTION_TYPE_NONE}; + + struct Option { + char name[50], defaultValue[300], currentValue[300]; + OptionType type; + int minValue, maxValue; + char comboValues[8][64]; + }; + + + /// + /// Variables + /// + + Option Options[] = { + { "Use Search Log", "false", "false", CHECK, 0, 0, {""} }, + { "Search Log Filename", "SearchLog.txt", "SearchLog.txt", STRING, 0, 0, {""} }, + { "Book File", "book.bin", "book.bin", STRING, 0, 0, {""} }, + { "Mobility (Middle Game)", "100", "100", SPIN, 0, 200, {""} }, + { "Mobility (Endgame)", "100", "100", SPIN, 0, 200, {""} }, + { "Pawn Structure (Middle Game)", "100", "100", SPIN, 0, 200, {""} }, + { "Pawn Structure (Endgame)", "100", "100", SPIN, 0, 200, {""} }, + { "Passed Pawns (Middle Game)", "100", "100", SPIN, 0, 200, {""} }, + { "Passed Pawns (Endgame)", "100", "100", SPIN, 0, 200, {""} }, + { "Aggressiveness", "100", "100", SPIN, 0, 200, {""} }, + { "Cowardice", "100", "100", SPIN, 0, 200, {""} }, + { "King Safety Curve", "Quadratic", "Quadratic", COMBO, 0, 0, + { "Quadratic", "Linear" /*, "From File"*/ } }, + { "King Safety Coefficient", "40", "40", SPIN, 1, 100 , {""} }, + { "King Safety X Intercept", "0", "0", SPIN, 0, 20, {""} }, + { "King Safety Max Slope", "30", "30", SPIN, 10, 100, {""} }, + { "King Safety Max Value", "500", "500", SPIN, 100, 1000, {""} }, + { "Queen Contact Check Bonus", "4", "4", SPIN, 0, 8, {""} }, + { "Rook Contact Check Bonus", "2", "2", SPIN, 0, 4, {""} }, + { "Queen Check Bonus", "2", "2", SPIN, 0, 4, {""} }, + { "Rook Check Bonus", "1", "1", SPIN, 0, 4, {""} }, + { "Bishop Check Bonus", "1", "1", SPIN, 0, 4, {""} }, + { "Knight Check Bonus", "1", "1", SPIN, 0, 4, {""} }, + { "Discovered Check Bonus", "3", "3", SPIN, 0, 8, {""} }, + { "Mate Threat Bonus", "3", "3", SPIN, 0, 8, {""} }, + { "Check Extension (PV nodes)", "2", "2", SPIN, 0, 2, {""} }, + { "Check Extension (non-PV nodes)", "1", "1", SPIN, 0, 2, {""} }, + { "Single Reply Extension (PV nodes)", "2", "2", SPIN, 0, 2, {""} }, + { "Single Reply Extension (non-PV nodes)", "2", "2", SPIN, 0, 2, {""} }, + { "Mate Threat Extension (PV nodes)", "0", "0", SPIN, 0, 2, {""} }, + { "Mate Threat Extension (non-PV nodes)", "0", "0", SPIN, 0, 2, {""} }, + { "Pawn Push to 7th Extension (PV nodes)", "1", "1", SPIN, 0, 2, {""} }, + { "Pawn Push to 7th Extension (non-PV nodes)", "1", "1", SPIN, 0, 2, {""} }, + { "Passed Pawn Extension (PV nodes)", "1", "1", SPIN, 0, 2, {""} }, + { "Passed Pawn Extension (non-PV nodes)", "0", "0", SPIN, 0, 2, {""} }, + { "Pawn Endgame Extension (PV nodes)", "2", "2", SPIN, 0, 2, {""} }, + { "Pawn Endgame Extension (non-PV nodes)", "2", "2", SPIN, 0, 2, {""} }, + { "Full Depth Moves (PV nodes)", "14", "14", SPIN, 1, 100, {""} }, + { "Full Depth Moves (non-PV nodes)", "3", "3", SPIN, 1, 100, {""} }, + { "Threat Depth", "5", "5", SPIN, 0, 100, {""} }, + { "Selective Plies", "7", "7", SPIN, 0, 10, {""} }, + { "Futility Pruning (Main Search)", "true", "true", CHECK, 0, 0, {""} }, + { "Futility Pruning (Quiescence Search)", "true", "true", CHECK, 0, 0, {""} }, + { "Futility Margin 0", "50", "50", SPIN, 0, 1000, {""} }, + { "Futility Margin 1", "100", "100", SPIN, 0, 1000, {""} }, + { "Futility Margin 2", "300", "300", SPIN, 0, 1000, {""} }, + { "Maximum Razoring Depth", "3", "3", SPIN, 0, 4, {""} }, + { "Razoring Margin", "300", "300", SPIN, 150, 600, {""} }, + { "Randomness", "0", "0", SPIN, 0, 10, {""} }, + { "Minimum Split Depth", "4", "4", SPIN, 4, 7, {""} }, + { "Maximum Number of Threads per Split Point", "5", "5", SPIN, 4, 8, {""} }, + { "Threads", "1", "1", SPIN, 1, 8, {""} }, + { "Hash", "32", "32", SPIN, 4, 4096, {""} }, + { "Clear Hash", "false", "false", BUTTON, 0, 0, {""} }, + { "Ponder", "true", "true", CHECK, 0, 0, {""} }, + { "OwnBook", "true", "true", CHECK, 0, 0, {""} }, + { "MultiPV", "1", "1", SPIN, 1, 500, {""} }, + { "UCI_ShowCurrLine", "false", "false", CHECK, 0, 0, {""} }, + { "UCI_Chess960", "false", "false", CHECK, 0, 0, {""} }, + { "", "", "", OPTION_TYPE_NONE, 0, 0, {""}} + }; + + + /// + /// Functions + /// + + Option *option_with_name(const char *optionName); + +} + + +//// +//// Functions +//// + +/// init_uci_options() initializes the UCI options. Currently, the only +/// thing this function does is to initialize the default value of the +/// "Threads" parameter to the number of available CPU cores. + +void init_uci_options() { + Option *o; + + o = option_with_name("Threads"); + assert(o != NULL); + + // Limit the default value of "Threads" to 7 even if we have 8 CPU cores. + // According to Ken Dail's tests, Glaurung plays much better with 7 than + // with 8 threads. This is weird, but it is probably difficult to find out + // why before I have a 8-core computer to experiment with myself. + sprintf(o->defaultValue, "%d", Min(cpu_count(), 7)); + sprintf(o->currentValue, "%d", Min(cpu_count(), 7)); + + // Increase the minimum split depth when the number of CPUs is big. + // It would probably be better to let this depend on the number of threads + // instead. + o = option_with_name("Minimum Split Depth"); + assert(o != NULL); + if(cpu_count() > 4) { + sprintf(o->defaultValue, "%d", 6); + sprintf(o->defaultValue, "%d", 6); + } +} + + +/// print_uci_options() prints all the UCI options to the standard output, +/// in the format defined by the UCI protocol. + +void print_uci_options() { + static const char optionTypeName[][16] = { + "spin", "combo", "check", "string", "button" + }; + for(Option *o = Options; o->type != OPTION_TYPE_NONE; o++) { + printf("option name %s type %s", o->name, optionTypeName[o->type]); + if(o->type != BUTTON) { + printf(" default %s", o->defaultValue); + if(o->type == SPIN) + printf(" min %d max %d", o->minValue, o->maxValue); + else if(o->type == COMBO) + for(int i = 0; strlen(o->comboValues[i]) > 0; i++) + printf(" var %s", o->comboValues[i]); + } + printf("\n"); + } +} + + +/// get_option_value_bool() returns the current value of a UCI parameter of +/// type "check". + +bool get_option_value_bool(const std::string &optionName) { + Option *o = option_with_name(optionName.c_str()); + return o != NULL && strcmp(o->currentValue, "true") == 0; +} + + +/// get_option_value_int() returns the value of a UCI parameter as an integer. +/// Normally, this function will be used for a parameter of type "spin", but +/// it could also be used with a "combo" parameter, where all the available +/// values are integers. + +int get_option_value_int(const std::string &optionName) { + Option *o = option_with_name(optionName.c_str()); + return atoi(o->currentValue); +} + + +/// get_option_value_string() returns the current value of a UCI parameter as +/// a string. It is used with parameters of type "combo" and "string". + +const std::string get_option_value_string(const std::string &optionName) { + Option *o = option_with_name(optionName.c_str()); + return o->currentValue; +} + + +/// button_was_pressed() tests whether a UCI parameter of type "button" has +/// been selected since the last time the function was called. + +bool button_was_pressed(const std::string &buttonName) { + if(get_option_value_bool(buttonName)) { + set_option_value(buttonName, "false"); + return true; + } + else + return false; +} + + +/// set_option_value() inserts a new value for a UCI parameter. Note that +/// the function does not check that the new value is legal for the given +/// parameter: This is assumed to be the responsibility of the GUI. + +void set_option_value(const std::string &optionName, + const std::string &newValue) { + Option *o = option_with_name(optionName.c_str()); + + if(o != NULL) + strcpy(o->currentValue, newValue.c_str()); + else + std::cout << "No such option: " << optionName << std::endl; +} + + +/// push_button() is used to tell the engine that a UCI parameter of type +/// "button" has been selected: + +void push_button(const std::string &buttonName) { + set_option_value(buttonName, "true"); +} + + +namespace { + + // option_with_name() tries to find a UCI option with a given + // name. It returns a pointer to the UCI option or the null pointer, + // depending on whether an option with the given name exists. + + Option *option_with_name(const char *optionName) { + for(Option *o = Options; o->type != OPTION_TYPE_NONE; o++) + if(strcmp(o->name, optionName) == 0) + return o; + return NULL; + } + +} diff --git a/src/ucioption.h b/src/ucioption.h new file mode 100644 index 00000000..f4209de5 --- /dev/null +++ b/src/ucioption.h @@ -0,0 +1,52 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +#if !defined(UCIOPTION_H_INCLUDED) +#define UCIOPTION_H_INCLUDED + +//// +//// Includes +//// + +#include + + +//// +//// Variables +//// + +extern bool Chess960; + + +//// +//// Prototypes +//// + +extern void init_uci_options(); +extern void print_uci_options(); +extern bool get_option_value_bool(const std::string &optionName); +extern int get_option_value_int(const std::string &optionName); +extern const std::string get_option_value_string(const std::string &optionName); +extern bool button_was_pressed(const std::string &buttonName); +extern void set_option_value(const std::string &optionName, + const std::string &newValue); +extern void push_button(const std::string &buttonName); + + +#endif // !defined(UCIOPTION_H_INCLUDED) diff --git a/src/value.cpp b/src/value.cpp new file mode 100644 index 00000000..a93236c5 --- /dev/null +++ b/src/value.cpp @@ -0,0 +1,95 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +//// +//// Includes +//// + +#include +#include + +#include "value.h" + + +//// +//// Functions +//// + +/// value_to_tt() adjusts a mate score from "plies to mate from the root" to +/// "plies to mate from the current ply". Non-mate scores are unchanged. +/// The function is called before storing a value to the transposition table. + +Value value_to_tt(Value v, int ply) { + if(v >= value_mate_in(100)) + return v + ply; + else if(v <= value_mated_in(100)) + return v - ply; + else + return v; +} + + +/// value_from_tt() is the inverse of value_to_tt(): It adjusts a mate score +/// from the transposition table to a mate score corrected for the current +/// ply depth. + +Value value_from_tt(Value v, int ply) { + if(v >= value_mate_in(100)) + return v - ply; + else if(v <= value_mated_in(100)) + return v + ply; + else + return v; +} + + +/// value_to_centipawns() converts a value from Glaurung's somewhat unusual +/// scale of pawn = 256 to the more conventional pawn = 100. + +int value_to_centipawns(Value v) { + return (int(v) * 100) / int(PawnValueMidgame); +} + + +/// value_from_centipawns() converts a centipawn value to Glaurung's internal +/// evaluation scale. It's used when reading the values of UCI options +/// containing material values (e.g. futility pruning margins). + +Value value_from_centipawns(int cp) { + return Value((cp * 256) / 100); +} + + +/// value_to_string() converts a value to a string suitable for use with the +/// UCI protocol. + +const std::string value_to_string(Value v) { + std::stringstream s; + + if(abs(v) < VALUE_MATE - 200) + s << "cp " << value_to_centipawns(v); + else { + s << "mate "; + if(v > 0) + s << (VALUE_MATE - v + 1) / 2; + else + s << -(VALUE_MATE + v) / 2; + } + return s.str(); +} diff --git a/src/value.h b/src/value.h new file mode 100644 index 00000000..f60e286f --- /dev/null +++ b/src/value.h @@ -0,0 +1,164 @@ +/* + Glaurung, a UCI chess playing engine. + Copyright (C) 2004-2008 Tord Romstad + + Glaurung 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. + + Glaurung 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 . +*/ + + +#if !defined(VALUE_H_INCLUDED) +#define VALUE_H_INCLUDED + +//// +//// Includes +//// + +#include "piece.h" + + +//// +//// Types +//// + +enum ValueType { + VALUE_TYPE_NONE = 0, + VALUE_TYPE_UPPER = 1, // Upper bound + VALUE_TYPE_LOWER = 2, // Lower bound + VALUE_TYPE_EXACT = 3 // Exact score +}; + + +enum Value { + VALUE_DRAW = 0, + VALUE_KNOWN_WIN = 15000, + VALUE_MATE = 30000, + VALUE_INFINITE = 30001, + VALUE_NONE = 30002 +}; + + +//// +//// Constants and variables +//// + +/// Piece values, middle game and endgame + +/// Important: If the material values are changed, one must also +/// adjust the piece square tables, and the method game_phase() in the +/// Position class! + +const Value PawnValueMidgame = Value(0xCC); +const Value PawnValueEndgame = Value(0x100); +const Value KnightValueMidgame = Value(0x340); +const Value KnightValueEndgame = Value(0x340); +const Value BishopValueMidgame = Value(0x340); +const Value BishopValueEndgame = Value(0x340); +const Value RookValueMidgame = Value(0x505); +const Value RookValueEndgame = Value(0x505); +const Value QueenValueMidgame = Value(0xA00); +const Value QueenValueEndgame = Value(0xA00); + +const Value PieceValueMidgame[17] = { + Value(0), + PawnValueMidgame, KnightValueMidgame, BishopValueMidgame, + RookValueMidgame, QueenValueMidgame, + Value(0), Value(0), Value(0), + PawnValueMidgame, KnightValueMidgame, BishopValueMidgame, + RookValueMidgame, QueenValueMidgame, + Value(0), Value(0), Value(0) +}; + +const Value PieceValueEndgame[17] = { + Value(0), + PawnValueEndgame, KnightValueEndgame, BishopValueEndgame, + RookValueEndgame, QueenValueEndgame, + Value(0), Value(0), Value(0), + PawnValueEndgame, KnightValueEndgame, BishopValueEndgame, + RookValueEndgame, QueenValueEndgame, + Value(0), Value(0), Value(0) +}; + +/// Bonus for having the side to move + +const Value TempoValueMidgame = Value(50); +const Value TempoValueEndgame = Value(20); + + +//// +//// Inline functions +//// + +inline Value operator+ (Value v, int i) { return Value(int(v) + i); } +inline Value operator+ (Value v1, Value v2) { return Value(int(v1) + int(v2)); } +inline void operator+= (Value &v1, Value v2) { + v1 = Value(int(v1) + int(v2)); +} +inline Value operator- (Value v, int i) { return Value(int(v) - i); } +inline Value operator- (Value v) { return Value(-int(v)); } +inline Value operator- (Value v1, Value v2) { return Value(int(v1) - int(v2)); } +inline void operator-= (Value &v1, Value v2) { + v1 = Value(int(v1) - int(v2)); +} +inline Value operator* (Value v, int i) { return Value(int(v) * i); } +inline void operator*= (Value &v, int i) { v = Value(int(v) * i); } +inline Value operator* (int i, Value v) { return Value(int(v) * i); } +inline Value operator/ (Value v, int i) { return Value(int(v) / i); } +inline void operator/= (Value &v, int i) { v = Value(int(v) / i); } + + +inline Value value_mate_in(int ply) { + return Value(VALUE_MATE - Value(ply)); +} + +inline Value value_mated_in(int ply) { + return Value(-VALUE_MATE + Value(ply)); +} + +inline bool is_upper_bound(ValueType vt) { + return (int(vt) & int(VALUE_TYPE_UPPER)) != 0; +} + +inline bool is_lower_bound(ValueType vt) { + return (int(vt) & int(VALUE_TYPE_LOWER)) != 0; +} + +inline Value piece_value_midgame(PieceType pt) { + return PieceValueMidgame[pt]; +} + +inline Value piece_value_endgame(PieceType pt) { + return PieceValueEndgame[pt]; +} + +inline Value piece_value_midgame(Piece p) { + return PieceValueMidgame[p]; +} + +inline Value piece_value_endgame(Piece p) { + return PieceValueEndgame[p]; +} + + +//// +//// Prototypes +//// + +extern Value value_to_tt(Value v, int ply); +extern Value value_from_tt(Value v, int ply); +extern int value_to_centipawns(Value v); +extern Value value_from_centipawns(int cp); +extern const std::string value_to_string(Value v); + + +#endif // !defined(VALUE_H_INCLUDED)