diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..a470cc0 --- /dev/null +++ b/.clang-format @@ -0,0 +1,44 @@ +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: Consecutive +AlignConsecutiveDeclarations: Consecutive +AlignEscapedNewlines: DontAlign +AlignOperands: AlignAfterOperator +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: false +AllowShortIfStatementsOnASingleLine: false +BreakTemplateDeclarations: Yes +BasedOnStyle: WebKit +BitFieldColonSpacing: After +BinPackParameters: false +BreakBeforeBinaryOperators: NonAssignment +BreakBeforeBraces: Custom +BraceWrapping: + AfterFunction: false + AfterClass: false + AfterControlStatement: true + BeforeElse: true +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: AfterColon +BreakStringLiterals: false +ColumnLimit: 100 +ContinuationIndentWidth: 2 +Cpp11BracedListStyle: true +IndentGotoLabels: false +IndentPPDirectives: BeforeHash +IndentWidth: 4 +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: None +PackConstructorInitializers: Never +ReflowComments: false +SortIncludes: false +SortUsingDeclarations: false +SpaceAfterCStyleCast: true +SpaceAfterTemplateKeyword: false +SpaceBeforeCaseColon: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeInheritanceColon: false +SpaceInEmptyBlock: false +SpacesBeforeTrailingComments: 2 diff --git a/.gitignore b/.gitignore index a54deda..bce9de6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,12 @@ # Ignore everything -* - -# But not these directories and files -!*.cpp -!*.hpp -!*.h -!Makefile -!README.md -!LICENSE -!.gitignore -!CMakeLists.txt \ No newline at end of file +!COPYING +src/*.nnue +src/*.o +src/cppchess_engine +!src/*.cpp +!src/*.hpp +!src/*.h +!src/layers/* +!src/features/half_ka_v2_hm.cpp +!src/features/half_ka_v2_hm.h +!scripts \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..39f8ea5 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,80 @@ +{ + "files.associations": { + "cmath": "cpp", + "array": "cpp", + "bitset": "cpp", + "cwchar": "cpp", + "unordered_map": "cpp", + "vector": "cpp", + "exception": "cpp", + "fstream": "cpp", + "functional": "cpp", + "initializer_list": "cpp", + "iosfwd": "cpp", + "istream": "cpp", + "new": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "tuple": "cpp", + "any": "cpp", + "atomic": "cpp", + "hash_map": "cpp", + "bit": "cpp", + "cctype": "cpp", + "charconv": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "codecvt": "cpp", + "compare": "cpp", + "complex": "cpp", + "concepts": "cpp", + "condition_variable": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "forward_list": "cpp", + "list": "cpp", + "map": "cpp", + "set": "cpp", + "string": "cpp", + "unordered_set": "cpp", + "algorithm": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "ratio": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "format": "cpp", + "iomanip": "cpp", + "iostream": "cpp", + "limits": "cpp", + "mutex": "cpp", + "numbers": "cpp", + "ostream": "cpp", + "queue": "cpp", + "ranges": "cpp", + "semaphore": "cpp", + "span": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "text_encoding": "cpp", + "thread": "cpp", + "cfenv": "cpp", + "cinttypes": "cpp", + "typeinfo": "cpp", + "valarray": "cpp", + "variant": "cpp" + } +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 8f8f427..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,76 +0,0 @@ -cmake_minimum_required(VERSION 3.10) -project(chess_engine) - -# Use C++17 -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -# Optional: enable warnings (MSVC specific flags) -if (MSVC) - add_compile_options(/W4 /permissive-) -else() - add_compile_options(-Wall -Wextra -pedantic) -endif() - -# Add the source files -set(SOURCES - main.cpp - eval.cpp - search.cpp - tt.cpp - movepick.cpp - uci.cpp - ucioptions.cpp - timeman.cpp -) - -# Define the executable -add_executable(${PROJECT_NAME} ${SOURCES}) - -# Threading support -find_package(Threads REQUIRED) -target_link_libraries(${PROJECT_NAME} Threads::Threads) - -# Platform-specific threading configurations -if(WIN32) - if(MSVC) - # Windows with MSVC - uses Windows threading API - target_compile_definitions(${PROJECT_NAME} PRIVATE WIN32_LEAN_AND_MEAN) - elseif(MINGW) - # MinGW on Windows - uses pthread - target_compile_definitions(${PROJECT_NAME} PRIVATE _WIN32_WINNT=0x0601) - # MinGW might need explicit pthread linking - if(CMAKE_THREAD_LIBS_INIT) - target_link_libraries(${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT}) - endif() - endif() -elseif(UNIX) - # Unix/Linux systems - uses pthread - if(CMAKE_THREAD_LIBS_INIT) - target_link_libraries(${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT}) - endif() - # Some older systems might need explicit -pthread flag - if(CMAKE_USE_PTHREADS_INIT) - target_compile_options(${PROJECT_NAME} PRIVATE -pthread) - target_link_options(${PROJECT_NAME} PRIVATE -pthread) - endif() -endif() - -# Optional: Add atomic library support for older compilers -if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "4.9") - target_link_libraries(${PROJECT_NAME} atomic) -endif() - -# Set default build type if none is specified -if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the build type (Debug, Release, etc.)" FORCE) -endif() - -# Optional: Add specific compile flags per build type -if(NOT MSVC) - set(CMAKE_CXX_FLAGS_DEBUG "-g -march=native -mtune=native") - set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG -march=native -mtune=native -funroll-loops -ftree-vectorize -fomit-frame-pointer") -else() - set(CMAKE_CXX_FLAGS_DEBUG "/Zi /Od") - set(CMAKE_CXX_FLAGS_RELEASE "/O2 /DNDEBUG") -endif() diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/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/Makefile b/Makefile deleted file mode 100644 index 36d7111..0000000 --- a/Makefile +++ /dev/null @@ -1,32 +0,0 @@ -# Compiler and Flags -CXX = g++ - -# Source files and object files -SRC = main.cpp eval.cpp search.cpp tt.cpp movepick.cpp timeman.cpp uci.cpp ucioptions.cpp -OBJ = $(SRC:.cpp=.o) - -# Output file -EXEC = chess_engine - -# Default target: build the project -all: $(EXEC) - -# Linking the object files into the executable -$(EXEC): $(OBJ) - $(CXX) $(LDFLAGS) -o $(EXEC) $(OBJ) - -# Rule to build object files from source files -%.o: %.cpp - $(CXX) $(CXXFLAGS) -std=c++17 -c $< -o $@ - -# Clean up the compiled files -clean: - rm -f $(OBJ) $(EXEC) - -# To run the program after compilation -run: $(EXEC) - ./$(EXEC) - -# Rebuild the project -rebuild: clean all - diff --git a/eval.cpp b/eval.cpp deleted file mode 100644 index a0ba6cd..0000000 --- a/eval.cpp +++ /dev/null @@ -1,777 +0,0 @@ -#include "eval.hpp" -#include -#define EvaluationResult int16_t -// 0. Evaluation result - -struct EvalBreakdown -{ - EvaluationResult phase; - EvaluationResult Material; - EvaluationResult Doubled; - EvaluationResult Isolated; - EvaluationResult Backward; - EvaluationResult Passed; - EvaluationResult Center; - EvaluationResult Mobility; - EvaluationResult Shield; - EvaluationResult KingTropism; - EvaluationResult Space; - EvaluationResult MGPSQT; - EvaluationResult EGPSQT; -}; - -EvalBreakdown wEval{}, bEval{}; -// 1. Weights, in case of tuning (centipawns) -// 1.1. Material weights -constexpr int16_t PawnValue = 100, KnightValue = 325, BishopValue = 350, RookValue = 500, QueenValue = 900; -// 1.2. Pawn structure -constexpr int16_t Doubled = -100, Isolated = -80, Backward = -100, wpassed = 100, cpassed = 50, - Center = 100; -// 1.3. Mobility - -// Mobility weights per piece type -constexpr int MOBILITY_WEIGHTS[] = { - 0, // EMPTY / None - 1, // PAWN (often ignored or handled separately) - 4, // KNIGHT - 4, // BISHOP - 6, // ROOK - 8, // QUEEN - 0 // KING (usually ignored) -}; -// 1.3. King safety -constexpr int16_t pawnShield = 20, KingTropism = 5; -// 1.4. Space -constexpr int16_t Space = 10; -// 1.5. Tempo -constexpr int16_t Tempo = 28; -// 1.6. PQST (source: Chessprogramming Wiki) -// clang-format off -int16_t mg_pawn_table[64] = { - 0, 0, 0, 0, 0, 0, 0, 0, - 98, 134, 61, 95, 68, 126, 34, -11, - -6, 7, 26, 31, 65, 56, 25, -20, - -14, 13, 6, 21, 23, 12, 17, -23, - -27, -2, -5, 12, 17, 6, 10, -25, - -26, -4, -4, -10, 3, 3, 33, -12, - -35, -1, -20, -23, -15, 24, 38, -22, - 0, 0, 0, 0, 0, 0, 0, 0, -}; - -int16_t eg_pawn_table[64] = { - 0, 0, 0, 0, 0, 0, 0, 0, - 178, 173, 158, 134, 147, 132, 165, 187, - 94, 100, 85, 67, 56, 53, 82, 84, - 32, 24, 13, 5, -2, 4, 17, 17, - 13, 9, -3, -7, -7, -8, 3, -1, - 4, 7, -6, 1, 0, -5, -1, -8, - 13, 8, 8, 10, 13, 0, 2, -7, - 0, 0, 0, 0, 0, 0, 0, 0, -}; - -int16_t mg_knight_table[64] = { - -167, -89, -34, -49, 61, -97, -15, -107, - -73, -41, 72, 36, 23, 62, 7, -17, - -47, 60, 37, 65, 84, 129, 73, 44, - -9, 17, 19, 53, 37, 69, 18, 22, - -13, 4, 16, 13, 28, 19, 21, -8, - -23, -9, 12, 10, 19, 17, 25, -16, - -29, -53, -12, -3, -1, 18, -14, -19, - -105, -21, -58, -33, -17, -28, -19, -23, -}; - -int16_t eg_knight_table[64] = { - -58, -38, -13, -28, -31, -27, -63, -99, - -25, -8, -25, -2, -9, -25, -24, -52, - -24, -20, 10, 9, -1, -9, -19, -41, - -17, 3, 22, 22, 22, 11, 8, -18, - -18, -6, 16, 25, 16, 17, 4, -18, - -23, -3, -1, 15, 10, -3, -20, -22, - -42, -20, -10, -5, -2, -20, -23, -44, - -29, -51, -23, -15, -22, -18, -50, -64, -}; - -int16_t mg_bishop_table[64] = { - -29, 4, -82, -37, -25, -42, 7, -8, - -26, 16, -18, -13, 30, 59, 18, -47, - -16, 37, 43, 40, 35, 50, 37, -2, - -4, 5, 19, 50, 37, 37, 7, -2, - -6, 13, 13, 26, 34, 12, 10, 4, - 0, 15, 15, 15, 14, 27, 18, 10, - 4, 15, 16, 0, 7, 21, 33, 1, - -33, -3, -14, -21, -13, -12, -39, -21, -}; - -int16_t eg_bishop_table[64] = { - -14, -21, -11, -8, -7, -9, -17, -24, - -8, -4, 7, -12, -3, -13, -4, -14, - 2, -8, 0, -1, -2, 6, 0, 4, - -3, 9, 12, 9, 14, 10, 3, 2, - -6, 3, 13, 19, 7, 10, -3, -9, - -12, -3, 8, 10, 13, 3, -7, -15, - -14, -18, -7, -1, 4, -9, -15, -27, - -23, -9, -23, -5, -9, -16, -5, -17, -}; - -int16_t mg_rook_table[64] = { - 32, 42, 32, 51, 63, 9, 31, 43, - 27, 32, 58, 62, 80, 67, 26, 44, - -5, 19, 26, 36, 17, 45, 61, 16, - -24, -11, 7, 26, 24, 35, -8, -20, - -36, -26, -12, -1, 9, -7, 6, -23, - -45, -25, -16, -17, 3, 0, -5, -33, - -44, -16, -20, -9, -1, 11, -6, -71, - -19, -13, 1, 17, 16, 7, -37, -26, -}; - -int16_t eg_rook_table[64] = { - 13, 10, 18, 15, 12, 12, 8, 5, - 11, 13, 13, 11, -3, 3, 8, 3, - 7, 7, 7, 5, 4, -3, -5, -3, - 4, 3, 13, 1, 2, 1, -1, 2, - 3, 5, 8, 4, -5, -6, -8, -11, - -4, 0, -5, -1, -7, -12, -8, -16, - -6, -6, 0, 2, -9, -9, -11, -3, - -9, 2, 3, -1, -5, -13, 4, -20, -}; - -int16_t mg_queen_table[64] = { - -28, 0, 29, 12, 59, 44, 43, 45, - -24, -39, -5, 1, -16, 57, 28, 54, - -13, -17, 7, 8, 29, 56, 47, 57, - -27, -27, -16, -16, -1, 17, -2, 1, - -9, -26, -9, -10, -2, -4, 3, -3, - -14, 2, -11, -2, -5, 2, 14, 5, - -35, -8, 11, 2, 8, 15, -3, 1, - -1, -18, -9, 10, -15, -25, -31, -50, -}; - -int16_t eg_queen_table[64] = { - -9, 22, 22, 27, 27, 19, 10, 20, - -17, 20, 32, 41, 58, 25, 30, 0, - -20, 6, 9, 49, 47, 35, 19, 9, - 3, 22, 24, 45, 57, 40, 57, 36, - -18, 28, 19, 47, 31, 34, 39, 23, - -16, -27, 15, 6, 9, 17, 10, 5, - -22, -23, -30, -16, -16, -23, -36, -32, - -33, -28, -22, -43, -5, -32, -20, -41, -}; - -int16_t mg_king_table[64] = { - -65, 23, 16, -15, -56, -34, 2, 13, - 29, -1, -20, -7, -8, -4, -38, -29, - -9, 24, 2, -16, -20, 6, 22, -22, - -17, -20, -12, -27, -30, -25, -14, -36, - -49, -1, -27, -39, -46, -44, -33, -51, - -14, -14, -22, -46, -44, -30, -15, -27, - 1, 7, -8, -64, -43, -16, 9, 8, - -15, 36, 12, -54, 8, -28, 24, 14, -}; - -int16_t eg_king_table[64] = { - -74, -35, -18, -18, -11, 15, 4, -17, - -12, 17, 14, 17, 17, 38, 23, 11, - 10, 17, 23, 15, 20, 45, 44, 13, - -8, 22, 24, 27, 26, 33, 26, 3, - -18, -4, 21, 24, 27, 23, 9, -11, - -19, -3, 11, 21, 23, 16, 7, -9, - -27, -11, 4, 13, 14, 4, -5, -17, - -53, -34, -21, -11, -28, -14, -24, -43 -}; - -int16_t* mg_pesto_table[6] = -{ - mg_pawn_table, - mg_knight_table, - mg_bishop_table, - mg_rook_table, - mg_queen_table, - mg_king_table -}; - -int16_t* eg_pesto_table[6] = -{ - eg_pawn_table, - eg_knight_table, - eg_bishop_table, - eg_rook_table, - eg_queen_table, - eg_king_table -}; - -// clang-format on -// 2. Evaluation functions -// 2.1. Phase -Evaluation int phase(const chess::Board &pos) -{ - constexpr int KnightPhase = 1; - constexpr int BishopPhase = 1; - constexpr int RookPhase = 2; - constexpr int QueenPhase = 4; - constexpr int TotalPhase = KnightPhase * 4 + BishopPhase * 4 + RookPhase * 4 + QueenPhase * 2; - - int phase = (pos.pieces(chess::PieceType::KNIGHT, chess::Color::WHITE).count() + - pos.pieces(chess::PieceType::KNIGHT, chess::Color::BLACK).count()) * - KnightPhase + - (pos.pieces(chess::PieceType::BISHOP, chess::Color::WHITE).count() + - pos.pieces(chess::PieceType::BISHOP, chess::Color::BLACK).count()) * - BishopPhase + - (pos.pieces(chess::PieceType::ROOK, chess::Color::WHITE).count() + - pos.pieces(chess::PieceType::ROOK, chess::Color::BLACK).count()) * - RookPhase + - (pos.pieces(chess::PieceType::QUEEN, chess::Color::WHITE).count() + - pos.pieces(chess::PieceType::QUEEN, chess::Color::BLACK).count()) * - QueenPhase; - - return wEval.phase = (phase * 256 + TotalPhase / 2) / TotalPhase; -} -// 2.2. Material (one-line) (all phases) -AllPhases inline int16_t material(const chess::Board &pos) -{ // Precompute piece counts (avoid multiple function calls) - int pieceCount[10] = { - pos.pieces(chess::PieceType::PAWN, chess::Color::WHITE).count(), - pos.pieces(chess::PieceType::KNIGHT, chess::Color::WHITE).count(), - pos.pieces(chess::PieceType::BISHOP, chess::Color::WHITE).count(), - pos.pieces(chess::PieceType::ROOK, chess::Color::WHITE).count(), - pos.pieces(chess::PieceType::QUEEN, chess::Color::WHITE).count(), - pos.pieces(chess::PieceType::PAWN, chess::Color::BLACK).count(), - pos.pieces(chess::PieceType::KNIGHT, chess::Color::BLACK).count(), - pos.pieces(chess::PieceType::BISHOP, chess::Color::BLACK).count(), - pos.pieces(chess::PieceType::ROOK, chess::Color::BLACK).count(), - pos.pieces(chess::PieceType::QUEEN, chess::Color::BLACK).count(), - }; - wEval.Material = pieceCount[0] * PawnValue + pieceCount[1] * KnightValue + pieceCount[2] * BishopValue + - pieceCount[3] * RookValue + pieceCount[4] * QueenValue; - bEval.Material = pieceCount[5] * PawnValue + pieceCount[6] * KnightValue + pieceCount[7] * BishopValue + - pieceCount[8] * RookValue + pieceCount[9] * QueenValue; - return wEval.Material - bEval.Material; -} -// 2.3. Pawn structure -// 2.3.1. Doubled pawns -Middlegame int16_t doubled(const chess::Board &pos) -{ - wEval.Doubled = bEval.Doubled = 0; - chess::Bitboard P = pos.pieces(chess::PieceType::PAWN, chess::Color::WHITE), - p = pos.pieces(chess::PieceType::PAWN, chess::Color::BLACK); - // We use bitwise tricks to avoid conditionals - for (int i = 0; i < 8; ++i) - { - chess::Bitboard white = P & chess::attacks::MASK_FILE[i], black = p & chess::attacks::MASK_FILE[i]; - if (white.count() > 1) - wEval.Doubled += (white.count() - 1) * Doubled; - if (black.count() > 1) - bEval.Doubled += (black.count() - 1) * Doubled; - } - return wEval.Doubled - bEval.Doubled; -} -// 2.3.2. Isolated pawns -int16_t isolated(const chess::Board &pos) -{ - chess::Bitboard P = pos.pieces(chess::PieceType::PAWN, chess::Color::WHITE); - chess::Bitboard p = pos.pieces(chess::PieceType::PAWN, chess::Color::BLACK); - - constexpr chess::Bitboard noAFile = 0xFEFEFEFEFEFEFEFEULL; // mask out file A - constexpr chess::Bitboard noHFile = 0x7F7F7F7F7F7F7F7FULL; // mask out file H - - chess::Bitboard adjP = ((P & noHFile) << 1) | ((P & noAFile) >> 1); - chess::Bitboard adjp = ((p & noHFile) << 1) | ((p & noAFile) >> 1); - - chess::Bitboard Pi = P & ~adjP; - chess::Bitboard pi = p & ~adjp; - - wEval.Isolated = Pi.count() * Isolated; - bEval.Isolated = pi.count() * Isolated; - - return wEval.Isolated - bEval.Isolated; -} - -// 2.3.3. Backward (https://www.chessprogramming.org/Backward_Pawns_(Bitboards)) -Middlegame inline int16_t backward(const chess::Board &pos) -{ - chess::Bitboard P = pos.pieces(chess::PieceType::PAWN, chess::Color::WHITE), - p = pos.pieces(chess::PieceType::PAWN, chess::Color::BLACK), wstop = P << 8, bstop = p << 8, - wAttacks = chess::attacks::pawnLeftAttacks(P) | - chess::attacks::pawnRightAttacks(P), - bAttacks = chess::attacks::pawnLeftAttacks(p) | - chess::attacks::pawnRightAttacks(p), - wback = (wstop & bAttacks & ~wAttacks) >> 8, bback = (bstop & wAttacks & ~bAttacks) >> 8; - wEval.Backward = wback.count() * Backward; - bEval.Backward = bback.count() * Backward; - return wEval.Backward - bEval.Backward; -} -// 2.3.4. Passed pawns (including candidate ones) (considered in both middlegame and endgame) - -AllPhases int16_t passed(const chess::Board &pos) -{ - chess::Bitboard whitePawns = pos.pieces(chess::PieceType::PAWN, chess::Color::WHITE); - chess::Bitboard blackPawns = pos.pieces(chess::PieceType::PAWN, chess::Color::BLACK); - - auto compute_score = [](chess::Bitboard P, chess::Bitboard p, bool isWhite) -> int16_t - { - chess::Bitboard mask = p; - mask |= (p << 1 | p >> 1) & 0x6F6F6F6F6F6F6F6F; // ~FILE_A&~FILE_H - - if (isWhite) - { - mask |= (mask << 8); - mask |= (mask << 16); - mask |= (mask << 32); - } - else - { - mask |= (mask >> 8); - mask |= (mask >> 16); - mask |= (mask >> 32); - } - - chess::Bitboard passed = P & ~mask; - chess::Bitboard candidate = P & mask; - - return wpassed * passed.count() + cpassed * candidate.count(); - }; - - wEval.Passed = compute_score(whitePawns, blackPawns, true); - bEval.Passed = compute_score(blackPawns, whitePawns, false); - - return wEval.Passed - bEval.Passed; -} - -Middlegame int16_t center(const chess::Board &pos) -{ - const chess::Square centerSquares[] = {chess::Square::SQ_E4, chess::Square::SQ_D4, chess::Square::SQ_E5, - chess::Square::SQ_D5}; - - int w = 0, b = 0; - - for (chess::Square sq : centerSquares) - { - // Count attackers - w += chess::attacks::attackers(pos, chess::Color::WHITE, sq).count(); - b += chess::attacks::attackers(pos, chess::Color::BLACK, sq).count(); - auto p = pos.at(sq); - if (p.color() == chess::Color::WHITE) - ++w; - if (p.color() == chess::Color::BLACK) - ++b; - } - - wEval.Center = w * Center; - bEval.Center = b * Center; - return wEval.Center - bEval.Center; -} -/* Pawn cache */ -struct PawnEntry -{ - uint64_t key; // Full pawn hash key (Zobrist) - int16_t evalWhite; // Pawn eval score for White - int16_t evalBlack; // Pawn eval score for Black -}; - -std::vector pawnHashTable(1 << 17); -template -AllPhases int pawn(const chess::Board &pos) -{ - if (trace) - { - return doubled(pos) + isolated(pos) + backward(pos) + center(pos); - } - else - { - // what? Nah. - auto a = pos.hash(); - auto entry = pawnHashTable[a & 131071]; - if (entry.key == a) - return entry.evalWhite - entry.evalBlack; - else - { - int16_t score = doubled(pos) + isolated(pos) + backward(pos) + center(pos); - PawnEntry entry = {a, - (int16_t)(wEval.Doubled + wEval.Isolated + wEval.Backward + wEval.Center), - (int16_t)(bEval.Doubled + bEval.Isolated + bEval.Backward + bEval.Center)}; - pawnHashTable[a & 131071] = entry; - return score; - } - } -} -// 2.4. Mobility -AllPhases int16_t mobility(const chess::Board &board) -{ - using namespace chess; - Bitboard occupied = board.occ(); - int mobilityScore[2] = {0, 0}; - const Color sides[2] = {Color::WHITE, Color::BLACK}; - - // Lookup table for attack functions per piece type - auto attackFuncs = [](Square sq, Bitboard occ, PieceType pt) -> Bitboard - { - switch (pt) - { - case (int)PieceType::KNIGHT: - return attacks::knight(sq); - case (int)PieceType::BISHOP: - return attacks::bishop(sq, occ); - case (int)PieceType::ROOK: - return attacks::rook(sq, occ); - case (int)PieceType::QUEEN: - return attacks::queen(sq, occ); - default: - return 0; - } - }; - - for (Color side : sides) - { - Bitboard us = board.us(side); - - for (PieceType pt : {PieceType::KNIGHT, PieceType::BISHOP, PieceType::ROOK, PieceType::QUEEN}) - { - int ptIndex = static_cast(pt); - Bitboard pieces = board.pieces(pt) & us; - while (pieces) - { - Square sq = pieces.pop(); - Bitboard attacks = attackFuncs(sq, occupied, pt); - attacks &= ~us; // remove friendly squares - mobilityScore[static_cast(side)] += MOBILITY_WEIGHTS[ptIndex] * attacks.count(); - } - } - } - - wEval.Mobility = mobilityScore[0]; - bEval.Mobility = mobilityScore[1]; - return mobilityScore[0] - mobilityScore[1]; -} - -// 2.5. King safety -// 2.5.1. Pawn shield -Middlegame int16_t pawn_shield(const chess::Board &board) -{ - constexpr chess::Bitboard SHIELD = 0x00ff00000000ff00ULL; // rank 2 and 7 for pawn shield - constexpr chess::Bitboard SHIELD2 = 0x0000ff0000ff0000ULL; // rank 3 and 6 for isolated pawns - chess::Bitboard wkingAttacksBB = chess::attacks::king(board.kingSq(chess::Color::WHITE)), - bKingAttacksBB = chess::attacks::king(board.kingSq(chess::Color::BLACK)); - int wshieldCount1 = (wkingAttacksBB & SHIELD).count(), wshieldCount2 = (wkingAttacksBB & SHIELD2).count(); - int bshieldCount1 = (bKingAttacksBB & SHIELD).count(), bshieldCount2 = (bKingAttacksBB & SHIELD2).count(); - wEval.Shield = (!wshieldCount1 * wshieldCount2 + wshieldCount1) * pawnShield; - bEval.Shield = (!bshieldCount1 * bshieldCount2 + bshieldCount1) * pawnShield; - return wEval.Shield - bEval.Shield; -} - -Middlegame int16_t king_tropism(const chess::Board &board) -{ - using namespace chess; - const Square wKing = board.kingSq(Color::WHITE); - const Square bKing = board.kingSq(Color::BLACK); - - int wScore = 0, bScore = 0; - - // Consider only attacking pieces - for (PieceType pt : {PieceType::QUEEN, PieceType::ROOK, PieceType::BISHOP, PieceType::KNIGHT}) - { - Bitboard whiteAttackers = board.pieces(pt, Color::WHITE); - Bitboard blackAttackers = board.pieces(pt, Color::BLACK); - - while (whiteAttackers) - { - Square sq = whiteAttackers.pop(); - wScore += 7 - chess::Square::value_distance(sq, bKing); // closer = higher danger - } - - while (blackAttackers) - { - Square sq = blackAttackers.pop(); - bScore += 7 - chess::Square::value_distance(sq, wKing); - } - } - - // Scale the result - wEval.KingTropism = wScore * KingTropism; - bEval.KingTropism = bScore * KingTropism; - - return wEval.KingTropism - bEval.KingTropism; -} - -Middlegame inline int16_t king_safety(const chess::Board &board) { return pawn_shield(board) + king_tropism(board); } -// 2.6. Space -AllPhases int space(const chess::Board &board) -{ - int spaceS[2] = {0, 0}; // [WHITE, BLACK] - - // Evaluate space control for both sides - for (chess::Color color : {chess::Color::WHITE, chess::Color::BLACK}) - { - chess::Color opponent = ~color; - - // Space Evaluation: Count controlled squares in the opponent's half - chess::Bitboard my_pawns = board.pieces(chess::PieceType::PAWN, color); - - while (my_pawns) - { - chess::Square sq = my_pawns.pop(); - chess::Bitboard pawn_attacks = chess::attacks::pawn(color, sq); - - while (pawn_attacks) - { - chess::Square target = pawn_attacks.pop(); - // Check if square is in opponent's half - bool in_opponent_half = - (color == chess::Color::WHITE && target >= 32) || (color == chess::Color::BLACK && target < 32); - - if (in_opponent_half && !board.isAttacked(target, opponent) && !board.at(target)) - { - spaceS[static_cast(color)]++; - } - } - } - } - wEval.Space = spaceS[0] * Space; - bEval.Space = spaceS[1] * Space; - return wEval.Space - bEval.Space; -} -// 2.7. Tempo (simple) (all phases) -AllPhases inline int16_t tempo(const chess::Board &board) -{ - return (board.sideToMove() == chess::Color::WHITE) ? Tempo : -Tempo; -} -// 2.8. Endgame -Endgame bool draw(const chess::Board &board) -{ - // KPvK with Rule of the Square - chess::Square wk = board.kingSq(chess::Color::WHITE), bk = board.kingSq(chess::Color::BLACK); - chess::Bitboard p = board.pieces(chess::PieceType::PAWN); - if (!board.hasNonPawnMaterial(chess::Color::WHITE) && !board.hasNonPawnMaterial(chess::Color::BLACK) && p.count() == 1) - { - chess::Square pawn = p.pop(); - auto c = board.at(pawn).color(); - chess::Square promoSq(pawn.file(), chess::Rank::rank(chess::Rank::RANK_8, c)); - if (chess::Square::value_distance((c ? bk : wk), promoSq) <= chess::Square::value_distance(pawn, promoSq)) - return 1; - } - return board.isInsufficientMaterial(); -} -template -AllPhases int16_t psqt_eval(const chess::Board &board) -{ - auto pieces = board.us(chess::Color::WHITE); - - while (pieces) - { - int sq = pieces.pop(); - chess::PieceType piece = board.at(sq); - if constexpr (Midgame) - wEval.MGPSQT += mg_pesto_table[(int)piece][sq]; - else - wEval.EGPSQT += eg_pesto_table[(int)piece][sq]; - } - pieces = board.us(chess::Color::BLACK); - - while (pieces) - { - chess::Square sq = pieces.pop(); - chess::PieceType piece = board.at(sq); - sq.flip(); - if constexpr (Midgame) - bEval.MGPSQT += mg_pesto_table[(int)piece][sq.index()]; - else - bEval.EGPSQT += eg_pesto_table[(int)piece][sq.index()]; - } - if constexpr (Midgame) - return wEval.MGPSQT - bEval.MGPSQT; // Negate for Black - else - return wEval.EGPSQT - bEval.EGPSQT; // Negate for Black -} -// huge table for distances, idk but it gives O(1) access -static const unsigned char chebyshev_distance[64][64] = { - {0, 1, 2, 3, 4, 5, 6, 7, 1, 1, 2, 3, 4, 5, 6, 7, 2, 2, 2, 3, 4, 5, 6, 7, 3, 3, 3, 3, 4, 5, 6, 7, 4, 4, 4, 4, 4, 5, 6, 7, 5, 5, 5, 5, 5, 5, 6, 7, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7}, - {1, 0, 1, 2, 3, 4, 5, 6, 1, 1, 1, 2, 3, 4, 5, 6, 2, 2, 2, 2, 3, 4, 5, 6, 3, 3, 3, 3, 3, 4, 5, 6, 4, 4, 4, 4, 4, 4, 5, 6, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7}, - {2, 1, 0, 1, 2, 3, 4, 5, 2, 1, 1, 1, 2, 3, 4, 5, 2, 2, 2, 2, 2, 3, 4, 5, 3, 3, 3, 3, 3, 3, 4, 5, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7}, - {3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7}, - {4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7}, - {5, 4, 3, 2, 1, 0, 1, 2, 5, 4, 3, 2, 1, 1, 1, 2, 5, 4, 3, 2, 2, 2, 2, 2, 5, 4, 3, 3, 3, 3, 3, 3, 5, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7}, - {6, 5, 4, 3, 2, 1, 0, 1, 6, 5, 4, 3, 2, 1, 1, 1, 6, 5, 4, 3, 2, 2, 2, 2, 6, 5, 4, 3, 3, 3, 3, 3, 6, 5, 4, 4, 4, 4, 4, 4, 6, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7}, - {7, 6, 5, 4, 3, 2, 1, 0, 7, 6, 5, 4, 3, 2, 1, 1, 7, 6, 5, 4, 3, 2, 2, 2, 7, 6, 5, 4, 3, 3, 3, 3, 7, 6, 5, 4, 4, 4, 4, 4, 7, 6, 5, 5, 5, 5, 5, 5, 7, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7}, - {1, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, 1, 1, 2, 3, 4, 5, 6, 7, 2, 2, 2, 3, 4, 5, 6, 7, 3, 3, 3, 3, 4, 5, 6, 7, 4, 4, 4, 4, 4, 5, 6, 7, 5, 5, 5, 5, 5, 5, 6, 7, 6, 6, 6, 6, 6, 6, 6, 7}, - {1, 1, 1, 2, 3, 4, 5, 6, 1, 0, 1, 2, 3, 4, 5, 6, 1, 1, 1, 2, 3, 4, 5, 6, 2, 2, 2, 2, 3, 4, 5, 6, 3, 3, 3, 3, 3, 4, 5, 6, 4, 4, 4, 4, 4, 4, 5, 6, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6}, - {2, 1, 1, 1, 2, 3, 4, 5, 2, 1, 0, 1, 2, 3, 4, 5, 2, 1, 1, 1, 2, 3, 4, 5, 2, 2, 2, 2, 2, 3, 4, 5, 3, 3, 3, 3, 3, 3, 4, 5, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6}, - {3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6}, - {4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6}, - {5, 4, 3, 2, 1, 1, 1, 2, 5, 4, 3, 2, 1, 0, 1, 2, 5, 4, 3, 2, 1, 1, 1, 2, 5, 4, 3, 2, 2, 2, 2, 2, 5, 4, 3, 3, 3, 3, 3, 3, 5, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6}, - {6, 5, 4, 3, 2, 1, 1, 1, 6, 5, 4, 3, 2, 1, 0, 1, 6, 5, 4, 3, 2, 1, 1, 1, 6, 5, 4, 3, 2, 2, 2, 2, 6, 5, 4, 3, 3, 3, 3, 3, 6, 5, 4, 4, 4, 4, 4, 4, 6, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6}, - {7, 6, 5, 4, 3, 2, 1, 1, 7, 6, 5, 4, 3, 2, 1, 0, 7, 6, 5, 4, 3, 2, 1, 1, 7, 6, 5, 4, 3, 2, 2, 2, 7, 6, 5, 4, 3, 3, 3, 3, 7, 6, 5, 4, 4, 4, 4, 4, 7, 6, 5, 5, 5, 5, 5, 5, 7, 6, 6, 6, 6, 6, 6, 6}, - {2, 2, 2, 3, 4, 5, 6, 7, 1, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, 1, 1, 2, 3, 4, 5, 6, 7, 2, 2, 2, 3, 4, 5, 6, 7, 3, 3, 3, 3, 4, 5, 6, 7, 4, 4, 4, 4, 4, 5, 6, 7, 5, 5, 5, 5, 5, 5, 6, 7}, - {2, 2, 2, 2, 3, 4, 5, 6, 1, 1, 1, 2, 3, 4, 5, 6, 1, 0, 1, 2, 3, 4, 5, 6, 1, 1, 1, 2, 3, 4, 5, 6, 2, 2, 2, 2, 3, 4, 5, 6, 3, 3, 3, 3, 3, 4, 5, 6, 4, 4, 4, 4, 4, 4, 5, 6, 5, 5, 5, 5, 5, 5, 5, 6}, - {2, 2, 2, 2, 2, 3, 4, 5, 2, 1, 1, 1, 2, 3, 4, 5, 2, 1, 0, 1, 2, 3, 4, 5, 2, 1, 1, 1, 2, 3, 4, 5, 2, 2, 2, 2, 2, 3, 4, 5, 3, 3, 3, 3, 3, 3, 4, 5, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5}, - {3, 2, 2, 2, 2, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5}, - {4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5}, - {5, 4, 3, 2, 2, 2, 2, 2, 5, 4, 3, 2, 1, 1, 1, 2, 5, 4, 3, 2, 1, 0, 1, 2, 5, 4, 3, 2, 1, 1, 1, 2, 5, 4, 3, 2, 2, 2, 2, 2, 5, 4, 3, 3, 3, 3, 3, 3, 5, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5}, - {6, 5, 4, 3, 2, 2, 2, 2, 6, 5, 4, 3, 2, 1, 1, 1, 6, 5, 4, 3, 2, 1, 0, 1, 6, 5, 4, 3, 2, 1, 1, 1, 6, 5, 4, 3, 2, 2, 2, 2, 6, 5, 4, 3, 3, 3, 3, 3, 6, 5, 4, 4, 4, 4, 4, 4, 6, 5, 5, 5, 5, 5, 5, 5}, - {7, 6, 5, 4, 3, 2, 2, 2, 7, 6, 5, 4, 3, 2, 1, 1, 7, 6, 5, 4, 3, 2, 1, 0, 7, 6, 5, 4, 3, 2, 1, 1, 7, 6, 5, 4, 3, 2, 2, 2, 7, 6, 5, 4, 3, 3, 3, 3, 7, 6, 5, 4, 4, 4, 4, 4, 7, 6, 5, 5, 5, 5, 5, 5}, - {3, 3, 3, 3, 4, 5, 6, 7, 2, 2, 2, 3, 4, 5, 6, 7, 1, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, 1, 1, 2, 3, 4, 5, 6, 7, 2, 2, 2, 3, 4, 5, 6, 7, 3, 3, 3, 3, 4, 5, 6, 7, 4, 4, 4, 4, 4, 5, 6, 7}, - {3, 3, 3, 3, 3, 4, 5, 6, 2, 2, 2, 2, 3, 4, 5, 6, 1, 1, 1, 2, 3, 4, 5, 6, 1, 0, 1, 2, 3, 4, 5, 6, 1, 1, 1, 2, 3, 4, 5, 6, 2, 2, 2, 2, 3, 4, 5, 6, 3, 3, 3, 3, 3, 4, 5, 6, 4, 4, 4, 4, 4, 4, 5, 6}, - {3, 3, 3, 3, 3, 3, 4, 5, 2, 2, 2, 2, 2, 3, 4, 5, 2, 1, 1, 1, 2, 3, 4, 5, 2, 1, 0, 1, 2, 3, 4, 5, 2, 1, 1, 1, 2, 3, 4, 5, 2, 2, 2, 2, 2, 3, 4, 5, 3, 3, 3, 3, 3, 3, 4, 5, 4, 4, 4, 4, 4, 4, 4, 5}, - {3, 3, 3, 3, 3, 3, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4}, - {4, 3, 3, 3, 3, 3, 3, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4}, - {5, 4, 3, 3, 3, 3, 3, 3, 5, 4, 3, 2, 2, 2, 2, 2, 5, 4, 3, 2, 1, 1, 1, 2, 5, 4, 3, 2, 1, 0, 1, 2, 5, 4, 3, 2, 1, 1, 1, 2, 5, 4, 3, 2, 2, 2, 2, 2, 5, 4, 3, 3, 3, 3, 3, 3, 5, 4, 4, 4, 4, 4, 4, 4}, - {6, 5, 4, 3, 3, 3, 3, 3, 6, 5, 4, 3, 2, 2, 2, 2, 6, 5, 4, 3, 2, 1, 1, 1, 6, 5, 4, 3, 2, 1, 0, 1, 6, 5, 4, 3, 2, 1, 1, 1, 6, 5, 4, 3, 2, 2, 2, 2, 6, 5, 4, 3, 3, 3, 3, 3, 6, 5, 4, 4, 4, 4, 4, 4}, - {7, 6, 5, 4, 3, 3, 3, 3, 7, 6, 5, 4, 3, 2, 2, 2, 7, 6, 5, 4, 3, 2, 1, 1, 7, 6, 5, 4, 3, 2, 1, 0, 7, 6, 5, 4, 3, 2, 1, 1, 7, 6, 5, 4, 3, 2, 2, 2, 7, 6, 5, 4, 3, 3, 3, 3, 7, 6, 5, 4, 4, 4, 4, 4}, - {4, 4, 4, 4, 4, 5, 6, 7, 3, 3, 3, 3, 4, 5, 6, 7, 2, 2, 2, 3, 4, 5, 6, 7, 1, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, 1, 1, 2, 3, 4, 5, 6, 7, 2, 2, 2, 3, 4, 5, 6, 7, 3, 3, 3, 3, 4, 5, 6, 7}, - {4, 4, 4, 4, 4, 4, 5, 6, 3, 3, 3, 3, 3, 4, 5, 6, 2, 2, 2, 2, 3, 4, 5, 6, 1, 1, 1, 2, 3, 4, 5, 6, 1, 0, 1, 2, 3, 4, 5, 6, 1, 1, 1, 2, 3, 4, 5, 6, 2, 2, 2, 2, 3, 4, 5, 6, 3, 3, 3, 3, 3, 4, 5, 6}, - {4, 4, 4, 4, 4, 4, 4, 5, 3, 3, 3, 3, 3, 3, 4, 5, 2, 2, 2, 2, 2, 3, 4, 5, 2, 1, 1, 1, 2, 3, 4, 5, 2, 1, 0, 1, 2, 3, 4, 5, 2, 1, 1, 1, 2, 3, 4, 5, 2, 2, 2, 2, 2, 3, 4, 5, 3, 3, 3, 3, 3, 3, 4, 5}, - {4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 3, 3, 3, 3, 3, 3, 4}, - {4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 3, 3, 3, 3, 3, 3}, - {5, 4, 4, 4, 4, 4, 4, 4, 5, 4, 3, 3, 3, 3, 3, 3, 5, 4, 3, 2, 2, 2, 2, 2, 5, 4, 3, 2, 1, 1, 1, 2, 5, 4, 3, 2, 1, 0, 1, 2, 5, 4, 3, 2, 1, 1, 1, 2, 5, 4, 3, 2, 2, 2, 2, 2, 5, 4, 3, 3, 3, 3, 3, 3}, - {6, 5, 4, 4, 4, 4, 4, 4, 6, 5, 4, 3, 3, 3, 3, 3, 6, 5, 4, 3, 2, 2, 2, 2, 6, 5, 4, 3, 2, 1, 1, 1, 6, 5, 4, 3, 2, 1, 0, 1, 6, 5, 4, 3, 2, 1, 1, 1, 6, 5, 4, 3, 2, 2, 2, 2, 6, 5, 4, 3, 3, 3, 3, 3}, - {7, 6, 5, 4, 4, 4, 4, 4, 7, 6, 5, 4, 3, 3, 3, 3, 7, 6, 5, 4, 3, 2, 2, 2, 7, 6, 5, 4, 3, 2, 1, 1, 7, 6, 5, 4, 3, 2, 1, 0, 7, 6, 5, 4, 3, 2, 1, 1, 7, 6, 5, 4, 3, 2, 2, 2, 7, 6, 5, 4, 3, 3, 3, 3}, - {5, 5, 5, 5, 5, 5, 6, 7, 4, 4, 4, 4, 4, 5, 6, 7, 3, 3, 3, 3, 4, 5, 6, 7, 2, 2, 2, 3, 4, 5, 6, 7, 1, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, 1, 1, 2, 3, 4, 5, 6, 7, 2, 2, 2, 3, 4, 5, 6, 7}, - {5, 5, 5, 5, 5, 5, 5, 6, 4, 4, 4, 4, 4, 4, 5, 6, 3, 3, 3, 3, 3, 4, 5, 6, 2, 2, 2, 2, 3, 4, 5, 6, 1, 1, 1, 2, 3, 4, 5, 6, 1, 0, 1, 2, 3, 4, 5, 6, 1, 1, 1, 2, 3, 4, 5, 6, 2, 2, 2, 2, 3, 4, 5, 6}, - {5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 5, 3, 3, 3, 3, 3, 3, 4, 5, 2, 2, 2, 2, 2, 3, 4, 5, 2, 1, 1, 1, 2, 3, 4, 5, 2, 1, 0, 1, 2, 3, 4, 5, 2, 1, 1, 1, 2, 3, 4, 5, 2, 2, 2, 2, 2, 3, 4, 5}, - {5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4}, - {5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 2, 2, 2, 2, 3}, - {5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 5, 4, 3, 3, 3, 3, 3, 3, 5, 4, 3, 2, 2, 2, 2, 2, 5, 4, 3, 2, 1, 1, 1, 2, 5, 4, 3, 2, 1, 0, 1, 2, 5, 4, 3, 2, 1, 1, 1, 2, 5, 4, 3, 2, 2, 2, 2, 2}, - {6, 5, 5, 5, 5, 5, 5, 5, 6, 5, 4, 4, 4, 4, 4, 4, 6, 5, 4, 3, 3, 3, 3, 3, 6, 5, 4, 3, 2, 2, 2, 2, 6, 5, 4, 3, 2, 1, 1, 1, 6, 5, 4, 3, 2, 1, 0, 1, 6, 5, 4, 3, 2, 1, 1, 1, 6, 5, 4, 3, 2, 2, 2, 2}, - {7, 6, 5, 5, 5, 5, 5, 5, 7, 6, 5, 4, 4, 4, 4, 4, 7, 6, 5, 4, 3, 3, 3, 3, 7, 6, 5, 4, 3, 2, 2, 2, 7, 6, 5, 4, 3, 2, 1, 1, 7, 6, 5, 4, 3, 2, 1, 0, 7, 6, 5, 4, 3, 2, 1, 1, 7, 6, 5, 4, 3, 2, 2, 2}, - {6, 6, 6, 6, 6, 6, 6, 7, 5, 5, 5, 5, 5, 5, 6, 7, 4, 4, 4, 4, 4, 5, 6, 7, 3, 3, 3, 3, 4, 5, 6, 7, 2, 2, 2, 3, 4, 5, 6, 7, 1, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, 1, 1, 2, 3, 4, 5, 6, 7}, - {6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 6, 4, 4, 4, 4, 4, 4, 5, 6, 3, 3, 3, 3, 3, 4, 5, 6, 2, 2, 2, 2, 3, 4, 5, 6, 1, 1, 1, 2, 3, 4, 5, 6, 1, 0, 1, 2, 3, 4, 5, 6, 1, 1, 1, 2, 3, 4, 5, 6}, - {6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 5, 3, 3, 3, 3, 3, 3, 4, 5, 2, 2, 2, 2, 2, 3, 4, 5, 2, 1, 1, 1, 2, 3, 4, 5, 2, 1, 0, 1, 2, 3, 4, 5, 2, 1, 1, 1, 2, 3, 4, 5}, - {6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4}, - {6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3}, - {6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 5, 4, 3, 3, 3, 3, 3, 3, 5, 4, 3, 2, 2, 2, 2, 2, 5, 4, 3, 2, 1, 1, 1, 2, 5, 4, 3, 2, 1, 0, 1, 2, 5, 4, 3, 2, 1, 1, 1, 2}, - {6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 6, 5, 4, 4, 4, 4, 4, 4, 6, 5, 4, 3, 3, 3, 3, 3, 6, 5, 4, 3, 2, 2, 2, 2, 6, 5, 4, 3, 2, 1, 1, 1, 6, 5, 4, 3, 2, 1, 0, 1, 6, 5, 4, 3, 2, 1, 1, 1}, - {7, 6, 6, 6, 6, 6, 6, 6, 7, 6, 5, 5, 5, 5, 5, 5, 7, 6, 5, 4, 4, 4, 4, 4, 7, 6, 5, 4, 3, 3, 3, 3, 7, 6, 5, 4, 3, 2, 2, 2, 7, 6, 5, 4, 3, 2, 1, 1, 7, 6, 5, 4, 3, 2, 1, 0, 7, 6, 5, 4, 3, 2, 1, 1}, - {7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 7, 5, 5, 5, 5, 5, 5, 6, 7, 4, 4, 4, 4, 4, 5, 6, 7, 3, 3, 3, 3, 4, 5, 6, 7, 2, 2, 2, 3, 4, 5, 6, 7, 1, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7}, - {7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 6, 4, 4, 4, 4, 4, 4, 5, 6, 3, 3, 3, 3, 3, 4, 5, 6, 2, 2, 2, 2, 3, 4, 5, 6, 1, 1, 1, 2, 3, 4, 5, 6, 1, 0, 1, 2, 3, 4, 5, 6}, - {7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 5, 3, 3, 3, 3, 3, 3, 4, 5, 2, 2, 2, 2, 2, 3, 4, 5, 2, 1, 1, 1, 2, 3, 4, 5, 2, 1, 0, 1, 2, 3, 4, 5}, - {7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4}, - {7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 4, 3, 2, 2, 2, 2, 2, 3, 4, 3, 2, 1, 1, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3}, - {7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 5, 4, 3, 3, 3, 3, 3, 3, 5, 4, 3, 2, 2, 2, 2, 2, 5, 4, 3, 2, 1, 1, 1, 2, 5, 4, 3, 2, 1, 0, 1, 2}, - {7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 5, 6, 5, 4, 4, 4, 4, 4, 4, 6, 5, 4, 3, 3, 3, 3, 3, 6, 5, 4, 3, 2, 2, 2, 2, 6, 5, 4, 3, 2, 1, 1, 1, 6, 5, 4, 3, 2, 1, 0, 1}, - {7, 7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 7, 6, 5, 5, 5, 5, 5, 5, 7, 6, 5, 4, 4, 4, 4, 4, 7, 6, 5, 4, 3, 3, 3, 3, 7, 6, 5, 4, 3, 2, 2, 2, 7, 6, 5, 4, 3, 2, 1, 1, 7, 6, 5, 4, 3, 2, 1, 0} -}; -inline int distance_to_corner(uint8_t sq){ - return std::min({chebyshev_distance[sq][0], // A1 - chebyshev_distance[sq][56], // A8 - chebyshev_distance[sq][7], // H1 - chebyshev_distance[sq][63]});// H8 -} -inline int distance_from_center(uint8_t sq){ - return std::min({chebyshev_distance[sq][34], // E4 - chebyshev_distance[sq][35], // D4 - chebyshev_distance[sq][42], // E5 - chebyshev_distance[sq][43]});// D5 -} -int mopUp(const chess::Board& pos) { - chess::Square whiteKingSq = pos.kingSq(chess::Color::WHITE); - chess::Square blackKingSq = pos.kingSq(chess::Color::BLACK); - - int whiteMaterial = wEval.Material; - int blackMaterial = bEval.Material; - int materialAdvantage = whiteMaterial - blackMaterial; - - // If neither side has a big material advantage, skip mop-up - if (std::abs(materialAdvantage) < 300) return 0; - - if (wEval.phase>64) return 0; - - // Distance to corner for both kings - int whiteCornerDist = distance_to_corner(whiteKingSq.index()); - int blackCornerDist = distance_to_corner(blackKingSq.index()); - - // Distance from center for both kings - int whiteCenterDist = distance_from_center(whiteKingSq.index()); - int blackCenterDist = distance_from_center(blackKingSq.index()); - - // White mop-up score (push black king to corner, bring white king to center) - int whiteMopUp = blackCornerDist * 4 - whiteCenterDist * 3; - - // Black mop-up score (push white king to corner, bring black king to center) - int blackMopUp = whiteCornerDist * 4 - blackCenterDist * 3; - - // Return net mop-up score favoring White (+ means White advantage) - return whiteMopUp - blackMopUp; -} - -// 3. Main evaluation -inline int16_t eg(const chess::Board &pos) -{ - if (draw(pos)) - return 0; - return space(pos) + tempo(pos) + material(pos) + passed(pos) + psqt_eval(pos) + mobility(pos) + king_safety(pos) + mopUp(pos); -} -template -inline int16_t mg(const chess::Board &pos) -{ - return material(pos) + space(pos) + tempo(pos) + mobility(pos) + king_safety(pos) + psqt_eval(pos) + pawn(pos); -} -struct CachedEvalEntry -{ - uint64_t key; // Full pawn hash key (Zobrist) - int16_t eval; -}; - -std::vector evalCache(1 << 17); -template -int16_t eval(const chess::Board &pos) -{ - auto hash = pos.hash(); - auto &entry = evalCache[hash & 131071]; - if constexpr (!trace) - if (entry.key == hash) - return entry.eval; - memset(&wEval, 0, sizeof(wEval)); - memset(&bEval, 0, sizeof(bEval)); - const int phase = ::phase(pos); - const int sign = pos.sideToMove() == chess::Color::WHITE ? 1 : -1; - - int mgScore = mg(pos); - int egScore = eg(pos); - int finalScore = ((mgScore * phase) + (egScore * (256 - phase))) / 256 * sign; - - return finalScore; -} -int16_t eval(const chess::Board &pos) -{ - return eval(pos); -} -void traceEvaluationResults(const char *label = nullptr, int16_t _eval = 0) -{ - if (label) - printf("\n[Trace: %s]\n", label); - - printf("+----------------+-------+-------+\n"); - printf("| Factor | White | Black |\n"); - printf("+----------------+-------+-------+\n"); - printf("| Phase | %13d |\n", wEval.phase); - printf("| Material | %5d | %5d |\n", wEval.Material, bEval.Material); - printf("| Doubled | %5d | %5d |\n", wEval.Doubled, bEval.Doubled); - printf("| Isolated | %5d | %5d |\n", wEval.Isolated, bEval.Isolated); - printf("| Backward | %5d | %5d |\n", wEval.Backward, bEval.Backward); - printf("| Passed | %5d | %5d |\n", wEval.Passed, bEval.Passed); - printf("| King tropism | %5d | %5d |\n", wEval.KingTropism, bEval.KingTropism); - printf("| Center control | %5d | %5d |\n", wEval.Center, bEval.Center); - printf("| Mobility | %5d | %5d |\n", wEval.Mobility, bEval.Mobility); - printf("| Shield | %5d | %5d |\n", wEval.Shield, bEval.Shield); - printf("| Space | %5d | %5d |\n", wEval.Space, bEval.Space); - printf("| MiddlegamePSQT | %5d | %5d |\n", wEval.MGPSQT, bEval.MGPSQT); - printf("| EndgamePSQT | %5d | %5d |\n", wEval.EGPSQT, bEval.EGPSQT); - printf("| Total | %13d |\n", _eval); - printf("+----------------+-------+-------+\n"); -} -void trace(const chess::Board &pos) { traceEvaluationResults("Handcrafted", eval(pos)); } -int16_t piece_value(chess::PieceType p) -{ - switch ((int)p) - { - case (int)chess::PieceType::PAWN: - return PawnValue; - case (int)chess::PieceType::KNIGHT: - return KnightValue; - case (int)chess::PieceType::BISHOP: - return BishopValue; - case (int)chess::PieceType::ROOK: - return RookValue; - case (int)chess::PieceType::QUEEN: - return QueenValue; - default: - return 0; - } -} diff --git a/eval.hpp b/eval.hpp deleted file mode 100644 index 08f98db..0000000 --- a/eval.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once -#include "chess.hpp" -#include -#include - -constexpr int MAX_PLY = 64; -constexpr int MAX_MATE = 32'000; -constexpr int MAX_SCORE_CP = 31'000; -inline int16_t MATE(int i) { return MAX_MATE - i; } -inline int16_t MATE_DISTANCE(int16_t i) { return MAX_MATE - abs(i); } -int16_t eval(const chess::Board &board); -void trace(const chess::Board &board); -int16_t piece_value(chess::PieceType); -// some clarity for documentation -#define Evaluation [[nodiscard]] -#define AllPhases Evaluation -#define Middlegame Evaluation -#define Endgame Evaluation diff --git a/main.cpp b/main.cpp deleted file mode 100644 index 1ec23e2..0000000 --- a/main.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include "chess.hpp" -#include "search.hpp" -#include "ucioptions.hpp" -#include "uci.hpp" -#include -#include // for setbuf -int main() { - // Some quirks competition programming only for flushing buffers - setbuf(stdout, NULL); - UCIOptions::addSpin("Hash", 16, 1, 1024, [](const UCIOptions::Option& opt)->void { - search::tt.resize(std::get(opt.value)); - }); - uci_loop(); - return 0; -} diff --git a/movepick.cpp b/movepick.cpp deleted file mode 100644 index 74452c0..0000000 --- a/movepick.cpp +++ /dev/null @@ -1,185 +0,0 @@ -#include "movepick.hpp" - -namespace movepick -{ - // 1. SEE - - // Return type changed to chess::Square for consistency - chess::Square select_least_valuable(chess::Board &board, chess::Bitboard bb) - { - if (bb.empty()) - return chess::Square::NO_SQ; // Use NO_SQ sentinel for no square - - int least_value = 5000; - chess::Square least_valuable_square = chess::Square::NO_SQ; - - while (bb) - { - int idx = bb.pop(); // idx is int index of bitboard square - chess::Square sq(idx); - int value = piece_value(board.at(sq)); - if (value < least_value) - { - least_value = value; - least_valuable_square = sq; - } - } - return least_valuable_square; - } - - int SEE(chess::Board &board, chess::Move move) - { - constexpr int max_gain_size = MAX_PLY + 1; // To avoid overflow in gain[d + 1] - std::array gain{}; - int d = 0; - chess::Color side = board.sideToMove(); - chess::Bitboard occ = board.occ(); - - chess::Square target_sq = move.to(); - - // Initial gain setup: - if (move.typeOf() & chess::Move::PROMOTION) - { - gain[0] = piece_value(move.promotionType()); - if (board.isCapture(move)) - gain[0] += piece_value(board.at(target_sq)); - } - else if (move.typeOf() == chess::Move::ENPASSANT) - { - // We'll treat it normally below, so just assign captured pawn value now: - gain[0] = piece_value(chess::PieceType::PAWN); - } - else - { - gain[0] = piece_value(board.at(target_sq)); - } - - chess::Square from_sq = move.from(); - - // Remove the moving piece from occupancy: - occ.clear(from_sq.index()); - - // Special case for en passant: remove the captured pawn square here: - if (move.typeOf() == chess::Move::ENPASSANT) - { - chess::Square ep_captured_sq(target_sq.file(), from_sq.rank()); // square behind target - occ.clear(ep_captured_sq.index()); - } - - chess::Square next_attacker_sq = from_sq; - - while (++d < MAX_PLY) - { - side = ~side; - - // Find all attackers of target square from side to move: - chess::Bitboard attackers = chess::attacks::attackers(board, side, target_sq) & occ; - - // Include x-ray attacks for sliding pieces: - chess::Bitboard rook_attacks = chess::attacks::rook(target_sq, occ); - chess::Bitboard bishop_attacks = chess::attacks::bishop(target_sq, occ); - - attackers |= rook_attacks & (board.pieces(chess::PieceType::ROOK, side) | board.pieces(chess::PieceType::QUEEN, side)); - attackers |= bishop_attacks & (board.pieces(chess::PieceType::BISHOP, side) | board.pieces(chess::PieceType::QUEEN, side)); - - if (attackers.empty()) - break; - - next_attacker_sq = select_least_valuable(board, attackers); - if (next_attacker_sq == chess::Square::NO_SQ) - break; - - int captured_value = piece_value(board.at(next_attacker_sq)); - gain[d] = -gain[d - 1] + captured_value; - - if (std::max(-gain[d - 1], gain[d]) < 0) - break; - - occ.clear(next_attacker_sq.index()); - } - - while (--d > 0) - { - gain[d] = std::max(-gain[d + 1], gain[d]); - } - - return gain[0]; - } - - // 2. Killer moves - chess::Move killerMoves[MAX_PLY][2] = {}; - - void updateKillerMoves(chess::Move m, int ply) - { - if (killerMoves[ply][0] != m) - { - killerMoves[ply][1] = killerMoves[ply][0]; - killerMoves[ply][0] = m; - } - } - - // 3. History heuristic - int historyHeuristic[64][64] = {}; // from-square to to-square - - void updateHistoryHeuristic(chess::Move m, int depth) - { - historyHeuristic[m.from().index()][m.to().index()] += depth * depth; - } - - void orderMoves(chess::Board &board, chess::Movelist &moves, chess::Move ttMove, chess::Move pvMove, int ply) - { - for (auto &move : moves) - { - if (move == ttMove) - move.setScore(10000); - else if (move == pvMove) - move.setScore(9000); - else if (board.isCapture(move)) - move.setScore(SEE(board, move)); - else if (move == killerMoves[ply][0]) - move.setScore(8500); - else if (move == killerMoves[ply][1]) - move.setScore(8000); - else - move.setScore(historyHeuristic[move.from().index()][move.to().index()]); - } - // Sort moves by score in descending order - std::stable_sort(moves.begin(), moves.end(), - [](const chess::Move &a, const chess::Move &b) - { return a.score() > b.score(); }); - } - - void qOrderMoves(chess::Board &board, chess::Movelist &moves) - { - // clang-format off - int16_t promotion_table[4][8] = { - {-58, -38, -13, -28, -31, -27, -63, -99}, // N - {-14, -21, -11, -8, -7, -9, -17, -24}, // B - { 13, 10, 18, 15, 12, 12, 8, 5}, // R - { -9, 22, 22, 27, 27, 19, 10, 20}, // Q - }; - // clang-format on - - for (auto &move : moves) - { - if (board.isCapture(move)) // Simple MVV-LVA - { - move.setScore(SEE(board, move)); - } - else if (move.typeOf() & chess::Move::PROMOTION) - { - int promoValue = piece_value(move.promotionType()); - int captureValue = board.isCapture(move) ? piece_value(board.at(move.to())) : 0; - move.setScore(promoValue + captureValue + promotion_table[move.promotionType() - 1][move.to().file()]); - } - else - { - move.setScore(historyHeuristic[move.from().index()][move.to().index()]); - } - } - // Sort moves by score in descending order - std::stable_sort(moves.begin(), moves.end(), - [](const chess::Move &a, const chess::Move &b) - { return a.score() > b.score(); }); - } -} diff --git a/movepick.hpp b/movepick.hpp deleted file mode 100644 index ce46757..0000000 --- a/movepick.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once -#include "chess.hpp" -#include "eval.hpp" -namespace movepick -{ - // Static Exchange Evaluation - evaluates material exchange on a square - int SEE(chess::Board &, chess::Move); - // Updates killer moves table for move ordering - void updateKillerMoves(chess::Move, int); - // Updates history heuristic table for move ordering - void updateHistoryHeuristic(chess::Move, int); - // Orders moves for regular search (with TT move, killers at given ply) - void orderMoves(chess::Board &, chess::Movelist &, chess::Move, chess::Move, int); - // Orders moves for quiescence search (captures/promotions) - void qOrderMoves(chess::Board &, chess::Movelist &); - // Killer moves table: [ply][slot] for move ordering heuristic - extern chess::Move killerMoves[MAX_PLY][2]; -} \ No newline at end of file diff --git a/scripts/.gitattributes b/scripts/.gitattributes new file mode 100644 index 0000000..dfdb8b7 --- /dev/null +++ b/scripts/.gitattributes @@ -0,0 +1 @@ +*.sh text eol=lf diff --git a/scripts/get_native_properties.sh b/scripts/get_native_properties.sh new file mode 100755 index 0000000..773d6c2 --- /dev/null +++ b/scripts/get_native_properties.sh @@ -0,0 +1,159 @@ +#!/bin/sh + +# +# Returns properties of the native system. +# best architecture as supported by the CPU +# filename of the best binary uploaded as an artifact during CI +# + +# Check if all the given flags are present in the CPU flags list +check_flags() { + for flag; do + printf '%s\n' "$flags" | grep -q -w "$flag" || return 1 + done +} + +# Set the CPU flags list +# remove underscores and points from flags, e.g. gcc uses avx512vnni, while some cpuinfo can have avx512_vnni, some systems use sse4_1 others sse4.1 +get_flags() { + flags=$(awk '/^flags[ \t]*:|^Features[ \t]*:/{gsub(/^flags[ \t]*:[ \t]*|^Features[ \t]*:[ \t]*|[_.]/, ""); line=$0} END{print line}' /proc/cpuinfo) +} + +# Check for gcc march "znver1" or "znver2" https://en.wikichip.org/wiki/amd/cpuid +check_znver_1_2() { + vendor_id=$(awk '/^vendor_id/{print $3; exit}' /proc/cpuinfo) + cpu_family=$(awk '/^cpu family/{print $4; exit}' /proc/cpuinfo) + [ "$vendor_id" = "AuthenticAMD" ] && [ "$cpu_family" = "23" ] && znver_1_2=true +} + +# Set the file CPU loongarch64 architecture +set_arch_loongarch64() { + if check_flags 'lasx'; then + true_arch='loongarch64-lasx' + elif check_flags 'lsx'; then + true_arch='lonngarch64-lsx' + else + true_arch='loongarch64' + fi +} + +# Set the file CPU x86_64 architecture +set_arch_x86_64() { + if check_flags 'avx512vnni' 'avx512dq' 'avx512f' 'avx512bw' 'avx512vl'; then + true_arch='x86-64-vnni256' + elif check_flags 'avx512f' 'avx512bw'; then + true_arch='x86-64-avx512' + elif [ -z "${znver_1_2+1}" ] && check_flags 'bmi2'; then + true_arch='x86-64-bmi2' + elif check_flags 'avx2'; then + true_arch='x86-64-avx2' + elif check_flags 'sse41' && check_flags 'popcnt'; then + true_arch='x86-64-sse41-popcnt' + else + true_arch='x86-64' + fi +} + +set_arch_ppc_64() { + if $(grep -q -w "altivec" /proc/cpuinfo); then + power=$(grep -oP -m 1 'cpu\t+: POWER\K\d+' /proc/cpuinfo) + if [ "0$power" -gt 7 ]; then + # VSX started with POWER8 + true_arch='ppc-64-vsx' + else + true_arch='ppc-64-altivec' + fi + else + true_arch='ppc-64' + fi +} + +# Check the system type +uname_s=$(uname -s) +uname_m=$(uname -m) +case $uname_s in + 'Darwin') # Mac OSX system + case $uname_m in + 'arm64') + true_arch='apple-silicon' + file_arch='m1-apple-silicon' + ;; + 'x86_64') + flags=$(sysctl -n machdep.cpu.features machdep.cpu.leaf7_features | tr '\n' ' ' | tr '[:upper:]' '[:lower:]' | tr -d '_.') + set_arch_x86_64 + if [ "$true_arch" = 'x86-64-vnni256' ] || [ "$true_arch" = 'x86-64-avx512' ]; then + file_arch='x86-64-bmi2' + fi + ;; + esac + file_os='macos' + file_ext='tar' + ;; + 'Linux') # Linux system + get_flags + case $uname_m in + 'x86_64') + file_os='ubuntu' + check_znver_1_2 + set_arch_x86_64 + ;; + 'i686') + file_os='ubuntu' + true_arch='x86-32' + ;; + 'ppc64'*) + file_os='ubuntu' + set_arch_ppc_64 + ;; + 'aarch64') + file_os='android' + true_arch='armv8' + if check_flags 'asimddp'; then + true_arch="$true_arch-dotprod" + fi + ;; + 'armv7'*) + file_os='android' + true_arch='armv7' + if check_flags 'neon'; then + true_arch="$true_arch-neon" + fi + ;; + 'loongarch64'*) + file_os='linux' + set_arch_loongarch64 + ;; + *) # Unsupported machine type, exit with error + printf 'Unsupported machine type: %s\n' "$uname_m" + exit 1 + ;; + esac + file_ext='tar' + ;; + 'MINGW'*'ARM64'*) # Windows ARM64 system with POSIX compatibility layer + # TODO: older chips might be armv8, but we have no good way to detect, /proc/cpuinfo shows x86 info + file_os='windows' + true_arch='armv8-dotprod' + file_ext='zip' + ;; + 'CYGWIN'*|'MINGW'*|'MSYS'*) # Windows x86_64system with POSIX compatibility layer + get_flags + check_znver_1_2 + set_arch_x86_64 + file_os='windows' + file_ext='zip' + ;; + *) + # Unknown system type, exit with error + printf 'Unsupported system type: %s\n' "$uname_s" + exit 1 + ;; +esac + +if [ -z "$file_arch" ]; then + file_arch=$true_arch +fi + +file_name="stockfish-$file_os-$file_arch.$file_ext" + +printf '%s %s\n' "$true_arch" "$file_name" diff --git a/scripts/net.sh b/scripts/net.sh new file mode 100755 index 0000000..1aa1fbf --- /dev/null +++ b/scripts/net.sh @@ -0,0 +1,76 @@ +#!/bin/sh + +wget_or_curl=$( (command -v wget > /dev/null 2>&1 && echo "wget -qO-") || \ + (command -v curl > /dev/null 2>&1 && echo "curl -skL")) + + +sha256sum=$( (command -v shasum > /dev/null 2>&1 && echo "shasum -a 256") || \ + (command -v sha256sum > /dev/null 2>&1 && echo "sha256sum")) + +if [ -z "$sha256sum" ]; then + >&2 echo "sha256sum not found, NNUE files will be assumed valid." +fi + +get_nnue_filename() { + grep "$1" evaluate.h | grep "#define" | sed "s/.*\(nn-[a-z0-9]\{12\}.nnue\).*/\1/" +} + +validate_network() { + # If no sha256sum command is available, assume the file is always valid. + if [ -n "$sha256sum" ] && [ -f "$1" ]; then + if [ "$1" != "nn-$($sha256sum "$1" | cut -c 1-12).nnue" ]; then + rm -f "$1" + return 1 + fi + fi +} + +fetch_network() { + _filename="$(get_nnue_filename "$1")" + + if [ -z "$_filename" ]; then + >&2 echo "NNUE file name not found for: $1" + return 1 + fi + + if [ -f "$_filename" ]; then + if validate_network "$_filename"; then + echo "Existing $_filename validated, skipping download" + return + else + echo "Removing invalid NNUE file: $_filename" + fi + fi + + if [ -z "$wget_or_curl" ]; then + >&2 printf "%s\n" "Neither wget or curl is installed." \ + "Install one of these tools to download NNUE files automatically." + exit 1 + fi + + for url in \ + "https://tests.stockfishchess.org/api/nn/$_filename" \ + "https://github.com/official-stockfish/networks/raw/master/$_filename"; do + echo "Downloading from $url ..." + if $wget_or_curl "$url" > "$_filename"; then + if validate_network "$_filename"; then + echo "Successfully validated $_filename" + else + echo "Downloaded $_filename is invalid" + continue + fi + else + echo "Failed to download from $url" + fi + if [ -f "$_filename" ]; then + return + fi + done + + # Download was not successful in the loop, return false. + >&2 echo "Failed to download $_filename" + return 1 +} + +fetch_network EvalFileDefaultNameBig && \ +fetch_network EvalFileDefaultNameSmall diff --git a/search.cpp b/search.cpp deleted file mode 100644 index 7ca18ff..0000000 --- a/search.cpp +++ /dev/null @@ -1,355 +0,0 @@ -#include "search.hpp" -#include "timeman.hpp" -#include -#include -#include -#include -#include - -namespace search -{ - struct PV - { - int8_t cmove; - chess::Move pv[MAX_PLY]; - }; - - constexpr int LMR_BONUS = 2; - std::atomic stop_requested(false); - PV stablePV; - TranspositionTable tt; - int seldepth = 0; - - struct SearchAbortException : public std::exception - { - SearchAbortException() = default; - }; - - inline bool isMateScore(int16_t score) - { - return std::abs(score) > MAX_SCORE_CP; - } - - int16_t adjustMateScore(int16_t score, int ply) - { - return isMateScore(score) ? (score > 0 ? score - ply : score + ply) : score; - } - - int16_t restoreMateScore(int16_t score, int ply) - { - return isMateScore(score) ? (score > 0 ? score + ply : score - ply) : score; - } - - int16_t quiescence(chess::Board &board, int16_t alpha, int16_t beta, int ply) - { - seldepth = std::max(seldepth, ply); - if (timeman::check_time() || stop_requested.load(std::memory_order_relaxed)) - throw SearchAbortException(); - - ++nodes; - - int16_t stand_pat = eval(board); - if (stand_pat >= beta) - return beta; - if (alpha < stand_pat) - alpha = stand_pat; - - chess::Movelist moves; - chess::movegen::legalmoves(moves, board); - if (moves.empty()) - return board.inCheck() ? -MATE(ply) : 0; - - chess::Movelist tacticalMoves; - for (auto mv : moves) - { - if (board.isCapture(mv) || (mv.typeOf() & chess::Move::PROMOTION) || board.givesCheck(mv) != chess::CheckType::NO_CHECK) - tacticalMoves.add(mv); - } - - movepick::qOrderMoves(board, tacticalMoves); - for (auto mv : tacticalMoves) - { - // --- BEGIN SEE FILTER --- - int gain = mv.score(); // Or use precomputed SEE - if (board.isCapture(mv) && gain < 0) - continue; - // --- END SEE FILTER --- - - // --- BEGIN DELTA PRUNING --- - int margin = 50; - if (stand_pat + gain + margin < alpha) - continue; - // --- END DELTA PRUNING --- - - board.makeMove(mv); - int16_t score = -quiescence(board, -beta, -alpha, ply + 1); - board.unmakeMove(mv); - - if (score >= beta) - return beta; - if (score > alpha) - alpha = score; - } - return alpha; - } - - int16_t alpha_beta(chess::Board &board, int depth, int16_t alpha, int16_t beta, int ply, PV *pv, int ext = 0) - { - pv->cmove = 0; - if (timeman::check_time() || stop_requested.load(std::memory_order_relaxed)) - throw SearchAbortException(); - - uint64_t h = board.hash(); - TTEntry *entry = tt.lookup(h); - chess::Move hash_move = entry ? entry->bestMove : chess::Move::NULL_MOVE; - - if (entry && entry->hash == h && entry->depth() >= depth) - { - int16_t score = adjustMateScore(entry->score(), ply); - if (entry->flag() == TTFlag::LOWERBOUND && score >= beta) - return score; - if (entry->flag() == TTFlag::UPPERBOUND && score <= alpha) - return score; - if (entry->flag() == TTFlag::EXACT) - return score; - } - - chess::Movelist moves; - chess::movegen::legalmoves(moves, board); - if (moves.empty()) - return board.inCheck() ? -MATE(ply) : 0; - - if (depth <= 0){ - ++nodes; - return quiescence(board, alpha, beta, ply); - } - - bool in_check = board.inCheck(); - int16_t evalScore = eval(board); - - // --- BEGIN RAZORING --- - if (depth <= 2 && !in_check) - { - if (evalScore + 150 < alpha) - return quiescence(board, alpha, beta, ply); - } - // --- END RAZORING --- - - bool can_do_null = (!in_check) && (depth >= 3); - if (can_do_null) - { - board.makeNullMove(); - int R = depth / 6; - int16_t score = -alpha_beta(board, depth - 1 - R, -beta, -beta + 1, ply + 1, pv); - board.unmakeNullMove(); - if (score >= beta) - { - tt.store(h, chess::Move::NULL_MOVE, restoreMateScore(score, ply), depth, TTFlag::LOWERBOUND); - return score; - } - } - - bool has_hash = (hash_move != chess::Move::NULL_MOVE); - if (!has_hash && depth >= 4) - { - PV temp; - temp.cmove = 0; - alpha_beta(board, depth - 2, alpha, beta, ply, &temp); - TTEntry *iid = tt.lookup(board.hash()); - if (iid && iid->bestMove != chess::Move::NULL_MOVE) - hash_move = iid->bestMove; - } - - movepick::orderMoves(board, moves, hash_move, stablePV.pv[0], ply); - - chess::Move best_move = chess::Move(); - int16_t best_score = -MATE(0); - - for (int i = 0; i < moves.size(); ++i) - { - chess::Move mv = moves[i]; - PV sub_pv; - sub_pv.cmove = 0; - - bool isCap = board.isCapture(mv); - bool do_lmr = (i > 0) && !isCap && !in_check; - bool givesCheck = board.givesCheck(mv) != chess::CheckType::NO_CHECK; - bool isPromo = mv.typeOf() & chess::Move::PROMOTION; - - // --- BEGIN FUTILITY PRUNING --- - if (depth <= 3 && !in_check && !isCap && !isPromo && !givesCheck) - { - const int margin = 100 * depth; - if (evalScore + margin <= alpha) - continue; - } - // --- END FUTILITY PRUNING --- - - // --- BEGIN EXTENSIONS --- - int extension = 0; - if (givesCheck) - extension = 1; - - if (board.at(mv.from()) == chess::PieceType::PAWN && (mv.to().rank() >= chess::Rank::RANK_6)) - { - int rank = board.sideToMove() == chess::Color::WHITE ? (int)mv.to().rank() : 7 - mv.to().rank(); - if (rank >= 5) - extension += 1; - } - - if (moves.size() == 1) - extension += 1; - - if (ext + extension > 16) - extension = 0; - - int new_depth = depth - 1 + extension; - // --- END EXTENSIONS --- - - board.makeMove(mv); - int16_t score; - - if (do_lmr && !extension && depth >= 3) - { - score = -alpha_beta(board, new_depth - LMR_BONUS, -alpha - 1, -alpha, ply + 1, &sub_pv, ext); - if (score > alpha) - score = -alpha_beta(board, new_depth, -beta, -alpha, ply + 1, &sub_pv, ext); - } - else - { - if (i == 0) - score = -alpha_beta(board, new_depth, -beta, -alpha, ply + 1, &sub_pv, ext + extension); - else - { - score = -alpha_beta(board, new_depth, -alpha - 1, -alpha, ply + 1, &sub_pv, ext + extension); - if (score > alpha) - score = -alpha_beta(board, new_depth, -beta, -alpha, ply + 1, &sub_pv, ext + extension); - } - } - - board.unmakeMove(mv); - - if (score > best_score) - { - best_score = score; - best_move = mv; - if (score > alpha) - { - alpha = score; - pv->pv[0] = mv; - std::memcpy(&pv->pv[1], sub_pv.pv, sizeof(chess::Move) * sub_pv.cmove); - pv->cmove = sub_pv.cmove + 1; - } - } - - if (score >= beta) - { - if (!isCap) - { - movepick::updateKillerMoves(mv, ply); - movepick::updateHistoryHeuristic(mv, depth); - } - break; - } - } - - if (best_score != -MATE(0)) - { - TTFlag flag = (best_score >= beta) ? TTFlag::LOWERBOUND - : (best_score > alpha) ? TTFlag::EXACT - : TTFlag::UPPERBOUND; - tt.store(h, best_move, restoreMateScore(best_score, ply), depth, flag); - } - - return best_score; - } - - void run_search(const chess::Board &board, const TimeControl &tc) - { - nodes = 0, seldepth = 0; - stop_requested.store(false, std::memory_order_relaxed); - timeman::setLimits(tc); - - PV child_pv; - child_pv.cmove = 0; - stablePV.cmove = 0; - - chess::Movelist rootMoves; - chess::movegen::legalmoves(rootMoves, board); - - int16_t last_score = 0; - int max_iter = std::min(tc.infinite ? MAX_PLY : tc.depth, (int)MAX_PLY); - - for (int current_depth = 1; current_depth <= max_iter; ++current_depth) - { - chess::Board _board = board; - child_pv.cmove = 0; - int16_t alpha = last_score - 30; - int16_t beta = last_score + 30; - - try - { - last_score = alpha_beta(_board, current_depth, alpha, beta, 0, &child_pv); - - if (last_score <= alpha || last_score >= beta) - { - alpha = -MATE(0); - beta = MATE(0); - last_score = alpha_beta(_board, current_depth, alpha, beta, 0, &child_pv); - } - - stablePV = child_pv; - auto elapsed = std::chrono::duration_cast( - std::chrono::high_resolution_clock::now() - timeman::start_time) - .count(); - - if (isMateScore(last_score)){ - // Confusement: break - printf("info depth %d seldepth %d nodes %" PRIu64 " nps %" PRIu64 " time %u score mate %d pv ", - current_depth, - seldepth, - nodes.load(), - (elapsed == 0 ? 0 : nodes.load() / elapsed * 1000), - elapsed, - last_score > 0 ? (MAX_MATE - last_score) : -(MAX_MATE + last_score)); - } - else - printf("info depth %d seldepth %d nodes %" PRIu64 " nps %" PRIu64 " time %u score cp %d pv ", - current_depth, - seldepth, - nodes.load(), - (elapsed == 0 ? 0 : nodes.load() / elapsed * 1000), - elapsed, - last_score); - - for (int j = 0; j < child_pv.cmove; ++j) - printf("%s ", chess::uci::moveToUci(child_pv.pv[j], board.chess960()).c_str()); - printf("\n"); - - // [DEBUG] - if (isMateScore(last_score)) break; - if (timeman::check_time()) - break; - } - catch (SearchAbortException &) - { - break; - } - } - - if (stablePV.cmove > 0) - { - printf("bestmove %s\n", - chess::uci::moveToUci(stablePV.pv[0], board.chess960()).c_str()); - } - else if (!rootMoves.empty()) - { - printf("bestmove %s\n", - chess::uci::moveToUci(rootMoves[0], board.chess960()).c_str()); - } - else - { - printf("bestmove 0000\n"); - } - } -} diff --git a/search.hpp b/search.hpp deleted file mode 100644 index 0733daf..0000000 --- a/search.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once -#include "chess.hpp" -#include "timeman.hpp" -#include "tt.hpp" -#include "movepick.hpp" -#include "eval.hpp" -#include "ucioptions.hpp" -#include -#include -#include -namespace search -{ - inline std::atomic nodes{0}; - void run_search(const chess::Board& board, const TimeControl& tc); - extern std::atomic stop_requested; - extern TranspositionTable tt; -} diff --git a/src/.depend b/src/.depend new file mode 100644 index 0000000..2dfa530 --- /dev/null +++ b/src/.depend @@ -0,0 +1,105 @@ +half_ka_v2_hm.o: nnue/features/half_ka_v2_hm.cpp \ + nnue/features/half_ka_v2_hm.h nnue/features/../../misc.h \ + nnue/features/../../types.h nnue/features/../nnue_common.h \ + nnue/features/../../misc.h nnue/features/../../bitboard.h \ + nnue/features/../../types.h nnue/features/../../position.h \ + nnue/features/../../chess.hpp \ + nnue/features/../../nnue/nnue_accumulator.h \ + nnue/features/../../nnue/../types.h \ + nnue/features/../../nnue/nnue_architecture.h \ + nnue/features/../../nnue/features/half_ka_v2_hm.h \ + nnue/features/../../nnue/layers/affine_transform.h \ + nnue/features/../../nnue/layers/../nnue_common.h \ + nnue/features/../../nnue/layers/../simd.h \ + nnue/features/../../nnue/layers/../../types.h \ + nnue/features/../../nnue/layers/../nnue_common.h \ + nnue/features/../../nnue/layers/affine_transform_sparse_input.h \ + nnue/features/../../nnue/layers/../../bitboard.h \ + nnue/features/../../nnue/layers/clipped_relu.h \ + nnue/features/../../nnue/layers/sqr_clipped_relu.h \ + nnue/features/../../nnue/nnue_common.h +evaluate.o: evaluate.cpp evaluate.h types.h nnue/network.h \ + nnue/../memory.h nnue/../types.h nnue/nnue_accumulator.h \ + nnue/nnue_architecture.h nnue/features/half_ka_v2_hm.h \ + nnue/features/../../misc.h nnue/features/../../types.h \ + nnue/features/../nnue_common.h nnue/features/../../misc.h \ + nnue/layers/affine_transform.h nnue/layers/../nnue_common.h \ + nnue/layers/../simd.h nnue/layers/../../types.h \ + nnue/layers/../nnue_common.h nnue/layers/affine_transform_sparse_input.h \ + nnue/layers/../../bitboard.h nnue/layers/../../types.h \ + nnue/layers/clipped_relu.h nnue/layers/sqr_clipped_relu.h \ + nnue/nnue_common.h nnue/nnue_feature_transformer.h nnue/../position.h \ + nnue/../chess.hpp nnue/../types.h nnue/../nnue/nnue_accumulator.h \ + nnue/simd.h nnue/nnue_misc.h nnue/nnue_misc.h position.h uci.hpp \ + chess.hpp search.h movepick.hpp timeman.hpp tt.hpp memory.h \ + ucioptions.hpp nnue/nnue_accumulator.h +main.o: main.cpp search.h evaluate.h types.h movepick.hpp chess.hpp \ + position.h nnue/nnue_accumulator.h nnue/../types.h \ + nnue/nnue_architecture.h nnue/features/half_ka_v2_hm.h \ + nnue/features/../../misc.h nnue/features/../../types.h \ + nnue/features/../nnue_common.h nnue/features/../../misc.h \ + nnue/layers/affine_transform.h nnue/layers/../nnue_common.h \ + nnue/layers/../simd.h nnue/layers/../../types.h \ + nnue/layers/../nnue_common.h nnue/layers/affine_transform_sparse_input.h \ + nnue/layers/../../bitboard.h nnue/layers/../../types.h \ + nnue/layers/clipped_relu.h nnue/layers/sqr_clipped_relu.h \ + nnue/nnue_common.h timeman.hpp tt.hpp memory.h nnue/network.h \ + nnue/../memory.h nnue/nnue_accumulator.h nnue/nnue_feature_transformer.h \ + nnue/../position.h nnue/simd.h nnue/nnue_misc.h uci.hpp ucioptions.hpp +search.o: search.cpp search.h evaluate.h types.h movepick.hpp chess.hpp \ + position.h nnue/nnue_accumulator.h nnue/../types.h \ + nnue/nnue_architecture.h nnue/features/half_ka_v2_hm.h \ + nnue/features/../../misc.h nnue/features/../../types.h \ + nnue/features/../nnue_common.h nnue/features/../../misc.h \ + nnue/layers/affine_transform.h nnue/layers/../nnue_common.h \ + nnue/layers/../simd.h nnue/layers/../../types.h \ + nnue/layers/../nnue_common.h nnue/layers/affine_transform_sparse_input.h \ + nnue/layers/../../bitboard.h nnue/layers/../../types.h \ + nnue/layers/clipped_relu.h nnue/layers/sqr_clipped_relu.h \ + nnue/nnue_common.h timeman.hpp tt.hpp memory.h nnue/network.h \ + nnue/../memory.h nnue/nnue_accumulator.h nnue/nnue_feature_transformer.h \ + nnue/../position.h nnue/simd.h nnue/nnue_misc.h +timeman.o: timeman.cpp timeman.hpp types.h +tt.o: tt.cpp tt.hpp chess.hpp types.h memory.h +uci.o: uci.cpp uci.hpp chess.hpp search.h evaluate.h types.h movepick.hpp \ + position.h nnue/nnue_accumulator.h nnue/../types.h \ + nnue/nnue_architecture.h nnue/features/half_ka_v2_hm.h \ + nnue/features/../../misc.h nnue/features/../../types.h \ + nnue/features/../nnue_common.h nnue/features/../../misc.h \ + nnue/layers/affine_transform.h nnue/layers/../nnue_common.h \ + nnue/layers/../simd.h nnue/layers/../../types.h \ + nnue/layers/../nnue_common.h nnue/layers/affine_transform_sparse_input.h \ + nnue/layers/../../bitboard.h nnue/layers/../../types.h \ + nnue/layers/clipped_relu.h nnue/layers/sqr_clipped_relu.h \ + nnue/nnue_common.h timeman.hpp tt.hpp memory.h nnue/network.h \ + nnue/../memory.h nnue/nnue_accumulator.h nnue/nnue_feature_transformer.h \ + nnue/../position.h nnue/simd.h nnue/nnue_misc.h ucioptions.hpp +ucioptions.o: ucioptions.cpp ucioptions.hpp +nnue_accumulator.o: nnue/nnue_accumulator.cpp nnue/nnue_accumulator.h \ + nnue/../types.h nnue/nnue_architecture.h nnue/features/half_ka_v2_hm.h \ + nnue/features/../../misc.h nnue/features/../../types.h \ + nnue/features/../nnue_common.h nnue/features/../../misc.h \ + nnue/layers/affine_transform.h nnue/layers/../nnue_common.h \ + nnue/layers/../simd.h nnue/layers/../../types.h \ + nnue/layers/../nnue_common.h nnue/layers/affine_transform_sparse_input.h \ + nnue/layers/../../bitboard.h nnue/layers/../../types.h \ + nnue/layers/clipped_relu.h nnue/layers/sqr_clipped_relu.h \ + nnue/nnue_common.h nnue/../bitboard.h nnue/../misc.h nnue/../position.h \ + nnue/../chess.hpp nnue/../types.h nnue/../nnue/nnue_accumulator.h \ + nnue/nnue_feature_transformer.h nnue/simd.h +network.o: nnue/network.cpp nnue/network.h nnue/../memory.h \ + nnue/../types.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \ + nnue/features/half_ka_v2_hm.h nnue/features/../../misc.h \ + nnue/features/../../types.h nnue/features/../nnue_common.h \ + nnue/features/../../misc.h nnue/layers/affine_transform.h \ + nnue/layers/../nnue_common.h nnue/layers/../simd.h \ + nnue/layers/../../types.h nnue/layers/../nnue_common.h \ + nnue/layers/affine_transform_sparse_input.h nnue/layers/../../bitboard.h \ + nnue/layers/../../types.h nnue/layers/clipped_relu.h \ + nnue/layers/sqr_clipped_relu.h nnue/nnue_common.h \ + nnue/nnue_feature_transformer.h nnue/../position.h nnue/../chess.hpp \ + nnue/../types.h nnue/../nnue/nnue_accumulator.h nnue/simd.h \ + nnue/nnue_misc.h nnue/../incbin/incbin.h nnue/../evaluate.h \ + nnue/../misc.h +memory.o: memory.cpp memory.h +movepick.o: movepick.cpp movepick.hpp chess.hpp types.h diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..4e18577 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,1128 @@ +#makefile of stockfish, but for cppchess_engine + +#Stockfish, a UCI chess playing engine derived from Glaurung 2.1 +#Copyright(C) 2004 - 2025 The Stockfish developers(see AUTHORS file) +# +#Stockfish is free software : you can redistribute it and / or modify +#it under the terms of the GNU General Public License as published by +#the Free Software Foundation, either version 3 of the License, or +#(at your option) any later version. +# +#Stockfish is distributed in the hope that it will be useful, +#but WITHOUT ANY WARRANTY; without even the implied warranty of +#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the +#GNU General Public License for more details. +# +#You should have received a copy of the GNU General Public License +#along with this program.If not, see < http: //www.gnu.org/licenses/>. + + +### ========================================================================== +### Section 1. General Configuration +### ========================================================================== + +### Establish the operating system name +KERNEL := $(shell uname -s) +ifeq ($(KERNEL),Linux) + OS := $(shell uname -o) +endif + +### Target Windows OS +ifeq ($(OS),Windows_NT) + ifneq ($(COMP),ndk) + target_windows = yes + endif +else ifeq ($(COMP),mingw) + target_windows = yes + ifeq ($(WINE_PATH),) + WINE_PATH := $(shell which wine) + endif +endif + +### Executable name +ifeq ($(target_windows),yes) + EXE = cppchess_engine.exe +else + EXE = cppchess_engine +endif + +### Installation dir definitions +PREFIX = /usr/local +BINDIR = $(PREFIX)/bin + +### Built-in benchmark for pgo-builds +PGOBENCH = $(WINE_PATH) ./$(EXE) bench + +### Source and object files +SRCS = nnue/features/half_ka_v2_hm.cpp evaluate.cpp \ + main.cpp search.cpp timeman.cpp tt.cpp uci.cpp \ + ucioptions.cpp nnue/nnue_accumulator.cpp nnue/network.cpp \ + memory.cpp movepick.cpp + +HEADERS = evaluate.h types.h movepick.hpp nnue/features/half_ka_v2_hm.h misc.h \ + nnue/layers/affine_transform.h nnue/layers/affine_transform_sparse_input.h nnue/layers/clipped_relu.h \ + nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \ + nnue/nnue_common.h nnue/nnue_feature_transformer.h nnue/simd.h position.h search.h \ + timeman.hpp tt.hpp types.h uci.hpp ucioptions.hpp nnue/network.h memory.h + +OBJS = $(SRCS:.cpp=.o) + +### ========================================================================== +### Section 2. High-level Configuration +### ========================================================================== +# +#flag-- - Comp switch -- -Description +#-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +# +#debug = yes / no-- - -DNDEBUG-- - Enable / Disable debug mode +#sanitize = none / < sanitizer>...(-fsanitize) +#-- -(undefined) -- - enable undefined behavior checks +#-- -(thread) -- - enable threading error checks +#-- -(address) -- - enable memory access checks +#-- -... etc... -- - see compiler documentation for supported sanitizers +#optimize = yes / no-- - (-O3 / -fast etc.)-- - Enable / Disable optimizations +#arch = (name)-- - (-arch)-- - Target architecture +#bits = 64 / 32 -- - -DIS_64BIT-- - 64 - / 32 - bit operating system +#prefetch = yes / no-- - -DUSE_PREFETCH-- - Use prefetch asm - instruction +#popcnt = yes / no-- - -DUSE_POPCNT-- - Use popcnt asm - instruction +#pext = yes / no-- - -DUSE_PEXT-- - Use pext x86_64 asm - instruction +#sse = yes / no-- - -msse-- - Use Intel Streaming SIMD Extensions +#mmx = yes / no-- - -mmmx-- - Use Intel MMX instructions +#sse2 = yes / no-- - -msse2-- - Use Intel Streaming SIMD Extensions 2 +#ssse3 = yes / no-- - -mssse3-- - Use Intel Supplemental Streaming SIMD Extensions 3 +#sse41 = yes / no-- - -msse4.1 -- -Use Intel Streaming SIMD Extensions 4.1 +#avx2 = yes / no-- - -mavx2-- - Use Intel Advanced Vector Extensions 2 +#avxvnni = yes / no-- - -mavxvnni-- - Use Intel Vector Neural Network Instructions AVX +#avx512 = yes / no-- - -mavx512bw-- - Use Intel Advanced Vector Extensions 512 +#vnni256 = yes / no-- - -mavx256vnni-- \ + - Use Intel Vector Neural Network Instructions 512 with 256bit operands +#vnni512 = yes / no-- - -mavx512vnni-- - Use Intel Vector Neural Network Instructions 512 +#altivec = yes / no-- - -maltivec-- - Use PowerPC Altivec SIMD extension +#vsx = yes / no-- - -mvsx-- - Use POWER VSX SIMD extension +#neon = yes / no-- - -DUSE_NEON-- - Use ARM SIMD architecture +#dotprod = yes / no-- - -DUSE_NEON_DOTPROD-- - Use ARM advanced SIMD Int8 dot product instructions +#lsx = yes / no-- - -mlsx-- - Use Loongson SIMD eXtension +#lasx = yes / no-- - -mlasx-- - use Loongson Advanced SIMD eXtension +# +#Note that Makefile is space sensitive, so when adding new architectures +# or modifying existing flags, you have to make sure there are no extra spaces +#at the end of the line for flag values. +# +#Example of use for these flags: +#make build ARCH = x86 - 64 - avx512 debug = yes sanitize = "address undefined" + + +### 2.1. General and architecture defaults + +ifeq ($(ARCH),) + ARCH = native +endif + +ifeq ($(ARCH), native) + override ARCH := $(shell $(SHELL) ../scripts/get_native_properties.sh | cut -d " " -f 1) +endif + +#explicitly check for the list of supported architectures(as listed with make help), +#the user can override with `make ARCH = x86 - 32 - vnni256 SUPPORTED_ARCH = true` +ifeq ($(ARCH), $(filter $(ARCH), \ + x86-64-vnni512 x86-64-vnni256 x86-64-avx512 x86-64-avxvnni x86-64-bmi2 \ + x86-64-avx2 x86-64-sse41-popcnt x86-64-modern x86-64-ssse3 x86-64-sse3-popcnt \ + x86-64 x86-32-sse41-popcnt x86-32-sse2 x86-32 ppc-64 ppc-64-altivec ppc-64-vsx ppc-32 e2k \ + armv7 armv7-neon armv8 armv8-dotprod apple-silicon general-64 general-32 riscv64 \ + loongarch64 loongarch64-lsx loongarch64-lasx)) + SUPPORTED_ARCH=true +else + SUPPORTED_ARCH=false +endif + +optimize = yes +debug = no +sanitize = none +bits = 64 +prefetch = no +popcnt = no +pext = no +sse = no +mmx = no +sse2 = no +ssse3 = no +sse41 = no +avx2 = no +avxvnni = no +avx512 = no +vnni256 = no +vnni512 = no +altivec = no +vsx = no +neon = no +dotprod = no +arm_version = 0 +lsx = no +lasx = no +STRIP = strip + +ifneq ($(shell which clang-format-20 2> /dev/null),) + CLANG-FORMAT = clang-format-20 +else + CLANG-FORMAT = clang-format +endif + +### 2.2 Architecture specific + +ifeq ($(findstring x86,$(ARCH)),x86) + +#x86 - 32 / 64 + +ifeq ($(findstring x86-32,$(ARCH)),x86-32) + arch = i386 + bits = 32 + sse = no + mmx = yes +else + arch = x86_64 + sse = yes + sse2 = yes +endif + +ifeq ($(findstring -sse,$(ARCH)),-sse) + sse = yes +endif + +ifeq ($(findstring -popcnt,$(ARCH)),-popcnt) + popcnt = yes +endif + +ifeq ($(findstring -mmx,$(ARCH)),-mmx) + mmx = yes +endif + +ifeq ($(findstring -sse2,$(ARCH)),-sse2) + sse = yes + sse2 = yes +endif + +ifeq ($(findstring -ssse3,$(ARCH)),-ssse3) + sse = yes + sse2 = yes + ssse3 = yes +endif + +ifeq ($(findstring -sse41,$(ARCH)),-sse41) + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes +endif + +ifeq ($(findstring -modern,$(ARCH)),-modern) + $(warning *** ARCH=$(ARCH) is deprecated, defaulting to ARCH=x86-64-sse41-popcnt. Execute `make help` for a list of available architectures. ***) + $(shell sleep 5) + popcnt = yes + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes +endif + +ifeq ($(findstring -avx2,$(ARCH)),-avx2) + popcnt = yes + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes + avx2 = yes +endif + +ifeq ($(findstring -avxvnni,$(ARCH)),-avxvnni) + popcnt = yes + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes + avx2 = yes + avxvnni = yes + pext = yes +endif + +ifeq ($(findstring -bmi2,$(ARCH)),-bmi2) + popcnt = yes + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes + avx2 = yes + pext = yes +endif + +ifeq ($(findstring -avx512,$(ARCH)),-avx512) + popcnt = yes + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes + avx2 = yes + pext = yes + avx512 = yes +endif + +ifeq ($(findstring -vnni256,$(ARCH)),-vnni256) + popcnt = yes + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes + avx2 = yes + pext = yes + vnni256 = yes +endif + +ifeq ($(findstring -vnni512,$(ARCH)),-vnni512) + popcnt = yes + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes + avx2 = yes + pext = yes + avx512 = yes + vnni512 = yes +endif + +ifeq ($(sse),yes) + prefetch = yes +endif + +# 64 - bit pext is not available on x86 - 32 +ifeq ($(bits),32) + pext = no +endif + +else + +#all other architectures + +ifeq ($(ARCH),general-32) + arch = any + bits = 32 +endif + +ifeq ($(ARCH),general-64) + arch = any +endif + +ifeq ($(ARCH),armv7) + arch = armv7 + prefetch = yes + bits = 32 + arm_version = 7 +endif + +ifeq ($(ARCH),armv7-neon) + arch = armv7 + prefetch = yes + popcnt = yes + neon = yes + bits = 32 + arm_version = 7 +endif + +ifeq ($(ARCH),armv8) + arch = armv8 + prefetch = yes + popcnt = yes + neon = yes + arm_version = 8 +endif + +ifeq ($(ARCH),armv8-dotprod) + arch = armv8 + prefetch = yes + popcnt = yes + neon = yes + dotprod = yes + arm_version = 8 +endif + +ifeq ($(ARCH),apple-silicon) + arch = arm64 + prefetch = yes + popcnt = yes + neon = yes + dotprod = yes + arm_version = 8 +endif + +ifeq ($(ARCH),ppc-32) + arch = ppc + bits = 32 +endif + +ifeq ($(ARCH),ppc-64) + arch = ppc64 + popcnt = yes + prefetch = yes +endif + +ifeq ($(ARCH),ppc-64-altivec) + arch = ppc64 + popcnt = yes + prefetch = yes + altivec = yes +endif + +ifeq ($(ARCH),ppc-64-vsx) + arch = ppc64 + popcnt = yes + prefetch = yes + vsx = yes +endif + +ifeq ($(findstring e2k,$(ARCH)),e2k) + arch = e2k + mmx = yes + bits = 64 + sse = yes + sse2 = yes + ssse3 = yes + sse41 = yes + popcnt = yes +endif + +ifeq ($(ARCH),riscv64) + arch = riscv64 +endif + +ifeq ($(findstring loongarch64,$(ARCH)),loongarch64) + arch = loongarch64 + prefetch = yes + +ifeq ($(findstring -lasx,$(ARCH)),-lasx) + lsx = yes + lasx = yes +endif + +ifeq ($(findstring -lsx,$(ARCH)),-lsx) + lsx = yes +endif + +endif +endif + + +### ========================================================================== +### Section 3. Low-level Configuration +### ========================================================================== + +### 3.1 Selecting compiler (default = gcc) +ifeq ($(MAKELEVEL),0) + export ENV_CXXFLAGS := $(CXXFLAGS) + export ENV_DEPENDFLAGS := $(DEPENDFLAGS) + export ENV_LDFLAGS := $(LDFLAGS) +endif + +CXXFLAGS = $(ENV_CXXFLAGS) -Wall -Wcast-qual -fno-exceptions -std=c++17 $(EXTRACXXFLAGS) +DEPENDFLAGS = $(ENV_DEPENDFLAGS) -std=c++17 +LDFLAGS = $(ENV_LDFLAGS) $(EXTRALDFLAGS) + +ifeq ($(COMP),) + COMP=gcc +endif + +ifeq ($(COMP),gcc) + comp=gcc + CXX=g++ + CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-declarations + + ifeq ($(arch),$(filter $(arch),armv7 armv8 riscv64)) + ifeq ($(OS),Android) + CXXFLAGS += -m$(bits) + LDFLAGS += -m$(bits) + endif + ifeq ($(ARCH),riscv64) + CXXFLAGS += -latomic + endif + else ifeq ($(arch),loongarch64) + CXXFLAGS += -latomic + else + CXXFLAGS += -m$(bits) + LDFLAGS += -m$(bits) + endif + + ifeq ($(arch),$(filter $(arch),armv7)) + LDFLAGS += -latomic + endif + + ifneq ($(KERNEL),Darwin) + LDFLAGS += -Wl,--no-as-needed + endif +endif + +ifeq ($(target_windows),yes) + LDFLAGS += -static +endif + +ifeq ($(COMP),mingw) + comp=mingw + + ifeq ($(bits),64) + ifeq ($(shell which x86_64-w64-mingw32-c++-posix 2> /dev/null),) + CXX=x86_64-w64-mingw32-c++ + else + CXX=x86_64-w64-mingw32-c++-posix + endif + else + ifeq ($(shell which i686-w64-mingw32-c++-posix 2> /dev/null),) + CXX=i686-w64-mingw32-c++ + else + CXX=i686-w64-mingw32-c++-posix + endif + endif + CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-declarations +endif + +ifeq ($(COMP),icx) + comp=icx + CXX=icpx + CXXFLAGS += --intel -pedantic -Wextra -Wshadow -Wmissing-prototypes \ + -Wconditional-uninitialized -Wabi -Wdeprecated +endif + +ifeq ($(COMP),clang) + comp=clang + CXX=clang++ + ifeq ($(target_windows),yes) + CXX=x86_64-w64-mingw32-clang++ + endif + + CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-prototypes \ + -Wconditional-uninitialized + + ifeq ($(filter $(KERNEL),Darwin OpenBSD FreeBSD),) + ifeq ($(target_windows),) + ifneq ($(RTLIB),compiler-rt) + LDFLAGS += -latomic + endif + endif + endif + + ifeq ($(arch),$(filter $(arch),armv7 armv8 riscv64)) + ifeq ($(OS),Android) + CXXFLAGS += -m$(bits) + LDFLAGS += -m$(bits) + endif + ifeq ($(ARCH),riscv64) + CXXFLAGS += -latomic + endif + else ifeq ($(arch),loongarch64) + CXXFLAGS += -latomic + else + CXXFLAGS += -m$(bits) + LDFLAGS += -m$(bits) + endif +endif + +ifeq ($(KERNEL),Darwin) + CXXFLAGS += -mmacosx-version-min=10.15 + LDFLAGS += -mmacosx-version-min=10.15 + ifneq ($(arch),any) + CXXFLAGS += -arch $(arch) + LDFLAGS += -arch $(arch) + endif + XCRUN = xcrun +endif + +#To cross - compile for Android, use NDK version r27c or later. +ifeq ($(COMP),ndk) + CXXFLAGS += -stdlib=libc++ + comp=clang + ifeq ($(arch),armv7) + CXX=armv7a-linux-androideabi29-clang++ + CXXFLAGS += -mthumb -march=armv7-a -mfloat-abi=softfp -mfpu=neon + ifneq ($(shell which arm-linux-androideabi-strip 2>/dev/null),) + STRIP=arm-linux-androideabi-strip + else + STRIP=llvm-strip + endif + endif + ifeq ($(arch),armv8) + CXX=aarch64-linux-android29-clang++ + ifneq ($(shell which aarch64-linux-android-strip 2>/dev/null),) + STRIP=aarch64-linux-android-strip + else + STRIP=llvm-strip + endif + endif + ifeq ($(arch),x86_64) + CXX=x86_64-linux-android29-clang++ + ifneq ($(shell which x86_64-linux-android-strip 2>/dev/null),) + STRIP=x86_64-linux-android-strip + else + STRIP=llvm-strip + endif + endif + LDFLAGS += -static-libstdc++ +endif + +### Allow overwriting CXX from command line +ifdef COMPCXX + CXX=$(COMPCXX) +endif + +#llvm - profdata must be version compatible with the specified CXX(be it clang, or the gcc alias) +#make - j profile - build CXX = clang++ - 20 COMP = clang +#Locate the version in the same directory as the compiler used, +#with fallback to a generic one if it can't be located + LLVM_PROFDATA := $(dir $(realpath $(shell which $(CXX) 2> /dev/null)))llvm-profdata +ifeq ($(wildcard $(LLVM_PROFDATA)),) + LLVM_PROFDATA := llvm-profdata +endif + +ifeq ($(comp),icx) + profile_make = icx-profile-make + profile_use = icx-profile-use +else ifeq ($(comp),clang) + profile_make = clang-profile-make + profile_use = clang-profile-use +else + profile_make = gcc-profile-make + profile_use = gcc-profile-use + ifeq ($(KERNEL),Darwin) + EXTRAPROFILEFLAGS = -fvisibility=hidden + endif +endif + +### Sometimes gcc is really clang +ifeq ($(COMP),gcc) + gccversion := $(shell $(CXX) --version 2>/dev/null) + gccisclang := $(findstring clang,$(gccversion)) + ifneq ($(gccisclang),) + profile_make = clang-profile-make + profile_use = clang-profile-use + endif +endif + +### On mingw use Windows threads, otherwise POSIX +ifneq ($(comp),mingw) + CXXFLAGS += -DUSE_PTHREADS +#On Android Bionic's C library comes with its own pthread implementation bundled in + ifneq ($(OS),Android) +#Haiku has pthreads in its libroot, so only link it in on other platforms + ifneq ($(KERNEL),Haiku) + ifneq ($(COMP),ndk) + LDFLAGS += -lpthread + endif + endif + endif +endif + +### 3.2.1 Debugging +ifeq ($(debug),no) + CXXFLAGS += -DNDEBUG +else + CXXFLAGS += -g +endif + +### 3.2.2 Debugging with undefined behavior sanitizers +ifneq ($(sanitize),none) + CXXFLAGS += -g3 $(addprefix -fsanitize=,$(sanitize)) + LDFLAGS += $(addprefix -fsanitize=,$(sanitize)) +endif + +### 3.3 Optimization +ifeq ($(optimize),yes) + + CXXFLAGS += -O3 -funroll-loops + + ifeq ($(comp),gcc) + ifeq ($(OS), Android) + CXXFLAGS += -fno-gcse -mthumb -march=armv7-a -mfloat-abi=softfp + endif + endif + + ifeq ($(KERNEL),Darwin) + ifeq ($(comp),$(filter $(comp),clang icx)) + CXXFLAGS += -mdynamic-no-pic + endif + + ifeq ($(comp),gcc) + ifneq ($(arch),arm64) + CXXFLAGS += -mdynamic-no-pic + endif + endif + endif + + ifeq ($(comp),clang) + clangmajorversion := $(shell $(CXX) -dumpversion 2>/dev/null | cut -f1 -d.) + ifeq ($(shell expr $(clangmajorversion) \< 16),1) + CXXFLAGS += -fexperimental-new-pass-manager + endif + endif +endif + +### 3.4 Bits +ifeq ($(bits),64) + CXXFLAGS += -DIS_64BIT +endif + +### 3.5 prefetch and popcount +ifeq ($(prefetch),yes) + ifeq ($(sse),yes) + CXXFLAGS += -msse + endif +else + CXXFLAGS += -DNO_PREFETCH +endif + +ifeq ($(popcnt),yes) + ifeq ($(arch),$(filter $(arch),ppc64 ppc64-altivec ppc64-vsx armv7 armv8 arm64)) + CXXFLAGS += -DUSE_POPCNT + else + CXXFLAGS += -msse3 -mpopcnt -DUSE_POPCNT + endif +endif + +### 3.6 SIMD architectures +ifeq ($(avx2),yes) + CXXFLAGS += -DUSE_AVX2 + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) + CXXFLAGS += -mavx2 -mbmi + endif +endif + +ifeq ($(avxvnni),yes) + CXXFLAGS += -DUSE_VNNI -DUSE_AVXVNNI + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) + CXXFLAGS += -mavxvnni + endif +endif + +ifeq ($(avx512),yes) + CXXFLAGS += -DUSE_AVX512 + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) + CXXFLAGS += -mavx512f -mavx512bw + endif +endif + +ifeq ($(vnni256),yes) + CXXFLAGS += -DUSE_VNNI + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) + CXXFLAGS += -mavx512f -mavx512bw -mavx512vnni -mavx512dq -mavx512vl -mprefer-vector-width=256 + endif +endif + +ifeq ($(vnni512),yes) + CXXFLAGS += -DUSE_VNNI + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) + CXXFLAGS += -mavx512f -mavx512bw -mavx512vnni -mavx512dq -mavx512vl -mprefer-vector-width=512 + endif +endif + +ifeq ($(sse41),yes) + CXXFLAGS += -DUSE_SSE41 + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) + CXXFLAGS += -msse4.1 + endif +endif + +ifeq ($(ssse3),yes) + CXXFLAGS += -DUSE_SSSE3 + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) + CXXFLAGS += -mssse3 + endif +endif + +ifeq ($(sse2),yes) + CXXFLAGS += -DUSE_SSE2 + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) + CXXFLAGS += -msse2 + endif +endif + +ifeq ($(mmx),yes) + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) + CXXFLAGS += -mmmx + endif +endif + +ifeq ($(altivec),yes) + CXXFLAGS += -maltivec + ifeq ($(COMP),gcc) + CXXFLAGS += -mabi=altivec + endif +endif + +ifeq ($(vsx),yes) + CXXFLAGS += -mvsx + ifeq ($(COMP),gcc) + CXXFLAGS += -DNO_WARN_X86_INTRINSICS -DUSE_SSE2 + endif +endif + +ifeq ($(neon),yes) + CXXFLAGS += -DUSE_NEON=$(arm_version) + ifeq ($(KERNEL),Linux) + ifneq ($(COMP),ndk) + ifneq ($(arch),armv8) + CXXFLAGS += -mfpu=neon + endif + endif + endif +endif + +ifeq ($(dotprod),yes) + CXXFLAGS += -march=armv8.2-a+dotprod -DUSE_NEON_DOTPROD +endif + +ifeq ($(lasx),yes) + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) + CXXFLAGS += -mlasx + endif +endif + +ifeq ($(lsx),yes) + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) + CXXFLAGS += -mlsx + endif +endif + +### 3.7 pext +ifeq ($(pext),yes) + CXXFLAGS += -DUSE_PEXT + ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) + CXXFLAGS += -mbmi2 + endif +endif + +### 3.8.1 Try to include git commit sha for versioning +GIT_SHA := $(shell git rev-parse HEAD 2>/dev/null | cut -c 1-8) +ifneq ($(GIT_SHA), ) + CXXFLAGS += -DGIT_SHA=$(GIT_SHA) +endif + +### 3.8.2 Try to include git commit date for versioning +GIT_DATE := $(shell git show -s --date=format:'%Y%m%d' --format=%cd HEAD 2>/dev/null) +ifneq ($(GIT_DATE), ) + CXXFLAGS += -DGIT_DATE=$(GIT_DATE) +endif + +### 3.8.3 Try to include architecture +ifneq ($(ARCH), ) + CXXFLAGS += -DARCH=$(ARCH) +endif + +### 3.9 Link Time Optimization +### This is a mix of compile and link time options because the lto link phase +### needs access to the optimization flags. +ifeq ($(optimize),yes) +ifeq ($(debug), no) + ifeq ($(comp),$(filter $(comp),clang icx)) + CXXFLAGS += -flto=full + ifeq ($(comp),icx) + CXXFLAGS += -fwhole-program-vtables + endif + ifeq ($(target_windows),yes) + CXXFLAGS += -fuse-ld=lld + endif + LDFLAGS += $(CXXFLAGS) + +#GCC and CLANG use different methods for parallelizing LTO and CLANG pretends to be +#GCC on some systems. + else ifeq ($(comp),gcc) + ifeq ($(gccisclang),) + CXXFLAGS += -flto -flto-partition=one + LDFLAGS += $(CXXFLAGS) -flto=jobserver + else + CXXFLAGS += -flto=full + LDFLAGS += $(CXXFLAGS) + endif + +#To use LTO and static linking on Windows, +#the tool chain requires gcc version 10.1 or later. + else ifeq ($(comp),mingw) + CXXFLAGS += -flto -flto-partition=one + LDFLAGS += $(CXXFLAGS) -save-temps + endif +endif +endif + +### 3.10 Android 5 can only run position independent executables. Note that this +### breaks Android 4.0 and earlier. +ifeq ($(OS), Android) + CXXFLAGS += -fPIE + LDFLAGS += -fPIE -pie +endif + +### ========================================================================== +### Section 4. Public Targets +### ========================================================================== + +help: + @echo "" && \ + echo "To compile cppchess_engine, type: " && \ + echo "" && \ + echo "make -j target [ARCH=arch] [COMP=compiler] [COMPCXX=cxx]" && \ + echo "" && \ + echo "Supported targets:" && \ + echo "" && \ + echo "help > Display architecture details" && \ + echo "profile-build > standard build with profile-guided optimization" && \ + echo "build > skip profile-guided optimization" && \ + echo "net > Download the default nnue nets" && \ + echo "strip > Strip executable" && \ + echo "install > Install executable" && \ + echo "clean > Clean up" && \ + echo "" && \ + echo "Supported archs:" && \ + echo "" && \ + echo "native > select the best architecture for the host processor (default)" && \ + echo "x86-64-vnni512 > x86 64-bit with vnni 512bit support" && \ + echo "x86-64-vnni256 > x86 64-bit with vnni 512bit support, limit operands to 256bit wide" && \ + echo "x86-64-avx512 > x86 64-bit with avx512 support" && \ + echo "x86-64-avxvnni > x86 64-bit with vnni 256bit support" && \ + echo "x86-64-bmi2 > x86 64-bit with bmi2 support" && \ + echo "x86-64-avx2 > x86 64-bit with avx2 support" && \ + echo "x86-64-sse41-popcnt > x86 64-bit with sse41 and popcnt support" && \ + echo "x86-64-modern > deprecated, currently x86-64-sse41-popcnt" && \ + echo "x86-64-ssse3 > x86 64-bit with ssse3 support" && \ + echo "x86-64-sse3-popcnt > x86 64-bit with sse3 compile and popcnt support" && \ + echo "x86-64 > x86 64-bit generic (with sse2 support)" && \ + echo "x86-32-sse41-popcnt > x86 32-bit with sse41 and popcnt support" && \ + echo "x86-32-sse2 > x86 32-bit with sse2 support" && \ + echo "x86-32 > x86 32-bit generic (with mmx compile support)" && \ + echo "ppc-64 > PPC 64-bit" && \ + echo "ppc-64-altivec > PPC 64-bit with altivec support" && \ + echo "ppc-64-vsx > PPC 64-bit with vsx support" && \ + echo "ppc-32 > PPC 32-bit" && \ + echo "armv7 > ARMv7 32-bit" && \ + echo "armv7-neon > ARMv7 32-bit with popcnt and neon" && \ + echo "armv8 > ARMv8 64-bit with popcnt and neon" && \ + echo "armv8-dotprod > ARMv8 64-bit with popcnt, neon and dot product support" && \ + echo "e2k > Elbrus 2000" && \ + echo "apple-silicon > Apple silicon ARM64" && \ + echo "general-64 > unspecified 64-bit" && \ + echo "general-32 > unspecified 32-bit" && \ + echo "riscv64 > RISC-V 64-bit" && \ + echo "loongarch64 > LoongArch 64-bit" && \ + echo "loongarch64-lsx > LoongArch 64-bit with SIMD eXtension" && \ + echo "loongarch64-lasx > LoongArch 64-bit with Advanced SIMD eXtension" && \ + echo "" && \ + echo "Supported compilers:" && \ + echo "" && \ + echo "gcc > GNU compiler (default)" && \ + echo "mingw > GNU compiler with MinGW under Windows" && \ + echo "clang > LLVM Clang compiler" && \ + echo "icx > Intel oneAPI DPC++/C++ Compiler" && \ + echo "ndk > Google NDK to cross-compile for Android" && \ + echo "" && \ + echo "Simple examples. If you don't know what to do, you likely want to run one of: " && \ + echo "" && \ + echo "make -j profile-build ARCH=x86-64-avx2 # typically a fast compile for common systems " && \ + echo "make -j profile-build ARCH=x86-64-sse41-popcnt # A more portable compile for 64-bit systems " && \ + echo "make -j profile-build ARCH=x86-64 # A portable compile for 64-bit systems " && \ + echo "" && \ + echo "Advanced examples, for experienced users: " && \ + echo "" && \ + echo "make -j profile-build ARCH=x86-64-avxvnni" && \ + echo "make -j profile-build ARCH=x86-64-avxvnni COMP=gcc COMPCXX=g++-12.0" && \ + echo "make -j build ARCH=x86-64-ssse3 COMP=clang" && \ + echo "" +ifneq ($(SUPPORTED_ARCH), true) + @echo "Specify a supported architecture with the ARCH option for more details" + @echo "" +endif + + +.PHONY: help analyze build profile-build strip install clean net \ + objclean profileclean config-sanity \ + icx-profile-use icx-profile-make \ + gcc-profile-use gcc-profile-make \ + clang-profile-use clang-profile-make FORCE \ + format analyze + +analyze: net config-sanity objclean + $(MAKE) -k ARCH=$(ARCH) COMP=$(COMP) $(OBJS) + +build: net config-sanity + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) all + +profile-build: net config-sanity objclean profileclean + @echo "" + @echo "Step 1/4. Building instrumented executable ..." + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_make) + @echo "" + @echo "Step 2/4. Running benchmark for pgo-build ..." + $(PGOBENCH) > PGOBENCH.out 2>&1 + tail -n 4 PGOBENCH.out + @echo "" + @echo "Step 3/4. Building optimized executable ..." + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) objclean + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_use) + @echo "" + @echo "Step 4/4. Deleting profile data ..." + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) profileclean + +strip: + $(STRIP) $(EXE) + +install: + -mkdir -p -m 755 $(BINDIR) + -cp $(EXE) $(BINDIR) + $(STRIP) $(BINDIR)/$(EXE) + +#clean all +clean: objclean profileclean + @rm -f .depend *~ core + +#clean binaries and objects +objclean: + @rm -f cppchess_engine cppchess_engine.exe *.o ./nnue/features/*.o ./nnue/*.o + +# clean auxiliary profiling files +profileclean: + @rm -rf profdir + @rm -f bench.txt *.gcda *.gcno ./nnue/*.gcda ./nnue/features/*.gcda *.s PGOBENCH.out + @rm -f cppchess_engine.profdata *.profraw + @rm -f cppchess_engine.*args* + @rm -f cppchess_engine.*lt* + @rm -f cppchess_engine.res + @rm -f ./-lstdc++.res + +# evaluation network (nnue) +net: + @$(SHELL) ../scripts/net.sh + +format: + $(CLANG-FORMAT) -i $(SRCS) $(HEADERS) -style=file + +# default target +default: + help + +### ========================================================================== +### Section 5. Private Targets +### ========================================================================== + +all: $(EXE) .depend + +config-sanity: net + @echo "" + @echo "Config:" && \ + echo "debug: '$(debug)'" && \ + echo "sanitize: '$(sanitize)'" && \ + echo "optimize: '$(optimize)'" && \ + echo "arch: '$(arch)'" && \ + echo "bits: '$(bits)'" && \ + echo "kernel: '$(KERNEL)'" && \ + echo "os: '$(OS)'" && \ + echo "prefetch: '$(prefetch)'" && \ + echo "popcnt: '$(popcnt)'" && \ + echo "pext: '$(pext)'" && \ + echo "sse: '$(sse)'" && \ + echo "mmx: '$(mmx)'" && \ + echo "sse2: '$(sse2)'" && \ + echo "ssse3: '$(ssse3)'" && \ + echo "sse41: '$(sse41)'" && \ + echo "avx2: '$(avx2)'" && \ + echo "avxvnni: '$(avxvnni)'" && \ + echo "avx512: '$(avx512)'" && \ + echo "vnni256: '$(vnni256)'" && \ + echo "vnni512: '$(vnni512)'" && \ + echo "altivec: '$(altivec)'" && \ + echo "vsx: '$(vsx)'" && \ + echo "neon: '$(neon)'" && \ + echo "dotprod: '$(dotprod)'" && \ + echo "arm_version: '$(arm_version)'" && \ + echo "lsx: '$(lsx)'" && \ + echo "lasx: '$(lasx)'" && \ + echo "target_windows: '$(target_windows)'" && \ + echo "" && \ + echo "Flags:" && \ + echo "CXX: $(CXX)" && \ + echo "CXXFLAGS: $(CXXFLAGS)" && \ + echo "LDFLAGS: $(LDFLAGS)" && \ + echo "" && \ + echo "Testing config sanity. If this fails, try 'make help' ..." && \ + echo "" && \ + (test "$(debug)" = "yes" || test "$(debug)" = "no") && \ + (test "$(optimize)" = "yes" || test "$(optimize)" = "no") && \ + (test "$(SUPPORTED_ARCH)" = "true") && \ + (test "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \ + test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || test "$(arch)" = "e2k" || \ + test "$(arch)" = "armv7" || test "$(arch)" = "armv8" || test "$(arch)" = "arm64" || \ + test "$(arch)" = "riscv64" || test "$(arch)" = "loongarch64") && \ + (test "$(bits)" = "32" || test "$(bits)" = "64") && \ + (test "$(prefetch)" = "yes" || test "$(prefetch)" = "no") && \ + (test "$(popcnt)" = "yes" || test "$(popcnt)" = "no") && \ + (test "$(pext)" = "yes" || test "$(pext)" = "no") && \ + (test "$(sse)" = "yes" || test "$(sse)" = "no") && \ + (test "$(mmx)" = "yes" || test "$(mmx)" = "no") && \ + (test "$(sse2)" = "yes" || test "$(sse2)" = "no") && \ + (test "$(ssse3)" = "yes" || test "$(ssse3)" = "no") && \ + (test "$(sse41)" = "yes" || test "$(sse41)" = "no") && \ + (test "$(avx2)" = "yes" || test "$(avx2)" = "no") && \ + (test "$(avx512)" = "yes" || test "$(avx512)" = "no") && \ + (test "$(vnni256)" = "yes" || test "$(vnni256)" = "no") && \ + (test "$(vnni512)" = "yes" || test "$(vnni512)" = "no") && \ + (test "$(altivec)" = "yes" || test "$(altivec)" = "no") && \ + (test "$(vsx)" = "yes" || test "$(vsx)" = "no") && \ + (test "$(neon)" = "yes" || test "$(neon)" = "no") && \ + (test "$(lsx)" = "yes" || test "$(lsx)" = "no") && \ + (test "$(lasx)" = "yes" || test "$(lasx)" = "no") && \ + (test "$(comp)" = "gcc" || test "$(comp)" = "icx" || test "$(comp)" = "mingw" || \ + test "$(comp)" = "clang" || test "$(comp)" = "armv7a-linux-androideabi16-clang" || \ + test "$(comp)" = "aarch64-linux-android21-clang") + +$(EXE): $(OBJS) + +$(CXX) -o $@ $(OBJS) $(LDFLAGS) + +# Force recompilation to ensure version info is up-to-date +misc.o: FORCE +FORCE: + +clang-profile-make: + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ + EXTRACXXFLAGS='-fprofile-generate ' \ + EXTRALDFLAGS=' -fprofile-generate' \ + all + +clang-profile-use: + $(XCRUN) $(LLVM_PROFDATA) merge -output=cppchess_engine.profdata *.profraw + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ + EXTRACXXFLAGS='-fprofile-use=cppchess_engine.profdata' \ + EXTRALDFLAGS='-fprofile-use ' \ + all + +gcc-profile-make: + @mkdir -p profdir + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ + EXTRACXXFLAGS='-fprofile-generate=profdir' \ + EXTRACXXFLAGS+=$(EXTRAPROFILEFLAGS) \ + EXTRALDFLAGS='-lgcov' \ + all + +gcc-profile-use: + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ + EXTRACXXFLAGS='-fprofile-use=profdir -fno-peel-loops -fno-tracer' \ + EXTRACXXFLAGS+=$(EXTRAPROFILEFLAGS) \ + EXTRALDFLAGS='-lgcov' \ + all + +icx-profile-make: + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ + EXTRACXXFLAGS='-fprofile-instr-generate ' \ + EXTRALDFLAGS=' -fprofile-instr-generate' \ + all + +icx-profile-use: + $(XCRUN) llvm-profdata merge -output=cppchess_engine.profdata *.profraw + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ + EXTRACXXFLAGS='-fprofile-instr-use=cppchess_engine.profdata' \ + EXTRALDFLAGS='-fprofile-use ' \ + all + +.depend: $(SRCS) + -@$(CXX) $(DEPENDFLAGS) -MM $(SRCS) > $@ 2> /dev/null + +ifeq (, $(filter $(MAKECMDGOALS), help strip install clean net objclean profileclean config-sanity)) +-include .depend +endif diff --git a/README.md b/src/README.md similarity index 88% rename from README.md rename to src/README.md index 1841726..d1bf29e 100644 --- a/README.md +++ b/src/README.md @@ -1,4 +1,4 @@ -# cppchess_engine +#cppchess_engine A minimal UCI-compilant (no time control now) chess engine diff --git a/src/bitboard.h b/src/bitboard.h new file mode 100644 index 0000000..d0de72a --- /dev/null +++ b/src/bitboard.h @@ -0,0 +1,141 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef BITBOARD_H_INCLUDED +#define BITBOARD_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include + +#include "types.h" + +namespace Stockfish { + +// Counts the number of non-zero bits in a bitboard. +inline int popcount(Bitboard b) { + +#ifndef USE_POPCNT + + b = b - ((b >> 1) & 0x5555555555555555ULL); + b = (b & 0x3333333333333333ULL) + ((b >> 2) & 0x3333333333333333ULL); + b = (b + (b >> 4)) & 0x0F0F0F0F0F0F0F0FULL; + return (b * 0x0101010101010101ULL) >> 56; + +#elif defined(_MSC_VER) + + return int(_mm_popcnt_u64(b)); + +#else // Assumed gcc or compatible compiler + + return __builtin_popcountll(b); + +#endif +} + +// Returns the least significant bit in a non-zero bitboard. +inline Square lsb(Bitboard b) { + assert(b); + +#if defined(__GNUC__) // GCC, Clang, ICX + + return Square(__builtin_ctzll(b)); + +#elif defined(_MSC_VER) + #ifdef _WIN64 // MSVC, WIN64 + + unsigned long idx; + _BitScanForward64(&idx, b); + return Square(idx); + + #else // MSVC, WIN32 + unsigned long idx; + + if (b & 0xffffffff) + { + _BitScanForward(&idx, int32_t(b)); + return Square(idx); + } + else + { + _BitScanForward(&idx, int32_t(b >> 32)); + return Square(idx + 32); + } + #endif +#else // Compiler is neither GCC nor MSVC compatible + #error "Compiler not supported." +#endif +} + +// Returns the most significant bit in a non-zero bitboard. +inline Square msb(Bitboard b) { + assert(b); + +#if defined(__GNUC__) // GCC, Clang, ICX + + return Square(63 ^ __builtin_clzll(b)); + +#elif defined(_MSC_VER) + #ifdef _WIN64 // MSVC, WIN64 + + unsigned long idx; + _BitScanReverse64(&idx, b); + return Square(idx); + + #else // MSVC, WIN32 + + unsigned long idx; + + if (b >> 32) + { + _BitScanReverse(&idx, int32_t(b >> 32)); + return Square(idx + 32); + } + else + { + _BitScanReverse(&idx, int32_t(b)); + return Square(idx); + } + #endif +#else // Compiler is neither GCC nor MSVC compatible + #error "Compiler not supported." +#endif +} + +// Returns the bitboard of the least significant +// square of a non-zero bitboard. It is equivalent to square_bb(lsb(bb)). +inline Bitboard least_significant_square_bb(Bitboard b) { + assert(b); + return b & -b; +} + +// Finds and clears the least significant bit in a non-zero bitboard. +inline Square pop_lsb(Bitboard& b) { + assert(b); + const Square s = lsb(b); + b &= b - 1; + return s; +} + +} // namespace Stockfish + +#endif // #ifndef BITBOARD_H_INCLUDED diff --git a/chess.hpp b/src/chess.hpp similarity index 60% rename from chess.hpp rename to src/chess.hpp index c2c7698..98ab643 100644 --- a/chess.hpp +++ b/src/chess.hpp @@ -25,12 +25,15 @@ THIS FILE IS AUTO GENERATED DO NOT CHANGE MANUALLY. Source: https://github.com/Disservin/chess-library -VERSION: 0.8.13 +VERSION: 0.8.2 */ #ifndef CHESS_HPP #define CHESS_HPP - +#ifndef NDEBUG + #define NDEBUG + #define CHESS_NO_EXCEPTIONS +#endif #include #include @@ -38,12 +41,12 @@ VERSION: 0.8.13 #include #ifdef CHESS_USE_PEXT -# include + #include #endif #if __cpp_lib_bitops >= 201907L -# include + #include #endif #include #include @@ -52,8 +55,8 @@ VERSION: 0.8.13 #include #if defined(_MSC_VER) -# include -# include + #include + #include #endif @@ -66,15 +69,26 @@ namespace chess { class Color { public: - enum class underlying : std::int8_t { WHITE = 0, BLACK = 1, NONE = -1 }; + enum class underlying : std::int8_t { + WHITE = 0, + BLACK = 1, + NONE = -1 + }; - constexpr Color() : color(underlying::NONE) {} - constexpr Color(underlying c) : color(c) { assert(isValid(static_cast(c))); } - constexpr Color(int c) : Color(static_cast(c)) { assert(isValid(c)); } - constexpr Color(std::string_view str) - : color(str == "w" ? underlying::WHITE - : str == "b" ? underlying::BLACK - : underlying::NONE) {} + constexpr Color() : + color(underlying::NONE) {} + constexpr Color(underlying c) : + color(c) { + assert(isValid(static_cast(c))); + } + constexpr Color(int c) : + Color(static_cast(c)) { + assert(isValid(c)); + } + constexpr Color(std::string_view str) : + color(str == "w" ? underlying::WHITE + : str == "b" ? underlying::BLACK + : underlying::NONE) {} /** * @brief Gets the long string representation of the color @@ -82,13 +96,14 @@ class Color { * "None" for NONE */ [[nodiscard]] std::string longStr() const { - switch (color) { - case underlying::WHITE: - return "White"; - case underlying::BLACK: - return "Black"; - default: - return "None"; + switch (color) + { + case underlying::WHITE : + return "White"; + case underlying::BLACK : + return "Black"; + default : + return "None"; } } @@ -108,8 +123,8 @@ class Color { [[nodiscard]] constexpr underlying internal() const noexcept { return color; } - friend std::ostream& operator<<(std::ostream& os, const Color& color) { - return os << static_cast(color); + friend std::ostream& operator<<(std::ostream& os, const Color& _color) { + return os << static_cast(_color); } static constexpr underlying WHITE = underlying::WHITE; @@ -123,9 +138,9 @@ class Color { }; constexpr Color::underlying operator~(Color::underlying color) { - return color == Color::underlying::WHITE ? Color::underlying::BLACK - : color == Color::underlying::BLACK ? Color::underlying::WHITE - : Color::underlying::NONE; + return color == Color::underlying::WHITE ? Color::underlying::BLACK + : color == Color::underlying::BLACK ? Color::underlying::WHITE + : Color::underlying::NONE; } } // namespace chess @@ -136,12 +151,14 @@ namespace chess { namespace utils { // Split a string by a delimiter -[[nodiscard]] inline std::vector splitString(std::string_view string, const char &delimiter) { +[[nodiscard]] inline std::vector splitString(std::string_view string, + const char& delimiter) { std::vector result; - size_t start = 0; - size_t end = string.find(delimiter); + size_t start = 0; + size_t end = string.find(delimiter); - while (end != std::string_view::npos) { + while (end != std::string_view::npos) + { result.push_back(string.substr(start, end - start)); start = end + 1; end = string.find(delimiter, start); @@ -161,7 +178,7 @@ constexpr char tolower(char c) { return (c >= 'A' && c <= 'Z') ? (c - 'A' + 'a') namespace chess { -#define CHESS_DECLARE_RANK(N) \ +#define CHESS_DECLARE_RANK(N) \ static constexpr auto SQ_A##N = underlying::SQ_A##N; \ static constexpr auto SQ_B##N = underlying::SQ_B##N; \ static constexpr auto SQ_C##N = underlying::SQ_C##N; \ @@ -173,13 +190,29 @@ namespace chess { class File { public: - enum class underlying : std::uint8_t { FILE_A, FILE_B, FILE_C, FILE_D, FILE_E, FILE_F, FILE_G, FILE_H, NO_FILE }; + enum class underlying : std::uint8_t { + FILE_A, + FILE_B, + FILE_C, + FILE_D, + FILE_E, + FILE_F, + FILE_G, + FILE_H, + NO_FILE + }; - constexpr File() : file(underlying::NO_FILE) {} - constexpr File(underlying file) : file(file) {} - constexpr File(int file) : file(static_cast(file)) {} - constexpr File(std::string_view sw) - : file(static_cast(static_cast(utils::tolower(static_cast(sw[0]))) - 'a')) {} + constexpr File() : + file(underlying::NO_FILE) {} + constexpr File(underlying file) : + file(file) {} + constexpr File(int file) : + file(static_cast(file)) {} + constexpr File(std::string_view sw) : + file(sw.empty() + ? underlying::NO_FILE + : static_cast( + static_cast(utils::tolower(static_cast(sw[0]))) - 'a')) {} [[nodiscard]] constexpr underlying internal() const noexcept { return file; } @@ -211,7 +244,9 @@ class File { constexpr operator int() const noexcept { return static_cast(file); } - explicit operator std::string() const { return std::string(1, static_cast(static_cast(file) + 'a')); } + explicit operator std::string() const { + return std::string(1, static_cast(static_cast(file) + 'a')); + } static constexpr underlying FILE_A = underlying::FILE_A; static constexpr underlying FILE_B = underlying::FILE_B; @@ -229,13 +264,27 @@ class File { class Rank { public: - enum class underlying { RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, NO_RANK }; + enum class underlying { + RANK_1, + RANK_2, + RANK_3, + RANK_4, + RANK_5, + RANK_6, + RANK_7, + RANK_8, + NO_RANK + }; - constexpr Rank() : rank_(underlying::NO_RANK) {} - constexpr Rank(underlying rank) : rank_(rank) {} - constexpr Rank(int rank) : rank_(static_cast(rank)) {} - constexpr Rank(std::string_view sw) - : rank_(static_cast(static_cast(utils::tolower(static_cast(sw[0]))) - '1')) {} + constexpr Rank() : + rank_(underlying::NO_RANK) {} + constexpr Rank(underlying rank) : + rank_(rank) {} + constexpr Rank(int rank) : + rank_(static_cast(rank)) {} + constexpr Rank(std::string_view sw) : + rank_(static_cast( + static_cast(utils::tolower(static_cast(sw[0]))) - '1')) {} [[nodiscard]] constexpr underlying internal() const noexcept { return rank_; } @@ -257,11 +306,15 @@ class Rank { return *this; } - operator std::string() const { return std::string(1, static_cast(static_cast(rank_) + '1')); } + operator std::string() const { + return std::string(1, static_cast(static_cast(rank_) + '1')); + } constexpr operator int() const noexcept { return static_cast(rank_); } - [[nodiscard]] constexpr std::uint64_t bb() const noexcept { return 0xffULL << (8 * static_cast(rank_)); } + [[nodiscard]] constexpr std::uint64_t bb() const noexcept { + return 0xffULL << (8 * static_cast(rank_)); + } [[nodiscard]] static constexpr bool back_rank(Rank r, Color color) noexcept { return r == Rank(static_cast(color) * 7); @@ -319,13 +372,21 @@ class Square { #endif - constexpr Square() : sq(underlying::NO_SQ) {} - - constexpr Square(int sq) : sq(static_cast(sq)) { assert(sq <= 64 && sq >= 0); } - constexpr Square(File file, Rank rank) : sq(static_cast(file + rank * 8)) {} - constexpr Square(Rank rank, File file) : sq(static_cast(file + rank * 8)) {} - constexpr Square(underlying sq) : sq(sq) {} - constexpr Square(std::string_view str) : sq(static_cast((str[0] - 'a') + (str[1] - '1') * 8)) { + constexpr Square() : + sq(underlying::NO_SQ) {} + + constexpr Square(int sq) : + sq(static_cast(sq)) { + assert(sq <= 64 && sq >= 0); + } + constexpr Square(File file, Rank rank) : + sq(static_cast(file + rank * 8)) {} + constexpr Square(Rank rank, File file) : + sq(static_cast(file + rank * 8)) {} + constexpr Square(underlying sq) : + sq(sq) {} + constexpr Square(std::string_view str) : + sq(static_cast((str[0] - 'a') + (str[1] - '1') * 8)) { assert(str.size() >= 2); } @@ -402,8 +463,7 @@ class Square { * @brief Check if the square is light. * @return */ - [[nodiscard]] constexpr bool is_light() const noexcept { return (file() + rank()) & 1; - } + [[nodiscard]] constexpr bool is_light() const noexcept { return (file() + rank()) & 1; } /** * @brief Check if the square is dark. @@ -412,10 +472,12 @@ class Square { [[nodiscard]] constexpr bool is_dark() const noexcept { return !is_light(); } /** - * @brief Check if the square is vali.d + * @brief Check if the square is valid * @return */ - [[nodiscard]] constexpr bool is_valid() const noexcept { return static_cast(sq) < 64; } + [[nodiscard]] constexpr bool is_valid() const noexcept { + return static_cast(sq) < 64; + } /** * @brief Check if the square is valid. @@ -508,7 +570,8 @@ class Square { * @param c * @return */ - [[nodiscard]] static constexpr Square castling_king_square(bool is_king_side, Color c) noexcept { + [[nodiscard]] static constexpr Square castling_king_square(bool is_king_side, + Color c) noexcept { return Square(is_king_side ? Square::SQ_G1 : Square::SQ_C1).relative_square(c); } @@ -518,7 +581,8 @@ class Square { * @param c * @return */ - [[nodiscard]] static constexpr Square castling_rook_square(bool is_king_side, Color c) noexcept { + [[nodiscard]] static constexpr Square castling_rook_square(bool is_king_side, + Color c) noexcept { return Square(is_king_side ? Square::SQ_F1 : Square::SQ_D1).relative_square(c); } @@ -555,7 +619,8 @@ enum class Direction : std::int8_t { }; [[nodiscard]] constexpr Direction make_direction(Direction dir, Color c) noexcept { - if (c == Color::BLACK) return static_cast(-static_cast(dir)); + if (c == Color::BLACK) + return static_cast(-static_cast(dir)); return dir; } @@ -571,13 +636,17 @@ namespace chess { class Bitboard { public: - constexpr Bitboard() : bits(0) {} - constexpr Bitboard(std::uint64_t bits) : bits(bits) {} - constexpr Bitboard(File file) : bits(0) { + constexpr Bitboard() : + bits(0) {} + constexpr Bitboard(std::uint64_t bits) : + bits(bits) {} + constexpr Bitboard(File file) : + bits(0) { assert(file != File::NO_FILE); bits = 0x0101010101010101ULL << static_cast(file.internal()); } - constexpr Bitboard(Rank rank) : bits(0) { + constexpr Bitboard(Rank rank) : + bits(0) { assert(rank != Rank::NO_RANK); bits = 0xFFULL << (8 * static_cast(rank.internal())); } @@ -586,11 +655,12 @@ class Bitboard { explicit operator std::string() const { std::bitset<64> b(bits); - std::string str_bitset = b.to_string(); + std::string str_bitset = b.to_string(); std::string str; - for (int i = 0; i < 64; i += 8) { + for (int i = 0; i < 64; i += 8) + { std::string x = str_bitset.substr(i, 8); std::reverse(x.begin(), x.end()); str += x + '\n'; @@ -603,14 +673,24 @@ class Bitboard { constexpr Bitboard operator&(std::uint64_t rhs) const noexcept { return Bitboard(bits & rhs); } constexpr Bitboard operator|(std::uint64_t rhs) const noexcept { return Bitboard(bits | rhs); } constexpr Bitboard operator^(std::uint64_t rhs) const noexcept { return Bitboard(bits ^ rhs); } - constexpr Bitboard operator<<(std::uint64_t rhs) const noexcept { return Bitboard(bits << rhs); } - constexpr Bitboard operator>>(std::uint64_t rhs) const noexcept { return Bitboard(bits >> rhs); } + constexpr Bitboard operator<<(std::uint64_t rhs) const noexcept { + return Bitboard(bits << rhs); + } + constexpr Bitboard operator>>(std::uint64_t rhs) const noexcept { + return Bitboard(bits >> rhs); + } constexpr bool operator==(std::uint64_t rhs) const noexcept { return bits == rhs; } constexpr bool operator!=(std::uint64_t rhs) const noexcept { return bits != rhs; } - constexpr Bitboard operator&(const Bitboard& rhs) const noexcept { return Bitboard(bits & rhs.bits); } - constexpr Bitboard operator|(const Bitboard& rhs) const noexcept { return Bitboard(bits | rhs.bits); } - constexpr Bitboard operator^(const Bitboard& rhs) const noexcept { return Bitboard(bits ^ rhs.bits); } + constexpr Bitboard operator&(const Bitboard& rhs) const noexcept { + return Bitboard(bits & rhs.bits); + } + constexpr Bitboard operator|(const Bitboard& rhs) const noexcept { + return Bitboard(bits | rhs.bits); + } + constexpr Bitboard operator^(const Bitboard& rhs) const noexcept { + return Bitboard(bits ^ rhs.bits); + } constexpr Bitboard operator~() const noexcept { return Bitboard(~bits); } constexpr Bitboard& operator&=(const Bitboard& rhs) noexcept { @@ -671,20 +751,20 @@ class Bitboard { #if __cpp_lib_bitops >= 201907L constexpr #endif - int lsb() const noexcept { + int lsb() const noexcept { assert(bits != 0); #if __cpp_lib_bitops >= 201907L return std::countr_zero(bits); #else -# if defined(__GNUC__) + #if defined(__GNUC__) return __builtin_ctzll(bits); -# elif defined(_MSC_VER) + #elif defined(_MSC_VER) unsigned long idx; _BitScanForward64(&idx, bits); return static_cast(idx); -# else -# error "Compiler not supported." -# endif + #else + #error "Compiler not supported." + #endif #endif } @@ -692,21 +772,21 @@ class Bitboard { #if __cpp_lib_bitops >= 201907L constexpr #endif - int msb() const noexcept { + int msb() const noexcept { assert(bits != 0); #if __cpp_lib_bitops >= 201907L return std::countl_zero(bits) ^ 63; #else -# if defined(__GNUC__) + #if defined(__GNUC__) return 63 ^ __builtin_clzll(bits); -# elif defined(_MSC_VER) + #elif defined(_MSC_VER) unsigned long idx; _BitScanReverse64(&idx, bits); return static_cast(idx); -# else -# error "Compiler not supported." -# endif + #else + #error "Compiler not supported." + #endif #endif } @@ -714,15 +794,15 @@ class Bitboard { #if __cpp_lib_bitops >= 201907L constexpr #endif - int count() const noexcept { + int count() const noexcept { #if __cpp_lib_bitops >= 201907L return std::popcount(bits); #else -# if defined(_MSC_VER) || defined(__INTEL_COMPILER) + #if defined(_MSC_VER) || defined(__INTEL_COMPILER) return static_cast(_mm_popcnt_u64(bits)); -# else + #else return __builtin_popcountll(bits); -# endif + #endif #endif } @@ -730,7 +810,7 @@ class Bitboard { #if __cpp_lib_bitops >= 201907L constexpr #endif - std::uint8_t pop() noexcept { + std::uint8_t pop() noexcept { assert(bits != 0); std::uint8_t index = lsb(); bits &= bits - 1; @@ -759,7 +839,6 @@ class Board; } // namespace chess - namespace chess { class PieceType { @@ -774,9 +853,12 @@ class PieceType { NONE, }; - constexpr PieceType() : pt(underlying::NONE) {} - constexpr PieceType(underlying pt) : pt(pt) {} - constexpr explicit PieceType(std::string_view type) : pt(underlying::NONE) { + constexpr PieceType() : + pt(underlying::NONE) {} + constexpr PieceType(underlying pt) : + pt(pt) {} + constexpr explicit PieceType(std::string_view type) : + pt(underlying::NONE) { assert(type.size() > 0); char c = type[0]; @@ -798,7 +880,8 @@ class PieceType { } explicit operator std::string() const { - if (pt == underlying::NONE) return " "; + if (pt == underlying::NONE) + return " "; constexpr static const char* pieceTypeStr[] = {"p", "n", "b", "r", "q", "k"}; return pieceTypeStr[static_cast(pt)]; } @@ -845,17 +928,24 @@ class Piece { NONE }; - constexpr Piece() : piece(underlying::NONE) {} - constexpr Piece(underlying piece) : piece(piece) {} - constexpr Piece(PieceType type, Color color) - : piece(color == Color::NONE ? Piece::NONE - : type == PieceType::NONE ? Piece::NONE - : static_cast(static_cast(color.internal()) * 6 + type)) {} - constexpr Piece(Color color, PieceType type) - : piece(color == Color::NONE ? Piece::NONE - : type == PieceType::NONE ? Piece::NONE - : static_cast(static_cast(color.internal()) * 6 + type)) {} - constexpr Piece(std::string_view p) : piece(underlying::NONE) { piece = convertCharToUnderlying(p[0]); } + constexpr Piece() : + piece(underlying::NONE) {} + constexpr Piece(underlying piece) : + piece(piece) {} + constexpr Piece(PieceType type, Color color) : + piece(color == Color::NONE ? Piece::NONE + : type == PieceType::NONE + ? Piece::NONE + : static_cast(static_cast(color.internal()) * 6 + type)) {} + constexpr Piece(Color color, PieceType type) : + piece(color == Color::NONE ? Piece::NONE + : type == PieceType::NONE + ? Piece::NONE + : static_cast(static_cast(color.internal()) * 6 + type)) {} + constexpr Piece(std::string_view p) : + piece(underlying::NONE) { + piece = convertCharToUnderlying(p[0]); + } constexpr bool operator<(const Piece& rhs) const noexcept { return piece < rhs.piece; } constexpr bool operator>(const Piece& rhs) const noexcept { return piece > rhs.piece; } @@ -871,21 +961,24 @@ class Piece { explicit operator std::string() const { constexpr static const char* pieceStr[] = {"P", "N", "B", "R", "Q", "K", // "p", "n", "b", "r", "q", "k"}; - if (piece == NONE) return "."; + if (piece == NONE) + return "."; return pieceStr[static_cast(piece)]; } constexpr operator int() const noexcept { return static_cast(piece); } [[nodiscard]] constexpr PieceType type() const noexcept { - if (piece == NONE) return PieceType::NONE; + if (piece == NONE) + return PieceType::NONE; // return static_cast(int(piece) % 6); - return static_cast(static_cast(piece) > 5 ? static_cast(piece) - 6 - : static_cast(piece)); + return static_cast( + static_cast(piece) > 5 ? static_cast(piece) - 6 : static_cast(piece)); } [[nodiscard]] constexpr Color color() const noexcept { - if (piece == NONE) return Color::NONE; + if (piece == NONE) + return Color::NONE; return static_cast(static_cast(piece) / 6); } @@ -909,33 +1002,34 @@ class Piece { underlying piece; [[nodiscard]] constexpr static underlying convertCharToUnderlying(char c) { - switch (c) { - case 'P': - return WHITEPAWN; - case 'N': - return WHITEKNIGHT; - case 'B': - return WHITEBISHOP; - case 'R': - return WHITEROOK; - case 'Q': - return WHITEQUEEN; - case 'K': - return WHITEKING; - case 'p': - return BLACKPAWN; - case 'n': - return BLACKKNIGHT; - case 'b': - return BLACKBISHOP; - case 'r': - return BLACKROOK; - case 'q': - return BLACKQUEEN; - case 'k': - return BLACKKING; - default: - return NONE; + switch (c) + { + case 'P' : + return WHITEPAWN; + case 'N' : + return WHITEKNIGHT; + case 'B' : + return WHITEBISHOP; + case 'R' : + return WHITEROOK; + case 'Q' : + return WHITEQUEEN; + case 'K' : + return WHITEKING; + case 'p' : + return BLACKPAWN; + case 'n' : + return BLACKKNIGHT; + case 'b' : + return BLACKBISHOP; + case 'r' : + return BLACKROOK; + case 'q' : + return BLACKQUEEN; + case 'k' : + return BLACKKING; + default : + return NONE; } } }; @@ -947,27 +1041,31 @@ class attacks { #ifdef CHESS_USE_PEXT struct Magic { - U64 mask; - Bitboard *attacks; - U64 operator()(Bitboard b) const noexcept { return _pext_u64(b.getBits(), mask); } + U64 mask; + Bitboard* attacks; + U64 operator()(Bitboard b) const noexcept { return _pext_u64(b.getBits(), mask); } }; #else struct Magic { - U64 mask; - U64 magic; - Bitboard *attacks; - U64 shift; - U64 operator()(Bitboard b) const noexcept { return (((b & mask)).getBits() * magic) >> shift; } + U64 mask; + U64 magic; + Bitboard* attacks; + U64 shift; + U64 operator()(Bitboard b) const noexcept { + return (((b & mask)).getBits() * magic) >> shift; + } }; #endif // Slow function to calculate bishop and rook attacks - template + template [[nodiscard]] static Bitboard sliderAttacks(Square sq, Bitboard occupied) noexcept; // Initializes the magic bitboard tables for sliding pieces - static void initSliders(Square sq, Magic table[], U64 magic, - const std::function &attacks); + static void initSliders(Square sq, + Magic table[], + U64 magic, + const std::function& attacks); // clang-format off // pre-calculated lookup table for pawn attacks @@ -1014,71 +1112,77 @@ class attacks { // pre-calculated lookup table for knight attacks static constexpr Bitboard KnightAttacks[64] = { - 0x0000000000020400, 0x0000000000050800, 0x00000000000A1100, 0x0000000000142200, 0x0000000000284400, - 0x0000000000508800, 0x0000000000A01000, 0x0000000000402000, 0x0000000002040004, 0x0000000005080008, - 0x000000000A110011, 0x0000000014220022, 0x0000000028440044, 0x0000000050880088, 0x00000000A0100010, - 0x0000000040200020, 0x0000000204000402, 0x0000000508000805, 0x0000000A1100110A, 0x0000001422002214, - 0x0000002844004428, 0x0000005088008850, 0x000000A0100010A0, 0x0000004020002040, 0x0000020400040200, - 0x0000050800080500, 0x00000A1100110A00, 0x0000142200221400, 0x0000284400442800, 0x0000508800885000, - 0x0000A0100010A000, 0x0000402000204000, 0x0002040004020000, 0x0005080008050000, 0x000A1100110A0000, - 0x0014220022140000, 0x0028440044280000, 0x0050880088500000, 0x00A0100010A00000, 0x0040200020400000, - 0x0204000402000000, 0x0508000805000000, 0x0A1100110A000000, 0x1422002214000000, 0x2844004428000000, - 0x5088008850000000, 0xA0100010A0000000, 0x4020002040000000, 0x0400040200000000, 0x0800080500000000, - 0x1100110A00000000, 0x2200221400000000, 0x4400442800000000, 0x8800885000000000, 0x100010A000000000, - 0x2000204000000000, 0x0004020000000000, 0x0008050000000000, 0x00110A0000000000, 0x0022140000000000, - 0x0044280000000000, 0x0088500000000000, 0x0010A00000000000, 0x0020400000000000}; + 0x0000000000020400, 0x0000000000050800, 0x00000000000A1100, 0x0000000000142200, + 0x0000000000284400, 0x0000000000508800, 0x0000000000A01000, 0x0000000000402000, + 0x0000000002040004, 0x0000000005080008, 0x000000000A110011, 0x0000000014220022, + 0x0000000028440044, 0x0000000050880088, 0x00000000A0100010, 0x0000000040200020, + 0x0000000204000402, 0x0000000508000805, 0x0000000A1100110A, 0x0000001422002214, + 0x0000002844004428, 0x0000005088008850, 0x000000A0100010A0, 0x0000004020002040, + 0x0000020400040200, 0x0000050800080500, 0x00000A1100110A00, 0x0000142200221400, + 0x0000284400442800, 0x0000508800885000, 0x0000A0100010A000, 0x0000402000204000, + 0x0002040004020000, 0x0005080008050000, 0x000A1100110A0000, 0x0014220022140000, + 0x0028440044280000, 0x0050880088500000, 0x00A0100010A00000, 0x0040200020400000, + 0x0204000402000000, 0x0508000805000000, 0x0A1100110A000000, 0x1422002214000000, + 0x2844004428000000, 0x5088008850000000, 0xA0100010A0000000, 0x4020002040000000, + 0x0400040200000000, 0x0800080500000000, 0x1100110A00000000, 0x2200221400000000, + 0x4400442800000000, 0x8800885000000000, 0x100010A000000000, 0x2000204000000000, + 0x0004020000000000, 0x0008050000000000, 0x00110A0000000000, 0x0022140000000000, + 0x0044280000000000, 0x0088500000000000, 0x0010A00000000000, 0x0020400000000000}; // pre-calculated lookup table for king attacks static constexpr Bitboard KingAttacks[64] = { - 0x0000000000000302, 0x0000000000000705, 0x0000000000000E0A, 0x0000000000001C14, 0x0000000000003828, - 0x0000000000007050, 0x000000000000E0A0, 0x000000000000C040, 0x0000000000030203, 0x0000000000070507, - 0x00000000000E0A0E, 0x00000000001C141C, 0x0000000000382838, 0x0000000000705070, 0x0000000000E0A0E0, - 0x0000000000C040C0, 0x0000000003020300, 0x0000000007050700, 0x000000000E0A0E00, 0x000000001C141C00, - 0x0000000038283800, 0x0000000070507000, 0x00000000E0A0E000, 0x00000000C040C000, 0x0000000302030000, - 0x0000000705070000, 0x0000000E0A0E0000, 0x0000001C141C0000, 0x0000003828380000, 0x0000007050700000, - 0x000000E0A0E00000, 0x000000C040C00000, 0x0000030203000000, 0x0000070507000000, 0x00000E0A0E000000, - 0x00001C141C000000, 0x0000382838000000, 0x0000705070000000, 0x0000E0A0E0000000, 0x0000C040C0000000, - 0x0003020300000000, 0x0007050700000000, 0x000E0A0E00000000, 0x001C141C00000000, 0x0038283800000000, - 0x0070507000000000, 0x00E0A0E000000000, 0x00C040C000000000, 0x0302030000000000, 0x0705070000000000, - 0x0E0A0E0000000000, 0x1C141C0000000000, 0x3828380000000000, 0x7050700000000000, 0xE0A0E00000000000, - 0xC040C00000000000, 0x0203000000000000, 0x0507000000000000, 0x0A0E000000000000, 0x141C000000000000, - 0x2838000000000000, 0x5070000000000000, 0xA0E0000000000000, 0x40C0000000000000}; + 0x0000000000000302, 0x0000000000000705, 0x0000000000000E0A, 0x0000000000001C14, + 0x0000000000003828, 0x0000000000007050, 0x000000000000E0A0, 0x000000000000C040, + 0x0000000000030203, 0x0000000000070507, 0x00000000000E0A0E, 0x00000000001C141C, + 0x0000000000382838, 0x0000000000705070, 0x0000000000E0A0E0, 0x0000000000C040C0, + 0x0000000003020300, 0x0000000007050700, 0x000000000E0A0E00, 0x000000001C141C00, + 0x0000000038283800, 0x0000000070507000, 0x00000000E0A0E000, 0x00000000C040C000, + 0x0000000302030000, 0x0000000705070000, 0x0000000E0A0E0000, 0x0000001C141C0000, + 0x0000003828380000, 0x0000007050700000, 0x000000E0A0E00000, 0x000000C040C00000, + 0x0000030203000000, 0x0000070507000000, 0x00000E0A0E000000, 0x00001C141C000000, + 0x0000382838000000, 0x0000705070000000, 0x0000E0A0E0000000, 0x0000C040C0000000, + 0x0003020300000000, 0x0007050700000000, 0x000E0A0E00000000, 0x001C141C00000000, + 0x0038283800000000, 0x0070507000000000, 0x00E0A0E000000000, 0x00C040C000000000, + 0x0302030000000000, 0x0705070000000000, 0x0E0A0E0000000000, 0x1C141C0000000000, + 0x3828380000000000, 0x7050700000000000, 0xE0A0E00000000000, 0xC040C00000000000, + 0x0203000000000000, 0x0507000000000000, 0x0A0E000000000000, 0x141C000000000000, + 0x2838000000000000, 0x5070000000000000, 0xA0E0000000000000, 0x40C0000000000000}; static constexpr U64 RookMagics[64] = { - 0x8a80104000800020ULL, 0x140002000100040ULL, 0x2801880a0017001ULL, 0x100081001000420ULL, - 0x200020010080420ULL, 0x3001c0002010008ULL, 0x8480008002000100ULL, 0x2080088004402900ULL, - 0x800098204000ULL, 0x2024401000200040ULL, 0x100802000801000ULL, 0x120800800801000ULL, - 0x208808088000400ULL, 0x2802200800400ULL, 0x2200800100020080ULL, 0x801000060821100ULL, - 0x80044006422000ULL, 0x100808020004000ULL, 0x12108a0010204200ULL, 0x140848010000802ULL, - 0x481828014002800ULL, 0x8094004002004100ULL, 0x4010040010010802ULL, 0x20008806104ULL, - 0x100400080208000ULL, 0x2040002120081000ULL, 0x21200680100081ULL, 0x20100080080080ULL, - 0x2000a00200410ULL, 0x20080800400ULL, 0x80088400100102ULL, 0x80004600042881ULL, - 0x4040008040800020ULL, 0x440003000200801ULL, 0x4200011004500ULL, 0x188020010100100ULL, - 0x14800401802800ULL, 0x2080040080800200ULL, 0x124080204001001ULL, 0x200046502000484ULL, - 0x480400080088020ULL, 0x1000422010034000ULL, 0x30200100110040ULL, 0x100021010009ULL, - 0x2002080100110004ULL, 0x202008004008002ULL, 0x20020004010100ULL, 0x2048440040820001ULL, - 0x101002200408200ULL, 0x40802000401080ULL, 0x4008142004410100ULL, 0x2060820c0120200ULL, - 0x1001004080100ULL, 0x20c020080040080ULL, 0x2935610830022400ULL, 0x44440041009200ULL, - 0x280001040802101ULL, 0x2100190040002085ULL, 0x80c0084100102001ULL, 0x4024081001000421ULL, - 0x20030a0244872ULL, 0x12001008414402ULL, 0x2006104900a0804ULL, 0x1004081002402ULL}; + 0x8a80104000800020ULL, 0x140002000100040ULL, 0x2801880a0017001ULL, 0x100081001000420ULL, + 0x200020010080420ULL, 0x3001c0002010008ULL, 0x8480008002000100ULL, 0x2080088004402900ULL, + 0x800098204000ULL, 0x2024401000200040ULL, 0x100802000801000ULL, 0x120800800801000ULL, + 0x208808088000400ULL, 0x2802200800400ULL, 0x2200800100020080ULL, 0x801000060821100ULL, + 0x80044006422000ULL, 0x100808020004000ULL, 0x12108a0010204200ULL, 0x140848010000802ULL, + 0x481828014002800ULL, 0x8094004002004100ULL, 0x4010040010010802ULL, 0x20008806104ULL, + 0x100400080208000ULL, 0x2040002120081000ULL, 0x21200680100081ULL, 0x20100080080080ULL, + 0x2000a00200410ULL, 0x20080800400ULL, 0x80088400100102ULL, 0x80004600042881ULL, + 0x4040008040800020ULL, 0x440003000200801ULL, 0x4200011004500ULL, 0x188020010100100ULL, + 0x14800401802800ULL, 0x2080040080800200ULL, 0x124080204001001ULL, 0x200046502000484ULL, + 0x480400080088020ULL, 0x1000422010034000ULL, 0x30200100110040ULL, 0x100021010009ULL, + 0x2002080100110004ULL, 0x202008004008002ULL, 0x20020004010100ULL, 0x2048440040820001ULL, + 0x101002200408200ULL, 0x40802000401080ULL, 0x4008142004410100ULL, 0x2060820c0120200ULL, + 0x1001004080100ULL, 0x20c020080040080ULL, 0x2935610830022400ULL, 0x44440041009200ULL, + 0x280001040802101ULL, 0x2100190040002085ULL, 0x80c0084100102001ULL, 0x4024081001000421ULL, + 0x20030a0244872ULL, 0x12001008414402ULL, 0x2006104900a0804ULL, 0x1004081002402ULL}; static constexpr U64 BishopMagics[64] = { - 0x40040844404084ULL, 0x2004208a004208ULL, 0x10190041080202ULL, 0x108060845042010ULL, - 0x581104180800210ULL, 0x2112080446200010ULL, 0x1080820820060210ULL, 0x3c0808410220200ULL, - 0x4050404440404ULL, 0x21001420088ULL, 0x24d0080801082102ULL, 0x1020a0a020400ULL, - 0x40308200402ULL, 0x4011002100800ULL, 0x401484104104005ULL, 0x801010402020200ULL, - 0x400210c3880100ULL, 0x404022024108200ULL, 0x810018200204102ULL, 0x4002801a02003ULL, - 0x85040820080400ULL, 0x810102c808880400ULL, 0xe900410884800ULL, 0x8002020480840102ULL, - 0x220200865090201ULL, 0x2010100a02021202ULL, 0x152048408022401ULL, 0x20080002081110ULL, - 0x4001001021004000ULL, 0x800040400a011002ULL, 0xe4004081011002ULL, 0x1c004001012080ULL, - 0x8004200962a00220ULL, 0x8422100208500202ULL, 0x2000402200300c08ULL, 0x8646020080080080ULL, - 0x80020a0200100808ULL, 0x2010004880111000ULL, 0x623000a080011400ULL, 0x42008c0340209202ULL, - 0x209188240001000ULL, 0x400408a884001800ULL, 0x110400a6080400ULL, 0x1840060a44020800ULL, - 0x90080104000041ULL, 0x201011000808101ULL, 0x1a2208080504f080ULL, 0x8012020600211212ULL, - 0x500861011240000ULL, 0x180806108200800ULL, 0x4000020e01040044ULL, 0x300000261044000aULL, - 0x802241102020002ULL, 0x20906061210001ULL, 0x5a84841004010310ULL, 0x4010801011c04ULL, - 0xa010109502200ULL, 0x4a02012000ULL, 0x500201010098b028ULL, 0x8040002811040900ULL, - 0x28000010020204ULL, 0x6000020202d0240ULL, 0x8918844842082200ULL, 0x4010011029020020ULL}; + 0x40040844404084ULL, 0x2004208a004208ULL, 0x10190041080202ULL, 0x108060845042010ULL, + 0x581104180800210ULL, 0x2112080446200010ULL, 0x1080820820060210ULL, 0x3c0808410220200ULL, + 0x4050404440404ULL, 0x21001420088ULL, 0x24d0080801082102ULL, 0x1020a0a020400ULL, + 0x40308200402ULL, 0x4011002100800ULL, 0x401484104104005ULL, 0x801010402020200ULL, + 0x400210c3880100ULL, 0x404022024108200ULL, 0x810018200204102ULL, 0x4002801a02003ULL, + 0x85040820080400ULL, 0x810102c808880400ULL, 0xe900410884800ULL, 0x8002020480840102ULL, + 0x220200865090201ULL, 0x2010100a02021202ULL, 0x152048408022401ULL, 0x20080002081110ULL, + 0x4001001021004000ULL, 0x800040400a011002ULL, 0xe4004081011002ULL, 0x1c004001012080ULL, + 0x8004200962a00220ULL, 0x8422100208500202ULL, 0x2000402200300c08ULL, 0x8646020080080080ULL, + 0x80020a0200100808ULL, 0x2010004880111000ULL, 0x623000a080011400ULL, 0x42008c0340209202ULL, + 0x209188240001000ULL, 0x400408a884001800ULL, 0x110400a6080400ULL, 0x1840060a44020800ULL, + 0x90080104000041ULL, 0x201011000808101ULL, 0x1a2208080504f080ULL, 0x8012020600211212ULL, + 0x500861011240000ULL, 0x180806108200800ULL, 0x4000020e01040044ULL, 0x300000261044000aULL, + 0x802241102020002ULL, 0x20906061210001ULL, 0x5a84841004010310ULL, 0x4010801011c04ULL, + 0xa010109502200ULL, 0x4a02012000ULL, 0x500201010098b028ULL, 0x8040002811040900ULL, + 0x28000010020204ULL, 0x6000020202d0240ULL, 0x8918844842082200ULL, 0x4010011029020020ULL}; static inline Bitboard RookAttacks[0x19000] = {}; static inline Bitboard BishopAttacks[0x1480] = {}; @@ -1087,12 +1191,13 @@ class attacks { static inline Magic BishopTable[64] = {}; public: - static constexpr Bitboard MASK_RANK[8] = {0xff, 0xff00, 0xff0000, 0xff000000, - 0xff00000000, 0xff0000000000, 0xff000000000000, 0xff00000000000000}; + static constexpr Bitboard MASK_RANK[8] = { + 0xff, 0xff00, 0xff0000, 0xff000000, + 0xff00000000, 0xff0000000000, 0xff000000000000, 0xff00000000000000}; static constexpr Bitboard MASK_FILE[8] = { - 0x101010101010101, 0x202020202020202, 0x404040404040404, 0x808080808080808, - 0x1010101010101010, 0x2020202020202020, 0x4040404040404040, 0x8080808080808080, + 0x101010101010101, 0x202020202020202, 0x404040404040404, 0x808080808080808, + 0x1010101010101010, 0x2020202020202020, 0x4040404040404040, 0x8080808080808080, }; /** @@ -1101,7 +1206,7 @@ class attacks { * @param b * @return */ - template + template [[nodiscard]] static constexpr Bitboard shift(const Bitboard b); /** @@ -1110,7 +1215,7 @@ class attacks { * @param pawns * @return */ - template + template [[nodiscard]] static Bitboard pawnLeftAttacks(const Bitboard pawns); /** @@ -1119,7 +1224,7 @@ class attacks { * @param pawns * @return */ - template + template [[nodiscard]] static Bitboard pawnRightAttacks(const Bitboard pawns); /** @@ -1169,13 +1274,14 @@ class attacks { [[nodiscard]] static Bitboard king(Square sq) noexcept; /** - * @brief Returns the attacks for a given piece on a given square + * @brief Returns the origin squares of pieces of a given color attacking a target square * @param board - * @param color - * @param square + * @param color Attacker Color + * @param square Attacked Square * @return */ - [[nodiscard]] static Bitboard attackers(const Board &board, Color color, Square square) noexcept; + [[nodiscard]] static Bitboard + attackers(const Board& board, Color color, Square square) noexcept; /** * @brief Returns the slider attacks for a given square @@ -1184,7 +1290,7 @@ class attacks { * @tparam pt * @return */ - template + template [[nodiscard]] static Bitboard slider(Square sq, Bitboard occupied) noexcept; /** @@ -1200,29 +1306,29 @@ class attacks { // check if charconv header is available #if __has_include() -# define CHESS_USE_CHARCONV -# include + #define CHESS_USE_CHARCONV + #include #else -# include + #include #endif - namespace chess::constants { constexpr Bitboard DEFAULT_CHECKMASK = Bitboard(0xFFFFFFFFFFFFFFFFull); -constexpr auto STARTPOS = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; -constexpr auto MAX_MOVES = 256; +constexpr auto STARTPOS = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; +constexpr auto MAX_MOVES = 256; } // namespace chess::constants - namespace chess { class Move { public: Move() = default; - constexpr Move(std::uint16_t move) : move_(move), score_(0) {} + constexpr Move(std::uint16_t move) : + move_(move), + score_(0) {} /** * @brief Creates a move from a source and target square. @@ -1232,11 +1338,13 @@ class Move { * @param pt leave this empty if it is not a promotion move, otherwise pass the piece type of the new piece. * @return */ - template - [[nodiscard]] static constexpr Move make(Square source, Square target, PieceType pt = PieceType::KNIGHT) noexcept { + template + [[nodiscard]] static constexpr Move + make(Square source, Square target, PieceType pt = PieceType::KNIGHT) noexcept { assert(pt >= PieceType(PieceType::KNIGHT) && pt <= PieceType(PieceType::QUEEN)); - std::uint16_t bits_promotion = static_cast(pt - PieceType(PieceType::KNIGHT)); + std::uint16_t bits_promotion = + static_cast(pt - PieceType(PieceType::KNIGHT)); return Move(MoveType + (bits_promotion << 12) + (source.index() << 6) + target.index()); } @@ -1245,7 +1353,9 @@ class Move { * @brief Get the source square of the move. * @return */ - [[nodiscard]] constexpr Square from() const noexcept { return static_cast((move_ >> 6) & 0x3F); } + [[nodiscard]] constexpr Square from() const noexcept { + return static_cast((move_ >> 6) & 0x3F); + } /** * @brief Get the target square of the move. @@ -1266,7 +1376,8 @@ class Move { * @return */ [[nodiscard]] constexpr PieceType promotionType() const noexcept { - return static_cast(((move_ >> 12) & 3) + PieceType(PieceType::KNIGHT)); + return static_cast(((move_ >> 12) & 3) + + PieceType(PieceType::KNIGHT)); } /** @@ -1276,10 +1387,10 @@ class Move { constexpr void setScore(std::int16_t score) noexcept { score_ = score; } [[nodiscard]] constexpr std::uint16_t move() const noexcept { return move_; } - [[nodiscard]] constexpr std::int16_t score() const noexcept { return score_; } + [[nodiscard]] constexpr std::int16_t score() const noexcept { return score_; } - constexpr bool operator==(const Move &rhs) const noexcept { return move_ == rhs.move_; } - constexpr bool operator!=(const Move &rhs) const noexcept { return move_ != rhs.move_; } + constexpr bool operator==(const Move& rhs) const noexcept { return move_ == rhs.move_; } + constexpr bool operator!=(const Move& rhs) const noexcept { return move_ != rhs.move_; } static constexpr std::uint16_t NO_MOVE = 0; static constexpr std::uint16_t NULL_MOVE = 65; @@ -1290,25 +1401,12 @@ class Move { private: std::uint16_t move_; - std::int16_t score_; + std::int16_t score_; }; -inline std::ostream &operator<<(std::ostream &os, const Move &move) { - Square from_sq = move.from(); - Square to_sq = move.to(); - - os << from_sq << to_sq; - - if (move.typeOf() == Move::PROMOTION) { - os << static_cast(move.promotionType()); - } - - return os; -} } // namespace chess - #include #include #include @@ -1334,40 +1432,32 @@ class Movelist { // Element access [[nodiscard]] constexpr reference at(size_type pos) { -#ifndef CHESS_NO_EXCEPTIONS - if (pos >= size_) { - throw std::out_of_range("Movelist::at: pos (which is " + std::to_string(pos) + ") >= size (which is " + - std::to_string(size_) + ")"); - } -#endif + // pos out-of-range ignored return moves_[pos]; } [[nodiscard]] constexpr const_reference at(size_type pos) const { -#ifndef CHESS_NO_EXCEPTIONS - if (pos >= size_) { - throw std::out_of_range("Movelist::at: pos (which is " + std::to_string(pos) + ") >= size (which is " + - std::to_string(size_) + ")"); - } -#endif + // pos out-of-range ignored return moves_[pos]; } [[nodiscard]] constexpr reference operator[](size_type pos) noexcept { return moves_[pos]; } - [[nodiscard]] constexpr const_reference operator[](size_type pos) const noexcept { return moves_[pos]; } + [[nodiscard]] constexpr const_reference operator[](size_type pos) const noexcept { + return moves_[pos]; + } - [[nodiscard]] constexpr reference front() noexcept { return moves_[0]; } + [[nodiscard]] constexpr reference front() noexcept { return moves_[0]; } [[nodiscard]] constexpr const_reference front() const noexcept { return moves_[0]; } - [[nodiscard]] constexpr reference back() noexcept { return moves_[size_ - 1]; } + [[nodiscard]] constexpr reference back() noexcept { return moves_[size_ - 1]; } [[nodiscard]] constexpr const_reference back() const noexcept { return moves_[size_ - 1]; } // Iterators - [[nodiscard]] constexpr iterator begin() noexcept { return &moves_[0]; } + [[nodiscard]] constexpr iterator begin() noexcept { return &moves_[0]; } [[nodiscard]] constexpr const_iterator begin() const noexcept { return &moves_[0]; } - [[nodiscard]] constexpr iterator end() noexcept { return &moves_[0] + size_; } + [[nodiscard]] constexpr iterator end() noexcept { return &moves_[0] + size_; } [[nodiscard]] constexpr const_iterator end() const noexcept { return &moves_[0] + size_; } // Capacity @@ -1416,9 +1506,12 @@ class Movelist { * @param move * @return */ - [[nodiscard]] [[deprecated("Use std::find() instead.")]] constexpr size_type find(value_type move) const noexcept { - for (size_type i = 0; i < size_; ++i) { - if (moves_[i] == move) { + [[nodiscard]] [[deprecated("Use std::find() instead.")]] constexpr size_type + find(value_type move) const noexcept { + for (size_type i = 0; i < size_; ++i) + { + if (moves_[i] == move) + { return i; } } @@ -1428,7 +1521,7 @@ class Movelist { private: std::array moves_; - size_type size_ = 0; + size_type size_ = 0; }; } // namespace chess @@ -1446,7 +1539,11 @@ class Board; class movegen { public: - enum class MoveGenType : std::uint8_t { ALL, CAPTURE, QUIET }; + enum class MoveGenType : std::uint8_t { + ALL, + CAPTURE, + QUIET + }; /** * @brief Generates all legal moves for a position. @@ -1455,36 +1552,47 @@ class movegen { * @param board * @param pieces */ - template - void static legalmoves(Movelist &movelist, const Board &board, - int pieces = PieceGenType::PAWN | PieceGenType::KNIGHT | PieceGenType::BISHOP | - PieceGenType::ROOK | PieceGenType::QUEEN | PieceGenType::KING); + template + void static legalmoves(Movelist& movelist, + const Board& board, + int pieces = PieceGenType::PAWN | PieceGenType::KNIGHT + | PieceGenType::BISHOP | PieceGenType::ROOK + | PieceGenType::QUEEN | PieceGenType::KING); private: - static auto init_squares_between(); + static auto init_squares_between(); static const std::array, 64> SQUARES_BETWEEN_BB; // Generate the checkmask. Returns a bitboard where the attacker path between the king and enemy piece is set. - template - [[nodiscard]] static std::pair checkMask(const Board &board, Square sq); + template + [[nodiscard]] static std::pair checkMask(const Board& board, Square sq); // Generate the pin mask for horizontal and vertical pins -> PieceType::ROOK // Generate the pin mask for diagonal pins. -> PieceType::BISHOP // Returns a bitboard where the ray between the king and the pinner is set. - template - [[nodiscard]] static Bitboard pinMask(const Board &board, Square sq, Bitboard occ_enemy, Bitboard occ_us) noexcept; + template + [[nodiscard]] static Bitboard + pinMask(const Board& board, Square sq, Bitboard occ_enemy, Bitboard occ_us) noexcept; // Returns the squares that are attacked by the enemy - template - [[nodiscard]] static Bitboard seenSquares(const Board &board, Bitboard enemy_empty); + template + [[nodiscard]] static Bitboard seenSquares(const Board& board, Bitboard enemy_empty); // Generate pawn moves. - template - static void generatePawnMoves(const Board &board, Movelist &moves, Bitboard pin_d, Bitboard pin_hv, - Bitboard checkmask, Bitboard occ_enemy); - - [[nodiscard]] static std::array generateEPMove(const Board &board, Bitboard checkmask, Bitboard pin_d, - Bitboard pawns_lr, Square ep, Color c); + template + static void generatePawnMoves(const Board& board, + Movelist& moves, + Bitboard pin_d, + Bitboard pin_hv, + Bitboard checkmask, + Bitboard occ_enemy); + + [[nodiscard]] static std::array generateEPMove(const Board& board, + Bitboard checkmask, + Bitboard pin_d, + Bitboard pawns_lr, + Square ep, + Color c); [[nodiscard]] static Bitboard generateKnightMoves(Square sq); @@ -1492,21 +1600,24 @@ class movegen { [[nodiscard]] static Bitboard generateRookMoves(Square sq, Bitboard pin_hv, Bitboard occ_all); - [[nodiscard]] static Bitboard generateQueenMoves(Square sq, Bitboard pin_d, Bitboard pin_hv, Bitboard occ_all); + [[nodiscard]] static Bitboard + generateQueenMoves(Square sq, Bitboard pin_d, Bitboard pin_hv, Bitboard occ_all); - [[nodiscard]] static Bitboard generateKingMoves(Square sq, Bitboard seen, Bitboard movable_square); + [[nodiscard]] static Bitboard + generateKingMoves(Square sq, Bitboard seen, Bitboard movable_square); - template - [[nodiscard]] static Bitboard generateCastleMoves(const Board &board, Square sq, Bitboard seen, Bitboard pinHV) noexcept; + template + [[nodiscard]] static Bitboard + generateCastleMoves(const Board& board, Square sq, Bitboard seen, Bitboard pinHV) noexcept; - template - static void whileBitboardAdd(Movelist &movelist, Bitboard mask, T func); + template + static void whileBitboardAdd(Movelist& movelist, Bitboard mask, T func); - template - static void legalmoves(Movelist &movelist, const Board &board, int pieces); + template + static void legalmoves(Movelist& movelist, const Board& board, int pieces); - template - static bool isEpSquareValid(const Board &board, Square ep); + template + static bool isEpSquareValid(const Board& board, Square ep); [[nodiscard]] static Bitboard between(Square sq1, Square sq2) noexcept; @@ -1516,168 +1627,206 @@ class movegen { } // namespace chess - namespace chess { class Zobrist { using U64 = std::uint64_t; static constexpr U64 RANDOM_ARRAY[781] = { - 0x9D39247E33776D41, 0x2AF7398005AAA5C7, 0x44DB015024623547, 0x9C15F73E62A76AE2, 0x75834465489C0C89, - 0x3290AC3A203001BF, 0x0FBBAD1F61042279, 0xE83A908FF2FB60CA, 0x0D7E765D58755C10, 0x1A083822CEAFE02D, - 0x9605D5F0E25EC3B0, 0xD021FF5CD13A2ED5, 0x40BDF15D4A672E32, 0x011355146FD56395, 0x5DB4832046F3D9E5, - 0x239F8B2D7FF719CC, 0x05D1A1AE85B49AA1, 0x679F848F6E8FC971, 0x7449BBFF801FED0B, 0x7D11CDB1C3B7ADF0, - 0x82C7709E781EB7CC, 0xF3218F1C9510786C, 0x331478F3AF51BBE6, 0x4BB38DE5E7219443, 0xAA649C6EBCFD50FC, - 0x8DBD98A352AFD40B, 0x87D2074B81D79217, 0x19F3C751D3E92AE1, 0xB4AB30F062B19ABF, 0x7B0500AC42047AC4, - 0xC9452CA81A09D85D, 0x24AA6C514DA27500, 0x4C9F34427501B447, 0x14A68FD73C910841, 0xA71B9B83461CBD93, - 0x03488B95B0F1850F, 0x637B2B34FF93C040, 0x09D1BC9A3DD90A94, 0x3575668334A1DD3B, 0x735E2B97A4C45A23, - 0x18727070F1BD400B, 0x1FCBACD259BF02E7, 0xD310A7C2CE9B6555, 0xBF983FE0FE5D8244, 0x9F74D14F7454A824, - 0x51EBDC4AB9BA3035, 0x5C82C505DB9AB0FA, 0xFCF7FE8A3430B241, 0x3253A729B9BA3DDE, 0x8C74C368081B3075, - 0xB9BC6C87167C33E7, 0x7EF48F2B83024E20, 0x11D505D4C351BD7F, 0x6568FCA92C76A243, 0x4DE0B0F40F32A7B8, - 0x96D693460CC37E5D, 0x42E240CB63689F2F, 0x6D2BDCDAE2919661, 0x42880B0236E4D951, 0x5F0F4A5898171BB6, - 0x39F890F579F92F88, 0x93C5B5F47356388B, 0x63DC359D8D231B78, 0xEC16CA8AEA98AD76, 0x5355F900C2A82DC7, - 0x07FB9F855A997142, 0x5093417AA8A7ED5E, 0x7BCBC38DA25A7F3C, 0x19FC8A768CF4B6D4, 0x637A7780DECFC0D9, - 0x8249A47AEE0E41F7, 0x79AD695501E7D1E8, 0x14ACBAF4777D5776, 0xF145B6BECCDEA195, 0xDABF2AC8201752FC, - 0x24C3C94DF9C8D3F6, 0xBB6E2924F03912EA, 0x0CE26C0B95C980D9, 0xA49CD132BFBF7CC4, 0xE99D662AF4243939, - 0x27E6AD7891165C3F, 0x8535F040B9744FF1, 0x54B3F4FA5F40D873, 0x72B12C32127FED2B, 0xEE954D3C7B411F47, - 0x9A85AC909A24EAA1, 0x70AC4CD9F04F21F5, 0xF9B89D3E99A075C2, 0x87B3E2B2B5C907B1, 0xA366E5B8C54F48B8, - 0xAE4A9346CC3F7CF2, 0x1920C04D47267BBD, 0x87BF02C6B49E2AE9, 0x092237AC237F3859, 0xFF07F64EF8ED14D0, - 0x8DE8DCA9F03CC54E, 0x9C1633264DB49C89, 0xB3F22C3D0B0B38ED, 0x390E5FB44D01144B, 0x5BFEA5B4712768E9, - 0x1E1032911FA78984, 0x9A74ACB964E78CB3, 0x4F80F7A035DAFB04, 0x6304D09A0B3738C4, 0x2171E64683023A08, - 0x5B9B63EB9CEFF80C, 0x506AACF489889342, 0x1881AFC9A3A701D6, 0x6503080440750644, 0xDFD395339CDBF4A7, - 0xEF927DBCF00C20F2, 0x7B32F7D1E03680EC, 0xB9FD7620E7316243, 0x05A7E8A57DB91B77, 0xB5889C6E15630A75, - 0x4A750A09CE9573F7, 0xCF464CEC899A2F8A, 0xF538639CE705B824, 0x3C79A0FF5580EF7F, 0xEDE6C87F8477609D, - 0x799E81F05BC93F31, 0x86536B8CF3428A8C, 0x97D7374C60087B73, 0xA246637CFF328532, 0x043FCAE60CC0EBA0, - 0x920E449535DD359E, 0x70EB093B15B290CC, 0x73A1921916591CBD, 0x56436C9FE1A1AA8D, 0xEFAC4B70633B8F81, - 0xBB215798D45DF7AF, 0x45F20042F24F1768, 0x930F80F4E8EB7462, 0xFF6712FFCFD75EA1, 0xAE623FD67468AA70, - 0xDD2C5BC84BC8D8FC, 0x7EED120D54CF2DD9, 0x22FE545401165F1C, 0xC91800E98FB99929, 0x808BD68E6AC10365, - 0xDEC468145B7605F6, 0x1BEDE3A3AEF53302, 0x43539603D6C55602, 0xAA969B5C691CCB7A, 0xA87832D392EFEE56, - 0x65942C7B3C7E11AE, 0xDED2D633CAD004F6, 0x21F08570F420E565, 0xB415938D7DA94E3C, 0x91B859E59ECB6350, - 0x10CFF333E0ED804A, 0x28AED140BE0BB7DD, 0xC5CC1D89724FA456, 0x5648F680F11A2741, 0x2D255069F0B7DAB3, - 0x9BC5A38EF729ABD4, 0xEF2F054308F6A2BC, 0xAF2042F5CC5C2858, 0x480412BAB7F5BE2A, 0xAEF3AF4A563DFE43, - 0x19AFE59AE451497F, 0x52593803DFF1E840, 0xF4F076E65F2CE6F0, 0x11379625747D5AF3, 0xBCE5D2248682C115, - 0x9DA4243DE836994F, 0x066F70B33FE09017, 0x4DC4DE189B671A1C, 0x51039AB7712457C3, 0xC07A3F80C31FB4B4, - 0xB46EE9C5E64A6E7C, 0xB3819A42ABE61C87, 0x21A007933A522A20, 0x2DF16F761598AA4F, 0x763C4A1371B368FD, - 0xF793C46702E086A0, 0xD7288E012AEB8D31, 0xDE336A2A4BC1C44B, 0x0BF692B38D079F23, 0x2C604A7A177326B3, - 0x4850E73E03EB6064, 0xCFC447F1E53C8E1B, 0xB05CA3F564268D99, 0x9AE182C8BC9474E8, 0xA4FC4BD4FC5558CA, - 0xE755178D58FC4E76, 0x69B97DB1A4C03DFE, 0xF9B5B7C4ACC67C96, 0xFC6A82D64B8655FB, 0x9C684CB6C4D24417, - 0x8EC97D2917456ED0, 0x6703DF9D2924E97E, 0xC547F57E42A7444E, 0x78E37644E7CAD29E, 0xFE9A44E9362F05FA, - 0x08BD35CC38336615, 0x9315E5EB3A129ACE, 0x94061B871E04DF75, 0xDF1D9F9D784BA010, 0x3BBA57B68871B59D, - 0xD2B7ADEEDED1F73F, 0xF7A255D83BC373F8, 0xD7F4F2448C0CEB81, 0xD95BE88CD210FFA7, 0x336F52F8FF4728E7, - 0xA74049DAC312AC71, 0xA2F61BB6E437FDB5, 0x4F2A5CB07F6A35B3, 0x87D380BDA5BF7859, 0x16B9F7E06C453A21, - 0x7BA2484C8A0FD54E, 0xF3A678CAD9A2E38C, 0x39B0BF7DDE437BA2, 0xFCAF55C1BF8A4424, 0x18FCF680573FA594, - 0x4C0563B89F495AC3, 0x40E087931A00930D, 0x8CFFA9412EB642C1, 0x68CA39053261169F, 0x7A1EE967D27579E2, - 0x9D1D60E5076F5B6F, 0x3810E399B6F65BA2, 0x32095B6D4AB5F9B1, 0x35CAB62109DD038A, 0xA90B24499FCFAFB1, - 0x77A225A07CC2C6BD, 0x513E5E634C70E331, 0x4361C0CA3F692F12, 0xD941ACA44B20A45B, 0x528F7C8602C5807B, - 0x52AB92BEB9613989, 0x9D1DFA2EFC557F73, 0x722FF175F572C348, 0x1D1260A51107FE97, 0x7A249A57EC0C9BA2, - 0x04208FE9E8F7F2D6, 0x5A110C6058B920A0, 0x0CD9A497658A5698, 0x56FD23C8F9715A4C, 0x284C847B9D887AAE, - 0x04FEABFBBDB619CB, 0x742E1E651C60BA83, 0x9A9632E65904AD3C, 0x881B82A13B51B9E2, 0x506E6744CD974924, - 0xB0183DB56FFC6A79, 0x0ED9B915C66ED37E, 0x5E11E86D5873D484, 0xF678647E3519AC6E, 0x1B85D488D0F20CC5, - 0xDAB9FE6525D89021, 0x0D151D86ADB73615, 0xA865A54EDCC0F019, 0x93C42566AEF98FFB, 0x99E7AFEABE000731, - 0x48CBFF086DDF285A, 0x7F9B6AF1EBF78BAF, 0x58627E1A149BBA21, 0x2CD16E2ABD791E33, 0xD363EFF5F0977996, - 0x0CE2A38C344A6EED, 0x1A804AADB9CFA741, 0x907F30421D78C5DE, 0x501F65EDB3034D07, 0x37624AE5A48FA6E9, - 0x957BAF61700CFF4E, 0x3A6C27934E31188A, 0xD49503536ABCA345, 0x088E049589C432E0, 0xF943AEE7FEBF21B8, - 0x6C3B8E3E336139D3, 0x364F6FFA464EE52E, 0xD60F6DCEDC314222, 0x56963B0DCA418FC0, 0x16F50EDF91E513AF, - 0xEF1955914B609F93, 0x565601C0364E3228, 0xECB53939887E8175, 0xBAC7A9A18531294B, 0xB344C470397BBA52, - 0x65D34954DAF3CEBD, 0xB4B81B3FA97511E2, 0xB422061193D6F6A7, 0x071582401C38434D, 0x7A13F18BBEDC4FF5, - 0xBC4097B116C524D2, 0x59B97885E2F2EA28, 0x99170A5DC3115544, 0x6F423357E7C6A9F9, 0x325928EE6E6F8794, - 0xD0E4366228B03343, 0x565C31F7DE89EA27, 0x30F5611484119414, 0xD873DB391292ED4F, 0x7BD94E1D8E17DEBC, - 0xC7D9F16864A76E94, 0x947AE053EE56E63C, 0xC8C93882F9475F5F, 0x3A9BF55BA91F81CA, 0xD9A11FBB3D9808E4, - 0x0FD22063EDC29FCA, 0xB3F256D8ACA0B0B9, 0xB03031A8B4516E84, 0x35DD37D5871448AF, 0xE9F6082B05542E4E, - 0xEBFAFA33D7254B59, 0x9255ABB50D532280, 0xB9AB4CE57F2D34F3, 0x693501D628297551, 0xC62C58F97DD949BF, - 0xCD454F8F19C5126A, 0xBBE83F4ECC2BDECB, 0xDC842B7E2819E230, 0xBA89142E007503B8, 0xA3BC941D0A5061CB, - 0xE9F6760E32CD8021, 0x09C7E552BC76492F, 0x852F54934DA55CC9, 0x8107FCCF064FCF56, 0x098954D51FFF6580, - 0x23B70EDB1955C4BF, 0xC330DE426430F69D, 0x4715ED43E8A45C0A, 0xA8D7E4DAB780A08D, 0x0572B974F03CE0BB, - 0xB57D2E985E1419C7, 0xE8D9ECBE2CF3D73F, 0x2FE4B17170E59750, 0x11317BA87905E790, 0x7FBF21EC8A1F45EC, - 0x1725CABFCB045B00, 0x964E915CD5E2B207, 0x3E2B8BCBF016D66D, 0xBE7444E39328A0AC, 0xF85B2B4FBCDE44B7, - 0x49353FEA39BA63B1, 0x1DD01AAFCD53486A, 0x1FCA8A92FD719F85, 0xFC7C95D827357AFA, 0x18A6A990C8B35EBD, - 0xCCCB7005C6B9C28D, 0x3BDBB92C43B17F26, 0xAA70B5B4F89695A2, 0xE94C39A54A98307F, 0xB7A0B174CFF6F36E, - 0xD4DBA84729AF48AD, 0x2E18BC1AD9704A68, 0x2DE0966DAF2F8B1C, 0xB9C11D5B1E43A07E, 0x64972D68DEE33360, - 0x94628D38D0C20584, 0xDBC0D2B6AB90A559, 0xD2733C4335C6A72F, 0x7E75D99D94A70F4D, 0x6CED1983376FA72B, - 0x97FCAACBF030BC24, 0x7B77497B32503B12, 0x8547EDDFB81CCB94, 0x79999CDFF70902CB, 0xCFFE1939438E9B24, - 0x829626E3892D95D7, 0x92FAE24291F2B3F1, 0x63E22C147B9C3403, 0xC678B6D860284A1C, 0x5873888850659AE7, - 0x0981DCD296A8736D, 0x9F65789A6509A440, 0x9FF38FED72E9052F, 0xE479EE5B9930578C, 0xE7F28ECD2D49EECD, - 0x56C074A581EA17FE, 0x5544F7D774B14AEF, 0x7B3F0195FC6F290F, 0x12153635B2C0CF57, 0x7F5126DBBA5E0CA7, - 0x7A76956C3EAFB413, 0x3D5774A11D31AB39, 0x8A1B083821F40CB4, 0x7B4A38E32537DF62, 0x950113646D1D6E03, - 0x4DA8979A0041E8A9, 0x3BC36E078F7515D7, 0x5D0A12F27AD310D1, 0x7F9D1A2E1EBE1327, 0xDA3A361B1C5157B1, - 0xDCDD7D20903D0C25, 0x36833336D068F707, 0xCE68341F79893389, 0xAB9090168DD05F34, 0x43954B3252DC25E5, - 0xB438C2B67F98E5E9, 0x10DCD78E3851A492, 0xDBC27AB5447822BF, 0x9B3CDB65F82CA382, 0xB67B7896167B4C84, - 0xBFCED1B0048EAC50, 0xA9119B60369FFEBD, 0x1FFF7AC80904BF45, 0xAC12FB171817EEE7, 0xAF08DA9177DDA93D, - 0x1B0CAB936E65C744, 0xB559EB1D04E5E932, 0xC37B45B3F8D6F2BA, 0xC3A9DC228CAAC9E9, 0xF3B8B6675A6507FF, - 0x9FC477DE4ED681DA, 0x67378D8ECCEF96CB, 0x6DD856D94D259236, 0xA319CE15B0B4DB31, 0x073973751F12DD5E, - 0x8A8E849EB32781A5, 0xE1925C71285279F5, 0x74C04BF1790C0EFE, 0x4DDA48153C94938A, 0x9D266D6A1CC0542C, - 0x7440FB816508C4FE, 0x13328503DF48229F, 0xD6BF7BAEE43CAC40, 0x4838D65F6EF6748F, 0x1E152328F3318DEA, - 0x8F8419A348F296BF, 0x72C8834A5957B511, 0xD7A023A73260B45C, 0x94EBC8ABCFB56DAE, 0x9FC10D0F989993E0, - 0xDE68A2355B93CAE6, 0xA44CFE79AE538BBE, 0x9D1D84FCCE371425, 0x51D2B1AB2DDFB636, 0x2FD7E4B9E72CD38C, - 0x65CA5B96B7552210, 0xDD69A0D8AB3B546D, 0x604D51B25FBF70E2, 0x73AA8A564FB7AC9E, 0x1A8C1E992B941148, - 0xAAC40A2703D9BEA0, 0x764DBEAE7FA4F3A6, 0x1E99B96E70A9BE8B, 0x2C5E9DEB57EF4743, 0x3A938FEE32D29981, - 0x26E6DB8FFDF5ADFE, 0x469356C504EC9F9D, 0xC8763C5B08D1908C, 0x3F6C6AF859D80055, 0x7F7CC39420A3A545, - 0x9BFB227EBDF4C5CE, 0x89039D79D6FC5C5C, 0x8FE88B57305E2AB6, 0xA09E8C8C35AB96DE, 0xFA7E393983325753, - 0xD6B6D0ECC617C699, 0xDFEA21EA9E7557E3, 0xB67C1FA481680AF8, 0xCA1E3785A9E724E5, 0x1CFC8BED0D681639, - 0xD18D8549D140CAEA, 0x4ED0FE7E9DC91335, 0xE4DBF0634473F5D2, 0x1761F93A44D5AEFE, 0x53898E4C3910DA55, - 0x734DE8181F6EC39A, 0x2680B122BAA28D97, 0x298AF231C85BAFAB, 0x7983EED3740847D5, 0x66C1A2A1A60CD889, - 0x9E17E49642A3E4C1, 0xEDB454E7BADC0805, 0x50B704CAB602C329, 0x4CC317FB9CDDD023, 0x66B4835D9EAFEA22, - 0x219B97E26FFC81BD, 0x261E4E4C0A333A9D, 0x1FE2CCA76517DB90, 0xD7504DFA8816EDBB, 0xB9571FA04DC089C8, - 0x1DDC0325259B27DE, 0xCF3F4688801EB9AA, 0xF4F5D05C10CAB243, 0x38B6525C21A42B0E, 0x36F60E2BA4FA6800, - 0xEB3593803173E0CE, 0x9C4CD6257C5A3603, 0xAF0C317D32ADAA8A, 0x258E5A80C7204C4B, 0x8B889D624D44885D, - 0xF4D14597E660F855, 0xD4347F66EC8941C3, 0xE699ED85B0DFB40D, 0x2472F6207C2D0484, 0xC2A1E7B5B459AEB5, - 0xAB4F6451CC1D45EC, 0x63767572AE3D6174, 0xA59E0BD101731A28, 0x116D0016CB948F09, 0x2CF9C8CA052F6E9F, - 0x0B090A7560A968E3, 0xABEEDDB2DDE06FF1, 0x58EFC10B06A2068D, 0xC6E57A78FBD986E0, 0x2EAB8CA63CE802D7, - 0x14A195640116F336, 0x7C0828DD624EC390, 0xD74BBE77E6116AC7, 0x804456AF10F5FB53, 0xEBE9EA2ADF4321C7, - 0x03219A39EE587A30, 0x49787FEF17AF9924, 0xA1E9300CD8520548, 0x5B45E522E4B1B4EF, 0xB49C3B3995091A36, - 0xD4490AD526F14431, 0x12A8F216AF9418C2, 0x001F837CC7350524, 0x1877B51E57A764D5, 0xA2853B80F17F58EE, - 0x993E1DE72D36D310, 0xB3598080CE64A656, 0x252F59CF0D9F04BB, 0xD23C8E176D113600, 0x1BDA0492E7E4586E, - 0x21E0BD5026C619BF, 0x3B097ADAF088F94E, 0x8D14DEDB30BE846E, 0xF95CFFA23AF5F6F4, 0x3871700761B3F743, - 0xCA672B91E9E4FA16, 0x64C8E531BFF53B55, 0x241260ED4AD1E87D, 0x106C09B972D2E822, 0x7FBA195410E5CA30, - 0x7884D9BC6CB569D8, 0x0647DFEDCD894A29, 0x63573FF03E224774, 0x4FC8E9560F91B123, 0x1DB956E450275779, - 0xB8D91274B9E9D4FB, 0xA2EBEE47E2FBFCE1, 0xD9F1F30CCD97FB09, 0xEFED53D75FD64E6B, 0x2E6D02C36017F67F, - 0xA9AA4D20DB084E9B, 0xB64BE8D8B25396C1, 0x70CB6AF7C2D5BCF0, 0x98F076A4F7A2322E, 0xBF84470805E69B5F, - 0x94C3251F06F90CF3, 0x3E003E616A6591E9, 0xB925A6CD0421AFF3, 0x61BDD1307C66E300, 0xBF8D5108E27E0D48, - 0x240AB57A8B888B20, 0xFC87614BAF287E07, 0xEF02CDD06FFDB432, 0xA1082C0466DF6C0A, 0x8215E577001332C8, - 0xD39BB9C3A48DB6CF, 0x2738259634305C14, 0x61CF4F94C97DF93D, 0x1B6BACA2AE4E125B, 0x758F450C88572E0B, - 0x959F587D507A8359, 0xB063E962E045F54D, 0x60E8ED72C0DFF5D1, 0x7B64978555326F9F, 0xFD080D236DA814BA, - 0x8C90FD9B083F4558, 0x106F72FE81E2C590, 0x7976033A39F7D952, 0xA4EC0132764CA04B, 0x733EA705FAE4FA77, - 0xB4D8F77BC3E56167, 0x9E21F4F903B33FD9, 0x9D765E419FB69F6D, 0xD30C088BA61EA5EF, 0x5D94337FBFAF7F5B, - 0x1A4E4822EB4D7A59, 0x6FFE73E81B637FB3, 0xDDF957BC36D8B9CA, 0x64D0E29EEA8838B3, 0x08DD9BDFD96B9F63, - 0x087E79E5A57D1D13, 0xE328E230E3E2B3FB, 0x1C2559E30F0946BE, 0x720BF5F26F4D2EAA, 0xB0774D261CC609DB, - 0x443F64EC5A371195, 0x4112CF68649A260E, 0xD813F2FAB7F5C5CA, 0x660D3257380841EE, 0x59AC2C7873F910A3, - 0xE846963877671A17, 0x93B633ABFA3469F8, 0xC0C0F5A60EF4CDCF, 0xCAF21ECD4377B28C, 0x57277707199B8175, - 0x506C11B9D90E8B1D, 0xD83CC2687A19255F, 0x4A29C6465A314CD1, 0xED2DF21216235097, 0xB5635C95FF7296E2, - 0x22AF003AB672E811, 0x52E762596BF68235, 0x9AEBA33AC6ECC6B0, 0x944F6DE09134DFB6, 0x6C47BEC883A7DE39, - 0x6AD047C430A12104, 0xA5B1CFDBA0AB4067, 0x7C45D833AFF07862, 0x5092EF950A16DA0B, 0x9338E69C052B8E7B, - 0x455A4B4CFE30E3F5, 0x6B02E63195AD0CF8, 0x6B17B224BAD6BF27, 0xD1E0CCD25BB9C169, 0xDE0C89A556B9AE70, - 0x50065E535A213CF6, 0x9C1169FA2777B874, 0x78EDEFD694AF1EED, 0x6DC93D9526A50E68, 0xEE97F453F06791ED, - 0x32AB0EDB696703D3, 0x3A6853C7E70757A7, 0x31865CED6120F37D, 0x67FEF95D92607890, 0x1F2B1D1F15F6DC9C, - 0xB69E38A8965C6B65, 0xAA9119FF184CCCF4, 0xF43C732873F24C13, 0xFB4A3D794A9A80D2, 0x3550C2321FD6109C, - 0x371F77E76BB8417E, 0x6BFA9AAE5EC05779, 0xCD04F3FF001A4778, 0xE3273522064480CA, 0x9F91508BFFCFC14A, - 0x049A7F41061A9E60, 0xFCB6BE43A9F2FE9B, 0x08DE8A1C7797DA9B, 0x8F9887E6078735A1, 0xB5B4071DBFC73A66, - 0x230E343DFBA08D33, 0x43ED7F5A0FAE657D, 0x3A88A0FBBCB05C63, 0x21874B8B4D2DBC4F, 0x1BDEA12E35F6A8C9, - 0x53C065C6C8E63528, 0xE34A1D250E7A8D6B, 0xD6B04D3B7651DD7E, 0x5E90277E7CB39E2D, 0x2C046F22062DC67D, - 0xB10BB459132D0A26, 0x3FA9DDFB67E2F199, 0x0E09B88E1914F7AF, 0x10E8B35AF3EEAB37, 0x9EEDECA8E272B933, - 0xD4C718BC4AE8AE5F, 0x81536D601170FC20, 0x91B534F885818A06, 0xEC8177F83F900978, 0x190E714FADA5156E, - 0xB592BF39B0364963, 0x89C350C893AE7DC1, 0xAC042E70F8B383F2, 0xB49B52E587A1EE60, 0xFB152FE3FF26DA89, - 0x3E666E6F69AE2C15, 0x3B544EBE544C19F9, 0xE805A1E290CF2456, 0x24B33C9D7ED25117, 0xE74733427B72F0C1, - 0x0A804D18B7097475, 0x57E3306D881EDB4F, 0x4AE7D6A36EB5DBCB, 0x2D8D5432157064C8, 0xD1E649DE1E7F268B, - 0x8A328A1CEDFE552C, 0x07A3AEC79624C7DA, 0x84547DDC3E203C94, 0x990A98FD5071D263, 0x1A4FF12616EEFC89, - 0xF6F7FD1431714200, 0x30C05B1BA332F41C, 0x8D2636B81555A786, 0x46C9FEB55D120902, 0xCCEC0A73B49C9921, - 0x4E9D2827355FC492, 0x19EBB029435DCB0F, 0x4659D2B743848A2C, 0x963EF2C96B33BE31, 0x74F85198B05A2E7D, - 0x5A0F544DD2B1FB18, 0x03727073C2E134B1, 0xC7F6AA2DE59AEA61, 0x352787BAA0D7C22F, 0x9853EAB63B5E0B35, - 0xABBDCDD7ED5C0860, 0xCF05DAF5AC8D77B0, 0x49CAD48CEBF4A71E, 0x7A4C10EC2158C4A6, 0xD9E92AA246BF719E, - 0x13AE978D09FE5557, 0x730499AF921549FF, 0x4E4B705B92903BA4, 0xFF577222C14F0A3A, 0x55B6344CF97AAFAE, - 0xB862225B055B6960, 0xCAC09AFBDDD2CDB4, 0xDAF8E9829FE96B5F, 0xB5FDFC5D3132C498, 0x310CB380DB6F7503, - 0xE87FBB46217A360E, 0x2102AE466EBB1148, 0xF8549E1A3AA5E00D, 0x07A69AFDCC42261A, 0xC4C118BFE78FEAAE, - 0xF9F4892ED96BD438, 0x1AF3DBE25D8F45DA, 0xF5B4B0B0D2DEEEB4, 0x962ACEEFA82E1C84, 0x046E3ECAAF453CE9, - 0xF05D129681949A4C, 0x964781CE734B3C84, 0x9C2ED44081CE5FBD, 0x522E23F3925E319E, 0x177E00F9FC32F791, - 0x2BC60A63A6F3B3F2, 0x222BBFAE61725606, 0x486289DDCC3D6780, 0x7DC7785B8EFDFC80, 0x8AF38731C02BA980, - 0x1FAB64EA29A2DDF7, 0xE4D9429322CD065A, 0x9DA058C67844F20C, 0x24C0E332B70019B0, 0x233003B5A6CFE6AD, - 0xD586BD01C5C217F6, 0x5E5637885F29BC2B, 0x7EBA726D8C94094B, 0x0A56A5F0BFE39272, 0xD79476A84EE20D06, - 0x9E4C1269BAA4BF37, 0x17EFEE45B0DEE640, 0x1D95B0A5FCF90BC6, 0x93CBE0B699C2585D, 0x65FA4F227A2B6D79, - 0xD5F9E858292504D5, 0xC2B5A03F71471A6F, 0x59300222B4561E00, 0xCE2F8642CA0712DC, 0x7CA9723FBB2E8988, - 0x2785338347F2BA08, 0xC61BB3A141E50E8C, 0x150F361DAB9DEC26, 0x9F6A419D382595F4, 0x64A53DC924FE7AC9, - 0x142DE49FFF7A7C3D, 0x0C335248857FA9E7, 0x0A9C32D5EAE45305, 0xE6C42178C4BBB92E, 0x71F1CE2490D20B07, - 0xF1BCC3D275AFE51A, 0xE728E8C83C334074, 0x96FBF83A12884624, 0x81A1549FD6573DA5, 0x5FA7867CAF35E149, - 0x56986E2EF3ED091B, 0x917F1DD5F8886C61, 0xD20D8C88C8FFE65F, 0x31D71DCE64B2C310, 0xF165B587DF898190, - 0xA57E6339DD2CF3A0, 0x1EF6E6DBB1961EC9, 0x70CC73D90BC26E24, 0xE21A6B35DF0C3AD7, 0x003A93D8B2806962, - 0x1C99DED33CB890A1, 0xCF3145DE0ADD4289, 0xD0E4427A5514FB72, 0x77C621CC9FB3A483, 0x67A34DAC4356550B, - 0xF8D626AAAF278509}; + 0x9D39247E33776D41, 0x2AF7398005AAA5C7, 0x44DB015024623547, 0x9C15F73E62A76AE2, + 0x75834465489C0C89, 0x3290AC3A203001BF, 0x0FBBAD1F61042279, 0xE83A908FF2FB60CA, + 0x0D7E765D58755C10, 0x1A083822CEAFE02D, 0x9605D5F0E25EC3B0, 0xD021FF5CD13A2ED5, + 0x40BDF15D4A672E32, 0x011355146FD56395, 0x5DB4832046F3D9E5, 0x239F8B2D7FF719CC, + 0x05D1A1AE85B49AA1, 0x679F848F6E8FC971, 0x7449BBFF801FED0B, 0x7D11CDB1C3B7ADF0, + 0x82C7709E781EB7CC, 0xF3218F1C9510786C, 0x331478F3AF51BBE6, 0x4BB38DE5E7219443, + 0xAA649C6EBCFD50FC, 0x8DBD98A352AFD40B, 0x87D2074B81D79217, 0x19F3C751D3E92AE1, + 0xB4AB30F062B19ABF, 0x7B0500AC42047AC4, 0xC9452CA81A09D85D, 0x24AA6C514DA27500, + 0x4C9F34427501B447, 0x14A68FD73C910841, 0xA71B9B83461CBD93, 0x03488B95B0F1850F, + 0x637B2B34FF93C040, 0x09D1BC9A3DD90A94, 0x3575668334A1DD3B, 0x735E2B97A4C45A23, + 0x18727070F1BD400B, 0x1FCBACD259BF02E7, 0xD310A7C2CE9B6555, 0xBF983FE0FE5D8244, + 0x9F74D14F7454A824, 0x51EBDC4AB9BA3035, 0x5C82C505DB9AB0FA, 0xFCF7FE8A3430B241, + 0x3253A729B9BA3DDE, 0x8C74C368081B3075, 0xB9BC6C87167C33E7, 0x7EF48F2B83024E20, + 0x11D505D4C351BD7F, 0x6568FCA92C76A243, 0x4DE0B0F40F32A7B8, 0x96D693460CC37E5D, + 0x42E240CB63689F2F, 0x6D2BDCDAE2919661, 0x42880B0236E4D951, 0x5F0F4A5898171BB6, + 0x39F890F579F92F88, 0x93C5B5F47356388B, 0x63DC359D8D231B78, 0xEC16CA8AEA98AD76, + 0x5355F900C2A82DC7, 0x07FB9F855A997142, 0x5093417AA8A7ED5E, 0x7BCBC38DA25A7F3C, + 0x19FC8A768CF4B6D4, 0x637A7780DECFC0D9, 0x8249A47AEE0E41F7, 0x79AD695501E7D1E8, + 0x14ACBAF4777D5776, 0xF145B6BECCDEA195, 0xDABF2AC8201752FC, 0x24C3C94DF9C8D3F6, + 0xBB6E2924F03912EA, 0x0CE26C0B95C980D9, 0xA49CD132BFBF7CC4, 0xE99D662AF4243939, + 0x27E6AD7891165C3F, 0x8535F040B9744FF1, 0x54B3F4FA5F40D873, 0x72B12C32127FED2B, + 0xEE954D3C7B411F47, 0x9A85AC909A24EAA1, 0x70AC4CD9F04F21F5, 0xF9B89D3E99A075C2, + 0x87B3E2B2B5C907B1, 0xA366E5B8C54F48B8, 0xAE4A9346CC3F7CF2, 0x1920C04D47267BBD, + 0x87BF02C6B49E2AE9, 0x092237AC237F3859, 0xFF07F64EF8ED14D0, 0x8DE8DCA9F03CC54E, + 0x9C1633264DB49C89, 0xB3F22C3D0B0B38ED, 0x390E5FB44D01144B, 0x5BFEA5B4712768E9, + 0x1E1032911FA78984, 0x9A74ACB964E78CB3, 0x4F80F7A035DAFB04, 0x6304D09A0B3738C4, + 0x2171E64683023A08, 0x5B9B63EB9CEFF80C, 0x506AACF489889342, 0x1881AFC9A3A701D6, + 0x6503080440750644, 0xDFD395339CDBF4A7, 0xEF927DBCF00C20F2, 0x7B32F7D1E03680EC, + 0xB9FD7620E7316243, 0x05A7E8A57DB91B77, 0xB5889C6E15630A75, 0x4A750A09CE9573F7, + 0xCF464CEC899A2F8A, 0xF538639CE705B824, 0x3C79A0FF5580EF7F, 0xEDE6C87F8477609D, + 0x799E81F05BC93F31, 0x86536B8CF3428A8C, 0x97D7374C60087B73, 0xA246637CFF328532, + 0x043FCAE60CC0EBA0, 0x920E449535DD359E, 0x70EB093B15B290CC, 0x73A1921916591CBD, + 0x56436C9FE1A1AA8D, 0xEFAC4B70633B8F81, 0xBB215798D45DF7AF, 0x45F20042F24F1768, + 0x930F80F4E8EB7462, 0xFF6712FFCFD75EA1, 0xAE623FD67468AA70, 0xDD2C5BC84BC8D8FC, + 0x7EED120D54CF2DD9, 0x22FE545401165F1C, 0xC91800E98FB99929, 0x808BD68E6AC10365, + 0xDEC468145B7605F6, 0x1BEDE3A3AEF53302, 0x43539603D6C55602, 0xAA969B5C691CCB7A, + 0xA87832D392EFEE56, 0x65942C7B3C7E11AE, 0xDED2D633CAD004F6, 0x21F08570F420E565, + 0xB415938D7DA94E3C, 0x91B859E59ECB6350, 0x10CFF333E0ED804A, 0x28AED140BE0BB7DD, + 0xC5CC1D89724FA456, 0x5648F680F11A2741, 0x2D255069F0B7DAB3, 0x9BC5A38EF729ABD4, + 0xEF2F054308F6A2BC, 0xAF2042F5CC5C2858, 0x480412BAB7F5BE2A, 0xAEF3AF4A563DFE43, + 0x19AFE59AE451497F, 0x52593803DFF1E840, 0xF4F076E65F2CE6F0, 0x11379625747D5AF3, + 0xBCE5D2248682C115, 0x9DA4243DE836994F, 0x066F70B33FE09017, 0x4DC4DE189B671A1C, + 0x51039AB7712457C3, 0xC07A3F80C31FB4B4, 0xB46EE9C5E64A6E7C, 0xB3819A42ABE61C87, + 0x21A007933A522A20, 0x2DF16F761598AA4F, 0x763C4A1371B368FD, 0xF793C46702E086A0, + 0xD7288E012AEB8D31, 0xDE336A2A4BC1C44B, 0x0BF692B38D079F23, 0x2C604A7A177326B3, + 0x4850E73E03EB6064, 0xCFC447F1E53C8E1B, 0xB05CA3F564268D99, 0x9AE182C8BC9474E8, + 0xA4FC4BD4FC5558CA, 0xE755178D58FC4E76, 0x69B97DB1A4C03DFE, 0xF9B5B7C4ACC67C96, + 0xFC6A82D64B8655FB, 0x9C684CB6C4D24417, 0x8EC97D2917456ED0, 0x6703DF9D2924E97E, + 0xC547F57E42A7444E, 0x78E37644E7CAD29E, 0xFE9A44E9362F05FA, 0x08BD35CC38336615, + 0x9315E5EB3A129ACE, 0x94061B871E04DF75, 0xDF1D9F9D784BA010, 0x3BBA57B68871B59D, + 0xD2B7ADEEDED1F73F, 0xF7A255D83BC373F8, 0xD7F4F2448C0CEB81, 0xD95BE88CD210FFA7, + 0x336F52F8FF4728E7, 0xA74049DAC312AC71, 0xA2F61BB6E437FDB5, 0x4F2A5CB07F6A35B3, + 0x87D380BDA5BF7859, 0x16B9F7E06C453A21, 0x7BA2484C8A0FD54E, 0xF3A678CAD9A2E38C, + 0x39B0BF7DDE437BA2, 0xFCAF55C1BF8A4424, 0x18FCF680573FA594, 0x4C0563B89F495AC3, + 0x40E087931A00930D, 0x8CFFA9412EB642C1, 0x68CA39053261169F, 0x7A1EE967D27579E2, + 0x9D1D60E5076F5B6F, 0x3810E399B6F65BA2, 0x32095B6D4AB5F9B1, 0x35CAB62109DD038A, + 0xA90B24499FCFAFB1, 0x77A225A07CC2C6BD, 0x513E5E634C70E331, 0x4361C0CA3F692F12, + 0xD941ACA44B20A45B, 0x528F7C8602C5807B, 0x52AB92BEB9613989, 0x9D1DFA2EFC557F73, + 0x722FF175F572C348, 0x1D1260A51107FE97, 0x7A249A57EC0C9BA2, 0x04208FE9E8F7F2D6, + 0x5A110C6058B920A0, 0x0CD9A497658A5698, 0x56FD23C8F9715A4C, 0x284C847B9D887AAE, + 0x04FEABFBBDB619CB, 0x742E1E651C60BA83, 0x9A9632E65904AD3C, 0x881B82A13B51B9E2, + 0x506E6744CD974924, 0xB0183DB56FFC6A79, 0x0ED9B915C66ED37E, 0x5E11E86D5873D484, + 0xF678647E3519AC6E, 0x1B85D488D0F20CC5, 0xDAB9FE6525D89021, 0x0D151D86ADB73615, + 0xA865A54EDCC0F019, 0x93C42566AEF98FFB, 0x99E7AFEABE000731, 0x48CBFF086DDF285A, + 0x7F9B6AF1EBF78BAF, 0x58627E1A149BBA21, 0x2CD16E2ABD791E33, 0xD363EFF5F0977996, + 0x0CE2A38C344A6EED, 0x1A804AADB9CFA741, 0x907F30421D78C5DE, 0x501F65EDB3034D07, + 0x37624AE5A48FA6E9, 0x957BAF61700CFF4E, 0x3A6C27934E31188A, 0xD49503536ABCA345, + 0x088E049589C432E0, 0xF943AEE7FEBF21B8, 0x6C3B8E3E336139D3, 0x364F6FFA464EE52E, + 0xD60F6DCEDC314222, 0x56963B0DCA418FC0, 0x16F50EDF91E513AF, 0xEF1955914B609F93, + 0x565601C0364E3228, 0xECB53939887E8175, 0xBAC7A9A18531294B, 0xB344C470397BBA52, + 0x65D34954DAF3CEBD, 0xB4B81B3FA97511E2, 0xB422061193D6F6A7, 0x071582401C38434D, + 0x7A13F18BBEDC4FF5, 0xBC4097B116C524D2, 0x59B97885E2F2EA28, 0x99170A5DC3115544, + 0x6F423357E7C6A9F9, 0x325928EE6E6F8794, 0xD0E4366228B03343, 0x565C31F7DE89EA27, + 0x30F5611484119414, 0xD873DB391292ED4F, 0x7BD94E1D8E17DEBC, 0xC7D9F16864A76E94, + 0x947AE053EE56E63C, 0xC8C93882F9475F5F, 0x3A9BF55BA91F81CA, 0xD9A11FBB3D9808E4, + 0x0FD22063EDC29FCA, 0xB3F256D8ACA0B0B9, 0xB03031A8B4516E84, 0x35DD37D5871448AF, + 0xE9F6082B05542E4E, 0xEBFAFA33D7254B59, 0x9255ABB50D532280, 0xB9AB4CE57F2D34F3, + 0x693501D628297551, 0xC62C58F97DD949BF, 0xCD454F8F19C5126A, 0xBBE83F4ECC2BDECB, + 0xDC842B7E2819E230, 0xBA89142E007503B8, 0xA3BC941D0A5061CB, 0xE9F6760E32CD8021, + 0x09C7E552BC76492F, 0x852F54934DA55CC9, 0x8107FCCF064FCF56, 0x098954D51FFF6580, + 0x23B70EDB1955C4BF, 0xC330DE426430F69D, 0x4715ED43E8A45C0A, 0xA8D7E4DAB780A08D, + 0x0572B974F03CE0BB, 0xB57D2E985E1419C7, 0xE8D9ECBE2CF3D73F, 0x2FE4B17170E59750, + 0x11317BA87905E790, 0x7FBF21EC8A1F45EC, 0x1725CABFCB045B00, 0x964E915CD5E2B207, + 0x3E2B8BCBF016D66D, 0xBE7444E39328A0AC, 0xF85B2B4FBCDE44B7, 0x49353FEA39BA63B1, + 0x1DD01AAFCD53486A, 0x1FCA8A92FD719F85, 0xFC7C95D827357AFA, 0x18A6A990C8B35EBD, + 0xCCCB7005C6B9C28D, 0x3BDBB92C43B17F26, 0xAA70B5B4F89695A2, 0xE94C39A54A98307F, + 0xB7A0B174CFF6F36E, 0xD4DBA84729AF48AD, 0x2E18BC1AD9704A68, 0x2DE0966DAF2F8B1C, + 0xB9C11D5B1E43A07E, 0x64972D68DEE33360, 0x94628D38D0C20584, 0xDBC0D2B6AB90A559, + 0xD2733C4335C6A72F, 0x7E75D99D94A70F4D, 0x6CED1983376FA72B, 0x97FCAACBF030BC24, + 0x7B77497B32503B12, 0x8547EDDFB81CCB94, 0x79999CDFF70902CB, 0xCFFE1939438E9B24, + 0x829626E3892D95D7, 0x92FAE24291F2B3F1, 0x63E22C147B9C3403, 0xC678B6D860284A1C, + 0x5873888850659AE7, 0x0981DCD296A8736D, 0x9F65789A6509A440, 0x9FF38FED72E9052F, + 0xE479EE5B9930578C, 0xE7F28ECD2D49EECD, 0x56C074A581EA17FE, 0x5544F7D774B14AEF, + 0x7B3F0195FC6F290F, 0x12153635B2C0CF57, 0x7F5126DBBA5E0CA7, 0x7A76956C3EAFB413, + 0x3D5774A11D31AB39, 0x8A1B083821F40CB4, 0x7B4A38E32537DF62, 0x950113646D1D6E03, + 0x4DA8979A0041E8A9, 0x3BC36E078F7515D7, 0x5D0A12F27AD310D1, 0x7F9D1A2E1EBE1327, + 0xDA3A361B1C5157B1, 0xDCDD7D20903D0C25, 0x36833336D068F707, 0xCE68341F79893389, + 0xAB9090168DD05F34, 0x43954B3252DC25E5, 0xB438C2B67F98E5E9, 0x10DCD78E3851A492, + 0xDBC27AB5447822BF, 0x9B3CDB65F82CA382, 0xB67B7896167B4C84, 0xBFCED1B0048EAC50, + 0xA9119B60369FFEBD, 0x1FFF7AC80904BF45, 0xAC12FB171817EEE7, 0xAF08DA9177DDA93D, + 0x1B0CAB936E65C744, 0xB559EB1D04E5E932, 0xC37B45B3F8D6F2BA, 0xC3A9DC228CAAC9E9, + 0xF3B8B6675A6507FF, 0x9FC477DE4ED681DA, 0x67378D8ECCEF96CB, 0x6DD856D94D259236, + 0xA319CE15B0B4DB31, 0x073973751F12DD5E, 0x8A8E849EB32781A5, 0xE1925C71285279F5, + 0x74C04BF1790C0EFE, 0x4DDA48153C94938A, 0x9D266D6A1CC0542C, 0x7440FB816508C4FE, + 0x13328503DF48229F, 0xD6BF7BAEE43CAC40, 0x4838D65F6EF6748F, 0x1E152328F3318DEA, + 0x8F8419A348F296BF, 0x72C8834A5957B511, 0xD7A023A73260B45C, 0x94EBC8ABCFB56DAE, + 0x9FC10D0F989993E0, 0xDE68A2355B93CAE6, 0xA44CFE79AE538BBE, 0x9D1D84FCCE371425, + 0x51D2B1AB2DDFB636, 0x2FD7E4B9E72CD38C, 0x65CA5B96B7552210, 0xDD69A0D8AB3B546D, + 0x604D51B25FBF70E2, 0x73AA8A564FB7AC9E, 0x1A8C1E992B941148, 0xAAC40A2703D9BEA0, + 0x764DBEAE7FA4F3A6, 0x1E99B96E70A9BE8B, 0x2C5E9DEB57EF4743, 0x3A938FEE32D29981, + 0x26E6DB8FFDF5ADFE, 0x469356C504EC9F9D, 0xC8763C5B08D1908C, 0x3F6C6AF859D80055, + 0x7F7CC39420A3A545, 0x9BFB227EBDF4C5CE, 0x89039D79D6FC5C5C, 0x8FE88B57305E2AB6, + 0xA09E8C8C35AB96DE, 0xFA7E393983325753, 0xD6B6D0ECC617C699, 0xDFEA21EA9E7557E3, + 0xB67C1FA481680AF8, 0xCA1E3785A9E724E5, 0x1CFC8BED0D681639, 0xD18D8549D140CAEA, + 0x4ED0FE7E9DC91335, 0xE4DBF0634473F5D2, 0x1761F93A44D5AEFE, 0x53898E4C3910DA55, + 0x734DE8181F6EC39A, 0x2680B122BAA28D97, 0x298AF231C85BAFAB, 0x7983EED3740847D5, + 0x66C1A2A1A60CD889, 0x9E17E49642A3E4C1, 0xEDB454E7BADC0805, 0x50B704CAB602C329, + 0x4CC317FB9CDDD023, 0x66B4835D9EAFEA22, 0x219B97E26FFC81BD, 0x261E4E4C0A333A9D, + 0x1FE2CCA76517DB90, 0xD7504DFA8816EDBB, 0xB9571FA04DC089C8, 0x1DDC0325259B27DE, + 0xCF3F4688801EB9AA, 0xF4F5D05C10CAB243, 0x38B6525C21A42B0E, 0x36F60E2BA4FA6800, + 0xEB3593803173E0CE, 0x9C4CD6257C5A3603, 0xAF0C317D32ADAA8A, 0x258E5A80C7204C4B, + 0x8B889D624D44885D, 0xF4D14597E660F855, 0xD4347F66EC8941C3, 0xE699ED85B0DFB40D, + 0x2472F6207C2D0484, 0xC2A1E7B5B459AEB5, 0xAB4F6451CC1D45EC, 0x63767572AE3D6174, + 0xA59E0BD101731A28, 0x116D0016CB948F09, 0x2CF9C8CA052F6E9F, 0x0B090A7560A968E3, + 0xABEEDDB2DDE06FF1, 0x58EFC10B06A2068D, 0xC6E57A78FBD986E0, 0x2EAB8CA63CE802D7, + 0x14A195640116F336, 0x7C0828DD624EC390, 0xD74BBE77E6116AC7, 0x804456AF10F5FB53, + 0xEBE9EA2ADF4321C7, 0x03219A39EE587A30, 0x49787FEF17AF9924, 0xA1E9300CD8520548, + 0x5B45E522E4B1B4EF, 0xB49C3B3995091A36, 0xD4490AD526F14431, 0x12A8F216AF9418C2, + 0x001F837CC7350524, 0x1877B51E57A764D5, 0xA2853B80F17F58EE, 0x993E1DE72D36D310, + 0xB3598080CE64A656, 0x252F59CF0D9F04BB, 0xD23C8E176D113600, 0x1BDA0492E7E4586E, + 0x21E0BD5026C619BF, 0x3B097ADAF088F94E, 0x8D14DEDB30BE846E, 0xF95CFFA23AF5F6F4, + 0x3871700761B3F743, 0xCA672B91E9E4FA16, 0x64C8E531BFF53B55, 0x241260ED4AD1E87D, + 0x106C09B972D2E822, 0x7FBA195410E5CA30, 0x7884D9BC6CB569D8, 0x0647DFEDCD894A29, + 0x63573FF03E224774, 0x4FC8E9560F91B123, 0x1DB956E450275779, 0xB8D91274B9E9D4FB, + 0xA2EBEE47E2FBFCE1, 0xD9F1F30CCD97FB09, 0xEFED53D75FD64E6B, 0x2E6D02C36017F67F, + 0xA9AA4D20DB084E9B, 0xB64BE8D8B25396C1, 0x70CB6AF7C2D5BCF0, 0x98F076A4F7A2322E, + 0xBF84470805E69B5F, 0x94C3251F06F90CF3, 0x3E003E616A6591E9, 0xB925A6CD0421AFF3, + 0x61BDD1307C66E300, 0xBF8D5108E27E0D48, 0x240AB57A8B888B20, 0xFC87614BAF287E07, + 0xEF02CDD06FFDB432, 0xA1082C0466DF6C0A, 0x8215E577001332C8, 0xD39BB9C3A48DB6CF, + 0x2738259634305C14, 0x61CF4F94C97DF93D, 0x1B6BACA2AE4E125B, 0x758F450C88572E0B, + 0x959F587D507A8359, 0xB063E962E045F54D, 0x60E8ED72C0DFF5D1, 0x7B64978555326F9F, + 0xFD080D236DA814BA, 0x8C90FD9B083F4558, 0x106F72FE81E2C590, 0x7976033A39F7D952, + 0xA4EC0132764CA04B, 0x733EA705FAE4FA77, 0xB4D8F77BC3E56167, 0x9E21F4F903B33FD9, + 0x9D765E419FB69F6D, 0xD30C088BA61EA5EF, 0x5D94337FBFAF7F5B, 0x1A4E4822EB4D7A59, + 0x6FFE73E81B637FB3, 0xDDF957BC36D8B9CA, 0x64D0E29EEA8838B3, 0x08DD9BDFD96B9F63, + 0x087E79E5A57D1D13, 0xE328E230E3E2B3FB, 0x1C2559E30F0946BE, 0x720BF5F26F4D2EAA, + 0xB0774D261CC609DB, 0x443F64EC5A371195, 0x4112CF68649A260E, 0xD813F2FAB7F5C5CA, + 0x660D3257380841EE, 0x59AC2C7873F910A3, 0xE846963877671A17, 0x93B633ABFA3469F8, + 0xC0C0F5A60EF4CDCF, 0xCAF21ECD4377B28C, 0x57277707199B8175, 0x506C11B9D90E8B1D, + 0xD83CC2687A19255F, 0x4A29C6465A314CD1, 0xED2DF21216235097, 0xB5635C95FF7296E2, + 0x22AF003AB672E811, 0x52E762596BF68235, 0x9AEBA33AC6ECC6B0, 0x944F6DE09134DFB6, + 0x6C47BEC883A7DE39, 0x6AD047C430A12104, 0xA5B1CFDBA0AB4067, 0x7C45D833AFF07862, + 0x5092EF950A16DA0B, 0x9338E69C052B8E7B, 0x455A4B4CFE30E3F5, 0x6B02E63195AD0CF8, + 0x6B17B224BAD6BF27, 0xD1E0CCD25BB9C169, 0xDE0C89A556B9AE70, 0x50065E535A213CF6, + 0x9C1169FA2777B874, 0x78EDEFD694AF1EED, 0x6DC93D9526A50E68, 0xEE97F453F06791ED, + 0x32AB0EDB696703D3, 0x3A6853C7E70757A7, 0x31865CED6120F37D, 0x67FEF95D92607890, + 0x1F2B1D1F15F6DC9C, 0xB69E38A8965C6B65, 0xAA9119FF184CCCF4, 0xF43C732873F24C13, + 0xFB4A3D794A9A80D2, 0x3550C2321FD6109C, 0x371F77E76BB8417E, 0x6BFA9AAE5EC05779, + 0xCD04F3FF001A4778, 0xE3273522064480CA, 0x9F91508BFFCFC14A, 0x049A7F41061A9E60, + 0xFCB6BE43A9F2FE9B, 0x08DE8A1C7797DA9B, 0x8F9887E6078735A1, 0xB5B4071DBFC73A66, + 0x230E343DFBA08D33, 0x43ED7F5A0FAE657D, 0x3A88A0FBBCB05C63, 0x21874B8B4D2DBC4F, + 0x1BDEA12E35F6A8C9, 0x53C065C6C8E63528, 0xE34A1D250E7A8D6B, 0xD6B04D3B7651DD7E, + 0x5E90277E7CB39E2D, 0x2C046F22062DC67D, 0xB10BB459132D0A26, 0x3FA9DDFB67E2F199, + 0x0E09B88E1914F7AF, 0x10E8B35AF3EEAB37, 0x9EEDECA8E272B933, 0xD4C718BC4AE8AE5F, + 0x81536D601170FC20, 0x91B534F885818A06, 0xEC8177F83F900978, 0x190E714FADA5156E, + 0xB592BF39B0364963, 0x89C350C893AE7DC1, 0xAC042E70F8B383F2, 0xB49B52E587A1EE60, + 0xFB152FE3FF26DA89, 0x3E666E6F69AE2C15, 0x3B544EBE544C19F9, 0xE805A1E290CF2456, + 0x24B33C9D7ED25117, 0xE74733427B72F0C1, 0x0A804D18B7097475, 0x57E3306D881EDB4F, + 0x4AE7D6A36EB5DBCB, 0x2D8D5432157064C8, 0xD1E649DE1E7F268B, 0x8A328A1CEDFE552C, + 0x07A3AEC79624C7DA, 0x84547DDC3E203C94, 0x990A98FD5071D263, 0x1A4FF12616EEFC89, + 0xF6F7FD1431714200, 0x30C05B1BA332F41C, 0x8D2636B81555A786, 0x46C9FEB55D120902, + 0xCCEC0A73B49C9921, 0x4E9D2827355FC492, 0x19EBB029435DCB0F, 0x4659D2B743848A2C, + 0x963EF2C96B33BE31, 0x74F85198B05A2E7D, 0x5A0F544DD2B1FB18, 0x03727073C2E134B1, + 0xC7F6AA2DE59AEA61, 0x352787BAA0D7C22F, 0x9853EAB63B5E0B35, 0xABBDCDD7ED5C0860, + 0xCF05DAF5AC8D77B0, 0x49CAD48CEBF4A71E, 0x7A4C10EC2158C4A6, 0xD9E92AA246BF719E, + 0x13AE978D09FE5557, 0x730499AF921549FF, 0x4E4B705B92903BA4, 0xFF577222C14F0A3A, + 0x55B6344CF97AAFAE, 0xB862225B055B6960, 0xCAC09AFBDDD2CDB4, 0xDAF8E9829FE96B5F, + 0xB5FDFC5D3132C498, 0x310CB380DB6F7503, 0xE87FBB46217A360E, 0x2102AE466EBB1148, + 0xF8549E1A3AA5E00D, 0x07A69AFDCC42261A, 0xC4C118BFE78FEAAE, 0xF9F4892ED96BD438, + 0x1AF3DBE25D8F45DA, 0xF5B4B0B0D2DEEEB4, 0x962ACEEFA82E1C84, 0x046E3ECAAF453CE9, + 0xF05D129681949A4C, 0x964781CE734B3C84, 0x9C2ED44081CE5FBD, 0x522E23F3925E319E, + 0x177E00F9FC32F791, 0x2BC60A63A6F3B3F2, 0x222BBFAE61725606, 0x486289DDCC3D6780, + 0x7DC7785B8EFDFC80, 0x8AF38731C02BA980, 0x1FAB64EA29A2DDF7, 0xE4D9429322CD065A, + 0x9DA058C67844F20C, 0x24C0E332B70019B0, 0x233003B5A6CFE6AD, 0xD586BD01C5C217F6, + 0x5E5637885F29BC2B, 0x7EBA726D8C94094B, 0x0A56A5F0BFE39272, 0xD79476A84EE20D06, + 0x9E4C1269BAA4BF37, 0x17EFEE45B0DEE640, 0x1D95B0A5FCF90BC6, 0x93CBE0B699C2585D, + 0x65FA4F227A2B6D79, 0xD5F9E858292504D5, 0xC2B5A03F71471A6F, 0x59300222B4561E00, + 0xCE2F8642CA0712DC, 0x7CA9723FBB2E8988, 0x2785338347F2BA08, 0xC61BB3A141E50E8C, + 0x150F361DAB9DEC26, 0x9F6A419D382595F4, 0x64A53DC924FE7AC9, 0x142DE49FFF7A7C3D, + 0x0C335248857FA9E7, 0x0A9C32D5EAE45305, 0xE6C42178C4BBB92E, 0x71F1CE2490D20B07, + 0xF1BCC3D275AFE51A, 0xE728E8C83C334074, 0x96FBF83A12884624, 0x81A1549FD6573DA5, + 0x5FA7867CAF35E149, 0x56986E2EF3ED091B, 0x917F1DD5F8886C61, 0xD20D8C88C8FFE65F, + 0x31D71DCE64B2C310, 0xF165B587DF898190, 0xA57E6339DD2CF3A0, 0x1EF6E6DBB1961EC9, + 0x70CC73D90BC26E24, 0xE21A6B35DF0C3AD7, 0x003A93D8B2806962, 0x1C99DED33CB890A1, + 0xCF3145DE0ADD4289, 0xD0E4427A5514FB72, 0x77C621CC9FB3A483, 0x67A34DAC4356550B, + 0xF8D626AAAF278509}; static constexpr std::array castlingKey = []() constexpr { auto generateCastlingKey = [](int index) constexpr -> U64 { @@ -1686,8 +1835,10 @@ class Zobrist { U64 key = 0; - for (int i = 0; i < RANDOM_COUNT; ++i) { - if (index & (1 << i)) { + for (int i = 0; i < RANDOM_COUNT; ++i) + { + if (index & (1 << i)) + { key ^= RANDOM_ARRAY[RANDOM_OFFSET + i]; } } @@ -1697,7 +1848,8 @@ class Zobrist { std::array arr{}; - for (int i = 0; i < 16; ++i) arr[i] = generateCastlingKey(i); + for (int i = 0; i < 16; ++i) + arr[i] = generateCastlingKey(i); return arr; }(); @@ -1736,25 +1888,29 @@ namespace chess { namespace detail { inline std::optional parseStringViewToInt(std::string_view sv) { - if (sv.empty()) return std::nullopt; + if (sv.empty()) + return std::nullopt; std::string_view parsed_sv = sv; - if (parsed_sv.back() == ';') parsed_sv.remove_suffix(1); + if (parsed_sv.back() == ';') + parsed_sv.remove_suffix(1); - if (parsed_sv.empty()) return std::nullopt; + if (parsed_sv.empty()) + return std::nullopt; #ifdef CHESS_USE_CHARCONV - int result; - const char *begin = parsed_sv.data(); - const char *end = begin + parsed_sv.size(); + int result; + const char* begin = parsed_sv.data(); + const char* end = begin + parsed_sv.size(); auto [ptr, ec] = std::from_chars(begin, end, result); - if (ec == std::errc() && ptr == end) { + if (ec == std::errc() && ptr == end) + { return result; } #else - std::string str(parsed_sv); + std::string str(parsed_sv); std::stringstream ss(str); ss.exceptions(std::ios::goodbit); @@ -1762,7 +1918,8 @@ inline std::optional parseStringViewToInt(std::string_view sv) { int value; ss >> value; - if (!ss.fail() && (ss.eof() || (ss >> std::ws).eof())) { + if (!ss.fail() && (ss.eof() || (ss >> std::ws).eof())) + { return value; } #endif @@ -1771,7 +1928,12 @@ inline std::optional parseStringViewToInt(std::string_view sv) { } } // namespace detail -enum class GameResult { WIN, LOSE, DRAW, NONE }; +enum class GameResult { + WIN, + LOSE, + DRAW, + NONE +}; enum class GameResultReason { CHECKMATE, @@ -1782,7 +1944,11 @@ enum class GameResultReason { NONE }; -enum class CheckType { NO_CHECK, DIRECT_CHECK, DISCOVERY_CHECK }; +enum class CheckType { + NO_CHECK, + DIRECT_CHECK, + DISCOVERY_CHECK +}; // A compact representation of the board in 24 bytes, // does not include the half-move clock or full move number. @@ -1794,13 +1960,18 @@ class Board { public: class CastlingRights { public: - enum class Side : std::uint8_t { KING_SIDE, QUEEN_SIDE }; + enum class Side : std::uint8_t { + KING_SIDE, + QUEEN_SIDE + }; constexpr void setCastlingRight(Color color, Side castle, File rook_file) { rooks[color][static_cast(castle)] = rook_file; } - constexpr void clear() { rooks[0][0] = rooks[0][1] = rooks[1][0] = rooks[1][1] = File::NO_FILE; } + constexpr void clear() { + rooks[0][0] = rooks[0][1] = rooks[1][0] = rooks[1][1] = File::NO_FILE; + } constexpr int clear(Color color, Side castle) { rooks[color][static_cast(castle)] = File::NO_FILE; @@ -1813,18 +1984,22 @@ class Board { return rooks[color][static_cast(castle)] != File::NO_FILE; } - constexpr bool has(Color color) const { return has(color, Side::KING_SIDE) || has(color, Side::QUEEN_SIDE); } + constexpr bool has(Color color) const { + return has(color, Side::KING_SIDE) || has(color, Side::QUEEN_SIDE); + } - constexpr File getRookFile(Color color, Side castle) const { return rooks[color][static_cast(castle)]; } + constexpr File getRookFile(Color color, Side castle) const { + return rooks[color][static_cast(castle)]; + } constexpr int hashIndex() const { - return has(Color::WHITE, Side::KING_SIDE) + 2 * has(Color::WHITE, Side::QUEEN_SIDE) + - 4 * has(Color::BLACK, Side::KING_SIDE) + 8 * has(Color::BLACK, Side::QUEEN_SIDE); + return has(Color::WHITE, Side::KING_SIDE) + 2 * has(Color::WHITE, Side::QUEEN_SIDE) + + 4 * has(Color::BLACK, Side::KING_SIDE) + 8 * has(Color::BLACK, Side::QUEEN_SIDE); } constexpr bool isEmpty() const { return !has(Color::WHITE) && !has(Color::BLACK); } - template + template static constexpr Side closestSide(T sq, T pred) { return sq > pred ? Side::KING_SIDE : Side::QUEEN_SIDE; } @@ -1835,22 +2010,27 @@ class Board { private: struct State { - U64 hash; + U64 hash; CastlingRights castling; - Square enpassant; - std::uint8_t half_moves; - Piece captured_piece; - - State(const U64 &hash, const CastlingRights &castling, const Square &enpassant, const std::uint8_t &half_moves, - const Piece &captured_piece) - : hash(hash), - castling(castling), - enpassant(enpassant), - half_moves(half_moves), - captured_piece(captured_piece) {} + Square enpassant; + std::uint8_t half_moves; + Piece captured_piece; + + State(const U64& hash, + const CastlingRights& castling, + const Square& enpassant, + const std::uint8_t& half_moves, + const Piece& captured_piece) : + hash(hash), + castling(castling), + enpassant(enpassant), + half_moves(half_moves), + captured_piece(captured_piece) {} }; - enum class PrivateCtor { CREATE }; + enum class PrivateCtor { + CREATE + }; // private constructor to avoid initialization Board(PrivateCtor) {} @@ -1885,36 +2065,47 @@ class Board { bool setEpd(const std::string_view epd) { auto parts = utils::splitString(epd, ' '); - if (parts.size() < 4) return false; + if (parts.size() < 4) + return false; int hm = 0; int fm = 1; - if (auto it = std::find(parts.begin(), parts.end(), "hmvc"); it != parts.end()) { - if (std::distance(it, parts.end()) > 1) { + if (auto it = std::find(parts.begin(), parts.end(), "hmvc"); it != parts.end()) + { + if (std::distance(it, parts.end()) > 1) + { auto num = *(it + 1); auto parsed = detail::parseStringViewToInt(num); - if (parsed) hm = *parsed; - } else { + if (parsed) + hm = *parsed; + } + else + { return false; } } - if (auto it = std::find(parts.begin(), parts.end(), "fmvn"); it != parts.end()) { - if (std::distance(it, parts.end()) > 1) { + if (auto it = std::find(parts.begin(), parts.end(), "fmvn"); it != parts.end()) + { + if (std::distance(it, parts.end()) > 1) + { auto num = *(it + 1); auto parsed = detail::parseStringViewToInt(num); if (parsed && *parsed > 0) fm = *parsed; else return false; - } else { + } + else + { return false; } } - auto fen = std::string(parts[0]) + " " + std::string(parts[1]) + " " + std::string(parts[2]) + " " + - std::string(parts[3]) + " " + std::to_string(hm) + " " + std::to_string(fm); + auto fen = std::string(parts[0]) + " " + std::string(parts[1]) + " " + std::string(parts[2]) + + " " + std::string(parts[3]) + " " + std::to_string(hm) + " " + + std::to_string(fm); return setFen(fen); } @@ -1929,26 +2120,32 @@ class Board { ss.reserve(100); // Loop through the ranks of the board in reverse order - for (int rank = 7; rank >= 0; rank--) { + for (int rank = 7; rank >= 0; rank--) + { std::uint32_t free_space = 0; // Loop through the files of the board - for (int file = 0; file < 8; file++) { + for (int file = 0; file < 8; file++) + { // Calculate the square index const int sq = rank * 8 + file; // If there is a piece at the current square - if (Piece piece = at(Square(sq)); piece != Piece::NONE) { + if (Piece piece = at(Square(sq)); piece != Piece::NONE) + { // If there were any empty squares before this piece, // append the number of empty squares to the FEN string - if (free_space) { + if (free_space) + { ss += std::to_string(free_space); free_space = 0; } // Append the character representing the piece to the FEN string ss += static_cast(piece); - } else { + } + else + { // If there is no piece at the current square, increment the // counter for the number of empty squares free_space++; @@ -1957,7 +2154,8 @@ class Board { // If there are any empty squares at the end of the rank, // append the number of empty squares to the FEN string - if (free_space != 0) { + if (free_space != 0) + { ss += std::to_string(free_space); } @@ -1973,7 +2171,8 @@ class Board { // whether castling is allowed for each player if (cr_.isEmpty()) ss += " -"; - else { + else + { ss += ' '; ss += getCastleString(); } @@ -1982,12 +2181,14 @@ class Board { // and the half-move clock and full move number to the FEN string if (ep_sq_ == Square::NO_SQ) ss += " -"; - else { + else + { ss += ' '; ss += static_cast(ep_sq_); } - if (move_counters) { + if (move_counters) + { ss += ' '; ss += std::to_string(halfMoveClock()); ss += ' '; @@ -2019,7 +2220,7 @@ class Board { * @tparam EXACT * @param move */ - template + template void makeMove(const Move move) { const auto capture = at(move.to()) != Piece::NONE && move.typeOf() != Move::CASTLING; const auto captured = at(move.to()); @@ -2033,53 +2234,66 @@ class Board { hfm_++; plies_++; - if (ep_sq_ != Square::NO_SQ) key_ ^= Zobrist::enpassant(ep_sq_.file()); + if (ep_sq_ != Square::NO_SQ) + key_ ^= Zobrist::enpassant(ep_sq_.file()); ep_sq_ = Square::NO_SQ; - if (capture) { + if (capture) + { removePiece(captured, move.to()); hfm_ = 0; key_ ^= Zobrist::piece(captured, move.to()); // remove castling rights if rook is captured - if (captured.type() == PieceType::ROOK && Rank::back_rank(move.to().rank(), ~stm_)) { + if (captured.type() == PieceType::ROOK && Rank::back_rank(move.to().rank(), ~stm_)) + { const auto king_sq = kingSq(~stm_); const auto file = CastlingRights::closestSide(move.to(), king_sq); - if (cr_.getRookFile(~stm_, file) == move.to().file()) { + if (cr_.getRookFile(~stm_, file) == move.to().file()) + { key_ ^= Zobrist::castlingIndex(cr_.clear(~stm_, file)); } } } // remove castling rights if king moves - if (pt == PieceType::KING && cr_.has(stm_)) { + if (pt == PieceType::KING && cr_.has(stm_)) + { key_ ^= Zobrist::castling(cr_.hashIndex()); cr_.clear(stm_); key_ ^= Zobrist::castling(cr_.hashIndex()); - } else if (pt == PieceType::ROOK && Square::back_rank(move.from(), stm_)) { + } + else if (pt == PieceType::ROOK && Square::back_rank(move.from(), stm_)) + { const auto king_sq = kingSq(stm_); const auto file = CastlingRights::closestSide(move.from(), king_sq); // remove castling rights if rook moves from back rank - if (cr_.getRookFile(stm_, file) == move.from().file()) { + if (cr_.getRookFile(stm_, file) == move.from().file()) + { key_ ^= Zobrist::castlingIndex(cr_.clear(stm_, file)); } - } else if (pt == PieceType::PAWN) { + } + else if (pt == PieceType::PAWN) + { hfm_ = 0; // double push - if (Square::value_distance(move.to(), move.from()) == 16) { + if (Square::value_distance(move.to(), move.from()) == 16) + { // imaginary attacks from the ep square from the pawn which moved Bitboard ep_mask = attacks::pawn(stm_, move.to().ep_square()); // add enpassant hash if enemy pawns are attacking the square - if (static_cast(ep_mask & pieces(PieceType::PAWN, ~stm_))) { + if (static_cast(ep_mask & pieces(PieceType::PAWN, ~stm_))) + { int found = -1; // check if the enemy can legally capture the pawn on the next move - if constexpr (EXACT) { + if constexpr (EXACT) + { const auto piece = at(move.from()); found = 0; @@ -2091,13 +2305,19 @@ class Board { bool valid; - if (stm_ == Color::WHITE) { - valid = movegen::isEpSquareValid(*this, move.to().ep_square()); - } else { - valid = movegen::isEpSquareValid(*this, move.to().ep_square()); + if (stm_ == Color::WHITE) + { + valid = + movegen::isEpSquareValid(*this, move.to().ep_square()); + } + else + { + valid = + movegen::isEpSquareValid(*this, move.to().ep_square()); } - if (valid) found = 1; + if (valid) + found = 1; // undo stm_ = ~stm_; @@ -2106,7 +2326,8 @@ class Board { placePieceInternal(piece, move.from()); } - if (found != 0) { + if (found != 0) + { assert(at(move.to().ep_square()) == Piece::NONE); ep_sq_ = move.to().ep_square(); key_ ^= Zobrist::enpassant(move.to().ep_square().file()); @@ -2115,7 +2336,8 @@ class Board { } } - if (move.typeOf() == Move::CASTLING) { + if (move.typeOf() == Move::CASTLING) + { assert(at(move.from()) == PieceType::KING); assert(at(move.to()) == PieceType::ROOK); @@ -2137,7 +2359,9 @@ class Board { key_ ^= Zobrist::piece(king, move.from()) ^ Zobrist::piece(king, kingTo); key_ ^= Zobrist::piece(rook, move.to()) ^ Zobrist::piece(rook, rookTo); - } else if (move.typeOf() == Move::PROMOTION) { + } + else if (move.typeOf() == Move::PROMOTION) + { const auto piece_pawn = Piece(PieceType::PAWN, stm_); const auto piece_prom = Piece(move.promotionType(), stm_); @@ -2145,7 +2369,9 @@ class Board { placePiece(piece_prom, move.to()); key_ ^= Zobrist::piece(piece_pawn, move.from()) ^ Zobrist::piece(piece_prom, move.to()); - } else { + } + else + { assert(at(move.from()) != Piece::NONE); assert(at(move.to()) == Piece::NONE); @@ -2157,7 +2383,8 @@ class Board { key_ ^= Zobrist::piece(piece, move.from()) ^ Zobrist::piece(piece, move.to()); } - if (move.typeOf() == Move::ENPASSANT) { + if (move.typeOf() == Move::ENPASSANT) + { assert(at(move.to().ep_square()) == PieceType::PAWN); const auto piece = Piece(PieceType::PAWN, ~stm_); @@ -2172,7 +2399,7 @@ class Board { } void unmakeMove(const Move move) { - const auto &prev = prev_states_.back(); + const auto& prev = prev_states_.back(); ep_sq_ = prev.enpassant; cr_ = prev.castling; @@ -2180,10 +2407,13 @@ class Board { stm_ = ~stm_; plies_--; - if (move.typeOf() == Move::CASTLING) { - const bool king_side = move.to() > move.from(); - const auto rook_from_sq = Square(king_side ? File::FILE_F : File::FILE_D, move.from().rank()); - const auto king_to_sq = Square(king_side ? File::FILE_G : File::FILE_C, move.from().rank()); + if (move.typeOf() == Move::CASTLING) + { + const bool king_side = move.to() > move.from(); + const auto rook_from_sq = + Square(king_side ? File::FILE_F : File::FILE_D, move.from().rank()); + const auto king_to_sq = + Square(king_side ? File::FILE_G : File::FILE_C, move.from().rank()); assert(at(rook_from_sq) == PieceType::ROOK); assert(at(king_to_sq) == PieceType::KING); @@ -2199,8 +2429,9 @@ class Board { placePiece(king, move.from()); placePiece(rook, move.to()); - - } else if (move.typeOf() == Move::PROMOTION) { + } + else if (move.typeOf() == Move::PROMOTION) + { const auto pawn = Piece(PieceType::PAWN, stm_); const auto piece = at(move.to()); @@ -2212,12 +2443,14 @@ class Board { removePiece(piece, move.to()); placePiece(pawn, move.from()); - if (prev.captured_piece != Piece::NONE) { + if (prev.captured_piece != Piece::NONE) + { assert(at(move.to()) == Piece::NONE); placePiece(prev.captured_piece, move.to()); } - - } else { + } + else + { assert(at(move.to()) != Piece::NONE); assert(at(move.from()) == Piece::NONE); @@ -2226,14 +2459,17 @@ class Board { removePiece(piece, move.to()); placePiece(piece, move.from()); - if (move.typeOf() == Move::ENPASSANT) { + if (move.typeOf() == Move::ENPASSANT) + { const auto pawn = Piece(PieceType::PAWN, ~stm_); const auto pawnTo = static_cast(ep_sq_ ^ 8); assert(at(pawnTo) == Piece::NONE); placePiece(pawn, pawnTo); - } else if (prev.captured_piece != Piece::NONE) { + } + else if (prev.captured_piece != Piece::NONE) + { assert(at(move.to()) == Piece::NONE); placePiece(prev.captured_piece, move.to()); @@ -2251,7 +2487,8 @@ class Board { prev_states_.emplace_back(key_, cr_, ep_sq_, hfm_, Piece::NONE); key_ ^= Zobrist::sideToMove(); - if (ep_sq_ != Square::NO_SQ) key_ ^= Zobrist::enpassant(ep_sq_.file()); + if (ep_sq_ != Square::NO_SQ) + key_ ^= Zobrist::enpassant(ep_sq_.file()); ep_sq_ = Square::NO_SQ; stm_ = ~stm_; @@ -2263,7 +2500,7 @@ class Board { * @brief Unmake a null move. (Switches the side to move) */ void unmakeNullMove() { - const auto &prev = prev_states_.back(); + const auto& prev = prev_states_.back(); ep_sq_ = prev.enpassant; cr_ = prev.castling; @@ -2331,7 +2568,8 @@ class Board { */ [[nodiscard]] Bitboard pieces(PieceType type) const noexcept { return pieces_bb_[type]; } - template && ...)>> + template && ...)>> [[nodiscard]] Bitboard pieces(Pieces... pieces) const noexcept { return (pieces_bb_[static_cast(pieces)] | ...); } @@ -2342,13 +2580,16 @@ class Board { * @param sq * @return */ - template + template [[nodiscard]] T at(Square sq) const noexcept { assert(sq.is_valid()); - if constexpr (std::is_same_v) { + if constexpr (std::is_same_v) + { return board_[sq.index()].type(); - } else { + } + else + { return board_[sq.index()]; } } @@ -2359,23 +2600,25 @@ class Board { * @return */ bool isCapture(const Move move) const noexcept { - return (at(move.to()) != Piece::NONE && move.typeOf() != Move::CASTLING) || move.typeOf() == Move::ENPASSANT; + return (at(move.to()) != Piece::NONE && move.typeOf() != Move::CASTLING) + || move.typeOf() == Move::ENPASSANT; } /** * @brief Get the current zobrist hash key of the board * @return */ - [[nodiscard]] U64 hash() const noexcept { return key_; } - [[nodiscard]] Color sideToMove() const noexcept { return stm_; } - [[nodiscard]] Square enpassantSq() const noexcept { return ep_sq_; } + [[nodiscard]] U64 hash() const noexcept { return key_; } + [[nodiscard]] Color sideToMove() const noexcept { return stm_; } + [[nodiscard]] Square enpassantSq() const noexcept { return ep_sq_; } [[nodiscard]] CastlingRights castlingRights() const noexcept { return cr_; } - [[nodiscard]] std::uint32_t halfMoveClock() const noexcept { return hfm_; } - [[nodiscard]] std::uint32_t fullMoveNumber() const noexcept { return 1 + plies_ / 2; } + [[nodiscard]] std::uint32_t halfMoveClock() const noexcept { return hfm_; } + [[nodiscard]] std::uint32_t fullMoveNumber() const noexcept { return 1 + plies_ / 2; } void set960(bool is960) { chess960_ = is960; - if (!original_fen_.empty()) setFen(original_fen_); + if (!original_fen_.empty()) + setFen(original_fen_); } /** @@ -2389,27 +2632,35 @@ class Board { * @return */ [[nodiscard]] std::string getCastleString() const { - static const auto get_file = [](const CastlingRights &cr, Color c, CastlingRights::Side side) { + static const auto get_file = [](const CastlingRights& cr, Color c, + CastlingRights::Side side) { auto file = static_cast(cr.getRookFile(c, side)); return c == Color::WHITE ? std::toupper(file[0]) : file[0]; }; - if (chess960_) { + if (chess960_) + { std::string ss; for (auto color : {Color::WHITE, Color::BLACK}) - for (auto side : {CastlingRights::Side::KING_SIDE, CastlingRights::Side::QUEEN_SIDE}) - if (cr_.has(color, side)) ss += get_file(cr_, color, side); + for (auto side : + {CastlingRights::Side::KING_SIDE, CastlingRights::Side::QUEEN_SIDE}) + if (cr_.has(color, side)) + ss += get_file(cr_, color, side); return ss; } std::string ss; - if (cr_.has(Color::WHITE, CastlingRights::Side::KING_SIDE)) ss += 'K'; - if (cr_.has(Color::WHITE, CastlingRights::Side::QUEEN_SIDE)) ss += 'Q'; - if (cr_.has(Color::BLACK, CastlingRights::Side::KING_SIDE)) ss += 'k'; - if (cr_.has(Color::BLACK, CastlingRights::Side::QUEEN_SIDE)) ss += 'q'; + if (cr_.has(Color::WHITE, CastlingRights::Side::KING_SIDE)) + ss += 'K'; + if (cr_.has(Color::WHITE, CastlingRights::Side::QUEEN_SIDE)) + ss += 'Q'; + if (cr_.has(Color::BLACK, CastlingRights::Side::KING_SIDE)) + ss += 'k'; + if (cr_.has(Color::BLACK, CastlingRights::Side::QUEEN_SIDE)) + ss += 'q'; return ss; } @@ -2428,9 +2679,12 @@ class Board { // be across half-moves. const auto size = static_cast(prev_states_.size()); - for (int i = size - 2; i >= 0 && i >= size - hfm_ - 1; i -= 2) { - if (prev_states_[i].hash == key_) c++; - if (c == count) return true; + for (int i = size - 2; i >= 0 && i >= size - hfm_ - 1; i -= 2) + { + if (prev_states_[i].hash == key_) + c++; + if (c == count) + return true; } return false; @@ -2454,7 +2708,8 @@ class Board { Movelist movelist; movegen::legalmoves(movelist, *this); - if (movelist.empty() && inCheck()) { + if (movelist.empty() && inCheck()) + { return {GameResultReason::CHECKMATE, GameResult::LOSE}; } @@ -2469,29 +2724,39 @@ class Board { const auto count = occ().count(); // only kings, draw - if (count == 2) return true; + if (count == 2) + return true; // only bishop + knight, cant mate - if (count == 3) { - if (pieces(PieceType::BISHOP, Color::WHITE) || pieces(PieceType::BISHOP, Color::BLACK)) return true; - if (pieces(PieceType::KNIGHT, Color::WHITE) || pieces(PieceType::KNIGHT, Color::BLACK)) return true; + if (count == 3) + { + if (pieces(PieceType::BISHOP, Color::WHITE) || pieces(PieceType::BISHOP, Color::BLACK)) + return true; + if (pieces(PieceType::KNIGHT, Color::WHITE) || pieces(PieceType::KNIGHT, Color::BLACK)) + return true; } // same colored bishops, cant mate - if (count == 4) { - if (pieces(PieceType::BISHOP, Color::WHITE) && pieces(PieceType::BISHOP, Color::BLACK) && - Square::same_color(pieces(PieceType::BISHOP, Color::WHITE).lsb(), - pieces(PieceType::BISHOP, Color::BLACK).lsb())) + if (count == 4) + { + if (pieces(PieceType::BISHOP, Color::WHITE) && pieces(PieceType::BISHOP, Color::BLACK) + && Square::same_color(pieces(PieceType::BISHOP, Color::WHITE).lsb(), + pieces(PieceType::BISHOP, Color::BLACK).lsb())) return true; // one side with two bishops which have the same color auto white_bishops = pieces(PieceType::BISHOP, Color::WHITE); auto black_bishops = pieces(PieceType::BISHOP, Color::BLACK); - if (white_bishops.count() == 2) { - if (Square::same_color(white_bishops.lsb(), white_bishops.msb())) return true; - } else if (black_bishops.count() == 2) { - if (Square::same_color(black_bishops.lsb(), black_bishops.msb())) return true; + if (white_bishops.count() == 2) + { + if (Square::same_color(white_bishops.lsb(), white_bishops.msb())) + return true; + } + else if (black_bishops.count() == 2) + { + if (Square::same_color(black_bishops.lsb(), black_bishops.msb())) + return true; } } @@ -2505,15 +2770,20 @@ class Board { * @return */ [[nodiscard]] std::pair isGameOver() const noexcept { - if (isHalfMoveDraw()) return getHalfMoveDrawType(); - if (isInsufficientMaterial()) return {GameResultReason::INSUFFICIENT_MATERIAL, GameResult::DRAW}; - if (isRepetition()) return {GameResultReason::THREEFOLD_REPETITION, GameResult::DRAW}; + if (isHalfMoveDraw()) + return getHalfMoveDrawType(); + if (isInsufficientMaterial()) + return {GameResultReason::INSUFFICIENT_MATERIAL, GameResult::DRAW}; + if (isRepetition()) + return {GameResultReason::THREEFOLD_REPETITION, GameResult::DRAW}; Movelist movelist; movegen::legalmoves(movelist, *this); - if (movelist.empty()) { - if (inCheck()) return {GameResultReason::CHECKMATE, GameResult::LOSE}; + if (movelist.empty()) + { + if (inCheck()) + return {GameResultReason::CHECKMATE, GameResult::LOSE}; return {GameResultReason::STALEMATE, GameResult::DRAW}; } @@ -2528,11 +2798,17 @@ class Board { */ [[nodiscard]] bool isAttacked(Square square, Color color) const noexcept { // cheap checks first - if (attacks::pawn(~color, square) & pieces(PieceType::PAWN, color)) return true; - if (attacks::knight(square) & pieces(PieceType::KNIGHT, color)) return true; - if (attacks::king(square) & pieces(PieceType::KING, color)) return true; - if (attacks::bishop(square, occ()) & pieces(PieceType::BISHOP, PieceType::QUEEN) & us(color)) return true; - if (attacks::rook(square, occ()) & pieces(PieceType::ROOK, PieceType::QUEEN) & us(color)) return true; + if (attacks::pawn(~color, square) & pieces(PieceType::PAWN, color)) + return true; + if (attacks::knight(square) & pieces(PieceType::KNIGHT, color)) + return true; + if (attacks::king(square) & pieces(PieceType::KING, color)) + return true; + if (attacks::bishop(square, occ()) & pieces(PieceType::BISHOP, PieceType::QUEEN) + & us(color)) + return true; + if (attacks::rook(square, occ()) & pieces(PieceType::ROOK, PieceType::QUEEN) & us(color)) + return true; return false; } @@ -2543,7 +2819,7 @@ class Board { */ [[nodiscard]] bool inCheck() const noexcept { return isAttacked(kingSq(stm_), ~stm_); } - [[nodiscard]] CheckType givesCheck(const Move &m) const noexcept; + [[nodiscard]] CheckType givesCheck(const Move& m) const noexcept; /** * @brief Checks if the given color has at least 1 piece thats not pawn and not king @@ -2563,16 +2839,19 @@ class Board { auto pieces = occ(); - while (pieces) { + while (pieces) + { const Square sq = pieces.pop(); hash_key ^= Zobrist::piece(at(sq), sq); } U64 ep_hash = 0ULL; - if (ep_sq_ != Square::NO_SQ) ep_hash ^= Zobrist::enpassant(ep_sq_.file()); + if (ep_sq_ != Square::NO_SQ) + ep_hash ^= Zobrist::enpassant(ep_sq_.file()); U64 stm_hash = 0ULL; - if (stm_ == Color::WHITE) stm_hash ^= Zobrist::sideToMove(); + if (stm_ == Color::WHITE) + stm_hash ^= Zobrist::sideToMove(); U64 castling_hash = 0ULL; castling_hash ^= Zobrist::castling(cr_.hashIndex()); @@ -2584,7 +2863,7 @@ class Board { return castling_path[c][isKingSide]; } - friend std::ostream &operator<<(std::ostream &os, const Board &board); + friend std::ostream& operator<<(std::ostream& os, const Board& board); /** * @brief Compresses the board into a PackedBoard. @@ -2599,9 +2878,11 @@ class Board { * @param board * @return */ - static PackedBoard encode(const Board &board) { return encodeState(board); } + static PackedBoard encode(const Board& board) { return encodeState(board); } - static PackedBoard encode(std::string_view fen, bool chess960 = false) { return encodeState(fen, chess960); } + static PackedBoard encode(std::string_view fen, bool chess960 = false) { + return encodeState(fen, chess960); + } /** * @brief Creates a Board object from a PackedBoard @@ -2609,7 +2890,7 @@ class Board { * @param chess960 If the board is a chess960 position, set this to true * @return */ - static Board decode(const PackedBoard &compressed, bool chess960 = false) { + static Board decode(const PackedBoard& compressed, bool chess960 = false) { Board board = Board(PrivateCtor::CREATE); board.chess960_ = chess960; decode(board, compressed); @@ -2637,7 +2918,7 @@ class Board { * * We will later deduce the square of the pieces from the occupancy bitboard. */ - static PackedBoard encodeState(const Board &board) { + static PackedBoard encodeState(const Board& board) { PackedBoard packed{}; packed[0] = board.occ().getBits() >> 56; @@ -2652,13 +2933,15 @@ class Board { auto offset = 8 * 2; auto occ = board.occ(); - while (occ) { + while (occ) + { // we now fill the packed array, since our convertedpiece only actually needs 4 bits, // we can store 2 pieces in one byte. - const auto sq = Square(occ.pop()); - const auto shift = (offset % 2 == 0 ? 4 : 0); - const auto meaning = convertMeaning(board.cr_, board.sideToMove(), board.ep_sq_, sq, board.at(sq)); - const auto nibble = meaning << shift; + const auto sq = Square(occ.pop()); + const auto shift = (offset % 2 == 0 ? 4 : 0); + const auto meaning = + convertMeaning(board.cr_, board.sideToMove(), board.ep_sq_, sq, board.at(sq)); + const auto nibble = meaning << shift; packed[offset / 2] |= nibble; offset++; @@ -2669,15 +2952,17 @@ class Board { static PackedBoard encodeState(std::string_view fen, bool chess960 = false) { // fallback to slower method - if (chess960) { + if (chess960) + { return encodeState(Board(fen, true)); } PackedBoard packed{}; - while (fen[0] == ' ') fen.remove_prefix(1); + while (fen[0] == ' ') + fen.remove_prefix(1); - const auto params = split_string_view<6>(fen); + const auto params = split_string_view<4>(fen); const auto position = params[0].has_value() ? *params[0] : ""; const auto move_right = params[1].has_value() ? *params[1] : "w"; const auto castling = params[2].has_value() ? *params[2] : "-"; @@ -2688,16 +2973,22 @@ class Board { CastlingRights cr; - for (char i : castling) { - if (i == '-') break; + for (char i : castling) + { + if (i == '-') + break; const auto king_side = CastlingRights::Side::KING_SIDE; const auto queen_side = CastlingRights::Side::QUEEN_SIDE; - if (i == 'K') cr.setCastlingRight(Color::WHITE, king_side, File::FILE_H); - if (i == 'Q') cr.setCastlingRight(Color::WHITE, queen_side, File::FILE_A); - if (i == 'k') cr.setCastlingRight(Color::BLACK, king_side, File::FILE_H); - if (i == 'q') cr.setCastlingRight(Color::BLACK, queen_side, File::FILE_A); + if (i == 'K') + cr.setCastlingRight(Color::WHITE, king_side, File::FILE_H); + if (i == 'Q') + cr.setCastlingRight(Color::WHITE, queen_side, File::FILE_A); + if (i == 'k') + cr.setCastlingRight(Color::BLACK, king_side, File::FILE_H); + if (i == 'q') + cr.setCastlingRight(Color::BLACK, queen_side, File::FILE_A); assert(i == 'K' || i == 'Q' || i == 'k' || i == 'q'); @@ -2706,23 +2997,31 @@ class Board { const auto parts = split_string_view<8>(position, '/'); - int offset = 8 * 2; - int square = 0; - Bitboard occ = 0ull; + int offset = 8 * 2; + int square = 0; + Bitboard occ = 0ull; - for (auto i = parts.rbegin(); i != parts.rend(); i++) { + for (auto i = parts.rbegin(); i != parts.rend(); i++) + { auto part = *i; - for (char curr : *part) { - if (isdigit(curr)) { + for (char curr : *part) + { + if (isdigit(curr)) + { square += (curr - '0'); - } else if (curr == '/') { + } + else if (curr == '/') + { square++; - } else { + } + else + { const auto p = Piece(std::string_view(&curr, 1)); const auto shift = (offset % 2 == 0 ? 4 : 0); - packed[offset / 2] |= convertMeaning(cr, stm, ep, Square(square), p) << shift; + packed[offset / 2] |= convertMeaning(cr, stm, ep, Square(square), p) + << shift; offset++; occ.set(square); @@ -2743,15 +3042,16 @@ class Board { return packed; } - static void decode(Board &board, const PackedBoard &compressed) { + static void decode(Board& board, const PackedBoard& compressed) { Bitboard occupied = 0ull; - for (int i = 0; i < 8; i++) { + for (int i = 0; i < 8; i++) + { occupied |= Bitboard(compressed[i]) << (56 - i * 8); } - int offset = 16; - int white_castle_idx = 0, black_castle_idx = 0; + int offset = 16; + int white_castle_idx = 0, black_castle_idx = 0; File white_castle[2] = {File::NO_FILE, File::NO_FILE}; File black_castle[2] = {File::NO_FILE, File::NO_FILE}; @@ -2771,12 +3071,14 @@ class Board { board.board_.fill(Piece::NONE); // place pieces back on the board - while (occupied) { + while (occupied) + { const auto sq = Square(occupied.pop()); const auto nibble = compressed[offset / 2] >> (offset % 2 == 0 ? 4 : 0) & 0b1111; const auto piece = convertPiece(nibble); - if (piece != Piece::NONE) { + if (piece != Piece::NONE) + { board.placePiece(piece, sq); offset++; @@ -2785,26 +3087,30 @@ class Board { // Piece has a special meaning, interpret it from the raw integer // pawn with ep square behind it - if (nibble == 12) { + if (nibble == 12) + { board.ep_sq_ = sq.ep_square(); // depending on the rank this is a white or black pawn auto color = sq.rank() == Rank::RANK_4 ? Color::WHITE : Color::BLACK; board.placePiece(Piece(PieceType::PAWN, color), sq); } // castling rights for white - else if (nibble == 13) { + else if (nibble == 13) + { assert(white_castle_idx < 2); white_castle[white_castle_idx++] = sq.file(); board.placePiece(Piece(PieceType::ROOK, Color::WHITE), sq); } // castling rights for black - else if (nibble == 14) { + else if (nibble == 14) + { assert(black_castle_idx < 2); black_castle[black_castle_idx++] = sq.file(); board.placePiece(Piece(PieceType::ROOK, Color::BLACK), sq); } // black to move - else if (nibble == 15) { + else if (nibble == 15) + { board.stm_ = Color::BLACK; board.placePiece(Piece(PieceType::KING, Color::BLACK), sq); } @@ -2813,8 +3119,10 @@ class Board { } // reapply castling - for (int i = 0; i < 2; i++) { - if (white_castle[i] != File::NO_FILE) { + for (int i = 0; i < 2; i++) + { + if (white_castle[i] != File::NO_FILE) + { const auto king_sq = board.kingSq(Color::WHITE); const auto file = white_castle[i]; const auto side = CastlingRights::closestSide(file, king_sq.file()); @@ -2822,7 +3130,8 @@ class Board { board.cr_.setCastlingRight(Color::WHITE, side, file); } - if (black_castle[i] != File::NO_FILE) { + if (black_castle[i] != File::NO_FILE) + { const auto king_sq = board.kingSq(Color::BLACK); const auto file = black_castle[i]; const auto side = CastlingRights::closestSide(file, king_sq.file()); @@ -2831,7 +3140,8 @@ class Board { } } - if (board.stm_ == Color::BLACK) { + if (board.stm_ == Color::BLACK) + { board.plies_++; } @@ -2843,7 +3153,8 @@ class Board { // for pieces with a special meaning return Piece::NONE since this is otherwise not used static Piece convertPiece(std::uint8_t piece) { - if (piece >= 12) return Piece::NONE; + if (piece >= 12) + return Piece::NONE; return Piece(Piece::underlying(piece)); } @@ -2851,23 +3162,31 @@ class Board { // 13 => any white rook with castling rights, side will be deduced from the file // 14 => any black rook with castling rights, side will be deduced from the file // 15 => black king and black is side to move - static std::uint8_t convertMeaning(const CastlingRights &cr, Color stm, Square ep, Square sq, Piece piece) { - if (piece.type() == PieceType::PAWN && ep != Square::NO_SQ) { - if (Square(static_cast(sq.index()) ^ 8) == ep) return 12; + static std::uint8_t + convertMeaning(const CastlingRights& cr, Color stm, Square ep, Square sq, Piece piece) { + if (piece.type() == PieceType::PAWN && ep != Square::NO_SQ) + { + if (Square(static_cast(sq.index()) ^ 8) == ep) + return 12; } - if (piece.type() == PieceType::ROOK) { - if (piece.color() == Color::WHITE && Square::back_rank(sq, Color::WHITE) && - (cr.getRookFile(Color::WHITE, CastlingRights::Side::KING_SIDE) == sq.file() || - cr.getRookFile(Color::WHITE, CastlingRights::Side::QUEEN_SIDE) == sq.file())) + if (piece.type() == PieceType::ROOK) + { + if (piece.color() == Color::WHITE && Square::back_rank(sq, Color::WHITE) + && (cr.getRookFile(Color::WHITE, CastlingRights::Side::KING_SIDE) == sq.file() + || cr.getRookFile(Color::WHITE, CastlingRights::Side::QUEEN_SIDE) + == sq.file())) return 13; - if (piece.color() == Color::BLACK && Square::back_rank(sq, Color::BLACK) && - (cr.getRookFile(Color::BLACK, CastlingRights::Side::KING_SIDE) == sq.file() || - cr.getRookFile(Color::BLACK, CastlingRights::Side::QUEEN_SIDE) == sq.file())) + if (piece.color() == Color::BLACK && Square::back_rank(sq, Color::BLACK) + && (cr.getRookFile(Color::BLACK, CastlingRights::Side::KING_SIDE) == sq.file() + || cr.getRookFile(Color::BLACK, CastlingRights::Side::QUEEN_SIDE) + == sq.file())) return 14; } - if (piece.type() == PieceType::KING && piece.color() == Color::BLACK && stm == Color::BLACK) { + if (piece.type() == PieceType::KING && piece.color() == Color::BLACK + && stm == Color::BLACK) + { return 15; } @@ -2884,14 +3203,14 @@ class Board { std::array pieces_bb_ = {}; std::array occ_bb_ = {}; - std::array board_ = {}; + std::array board_ = {}; - U64 key_ = 0ULL; - CastlingRights cr_ = {}; - std::uint16_t plies_ = 0; - Color stm_ = Color::WHITE; - Square ep_sq_ = Square::NO_SQ; - std::uint8_t hfm_ = 0; + U64 key_ = 0ULL; + CastlingRights cr_ = {}; + std::uint16_t plies_ = 0; + Color stm_ = Color::WHITE; + Square ep_sq_ = Square::NO_SQ; + std::uint8_t hfm_ = 0; bool chess960_ = false; @@ -2930,15 +3249,17 @@ class Board { board_[index] = piece; } - template + template bool setFenInternal(std::string_view fen) { original_fen_ = fen; reset(); - while (!fen.empty() && fen[0] == ' ') fen.remove_prefix(1); + while (!fen.empty() && fen[0] == ' ') + fen.remove_prefix(1); - if (fen.empty()) return false; + if (fen.empty()) + return false; const auto params = split_string_view<6>(fen); const auto position = params[0].has_value() ? *params[0] : ""; @@ -2948,9 +3269,11 @@ class Board { const auto half_move = params[4].has_value() ? *params[4] : "0"; const auto full_move = params[5].has_value() ? *params[5] : "1"; - if (position.empty()) return false; + if (position.empty()) + return false; - if (move_right != "w" && move_right != "b") return false; + if (move_right != "w" && move_right != "b") + return false; const auto half_move_opt = detail::parseStringViewToInt(half_move).value_or(0); hfm_ = half_move_opt; @@ -2960,36 +3283,52 @@ class Board { plies_ = plies_ * 2 - 2; - if (en_passant != "-") { - if (!Square::is_valid_string_sq(en_passant)) { + if (en_passant != "-") + { + if (!Square::is_valid_string_sq(en_passant)) + { return false; } ep_sq_ = Square(en_passant); - if (ep_sq_ == Square::NO_SQ) return false; + if (ep_sq_ == Square::NO_SQ) + return false; } stm_ = (move_right == "w") ? Color::WHITE : Color::BLACK; - if (stm_ == Color::BLACK) { + if (stm_ == Color::BLACK) + { plies_++; - } else { + } + else + { key_ ^= Zobrist::sideToMove(); } auto square = 56; - for (char curr : position) { - if (isdigit(curr)) { + for (char curr : position) + { + if (isdigit(curr)) + { square += (curr - '0'); - } else if (curr == '/') { + } + else if (curr == '/') + { square -= 16; - } else { + } + else + { auto p = Piece(std::string_view(&curr, 1)); - if (p == Piece::NONE || !Square::is_valid_sq(square) || at(square) != Piece::NONE) return false; + if (p == Piece::NONE || !Square::is_valid_sq(square) || at(square) != Piece::NONE) + return false; - if constexpr (ctor) { + if constexpr (ctor) + { placePieceInternal(p, Square(square)); - } else { + } + else + { placePiece(p, square); } @@ -2998,16 +3337,20 @@ class Board { } } - static const auto find_rook = [](const Board &board, CastlingRights::Side side, Color color) -> File { + static const auto find_rook = [](const Board& board, CastlingRights::Side side, + Color color) -> File { const auto king_side = CastlingRights::Side::KING_SIDE; const auto king_sq = board.kingSq(color); - const auto sq_corner = Square(side == king_side ? Square::SQ_H1 : Square::SQ_A1).relative_square(color); + const auto sq_corner = + Square(side == king_side ? Square::SQ_H1 : Square::SQ_A1).relative_square(color); const auto start = side == king_side ? king_sq + 1 : king_sq - 1; for (Square sq = start; (side == king_side ? sq <= sq_corner : sq >= sq_corner); - (side == king_side ? sq++ : sq--)) { - if (board.at(sq) == PieceType::ROOK && board.at(sq).color() == color) { + (side == king_side ? sq++ : sq--)) + { + if (board.at(sq) == PieceType::ROOK && board.at(sq).color() == color) + { return sq.file(); } } @@ -3016,13 +3359,16 @@ class Board { }; // Parse castling rights - for (char i : castling) { - if (i == '-') break; + for (char i : castling) + { + if (i == '-') + break; const auto king_side = CastlingRights::Side::KING_SIDE; const auto queen_side = CastlingRights::Side::QUEEN_SIDE; - if (!chess960_) { + if (!chess960_) + { if (i == 'K') cr_.setCastlingRight(Color::WHITE, king_side, File::FILE_H); else if (i == 'Q') @@ -3041,33 +3387,47 @@ class Board { const auto color = isupper(i) ? Color::WHITE : Color::BLACK; const auto king_sq = kingSq(color); - if (i == 'K' || i == 'k') { + if (i == 'K' || i == 'k') + { auto file = find_rook(*this, king_side, color); - if (file == File::NO_FILE) return false; + if (file == File::NO_FILE) + return false; cr_.setCastlingRight(color, king_side, file); - } else if (i == 'Q' || i == 'q') { + } + else if (i == 'Q' || i == 'q') + { auto file = find_rook(*this, queen_side, color); - if (file == File::NO_FILE) return false; + if (file == File::NO_FILE) + return false; cr_.setCastlingRight(color, queen_side, file); - } else { + } + else + { const auto file = File(std::string_view(&i, 1)); - if (file == File::NO_FILE) return false; + if (file == File::NO_FILE) + return false; const auto side = CastlingRights::closestSide(file, king_sq.file()); cr_.setCastlingRight(color, side, file); } } - if (ep_sq_ != Square::NO_SQ && !((ep_sq_.rank() == Rank::RANK_3 && stm_ == Color::BLACK) || - (ep_sq_.rank() == Rank::RANK_6 && stm_ == Color::WHITE))) { + if (ep_sq_ != Square::NO_SQ + && !((ep_sq_.rank() == Rank::RANK_3 && stm_ == Color::BLACK) + || (ep_sq_.rank() == Rank::RANK_6 && stm_ == Color::WHITE))) + { ep_sq_ = Square::NO_SQ; } - if (ep_sq_ != Square::NO_SQ) { + if (ep_sq_ != Square::NO_SQ) + { bool valid; - if (stm_ == Color::WHITE) { + if (stm_ == Color::WHITE) + { valid = movegen::isEpSquareValid(*this, ep_sq_); - } else { + } + else + { valid = movegen::isEpSquareValid(*this, ep_sq_); } @@ -3082,26 +3442,32 @@ class Board { assert(key_ == zobrist()); // init castling_path - for (Color c : {Color::WHITE, Color::BLACK}) { + for (Color c : {Color::WHITE, Color::BLACK}) + { const auto king_from = kingSq(c); - for (const auto side : {CastlingRights::Side::KING_SIDE, CastlingRights::Side::QUEEN_SIDE}) { - if (!cr_.has(c, side)) continue; + for (const auto side : + {CastlingRights::Side::KING_SIDE, CastlingRights::Side::QUEEN_SIDE}) + { + if (!cr_.has(c, side)) + continue; const auto rook_from = Square(cr_.getRookFile(c, side), king_from.rank()); - const auto king_to = Square::castling_king_square(side == CastlingRights::Side::KING_SIDE, c); - const auto rook_to = Square::castling_rook_square(side == CastlingRights::Side::KING_SIDE, c); + const auto king_to = + Square::castling_king_square(side == CastlingRights::Side::KING_SIDE, c); + const auto rook_to = + Square::castling_rook_square(side == CastlingRights::Side::KING_SIDE, c); castling_path[c][side == CastlingRights::Side::KING_SIDE] = - (movegen::between(rook_from, rook_to) | movegen::between(king_from, king_to)) & - ~(Bitboard::fromSquare(king_from) | Bitboard::fromSquare(rook_from)); + (movegen::between(rook_from, rook_to) | movegen::between(king_from, king_to)) + & ~(Bitboard::fromSquare(king_from) | Bitboard::fromSquare(rook_from)); } } return true; } - template + template std::array, N> static split_string_view(std::string_view fen, char delimiter = ' ') { std::array, N> arr = {}; @@ -3109,9 +3475,11 @@ class Board { std::size_t start = 0; std::size_t end = 0; - for (std::size_t i = 0; i < N; i++) { + for (std::size_t i = 0; i < N; i++) + { end = fen.find(delimiter, start); - if (end == std::string::npos) { + if (end == std::string::npos) + { arr[i] = fen.substr(start); break; } @@ -3141,9 +3509,11 @@ class Board { std::string original_fen_; }; -inline std::ostream &operator<<(std::ostream &os, const Board &b) { - for (int i = 63; i >= 0; i -= 8) { - for (int j = 7; j >= 0; j--) { +inline std::ostream& operator<<(std::ostream& os, const Board& b) { + for (int i = 63; i >= 0; i -= 8) + { + for (int j = 7; j >= 0; j--) + { os << " " << static_cast(b.board_[i - j]); } @@ -3163,37 +3533,49 @@ inline std::ostream &operator<<(std::ostream &os, const Board &b) { return os; } -inline CheckType Board::givesCheck(const Move &m) const noexcept { - const static auto getSniper = [](const Board *board, Square ksq, Bitboard oc) { +inline CheckType Board::givesCheck(const Move& m) const noexcept { + const static auto getSniper = [](const Board* board, Square ksq, Bitboard oc) { const auto us_occ = board->us(board->sideToMove()); - const auto bishop = attacks::bishop(ksq, oc) & board->pieces(PieceType::BISHOP, PieceType::QUEEN) & us_occ; - const auto rook = attacks::rook(ksq, oc) & board->pieces(PieceType::ROOK, PieceType::QUEEN) & us_occ; + const auto bishop = + attacks::bishop(ksq, oc) & board->pieces(PieceType::BISHOP, PieceType::QUEEN) & us_occ; + const auto rook = + attacks::rook(ksq, oc) & board->pieces(PieceType::ROOK, PieceType::QUEEN) & us_occ; return (bishop | rook); }; assert(at(m.from()).color() == stm_); - const Square from = m.from(); - const Square to = m.to(); - const Square ksq = kingSq(~stm_); - const Bitboard toBB = Bitboard::fromSquare(to); - const PieceType pt = at(from).type(); + const Square from = m.from(); + const Square to = m.to(); + const Square ksq = kingSq(~stm_); + const Bitboard toBB = Bitboard::fromSquare(to); + const PieceType pt = at(from).type(); Bitboard fromKing = 0ull; - if (pt == PieceType::PAWN) { + if (pt == PieceType::PAWN) + { fromKing = attacks::pawn(~stm_, ksq); - } else if (pt == PieceType::KNIGHT) { + } + else if (pt == PieceType::KNIGHT) + { fromKing = attacks::knight(ksq); - } else if (pt == PieceType::BISHOP) { + } + else if (pt == PieceType::BISHOP) + { fromKing = attacks::bishop(ksq, occ()); - } else if (pt == PieceType::ROOK) { + } + else if (pt == PieceType::ROOK) + { fromKing = attacks::rook(ksq, occ()); - } else if (pt == PieceType::QUEEN) { + } + else if (pt == PieceType::QUEEN) + { fromKing = attacks::queen(ksq, occ()); } - if (fromKing & toBB) return CheckType::DIRECT_CHECK; + if (fromKing & toBB) + return CheckType::DIRECT_CHECK; // Discovery check const Bitboard fromBB = Bitboard::fromSquare(from); @@ -3201,47 +3583,54 @@ inline CheckType Board::givesCheck(const Move &m) const noexcept { Bitboard sniper = getSniper(this, ksq, oc); - while (sniper) { + while (sniper) + { Square sq = sniper.pop(); - return (!(movegen::between(ksq, sq) & toBB) || m.typeOf() == Move::CASTLING) ? CheckType::DISCOVERY_CHECK - : CheckType::NO_CHECK; + return (!(movegen::between(ksq, sq) & toBB) || m.typeOf() == Move::CASTLING) + ? CheckType::DISCOVERY_CHECK + : CheckType::NO_CHECK; } - switch (m.typeOf()) { - case Move::NORMAL: - return CheckType::NO_CHECK; + switch (m.typeOf()) + { + case Move::NORMAL : + return CheckType::NO_CHECK; - case Move::PROMOTION: { - Bitboard attacks = 0ull; + case Move::PROMOTION : { + Bitboard attacks = 0ull; - switch (m.promotionType()) { - case static_cast(PieceType::KNIGHT): - attacks = attacks::knight(to); - break; - case static_cast(PieceType::BISHOP): - attacks = attacks::bishop(to, oc); - break; - case static_cast(PieceType::ROOK): - attacks = attacks::rook(to, oc); - break; - case static_cast(PieceType::QUEEN): - attacks = attacks::queen(to, oc); - } - - return (attacks & pieces(PieceType::KING, ~stm_)) ? CheckType::DIRECT_CHECK : CheckType::NO_CHECK; + switch (m.promotionType()) + { + case static_cast(PieceType::KNIGHT) : + attacks = attacks::knight(to); + break; + case static_cast(PieceType::BISHOP) : + attacks = attacks::bishop(to, oc); + break; + case static_cast(PieceType::ROOK) : + attacks = attacks::rook(to, oc); + break; + case static_cast(PieceType::QUEEN) : + attacks = attacks::queen(to, oc); } - case Move::ENPASSANT: { - Square capSq(to.file(), from.rank()); - return (getSniper(this, ksq, (oc ^ Bitboard::fromSquare(capSq)) | toBB)) ? CheckType::DISCOVERY_CHECK - : CheckType::NO_CHECK; - } + return (attacks & pieces(PieceType::KING, ~stm_)) ? CheckType::DIRECT_CHECK + : CheckType::NO_CHECK; + } - case Move::CASTLING: { - Square rookTo = Square::castling_rook_square(to > from, stm_); - return (attacks::rook(ksq, occ()) & Bitboard::fromSquare(rookTo)) ? CheckType::DISCOVERY_CHECK - : CheckType::NO_CHECK; - } + case Move::ENPASSANT : { + Square capSq(to.file(), from.rank()); + return (getSniper(this, ksq, (oc ^ Bitboard::fromSquare(capSq)) | toBB)) + ? CheckType::DISCOVERY_CHECK + : CheckType::NO_CHECK; + } + + case Move::CASTLING : { + Square rookTo = Square::castling_rook_square(to > from, stm_); + return (attacks::rook(ksq, occ()) & Bitboard::fromSquare(rookTo)) + ? CheckType::DISCOVERY_CHECK + : CheckType::NO_CHECK; + } } assert(false); @@ -3252,25 +3641,26 @@ inline CheckType Board::givesCheck(const Move &m) const noexcept { namespace chess { -template +template [[nodiscard]] inline constexpr Bitboard attacks::shift(const Bitboard b) { - switch (direction) { - case Direction::NORTH: - return b << 8; - case Direction::SOUTH: - return b >> 8; - case Direction::NORTH_WEST: - return (b & ~MASK_FILE[0]) << 7; - case Direction::WEST: - return (b & ~MASK_FILE[0]) >> 1; - case Direction::SOUTH_WEST: - return (b & ~MASK_FILE[0]) >> 9; - case Direction::NORTH_EAST: - return (b & ~MASK_FILE[7]) << 9; - case Direction::EAST: - return (b & ~MASK_FILE[7]) << 1; - case Direction::SOUTH_EAST: - return (b & ~MASK_FILE[7]) >> 7; + switch (direction) + { + case Direction::NORTH : + return b << 8; + case Direction::SOUTH : + return b >> 8; + case Direction::NORTH_WEST : + return (b & ~MASK_FILE[0]) << 7; + case Direction::WEST : + return (b & ~MASK_FILE[0]) >> 1; + case Direction::SOUTH_WEST : + return (b & ~MASK_FILE[0]) >> 9; + case Direction::NORTH_EAST : + return (b & ~MASK_FILE[7]) << 9; + case Direction::EAST : + return (b & ~MASK_FILE[7]) << 1; + case Direction::SOUTH_EAST : + return (b & ~MASK_FILE[7]) >> 7; } assert(false); @@ -3278,21 +3668,25 @@ template return {}; } -template +template [[nodiscard]] inline Bitboard attacks::pawnLeftAttacks(const Bitboard pawns) { return c == Color::WHITE ? (pawns << 7) & ~MASK_FILE[static_cast(File::FILE_H)] : (pawns >> 7) & ~MASK_FILE[static_cast(File::FILE_A)]; } -template +template [[nodiscard]] inline Bitboard attacks::pawnRightAttacks(const Bitboard pawns) { return c == Color::WHITE ? (pawns << 9) & ~MASK_FILE[static_cast(File::FILE_A)] : (pawns >> 9) & ~MASK_FILE[static_cast(File::FILE_H)]; } -[[nodiscard]] inline Bitboard attacks::pawn(Color c, Square sq) noexcept { return PawnAttacks[c][sq.index()]; } +[[nodiscard]] inline Bitboard attacks::pawn(Color c, Square sq) noexcept { + return PawnAttacks[c][sq.index()]; +} -[[nodiscard]] inline Bitboard attacks::knight(Square sq) noexcept { return KnightAttacks[sq.index()]; } +[[nodiscard]] inline Bitboard attacks::knight(Square sq) noexcept { + return KnightAttacks[sq.index()]; +} [[nodiscard]] inline Bitboard attacks::bishop(Square sq, Bitboard occupied) noexcept { return BishopTable[sq.index()].attacks[BishopTable[sq.index()](occupied)]; @@ -3308,7 +3702,8 @@ template [[nodiscard]] inline Bitboard attacks::king(Square sq) noexcept { return KingAttacks[sq.index()]; } -[[nodiscard]] inline Bitboard attacks::attackers(const Board &board, Color color, Square square) noexcept { +[[nodiscard]] inline Bitboard +attacks::attackers(const Board& board, Color color, Square square) noexcept { const auto queens = board.pieces(PieceType::QUEEN, color); const auto occupied = board.occ(); @@ -3322,51 +3717,61 @@ template return atks & occupied; } -template +template [[nodiscard]] inline Bitboard attacks::slider(Square sq, Bitboard occupied) noexcept { static_assert(pt == PieceType::BISHOP || pt == PieceType::ROOK || pt == PieceType::QUEEN, "PieceType must be a slider!"); - if constexpr (pt == PieceType::BISHOP) return bishop(sq, occupied); - if constexpr (pt == PieceType::ROOK) return rook(sq, occupied); - if constexpr (pt == PieceType::QUEEN) return queen(sq, occupied); + if constexpr (pt == PieceType::BISHOP) + return bishop(sq, occupied); + if constexpr (pt == PieceType::ROOK) + return rook(sq, occupied); + if constexpr (pt == PieceType::QUEEN) + return queen(sq, occupied); } -template +template [[nodiscard]] inline Bitboard attacks::sliderAttacks(Square sq, Bitboard occupied) noexcept { - static constexpr int dirs[2][4][2] = {{1, 1, 1, -1, -1, -1, -1, 1}, {1, 0, 0, -1, -1, 0, 0, 1}}; + static constexpr int dirs[2][4][2] = {{{1, 1}, {1, -1}, {-1, -1}, {-1, 1}}, + {{1, 0}, {0, -1}, {-1, 0}, {0, 1}}}; Bitboard attacks = 0ull; File pf = sq.file(); Rank pr = sq.rank(); - for (int i = 0; i < 4; ++i) { + for (int i = 0; i < 4; ++i) + { int off_f = dirs[ISROOK][i][0]; int off_r = dirs[ISROOK][i][1]; File f; Rank r; - for (f = pf + off_f, r = pr + off_r; Square::is_valid(r, f); f += off_f, r += off_r) { + for (f = pf + off_f, r = pr + off_r; Square::is_valid(r, f); f += off_f, r += off_r) + { const auto index = Square(f, r).index(); attacks.set(index); - if (occupied.check(index)) break; + if (occupied.check(index)) + break; } } return attacks; } -inline void attacks::initSliders(Square sq, Magic table[], U64 magic, - const std::function &attacks) { +inline void attacks::initSliders(Square sq, + Magic table[], + U64 magic, + const std::function& attacks) { // The edges of the board are not considered for the attacks // i.e. for the sq h7 edges will be a1-h1, a1-a8, a8-h8, ignoring the edge of the current square - const Bitboard edges = ((Bitboard(Rank::RANK_1) | Bitboard(Rank::RANK_8)) & ~Bitboard(sq.rank())) | - ((Bitboard(File::FILE_A) | Bitboard(File::FILE_H)) & ~Bitboard(sq.file())); + const Bitboard edges = + ((Bitboard(Rank::RANK_1) | Bitboard(Rank::RANK_8)) & ~Bitboard(sq.rank())) + | ((Bitboard(File::FILE_A) | Bitboard(File::FILE_H)) & ~Bitboard(sq.file())); U64 occ = 0ULL; - auto &table_sq = table[sq.index()]; + auto& table_sq = table[sq.index()]; #ifndef CHESS_USE_PEXT table_sq.magic = magic; @@ -3376,11 +3781,14 @@ inline void attacks::initSliders(Square sq, Magic table[], U64 magic, table_sq.shift = 64 - Bitboard(table_sq.mask).count(); #endif - if (sq < 64 - 1) { - table[sq.index() + 1].attacks = table_sq.attacks + (1ull << Bitboard(table_sq.mask).count()); + if (sq < 64 - 1) + { + table[sq.index() + 1].attacks = + table_sq.attacks + (1ull << Bitboard(table_sq.mask).count()); } - do { + do + { table_sq.attacks[table_sq(occ)] = attacks(sq, occ); occ = (occ - table_sq.mask) & table_sq.mask; } while (occ); @@ -3390,7 +3798,8 @@ inline void attacks::initAttacks() { BishopTable[0].attacks = BishopAttacks; RookTable[0].attacks = RookAttacks; - for (int i = 0; i < 64; i++) { + for (int i = 0; i < 64; i++) + { initSliders(static_cast(i), BishopTable, BishopMagics[i], sliderAttacks); initSliders(static_cast(i), RookTable, RookMagics[i], sliderAttacks); } @@ -3398,7 +3807,6 @@ inline void attacks::initAttacks() { } // namespace chess - namespace chess { inline auto movegen::init_squares_between() { @@ -3408,12 +3816,16 @@ inline auto movegen::init_squares_between() { return (pt == PieceType::BISHOP) ? attacks::bishop(sq, occ) : attacks::rook(sq, occ); }; - for (int sq1 = 0; sq1 < 64; ++sq1) { - for (PieceType pt : {PieceType::BISHOP, PieceType::ROOK}) { - for (int sq2 = 0; sq2 < 64; ++sq2) { - if (att(pt, sq1, 0).check(sq2)) { - squares_between_bb[sq1][sq2] = - att(pt, sq1, Bitboard::fromSquare(sq2)) & att(pt, sq2, Bitboard::fromSquare(sq1)); + for (int sq1 = 0; sq1 < 64; ++sq1) + { + for (PieceType pt : {PieceType::BISHOP, PieceType::ROOK}) + { + for (int sq2 = 0; sq2 < 64; ++sq2) + { + if (att(pt, sq1, 0).check(sq2)) + { + squares_between_bb[sq1][sq2] = att(pt, sq1, Bitboard::fromSquare(sq2)) + & att(pt, sq2, Bitboard::fromSquare(sq1)); } squares_between_bb[sq1][sq2].set(sq2); } @@ -3423,8 +3835,8 @@ inline auto movegen::init_squares_between() { return squares_between_bb; } -template -[[nodiscard]] inline std::pair movegen::checkMask(const Board &board, Square sq) { +template +[[nodiscard]] inline std::pair movegen::checkMask(const Board& board, Square sq) { const auto opp_knight = board.pieces(PieceType::KNIGHT, ~c); const auto opp_bishop = board.pieces(PieceType::BISHOP, ~c); const auto opp_rook = board.pieces(PieceType::ROOK, ~c); @@ -3448,15 +3860,18 @@ template // check for bishop checks Bitboard bishop_attacks = attacks::bishop(sq, board.occ()) & (opp_bishop | opp_queen); - if (bishop_attacks) { + if (bishop_attacks) + { mask |= between(sq, bishop_attacks.lsb()); checks++; } Bitboard rook_attacks = attacks::rook(sq, board.occ()) & (opp_rook | opp_queen); - if (rook_attacks) { - if (rook_attacks.count() > 1) { + if (rook_attacks) + { + if (rook_attacks.count() > 1) + { checks = 2; return {mask, checks}; } @@ -3465,16 +3880,17 @@ template checks++; } - if (!mask) { + if (!mask) + { return {constants::DEFAULT_CHECKMASK, checks}; } return {mask, checks}; } -template -[[nodiscard]] inline Bitboard movegen::pinMask(const Board &board, Square sq, Bitboard occ_opp, - Bitboard occ_us) noexcept { +template +[[nodiscard]] inline Bitboard +movegen::pinMask(const Board& board, Square sq, Bitboard occ_opp, Bitboard occ_us) noexcept { static_assert(pt == PieceType::BISHOP || pt == PieceType::ROOK, "Only bishop or rook allowed!"); const auto opp_pt_queen = board.pieces(pt, PieceType::QUEEN) & board.us(~c); @@ -3483,20 +3899,23 @@ template Bitboard pin = 0ull; - while (pt_attacks) { + while (pt_attacks) + { const auto possible_pin = between(sq, pt_attacks.pop()); - if ((possible_pin & occ_us).count() == 1) pin |= possible_pin; + if ((possible_pin & occ_us).count() == 1) + pin |= possible_pin; } return pin; } -template -[[nodiscard]] inline Bitboard movegen::seenSquares(const Board &board, Bitboard enemy_empty) { - auto king_sq = board.kingSq(~c); +template +[[nodiscard]] inline Bitboard movegen::seenSquares(const Board& board, Bitboard enemy_empty) { + auto king_sq = board.kingSq(~c); Bitboard map_king_atk = attacks::king(king_sq) & enemy_empty; - if (map_king_atk == Bitboard(0ull) && !board.chess960()) return 0ull; + if (map_king_atk == Bitboard(0ull) && !board.chess960()) + return 0ull; auto occ = board.occ() ^ Bitboard::fromSquare(king_sq); auto queens = board.pieces(PieceType::QUEEN, c); @@ -3507,15 +3926,18 @@ template Bitboard seen = attacks::pawnLeftAttacks(pawns) | attacks::pawnRightAttacks(pawns); - while (knights) { + while (knights) + { seen |= attacks::knight(knights.pop()); } - while (bishops) { + while (bishops) + { seen |= attacks::bishop(bishops.pop(), occ); } - while (rooks) { + while (rooks) + { seen |= attacks::rook(rooks.pop(), occ); } @@ -3524,9 +3946,13 @@ template return seen; } -template -inline void movegen::generatePawnMoves(const Board &board, Movelist &moves, Bitboard pin_d, Bitboard pin_hv, - Bitboard checkmask, Bitboard occ_opp) { +template +inline void movegen::generatePawnMoves(const Board& board, + Movelist& moves, + Bitboard pin_d, + Bitboard pin_hv, + Bitboard checkmask, + Bitboard occ_opp) { // flipped for black constexpr auto UP = make_direction(Direction::NORTH, c); @@ -3547,8 +3973,10 @@ inline void movegen::generatePawnMoves(const Board &board, Movelist &moves, Bitb const Bitboard unpinned_pawns_lr = pawns_lr & ~pin_d; const Bitboard pinned_pawns_lr = pawns_lr & pin_d; - auto l_pawns = attacks::shift(unpinned_pawns_lr) | (attacks::shift(pinned_pawns_lr) & pin_d); - auto r_pawns = attacks::shift(unpinned_pawns_lr) | (attacks::shift(pinned_pawns_lr) & pin_d); + auto l_pawns = attacks::shift(unpinned_pawns_lr) + | (attacks::shift(pinned_pawns_lr) & pin_d); + auto r_pawns = attacks::shift(unpinned_pawns_lr) + | (attacks::shift(pinned_pawns_lr) & pin_d); // Prune moves that don't capture a piece and are not on the checkmask. l_pawns &= occ_opp & checkmask; @@ -3567,18 +3995,21 @@ inline void movegen::generatePawnMoves(const Board &board, Movelist &moves, Bitb // Prune moves that are not on the checkmask. Bitboard single_push = (single_push_unpinned | single_push_pinned) & checkmask; - Bitboard double_push = ((attacks::shift(single_push_unpinned & DOUBLE_PUSH_RANK) & ~board.occ()) | - (attacks::shift(single_push_pinned & DOUBLE_PUSH_RANK) & ~board.occ())) & - checkmask; + Bitboard double_push = + ((attacks::shift(single_push_unpinned & DOUBLE_PUSH_RANK) & ~board.occ()) + | (attacks::shift(single_push_pinned & DOUBLE_PUSH_RANK) & ~board.occ())) + & checkmask; - if (pawns & RANK_B_PROMO) { + if (pawns & RANK_B_PROMO) + { Bitboard promo_left = l_pawns & RANK_PROMO; Bitboard promo_right = r_pawns & RANK_PROMO; Bitboard promo_push = single_push & RANK_PROMO; // Skip capturing promotions if we are only generating quiet moves. // Generates at ALL and CAPTURE - while (mt != MoveGenType::QUIET && promo_left) { + while (mt != MoveGenType::QUIET && promo_left) + { const auto index = promo_left.pop(); moves.add(Move::make(index + DOWN_RIGHT, index, PieceType::QUEEN)); moves.add(Move::make(index + DOWN_RIGHT, index, PieceType::ROOK)); @@ -3588,7 +4019,8 @@ inline void movegen::generatePawnMoves(const Board &board, Movelist &moves, Bitb // Skip capturing promotions if we are only generating quiet moves. // Generates at ALL and CAPTURE - while (mt != MoveGenType::QUIET && promo_right) { + while (mt != MoveGenType::QUIET && promo_right) + { const auto index = promo_right.pop(); moves.add(Move::make(index + DOWN_LEFT, index, PieceType::QUEEN)); moves.add(Move::make(index + DOWN_LEFT, index, PieceType::ROOK)); @@ -3598,7 +4030,8 @@ inline void movegen::generatePawnMoves(const Board &board, Movelist &moves, Bitb // Skip quiet promotions if we are only generating captures. // Generates at ALL and QUIET - while (mt != MoveGenType::CAPTURE && promo_push) { + while (mt != MoveGenType::CAPTURE && promo_push) + { const auto index = promo_push.pop(); moves.add(Move::make(index + DOWN, index, PieceType::QUEEN)); moves.add(Move::make(index + DOWN, index, PieceType::ROOK)); @@ -3611,46 +4044,54 @@ inline void movegen::generatePawnMoves(const Board &board, Movelist &moves, Bitb l_pawns &= ~RANK_PROMO; r_pawns &= ~RANK_PROMO; - while (mt != MoveGenType::QUIET && l_pawns) { + while (mt != MoveGenType::QUIET && l_pawns) + { const auto index = l_pawns.pop(); moves.add(Move::make(index + DOWN_RIGHT, index)); } - while (mt != MoveGenType::QUIET && r_pawns) { + while (mt != MoveGenType::QUIET && r_pawns) + { const auto index = r_pawns.pop(); moves.add(Move::make(index + DOWN_LEFT, index)); } - while (mt != MoveGenType::CAPTURE && single_push) { + while (mt != MoveGenType::CAPTURE && single_push) + { const auto index = single_push.pop(); moves.add(Move::make(index + DOWN, index)); } - while (mt != MoveGenType::CAPTURE && double_push) { + while (mt != MoveGenType::CAPTURE && double_push) + { const auto index = double_push.pop(); moves.add(Move::make(index + DOWN + DOWN, index)); } - if constexpr (mt == MoveGenType::QUIET) return; + if constexpr (mt == MoveGenType::QUIET) + return; const Square ep = board.enpassantSq(); - if (ep != Square::NO_SQ) { + if (ep != Square::NO_SQ) + { auto m = generateEPMove(board, checkmask, pin_d, pawns_lr, ep, c); - for (const auto &move : m) { - if (move != Move::NO_MOVE) moves.add(move); + for (const auto& move : m) + { + if (move != Move::NO_MOVE) + moves.add(move); } } } -[[nodiscard]] inline std::array movegen::generateEPMove(const Board &board, Bitboard checkmask, Bitboard pin_d, - Bitboard pawns_lr, Square ep, Color c) { - assert((ep.rank() == Rank::RANK_3 && board.sideToMove() == Color::BLACK) || - (ep.rank() == Rank::RANK_6 && board.sideToMove() == Color::WHITE)); +[[nodiscard]] inline std::array movegen::generateEPMove( + const Board& board, Bitboard checkmask, Bitboard pin_d, Bitboard pawns_lr, Square ep, Color c) { + assert((ep.rank() == Rank::RANK_3 && board.sideToMove() == Color::BLACK) + || (ep.rank() == Rank::RANK_6 && board.sideToMove() == Color::WHITE)); std::array moves = {Move::NO_MOVE, Move::NO_MOVE}; - int i = 0; + int i = 0; const auto DOWN = make_direction(Direction::SOUTH, c); const auto epPawnSq = ep + DOWN; @@ -3660,16 +4101,18 @@ inline void movegen::generatePawnMoves(const Board &board, Movelist &moves, Bitb that just moved are not on the checkmask en passant is not available. */ - if ((checkmask & (Bitboard::fromSquare(epPawnSq) | Bitboard::fromSquare(ep))) == 0ull) return moves; + if ((checkmask & (Bitboard::fromSquare(epPawnSq) | Bitboard::fromSquare(ep))) == 0ull) + return moves; - const Square kSQ = board.kingSq(c); + const Square kSQ = board.kingSq(c); const Bitboard kingMask = Bitboard::fromSquare(kSQ) & epPawnSq.rank().bb(); const Bitboard enemyQueenRook = board.pieces(PieceType::ROOK, PieceType::QUEEN) & board.us(~c); auto epBB = attacks::pawn(~c, ep) & pawns_lr; // For one en passant square two pawns could potentially take there. - while (epBB) { + while (epBB) + { const auto from = epBB.pop(); const auto to = ep; @@ -3677,7 +4120,8 @@ inline void movegen::generatePawnMoves(const Board &board, Movelist &moves, Bitb If the pawn is pinned but the en passant square is not on the pin mask then the move is illegal. */ - if ((Bitboard::fromSquare(from) & pin_d) && !(pin_d & Bitboard::fromSquare(ep))) continue; + if ((Bitboard::fromSquare(from) & pin_d) && !(pin_d & Bitboard::fromSquare(ep))) + continue; const auto connectingPawns = Bitboard::fromSquare(epPawnSq) | Bitboard::fromSquare(from); @@ -3691,7 +4135,9 @@ inline void movegen::generatePawnMoves(const Board &board, Movelist &moves, Bitb */ const auto isPossiblePin = kingMask && enemyQueenRook; - if (isPossiblePin && (attacks::rook(kSQ, board.occ() ^ connectingPawns) & enemyQueenRook) != 0ull) break; + if (isPossiblePin + && (attacks::rook(kSQ, board.occ() ^ connectingPawns) & enemyQueenRook) != 0ull) + break; moves[i++] = Move::make(from, to); } @@ -3699,29 +4145,36 @@ inline void movegen::generatePawnMoves(const Board &board, Movelist &moves, Bitb return moves; } -[[nodiscard]] inline Bitboard movegen::generateKnightMoves(Square sq) { return attacks::knight(sq); } +[[nodiscard]] inline Bitboard movegen::generateKnightMoves(Square sq) { + return attacks::knight(sq); +} -[[nodiscard]] inline Bitboard movegen::generateBishopMoves(Square sq, Bitboard pin_d, Bitboard occ_all) { +[[nodiscard]] inline Bitboard +movegen::generateBishopMoves(Square sq, Bitboard pin_d, Bitboard occ_all) { // The Bishop is pinned diagonally thus can only move diagonally. - if (pin_d & Bitboard::fromSquare(sq)) return attacks::bishop(sq, occ_all) & pin_d; + if (pin_d & Bitboard::fromSquare(sq)) + return attacks::bishop(sq, occ_all) & pin_d; return attacks::bishop(sq, occ_all); } -[[nodiscard]] inline Bitboard movegen::generateRookMoves(Square sq, Bitboard pin_hv, Bitboard occ_all) { +[[nodiscard]] inline Bitboard +movegen::generateRookMoves(Square sq, Bitboard pin_hv, Bitboard occ_all) { // The Rook is pinned horizontally thus can only move horizontally. - if (pin_hv & Bitboard::fromSquare(sq)) return attacks::rook(sq, occ_all) & pin_hv; + if (pin_hv & Bitboard::fromSquare(sq)) + return attacks::rook(sq, occ_all) & pin_hv; return attacks::rook(sq, occ_all); } -[[nodiscard]] inline Bitboard movegen::generateQueenMoves(Square sq, Bitboard pin_d, Bitboard pin_hv, - Bitboard occ_all) { +[[nodiscard]] inline Bitboard +movegen::generateQueenMoves(Square sq, Bitboard pin_d, Bitboard pin_hv, Bitboard occ_all) { Bitboard moves = 0ULL; if (pin_d & Bitboard::fromSquare(sq)) moves |= attacks::bishop(sq, occ_all) & pin_d; else if (pin_hv & Bitboard::fromSquare(sq)) moves |= attacks::rook(sq, occ_all) & pin_hv; - else { + else + { moves |= attacks::rook(sq, occ_all); moves |= attacks::bishop(sq, occ_all); } @@ -3729,34 +4182,45 @@ inline void movegen::generatePawnMoves(const Board &board, Movelist &moves, Bitb return moves; } -[[nodiscard]] inline Bitboard movegen::generateKingMoves(Square sq, Bitboard seen, Bitboard movable_square) { +[[nodiscard]] inline Bitboard +movegen::generateKingMoves(Square sq, Bitboard seen, Bitboard movable_square) { return attacks::king(sq) & movable_square & ~seen; } -template -[[nodiscard]] inline Bitboard movegen::generateCastleMoves(const Board &board, Square sq, Bitboard seen, - Bitboard pin_hv) noexcept { - if (!Square::back_rank(sq, c) || !board.castlingRights().has(c)) return 0ull; +template +[[nodiscard]] inline Bitboard movegen::generateCastleMoves(const Board& board, + Square sq, + Bitboard seen, + Bitboard pin_hv) noexcept { + if (!Square::back_rank(sq, c) || !board.castlingRights().has(c)) + return 0ull; const auto rights = board.castlingRights(); Bitboard moves = 0ull; - for (const auto side : {Board::CastlingRights::Side::KING_SIDE, Board::CastlingRights::Side::QUEEN_SIDE}) { - if (!rights.has(c, side)) continue; + for (const auto side : + {Board::CastlingRights::Side::KING_SIDE, Board::CastlingRights::Side::QUEEN_SIDE}) + { + if (!rights.has(c, side)) + continue; const auto is_king_side = side == Board::CastlingRights::Side::KING_SIDE; // No pieces on the castling path - if (board.occ() & board.getCastlingPath(c, is_king_side)) continue; + if (board.occ() & board.getCastlingPath(c, is_king_side)) + continue; // No attacks on the king path const auto king_to = Square::castling_king_square(is_king_side, c); - if (between(sq, king_to) & seen) continue; + if (between(sq, king_to) & seen) + continue; // Chess960: Rook is pinned on the backrank. - const auto from_rook_bb = Bitboard::fromSquare(Square(rights.getRookFile(c, side), sq.rank())); - if (board.chess960() && (pin_hv & board.us(board.sideToMove()) & from_rook_bb)) continue; + const auto from_rook_bb = + Bitboard::fromSquare(Square(rights.getRookFile(c, side), sq.rank())); + if (board.chess960() && (pin_hv & board.us(board.sideToMove()) & from_rook_bb)) + continue; moves |= from_rook_bb; } @@ -3764,20 +4228,22 @@ template return moves; } -template -inline void movegen::whileBitboardAdd(Movelist &movelist, Bitboard mask, T func) { - while (mask) { - const Square from = mask.pop(); - auto moves = func(from); - while (moves) { +template +inline void movegen::whileBitboardAdd(Movelist& movelist, Bitboard mask, T func) { + while (mask) + { + const Square from = mask.pop(); + auto moves = func(from); + while (moves) + { const Square to = moves.pop(); movelist.add(Move::make(from, to)); } } } -template -inline void movegen::legalmoves(Movelist &movelist, const Board &board, int pieces) { +template +inline void movegen::legalmoves(Movelist& movelist, const Board& board, int pieces) { /* The size of the movelist might not be 0! This is done on purpose since it enables @@ -3807,16 +4273,19 @@ inline void movegen::legalmoves(Movelist &movelist, const Board &board, int piec else // QUIET moves movable_square = ~occ_all; - if (pieces & PieceGenType::KING) { + if (pieces & PieceGenType::KING) + { Bitboard seen = seenSquares<~c>(board, opp_empty); whileBitboardAdd(movelist, Bitboard::fromSquare(king_sq), [&](Square sq) { return generateKingMoves(sq, seen, movable_square); }); - if (mt != MoveGenType::CAPTURE && checks == 0) { + if (mt != MoveGenType::CAPTURE && checks == 0) + { Bitboard moves_bb = generateCastleMoves(board, king_sq, seen, pin_hv); - while (moves_bb) { + while (moves_bb) + { Square to = moves_bb.pop(); movelist.add(Move::make(king_sq, to)); } @@ -3824,50 +4293,60 @@ inline void movegen::legalmoves(Movelist &movelist, const Board &board, int piec } // Early return for double check as described earlier - if (checks == 2) return; + if (checks == 2) + return; // Moves have to be on the checkmask movable_square &= checkmask; // Add the moves to the movelist. - if (pieces & PieceGenType::PAWN) { + if (pieces & PieceGenType::PAWN) + { generatePawnMoves(board, movelist, pin_d, pin_hv, checkmask, occ_opp); } - if (pieces & PieceGenType::KNIGHT) { + if (pieces & PieceGenType::KNIGHT) + { // Prune knights that are pinned since these cannot move. Bitboard knights_mask = board.pieces(PieceType::KNIGHT, c) & ~(pin_d | pin_hv); - whileBitboardAdd(movelist, knights_mask, [&](Square sq) { return generateKnightMoves(sq) & movable_square; }); + whileBitboardAdd(movelist, knights_mask, + [&](Square sq) { return generateKnightMoves(sq) & movable_square; }); } - if (pieces & PieceGenType::BISHOP) { + if (pieces & PieceGenType::BISHOP) + { // Prune horizontally pinned bishops Bitboard bishops_mask = board.pieces(PieceType::BISHOP, c) & ~pin_hv; - whileBitboardAdd(movelist, bishops_mask, - [&](Square sq) { return generateBishopMoves(sq, pin_d, occ_all) & movable_square; }); + whileBitboardAdd(movelist, bishops_mask, [&](Square sq) { + return generateBishopMoves(sq, pin_d, occ_all) & movable_square; + }); } - if (pieces & PieceGenType::ROOK) { + if (pieces & PieceGenType::ROOK) + { // Prune diagonally pinned rooks Bitboard rooks_mask = board.pieces(PieceType::ROOK, c) & ~pin_d; - whileBitboardAdd(movelist, rooks_mask, - [&](Square sq) { return generateRookMoves(sq, pin_hv, occ_all) & movable_square; }); + whileBitboardAdd(movelist, rooks_mask, [&](Square sq) { + return generateRookMoves(sq, pin_hv, occ_all) & movable_square; + }); } - if (pieces & PieceGenType::QUEEN) { + if (pieces & PieceGenType::QUEEN) + { // Prune double pinned queens Bitboard queens_mask = board.pieces(PieceType::QUEEN, c) & ~(pin_d & pin_hv); - whileBitboardAdd(movelist, queens_mask, - [&](Square sq) { return generateQueenMoves(sq, pin_d, pin_hv, occ_all) & movable_square; }); + whileBitboardAdd(movelist, queens_mask, [&](Square sq) { + return generateQueenMoves(sq, pin_d, pin_hv, occ_all) & movable_square; + }); } } -template -inline void movegen::legalmoves(Movelist &movelist, const Board &board, int pieces) { +template +inline void movegen::legalmoves(Movelist& movelist, const Board& board, int pieces) { movelist.clear(); if (board.sideToMove() == Color::WHITE) @@ -3876,25 +4355,27 @@ inline void movegen::legalmoves(Movelist &movelist, const Board &board, int piec legalmoves(movelist, board, pieces); } -template -inline bool movegen::isEpSquareValid(const Board &board, Square ep) { +template +inline bool movegen::isEpSquareValid(const Board& board, Square ep) { const auto stm = board.sideToMove(); Bitboard occ_us = board.us(stm); Bitboard occ_opp = board.us(~stm); - auto king_sq = board.kingSq(stm); + auto king_sq = board.kingSq(stm); const auto [checkmask, checks] = movegen::checkMask(board, king_sq); - const auto pin_hv = movegen::pinMask(board, king_sq, occ_opp, occ_us); - const auto pin_d = movegen::pinMask(board, king_sq, occ_opp, occ_us); + const auto pin_hv = movegen::pinMask(board, king_sq, occ_opp, occ_us); + const auto pin_d = movegen::pinMask(board, king_sq, occ_opp, occ_us); const auto pawns = board.pieces(PieceType::PAWN, stm); const auto pawns_lr = pawns & ~pin_hv; const auto m = movegen::generateEPMove(board, checkmask, pin_d, pawns_lr, ep, stm); - bool found = false; + bool found = false; - for (const auto &move : m) { - if (move != Move::NO_MOVE) { + for (const auto& move : m) + { + if (move != Move::NO_MOVE) + { found = true; break; } @@ -3932,7 +4413,8 @@ class StringBuffer { std::string_view get() const noexcept { return std::string_view(buffer_.data(), index_); } bool add(char c) { - if (index_ >= N) { + if (index_ >= N) + { return false; } @@ -3956,22 +4438,26 @@ class StringBuffer { * @brief Private class * @tparam BUFFER_SIZE */ -template +template class StreamBuffer { private: static constexpr std::size_t N = BUFFER_SIZE; using BufferType = std::array; public: - StreamBuffer(std::istream &stream) : stream_(stream) {} + StreamBuffer(std::istream& stream) : + stream_(stream) {} // Get the current character, skip carriage returns std::optional some() { - while (true) { - if (buffer_index_ < bytes_read_) { + while (true) + { + if (buffer_index_ < bytes_read_) + { const auto c = buffer_[buffer_index_]; - if (c == '\r') { + if (c == '\r') + { ++buffer_index_; continue; } @@ -3979,7 +4465,8 @@ class StreamBuffer { return c; } - if (!fill()) { + if (!fill()) + { return std::nullopt; } } @@ -3989,23 +4476,32 @@ class StreamBuffer { bool skipUntil(char open_delim, char close_delim) { int stack = 0; - while (true) { + while (true) + { const auto ret = some(); advance(); - if (!ret.has_value()) { + if (!ret.has_value()) + { return false; } - if (*ret == open_delim) { + if (*ret == open_delim) + { ++stack; - } else if (*ret == close_delim) { - if (stack == 0) { + } + else if (*ret == close_delim) + { + if (stack == 0) + { // Mismatched closing delimiter return false; - } else { + } + else + { --stack; - if (stack == 0) { + if (stack == 0) + { // Matching closing delimiter found return true; } @@ -4027,7 +4523,8 @@ class StreamBuffer { } void advance() { - if (buffer_index_ >= bytes_read_) { + if (buffer_index_ >= bytes_read_) + { fill(); } @@ -4035,7 +4532,8 @@ class StreamBuffer { } char peek() { - if (buffer_index_ + 1 >= bytes_read_) { + if (buffer_index_ + 1 >= bytes_read_) + { return stream_.peek(); } @@ -4043,7 +4541,8 @@ class StreamBuffer { } std::optional current() { - if (buffer_index_ >= bytes_read_) { + if (buffer_index_ >= bytes_read_) + { return fill() ? std::optional(buffer_[buffer_index_]) : std::nullopt; } @@ -4051,8 +4550,8 @@ class StreamBuffer { } private: - std::istream &stream_; - BufferType buffer_; + std::istream& stream_; + BufferType buffer_; std::streamsize bytes_read_ = 0; std::streamsize buffer_index_ = 0; }; @@ -4118,34 +4617,37 @@ class StreamParserError { NotEnoughData }; - StreamParserError() : code_(None) {} + StreamParserError() : + code_(None) {} - StreamParserError(Code code) : code_(code) {} + StreamParserError(Code code) : + code_(code) {} Code code() const { return code_; } bool hasError() const { return code_ != None; } std::string message() const { - switch (code_) { - case None: - return "No error"; - case InvalidHeaderMissingClosingBracket: - return "Invalid header: missing closing bracket"; - case InvalidHeaderMissingClosingQuote: - return "Invalid header: missing closing quote"; - case NotEnoughData: - return "Not enough data"; - default: - assert(false); - return "Unknown error"; + switch (code_) + { + case None : + return "No error"; + case InvalidHeaderMissingClosingBracket : + return "Invalid header: missing closing bracket"; + case InvalidHeaderMissingClosingQuote : + return "Invalid header: missing closing quote"; + case NotEnoughData : + return "Not enough data"; + default : + assert(false); + return "Unknown error"; } } bool operator==(Code code) const { return code_ == code; } bool operator!=(Code code) const { return code_ != code; } - bool operator==(const StreamParserError &other) const { return code_ == other.code_; } - bool operator!=(const StreamParserError &other) const { return code_ != other.code_; } + bool operator==(const StreamParserError& other) const { return code_ == other.code_; } + bool operator!=(const StreamParserError& other) const { return code_ != other.code_; } operator bool() const { return code_ != None; } @@ -4153,54 +4655,64 @@ class StreamParserError { Code code_; }; -template + > class StreamParser { public: - StreamParser(std::istream &stream) : stream_buffer(stream) {} + StreamParser(std::istream& stream) : + stream_buffer(stream) {} - StreamParserError readGames(Visitor &vis) { + StreamParserError readGames(Visitor& vis) { visitor = &vis; - if (!stream_buffer.fill()) { + if (!stream_buffer.fill()) + { return StreamParserError::NotEnoughData; } - while (auto c = stream_buffer.some()) { - if (in_header) { + while (auto c = stream_buffer.some()) + { + if (in_header) + { visitor->skipPgn(false); - if (*c == '[') { + if (*c == '[') + { visitor->startPgn(); pgn_end = false; processHeader(); - if (error != StreamParserError::None) { + if (error != StreamParserError::None) + { return error; } } - - } else if (in_body) { + } + else if (in_body) + { processBody(); - if (error != StreamParserError::None) { + if (error != StreamParserError::None) + { return error; } } - if (!dont_advance_after_body) stream_buffer.advance(); + if (!dont_advance_after_body) + stream_buffer.advance(); dont_advance_after_body = false; } - if (!pgn_end) { + if (!pgn_end) + { onEnd(); } @@ -4220,8 +4732,10 @@ class StreamParser { } void callVisitorMoveFunction() { - if (!move.empty()) { - if (!visitor->skip()) visitor->move(move.get(), comment); + if (!move.empty()) + { + if (!visitor->skip()) + visitor->move(move.get(), comment); move.clear(); comment.clear(); @@ -4231,93 +4745,114 @@ class StreamParser { void processHeader() { bool backslash = false; - while (auto c = stream_buffer.some()) { - switch (*c) { - // tag start - case '[': - stream_buffer.advance(); - - while (auto k = stream_buffer.some()) { - if (is_space(*k)) { - break; - } else { - if (!header.first.add(*k)) { - error = StreamParserError::ExceededMaxStringLength; - return; - } + while (auto c = stream_buffer.some()) + { + switch (*c) + { + // tag start + case '[' : + stream_buffer.advance(); - stream_buffer.advance(); + while (auto k = stream_buffer.some()) + { + if (is_space(*k)) + { + break; + } + else + { + if (!header.first.add(*k)) + { + error = StreamParserError::ExceededMaxStringLength; + return; } + + stream_buffer.advance(); } + } - stream_buffer.advance(); - break; - case '"': - stream_buffer.advance(); + stream_buffer.advance(); + break; + case '"' : + stream_buffer.advance(); + + while (auto k = stream_buffer.some()) + { + if (*k == '\\') + { + backslash = true; + // don't add backslash to header, is this really correct? + stream_buffer.advance(); + } + else if (*k == '"' && !backslash) + { + stream_buffer.advance(); - while (auto k = stream_buffer.some()) { - if (*k == '\\') { - backslash = true; - // don't add backslash to header, is this really correct? - stream_buffer.advance(); - } else if (*k == '"' && !backslash) { - stream_buffer.advance(); - - // we should be now at ] - if (stream_buffer.current().value_or('\0') != ']') { - error = StreamParserError::InvalidHeaderMissingClosingBracket; - return; - } - - stream_buffer.advance(); - - break; - } else if (*k == '\n') { - // we missed the closing quote and read until the newline character - // this is an invalid pgn, let's throw an error - error = StreamParserError::InvalidHeaderMissingClosingQuote; + // we should be now at ] + if (stream_buffer.current().value_or('\0') != ']') + { + error = StreamParserError::InvalidHeaderMissingClosingBracket; return; - } else { - backslash = false; + } - if (!header.second.add(*k)) { - error = StreamParserError::ExceededMaxStringLength; - return; - } + stream_buffer.advance(); - stream_buffer.advance(); - } + break; } + else if (*k == '\n') + { + // we missed the closing quote and read until the newline character + // this is an invalid pgn, let's throw an error + error = StreamParserError::InvalidHeaderMissingClosingQuote; + return; + } + else + { + backslash = false; + + if (!header.second.add(*k)) + { + error = StreamParserError::ExceededMaxStringLength; + return; + } - // manually skip carriage return, otherwise we would be in the body - // ideally we should completely skip all carriage returns and newlines to avoid this - if (stream_buffer.current() == '\r') { stream_buffer.advance(); } + } - if (!visitor->skip()) visitor->header(header.first.get(), header.second.get()); + // manually skip carriage return, otherwise we would be in the body + // ideally we should completely skip all carriage returns and newlines to avoid this + if (stream_buffer.current() == '\r') + { + stream_buffer.advance(); + } - header.first.clear(); - header.second.clear(); + if (!visitor->skip()) + visitor->header(header.first.get(), header.second.get()); - stream_buffer.advance(); - break; - case '\n': - in_header = false; - in_body = true; + header.first.clear(); + header.second.clear(); - if (!visitor->skip()) visitor->startMoves(); + stream_buffer.advance(); + break; + case '\n' : + in_header = false; + in_body = true; - return; - default: - // this should normally not happen - // lets just go into the body, will this always be save? - in_header = false; - in_body = true; + if (!visitor->skip()) + visitor->startMoves(); - if (!visitor->skip()) visitor->startMoves(); + return; + default : + // this should normally not happen + // lets just go into the body, will this always be save? + in_header = false; + in_body = true; - return; + if (!visitor->skip()) + visitor->startMoves(); + + return; } } } @@ -4326,7 +4861,7 @@ class StreamParser { auto is_termination_symbol = false; auto has_comment = false; - start: +start: /* Skip first move number or game termination Also skip - * / to fix games @@ -4334,22 +4869,30 @@ class StreamParser { this https://github.com/Disservin/chess-library/issues/68 */ - while (auto c = stream_buffer.some()) { - if (*c == ' ' || is_digit(*c)) { + while (auto c = stream_buffer.some()) + { + if (*c == ' ' || is_digit(*c)) + { stream_buffer.advance(); - } else if (*c == '-' || *c == '*' || c == '/') { + } + else if (*c == '-' || *c == '*' || *c == '/') + { is_termination_symbol = true; stream_buffer.advance(); - } else if (*c == '{') { + } + else if (*c == '{') + { has_comment = true; // reading comment stream_buffer.advance(); - while (auto k = stream_buffer.some()) { + while (auto k = stream_buffer.some()) + { stream_buffer.advance(); - if (*k == '}') { + if (*k == '}') + { break; } @@ -4357,29 +4900,37 @@ class StreamParser { } // the game has no moves, but a comment followed by a game termination - if (!visitor->skip()) { + if (!visitor->skip()) + { visitor->move("", comment); + has_comment = false; comment.clear(); } - } else { + } + else + { break; } } // we need to reparse the termination symbol - if (has_comment && !is_termination_symbol) { + if (!visitor->skip() && has_comment && !is_termination_symbol) + { goto start; } // game had no moves, so we can skip it and call endPgn - if (is_termination_symbol) { + if (is_termination_symbol) + { onEnd(); return; } - while (auto c = stream_buffer.some()) { - if (is_space(*c)) { + while (auto c = stream_buffer.some()) + { + if (is_space(*c)) + { stream_buffer.advance(); continue; } @@ -4387,14 +4938,16 @@ class StreamParser { break; } - while (auto cd = stream_buffer.some()) { + while (auto cd = stream_buffer.some()) + { // Pgn are build up in the following way. // {move_number} {move} {comment} {move} {comment} {move_number} ... // So we need to skip the move_number then start reading the move, then save the comment // then read the second move in the group. After that a move_number will follow again. // [ is unexpected here, it probably is a new pgn and the current one is finished - if (*cd == '[') { + if (*cd == '[') + { onEnd(); dont_advance_after_body = true; // break; @@ -4402,42 +4955,59 @@ class StreamParser { } // skip move number digits - while (auto c = stream_buffer.some()) { - if (is_space(*c) || is_digit(*c)) { + while (auto c = stream_buffer.some()) + { + if (is_space(*c) || is_digit(*c)) + { stream_buffer.advance(); - } else { + } + else + { break; } } // skip dots - while (auto c = stream_buffer.some()) { - if (*c == '.') { + while (auto c = stream_buffer.some()) + { + if (*c == '.') + { stream_buffer.advance(); - } else { + } + else + { break; } } // skip spaces - while (auto c = stream_buffer.some()) { - if (is_space(*c)) { + while (auto c = stream_buffer.some()) + { + if (is_space(*c)) + { stream_buffer.advance(); - } else { + } + else + { break; } } // parse move - if (parseMove()) { + if (parseMove()) + { break; } // skip spaces - while (auto c = stream_buffer.some()) { - if (is_space(*c)) { + while (auto c = stream_buffer.some()) + { + if (is_space(*c)) + { stream_buffer.advance(); - } else { + } + else + { break; } } @@ -4445,13 +5015,15 @@ class StreamParser { // game termination auto curr = stream_buffer.current(); - if (!curr.has_value()) { + if (!curr.has_value()) + { onEnd(); break; } // game termination - if (*curr == '*') { + if (*curr == '*') + { onEnd(); stream_buffer.advance(); @@ -4460,15 +5032,20 @@ class StreamParser { const auto peek = stream_buffer.peek(); - if (*curr == '1') { - if (peek == '-') { + if (*curr == '1') + { + if (peek == '-') + { stream_buffer.advance(); stream_buffer.advance(); onEnd(); break; - } else if (peek == '/') { - for (size_t i = 0; i <= 6; ++i) { + } + else if (peek == '/') + { + for (size_t i = 0; i <= 6; ++i) + { stream_buffer.advance(); } @@ -4478,32 +5055,38 @@ class StreamParser { } // might be 0-1 (game termination) or 0-0-0/0-0 (castling) - if (*curr == '0' && stream_buffer.peek() == '-') { + if (*curr == '0' && stream_buffer.peek() == '-') + { stream_buffer.advance(); stream_buffer.advance(); const auto c = stream_buffer.current(); - if (!c.has_value()) { + if (!c.has_value()) + { onEnd(); break; } // game termination - if (*c == '1') { + if (*c == '1') + { onEnd(); stream_buffer.advance(); break; } // castling - else { - if (!move.add('0') || !move.add('-')) { + else + { + if (!move.add('0') || !move.add('-')) + { error = StreamParserError::ExceededMaxStringLength; return; } - if (parseMove()) { + if (parseMove()) + { stream_buffer.advance(); break; } @@ -4514,12 +5097,15 @@ class StreamParser { bool parseMove() { // reading move - while (auto c = stream_buffer.some()) { - if (is_space(*c)) { + while (auto c = stream_buffer.some()) + { + if (is_space(*c)) + { break; } - if (!move.add(*c)) { + if (!move.add(*c)) + { error = StreamParserError::ExceededMaxStringLength; return true; } @@ -4531,60 +5117,69 @@ class StreamParser { } bool parseMoveAppendix() { - while (true) { + while (true) + { auto curr = stream_buffer.current(); - if (!curr.has_value()) { + if (!curr.has_value()) + { onEnd(); return true; } - switch (*curr) { - case '{': { - // reading comment - stream_buffer.advance(); - - while (auto c = stream_buffer.some()) { - stream_buffer.advance(); + switch (*curr) + { + case '{' : { + // reading comment + stream_buffer.advance(); - if (*c == '}') { - break; - } + while (auto c = stream_buffer.some()) + { + stream_buffer.advance(); - comment += *c; + if (*c == '}') + { + break; } - break; - } - case '(': { - stream_buffer.skipUntil('(', ')'); - break; + comment += *c; } - case '$': { - while (auto c = stream_buffer.some()) { - if (is_space(*c)) { - break; - } - stream_buffer.advance(); + break; + } + case '(' : { + stream_buffer.skipUntil('(', ')'); + break; + } + case '$' : { + while (auto c = stream_buffer.some()) + { + if (is_space(*c)) + { + break; } - break; + stream_buffer.advance(); } - case ' ': { - while (auto c = stream_buffer.some()) { - if (!is_space(*c)) { - break; - } - stream_buffer.advance(); + break; + } + case ' ' : { + while (auto c = stream_buffer.some()) + { + if (!is_space(*c)) + { + break; } - break; + stream_buffer.advance(); } - default: - callVisitorMoveFunction(); - return false; + + break; + } + default : + callVisitorMoveFunction(); + return false; } } } @@ -4600,44 +5195,47 @@ class StreamParser { } bool is_space(const char c) noexcept { - switch (c) { - case ' ': - case '\t': - case '\n': - case '\r': - return true; - default: - return false; + switch (c) + { + case ' ' : + case '\t' : + case '\n' : + case '\r' : + return true; + default : + return false; } } bool is_digit(const char c) noexcept { - switch (c) { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - return true; - default: - return false; + switch (c) + { + case '0' : + case '1' : + case '2' : + case '3' : + case '4' : + case '5' : + case '6' : + case '7' : + case '8' : + case '9' : + return true; + default : + return false; } } detail::StreamBuffer stream_buffer; - Visitor *visitor = nullptr; + Visitor* visitor = nullptr; // one time allocations - std::pair header = {detail::StringBuffer{}, detail::StringBuffer{}}; + std::pair header = {detail::StringBuffer{}, + detail::StringBuffer{}}; - detail::StringBuffer move = {}; - std::string comment = {}; + detail::StringBuffer move = {}; + std::string comment = {}; // State @@ -4664,14 +5262,16 @@ class uci { * @param chess960 * @return */ - [[nodiscard]] static std::string moveToUci(const Move &move, bool chess960 = false) noexcept(false) { + [[nodiscard]] static std::string moveToUci(const Move& move, + bool chess960 = false) noexcept(false) { // Get the from and to squares Square from_sq = move.from(); Square to_sq = move.to(); // If the move is not a chess960 castling move and is a king moving more than one square, // update the to square to be the correct square for a regular castling move - if (!chess960 && move.typeOf() == Move::CASTLING) { + if (!chess960 && move.typeOf() == Move::CASTLING) + { to_sq = Square(to_sq > from_sq ? File::FILE_G : File::FILE_C, from_sq.rank()); } @@ -4681,7 +5281,8 @@ class uci { ss << from_sq << to_sq; // If the move is a promotion, add the promoted piece to the string stream - if (move.typeOf() == Move::PROMOTION) { + if (move.typeOf() == Move::PROMOTION) + { ss << static_cast(move.promotionType()); } @@ -4694,43 +5295,53 @@ class uci { * @param uci * @return */ - [[nodiscard]] static Move uciToMove(const Board &board, const std::string &uci) noexcept(false) { - if (uci.length() < 4) { + [[nodiscard]] static Move uciToMove(const Board& board, + const std::string& uci) noexcept(false) { + if (uci.length() < 4) + { return Move::NO_MOVE; } Square source = Square(uci.substr(0, 2)); Square target = Square(uci.substr(2, 2)); - if (!source.is_valid() || !target.is_valid()) { + if (!source.is_valid() || !target.is_valid()) + { return Move::NO_MOVE; } auto pt = board.at(source).type(); // castling in chess960 - if (board.chess960() && pt == PieceType::KING && board.at(target).type() == PieceType::ROOK && - board.at(target).color() == board.sideToMove()) { + if (board.chess960() && pt == PieceType::KING && board.at(target).type() == PieceType::ROOK + && board.at(target).color() == board.sideToMove()) + { return Move::make(source, target); } // convert to king captures rook // in chess960 the move should be sent as king captures rook already! - if (!board.chess960() && pt == PieceType::KING && Square::distance(target, source) == 2) { + if (!board.chess960() && pt == PieceType::KING && Square::distance(target, source) == 2) + { target = Square(target > source ? File::FILE_H : File::FILE_A, source.rank()); return Move::make(source, target); } // en passant - if (pt == PieceType::PAWN && target == board.enpassantSq()) { + if (pt == PieceType::PAWN && target == board.enpassantSq()) + { return Move::make(source, target); } // promotion - if (pt == PieceType::PAWN && uci.length() == 5 && Square::back_rank(target, ~board.sideToMove())) { + if (pt == PieceType::PAWN && uci.length() == 5 + && Square::back_rank(target, ~board.sideToMove())) + { auto promotion = PieceType(uci.substr(4, 1)); - if (promotion == PieceType::NONE || promotion == PieceType::KING || promotion == PieceType::PAWN) { + if (promotion == PieceType::NONE || promotion == PieceType::KING + || promotion == PieceType::PAWN) + { return Move::NO_MOVE; } @@ -4746,7 +5357,8 @@ class uci { * @param move * @return */ - [[nodiscard]] static std::string moveToSan(const Board &board, const Move &move) noexcept(false) { + [[nodiscard]] static std::string moveToSan(const Board& board, + const Move& move) noexcept(false) { std::string san; moveToRep(board, move, san); return san; @@ -4758,35 +5370,40 @@ class uci { * @param move * @return */ - [[nodiscard]] static std::string moveToLan(const Board &board, const Move &move) noexcept(false) { + [[nodiscard]] static std::string moveToLan(const Board& board, + const Move& move) noexcept(false) { std::string lan; moveToRep(board, move, lan); return lan; } - class SanParseError : public std::exception { + class SanParseError: public std::exception { public: - explicit SanParseError(const char *message) : msg_(message) {} + explicit SanParseError(const char* message) : + msg_(message) {} - explicit SanParseError(const std::string &message) : msg_(message) {} + explicit SanParseError(const std::string& message) : + msg_(message) {} virtual ~SanParseError() noexcept {} - virtual const char *what() const noexcept { return msg_.c_str(); } + virtual const char* what() const noexcept { return msg_.c_str(); } protected: std::string msg_; }; - class AmbiguousMoveError : public std::exception { + class AmbiguousMoveError: public std::exception { public: - explicit AmbiguousMoveError(const char *message) : msg_(message) {} + explicit AmbiguousMoveError(const char* message) : + msg_(message) {} - explicit AmbiguousMoveError(const std::string &message) : msg_(message) {} + explicit AmbiguousMoveError(const std::string& message) : + msg_(message) {} virtual ~AmbiguousMoveError() noexcept {} - virtual const char *what() const noexcept { return msg_.c_str(); } + virtual const char* what() const noexcept { return msg_.c_str(); } protected: std::string msg_; @@ -4799,7 +5416,7 @@ class uci { * @param san * @return */ - [[nodiscard]] static Move parseSan(const Board &board, std::string_view san) noexcept(false) { + [[nodiscard]] static Move parseSan(const Board& board, std::string_view san) noexcept(false) { Movelist moves; return parseSan(board, san, moves); @@ -4813,76 +5430,98 @@ class uci { * @param moves * @return */ - [[nodiscard]] static Move parseSan(const Board &board, std::string_view san, Movelist &moves) noexcept(false) { - if (san.empty()) { + [[nodiscard]] static Move + parseSan(const Board& board, std::string_view san, Movelist& moves) noexcept(false) { + if (san.empty()) + { return Move::NO_MOVE; } - static constexpr auto pt_to_pgt = [](PieceType pt) { return 1 << (pt); }; - const SanMoveInformation info = parseSanInfo(san); + static constexpr auto pt_to_pgt = [](PieceType pt) { return 1 << (pt); }; + const SanMoveInformation info = parseSanInfo(san); - if (info.capture) { + if (info.capture) + { movegen::legalmoves(moves, board, pt_to_pgt(info.piece)); - } else { + } + else + { movegen::legalmoves(moves, board, pt_to_pgt(info.piece)); } - if (info.castling_short || info.castling_long) { - for (const auto &move : moves) { - if (move.typeOf() == Move::CASTLING) { - if ((info.castling_short && move.to() > move.from()) || - (info.castling_long && move.to() < move.from())) { + if (info.castling_short || info.castling_long) + { + for (const auto& move : moves) + { + if (move.typeOf() == Move::CASTLING) + { + if ((info.castling_short && move.to() > move.from()) + || (info.castling_long && move.to() < move.from())) + { return move; } } } -#ifndef CHESS_NO_EXCEPTIONS - throw SanParseError("Failed to parse san. At step 2: " + std::string(san) + " " + board.getFen()); +#ifndef NDEBUG + throw SanParseError("Failed to parse san. At step 2: " + std::string(san) + " " + + board.getFen()); #endif } Move matchingMove = Move::NO_MOVE; bool foundMatch = false; - for (const auto &move : moves) { + for (const auto& move : moves) + { // Skip all moves that are not to the correct square // or are castling moves - if (move.to() != info.to || move.typeOf() == Move::CASTLING) { + if (move.to() != info.to || move.typeOf() == Move::CASTLING) + { continue; } // Handle promotion moves - if (info.promotion != PieceType::NONE) { - if (move.typeOf() != Move::PROMOTION || info.promotion != move.promotionType() || - move.from().file() != info.from_file) { + if (info.promotion != PieceType::NONE) + { + if (move.typeOf() != Move::PROMOTION || info.promotion != move.promotionType() + || move.from().file() != info.from_file) + { continue; } } // Handle en passant moves - else if (move.typeOf() == Move::ENPASSANT) { - if (move.from().file() != info.from_file) { + else if (move.typeOf() == Move::ENPASSANT) + { + if (move.from().file() != info.from_file) + { continue; } } // Handle moves with specific from square - else if (info.from != Square::NO_SQ) { - if (move.from() != info.from) { + else if (info.from != Square::NO_SQ) + { + if (move.from() != info.from) + { continue; } } // Handle moves with partial from information (rank or file) - else if (info.from_rank != Rank::NO_RANK || info.from_file != File::NO_FILE) { - if ((info.from_file != File::NO_FILE && move.from().file() != info.from_file) || - (info.from_rank != Rank::NO_RANK && move.from().rank() != info.from_rank)) { + else if (info.from_rank != Rank::NO_RANK || info.from_file != File::NO_FILE) + { + if ((info.from_file != File::NO_FILE && move.from().file() != info.from_file) + || (info.from_rank != Rank::NO_RANK && move.from().rank() != info.from_rank)) + { continue; } } // If we get here, the move matches our criteria - if (foundMatch) { -#ifndef CHESS_NO_EXCEPTIONS - throw AmbiguousMoveError("Ambiguous san: " + std::string(san) + " in " + board.getFen()); + if (foundMatch) + { +#ifndef NDEBUG + throw AmbiguousMoveError("Ambiguous san: " + std::string(san) + " in " + + board.getFen()); #endif } @@ -4890,9 +5529,11 @@ class uci { foundMatch = true; } - if (!foundMatch) { -#ifndef CHESS_NO_EXCEPTIONS - throw SanParseError("Failed to parse san. At step 3: " + std::string(san) + " " + board.getFen()); + if (!foundMatch) + { +#ifndef NDEBUG + throw SanParseError("Failed to parse san, illegal move: " + std::string(san) + " " + + board.getFen()); #endif } @@ -4904,23 +5545,28 @@ class uci { * @param move * @return */ - static bool isUciMove(const std::string &move) noexcept { + static bool isUciMove(const std::string& move) noexcept { bool is_uci = false; static constexpr auto is_digit = [](char c) { return c >= '1' && c <= '8'; }; static constexpr auto is_file = [](char c) { return c >= 'a' && c <= 'h'; }; - static constexpr auto is_promotion = [](char c) { return c == 'n' || c == 'b' || c == 'r' || c == 'q'; }; + static constexpr auto is_promotion = [](char c) { + return c == 'n' || c == 'b' || c == 'r' || c == 'q'; + }; // assert that the move is in uci format, [abcdefgh][1-8][abcdefgh][1-8][nbrq] - if (move.size() >= 4) { + if (move.size() >= 4) + { is_uci = is_file(move[0]) && is_digit(move[1]) && is_file(move[2]) && is_digit(move[3]); } - if (move.size() == 5) { + if (move.size() == 5) + { is_uci = is_uci && is_promotion(move[4]); } - if (move.size() > 5) { + if (move.size() > 5) + { return false; } @@ -4948,12 +5594,14 @@ class uci { }; [[nodiscard]] static SanMoveInformation parseSanInfo(std::string_view san) noexcept(false) { -#ifndef CHESS_NO_EXCEPTIONS - if (san.length() < 2) { +#ifndef NDEBUG + if (san.length() < 2) + { throw SanParseError("Failed to parse san. At step 0: " + std::string(san)); } #endif - constexpr auto parse_castle = [](std::string_view &san, SanMoveInformation &info, char castling_char) { + constexpr auto parse_castle = [](std::string_view& san, SanMoveInformation& info, + char castling_char) { info.piece = PieceType::KING; san.remove_prefix(3); @@ -4961,79 +5609,95 @@ class uci { info.castling_short = san.length() == 0 || (san.length() >= 1 && san[0] != '-'); info.castling_long = san.length() >= 2 && san[0] == '-' && san[1] == castling_char; - assert((info.castling_short && !info.castling_long) || (!info.castling_short && info.castling_long) || - (!info.castling_short && !info.castling_long)); + assert((info.castling_short && !info.castling_long) + || (!info.castling_short && info.castling_long) + || (!info.castling_short && !info.castling_long)); }; static constexpr auto isRank = [](char c) { return c >= '1' && c <= '8'; }; static constexpr auto isFile = [](char c) { return c >= 'a' && c <= 'h'; }; - static constexpr auto sw = [](const char &c) { return std::string_view(&c, 1); }; + static constexpr auto sw = [](const char& c) { return std::string_view(&c, 1); }; SanMoveInformation info; + bool throw_error = false; // set to 1 to skip piece type offset std::size_t index = 1; - if (san[0] == 'O' || san[0] == '0') { + if (san[0] == 'O' || san[0] == '0') + { parse_castle(san, info, san[0]); return info; - } else if (isFile(san[0])) { + } + else if (isFile(san[0])) + { index--; info.piece = PieceType::PAWN; - } else { + } + else + { info.piece = PieceType(san); + if (info.piece == PieceType::NONE) + { + throw_error = true; + } } File file_to = File::NO_FILE; Rank rank_to = Rank::NO_RANK; // check if san starts with a file, if so it will be start file - if (index < san.size() && isFile(san[index])) { + if (index < san.size() && isFile(san[index])) + { info.from_file = File(sw(san[index])); index++; } // check if san starts with a rank, if so it will be start rank - if (index < san.size() && isRank(san[index])) { + if (index < san.size() && isRank(san[index])) + { info.from_rank = Rank(sw(san[index])); index++; } // skip capture sign - if (index < san.size() && san[index] == 'x') { + if (index < san.size() && san[index] == 'x') + { info.capture = true; index++; } // to file - if (index < san.size() && isFile(san[index])) { + if (index < san.size() && isFile(san[index])) + { file_to = File(sw(san[index])); index++; } // to rank - if (index < san.size() && isRank(san[index])) { + if (index < san.size() && isRank(san[index])) + { rank_to = Rank(sw(san[index])); index++; } // promotion - if (index < san.size() && san[index] == '=') { + if (index < san.size() && san[index] == '=') + { index++; info.promotion = PieceType(sw(san[index])); - -#ifndef CHESS_NO_EXCEPTIONS - if (info.promotion == PieceType::KING || info.promotion == PieceType::PAWN || - info.promotion == PieceType::NONE) - throw SanParseError("Failed to parse promotion, during san conversion." + std::string(san)); -#endif - + if (info.promotion == PieceType::KING || info.promotion == PieceType::PAWN + || info.promotion == PieceType::NONE) + { + throw_error = true; + } index++; } // for simple moves like Nf3, e4, etc. all the information is contained // in the from file and rank. Thus we need to move it to the to file and rank. - if (file_to == File::NO_FILE && rank_to == Rank::NO_RANK) { + if (file_to == File::NO_FILE && rank_to == Rank::NO_RANK) + { file_to = info.from_file; rank_to = info.from_rank; @@ -5042,86 +5706,117 @@ class uci { } // pawns which are not capturing stay on the same file - if (info.piece == PieceType::PAWN && info.from_file == File::NO_FILE && !info.capture) { + if (info.piece == PieceType::PAWN && info.from_file == File::NO_FILE && !info.capture) + { info.from_file = file_to; } - info.to = Square(file_to, rank_to); + if (file_to != File::NO_FILE && rank_to != Rank::NO_RANK) + { + info.to = Square(file_to, rank_to); + } + else + { + throw_error = true; + } + +#ifndef NDEBUG + if (throw_error) + { + throw SanParseError("Failed to parse san. At step 1: " + std::string(san)); + } +#endif - if (info.from_file != File::NO_FILE && info.from_rank != Rank::NO_RANK) { + if (info.from_file != File::NO_FILE && info.from_rank != Rank::NO_RANK) + { info.from = Square(info.from_file, info.from_rank); } return info; } - template - static void moveToRep(Board board, const Move &move, std::string &str) { - if (handleCastling(move, str)) { + template + static void moveToRep(Board board, const Move& move, std::string& str) { + if (handleCastling(move, str)) + { board.makeMove(move); - if (board.inCheck()) appendCheckSymbol(board, str); + if (board.inCheck()) + appendCheckSymbol(board, str); return; } - const PieceType pt = board.at(move.from()).type(); - const bool isCapture = board.at(move.to()) != Piece::NONE || move.typeOf() == Move::ENPASSANT; + const PieceType pt = board.at(move.from()).type(); + const bool isCapture = + board.at(move.to()) != Piece::NONE || move.typeOf() == Move::ENPASSANT; assert(pt != PieceType::NONE); - if (pt != PieceType::PAWN) { + if (pt != PieceType::PAWN) + { appendPieceSymbol(pt, str); } - if constexpr (LAN) { + if constexpr (LAN) + { appendSquare(move.from(), str); - } else { - if (pt == PieceType::PAWN) { + } + else + { + if (pt == PieceType::PAWN) + { str += isCapture ? static_cast(move.from().file()) : ""; - } else { + } + else + { resolveAmbiguity(board, move, pt, str); } } - if (isCapture) { + if (isCapture) + { str += 'x'; } appendSquare(move.to(), str); - if (move.typeOf() == Move::PROMOTION) appendPromotion(move, str); + if (move.typeOf() == Move::PROMOTION) + appendPromotion(move, str); board.makeMove(move); - if (board.inCheck()) appendCheckSymbol(board, str); + if (board.inCheck()) + appendCheckSymbol(board, str); } - static bool handleCastling(const Move &move, std::string &str) { - if (move.typeOf() != Move::CASTLING) return false; + static bool handleCastling(const Move& move, std::string& str) { + if (move.typeOf() != Move::CASTLING) + return false; str = (move.to().file() > move.from().file()) ? "O-O" : "O-O-O"; return true; } - static void appendPieceSymbol(PieceType pieceType, std::string &str) { + static void appendPieceSymbol(PieceType pieceType, std::string& str) { str += std::toupper(static_cast(pieceType)[0]); } - static void appendSquare(Square square, std::string &str) { + static void appendSquare(Square square, std::string& str) { str += static_cast(square.file()); str += static_cast(square.rank()); } - static void appendPromotion(const Move &move, std::string &str) { + static void appendPromotion(const Move& move, std::string& str) { str += '='; str += std::toupper(static_cast(move.promotionType())[0]); } - static void appendCheckSymbol(Board &board, std::string &str) { + static void appendCheckSymbol(Board& board, std::string& str) { const auto gameState = board.isGameOver().second; str += (gameState == GameResult::LOSE) ? '#' : '+'; } - static void resolveAmbiguity(const Board &board, const Move &move, PieceType pieceType, std::string &str) { + static void + resolveAmbiguity(const Board& board, const Move& move, PieceType pieceType, std::string& str) { Movelist moves; movegen::legalmoves(moves, board, 1 << pieceType); @@ -5129,8 +5824,10 @@ class uci { bool needRank = false; bool hasAmbiguousMove = false; - for (const auto &m : moves) { - if (m != move && m.to() == move.to()) { + for (const auto& m : moves) + { + if (m != move && m.to() == move.to()) + { hasAmbiguousMove = true; /* @@ -5146,44 +5843,55 @@ class uci { letter. */ - if (isIdentifiableByType(moves, move, move.from().file())) { + if (isIdentifiableByType(moves, move, move.from().file())) + { needFile = true; break; } - if (isIdentifiableByType(moves, move, move.from().rank())) { + if (isIdentifiableByType(moves, move, move.from().rank())) + { needRank = true; break; } } } - if (needFile) str += static_cast(move.from().file()); - if (needRank) str += static_cast(move.from().rank()); + if (needFile) + str += static_cast(move.from().file()); + if (needRank) + str += static_cast(move.from().rank()); // we weren't able to disambiguate the move by either file or rank, so we need to use both - if (hasAmbiguousMove && !needFile && !needRank) { + if (hasAmbiguousMove && !needFile && !needRank) + { appendSquare(move.from(), str); } } - template - static bool isIdentifiableByType(const Movelist &moves, const Move move, CoordinateType type) { + template + static bool isIdentifiableByType(const Movelist& moves, const Move move, CoordinateType type) { static_assert(std::is_same_v || std::is_same_v, "CoordinateType must be either File or Rank"); - for (const auto &m : moves) { - if (m == move || m.to() != move.to()) { + for (const auto& m : moves) + { + if (m == move || m.to() != move.to()) + { continue; } // file - if constexpr (std::is_same_v) { - if (type == m.from().file()) return false; + if constexpr (std::is_same_v) + { + if (type == m.from().file()) + return false; } // rank - else { - if (type == m.from().rank()) return false; + else + { + if (type == m.from().rank()) + return false; } } diff --git a/src/evaluate.cpp b/src/evaluate.cpp new file mode 100644 index 0000000..42c76d2 --- /dev/null +++ b/src/evaluate.cpp @@ -0,0 +1,124 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "evaluate.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nnue/network.h" +#include "nnue/nnue_misc.h" +#include "position.h" +#include "types.h" +#include "uci.hpp" +#include "nnue/nnue_accumulator.h" + +namespace Stockfish { + +// Returns a static, purely materialistic evaluation of the position from +// the point of view of the side to move. It can be divided by PawnValue to get +// an approximation of the material advantage on the board in terms of pawns. +int Eval::simple_eval(const Position& pos) { + Color c = pos.side_to_move(); + return PawnValue * (pos.count(c) - pos.count(~c)) + + (pos.non_pawn_material(c) - pos.non_pawn_material(~c)); +} + +bool Eval::use_smallnet(const Position& pos) { return std::abs(simple_eval(pos)) > 962; } + +// Evaluate is the evaluator for the outer world. It returns a static evaluation +// of the position from the point of view of the side to move. +Value Eval::evaluate(const Eval::NNUE::Networks& networks, + const Position& pos, + Eval::NNUE::AccumulatorStack& accumulators, + Eval::NNUE::AccumulatorCaches& caches, + int optimism) { + + assert(!pos.b.inCheck()); + + bool smallNet = use_smallnet(pos); + auto [psqt, positional] = smallNet ? networks.small.evaluate(pos, accumulators, &caches.small) + : networks.big.evaluate(pos, accumulators, &caches.big); + + Value nnue = (125 * psqt + 131 * positional) / 128; + + // Re-evaluate the position when higher eval accuracy is worth the time spent + if (smallNet && (std::abs(nnue) < 236)) + { + std::tie(psqt, positional) = networks.big.evaluate(pos, accumulators, &caches.big); + nnue = (125 * psqt + 131 * positional) / 128; + smallNet = false; + } + + // Blend optimism and eval with nnue complexity + int nnueComplexity = std::abs(psqt - positional); + optimism += optimism * nnueComplexity / 468; + nnue -= nnue * nnueComplexity / 18000; + + int material = 535 * pos.count() + pos.non_pawn_material(); + int v = (nnue * (77777 + material) + optimism * (7777 + material)) / 77777; + + // Damp down the evaluation linearly when shuffling + v -= v * pos.rule50_count() / 212; + + // Guarantee evaluation does not hit the tablebase range + v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); + + return v; +} + +// Like evaluate(), but instead of returning a value, it returns +// a string (suitable for outputting to stdout) that contains the detailed +// descriptions and values of each evaluation term. Useful for debugging. +// Trace scores are from white's point of view +std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) { + + if (pos.b.inCheck()) + return "Final evaluation: none (in check)"; + + Eval::NNUE::AccumulatorStack accumulators; + auto caches = std::make_unique(networks); + + std::stringstream ss; + ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); + ss << '\n' << NNUE::trace(pos, networks, *caches) << '\n'; + + ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); + + auto [psqt, positional] = networks.big.evaluate(pos, accumulators, &caches->big); + Value v = psqt + positional; + v = pos.side_to_move() == WHITE ? v : -v; + ss << "NNUE evaluation " << 0.01 * to_cp(v, pos) << " (white side)\n"; + + v = evaluate(networks, pos, accumulators, *caches, VALUE_ZERO); + v = pos.side_to_move() == WHITE ? v : -v; + ss << "Final evaluation " << 0.01 * to_cp(v, pos) << " (white side)"; + ss << " [with scaled NNUE, ...]"; + ss << "\n"; + + return ss.str(); +} + +} // namespace Stockfish diff --git a/src/evaluate.h b/src/evaluate.h new file mode 100644 index 0000000..2a6c9af --- /dev/null +++ b/src/evaluate.h @@ -0,0 +1,58 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef EVALUATE_H_INCLUDED +#define EVALUATE_H_INCLUDED + +#include + +#include "types.h" + +namespace Stockfish { + +class Position; + +namespace Eval { + +// The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue +// for the build process (profile-build and fishtest) to work. Do not change the +// name of the macro or the location where this macro is defined, as it is used +// in the Makefile/Fishtest. +#define EvalFileDefaultNameBig "nn-1c0000000000.nnue" +#define EvalFileDefaultNameSmall "nn-37f18f62d772.nnue" + +namespace NNUE { +struct Networks; +struct AccumulatorCaches; +class AccumulatorStack; +} + +std::string trace(Position& pos, const Eval::NNUE::Networks& networks); + +int simple_eval(const Position& pos); +bool use_smallnet(const Position& pos); +Value evaluate(const NNUE::Networks& networks, + const Position& pos, + Eval::NNUE::AccumulatorStack& accumulators, + Eval::NNUE::AccumulatorCaches& caches, + int optimism); +} // namespace Eval + +} // namespace Stockfish + +#endif // #ifndef EVALUATE_H_INCLUDED diff --git a/src/incbin/UNLICENCE b/src/incbin/UNLICENCE new file mode 100644 index 0000000..32484ab --- /dev/null +++ b/src/incbin/UNLICENCE @@ -0,0 +1,26 @@ +The file "incbin.h" is free and unencumbered software released into +the public domain by Dale Weiler, see: + + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/src/incbin/incbin.h b/src/incbin/incbin.h new file mode 100644 index 0000000..3f662e1 --- /dev/null +++ b/src/incbin/incbin.h @@ -0,0 +1,476 @@ +/** + * @file incbin.h + * @author Dale Weiler + * @brief Utility for including binary files + * + * Facilities for including binary files into the current translation unit and + * making use from them externally in other translation units. + */ +#ifndef INCBIN_HDR +#define INCBIN_HDR +#include +#if defined(__AVX512BW__) || \ + defined(__AVX512CD__) || \ + defined(__AVX512DQ__) || \ + defined(__AVX512ER__) || \ + defined(__AVX512PF__) || \ + defined(__AVX512VL__) || \ + defined(__AVX512F__) +# define INCBIN_ALIGNMENT_INDEX 6 +#elif defined(__AVX__) || \ + defined(__AVX2__) +# define INCBIN_ALIGNMENT_INDEX 5 +#elif defined(__SSE__) || \ + defined(__SSE2__) || \ + defined(__SSE3__) || \ + defined(__SSSE3__) || \ + defined(__SSE4_1__) || \ + defined(__SSE4_2__) || \ + defined(__neon__) || \ + defined(__ARM_NEON) || \ + defined(__ALTIVEC__) +# define INCBIN_ALIGNMENT_INDEX 4 +#elif ULONG_MAX != 0xffffffffu +# define INCBIN_ALIGNMENT_INDEX 3 +# else +# define INCBIN_ALIGNMENT_INDEX 2 +#endif + +/* Lookup table of (1 << n) where `n' is `INCBIN_ALIGNMENT_INDEX' */ +#define INCBIN_ALIGN_SHIFT_0 1 +#define INCBIN_ALIGN_SHIFT_1 2 +#define INCBIN_ALIGN_SHIFT_2 4 +#define INCBIN_ALIGN_SHIFT_3 8 +#define INCBIN_ALIGN_SHIFT_4 16 +#define INCBIN_ALIGN_SHIFT_5 32 +#define INCBIN_ALIGN_SHIFT_6 64 + +/* Actual alignment value */ +#define INCBIN_ALIGNMENT \ + INCBIN_CONCATENATE( \ + INCBIN_CONCATENATE(INCBIN_ALIGN_SHIFT, _), \ + INCBIN_ALIGNMENT_INDEX) + +/* Stringize */ +#define INCBIN_STR(X) \ + #X +#define INCBIN_STRINGIZE(X) \ + INCBIN_STR(X) +/* Concatenate */ +#define INCBIN_CAT(X, Y) \ + X ## Y +#define INCBIN_CONCATENATE(X, Y) \ + INCBIN_CAT(X, Y) +/* Deferred macro expansion */ +#define INCBIN_EVAL(X) \ + X +#define INCBIN_INVOKE(N, ...) \ + INCBIN_EVAL(N(__VA_ARGS__)) +/* Variable argument count for overloading by arity */ +#define INCBIN_VA_ARG_COUNTER(_1, _2, _3, N, ...) N +#define INCBIN_VA_ARGC(...) INCBIN_VA_ARG_COUNTER(__VA_ARGS__, 3, 2, 1, 0) + +/* Green Hills uses a different directive for including binary data */ +#if defined(__ghs__) +# if (__ghs_asm == 2) +# define INCBIN_MACRO ".file" +/* Or consider the ".myrawdata" entry in the ld file */ +# else +# define INCBIN_MACRO "\tINCBIN" +# endif +#else +# define INCBIN_MACRO ".incbin" +#endif + +#ifndef _MSC_VER +# define INCBIN_ALIGN \ + __attribute__((aligned(INCBIN_ALIGNMENT))) +#else +# define INCBIN_ALIGN __declspec(align(INCBIN_ALIGNMENT)) +#endif + +#if defined(__arm__) || /* GNU C and RealView */ \ + defined(__arm) || /* Diab */ \ + defined(_ARM) /* ImageCraft */ +# define INCBIN_ARM +#endif + +#ifdef __GNUC__ +/* Utilize .balign where supported */ +# define INCBIN_ALIGN_HOST ".balign " INCBIN_STRINGIZE(INCBIN_ALIGNMENT) "\n" +# define INCBIN_ALIGN_BYTE ".balign 1\n" +#elif defined(INCBIN_ARM) +/* + * On arm assemblers, the alignment value is calculated as (1 << n) where `n' is + * the shift count. This is the value passed to `.align' + */ +# define INCBIN_ALIGN_HOST ".align " INCBIN_STRINGIZE(INCBIN_ALIGNMENT_INDEX) "\n" +# define INCBIN_ALIGN_BYTE ".align 0\n" +#else +/* We assume other inline assembler's treat `.align' as `.balign' */ +# define INCBIN_ALIGN_HOST ".align " INCBIN_STRINGIZE(INCBIN_ALIGNMENT) "\n" +# define INCBIN_ALIGN_BYTE ".align 1\n" +#endif + +/* INCBIN_CONST is used by incbin.c generated files */ +#if defined(__cplusplus) +# define INCBIN_EXTERNAL extern "C" +# define INCBIN_CONST extern const +#else +# define INCBIN_EXTERNAL extern +# define INCBIN_CONST const +#endif + +/** + * @brief Optionally override the linker section into which size and data is + * emitted. + * + * @warning If you use this facility, you might have to deal with + * platform-specific linker output section naming on your own. + */ +#if !defined(INCBIN_OUTPUT_SECTION) +# if defined(__APPLE__) +# define INCBIN_OUTPUT_SECTION ".const_data" +# else +# define INCBIN_OUTPUT_SECTION ".rodata" +# endif +#endif + +/** + * @brief Optionally override the linker section into which data is emitted. + * + * @warning If you use this facility, you might have to deal with + * platform-specific linker output section naming on your own. + */ +#if !defined(INCBIN_OUTPUT_DATA_SECTION) +# define INCBIN_OUTPUT_DATA_SECTION INCBIN_OUTPUT_SECTION +#endif + +/** + * @brief Optionally override the linker section into which size is emitted. + * + * @warning If you use this facility, you might have to deal with + * platform-specific linker output section naming on your own. + * + * @note This is useful for Harvard architectures where program memory cannot + * be directly read from the program without special instructions. With this you + * can chose to put the size variable in RAM rather than ROM. + */ +#if !defined(INCBIN_OUTPUT_SIZE_SECTION) +# define INCBIN_OUTPUT_SIZE_SECTION INCBIN_OUTPUT_SECTION +#endif + +#if defined(__APPLE__) +# include "TargetConditionals.h" +# if defined(TARGET_OS_IPHONE) && !defined(INCBIN_SILENCE_BITCODE_WARNING) +# warning "incbin is incompatible with bitcode. Using the library will break upload to App Store if you have bitcode enabled. Add `#define INCBIN_SILENCE_BITCODE_WARNING` before including this header to silence this warning." +# endif +/* The directives are different for Apple branded compilers */ +# define INCBIN_SECTION INCBIN_OUTPUT_SECTION "\n" +# define INCBIN_GLOBAL(NAME) ".globl " INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME "\n" +# define INCBIN_INT ".long " +# define INCBIN_MANGLE "_" +# define INCBIN_BYTE ".byte " +# define INCBIN_TYPE(...) +#else +# define INCBIN_SECTION ".section " INCBIN_OUTPUT_SECTION "\n" +# define INCBIN_GLOBAL(NAME) ".global " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME "\n" +# if defined(__ghs__) +# define INCBIN_INT ".word " +# else +# define INCBIN_INT ".int " +# endif +# if defined(__USER_LABEL_PREFIX__) +# define INCBIN_MANGLE INCBIN_STRINGIZE(__USER_LABEL_PREFIX__) +# else +# define INCBIN_MANGLE "" +# endif +# if defined(INCBIN_ARM) +/* On arm assemblers, `@' is used as a line comment token */ +# define INCBIN_TYPE(NAME) ".type " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME ", %object\n" +# elif defined(__MINGW32__) || defined(__MINGW64__) +/* Mingw doesn't support this directive either */ +# define INCBIN_TYPE(NAME) +# else +/* It's safe to use `@' on other architectures */ +# define INCBIN_TYPE(NAME) ".type " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME ", @object\n" +# endif +# define INCBIN_BYTE ".byte " +#endif + +/* List of style types used for symbol names */ +#define INCBIN_STYLE_CAMEL 0 +#define INCBIN_STYLE_SNAKE 1 + +/** + * @brief Specify the prefix to use for symbol names. + * + * @note By default this is "g". + * + * @code + * #define INCBIN_PREFIX incbin + * #include "incbin.h" + * INCBIN(Foo, "foo.txt"); + * + * // Now you have the following symbols instead: + * // const unsigned char incbinFoo[]; + * // const unsigned char *const incbinFoo; + * // const unsigned int incbinFoo; + * @endcode + */ +#if !defined(INCBIN_PREFIX) +# define INCBIN_PREFIX g +#endif + +/** + * @brief Specify the style used for symbol names. + * + * Possible options are + * - INCBIN_STYLE_CAMEL "CamelCase" + * - INCBIN_STYLE_SNAKE "snake_case" + * + * @note By default this is INCBIN_STYLE_CAMEL + * + * @code + * #define INCBIN_STYLE INCBIN_STYLE_SNAKE + * #include "incbin.h" + * INCBIN(foo, "foo.txt"); + * + * // Now you have the following symbols: + * // const unsigned char foo_data[]; + * // const unsigned char *const foo_end; + * // const unsigned int foo_size; + * @endcode + */ +#if !defined(INCBIN_STYLE) +# define INCBIN_STYLE INCBIN_STYLE_CAMEL +#endif + +/* Style lookup tables */ +#define INCBIN_STYLE_0_DATA Data +#define INCBIN_STYLE_0_END End +#define INCBIN_STYLE_0_SIZE Size +#define INCBIN_STYLE_1_DATA _data +#define INCBIN_STYLE_1_END _end +#define INCBIN_STYLE_1_SIZE _size + +/* Style lookup: returning identifier */ +#define INCBIN_STYLE_IDENT(TYPE) \ + INCBIN_CONCATENATE( \ + INCBIN_STYLE_, \ + INCBIN_CONCATENATE( \ + INCBIN_EVAL(INCBIN_STYLE), \ + INCBIN_CONCATENATE(_, TYPE))) + +/* Style lookup: returning string literal */ +#define INCBIN_STYLE_STRING(TYPE) \ + INCBIN_STRINGIZE( \ + INCBIN_STYLE_IDENT(TYPE)) \ + +/* Generate the global labels by indirectly invoking the macro with our style + * type and concatenating the name against them. */ +#define INCBIN_GLOBAL_LABELS(NAME, TYPE) \ + INCBIN_INVOKE( \ + INCBIN_GLOBAL, \ + INCBIN_CONCATENATE( \ + NAME, \ + INCBIN_INVOKE( \ + INCBIN_STYLE_IDENT, \ + TYPE))) \ + INCBIN_INVOKE( \ + INCBIN_TYPE, \ + INCBIN_CONCATENATE( \ + NAME, \ + INCBIN_INVOKE( \ + INCBIN_STYLE_IDENT, \ + TYPE))) + +/** + * @brief Externally reference binary data included in another translation unit. + * + * Produces three external symbols that reference the binary data included in + * another translation unit. + * + * The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with + * "Data", as well as "End" and "Size" after. An example is provided below. + * + * @param TYPE Optional array type. Omitting this picks a default of `unsigned char`. + * @param NAME The name given for the binary data + * + * @code + * INCBIN_EXTERN(Foo); + * + * // Now you have the following symbols: + * // extern const unsigned char Foo[]; + * // extern const unsigned char *const Foo; + * // extern const unsigned int Foo; + * @endcode + * + * You may specify a custom optional data type as well as the first argument. + * @code + * INCBIN_EXTERN(custom_type, Foo); + * + * // Now you have the following symbols: + * // extern const custom_type Foo[]; + * // extern const custom_type *const Foo; + * // extern const unsigned int Foo; + * @endcode + */ +#define INCBIN_EXTERN(...) \ + INCBIN_CONCATENATE(INCBIN_EXTERN_, INCBIN_VA_ARGC(__VA_ARGS__))(__VA_ARGS__) +#define INCBIN_EXTERN_1(NAME, ...) \ + INCBIN_EXTERN_2(unsigned char, NAME) +#define INCBIN_EXTERN_2(TYPE, NAME) \ + INCBIN_EXTERNAL const INCBIN_ALIGN TYPE \ + INCBIN_CONCATENATE( \ + INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \ + INCBIN_STYLE_IDENT(DATA))[]; \ + INCBIN_EXTERNAL const INCBIN_ALIGN TYPE *const \ + INCBIN_CONCATENATE( \ + INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \ + INCBIN_STYLE_IDENT(END)); \ + INCBIN_EXTERNAL const unsigned int \ + INCBIN_CONCATENATE( \ + INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \ + INCBIN_STYLE_IDENT(SIZE)) + +/** + * @brief Externally reference textual data included in another translation unit. + * + * Produces three external symbols that reference the textual data included in + * another translation unit. + * + * The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with + * "Data", as well as "End" and "Size" after. An example is provided below. + * + * @param NAME The name given for the textual data + * + * @code + * INCBIN_EXTERN(Foo); + * + * // Now you have the following symbols: + * // extern const char Foo[]; + * // extern const char *const Foo; + * // extern const unsigned int Foo; + * @endcode + */ +#define INCTXT_EXTERN(NAME) \ + INCBIN_EXTERN_2(char, NAME) + +/** + * @brief Include a binary file into the current translation unit. + * + * Includes a binary file into the current translation unit, producing three symbols + * for objects that encode the data and size respectively. + * + * The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with + * "Data", as well as "End" and "Size" after. An example is provided below. + * + * @param TYPE Optional array type. Omitting this picks a default of `unsigned char`. + * @param NAME The name to associate with this binary data (as an identifier.) + * @param FILENAME The file to include (as a string literal.) + * + * @code + * INCBIN(Icon, "icon.png"); + * + * // Now you have the following symbols: + * // const unsigned char Icon[]; + * // const unsigned char *const Icon; + * // const unsigned int Icon; + * @endcode + * + * You may specify a custom optional data type as well as the first argument. + * These macros are specialized by arity. + * @code + * INCBIN(custom_type, Icon, "icon.png"); + * + * // Now you have the following symbols: + * // const custom_type Icon[]; + * // const custom_type *const Icon; + * // const unsigned int Icon; + * @endcode + * + * @warning This must be used in global scope + * @warning The identifiers may be different if INCBIN_STYLE is not default + * + * To externally reference the data included by this in another translation unit + * please @see INCBIN_EXTERN. + */ +#ifdef _MSC_VER +# define INCBIN(NAME, FILENAME) \ + INCBIN_EXTERN(NAME) +#else +# define INCBIN(...) \ + INCBIN_CONCATENATE(INCBIN_, INCBIN_VA_ARGC(__VA_ARGS__))(__VA_ARGS__) +# if defined(__GNUC__) +# define INCBIN_1(...) _Pragma("GCC error \"Single argument INCBIN not allowed\"") +# elif defined(__clang__) +# define INCBIN_1(...) _Pragma("clang error \"Single argument INCBIN not allowed\"") +# else +# define INCBIN_1(...) /* Cannot do anything here */ +# endif +# define INCBIN_2(NAME, FILENAME) \ + INCBIN_3(unsigned char, NAME, FILENAME) +# define INCBIN_3(TYPE, NAME, FILENAME) INCBIN_COMMON(TYPE, NAME, FILENAME, /* No terminator for binary data */) +# define INCBIN_COMMON(TYPE, NAME, FILENAME, TERMINATOR) \ + __asm__(INCBIN_SECTION \ + INCBIN_GLOBAL_LABELS(NAME, DATA) \ + INCBIN_ALIGN_HOST \ + INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(DATA) ":\n" \ + INCBIN_MACRO " \"" FILENAME "\"\n" \ + TERMINATOR \ + INCBIN_GLOBAL_LABELS(NAME, END) \ + INCBIN_ALIGN_BYTE \ + INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(END) ":\n" \ + INCBIN_BYTE "1\n" \ + INCBIN_GLOBAL_LABELS(NAME, SIZE) \ + INCBIN_ALIGN_HOST \ + INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(SIZE) ":\n" \ + INCBIN_INT INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(END) " - " \ + INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(DATA) "\n" \ + INCBIN_ALIGN_HOST \ + ".text\n" \ + ); \ + INCBIN_EXTERN(TYPE, NAME) +#endif + +/** + * @brief Include a textual file into the current translation unit. + * + * This behaves the same as INCBIN except it produces char compatible arrays + * and implicitly adds a null-terminator byte, thus the size of data included + * by this is one byte larger than that of INCBIN. + * + * Includes a textual file into the current translation unit, producing three + * symbols for objects that encode the data and size respectively. + * + * The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with + * "Data", as well as "End" and "Size" after. An example is provided below. + * + * @param NAME The name to associate with this binary data (as an identifier.) + * @param FILENAME The file to include (as a string literal.) + * + * @code + * INCTXT(Readme, "readme.txt"); + * + * // Now you have the following symbols: + * // const char Readme[]; + * // const char *const Readme; + * // const unsigned int Readme; + * @endcode + * + * @warning This must be used in global scope + * @warning The identifiers may be different if INCBIN_STYLE is not default + * + * To externally reference the data included by this in another translation unit + * please @see INCBIN_EXTERN. + */ +#if defined(_MSC_VER) +# define INCTXT(NAME, FILENAME) \ + INCBIN_EXTERN(NAME) +#else +# define INCTXT(NAME, FILENAME) \ + INCBIN_COMMON(char, NAME, FILENAME, INCBIN_BYTE "0\n") +#endif + +#endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..304f831 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,28 @@ +#include +#include "search.h" +#include "uci.hpp" +#include "ucioptions.hpp" + +int main(int argc, char** argv) { + UCIOptions::addSpin("Hash", 16, 1, 1048576, [](const UCIOptions::Option& opt) { + search::tt.resize(std::get(opt.value)); + }); + UCIOptions::setOption("Hash", "16"); + search::init(); + UCIOptions::addString("NNUEEvalFileBig", EvalFileDefaultNameBig, + [](const UCIOptions::Option& opt) { + search::nn->big.load(".", std::get(opt.value)); + }); + UCIOptions::addString("NNUEEvalFileSmall", EvalFileDefaultNameSmall, + [](const UCIOptions::Option& opt) { + search::nn->small.load(".", std::get(opt.value)); + }); + search::init(); + if (argc == 2 && !strcmp(argv[1], "bench")) // SF bench (Makefile) + { + handle_bench(); + return 0; + } + uci_loop(); + return 0; +} diff --git a/src/memory.cpp b/src/memory.cpp new file mode 100644 index 0000000..d4ccd8d --- /dev/null +++ b/src/memory.cpp @@ -0,0 +1,266 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "memory.h" + +#include + +#if __has_include("features.h") + #include +#endif + +#if defined(__linux__) && !defined(__ANDROID__) + #include +#endif + +#if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__) \ + || (defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC) && !defined(_WIN32)) \ + || defined(__e2k__) + #define POSIXALIGNEDALLOC + #include +#endif + +#ifdef _WIN32 + #if _WIN32_WINNT < 0x0601 + #undef _WIN32_WINNT + #define _WIN32_WINNT 0x0601 // Force to include needed API prototypes + #endif + + #ifndef NOMINMAX + #define NOMINMAX + #endif + + #include // std::hex, std::dec + #include // std::cerr + #include // std::endl + #include + +// The needed Windows API for processor groups could be missed from old Windows +// versions, so instead of calling them directly (forcing the linker to resolve +// the calls at compile time), try to load them at runtime. To do this we need +// first to define the corresponding function pointers. + +extern "C" { +using OpenProcessToken_t = bool (*)(HANDLE, DWORD, PHANDLE); +using LookupPrivilegeValueA_t = bool (*)(LPCSTR, LPCSTR, PLUID); +using AdjustTokenPrivileges_t = + bool (*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, PDWORD); +} +#endif + +namespace NNUEParser { + +// Wrappers for systems where the c++17 implementation does not guarantee the +// availability of aligned_alloc(). Memory allocated with std_aligned_alloc() +// must be freed with std_aligned_free(). + +void* std_aligned_alloc(size_t alignment, size_t size) { +#if defined(_ISOC11_SOURCE) + return aligned_alloc(alignment, size); // C11 aligned_alloc, must free with free() +#elif defined(POSIXALIGNEDALLOC) + void* mem = nullptr; + return posix_memalign(&mem, alignment, size) == 0 ? mem + : nullptr; // POSIX, must free with free() +#elif defined(_WIN32) && !defined(_M_ARM) && !defined(_M_ARM64) + return _mm_malloc(size, alignment); // Intel intrinsics on Windows, must free with _mm_free() +#elif defined(_WIN32) + return _aligned_malloc(size, alignment); // MSVC aligned alloc, must free with _aligned_free() +#else + return std::aligned_alloc(alignment, size); // C++17 stdlib +#endif +} + +void std_aligned_free(void* ptr) { + +#if defined(POSIXALIGNEDALLOC) + free(ptr); +#elif defined(_WIN32) && !defined(_M_ARM) && !defined(_M_ARM64) + _mm_free(ptr); +#elif defined(_WIN32) + _aligned_free(ptr); +#else + free(ptr); +#endif +} + +// aligned_large_pages_alloc() will return suitably aligned memory, +// if possible using large pages. + +#if defined(_WIN32) + +static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize) { + + #if !defined(_WIN64) + return nullptr; + #else + + HANDLE hProcessToken{}; + LUID luid{}; + void* mem = nullptr; + + const size_t largePageSize = GetLargePageMinimum(); + if (!largePageSize) + return nullptr; + + // Dynamically link OpenProcessToken, LookupPrivilegeValue and + // AdjustTokenPrivileges + + HMODULE hAdvapi32 = GetModuleHandle(TEXT("advapi32.dll")); + + if (!hAdvapi32) + hAdvapi32 = LoadLibrary(TEXT("advapi32.dll")); + + auto OpenProcessToken_f = auto OpenProcessToken_f = + reinterpret_cast(GetProcAddress(hAdvapi32, "OpenProcessToken")); + if (!OpenProcessToken_f) + return nullptr; + auto LookupPrivilegeValueA_f = + reinterpret_cast(GetProcAddress(hAdvapi32, "LookupPrivilegeValueA")); + if (!LookupPrivilegeValueA_f) + return nullptr; + auto AdjustTokenPrivileges_f = + reinterpret_cast(GetProcAddress(hAdvapi32, "AdjustTokenPrivileges")); + if (!AdjustTokenPrivileges_f) + return nullptr; + // We need SeLockMemoryPrivilege, so try to enable it for the process + + if (!OpenProcessToken_f( // OpenProcessToken() + GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken)) + return nullptr; + + if (LookupPrivilegeValueA_f(nullptr, "SeLockMemoryPrivilege", &luid)) + { + TOKEN_PRIVILEGES tp{}; + TOKEN_PRIVILEGES prevTp{}; + DWORD prevTpLen = 0; + + tp.PrivilegeCount = 1; + tp.Privileges[0].Luid = luid; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + // Try to enable SeLockMemoryPrivilege. Note that even if + // AdjustTokenPrivileges() succeeds, we still need to query GetLastError() + // to ensure that the privileges were actually obtained. + + if (AdjustTokenPrivileges_f(hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, + &prevTpLen) + && GetLastError() == ERROR_SUCCESS) + { + // Round up size to full pages and allocate + allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1); + mem = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, + PAGE_READWRITE); + + // Privilege no longer needed, restore previous state + AdjustTokenPrivileges_f(hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr); + } + } + + CloseHandle(hProcessToken); + + return mem; + + #endif +} + +void* aligned_large_pages_alloc(size_t allocSize) { + + // Try to allocate large pages + void* mem = aligned_large_pages_alloc_windows(allocSize); + + // Fall back to regular, page-aligned, allocation if necessary + if (!mem) + mem = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + + return mem; +} + +#else + +void* aligned_large_pages_alloc(size_t allocSize) { + + #if defined(__linux__) + constexpr size_t alignment = 2 * 1024 * 1024; // 2MB page size assumed + #else + constexpr size_t alignment = 4096; // small page size assumed + #endif + + // Round up to multiples of alignment + size_t size = ((allocSize + alignment - 1) / alignment) * alignment; + void* mem = std_aligned_alloc(alignment, size); + #if defined(MADV_HUGEPAGE) + madvise(mem, size, MADV_HUGEPAGE); + #endif + return mem; +} + +#endif + +bool has_large_pages() { + +#if defined(_WIN32) + + constexpr size_t page_size = 2 * 1024 * 1024; // 2MB page size assumed + void* mem = aligned_large_pages_alloc_windows(page_size); + if (mem == nullptr) + { + return false; + } + else + { + aligned_large_pages_free(mem); + return true; + } + +#elif defined(__linux__) + + #if defined(MADV_HUGEPAGE) + return true; + #else + return false; + #endif + +#else + + return false; + +#endif +} + +// aligned_large_pages_free() will free the previously memory allocated +// by aligned_large_pages_alloc(). The effect is a nop if mem == nullptr. + +#if defined(_WIN32) + +void aligned_large_pages_free(void* mem) { + + if (mem && !VirtualFree(mem, 0, MEM_RELEASE)) + { + DWORD err = GetLastError(); + std::cerr << "Failed to free large page memory. Error code: 0x" << std::hex << err + << std::dec << std::endl; + exit(EXIT_FAILURE); + } +} + +#else + +void aligned_large_pages_free(void* mem) { std_aligned_free(mem); } + +#endif +} // namespace NNUEParser \ No newline at end of file diff --git a/src/memory.h b/src/memory.h new file mode 100644 index 0000000..f90b368 --- /dev/null +++ b/src/memory.h @@ -0,0 +1,218 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef MEMORY_H_INCLUDED +#define MEMORY_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include +#define ASSERT_ALIGNED(ptr, alignment) assert(reinterpret_cast(ptr) % alignment == 0) + +namespace NNUEParser { + +void* std_aligned_alloc(size_t alignment, size_t size); +void std_aligned_free(void* ptr); + +// Memory aligned by page size, min alignment: 4096 bytes +void* aligned_large_pages_alloc(size_t size); +void aligned_large_pages_free(void* mem); + +bool has_large_pages(); + +// Frees memory which was placed there with placement new. +// Works for both single objects and arrays of unknown bound. +template +void memory_deleter(T* ptr, FREE_FUNC free_func) { + if (!ptr) + return; + + // Explicitly needed to call the destructor + if constexpr (!std::is_trivially_destructible_v) + ptr->~T(); + + free_func(ptr); +} + +// Frees memory which was placed there with placement new. +// Works for both single objects and arrays of unknown bound. +template +void memory_deleter_array(T* ptr, FREE_FUNC free_func) { + if (!ptr) + return; + + // Move back on the pointer to where the size is allocated + const size_t array_offset = std::max(sizeof(size_t), alignof(T)); + char* raw_memory = reinterpret_cast(ptr) - array_offset; + + if constexpr (!std::is_trivially_destructible_v) + { + const size_t size = *reinterpret_cast(raw_memory); + + // Explicitly call the destructor for each element in reverse order + for (size_t i = size; i-- > 0;) + ptr[i].~T(); + } + + free_func(raw_memory); +} + +// Allocates memory for a single object and places it there with placement new +template +inline std::enable_if_t, T*> memory_allocator(ALLOC_FUNC alloc_func, + Args&&... args) { + void* raw_memory = alloc_func(sizeof(T)); + ASSERT_ALIGNED(raw_memory, alignof(T)); + return new (raw_memory) T(std::forward(args)...); +} + +// Allocates memory for an array of unknown bound and places it there with +// placement new +template +inline std::enable_if_t, std::remove_extent_t*> +memory_allocator(ALLOC_FUNC alloc_func, size_t num) { + using ElementType = std::remove_extent_t; + + const size_t array_offset = std::max(sizeof(size_t), alignof(ElementType)); + + // Save the array size in the memory location + char* raw_memory = + reinterpret_cast(alloc_func(array_offset + num * sizeof(ElementType))); + ASSERT_ALIGNED(raw_memory, alignof(ElementType)); + + new (raw_memory) size_t(num); + + for (size_t i = 0; i < num; ++i) + new (raw_memory + array_offset + i * sizeof(ElementType)) ElementType(); + + // Need to return the pointer at the start of the array so that + // the indexing in unique_ptr works. + return reinterpret_cast(raw_memory + array_offset); +} + +// +// +// aligned large page unique ptr +// +// + +template +struct LargePageDeleter { + void operator()(T* ptr) const { return memory_deleter(ptr, aligned_large_pages_free); } +}; + +template +struct LargePageArrayDeleter { + void operator()(T* ptr) const { return memory_deleter_array(ptr, aligned_large_pages_free); } +}; + +template +using LargePagePtr = + std::conditional_t, + std::unique_ptr>>, + std::unique_ptr>>; + +// make_unique_large_page for single objects +template +std::enable_if_t, LargePagePtr> make_unique_large_page(Args&&... args) { + static_assert(alignof(T) <= 4096, "aligned_large_pages_alloc() may fail for " + "such a big alignment requirement of T"); + + T* obj = memory_allocator(aligned_large_pages_alloc, std::forward(args)...); + + return LargePagePtr(obj); +} + +// make_unique_large_page for arrays of unknown bound +template +std::enable_if_t, LargePagePtr> make_unique_large_page(size_t num) { + using ElementType = std::remove_extent_t; + + static_assert(alignof(ElementType) <= 4096, + "aligned_large_pages_alloc() may fail for such a big alignment " + "requirement of T"); + + ElementType* memory = memory_allocator(aligned_large_pages_alloc, num); + + return LargePagePtr(memory); +} + +// +// +// aligned unique ptr +// +// + +template +struct AlignedDeleter { + void operator()(T* ptr) const { return memory_deleter(ptr, std_aligned_free); } +}; + +template +struct AlignedArrayDeleter { + void operator()(T* ptr) const { return memory_deleter_array(ptr, std_aligned_free); } +}; + +template +using AlignedPtr = + std::conditional_t, + std::unique_ptr>>, + std::unique_ptr>>; + +// make_unique_aligned for single objects +template +std::enable_if_t, AlignedPtr> make_unique_aligned(Args&&... args) { + const auto func = [](size_t size) { return std_aligned_alloc(alignof(T), size); }; + T* obj = memory_allocator(func, std::forward(args)...); + + return AlignedPtr(obj); +} + +// make_unique_aligned for arrays of unknown bound +template +std::enable_if_t, AlignedPtr> make_unique_aligned(size_t num) { + using ElementType = std::remove_extent_t; + + const auto func = [](size_t size) { return std_aligned_alloc(alignof(ElementType), size); }; + ElementType* memory = memory_allocator(func, num); + + return AlignedPtr(memory); +} + +// Get the first aligned element of an array. +// ptr must point to an array of size at least `sizeof(T) * N + alignment` +// bytes, where N is the number of elements in the array. +template +T* align_ptr_up(T* ptr) { + static_assert(alignof(T) <= Alignment); + + const uintptr_t ptrint = reinterpret_cast(reinterpret_cast(ptr)); + return reinterpret_cast( + reinterpret_cast((ptrint + (Alignment - 1)) / Alignment * Alignment)); +} + +} // namespace NNUEParser +namespace Stockfish { +using namespace NNUEParser; +} +#endif // #ifndef MEMORY_H_INCLUDED diff --git a/src/misc.h b/src/misc.h new file mode 100644 index 0000000..6bcb293 --- /dev/null +++ b/src/misc.h @@ -0,0 +1,62 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef MISC_H_INCLUDED +#define MISC_H_INCLUDED + +namespace Stockfish { // misc.h, /IsLittleEndian +// True if and only if the binary is compiled on a little-endian machine +static inline const std::uint16_t Le = 1; +static inline const bool IsLittleEndian = *reinterpret_cast(&Le) == 1; +template +class ValueList { + + public: + std::size_t size() const { return size_; } + void push_back(const T& value) { values_[size_++] = value; } + const T* begin() const { return values_; } + const T* end() const { return values_ + size_; } + const T& operator[](int index) const { return values_[index]; } + + private: + T values_[MaxSize]; + std::size_t size_ = 0; +}; + +#if defined(__GNUC__) && !defined(__clang__) + #if __GNUC__ >= 13 + #define sf_assume(cond) __attribute__((assume(cond))) + #else + #define sf_assume(cond) \ + do \ + { \ + if (!(cond)) \ + __builtin_unreachable(); \ + } while (0) + #endif +#else + // do nothing for other compilers + #define sf_assume(cond) +#endif + +#define sync_cout std::cout +#define sync_endl std::endl + +} // namespace Stockfish + +#endif // #ifndef MISC_H_INCLUDED diff --git a/src/movepick.cpp b/src/movepick.cpp new file mode 100644 index 0000000..d5cda9c --- /dev/null +++ b/src/movepick.cpp @@ -0,0 +1,200 @@ +#include "movepick.hpp" + +namespace movepick { +inline int piece_value(chess::PieceType pt) { // fast and static check + switch (pt) + { + case (int) chess::PieceType::PAWN : + return Stockfish::PawnValue; + case (int) chess::PieceType::KNIGHT : + return Stockfish::KnightValue; + case (int) chess::PieceType::BISHOP : + return Stockfish::BishopValue; + case (int) chess::PieceType::ROOK : + return Stockfish::RookValue; + case (int) chess::PieceType::QUEEN : + return Stockfish::QueenValue; + default : + return 0; // includes king + } +} +// 1. SEE + +// Return type changed to chess::Square for consistency +static chess::Square select_least_valuable(chess::Board& board, chess::Bitboard bb) { + if (bb.empty()) + return chess::Square::NO_SQ; // Use NO_SQ sentinel for no square + + int least_value = 0x7fffffff; + chess::Square least_valuable_square = chess::Square::NO_SQ; + + while (bb) + { + int idx = bb.pop(); // idx is int index of bitboard square + chess::Square sq(idx); + int value = piece_value(board.at(sq)); + if (value < least_value) + { + least_value = value; + least_valuable_square = sq; + } + } + return least_valuable_square; +} + +int SEE(chess::Board& board, chess::Move move) { + constexpr int max_gain_size = Stockfish::MAX_PLY + 1; // To avoid overflow in gain[d + 1] + std::array gain{}; + int d = 0; + chess::Color side = board.sideToMove(); + chess::Bitboard occ = board.occ(); + + chess::Square target_sq = move.to(); + + // Initial gain setup: + if (move.typeOf() & chess::Move::PROMOTION) + { + gain[0] = piece_value(move.promotionType()); + if (board.isCapture(move)) + gain[0] += piece_value(board.at(target_sq)); + } + else if (move.typeOf() == chess::Move::ENPASSANT) + { + // We'll treat it normally below, so just assign captured pawn value now: + gain[0] = piece_value(chess::PieceType::PAWN); + } + else + { + gain[0] = piece_value(board.at(target_sq)); + } + + chess::Square from_sq = move.from(); + + // Remove the moving piece from occupancy: + occ.clear(from_sq.index()); + + // Special case for en passant: remove the captured pawn square here: + if (move.typeOf() == chess::Move::ENPASSANT) + { + chess::Square ep_captured_sq(target_sq.file(), + from_sq.rank()); // square behind target + occ.clear(ep_captured_sq.index()); + } + + chess::Square next_attacker_sq = from_sq; + + while (++d < max_gain_size) + { + side = ~side; + + // Find all attackers of target square from side to move: + chess::Bitboard attackers = chess::attacks::attackers(board, side, target_sq) & occ; + + // Include x-ray attacks for sliding pieces: + chess::Bitboard rook_attacks = chess::attacks::rook(target_sq, occ); + chess::Bitboard bishop_attacks = chess::attacks::bishop(target_sq, occ); + + attackers |= rook_attacks + & (board.pieces(chess::PieceType::ROOK, side) + | board.pieces(chess::PieceType::QUEEN, side)); + attackers |= bishop_attacks + & (board.pieces(chess::PieceType::BISHOP, side) + | board.pieces(chess::PieceType::QUEEN, side)); + + if (attackers.empty()) + break; + + next_attacker_sq = select_least_valuable(board, attackers); + if (next_attacker_sq == chess::Square::NO_SQ) + break; + + int captured_value = piece_value(board.at(next_attacker_sq)); + gain[d] = -gain[d - 1] + captured_value; + + if (std::max(-gain[d - 1], gain[d]) < 0) + break; + + occ.clear(next_attacker_sq.index()); + } + + while (--d > 0) + { + gain[d] = std::max(-gain[d + 1], gain[d]); + } + + return gain[0]; +} + +// 2. Killer moves +chess::Move killerMoves[Stockfish::MAX_PLY][2] = {}; + +void updateKillerMoves(chess::Move m, int ply) { + if (killerMoves[ply][0] != m) + { + killerMoves[ply][1] = killerMoves[ply][0]; + killerMoves[ply][0] = m; + } +} + +// 3. History heuristic +int historyHeuristic[64][64] = {}; // from-square to to-square + +void updateHistoryHeuristic(chess::Move m, int depth) { + historyHeuristic[m.from().index()][m.to().index()] += depth * depth; +} + +void orderMoves(chess::Board& board, chess::Movelist& moves, chess::Move ttMove, int ply) { + for (auto& move : moves) + { + if (move == ttMove) + move.setScore(10000); + else if (board.isCapture(move)) + move.setScore(SEE(board, move)); + else if (move == killerMoves[ply][0]) + move.setScore(8500); + else if (move == killerMoves[ply][1]) + move.setScore(8000); + else + move.setScore(historyHeuristic[move.from().index()][move.to().index()]); + } + // Sort moves by score in descending order + std::stable_sort(moves.begin(), moves.end(), [](const chess::Move& a, const chess::Move& b) { + return a.score() > b.score(); + }); +} + +void qOrderMoves(chess::Board& board, chess::Movelist& moves) { + // clang-format off + int16_t promotion_table[4][8] = { + {-58, -38, -13, -28, -31, -27, -63, -99}, // N + {-14, -21, -11, -8, -7, -9, -17, -24}, // B + { 13, 10, 18, 15, 12, 12, 8, 5}, // R + { -9, 22, 22, 27, 27, 19, 10, 20}, // Q + }; + // clang-format on + + for (auto& move : moves) + { + if (board.isCapture(move)) // Simple SEE + { + move.setScore(SEE(board, move)); + } + else if (move.typeOf() & chess::Move::PROMOTION) + { + int promoValue = piece_value(move.promotionType()); + int captureValue = + board.isCapture(move) ? piece_value(board.at(move.to())) : 0; + move.setScore(promoValue + captureValue + + promotion_table[move.promotionType() - 1][move.to().file()]); + } + else + { + move.setScore(historyHeuristic[move.from().index()][move.to().index()]); + } + } + // Sort moves by score in descending order + std::stable_sort(moves.begin(), moves.end(), [](const chess::Move& a, const chess::Move& b) { + return a.score() > b.score(); + }); +} +} // namespace movepick diff --git a/src/movepick.hpp b/src/movepick.hpp new file mode 100644 index 0000000..d8b00e4 --- /dev/null +++ b/src/movepick.hpp @@ -0,0 +1,17 @@ +#pragma once +#include "chess.hpp" +#include "types.h" +namespace movepick { +// Static Exchange Evaluation - evaluates material exchange on a square +int SEE(chess::Board&, chess::Move); +// Updates killer moves table for move ordering +void updateKillerMoves(chess::Move, int); +// Updates history heuristic table for move ordering +void updateHistoryHeuristic(chess::Move, int); +// Orders moves for regular search (with TT move, killers at given ply) +void orderMoves(chess::Board&, chess::Movelist&, chess::Move, int); +// Orders moves for quiescence search (captures/promotions) +void qOrderMoves(chess::Board&, chess::Movelist&); +// Killer moves table: [ply][slot] for move ordering heuristic +extern chess::Move killerMoves[Stockfish::MAX_PLY][2]; +} // namespace movepick diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp new file mode 100644 index 0000000..70e9bee --- /dev/null +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -0,0 +1,86 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +//Definition of input features HalfKAv2_hm of NNUE evaluation function + +#include "half_ka_v2_hm.h" + +#include "../../bitboard.h" +#include "../../position.h" +#include "../../types.h" +#include "../nnue_common.h" + +namespace Stockfish::Eval::NNUE::Features { + +// Index of a feature for a given king position and another piece on some square +template +inline IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq) { + return IndexType((int(s) ^ OrientTBL[Perspective][ksq]) + PieceSquareIndex[Perspective][pc] + + KingBuckets[Perspective][ksq]); +} + +// Get a list of indices for active features +template +void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active) { + Square ksq = pos.square(Perspective); + Bitboard bb = pos.pieces(); + while (bb) + { + Square s = pop_lsb(bb); + active.push_back(make_index(s, pos.piece_on(s), ksq)); + } +} + +// Explicit template instantiations +template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); +template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); +template IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq); +template IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq); + +// Get a list of indices for recently changed features +template +void HalfKAv2_hm::append_changed_indices(Square ksq, + const DirtyPiece& dp, + IndexList& removed, + IndexList& added) { + removed.push_back(make_index(dp.from, dp.pc, ksq)); + if (dp.to != SQ_NONE) + added.push_back(make_index(dp.to, dp.pc, ksq)); + + if (dp.remove_sq != SQ_NONE) + removed.push_back(make_index(dp.remove_sq, dp.remove_pc, ksq)); + + if (dp.add_sq != SQ_NONE) + added.push_back(make_index(dp.add_sq, dp.add_pc, ksq)); +} + +// Explicit template instantiations +template void HalfKAv2_hm::append_changed_indices(Square ksq, + const DirtyPiece& dp, + IndexList& removed, + IndexList& added); +template void HalfKAv2_hm::append_changed_indices(Square ksq, + const DirtyPiece& dp, + IndexList& removed, + IndexList& added); + +bool HalfKAv2_hm::requires_refresh(const DirtyPiece& dirtyPiece, Color perspective) { + return dirtyPiece.pc == make_piece(perspective, KING); +} + +} // namespace Stockfish::Eval::NNUE::Features diff --git a/src/nnue/features/half_ka_v2_hm.h b/src/nnue/features/half_ka_v2_hm.h new file mode 100644 index 0000000..af3bf48 --- /dev/null +++ b/src/nnue/features/half_ka_v2_hm.h @@ -0,0 +1,144 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +//Definition of input features HalfKAv2_hm of NNUE evaluation function + +#ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED +#define NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED + +#include + +#include "../../misc.h" +#include "../../types.h" +#include "../nnue_common.h" + +namespace Stockfish { +class Position; +} + +namespace Stockfish::Eval::NNUE::Features { + +// Feature HalfKAv2_hm: Combination of the position of own king and the +// position of pieces. Position mirrored such that king is always on e..h files. +class HalfKAv2_hm { + + // Unique number for each piece type on each square + enum { + PS_NONE = 0, + PS_W_PAWN = 0, + PS_B_PAWN = 1 * SQUARE_NB, + PS_W_KNIGHT = 2 * SQUARE_NB, + PS_B_KNIGHT = 3 * SQUARE_NB, + PS_W_BISHOP = 4 * SQUARE_NB, + PS_B_BISHOP = 5 * SQUARE_NB, + PS_W_ROOK = 6 * SQUARE_NB, + PS_B_ROOK = 7 * SQUARE_NB, + PS_W_QUEEN = 8 * SQUARE_NB, + PS_B_QUEEN = 9 * SQUARE_NB, + PS_KING = 10 * SQUARE_NB, + PS_NB = 11 * SQUARE_NB + }; + + static constexpr IndexType PieceSquareIndex[COLOR_NB][PIECE_NB] = { + // Convention: W - us, B - them + // Viewed from other side, W and B are reversed + {PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE, + PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE}, + {PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE, + PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE}}; + + public: + // Feature name + static constexpr const char* Name = "HalfKAv2_hm(Friend)"; + + // Hash value embedded in the evaluation file + static constexpr std::uint32_t HashValue = 0x7f234cb8u; + + // Number of feature dimensions + static constexpr IndexType Dimensions = + static_cast(SQUARE_NB) * static_cast(PS_NB) / 2; + +#define B(v) (v * PS_NB) + // clang-format off + static constexpr int KingBuckets[COLOR_NB][SQUARE_NB] = { + { B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28), + B(24), B(25), B(26), B(27), B(27), B(26), B(25), B(24), + B(20), B(21), B(22), B(23), B(23), B(22), B(21), B(20), + B(16), B(17), B(18), B(19), B(19), B(18), B(17), B(16), + B(12), B(13), B(14), B(15), B(15), B(14), B(13), B(12), + B( 8), B( 9), B(10), B(11), B(11), B(10), B( 9), B( 8), + B( 4), B( 5), B( 6), B( 7), B( 7), B( 6), B( 5), B( 4), + B( 0), B( 1), B( 2), B( 3), B( 3), B( 2), B( 1), B( 0) }, + { B( 0), B( 1), B( 2), B( 3), B( 3), B( 2), B( 1), B( 0), + B( 4), B( 5), B( 6), B( 7), B( 7), B( 6), B( 5), B( 4), + B( 8), B( 9), B(10), B(11), B(11), B(10), B( 9), B( 8), + B(12), B(13), B(14), B(15), B(15), B(14), B(13), B(12), + B(16), B(17), B(18), B(19), B(19), B(18), B(17), B(16), + B(20), B(21), B(22), B(23), B(23), B(22), B(21), B(20), + B(24), B(25), B(26), B(27), B(27), B(26), B(25), B(24), + B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28) } + }; + // clang-format on +#undef B + // clang-format off + // Orient a square according to perspective (rotates by 180 for black) + static constexpr int OrientTBL[COLOR_NB][SQUARE_NB] = { + { SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, + SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, + SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, + SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, + SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, + SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, + SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, + SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1 }, + { SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8, + SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8, + SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8, + SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8, + SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8, + SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8, + SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8, + SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8 } + }; + // clang-format on + + // Maximum number of simultaneously active features. + static constexpr IndexType MaxActiveDimensions = 32; + using IndexList = ValueList; + + // Index of a feature for a given king position and another piece on some square + template + static IndexType make_index(Square s, Piece pc, Square ksq); + + // Get a list of indices for active features + template + static void append_active_indices(const Position& pos, IndexList& active); + + // Get a list of indices for recently changed features + template + static void + append_changed_indices(Square ksq, const DirtyPiece& dp, IndexList& removed, IndexList& added); + + // Returns whether the change stored in this DirtyPiece means + // that a full accumulator refresh is required. + static bool requires_refresh(const DirtyPiece& dirtyPiece, Color perspective); +}; + +} // namespace Stockfish::Eval::NNUE::Features + +#endif // #ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED diff --git a/src/nnue/features/half_ka_v2_hm.o b/src/nnue/features/half_ka_v2_hm.o new file mode 100644 index 0000000..91a9483 Binary files /dev/null and b/src/nnue/features/half_ka_v2_hm.o differ diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h new file mode 100644 index 0000000..b4440c1 --- /dev/null +++ b/src/nnue/layers/affine_transform.h @@ -0,0 +1,302 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Definition of layer AffineTransform of NNUE evaluation function + +#ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED +#define NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED + +#include +#include + +#include "../nnue_common.h" +#include "../simd.h" + +/* + This file contains the definition for a fully connected layer (aka affine transform). + + - expected use-case is for when PaddedInputDimensions == 32 and InputDimensions <= 32. + - that's why AVX512 is hard to implement + - expected use-case is small layers + - inputs are processed in chunks of 4, weights are respectively transposed + - accumulation happens directly to int32s +*/ + +namespace Stockfish::Eval::NNUE::Layers { + +#if defined(USE_SSSE3) || defined(USE_NEON_DOTPROD) + #define ENABLE_SEQ_OPT +#endif + +// Fallback implementation for older/other architectures. +// Requires the input to be padded to at least 16 values. +#ifndef ENABLE_SEQ_OPT + +template +static void affine_transform_non_ssse3(std::int32_t* output, + const std::int8_t* weights, + const std::int32_t* biases, + const std::uint8_t* input) { + #if defined(USE_SSE2) || defined(USE_NEON) + #if defined(USE_SSE2) + // At least a multiple of 16, with SSE2. + constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; + const __m128i Zeros = _mm_setzero_si128(); + const auto inputVector = reinterpret_cast(input); + + #elif defined(USE_NEON) + constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 16) / 16; + const auto inputVector = reinterpret_cast(input); + #endif + + for (IndexType i = 0; i < OutputDimensions; ++i) + { + const IndexType offset = i * PaddedInputDimensions; + + #if defined(USE_SSE2) + __m128i sumLo = _mm_cvtsi32_si128(biases[i]); + __m128i sumHi = Zeros; + const auto row = reinterpret_cast(&weights[offset]); + for (IndexType j = 0; j < NumChunks; ++j) + { + __m128i row_j = _mm_load_si128(&row[j]); + __m128i input_j = _mm_load_si128(&inputVector[j]); + __m128i extendedRowLo = _mm_srai_epi16(_mm_unpacklo_epi8(row_j, row_j), 8); + __m128i extendedRowHi = _mm_srai_epi16(_mm_unpackhi_epi8(row_j, row_j), 8); + __m128i extendedInputLo = _mm_unpacklo_epi8(input_j, Zeros); + __m128i extendedInputHi = _mm_unpackhi_epi8(input_j, Zeros); + __m128i productLo = _mm_madd_epi16(extendedRowLo, extendedInputLo); + __m128i productHi = _mm_madd_epi16(extendedRowHi, extendedInputHi); + sumLo = _mm_add_epi32(sumLo, productLo); + sumHi = _mm_add_epi32(sumHi, productHi); + } + __m128i sum = _mm_add_epi32(sumLo, sumHi); + __m128i sumHigh_64 = _mm_shuffle_epi32(sum, _MM_SHUFFLE(1, 0, 3, 2)); + sum = _mm_add_epi32(sum, sumHigh_64); + __m128i sum_second_32 = _mm_shufflelo_epi16(sum, _MM_SHUFFLE(1, 0, 3, 2)); + sum = _mm_add_epi32(sum, sum_second_32); + output[i] = _mm_cvtsi128_si32(sum); + + #elif defined(USE_NEON) + + int32x4_t sum = {biases[i]}; + const auto row = reinterpret_cast(&weights[offset]); + for (IndexType j = 0; j < NumChunks; ++j) + { + int16x8_t product = vmull_s8(inputVector[j * 2], row[j * 2]); + product = vmlal_s8(product, inputVector[j * 2 + 1], row[j * 2 + 1]); + sum = vpadalq_s16(sum, product); + } + output[i] = SIMD::neon_m128_reduce_add_epi32(sum); + + #endif + } + #else + std::memcpy(output, biases, sizeof(std::int32_t) * OutputDimensions); + + // Traverse weights in transpose order to take advantage of input sparsity + for (IndexType i = 0; i < InputDimensions; ++i) + if (input[i]) + { + const std::int8_t* w = &weights[i]; + const int in = input[i]; + for (IndexType j = 0; j < OutputDimensions; ++j) + output[j] += w[j * PaddedInputDimensions] * in; + } + #endif +} + +#endif // !ENABLE_SEQ_OPT + +template +class AffineTransform { + public: + // Input/output type + using InputType = std::uint8_t; + using OutputType = std::int32_t; + + // Number of input/output dimensions + static constexpr IndexType InputDimensions = InDims; + static constexpr IndexType OutputDimensions = OutDims; + + static constexpr IndexType PaddedInputDimensions = + ceil_to_multiple(InputDimensions, MaxSimdWidth); + static constexpr IndexType PaddedOutputDimensions = + ceil_to_multiple(OutputDimensions, MaxSimdWidth); + + using OutputBuffer = OutputType[PaddedOutputDimensions]; + + // Hash value embedded in the evaluation file + static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) { + std::uint32_t hashValue = 0xCC03DAE4u; + hashValue += OutputDimensions; + hashValue ^= prevHash >> 1; + hashValue ^= prevHash << 31; + return hashValue; + } + + static constexpr IndexType get_weight_index_scrambled(IndexType i) { + return (i / 4) % (PaddedInputDimensions / 4) * OutputDimensions * 4 + + i / PaddedInputDimensions * 4 + i % 4; + } + + static constexpr IndexType get_weight_index(IndexType i) { +#ifdef ENABLE_SEQ_OPT + return get_weight_index_scrambled(i); +#else + return i; +#endif + } + + // Read network parameters + bool read_parameters(std::istream& stream) { + read_little_endian(stream, biases, OutputDimensions); + for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) + weights[get_weight_index(i)] = read_little_endian(stream); + + return !stream.fail(); + } + + // Write network parameters + bool write_parameters(std::ostream& stream) const { + write_little_endian(stream, biases, OutputDimensions); + + for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) + write_little_endian(stream, weights[get_weight_index(i)]); + + return !stream.fail(); + } + // Forward propagation + void propagate(const InputType* input, OutputType* output) const { + +#ifdef ENABLE_SEQ_OPT + + if constexpr (OutputDimensions > 1) + { + #if defined(USE_AVX512) + using vec_t = __m512i; + #define vec_set_32 _mm512_set1_epi32 + #define vec_add_dpbusd_32 SIMD::m512_add_dpbusd_epi32 + #elif defined(USE_AVX2) + using vec_t = __m256i; + #define vec_set_32 _mm256_set1_epi32 + #define vec_add_dpbusd_32 SIMD::m256_add_dpbusd_epi32 + #elif defined(USE_SSSE3) + using vec_t = __m128i; + #define vec_set_32 _mm_set1_epi32 + #define vec_add_dpbusd_32 SIMD::m128_add_dpbusd_epi32 + #elif defined(USE_NEON_DOTPROD) + using vec_t = int32x4_t; + #define vec_set_32 vdupq_n_s32 + #define vec_add_dpbusd_32(acc, a, b) \ + SIMD::dotprod_m128_add_dpbusd_epi32(acc, vreinterpretq_s8_s32(a), \ + vreinterpretq_s8_s32(b)) + #endif + + static constexpr IndexType OutputSimdWidth = sizeof(vec_t) / sizeof(OutputType); + + static_assert(OutputDimensions % OutputSimdWidth == 0); + + constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 8) / 4; + constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth; + + const auto input32 = reinterpret_cast(input); + const vec_t* biasvec = reinterpret_cast(biases); + vec_t acc[NumRegs]; + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = biasvec[k]; + + for (IndexType i = 0; i < NumChunks; ++i) + { + const vec_t in0 = vec_set_32(input32[i]); + const auto col0 = + reinterpret_cast(&weights[i * OutputDimensions * 4]); + + for (IndexType k = 0; k < NumRegs; ++k) + vec_add_dpbusd_32(acc[k], in0, col0[k]); + } + + vec_t* outptr = reinterpret_cast(output); + for (IndexType k = 0; k < NumRegs; ++k) + outptr[k] = acc[k]; + + #undef vec_set_32 + #undef vec_add_dpbusd_32 + } + else if constexpr (OutputDimensions == 1) + { + // We cannot use AVX512 for the last layer because there are only 32 inputs + // and the buffer is not padded to 64 elements. + #if defined(USE_AVX2) + using vec_t = __m256i; + #define vec_setzero() _mm256_setzero_si256() + #define vec_add_dpbusd_32 SIMD::m256_add_dpbusd_epi32 + #define vec_hadd SIMD::m256_hadd + #elif defined(USE_SSSE3) + using vec_t = __m128i; + #define vec_setzero() _mm_setzero_si128() + #define vec_add_dpbusd_32 SIMD::m128_add_dpbusd_epi32 + #define vec_hadd SIMD::m128_hadd + #elif defined(USE_NEON_DOTPROD) + using vec_t = int32x4_t; + #define vec_setzero() vdupq_n_s32(0) + #define vec_add_dpbusd_32(acc, a, b) \ + SIMD::dotprod_m128_add_dpbusd_epi32(acc, vreinterpretq_s8_s32(a), \ + vreinterpretq_s8_s32(b)) + #define vec_hadd SIMD::neon_m128_hadd + #endif + + const auto inputVector = reinterpret_cast(input); + + static constexpr IndexType InputSimdWidth = sizeof(vec_t) / sizeof(InputType); + + static_assert(PaddedInputDimensions % InputSimdWidth == 0); + + constexpr IndexType NumChunks = PaddedInputDimensions / InputSimdWidth; + vec_t sum0 = vec_setzero(); + const auto row0 = reinterpret_cast(&weights[0]); + + for (int j = 0; j < int(NumChunks); ++j) + { + const vec_t in = inputVector[j]; + vec_add_dpbusd_32(sum0, in, row0[j]); + } + output[0] = vec_hadd(sum0, biases[0]); + + #undef vec_setzero + #undef vec_add_dpbusd_32 + #undef vec_hadd + } +#else + // Use old implementation for the other architectures. + affine_transform_non_ssse3( + output, weights, biases, input); +#endif + } + + private: + using BiasType = OutputType; + using WeightType = std::int8_t; + + alignas(CacheLineSize) BiasType biases[OutputDimensions]; + alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions]; +}; + +} // namespace Stockfish::Eval::NNUE::Layers + +#endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h new file mode 100644 index 0000000..7c74d5e --- /dev/null +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -0,0 +1,262 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Definition of layer AffineTransformSparseInput of NNUE evaluation function + +#ifndef NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED +#define NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED + +#include +#include +#include + +#include "../../bitboard.h" +#include "../simd.h" +#include "../nnue_common.h" + +/* + This file contains the definition for a fully connected layer (aka affine transform) with block sparse input. +*/ + +namespace Stockfish::Eval::NNUE::Layers { + +#if (USE_SSSE3 | (USE_NEON >= 8)) +static constexpr int lsb_index64[64] = { + 0, 47, 1, 56, 48, 27, 2, 60, 57, 49, 41, 37, 28, 16, 3, 61, 54, 58, 35, 52, 50, 42, + 21, 44, 38, 32, 29, 23, 17, 11, 4, 62, 46, 55, 26, 59, 40, 36, 15, 53, 34, 51, 20, 43, + 31, 22, 10, 45, 25, 39, 14, 33, 19, 30, 9, 24, 13, 18, 8, 12, 7, 6, 5, 63}; + +constexpr int constexpr_lsb(uint64_t bb) { + assert(bb != 0); + constexpr uint64_t debruijn64 = 0x03F79D71B4CB0A89ULL; + return lsb_index64[((bb ^ (bb - 1)) * debruijn64) >> 58]; +} + +alignas(CacheLineSize) static constexpr struct OffsetIndices { + + #if (USE_SSE41) + std::uint8_t offset_indices[256][8]; + #else + std::uint16_t offset_indices[256][8]; + #endif + + constexpr OffsetIndices() : + offset_indices() { + for (int i = 0; i < 256; ++i) + { + std::uint64_t j = i, k = 0; + while (j) + { + offset_indices[i][k++] = constexpr_lsb(j); + j &= j - 1; + } + while (k < 8) + offset_indices[i][k++] = 0; + } + } + +} Lookup; + +// Find indices of nonzero numbers in an int32_t array +template +void find_nnz(const std::int32_t* input, std::uint16_t* out, IndexType& count_out) { + using namespace SIMD; + + constexpr IndexType InputSimdWidth = sizeof(vec_uint_t) / sizeof(std::int32_t); + // Inputs are processed InputSimdWidth at a time and outputs are processed 8 at a time so we process in chunks of max(InputSimdWidth, 8) + constexpr IndexType ChunkSize = std::max(InputSimdWidth, 8); + constexpr IndexType NumChunks = InputDimensions / ChunkSize; + constexpr IndexType InputsPerChunk = ChunkSize / InputSimdWidth; + constexpr IndexType OutputsPerChunk = ChunkSize / 8; + + const auto inputVector = reinterpret_cast(input); + IndexType count = 0; + vec128_t base = vec128_zero; + const vec128_t increment = vec128_set_16(8); + for (IndexType i = 0; i < NumChunks; ++i) + { + // bitmask of nonzero values in this chunk + unsigned nnz = 0; + for (IndexType j = 0; j < InputsPerChunk; ++j) + { + const vec_uint_t inputChunk = inputVector[i * InputsPerChunk + j]; + nnz |= unsigned(vec_nnz(inputChunk)) << (j * InputSimdWidth); + } + for (IndexType j = 0; j < OutputsPerChunk; ++j) + { + const unsigned lookup = (nnz >> (j * 8)) & 0xFF; + const vec128_t offsets = + vec128_load(reinterpret_cast(&Lookup.offset_indices[lookup])); + vec128_storeu(reinterpret_cast(out + count), vec128_add(base, offsets)); + count += popcount(lookup); + base = vec128_add(base, increment); + } + } + count_out = count; +} + +#endif + +// Sparse input implementation +template +class AffineTransformSparseInput { + public: + // Input/output type + using InputType = std::uint8_t; + using OutputType = std::int32_t; + + // Number of input/output dimensions + static constexpr IndexType InputDimensions = InDims; + static constexpr IndexType OutputDimensions = OutDims; + + static_assert(OutputDimensions % 16 == 0, + "Only implemented for OutputDimensions divisible by 16."); + + static constexpr IndexType PaddedInputDimensions = + ceil_to_multiple(InputDimensions, MaxSimdWidth); + static constexpr IndexType PaddedOutputDimensions = + ceil_to_multiple(OutputDimensions, MaxSimdWidth); + +#if (USE_SSSE3 | (USE_NEON >= 8)) + static constexpr IndexType ChunkSize = 4; +#else + static constexpr IndexType ChunkSize = 1; +#endif + + using OutputBuffer = OutputType[PaddedOutputDimensions]; + + // Hash value embedded in the evaluation file + static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) { + std::uint32_t hashValue = 0xCC03DAE4u; + hashValue += OutputDimensions; + hashValue ^= prevHash >> 1; + hashValue ^= prevHash << 31; + return hashValue; + } + + static constexpr IndexType get_weight_index_scrambled(IndexType i) { + return (i / ChunkSize) % (PaddedInputDimensions / ChunkSize) * OutputDimensions * ChunkSize + + i / PaddedInputDimensions * ChunkSize + i % ChunkSize; + } + + static constexpr IndexType get_weight_index(IndexType i) { +#if (USE_SSSE3 | (USE_NEON >= 8)) + return get_weight_index_scrambled(i); +#else + return i; +#endif + } + + // Read network parameters + bool read_parameters(std::istream& stream) { + read_little_endian(stream, biases, OutputDimensions); + for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) + weights[get_weight_index(i)] = read_little_endian(stream); + + return !stream.fail(); + } + + // Write network parameters + bool write_parameters(std::ostream& stream) const { + write_little_endian(stream, biases, OutputDimensions); + + for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i) + write_little_endian(stream, weights[get_weight_index(i)]); + + return !stream.fail(); + } + // Forward propagation + void propagate(const InputType* input, OutputType* output) const { + +#if (USE_SSSE3 | (USE_NEON >= 8)) + #if defined(USE_AVX512) + using invec_t = __m512i; + using outvec_t = __m512i; + #define vec_set_32 _mm512_set1_epi32 + #define vec_add_dpbusd_32 SIMD::m512_add_dpbusd_epi32 + #elif defined(USE_AVX2) + using invec_t = __m256i; + using outvec_t = __m256i; + #define vec_set_32 _mm256_set1_epi32 + #define vec_add_dpbusd_32 SIMD::m256_add_dpbusd_epi32 + #elif defined(USE_SSSE3) + using invec_t = __m128i; + using outvec_t = __m128i; + #define vec_set_32 _mm_set1_epi32 + #define vec_add_dpbusd_32 SIMD::m128_add_dpbusd_epi32 + #elif defined(USE_NEON_DOTPROD) + using invec_t = int8x16_t; + using outvec_t = int32x4_t; + #define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a)) + #define vec_add_dpbusd_32 SIMD::dotprod_m128_add_dpbusd_epi32 + #elif defined(USE_NEON) + using invec_t = int8x16_t; + using outvec_t = int32x4_t; + #define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a)) + #define vec_add_dpbusd_32 SIMD::neon_m128_add_dpbusd_epi32 + #endif + static constexpr IndexType OutputSimdWidth = sizeof(outvec_t) / sizeof(OutputType); + + constexpr IndexType NumChunks = ceil_to_multiple(InputDimensions, 8) / ChunkSize; + constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth; + std::uint16_t nnz[NumChunks]; + IndexType count; + + const auto input32 = reinterpret_cast(input); + + // Find indices of nonzero 32-bit blocks + find_nnz(input32, nnz, count); + + const outvec_t* biasvec = reinterpret_cast(biases); + outvec_t acc[NumRegs]; + for (IndexType k = 0; k < NumRegs; ++k) + acc[k] = biasvec[k]; + + for (IndexType j = 0; j < count; ++j) + { + const auto i = nnz[j]; + const invec_t in = vec_set_32(input32[i]); + const auto col = + reinterpret_cast(&weights[i * OutputDimensions * ChunkSize]); + for (IndexType k = 0; k < NumRegs; ++k) + vec_add_dpbusd_32(acc[k], in, col[k]); + } + + outvec_t* outptr = reinterpret_cast(output); + for (IndexType k = 0; k < NumRegs; ++k) + outptr[k] = acc[k]; + #undef vec_set_32 + #undef vec_add_dpbusd_32 +#else + // Use dense implementation for the other architectures. + affine_transform_non_ssse3( + output, weights, biases, input); +#endif + } + + private: + using BiasType = OutputType; + using WeightType = std::int8_t; + + alignas(CacheLineSize) BiasType biases[OutputDimensions]; + alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions]; +}; + +} // namespace Stockfish::Eval::NNUE::Layers + +#endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED diff --git a/src/nnue/layers/clipped_relu.h b/src/nnue/layers/clipped_relu.h new file mode 100644 index 0000000..2ad5a86 --- /dev/null +++ b/src/nnue/layers/clipped_relu.h @@ -0,0 +1,164 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Definition of layer ClippedReLU of NNUE evaluation function + +#ifndef NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED +#define NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED + +#include +#include +#include + +#include "../nnue_common.h" + +namespace Stockfish::Eval::NNUE::Layers { + +// Clipped ReLU +template +class ClippedReLU { + public: + // Input/output type + using InputType = std::int32_t; + using OutputType = std::uint8_t; + + // Number of input/output dimensions + static constexpr IndexType InputDimensions = InDims; + static constexpr IndexType OutputDimensions = InputDimensions; + static constexpr IndexType PaddedOutputDimensions = + ceil_to_multiple(OutputDimensions, 32); + + using OutputBuffer = OutputType[PaddedOutputDimensions]; + + // Hash value embedded in the evaluation file + static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) { + std::uint32_t hashValue = 0x538D24C7u; + hashValue += prevHash; + return hashValue; + } + + // Read network parameters + bool read_parameters(std::istream&) { return true; } + + // Write network parameters + bool write_parameters(std::ostream&) const { return true; } + + // Forward propagation + void propagate(const InputType* input, OutputType* output) const { + +#if defined(USE_AVX2) + if constexpr (InputDimensions % SimdWidth == 0) + { + constexpr IndexType NumChunks = InputDimensions / SimdWidth; + const __m256i Offsets = _mm256_set_epi32(7, 3, 6, 2, 5, 1, 4, 0); + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast<__m256i*>(output); + for (IndexType i = 0; i < NumChunks; ++i) + { + const __m256i words0 = + _mm256_srli_epi16(_mm256_packus_epi32(_mm256_load_si256(&in[i * 4 + 0]), + _mm256_load_si256(&in[i * 4 + 1])), + WeightScaleBits); + const __m256i words1 = + _mm256_srli_epi16(_mm256_packus_epi32(_mm256_load_si256(&in[i * 4 + 2]), + _mm256_load_si256(&in[i * 4 + 3])), + WeightScaleBits); + _mm256_store_si256(&out[i], _mm256_permutevar8x32_epi32( + _mm256_packs_epi16(words0, words1), Offsets)); + } + } + else + { + constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2); + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast<__m128i*>(output); + for (IndexType i = 0; i < NumChunks; ++i) + { + const __m128i words0 = _mm_srli_epi16( + _mm_packus_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])), + WeightScaleBits); + const __m128i words1 = _mm_srli_epi16( + _mm_packus_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])), + WeightScaleBits); + _mm_store_si128(&out[i], _mm_packs_epi16(words0, words1)); + } + } + constexpr IndexType Start = InputDimensions % SimdWidth == 0 + ? InputDimensions / SimdWidth * SimdWidth + : InputDimensions / (SimdWidth / 2) * (SimdWidth / 2); + +#elif defined(USE_SSE2) + constexpr IndexType NumChunks = InputDimensions / SimdWidth; + + #ifndef USE_SSE41 + const __m128i k0x80s = _mm_set1_epi8(-128); + #endif + + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast<__m128i*>(output); + for (IndexType i = 0; i < NumChunks; ++i) + { + #if defined(USE_SSE41) + const __m128i words0 = _mm_srli_epi16( + _mm_packus_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])), + WeightScaleBits); + const __m128i words1 = _mm_srli_epi16( + _mm_packus_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])), + WeightScaleBits); + _mm_store_si128(&out[i], _mm_packs_epi16(words0, words1)); + #else + const __m128i words0 = _mm_srai_epi16( + _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])), + WeightScaleBits); + const __m128i words1 = _mm_srai_epi16( + _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])), + WeightScaleBits); + const __m128i packedbytes = _mm_packs_epi16(words0, words1); + _mm_store_si128(&out[i], _mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s)); + #endif + } + constexpr IndexType Start = NumChunks * SimdWidth; + +#elif defined(USE_NEON) + constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2); + const int8x8_t Zero = {0}; + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast(output); + for (IndexType i = 0; i < NumChunks; ++i) + { + int16x8_t shifted; + const auto pack = reinterpret_cast(&shifted); + pack[0] = vqshrn_n_s32(in[i * 2 + 0], WeightScaleBits); + pack[1] = vqshrn_n_s32(in[i * 2 + 1], WeightScaleBits); + out[i] = vmax_s8(vqmovn_s16(shifted), Zero); + } + constexpr IndexType Start = NumChunks * (SimdWidth / 2); +#else + constexpr IndexType Start = 0; +#endif + + for (IndexType i = Start; i < InputDimensions; ++i) + { + output[i] = static_cast(std::clamp(input[i] >> WeightScaleBits, 0, 127)); + } + } +}; + +} // namespace Stockfish::Eval::NNUE::Layers + +#endif // NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED diff --git a/src/nnue/layers/sqr_clipped_relu.h b/src/nnue/layers/sqr_clipped_relu.h new file mode 100644 index 0000000..d14f1e0 --- /dev/null +++ b/src/nnue/layers/sqr_clipped_relu.h @@ -0,0 +1,103 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Definition of layer ClippedReLU of NNUE evaluation function + +#ifndef NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED +#define NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED + +#include +#include +#include + +#include "../nnue_common.h" + +namespace Stockfish::Eval::NNUE::Layers { + +// Clipped ReLU +template +class SqrClippedReLU { + public: + // Input/output type + using InputType = std::int32_t; + using OutputType = std::uint8_t; + + // Number of input/output dimensions + static constexpr IndexType InputDimensions = InDims; + static constexpr IndexType OutputDimensions = InputDimensions; + static constexpr IndexType PaddedOutputDimensions = + ceil_to_multiple(OutputDimensions, 32); + + using OutputBuffer = OutputType[PaddedOutputDimensions]; + + // Hash value embedded in the evaluation file + static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) { + std::uint32_t hashValue = 0x538D24C7u; + hashValue += prevHash; + return hashValue; + } + + // Read network parameters + bool read_parameters(std::istream&) { return true; } + + // Write network parameters + bool write_parameters(std::ostream&) const { return true; } + + // Forward propagation + void propagate(const InputType* input, OutputType* output) const { + +#if defined(USE_SSE2) + constexpr IndexType NumChunks = InputDimensions / 16; + + static_assert(WeightScaleBits == 6); + const auto in = reinterpret_cast(input); + const auto out = reinterpret_cast<__m128i*>(output); + for (IndexType i = 0; i < NumChunks; ++i) + { + __m128i words0 = + _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])); + __m128i words1 = + _mm_packs_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])); + + // We shift by WeightScaleBits * 2 = 12 and divide by 128 + // which is an additional shift-right of 7, meaning 19 in total. + // MulHi strips the lower 16 bits so we need to shift out 3 more to match. + words0 = _mm_srli_epi16(_mm_mulhi_epi16(words0, words0), 3); + words1 = _mm_srli_epi16(_mm_mulhi_epi16(words1, words1), 3); + + _mm_store_si128(&out[i], _mm_packs_epi16(words0, words1)); + } + constexpr IndexType Start = NumChunks * 16; + +#else + constexpr IndexType Start = 0; +#endif + + for (IndexType i = Start; i < InputDimensions; ++i) + { + output[i] = static_cast( + // Really should be /127 but we need to make it fast so we right-shift + // by an extra 7 bits instead. Needs to be accounted for in the trainer. + std::min(127ll, ((long long) (input[i]) * input[i]) >> (2 * WeightScaleBits + 7))); + } + } +}; + +} // namespace Stockfish::Eval::NNUE::Layers + +#endif // NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp new file mode 100644 index 0000000..957dc7b --- /dev/null +++ b/src/nnue/network.cpp @@ -0,0 +1,442 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "network.h" + +#include +#include +#include +#include +#include +#include +#include + +#define INCBIN_SILENCE_BITCODE_WARNING +#include "../incbin/incbin.h" + +#include "../evaluate.h" +#include "../memory.h" +#include "../misc.h" +#include "../position.h" +#include "../types.h" +#include "nnue_architecture.h" +#include "nnue_common.h" +#include "nnue_misc.h" + +// Macro to embed the default efficiently updatable neural network (NNUE) file +// data in the engine binary (using incbin.h, by Dale Weiler). +// This macro invocation will declare the following three variables +// const unsigned char gEmbeddedNNUEData[]; // a pointer to the embedded data +// const unsigned char *const gEmbeddedNNUEEnd; // a marker to the end +// const unsigned int gEmbeddedNNUESize; // the size of the embedded file +// Note that this does not work in Microsoft Visual Studio. +#if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF) +INCBIN(EmbeddedNNUEBig, EvalFileDefaultNameBig); +INCBIN(EmbeddedNNUESmall, EvalFileDefaultNameSmall); +#else +const unsigned char gEmbeddedNNUEBigData[1] = {0x0}; +const unsigned char* const gEmbeddedNNUEBigEnd = &gEmbeddedNNUEBigData[1]; +const unsigned int gEmbeddedNNUEBigSize = 1; +const unsigned char gEmbeddedNNUESmallData[1] = {0x0}; +const unsigned char* const gEmbeddedNNUESmallEnd = &gEmbeddedNNUESmallData[1]; +const unsigned int gEmbeddedNNUESmallSize = 1; +#endif + +namespace { + +struct EmbeddedNNUE { + EmbeddedNNUE(const unsigned char* embeddedData, + const unsigned char* embeddedEnd, + const unsigned int embeddedSize) : + data(embeddedData), + end(embeddedEnd), + size(embeddedSize) {} + const unsigned char* data; + const unsigned char* end; + const unsigned int size; +}; + +using namespace Stockfish::Eval::NNUE; + +EmbeddedNNUE get_embedded(EmbeddedNNUEType type) { + if (type == EmbeddedNNUEType::BIG) + return EmbeddedNNUE(gEmbeddedNNUEBigData, gEmbeddedNNUEBigEnd, gEmbeddedNNUEBigSize); + else + return EmbeddedNNUE(gEmbeddedNNUESmallData, gEmbeddedNNUESmallEnd, gEmbeddedNNUESmallSize); +} + +} + + +namespace Stockfish::Eval::NNUE { + + +namespace Detail { + +// Read evaluation function parameters +template +bool read_parameters(std::istream& stream, T& reference) { + + std::uint32_t header; + header = read_little_endian(stream); + if (!stream || header != T::get_hash_value()) + return false; + return reference.read_parameters(stream); +} + +// Write evaluation function parameters +template +bool write_parameters(std::ostream& stream, T& reference) { + + write_little_endian(stream, T::get_hash_value()); + return reference.write_parameters(stream); +} + +} // namespace Detail + +template +Network::Network(const Network& other) : + evalFile(other.evalFile), + embeddedType(other.embeddedType) { + + if (other.featureTransformer) + featureTransformer = make_unique_large_page(*other.featureTransformer); + + network = make_unique_aligned(LayerStacks); + + if (!other.network) + return; + + for (std::size_t i = 0; i < LayerStacks; ++i) + network[i] = other.network[i]; +} + +template +Network& +Network::operator=(const Network& other) { + evalFile = other.evalFile; + embeddedType = other.embeddedType; + + if (other.featureTransformer) + featureTransformer = make_unique_large_page(*other.featureTransformer); + + network = make_unique_aligned(LayerStacks); + + if (!other.network) + return *this; + + for (std::size_t i = 0; i < LayerStacks; ++i) + network[i] = other.network[i]; + + return *this; +} + +template +void Network::load(const std::string& rootDirectory, std::string evalfilePath) { +#if defined(DEFAULT_NNUE_DIRECTORY) + std::vector dirs = {"", "", rootDirectory, + stringify(DEFAULT_NNUE_DIRECTORY)}; +#else + std::vector dirs = {"", "", rootDirectory}; +#endif + + if (evalfilePath.empty()) + evalfilePath = evalFile.defaultName; + + for (const auto& directory : dirs) + { + if (evalFile.current != evalfilePath) + { + if (directory != "") + { + load_user_net(directory, evalfilePath); + } + + if (directory == "" && evalfilePath == evalFile.defaultName) + { + load_internal(); + } + } + } +} + + +template +bool Network::save(const std::optional& filename) const { + std::string actualFilename; + std::string msg; + + if (filename.has_value()) + actualFilename = filename.value(); + else + { + if (evalFile.current != evalFile.defaultName) + { + msg = "Failed to export a net. " + "A non-embedded net can only be saved if the filename is specified"; + + sync_cout << msg << sync_endl; + return false; + } + + actualFilename = evalFile.defaultName; + } + + std::ofstream stream(actualFilename, std::ios_base::binary); + bool saved = save(stream, evalFile.current, evalFile.netDescription); + + msg = saved ? "Network saved successfully to " + actualFilename : "Failed to export a net"; + + sync_cout << msg << sync_endl; + return saved; +} + + +template +NetworkOutput +Network::evaluate(const Position& pos, + AccumulatorStack& accumulatorStack, + AccumulatorCaches::Cache* cache) const { + + constexpr uint64_t alignment = CacheLineSize; + + alignas(alignment) + TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize]; + + ASSERT_ALIGNED(transformedFeatures, alignment); + + const int bucket = (pos.count() - 1) / 4; + const auto psqt = + featureTransformer->transform(pos, accumulatorStack, cache, transformedFeatures, bucket); + const auto positional = network[bucket].propagate(transformedFeatures); + return {static_cast(psqt / OutputScale), static_cast(positional / OutputScale)}; +} + + +template +void Network::verify(std::string evalfilePath, + const std::function& f) const { + if (evalfilePath.empty()) + evalfilePath = evalFile.defaultName; + + if (evalFile.current != evalfilePath) + { + if (f) + { + std::string msg1 = + "Network evaluation parameters compatible with the engine must be available."; + std::string msg2 = "The network file " + evalfilePath + " was not loaded successfully."; + std::string msg3 = "The UCI option EvalFile might need to specify the full path, " + "including the directory name, to the network file."; + std::string msg4 = "The default net can be downloaded from: " + "https://tests.stockfishchess.org/api/nn/" + + evalFile.defaultName; + std::string msg5 = "The engine will be terminated now."; + + std::string msg = "ERROR: " + msg1 + '\n' + "ERROR: " + msg2 + '\n' + "ERROR: " + msg3 + + '\n' + "ERROR: " + msg4 + '\n' + "ERROR: " + msg5 + '\n'; + + f(msg); + } + + exit(EXIT_FAILURE); + } + + if (f) + { + size_t size = sizeof(*featureTransformer) + sizeof(Arch) * LayerStacks; + f("NNUE evaluation using " + evalfilePath + " (" + std::to_string(size / (1024 * 1024)) + + "MiB, (" + std::to_string(featureTransformer->InputDimensions) + ", " + + std::to_string(network[0].TransformedFeatureDimensions) + ", " + + std::to_string(network[0].FC_0_OUTPUTS) + ", " + std::to_string(network[0].FC_1_OUTPUTS) + + ", 1))"); + } +} + + +template +NnueEvalTrace +Network::trace_evaluate(const Position& pos, + AccumulatorStack& accumulatorStack, + AccumulatorCaches::Cache* cache) const { + + constexpr uint64_t alignment = CacheLineSize; + + alignas(alignment) + TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize]; + + ASSERT_ALIGNED(transformedFeatures, alignment); + + NnueEvalTrace t{}; + t.correctBucket = (pos.count() - 1) / 4; + for (IndexType bucket = 0; bucket < LayerStacks; ++bucket) + { + const auto materialist = + featureTransformer->transform(pos, accumulatorStack, cache, transformedFeatures, bucket); + const auto positional = network[bucket].propagate(transformedFeatures); + + t.psqt[bucket] = static_cast(materialist / OutputScale); + t.positional[bucket] = static_cast(positional / OutputScale); + } + + return t; +} + + +template +void Network::load_user_net(const std::string& dir, + const std::string& evalfilePath) { + std::ifstream stream(dir + evalfilePath, std::ios::binary); + auto description = load(stream); + + if (description.has_value()) + { + evalFile.current = evalfilePath; + evalFile.netDescription = description.value(); + } +} + + +template +void Network::load_internal() { + // C++ way to prepare a buffer for a memory stream + class MemoryBuffer: public std::basic_streambuf { + public: + MemoryBuffer(char* p, size_t n) { + setg(p, p, p + n); + setp(p, p + n); + } + }; + + const auto embedded = get_embedded(embeddedType); + + MemoryBuffer buffer(const_cast(reinterpret_cast(embedded.data)), + size_t(embedded.size)); + + std::istream stream(&buffer); + auto description = load(stream); + + if (description.has_value()) + { + evalFile.current = evalFile.defaultName; + evalFile.netDescription = description.value(); + } +} + + +template +void Network::initialize() { + featureTransformer = make_unique_large_page(); + network = make_unique_aligned(LayerStacks); +} + + +template +bool Network::save(std::ostream& stream, + const std::string& name, + const std::string& netDescription) const { + if (name.empty() || name == "None") + return false; + + return write_parameters(stream, netDescription); +} + + +template +std::optional Network::load(std::istream& stream) { + initialize(); + std::string description; + + return read_parameters(stream, description) ? std::make_optional(description) : std::nullopt; +} + + +// Read network header +template +bool Network::read_header(std::istream& stream, + std::uint32_t* hashValue, + std::string* desc) const { + std::uint32_t version, size; + + version = read_little_endian(stream); + *hashValue = read_little_endian(stream); + size = read_little_endian(stream); + if (!stream || version != Version) + return false; + desc->resize(size); + stream.read(&(*desc)[0], size); + return !stream.fail(); +} + + +// Write network header +template +bool Network::write_header(std::ostream& stream, + std::uint32_t hashValue, + const std::string& desc) const { + write_little_endian(stream, Version); + write_little_endian(stream, hashValue); + write_little_endian(stream, std::uint32_t(desc.size())); + stream.write(&desc[0], desc.size()); + return !stream.fail(); +} + + +template +bool Network::read_parameters(std::istream& stream, + std::string& netDescription) const { + std::uint32_t hashValue; + if (!read_header(stream, &hashValue, &netDescription)) + return false; + if (hashValue != Network::hash) + return false; + if (!Detail::read_parameters(stream, *featureTransformer)) + return false; + for (std::size_t i = 0; i < LayerStacks; ++i) + { + if (!Detail::read_parameters(stream, network[i])) + return false; + } + return stream && stream.peek() == std::ios::traits_type::eof(); +} + + +template +bool Network::write_parameters(std::ostream& stream, + const std::string& netDescription) const { + if (!write_header(stream, Network::hash, netDescription)) + return false; + if (!Detail::write_parameters(stream, *featureTransformer)) + return false; + for (std::size_t i = 0; i < LayerStacks; ++i) + { + if (!Detail::write_parameters(stream, network[i])) + return false; + } + return bool(stream); +} + +// Explicit template instantiations + +template class Network, + FeatureTransformer>; + +template class Network, + FeatureTransformer>; + +} // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/network.h b/src/nnue/network.h new file mode 100644 index 0000000..c935882 --- /dev/null +++ b/src/nnue/network.h @@ -0,0 +1,137 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef NETWORK_H_INCLUDED +#define NETWORK_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../memory.h" +#include "../types.h" +#include "nnue_accumulator.h" +#include "nnue_architecture.h" +#include "nnue_common.h" +#include "nnue_feature_transformer.h" +#include "nnue_misc.h" + +namespace Stockfish { +class Position; +} + +namespace Stockfish::Eval::NNUE { + +enum class EmbeddedNNUEType { + BIG, + SMALL, +}; + +using NetworkOutput = std::tuple; + +template +class Network { + static constexpr IndexType FTDimensions = Arch::TransformedFeatureDimensions; + + public: + Network(EvalFile file, EmbeddedNNUEType type) : + evalFile(file), + embeddedType(type) {} + + Network(const Network& other); + Network(Network&& other) = default; + + Network& operator=(const Network& other); + Network& operator=(Network&& other) = default; + + void load(const std::string& rootDirectory, std::string evalfilePath); + bool save(const std::optional& filename) const; + + NetworkOutput evaluate(const Position& pos, + AccumulatorStack& accumulatorStack, + AccumulatorCaches::Cache* cache) const; + + + void verify(std::string evalfilePath, const std::function&) const; + NnueEvalTrace trace_evaluate(const Position& pos, + AccumulatorStack& accumulatorStack, + AccumulatorCaches::Cache* cache) const; + + private: + void load_user_net(const std::string&, const std::string&); + void load_internal(); + + void initialize(); + + bool save(std::ostream&, const std::string&, const std::string&) const; + std::optional load(std::istream&); + + bool read_header(std::istream&, std::uint32_t*, std::string*) const; + bool write_header(std::ostream&, std::uint32_t, const std::string&) const; + + bool read_parameters(std::istream&, std::string&) const; + bool write_parameters(std::ostream&, const std::string&) const; + + // Input feature converter + LargePagePtr featureTransformer; + + // Evaluation function + AlignedPtr network; + + EvalFile evalFile; + EmbeddedNNUEType embeddedType; + + // Hash value of evaluation function structure + static constexpr std::uint32_t hash = Transformer::get_hash_value() ^ Arch::get_hash_value(); + + template + friend struct AccumulatorCaches::Cache; + + friend class AccumulatorStack; +}; + +// Definitions of the network types +using SmallFeatureTransformer = FeatureTransformer; +using SmallNetworkArchitecture = + NetworkArchitecture; + +using BigFeatureTransformer = FeatureTransformer; +using BigNetworkArchitecture = NetworkArchitecture; + +using NetworkBig = Network; +using NetworkSmall = Network; + + +struct Networks { + Networks(NetworkBig&& nB, NetworkSmall&& nS) : + big(std::move(nB)), + small(std::move(nS)) {} + + NetworkBig big; + NetworkSmall small; +}; + + +} // namespace Stockfish + +#endif diff --git a/src/nnue/network.o b/src/nnue/network.o new file mode 100644 index 0000000..d443d9f Binary files /dev/null and b/src/nnue/network.o differ diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp new file mode 100644 index 0000000..d13105a --- /dev/null +++ b/src/nnue/nnue_accumulator.cpp @@ -0,0 +1,531 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "nnue_accumulator.h" + +#include +#include +#include +#include + +#include "../bitboard.h" +#include "../misc.h" +#include "../position.h" +#include "../types.h" +#include "nnue_architecture.h" +#include "nnue_feature_transformer.h" // IWYU pragma: keep +#include "simd.h" + +namespace Stockfish::Eval::NNUE { + +using namespace SIMD; + +namespace { + +template +void double_inc_update(const FeatureTransformer& featureTransformer, + const Square ksq, + AccumulatorState& middle_state, + AccumulatorState& target_state, + const AccumulatorState& computed); + +template +void update_accumulator_incremental( + const FeatureTransformer& featureTransformer, + const Square ksq, + AccumulatorState& target_state, + const AccumulatorState& computed); + +template +void update_accumulator_refresh_cache(const FeatureTransformer& featureTransformer, + const Position& pos, + AccumulatorState& accumulatorState, + AccumulatorCaches::Cache& cache); + +} + +void AccumulatorState::reset(const DirtyPiece& dp) noexcept { + dirtyPiece = dp; + accumulatorBig.computed.fill(false); + accumulatorSmall.computed.fill(false); +} + +const AccumulatorState& AccumulatorStack::latest() const noexcept { return accumulators[size - 1]; } + +AccumulatorState& AccumulatorStack::mut_latest() noexcept { return accumulators[size - 1]; } + +void AccumulatorStack::reset() noexcept { + accumulators[0].reset({}); + size = 1; +} + +void AccumulatorStack::push(const DirtyPiece& dirtyPiece) noexcept { + assert(size + 1 < accumulators.size()); + accumulators[size].reset(dirtyPiece); + size++; +} + +void AccumulatorStack::pop() noexcept { + assert(size > 1); + size--; +} + +template +void AccumulatorStack::evaluate(const Position& pos, + const FeatureTransformer& featureTransformer, + AccumulatorCaches::Cache& cache) noexcept { + + evaluate_side(pos, featureTransformer, cache); + evaluate_side(pos, featureTransformer, cache); +} + +template +void AccumulatorStack::evaluate_side(const Position& pos, + const FeatureTransformer& featureTransformer, + AccumulatorCaches::Cache& cache) noexcept { + + const auto last_usable_accum = find_last_usable_accumulator(); + + if ((accumulators[last_usable_accum].template acc()).computed[Perspective]) + forward_update_incremental(pos, featureTransformer, last_usable_accum); + + else + { + update_accumulator_refresh_cache(featureTransformer, pos, mut_latest(), cache); + backward_update_incremental(pos, featureTransformer, last_usable_accum); + } +} + +// Find the earliest usable accumulator, this can either be a computed accumulator or the accumulator +// state just before a change that requires full refresh. +template +std::size_t AccumulatorStack::find_last_usable_accumulator() const noexcept { + + for (std::size_t curr_idx = size - 1; curr_idx > 0; curr_idx--) + { + if ((accumulators[curr_idx].template acc()).computed[Perspective]) + return curr_idx; + + if (FeatureSet::requires_refresh(accumulators[curr_idx].dirtyPiece, Perspective)) + return curr_idx; + } + + return 0; +} + +template +void AccumulatorStack::forward_update_incremental( + const Position& pos, + const FeatureTransformer& featureTransformer, + const std::size_t begin) noexcept { + + assert(begin < accumulators.size()); + assert((accumulators[begin].acc()).computed[Perspective]); + + const Square ksq = pos.square(Perspective); + + for (std::size_t next = begin + 1; next < size; next++) + { + if (next + 1 < size) + { + DirtyPiece& dp1 = accumulators[next].dirtyPiece; + DirtyPiece& dp2 = accumulators[next + 1].dirtyPiece; + + if (dp1.to != SQ_NONE && dp1.to == dp2.remove_sq) + { + const Square captureSq = dp1.to; + dp1.to = dp2.remove_sq = SQ_NONE; + double_inc_update(featureTransformer, ksq, accumulators[next], + accumulators[next + 1], accumulators[next - 1]); + dp1.to = dp2.remove_sq = captureSq; + + next++; + continue; + } + } + update_accumulator_incremental( + featureTransformer, ksq, accumulators[next], accumulators[next - 1]); + } + + assert((latest().acc()).computed[Perspective]); +} + +template +void AccumulatorStack::backward_update_incremental( + const Position& pos, + const FeatureTransformer& featureTransformer, + const std::size_t end) noexcept { + + assert(end < accumulators.size()); + assert(end < size); + assert((latest().acc()).computed[Perspective]); + + const Square ksq = pos.square(Perspective); + + for (std::int64_t next = std::int64_t(size) - 2; next >= std::int64_t(end); next--) + update_accumulator_incremental( + featureTransformer, ksq, accumulators[next], accumulators[next + 1]); + + assert((accumulators[end].acc()).computed[Perspective]); +} + +// Explicit template instantiations +template void AccumulatorStack::evaluate( + const Position& pos, + const FeatureTransformer& featureTransformer, + AccumulatorCaches::Cache& cache) noexcept; +template void AccumulatorStack::evaluate( + const Position& pos, + const FeatureTransformer& featureTransformer, + AccumulatorCaches::Cache& cache) noexcept; + + +namespace { + +template, bool> = true> +void fused_row_reduce(const ElementType* in, ElementType* out, const Ts* const... rows) { + constexpr IndexType size = Width * sizeof(ElementType) / sizeof(typename VectorWrapper::type); + + auto* vecIn = reinterpret_cast(in); + auto* vecOut = reinterpret_cast(out); + + for (IndexType i = 0; i < size; ++i) + vecOut[i] = fused( + vecIn[i], reinterpret_cast(rows)[i]...); +} + +template +struct AccumulatorUpdateContext { + const FeatureTransformer& featureTransformer; + const AccumulatorState& from; + AccumulatorState& to; + + AccumulatorUpdateContext(const FeatureTransformer& ft, + const AccumulatorState& accF, + AccumulatorState& accT) noexcept : + featureTransformer{ft}, + from{accF}, + to{accT} {} + + template, bool> = true> + void apply(const Ts... indices) { + auto to_weight_vector = [&](const IndexType index) { + return &featureTransformer.weights[index * Dimensions]; + }; + + auto to_psqt_weight_vector = [&](const IndexType index) { + return &featureTransformer.psqtWeights[index * PSQTBuckets]; + }; + + fused_row_reduce( + (from.acc()).accumulation[Perspective], + (to.acc()).accumulation[Perspective], to_weight_vector(indices)...); + + fused_row_reduce( + (from.acc()).psqtAccumulation[Perspective], + (to.acc()).psqtAccumulation[Perspective], to_psqt_weight_vector(indices)...); + } +}; + +template +auto make_accumulator_update_context(const FeatureTransformer& featureTransformer, + const AccumulatorState& accumulatorFrom, + AccumulatorState& accumulatorTo) noexcept { + return AccumulatorUpdateContext{featureTransformer, accumulatorFrom, + accumulatorTo}; +} + +template +void double_inc_update(const FeatureTransformer& featureTransformer, + const Square ksq, + AccumulatorState& middle_state, + AccumulatorState& target_state, + const AccumulatorState& computed) { + + assert(computed.acc().computed[Perspective]); + assert(!middle_state.acc().computed[Perspective]); + assert(!target_state.acc().computed[Perspective]); + + FeatureSet::IndexList removed, added; + FeatureSet::append_changed_indices(ksq, middle_state.dirtyPiece, removed, added); + // you can't capture a piece that was just involved in castling since the rook ends up + // in a square that the king passed + assert(added.size() < 2); + FeatureSet::append_changed_indices(ksq, target_state.dirtyPiece, removed, added); + + assert(added.size() == 1); + assert(removed.size() == 2 || removed.size() == 3); + + // Workaround compiler warning for uninitialized variables, replicated on + // profile builds on windows with gcc 14.2.0. + // TODO remove once unneeded + sf_assume(added.size() == 1); + sf_assume(removed.size() == 2 || removed.size() == 3); + + auto updateContext = + make_accumulator_update_context(featureTransformer, computed, target_state); + + if (removed.size() == 2) + { + updateContext.template apply(added[0], removed[0], removed[1]); + } + else + { + updateContext.template apply(added[0], removed[0], removed[1], + removed[2]); + } + + target_state.acc().computed[Perspective] = true; +} + +template +void update_accumulator_incremental( + const FeatureTransformer& featureTransformer, + const Square ksq, + AccumulatorState& target_state, + const AccumulatorState& computed) { + + assert((computed.acc()).computed[Perspective]); + assert(!(target_state.acc()).computed[Perspective]); + + // The size must be enough to contain the largest possible update. + // That might depend on the feature set and generally relies on the + // feature set's update cost calculation to be correct and never allow + // updates with more added/removed features than MaxActiveDimensions. + // In this case, the maximum size of both feature addition and removal + // is 2, since we are incrementally updating one move at a time. + FeatureSet::IndexList removed, added; + if constexpr (Forward) + FeatureSet::append_changed_indices(ksq, target_state.dirtyPiece, removed, + added); + else + FeatureSet::append_changed_indices(ksq, computed.dirtyPiece, added, removed); + + assert(added.size() == 1 || added.size() == 2); + assert(removed.size() == 1 || removed.size() == 2); + assert((Forward && added.size() <= removed.size()) + || (!Forward && added.size() >= removed.size())); + + // Workaround compiler warning for uninitialized variables, replicated on + // profile builds on windows with gcc 14.2.0. + // TODO remove once unneeded + sf_assume(added.size() == 1 || added.size() == 2); + sf_assume(removed.size() == 1 || removed.size() == 2); + + auto updateContext = + make_accumulator_update_context(featureTransformer, computed, target_state); + + if ((Forward && removed.size() == 1) || (!Forward && added.size() == 1)) + { + assert(added.size() == 1 && removed.size() == 1); + updateContext.template apply(added[0], removed[0]); + } + else if (Forward && added.size() == 1) + { + assert(removed.size() == 2); + updateContext.template apply(added[0], removed[0], removed[1]); + } + else if (!Forward && removed.size() == 1) + { + assert(added.size() == 2); + updateContext.template apply(added[0], added[1], removed[0]); + } + else + { + assert(added.size() == 2 && removed.size() == 2); + updateContext.template apply(added[0], added[1], removed[0], + removed[1]); + } + + (target_state.acc()).computed[Perspective] = true; +} + +template +void update_accumulator_refresh_cache(const FeatureTransformer& featureTransformer, + const Position& pos, + AccumulatorState& accumulatorState, + AccumulatorCaches::Cache& cache) { + + using Tiling [[maybe_unused]] = SIMDTiling; + + const Square ksq = pos.square(Perspective); + auto& entry = cache[ksq][Perspective]; + FeatureSet::IndexList removed, added; + + for (Color c : {WHITE, BLACK}) + { + for (PieceType pt = PAWN; pt <= KING; ++pt) + { + const Piece piece = make_piece(c, pt); + const Bitboard oldBB = entry.byColorBB[c] & entry.byTypeBB[pt]; + const Bitboard newBB = pos.pieces(c, pt); + Bitboard toRemove = oldBB & ~newBB; + Bitboard toAdd = newBB & ~oldBB; + + while (toRemove) + { + Square sq = pop_lsb(toRemove); + removed.push_back(FeatureSet::make_index(sq, piece, ksq)); + } + while (toAdd) + { + Square sq = pop_lsb(toAdd); + added.push_back(FeatureSet::make_index(sq, piece, ksq)); + } + } + } + + auto& accumulator = accumulatorState.acc(); + accumulator.computed[Perspective] = true; + +#ifdef VECTOR + vec_t acc[Tiling::NumRegs]; + psqt_vec_t psqt[Tiling::NumPsqtRegs]; + + for (IndexType j = 0; j < Dimensions / Tiling::TileHeight; ++j) + { + auto* accTile = + reinterpret_cast(&accumulator.accumulation[Perspective][j * Tiling::TileHeight]); + auto* entryTile = reinterpret_cast(&entry.accumulation[j * Tiling::TileHeight]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = entryTile[k]; + + IndexType i = 0; + for (; i < std::min(removed.size(), added.size()); ++i) + { + IndexType indexR = removed[i]; + const IndexType offsetR = Dimensions * indexR + j * Tiling::TileHeight; + auto* columnR = reinterpret_cast(&featureTransformer.weights[offsetR]); + IndexType indexA = added[i]; + const IndexType offsetA = Dimensions * indexA + j * Tiling::TileHeight; + auto* columnA = reinterpret_cast(&featureTransformer.weights[offsetA]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = fused(acc[k], columnA[k], columnR[k]); + } + for (; i < removed.size(); ++i) + { + IndexType index = removed[i]; + const IndexType offset = Dimensions * index + j * Tiling::TileHeight; + auto* column = reinterpret_cast(&featureTransformer.weights[offset]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_sub_16(acc[k], column[k]); + } + for (; i < added.size(); ++i) + { + IndexType index = added[i]; + const IndexType offset = Dimensions * index + j * Tiling::TileHeight; + auto* column = reinterpret_cast(&featureTransformer.weights[offset]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } + + for (IndexType k = 0; k < Tiling::NumRegs; k++) + vec_store(&entryTile[k], acc[k]); + for (IndexType k = 0; k < Tiling::NumRegs; k++) + vec_store(&accTile[k], acc[k]); + } + + for (IndexType j = 0; j < PSQTBuckets / Tiling::PsqtTileHeight; ++j) + { + auto* accTilePsqt = reinterpret_cast( + &accumulator.psqtAccumulation[Perspective][j * Tiling::PsqtTileHeight]); + auto* entryTilePsqt = + reinterpret_cast(&entry.psqtAccumulation[j * Tiling::PsqtTileHeight]); + + for (IndexType k = 0; k < Tiling::NumPsqtRegs; ++k) + psqt[k] = entryTilePsqt[k]; + + for (IndexType i = 0; i < removed.size(); ++i) + { + IndexType index = removed[i]; + const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; + auto* columnPsqt = + reinterpret_cast(&featureTransformer.psqtWeights[offset]); + + for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) + psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); + } + for (IndexType i = 0; i < added.size(); ++i) + { + IndexType index = added[i]; + const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; + auto* columnPsqt = + reinterpret_cast(&featureTransformer.psqtWeights[offset]); + + for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) + psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); + } + + for (IndexType k = 0; k < Tiling::NumPsqtRegs; ++k) + vec_store_psqt(&entryTilePsqt[k], psqt[k]); + for (IndexType k = 0; k < Tiling::NumPsqtRegs; ++k) + vec_store_psqt(&accTilePsqt[k], psqt[k]); + } + +#else + + for (const auto index : removed) + { + const IndexType offset = Dimensions * index; + for (IndexType j = 0; j < Dimensions; ++j) + entry.accumulation[j] -= featureTransformer.weights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + entry.psqtAccumulation[k] -= featureTransformer.psqtWeights[index * PSQTBuckets + k]; + } + for (const auto index : added) + { + const IndexType offset = Dimensions * index; + for (IndexType j = 0; j < Dimensions; ++j) + entry.accumulation[j] += featureTransformer.weights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + entry.psqtAccumulation[k] += featureTransformer.psqtWeights[index * PSQTBuckets + k]; + } + + // The accumulator of the refresh entry has been updated. + // Now copy its content to the actual accumulator we were refreshing. + + std::memcpy(accumulator.accumulation[Perspective], entry.accumulation, + sizeof(BiasType) * Dimensions); + + std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation, + sizeof(int32_t) * PSQTBuckets); +#endif + + for (Color c : {WHITE, BLACK}) + entry.byColorBB[c] = pos.pieces(c); + + for (PieceType pt = PAWN; pt <= KING; ++pt) + entry.byTypeBB[pt] = pos.pieces(pt); +} + +} + +} diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h new file mode 100644 index 0000000..10aadc9 --- /dev/null +++ b/src/nnue/nnue_accumulator.h @@ -0,0 +1,187 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Class for difference calculation of NNUE evaluation function + +#ifndef NNUE_ACCUMULATOR_H_INCLUDED +#define NNUE_ACCUMULATOR_H_INCLUDED + +#include +#include +#include +#include +#include + +#include "../types.h" +#include "nnue_architecture.h" +#include "nnue_common.h" + +namespace Stockfish { +class Position; +} + +namespace Stockfish::Eval::NNUE { + +template +struct alignas(CacheLineSize) Accumulator; + +template +class FeatureTransformer; + +// Class that holds the result of affine transformation of input features +template +struct alignas(CacheLineSize) Accumulator { + std::int16_t accumulation[COLOR_NB][Size]; + std::int32_t psqtAccumulation[COLOR_NB][PSQTBuckets]; + std::array computed; +}; + + +// AccumulatorCaches struct provides per-thread accumulator caches, where each +// cache contains multiple entries for each of the possible king squares. +// When the accumulator needs to be refreshed, the cached entry is used to more +// efficiently update the accumulator, instead of rebuilding it from scratch. +// This idea, was first described by Luecx (author of Koivisto) and +// is commonly referred to as "Finny Tables". +struct AccumulatorCaches { + + template + AccumulatorCaches(const Networks& networks) { + clear(networks); + } + + template + struct alignas(CacheLineSize) Cache { + + struct alignas(CacheLineSize) Entry { + BiasType accumulation[Size]; + PSQTWeightType psqtAccumulation[PSQTBuckets]; + Bitboard byColorBB[COLOR_NB]; + Bitboard byTypeBB[PIECE_TYPE_NB]; + + // To initialize a refresh entry, we set all its bitboards empty, + // so we put the biases in the accumulation, without any weights on top + void clear(const BiasType* biases) { + + std::memcpy(accumulation, biases, sizeof(accumulation)); + std::memset((uint8_t*) this + offsetof(Entry, psqtAccumulation), 0, + sizeof(Entry) - offsetof(Entry, psqtAccumulation)); + } + }; + + template + void clear(const Network& network) { + for (auto& entries1D : entries) + for (auto& entry : entries1D) + entry.clear(network.featureTransformer->biases); + } + + std::array& operator[](Square sq) { return entries[sq]; } + + std::array, SQUARE_NB> entries; + }; + + template + void clear(const Networks& networks) { + big.clear(networks.big); + small.clear(networks.small); + } + + Cache big; + Cache small; +}; + + +struct AccumulatorState { + Accumulator accumulatorBig; + Accumulator accumulatorSmall; + DirtyPiece dirtyPiece; + + template + auto& acc() noexcept { + static_assert(Size == TransformedFeatureDimensionsBig + || Size == TransformedFeatureDimensionsSmall, + "Invalid size for accumulator"); + + if constexpr (Size == TransformedFeatureDimensionsBig) + return accumulatorBig; + else if constexpr (Size == TransformedFeatureDimensionsSmall) + return accumulatorSmall; + } + + template + const auto& acc() const noexcept { + static_assert(Size == TransformedFeatureDimensionsBig + || Size == TransformedFeatureDimensionsSmall, + "Invalid size for accumulator"); + + if constexpr (Size == TransformedFeatureDimensionsBig) + return accumulatorBig; + else if constexpr (Size == TransformedFeatureDimensionsSmall) + return accumulatorSmall; + } + + void reset(const DirtyPiece& dp) noexcept; +}; + + +class AccumulatorStack { + public: + AccumulatorStack() : + accumulators(MAX_PLY + 1), + size{1} {} + + [[nodiscard]] const AccumulatorState& latest() const noexcept; + + void reset() noexcept; + void push(const DirtyPiece& dirtyPiece) noexcept; + void pop() noexcept; + + template + void evaluate(const Position& pos, + const FeatureTransformer& featureTransformer, + AccumulatorCaches::Cache& cache) noexcept; + + private: + [[nodiscard]] AccumulatorState& mut_latest() noexcept; + + template + void evaluate_side(const Position& pos, + const FeatureTransformer& featureTransformer, + AccumulatorCaches::Cache& cache) noexcept; + + template + [[nodiscard]] std::size_t find_last_usable_accumulator() const noexcept; + + template + void forward_update_incremental(const Position& pos, + const FeatureTransformer& featureTransformer, + const std::size_t begin) noexcept; + + template + void backward_update_incremental(const Position& pos, + const FeatureTransformer& featureTransformer, + const std::size_t end) noexcept; + + std::vector accumulators; + std::size_t size; +}; + +} // namespace Stockfish::Eval::NNUE + +#endif // NNUE_ACCUMULATOR_H_INCLUDED diff --git a/src/nnue/nnue_accumulator.o b/src/nnue/nnue_accumulator.o new file mode 100644 index 0000000..587a4e5 Binary files /dev/null and b/src/nnue/nnue_accumulator.o differ diff --git a/src/nnue/nnue_architecture.h b/src/nnue/nnue_architecture.h new file mode 100644 index 0000000..c020ce0 --- /dev/null +++ b/src/nnue/nnue_architecture.h @@ -0,0 +1,143 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Input features and network structure used in NNUE evaluation function + +#ifndef NNUE_ARCHITECTURE_H_INCLUDED +#define NNUE_ARCHITECTURE_H_INCLUDED + +#include +#include +#include + +#include "features/half_ka_v2_hm.h" +#include "layers/affine_transform.h" +#include "layers/affine_transform_sparse_input.h" +#include "layers/clipped_relu.h" +#include "layers/sqr_clipped_relu.h" +#include "nnue_common.h" + +namespace Stockfish::Eval::NNUE { + +// Input features used in evaluation function +using FeatureSet = Features::HalfKAv2_hm; + +// Number of input feature dimensions after conversion +constexpr IndexType TransformedFeatureDimensionsBig = 3072; +constexpr int L2Big = 15; +constexpr int L3Big = 32; + +constexpr IndexType TransformedFeatureDimensionsSmall = 128; +constexpr int L2Small = 15; +constexpr int L3Small = 32; + +constexpr IndexType PSQTBuckets = 8; +constexpr IndexType LayerStacks = 8; + +// If vector instructions are enabled, we update and refresh the +// accumulator tile by tile such that each tile fits in the CPU's +// vector registers. +static_assert(PSQTBuckets % 8 == 0, + "Per feature PSQT values cannot be processed at granularity lower than 8 at a time."); + +template +struct NetworkArchitecture { + static constexpr IndexType TransformedFeatureDimensions = L1; + static constexpr int FC_0_OUTPUTS = L2; + static constexpr int FC_1_OUTPUTS = L3; + + Layers::AffineTransformSparseInput fc_0; + Layers::SqrClippedReLU ac_sqr_0; + Layers::ClippedReLU ac_0; + Layers::AffineTransform fc_1; + Layers::ClippedReLU ac_1; + Layers::AffineTransform fc_2; + + // Hash value embedded in the evaluation file + static constexpr std::uint32_t get_hash_value() { + // input slice hash + std::uint32_t hashValue = 0xEC42E90Du; + hashValue ^= TransformedFeatureDimensions * 2; + + hashValue = decltype(fc_0)::get_hash_value(hashValue); + hashValue = decltype(ac_0)::get_hash_value(hashValue); + hashValue = decltype(fc_1)::get_hash_value(hashValue); + hashValue = decltype(ac_1)::get_hash_value(hashValue); + hashValue = decltype(fc_2)::get_hash_value(hashValue); + + return hashValue; + } + + // Read network parameters + bool read_parameters(std::istream& stream) { + return fc_0.read_parameters(stream) && ac_0.read_parameters(stream) + && fc_1.read_parameters(stream) && ac_1.read_parameters(stream) + && fc_2.read_parameters(stream); + } + + // Write network parameters + bool write_parameters(std::ostream& stream) const { + return fc_0.write_parameters(stream) && ac_0.write_parameters(stream) + && fc_1.write_parameters(stream) && ac_1.write_parameters(stream) + && fc_2.write_parameters(stream); + } + + std::int32_t propagate(const TransformedFeatureType* transformedFeatures) { + struct alignas(CacheLineSize) Buffer { + alignas(CacheLineSize) typename decltype(fc_0)::OutputBuffer fc_0_out; + alignas(CacheLineSize) typename decltype(ac_sqr_0)::OutputType + ac_sqr_0_out[ceil_to_multiple(FC_0_OUTPUTS * 2, 32)]; + alignas(CacheLineSize) typename decltype(ac_0)::OutputBuffer ac_0_out; + alignas(CacheLineSize) typename decltype(fc_1)::OutputBuffer fc_1_out; + alignas(CacheLineSize) typename decltype(ac_1)::OutputBuffer ac_1_out; + alignas(CacheLineSize) typename decltype(fc_2)::OutputBuffer fc_2_out; + + Buffer() { std::memset(this, 0, sizeof(*this)); } + }; + +#if defined(__clang__) && (__APPLE__) + // workaround for a bug reported with xcode 12 + static thread_local auto tlsBuffer = std::make_unique(); + // Access TLS only once, cache result. + Buffer& buffer = *tlsBuffer; +#else + alignas(CacheLineSize) static thread_local Buffer buffer; +#endif + + fc_0.propagate(transformedFeatures, buffer.fc_0_out); + ac_sqr_0.propagate(buffer.fc_0_out, buffer.ac_sqr_0_out); + ac_0.propagate(buffer.fc_0_out, buffer.ac_0_out); + std::memcpy(buffer.ac_sqr_0_out + FC_0_OUTPUTS, buffer.ac_0_out, + FC_0_OUTPUTS * sizeof(typename decltype(ac_0)::OutputType)); + fc_1.propagate(buffer.ac_sqr_0_out, buffer.fc_1_out); + ac_1.propagate(buffer.fc_1_out, buffer.ac_1_out); + fc_2.propagate(buffer.ac_1_out, buffer.fc_2_out); + + // buffer.fc_0_out[FC_0_OUTPUTS] is such that 1.0 is equal to 127*(1<. +*/ + +// Constants used in NNUE evaluation function + +#ifndef NNUE_COMMON_H_INCLUDED +#define NNUE_COMMON_H_INCLUDED + +#include +#include +#include +#include +#include +#include +#include "../misc.h" + +#if defined(USE_AVX2) + #include + +#elif defined(USE_SSE41) + #include + +#elif defined(USE_SSSE3) + #include + +#elif defined(USE_SSE2) + #include + +#elif defined(USE_NEON) + #include +#endif + +namespace Stockfish::Eval::NNUE { + +using BiasType = std::int16_t; +using WeightType = std::int16_t; +using PSQTWeightType = std::int32_t; +using IndexType = std::uint32_t; + +// Version of the evaluation file +constexpr std::uint32_t Version = 0x7AF32F20u; + +// Constant used in evaluation value calculation +constexpr int OutputScale = 16; +constexpr int WeightScaleBits = 6; + +// Size of cache line (in bytes) +constexpr std::size_t CacheLineSize = 64; + +constexpr const char Leb128MagicString[] = "COMPRESSED_LEB128"; +constexpr const std::size_t Leb128MagicStringSize = sizeof(Leb128MagicString) - 1; + +// SIMD width (in bytes) +#if defined(USE_AVX2) +constexpr std::size_t SimdWidth = 32; + +#elif defined(USE_SSE2) +constexpr std::size_t SimdWidth = 16; + +#elif defined(USE_NEON) +constexpr std::size_t SimdWidth = 16; +#endif + +constexpr std::size_t MaxSimdWidth = 32; + +// Type of input feature after conversion +using TransformedFeatureType = std::uint8_t; +using IndexType = std::uint32_t; + +// Round n up to be a multiple of base +template +constexpr IntType ceil_to_multiple(IntType n, IntType base) { + return (n + base - 1) / base * base; +} + + +// Utility to read an integer (signed or unsigned, any size) +// from a stream in little-endian order. We swap the byte order after the read if +// necessary to return a result with the byte ordering of the compiling machine. +template +inline IntType read_little_endian(std::istream& stream) { + IntType result; + + if (IsLittleEndian) + stream.read(reinterpret_cast(&result), sizeof(IntType)); + else + { + std::uint8_t u[sizeof(IntType)]; + std::make_unsigned_t v = 0; + + stream.read(reinterpret_cast(u), sizeof(IntType)); + for (std::size_t i = 0; i < sizeof(IntType); ++i) + v = (v << 8) | u[sizeof(IntType) - i - 1]; + + std::memcpy(&result, &v, sizeof(IntType)); + } + + return result; +} + + +// Utility to write an integer (signed or unsigned, any size) +// to a stream in little-endian order. We swap the byte order before the write if +// necessary to always write in little-endian order, independently of the byte +// ordering of the compiling machine. +template +inline void write_little_endian(std::ostream& stream, IntType value) { + + if (IsLittleEndian) + stream.write(reinterpret_cast(&value), sizeof(IntType)); + else + { + std::uint8_t u[sizeof(IntType)]; + std::make_unsigned_t v = value; + + std::size_t i = 0; + // if constexpr to silence the warning about shift by 8 + if constexpr (sizeof(IntType) > 1) + { + for (; i + 1 < sizeof(IntType); ++i) + { + u[i] = std::uint8_t(v); + v >>= 8; + } + } + u[i] = std::uint8_t(v); + + stream.write(reinterpret_cast(u), sizeof(IntType)); + } +} + + +// Read integers in bulk from a little-endian stream. +// This reads N integers from stream s and puts them in array out. +template +inline void read_little_endian(std::istream& stream, IntType* out, std::size_t count) { + if (IsLittleEndian) + stream.read(reinterpret_cast(out), sizeof(IntType) * count); + else + for (std::size_t i = 0; i < count; ++i) + out[i] = read_little_endian(stream); +} + + +// Write integers in bulk to a little-endian stream. +// This takes N integers from array values and writes them on stream s. +template +inline void write_little_endian(std::ostream& stream, const IntType* values, std::size_t count) { + if (IsLittleEndian) + stream.write(reinterpret_cast(values), sizeof(IntType) * count); + else + for (std::size_t i = 0; i < count; ++i) + write_little_endian(stream, values[i]); +} + + +// Read N signed integers from the stream s, putting them in the array out. +// The stream is assumed to be compressed using the signed LEB128 format. +// See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme. +template +inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count) { + + // Check the presence of our LEB128 magic string + char leb128MagicString[Leb128MagicStringSize]; + stream.read(leb128MagicString, Leb128MagicStringSize); + assert(strncmp(Leb128MagicString, leb128MagicString, Leb128MagicStringSize) == 0); + + static_assert(std::is_signed_v, "Not implemented for unsigned types"); + + const std::uint32_t BUF_SIZE = 4096; + std::uint8_t buf[BUF_SIZE]; + + auto bytes_left = read_little_endian(stream); + + std::uint32_t buf_pos = BUF_SIZE; + for (std::size_t i = 0; i < count; ++i) + { + IntType result = 0; + size_t shift = 0; + do + { + if (buf_pos == BUF_SIZE) + { + stream.read(reinterpret_cast(buf), std::min(bytes_left, BUF_SIZE)); + buf_pos = 0; + } + + std::uint8_t byte = buf[buf_pos++]; + --bytes_left; + result |= (byte & 0x7f) << shift; + shift += 7; + + if ((byte & 0x80) == 0) + { + out[i] = (sizeof(IntType) * 8 <= shift || (byte & 0x40) == 0) + ? result + : result | ~((1 << shift) - 1); + break; + } + } while (shift < sizeof(IntType) * 8); + } + + assert(bytes_left == 0); +} + + +// Write signed integers to a stream with LEB128 compression. +// This takes N integers from array values, compresses them with +// the LEB128 algorithm and writes the result on the stream s. +// See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme. +template +inline void write_leb_128(std::ostream& stream, const IntType* values, std::size_t count) { + + // Write our LEB128 magic string + stream.write(Leb128MagicString, Leb128MagicStringSize); + + static_assert(std::is_signed_v, "Not implemented for unsigned types"); + + std::uint32_t byte_count = 0; + for (std::size_t i = 0; i < count; ++i) + { + IntType value = values[i]; + std::uint8_t byte; + do + { + byte = value & 0x7f; + value >>= 7; + ++byte_count; + } while ((byte & 0x40) == 0 ? value != 0 : value != -1); + } + + write_little_endian(stream, byte_count); + + const std::uint32_t BUF_SIZE = 4096; + std::uint8_t buf[BUF_SIZE]; + std::uint32_t buf_pos = 0; + + auto flush = [&]() { + if (buf_pos > 0) + { + stream.write(reinterpret_cast(buf), buf_pos); + buf_pos = 0; + } + }; + + auto write = [&](std::uint8_t byte) { + buf[buf_pos++] = byte; + if (buf_pos == BUF_SIZE) + flush(); + }; + + for (std::size_t i = 0; i < count; ++i) + { + IntType value = values[i]; + while (true) + { + std::uint8_t byte = value & 0x7f; + value >>= 7; + if ((byte & 0x40) == 0 ? value == 0 : value == -1) + { + write(byte); + break; + } + write(byte | 0x80); + } + } + + flush(); +} + +} // namespace Stockfish::Eval::NNUE + +#endif // #ifndef NNUE_COMMON_H_INCLUDED diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h new file mode 100644 index 0000000..37634cb --- /dev/null +++ b/src/nnue/nnue_feature_transformer.h @@ -0,0 +1,312 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// A class that converts the input features of the NNUE evaluation function + +#ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED +#define NNUE_FEATURE_TRANSFORMER_H_INCLUDED + +#include +#include +#include +#include + +#include "../position.h" +#include "../types.h" +#include "nnue_accumulator.h" +#include "nnue_architecture.h" +#include "nnue_common.h" +#include "simd.h" + +namespace Stockfish::Eval::NNUE { + +// Returns the inverse of a permutation +template +constexpr std::array +invert_permutation(const std::array& order) { + std::array inverse{}; + for (std::size_t i = 0; i < order.size(); i++) + inverse[order[i]] = i; + return inverse; +} + +// Divide a byte region of size TotalSize to chunks of size +// BlockSize, and permute the blocks by a given order +template +void permute(T (&data)[N], const std::array& order) { + constexpr std::size_t TotalSize = N * sizeof(T); + + static_assert(TotalSize % (BlockSize * OrderSize) == 0, + "ChunkSize * OrderSize must perfectly divide TotalSize"); + + constexpr std::size_t ProcessChunkSize = BlockSize * OrderSize; + + std::array buffer{}; + + std::byte* const bytes = reinterpret_cast(data); + + for (std::size_t i = 0; i < TotalSize; i += ProcessChunkSize) + { + std::byte* const values = &bytes[i]; + + for (std::size_t j = 0; j < OrderSize; j++) + { + auto* const buffer_chunk = &buffer[j * BlockSize]; + auto* const value_chunk = &values[order[j] * BlockSize]; + + std::copy(value_chunk, value_chunk + BlockSize, buffer_chunk); + } + + std::copy(std::begin(buffer), std::end(buffer), values); + } +} + +// Input feature converter +template +class FeatureTransformer { + + // Number of output dimensions for one side + static constexpr IndexType HalfDimensions = TransformedFeatureDimensions; + + public: + // Output type + using OutputType = TransformedFeatureType; + + // Number of input/output dimensions + static constexpr IndexType InputDimensions = FeatureSet::Dimensions; + static constexpr IndexType OutputDimensions = HalfDimensions; + + // Size of forward propagation buffer + static constexpr std::size_t BufferSize = OutputDimensions * sizeof(OutputType); + + // Store the order by which 128-bit blocks of a 1024-bit data must + // be permuted so that calling packus on adjacent vectors of 16-bit + // integers loaded from the data results in the pre-permutation order + static constexpr auto PackusEpi16Order = []() -> std::array { +#if defined(USE_AVX512) + // _mm512_packus_epi16 after permutation: + // | 0 | 2 | 4 | 6 | // Vector 0 + // | 1 | 3 | 5 | 7 | // Vector 1 + // | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | // Packed Result + return {0, 2, 4, 6, 1, 3, 5, 7}; +#elif defined(USE_AVX2) + // _mm256_packus_epi16 after permutation: + // | 0 | 2 | | 4 | 6 | // Vector 0, 2 + // | 1 | 3 | | 5 | 7 | // Vector 1, 3 + // | 0 | 1 | 2 | 3 | | 4 | 5 | 6 | 7 | // Packed Result + return {0, 2, 1, 3, 4, 6, 5, 7}; +#else + return {0, 1, 2, 3, 4, 5, 6, 7}; +#endif + }(); + + static constexpr auto InversePackusEpi16Order = invert_permutation(PackusEpi16Order); + + // Hash value embedded in the evaluation file + static constexpr std::uint32_t get_hash_value() { + return FeatureSet::HashValue ^ (OutputDimensions * 2); + } + + void permute_weights() { + permute<16>(biases, PackusEpi16Order); + permute<16>(weights, PackusEpi16Order); + } + + void unpermute_weights() { + permute<16>(biases, InversePackusEpi16Order); + permute<16>(weights, InversePackusEpi16Order); + } + + inline void scale_weights(bool read) { + for (IndexType j = 0; j < InputDimensions; ++j) + { + WeightType* w = &weights[j * HalfDimensions]; + for (IndexType i = 0; i < HalfDimensions; ++i) + w[i] = read ? w[i] * 2 : w[i] / 2; + } + + for (IndexType i = 0; i < HalfDimensions; ++i) + biases[i] = read ? biases[i] * 2 : biases[i] / 2; + } + + // Read network parameters + bool read_parameters(std::istream& stream) { + + read_leb_128(stream, biases, HalfDimensions); + read_leb_128(stream, weights, HalfDimensions * InputDimensions); + read_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); + + permute_weights(); + scale_weights(true); + return !stream.fail(); + } + + // Write network parameters + bool write_parameters(std::ostream& stream) { + + unpermute_weights(); + scale_weights(false); + + write_leb_128(stream, biases, HalfDimensions); + write_leb_128(stream, weights, HalfDimensions * InputDimensions); + write_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); + + permute_weights(); + scale_weights(true); + return !stream.fail(); + } + + // Convert input features + std::int32_t transform(const Position& pos, + AccumulatorStack& accumulatorStack, + AccumulatorCaches::Cache* cache, + OutputType* output, + int bucket) const { + + using namespace SIMD; + + accumulatorStack.evaluate(pos, *this, *cache); + const auto& accumulatorState = accumulatorStack.latest(); + + const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()}; + const auto& psqtAccumulation = (accumulatorState.acc()).psqtAccumulation; + const auto psqt = + (psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket]) + / 2; + + const auto& accumulation = (accumulatorState.acc()).accumulation; + + for (IndexType p = 0; p < 2; ++p) + { + const IndexType offset = (HalfDimensions / 2) * p; + +#if defined(VECTOR) + + constexpr IndexType OutputChunkSize = MaxChunkSize; + static_assert((HalfDimensions / 2) % OutputChunkSize == 0); + constexpr IndexType NumOutputChunks = HalfDimensions / 2 / OutputChunkSize; + + const vec_t Zero = vec_zero(); + const vec_t One = vec_set_16(127 * 2); + + const vec_t* in0 = reinterpret_cast(&(accumulation[perspectives[p]][0])); + const vec_t* in1 = + reinterpret_cast(&(accumulation[perspectives[p]][HalfDimensions / 2])); + vec_t* out = reinterpret_cast(output + offset); + + // Per the NNUE architecture, here we want to multiply pairs of + // clipped elements and divide the product by 128. To do this, + // we can naively perform min/max operation to clip each of the + // four int16 vectors, mullo pairs together, then pack them into + // one int8 vector. However, there exists a faster way. + + // The idea here is to use the implicit clipping from packus to + // save us two vec_max_16 instructions. This clipping works due + // to the fact that any int16 integer below zero will be zeroed + // on packus. + + // Consider the case where the second element is negative. + // If we do standard clipping, that element will be zero, which + // means our pairwise product is zero. If we perform packus and + // remove the lower-side clip for the second element, then our + // product before packus will be negative, and is zeroed on pack. + // The two operation produce equivalent results, but the second + // one (using packus) saves one max operation per pair. + + // But here we run into a problem: mullo does not preserve the + // sign of the multiplication. We can get around this by doing + // mulhi, which keeps the sign. But that requires an additional + // tweak. + + // mulhi cuts off the last 16 bits of the resulting product, + // which is the same as performing a rightward shift of 16 bits. + // We can use this to our advantage. Recall that we want to + // divide the final product by 128, which is equivalent to a + // 7-bit right shift. Intuitively, if we shift the clipped + // value left by 9, and perform mulhi, which shifts the product + // right by 16 bits, then we will net a right shift of 7 bits. + // However, this won't work as intended. Since we clip the + // values to have a maximum value of 127, shifting it by 9 bits + // might occupy the signed bit, resulting in some positive + // values being interpreted as negative after the shift. + + // There is a way, however, to get around this limitation. When + // loading the network, scale accumulator weights and biases by + // 2. To get the same pairwise multiplication result as before, + // we need to divide the product by 128 * 2 * 2 = 512, which + // amounts to a right shift of 9 bits. So now we only have to + // shift left by 7 bits, perform mulhi (shifts right by 16 bits) + // and net a 9 bit right shift. Since we scaled everything by + // two, the values are clipped at 127 * 2 = 254, which occupies + // 8 bits. Shifting it by 7 bits left will no longer occupy the + // signed bit, so we are safe. + + // Note that on NEON processors, we shift left by 6 instead + // because the instruction "vqdmulhq_s16" also doubles the + // return value after the multiplication, adding an extra shift + // to the left by 1, so we compensate by shifting less before + // the multiplication. + + constexpr int shift = + #if defined(USE_SSE2) + 7; + #else + 6; + #endif + + for (IndexType j = 0; j < NumOutputChunks; ++j) + { + const vec_t sum0a = + vec_slli_16(vec_max_16(vec_min_16(in0[j * 2 + 0], One), Zero), shift); + const vec_t sum0b = + vec_slli_16(vec_max_16(vec_min_16(in0[j * 2 + 1], One), Zero), shift); + const vec_t sum1a = vec_min_16(in1[j * 2 + 0], One); + const vec_t sum1b = vec_min_16(in1[j * 2 + 1], One); + + const vec_t pa = vec_mulhi_16(sum0a, sum1a); + const vec_t pb = vec_mulhi_16(sum0b, sum1b); + + out[j] = vec_packus_16(pa, pb); + } + +#else + + for (IndexType j = 0; j < HalfDimensions / 2; ++j) + { + BiasType sum0 = accumulation[static_cast(perspectives[p])][j + 0]; + BiasType sum1 = + accumulation[static_cast(perspectives[p])][j + HalfDimensions / 2]; + sum0 = std::clamp(sum0, 0, 127 * 2); + sum1 = std::clamp(sum1, 0, 127 * 2); + output[offset + j] = static_cast(unsigned(sum0 * sum1) / 512); + } + +#endif + } + + return psqt; + } // end of function transform() + + alignas(CacheLineSize) BiasType biases[HalfDimensions]; + alignas(CacheLineSize) WeightType weights[HalfDimensions * InputDimensions]; + alignas(CacheLineSize) PSQTWeightType psqtWeights[InputDimensions * PSQTBuckets]; +}; + +} // namespace Stockfish::Eval::NNUE + +#endif // #ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp new file mode 100644 index 0000000..c998740 --- /dev/null +++ b/src/nnue/nnue_misc.cpp @@ -0,0 +1,193 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Code for calculating NNUE evaluation function + +#include "nnue_misc.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../position.h" +#include "../types.h" +#include "../uci.h" +#include "network.h" +#include "nnue_accumulator.h" + +namespace Stockfish::Eval::NNUE { + + +constexpr std::string_view PieceToChar(" PNBRQK pnbrqk"); + + +namespace { +// Converts a Value into (centi)pawns and writes it in a buffer. +// The buffer must have capacity for at least 5 chars. +void format_cp_compact(Value v, char* buffer, const Position& pos) { + + buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' '); + + int cp = std::abs(UCIEngine::to_cp(v, pos)); + if (cp >= 10000) + { + buffer[1] = '0' + cp / 10000; + cp %= 10000; + buffer[2] = '0' + cp / 1000; + cp %= 1000; + buffer[3] = '0' + cp / 100; + buffer[4] = ' '; + } + else if (cp >= 1000) + { + buffer[1] = '0' + cp / 1000; + cp %= 1000; + buffer[2] = '0' + cp / 100; + cp %= 100; + buffer[3] = '.'; + buffer[4] = '0' + cp / 10; + } + else + { + buffer[1] = '0' + cp / 100; + cp %= 100; + buffer[2] = '.'; + buffer[3] = '0' + cp / 10; + cp %= 10; + buffer[4] = '0' + cp / 1; + } +} + + +// Converts a Value into pawns, always keeping two decimals +void format_cp_aligned_dot(Value v, std::stringstream& stream, const Position& pos) { + + const double pawns = std::abs(0.01 * UCIEngine::to_cp(v, pos)); + + stream << (v < 0 ? '-' + : v > 0 ? '+' + : ' ') + << std::setiosflags(std::ios::fixed) << std::setw(6) << std::setprecision(2) << pawns; +} +} + + +// Returns a string with the value of each piece on a board, +// and a table for (PSQT, Layers) values bucket by bucket. +std::string +trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::AccumulatorCaches& caches) { + + std::stringstream ss; + + char board[3 * 8 + 1][8 * 8 + 2]; + std::memset(board, ' ', sizeof(board)); + for (int row = 0; row < 3 * 8 + 1; ++row) + board[row][8 * 8 + 1] = '\0'; + + // A lambda to output one box of the board + auto writeSquare = [&board, &pos](File file, Rank rank, Piece pc, Value value) { + const int x = int(file) * 8; + const int y = (7 - int(rank)) * 3; + for (int i = 1; i < 8; ++i) + board[y][x + i] = board[y + 3][x + i] = '-'; + for (int i = 1; i < 3; ++i) + board[y + i][x] = board[y + i][x + 8] = '|'; + board[y][x] = board[y][x + 8] = board[y + 3][x + 8] = board[y + 3][x] = '+'; + if (pc != NO_PIECE) + board[y + 1][x + 4] = PieceToChar[pc]; + if (is_valid(value)) + format_cp_compact(value, &board[y + 2][x + 2], pos); + }; + + AccumulatorStack accumulators; + + // We estimate the value of each piece by doing a differential evaluation from + // the current base eval, simulating the removal of the piece from its square. + auto [psqt, positional] = networks.big.evaluate(pos, accumulators, &caches.big); + Value base = psqt + positional; + base = pos.side_to_move() == WHITE ? base : -base; + + for (File f = FILE_A; f <= FILE_H; ++f) + for (Rank r = RANK_1; r <= RANK_8; ++r) + { + Square sq = make_square(f, r); + Piece pc = pos.piece_on(sq); + Value v = VALUE_NONE; + + if (pc != NO_PIECE && type_of(pc) != KING) + { + pos.remove_piece(sq); + + accumulators.reset(); + std::tie(psqt, positional) = networks.big.evaluate(pos, accumulators, &caches.big); + Value eval = psqt + positional; + eval = pos.side_to_move() == WHITE ? eval : -eval; + v = base - eval; + + pos.put_piece(pc, sq); + } + + writeSquare(f, r, pc, v); + } + + ss << " NNUE derived piece values:\n"; + for (int row = 0; row < 3 * 8 + 1; ++row) + ss << board[row] << '\n'; + ss << '\n'; + + accumulators.reset(); + auto t = networks.big.trace_evaluate(pos, accumulators, &caches.big); + + ss << " NNUE network contributions " + << (pos.side_to_move() == WHITE ? "(White to move)" : "(Black to move)") << std::endl + << "+------------+------------+------------+------------+\n" + << "| Bucket | Material | Positional | Total |\n" + << "| | (PSQT) | (Layers) | |\n" + << "+------------+------------+------------+------------+\n"; + + for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket) + { + ss << "| " << bucket << " " // + << " | "; + format_cp_aligned_dot(t.psqt[bucket], ss, pos); + ss << " " // + << " | "; + format_cp_aligned_dot(t.positional[bucket], ss, pos); + ss << " " // + << " | "; + format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss, pos); + ss << " " // + << " |"; + if (bucket == t.correctBucket) + ss << " <-- this bucket is used"; + ss << '\n'; + } + + ss << "+------------+------------+------------+------------+\n"; + + return ss.str(); +} + + +} // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/nnue_misc.h b/src/nnue/nnue_misc.h new file mode 100644 index 0000000..0221216 --- /dev/null +++ b/src/nnue/nnue_misc.h @@ -0,0 +1,61 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef NNUE_MISC_H_INCLUDED +#define NNUE_MISC_H_INCLUDED + +#include +#include + +#include "../types.h" +#include "nnue_architecture.h" + +namespace Stockfish { + +class Position; + +namespace Eval::NNUE { + +struct EvalFile { + // Default net name, will use one of the EvalFileDefaultName* macros defined + // in evaluate.h + std::string defaultName; + // Selected net name, either via uci option or default + std::string current; + // Net description extracted from the net file + std::string netDescription; +}; + + +struct NnueEvalTrace { + static_assert(LayerStacks == PSQTBuckets); + + Value psqt[LayerStacks]; + Value positional[LayerStacks]; + std::size_t correctBucket; +}; + +struct Networks; +struct AccumulatorCaches; + +std::string trace(Position& pos, const Networks& networks, AccumulatorCaches& caches); + +} // namespace Stockfish::Eval::NNUE +} // namespace Stockfish + +#endif // #ifndef NNUE_MISC_H_INCLUDED diff --git a/src/nnue/simd.h b/src/nnue/simd.h new file mode 100644 index 0000000..5602230 --- /dev/null +++ b/src/nnue/simd.h @@ -0,0 +1,418 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef NNUE_SIMD_H_INCLUDED +#define NNUE_SIMD_H_INCLUDED + +#if defined(USE_AVX2) + #include + +#elif defined(USE_SSE41) + #include + +#elif defined(USE_SSSE3) + #include + +#elif defined(USE_SSE2) + #include + +#elif defined(USE_NEON) + #include +#endif + +#include "../types.h" +#include "nnue_common.h" + +namespace Stockfish::Eval::NNUE::SIMD { + +// If vector instructions are enabled, we update and refresh the +// accumulator tile by tile such that each tile fits in the CPU's +// vector registers. +#define VECTOR + +#ifdef USE_AVX512 +using vec_t = __m512i; +using vec128_t = __m128i; +using psqt_vec_t = __m256i; +using vec_uint_t = __m512i; + #define vec_load(a) _mm512_load_si512(a) + #define vec_store(a, b) _mm512_store_si512(a, b) + #define vec_add_16(a, b) _mm512_add_epi16(a, b) + #define vec_sub_16(a, b) _mm512_sub_epi16(a, b) + #define vec_mulhi_16(a, b) _mm512_mulhi_epi16(a, b) + #define vec_zero() _mm512_setzero_epi32() + #define vec_set_16(a) _mm512_set1_epi16(a) + #define vec_max_16(a, b) _mm512_max_epi16(a, b) + #define vec_min_16(a, b) _mm512_min_epi16(a, b) + #define vec_slli_16(a, b) _mm512_slli_epi16(a, b) + // Inverse permuted at load time + #define vec_packus_16(a, b) _mm512_packus_epi16(a, b) + #define vec_load_psqt(a) _mm256_load_si256(a) + #define vec_store_psqt(a, b) _mm256_store_si256(a, b) + #define vec_add_psqt_32(a, b) _mm256_add_epi32(a, b) + #define vec_sub_psqt_32(a, b) _mm256_sub_epi32(a, b) + #define vec_zero_psqt() _mm256_setzero_si256() + + #ifdef USE_SSSE3 + #define vec_nnz(a) _mm512_cmpgt_epi32_mask(a, _mm512_setzero_si512()) + #endif + + #define vec128_zero _mm_setzero_si128() + #define vec128_set_16(a) _mm_set1_epi16(a) + #if (USE_SSE41) + #define vec128_load(a) _mm_cvtepu8_epi16(_mm_loadl_epi64(a)) + #else + #define vec128_load(a) _mm_load_si128(a) + #endif + #define vec128_storeu(a, b) _mm_storeu_si128(a, b) + #define vec128_add(a, b) _mm_add_epi16(a, b) + #define NumRegistersSIMD 16 + #define MaxChunkSize 64 + +#elif USE_AVX2 +using vec_t = __m256i; +using vec128_t = __m128i; +using psqt_vec_t = __m256i; +using vec_uint_t = __m256i; + #define vec_load(a) _mm256_load_si256(a) + #define vec_store(a, b) _mm256_store_si256(a, b) + #define vec_add_16(a, b) _mm256_add_epi16(a, b) + #define vec_sub_16(a, b) _mm256_sub_epi16(a, b) + #define vec_mulhi_16(a, b) _mm256_mulhi_epi16(a, b) + #define vec_zero() _mm256_setzero_si256() + #define vec_set_16(a) _mm256_set1_epi16(a) + #define vec_max_16(a, b) _mm256_max_epi16(a, b) + #define vec_min_16(a, b) _mm256_min_epi16(a, b) + #define vec_slli_16(a, b) _mm256_slli_epi16(a, b) + // Inverse permuted at load time + #define vec_packus_16(a, b) _mm256_packus_epi16(a, b) + #define vec_load_psqt(a) _mm256_load_si256(a) + #define vec_store_psqt(a, b) _mm256_store_si256(a, b) + #define vec_add_psqt_32(a, b) _mm256_add_epi32(a, b) + #define vec_sub_psqt_32(a, b) _mm256_sub_epi32(a, b) + #define vec_zero_psqt() _mm256_setzero_si256() + + #ifdef USE_SSSE3 + #if defined(USE_VNNI) && !defined(USE_AVXVNNI) + #define vec_nnz(a) _mm256_cmpgt_epi32_mask(a, _mm256_setzero_si256()) + #else + #define vec_nnz(a) \ + _mm256_movemask_ps( \ + _mm256_castsi256_ps(_mm256_cmpgt_epi32(a, _mm256_setzero_si256()))) + #endif + #endif + + #define vec128_zero _mm_setzero_si128() + #define vec128_set_16(a) _mm_set1_epi16(a) + #if (USE_SSE41) + #define vec128_load(a) _mm_cvtepu8_epi16(_mm_loadl_epi64(a)) + #else + #define vec128_load(a) _mm_load_si128(a) + #endif + #define vec128_storeu(a, b) _mm_storeu_si128(a, b) + #define vec128_add(a, b) _mm_add_epi16(a, b) + + #define NumRegistersSIMD 16 + #define MaxChunkSize 32 + +#elif USE_SSE2 +using vec_t = __m128i; +using vec128_t = __m128i; +using psqt_vec_t = __m128i; +using vec_uint_t = __m128i; + #define vec_load(a) (*(a)) + #define vec_store(a, b) *(a) = (b) + #define vec_add_16(a, b) _mm_add_epi16(a, b) + #define vec_sub_16(a, b) _mm_sub_epi16(a, b) + #define vec_mulhi_16(a, b) _mm_mulhi_epi16(a, b) + #define vec_zero() _mm_setzero_si128() + #define vec_set_16(a) _mm_set1_epi16(a) + #define vec_max_16(a, b) _mm_max_epi16(a, b) + #define vec_min_16(a, b) _mm_min_epi16(a, b) + #define vec_slli_16(a, b) _mm_slli_epi16(a, b) + #define vec_packus_16(a, b) _mm_packus_epi16(a, b) + #define vec_load_psqt(a) (*(a)) + #define vec_store_psqt(a, b) *(a) = (b) + #define vec_add_psqt_32(a, b) _mm_add_epi32(a, b) + #define vec_sub_psqt_32(a, b) _mm_sub_epi32(a, b) + #define vec_zero_psqt() _mm_setzero_si128() + + #ifdef USE_SSSE3 + #define vec_nnz(a) \ + _mm_movemask_ps(_mm_castsi128_ps(_mm_cmpgt_epi32(a, _mm_setzero_si128()))) + #endif + + #define vec128_zero _mm_setzero_si128() + #define vec128_set_16(a) _mm_set1_epi16(a) + #if (USE_SSE41) + #define vec128_load(a) _mm_cvtepu8_epi16(_mm_loadl_epi64(a)) + #else + #define vec128_load(a) _mm_load_si128(a) + #endif + #define vec128_storeu(a, b) _mm_storeu_si128(a, b) + #define vec128_add(a, b) _mm_add_epi16(a, b) + + #define NumRegistersSIMD (Is64Bit ? 16 : 8) + #define MaxChunkSize 16 + +#elif USE_NEON +using vec_t = int16x8_t; +using psqt_vec_t = int32x4_t; +using vec128_t = uint16x8_t; +using vec_uint_t = uint32x4_t; + #define vec_load(a) (*(a)) + #define vec_store(a, b) *(a) = (b) + #define vec_add_16(a, b) vaddq_s16(a, b) + #define vec_sub_16(a, b) vsubq_s16(a, b) + #define vec_mulhi_16(a, b) vqdmulhq_s16(a, b) + #define vec_zero() vec_t{0} + #define vec_set_16(a) vdupq_n_s16(a) + #define vec_max_16(a, b) vmaxq_s16(a, b) + #define vec_min_16(a, b) vminq_s16(a, b) + #define vec_slli_16(a, b) vshlq_s16(a, vec_set_16(b)) + #define vec_packus_16(a, b) reinterpret_cast(vcombine_u8(vqmovun_s16(a), vqmovun_s16(b))) + #define vec_load_psqt(a) (*(a)) + #define vec_store_psqt(a, b) *(a) = (b) + #define vec_add_psqt_32(a, b) vaddq_s32(a, b) + #define vec_sub_psqt_32(a, b) vsubq_s32(a, b) + #define vec_zero_psqt() psqt_vec_t{0} + +static constexpr std::uint32_t Mask[4] = {1, 2, 4, 8}; + #define vec_nnz(a) vaddvq_u32(vandq_u32(vtstq_u32(a, a), vld1q_u32(Mask))) + #define vec128_zero vdupq_n_u16(0) + #define vec128_set_16(a) vdupq_n_u16(a) + #define vec128_load(a) vld1q_u16(reinterpret_cast(a)) + #define vec128_storeu(a, b) vst1q_u16(reinterpret_cast(a), b) + #define vec128_add(a, b) vaddq_u16(a, b) + + #define NumRegistersSIMD 16 + #define MaxChunkSize 16 + +#else + #undef VECTOR + +#endif + +struct Vec16Wrapper { +#ifdef VECTOR + using type = vec_t; + static type add(const type& lhs, const type& rhs) { return vec_add_16(lhs, rhs); } + static type sub(const type& lhs, const type& rhs) { return vec_sub_16(lhs, rhs); } +#else + using type = BiasType; + static type add(const type& lhs, const type& rhs) { return lhs + rhs; } + static type sub(const type& lhs, const type& rhs) { return lhs - rhs; } +#endif +}; + +struct Vec32Wrapper { +#ifdef VECTOR + using type = psqt_vec_t; + static type add(const type& lhs, const type& rhs) { return vec_add_psqt_32(lhs, rhs); } + static type sub(const type& lhs, const type& rhs) { return vec_sub_psqt_32(lhs, rhs); } +#else + using type = PSQTWeightType; + static type add(const type& lhs, const type& rhs) { return lhs + rhs; } + static type sub(const type& lhs, const type& rhs) { return lhs - rhs; } +#endif +}; + +enum UpdateOperation { + Add, + Sub +}; + +template = true> +typename VecWrapper::type fused(const typename VecWrapper::type& in) { + return in; +} + +template, bool> = true, + std::enable_if_t = true> +typename VecWrapper::type +fused(const typename VecWrapper::type& in, const T& operand, const Ts&... operands) { + switch (update_op) + { + case Add : + return fused(VecWrapper::add(in, operand), operands...); + case Sub : + return fused(VecWrapper::sub(in, operand), operands...); + default : + static_assert(update_op == Add || update_op == Sub, + "Only Add and Sub are currently supported."); + return typename VecWrapper::type(); + } +} + +#if defined(USE_AVX512) + +[[maybe_unused]] static int m512_hadd(__m512i sum, int bias) { + return _mm512_reduce_add_epi32(sum) + bias; +} + +[[maybe_unused]] static void m512_add_dpbusd_epi32(__m512i& acc, __m512i a, __m512i b) { + + #if defined(USE_VNNI) + acc = _mm512_dpbusd_epi32(acc, a, b); + #else + __m512i product0 = _mm512_maddubs_epi16(a, b); + product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1)); + acc = _mm512_add_epi32(acc, product0); + #endif +} + +#endif + +#if defined(USE_AVX2) + +[[maybe_unused]] static int m256_hadd(__m256i sum, int bias) { + __m128i sum128 = _mm_add_epi32(_mm256_castsi256_si128(sum), _mm256_extracti128_si256(sum, 1)); + sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_BADC)); + sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_CDAB)); + return _mm_cvtsi128_si32(sum128) + bias; +} + +[[maybe_unused]] static void m256_add_dpbusd_epi32(__m256i& acc, __m256i a, __m256i b) { + + #if defined(USE_VNNI) + acc = _mm256_dpbusd_epi32(acc, a, b); + #else + __m256i product0 = _mm256_maddubs_epi16(a, b); + product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1)); + acc = _mm256_add_epi32(acc, product0); + #endif +} + +#endif + +#if defined(USE_SSSE3) + +[[maybe_unused]] static int m128_hadd(__m128i sum, int bias) { + sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0x4E)); //_MM_PERM_BADC + sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0xB1)); //_MM_PERM_CDAB + return _mm_cvtsi128_si32(sum) + bias; +} + +[[maybe_unused]] static void m128_add_dpbusd_epi32(__m128i& acc, __m128i a, __m128i b) { + + __m128i product0 = _mm_maddubs_epi16(a, b); + product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1)); + acc = _mm_add_epi32(acc, product0); +} + +#endif + +#if defined(USE_NEON_DOTPROD) + +[[maybe_unused]] static void +dotprod_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) { + + acc = vdotq_s32(acc, a, b); +} +#endif + +#if defined(USE_NEON) + +[[maybe_unused]] static int neon_m128_reduce_add_epi32(int32x4_t s) { + #if USE_NEON >= 8 + return vaddvq_s32(s); + #else + return s[0] + s[1] + s[2] + s[3]; + #endif +} + +[[maybe_unused]] static int neon_m128_hadd(int32x4_t sum, int bias) { + return neon_m128_reduce_add_epi32(sum) + bias; +} + +#endif + +#if USE_NEON >= 8 +[[maybe_unused]] static void neon_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) { + + int16x8_t product0 = vmull_s8(vget_low_s8(a), vget_low_s8(b)); + int16x8_t product1 = vmull_high_s8(a, b); + int16x8_t sum = vpaddq_s16(product0, product1); + acc = vpadalq_s16(acc, sum); +} +#endif + + +// Compute optimal SIMD register count for feature transformer accumulation. +template +class SIMDTiling { +#ifdef VECTOR + // We use __m* types as template arguments, which causes GCC to emit warnings + // about losing some attribute information. This is irrelevant to us as we + // only take their size, so the following pragma are harmless. + #if defined(__GNUC__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wignored-attributes" + #endif + + template + static constexpr int BestRegisterCount() { + constexpr std::size_t RegisterSize = sizeof(SIMDRegisterType); + constexpr std::size_t LaneSize = sizeof(LaneType); + + static_assert(RegisterSize >= LaneSize); + static_assert(MaxRegisters <= NumRegistersSIMD); + static_assert(MaxRegisters > 0); + static_assert(NumRegistersSIMD > 0); + static_assert(RegisterSize % LaneSize == 0); + static_assert((NumLanes * LaneSize) % RegisterSize == 0); + + const int ideal = (NumLanes * LaneSize) / RegisterSize; + if (ideal <= MaxRegisters) + return ideal; + + // Look for the largest divisor of the ideal register count that is smaller than MaxRegisters + for (int divisor = MaxRegisters; divisor > 1; --divisor) + if (ideal % divisor == 0) + return divisor; + + return 1; + } + + #if defined(__GNUC__) + #pragma GCC diagnostic pop + #endif + + public: + static constexpr int NumRegs = + BestRegisterCount(); + static constexpr int NumPsqtRegs = + BestRegisterCount(); + + static constexpr IndexType TileHeight = NumRegs * sizeof(vec_t) / 2; + static constexpr IndexType PsqtTileHeight = NumPsqtRegs * sizeof(psqt_vec_t) / 4; + + static_assert(HalfDimensions % TileHeight == 0, "TileHeight must divide HalfDimensions"); + static_assert(PSQTBuckets % PsqtTileHeight == 0, "PsqtTileHeight must divide PSQTBuckets"); +#endif +}; +} + +#endif diff --git a/src/position.h b/src/position.h new file mode 100644 index 0000000..3c1b340 --- /dev/null +++ b/src/position.h @@ -0,0 +1,239 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef POSITION_H_INCLUDED +#define POSITION_H_INCLUDED +#include "chess.hpp" +#include "types.h" +#include "nnue/nnue_accumulator.h" +namespace Stockfish { +class Position { + public: + Position(const chess::Board& board) { + b = board; + npm[1] = piece_value(chess::PieceType::KNIGHT) + * b.pieces(chess::PieceType::KNIGHT, chess::Color::BLACK).count() + + piece_value(chess::PieceType::BISHOP) + * b.pieces(chess::PieceType::BISHOP, chess::Color::BLACK).count() + + piece_value(chess::PieceType::ROOK) + * b.pieces(chess::PieceType::ROOK, chess::Color::BLACK).count() + + piece_value(chess::PieceType::QUEEN) + * b.pieces(chess::PieceType::QUEEN, chess::Color::BLACK).count(); + npm[0] = piece_value(chess::PieceType::KNIGHT) + * b.pieces(chess::PieceType::KNIGHT, chess::Color::WHITE).count() + + piece_value(chess::PieceType::BISHOP) + * b.pieces(chess::PieceType::BISHOP, chess::Color::WHITE).count() + + piece_value(chess::PieceType::ROOK) + * b.pieces(chess::PieceType::ROOK, chess::Color::WHITE).count() + + piece_value(chess::PieceType::QUEEN) + * b.pieces(chess::PieceType::QUEEN, chess::Color::WHITE).count(); + } + inline Stockfish::Bitboard pieces(Stockfish::Color c) const { return b.us(c).getBits(); } + inline Stockfish::Bitboard pieces(Stockfish::Color c, Stockfish::PieceType pt) const { + switch (pt) + { + case Stockfish::ALL_PIECES : + return pieces(c); + default : + return b.pieces(chess::PieceType(static_cast(pt - 1)), c) + .getBits(); + } + } + inline Stockfish::Bitboard pieces(Stockfish::PieceType pt) const { + switch (pt) + { + case Stockfish::ALL_PIECES : + return b.occ().getBits(); + default : + return b.pieces(chess::PieceType(static_cast(pt - 1))) + .getBits(); + } + } + inline Stockfish::Bitboard pieces() const { return b.occ().getBits(); } + template + inline int count() const { + switch (Pt) + { + case Stockfish::ALL_PIECES : + return b.occ().count(); + default : + return b.pieces(chess::PieceType(static_cast(Pt - 1))) + .count(); + } + } + template + inline int count(Stockfish::Color c) const { + switch (Pt) + { + case Stockfish::ALL_PIECES : + return b.occ().count(); + default : + return b.pieces(chess::PieceType(static_cast(Pt - 1)), c) + .count(); + } + } + template + inline Stockfish::Square square(Stockfish::Color c) const { + assert(count(c) == 1); + switch (Pt) + { + case Stockfish::ALL_PIECES : + return static_cast(b.occ().lsb()); + default : + return static_cast( + b.pieces(chess::PieceType(static_cast(Pt - 1)), c) + .lsb()); + } + } + inline Stockfish::Color side_to_move() const { + return (Stockfish::Color) static_cast(b.sideToMove()); + } + inline Stockfish::Piece piece_on(const Stockfish::Square sq) const { + return to_engine_piece(b.at(sq).internal()); + } + inline Stockfish::Value non_pawn_material() const { return npm[0] + npm[1]; } + inline Stockfish::Value non_pawn_material(Stockfish::Color c) const { return npm[(int) c]; } + inline int rule50_count() const { return b.halfMoveClock(); } + void do_move(chess::Move m) { + Stockfish::DirtyPiece dp; + dp.pc = to_engine_piece(b.at(m.from()).internal()); + dp.from = (Stockfish::Square) m.from().index(); + dp.to = (Stockfish::Square) m.to().index(); + dp.add_sq = Stockfish::SQ_NONE; + Stockfish::Color us = side_to_move(); + Stockfish::Color them = ~us; + Stockfish::Piece captured = m.typeOf() == m.ENPASSANT + ? Stockfish::make_piece(them, Stockfish::PAWN) + : piece_on(dp.to); + if (m.typeOf() == m.CASTLING) + { + const bool king_side = m.to() > m.from(); + const auto rookTo = chess::Square::castling_rook_square(king_side, side_to_move()); + const auto kingTo = chess::Square::castling_king_square(king_side, side_to_move()); + dp.to = (Stockfish::Square) kingTo.index(); + dp.remove_pc = dp.add_pc = make_piece(us, Stockfish::ROOK); + dp.remove_sq = (Stockfish::Square) m.to().index(); + dp.add_sq = (Stockfish::Square) rookTo.index(); + captured = Stockfish::NO_PIECE; + } + else if (captured) + { + Stockfish::Square capsq = + m.typeOf() == m.ENPASSANT + ? Stockfish::Square(m.to().index() - (us == Stockfish::WHITE ? 8 : -8)) + : dp.to; + dp.remove_pc = captured; + dp.remove_sq = capsq; + } + else + dp.remove_sq = Stockfish::SQ_NONE; + if (m.typeOf() == m.PROMOTION) + { + dp.add_pc = + make_piece(us, + static_cast( + 1 + m.promotionType())); // promotionType() returns a PieceType, + // which is 0 for NO_PIECE_TYPE, so we + // add 1 to get the Piece + // bonus: int + (OOP type) do the promotion of type integers because int can represent all the values in chess::PieceType (int8_t(m.to())); + + b.makeMove(m); // finally no + stack.push(dp); + //return dp; + } + void undo_move(chess::Move m) { + b.unmakeMove(m); + Stockfish::Color us = side_to_move(); + Stockfish::Color them = ~side_to_move(); + if (m.typeOf() == m.PROMOTION) + { + npm[(int) us] -= piece_value(m.promotionType()); + } + // en passant and PxP are handled in piece_value + if (b.isCapture(m)) + npm[(int) them] += piece_value(b.at(m.to())); + stack.pop(); + } + void do_null_move() { b.makeNullMove(); } + void undo_null_move() { b.unmakeNullMove(); } + chess::Board b; // exposure to generate legal moves + Stockfish::Eval::NNUE::AccumulatorStack stack; + + private: + Stockfish::Value npm[2] = {0, 0}; // non-pawn material for WHITE and BLACK + inline Stockfish::Value piece_value(chess::PieceType pt) { // fast and static check + switch (pt) + { + case (int) chess::PieceType::KNIGHT : + return Stockfish::KnightValue; + case (int) chess::PieceType::BISHOP : + return Stockfish::BishopValue; + case (int) chess::PieceType::ROOK : + return Stockfish::RookValue; + case (int) chess::PieceType::QUEEN : + return Stockfish::QueenValue; + default : + return 0; // includes king + } + } + inline Stockfish::Piece to_engine_piece(chess::Piece::underlying u) const { + switch (u) + { + case chess::Piece::underlying::WHITEPAWN : + return Stockfish::W_PAWN; + case chess::Piece::underlying::WHITEKNIGHT : + return Stockfish::W_KNIGHT; + case chess::Piece::underlying::WHITEBISHOP : + return Stockfish::W_BISHOP; + case chess::Piece::underlying::WHITEROOK : + return Stockfish::W_ROOK; + case chess::Piece::underlying::WHITEQUEEN : + return Stockfish::W_QUEEN; + case chess::Piece::underlying::WHITEKING : + return Stockfish::W_KING; + case chess::Piece::underlying::BLACKPAWN : + return Stockfish::B_PAWN; + case chess::Piece::underlying::BLACKKNIGHT : + return Stockfish::B_KNIGHT; + case chess::Piece::underlying::BLACKBISHOP : + return Stockfish::B_BISHOP; + case chess::Piece::underlying::BLACKROOK : + return Stockfish::B_ROOK; + case chess::Piece::underlying::BLACKQUEEN : + return Stockfish::B_QUEEN; + case chess::Piece::underlying::BLACKKING : + return Stockfish::B_KING; + default : + return Stockfish::NO_PIECE; + } + } +}; +} +#endif diff --git a/src/search.cpp b/src/search.cpp new file mode 100644 index 0000000..c959007 --- /dev/null +++ b/src/search.cpp @@ -0,0 +1,254 @@ +#include "search.h" +#include "evaluate.h" +#include +#include +#include +#include +#include + +namespace search { +using namespace Stockfish; // maybe.... +using Move = uint16_t; + +std::unique_ptr nn; +std::unique_ptr cache; +TranspositionTable tt; +std::chrono::time_point start; +std::atomic stop_requested; +int seldepth = 0; + +struct SearchStackEntry { + std::unique_ptr pv; + Value eval = Stockfish::VALUE_NONE; + + SearchStackEntry() : + pv(std::make_unique(Stockfish::MAX_PLY)) {} +}; + +inline Value value_draw(size_t nodes) { return VALUE_DRAW - 1 + Value(nodes & 0x2); } +inline Value value_to_tt(Value v, int ply) { + return is_win(v) ? v + ply : is_loss(v) ? v - ply : v; +} + +static Value value_from_tt(Value v, int ply, int r50c) { + if (is_win(v)) + { + if (v >= VALUE_MATE_IN_MAX_PLY && VALUE_MATE - v > 100 - r50c) + return VALUE_TB_WIN_IN_MAX_PLY - 1; + if (VALUE_TB - v > 100 - r50c) + return VALUE_TB_WIN_IN_MAX_PLY - 1; + return v - ply; + } + if (is_loss(v)) + { + if (v <= VALUE_MATED_IN_MAX_PLY && VALUE_MATE + v > 100 - r50c) + return VALUE_TB_LOSS_IN_MAX_PLY + 1; + if (VALUE_TB + v > 100 - r50c) + return VALUE_TB_LOSS_IN_MAX_PLY + 1; + return v + ply; + } + return v; +} + +static void update_pv(Move* pv, Move move, const Move* childPv) { + *pv++ = move; + while (childPv && *childPv) + *pv++ = *childPv++; + *pv = 0; +} +static Value qsearch(Position& pos, Value alpha, Value beta, int ply, SearchStackEntry* ss) { + seldepth = std::max(seldepth, ply+1); + + Value stand_pat = -VALUE_INFINITE; + if (!pos.b.inCheck()) + { + stand_pat = Stockfish::Eval::evaluate(*nn, pos, pos.stack, *cache, 0); + ss->eval = stand_pat; + if (stand_pat >= beta) + return stand_pat; + if (stand_pat > alpha) + alpha = stand_pat; + } + + chess::Movelist list, list2; + chess::movegen::legalmoves(list, pos.b); + if (list.size() == 0) + return pos.b.inCheck() ? mated_in(ply + 1) : value_draw(nodes.load()); + for (int i = 0; i < list.size(); ++i) + { + chess::Move mv = list[i]; + if (pos.b.isCapture(mv) || pos.b.givesCheck(mv) != chess::CheckType::NO_CHECK + || mv.typeOf() == mv.PROMOTION) + list2.add(mv); + } + movepick::qOrderMoves(pos.b, list2); + + for (int i = 0; i < list2.size(); ++i) + { + chess::Move mv = list2[i]; + pos.do_move(mv); + ++nodes; + Value score = -qsearch(pos, -beta, -alpha, ply + 1, ss + 1); + pos.undo_move(mv); + + if (score >= beta) + return score; + if (score > alpha) + alpha = score; + } + + return alpha; +} + +static Value +negamax(Position& pos, int depth, Value alpha, Value beta, int ply, SearchStackEntry* ss) { + seldepth = std::max(seldepth, ply+1); + if (stop_requested) return VALUE_NONE; + if (pos.b.isRepetition(1) || pos.b.halfMoveClock() >= 99) + return VALUE_DRAW; + + uint64_t key = pos.b.hash(); + ss->pv[0] = 0; + + TTEntry* entry = tt.lookup(key); + if (entry && entry->depth >= depth) + { + Value tt_score = value_from_tt(entry->score, ply, pos.b.halfMoveClock()); + if ((entry->flag == TTFlag::EXACT) + || (entry->flag == TTFlag::LOWERBOUND && tt_score >= beta) + || (entry->flag == TTFlag::UPPERBOUND && tt_score <= alpha)) + return tt_score; + } + + if (depth <= 0) + return qsearch(pos, alpha, beta, ply, ss); + + chess::Movelist list; + chess::movegen::legalmoves(list, pos.b); + if (list.empty()) + return pos.b.inCheck() ? mated_in(ply + 1) : value_draw(nodes.load()); + + chess::Move bestMove; + Value best = -VALUE_INFINITE, orig_alpha = alpha; + + movepick::orderMoves(pos.b, list, entry ? entry->move : chess::Move::NULL_MOVE, ply); + + for (int i = 0; i < list.size(); ++i) + { + chess::Move mv = list[i]; + if (ply == 0){ + auto end = std::chrono::steady_clock::now(); + auto s = std::chrono::duration_cast(end - start).count(); + if (s>=1) + std::cout << "info currmove " << chess::uci::moveToUci(mv) << " currmovenum " << i + 1 + << std::endl; + } + pos.do_move(mv); + ++nodes; + Value score = -negamax(pos, depth - 1, -beta, -alpha, ply + 1, ss + 1); + pos.undo_move(mv); + if (stop_requested) break; + if (score > best) + { + best = score; + bestMove = mv; + update_pv(ss->pv.get(), mv.move(), (ss + 1)->pv.get()); + } + + if (score > alpha) + alpha = score; + if (score >= beta) + { + if (!pos.b.isCapture(mv) && pos.b.givesCheck(mv) == chess::CheckType::NO_CHECK + && mv.typeOf() != mv.PROMOTION) + { + movepick::updateHistoryHeuristic(mv, depth); + movepick::updateKillerMoves(mv, ply); + } + break; + } + } + if (!stop_requested){ + TTFlag flag = (best >= beta) ? TTFlag::LOWERBOUND + : (best <= orig_alpha) ? TTFlag::UPPERBOUND + : TTFlag::EXACT; + + tt.store(key, bestMove, value_to_tt(best, ply), depth, flag); + } + ss->eval = best; + return best; +} +void run_search(const chess::Board& board, const TimeControl& tc) { + std::vector ss(MAX_PLY); + int rundepth = tc.depth ? tc.depth : 5; + Position pos(board); + std::vector pv(MAX_PLY); + seldepth = 0; + start = std::chrono::steady_clock::now(); + for (int d = 1; d <= rundepth && !stop_requested; ++d) + { + cache->clear(*nn); + for (auto& s : ss) + { + std::fill(s.pv.get(), s.pv.get() + MAX_PLY, 0); + s.eval = VALUE_NONE; + } + + pos.stack.reset(); + Value v = negamax(pos, d, -VALUE_INFINITE, VALUE_INFINITE, 0, ss.data()); + auto end = std::chrono::steady_clock::now(); + auto nanos = std::chrono::duration_cast(end - start).count(); + + std::cout << "info depth " << d << " seldepth " << seldepth << " score "; + if (is_decisive(v)) + { + int m = (v > 0 ? VALUE_MATE - v : -VALUE_MATE - v); + std::cout << "mate " << (v > 0 ? (m + 1) / 2 : -(m + 1) / 2); + } + else + { + std::cout << "cp " << v; + } + std::cout << " nodes " << nodes << " nps " << (nodes * 1000000000 / nanos); + std::cout << " time " << nanos / 1000000 << " hashfull " << tt.hashfull() << " pv "; + for (int j = 0; j < MAX_PLY && ss[0].pv[j]; ++j) + std::cout << chess::uci::moveToUci(chess::Move(ss[0].pv[j])) << " "; + std::cout << std::endl; + if (!stop_requested) { + for (int i = 0; i < MAX_PLY; ++i) { + pv[i] = ss[0].pv[i]; + } + } + + } + + if (pv[0]) + std::cout << "bestmove " << chess::uci::moveToUci(chess::Move(pv[0])) << std::endl; +} + +// ---------------------- Initialization --------------------------- +template +void init() { + if constexpr (init_nn) + { + Stockfish::Eval::NNUE::NetworkBig nBig( + Stockfish::Eval::NNUE::EvalFile{EvalFileDefaultNameBig, "", EvalFileDefaultNameBig}, + Stockfish::Eval::NNUE::EmbeddedNNUEType::BIG); + Stockfish::Eval::NNUE::NetworkSmall nSmall( + Stockfish::Eval::NNUE::EvalFile{EvalFileDefaultNameSmall, "", EvalFileDefaultNameSmall}, + Stockfish::Eval::NNUE::EmbeddedNNUEType::SMALL); + nn = std::make_unique(std::move(nBig), std::move(nSmall)); + } + else + { + nn->big.verify(EvalFileDefaultNameBig, onVerify); + nn->small.verify(EvalFileDefaultNameSmall, onVerify); + cache = std::make_unique(*nn); + } +} + +// Explicit instantiations for the linker +template void init(); +template void init(); + +} // namespace search diff --git a/src/search.h b/src/search.h new file mode 100644 index 0000000..6c09ac2 --- /dev/null +++ b/src/search.h @@ -0,0 +1,24 @@ +#ifndef CHESS_ENGINE_SEARCH_H +#define CHESS_ENGINE_SEARCH_H +#include "evaluate.h" +#include "movepick.hpp" +#include "position.h" +#include "timeman.hpp" +#include "tt.hpp" +#include "types.h" +#include "nnue/network.h" +#include +namespace search { +inline std::atomic nodes{0}; +struct SearchParams { + TimeControl tc; +}; +template +void init(); // Called twice (one for UCIOptions NNUE paths and one for anything else +void run_search(const chess::Board& board, const TimeControl& tc); +extern std::atomic stop_requested; +extern TranspositionTable tt; +extern std::unique_ptr nn; // defer construction +inline void onVerify(std::string_view sv) { std::cout << sv << std::endl; } +} // namespace search +#endif // CHESS_ENGINE_SEARCH_H diff --git a/src/timeman.cpp b/src/timeman.cpp new file mode 100644 index 0000000..e4c76b4 --- /dev/null +++ b/src/timeman.cpp @@ -0,0 +1,107 @@ +#include "timeman.hpp" +#include +#include +#include +#include +#include + +namespace timeman { +using namespace std::chrono; + +// Global state +TimeControl current_tc; // store the whole TimeControl struct globally +time_point start_time; +milliseconds inc = milliseconds(0); +milliseconds time_limit = milliseconds(0); +int moves_to_go = 40; +int depth = 0; +std::array times{}; +bool infinite = false; + +constexpr int MIN_SAFE_BUFFER_MS = 100; +constexpr double BUFFER_PERCENTAGE = 0.05; +constexpr int MIN_PER_MOVE = 400; +constexpr int MAX_PER_MOVE = 3000; +constexpr double PHASE_SCALE_OPENING = 0.7; +constexpr double PHASE_SCALE_MIDDLE = 1.0; +constexpr double PHASE_SCALE_ENDGAME = 1.25; +constexpr int OPENING_DEPTH = 5; +constexpr int MIDDLE_DEPTH = 10; + +static double get_phase_scale(int current_depth) { + if (current_depth <= OPENING_DEPTH) + return PHASE_SCALE_OPENING; + else if (current_depth <= MIDDLE_DEPTH) + return PHASE_SCALE_MIDDLE; + else + return PHASE_SCALE_ENDGAME; +} + +inline void reset_start_time() { start_time = high_resolution_clock::now(); } +void setLimits(const TimeControl& tc) { + current_tc = tc; + infinite = tc.infinite; + moves_to_go = std::max(1, tc.movestogo); + depth = tc.depth; + + if (infinite && tc.depth > 0) + { + time_limit = milliseconds::max(); + inc = milliseconds(0); + std::cout << "[TimeMan] Infinite or fixed depth mode.\n"; + } + else if (tc.movetime >= 0 && tc.movetime < INFINITE_TIME) + { + // UCI: movetime overrides all other time settings + time_limit = milliseconds(tc.movetime); + inc = milliseconds(0); + std::cout << "[TimeMan] Using fixed movetime: " << tc.movetime << " ms\n"; + } + else + { + // Fall back to clock-based time control + int time_left = tc.white_to_move ? tc.wtime : tc.btime; + int inc_ms = tc.white_to_move ? tc.winc : tc.binc; + + if (time_left != INFINITE_TIME) + { + int base_time = time_left / moves_to_go; + int safe_time = base_time / 16 + inc_ms; + safe_time = std::clamp(safe_time, MIN_PER_MOVE, MAX_PER_MOVE); + + double scale = get_phase_scale(depth); + safe_time = static_cast(safe_time * scale); + + time_limit = milliseconds(safe_time); + inc = milliseconds(inc_ms); + + std::cout << "[TimeMan] Using clock. Left: " << time_left << " ms, inc: " << inc_ms + << " ms, moves_to_go: " << moves_to_go << ", scaled: " << safe_time + << " ms\n"; + } + } + + reset_start_time(); +} + +bool check_time() { + if (infinite) + return false; + + int elapsed = duration_cast(high_resolution_clock::now() - start_time).count(); + + int dynamic_buffer = + std::max(MIN_SAFE_BUFFER_MS, static_cast(time_limit.count() * BUFFER_PERCENTAGE)); + + bool out_of_time = elapsed + dynamic_buffer >= time_limit.count(); + + if (out_of_time) + { + std::cout << "[TimeMan] Out of time! Elapsed: " << elapsed + << " ms, limit: " << time_limit.count() << " ms, buffer: " << dynamic_buffer + << " ms" << std::endl; + } + + return out_of_time; +} +} // namespace timeman diff --git a/src/timeman.hpp b/src/timeman.hpp new file mode 100644 index 0000000..8b89c52 --- /dev/null +++ b/src/timeman.hpp @@ -0,0 +1,29 @@ +#pragma once +#include "types.h" +#include +constexpr int INFINITE_TIME = 86400000; +struct TimeControl { + int wtime = INFINITE_TIME; + int btime = INFINITE_TIME; + int winc = 0; + int binc = 0; + int movestogo = 40; + int movetime = INFINITE_TIME; + uint8_t depth = Stockfish::MAX_PLY; + bool infinite = false; + bool white_to_move = true; +}; + +namespace timeman { +extern std::chrono::time_point start_time; +extern std::chrono::milliseconds time_buffer; +extern std::chrono::milliseconds inc; +extern std::chrono::milliseconds time_limit; +extern int moves_to_go; +extern bool infinite; + +bool check_time(); + +// Set time control limits using the TimeControl struct +void setLimits(const TimeControl& tc); +} // namespace timeman diff --git a/src/tt.cpp b/src/tt.cpp new file mode 100644 index 0000000..6fd19b2 --- /dev/null +++ b/src/tt.cpp @@ -0,0 +1,69 @@ +#include "tt.hpp" +void TranspositionTable::store( + uint64_t hash, chess::Move best, Stockfish::Value score, int8_t depth, TTFlag flag) { + // 2 entries per bucket + if (buckets == 0) + return; + uint64_t index = (hash & (buckets - 1)) * 2; + TTEntry &e0 = table[index], &e1 = table[index + 1]; + // Store the entry + // Store the entry + TTEntry* replace = &e0; // Default to first entry + for (TTEntry* e : {&e0, &e1}) + { + if (e->hash == hash) + { + // Always replace exact hash match + e->set(depth, flag, score, best); + return; + } + if (e->depth < depth) + { + // Prefer entry with lower depth + replace = e; + break; + } + if (e->depth < replace->depth) + { + // Among candidates, choose the one with lowest depth + replace = e; + } + } + replace->hash = hash; + replace->set(depth, flag, score, best); +} + +TTEntry* TranspositionTable::lookup(uint64_t hash) { + // 2 entries per bucket + if (buckets == 0) + return nullptr; + uint64_t index = (hash & (buckets - 1)) * 2; + TTEntry &e0 = table[index], &e1 = table[index + 1]; + // Check the entries + for (TTEntry* e : {&e0, &e1}) + { + if (e->hash == hash) + return e; + } + return nullptr; +} +int TranspositionTable::hashfull() const { + size_t count = 0; + + // Unroll loop for speed (process 4 entries per iteration) + size_t i = 0; + size_t limit = size & ~3ULL; // align to multiple of 4 + for (; i < limit; i += 4) + { + count += (table[i].hash != 0); + count += (table[i + 1].hash != 0); + count += (table[i + 2].hash != 0); + count += (table[i + 3].hash != 0); + } + for (; i < size; ++i) + { + count += (table[i].hash != 0); + } + + return static_cast((count * 1000) / size); +} diff --git a/src/tt.hpp b/src/tt.hpp new file mode 100644 index 0000000..c2f1b36 --- /dev/null +++ b/src/tt.hpp @@ -0,0 +1,66 @@ +#pragma once +#include "chess.hpp" +#include "types.h" +#include +#include "memory.h" // Provides LargePagePtr and make_unique_large_page + +enum class TTFlag : uint8_t { + EXACT, + LOWERBOUND, + UPPERBOUND +}; + +struct TTEntry { + uint64_t hash; + uint16_t move; + Stockfish::Value score; + TTFlag flag; + int8_t depth; + void set(int8_t d, TTFlag f, Stockfish::Value s, chess::Move m) { + depth = d; + flag = f; + score = s; + move = m.move(); + } +}; + +class TranspositionTable { + NNUEParser::LargePagePtr table; // Aligned (large-page) buffer + int size = 0; + int buckets = 0; + + public: + TranspositionTable() = default; + + TranspositionTable(int sizeInMB) { resize(sizeInMB); } + + void resize(int sizeInMB) { + // Round down to power of 2 + while (sizeInMB & (sizeInMB - 1)) + sizeInMB >>= 1; + + int newSize = sizeInMB * 1048576 / sizeof(TTEntry); + if (newSize == size) + { + clear(); + return; + } + + table = NNUEParser::make_unique_large_page(newSize); + if (!table) + { + std::cerr << "info string TT allocation failed\n"; + std::exit(EXIT_FAILURE); + } + + size = newSize; + buckets = size / 2; + clear(); + } + + inline void clear() { std::fill_n(table.get(), size, TTEntry{}); } + + int hashfull() const; // Implement full-scan or sampling + TTEntry* lookup(uint64_t h); // Existing lookup logic + void store(uint64_t h, chess::Move m, Stockfish::Value s, int8_t d, TTFlag f); +}; diff --git a/src/types.h b/src/types.h new file mode 100644 index 0000000..a2ae2b0 --- /dev/null +++ b/src/types.h @@ -0,0 +1,444 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef TYPES_H_INCLUDED +#define TYPES_H_INCLUDED + +// When compiling with provided Makefile (e.g. for Linux and OSX), configuration +// is done automatically. To get started type 'make help'. +// +// When Makefile is not used (e.g. with Microsoft Visual Studio) some switches +// need to be set manually: +// +// -DNDEBUG | Disable debugging mode. Always use this for release. +// +// -DNO_PREFETCH | Disable use of prefetch asm-instruction. You may need this to +// | run on some very old machines. +// +// -DUSE_POPCNT | Add runtime support for use of popcnt asm-instruction. Works +// | only in 64-bit mode and requires hardware with popcnt support. +// +// -DUSE_PEXT | Add runtime support for use of pext asm-instruction. Works +// | only in 64-bit mode and requires hardware with pext support. + +#include +#include +#include +#include + +#if defined(_MSC_VER) + // Disable some silly and noisy warnings from MSVC compiler + #pragma warning(disable: 4127) // Conditional expression is constant + #pragma warning(disable: 4146) // Unary minus operator applied to unsigned type + #pragma warning(disable: 4800) // Forcing value to bool 'true' or 'false' +#endif + +// Predefined macros hell: +// +// __GNUC__ Compiler is GCC, Clang or ICX +// __clang__ Compiler is Clang or ICX +// __INTEL_LLVM_COMPILER Compiler is ICX +// _MSC_VER Compiler is MSVC +// _WIN32 Building on Windows (any) +// _WIN64 Building on Windows 64 bit + +// Enforce minimum GCC version +#if defined(__GNUC__) && !defined(__clang__) \ + && (__GNUC__ < 9 || (__GNUC__ == 9 && __GNUC_MINOR__ < 3)) + #error "Stockfish requires GCC 9.3 or later for correct compilation" +#endif + +// Enforce minimum Clang version +#if defined(__clang__) && (__clang_major__ < 10) + #error "Stockfish requires Clang 10.0 or later for correct compilation" +#endif + +#define ASSERT_ALIGNED(ptr, alignment) assert(reinterpret_cast(ptr) % alignment == 0) + +#if defined(_WIN64) && defined(_MSC_VER) // No Makefile used + #include // Microsoft header for _BitScanForward64() + #define IS_64BIT +#endif + +#if defined(USE_POPCNT) && defined(_MSC_VER) + #include // Microsoft header for _mm_popcnt_u64() +#endif + +#if !defined(NO_PREFETCH) && defined(_MSC_VER) + #include // Microsoft header for _mm_prefetch() +#endif + +#if defined(USE_PEXT) + #include // Header for _pext_u64() intrinsic + #define pext(b, m) _pext_u64(b, m) +#else + #define pext(b, m) 0 +#endif + +namespace Stockfish { + +#ifdef USE_POPCNT +constexpr bool HasPopCnt = true; +#else +constexpr bool HasPopCnt = false; +#endif + +#ifdef USE_PEXT +constexpr bool HasPext = true; +#else +constexpr bool HasPext = false; +#endif + +#ifdef IS_64BIT +constexpr bool Is64Bit = true; +#else +constexpr bool Is64Bit = false; +#endif + +using Key = uint64_t; +using Bitboard = uint64_t; + +constexpr int MAX_MOVES = 256; +constexpr int MAX_PLY = 246; + +enum Color : int8_t { + WHITE, + BLACK, + COLOR_NB = 2 +}; + +enum CastlingRights : int8_t { + NO_CASTLING, + WHITE_OO, + WHITE_OOO = WHITE_OO << 1, + BLACK_OO = WHITE_OO << 2, + BLACK_OOO = WHITE_OO << 3, + + KING_SIDE = WHITE_OO | BLACK_OO, + QUEEN_SIDE = WHITE_OOO | BLACK_OOO, + WHITE_CASTLING = WHITE_OO | WHITE_OOO, + BLACK_CASTLING = BLACK_OO | BLACK_OOO, + ANY_CASTLING = WHITE_CASTLING | BLACK_CASTLING, + + CASTLING_RIGHT_NB = 16 +}; + +enum Bound : int8_t { + BOUND_NONE, + BOUND_UPPER, + BOUND_LOWER, + BOUND_EXACT = BOUND_UPPER | BOUND_LOWER +}; + +// Value is used as an alias for int, this is done to differentiate between a search +// value and any other integer value. The values used in search are always supposed +// to be in the range (-VALUE_NONE, VALUE_NONE] and should not exceed this range. +using Value = int; + +constexpr Value VALUE_ZERO = 0; +constexpr Value VALUE_DRAW = 0; +constexpr Value VALUE_NONE = 32002; +constexpr Value VALUE_INFINITE = 32001; + +constexpr Value VALUE_MATE = 32000; +constexpr Value VALUE_MATE_IN_MAX_PLY = VALUE_MATE - MAX_PLY; +constexpr Value VALUE_MATED_IN_MAX_PLY = -VALUE_MATE_IN_MAX_PLY; + +constexpr Value VALUE_TB = VALUE_MATE_IN_MAX_PLY - 1; +constexpr Value VALUE_TB_WIN_IN_MAX_PLY = VALUE_TB - MAX_PLY; +constexpr Value VALUE_TB_LOSS_IN_MAX_PLY = -VALUE_TB_WIN_IN_MAX_PLY; + + +constexpr bool is_valid(Value value) { return value != VALUE_NONE; } + +constexpr bool is_win(Value value) { + assert(is_valid(value)); + return value >= VALUE_TB_WIN_IN_MAX_PLY; +} + +constexpr bool is_loss(Value value) { + assert(is_valid(value)); + return value <= VALUE_TB_LOSS_IN_MAX_PLY; +} + +constexpr bool is_decisive(Value value) { return is_win(value) || is_loss(value); } + +// In the code, we make the assumption that these values +// are such that non_pawn_material() can be used to uniquely +// identify the material on the board. +constexpr Value PawnValue = 208; +constexpr Value KnightValue = 781; +constexpr Value BishopValue = 825; +constexpr Value RookValue = 1276; +constexpr Value QueenValue = 2538; + + +// clang-format off +enum PieceType : std::int8_t { + NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, + ALL_PIECES = 0, + PIECE_TYPE_NB = 8 +}; + +enum Piece : std::int8_t { + NO_PIECE, + W_PAWN = PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, + B_PAWN = PAWN + 8, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING, + PIECE_NB = 16 +}; +// clang-format on + +constexpr Value PieceValue[PIECE_NB] = { + VALUE_ZERO, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, VALUE_ZERO, VALUE_ZERO, + VALUE_ZERO, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, VALUE_ZERO, VALUE_ZERO}; + +using Depth = int; + +// The following DEPTH_ constants are used for transposition table entries +// and quiescence search move generation stages. In regular search, the +// depth stored in the transposition table is literal: the search depth +// (effort) used to make the corresponding transposition table value. In +// quiescence search, however, the transposition table entries only store +// the current quiescence move generation stage (which should thus compare +// lower than any regular search depth). +constexpr Depth DEPTH_QS = 0; +// For transposition table entries where no searching at all was done +// (whether regular or qsearch) we use DEPTH_UNSEARCHED, which should thus +// compare lower than any quiescence or regular depth. DEPTH_ENTRY_OFFSET +// is used only for the transposition table entry occupancy check (see tt.cpp), +// and should thus be lower than DEPTH_UNSEARCHED. +constexpr Depth DEPTH_UNSEARCHED = -2; +constexpr Depth DEPTH_ENTRY_OFFSET = -3; + +// clang-format off +enum Square : int8_t { + SQ_A1, SQ_B1, SQ_C1, SQ_D1, SQ_E1, SQ_F1, SQ_G1, SQ_H1, + SQ_A2, SQ_B2, SQ_C2, SQ_D2, SQ_E2, SQ_F2, SQ_G2, SQ_H2, + SQ_A3, SQ_B3, SQ_C3, SQ_D3, SQ_E3, SQ_F3, SQ_G3, SQ_H3, + SQ_A4, SQ_B4, SQ_C4, SQ_D4, SQ_E4, SQ_F4, SQ_G4, SQ_H4, + SQ_A5, SQ_B5, SQ_C5, SQ_D5, SQ_E5, SQ_F5, SQ_G5, SQ_H5, + SQ_A6, SQ_B6, SQ_C6, SQ_D6, SQ_E6, SQ_F6, SQ_G6, SQ_H6, + SQ_A7, SQ_B7, SQ_C7, SQ_D7, SQ_E7, SQ_F7, SQ_G7, SQ_H7, + SQ_A8, SQ_B8, SQ_C8, SQ_D8, SQ_E8, SQ_F8, SQ_G8, SQ_H8, + SQ_NONE, + + SQUARE_ZERO = 0, + SQUARE_NB = 64 +}; +// clang-format on + +enum Direction : int8_t { + NORTH = 8, + EAST = 1, + SOUTH = -NORTH, + WEST = -EAST, + + NORTH_EAST = NORTH + EAST, + SOUTH_EAST = SOUTH + EAST, + SOUTH_WEST = SOUTH + WEST, + NORTH_WEST = NORTH + WEST +}; + +enum File : int8_t { + FILE_A, + FILE_B, + FILE_C, + FILE_D, + FILE_E, + FILE_F, + FILE_G, + FILE_H, + FILE_NB +}; + +enum Rank : int8_t { + RANK_1, + RANK_2, + RANK_3, + RANK_4, + RANK_5, + RANK_6, + RANK_7, + RANK_8, + RANK_NB +}; + +// Keep track of what a move changes on the board (used by NNUE) +struct DirtyPiece { + Piece pc; // this is never allowed to be NO_PIECE + Square from, to; // to should be SQ_NONE for promotions + + // if {add,remove}_sq is SQ_NONE, {add,remove}_pc is allowed to be + // uninitialized + // castling uses add_sq and remove_sq to remove and add the rook + Square remove_sq, add_sq; + Piece remove_pc, add_pc; +}; + +#define ENABLE_INCR_OPERATORS_ON(T) \ + constexpr T& operator++(T& d) { return d = T(int(d) + 1); } \ + constexpr T& operator--(T& d) { return d = T(int(d) - 1); } + +ENABLE_INCR_OPERATORS_ON(PieceType) +ENABLE_INCR_OPERATORS_ON(Square) +ENABLE_INCR_OPERATORS_ON(File) +ENABLE_INCR_OPERATORS_ON(Rank) + +#undef ENABLE_INCR_OPERATORS_ON + +constexpr Direction operator+(Direction d1, Direction d2) { return Direction(int(d1) + int(d2)); } +constexpr Direction operator*(int i, Direction d) { return Direction(i * int(d)); } + +// Additional operators to add a Direction to a Square +constexpr Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); } +constexpr Square operator-(Square s, Direction d) { return Square(int(s) - int(d)); } +constexpr Square& operator+=(Square& s, Direction d) { return s = s + d; } +constexpr Square& operator-=(Square& s, Direction d) { return s = s - d; } + +// Toggle color +constexpr Color operator~(Color c) { return Color(c ^ BLACK); } + +// Swap A1 <-> A8 +constexpr Square flip_rank(Square s) { return Square(s ^ SQ_A8); } + +// Swap A1 <-> H1 +constexpr Square flip_file(Square s) { return Square(s ^ SQ_H1); } + +// Swap color of piece B_KNIGHT <-> W_KNIGHT +constexpr Piece operator~(Piece pc) { return Piece(pc ^ 8); } + +constexpr CastlingRights operator&(Color c, CastlingRights cr) { + return CastlingRights((c == WHITE ? WHITE_CASTLING : BLACK_CASTLING) & cr); +} + +constexpr Value mate_in(int ply) { return VALUE_MATE - ply; } + +constexpr Value mated_in(int ply) { return -VALUE_MATE + ply; } + +constexpr Square make_square(File f, Rank r) { return Square((r << 3) + f); } + +constexpr Piece make_piece(Color c, PieceType pt) { return Piece((c << 3) + pt); } + +constexpr PieceType type_of(Piece pc) { return PieceType(pc & 7); } + +constexpr Color color_of(Piece pc) { + assert(pc != NO_PIECE); + return Color(pc >> 3); +} + +constexpr bool is_ok(Square s) { return s >= SQ_A1 && s <= SQ_H8; } + +constexpr File file_of(Square s) { return File(s & 7); } + +constexpr Rank rank_of(Square s) { return Rank(s >> 3); } + +constexpr Square relative_square(Color c, Square s) { return Square(s ^ (c * 56)); } + +constexpr Rank relative_rank(Color c, Rank r) { return Rank(r ^ (c * 7)); } + +constexpr Rank relative_rank(Color c, Square s) { return relative_rank(c, rank_of(s)); } + +constexpr Direction pawn_push(Color c) { return c == WHITE ? NORTH : SOUTH; } + + +// Based on a congruential pseudo-random number generator +constexpr Key make_key(uint64_t seed) { + return seed * 6364136223846793005ULL + 1442695040888963407ULL; +} + + +enum MoveType { + NORMAL, + PROMOTION = 1 << 14, + EN_PASSANT = 2 << 14, + CASTLING = 3 << 14 +}; + +// A move needs 16 bits to be stored +// +// bit 0- 5: destination square (from 0 to 63) +// bit 6-11: origin square (from 0 to 63) +// bit 12-13: promotion piece type - 2 (from KNIGHT-2 to QUEEN-2) +// bit 14-15: special move flag: promotion (1), en passant (2), castling (3) +// NOTE: en passant bit is set only when a pawn can be captured +// +// Special cases are Move::none() and Move::null(). We can sneak these in because +// in any normal move the destination square and origin square are always different, +// but Move::none() and Move::null() have the same origin and destination square. + +class Move { + public: + Move() = default; + constexpr explicit Move(std::uint16_t d) : + data(d) {} + + constexpr Move(Square from, Square to) : + data((from << 6) + to) {} + + template + static constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) { + return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to); + } + + constexpr Square from_sq() const { + assert(is_ok()); + return Square((data >> 6) & 0x3F); + } + + constexpr Square to_sq() const { + assert(is_ok()); + return Square(data & 0x3F); + } + + constexpr int from_to() const { return data & 0xFFF; } + + constexpr MoveType type_of() const { return MoveType(data & (3 << 14)); } + + constexpr PieceType promotion_type() const { return PieceType(((data >> 12) & 3) + KNIGHT); } + + constexpr bool is_ok() const { return none().data != data && null().data != data; } + + static constexpr Move null() { return Move(65); } + static constexpr Move none() { return Move(0); } + + constexpr bool operator==(const Move& m) const { return data == m.data; } + constexpr bool operator!=(const Move& m) const { return data != m.data; } + + constexpr explicit operator bool() const { return data != 0; } + + constexpr std::uint16_t raw() const { return data; } + + struct MoveHash { + std::size_t operator()(const Move& m) const { return make_key(m.data); } + }; + + protected: + std::uint16_t data; +}; + +template +struct is_all_same { + static constexpr bool value = (std::is_same_v && ...); +}; + +template +constexpr auto is_all_same_v = is_all_same::value; + +} // namespace Stockfish + +#endif // #ifndef TYPES_H_INCLUDED diff --git a/src/uci.cpp b/src/uci.cpp new file mode 100644 index 0000000..e705d1b --- /dev/null +++ b/src/uci.cpp @@ -0,0 +1,333 @@ +#include "uci.hpp" +#include + +chess::Board board; +std::mutex board_mutex; +// Thread handle +static std::thread search_thread; +static void handleSetOption(const std::string& input) { + std::istringstream iss(input); + std::string token, name, value; + bool reading_name = false, reading_value = false; + + while (iss >> token) + { + if (token == "setoption") + continue; + if (token == "name") + { + reading_name = true; + reading_value = false; + name.clear(); + value.clear(); + continue; + } + if (token == "value") + { + reading_value = true; + reading_name = false; + continue; + } + + if (reading_name) + { + if (!name.empty()) + name += " "; + name += token; + } + else if (reading_value) + { + if (!value.empty()) + value += " "; + value += token; + } + } + + // Clean up name + auto pos = name.find_last_not_of(" \n\r\t"); + if (pos != std::string::npos) + { + name.erase(pos + 1); + } + else + { + name.clear(); // String contains only whitespace + } + + if (!UCIOptions::options.count(name)) + { + std::cerr << "info string Unknown option: " << name << "\n"; + return; + } + std::cerr << "info string option='" << name << "' value='" << value << "'\n"; + UCIOptions::setOption(name, value); +} +static void handle_uci() { + std::cout << "id name cppchess_engine\n"; + std::cout << "id author winapiadmin\n"; + UCIOptions::printOptions(); + std::cout << "uciok" << std::endl; +} + +inline void handle_isready() { std::cout << "readyok" << std::endl; } + +inline void handle_quit() { + search::stop_requested = true; + if (search_thread.joinable()) + search_thread.join(); +} + +inline void handle_stop() { search::stop_requested = true; } + +inline void handle_display() { std::cout << board << std::endl; } + +static void handle_position(std::istringstream& iss) { + std::string token; + iss >> token; + + if (token == "startpos") + { + board.setFen(chess::constants::STARTPOS); + iss >> token; + } + else if (token == "fen") + { + std::vector fen_parts; + while (iss >> token && token != "moves") + { + fen_parts.push_back(token); + } + + // Assign default values if necessary + while (fen_parts.size() < 6) + { + switch (fen_parts.size()) + { + case 1 : + fen_parts.push_back("w"); + break; // Active color + case 2 : + fen_parts.push_back("-"); + break; // Castling availability + case 3 : + fen_parts.push_back("-"); + break; // En passant target square + case 4 : + fen_parts.push_back("0"); + break; // Halfmove clock + case 5 : + fen_parts.push_back("1"); + break; // Fullmove number + } + } + + // Reconstruct the FEN string + std::string fen = fen_parts[0]; + for (size_t i = 1; i < 6; ++i) + { + fen += " " + fen_parts[i]; + } + + { + std::lock_guard lock(board_mutex); + board.setFen(fen); + } + } + + if (token == "moves") + { + while (iss >> token) + { + chess::Move mv = chess::uci::uciToMove(board, token); + { + std::lock_guard lock(board_mutex); + board.makeMove(mv); + } + } + } +} + +void handle_bench() { + uint64_t nodes = 0; + std::vector fen_list = { + "r7/pp3kb1/7p/2nr4/4p3/7P/PP1N1PP1/R1B1K2R b KQ - 1 19", + "r1bqk2r/pppp1ppp/2n5/4p3/4P3/2N5/PPPP1PPP/R1BQK2R w KQkq - 0 1", + "rnbqkb1r/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR b KQkq - 0 1", + "8/pp3k2/7p/2nR4/4p3/7P/Pb3PP1/6K1 b - - 1 25", + "8/p4k2/1p1R3p/2n5/4p3/7P/Pb3PP1/6K1 b - - 1 26", + "8/p4k2/1p1R1b1p/2n5/4p3/7P/P4PP1/5K2 b - - 3 27", + "8/p3k3/1pR2b1p/2n5/4p3/7P/P4PP1/5K2 b - - 5 28", + "1R6/8/1p1knb1p/p7/4p3/7P/P3KPP1/8 b - - 3 32", + "3R4/8/1p1k3p/p7/3bp2P/6Pn/P3KP2/8 b - - 2 35", + "8/4R3/1p6/p5PP/3b4/2n2K2/P2kp3/8 w - - 1 46", + "rnbqkb1r/ppp1pppp/1n6/8/8/2N2N2/PPPP1PPP/R1BQKB1R w KQkq - 2 5", + "r2qr1k1/1ppb1pbp/np4p1/3Pp3/4N3/P2B1N1P/1PP2PP1/2RQR1K1 b - - 6 16", + "8/8/5k2/8/8/4Q1K1/PPP1PPPP/3R1BR1 w - - 67 88"}; + std::vector boards(fen_list.size()); + auto start_time = std::chrono::high_resolution_clock::now(); + for (size_t i = 0; i < fen_list.size(); ++i) + { + std::cout << "Position " << i + 1 << "/" << fen_list.size() << std::endl; + boards[i].setFen(fen_list[i]); + TimeControl tc{}; + tc.movestogo = 1; + tc.depth = 5; + tc.infinite = false; + // All other fields remain at default (0 or -1) + + search::run_search(boards[i], tc); + + nodes += search::nodes; + } + auto end_time = std::chrono::high_resolution_clock::now(); + std::chrono::duration elapsed = end_time - start_time; + + std::cout << "===========================\n"; + std::cout << "Total time (ms) : " << static_cast(elapsed.count() * 1000) << "\n"; + std::cout << "Nodes searched : " << nodes << "\n"; + std::cout << "Nodes/second : " << static_cast(nodes / elapsed.count()) + << std::endl; +} +static void handle_go(std::istringstream& iss) { + TimeControl tc; + bool white = (board.sideToMove() == chess::Color::WHITE); + std::string sub; + int depth = Stockfish::MAX_PLY; + while (iss >> sub) + { + if (sub == "depth") + iss >> depth; + else if (sub == "movetime") + iss >> tc.movetime; + else if (sub == "wtime") + iss >> tc.wtime; + else if (sub == "btime") + iss >> tc.btime; + else if (sub == "winc") + iss >> tc.winc; + else if (sub == "binc") + iss >> tc.binc; + else if (sub == "movestogo") + iss >> tc.movestogo; + else if (sub == "infinite") + { + tc.infinite = true; + tc.depth = Stockfish::MAX_PLY; + } + } + tc.white_to_move = white; + tc.depth = depth; + if (search_thread.joinable()) + { + search::stop_requested = true; + search_thread.join(); + } + + search::stop_requested = false; + + chess::Board board_copy; + { + std::lock_guard lock(board_mutex); + board_copy = board; + } + search_thread = std::thread(search::run_search, board_copy, tc); // keep joinable +} + +inline void handle_ucinewgame() { + std::lock_guard lock(board_mutex); + board.setFen(chess::constants::STARTPOS); + search::tt.clear(); +} +void uci_loop() { + std::string line; + while (std::getline(std::cin, line)) + { + std::istringstream iss(line); + std::string token; + iss >> token; + if (token == "ucinewgame") + handle_ucinewgame(); + else if (token == "uci") + handle_uci(); + else if (token == "isready") + handle_isready(); + else if (token == "quit") + { + handle_quit(); + return; // Exit the loop after quitting + } + else if (token == "stop") + handle_stop(); + else if (token == "setoption") + handleSetOption(line); + else if (token == "position") + handle_position(iss); + else if (token == "go") + handle_go(iss); + else if (token == "d") + handle_display(); + else if (token == "bench") + handle_bench(); + else + std::cerr << "[DEBUG] Unknown command: " << token << "\n"; + } +} + +struct WinRateParams { + double a; + double b; +}; +static int win_rate_model(Stockfish::Value v, const Stockfish::Position& pos); +static WinRateParams win_rate_params(const Stockfish::Position& pos) { + + int material = pos.count() + 3 * pos.count() + + 3 * pos.count() + 5 * pos.count() + + 9 * pos.count(); + + // The fitted model only uses data for material counts in [17, 78], and is anchored at count 58. + double m = std::clamp(material, 17, 78) / 58.0; + + // Return a = p_a(material) and b = p_b(material), see github.com/official-stockfish/WDL_model + constexpr double as[] = {-13.50030198, 40.92780883, -36.82753545, 386.83004070}; + constexpr double bs[] = {96.53354896, -165.79058388, 90.89679019, 49.29561889}; + + double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; + double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; + + return {a, b}; +} + +// The win rate model is 1 / (1 + exp((a - eval) / b)), where a = p_a(material) and b = p_b(material). +// It fits the LTC fishtest statistics rather accurately. +static int win_rate_model(Stockfish::Value v, const Stockfish::Position& pos) { + + auto [a, b] = win_rate_params(pos); + + // Return the win rate in per mille units, rounded to the nearest integer. + return int(0.5 + 1000 / (1 + std::exp((a - double(v)) / b))); +} + +// Turns a Value to an integer centipawn number, +// without treatment of mate and similar special scores. +int to_cp(Stockfish::Value v, const Stockfish::Position& pos) { + + // In general, the score can be defined via the WDL as + // (log(1/L - 1) - log(1/W - 1)) / (log(1/L - 1) + log(1/W - 1)). + // Based on our win_rate_model, this simply yields v / a. + + auto [a, b] = win_rate_params(pos); + + return std::round(100 * int(v) / a); +} + +std::string wdl(Stockfish::Value v, const Stockfish::Position& pos) { + std::stringstream ss; + + int wdl_w = win_rate_model(v, pos); + int wdl_l = win_rate_model(-v, pos); + int wdl_d = 1000 - wdl_w - wdl_l; + ss << wdl_w << " " << wdl_d << " " << wdl_l; + + return ss.str(); +} diff --git a/uci.hpp b/src/uci.hpp similarity index 55% rename from uci.hpp rename to src/uci.hpp index 8ea9bb4..595b4ab 100644 --- a/uci.hpp +++ b/src/uci.hpp @@ -1,4 +1,7 @@ #pragma once +#include "chess.hpp" +#include "search.h" +#include "ucioptions.hpp" #include #include #include @@ -6,6 +9,6 @@ #include #include #include -#include "chess.hpp" -#include "search.hpp" void uci_loop(); +void handle_bench(); // main() +int to_cp(Stockfish::Value v, const Stockfish::Position& pos); diff --git a/src/ucioptions.cpp b/src/ucioptions.cpp new file mode 100644 index 0000000..f1ae195 --- /dev/null +++ b/src/ucioptions.cpp @@ -0,0 +1,180 @@ +#include "ucioptions.hpp" +#include +#include +#include + +namespace UCIOptions { + +// Global options map +std::unordered_map options; + +// Constructors with callback support +Option::Option(Type t, int def, int min_, int max_, std::function cb) : + type(t), + min(min_), + max(max_), + value(def), + on_change(std::move(cb)) {} + +Option::Option(Type t, const std::string& def, std::function cb) : + type(t), + default_str(def), + value(def), + on_change(std::move(cb)) {} + +Option::Option(Type t, + const std::string& def, + const std::vector& _options, + std::function cb) : + type(t), + default_str(def), + vars(_options), + value(def), + on_change(std::move(cb)) {} + +// Adders with optional callbacks +void addSpin(const std::string& name, + int defaultValue, + int min, + int max, + std::function cb) { + options[name] = Option(Option::SPIN, defaultValue, min, max, std::move(cb)); + if (options[name].on_change) + options[name].on_change(options[name]); +} + +void addCheck(const std::string& name, bool defaultValue, std::function cb) { + options[name] = Option(Option::CHECK, defaultValue ? 1 : 0, 0, 1, std::move(cb)); + if (options[name].on_change) + options[name].on_change(options[name]); +} + +void addString(const std::string& name, + const std::string& defaultValue, + std::function cb) { + options[name] = Option(Option::STRING, defaultValue, std::move(cb)); + if (options[name].on_change) + options[name].on_change(options[name]); +} + +void addCombo(const std::string& name, + const std::string& defaultValue, + const std::vector& vars, + std::function cb) { + options[name] = Option(Option::COMBO, defaultValue, vars, std::move(cb)); + if (options[name].on_change) + options[name].on_change(options[name]); +} + +void addButton(const std::string& name, std::function cb) { + options[name] = Option(Option::BUTTON, "", {}, std::move(cb)); + if (options[name].on_change) + options[name].on_change(options[name]); +} + +void setOption(const std::string& name, const std::string& val) { + const auto it = options.find(name); + if (it == options.end()) + { + std::cerr << "info string Unknown option " << name << '\n'; + return; // Early-out – nothing to update + } + auto& opt = it->second; + + switch (opt.type) + { + case Option::CHECK : + if (val == "true" || val == "1") + opt.value = 1; + else if (val == "false" || val == "0") + opt.value = 0; + else + { + std::cerr << "info string Invalid value for option " << name << ": " << val + << " (expected true/false or 1/0)\n"; + return; + } + break; + case Option::SPIN : { + int v{}; + auto [p, ec] = std::from_chars(val.data(), val.data() + val.size(), v); + if (ec != std::errc() || p != val.data() + val.size()) + { + std::cerr << "info string Invalid value for option " << name << ": " << val + << " (not a valid integer)\n"; + return; + } + opt.value = std::clamp(v, opt.min, opt.max); + break; + } + case Option::STRING : + opt.value = val; + break; + case Option::COMBO : + if (std::find(opt.vars.begin(), opt.vars.end(), val) != opt.vars.end()) + { + opt.value = val; + } + else + { + std::cerr << "info string Illegal value \"" << val << "\" for option " << name << "\n"; + return; + } + break; + case Option::BUTTON : + // no value change + break; + } + + // Invoke the callback if present + if (opt.on_change) + opt.on_change(opt); +} + +int getInt(const std::string& name) { + const auto& opt = options.at(name); + if (auto pval = std::get_if(&opt.value)) + return *pval; + std::cerr << "info string Option " + name + " is not an integer"; + exit(1); +} + +std::string getString(const std::string& name) { + const auto& opt = options.at(name); + if (auto pval = std::get_if(&opt.value)) + return *pval; + if (auto pval = std::get_if(&opt.value)) + return std::to_string(*pval); + return ""; +} + +void printOptions() { + for (const auto& [name, opt] : options) + { + std::cout << "option name " << name << " type "; + switch (opt.type) + { + case Option::CHECK : + std::cout << "check default " << (std::get(opt.value) ? "true" : "false") << "\n"; + break; + case Option::SPIN : + std::cout << "spin default " << std::get(opt.value) << " min " << opt.min + << " max " << opt.max << "\n"; + break; + case Option::STRING : + std::cout << "string default " << std::get(opt.value) << "\n"; + break; + case Option::COMBO : + std::cout << "combo default " << std::get(opt.value); + for (const auto& var : opt.vars) + std::cout << " var " << var; + std::cout << "\n"; + break; + case Option::BUTTON : + std::cout << "button\n"; + break; + } + } +} + +} // namespace UCIOptions diff --git a/src/ucioptions.hpp b/src/ucioptions.hpp new file mode 100644 index 0000000..af3e1af --- /dev/null +++ b/src/ucioptions.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace UCIOptions { + +struct Option { + enum Type { + CHECK, + SPIN, + COMBO, + BUTTON, + STRING + } type; + + std::string default_str; + std::vector vars; // for COMBO + int min = 0, max = 0; + + std::variant value; + + // Optional callback when the option is set + std::function on_change; + + Option() = default; + Option(Type t, int def, int min_ = 0, int max_ = 0, std::function cb = {}); + Option(Type t, const std::string& def, std::function cb = {}); + Option(Type t, + const std::string& def, + const std::vector& _options, + std::function cb = {}); +}; + +// Declare the global options map +extern std::unordered_map options; + +// Adders with optional callback support +void addSpin(const std::string& name, + int defaultValue, + int min, + int max, + std::function cb = {}); +void addCheck(const std::string& name, + bool defaultValue, + std::function cb = {}); +void addString(const std::string& name, + const std::string& defaultValue, + std::function cb = {}); +void addCombo(const std::string& name, + const std::string& defaultValue, + const std::vector& vars, + std::function cb = {}); +void addButton(const std::string& name, std::function cb = {}); + +void setOption(const std::string& name, const std::string& val); + +int getInt(const std::string& name); +std::string getString(const std::string& name); + +void printOptions(); + +} // namespace UCIOptions diff --git a/timeman.cpp b/timeman.cpp deleted file mode 100644 index fbafa58..0000000 --- a/timeman.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#include "timeman.hpp" -#include -#include -#include -#include -#include - -namespace timeman -{ - using namespace std::chrono; - - // Global state - TimeControl current_tc; // store the whole TimeControl struct globally - time_point start_time; - milliseconds inc = milliseconds(0); - milliseconds time_limit = milliseconds(0); - int moves_to_go = 40; - int depth = 0; - std::array times{}; - bool infinite = false; - - constexpr int MIN_SAFE_BUFFER_MS = 100; - constexpr double BUFFER_PERCENTAGE = 0.05; - constexpr int MIN_PER_MOVE = 400; - constexpr int MAX_PER_MOVE = 3000; - constexpr double PHASE_SCALE_OPENING = 0.7; - constexpr double PHASE_SCALE_MIDDLE = 1.0; - constexpr double PHASE_SCALE_ENDGAME = 1.25; - constexpr int OPENING_DEPTH = 5; - constexpr int MIDDLE_DEPTH = 10; - - double get_phase_scale(int current_depth) - { - if (current_depth <= OPENING_DEPTH) - return PHASE_SCALE_OPENING; - else if (current_depth <= MIDDLE_DEPTH) - return PHASE_SCALE_MIDDLE; - else - return PHASE_SCALE_ENDGAME; - } - - void reset_start_time() - { - start_time = high_resolution_clock::now(); - } -void setLimits(const TimeControl &tc) -{ - current_tc = tc; - infinite = tc.infinite; - moves_to_go = std::max(1, tc.movestogo); - depth = tc.depth; - - if (infinite && tc.depth > 0) { - time_limit = milliseconds::max(); - inc = milliseconds(0); - std::cout << "[TimeMan] Infinite or fixed depth mode.\n"; - } - else if (tc.movetime >= 0 && tc.movetime < INFINITE_TIME) { - // UCI: movetime overrides all other time settings - time_limit = milliseconds(tc.movetime); - inc = milliseconds(0); - std::cout << "[TimeMan] Using fixed movetime: " << tc.movetime << " ms\n"; - } - else { - // Fall back to clock-based time control - int time_left = tc.white_to_move ? tc.wtime : tc.btime; - int inc_ms = tc.white_to_move ? tc.winc : tc.binc; - - if (time_left != INFINITE_TIME) { - int base_time = time_left / moves_to_go; - int safe_time = base_time / 16 + inc_ms; - safe_time = std::clamp(safe_time, MIN_PER_MOVE, MAX_PER_MOVE); - - double scale = get_phase_scale(depth); - safe_time = static_cast(safe_time * scale); - - time_limit = milliseconds(safe_time); - inc = milliseconds(inc_ms); - - std::cout << "[TimeMan] Using clock. Left: " << time_left - << " ms, inc: " << inc_ms << " ms, moves_to_go: " - << moves_to_go << ", scaled: " << safe_time << " ms\n"; - } - } - - reset_start_time(); -} - - - bool check_time() - { - if (infinite) - return false; - - int elapsed = duration_cast(high_resolution_clock::now() - start_time).count(); - - int dynamic_buffer = std::max(MIN_SAFE_BUFFER_MS, static_cast(time_limit.count() * BUFFER_PERCENTAGE)); - - bool out_of_time = elapsed + dynamic_buffer >= time_limit.count(); - - if (out_of_time) - { - std::cout << "[TimeMan] Out of time! Elapsed: " << elapsed - << " ms, limit: " << time_limit.count() - << " ms, buffer: " << dynamic_buffer << " ms" << std::endl; - } - - return out_of_time; - } -} diff --git a/timeman.hpp b/timeman.hpp deleted file mode 100644 index e24641e..0000000 --- a/timeman.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once -#include -#include "eval.hpp" -constexpr int INFINITE_TIME = 86400000; -struct TimeControl -{ - int wtime = INFINITE_TIME; - int btime = INFINITE_TIME; - int winc = 0; - int binc = 0; - int movestogo = 40; - int movetime = INFINITE_TIME; - uint8_t depth = MAX_PLY; - bool infinite = false; - bool white_to_move = true; -}; - -namespace timeman -{ - extern std::chrono::time_point start_time; - extern std::chrono::milliseconds time_buffer; - extern std::chrono::milliseconds inc; - extern std::chrono::milliseconds time_limit; - extern int moves_to_go; - extern bool infinite; - - bool check_time(); - - // Set time control limits using the TimeControl struct - void setLimits(const TimeControl &tc); -} diff --git a/tt.cpp b/tt.cpp deleted file mode 100644 index ee69361..0000000 --- a/tt.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include "tt.hpp" - -void TranspositionTable::newSearch() -{ - time = 0; -} - -void TranspositionTable::store(uint64_t hash, chess::Move best, int16_t score, int8_t depth, TTFlag flag) -{ - // 2 entries per bucket - if (buckets == 0) - return; - uint64_t index = hash % buckets; - if (index >= buckets - 1) - index = buckets - 2; // Ensure we don't overflow - TTEntry &e0 = table[index], &e1 = table[index + 1]; - // Store the entry - - ++time; // monotonically increasing stamp - for (TTEntry *e : {&e0, &e1}) - { - if (!e->valid() || e->hash == hash || e->depth() < depth) - { - e->hash = hash; - e->set(depth, flag, score, time, true, best); - return; - } - } - // If we get here, we need to evict an entry - // Find the oldest entry - TTEntry *oldest = (e0.timestamp() < e1.timestamp()) ? &e0 : &e1; - // Evict it - oldest->hash = hash; - oldest->set(depth, flag, score, time, true, best); -} - -TTEntry *TranspositionTable::lookup(uint64_t hash) -{ - // 2 entries per bucket - if (buckets == 0) - return nullptr; - uint64_t index = hash % buckets; - if (index >= buckets - 1) - index = buckets - 2; // Ensure we don't overflow - TTEntry &e0 = table[index], &e1 = table[index + 1]; - // Check the entries - for (TTEntry *e : {&e0, &e1}) - { - if (e->valid() && e->hash == hash) - return e; - } - return nullptr; -} diff --git a/tt.hpp b/tt.hpp deleted file mode 100644 index 0ec4a93..0000000 --- a/tt.hpp +++ /dev/null @@ -1,122 +0,0 @@ -#pragma once -#include "chess.hpp" -#include -#include -enum class TTFlag : uint8_t -{ - EXACT = 0, - LOWERBOUND = 1, - UPPERBOUND = 2, - // 3 is unused or reserved -}; - -struct TTEntry -{ - uint64_t hash; - chess::Move bestMove; - uint64_t packed; - - // Getters - uint8_t depth() const - { - return ((packed >> 58) & 0x3F) + 1; // 6 bits depth-1 - } - - TTFlag flag() const - { - return static_cast((packed >> 56) & 0x3); - } - - uint64_t timestamp() const - { - return (packed >> 17) & 0x7FFFFFFFFFULL; // 39 bits - } - - bool valid() const - { - return (packed & 1) != 0; - } - - int16_t score() const - { - return bestMove.score(); - } - - // Setter - void set(uint8_t d, TTFlag f, int16_t s, uint64_t ts, bool v, chess::Move move) - { - // Store depth - 1 in 6 bits, clamp at 63 - uint8_t depth_encoded = (d > 0) ? (d - 1) : 0; - if (depth_encoded > 63) - depth_encoded = 63; - - packed = (uint64_t(depth_encoded) << 58) | - (uint64_t((int)f & 0x3) << 56) | - ((ts & 0x7FFFFFFFFFULL) << 17) | - (v); - bestMove=move; - bestMove.setScore(s); - } -}; -class TranspositionTable -{ - TTEntry *table; - int buckets; // number of buckets (pairs) - uint32_t time; - -public: - int size; // total number of TTEntry elements (must be even) - TranspositionTable() : table(nullptr), buckets(0), time(0), size(0) {} - - TranspositionTable(int sizeInMB) : time(0) - { - size = sizeInMB * 1048576 / sizeof(TTEntry); - if (size % 2 != 0) - size--; // Ensure even size - buckets = size / 2; - table = new TTEntry[size]; - clear(); - } - - ~TranspositionTable() - { - delete[] table; - } - - void resize(int sizeInMB) - { - TTEntry *old_table = table; - int old_size = size; - - size = sizeInMB * 1048576 / sizeof(TTEntry); - if (size % 2 != 0) - size--; - buckets = size / 2; - - TTEntry *new_table = new (std::nothrow) TTEntry[size]; - if (!new_table) - { - // Restore old values on failure - table = old_table; - size = old_size; - buckets = old_size / 2; - throw std::bad_alloc(); - } - - std::memcpy(new_table, old_table, - sizeof(TTEntry) * std::min(size, old_size)); - delete[] old_table; - table = new_table; - buckets = size / 2; - } - - void newSearch(); - void store(uint64_t hash, chess::Move best, int16_t score, int8_t depth, TTFlag flag); - - inline void clear() - { - std::fill_n(table, size, TTEntry{}); - } - - TTEntry *lookup(uint64_t hash); -}; diff --git a/uci.cpp b/uci.cpp deleted file mode 100644 index d27dd8f..0000000 --- a/uci.cpp +++ /dev/null @@ -1,270 +0,0 @@ -#include "uci.hpp" - -chess::Board board; -// Thread handle -static std::thread search_thread; -void handleSetOption(const std::string &input) -{ - std::istringstream iss(input); - std::string token, name, value; - bool reading_name = false, reading_value = false; - - while (iss >> token) - { - if (token == "setoption") - continue; - if (token == "name") - { - reading_name = true; - reading_value = false; - name.clear(); - value.clear(); - continue; - } - if (token == "value") - { - reading_value = true; - reading_name = false; - continue; - } - - if (reading_name) - { - if (!name.empty()) - name += " "; - name += token; - } - else if (reading_value) - { - if (!value.empty()) - value += " "; - value += token; - } - } - - // Clean up name - auto pos = name.find_last_not_of(" \n\r\t"); - if (pos != std::string::npos) - { - name.erase(pos + 1); - } - else - { - name.clear(); // String contains only whitespace - } - - if (!UCIOptions::options.count(name)) - { - std::cerr << "info string Unknown option: " << name << "\n"; - return; - } - - UCIOptions::setOption(name, value); -} -void handle_uci() -{ - std::cout << "id name cppchess_engine\n"; - std::cout << "id author winapiadmin\n"; - UCIOptions::printOptions(); - std::cout << "uciok\n"; -} - -inline void handle_isready() { std::cout << "readyok\n"; } - -void handle_quit() -{ - search::stop_requested = true; - if (search_thread.joinable()) - search_thread.join(); - std::exit(0); -} - -inline void handle_stop() { search::stop_requested = true; } - -inline void handle_display() { std::cout << board << std::endl; } - -inline void handle_eval() { trace(board); } -void handle_position(std::istringstream &iss) -{ - std::string token; - iss >> token; - - if (token == "startpos") - { - board.setFen(chess::constants::STARTPOS); - iss >> token; - } - else if (token == "fen") - { - std::vector fen_parts; - while (iss >> token && token != "moves") - { - fen_parts.push_back(token); - } - - // Assign default values if necessary - while (fen_parts.size() < 6) - { - switch (fen_parts.size()) - { - case 1: - fen_parts.push_back("w"); - break; // Active color - case 2: - fen_parts.push_back("-"); - break; // Castling availability - case 3: - fen_parts.push_back("-"); - break; // En passant target square - case 4: - fen_parts.push_back("0"); - break; // Halfmove clock - case 5: - fen_parts.push_back("1"); - break; // Fullmove number - } - } - - // Reconstruct the FEN string - std::string fen = fen_parts[0]; - for (size_t i = 1; i < 6; ++i) - { - fen += " " + fen_parts[i]; - } - - board.setFen(fen); - } - - if (token == "moves") - { - while (iss >> token) - { - chess::Move mv = chess::uci::uciToMove(board, token); - board.makeMove(mv); - } - } - - search::tt.clear(); -} - -void handle_bench() -{ - uint64_t nodes = 0; - std::vector fen_list = {"r7/pp3kb1/7p/2nr4/4p3/7P/PP1N1PP1/R1B1K2R b KQ - 1 19", - "r1bqk2r/pppp1ppp/2n5/4p3/4P3/2N5/PPPP1PPP/R1BQK2R w KQkq - 0 1", - "rnbqkb1r/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", - "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR b KQkq - 0 1", - "8/pp3k2/7p/2nR4/4p3/7P/Pb3PP1/6K1 b - - 1 25", - "8/p4k2/1p1R3p/2n5/4p3/7P/Pb3PP1/6K1 b - - 1 26", - "8/p4k2/1p1R1b1p/2n5/4p3/7P/P4PP1/5K2 b - - 3 27", - "8/p3k3/1pR2b1p/2n5/4p3/7P/P4PP1/5K2 b - - 5 28", - "1R6/8/1p1knb1p/p7/4p3/7P/P3KPP1/8 b - - 3 32", - "3R4/8/1p1k3p/p7/3bp2P/6Pn/P3KP2/8 b - - 2 35", - "8/4R3/1p6/p5PP/3b4/2n2K2/P2kp3/8 w - - 1 46", - "rnbqkb1r/ppp1pppp/1n6/8/8/2N2N2/PPPP1PPP/R1BQKB1R w KQkq - 2 5", - "r2qr1k1/1ppb1pbp/np4p1/3Pp3/4N3/P2B1N1P/1PP2PP1/2RQR1K1 b - - 6 16", - "8/8/5k2/8/8/4Q1K1/PPP1PPPP/3R1BR1 w - - 67 88"}; - std::vector boards(fen_list.size()); - auto start_time = std::chrono::high_resolution_clock::now(); - for (size_t i = 0; i < fen_list.size(); ++i) - { - std::cout << "Position " << i + 1 << "/" << fen_list.size() << std::endl; - boards[i].setFen(fen_list[i]); - TimeControl tc{}; - tc.movestogo = 1; - tc.depth = 8; - tc.infinite = false; - // All other fields remain at default (0 or -1) - - search::run_search(boards[i], tc); - - nodes += search::nodes; - } - auto end_time = std::chrono::high_resolution_clock::now(); - std::chrono::duration elapsed = end_time - start_time; - - std::cout << "===========================\n"; - std::cout << "Total time (ms) : " << static_cast(elapsed.count() * 1000) << "\n"; - std::cout << "Nodes searched : " << nodes << "\n"; - std::cout << "Nodes/second : " << static_cast(nodes / elapsed.count()) << std::endl; -} -void handle_go(std::istringstream &iss) -{ - TimeControl tc; - bool white = (board.sideToMove() == chess::Color::WHITE); - std::string sub; - - while (iss >> sub) - { - if (sub == "depth") - iss >> tc.depth; - else if (sub == "movetime") - iss >> tc.movetime; - else if (sub == "wtime") - iss >> tc.wtime; - else if (sub == "btime") - iss >> tc.btime; - else if (sub == "winc") - iss >> tc.winc; - else if (sub == "binc") - iss >> tc.binc; - else if (sub == "movestogo") - iss >> tc.movestogo; - else if (sub == "infinite") - { - tc.infinite = true; - tc.depth = MAX_PLY; - } - } - tc.white_to_move=white; - if (search_thread.joinable()) - { - search::stop_requested = true; - search_thread.join(); - } - - search::stop_requested = false; - - search_thread = std::thread(search::run_search, board, tc); // keep joinable -} - -void handle_ucinewgame() -{ - board.setFen(chess::constants::STARTPOS); - search::tt.clear(); - search::tt.newSearch(); -} -void uci_loop() -{ - std::string line; - while (std::getline(std::cin, line)) - { - std::istringstream iss(line); - std::string token; - iss >> token; - if (token == "ucinewgame") - handle_ucinewgame(); - else if (token == "uci") - handle_uci(); - else if (token == "isready") - handle_isready(); - else if (token == "quit") - handle_quit(); - else if (token == "stop") - handle_stop(); - else if (token == "setoption") - handleSetOption(line); - else if (token == "position") - handle_position(iss); - else if (token == "go") - handle_go(iss); - else if (token == "d") - handle_display(); - else if (token == "bench") - handle_bench(); - else if (token == "eval") - handle_eval(); - else - std::cerr << "[DEBUG] Unknown command: " << token << "\n"; - } -} diff --git a/ucioptions.cpp b/ucioptions.cpp deleted file mode 100644 index 4a4f22c..0000000 --- a/ucioptions.cpp +++ /dev/null @@ -1,168 +0,0 @@ -#include "ucioptions.hpp" -#include -#include -#include - -namespace UCIOptions -{ - - // Global options map - std::unordered_map options; - - // Constructors with callback support - Option::Option(Type t, int def, int min_, int max_, std::function cb) - : type(t), min(min_), max(max_), value(def), on_change(std::move(cb)) {} - - Option::Option(Type t, const std::string &def, std::function cb) - : type(t), default_str(def), value(def), on_change(std::move(cb)) {} - - Option::Option(Type t, const std::string &def, const std::vector &options, std::function cb) - : type(t), default_str(def), vars(options), value(def), on_change(std::move(cb)) {} - - // Adders with optional callbacks - void addSpin(const std::string &name, int defaultValue, int min, int max, std::function cb) - { - options[name] = Option(Option::SPIN, defaultValue, min, max, std::move(cb)); - if (options[name].on_change) - options[name].on_change(options[name]); - } - - void addCheck(const std::string &name, bool defaultValue, std::function cb) - { - options[name] = Option(Option::CHECK, defaultValue ? 1 : 0, 0, 1, std::move(cb)); - if (options[name].on_change) - options[name].on_change(options[name]); - } - - void addString(const std::string &name, const std::string &defaultValue, std::function cb) - { - options[name] = Option(Option::STRING, defaultValue, std::move(cb)); - if (options[name].on_change) - options[name].on_change(options[name]); - } - - void addCombo(const std::string &name, const std::string &defaultValue, const std::vector &vars, std::function cb) - { - options[name] = Option(Option::COMBO, defaultValue, vars, std::move(cb)); - if (options[name].on_change) - options[name].on_change(options[name]); - } - - void addButton(const std::string &name, std::function cb) - { - options[name] = Option(Option::BUTTON, "", {}, std::move(cb)); - if (options[name].on_change) - options[name].on_change(options[name]); - } - - void setOption(const std::string &name, const std::string &val) - { - const auto it = options.find(name); - if (it == options.end()) { - std::cerr << "info string Unknown option " << name << '\n'; - return; // Early-out – nothing to update - } - auto &opt = it->second; - - switch (opt.type) - { - case Option::CHECK: - if (val == "true" || val == "1") - opt.value = 1; - else if (val == "false" || val == "0") - opt.value = 0; - else { - std::cerr << "info string Invalid value for option " << name - << ": " << val << " (expected true/false or 1/0)\n"; - return; - } - break; - case Option::SPIN: - { - try - { - int v{}; - auto [p, ec] = std::from_chars(val.data(), val.data() + val.size(), v); - if (ec != std::errc() || p != val.data() + val.size()) { - throw std::invalid_argument("not an int"); - } - opt.value = std::clamp(v, opt.min, opt.max); - } - catch (const std::exception &e) - { - std::cerr << "info string Invalid value for option " << name << ": " << val << "\n"; - return; - } - break; - } - case Option::STRING: - opt.value = val; - break; - case Option::COMBO: - if (std::find(opt.vars.begin(), opt.vars.end(), val) != opt.vars.end()) { - opt.value = val; - } else { - std::cerr << "info string Illegal value \"" << val - << "\" for option " << name << "\n"; - return; - } - break; - case Option::BUTTON: - // no value change - break; - } - - // Invoke the callback if present - if (opt.on_change) - opt.on_change(opt); - } - - int getInt(const std::string &name) - { - const auto &opt = options.at(name); - if (auto pval = std::get_if(&opt.value)) - return *pval; - throw std::runtime_error("Option " + name + " is not an integer"); - } - - std::string getString(const std::string &name) - { - const auto &opt = options.at(name); - if (auto pval = std::get_if(&opt.value)) - return *pval; - if (auto pval = std::get_if(&opt.value)) - return std::to_string(*pval); - return ""; - } - - void printOptions() - { - for (const auto &[name, opt] : options) - { - std::cout << "option name " << name << " type "; - switch (opt.type) - { - case Option::CHECK: - std::cout << "check default " << (std::get(opt.value) ? "true" : "false") << "\n"; - break; - case Option::SPIN: - std::cout << "spin default " << std::get(opt.value) - << " min " << opt.min << " max " << opt.max << "\n"; - break; - case Option::STRING: - std::cout << "string default " << std::get(opt.value) << "\n"; - break; - case Option::COMBO: - std::cout << "combo default " << std::get(opt.value); - for (const auto &var : opt.vars) - std::cout << " var " << var; - std::cout << "\n"; - break; - case Option::BUTTON: - std::cout << "button\n"; - break; - } - } - } - -} // namespace UCIOptions diff --git a/ucioptions.hpp b/ucioptions.hpp deleted file mode 100644 index 79130bd..0000000 --- a/ucioptions.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -namespace UCIOptions { - -struct Option { - enum Type { CHECK, SPIN, COMBO, BUTTON, STRING } type; - - std::string default_str; - std::vector vars; // for COMBO - int min = 0, max = 0; - - std::variant value; - - // Optional callback when the option is set - std::function on_change; - - Option() = default; - Option(Type t, int def, int min_ = 0, int max_ = 0, std::function cb = {}); - Option(Type t, const std::string& def, std::function cb = {}); - Option(Type t, const std::string& def, const std::vector& options, std::function cb = {}); -}; - -// Declare the global options map -extern std::unordered_map options; - -// Adders with optional callback support -void addSpin(const std::string& name, int defaultValue, int min, int max, std::function cb = {}); -void addCheck(const std::string& name, bool defaultValue, std::function cb = {}); -void addString(const std::string& name, const std::string& defaultValue, std::function cb = {}); -void addCombo(const std::string& name, const std::string& defaultValue, const std::vector& vars, std::function cb = {}); -void addButton(const std::string& name, std::function cb = {}); - -void setOption(const std::string& name, const std::string& val); - -int getInt(const std::string& name); -std::string getString(const std::string& name); - -void printOptions(); - -} // namespace UCIOptions